[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 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -582,16 +582,16 @@ let sigStrengthUnitIdx = loadSetting("sigStrengthUnit", 0);
|
|||||||
function formatSigStrength(dbm) {
|
function formatSigStrength(dbm) {
|
||||||
if (!Number.isFinite(dbm)) return "--";
|
if (!Number.isFinite(dbm)) return "--";
|
||||||
const unit = SIG_STRENGTH_UNITS[sigStrengthUnitIdx] || "dBFS";
|
const unit = SIG_STRENGTH_UNITS[sigStrengthUnitIdx] || "dBFS";
|
||||||
if (unit === "dBm") return `${dbm} dBm`;
|
if (unit === "dBm") return `${dbm.toFixed(1)} dBm`;
|
||||||
if (unit === "dBf") {
|
if (unit === "dBf") {
|
||||||
// dBf = dBm + 107 (referenced to 1 femtowatt across 50 Ω)
|
// dBf = dBm + 107 (referenced to 1 femtowatt across 50 Ω)
|
||||||
const dbf = dbm + 107;
|
const dbf = dbm + 107;
|
||||||
return `${dbf.toFixed(0)} dBf`;
|
return `${dbf.toFixed(1)} dBf`;
|
||||||
}
|
}
|
||||||
// dBFS: map receiver range to a full-scale reference
|
// dBFS: map receiver range to a full-scale reference
|
||||||
// Typical receiver: -140 dBm (noise floor) to 0 dBm (full scale)
|
// Typical receiver: -140 dBm (noise floor) to 0 dBm (full scale)
|
||||||
const dbfs = Math.max(-140, Math.min(0, dbm));
|
const dbfs = Math.max(-140, Math.min(0, dbm));
|
||||||
return `${dbfs} dBFS`;
|
return `${dbfs.toFixed(1)} dBFS`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshSigStrengthDisplay() {
|
function refreshSigStrengthDisplay() {
|
||||||
@@ -9199,7 +9199,7 @@ function startSpectrumStreaming() {
|
|||||||
vchanRdsById = next;
|
vchanRdsById = next;
|
||||||
vchanSignalDbById = nextSig;
|
vchanSignalDbById = nextSig;
|
||||||
if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) {
|
if (typeof vchanActiveId !== "undefined" && vchanActiveId && nextSig.has(vchanActiveId)) {
|
||||||
sigLastDbm = Math.round(nextSig.get(vchanActiveId));
|
sigLastDbm = nextSig.get(vchanActiveId);
|
||||||
refreshSigStrengthDisplay();
|
refreshSigStrengthDisplay();
|
||||||
}
|
}
|
||||||
updateRdsPsOverlay(primaryRds);
|
updateRdsPsOverlay(primaryRds);
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ impl RigMachineState {
|
|||||||
tx_en: true,
|
tx_en: true,
|
||||||
vfo: data.vfo.clone(),
|
vfo: data.vfo.clone(),
|
||||||
tx: data.tx.clone(),
|
tx: data.tx.clone(),
|
||||||
rx: Some(RigRxStatus { sig: Some(0) }),
|
rx: Some(RigRxStatus { sig: Some(0.0) }),
|
||||||
lock: Some(data.locked),
|
lock: Some(data.locked),
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -125,6 +125,15 @@ pub trait RigCat: Rig + Send {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
) -> Pin<Box<dyn Future<Output = DynResult<u8>> + Send + 'a>>;
|
) -> Pin<Box<dyn Future<Output = DynResult<u8>> + 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<Box<dyn Future<Output = Option<f64>> + Send + 'a>> {
|
||||||
|
Box::pin(std::future::ready(None))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_tx_power<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = DynResult<u8>> + Send + 'a>>;
|
fn get_tx_power<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = DynResult<u8>> + Send + 'a>>;
|
||||||
|
|
||||||
fn get_tx_limit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = DynResult<u8>> + Send + 'a>>;
|
fn get_tx_limit<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = DynResult<u8>> + Send + 'a>>;
|
||||||
@@ -343,9 +352,9 @@ pub struct RigTxStatus {
|
|||||||
pub alc: Option<u8>,
|
pub alc: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct RigRxStatus {
|
pub struct RigRxStatus {
|
||||||
pub sig: Option<i32>,
|
pub sig: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configurable control settings that can be pushed to the rig.
|
/// Configurable control settings that can be pushed to the rig.
|
||||||
|
|||||||
@@ -887,7 +887,9 @@ async fn refresh_state_from_cat(rig: &mut Box<dyn RigCat>, state: &mut RigState)
|
|||||||
state.status.vfo = vfo;
|
state.status.vfo = vfo;
|
||||||
|
|
||||||
if state.status.tx_en {
|
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 {
|
} else if let Ok(meter) = rig.get_signal_strength().await {
|
||||||
let sig = map_signal_strength(&state.status.mode, meter);
|
let sig = map_signal_strength(&state.status.mode, meter);
|
||||||
state.status.rx.get_or_insert(RigRxStatus { sig: None }).sig = Some(sig);
|
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.
|
/// 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
|
// FT-817 returns 0-15 for signal strength
|
||||||
// Map to approximate dBm / S-units
|
// Map to approximate dBm / S-units
|
||||||
match mode {
|
match mode {
|
||||||
RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120 + (raw as i32 * 6),
|
RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120.0 + (raw as f64 * 6.0),
|
||||||
_ => -127 + (raw as i32 * 6),
|
_ => -127.0 + (raw as f64 * 6.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -757,7 +757,13 @@ impl ChannelDsp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Signal strength measurement (before AGC).
|
// 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 {
|
if self.mode == RigMode::WFM {
|
||||||
// WFM: smooth envelope power directly.
|
// WFM: smooth envelope power directly.
|
||||||
// FM is constant-envelope, so I²+Q² is inherently stable
|
// 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;
|
let pwr = s.re * s.re + s.im * s.im;
|
||||||
self.carrier_iq_i += alpha * (pwr - self.carrier_iq_i);
|
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 {
|
} else {
|
||||||
// Other modes: peak IQ magnitude with EMA smoothing.
|
// Other modes: peak IQ magnitude with EMA smoothing.
|
||||||
const SIGNAL_EMA_ALPHA: f32 = 0.4;
|
const SIGNAL_EMA_ALPHA: f32 = 0.4;
|
||||||
@@ -777,7 +784,7 @@ impl ChannelDsp {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.re * s.re + s.im * s.im)
|
.map(|s| s.re * s.re + s.im * s.im)
|
||||||
.fold(0.0_f32, f32::max);
|
.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);
|
self.last_signal_db += SIGNAL_EMA_ALPHA * (peak_db - self.last_signal_db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -567,6 +567,19 @@ impl RigCat for SoapySdrRig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_signal_strength_db<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
) -> Pin<Box<dyn std::future::Future<Output = Option<f64>> + 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 -------------------------------------------
|
// -- TX / unsupported methods -------------------------------------------
|
||||||
|
|
||||||
fn set_ptt<'a>(
|
fn set_ptt<'a>(
|
||||||
|
|||||||
Reference in New Issue
Block a user