use crate::storage::ControllerData; mod pl; mod tristar; pub struct Controller { name: String, interval: std::time::Duration, inner: ControllerInner, data: std::sync::Arc, voltage_rx: Option>, voltage_tx: Option, } #[derive(Default, serde::Serialize, Clone)] pub struct CommonData { pub battery_voltage: f64, pub target_voltage: f64, pub battery_temp: f64, } #[derive(serde::Serialize, Clone)] #[serde(tag = "model")] pub enum ControllerState { Pl(pl::PlState), Tristar(tristar::TristarState), } impl ControllerState { pub fn common(&self) -> CommonData { match self { Self::Pl(pl_state) => crate::controller::CommonData { battery_voltage: pl_state.battery_voltage, target_voltage: pl_state.target_voltage, battery_temp: pl_state.battery_temp, }, Self::Tristar(tristar_state) => crate::controller::CommonData { battery_voltage: tristar_state.battery_voltage, target_voltage: tristar_state.target_voltage, battery_temp: f64::from(tristar_state.battery_temp), }, } } } #[derive(serde::Serialize, Clone)] #[serde(tag = "model")] pub enum ControllerSettings { Tristar(tristar::TristarSettings), } #[derive(Clone, Copy, Debug)] pub enum VoltageCommand { Set(f64), } impl Controller { pub async fn new( config: crate::config::ChargeControllerConfig, ) -> eyre::Result<( Self, Option>, )> { let inner = match config.variant { crate::config::ChargeControllerVariant::Tristar => ControllerInner::Tristar( tristar::Tristar::new(&config.name, &config.transport).await?, ), crate::config::ChargeControllerVariant::Pl { timeout_milliseconds, } => match &config.transport { crate::config::Transport::Serial { port, baud_rate } => ControllerInner::Pl( pl::Pli::new(port, &config.name, *baud_rate, timeout_milliseconds)?, ), crate::config::Transport::Tcp { ip: _, port: _ } => { return Err(eyre::eyre!("pl doesn't support tcp")) } }, }; let data = std::sync::Arc::new(ControllerData::new()); let (voltage_tx, voltage_rx) = if config.follow_primary { let (a, b) = tokio::sync::mpsc::unbounded_channel(); (Some(a), Some(b)) } else { (None, None) }; Ok(( Self { name: config.name, interval: std::time::Duration::from_secs(config.watch_interval_seconds), inner, data, voltage_rx, voltage_tx: None, }, voltage_tx, )) } pub fn get_data_ptr(&self) -> std::sync::Arc { self.data.clone() } pub async fn refresh(&mut self) -> eyre::Result<()> { let (data, settings) = self.inner.refresh().await?; if let Some(tx) = self.voltage_tx.as_mut() { if crate::config::access_config() .await .enable_secondary_control { let target = data.common().target_voltage; log::debug!( "tristar {}: primary: sending target voltage {}", self.name, target ); tx.send_to_all(VoltageCommand::Set(target)); } } *self.data.write_state().await = Some(data); if let Some(settings) = settings { *self.data.write_settings().await = Some(settings); } Ok(()) } pub const fn timeout_interval(&self) -> std::time::Duration { self.interval } pub fn name(&self) -> &str { &self.name } pub fn set_tx_to_secondary(&mut self, tx: MultiTx) { assert!( self.voltage_rx.is_none(), "trying to set {} as primary when it is also a secondary!", self.name ); self.voltage_tx = Some(tx); } pub fn get_rx(&mut self) -> Option<&mut tokio::sync::mpsc::UnboundedReceiver> { self.voltage_rx.as_mut() } pub async fn process_command(&mut self, command: VoltageCommand) -> eyre::Result<()> { match command { VoltageCommand::Set(target_voltage) => { self.inner.set_target_voltage(target_voltage).await } } } } #[derive(Clone)] pub struct MultiTx(pub Vec>); impl MultiTx { pub fn send_to_all(&self, command: VoltageCommand) { for sender in &self.0 { if let Err(e) = sender.send(command) { log::error!("failed to send command {command:?}: {e:?}"); } } } } #[expect(clippy::large_enum_variant)] pub enum ControllerInner { Pl(pl::Pli), Tristar(tristar::Tristar), } impl ControllerInner { pub async fn refresh(&mut self) -> eyre::Result<(ControllerState, Option)> { match self { ControllerInner::Pl(pli) => { let pl_data = pli.refresh().await?; Ok((ControllerState::Pl(pl_data), None)) } ControllerInner::Tristar(tristar) => { let settings = if tristar.needs_new_settings() { match tristar.read_settings().await { Ok(v) => Some(ControllerSettings::Tristar(v)), Err(e) => { log::error!( "couldn't read config from tristar {}: {e:?}", tristar.name() ); None } } } else { None }; let tristar_data = tristar.refresh().await?; Ok((ControllerState::Tristar(tristar_data), settings)) } } } pub async fn set_target_voltage(&mut self, target_voltage: f64) -> eyre::Result<()> { match self { ControllerInner::Pl(_) => todo!(), ControllerInner::Tristar(tristar) => tristar.set_target_voltage(target_voltage).await, } } }