use std::time::Duration; use anyhow::Context; use metrics::{describe_gauge, gauge, Gauge}; use serialport::SerialPort; pub struct Pli { port: Box, voltage_gauge: Gauge, duty_cycle: Gauge, } impl Pli { pub fn new(serial_port: String, baud_rate: u32) -> anyhow::Result { let port = serialport::new(serial_port, baud_rate) .timeout(Duration::from_millis(250)) .open()?; describe_gauge!("pl_battery_voltage", "Battery voltage"); let voltage_gauge = gauge!("pl_battery_voltage"); describe_gauge!("pl_duty_cycle", "Duty cycle"); let duty_cycle = gauge!("pl_duty_cycle"); Ok(Self { port, voltage_gauge, duty_cycle, }) } pub fn refresh(&mut self) { if let Ok(batv) = self.read_ram(50) { self.voltage_gauge.set((batv as f64) * (4. / 10.)); } if let Ok(duty_cycle) = self.read_ram(39) { self.duty_cycle.set((duty_cycle as f64) / 255.); } } fn send_command(&mut self, req: [u8; 4]) { self.port .write_all(&req) .expect("failed to write to serial port"); } fn receive(&mut self) -> [u8; LENGTH] { let mut buf = [0; LENGTH]; match self.port.read_exact(&mut buf) { Ok(_) => {} Err(e) => log::error!("PLI serial read error: {e:#?}"), } buf } fn read_ram(&mut self, address: u8) -> anyhow::Result { self.send_command(command(20, address, 0)); let buf: [u8; 2] = self.receive(); if buf[0] == 200 { Some(buf[1]) } else { None } .context(format!("Error from PLI: {}", buf[0])) } } fn command(operation: u8, address: u8, data: u8) -> [u8; 4] { [operation, address, data, !operation] }