From 6ffc7d4d87ba5ca9961cb32f5afa128ccc738ed5 Mon Sep 17 00:00:00 2001 From: Claas Date: Wed, 12 Nov 2025 00:12:21 +0100 Subject: [PATCH] Refactor client to not only control fans through it --- fan-controller/src/fan.rs | 276 +-------------------------- fan-controller/src/main.rs | 26 +-- fan-controller/src/modbus/client.rs | 281 ++++++++++++++++++++++++++++ fan-controller/src/modbus/mod.rs | 2 + fan-controller/src/task.rs | 6 +- 5 files changed, 309 insertions(+), 282 deletions(-) create mode 100644 fan-controller/src/modbus/client.rs diff --git a/fan-controller/src/fan.rs b/fan-controller/src/fan.rs index b9c5863..2706224 100644 --- a/fan-controller/src/fan.rs +++ b/fan-controller/src/fan.rs @@ -28,8 +28,6 @@ pub(crate) fn get_configuration() -> uart::Config { configuration } -const BLOCK_FOR: Duration = Duration::from_micros(5_000); - pub(crate) const MAX_SET_POINT: u16 = 64_000; /// Describes the desired speed of the fan from 0 to [`MAX_SET_POINT`] @@ -124,11 +122,11 @@ pub(super) mod holding_registers { pub(crate) const REFERENCE_SET_POINT: [u8; 2] = 0xd001_u16.to_be_bytes(); } -mod input_registers { - pub(super) const TEMPERATURE_SENSOR_1: [u8; 2] = 0xd02e_u16.to_be_bytes(); - pub(super) const HUMIDITY_SENSOR_1: [u8; 2] = 0xd02f_u16.to_be_bytes(); - pub(super) const TEMPERATURE_SENSOR_2: [u8; 2] = 0xd030_u16.to_be_bytes(); - pub(super) const HUMIDITY_SENSOR_2: [u8; 2] = 0xd031_u16.to_be_bytes(); +pub(crate) mod input_registers { + pub(crate) const TEMPERATURE_SENSOR_1: [u8; 2] = 0xd02e_u16.to_be_bytes(); + pub(crate) const HUMIDITY_SENSOR_1: [u8; 2] = 0xd02f_u16.to_be_bytes(); + pub(crate) const TEMPERATURE_SENSOR_2: [u8; 2] = 0xd030_u16.to_be_bytes(); + pub(crate) const HUMIDITY_SENSOR_2: [u8; 2] = 0xd031_u16.to_be_bytes(); } pub(crate) enum Fan { @@ -136,275 +134,17 @@ pub(crate) enum Fan { Two, } -/// Modbus messages are sent through UART to MAX845 to control fans. -/// The pin is used to enable the DE pin to switch between reading and writing -pub(crate) struct Client<'a, UART: uart::Instance, PIN: Pin> { - uart: BufferedUart<'a, UART>, - driver_enable: Output<'a, PIN>, -} - -pub(crate) enum Error { - Timeout(TimeoutError), - Uart(uart::Error), -} - -// Is there a way to implement the From trait with a macro like thiserror -impl From for Error { - fn from(error: TimeoutError) -> Self { - Self::Timeout(error) - } -} - -impl From for Error { - fn from(error: uart::Error) -> Self { - Self::Uart(error) - } -} - -struct FanResponse { +pub(crate) struct FanResponse { data: [u8; N], length: usize, } impl FanResponse { - fn new(data: [u8; N], length: usize) -> Self { + pub(crate) fn new(data: [u8; N], length: usize) -> Self { Self { data, length } } - fn as_slice(&self) -> &[u8] { + pub(crate) fn as_slice(&self) -> &[u8] { &self.data[..self.length] } } - -impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { - pub(crate) fn new( - uart: impl Peripheral

+ 'a, - tx: impl Peripheral

> + 'a, - rx: impl Peripheral

> + 'a, - irq: impl Binding>, - tx_dma: impl Peripheral

+ 'a, - rx_dma: impl Peripheral

+ 'a, - driver_enable: impl Peripheral

