use chrono::Timelike; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio_serial::{SerialPort, SerialPortBuilderExt}; use crate::gauges::{ BATTERY_TEMP, BATTERY_VOLTAGE, CHARGE_STATE, INPUT_CURRENT, PL_DUTY_CYCLE, PL_LOAD_CURRENT, TARGET_VOLTAGE, }; pub struct Pli { friendly_name: String, port: tokio_serial::SerialStream, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] 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 battery_temp: f64, pub regulator_state: RegulatorState, // pub internal_charge_ah_accumulator: u16, // pub external_charge_ah_accumulator: u16, // pub internal_load_ah_accumulator: u16, // pub external_load_ah_accumulator: u16, // pub internal_charge_ah: u16, // pub external_charge_ah: u16, // pub internal_load_ah: u16, // pub external_load_ah: u16, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum RegulatorState { Boost, Equalise, Absorption, Float, } impl From<u8> for RegulatorState { fn from(value: u8) -> Self { match value & 0b11 { 0b00 => Self::Boost, 0b01 => Self::Equalise, 0b10 => Self::Absorption, 0b11 => Self::Float, _ => unreachable!(), } } } impl From<RegulatorState> for u8 { fn from(value: RegulatorState) -> Self { match value { RegulatorState::Boost => 0b00, RegulatorState::Equalise => 0b01, RegulatorState::Absorption => 0b10, RegulatorState::Float => 0b11, } } } impl Default for RegulatorState { fn default() -> Self { Self::Absorption } } fn set_regulator_gauges(state: RegulatorState, label: &str) { let boost = CHARGE_STATE.with_label_values(&[label, "boost"]); let equalise = CHARGE_STATE.with_label_values(&[label, "equalise"]); let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]); let float = CHARGE_STATE.with_label_values(&[label, "float"]); match state { RegulatorState::Boost => { boost.set(1); equalise.set(0); absorption.set(0); float.set(0); } RegulatorState::Equalise => { boost.set(0); equalise.set(1); absorption.set(0); float.set(0); } RegulatorState::Absorption => { boost.set(0); equalise.set(0); absorption.set(1); float.set(0); } RegulatorState::Float => { boost.set(0); equalise.set(0); absorption.set(0); float.set(1); } } } #[expect(dead_code, reason = "writing settings is not yet implemented")] #[derive(Debug, Clone, Copy)] pub enum PliRequest { ReadRam(u8), ReadEeprom(u8), SyncTime, SetRegulatorState(RegulatorState), } impl Pli { pub fn new( serial_port: &str, friendly_name: &str, baud_rate: u32, timeout: u64, ) -> Result<Self, tokio_serial::Error> { let port = tokio_serial::new(serial_port, baud_rate) .timeout(std::time::Duration::from_millis(timeout)) .open_native_async()?; Ok(Self { friendly_name: friendly_name.to_owned(), port, }) } pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> { let new_state = self.read_state().await?; BATTERY_VOLTAGE .with_label_values(&[&self.friendly_name]) .set(new_state.battery_voltage); TARGET_VOLTAGE .with_label_values(&[&self.friendly_name]) .set(new_state.target_voltage); PL_DUTY_CYCLE .with_label_values(&[&self.friendly_name]) .set(new_state.duty_cycle); INPUT_CURRENT .with_label_values(&[&self.friendly_name]) .set(new_state.internal_charge_current); PL_LOAD_CURRENT .with_label_values(&[&self.friendly_name]) .set(new_state.internal_load_current); BATTERY_TEMP .with_label_values(&[&self.friendly_name]) .set(new_state.battery_temp); set_regulator_gauges(new_state.regulator_state, &self.friendly_name); Ok(crate::controller::CommonData { battery_voltage: new_state.battery_voltage, target_voltage: new_state.target_voltage, battery_temp: new_state.battery_temp, }) } #[expect(dead_code, reason = "writing settings is not yet implemented")] pub async fn process_request(&mut self, message: PliRequest) -> eyre::Result<()> { match message { PliRequest::ReadRam(address) => { let data = self.read_ram_with_retires(address).await?; log::warn!("Read RAM at {address}: data {data}"); } PliRequest::ReadEeprom(address) => { let data = self.read_eeprom(address).await?; log::warn!("Read EEPROM at {address}: data {data}"); } PliRequest::SyncTime => { let now = chrono::Local::now(); let timestamp = (((now.hour() * 10) + (now.minute() / 6)) & 0xFF) as u8; let min = (now.minute() % 6) as u8; let sec = (now.second() / 2).min(29) as u8; self.write_ram(PlRamAddress::Hour, timestamp).await?; self.write_ram(PlRamAddress::Min, min).await?; self.write_ram(PlRamAddress::Sec, sec).await?; log::warn!( "Set time: {now} corresponds to {timestamp} + minutes {min} + seconds {sec}" ); } PliRequest::SetRegulatorState(state) => { log::warn!("Setting regulator state to {state:?}"); self.write_ram(PlRamAddress::Rstate, state.into()).await?; } } Ok(()) } async fn read_state(&mut self) -> eyre::Result<PlState> { // let int_charge_acc_low = self.read_ram(PlRamAddress::Ciacc1).await?; // let int_charge_acc = self.read_ram(PlRamAddress::Ciacc2).await?; // let int_charge_acc_high = self.read_ram(PlRamAddress::Ciacc3).await?; // let mut internal_charge_ah_accumulator = u16::from(int_charge_acc_high) << 9; // internal_charge_ah_accumulator |= u16::from(int_charge_acc) << 1; // internal_charge_ah_accumulator |= u16::from(int_charge_acc_low & 0b1); // let int_charge_low = self.read_ram(PlRamAddress::Ciahl).await?; // let int_charge_high = self.read_ram(PlRamAddress::Ciahh).await?; // let internal_charge_ah = u16::from_le_bytes([int_charge_low, int_charge_high]); // let int_load_acc_low = self.read_ram(PlRamAddress::Liacc1).await?; // let int_load_acc = self.read_ram(PlRamAddress::Liacc2).await?; // let int_load_acc_high = self.read_ram(PlRamAddress::Liacc3).await?; // let mut internal_load_ah_accumulator = u16::from(int_load_acc_high) << 9; // internal_load_ah_accumulator |= u16::from(int_load_acc) << 1; // internal_load_ah_accumulator |= u16::from(int_load_acc_low & 0b1); // let int_load_low = self.read_ram(PlRamAddress::Liahl).await?; // let int_load_high = self.read_ram(PlRamAddress::Liahh).await?; // let internal_load_ah = u16::from_le_bytes([int_load_low, int_load_high]); // let ext_charge_acc_low = self.read_ram(PlRamAddress::Ceacc1).await?; // let ext_charge_acc = self.read_ram(PlRamAddress::Ceacc2).await?; // let ext_charge_acc_high = self.read_ram(PlRamAddress::Ceacc3).await?; // let mut external_charge_ah_accumulator = u16::from(ext_charge_acc_high) << 9; // external_charge_ah_accumulator |= u16::from(ext_charge_acc) << 1; // external_charge_ah_accumulator |= u16::from(ext_charge_acc_low & 0b1); // let ext_charge_low = self.read_ram(PlRamAddress::Ceahl).await?; // let ext_charge_high = self.read_ram(PlRamAddress::Ceahh).await?; // let external_charge_ah = u16::from_le_bytes([ext_charge_low, ext_charge_high]); // let ext_load_acc_low = self.read_ram(PlRamAddress::Leacc1).await?; // let ext_load_acc = self.read_ram(PlRamAddress::Leacc2).await?; // let ext_load_acc_high = self.read_ram(PlRamAddress::Leacc3).await?; // let mut external_load_ah_accumulator = u16::from(ext_load_acc_high) << 9; // external_load_ah_accumulator |= u16::from(ext_load_acc) << 1; // external_load_ah_accumulator |= u16::from(ext_load_acc_low & 0b1); // let ext_load_low = self.read_ram(PlRamAddress::Leahl).await?; // let ext_load_high = self.read_ram(PlRamAddress::Leahh).await?; // let external_load_ah = u16::from_le_bytes([ext_load_low, ext_load_high]); Ok(PlState { battery_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Batv).await?) * (4. / 10.), target_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Vreg).await?) * (4. / 10.), duty_cycle: f64::from(self.read_ram_with_retires(PlRamAddress::Dutycyc).await?) / 255., internal_charge_current: f64::from( self.read_ram_with_retires(PlRamAddress::Cint).await?, ) * (4. / 10.), internal_load_current: f64::from(self.read_ram_with_retires(PlRamAddress::Lint).await?) * (2. / 10.), battery_temp: f64::from(self.read_ram_with_retires(PlRamAddress::Battemp).await?), regulator_state: self .read_ram_with_retires(PlRamAddress::Rstate) .await? .into(), // internal_charge_ah_accumulator, // external_charge_ah_accumulator, // internal_load_ah_accumulator, // external_load_ah_accumulator, // internal_charge_ah, // external_charge_ah, // internal_load_ah, // external_load_ah, }) } async fn send_command(&mut self, req: [u8; 4]) -> eyre::Result<()> { self.flush()?; self.port.write_all(&req).await?; Ok(()) } fn flush(&mut self) -> eyre::Result<()> { self.port.clear(tokio_serial::ClearBuffer::All)?; Ok(()) } async fn receive<const LENGTH: usize>(&mut self) -> eyre::Result<[u8; LENGTH]> { let mut buf = [0; LENGTH]; self.port.read_exact(&mut buf).await?; Ok(buf) } async fn read_ram_with_retires<T>(&mut self, address: T) -> eyre::Result<u8> where T: Into<u8>, { const READ_TRIES: usize = 3; let address: u8 = address.into(); let mut last_err = None; for _ in 0..READ_TRIES { match self.read_ram_single(address).await { Ok(v) => return Ok(v), Err(e) => last_err = Some(e), } } Err(last_err.unwrap_or(eyre::eyre!( "no error was stored in read_ram_with_retries: this should be unreachable??" ))) } async fn read_ram_single<T>(&mut self, address: T) -> eyre::Result<u8> where T: Into<u8>, { self.send_command(command(20, address.into(), 0)).await?; let buf = self.receive::<1>().await?; if buf[0] == 200 { Ok(self.receive::<1>().await?[0]) } else { Err(eyre::eyre!("read error: result is {}", buf[0])) } } async fn write_ram<T>(&mut self, address: T, data: u8) -> eyre::Result<()> where T: Into<u8>, { self.send_command(command(152, address.into(), data)) .await?; Ok(()) } async fn read_eeprom<T>(&mut self, address: T) -> eyre::Result<u8> where T: Into<u8>, { self.send_command(command(72, address.into(), 0)).await?; let buf = self.receive::<1>().await?; if buf[0] == 200 { Ok(self.receive::<1>().await?[0]) } else { Err(eyre::eyre!("read error: result is {}", buf[0])) } } } #[allow(dead_code)] enum PlRamAddress { Dutycyc, Sec, Min, Hour, Batv, Battemp, Rstate, Vreg, Cint, Lint, Ciacc1, Ciacc2, Ciacc3, Ciahl, Ciahh, Ceacc1, Ceacc2, Ceacc3, Ceahl, Ceahh, Liacc1, Liacc2, Liacc3, Liahl, Liahh, Leacc1, Leacc2, Leacc3, Leahl, Leahh, } impl From<PlRamAddress> for u8 { fn from(value: PlRamAddress) -> Self { match value { PlRamAddress::Dutycyc => 39, PlRamAddress::Sec => 46, PlRamAddress::Min => 47, PlRamAddress::Hour => 48, PlRamAddress::Batv => 50, PlRamAddress::Battemp => 52, PlRamAddress::Rstate => 101, PlRamAddress::Vreg => 105, PlRamAddress::Cint => 213, PlRamAddress::Lint => 217, PlRamAddress::Ciacc1 => 0xB9, PlRamAddress::Ciacc2 => 0xBA, PlRamAddress::Ciacc3 => 0xBB, PlRamAddress::Ciahl => 0xBC, PlRamAddress::Ciahh => 0xBD, PlRamAddress::Ceacc1 => 0xBE, PlRamAddress::Ceacc2 => 0xBF, PlRamAddress::Ceacc3 => 0xC0, PlRamAddress::Ceahl => 0xC1, PlRamAddress::Ceahh => 0xC2, PlRamAddress::Liacc1 => 0xC3, PlRamAddress::Liacc2 => 0xC4, PlRamAddress::Liacc3 => 0xC5, PlRamAddress::Liahl => 0xC6, PlRamAddress::Liahh => 0xC7, PlRamAddress::Leacc1 => 0xC8, PlRamAddress::Leacc2 => 0xC9, PlRamAddress::Leacc3 => 0xCA, PlRamAddress::Leahl => 0xCB, PlRamAddress::Leahh => 0xCC, } } } fn command(operation: u8, address: u8, data: u8) -> [u8; 4] { [operation, address, data, !operation] }