1
0
mirror of https://github.com/danog/libdvb.git synced 2024-11-26 20:04:39 +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();
status.read(&fe)?;
println!("{}", &status);
println!("Status: {}", &status);
Ok(())
}

View File

@ -315,7 +315,6 @@ impl FeDevice {
pub fn get_event(&self, event: &mut FeEvent) -> Result<()> {
// FE_GET_EVENT
ioctl_read!(#[inline] ioctl_call, b'o', 78, FeEvent);
unsafe {
ioctl_call(self.as_raw_fd(), event as *mut _)
}.context("FE: get event")?;
@ -323,6 +322,79 @@ impl FeDevice {
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
///
/// allowed `value`'s:

View File

@ -1,17 +1,9 @@
use {
std::{
fmt,
os::unix::io::AsRawFd,
},
anyhow::{
Context,
Result,
},
nix::{
ioctl_read,
},
anyhow::Result,
super::{
FeDevice,
@ -27,7 +19,7 @@ pub struct FeStatus {
status: u32,
/// properties
props: [DtvProperty; 4],
props: [DtvProperty; 6],
}
@ -36,14 +28,18 @@ impl Default for FeStatus {
FeStatus {
status: 0,
props: [
// 0: signal level
// 0: delivery system
DtvProperty::new(DTV_DELIVERY_SYSTEM, FE_NONE),
// 1: signal level
DtvProperty::new(DTV_STAT_SIGNAL_STRENGTH, 0),
// 1: signal-to-noise ratio
// 2: signal-to-noise ratio
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),
// 3: unc - number of block errors
// 4: unc - number of block errors
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
///
/// Tuner is turned off:
///
/// ```text
/// Status:SCVYL S:-38.56dBm (59%) Q:14.57dB (70%) BER:0 UNC:0
/// OFF
/// ```
///
/// Status:
/// - S - Signal
/// - C - Carrier
/// - V - FEC
/// - Y - Sync
/// - L - Lock
/// Tuner acquiring signal but has no lock:
///
/// ```text
/// NO-LOCK 0x01 | Signal -38.56dBm (59%)
/// NO-LOCK 0x03 | Signal -38.56dBm (59%) | Quality 5.32dB (25%)
/// ```
///
/// 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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Status:")?;
if self.status == FE_NONE {
write!(f, "OFF")?;
return Ok(());
}
const STATUS_MAP: &[char] = &['S', 'C', 'V', 'Y', 'L'];
for (i, s) in STATUS_MAP.iter().enumerate() {
let c = if self.status & (1 << i) != 0 { *s } else { '_' };
write!(f, "{}", c)?;
if self.status & FE_HAS_LOCK != 0 {
write!(f, "LOCK {}", DeliverySystemDisplay(self.get_delivery_system()))?;
} else {
write!(f, "NO-LOCK 0x{:02X}", self.status)?;
}
if self.status & FE_HAS_SIGNAL == 0 {
return Ok(());
}
write!(f, " Signal:")?;
if let Some(s) = self.get_signal_level() {
// TODO: config for lo/hi
let lo: f64 = -85.0;
let hi: f64 = -6.0;
let relative = 100.0 - (s - hi) * 100.0 / (lo - hi);
write!(f, "{:.02}dBm ({:.0}%)", s, relative)?;
} else {
write!(f, "-")?;
}
write!(
f,
" | Signal {:.02}dBm ({}%)",
self.get_signal_strength_decibel().unwrap_or(0.0),
self.get_signal_strength().unwrap_or(0)
)?;
if self.status & FE_HAS_CARRIER == 0 {
return Ok(());
}
write!(f, " Quality:")?;
if let Some(q) = self.get_signal_noise_ratio() {
write!(f, "{:.02}dB", q)?;
} else {
write!(f, "-")?;
}
write!(
f,
" | Quality {:.02}dB ({}%)",
self.get_snr_decibel().unwrap_or(0.0),
self.get_snr().unwrap_or(0)
)?;
if self.status & FE_HAS_LOCK == 0 {
return Ok(());
}
write!(f, " BER:")?;
write!(f, " | BER:")?;
if let Some(ber) = self.get_ber() {
write!(f, "{}", ber)?;
} else {
write!(f, "-")?;
}
write!(f, " UNC:")?;
write!(f, " | UNC:")?;
if let Some(unc) = self.get_unc() {
write!(f, "{}", unc)?;
} else {
@ -127,64 +134,241 @@ impl fmt::Display for FeStatus {
impl FeStatus {
fn get_stats_decibel(&self, u: usize) -> Option<f64> {
let stats = self.props.get(u)?;
let stats = unsafe { &stats.u.st };
/// Returns current delivery system
#[inline]
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());
for s in stats.stat[.. len].iter() {
if s.scale == FE_SCALE_DECIBEL {
return Some((s.value as f64) / 1000.0);
}
/// Returns current modulation
#[inline]
pub fn get_modulation(&self) -> u32 { unsafe { self.props[5].u.data } }
/// 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
#[inline]
pub fn get_signal_level(&self) -> Option<f64> { self.get_stats_decibel(0) }
/// Returns Signal Strength in percentage
pub fn get_signal_strength(&self) -> Option<u32> {
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
#[inline]
pub fn get_signal_noise_ratio(&self) -> Option<f64> { self.get_stats_decibel(1) }
fn get_stats_counter(&self, u: usize) -> Option<u32> {
let stats = self.props.get(u)?;
let stats = unsafe { &stats.u.st };
if stats.len > 0 {
let s = &stats.stat[0];
if s.scale == FE_SCALE_COUNTER {
return Some((s.value & 0xFFFF) as u32);
}
pub fn get_snr_decibel(&self) -> Option<f64> {
let stat = unsafe { &self.props[2].u.st.stat[0] };
if stat.scale == FE_SCALE_DECIBEL {
Some((stat.value as f64) / 1000.0)
} else {
None
}
}
/// 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
#[inline]
pub fn get_ber(&self) -> Option<u32> { self.get_stats_counter(2) }
pub fn get_ber(&self) -> Option<u32> {
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
#[inline]
pub fn get_unc(&self) -> Option<u32> { self.get_stats_counter(3) }
pub fn get_unc(&self) -> Option<u32> {
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<()> {
self.status = FE_NONE;
// 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")?;
self.status = fe.read_status()?;
if self.status == FE_NONE {
return Ok(());
}
fe.get_properties(&mut self.props)?;
Ok(())
self.normalize_props(fe)
}
}