use crate::{
error::Result,
fe::{
FeDevice,
sys::*,
},
};
const IDX_DELIVERY_SYSTEM: usize = 0;
const IDX_MODULATION: usize = 1;
const IDX_SIGNAL_STRENGTH: usize = 2;
const IDX_SNR: usize = 3;
const IDX_BER: usize = 4;
const IDX_UNC: usize = 5;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct FeLevel {
decibel: Option<f64>,
relative: Option<u32>,
}
impl FeLevel {
pub fn decibel(&self) -> Option<f64> {
self.decibel
}
pub fn relative(&self) -> Option<u32> {
self.relative
}
pub fn is_empty(&self) -> bool {
self.decibel.is_none() && self.relative.is_none()
}
}
#[derive(Debug, Clone, Copy)]
pub struct FeStats {
status: FeStatusFlags,
delivery_system: DeliverySystem,
modulation: Modulation,
signal: FeLevel,
cnr: FeLevel,
ber: Option<u32>,
unc: Option<u32>,
}
impl Default for FeStats {
fn default() -> FeStats {
FeStats {
status: FeStatusFlags::empty(),
delivery_system: DeliverySystem::Undefined,
modulation: Modulation::Qpsk,
signal: FeLevel::default(),
cnr: FeLevel::default(),
ber: None,
unc: None,
}
}
}
impl FeStats {
pub fn status(&self) -> FeStatusFlags {
self.status
}
pub fn has_lock(&self) -> bool {
self.status.contains(FeStatusFlags::HAS_LOCK)
}
pub fn delivery_system(&self) -> DeliverySystem {
self.delivery_system
}
pub fn modulation(&self) -> Modulation {
self.modulation
}
pub fn signal(&self) -> FeLevel {
self.signal
}
pub fn cnr(&self) -> FeLevel {
self.cnr
}
pub fn ber(&self) -> Option<u32> {
self.ber
}
pub fn unc(&self) -> Option<u32> {
self.unc
}
pub fn to_status_string(&self) -> String {
if self.status.is_empty() {
return "OFF".to_string();
}
let mut result = Vec::new();
if self.status.contains(FeStatusFlags::HAS_LOCK) {
result.push(format!("LOCK {}", self.delivery_system));
} else {
result.push(format!("NO-LOCK 0x{:02X}", self.status.bits()));
};
if self.status.contains(FeStatusFlags::HAS_SIGNAL) {
result.push(format!(
"Signal {:.02}dBm ({}%)",
self.signal.decibel().unwrap_or(0.0),
self.signal.relative().unwrap_or(0)
));
}
if self.status.contains(FeStatusFlags::HAS_CARRIER) {
result.push(format!(
"Quality {:.02}dB ({}%)",
self.cnr.decibel().unwrap_or(0.0),
self.cnr.relative().unwrap_or(0)
));
}
if self.status.contains(FeStatusFlags::HAS_LOCK) {
let ber = self.ber.map(|v| v.to_string());
result.push(format!("BER:{}", ber.as_deref().unwrap_or("-")));
let unc = self.unc.map(|v| v.to_string());
result.push(format!("UNC:{}", unc.as_deref().unwrap_or("-")));
}
result.join(" | ")
}
pub(crate) fn read(fe: &FeDevice) -> Result<FeStats> {
let status = fe.read_status()?;
let mut result = FeStats {
status,
..FeStats::default()
};
if status.is_empty() {
return Ok(result);
}
let mut props = [
DtvPropertyRaw::new(DTV_DELIVERY_SYSTEM, 0),
DtvPropertyRaw::new(DTV_MODULATION, Modulation::Qpsk as u32),
DtvPropertyRaw::new(DTV_STAT_SIGNAL_STRENGTH, 0),
DtvPropertyRaw::new(DTV_STAT_CNR, 0),
DtvPropertyRaw::new(DTV_STAT_PRE_ERROR_BIT_COUNT, 0),
DtvPropertyRaw::new(DTV_STAT_ERROR_BLOCK_COUNT, 0),
];
fe.get_properties(&mut props)?;
result.delivery_system = DeliverySystem::try_from(props[IDX_DELIVERY_SYSTEM].data())
.unwrap_or(DeliverySystem::Undefined);
result.modulation =
Modulation::try_from(props[IDX_MODULATION].data()).unwrap_or(Modulation::Qpsk);
result.signal = normalize_signal_strength(status, &props[IDX_SIGNAL_STRENGTH].stats(), fe);
result.cnr = normalize_snr(
status,
result.delivery_system,
result.modulation,
&props[IDX_SNR].stats(),
fe,
);
result.ber = normalize_ber(status, &props[IDX_BER].stats(), fe);
result.unc = normalize_unc(status, &props[IDX_UNC].stats(), fe);
Ok(result)
}
}
fn normalize_signal_strength(
status: FeStatusFlags,
stats: &DtvFrontendStats,
fe: &FeDevice,
) -> FeLevel {
let mut level = FeLevel::default();
let mut decibel = None;
for i in 0 .. usize::from(stats.len).min(stats.stat.len()) {
let stat = stats.stat[i];
match stat.scale {
FE_SCALE_DECIBEL => decibel = Some(stat.value),
FE_SCALE_RELATIVE => {
level.relative = Some(((stat.value & 0xFFFF) * 100 / 65535) as u32)
}
_ => {}
}
}
if let Some(value) = decibel {
level.decibel = Some((value as f64) / 1000.0);
if level.relative.is_none() && status.contains(FeStatusFlags::HAS_SIGNAL) {
const LO: i64 = -85000;
const HI: i64 = -6000;
let value = if value > HI {
65535
} else if value < LO {
0
} else {
65535 * (LO - value) / (LO - HI)
};
level.relative = Some((value * 100 / 65535) as u32);
}
}
if level.relative.is_none()
&& status.contains(FeStatusFlags::HAS_SIGNAL)
&& let Ok(value) = fe.read_signal_strength()
{
level.relative = Some(u32::from(value) * 100 / 65535);
}
level
}
fn normalize_snr(
status: FeStatusFlags,
delivery_system: DeliverySystem,
modulation: Modulation,
stats: &DtvFrontendStats,
fe: &FeDevice,
) -> FeLevel {
let mut level = FeLevel::default();
let mut decibel = None;
for i in 0 .. usize::from(stats.len).min(stats.stat.len()) {
let stat = stats.stat[i];
match stat.scale {
FE_SCALE_DECIBEL => decibel = Some(stat.value),
FE_SCALE_RELATIVE => {
level.relative = Some(((stat.value & 0xFFFF) * 100 / 65535) as u32)
}
_ => {}
}
}
if let Some(value) = decibel {
level.decibel = Some((value as f64) / 1000.0);
if level.relative.is_none() && status.contains(FeStatusFlags::HAS_CARRIER) {
let hi = match delivery_system {
DeliverySystem::Dvbs | DeliverySystem::Dvbs2 => 15000,
DeliverySystem::DvbcAnnexA
| DeliverySystem::DvbcAnnexB
| DeliverySystem::DvbcAnnexC
| DeliverySystem::Dvbc2 => 28000,
DeliverySystem::Dvbt | DeliverySystem::Dvbt2 => 19000,
DeliverySystem::Atsc => match modulation {
Modulation::Vsb8 | Modulation::Vsb16 => 19000,
_ => 28000,
},
_ => return level,
};
let value = if value >= hi {
65535
} else if value <= 0 {
0
} else {
65535 * value / hi
};
level.relative = Some((value * 100 / 65535) as u32);
}
}
if level.relative.is_none()
&& status.contains(FeStatusFlags::HAS_CARRIER)
&& let Ok(value) = fe.read_snr()
{
level.relative = Some(u32::from(value) * 100 / 65535);
}
level
}
fn normalize_ber(status: FeStatusFlags, stats: &DtvFrontendStats, fe: &FeDevice) -> Option<u32> {
for i in 0 .. usize::from(stats.len).min(stats.stat.len()) {
let stat = stats.stat[i];
if stat.scale == FE_SCALE_COUNTER {
return Some((stat.value & 0xFFFF_FFFF) as u32);
}
}
if status.contains(FeStatusFlags::HAS_LOCK) {
return fe.read_ber().ok();
}
None
}
fn normalize_unc(status: FeStatusFlags, stats: &DtvFrontendStats, fe: &FeDevice) -> Option<u32> {
for i in 0 .. usize::from(stats.len).min(stats.stat.len()) {
let stat = stats.stat[i];
if stat.scale == FE_SCALE_COUNTER {
return Some((stat.value & 0xFFFF_FFFF) as u32);
}
}
if status.contains(FeStatusFlags::HAS_LOCK) {
return fe.read_unc().ok();
}
None
}