diff --git a/charge-controller-supervisor/src/controller/tristar/modbus_wrapper.rs b/charge-controller-supervisor/src/controller/tristar/modbus_wrapper.rs index 913f583..9357871 100644 --- a/charge-controller-supervisor/src/controller/tristar/modbus_wrapper.rs +++ b/charge-controller-supervisor/src/controller/tristar/modbus_wrapper.rs @@ -62,10 +62,13 @@ impl<T> TryInsert for Option<T> { #[derive(Default, Debug)] struct Counters { + gateway: usize, timeout: usize, protocol: usize, } +const MAX_GATEWAY_ERRORS: usize = 3; + const MAX_TIMEOUTS: usize = 3; const MAX_PROTOCOL_ERRORS: usize = 3; @@ -76,10 +79,14 @@ impl Counters { } const fn any_above_max(&self) -> bool { - self.timeout > MAX_TIMEOUTS || self.protocol > MAX_PROTOCOL_ERRORS + self.gateway > MAX_GATEWAY_ERRORS + || self.timeout > MAX_TIMEOUTS + || self.protocol > MAX_PROTOCOL_ERRORS } } +const NUM_TRIES: usize = 3; + impl ModbusTimeout { pub async fn new(transport_settings: crate::config::Transport) -> eyre::Result<Self> { let context = Some(connect(&transport_settings).await?); @@ -90,52 +97,65 @@ impl ModbusTimeout { }) } - async fn with_context<R, D>( + async fn with_context<R, D: Copy>( &mut self, f: ContextFn<R, D>, data: D, ) -> eyre::Result<Result<R, tokio_modbus::ExceptionCode>> { - if let Ok(context) = self - .context - .get_or_try_insert_with(async || connect(&self.transport_settings).await) - .await - { - let res = tokio::time::timeout(MODBUS_TIMEOUT, f(context, data)).await; - match res { - Ok(Ok(v)) => { - self.counters.reset(); - return Ok(v); + let mut last_err = None; + for _ in 0..NUM_TRIES { + if let Ok(context) = self + .context + .get_or_try_insert_with(async || connect(&self.transport_settings).await) + .await + { + let res = tokio::time::timeout(MODBUS_TIMEOUT, f(context, data)).await; + match res { + Ok(Ok(Err(e))) + if e == tokio_modbus::ExceptionCode::GatewayTargetDevice + || e == tokio_modbus::ExceptionCode::GatewayPathUnavailable => + { + log::warn!("gateway error: {e:?}"); + last_err = Some(e.into()); + self.counters.gateway += 1; + } + Ok(Ok(v)) => { + self.counters.reset(); + return Ok(v); + } + Ok(Err(tokio_modbus::Error::Protocol(e))) => { + // protocol error + log::warn!("protocol error: {e:?}"); + last_err = Some(e.into()); + self.counters.protocol += 1; + } + Ok(Err(tokio_modbus::Error::Transport(e))) => { + // transport error + log::warn!("reconnecting due to transport error: {e:?}"); + last_err = Some(e.into()); + self.context = None; + } + Err(_) => { + // timeout + last_err = Some(eyre::eyre!("timeout")); + self.counters.timeout += 1; + } } - - Ok(Err(tokio_modbus::Error::Protocol(e))) => { - // protocol error - log::warn!("protocol error: {e:?}"); - self.counters.protocol += 1; - } - Ok(Err(tokio_modbus::Error::Transport(e))) => { - // transport error - log::warn!("reconnecting due to transport error: {e:?}"); + if self.counters.any_above_max() { self.context = None; + log::warn!( + "reconnecting due to multiple errors without a successful operation: {:?}", + self.counters + ); + self.counters.reset(); } - Err(_) => { - // timeout - self.counters.timeout += 1; - } + } else { + // failed to reconnect + return Err(eyre::eyre!("failed to reconnect to controller")); } - if self.counters.any_above_max() { - self.context = None; - log::warn!( - "reconnecting due to multiple errors without a successful operation: {:?}", - self.counters - ); - self.counters.reset(); - } - } else { - // failed to reconnect - return Err(eyre::eyre!("failed to reconnect to controller")); } - Err(eyre::eyre!(":(")) + Err(last_err.unwrap_or_else(|| eyre::eyre!("unknown last error????"))) } pub async fn write_single_register(