From 0efdb5e3608a5984c644fc748dd05be13d0db1af Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Fri, 27 Mar 2026 20:06:36 +0100 Subject: [PATCH] [fix](trx-rs): show signal strength with decimal precision Change RigRxStatus.sig from i32 to f64 and add get_signal_strength_db to RigCat trait so SDR backends can bypass the coarse 0..15 quantisation. Compensate for decimation processing gain so the meter matches the spectrum peak. Display with one decimal place in all units. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 8 ++++---- src/trx-core/src/rig/controller/machine.rs | 2 +- src/trx-core/src/rig/mod.rs | 13 +++++++++++-- src/trx-server/src/rig_task.rs | 10 ++++++---- .../trx-backend-soapysdr/src/dsp/channel.rs | 11 +++++++++-- .../trx-backend/trx-backend-soapysdr/src/lib.rs | 13 +++++++++++++ 6 files changed, 44 insertions(+), 13 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 f043566..0a16931 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 @@ -582,16 +582,16 @@ let sigStrengthUnitIdx = loadSetting("sigStrengthUnit", 0); function formatSigStrength(dbm) { if (!Number.isFinite(dbm)) return "--"; const unit = SIG_STRENGTH_UNITS[sigStrengthUnitIdx] || "dBFS"; - if (unit === "dBm") return `${dbm} dBm`; + if (unit === "dBm") return `${dbm.toFixed(1)} dBm`; if (unit === "dBf") { // dBf = dBm + 107 (referenced to 1 femtowatt across 50 Ω) const dbf = dbm + 107; - return `${dbf.toFixed(0)} dBf`; + return `${dbf.toFixed(1)} dBf`; } // dBFS: map receiver range to a full-scale reference // Typical receiver: -140 dBm (noise floor) to 0 dBm (full scale) const dbfs = Math.max(-140, Math.min(0, dbm)); - return `${dbfs} dBFS`; + return `${dbfs.toFixed(1)} dBFS`; } function refreshSigStrengthDisplay() { @@ -9199,7 +9199,7 @@ function startSpectrumStreaming() { vchanRdsById = next; vchanSignalDbById = nextSig; if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) { - sigLastDbm = Math.round(nextSig.get(vchanActiveId)); + sigLastDbm = nextSig.get(vchanActiveId); refreshSigStrengthDisplay(); } updateRdsPsOverlay(primaryRds); diff --git a/src/trx-core/src/rig/controller/machine.rs b/src/trx-core/src/rig/controller/machine.rs index 9a003e0..9f27708 100644 --- a/src/trx-core/src/rig/controller/machine.rs +++ b/src/trx-core/src/rig/controller/machine.rs @@ -307,7 +307,7 @@ impl RigMachineState { tx_en: true, vfo: data.vfo.clone(), tx: data.tx.clone(), - rx: Some(RigRxStatus { sig: Some(0) }), + rx: Some(RigRxStatus { sig: Some(0.0) }), lock: Some(data.locked), }), _ => None, diff --git a/src/trx-core/src/rig/mod.rs b/src/trx-core/src/rig/mod.rs index d3359fe..4bef2e3 100644 --- a/src/trx-core/src/rig/mod.rs +++ b/src/trx-core/src/rig/mod.rs @@ -125,6 +125,15 @@ pub trait RigCat: Rig + Send { &'a mut self, ) -> Pin> + Send + 'a>>; + /// Return precise signal strength in dBm/dBFS as a float. + /// Backends with continuous measurements (e.g. SDR) override this + /// to bypass the coarse 0..15 quantisation of `get_signal_strength`. + fn get_signal_strength_db<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + Box::pin(std::future::ready(None)) + } + fn get_tx_power<'a>(&'a mut self) -> Pin> + Send + 'a>>; fn get_tx_limit<'a>(&'a mut self) -> Pin> + Send + 'a>>; @@ -343,9 +352,9 @@ pub struct RigTxStatus { pub alc: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct RigRxStatus { - pub sig: Option, + pub sig: Option, } /// Configurable control settings that can be pushed to the rig. diff --git a/src/trx-server/src/rig_task.rs b/src/trx-server/src/rig_task.rs index 4320cbc..3feaf4f 100644 --- a/src/trx-server/src/rig_task.rs +++ b/src/trx-server/src/rig_task.rs @@ -887,7 +887,9 @@ async fn refresh_state_from_cat(rig: &mut Box, state: &mut RigState) state.status.vfo = vfo; if state.status.tx_en { - state.status.rx.get_or_insert(RigRxStatus { sig: None }).sig = Some(0); + state.status.rx.get_or_insert(RigRxStatus { sig: None }).sig = Some(0.0); + } else if let Some(db) = rig.get_signal_strength_db().await { + state.status.rx.get_or_insert(RigRxStatus { sig: None }).sig = Some(db); } else if let Ok(meter) = rig.get_signal_strength().await { let sig = map_signal_strength(&state.status.mode, meter); state.status.rx.get_or_insert(RigRxStatus { sig: None }).sig = Some(sig); @@ -1015,12 +1017,12 @@ async fn prime_vfo_state( } /// Map raw signal strength to S-meter value based on mode. -fn map_signal_strength(mode: &RigMode, raw: u8) -> i32 { +fn map_signal_strength(mode: &RigMode, raw: u8) -> f64 { // FT-817 returns 0-15 for signal strength // Map to approximate dBm / S-units match mode { - RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120 + (raw as i32 * 6), - _ => -127 + (raw as i32 * 6), + RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120.0 + (raw as f64 * 6.0), + _ => -127.0 + (raw as f64 * 6.0), } } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs index c4698ec..bd8b294 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs @@ -757,7 +757,13 @@ impl ChannelDsp { } // Signal strength measurement (before AGC). + // + // The decimated samples carry the total power of the channel bandwidth, + // which is higher than the per-bin spectral density shown on the + // spectrum display by approximately 10·log10(decim_factor). Subtract + // that so the meter and the spectrum peak agree. { + let decim_correction = 10.0 * (self.decim_factor as f32).max(1.0).log10(); if self.mode == RigMode::WFM { // WFM: smooth envelope power directly. // FM is constant-envelope, so I²+Q² is inherently stable @@ -769,7 +775,8 @@ impl ChannelDsp { let pwr = s.re * s.re + s.im * s.im; self.carrier_iq_i += alpha * (pwr - self.carrier_iq_i); } - self.last_signal_db = 10.0 * self.carrier_iq_i.max(1e-12).log10(); + self.last_signal_db = + 10.0 * self.carrier_iq_i.max(1e-12).log10() - decim_correction; } else { // Other modes: peak IQ magnitude with EMA smoothing. const SIGNAL_EMA_ALPHA: f32 = 0.4; @@ -777,7 +784,7 @@ impl ChannelDsp { .iter() .map(|s| s.re * s.re + s.im * s.im) .fold(0.0_f32, f32::max); - let peak_db = 10.0 * peak_power.max(1e-12).log10(); + let peak_db = 10.0 * peak_power.max(1e-12).log10() - decim_correction; self.last_signal_db += SIGNAL_EMA_ALPHA * (peak_db - self.last_signal_db); } } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs index d73f402..5e38227 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs @@ -567,6 +567,19 @@ impl RigCat for SoapySdrRig { }) } + fn get_signal_strength_db<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + self.pipeline + .channel_dsps + .read() + .unwrap() + .get(self.primary_channel_idx) + .and_then(|dsp| dsp.lock().ok().map(|d| d.signal_db() as f64)) + }) + } + // -- TX / unsupported methods ------------------------------------------- fn set_ptt<'a>(