// --- WSPR Decoder Plugin (server-side decode) --- const wsprStatus = document.getElementById("wspr-status"); const wsprPeriodEl = document.getElementById("wspr-period"); const wsprMessagesEl = document.getElementById("wspr-messages"); const wsprFilterInput = document.getElementById("wspr-filter"); const WSPR_MAX_MESSAGES = 200; const WSPR_PERIOD_SECONDS = 120; let wsprFilterText = ""; function fmtWsprTime(tsMs) { if (!tsMs) return "--:--:--"; return new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } function updateWsprPeriodTimer() { if (!wsprPeriodEl) return; const nowSec = Math.floor(Date.now() / 1000); const remaining = WSPR_PERIOD_SECONDS - (nowSec % WSPR_PERIOD_SECONDS); const mm = String(Math.floor(remaining / 60)).padStart(2, "0"); const ss = String(remaining % 60).padStart(2, "0"); wsprPeriodEl.textContent = `Next slot ${mm}:${ss}`; } updateWsprPeriodTimer(); setInterval(updateWsprPeriodTimer, 500); function renderWsprRow(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 baseHz = Number.isFinite(window.ft8BaseHz) ? window.ft8BaseHz : null; const rfHz = Number.isFinite(msg.freq_hz) && Number.isFinite(baseHz) ? (baseHz + msg.freq_hz) : null; const freq = Number.isFinite(rfHz) ? rfHz.toFixed(0) : "--"; const message = (msg.message || "").toString(); row.dataset.message = message.toUpperCase(); row.innerHTML = `${fmtWsprTime(msg.ts_ms)}${snr}${dt}${freq}${escapeWsprHtml(message)}`; applyWsprFilterToRow(row); return row; } function addWsprMessage(msg) { wsprMessagesEl.prepend(renderWsprRow(msg)); while (wsprMessagesEl.children.length > WSPR_MAX_MESSAGES) { wsprMessagesEl.removeChild(wsprMessagesEl.lastChild); } } function escapeWsprHtml(input) { return input .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """); } function extractAllGrids(message) { const out = []; const seen = new Set(); const parts = message.toUpperCase().split(/[^A-Z0-9]+/); for (const token of parts) { if (!token) continue; if (/^[A-R]{2}\d{2}(?:[A-X]{2})?$/.test(token) && !seen.has(token)) { seen.add(token); out.push(token); } } return out; } function extractLikelyCallsign(message) { const parts = String(message || "").toUpperCase().split(/[^A-Z0-9/]+/); for (const token of parts) { if (!token) continue; if (token.length < 3 || token.length > 12) continue; if (token === "CQ" || token === "DE" || token === "QRZ" || token === "DX") continue; if (/^[A-R]{2}\d{2}(?:[A-X]{2})?$/.test(token)) continue; if (/^[A-Z0-9/]{1,5}\d[A-Z0-9/]{1,6}$/.test(token)) return token; } return null; } function applyWsprFilterToRow(row) { if (!wsprFilterText) { row.style.display = ""; return; } const message = row.dataset.message || ""; row.style.display = message.includes(wsprFilterText) ? "" : "none"; } function applyWsprFilterToAll() { const rows = wsprMessagesEl.querySelectorAll(".ft8-row"); rows.forEach((row) => applyWsprFilterToRow(row)); } window.resetWsprHistoryView = function() { wsprMessagesEl.innerHTML = ""; if (window.clearMapMarkersByType) window.clearMapMarkersByType("wspr"); }; if (wsprFilterInput) { wsprFilterInput.addEventListener("input", () => { wsprFilterText = wsprFilterInput.value.trim().toUpperCase(); applyWsprFilterToAll(); }); } document.getElementById("wspr-decode-toggle-btn").addEventListener("click", async () => { try { await postPath("/toggle_wspr_decode"); } catch (e) { console.error("WSPR toggle failed", e); } }); document.getElementById("wspr-clear-btn").addEventListener("click", async () => { try { await postPath("/clear_wspr_decode"); window.resetWsprHistoryView(); } catch (e) { console.error("WSPR clear failed", e); } }); window.onServerWspr = function(msg) { wsprStatus.textContent = "Receiving"; const raw = (msg.message || "").toString(); const grids = extractAllGrids(raw); const station = extractLikelyCallsign(raw); const baseHz = Number.isFinite(window.ft8BaseHz) ? Number(window.ft8BaseHz) : null; const rfHz = Number.isFinite(msg.freq_hz) && Number.isFinite(baseHz) ? (baseHz + Number(msg.freq_hz)) : (Number.isFinite(msg.freq_hz) ? Number(msg.freq_hz) : null); if (grids.length > 0 && window.ft8MapAddLocator) { window.ft8MapAddLocator(raw, grids, "wspr", station, { ...msg, freq_hz: rfHz, }); } addWsprMessage({ receiver: window.getDecodeRigMeta ? window.getDecodeRigMeta() : null, ts_ms: msg.ts_ms, snr_db: msg.snr_db, dt_s: msg.dt_s, freq_hz: msg.freq_hz, message: raw, }); };