tesla-charge-controller/charge-controller-supervisor/src/controller.rs
2025-01-10 15:09:16 +11:00

219 lines
6.5 KiB
Rust

use crate::storage::ControllerData;
mod pl;
mod tristar;
pub struct Controller {
name: String,
interval: std::time::Duration,
inner: ControllerInner,
data: std::sync::Arc<ControllerData>,
voltage_rx: Option<tokio::sync::mpsc::UnboundedReceiver<VoltageCommand>>,
voltage_tx: Option<MultiTx>,
}
#[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<tokio::sync::mpsc::UnboundedSender<VoltageCommand>>,
)> {
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<ControllerData> {
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<VoltageCommand>> {
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<tokio::sync::mpsc::UnboundedSender<VoltageCommand>>);
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<ControllerSettings>)> {
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,
}
}
}