diff --git a/.env.example b/.env.example index 8e17525..de33e6c 100644 --- a/.env.example +++ b/.env.example @@ -93,3 +93,5 @@ WAVELOG_BASE_URL= WAVELOG_RADIO_NAME=RMS Remote # HTTP timeout for the Wavelog API call (ms). WAVELOG_TIMEOUT_MS=8000 +# Min interval between live-tuning pushes per operator (ms, trailing-debounced). +WAVELOG_MIN_INTERVAL_MS=3000 diff --git a/docs/plugins/rms-wavelog-cat.md b/docs/plugins/rms-wavelog-cat.md index ace5a9b..b58adaa 100644 --- a/docs/plugins/rms-wavelog-cat.md +++ b/docs/plugins/rms-wavelog-cat.md @@ -9,9 +9,13 @@ - `baseUrl` / `WAVELOG_BASE_URL` – URL der Wavelog-Instanz (ohne Slash am Ende). Leer = deaktiviert. - `radioName` / `WAVELOG_RADIO_NAME` – Anzeigename in Wavelog (Default `RMS Remote`). - `timeoutMs` / `WAVELOG_TIMEOUT_MS` – HTTP-Timeout (Default 8000). -- Ausloeser: Der Core ruft die Capability beim Bandwechsel (`/v1/openwebrx/bands/select`) auf und - uebergibt `centerFreqHz` + `startMod` sowie den Wavelog-Key des aktiven Operators. Best-effort: - Fehler brechen den Bandwechsel nie ab. +- Ausloeser: + - **Bandwechsel** (`/v1/openwebrx/bands/select`): sofortiger Push der Band-Mittenfrequenz + (`centerFreqHz` + `startMod`). + - **Live-Tuning in OpenWebRX** (`/v1/openwebrx/plugin/live-state`): Push der tatsaechlich getunten + Frequenz/Mode des aktiven Operators, **gedrosselt** (Trailing-Debounce, `WAVELOG_MIN_INTERVAL_MS`, + Default 3000 ms) damit Feintunen die API nicht flutet. + - Beides best-effort: Fehler brechen den jeweiligen Flow nie ab. - Key-Ablage: pro Benutzer in `users.json` (`wavelogApiKey`), setzbar via `PUT /v1/me/wavelog-key` und sichtbar im OpenWebRX-Panel. **Kein** Key im Repo – der Key ist Laufzeit-/Benutzerdatum. - Mode-Mapping: `usb|lsb|ssb → SSB`, `cw|cwr → CW`, `fm|nfm|wfm → FM`, `am|sam → AM`, sonst Rohwert diff --git a/server/index.js b/server/index.js index 02c4033..86e1fe0 100644 --- a/server/index.js +++ b/server/index.js @@ -44,6 +44,7 @@ const config = { actionRateLimit: Number(process.env.ACTION_RATE_LIMIT || 30), openWebRxPttLiveFreqToleranceHz: Number(process.env.OPENWEBRX_PTT_LIVE_FREQ_TOLERANCE_HZ || 50), openWebRxLiveStateTtlMs: Number(process.env.OPENWEBRX_LIVE_STATE_TTL_MS || 10000), + wavelogMinIntervalMs: Number(process.env.WAVELOG_MIN_INTERVAL_MS || 3000), openWebRxPttBlockedBandConfigIdsRaw: String(process.env.OPENWEBRX_PTT_BLOCKED_BAND_CONFIG_IDS || "27,6,24"), openWebRxTxPollMs: Number(process.env.OPENWEBRX_TX_POLL_MS || 3000), txAudioEnabled: String(process.env.TX_AUDIO_ENABLED || "true").trim().toLowerCase(), @@ -196,6 +197,7 @@ const runtime = { lastEnsureSdrAtMs: 0 }, openWebRxLiveStateByUserId: {}, + wavelogCatByUserId: {}, rotor: { azimuth: null, rawAzimuth: null, @@ -3717,9 +3719,9 @@ async function handleOpenWebRxBandSelect(res, user, body) { } } -// Push current frequency/mode to the active operator's Wavelog logbook (CAT API). -// Best-effort: never throws into the band-select flow. -async function maybePublishWavelogCat(user, bandResult) { +// Push a frequency/mode to the active operator's Wavelog logbook (CAT API). +// Best-effort: never throws into the caller's flow. +async function sendWavelogCat(user, frequencyHz, mode) { try { if (!user || !user.wavelogApiKey) { return; @@ -3730,13 +3732,13 @@ async function maybePublishWavelogCat(user, bandResult) { if (!runtime.pluginState.providers["logging.cat.publish"]) { return; } - const frequencyHz = Number(bandResult && bandResult.centerFreqHz); - if (!Number.isFinite(frequencyHz) || frequencyHz <= 0) { + const freq = Number(frequencyHz); + if (!Number.isFinite(freq) || freq <= 0) { return; } const outcome = await executeCapability("logging.cat.publish", "publish", { - frequencyHz, - mode: bandResult && bandResult.startMod ? bandResult.startMod : null, + frequencyHz: freq, + mode: mode || null, apiKey: user.wavelogApiKey }, { user, skipTxSafety: true }); if (!outcome || outcome.ok !== true) { @@ -3753,6 +3755,62 @@ async function maybePublishWavelogCat(user, bandResult) { } } +// Band select: push the band center immediately (best-effort). +async function maybePublishWavelogCat(user, bandResult) { + await sendWavelogCat(user, bandResult && bandResult.centerFreqHz, bandResult && bandResult.startMod ? bandResult.startMod : null); +} + +// Live tuning in OpenWebRX: push the actual tuned frequency/mode, throttled so +// fine-tuning doesn't flood the Wavelog API. Trailing-edge: the latest value is +// always sent once the throttle window elapses. +function scheduleWavelogCatLive(user, frequencyHz, mode) { + try { + if (!user || !user.wavelogApiKey || !runtime.pluginState.enabled["rms.wavelog.cat"]) { + return; + } + const freq = Number(frequencyHz); + if (!Number.isFinite(freq) || freq <= 0) { + return; + } + const key = String(user.id || ""); + if (!key) { + return; + } + const minIntervalMs = Number.isFinite(config.wavelogMinIntervalMs) ? Math.max(500, config.wavelogMinIntervalMs) : 3000; + const rec = runtime.wavelogCatByUserId[key] || { lastFreq: null, lastMode: null, lastPushMs: 0, timer: null }; + runtime.wavelogCatByUserId[key] = rec; + rec.pendingFreq = freq; + rec.pendingMode = mode || null; + rec.pendingUser = user; + if (rec.lastFreq === freq && rec.lastMode === (mode || null)) { + return; + } + const fire = () => { + rec.timer = null; + rec.lastFreq = rec.pendingFreq; + rec.lastMode = rec.pendingMode; + rec.lastPushMs = Date.now(); + sendWavelogCat(rec.pendingUser, rec.pendingFreq, rec.pendingMode); + }; + const elapsed = Date.now() - (rec.lastPushMs || 0); + if (!rec.timer && elapsed >= minIntervalMs) { + fire(); + } else if (!rec.timer) { + rec.timer = setTimeout(fire, Math.max(250, minIntervalMs - elapsed)); + if (rec.timer && typeof rec.timer.unref === "function") { + rec.timer.unref(); + } + } + // If a timer is already pending, pendingFreq/Mode were updated above so the + // trailing push sends the latest tuned frequency. + } catch (error) { + console.warn("wavelog.cat live schedule error", { + userId: user && user.id, + error: String(error && error.message ? error.message : error) + }); + } +} + async function handleOpenWebRxTxEnable(res, user) { if (!runtime.station.isInUse || runtime.station.activeByUserId !== user.id) { return sendError(res, 403, "openwebrx.not_owner", "OpenWebRX TX nur fuer aktiven Stationsbenutzer"); @@ -5033,6 +5091,7 @@ async function handleOpenWebRxPluginLiveState(req, res, url, body) { updatedAtMs: Date.now(), source: "plugin" }); + scheduleWavelogCatLive(user, parsed.frequencyHz, parsed.mode); return sendJson(res, 200, { ok: true, liveState: {