ccs: follow primary

so far only printing so that we dont cook it
This commit is contained in:
Alex Janka 2024-12-30 13:47:01 +11:00
parent 59994c75c1
commit 11503615de
4 changed files with 182 additions and 14 deletions

View file

@ -14,6 +14,8 @@ pub struct ChargeControllerConfig {
pub baud_rate: u32, pub baud_rate: u32,
pub watch_interval_seconds: u64, pub watch_interval_seconds: u64,
pub variant: ChargeControllerVariant, pub variant: ChargeControllerVariant,
#[serde(default)]
pub follow_primary: bool,
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]

View file

@ -3,6 +3,8 @@ pub struct Controller {
interval: std::time::Duration, interval: std::time::Duration,
inner: ControllerInner, inner: ControllerInner,
data: std::sync::Arc<tokio::sync::RwLock<CommonData>>, data: std::sync::Arc<tokio::sync::RwLock<CommonData>>,
voltage_rx: Option<tokio::sync::mpsc::UnboundedReceiver<VoltageCommand>>,
voltage_tx: Option<MultiTx>,
} }
#[derive(Default, serde::Serialize, Clone)] #[derive(Default, serde::Serialize, Clone)]
@ -12,8 +14,18 @@ pub struct CommonData {
pub battery_temp: f64, pub battery_temp: f64,
} }
#[derive(Clone, Copy, Debug)]
pub enum VoltageCommand {
Set(f64),
}
impl Controller { impl Controller {
pub fn new(config: crate::config::ChargeControllerConfig) -> eyre::Result<Self> { pub fn new(
config: crate::config::ChargeControllerConfig,
) -> eyre::Result<(
Self,
Option<tokio::sync::mpsc::UnboundedSender<VoltageCommand>>,
)> {
let inner = match config.variant { let inner = match config.variant {
crate::config::ChargeControllerVariant::Tristar => ControllerInner::Tristar( crate::config::ChargeControllerVariant::Tristar => ControllerInner::Tristar(
crate::tristar::Tristar::new(&config.serial_port, &config.name, config.baud_rate)?, crate::tristar::Tristar::new(&config.serial_port, &config.name, config.baud_rate)?,
@ -32,12 +44,24 @@ impl Controller {
let data = std::sync::Arc::new(tokio::sync::RwLock::new(data)); let data = std::sync::Arc::new(tokio::sync::RwLock::new(data));
Ok(Self { let (voltage_tx, voltage_rx) = if config.follow_primary {
name: config.name, let (a, b) = tokio::sync::mpsc::unbounded_channel();
interval: std::time::Duration::from_secs(config.watch_interval_seconds), (Some(a), Some(b))
inner, } else {
data, (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<tokio::sync::RwLock<CommonData>> { pub fn get_data_ptr(&self) -> std::sync::Arc<tokio::sync::RwLock<CommonData>> {
@ -46,6 +70,18 @@ impl Controller {
pub async fn refresh(&mut self) -> eyre::Result<()> { pub async fn refresh(&mut self) -> eyre::Result<()> {
let data = self.inner.refresh().await?; let data = self.inner.refresh().await?;
if let Some(tx) = self.voltage_tx.as_mut() {
if *crate::IS_PRINTING_TARGETS {
log::error!(
"tristar {}: primary: sending target voltage {}",
self.name,
data.target_voltage
);
}
tx.send_to_all(VoltageCommand::Set(data.target_voltage));
}
*self.data.write().await = data; *self.data.write().await = data;
Ok(()) Ok(())
@ -58,6 +94,44 @@ impl Controller {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.name &self.name
} }
pub fn set_tx_to_secondary(
&mut self,
tx: Vec<tokio::sync::mpsc::UnboundedSender<VoltageCommand>>,
) {
if self.voltage_rx.is_some() {
panic!(
"trying to set {} as primary when it is also a secondary!",
self.name
);
}
self.voltage_tx = Some(MultiTx(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
}
}
}
}
struct MultiTx(Vec<tokio::sync::mpsc::UnboundedSender<VoltageCommand>>);
impl MultiTx {
fn send_to_all(&mut self, command: VoltageCommand) {
for sender in self.0.iter_mut() {
if let Err(e) = sender.send(command) {
log::error!("failed to send command {command:?}: {e:?}");
}
}
}
} }
pub enum ControllerInner { pub enum ControllerInner {
@ -78,4 +152,11 @@ impl ControllerInner {
} }
} }
} }
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,
}
}
} }

View file

@ -65,28 +65,57 @@ async fn run() -> eyre::Result<()> {
} }
} }
static IS_PRINTING_TARGETS: std::sync::LazyLock<bool> =
std::sync::LazyLock::new(|| std::env::var("SECONDARIES_PRINT_TARGET").is_ok());
async fn watch(args: Args) -> eyre::Result<()> { async fn watch(args: Args) -> eyre::Result<()> {
let config: config::Config = serde_json::from_reader(std::fs::File::open(args.config)?)?; let config: config::Config = serde_json::from_reader(std::fs::File::open(args.config)?)?;
if config
.charge_controllers
.iter()
.any(|cc| cc.follow_primary && cc.name == config.primary_charge_controller)
{
return Err(eyre::eyre!(
"primary charge controller is set to follow primary!"
));
}
let mut controllers = futures::stream::FuturesUnordered::new(); // let mut controllers = futures::stream::FuturesUnordered::new();
let mut controllers = Vec::new();
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
let mut follow_voltage_tx = Vec::new();
for config in config.charge_controllers { for config in config.charge_controllers {
let n = config.name.clone(); let n = config.name.clone();
match controller::Controller::new(config) { match controller::Controller::new(config) {
Ok(v) => { Ok((v, voltage_tx)) => {
map.insert(n, v.get_data_ptr()); map.insert(n, v.get_data_ptr());
controllers.push(run_loop(v)); controllers.push(v);
if let Some(voltage_tx) = voltage_tx {
follow_voltage_tx.push(voltage_tx);
}
} }
Err(e) => log::error!("couldn't connect to {}: {e:?}", n), Err(e) => log::error!("couldn't connect to {}: {e:?}", n),
} }
} }
if let Some(primary) = controllers
.iter_mut()
.find(|c| c.name() == config.primary_charge_controller)
{
primary.set_tx_to_secondary(follow_voltage_tx);
}
let mut controller_tasks = futures::stream::FuturesUnordered::new();
for controller in controllers {
controller_tasks.push(run_loop(controller));
}
let server = web::rocket(web::ServerState::new(config.primary_charge_controller, map)); let server = web::rocket(web::ServerState::new(config.primary_charge_controller, map));
let server_task = tokio::task::spawn(server.launch()); let server_task = tokio::task::spawn(server.launch());
tokio::select! { tokio::select! {
v = controllers.next() => { v = controller_tasks.next() => {
match v { match v {
Some(Err(e)) => { Some(Err(e)) => {
log::error!("{e:?}"); log::error!("{e:?}");
@ -110,10 +139,28 @@ async fn watch(args: Args) -> eyre::Result<()> {
async fn run_loop(mut controller: controller::Controller) -> eyre::Result<()> { async fn run_loop(mut controller: controller::Controller) -> eyre::Result<()> {
let mut timeout = tokio::time::interval(controller.timeout_interval()); let mut timeout = tokio::time::interval(controller.timeout_interval());
loop { loop {
timeout.tick().await; if let Some(rx) = controller.get_rx() {
if let Err(e) = controller.refresh().await { tokio::select! {
log::warn!("error reading controller {}: {e:?}", controller.name()); _ = timeout.tick() => {
do_refresh(&mut controller).await;
}
Some(command) = rx.recv() => {
if let Err(e) = controller.process_command(command).await {
log::error!("controller {} failed to process command: {e}", controller.name());
}
}
}
} else {
timeout.tick().await;
do_refresh(&mut controller).await;
} }
} }
} }
async fn do_refresh(controller: &mut controller::Controller) {
if let Err(e) = controller.refresh().await {
log::warn!("error reading controller {}: {e:?}", controller.name());
}
}

View file

@ -40,6 +40,10 @@ impl Scaling {
fn get_power(&self, data: u16) -> f64 { fn get_power(&self, data: u16) -> f64 {
f64::from(data) * self.v_scale * self.i_scale * f64::powf(2., -17.) f64::from(data) * self.v_scale * self.i_scale * f64::powf(2., -17.)
} }
fn from_voltage(&self, voltage: f64) -> u16 {
(voltage / (self.v_scale * f64::powf(2., -15.))) as u16
}
} }
pub struct Tristar { pub struct Tristar {
@ -47,10 +51,12 @@ pub struct Tristar {
modbus: tokio_modbus::client::Context, modbus: tokio_modbus::client::Context,
charge_state_gauges: ChargeStateGauges, charge_state_gauges: ChargeStateGauges,
consecutive_errors: usize, consecutive_errors: usize,
scaling: Option<Scaling>,
} }
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct TristarState { pub struct TristarState {
scaling: Option<Scaling>,
battery_voltage: f64, battery_voltage: f64,
target_voltage: f64, target_voltage: f64,
input_current: f64, input_current: f64,
@ -79,6 +85,7 @@ impl TristarState {
fn from_ram(ram: &[u16]) -> Self { fn from_ram(ram: &[u16]) -> Self {
let scaling = Scaling::from(ram); let scaling = Scaling::from(ram);
Self { Self {
scaling: Some(scaling),
battery_voltage: scaling.get_voltage(ram[TristarRamAddress::AdcVbFMed]), battery_voltage: scaling.get_voltage(ram[TristarRamAddress::AdcVbFMed]),
target_voltage: scaling.get_voltage(ram[TristarRamAddress::VbRef]), target_voltage: scaling.get_voltage(ram[TristarRamAddress::VbRef]),
input_current: scaling.get_current(ram[TristarRamAddress::AdcIaFShadow]), input_current: scaling.get_current(ram[TristarRamAddress::AdcIaFShadow]),
@ -258,11 +265,14 @@ impl Tristar {
modbus, modbus,
charge_state_gauges, charge_state_gauges,
consecutive_errors: 0, consecutive_errors: 0,
scaling: None,
}) })
} }
pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> { pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> {
let new_state = self.get_data().await?; let new_state = self.get_data().await?;
self.scaling = new_state.scaling;
self.consecutive_errors = 0; self.consecutive_errors = 0;
BATTERY_VOLTAGE BATTERY_VOLTAGE
.with_label_values(&[&self.friendly_name]) .with_label_values(&[&self.friendly_name])
@ -316,11 +326,39 @@ impl Tristar {
}) })
} }
pub async fn set_target_voltage(&mut self, target_voltage: f64) -> eyre::Result<()> {
let scaled_voltage: u16 = self.scale_voltage(target_voltage)?;
// self.modbus
// .write_single_register(TristarRamAddress::VbRefSlave as u16, scaled_voltage)
// .await??;
if *crate::IS_PRINTING_TARGETS {
log::error!(
"tristar {} being set to voltage {target_voltage} (scaled: {scaled_voltage:#X?})",
self.friendly_name
);
}
Ok(())
}
fn scale_voltage(&self, voltage: f64) -> eyre::Result<u16> {
let Some(scaling) = &self.scaling else {
return Err(eyre::eyre!("no scaling data present"));
};
Ok(scaling.from_voltage(voltage))
}
async fn get_data(&mut self) -> eyre::Result<TristarState> { async fn get_data(&mut self) -> eyre::Result<TristarState> {
let data = self let data = self
.modbus .modbus
.read_holding_registers(0x0000, RAM_DATA_SIZE + 1) .read_holding_registers(0x0000, RAM_DATA_SIZE + 1)
.await??; .await??;
if *crate::IS_PRINTING_TARGETS {
log::error!(
"tristar {}: target voltage raw {:#X?}",
self.friendly_name,
data[TristarRamAddress::VbRef as usize]
)
}
Ok(TristarState::from_ram(&data)) Ok(TristarState::from_ram(&data))
} }
} }