1
0
mirror of https://github.com/danog/libdvb.git synced 2024-11-30 04:19:00 +01:00

fe status reading with fallback to DVBv3 API

This commit is contained in:
Andrey Dyldin 2021-02-04 12:23:21 +02:00
parent 8198ccca69
commit 8a558d7470
3 changed files with 342 additions and 86 deletions

View File

@ -23,7 +23,7 @@ fn check_frontend(path: &Path) -> Result<()> {
let mut status = FeStatus::default(); let mut status = FeStatus::default();
status.read(&fe)?; status.read(&fe)?;
println!("{}", &status); println!("Status: {}", &status);
Ok(()) Ok(())
} }

View File

@ -315,7 +315,6 @@ impl FeDevice {
pub fn get_event(&self, event: &mut FeEvent) -> Result<()> { pub fn get_event(&self, event: &mut FeEvent) -> Result<()> {
// FE_GET_EVENT // FE_GET_EVENT
ioctl_read!(#[inline] ioctl_call, b'o', 78, FeEvent); ioctl_read!(#[inline] ioctl_call, b'o', 78, FeEvent);
unsafe { unsafe {
ioctl_call(self.as_raw_fd(), event as *mut _) ioctl_call(self.as_raw_fd(), event as *mut _)
}.context("FE: get event")?; }.context("FE: get event")?;
@ -323,6 +322,79 @@ impl FeDevice {
Ok(()) Ok(())
} }
/// Returns frontend status
/// - [`FE_NONE`]
/// - [`FE_HAS_SIGNAL`]
/// - [`FE_HAS_CARRIER`]
/// - [`FE_HAS_VITERBI`]
/// - [`FE_HAS_SYNC`]
/// - [`FE_HAS_LOCK`]
/// - [`FE_TIMEDOUT`]
/// - [`FE_REINIT`]
pub fn read_status(&self) -> Result<u32> {
let mut result: u32 = FE_NONE;
// FE_READ_STATUS
ioctl_read!(#[inline] ioctl_call, b'o', 69, u32);
unsafe {
ioctl_call(self.as_raw_fd(), &mut result as *mut _)
}.context("FE: read status")?;
Ok(result)
}
/// Reads and returns a signal strength relative value (DVBv3 API)
pub fn read_signal_strength(&self) -> Result<u16> {
let mut result: u16 = 0;
// FE_READ_SIGNAL_STRENGTH
ioctl_read!(#[inline] ioctl_call, b'o', 71, u16);
unsafe {
ioctl_call(self.as_raw_fd(), &mut result as *mut _)
}.context("FE: read signal strength")?;
Ok(result)
}
/// Reads and returns a signal-to-noise ratio, relative value (DVBv3 API)
pub fn read_snr(&self) -> Result<u16> {
let mut result: u16 = 0;
// FE_READ_SNR
ioctl_read!(#[inline] ioctl_call, b'o', 72, u16);
unsafe {
ioctl_call(self.as_raw_fd(), &mut result as *mut _)
}.context("FE: read snr")?;
Ok(result)
}
/// Reads and returns a bit error counter (DVBv3 API)
pub fn read_ber(&self) -> Result<u32> {
let mut result: u32 = 0;
// FE_READ_BER
ioctl_read!(#[inline] ioctl_call, b'o', 70, u32);
unsafe {
ioctl_call(self.as_raw_fd(), &mut result as *mut _)
}.context("FE: read ber")?;
Ok(result)
}
/// Reads and returns an uncorrected blocks counter (DVBv3 API)
pub fn read_unc(&self) -> Result<u32> {
let mut result: u32 = 0;
// FE_READ_UNCORRECTED_BLOCKS
ioctl_read!(#[inline] ioctl_call, b'o', 73, u32);
unsafe {
ioctl_call(self.as_raw_fd(), &mut result as *mut _)
}.context("FE: read uncorrected blocks")?;
Ok(result)
}
/// Turns on/off generation of the continuous 22kHz tone /// Turns on/off generation of the continuous 22kHz tone
/// ///
/// allowed `value`'s: /// allowed `value`'s:

View File

@ -1,17 +1,9 @@
use { use {
std::{ std::{
fmt, fmt,
os::unix::io::AsRawFd,
}, },
anyhow::{ anyhow::Result,
Context,
Result,
},
nix::{
ioctl_read,
},
super::{ super::{
FeDevice, FeDevice,
@ -27,7 +19,7 @@ pub struct FeStatus {
status: u32, status: u32,
/// properties /// properties
props: [DtvProperty; 4], props: [DtvProperty; 6],
} }
@ -36,14 +28,18 @@ impl Default for FeStatus {
FeStatus { FeStatus {
status: 0, status: 0,
props: [ props: [
// 0: signal level // 0: delivery system
DtvProperty::new(DTV_DELIVERY_SYSTEM, FE_NONE),
// 1: signal level
DtvProperty::new(DTV_STAT_SIGNAL_STRENGTH, 0), DtvProperty::new(DTV_STAT_SIGNAL_STRENGTH, 0),
// 1: signal-to-noise ratio // 2: signal-to-noise ratio
DtvProperty::new(DTV_STAT_CNR, 0), DtvProperty::new(DTV_STAT_CNR, 0),
// 2: ber - number of bit errors // 3: ber - number of bit errors
DtvProperty::new(DTV_STAT_PRE_ERROR_BIT_COUNT, 0), DtvProperty::new(DTV_STAT_PRE_ERROR_BIT_COUNT, 0),
// 3: unc - number of block errors // 4: unc - number of block errors
DtvProperty::new(DTV_STAT_ERROR_BLOCK_COUNT, 0), DtvProperty::new(DTV_STAT_ERROR_BLOCK_COUNT, 0),
// 5: modulation
DtvProperty::new(DTV_MODULATION, QPSK),
], ],
} }
} }
@ -52,69 +48,80 @@ impl Default for FeStatus {
/// Returns an object that implements `Display` for different verbosity levels /// Returns an object that implements `Display` for different verbosity levels
/// ///
/// Tuner is turned off:
///
/// ```text /// ```text
/// Status:SCVYL S:-38.56dBm (59%) Q:14.57dB (70%) BER:0 UNC:0 /// OFF
/// ``` /// ```
/// ///
/// Status: /// Tuner acquiring signal but has no lock:
/// - S - Signal ///
/// - C - Carrier /// ```text
/// - V - FEC /// NO-LOCK 0x01 | Signal -38.56dBm (59%)
/// - Y - Sync /// NO-LOCK 0x03 | Signal -38.56dBm (59%) | Quality 5.32dB (25%)
/// - L - Lock /// ```
///
/// Hex number after `NO-LOCK` this is tuner status bit flags:
/// - 0x01 - has signal
/// - 0x02 - has carrier
/// - 0x04 - has viterbi
/// - 0x08 - has sync
/// - 0x10 - has lock
/// - 0x20 - timed-out
/// - 0x40 - re-init
///
/// Tuner has lock
///
/// ```text
/// LOCK dvb-s2 | Signal -38.56dBm (59%) | Quality 14.57dB (70%) | BER:0 | UNC:0
/// ```
impl fmt::Display for FeStatus { impl fmt::Display for FeStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Status:")?;
if self.status == FE_NONE { if self.status == FE_NONE {
write!(f, "OFF")?; write!(f, "OFF")?;
return Ok(()); return Ok(());
} }
const STATUS_MAP: &[char] = &['S', 'C', 'V', 'Y', 'L']; if self.status & FE_HAS_LOCK != 0 {
for (i, s) in STATUS_MAP.iter().enumerate() { write!(f, "LOCK {}", DeliverySystemDisplay(self.get_delivery_system()))?;
let c = if self.status & (1 << i) != 0 { *s } else { '_' }; } else {
write!(f, "{}", c)?; write!(f, "NO-LOCK 0x{:02X}", self.status)?;
} }
if self.status & FE_HAS_SIGNAL == 0 { if self.status & FE_HAS_SIGNAL == 0 {
return Ok(()); return Ok(());
} }
write!(f, " Signal:")?; write!(
if let Some(s) = self.get_signal_level() { f,
// TODO: config for lo/hi " | Signal {:.02}dBm ({}%)",
let lo: f64 = -85.0; self.get_signal_strength_decibel().unwrap_or(0.0),
let hi: f64 = -6.0; self.get_signal_strength().unwrap_or(0)
let relative = 100.0 - (s - hi) * 100.0 / (lo - hi); )?;
write!(f, "{:.02}dBm ({:.0}%)", s, relative)?;
} else {
write!(f, "-")?;
}
if self.status & FE_HAS_CARRIER == 0 { if self.status & FE_HAS_CARRIER == 0 {
return Ok(()); return Ok(());
} }
write!(f, " Quality:")?; write!(
if let Some(q) = self.get_signal_noise_ratio() { f,
write!(f, "{:.02}dB", q)?; " | Quality {:.02}dB ({}%)",
} else { self.get_snr_decibel().unwrap_or(0.0),
write!(f, "-")?; self.get_snr().unwrap_or(0)
} )?;
if self.status & FE_HAS_LOCK == 0 { if self.status & FE_HAS_LOCK == 0 {
return Ok(()); return Ok(());
} }
write!(f, " BER:")?; write!(f, " | BER:")?;
if let Some(ber) = self.get_ber() { if let Some(ber) = self.get_ber() {
write!(f, "{}", ber)?; write!(f, "{}", ber)?;
} else { } else {
write!(f, "-")?; write!(f, "-")?;
} }
write!(f, " UNC:")?; write!(f, " | UNC:")?;
if let Some(unc) = self.get_unc() { if let Some(unc) = self.get_unc() {
write!(f, "{}", unc)?; write!(f, "{}", unc)?;
} else { } else {
@ -127,64 +134,241 @@ impl fmt::Display for FeStatus {
impl FeStatus { impl FeStatus {
fn get_stats_decibel(&self, u: usize) -> Option<f64> { /// Returns current delivery system
let stats = self.props.get(u)?; #[inline]
let stats = unsafe { &stats.u.st }; pub fn get_delivery_system(&self) -> u32 { unsafe { self.props[0].u.data } }
let len = ::std::cmp::min(stats.len as usize, stats.stat.len()); /// Returns current modulation
for s in stats.stat[.. len].iter() { #[inline]
if s.scale == FE_SCALE_DECIBEL { pub fn get_modulation(&self) -> u32 { unsafe { self.props[5].u.data } }
return Some((s.value as f64) / 1000.0);
} /// Returns Signal Strength in dBm
pub fn get_signal_strength_decibel(&self) -> Option<f64> {
let stat = unsafe { &self.props[1].u.st.stat[0] };
if stat.scale == FE_SCALE_DECIBEL {
Some((stat.value as f64) / 1000.0)
} else {
None
} }
None
} }
/// Returns Signal Level in dBm /// Returns Signal Strength in percentage
#[inline] pub fn get_signal_strength(&self) -> Option<u32> {
pub fn get_signal_level(&self) -> Option<f64> { self.get_stats_decibel(0) } let stat = unsafe { &self.props[1].u.st.stat[1] };
if stat.scale == FE_SCALE_RELATIVE {
Some(((stat.value & 0xFFFF) * 100 / 65535) as u32)
} else {
None
}
}
/// Returns Signal to noise ratio in dB /// Returns Signal to noise ratio in dB
#[inline] pub fn get_snr_decibel(&self) -> Option<f64> {
pub fn get_signal_noise_ratio(&self) -> Option<f64> { self.get_stats_decibel(1) } let stat = unsafe { &self.props[2].u.st.stat[0] };
if stat.scale == FE_SCALE_DECIBEL {
fn get_stats_counter(&self, u: usize) -> Option<u32> { Some((stat.value as f64) / 1000.0)
let stats = self.props.get(u)?; } else {
let stats = unsafe { &stats.u.st }; None
if stats.len > 0 { }
let s = &stats.stat[0]; }
if s.scale == FE_SCALE_COUNTER {
return Some((s.value & 0xFFFF) as u32); /// Returns Signal Strength in percentage
} pub fn get_snr(&self) -> Option<u32> {
let stat = unsafe { &self.props[2].u.st.stat[1] };
if stat.scale == FE_SCALE_RELATIVE {
Some(((stat.value & 0xFFFF) * 100 / 65535) as u32)
} else {
None
} }
None
} }
/// Returns BER value if available /// Returns BER value if available
#[inline] pub fn get_ber(&self) -> Option<u32> {
pub fn get_ber(&self) -> Option<u32> { self.get_stats_counter(2) } let stat = unsafe { &self.props[3].u.st.stat[0] };
if stat.scale == FE_SCALE_COUNTER {
Some((stat.value & 0xFFFF) as u32)
} else {
None
}
}
/// Returns UNC value if available /// Returns UNC value if available
#[inline] pub fn get_unc(&self) -> Option<u32> {
pub fn get_unc(&self) -> Option<u32> { self.get_stats_counter(3) } let stat = unsafe { &self.props[4].u.st.stat[0] };
if stat.scale == FE_SCALE_COUNTER {
Some((stat.value & 0xFFFF) as u32)
} else {
None
}
}
/// Reads frontend status fn normalize_signal_strength(&mut self, fe: &FeDevice) -> Result<()> {
let mut stats = unsafe { &mut self.props[1].u.st };
for i in usize::from(stats.len) .. 2 {
stats.stat[i].scale = FE_SCALE_NOT_AVAILABLE;
stats.stat[i].value = 0;
}
stats.len = 2;
if stats.stat[0].scale == FE_SCALE_RELATIVE {
stats.stat.swap(0, 1);
return Ok(())
}
if stats.stat[1].scale == FE_SCALE_RELATIVE || (self.status & FE_HAS_SIGNAL) == 0 {
return Ok(())
}
// fallback to DVBv3 API
if let Ok(value) = fe.read_signal_strength() {
stats.stat[1].scale = FE_SCALE_RELATIVE;
stats.stat[1].value = i64::from(value);
} else if stats.stat[0].scale == FE_SCALE_DECIBEL {
// TODO: check delivery_system
let lo: i64 = -85000;
let hi: i64 = -6000;
stats.stat[1].scale = FE_SCALE_RELATIVE;
stats.stat[1].value = {
if stats.stat[0].value > hi {
65545
} else if stats.stat[0].value < lo {
0
} else {
65545 * (lo - stats.stat[0].value) / (lo - hi)
}
};
}
Ok(())
}
fn normalize_snr(&mut self, fe: &FeDevice) -> Result<()> {
let delivery_system = self.get_delivery_system();
let modulation = self.get_modulation();
let mut stats = unsafe { &mut self.props[2].u.st };
for i in usize::from(stats.len) .. 2 {
stats.stat[i].scale = FE_SCALE_NOT_AVAILABLE;
stats.stat[i].value = 0;
}
stats.len = 2;
if stats.stat[0].scale == FE_SCALE_RELATIVE {
stats.stat.swap(0, 1);
return Ok(())
}
if stats.stat[1].scale == FE_SCALE_RELATIVE || (self.status & FE_HAS_CARRIER) == 0 {
return Ok(())
}
// fallback to DVBv3 API
if let Ok(value) = fe.read_snr() {
stats.stat[1].scale = FE_SCALE_RELATIVE;
stats.stat[1].value = i64::from(value);
} else if stats.stat[0].scale == FE_SCALE_DECIBEL {
let maxdb = match delivery_system {
SYS_DVBS |
SYS_DVBS2 => 15000,
SYS_DVBC_ANNEX_A |
SYS_DVBC_ANNEX_B |
SYS_DVBC_ANNEX_C |
SYS_DVBC2 => 28000,
SYS_DVBT |
SYS_DVBT2 => 19000,
SYS_ATSC => {
match modulation {
VSB_8 | VSB_16 => 19000,
_ => 28000,
}
}
_ => 0
};
if maxdb != 0 {
stats.stat[1].scale = FE_SCALE_RELATIVE;
stats.stat[1].value = {
if stats.stat[0].value >= maxdb {
65535
} else if stats.stat[0].value <= 0 {
0
} else {
65535 * stats.stat[0].value / maxdb
}
};
}
}
Ok(())
}
fn normalize_ber(&mut self, fe: &FeDevice) -> Result<()> {
let mut stats = unsafe { &mut self.props[3].u.st };
if stats.len == 0 || stats.stat[0].scale != FE_SCALE_COUNTER {
if let Ok(value) = fe.read_ber() {
stats.stat[0].scale = FE_SCALE_COUNTER;
stats.stat[0].value = i64::from(value);
} else {
stats.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
stats.stat[0].value = 0;
}
stats.len = 1;
}
Ok(())
}
fn normalize_unc(&mut self, fe: &FeDevice) -> Result<()> {
let mut stats = unsafe { &mut self.props[4].u.st };
if stats.len == 0 || stats.stat[0].scale != FE_SCALE_COUNTER {
if let Ok(value) = fe.read_unc() {
stats.stat[0].scale = FE_SCALE_COUNTER;
stats.stat[0].value = i64::from(value);
} else {
stats.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
stats.stat[0].value = 0;
}
stats.len = 1;
}
Ok(())
}
/// set decibel to `stat[0]` and relative to `stat[1]` and fallback to DVBv3 API
fn normalize_props(&mut self, fe: &FeDevice) -> Result<()> {
self.normalize_signal_strength(fe)?;
self.normalize_snr(fe)?;
self.normalize_ber(fe)?;
self.normalize_unc(fe)?;
Ok(())
}
/// Reads frontend status with fallback to DVBv3 API
pub fn read(&mut self, fe: &FeDevice) -> Result<()> { pub fn read(&mut self, fe: &FeDevice) -> Result<()> {
self.status = FE_NONE; self.status = fe.read_status()?;
// FE_READ_STATUS
ioctl_read!(#[inline] ioctl_call, b'o', 69, u32);
unsafe {
ioctl_call(fe.as_raw_fd(), &mut self.status as *mut _)
}.context("FE: read status")?;
if self.status == FE_NONE { if self.status == FE_NONE {
return Ok(()); return Ok(());
} }
fe.get_properties(&mut self.props)?; fe.get_properties(&mut self.props)?;
self.normalize_props(fe)
Ok(())
} }
} }