ccs: tristar: proper timeout on serial

tokio_serial::SerialStream ignored timeouts??? and now this will
actually prevent charge controllers being added if they don't respond
via modbus
This commit is contained in:
Alex Janka 2025-01-09 12:22:56 +11:00
parent 47e711f111
commit 8bc7c8e17c
3 changed files with 49 additions and 18 deletions

View file

@ -20,7 +20,7 @@ pub enum VoltageCommand {
}
impl Controller {
pub fn new(
pub async fn new(
config: crate::config::ChargeControllerConfig,
) -> eyre::Result<(
Self,
@ -28,7 +28,8 @@ 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)?,
crate::tristar::Tristar::new(&config.serial_port, &config.name, config.baud_rate)
.await?,
),
crate::config::ChargeControllerVariant::Pl {
timeout_milliseconds,

View file

@ -87,7 +87,7 @@ async fn watch(args: Args) -> eyre::Result<()> {
for config in &config.charge_controllers {
let n = config.name.clone();
match controller::Controller::new(config.clone()) {
match controller::Controller::new(config.clone()).await {
Ok((v, voltage_tx)) => {
map.insert(n, v.get_data_ptr());
controllers.push(v);

View file

@ -49,15 +49,40 @@ impl Scaling {
pub struct Tristar {
friendly_name: String,
modbus: tokio_modbus::client::Context,
modbus: ModbusTimeout,
charge_state_gauges: ChargeStateGauges,
consecutive_errors: usize,
scaling: Option<Scaling>,
scaling: Scaling,
}
#[derive(Default, Debug, Clone, Copy)]
struct ModbusTimeout(tokio_modbus::client::Context);
const MODBUS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
impl ModbusTimeout {
pub async fn write_single_register(
&mut self,
addr: tokio_modbus::Address,
word: u16,
) -> eyre::Result<()> {
tokio::time::timeout(MODBUS_TIMEOUT, self.0.write_single_register(addr, word)).await???;
Ok(())
}
pub async fn read_holding_registers(
&mut self,
addr: tokio_modbus::Address,
cnt: tokio_modbus::Quantity,
) -> eyre::Result<Vec<u16>> {
let v = tokio::time::timeout(MODBUS_TIMEOUT, self.0.read_holding_registers(addr, cnt))
.await???;
Ok(v)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TristarState {
scaling: Option<Scaling>,
scaling: Scaling,
battery_voltage: f64,
target_voltage: f64,
input_current: f64,
@ -86,7 +111,7 @@ impl TristarState {
fn from_ram(ram: &[u16]) -> Self {
let scaling = Scaling::from(ram);
Self {
scaling: Some(scaling),
scaling,
battery_voltage: scaling.get_voltage(ram[TristarRamAddress::AdcVbFMed]),
target_voltage: scaling.get_voltage(ram[TristarRamAddress::VbRef]),
input_current: scaling.get_current(ram[TristarRamAddress::AdcIaFShadow]),
@ -255,20 +280,28 @@ impl ChargeStateGauges {
}
impl Tristar {
pub fn new(serial_port: &str, friendly_name: &str, baud_rate: u32) -> eyre::Result<Self> {
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)),
)?;
let slave = tokio_modbus::Slave(DEVICE_ID);
let modbus = tokio_modbus::client::rtu::attach_slave(modbus_serial, slave);
let mut modbus = ModbusTimeout(modbus);
let scaling = {
let data = modbus.read_holding_registers(0x0000, 4).await?;
Scaling::from(&data)
};
let charge_state_gauges = ChargeStateGauges::new(friendly_name);
Ok(Self {
friendly_name: friendly_name.to_owned(),
modbus,
charge_state_gauges,
consecutive_errors: 0,
scaling: None,
scaling,
})
}
@ -330,10 +363,10 @@ impl Tristar {
}
pub async fn set_target_voltage(&mut self, target_voltage: f64) -> eyre::Result<()> {
let scaled_voltage: u16 = self.scale_voltage(target_voltage)?;
let scaled_voltage: u16 = self.scale_voltage(target_voltage);
self.modbus
.write_single_register(TristarRamAddress::VbRefSlave as u16, scaled_voltage)
.await??;
.await?;
log::debug!(
"tristar {} being set to voltage {target_voltage} (scaled: {scaled_voltage:#X?})",
@ -343,18 +376,15 @@ impl Tristar {
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.inverse_voltage(voltage))
fn scale_voltage(&self, voltage: f64) -> u16 {
self.scaling.inverse_voltage(voltage)
}
async fn get_data(&mut self) -> eyre::Result<TristarState> {
let data = self
.modbus
.read_holding_registers(0x0000, RAM_DATA_SIZE + 1)
.await??;
.await?;
Ok(TristarState::from_ram(&data))
}
}