+ 'a, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - ) -> Self { - let uart = BufferedUart::new(uart, irq, tx, rx, tx_buffer, rx_buffer, get_configuration()); - let driver_enable = Output::new(driver_enable, Level::Low); - - Self { - uart, - driver_enable, - } - } - - async fn send_2( - &mut self, - message: impl modbus::ToBytes, - ) -> Result, Error> { - // Write then read - // Set pin setting DE (driver enable) to on (high) on the MAX845 to send data - self.driver_enable.set_high(); - - let bytes = message.to_bytes(); - info!("Sending message to fan: {:?}", bytes); - // As ref because &[u8; 8] is not the same as &[u8] - let result = with_timeout(configuration::FAN_TIMEOUT, self.uart.write_all(&bytes)).await?; - - info!("uart write result: {:?}", result); - - // Before closing we need to flush the buffer to ensure that all data is written - // This requires blocking or we get a WouldBlock error. I don't understand why (TODO) - let result = self.uart.blocking_flush(); - if let Err(error) = result { - error!("uart flush error"); - } - - // In addition to flushing we need to wait for some time before turning off data in on the - // MAX845 because we might be too fast and cut off the last byte or more. (This happened) - // I saw someone using 120 microseconds (https://youtu.be/i46jdhvRej4?t=886). - // This number is based on trial and error. Don't feel bad to change it if it doesn't work. - // Also timings in microseconds are not accurate. - // I assume this should be below the modbus message delay - // Timer::after(Duration::from_micros(1_000)).await; - // Using await timer breaks this too. Probably because it yields to the scheduler - block_for(BLOCK_FOR); - - // Close sending data to enable receiving data - self.driver_enable.set_low(); - - // Read - // Read response from fan. The response can vary in length - let mut response_buffer: [u8; RESPONSE] = [0; RESPONSE]; - info!("Waiting for response from fan"); - let bytes_read = with_timeout( - configuration::FAN_TIMEOUT, - //TODO test this does not wait for bytes to fill the buffer - // leading to a timeout because the response is only 7 bytes but the buffer is 8 and it waits for the last byte to arrive - self.uart.read(&mut response_buffer), - ) - .await??; - - info!("response from fan: {:?} {:?}", bytes_read, response_buffer); - let response = FanResponse::new(response_buffer, bytes_read); - - //TODO validate response from fan - // Read the correct number of bytes - Ok(response) - } - async fn send( - &mut self, - message: impl AsRef<[u8]>, - ) -> Result, Error> { - // Write then read - // Set pin setting DE (driver enable) to on (high) on the MAX845 to send data - self.driver_enable.set_high(); - - info!("Sending message to fan: {:?}", message.as_ref()); - // As ref because &[u8; 8] is not the same as &[u8] - let result = with_timeout( - configuration::FAN_TIMEOUT, - self.uart.write_all(message.as_ref()), - ) - .await?; - - info!("uart write result: {:?}", result); - - // Before closing we need to flush the buffer to ensure that all data is written - // This requires blocking or we get a WouldBlock error. I don't understand why (TODO) - let result = self.uart.blocking_flush(); - if let Err(error) = result { - error!("uart flush error"); - } - - // In addition to flushing we need to wait for some time before turning off data in on the - // MAX845 because we might be too fast and cut off the last byte or more. (This happened) - // I saw someone using 120 microseconds (https://youtu.be/i46jdhvRej4?t=886). - // This number is based on trial and error. Don't feel bad to change it if it doesn't work. - // Also timings in microseconds are not accurate. - // I assume this should be below the modbus message delay - // Timer::after(Duration::from_micros(1_000)).await; - // Using await timer breaks this too. Probably because it yields to the scheduler - block_for(BLOCK_FOR); - - // Close sending data to enable receiving data - self.driver_enable.set_low(); - - // Read - // Read response from fan. The response can vary in length - let mut response_buffer: [u8; N] = [0; N]; - info!("Waiting for response from fan"); - let bytes_read = with_timeout( - configuration::FAN_TIMEOUT, - //TODO test this does not wait for bytes to fill the buffer - // leading to a timeout because the response is only 7 bytes but the buffer is 8 and it waits for the last byte to arrive - self.uart.read(&mut response_buffer), - ) - .await??; - - info!("response from fan: {:?} {:?}", bytes_read, response_buffer); - let response = FanResponse::new(response_buffer, bytes_read); - - //TODO validate response from fan - // Read the correct number of bytes - Ok(response) - } - - /// The mutable reference to self here is important as there can only be one writer to the (mod)bus at a time - pub(crate) async fn set_set_point( - &mut self, - SetPoint(set_point): &SetPoint, - ) -> Result<(), Error> { - // Send update through UART to MAX845 to modbus fans - // Form message to fan 1 - let mut message: [u8; 8] = [ - // Device address fan 1 - address::FAN_1, - // Modbus function code - modbus::function_code::WRITE_SINGLE_REGISTER, - // Holding register address - holding_registers::REFERENCE_SET_POINT[0], - holding_registers::REFERENCE_SET_POINT[1], - // Value to set - (set_point >> 8) as u8, - *set_point as u8, - // CRC is set later - 0, - 0, - ]; - - let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); - - // They come out reversed (or is us using to_be_bytes reversed?) - message[6] = checksum[1]; - message[7] = checksum[0]; - info!("Sending message to fan 1: {:?}", message); - - let _ = self.send::<8>(&message).await?; - - /// Messsage delay between modbus messages in microseconds - const MESSAGE_DELAY: u64 = modbus::get_message_delay(BAUD_RATE); - info!("Message delay {}", MESSAGE_DELAY); - // We can yield the future here because the wait time between messages is a minimum and can be longer - Timer::after_micros(MESSAGE_DELAY).await; - - // Form message to fan 2 - // Update the fan address and therefore the CRC - // Keep speed as both fans should be running at the same speed - message[0] = address::FAN_2; - let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); - message[6] = checksum[1]; - message[7] = checksum[0]; - - info!("sending message to fan 2: {:?}", message); - let _ = self.send::<8>(&message).await?; - Ok(()) - } - - pub(crate) async fn get_temperature(&mut self, fan: Fan) -> Result { - let message = modbus::Message::new( - match fan { - Fan::One => address::FAN_1, - Fan::Two => address::FAN_2, - }, - ReadInputRegister::new(0xd02e, 1), - ); - - let test: FanResponse<7> = self.send_2(message).await?; - - let mut message: [u8; 8] = [ - // Device address - match fan { - Fan::One => address::FAN_1, - Fan::Two => address::FAN_2, - }, - // Modbus function code - modbus::function_code::READ_INPUT_REGISTER, - // Input register address - input_registers::TEMPERATURE_SENSOR_1[0], - input_registers::TEMPERATURE_SENSOR_1[1], - // Number of registers to read - 0, - 1, - // CRC is set later - 0, - 0, - ]; - - let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); - - // They come out reversed (or is us using to_be_bytes reversed?) - message[6] = checksum[1]; - message[7] = checksum[0]; - info!("Sending read temperature message {:?}", message); - - let response = self.send::<7>(&message).await?; - let response = response.as_slice(); - - //TODO read the correct number of bytes - let length = response[2]; - let temperature = u16::from_be_bytes([response[3], response[4]]); - info!("Temperature (divide by 10): {}", temperature); - - Ok(temperature) - } -} diff --git a/fan-controller/src/main.rs b/fan-controller/src/main.rs index 7fedaab..2bf52b6 100644 --- a/fan-controller/src/main.rs +++ b/fan-controller/src/main.rs @@ -36,7 +36,7 @@ use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; use self::mqtt::packet; -use crate::fan::{ParseSetPointError, SetPoint}; +use crate::fan::{Fan, ParseSetPointError, SetPoint}; use crate::mqtt::packet::ping_request::PingRequest; use crate::mqtt::packet::{connect, publish, subscribe}; use crate::task::{set_up_network_stack, MqttBrokerConfiguration, Publish}; @@ -235,11 +235,11 @@ async fn update_fans() { match fans.set_set_point(&setting).await { Ok(_) => {} - Err(fan::Error::Timeout(TimeoutError)) => { + Err(modbus::client::Error::Timeout(_)) => { error!("Timeout setting fan speed"); continue; } - Err(fan::Error::Uart(error)) => { + Err(modbus::client::Error::Uart(error)) => { error!("Uart error setting fan speed: {:?}", error); continue; } @@ -250,7 +250,7 @@ async fn update_fans() { } } -type Fans = Mutex>>; +type Fans = Mutex>>; /// Use this to make calls to the fans through modbus static FANS: Fans = Mutex::new(None); @@ -352,11 +352,6 @@ async fn led_routine(pin_21: PIN_21, pin_20: PIN_20) { } } -enum Fan { - One, - Two, -} - enum FanCommand { SetSpeed { set_point: SetPoint }, } @@ -542,8 +537,17 @@ async fn main(spawner: Spawner) { static RX_BUFFER: StaticCell<[u8; 16]> = StaticCell::new(); let rx_buffer = &mut RX_BUFFER.init([0; 16])[..]; - let client = fan::Client::new( - uart0, pin_12, pin_13, Irqs, dma_ch1, dma_ch2, pin_4, tx_buffer, rx_buffer, + let client = modbus::client::Client::new( + uart0, + pin_12, + pin_13, + Irqs, + dma_ch1, + dma_ch2, + pin_4, + tx_buffer, + rx_buffer, + fan::get_configuration(), ); //TODO load fan setting from fan // Inner scope to drop the guard after assigning diff --git a/fan-controller/src/modbus/client.rs b/fan-controller/src/modbus/client.rs new file mode 100644 index 0000000..3ee310f --- /dev/null +++ b/fan-controller/src/modbus/client.rs @@ -0,0 +1,281 @@ +use defmt::{error, info}; +use embassy_rp::{ + dma, + gpio::{Level, Output, Pin}, + interrupt::typelevel::Binding, + uart::{self, BufferedInterruptHandler, BufferedUart, RxPin, TxPin}, + Peripheral, +}; +use embassy_time::{block_for, with_timeout, Duration, TimeoutError, Timer}; +use embedded_io_async::{Read, Write}; + +use crate::{ + configuration, + fan::{self, address, holding_registers, Fan, FanResponse, SetPoint, BAUD_RATE}, + modbus::{self, ReadInputRegister}, +}; + +pub(crate) enum Error { + Timeout(TimeoutError), + Uart(uart::Error), +} + +impl From for Error { + fn from(error: TimeoutError) -> Self { + Self::Timeout(error) + } +} + +impl From for Error { + fn from(error: uart::Error) -> Self { + Self::Uart(error) + } +} + +const BLOCK_FOR: Duration = Duration::from_micros(5_000); + +/// Modbus messages are sent through UART to MAX845 to control fans. +/// The pin is used to enable the DE pin to switch between reading and writing +pub(crate) struct Client<'a, UART: uart::Instance, PIN: Pin> { + uart: BufferedUart<'a, UART>, + driver_enable: Output<'a, PIN>, +} + +impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { + pub(crate) fn new( + uart: impl Peripheral

