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()
}