[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:
@@ -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="--" />
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user