[feat](trx-rs): add ft8 decoder
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -248,12 +248,17 @@ function render(update) {
|
||||
const modeUpper = update.status && update.status.mode ? normalizeMode(update.status.mode).toUpperCase() : "";
|
||||
const aprsStatus = document.getElementById("aprs-status");
|
||||
const cwStatus = document.getElementById("cw-status");
|
||||
const ft8Status = document.getElementById("ft8-status");
|
||||
if (aprsStatus && modeUpper !== "PKT" && aprsStatus.textContent === "Receiving") {
|
||||
aprsStatus.textContent = "Connected, listening for packets";
|
||||
}
|
||||
if (cwStatus && modeUpper !== "CW" && modeUpper !== "CWR" && cwStatus.textContent === "Receiving") {
|
||||
cwStatus.textContent = "Connected, listening for packets";
|
||||
}
|
||||
const ft8Enabled = !!update.ft8_decode_enabled;
|
||||
if (ft8Status && (!ft8Enabled || (modeUpper !== "DIG" && modeUpper !== "USB")) && ft8Status.textContent === "Receiving") {
|
||||
ft8Status.textContent = "Connected, listening for packets";
|
||||
}
|
||||
if (update.status && typeof update.status.tx_en === "boolean") {
|
||||
lastTxEn = update.status.tx_en;
|
||||
pttBtn.textContent = update.status.tx_en ? "PTT On" : "PTT Off";
|
||||
@@ -267,6 +272,13 @@ function render(update) {
|
||||
pttBtn.style.color = "";
|
||||
}
|
||||
}
|
||||
const ft8ToggleBtn = document.getElementById("ft8-decode-toggle-btn");
|
||||
if (ft8ToggleBtn) {
|
||||
const ft8On = !!update.ft8_decode_enabled;
|
||||
ft8ToggleBtn.textContent = ft8On ? "Disable FT8" : "Enable FT8";
|
||||
ft8ToggleBtn.style.borderColor = ft8On ? "#00d17f" : "";
|
||||
ft8ToggleBtn.style.color = ft8On ? "#00d17f" : "";
|
||||
}
|
||||
const cwAutoEl = document.getElementById("cw-auto");
|
||||
const cwWpmEl = document.getElementById("cw-wpm");
|
||||
const cwToneEl = document.getElementById("cw-tone");
|
||||
@@ -1156,8 +1168,10 @@ let decodeConnected = false;
|
||||
function updateDecodeStatus(text) {
|
||||
const aprs = document.getElementById("aprs-status");
|
||||
const cw = document.getElementById("cw-status");
|
||||
const ft8 = document.getElementById("ft8-status");
|
||||
if (aprs && aprs.textContent !== "Receiving") aprs.textContent = text;
|
||||
if (cw && cw.textContent !== "Receiving") cw.textContent = text;
|
||||
if (ft8 && ft8.textContent !== "Receiving") ft8.textContent = text;
|
||||
}
|
||||
function connectDecode() {
|
||||
if (decodeSource) { decodeSource.close(); }
|
||||
@@ -1171,6 +1185,7 @@ function connectDecode() {
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (msg.type === "aprs" && window.onServerAprs) window.onServerAprs(msg);
|
||||
if (msg.type === "cw" && window.onServerCw) window.onServerCw(msg);
|
||||
if (msg.type === "ft8" && window.onServerFt8) window.onServerFt8(msg);
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
<button class="sub-tab active" data-subtab="overview">Overview</button>
|
||||
<button class="sub-tab" data-subtab="map">Map</button>
|
||||
<button class="sub-tab" data-subtab="aprs">APRS</button>
|
||||
<button class="sub-tab" data-subtab="ft8">FT8</button>
|
||||
<button class="sub-tab" data-subtab="cw">CW</button>
|
||||
</div>
|
||||
<div id="subtab-overview" class="sub-tab-panel">
|
||||
@@ -150,6 +151,14 @@
|
||||
</div>
|
||||
<div id="aprs-packets"></div>
|
||||
</div>
|
||||
<div id="subtab-ft8" class="sub-tab-panel" style="display:none;">
|
||||
<div class="ft8-controls">
|
||||
<button id="ft8-decode-toggle-btn" type="button">Enable FT8</button>
|
||||
<button id="ft8-clear-btn" type="button">Clear</button>
|
||||
<small id="ft8-status" style="color:var(--text-muted);">Waiting for server decode</small>
|
||||
</div>
|
||||
<div id="ft8-messages"></div>
|
||||
</div>
|
||||
<div id="subtab-cw" class="sub-tab-panel" style="display:none;">
|
||||
<div class="cw-controls">
|
||||
<button id="cw-clear-btn" type="button">Clear</button>
|
||||
@@ -184,6 +193,7 @@
|
||||
</div>
|
||||
<script src="/app.js"></script>
|
||||
<script src="/aprs.js"></script>
|
||||
<script src="/ft8.js"></script>
|
||||
<script src="/cw.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// --- FT8 Decoder Plugin (server-side decode) ---
|
||||
const ft8Status = document.getElementById("ft8-status");
|
||||
const ft8MessagesEl = document.getElementById("ft8-messages");
|
||||
const FT8_MAX_MESSAGES = 200;
|
||||
|
||||
function fmtTime(tsMs) {
|
||||
if (!tsMs) return "--:--:--";
|
||||
return new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
||||
}
|
||||
|
||||
function renderFt8Row(msg) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "ft8-row";
|
||||
const snr = Number.isFinite(msg.snr_db) ? msg.snr_db.toFixed(1) : "--";
|
||||
const dt = Number.isFinite(msg.dt_s) ? msg.dt_s.toFixed(2) : "--";
|
||||
const freq = Number.isFinite(msg.freq_hz) ? msg.freq_hz.toFixed(0) : "--";
|
||||
row.innerHTML = `<span class="ft8-time">${fmtTime(msg.ts_ms)}</span><span class="ft8-snr">${snr}</span><span class="ft8-dt">${dt}</span><span class="ft8-freq">${freq}</span><span class="ft8-msg">${msg.message || ""}</span>`;
|
||||
return row;
|
||||
}
|
||||
|
||||
function addFt8Message(msg) {
|
||||
ft8MessagesEl.prepend(renderFt8Row(msg));
|
||||
while (ft8MessagesEl.children.length > FT8_MAX_MESSAGES) {
|
||||
ft8MessagesEl.removeChild(ft8MessagesEl.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("ft8-decode-toggle-btn").addEventListener("click", async () => {
|
||||
try { await postPath("/toggle_ft8_decode"); } catch (e) { console.error("FT8 toggle failed", e); }
|
||||
});
|
||||
|
||||
document.getElementById("ft8-clear-btn").addEventListener("click", async () => {
|
||||
ft8MessagesEl.innerHTML = "";
|
||||
try { await postPath("/clear_ft8_decode"); } catch (e) { console.error("FT8 clear failed", e); }
|
||||
});
|
||||
|
||||
// --- Server-side FT8 decode handler ---
|
||||
window.onServerFt8 = function(msg) {
|
||||
ft8Status.textContent = "Receiving";
|
||||
addFt8Message({
|
||||
ts_ms: msg.ts_ms,
|
||||
snr_db: msg.snr_db,
|
||||
dt_s: msg.dt_s,
|
||||
freq_hz: msg.freq_hz,
|
||||
message: msg.message,
|
||||
});
|
||||
};
|
||||
@@ -223,6 +223,15 @@ small { color: var(--text-muted); }
|
||||
.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; }
|
||||
.ft8-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
||||
#ft8-messages { max-height: 360px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.85rem; padding: 0.35rem 0.5rem; }
|
||||
.ft8-row { display: flex; gap: 0.6rem; line-height: 1.4; border-bottom: 1px solid var(--border); padding: 0.25rem 0; }
|
||||
.ft8-row:last-child { border-bottom: none; }
|
||||
.ft8-time { color: var(--text-muted); min-width: 4.6rem; }
|
||||
.ft8-snr { color: var(--accent-yellow); min-width: 3.6rem; text-align: right; }
|
||||
.ft8-dt { color: var(--text-muted); min-width: 3.6rem; text-align: right; }
|
||||
.ft8-freq { color: var(--accent-green); min-width: 4.6rem; text-align: right; }
|
||||
.ft8-msg { flex: 1; }
|
||||
|
||||
.cw-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
||||
.cw-config { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.75rem; }
|
||||
|
||||
Reference in New Issue
Block a user