[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
@@ -183,6 +183,7 @@ function bmReadDecoders() {
if (document.getElementById("bm-dec-ais").checked) decoders.push("ais");
if (document.getElementById("bm-dec-ft8").checked) decoders.push("ft8");
if (document.getElementById("bm-dec-ft4").checked) decoders.push("ft4");
if (document.getElementById("bm-dec-ft2").checked) decoders.push("ft2");
if (document.getElementById("bm-dec-wspr").checked) decoders.push("wspr");
if (document.getElementById("bm-dec-hf-aprs").checked) decoders.push("hf-aprs");
return decoders;
@@ -195,6 +196,7 @@ function bmWriteDecoders(decoders) {
document.getElementById("bm-dec-ais").checked = list.includes("ais");
document.getElementById("bm-dec-ft8").checked = list.includes("ft8");
document.getElementById("bm-dec-ft4").checked = list.includes("ft4");
document.getElementById("bm-dec-ft2").checked = list.includes("ft2");
document.getElementById("bm-dec-wspr").checked = list.includes("wspr");
document.getElementById("bm-dec-hf-aprs").checked = list.includes("hf-aprs");
}
@@ -358,6 +360,10 @@ async function bmApply(bm) {
if (wantFt4 !== !!st.ft4_decode_enabled) {
await postPath("/toggle_ft4_decode");
}
const wantFt2 = bm.decoders.includes("ft2");
if (wantFt2 !== !!st.ft2_decode_enabled) {
await postPath("/toggle_ft2_decode");
}
const wantWspr = bm.decoders.includes("wspr");
if (wantWspr !== !!st.wspr_decode_enabled) {
await postPath("/toggle_wspr_decode");