libdvb_rs/fe/
status.rs

1use crate::get_dtv_properties;
2
3use {
4    super::{sys::*, FeDevice},
5    anyhow::{Result},
6    std::fmt,
7};
8
9/// Frontend status
10#[derive(Debug)]
11pub struct FeStatus {
12    /// `sys::frontend::FeStatus`
13    status: fe_status,
14
15    delivery_system: Option<fe_delivery_system>,
16    modulation: Option<fe_modulation>,
17    signal_strength_decibel: Option<f64>,
18    signal_strength_percentage: Option<u8>,
19    snr_decibel: Option<f64>,
20    snr_percentage: Option<u8>,
21    // ber - number of bit errors
22    ber: Option<u64>,
23    // unc - number of block errors
24    unc: Option<u64>,
25}
26
27impl Default for FeStatus {
28    fn default() -> FeStatus {
29        FeStatus {
30            status: fe_status::FE_NONE,
31            delivery_system: None,
32            modulation: None,
33            signal_strength_decibel: None,
34            signal_strength_percentage: None,
35            snr_decibel: None,
36            snr_percentage: None,
37            ber: None,
38            unc: None,
39        }
40    }
41}
42
43/// Returns an object that implements `Display` for different verbosity levels
44///
45/// Tuner is turned off:
46///
47/// ```text
48/// OFF
49/// ```
50///
51/// Tuner acquiring signal but has no lock:
52///
53/// ```text
54/// NO-LOCK 0x01 | Signal -38.56dBm (59%)
55/// NO-LOCK 0x03 | Signal -38.56dBm (59%) | Quality 5.32dB (25%)
56/// ```
57///
58/// Hex number after `NO-LOCK` this is tuner status bit flags:
59/// - 0x01 - has signal
60/// - 0x02 - has carrier
61/// - 0x04 - has viterbi
62/// - 0x08 - has sync
63/// - 0x10 - has lock
64/// - 0x20 - timed-out
65/// - 0x40 - re-init
66///
67/// Tuner has lock
68///
69/// ```text
70/// LOCK dvb-s2 | Signal -38.56dBm (59%) | Quality 14.57dB (70%) | BER:0 | UNC:0
71/// ```
72impl fmt::Display for FeStatus {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        if self.status == fe_status::FE_NONE {
75            write!(f, "OFF")?;
76            return Ok(());
77        }
78
79        if self.status.contains(fe_status::FE_HAS_LOCK) {
80            write!(
81                f,
82                "LOCK {}",
83                self.get_delivery_system()
84                    .as_ref()
85                    .unwrap_or(&fe_delivery_system::SYS_UNDEFINED)
86            )?;
87        } else {
88            write!(f, "NO-LOCK 0x{:02X}", self.status)?;
89        }
90
91        if !self.status.contains(fe_status::FE_HAS_SIGNAL) {
92            return Ok(());
93        }
94
95        write!(
96            f,
97            " | Signal {:.02}dBm ({}%)",
98            self.get_signal_strength_decibel().unwrap_or(0.0),
99            self.get_signal_strength().unwrap_or(0)
100        )?;
101
102        if !self.status.contains(fe_status::FE_HAS_CARRIER) {
103            return Ok(());
104        }
105
106        write!(
107            f,
108            " | Quality {:.02}dB ({}%)",
109            self.get_snr_decibel().unwrap_or(0.0),
110            self.get_snr().unwrap_or(0)
111        )?;
112
113        if !self.status.contains(fe_status::FE_HAS_LOCK) {
114            return Ok(());
115        }
116
117        write!(f, " | BER:")?;
118        if let Some(ber) = self.get_ber() {
119            write!(f, "{}", ber)?;
120        } else {
121            write!(f, "-")?;
122        }
123
124        write!(f, " | UNC:")?;
125        if let Some(unc) = self.get_unc() {
126            write!(f, "{}", unc)?;
127        } else {
128            write!(f, "-")?;
129        }
130
131        Ok(())
132    }
133}
134
135impl FeStatus {
136    /// Returns current delivery system
137    #[inline]
138    pub fn get_delivery_system(&self) -> &Option<fe_delivery_system> {
139        &self.delivery_system
140    }
141
142    /// Returns current modulation
143    #[inline]
144    pub fn get_modulation(&self) -> &Option<fe_modulation> {
145        &self.modulation
146    }
147
148    /// Returns Signal Strength in dBm
149    pub fn get_signal_strength_decibel(&self) -> &Option<f64> {
150        &self.signal_strength_decibel
151    }
152
153    /// Returns Signal Strength in percentage
154    pub fn get_signal_strength(&self) -> &Option<u8> {
155        &self.signal_strength_percentage
156    }
157
158    /// Returns Signal to noise ratio in dB
159    pub fn get_snr_decibel(&self) -> &Option<f64> {
160        &self.snr_decibel
161    }
162
163    /// Returns Signal Strength in percentage
164    pub fn get_snr(&self) -> &Option<u8> {
165        &self.snr_percentage
166    }
167
168    /// Returns BER value if available
169    pub fn get_ber(&self) -> &Option<u64> {
170        &self.ber
171    }
172
173    /// Returns UNC value if available
174    pub fn get_unc(&self) -> &Option<u64> {
175        &self.unc
176    }
177
178    fn normalize_signal_strength(&mut self, stats: DtvFrontendStats) {
179        self.signal_strength_decibel = stats.get_decibel_float();
180        self.signal_strength_percentage = match (stats.get_relative(), stats.get_decibel()) {
181            (Some(v), _) => Some(((v as u32) * 100 / 65535) as u8),
182            (None, Some(decibel)) if self.status.contains(fe_status::FE_HAS_SIGNAL) => {
183                // TODO: check delivery_system
184                // TODO: this logic looks very sus
185                let lo: i64 = -85000;
186                let hi: i64 = -6000;
187                Some({
188                    if decibel > hi {
189                        100
190                    } else if decibel < lo {
191                        0
192                    } else {
193                        (((lo - decibel) * 100) / (lo - hi)) as u8
194                    }
195                })
196            }
197            _ => None,
198        };
199    }
200
201    fn normalize_snr(&mut self, stats: DtvFrontendStats) {
202        self.snr_decibel = stats.get_decibel_float();
203        self.snr_percentage = match (stats.get_relative(), stats.get_decibel()) {
204            (Some(v), _) => Some(((v as u32) * 100 / 65535) as u8),
205            (None, Some(decibel)) if self.status.contains(fe_status::FE_HAS_CARRIER) => {
206                match match self.delivery_system {
207                    Some(SYS_DVBS) | Some(SYS_DVBS2) => Some(15000),
208
209                    Some(SYS_DVBC_ANNEX_A) | Some(SYS_DVBC_ANNEX_B) | Some(SYS_DVBC_ANNEX_C) | Some(SYS_DVBC2) => {
210                        Some(28000)
211                    }
212
213                    Some(SYS_DVBT) | Some(SYS_DVBT2) => Some(19000),
214
215                    Some(SYS_ATSC) => Some(match self.modulation {
216                        Some(VSB_8 | VSB_16) => 19000,
217                        _ => 28000,
218                    }),
219
220                    _ => None,
221                } {
222                    Some(_) if decibel <= 0 => Some(0),
223                    Some(vhi) if decibel >= vhi => Some(100),
224                    Some(vhi) => Some(((decibel * 100) / vhi) as u8),
225                    _ => None,
226                }
227            }
228            _ => None,
229        };
230    }
231
232    /// Reads frontend status with fallback to DVBv3 API
233    pub fn read(&mut self, fe: &FeDevice) -> Result<()> {
234        self.status = fe.read_status()?;
235
236        if self.status == fe_status::FE_NONE {
237            return Ok(());
238        }
239
240        let (delivery_system, modulation, signal_strength, snr, ber, unc) = get_dtv_properties!(
241            fe,
242            DTV_DELIVERY_SYSTEM,
243            DTV_MODULATION,
244            DTV_STAT_SIGNAL_STRENGTH,
245            DTV_STAT_CNR,
246            DTV_STAT_PRE_ERROR_BIT_COUNT,
247            DTV_STAT_ERROR_BLOCK_COUNT
248        )?;
249        self.delivery_system = Some(delivery_system);
250        self.modulation = Some(modulation);
251        self.normalize_signal_strength(signal_strength);
252        self.normalize_snr(snr);
253        self.ber = match ber.get_counter() {
254            Some(v) => Some(v),
255            None if self.status.contains(fe_status::FE_HAS_LOCK) => fe.read_ber().ok(),
256            None => None,
257        };
258        self.unc = match unc.get_counter() {
259            Some(v) => Some(v),
260            None if self.status.contains(fe_status::FE_HAS_LOCK) => fe.read_unc().ok(),
261            None => None,
262        };
263
264        Ok(())
265    }
266}