#![no_std]
#![doc = include_str!("../README.md")]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use core::{ops::Deref, str};
pub use buffer::Buffer;
use embassy_net::{HardwareAddress, Stack};
use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, once_lock::OnceLock,
};
use heapless::String;
pub use io::McutieTask;
pub use mqttrs::QoS;
use mqttrs::{Pid, SubscribeReturnCodes};
pub use publish::*;
pub use topic::Topic;
pub(crate) mod fmt;
mod buffer;
#[cfg(feature = "homeassistant")]
pub mod homeassistant;
mod io;
mod pipe;
mod publish;
mod topic;
const TOPIC_LENGTH: usize = 256;
const PAYLOAD_LENGTH: usize = 2048;
pub type TopicString = String<TOPIC_LENGTH>;
pub type Payload = Buffer<PAYLOAD_LENGTH>;
const DEFAULT_BACKOFF: u64 = 5000;
const RESET_BACKOFF: u64 = 200;
const CONFIRMATION_TIMEOUT: u64 = 2000;
static DATA_CHANNEL: Channel<CriticalSectionRawMutex, MqttMessage, 10> = Channel::new();
static DEVICE_TYPE: OnceLock<String<32>> = OnceLock::new();
static DEVICE_ID: OnceLock<String<32>> = OnceLock::new();
fn device_id() -> &'static str {
DEVICE_ID.try_get().unwrap()
}
fn device_type() -> &'static str {
DEVICE_TYPE.try_get().unwrap()
}
#[derive(Debug)]
pub enum Error {
IOError,
TimedOut,
TooLarge,
PacketError,
Invalid,
}
#[allow(clippy::large_enum_variant)]
pub enum MqttMessage {
Connected,
Publish(Topic<TopicString>, Payload),
Disconnected,
#[cfg(feature = "homeassistant")]
HomeAssistantOnline,
}
#[derive(Clone)]
enum ControlMessage {
Published(Pid),
Subscribed(Pid, SubscribeReturnCodes),
Unsubscribed(Pid),
}
pub struct McutieReceiver;
impl McutieReceiver {
pub async fn receive(&self) -> MqttMessage {
DATA_CHANNEL.receive().await
}
}
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> {
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>
{
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>
{
pub fn with_authentication(self, username: &'t str, password: &'t str) -> Self {
Self {
username: Some(username),
password: Some(password),
..self
}
}
pub fn with_last_will(self, last_will: L) -> Self {
Self {
last_will: Some(last_will),
..self
}
}
pub fn with_device_id(self, device_id: &'t str) -> Self {
Self {
device_id: Some(device_id),
..self
}
}
pub fn build(self) -> (McutieReceiver, McutieTask<'t, T, L, S>) {
let mut dtype = String::<32>::new();
dtype.push_str(self.device_type).unwrap();
DEVICE_TYPE.init(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.init(did).unwrap();
(
McutieReceiver {},
McutieTask {
network: self.network,
broker: self.broker,
last_will: self.last_will,
username: self.username,
password: self.password,
subscriptions: self.subscriptions,
},
)
}
}