diff --git a/src/net/messages.rs b/src/net/messages.rs new file mode 100644 index 0000000..2f54f52 --- /dev/null +++ b/src/net/messages.rs @@ -0,0 +1,124 @@ +use heapless::{consts, String, Vec}; +use serde::Serialize; + +use core::fmt::Write; + +#[derive(Debug, Copy, Clone)] +pub enum SettingsResponseCode { + NoError = 0, + NoTopic = 1, + InvalidPrefix = 2, + UnknownTopic = 3, + UpdateFailure = 4, +} + +/// Represents a generic MQTT message. +pub struct MqttMessage<'a> { + pub topic: &'a str, + pub message: Vec, + pub properties: Vec, consts::U1>, +} + +/// The payload of the MQTT response message to a settings update request. +#[derive(Serialize)] +pub struct SettingsResponse { + code: u8, + msg: String, +} + +impl<'a> MqttMessage<'a> { + /// Construct a new MQTT message from an incoming message. + /// + /// # Args + /// * `properties` - A list of properties associated with the inbound message. + /// * `default_response` - The default response topic for the message + /// * `msg` - The response associated with the message. Must fit within 128 bytes. + pub fn new<'b: 'a>( + properties: &[minimq::Property<'a>], + default_response: &'b str, + msg: &impl Serialize, + ) -> Self { + // Extract the MQTT response topic. + let topic = properties + .iter() + .find_map(|prop| { + if let minimq::Property::ResponseTopic(topic) = prop { + Some(topic) + } else { + None + } + }) + .unwrap_or(&default_response); + + // Associate any provided correlation data with the response. + let mut correlation_data: Vec, consts::U1> = + Vec::new(); + if let Some(data) = properties + .iter() + .find(|prop| matches!(prop, minimq::Property::CorrelationData(_))) + { + // Note(unwrap): Unwrap can not fail, as we only ever push one value. + correlation_data.push(*data).unwrap(); + } + + Self { + topic, + // Note(unwrap): All SettingsResponse objects are guaranteed to fit in the vector. + message: miniconf::serde_json_core::to_vec(msg).unwrap(), + properties: correlation_data, + } + } +} + +impl SettingsResponse { + /// Construct a settings response upon successful settings update. + /// + /// # Args + /// * `path` - The path of the setting that was updated. + pub fn update_success(path: &str) -> Self { + let mut msg: String = String::new(); + if write!(&mut msg, "{} updated", path).is_err() { + msg = String::from("Latest update succeeded"); + } + + Self { + msg, + code: SettingsResponseCode::NoError as u8, + } + } + + /// Construct a response when a settings update failed. + /// + /// # Args + /// * `path` - The settings path that configuration failed for. + /// * `err` - The settings update error that occurred. + pub fn update_failure(path: &str, err: miniconf::Error) -> Self { + let mut msg: String = String::new(); + if write!(&mut msg, "{} update failed: {:?}", path, err).is_err() { + if write!(&mut msg, "Latest update failed: {:?}", err).is_err() { + msg = String::from("Latest update failed"); + } + } + + Self { + msg, + code: SettingsResponseCode::UpdateFailure as u8, + } + } + + /// Construct a response from a custom response code. + /// + /// # Args + /// * `code` - The response code to provide. + pub fn code(code: SettingsResponseCode) -> Self { + let mut msg: String = String::new(); + + // Note(unwrap): All code debug names shall fit in the 64 byte string. + write!(&mut msg, "{:?}", code).unwrap(); + + Self { + code: code as u8, + msg, + } + } +} diff --git a/src/net/mod.rs b/src/net/mod.rs index a1e1399..9a86b85 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,11 +1,18 @@ +///! Stabilizer network management module +///! +///! # Design +///! The stabilizer network architecture supports numerous layers to permit transmission of +///! telemetry (via MQTT), configuration of run-time settings (via MQTT + Miniconf), and live data +///! streaming over raw UDP/TCP sockets. This module encompasses the main processing routines +///! related to Stabilizer networking operations. use heapless::{consts, String}; use core::fmt::Write; +mod messages; mod mqtt_interface; -mod router; +use messages::{MqttMessage, SettingsResponse, SettingsResponseCode}; pub use mqtt_interface::MqttInterface; -use router::{RouteResult, SettingsResponse}; /// Potential actions for firmware to take. pub enum Action { diff --git a/src/net/mqtt_interface.rs b/src/net/mqtt_interface.rs index 374714d..209a67d 100644 --- a/src/net/mqtt_interface.rs +++ b/src/net/mqtt_interface.rs @@ -7,7 +7,7 @@ use core::{cell::RefCell, fmt::Write}; use heapless::{consts, String}; use serde::Serialize; -use super::{Action, RouteResult, SettingsResponse}; +use super::{Action, MqttMessage, SettingsResponse, SettingsResponseCode}; /// MQTT settings interface. pub struct MqttInterface @@ -136,7 +136,7 @@ where self.route_message(topic, message, properties); client .publish( - response.response_topic, + response.topic, &response.message, minimq::QoS::AtMostOnce, &response.properties, @@ -175,33 +175,41 @@ where topic: &str, message: &[u8], properties: &[minimq::Property<'a>], - ) -> (RouteResult<'a>, bool) { - let mut response = - RouteResult::new(properties, &self.default_response_topic); + ) -> (MqttMessage<'a>, bool) { let mut update = false; - - if let Some(path) = topic.strip_prefix(self.id.as_str()) { - let mut parts = path[1..].split('/'); - match parts.next() { - Some("settings") => { - let result = self - .settings - .borrow_mut() - .string_set(parts.peekable(), message); - update = result.is_ok(); - response.set_message(SettingsResponse::new(result, topic)); + let response_msg = + if let Some(path) = topic.strip_prefix(self.id.as_str()) { + let mut parts = path[1..].split('/'); + match parts.next() { + Some("settings") => { + match self + .settings + .borrow_mut() + .string_set(parts.peekable(), message) + { + Ok(_) => { + update = true; + SettingsResponse::update_success(path) + } + Err(error) => { + SettingsResponse::update_failure(path, error) + } + } + } + Some(_) => SettingsResponse::code( + SettingsResponseCode::UnknownTopic, + ), + _ => SettingsResponse::code(SettingsResponseCode::NoTopic), } - Some(_) => response.set_message(SettingsResponse::custom( - "Unknown topic", - 255, - )), - _ => response - .set_message(SettingsResponse::custom("No topic", 254)), - } - } else { - response - .set_message(SettingsResponse::custom("Invalid prefix", 253)); - } + } else { + SettingsResponse::code(SettingsResponseCode::InvalidPrefix) + }; + + let response = MqttMessage::new( + properties, + &self.default_response_topic, + &response_msg, + ); (response, update) } diff --git a/src/net/router.rs b/src/net/router.rs deleted file mode 100644 index c8b7776..0000000 --- a/src/net/router.rs +++ /dev/null @@ -1,91 +0,0 @@ -use heapless::{consts, String, Vec}; -use serde::Serialize; - -use core::fmt::Write; - -pub struct RouteResult<'a> { - pub response_topic: &'a str, - pub message: Vec, - pub properties: Vec, consts::U1>, -} - -#[derive(Serialize)] -pub struct SettingsResponse { - code: u8, - msg: String, -} - -impl<'a> RouteResult<'a> { - pub fn new<'b: 'a>( - properties: &[minimq::Property<'a>], - default_response: &'b str, - ) -> Self { - // Extract the MQTT response topic. - let response_topic = properties - .iter() - .find_map(|prop| { - if let minimq::Property::ResponseTopic(topic) = prop { - Some(topic) - } else { - None - } - }) - .unwrap_or(&default_response); - - // Associate any provided correlation data with the response. - let mut correlation_data: Vec, consts::U1> = - Vec::new(); - if let Some(data) = properties - .iter() - .find(|prop| matches!(prop, minimq::Property::CorrelationData(_))) - { - // Note(unwrap): Unwrap can not fail, as we only ever push one value. - correlation_data.push(*data).unwrap(); - } - - RouteResult { - response_topic, - message: Vec::new(), - properties: correlation_data, - } - } - - pub fn set_message(&mut self, response: impl Serialize) { - self.message = miniconf::serde_json_core::to_vec(&response).unwrap(); - } -} - -impl SettingsResponse { - pub fn new(result: Result<(), miniconf::Error>, path: &str) -> Self { - match result { - Ok(_) => { - let mut msg: String = String::new(); - if write!(&mut msg, "{} updated", path).is_err() { - msg = String::from("Latest update succeeded"); - } - - Self { msg, code: 0 } - } - Err(error) => { - let mut msg: String = String::new(); - if write!(&mut msg, "{} update failed: {:?}", path, error) - .is_err() - { - if write!(&mut msg, "Latest update failed: {:?}", error) - .is_err() - { - msg = String::from("Latest update failed"); - } - } - Self { msg, code: 5 } - } - } - } - - pub fn custom(msg: &str, code: u8) -> Self { - Self { - code, - msg: String::from(msg), - } - } -}