[feat](trx-rs): add FT2 decoder support (wired to FT4)

Mirrors the FT4 implementation across the full stack. The trx-ft8
crate wires Ft8Decoder::new_ft2() to FTX_PROTOCOL_FT4 as a
placeholder pending a dedicated FT2 implementation.

Changes:
- trx-ft8: Ft8Decoder::new_ft2() delegates to with_protocol(Ft4)
- trx-core: DecodedMessage::Ft2, AUDIO_MSG_FT2_DECODE (0x15),
  ft2_decode_enabled/ft2_decode_reset_seq state, SetFt2DecodeEnabled/
  ResetFt2Decoder commands, protocol mapping
- trx-server: DecoderHistories::ft2, run_ft2_decoder (7.5s slots),
  run_background_ft2_decoder, history push/replay, decoder task spawn
- trx-frontend-http: ft2_history in FrontendRuntimeContext,
  toggle/clear endpoints, /ft2.js route, bookmark/scheduler/background
  decode support, DecodeHistoryPayload ft2 field
- web: ft2.js plugin (3.75s period timer), FT2 subtab in index.html,
  FT2 map source (distinct hue), app.js dispatch, decode-history-worker
  HISTORY_GROUP_KEYS, bookmarks read/write/apply

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-03-14 19:34:41 +01:00
parent 708c00a84c
commit d547c45a9c
25 changed files with 683 additions and 26 deletions
@@ -375,6 +375,7 @@
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ais" value="ais" /> AIS</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft8" value="ft8" /> FT8</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft4" value="ft4" /> FT4</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft2" value="ft2" /> FT2</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-wspr" value="wspr" /> WSPR</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-hf-aprs" value="hf-aprs" /> HF APRS</label>
</div>
@@ -427,6 +428,7 @@
<button class="sub-tab" data-subtab="cw">CW</button>
<button class="sub-tab" data-subtab="ft8">FT8</button>
<button class="sub-tab" data-subtab="ft4">FT4</button>
<button class="sub-tab" data-subtab="ft2">FT2</button>
<button class="sub-tab" data-subtab="wspr">WSPR</button>
<button class="sub-tab" data-subtab="rds">RDS</button>
</div>
@@ -467,6 +469,12 @@
Decodes FT4 messages from RX audio (DIG/USB only, toggle required). 7.5-second slots.
</div>
</div>
<div class="plugin-item">
<strong>FT2 Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes FT2 messages from RX audio (DIG/USB only, toggle required). 3.75-second slots.
</div>
</div>
<div class="plugin-item">
<strong>WSPR Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
@@ -660,6 +668,24 @@
</div>
<div id="ft4-messages"></div>
</div>
<div id="subtab-ft2" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="ft2-decode-toggle-btn" type="button">Enable FT2</button>
<button id="ft2-pause-btn" type="button">Pause</button>
<button id="ft2-clear-btn" type="button">Clear</button>
<input id="ft2-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
<small id="ft2-status" style="color:var(--text-muted);">Waiting for server decode</small>
<small id="ft2-period" style="color:var(--text-muted);">Next slot --s</small>
</div>
<div class="ft8-header">
<span class="ft8-time">Time</span>
<span class="ft8-snr">SNR</span>
<span class="ft8-dt">DT</span>
<span class="ft8-freq">RF</span>
<span class="ft8-msg">Message</span>
</div>
<div id="ft2-messages"></div>
</div>
<div id="subtab-wspr" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="wspr-decode-toggle-btn" type="button">Enable WSPR</button>
@@ -937,6 +963,7 @@
<script src="/hf-aprs.js"></script>
<script src="/ft8.js"></script>
<script src="/ft4.js"></script>
<script src="/ft2.js"></script>
<script src="/wspr.js"></script>
<script src="/cw.js"></script>
<script src="/bookmarks.js"></script>