From 23fe7acbb0d011fc66c098e18efd047d43cbf441 Mon Sep 17 00:00:00 2001 From: Alex Janka <alex@alexjanka.com> Date: Wed, 8 Jan 2025 22:02:40 +1100 Subject: [PATCH] ccs: modbus tcp --- charge-controller-supervisor/src/config.rs | 33 ++++++--- .../src/config/outdated.rs | 72 +++++++++++++++++++ .../src/controller.rs | 17 ++--- charge-controller-supervisor/src/main.rs | 27 ++++++- charge-controller-supervisor/src/tristar.rs | 22 ++++-- 5 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 charge-controller-supervisor/src/config/outdated.rs diff --git a/charge-controller-supervisor/src/config.rs b/charge-controller-supervisor/src/config.rs index 1792fe9..51c48b6 100644 --- a/charge-controller-supervisor/src/config.rs +++ b/charge-controller-supervisor/src/config.rs @@ -121,9 +121,13 @@ pub async fn write_to_config<'a>() -> ConfigHandle<'a> { #[serde(tag = "version")] pub enum ConfigStorage { #[serde(rename = "1")] - V1(Config), + V1(outdated::ConfigV1), + #[serde(rename = "2")] + V2(Config), } +mod outdated; + impl Default for ConfigStorage { fn default() -> Self { Self::from_latest(Default::default()) @@ -131,8 +135,15 @@ impl Default for ConfigStorage { } impl ConfigStorage { - const fn from_latest(config: Config) -> Self { - Self::V1(config) + pub const fn from_latest(config: Config) -> Self { + Self::V2(config) + } + + pub fn into_latest(self) -> Config { + match self { + ConfigStorage::V1(v1) => v1.into(), + ConfigStorage::V2(config) => config, + } } fn load(path: impl AsRef<std::path::Path>) -> eyre::Result<Self> { @@ -149,12 +160,6 @@ impl ConfigStorage { fn save(&self) -> eyre::Result<()> { self.save_to(CONFIG_PATH.get().unwrap()) } - - fn into_latest(self) -> Config { - match self { - ConfigStorage::V1(config) => config, - } - } } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] @@ -181,12 +186,11 @@ impl Config { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct ChargeControllerConfig { pub name: String, - pub serial_port: String, - pub baud_rate: u32, pub watch_interval_seconds: u64, pub variant: ChargeControllerVariant, #[serde(default)] pub follow_primary: bool, + pub transport: Transport, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] @@ -194,3 +198,10 @@ pub enum ChargeControllerVariant { Tristar, Pl { timeout_milliseconds: u64 }, } + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Transport { + Serial { port: String, baud_rate: u32 }, + Tcp { ip: std::net::IpAddr, port: u16 }, +} diff --git a/charge-controller-supervisor/src/config/outdated.rs b/charge-controller-supervisor/src/config/outdated.rs new file mode 100644 index 0000000..320cf72 --- /dev/null +++ b/charge-controller-supervisor/src/config/outdated.rs @@ -0,0 +1,72 @@ +pub use v1::ConfigV1; + +mod v1 { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] + #[serde(default)] + pub struct ConfigV1 { + primary_charge_controller: String, + enable_secondary_control: bool, + charge_controllers: Vec<ChargeControllerConfigV1>, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + struct ChargeControllerConfigV1 { + name: String, + serial_port: String, + baud_rate: u32, + watch_interval_seconds: u64, + variant: ChargeControllerVariantV1, + #[serde(default)] + follow_primary: bool, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] + enum ChargeControllerVariantV1 { + Tristar, + Pl { timeout_milliseconds: u64 }, + } + + impl From<ChargeControllerConfigV1> for crate::config::ChargeControllerConfig { + fn from(value: ChargeControllerConfigV1) -> Self { + Self { + name: value.name, + transport: crate::config::Transport::Serial { + port: value.serial_port, + baud_rate: value.baud_rate, + }, + watch_interval_seconds: value.watch_interval_seconds, + variant: value.variant.into(), + follow_primary: value.follow_primary, + } + } + } + + impl From<ChargeControllerVariantV1> for crate::config::ChargeControllerVariant { + fn from(value: ChargeControllerVariantV1) -> Self { + match value { + ChargeControllerVariantV1::Tristar => Self::Tristar, + ChargeControllerVariantV1::Pl { + timeout_milliseconds, + } => Self::Pl { + timeout_milliseconds, + }, + } + } + } + + impl From<ConfigV1> for crate::config::Config { + fn from(value: ConfigV1) -> Self { + Self { + primary_charge_controller: value.primary_charge_controller, + enable_secondary_control: value.enable_secondary_control, + charge_controllers: value + .charge_controllers + .into_iter() + .map(Into::into) + .collect(), + } + } + } +} diff --git a/charge-controller-supervisor/src/controller.rs b/charge-controller-supervisor/src/controller.rs index c4b5ece..c89da12 100644 --- a/charge-controller-supervisor/src/controller.rs +++ b/charge-controller-supervisor/src/controller.rs @@ -28,17 +28,18 @@ impl Controller { )> { let inner = match config.variant { crate::config::ChargeControllerVariant::Tristar => ControllerInner::Tristar( - crate::tristar::Tristar::new(&config.serial_port, &config.name, config.baud_rate) - .await?, + crate::tristar::Tristar::new(&config.name, &config.transport).await?, ), crate::config::ChargeControllerVariant::Pl { timeout_milliseconds, - } => ControllerInner::Pl(crate::pl::Pli::new( - &config.serial_port, - &config.name, - config.baud_rate, - timeout_milliseconds, - )?), + } => match &config.transport { + crate::config::Transport::Serial { port, baud_rate } => ControllerInner::Pl( + crate::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 = CommonData::default(); diff --git a/charge-controller-supervisor/src/main.rs b/charge-controller-supervisor/src/main.rs index bf0b3cd..fc0c4db 100644 --- a/charge-controller-supervisor/src/main.rs +++ b/charge-controller-supervisor/src/main.rs @@ -59,7 +59,32 @@ async fn run() -> eyre::Result<()> { match args.command { Commands::Watch => watch(args).await, Commands::GenerateConfig => { - let config = config::ConfigStorage::default(); + let mut config = config::Config::default(); + config + .charge_controllers + .push(config::ChargeControllerConfig { + name: String::from("tcp"), + transport: config::Transport::Tcp { + ip: std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 102)), + port: 420, + }, + watch_interval_seconds: 0, + variant: config::ChargeControllerVariant::Tristar, + follow_primary: false, + }); + config + .charge_controllers + .push(config::ChargeControllerConfig { + name: String::from("serial"), + transport: config::Transport::Serial { + port: "/dev/someport".to_string(), + baud_rate: 69, + }, + watch_interval_seconds: 0, + variant: config::ChargeControllerVariant::Tristar, + follow_primary: false, + }); + let config = config::ConfigStorage::from_latest(config); let json = serde_json::to_string_pretty(&config)?; println!("{json}"); Ok(()) diff --git a/charge-controller-supervisor/src/tristar.rs b/charge-controller-supervisor/src/tristar.rs index 4ec9b1a..e0bce38 100644 --- a/charge-controller-supervisor/src/tristar.rs +++ b/charge-controller-supervisor/src/tristar.rs @@ -280,13 +280,23 @@ impl ChargeStateGauges { } impl Tristar { - pub async fn new(serial_port: &str, friendly_name: &str, baud_rate: u32) -> eyre::Result<Self> { - let modbus_serial = tokio_serial::SerialStream::open( - &tokio_serial::new(serial_port, baud_rate).timeout(std::time::Duration::from_secs(3)), - )?; - + pub async fn new( + friendly_name: &str, + transport: &crate::config::Transport, + ) -> eyre::Result<Self> { let slave = tokio_modbus::Slave(DEVICE_ID); - let modbus = tokio_modbus::client::rtu::attach_slave(modbus_serial, slave); + + let modbus = match transport { + crate::config::Transport::Serial { port, baud_rate } => { + let modbus_serial = + tokio_serial::SerialStream::open(&tokio_serial::new(port, *baud_rate))?; + tokio_modbus::client::rtu::attach_slave(modbus_serial, slave) + } + crate::config::Transport::Tcp { ip, port } => { + tokio_modbus::client::tcp::connect((*ip, *port).into()).await? + } + }; + let mut modbus = ModbusTimeout(modbus); let scaling = {