Add mcutie MQTT client implementation and improve library structure
- Integrated `mcutie` library as a core MQTT client for device communication. - Added support for Home Assistant entities (binary sensor, button) via MQTT. - Implemented buffer management, async operations, and packet encoding/decoding. - Introduced structured error handling and device registration features. - Updated `Cargo.toml` with new dependencies and enabled feature flags for `serde` and `log`. - Enhanced logging macros with configurable options (`defmt` or `log`). - Organized codebase into modules (buffer, components, IO, publish, etc.) for better maintainability.
This commit is contained in:
227
Software/MainBoard/rust/src/mcutie_3_0_0/lib.rs
Normal file
227
Software/MainBoard/rust/src/mcutie_3_0_0/lib.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
#![no_std]
|
||||
#![deny(unreachable_pub)]
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
//! MQTT client support crate vendored into this repository.
|
||||
|
||||
use core::{ops::Deref, str};
|
||||
|
||||
pub use buffer::Buffer;
|
||||
use embassy_net::{HardwareAddress, Stack};
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
|
||||
use heapless::String;
|
||||
pub use io::McutieTask;
|
||||
pub use mqttrs::QoS;
|
||||
use mqttrs::{Pid, SubscribeReturnCodes};
|
||||
use once_cell::sync::OnceCell;
|
||||
pub use publish::*;
|
||||
pub use topic::Topic;
|
||||
|
||||
// This must come first so the macros are visible
|
||||
pub(crate) mod fmt;
|
||||
|
||||
mod buffer;
|
||||
#[cfg(feature = "homeassistant")]
|
||||
pub mod homeassistant;
|
||||
mod io;
|
||||
mod pipe;
|
||||
mod publish;
|
||||
mod topic;
|
||||
|
||||
// This really needs to match that used by mqttrs.
|
||||
const TOPIC_LENGTH: usize = 256;
|
||||
const PAYLOAD_LENGTH: usize = 2048;
|
||||
|
||||
/// A fixed length stack allocated string. The length is fixed by the mqttrs crate.
|
||||
pub type TopicString = String<TOPIC_LENGTH>;
|
||||
/// A fixed length buffer of 2048 bytes.
|
||||
pub type Payload = Buffer<PAYLOAD_LENGTH>;
|
||||
|
||||
// By default in the event of an error connecting to the broker we will wait for 5s.
|
||||
const DEFAULT_BACKOFF: u64 = 5000;
|
||||
// If the connection dropped then re-connect more quickly.
|
||||
const RESET_BACKOFF: u64 = 200;
|
||||
// How long to wait for the broker to confirm actions.
|
||||
const CONFIRMATION_TIMEOUT: u64 = 2000;
|
||||
|
||||
static DATA_CHANNEL: Channel<CriticalSectionRawMutex, MqttMessage, 10> = Channel::new();
|
||||
|
||||
static DEVICE_TYPE: OnceCell<String<32>> = OnceCell::new();
|
||||
static DEVICE_ID: OnceCell<String<32>> = OnceCell::new();
|
||||
|
||||
fn device_id() -> &'static str {
|
||||
DEVICE_ID.get().unwrap()
|
||||
}
|
||||
|
||||
fn device_type() -> &'static str {
|
||||
DEVICE_TYPE.get().unwrap()
|
||||
}
|
||||
|
||||
/// Various errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
/// An IO error occured.
|
||||
IOError,
|
||||
/// The operation timed out.
|
||||
TimedOut,
|
||||
/// An attempt was made to encode something too large.
|
||||
TooLarge,
|
||||
/// A packet or payload could not be decoded or encoded.
|
||||
PacketError,
|
||||
/// An invalid or unsupported operation was attempted.
|
||||
Invalid,
|
||||
/// A value was rejected.
|
||||
Rejected,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
/// A message from the MQTT broker.
|
||||
pub enum MqttMessage {
|
||||
/// The broker has been connected to successfully. Generally in response to this message a
|
||||
/// device should subscribe to topics of interest and send out any device state.
|
||||
Connected,
|
||||
/// New data received from the broker.
|
||||
Publish(Topic<TopicString>, Payload),
|
||||
/// The connection to the broker has been dropped.
|
||||
Disconnected,
|
||||
/// Home Assistant has come online and you should send any discovery messages.
|
||||
#[cfg(feature = "homeassistant")]
|
||||
HomeAssistantOnline,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ControlMessage {
|
||||
Published(Pid),
|
||||
Subscribed(Pid, SubscribeReturnCodes),
|
||||
Unsubscribed(Pid),
|
||||
}
|
||||
|
||||
/// Receives messages from the broker.
|
||||
pub struct McutieReceiver;
|
||||
|
||||
impl McutieReceiver {
|
||||
/// Waits for the next message from the broker.
|
||||
pub async fn receive(&self) -> MqttMessage {
|
||||
DATA_CHANNEL.receive().await
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder to configure the MQTT stack.
|
||||
pub struct McutieBuilder<'t, T, L, const S: usize>
|
||||
where
|
||||
T: Deref<Target = str> + 't,
|
||||
L: Publishable + 't,
|
||||
{
|
||||
network: Stack<'t>,
|
||||
device_type: &'t str,
|
||||
device_id: Option<&'t str>,
|
||||
broker: &'t str,
|
||||
last_will: Option<L>,
|
||||
username: Option<&'t str>,
|
||||
password: Option<&'t str>,
|
||||
subscriptions: [Topic<T>; S],
|
||||
}
|
||||
|
||||
impl<'t, T: Deref<Target = str> + 't, L: Publishable + 't> McutieBuilder<'t, T, L, 0> {
|
||||
/// Creates a new builder with the initial required configuration.
|
||||
///
|
||||
/// `device_type` is expected to be the same for all devices of the same type.
|
||||
/// `broker` may be an IP address or a DNS name for the broker to connect to.
|
||||
pub fn new(network: Stack<'t>, device_type: &'t str, broker: &'t str) -> Self {
|
||||
Self {
|
||||
network,
|
||||
device_type,
|
||||
broker,
|
||||
device_id: None,
|
||||
last_will: None,
|
||||
username: None,
|
||||
password: None,
|
||||
subscriptions: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, T: Deref<Target = str> + 't, L: Publishable + 't, const S: usize>
|
||||
McutieBuilder<'t, T, L, S>
|
||||
{
|
||||
/// Add some default topics to subscribe to.
|
||||
pub fn with_subscriptions<const N: usize>(
|
||||
self,
|
||||
subscriptions: [Topic<T>; N],
|
||||
) -> McutieBuilder<'t, T, L, N> {
|
||||
McutieBuilder {
|
||||
network: self.network,
|
||||
device_type: self.device_type,
|
||||
broker: self.broker,
|
||||
device_id: self.device_id,
|
||||
last_will: self.last_will,
|
||||
username: self.username,
|
||||
password: self.password,
|
||||
subscriptions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, T: Deref<Target = str> + 't, L: Publishable + 't, const S: usize>
|
||||
McutieBuilder<'t, T, L, S>
|
||||
{
|
||||
/// Adds authentication for the broker.
|
||||
pub fn with_authentication(self, username: &'t str, password: &'t str) -> Self {
|
||||
Self {
|
||||
username: Some(username),
|
||||
password: Some(password),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a last will message to be published in the event of disconnection.
|
||||
pub fn with_last_will(self, last_will: L) -> Self {
|
||||
Self {
|
||||
last_will: Some(last_will),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a custom unique device identifier. If none is set then the network
|
||||
/// MAC address is used.
|
||||
pub fn with_device_id(self, device_id: &'t str) -> Self {
|
||||
Self {
|
||||
device_id: Some(device_id),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialises the MQTT stack returning a receiver for listening to
|
||||
/// messages from the broker and a future that must be run in order for the
|
||||
/// stack to operate.
|
||||
pub fn build(self, keep_alive: u16) -> (McutieReceiver, McutieTask<'t, T, L, S>) {
|
||||
let mut dtype = String::<32>::new();
|
||||
dtype.push_str(self.device_type).unwrap();
|
||||
DEVICE_TYPE.set(dtype).unwrap();
|
||||
|
||||
let mut did = String::<32>::new();
|
||||
if let Some(device_id) = self.device_id {
|
||||
did.push_str(device_id).unwrap();
|
||||
} else if let HardwareAddress::Ethernet(address) = self.network.hardware_address() {
|
||||
let mut buffer = [0_u8; 12];
|
||||
hex::encode_to_slice(address.as_bytes(), &mut buffer).unwrap();
|
||||
did.push_str(str::from_utf8(&buffer).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
DEVICE_ID.set(did).unwrap();
|
||||
|
||||
(
|
||||
McutieReceiver {},
|
||||
McutieTask {
|
||||
network: self.network,
|
||||
broker: self.broker,
|
||||
last_will: self.last_will,
|
||||
username: self.username,
|
||||
password: self.password,
|
||||
subscriptions: self.subscriptions,
|
||||
keep_alive
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user