vcgencmd/
lib.rs

1//! # Bindings for the RaspberryPi's vcgencmd cli utility
2
3use std::num::{ParseFloatError, ParseIntError};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use subprocess::{Exec, PopenError, Redirection};
8
9use bitpat::bitpat;
10
11mod parsers;
12
13#[derive(Debug)]
14pub enum ExecutionError {
15    Popen(PopenError),
16    ParseInt(ParseIntError),
17    ParseFloat(ParseFloatError),
18}
19
20pub enum ClockSrc {
21    Arm,
22    Core,
23    Dpi,
24    Emmc,
25    H264,
26    Hdmi,
27    Isp,
28    Pixel,
29    Pwm,
30    Uart,
31    V3d,
32    Vec,
33}
34
35pub enum VoltSrc {
36    Core,
37    SdramC,
38    SdramI,
39    SdramP,
40}
41
42pub enum MemSrc {
43    Arm,
44    Gpu,
45}
46
47pub enum Src {
48    Clock(ClockSrc),
49    Mem(MemSrc),
50    Volt(VoltSrc),
51}
52
53pub enum Cmd {
54    GetMem,
55    GetThrottled,
56    MeasureClock,
57    MeasureTemp,
58    MeasureVolts,
59}
60
61/// This struct represents the possible information in a bit-pattern you would get
62/// from the get_throttled command.
63#[derive(Debug, Default, PartialOrd, PartialEq)]
64#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
65pub struct ThrottledStatus {
66    pub arm_frequency_cap_occurred: bool,
67    pub arm_frequency_capped: bool,
68    pub currently_throttled: bool,
69    pub soft_temp_limit_active: bool,
70    pub soft_temp_limit_occurred: bool,
71    pub throttling_occurred: bool,
72    pub under_voltage: bool,
73    pub under_voltage_occurred: bool,
74}
75
76impl ThrottledStatus {
77    pub fn new(bit_pattern: isize) -> ThrottledStatus {
78        interpret_bit_pattern(bit_pattern)
79    }
80}
81
82/// Execute the given command and capture its std_output without modifying it
83pub fn exec_command(command: Cmd, src: Option<Src>) -> Result<String, PopenError> {
84    // "vcgencmd" must be in PATH
85    const VCGENCMD_INVOCATION: &str = "vcgencmd";
86
87    let vcgencmd_output = Exec::cmd("sudo")
88        .arg(VCGENCMD_INVOCATION)
89        .arg(resolve_command(command))
90        .arg(resolve_src(src).unwrap_or_default())
91        .stdout(Redirection::Pipe)
92        .capture()?
93        .stdout_str();
94
95    Ok(vcgencmd_output)
96}
97
98/// Measure the clock of the selected `ClockSrc`, returning the frequency as an isize
99pub fn measure_clock(src: Src) -> Result<isize, ExecutionError> {
100    let output = exec_command(Cmd::MeasureClock, Some(src)).map_err(ExecutionError::Popen)?;
101    let frequency = parsers::frequency(&output).map_err(ExecutionError::ParseInt)?;
102
103    Ok(frequency)
104}
105
106pub fn measure_volts(src: Src) -> Result<f64, ExecutionError> {
107    let output = exec_command(Cmd::MeasureVolts, Some(src)).map_err(ExecutionError::Popen)?;
108    let volts = parsers::volts(&output).map_err(ExecutionError::ParseFloat)?;
109
110    Ok(volts)
111}
112
113pub fn measure_temp() -> Result<f64, ExecutionError> {
114    let output = exec_command(Cmd::MeasureTemp, None).map_err(ExecutionError::Popen)?;
115    let temperature = parsers::temp(&output).map_err(ExecutionError::ParseFloat)?;
116
117    Ok(temperature)
118}
119
120pub fn get_mem(src: Src) -> Result<isize, ExecutionError> {
121    let output = exec_command(Cmd::GetMem, Some(src)).map_err(ExecutionError::Popen)?;
122    let mem = parsers::mem(&output).map_err(ExecutionError::ParseInt)?;
123
124    Ok(mem)
125}
126
127pub fn get_throttled() -> Result<isize, ExecutionError> {
128    let output = exec_command(Cmd::GetThrottled, None).map_err(ExecutionError::Popen)?;
129    let bit_pattern = parsers::throttled(&output).map_err(ExecutionError::ParseInt)?;
130    Ok(bit_pattern)
131}
132
133/// Interprets a bit pattern obtained from `get_throttled` in the following way:
134/// ```txt
135/// 111100000000000001010
136/// ||||             ||||_ under-voltage
137/// ||||             |||_ currently throttled
138/// ||||             ||_ arm frequency capped
139/// ||||             |_ soft temperature reached
140/// ||||_ under-voltage has occurred since last reboot
141/// |||_ throttling has occurred since last reboot
142/// ||_ arm frequency capped has occurred since last reboot
143/// |_ soft temperature reached since last reboot
144/// ```
145///
146/// > Note: This interpretation might be false/outdated for different versions of vcgencmd...
147///
148/// # Examples
149///
150/// Basic usage:
151///
152/// ```rust
153/// use vcgencmd::{interpret_bit_pattern, ThrottledStatus};
154/// let throttle_status = interpret_bit_pattern(0b111100000000000001010_isize);
155/// // or bit_pattern = get_throttle().unwrap();
156/// // let throttle status = interpret_bit_pattern(bit_pattern);
157/// assert_eq!(throttle_status,
158///            ThrottledStatus {
159///               arm_frequency_cap_occurred: true,
160///               arm_frequency_capped: false,
161///               currently_throttled: true,
162///               soft_temp_limit_active: true,
163///               soft_temp_limit_occurred: true,
164///               throttling_occurred: true,
165///               under_voltage: false,
166///               under_voltage_occurred: true,
167/// })
168/// ```
169pub fn interpret_bit_pattern(pattern: isize) -> ThrottledStatus {
170    let soft_temp_limit_occurred = bitpat!(1 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _)(pattern);
171    let arm_frequency_cap_occurred = bitpat!(_ 1 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _)(pattern);
172    let throttling_occurred = bitpat!(_ _ 1 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _)(pattern);
173    let under_voltage_occurred = bitpat!(_ _ _ 1 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _)(pattern);
174
175    let soft_temp_limit_active = bitpat!(_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 1 _ _ _)(pattern);
176    let arm_frequency_capped = bitpat!(_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 1 _ _)(pattern);
177    let currently_throttled = bitpat!(_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 1 _)(pattern);
178    let under_voltage = bitpat!(_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 1)(pattern);
179
180    ThrottledStatus {
181        arm_frequency_cap_occurred,
182        arm_frequency_capped,
183        currently_throttled,
184        soft_temp_limit_active,
185        soft_temp_limit_occurred,
186        throttling_occurred,
187        under_voltage,
188        under_voltage_occurred,
189    }
190}
191
192fn resolve_command(cmd: Cmd) -> String {
193    match cmd {
194        Cmd::GetMem => "get_mem",
195        Cmd::GetThrottled => "get_throttled",
196        Cmd::MeasureClock => "measure_clock",
197        Cmd::MeasureTemp => "measure_temp",
198        Cmd::MeasureVolts => "measure_volts",
199    }
200    .to_owned()
201}
202
203fn resolve_src(src: Option<Src>) -> Option<String> {
204    // check for None
205    let src = src.as_ref()?;
206
207    match src {
208        Src::Clock(ClockSrc::Arm) => Some("arm".to_owned()),
209        Src::Clock(ClockSrc::Core) => Some("core".to_owned()),
210        Src::Clock(ClockSrc::Dpi) => Some("dpi".to_owned()),
211        Src::Clock(ClockSrc::Emmc) => Some("emmc".to_owned()),
212        Src::Clock(ClockSrc::H264) => Some("h264".to_owned()),
213        Src::Clock(ClockSrc::Hdmi) => Some("hdmi".to_owned()),
214        Src::Clock(ClockSrc::Isp) => Some("isp".to_owned()),
215        Src::Clock(ClockSrc::Pixel) => Some("pixel".to_owned()),
216        Src::Clock(ClockSrc::Pwm) => Some("pwm".to_owned()),
217        Src::Clock(ClockSrc::Uart) => Some("uart".to_owned()),
218        Src::Clock(ClockSrc::V3d) => Some("v3d".to_owned()),
219        Src::Clock(ClockSrc::Vec) => Some("vec".to_owned()),
220        Src::Mem(MemSrc::Arm) => Some("arm".to_owned()),
221        Src::Mem(MemSrc::Gpu) => Some("gpu".to_owned()),
222        Src::Volt(VoltSrc::Core) => Some("core".to_owned()),
223        Src::Volt(VoltSrc::SdramC) => Some("sdram_c".to_owned()),
224        Src::Volt(VoltSrc::SdramI) => Some("sdram_i".to_owned()),
225        Src::Volt(VoltSrc::SdramP) => Some("sdram_p".to_owned()),
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_resolve_src() {
235        assert_eq!(
236            Some(String::from("arm")),
237            resolve_src(Some(Src::Clock(ClockSrc::Arm)))
238        );
239
240        assert_eq!(None, resolve_src(None));
241    }
242
243    #[test]
244    fn test_resolve_command() {
245        assert_eq!("measure_temp", resolve_command(Cmd::MeasureTemp));
246        assert_eq!("measure_clock", resolve_command(Cmd::MeasureClock));
247    }
248
249    #[test]
250    fn test_throttled_status_methods() {
251        let throttled_status = ThrottledStatus::new(0b111100000000000001010);
252        assert_eq!(
253            throttled_status,
254            ThrottledStatus {
255                arm_frequency_cap_occurred: true,
256                arm_frequency_capped: false,
257                currently_throttled: true,
258                soft_temp_limit_active: true,
259                soft_temp_limit_occurred: true,
260                throttling_occurred: true,
261                under_voltage: false,
262                under_voltage_occurred: true,
263            }
264        )
265    }
266
267    #[test]
268    fn test_interpret_bit_pattern() {
269        let throttled_info = interpret_bit_pattern(0b111100000000000001010);
270        assert_eq!(
271            throttled_info,
272            ThrottledStatus {
273                arm_frequency_cap_occurred: true,
274                arm_frequency_capped: false,
275                currently_throttled: true,
276                soft_temp_limit_active: true,
277                soft_temp_limit_occurred: true,
278                throttling_occurred: true,
279                under_voltage: false,
280                under_voltage_occurred: true,
281            }
282        );
283
284        let throttled_info2 = interpret_bit_pattern(0b111100000000000001111);
285        assert_eq!(
286            throttled_info2,
287            ThrottledStatus {
288                arm_frequency_cap_occurred: true,
289                arm_frequency_capped: true,
290                currently_throttled: true,
291                soft_temp_limit_active: true,
292                soft_temp_limit_occurred: true,
293                throttling_occurred: true,
294                under_voltage: true,
295                under_voltage_occurred: true,
296            }
297        )
298    }
299
300    #[cfg(target_arch = "arm")]
301    #[test]
302    fn test_exec_command() {
303        let output = exec_command(Cmd::MeasureClock, Some(Src::Clock(ClockSrc::Core))).unwrap();
304        dbg!(&output);
305        assert!(output.contains("frequency"));
306    }
307
308    #[cfg(target_arch = "arm")]
309    #[test]
310    fn test_get_mem() {
311        let output = get_mem(Src::Mem(MemSrc::Arm));
312        dbg!(&output);
313        debug_assert_eq!(output.is_ok(), true)
314    }
315
316    #[cfg(target_arch = "arm")]
317    #[test]
318    fn test_measure_temp() {
319        let output = measure_temp();
320        dbg!(&output);
321        debug_assert_eq!(output.is_ok(), true)
322    }
323
324    #[cfg(target_arch = "arm")]
325    #[test]
326    fn test_measure_volts() {
327        let output = measure_volts(Src::Volt(VoltSrc::Core));
328        dbg!(&output);
329        debug_assert_eq!(output.is_ok(), true)
330    }
331
332    #[cfg(target_arch = "arm")]
333    #[test]
334    fn test_measure_frequency() {
335        let output = get_mem(Src::Mem(MemSrc::Arm));
336        dbg!(&output);
337        debug_assert_eq!(output.is_ok(), true)
338    }
339}