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 a5af574..be23825 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 @@ -362,6 +362,7 @@ let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000)); let decodeHistoryRetentionMin = 24 * 60; let primaryRds = null; let vchanRdsById = new Map(); +let vchanSignalDbById = new Map(); let rdsOverlayEntries = []; function currentDecodeHistoryRetentionMs() { @@ -9163,12 +9164,21 @@ function startSpectrumStreaming() { try { const payload = evt.data === "null" ? [] : JSON.parse(evt.data); const next = new Map(); + const nextSig = new Map(); if (Array.isArray(payload)) { payload.forEach((entry) => { - if (entry && entry.id) next.set(entry.id, entry.rds ?? null); + if (entry && entry.id) { + next.set(entry.id, entry.rds ?? null); + if (typeof entry.signal_db === "number") nextSig.set(entry.id, entry.signal_db); + } }); } vchanRdsById = next; + vchanSignalDbById = nextSig; + if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) { + sigLastDbm = Math.round(nextSig.get(vchanActiveId)); + refreshSigStrengthDisplay(); + } updateRdsPsOverlay(primaryRds); } catch (_) {} }); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index a946be6..66897e2 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -178,7 +178,7 @@
--
-
Sig Strength
+
Signal strength
diff --git a/src/trx-core/src/rig/state.rs b/src/trx-core/src/rig/state.rs index ff08906..b4e88d7 100644 --- a/src/trx-core/src/rig/state.rs +++ b/src/trx-core/src/rig/state.rs @@ -437,6 +437,9 @@ pub struct VchanRdsEntry { /// Latest RDS data, if decoded. #[serde(default, skip_serializing_if = "Option::is_none")] pub rds: Option, + /// Channel signal level in dBFS. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub signal_db: Option, } /// Read-only projection of state shared with clients. 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 85ae66b..dce3c05 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 @@ -296,6 +296,7 @@ pub struct ChannelDsp { force_mono_pcm: bool, squelch: VirtualSquelch, noise_blanker: NoiseBlanker, + last_signal_db: f32, } impl ChannelDsp { @@ -521,6 +522,7 @@ impl ChannelDsp { force_mono_pcm, squelch: VirtualSquelch::new(squelch_cfg), noise_blanker: NoiseBlanker::new(nb_cfg.enabled, nb_cfg.threshold), + last_signal_db: -120.0, } } @@ -615,6 +617,10 @@ impl ChannelDsp { .unwrap_or(0) } + pub fn signal_db(&self) -> f32 { + self.last_signal_db + } + pub fn reset_rds(&mut self) { if let Some(decoder) = &mut self.wfm_decoder { decoder.reset_rds(); @@ -740,6 +746,7 @@ impl ChannelDsp { .sum::() / decimated.len() as f32; let signal_db = 10.0 * signal_power.max(1e-12).log10(); + self.last_signal_db = signal_db; const WFM_OUTPUT_GAIN: f32 = 0.50; let mut audio = if let Some(decoder) = self.wfm_decoder.as_mut() { let mut out = decoder.process_iq(decimated); 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 0a184b3..d73f402 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 @@ -550,8 +550,21 @@ impl RigCat for SoapySdrRig { fn get_signal_strength<'a>( &'a mut self, ) -> Pin> + Send + 'a>> { - // RSSI from real device pending SDR hardware wiring; return 0 for now. - Box::pin(async move { Ok(0u8) }) + Box::pin(async move { + let signal_db = self + .pipeline + .channel_dsps + .read() + .unwrap() + .get(self.primary_channel_idx) + .and_then(|dsp| dsp.lock().ok().map(|d| d.signal_db())) + .unwrap_or(-120.0); + // Map DSP signal power (roughly -120 .. 0 dBFS) to 0..15 range + // to match the FT-817 meter scale used by map_signal_strength. + let clamped = signal_db.clamp(-120.0, 0.0); + let raw = ((clamped + 120.0) / 120.0 * 15.0).round() as u8; + Ok(raw.min(15)) + }) } // -- TX / unsupported methods ------------------------------------------- diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs index 0643a7d..5b5f2ca 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs @@ -245,12 +245,19 @@ impl SdrVirtualChannelManager { let dsps = self.pipeline.channel_dsps.read().unwrap(); channels .iter() - .filter(|ch| matches!(ch.mode, RigMode::WFM)) .map(|ch| { - let rds = dsps - .get(ch.pipeline_slot) - .and_then(|dsp| dsp.lock().ok().and_then(|d| d.rds_data())); - VchanRdsEntry { id: ch.id, rds } + let dsp_guard = dsps.get(ch.pipeline_slot).and_then(|dsp| dsp.lock().ok()); + let rds = if matches!(ch.mode, RigMode::WFM) { + dsp_guard.as_ref().and_then(|d| d.rds_data()) + } else { + None + }; + let signal_db = dsp_guard.as_ref().map(|d| d.signal_db()); + VchanRdsEntry { + id: ch.id, + rds, + signal_db, + } }) .collect() }