diff --git a/fan-controller/src/fan/mod.rs b/fan-controller/src/fan/mod.rs index 8bf7887..1b9a633 100644 --- a/fan-controller/src/fan/mod.rs +++ b/fan-controller/src/fan/mod.rs @@ -3,19 +3,8 @@ pub(crate) mod set_point; -use core::str::FromStr; - -use crate::{configuration, modbus}; -use defmt::{error, info, Format}; -use embassy_rp::dma::Channel; -use embassy_rp::gpio::{Level, Output, Pin}; -use embassy_rp::interrupt::typelevel::Binding; -use embassy_rp::uart::{ - BufferedInterruptHandler, BufferedUart, DataBits, Parity, RxPin, StopBits, TxPin, -}; -use embassy_rp::{uart, Peripheral}; -use embassy_time::{block_for, with_timeout, Duration, TimeoutError, Timer}; -use embedded_io_async::{Read, Write}; +use defmt::Format; +use embassy_rp::uart::{self, DataBits, Parity, StopBits}; pub(crate) const BAUD_RATE: u32 = 19_200; pub(crate) fn get_configuration() -> uart::Config { @@ -32,10 +21,7 @@ pub(crate) fn get_configuration() -> uart::Config { /// Settings specific to our use case for these fans. They are custom tuned to the house. /// For example, we don't run the fans at full speed to reduce wear on them pub(crate) mod user_setting { - use crate::fan::{ - self, - set_point::{self, SetPoint}, - }; + use crate::fan::set_point::{self, SetPoint}; /// Max speed 64000 / 3.3 pub(crate) const LOW: SetPoint = match SetPoint::new(19_393) { @@ -76,19 +62,31 @@ impl State { } pub(crate) mod address { - pub(crate) const FAN_1: u8 = 0x02; - pub(crate) const FAN_2: u8 = 0x03; + use crate::modbus; + + /// Starting fan with address 0x02 as 0x01 might be occupied by as a default address + pub(crate) const FAN_1: modbus::device::Address = modbus::device::Address::new(0x02); + pub(crate) const FAN_2: modbus::device::Address = modbus::device::Address::new(0x03); } pub(super) mod holding_registers { - pub(crate) const REFERENCE_SET_POINT: [u8; 2] = 0xd001_u16.to_be_bytes(); + use crate::modbus; + + pub(crate) const REFERENCE_SET_POINT: modbus::register::Address = + modbus::register::Address::new(0xd001_u16); } 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(); + use crate::modbus; + + pub(crate) const TEMPERATURE_SENSOR_1: modbus::register::Address = + modbus::register::Address::new(0xd02e_u16); + pub(crate) const HUMIDITY_SENSOR_1: modbus::register::Address = + modbus::register::Address::new(0xd02f_u16); + pub(crate) const TEMPERATURE_SENSOR_2: modbus::register::Address = + modbus::register::Address::new(0xd030_u16); + pub(crate) const HUMIDITY_SENSOR_2: modbus::register::Address = + modbus::register::Address::new(0xd031_u16); } pub(crate) enum Fan { diff --git a/fan-controller/src/fan/set_point.rs b/fan-controller/src/fan/set_point.rs index c112641..2356085 100644 --- a/fan-controller/src/fan/set_point.rs +++ b/fan-controller/src/fan/set_point.rs @@ -1,4 +1,4 @@ -use core::str::FromStr; +use core::{ops::Deref, str::FromStr}; use defmt::Format; @@ -30,6 +30,14 @@ impl SetPoint { } } +impl Deref for SetPoint { + type Target = u16; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + pub(crate) enum ParseSetPointError { ParseInt(core::num::ParseIntError), SettingOutOfBounds(SetPointOutOfBoundsError), diff --git a/fan-controller/src/main.rs b/fan-controller/src/main.rs index fe893d5..13af2fb 100644 --- a/fan-controller/src/main.rs +++ b/fan-controller/src/main.rs @@ -10,10 +10,7 @@ use debounce::Debouncer; use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_net::dns::{DnsQueryType, DnsSocket}; -use embassy_net::tcp::{TcpReader, TcpSocket, TcpWriter}; -use embassy_net::{tcp, Config, IpEndpoint, Stack, StackResources}; -use embassy_rp::clocks::RoscRng; +use embassy_net::Stack; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{ DMA_CH0, PIN_18, PIN_20, PIN_21, PIN_23, PIN_25, PIN_4, PIO0, UART0, @@ -28,19 +25,16 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_sync::signal::Signal; use embassy_sync::watch::Watch; -use embassy_time::{Duration, Instant, TimeoutError, Timer}; -use embedded_nal_async::TcpConnect; +use embassy_time::{Duration, Instant, Timer}; use mqtt::TryDecode; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -use self::mqtt::packet; use crate::fan::set_point::{ParseSetPointError, SetPoint}; use crate::fan::Fan; use crate::mqtt::packet::ping_request::PingRequest; -use crate::mqtt::packet::{connect, publish, subscribe}; +use crate::mqtt::packet::publish; use crate::task::{set_up_network_stack, MqttBrokerConfiguration, Publish}; mod async_callback; @@ -494,6 +488,7 @@ async fn mqtt_brain_routine( #[embassy_executor::task] async fn fan_control_routine( + fan_address: modbus::device::Address, fan_state: &'static Signal, fans: &'static FansOnceLock, ) { @@ -503,7 +498,14 @@ async fn fan_control_routine( info!("Received fan state"); // Instruct modbus to send update //TODO restart waiting for lock when the fan state gets updated before the lock is released - let fans = fans.get().await.lock().await.deref_mut(); + let fans = fans.get().await; + let mut fans = fans.lock().await; + let function = modbus::function::WriteHoldingRegister::new( + fan_address, + fan::holding_registers::REFERENCE_SET_POINT, + *state, + ); + fans.send_3(function).await; } } @@ -597,8 +599,16 @@ async fn main(spawner: Spawner) { &FAN_TWO_STATE ))); - unwrap!(spawner.spawn(fan_control_routine(&FAN_ONE_STATE, &FANS))); - unwrap!(spawner.spawn(fan_control_routine(&FAN_TWO_STATE, &FANS))); + unwrap!(spawner.spawn(fan_control_routine( + fan::address::FAN_1, + &FAN_ONE_STATE, + &FANS + ))); + unwrap!(spawner.spawn(fan_control_routine( + fan::address::FAN_2, + &FAN_TWO_STATE, + &FANS + ))); } #[cfg(test)] diff --git a/fan-controller/src/modbus/client.rs b/fan-controller/src/modbus/client.rs index ad6ecf5..f96bb5d 100644 --- a/fan-controller/src/modbus/client.rs +++ b/fan-controller/src/modbus/client.rs @@ -12,7 +12,10 @@ use embedded_io_async::{Read, Write}; use crate::{ configuration, fan::{self, address, holding_registers, set_point::SetPoint, Fan, FanResponse, BAUD_RATE}, - modbus::{self, function::read_input_register::ReadInputRegister}, + modbus::{ + self, + function::{read_input_register::ReadInputRegister, WriteHoldingRegister}, + }, }; pub(crate) enum Error { @@ -63,7 +66,7 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { } } - async fn send_2( + pub(crate) async fn send_2( &mut self, message: impl modbus::ToBytes, ) -> Result, Error> { @@ -118,6 +121,57 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { Ok(response) } + pub(crate) async fn send_3(&mut self, message: WriteHoldingRegister) -> 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.as_ref(); + 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; 8] = [0; 8]; + 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); + + //TODO validate response from fan + // Read the correct number of bytes + Ok(()) + } + async fn send( &mut self, message: impl AsRef<[u8]>, @@ -185,14 +239,15 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { ) -> Result<(), Error> { // Send update through UART to MAX845 to modbus fans // Form message to fan 1 + let register_address = (*holding_registers::REFERENCE_SET_POINT).to_be_bytes(); let mut message: [u8; 8] = [ // Device address fan 1 - 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], + register_address[0], + register_address[1], // Value to set (set_point >> 8) as u8, *set_point as u8, @@ -219,7 +274,7 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { // 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; + message[0] = *address::FAN_2; let checksum = modbus::CRC.checksum(&message[..6]).to_be_bytes(); message[6] = checksum[1]; message[7] = checksum[0]; @@ -234,25 +289,26 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { 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, + Fan::One => *address::FAN_1, + Fan::Two => *address::FAN_2, }, ReadInputRegister::new(0xd02e, 1), ); let test: FanResponse<7> = self.send_2(message).await?; + let register_address = (*fan::input_registers::TEMPERATURE_SENSOR_1).to_be_bytes(); let mut message: [u8; 8] = [ // Device address match fan { - Fan::One => address::FAN_1, - Fan::Two => address::FAN_2, + 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], + register_address[0], + register_address[1], // Number of registers to read 0, 1, diff --git a/fan-controller/src/modbus/device.rs b/fan-controller/src/modbus/device.rs new file mode 100644 index 0000000..5028282 --- /dev/null +++ b/fan-controller/src/modbus/device.rs @@ -0,0 +1,34 @@ +mod address { + use core::ops::Deref; + + #[derive(Debug, Clone, Copy)] + pub(crate) struct Address(u8); + + impl Address { + pub(crate) const fn new(value: u8) -> Self { + Self(value) + } + } + + impl Into for Address { + fn into(self) -> u8 { + self.0 + } + } + + impl From for Address { + fn from(value: u8) -> Self { + Self(value) + } + } + + impl Deref for Address { + type Target = u8; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } +} + +pub(crate) use address::Address; diff --git a/fan-controller/src/modbus/function/mod.rs b/fan-controller/src/modbus/function/mod.rs index ff2183f..11bc9d8 100644 --- a/fan-controller/src/modbus/function/mod.rs +++ b/fan-controller/src/modbus/function/mod.rs @@ -2,6 +2,7 @@ pub(super) mod code; pub(crate) mod read_input_register; pub(crate) mod write_holding_register; +pub(crate) use write_holding_register::WriteHoldingRegister; pub(crate) trait Function { const CODE: u8; } diff --git a/fan-controller/src/modbus/function/write_holding_register.rs b/fan-controller/src/modbus/function/write_holding_register.rs index ed5242a..e0ce8bf 100644 --- a/fan-controller/src/modbus/function/write_holding_register.rs +++ b/fan-controller/src/modbus/function/write_holding_register.rs @@ -4,10 +4,14 @@ use crate::modbus; pub(crate) struct WriteHoldingRegister([u8; 8]); impl WriteHoldingRegister { - pub(crate) fn new(device_address: u8, register_address: u16, value: u16) -> Self { + pub(crate) fn new( + device_address: modbus::device::Address, + register_address: modbus::register::Address, + value: u16, + ) -> Self { let register_address = register_address.to_be_bytes(); let mut data = [ - device_address, + *device_address, modbus::function::code::WRITE_SINGLE_REGISTER, register_address[0], register_address[1], @@ -26,3 +30,9 @@ impl WriteHoldingRegister { Self(data) } } + +impl AsRef<[u8]> for WriteHoldingRegister { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} diff --git a/fan-controller/src/modbus/mod.rs b/fan-controller/src/modbus/mod.rs index 7e7c898..65ee110 100644 --- a/fan-controller/src/modbus/mod.rs +++ b/fan-controller/src/modbus/mod.rs @@ -1,5 +1,7 @@ pub(crate) mod client; -mod function; +pub(crate) mod device; +pub(crate) mod function; +pub(crate) mod register; use crc::{Crc, CRC_16_MODBUS}; diff --git a/fan-controller/src/modbus/register.rs b/fan-controller/src/modbus/register.rs new file mode 100644 index 0000000..4543ded --- /dev/null +++ b/fan-controller/src/modbus/register.rs @@ -0,0 +1,34 @@ +mod address { + use core::ops::Deref; + + #[derive(Debug, Clone, Copy)] + pub(crate) struct Address(u16); + + impl Address { + pub(crate) const fn new(value: u16) -> Self { + Self(value) + } + } + + impl Into for Address { + fn into(self) -> u16 { + self.0 + } + } + + impl From for Address { + fn from(value: u16) -> Self { + Self(value) + } + } + + impl Deref for Address { + type Target = u16; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } +} + +pub(crate) use address::Address; diff --git a/fan-controller/src/mqtt/client.rs b/fan-controller/src/mqtt/client.rs index f95b3f1..c8e11f0 100644 --- a/fan-controller/src/mqtt/client.rs +++ b/fan-controller/src/mqtt/client.rs @@ -15,13 +15,13 @@ use crate::{ }, }; use core::{ - fmt::{self, Debug}, + fmt::Debug, future::{poll_fn, Future}, num::NonZeroU16, task::Poll, }; use defmt::{error, info, warn, Format}; -use embassy_net::tcp::{TcpReader, TcpSocket, TcpWriter}; +use embassy_net::tcp::TcpSocket; use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex, signal::Signal, waitqueue::AtomicWaker, diff --git a/fan-controller/src/mqtt/task.rs b/fan-controller/src/mqtt/task.rs index 16c9087..15d8cb6 100644 --- a/fan-controller/src/mqtt/task.rs +++ b/fan-controller/src/mqtt/task.rs @@ -6,9 +6,7 @@ use crate::mqtt::{packet, ConnectErrorReasonCode}; use crate::mqtt::{TryDecode, TryEncode}; use core::fmt::Debug; use defmt::{info, warn, Format}; -use embassy_net::tcp; -use embassy_net::tcp::TcpSocket; -use embedded_io_async::{ErrorType, Read, Write}; +use embedded_io_async::{Read, Write}; ///! Tasks that need to be done to run MQTT ///! - Keep alive diff --git a/fan-controller/src/task.rs b/fan-controller/src/task.rs index ce2023c..ede1847 100644 --- a/fan-controller/src/task.rs +++ b/fan-controller/src/task.rs @@ -22,7 +22,7 @@ use embassy_executor::Spawner; use embassy_futures::join::{join, join4}; use embassy_net::dns::{DnsQueryType, DnsSocket}; use embassy_net::driver::Driver; -use embassy_net::tcp::{TcpReader, TcpSocket, TcpWriter}; +use embassy_net::tcp::{TcpSocket, TcpWriter}; use embassy_net::{Config, IpAddress, IpEndpoint, Stack, StackResources}; use embassy_rp::clocks::RoscRng; use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; @@ -791,12 +791,12 @@ pub(super) async fn mqtt_with_connect< //TODO yes static "global" state is bad, but I am still learning how to use wakers and polling // with futures so this will be refactored when I made it work - /// Contains the status of the subscribe packets send out. The packet identifier represents the - /// index in the array + // Contains the status of the subscribe packets send out. The packet identifier represents the + // index in the array let acknowledgements: Mutex = Mutex::new([false, false]); - /// The waker needs to be woken to complete the subscribe acknowledgement future. - /// The embassy documentation does not explain when to use [`AtomicWaker`] but I am assuming - /// it is useful for cases like this where I need to mutate a static. + // The waker needs to be woken to complete the subscribe acknowledgement future. + // The embassy documentation does not explain when to use [`AtomicWaker`] but I am assuming + // it is useful for cases like this where I need to mutate a static. let waker: AtomicWaker = AtomicWaker::new(); let ping_response: Signal = Signal::new(); @@ -804,7 +804,7 @@ pub(super) async fn mqtt_with_connect< let client_state: Signal = Signal::new(); let outgoing: Channel, 8> = Channel::new(); - /// The instant when the last packet was sent to determine when the next keep alive has to be sent + // The instant when the last packet was sent to determine when the next keep alive has to be sent let last_packet: Signal = Signal::new(); // Using a mutex for the writer, so it can be shared between the task that sends messages (for