From b3293f1de37aa60ac0b01a883409b85779283c8b Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Mon, 9 Feb 2026 19:38:25 +0100 Subject: [PATCH] [feat](trx-rs): server-side decode and aprs byte rendering Co-authored-by: Codex Signed-off-by: Stanislaw Grams --- .../trx-frontend-http/assets/web/app.js | 20 -------------- .../assets/web/plugins/aprs.js | 27 ++++++++++++++++--- .../trx-frontend-http/assets/web/style.css | 1 + src/trx-core/src/decode.rs | 1 + src/trx-server/src/audio.rs | 6 ++--- src/trx-server/src/decode/aprs.rs | 1 + 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index ad5b197..2ca326e 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -70,8 +70,6 @@ let supportedModes = []; let supportedBands = []; let freqDirty = false; let modeDirty = false; -let aprsAutoToggleInFlight = false; -let cwAutoToggleInFlight = false; let initialized = false; let lastEventAt = Date.now(); let es; @@ -247,24 +245,6 @@ function render(update) { const mode = normalizeMode(update.status.mode); modeEl.value = mode ? mode.toUpperCase() : ""; } - const currentMode = update.status && update.status.mode ? normalizeMode(update.status.mode) : ""; - const modeUpper = currentMode ? currentMode.toUpperCase() : ""; - const aprsDesired = modeUpper === "PKT"; - const cwDesired = modeUpper === "CW" || modeUpper === "CWR"; - const aprsEnabled = !!update.aprs_decode_enabled; - const cwEnabled = !!update.cw_decode_enabled; - if (aprsDesired !== aprsEnabled && !aprsAutoToggleInFlight) { - aprsAutoToggleInFlight = true; - postPath("/toggle_aprs_decode") - .catch((e) => console.error("APRS auto-toggle failed", e)) - .finally(() => { aprsAutoToggleInFlight = false; }); - } - if (cwDesired !== cwEnabled && !cwAutoToggleInFlight) { - cwAutoToggleInFlight = true; - postPath("/toggle_cw_decode") - .catch((e) => console.error("CW auto-toggle failed", e)) - .finally(() => { cwAutoToggleInFlight = false; }); - } if (update.status && typeof update.status.tx_en === "boolean") { lastTxEn = update.status.tx_en; pttBtn.textContent = update.status.tx_en ? "PTT On" : "PTT Off"; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js index ff86b81..c2b7f10 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js @@ -6,7 +6,27 @@ const APRS_MAX_PACKETS = 100; // Persistent packet history let aprsPacketHistory = loadSetting("aprsPackets", []); -function escapeAprsInfo(str) { +function renderAprsInfo(pkt) { + const bytes = Array.isArray(pkt.info_bytes) ? pkt.info_bytes : null; + if (bytes && bytes.length > 0) { + let out = ""; + for (let i = 0; i < bytes.length; i++) { + const b = bytes[i]; + if (b >= 0x20 && b <= 0x7e) { + const ch = String.fromCharCode(b); + if (ch === "<") out += "<"; + else if (ch === ">") out += ">"; + else if (ch === "&") out += "&"; + else if (ch === '"') out += """; + else out += ch; + } else { + const hex = b.toString(16).toUpperCase().padStart(2, "0"); + out += `0x${hex}`; + } + } + return out; + } + const str = pkt.info || ""; let out = ""; for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i); @@ -19,7 +39,7 @@ function escapeAprsInfo(str) { else out += ch; } else { const hex = code.toString(16).toUpperCase().padStart(2, "0"); - out += `[0x${hex}]`; + out += `0x${hex}`; } } return out; @@ -46,7 +66,7 @@ function renderAprsRow(pkt) { const osmUrl = `https://www.openstreetmap.org/?mlat=${pkt.lat}&mlon=${pkt.lon}#map=15/${pkt.lat}/${pkt.lon}`; posHtml = ` ${pkt.lat.toFixed(4)}, ${pkt.lon.toFixed(4)}`; } - row.innerHTML = `${ts}${symbolHtml}${pkt.srcCall}>${pkt.destCall}${pkt.path ? "," + pkt.path : ""}: ${escapeAprsInfo(pkt.info)}${posHtml}${crcTag}`; + row.innerHTML = `${ts}${symbolHtml}${pkt.srcCall}>${pkt.destCall}${pkt.path ? "," + pkt.path : ""}: ${renderAprsInfo(pkt)}${posHtml}${crcTag}`; return row; } @@ -96,6 +116,7 @@ window.onServerAprs = function(pkt) { destCall: pkt.dest_call, path: pkt.path, info: pkt.info, + info_bytes: pkt.info_bytes, type: pkt.packet_type, crcOk: pkt.crc_ok, lat: pkt.lat, diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index 6423fd1..83bc48d 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -222,6 +222,7 @@ small { color: var(--text-muted); } .aprs-symbol { display: inline-block; width: 24px; height: 24px; background-size: 384px 192px; vertical-align: middle; margin-right: 0.3rem; } .aprs-pos { color: var(--accent-green); text-decoration: none; margin-left: 0.3rem; font-size: 0.8rem; } .aprs-pos:hover { text-decoration: underline; } +.aprs-byte { color: var(--accent-yellow); background: rgba(255, 214, 0, 0.12); border: 1px solid rgba(255, 214, 0, 0.25); border-radius: 4px; padding: 0 0.2rem; margin: 0 0.1rem; font-size: 0.78em; } .cw-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; } .cw-config { display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap; margin-bottom: 0.75rem; } diff --git a/src/trx-core/src/decode.rs b/src/trx-core/src/decode.rs index 0d530ca..26f6fae 100644 --- a/src/trx-core/src/decode.rs +++ b/src/trx-core/src/decode.rs @@ -22,6 +22,7 @@ pub struct AprsPacket { pub dest_call: String, pub path: String, pub info: String, + pub info_bytes: Vec, pub packet_type: String, pub crc_ok: bool, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index 2aff68a..ad17606 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -328,8 +328,7 @@ pub async fn run_aprs_decoder( Ok(frame) => { let state = state_rx.borrow().clone(); let mode = &state.status.mode; - let enabled = state.aprs_decode_enabled; - let active = enabled && matches!(mode, RigMode::PKT); + let active = matches!(mode, RigMode::PKT); // Check for reset request if state.aprs_decode_reset_seq != last_reset_seq { @@ -390,8 +389,7 @@ pub async fn run_cw_decoder( Ok(frame) => { let state = state_rx.borrow().clone(); let mode = &state.status.mode; - let enabled = state.cw_decode_enabled; - let active = enabled && matches!(mode, RigMode::CW | RigMode::CWR); + let active = matches!(mode, RigMode::CW | RigMode::CWR); // Check for reset request if state.cw_decode_reset_seq != last_reset_seq { diff --git a/src/trx-server/src/decode/aprs.rs b/src/trx-server/src/decode/aprs.rs index 5560e7e..6fbd3a7 100644 --- a/src/trx-server/src/decode/aprs.rs +++ b/src/trx-server/src/decode/aprs.rs @@ -421,6 +421,7 @@ fn parse_aprs(ax25: &Ax25Frame) -> AprsPacket { dest_call, path, info: info_str, + info_bytes: info.to_vec(), packet_type: packet_type.to_string(), crc_ok: false, // set by caller lat,