use std::{ io::Write, sync::{Arc, RwLock}, time::Duration, }; use anyhow::Context; use metrics::{describe_gauge, gauge, Gauge}; use serde::{Deserialize, Serialize}; use serialport::SerialPort; use termcolor::WriteColor; pub struct Pli { pub state: Arc>, port: Box, voltage_gauge: Gauge, target_voltage_gauge: Gauge, duty_cycle: Gauge, internal_charge_current: Gauge, internal_load_current: Gauge, regulator_gauges: RegulatorGauges, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct PlState { pub battery_voltage: f64, pub target_voltage: f64, pub duty_cycle: f64, pub internal_charge_current: f64, pub internal_load_current: f64, pub regulator_state: RegulatorState, } impl Default for PlState { fn default() -> Self { Self { battery_voltage: Default::default(), target_voltage: Default::default(), duty_cycle: Default::default(), internal_charge_current: Default::default(), internal_load_current: Default::default(), regulator_state: RegulatorState::Absorption, } } } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum RegulatorState { Boost, Equalise, Absorption, Float, } impl From for RegulatorState { fn from(value: u8) -> Self { match value & 0b11 { 0b00 => Self::Boost, 0b01 => Self::Equalise, 0b10 => Self::Absorption, 0b11 => Self::Float, _ => unreachable!(), } } } struct RegulatorGauges { boost: Gauge, equalise: Gauge, absorption: Gauge, float: Gauge, } impl RegulatorGauges { fn new() -> Self { describe_gauge!("pl_regulator", "Regulator state"); let boost = gauge!("pl_regulator", "state" => "boost"); let equalise = gauge!("pl_regulator", "state" => "equalise"); let absorption = gauge!("pl_regulator", "state" => "absorption"); let float = gauge!("pl_regulator", "state" => "float"); Self { boost, equalise, absorption, float, } } fn set(&mut self, state: &RegulatorState) { match state { RegulatorState::Boost => { self.boost.set(1.); self.equalise.set(0.); self.absorption.set(0.); self.float.set(0.); } RegulatorState::Equalise => { self.boost.set(0.); self.equalise.set(1.); self.absorption.set(0.); self.float.set(0.); } RegulatorState::Absorption => { self.boost.set(0.); self.equalise.set(0.); self.absorption.set(1.); self.float.set(0.); } RegulatorState::Float => { self.boost.set(0.); self.equalise.set(0.); self.absorption.set(0.); self.float.set(1.); } } } } #[derive(Debug, Clone, Copy)] pub enum PliRequest { ReadRam(u8), } 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_target_voltage", "Target voltage"); let target_voltage_gauge = gauge!("pl_target_voltage"); describe_gauge!("pl_duty_cycle", "Duty cycle"); let duty_cycle = gauge!("pl_duty_cycle"); describe_gauge!("pl_internal_charge_current", "Internal charge current"); let internal_charge_current = gauge!("pl_internal_charge_current"); describe_gauge!("pl_internal_load_current", "Internal load current"); let internal_load_current = gauge!("pl_internal_load_current"); Ok(Self { state: Arc::new(RwLock::new(Default::default())), port, voltage_gauge, target_voltage_gauge, duty_cycle, internal_charge_current, internal_load_current, regulator_gauges: RegulatorGauges::new(), }) } pub fn refresh(&mut self) { if let Ok(new_state) = self.read_state() { self.voltage_gauge.set(new_state.battery_voltage); self.target_voltage_gauge.set(new_state.target_voltage); self.duty_cycle.set(new_state.duty_cycle); self.internal_charge_current .set(new_state.internal_charge_current); self.internal_load_current .set(new_state.internal_load_current); self.regulator_gauges.set(&new_state.regulator_state); *self.state.write().expect("PLI state handler panicked!!") = new_state; } } pub fn process_request(&mut self, message: PliRequest) { match message { PliRequest::ReadRam(address) => match self.read_ram(address) { Ok(data) => { let mut stdout = termcolor::StandardStream::stdout(termcolor::ColorChoice::Always); let _ = stdout.set_color( termcolor::ColorSpec::new().set_fg(Some(termcolor::Color::Green)), ); if writeln!(&mut stdout, "Read RAM at {address}: data {data}").is_err() { log::warn!( "Failed to set stdout colour\nRead RAM at {address}: data {data}" ); }; } Err(e) => log::error!("RAM read error: {e:?}"), }, } } fn read_state(&mut self) -> anyhow::Result { Ok(PlState { battery_voltage: (self.read_ram(PlRamAddress::Batv)? as f64) * (4. / 10.), target_voltage: (self.read_ram(PlRamAddress::Vreg)? as f64) * (4. / 10.), duty_cycle: (self.read_ram(PlRamAddress::Dutycyc)? as f64) / 255., internal_charge_current: (self.read_ram(PlRamAddress::Cint)? as f64) * (4. / 10.), internal_load_current: (self.read_ram(PlRamAddress::Lint)? as f64) * (4. / 10.), regulator_state: self.read_ram(PlRamAddress::Rstate)?.into(), }) } 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: T) -> anyhow::Result where T: Into, { self.send_command(command(20, address.into(), 0)); let buf: [u8; 2] = self.receive(); if buf[0] == 200 { Some(buf[1]) } else { None } .context(format!("Error from PLI: {}", buf[0])) } } enum PlRamAddress { Dutycyc, Batv, Rstate, Vreg, Cint, Lint, } impl From for u8 { fn from(value: PlRamAddress) -> Self { match value { PlRamAddress::Dutycyc => 39, PlRamAddress::Batv => 50, PlRamAddress::Rstate => 101, PlRamAddress::Vreg => 105, PlRamAddress::Cint => 213, PlRamAddress::Lint => 217, } } } fn command(operation: u8, address: u8, data: u8) -> [u8; 4] { [operation, address, data, !operation] }