monarch2 0.1.0-beta.1

A driver crate for the Sequans Monarch 2 Platform chips.
Documentation
use atat::{
    AtatLen,
    atat_derive::{AtatCmd, AtatEnum, AtatResp, AtatUrc},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};

pub mod coap;
pub mod device;
#[cfg(feature = "gm02sp")]
pub mod gnss;
pub mod manufacturing;
pub mod mobile_equipment;
pub mod mqtt;
pub mod network;
pub mod nvm;
pub mod pdp;
pub mod sim;
pub mod sms;
pub mod ssl_tls;
pub mod system_features;

#[derive(Clone, AtatResp)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NoResponse;

#[derive(Clone, AtatCmd)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[at_cmd("", NoResponse)]
pub struct AT;

#[derive(Debug, Clone, AtatUrc)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(clippy::large_enum_variant)]
pub enum Urc {
    #[cfg(feature = "gm02sp")]
    #[at_urc("+LPGNSSFIXREADY")]
    GnssFixReady(gnss::urc::GnssFixReady),

    #[at_urc("+SQNSMQTTONCONNECT")]
    MqttConnected(mqtt::urc::Connected),
    #[at_urc("+SQNSMQTTONDISCONNECT")]
    MqttDisconnected(mqtt::urc::Disconnected),
    #[at_urc("+SQNSMQTTONPUBLISH")]
    MqttMessagePublished(mqtt::urc::PublishResponse),
    #[at_urc("+SQNSMQTTONMESSAGE")]
    MqttMessageReceived(mqtt::urc::Received),
    #[at_urc("+SQNSMQTTONSUBSCRIBE")]
    MqttSubscribed(mqtt::urc::Subscribed),
    #[at_urc("+SQNSMQTTPUBLISH")]
    MqttPromptToPublish(mqtt::urc::PromptToPublish),

    /// The + SHUTDOWN URC indicates that the ME has completed the shutdown procedure and is about to restart.
    #[at_urc("+SHUTDOWN")]
    Shutdown,

    /// The +SYSSTART URC indicates that the ME has started (or restarted after a AT^ RESET) and is ready to operate.
    #[at_urc("+SYSSTART")]
    Start,

    #[at_urc("+CEREG")]
    NetworkRegistrationStatus(network::urc::NetworkRegistrationStatus),

    #[at_urc("+SQNCOAPCONNECTED")]
    CoapConnected(coap::urc::Connected),
}

/// Custom boolean needed for communication with the Sequans Monarch 2 chips.
/// The ATAT commands use 0 and 1 to represent booleans which isn't compatible
/// with atat and thus require custom implementation.
#[derive(Clone, Debug, PartialEq, AtatEnum, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[at_enum(u8)]
pub enum Bool {
    #[default]
    False = 0,
    True = 1,
}

impl From<bool> for Bool {
    fn from(b: bool) -> Self {
        if b { Bool::True } else { Bool::False }
    }
}

impl From<Bool> for bool {
    fn from(b: Bool) -> Self {
        b == Bool::True
    }
}

/// Used for reserved fields that are currently ignored but can't be skipped
/// during serialization.
#[derive(Clone, PartialEq, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Reserved;

impl Serialize for Reserved {
    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_bytes(&[])
    }
}

impl AtatLen for Reserved {
    // 0 would result in the field being completely omitted which is not what we want.
    const LEN: usize = 1;
}

impl<'de> Deserialize<'de> for Reserved {
    fn deserialize<D>(deserializer: D) -> Result<Reserved, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ReservedVisitor;

        impl<'de> de::Visitor<'de> for ReservedVisitor {
            type Value = Reserved;

            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
                formatter.write_str("a reserved field (ignored content)")
            }

            fn visit_bytes<E>(self, _v: &[u8]) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                Ok(Reserved)
            }
        }

        deserializer.deserialize_any(ReservedVisitor)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use atat::Parser;

    #[test]
    fn test_urc_parse() {
        let input = b"\r\n+LPGNSSFIXREADY: 0,\"2025-06-24T15:55:20.000000\",66563,\"20000000.000000\",\"0.000000\",\"0.000000\",\"0.000000\",\"0.000000\",\"0.000000\",\"0.000000\",\"+oyFVQ4AAADeYQAAAAAAAIADTG5IQAAAALCAxgJAAAAAAAAALkDoAwAAAwQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQEnNBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaMpaaAAAAAA=\"\r\n";
        let x = Urc::parse(input);
        assert_eq!(708, x.unwrap().1);
    }
}