diff --git a/fan-controller/src/fan/set_point.rs b/fan-controller/src/fan/set_point.rs index 2356085..856d860 100644 --- a/fan-controller/src/fan/set_point.rs +++ b/fan-controller/src/fan/set_point.rs @@ -6,7 +6,7 @@ pub(crate) const MAX: u16 = 64_000; /// Describes the desired speed of the fan from 0 to [`MAX_SET_POINT`] #[derive(Debug, Format, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct SetPoint(pub(crate) u16); +pub(crate) struct SetPoint(u16); #[derive(Debug, Format)] pub(crate) struct SetPointOutOfBoundsError; @@ -17,6 +17,16 @@ impl SetPoint { Err(error) => panic!("Invalid value. This should not be reachable."), }; + pub(crate) const MAX: Self = match Self::new(MAX) { + Ok(setting) => setting, + Err(error) => panic!("Invalid value. This should not be reachable."), + }; + + pub(crate) const MIN: Self = match Self::new(0) { + Ok(setting) => setting, + Err(error) => panic!("Invalid value. This should not be reachable."), + }; + pub(crate) const fn new(set_point: u16) -> Result { if set_point > MAX { return Err(SetPointOutOfBoundsError); @@ -28,6 +38,12 @@ impl SetPoint { const fn get(&self) -> u16 { self.0 } + + /// This should always succeed + pub(crate) fn to_string(&self) -> heapless::String<5> { + heapless::String::<5>::try_from(self.0) + .expect("The maximum value for a u16 is 65535 which is a 5 digit number and should should be represented as a string with 5 characters and thus fit into a string with a capacity of 5") + } } impl Deref for SetPoint { @@ -51,3 +67,36 @@ impl FromStr for SetPoint { Self::new(set_point).map_err(ParseSetPointError::SettingOutOfBounds) } } + +#[cfg(test)] +mod tests { + //! The tests don't run on the embedded target, so we need to import the std crate + //TODO needs to be fixed to make run + extern crate std; + use crate::fan::{SetPoint, SetPointOutOfBoundsError}; + + use super::*; + + /// These are important hardcoded values I want to make sure are not changed accidentally + #[test] + fn setting_does_not_exceed_max_set_point() { + core::assert_eq!(fan::MAX_SET_POINT, 64_000); + core::assert_eq!(SetPoint::new(64_000), Ok(SetPoint(64_000))); + core::assert_eq!(SetPoint::new(64_000 + 1), Err(SetPointOutOfBoundsError)); + core::assert_eq!(SetPoint::new(u16::MAX), Err(SetPointOutOfBoundsError)); + } + + //TODO have not checked if test compiles + #[test] + fn fits_into_string() -> Result<(), SetPointOutOfBoundsError> { + let set_point = SetPoint::new(12345)?; + let string = set_point.to_string(); + core::assert_eq!(string.len(), 5); + + let set_point = SetPoint::MAX; + core::assert_eq!(*set_point, 64_000); + core::assert_eq!(set_point.to_string(), "64000"); + + Ok(()) + } +} diff --git a/fan-controller/src/main.rs b/fan-controller/src/main.rs index e58031b..96e042f 100644 --- a/fan-controller/src/main.rs +++ b/fan-controller/src/main.rs @@ -298,6 +298,7 @@ async fn display_routine( &'static Signal, ), led_state: &'static Signal, + mqtt_out: channel::Sender<'static, CriticalSectionRawMutex, OutgoingPublish, 3>, ) { // The current fan state that was last recorded let mut current_display_state: (Option, Option) = (None, None); @@ -369,7 +370,7 @@ async fn display_routine( if let Some(update) = display_update_state.0 { //TODO update MQTT - + // mqtt_out.send() // Persist new state current_display_state.0.replace(update); // Reset @@ -455,7 +456,7 @@ enum FanCommand { SetSpeed { set_point: SetPoint }, } -enum FanControlPublish { +enum IncomingPublish { FanCommand { /// The fan the publish is addressed to target: Fan, @@ -471,7 +472,7 @@ enum FromPublishError { UnknownTopic, } -impl TryFrom> for FanControlPublish { +impl TryFrom> for IncomingPublish { type Error = FromPublishError; fn try_from(publish: publish::Publish<'_>) -> Result { @@ -483,7 +484,7 @@ impl TryFrom> for FanControlPublish { let set_point: SetPoint = payload.parse().map_err(FromPublishError::ParseSetPoint)?; - Ok(FanControlPublish::FanCommand { + Ok(IncomingPublish::FanCommand { target: Fan::One, command: FanCommand::SetSpeed { set_point }, }) @@ -495,7 +496,7 @@ impl TryFrom> for FanControlPublish { let set_point: SetPoint = payload.parse().map_err(FromPublishError::ParseSetPoint)?; - Ok(FanControlPublish::FanCommand { + Ok(IncomingPublish::FanCommand { target: Fan::Two, command: FanCommand::SetSpeed { set_point }, }) @@ -511,13 +512,43 @@ impl TryFrom> for FanControlPublish { } } -impl Publish for FanControlPublish { +struct UpdateStatePayload(heapless::String<5>); + +impl From for UpdateStatePayload { + fn from(set_point: SetPoint) -> Self { + let buffer = set_point.to_string(); + Self(buffer) + } +} + +enum OutgoingPublish { + UpdateState { + fan: Fan, + payload: UpdateStatePayload, + }, +} + +impl Publish for OutgoingPublish { fn topic(&self) -> &str { - "temporary" + match self { + OutgoingPublish::UpdateState { + fan: Fan::One, + payload: _, + } => topic::fan_controller::fan_1::STATE, + OutgoingPublish::UpdateState { + fan: Fan::Two, + payload: _, + } => topic::fan_controller::fan_2::STATE, + } } fn payload(&self) -> &[u8] { - b"25.5" + match self { + OutgoingPublish::UpdateState { fan: _, payload } => { + // set_point.0.to_be_bytes() + payload.0.as_bytes() + } + } } } @@ -534,10 +565,10 @@ async fn mqtt_routine( sender_in: channel::Sender< 'static, CriticalSectionRawMutex, - Result, + Result, 3, >, - receiver_out: channel::Receiver<'static, CriticalSectionRawMutex, FanControlPublish, 3>, + receiver_out: channel::Receiver<'static, CriticalSectionRawMutex, OutgoingPublish, 3>, ) { // Setting up the network in the task to not block from controlling the device without server connection let stack = set_up_network_stack(spawner, pwr_pin, cs_pin, pio, dma, dio, clk).await; @@ -552,7 +583,7 @@ async fn mqtt_brain_routine( receiver_in: channel::Receiver< 'static, CriticalSectionRawMutex, - Result, + Result, 3, >, fan_one_state: &'static Signal, @@ -581,7 +612,7 @@ async fn mqtt_brain_routine( info!("Received valid payload!"); match publish { - FanControlPublish::FanCommand { + IncomingPublish::FanCommand { target, command: FanCommand::SetSpeed { set_point }, } => match target { @@ -843,12 +874,12 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(update_fans(&FANS))); /// Channel for messages incoming from the MQTT broker to this fan controller - static IN: Channel, 3> = + static IN: Channel, 3> = Channel::new(); let sender_in = IN.sender(); /// Channel for messages outgoing from this fan controller to the MQTT broker - static OUT: Channel = Channel::new(); + static OUT: Channel = Channel::new(); let receiver_out = OUT.receiver(); // The MQTT task waits for publishes from MQTT and sends them to the modbus task. @@ -872,9 +903,11 @@ async fn main(spawner: Spawner) { // and is used to update any component that displays the fan state like the LEDs or Home Assistant through MQTT static FAN_ONE_DISPLAY_STATE: Signal = Signal::new(); static FAN_TWO_DISPLAY_STATE: Signal = Signal::new(); + let sender_out = OUT.sender(); unwrap!(spawner.spawn(display_routine( (&FAN_ONE_DISPLAY_STATE, &FAN_TWO_DISPLAY_STATE), - &LED_STATE + &LED_STATE, + sender_out ))); static FAN_ONE_STATE: Signal = Signal::new(); @@ -901,22 +934,3 @@ async fn main(spawner: Spawner) { &FAN_TWO_DISPLAY_STATE, ))); } - -#[cfg(test)] -mod tests { - // The tests don't run on the embedded target, so we need to import the std crate - - extern crate std; - use crate::fan::{SetPoint, SetPointOutOfBoundsError}; - - use super::*; - - /// These are important hardcoded values I want to make sure are not changed accidentally - #[test] - fn setting_does_not_exceed_max_set_point() { - core::assert_eq!(fan::MAX_SET_POINT, 64_000); - core::assert_eq!(SetPoint::new(64_000), Ok(SetPoint(64_000))); - core::assert_eq!(SetPoint::new(64_000 + 1), Err(SetPointOutOfBoundsError)); - core::assert_eq!(SetPoint::new(u16::MAX), Err(SetPointOutOfBoundsError)); - } -} diff --git a/fan-controller/src/modbus/client.rs b/fan-controller/src/modbus/client.rs index f61f9a3..dae8dcb 100644 --- a/fan-controller/src/modbus/client.rs +++ b/fan-controller/src/modbus/client.rs @@ -1,3 +1,5 @@ +use core::ops::Deref; + use defmt::{error, info}; use embassy_rp::{ Peripheral, dma, @@ -232,13 +234,11 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { //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> { + pub(crate) async fn set_set_point(&mut self, set_point: &SetPoint) -> 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 value: u16 = set_point.deref().clone(); let mut message: [u8; 8] = [ // Device address fan 1 *address::FAN_1, @@ -248,8 +248,8 @@ impl<'a, UART: uart::Instance, PIN: Pin> Client<'a, UART, PIN> { register_address[0], register_address[1], // Value to set - (set_point >> 8) as u8, - *set_point as u8, + (value >> 8) as u8, + value as u8, // CRC is set later 0, 0, diff --git a/fan-controller/src/task.rs b/fan-controller/src/task.rs index 4c5ba0e..e831889 100644 --- a/fan-controller/src/task.rs +++ b/fan-controller/src/task.rs @@ -357,7 +357,7 @@ async fn talk( PredefinedPublish::FanPercentageState { setting } => { info!("Sending percentage state publish {}", setting); // let buffer: StringBuffer<5> = setting.into(); - let buffer = match heapless::String::<5>::try_from(setting.0) { + let buffer = match heapless::String::<5>::try_from(*setting) { Ok(buffer) => buffer, Err(error) => { error!("Error converting setting to string: {:?}", error);