[fix](trx-rs): wire DSP signal strength to Signal strength field and per-vchan SSE

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-27 14:24:44 +01:00
parent d864969742
commit 4fc32f0e90
6 changed files with 49 additions and 9 deletions
@@ -362,6 +362,7 @@ let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000));
let decodeHistoryRetentionMin = 24 * 60; let decodeHistoryRetentionMin = 24 * 60;
let primaryRds = null; let primaryRds = null;
let vchanRdsById = new Map(); let vchanRdsById = new Map();
let vchanSignalDbById = new Map();
let rdsOverlayEntries = []; let rdsOverlayEntries = [];
function currentDecodeHistoryRetentionMs() { function currentDecodeHistoryRetentionMs() {
@@ -9163,12 +9164,21 @@ function startSpectrumStreaming() {
try { try {
const payload = evt.data === "null" ? [] : JSON.parse(evt.data); const payload = evt.data === "null" ? [] : JSON.parse(evt.data);
const next = new Map(); const next = new Map();
const nextSig = new Map();
if (Array.isArray(payload)) { if (Array.isArray(payload)) {
payload.forEach((entry) => { 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; vchanRdsById = next;
vchanSignalDbById = nextSig;
if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) {
sigLastDbm = Math.round(nextSig.get(vchanActiveId));
refreshSigStrengthDisplay();
}
updateRdsPsOverlay(primaryRds); updateRdsPsOverlay(primaryRds);
} catch (_) {} } catch (_) {}
}); });
@@ -178,7 +178,7 @@
</div> </div>
<div class="freq-field sig-strength-col"> <div class="freq-field sig-strength-col">
<div class="sig-strength-display" id="sig-strength" title="Click to change unit">--</div> <div class="sig-strength-display" id="sig-strength" title="Click to change unit">--</div>
<div class="label"><span>Sig Strength</span></div> <div class="label"><span>Signal strength</span></div>
</div> </div>
<div class="freq-field frequency-col"> <div class="freq-field frequency-col">
<input class="status-input" id="freq" type="text" value="--" /> <input class="status-input" id="freq" type="text" value="--" />
+3
View File
@@ -437,6 +437,9 @@ pub struct VchanRdsEntry {
/// Latest RDS data, if decoded. /// Latest RDS data, if decoded.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub rds: Option<RdsData>, pub rds: Option<RdsData>,
/// Channel signal level in dBFS.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signal_db: Option<f32>,
} }
/// Read-only projection of state shared with clients. /// Read-only projection of state shared with clients.
@@ -296,6 +296,7 @@ pub struct ChannelDsp {
force_mono_pcm: bool, force_mono_pcm: bool,
squelch: VirtualSquelch, squelch: VirtualSquelch,
noise_blanker: NoiseBlanker, noise_blanker: NoiseBlanker,
last_signal_db: f32,
} }
impl ChannelDsp { impl ChannelDsp {
@@ -521,6 +522,7 @@ impl ChannelDsp {
force_mono_pcm, force_mono_pcm,
squelch: VirtualSquelch::new(squelch_cfg), squelch: VirtualSquelch::new(squelch_cfg),
noise_blanker: NoiseBlanker::new(nb_cfg.enabled, nb_cfg.threshold), noise_blanker: NoiseBlanker::new(nb_cfg.enabled, nb_cfg.threshold),
last_signal_db: -120.0,
} }
} }
@@ -615,6 +617,10 @@ impl ChannelDsp {
.unwrap_or(0) .unwrap_or(0)
} }
pub fn signal_db(&self) -> f32 {
self.last_signal_db
}
pub fn reset_rds(&mut self) { pub fn reset_rds(&mut self) {
if let Some(decoder) = &mut self.wfm_decoder { if let Some(decoder) = &mut self.wfm_decoder {
decoder.reset_rds(); decoder.reset_rds();
@@ -740,6 +746,7 @@ impl ChannelDsp {
.sum::<f32>() .sum::<f32>()
/ decimated.len() as f32; / decimated.len() as f32;
let signal_db = 10.0 * signal_power.max(1e-12).log10(); let signal_db = 10.0 * signal_power.max(1e-12).log10();
self.last_signal_db = signal_db;
const WFM_OUTPUT_GAIN: f32 = 0.50; const WFM_OUTPUT_GAIN: f32 = 0.50;
let mut audio = if let Some(decoder) = self.wfm_decoder.as_mut() { let mut audio = if let Some(decoder) = self.wfm_decoder.as_mut() {
let mut out = decoder.process_iq(decimated); let mut out = decoder.process_iq(decimated);
@@ -550,8 +550,21 @@ impl RigCat for SoapySdrRig {
fn get_signal_strength<'a>( fn get_signal_strength<'a>(
&'a mut self, &'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<u8>> + Send + 'a>> { ) -> Pin<Box<dyn std::future::Future<Output = DynResult<u8>> + Send + 'a>> {
// RSSI from real device pending SDR hardware wiring; return 0 for now. Box::pin(async move {
Box::pin(async move { Ok(0u8) }) 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 ------------------------------------------- // -- TX / unsupported methods -------------------------------------------
@@ -245,12 +245,19 @@ impl SdrVirtualChannelManager {
let dsps = self.pipeline.channel_dsps.read().unwrap(); let dsps = self.pipeline.channel_dsps.read().unwrap();
channels channels
.iter() .iter()
.filter(|ch| matches!(ch.mode, RigMode::WFM))
.map(|ch| { .map(|ch| {
let rds = dsps let dsp_guard = dsps.get(ch.pipeline_slot).and_then(|dsp| dsp.lock().ok());
.get(ch.pipeline_slot) let rds = if matches!(ch.mode, RigMode::WFM) {
.and_then(|dsp| dsp.lock().ok().and_then(|d| d.rds_data())); dsp_guard.as_ref().and_then(|d| d.rds_data())
VchanRdsEntry { id: ch.id, rds } } else {
None
};
let signal_db = dsp_guard.as_ref().map(|d| d.signal_db());
VchanRdsEntry {
id: ch.id,
rds,
signal_db,
}
}) })
.collect() .collect()
} }