+ 'a, + tx: impl Peripheral

> + 'a, + rx: impl Peripheral

> + 'a, + irq: impl Binding>, + tx_dma: impl Peripheral

+ 'a, + rx_dma: impl Peripheral

+ 'a, + driver_enable: impl Peripheral

+ 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + configuration: uart::Config, + ) -> Self { + let uart = BufferedUart::new(uart, irq, tx, rx, tx_buffer, rx_buffer, configuration); + let driver_enable = Output::new(driver_enable, Level::Low); + + Self { + uart, + driver_enable, + } + } + + async fn send_2( + &mut self, + message: impl modbus::ToBytes, + ) -> Result, Error> { + // Write then read + // Set pin setting DE (driver enable) to on (high) on the MAX845 to send data + self.driver_enable.set_high(); + + let bytes = message.to_bytes(); + info!("Sending message to fan: {:?}", bytes); + // As ref because &[u8; 8] is not the same as &[u8] + let result = with_timeout(configuration::FAN_TIMEOUT, self.uart.write_all(&bytes)).await?; + + info!("uart write result: {:?}", result); + + // Before closing we need to flush the buffer to ensure that all data is written + // This requires blocking or we get a WouldBlock error. I don't understand why (TODO) + let result = self.uart.blocking_flush(); + if let Err(error) = result { + error!("uart flush error"); + } + + // In addition to flushing we need to wait for some time before turning off data in on the + // MAX845 because we might be too fast and cut off the last byte or more. (This happened) + // I saw someone using 120 microseconds (https://youtu.be/i46jdhvRej4?t=886). + // This number is based on trial and error. Don't feel bad to change it if it doesn't work. + // Also timings in microseconds are not accurate. + // I assume this should be below the modbus message delay + // Timer::after(Duration::from_micros(1_000)).await; + // Using await timer breaks this too. Probably because it yields to the scheduler + block_for(BLOCK_FOR); + + // Close sending data to enable receiving data + self.driver_enable.set_low(); + + // Read + // Read response from fan. The response can vary in length + let mut response_buffer: [u8; RESPONSE] = [0; RESPONSE]; + info!("Waiting for response from fan"); + let bytes_read = with_timeout( + configuration::FAN_TIMEOUT, + //TODO test this does not wait for bytes to fill the buffer + // leading to a timeout because the response is only 7 bytes but the buffer is 8 and it waits for the last byte to arrive + self.uart.read(&mut response_buffer), + ) + .await??; + + info!("response from fan: {:?} {:?}", bytes_read, response_buffer); + let response = FanResponse::new(response_buffer, bytes_read); + + //TODO validate response from fan + // Read the correct number of bytes + Ok(response) + } + + async fn send( + &mut self, + message: impl AsRef<[u8]>, + ) -> Result, Error> { + // Write then read + // Set pin setting DE (driver enable) to on (high) on the MAX845 to send data + self.driver_enable.set_high(); + + info!("Sending message to fan: {:?}", message.as_ref()); + // As ref because &[u8; 8] is not the same as &[u8] + let result = with_timeout( + configuration::FAN_TIMEOUT, + self.uart.write_all(message.as_ref()), + ) + .await?; + + info!("uart write result: {:?}", result); + + // Before closing we need to flush the buffer to ensure that all data is written + // This requires blocking or we get a WouldBlock error. I don't understand why (TODO) + let result = self.uart.blocking_flush(); + if let Err(error) = result { + error!("uart flush error"); + } + + // In addition to flushing we need to wait for some time before turning off data in on the + // MAX845 because we might be too fast and cut off the last byte or more. (This happened) + // I saw someone using 120 microseconds (https://youtu.be/i46jdhvRej4?t=886). + // This number is based on trial and error. Don't feel bad to change it if it doesn't work. + // Also timings in microseconds are not accurate. + // I assume this should be below the modbus message delay + // Timer::after(Duration::from_micros(1_000)).await; + // Using await timer breaks this too. Probably because it yields to the scheduler + block_for(BLOCK_FOR); + + // Close sending data to enable receiving data + self.driver_enable.set_low(); + + // Read + // Read response from fan. The response can vary in length + let mut response_buffer: [u8; N] = [0; N]; + info!("Waiting for response from fan"); + let bytes_read = with_timeout( + configuration::FAN_TIMEOUT, + //TODO test this does not wait for bytes to fill the buffer + // leading to a timeout because the response is only 7 bytes but the buffer is 8 and it waits for the last byte to arrive + self.uart.read(&mut response_buffer), + ) + .await??; + + info!("response from fan: {:?} {:?}", bytes_read, response_buffer); + let response = FanResponse::new(response_buffer, bytes_read); + + //TODO validate response from fan + // Read the correct number of bytes + Ok(response) + } + + //TODO decouple + /// The mutable reference to self here is important as there can only be one writer to the (mod)bus at a time + #[deprecated(note = "Decoupled fan from modbus")] + pub(crate) async fn set_set_point( + &mut self, + SetPoint(set_point): &SetPoint, + ) -> Result<(), Error> { + // Send update through UART to MAX845 to modbus fans + // Form message to fan 1 + let mut message: [u8; 8] = [ + // Device address fan 1 + address::FAN_1, + // Modbus function code + modbus::function_code::WRITE_SINGLE_REGISTER, + // Holding register address + holding_registers::REFERENCE_SET_POINT[0], + holding_registers::REFERENCE_SET_POINT[1], + // Value to set + (set_point >> 8) as u8, + *set_point as u8, + // CRC is set later + 0, + 0, + ]; + + let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); + + // They come out reversed (or is us using to_be_bytes reversed?) + message[6] = checksum[1]; + message[7] = checksum[0]; + info!("Sending message to fan 1: {:?}", message); + + let _ = self.send::<8>(&message).await?; + + /// Messsage delay between modbus messages in microseconds + const MESSAGE_DELAY: u64 = modbus::get_message_delay(BAUD_RATE); + info!("Message delay {}", MESSAGE_DELAY); + // We can yield the future here because the wait time between messages is a minimum and can be longer + Timer::after_micros(MESSAGE_DELAY).await; + + // Form message to fan 2 + // Update the fan address and therefore the CRC + // Keep speed as both fans should be running at the same speed + message[0] = address::FAN_2; + let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); + message[6] = checksum[1]; + message[7] = checksum[0]; + + info!("sending message to fan 2: {:?}", message); + let _ = self.send::<8>(&message).await?; + Ok(()) + } + + ///TODO decouple from modbus + #[deprecated(note = "Needs to be decoupled from modbus")] + pub(crate) async fn get_temperature(&mut self, fan: Fan) -> Result { + let message = modbus::Message::new( + match fan { + Fan::One => address::FAN_1, + Fan::Two => address::FAN_2, + }, + ReadInputRegister::new(0xd02e, 1), + ); + + let test: FanResponse<7> = self.send_2(message).await?; + + let mut message: [u8; 8] = [ + // Device address + match fan { + Fan::One => address::FAN_1, + Fan::Two => address::FAN_2, + }, + // Modbus function code + modbus::function_code::READ_INPUT_REGISTER, + // Input register address + fan::input_registers::TEMPERATURE_SENSOR_1[0], + fan::input_registers::TEMPERATURE_SENSOR_1[1], + // Number of registers to read + 0, + 1, + // CRC is set later + 0, + 0, + ]; + + let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); + + // They come out reversed (or is us using to_be_bytes reversed?) + message[6] = checksum[1]; + message[7] = checksum[0]; + info!("Sending read temperature message {:?}", message); + + let response = self.send::<7>(&message).await?; + let response = response.as_slice(); + + //TODO read the correct number of bytes + let length = response[2]; + let temperature = u16::from_be_bytes([response[3], response[4]]); + info!("Temperature (divide by 10): {}", temperature); + + Ok(temperature) + } +} diff --git a/fan-controller/src/modbus/mod.rs b/fan-controller/src/modbus/mod.rs index 408641a..cd5cf4b 100644 --- a/fan-controller/src/modbus/mod.rs +++ b/fan-controller/src/modbus/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod client; + use crc::{Crc, CRC_16_MODBUS}; pub(super) mod function_code { pub const READ_HOLDING_REGISTER: u8 = 0x03; diff --git a/fan-controller/src/task.rs b/fan-controller/src/task.rs index ede9cfd..5f63d9d 100644 --- a/fan-controller/src/task.rs +++ b/fan-controller/src/task.rs @@ -7,9 +7,9 @@ use crate::mqtt::packet::subscribe_acknowledgement::SubscribeAcknowledgement; use crate::mqtt::task::send; use crate::mqtt::{self}; use crate::mqtt::{non_zero_u16, TryDecode}; -use crate::Fans; use crate::PingRequest; use crate::{configuration, fan, gain_control, FanState}; +use crate::{modbus, Fans}; use ::mqtt::QualityOfService; use core::future::poll_fn; use core::num::NonZeroU16; @@ -570,11 +570,11 @@ async fn poll_sensors(fans: Fans) { let temperature = match fans.get_temperature(fan::Fan::One).await { Ok(temperature) => temperature, - Err(fan::Error::Timeout(TimeoutError)) => { + Err(modbus::client::Error::Timeout(_)) => { error!("Timeout getting temperature for fan 1"); return; } - Err(fan::Error::Uart(error)) => { + Err(modbus::client::Error::Uart(error)) => { error!("Uart error getting temperature for fan 1: {:?}", error); return; } -- 2.51.2