donglora-protocol 1.1.0

DongLoRa wire protocol types and COBS framing — shared between firmware and host crates
Documentation
//! Radio chip identifier enum from `PROTOCOL.md §8`.
//!
//! Reported by the device in `GET_INFO.radio_chip_id` so the host can
//! make chip-specific decisions (bandwidth range, SF range, TX power
//! range, modulation support) without having to infer them from the
//! bitmap fields alone.

/// Radio chip identifier (u16, little-endian on the wire).
///
/// Values are assigned by `PROTOCOL.md §8`. Ranges are grouped by chip
/// family so future additions slot in next to their siblings.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u16)]
pub enum RadioChipId {
    /// Firmware could not identify its radio. Hosts should abort: the
    /// device will not function.
    Unknown = 0x0000,

    // Gen 2 sub-GHz
    Sx1261 = 0x0001,
    Sx1262 = 0x0002,
    Sx1268 = 0x0003,
    Llcc68 = 0x0004,

    // Gen 1 sub-GHz
    Sx1272 = 0x0010,
    Sx1276 = 0x0011,
    Sx1277 = 0x0012,
    Sx1278 = 0x0013,
    Sx1279 = 0x0014,

    // Gen 2 2.4 GHz
    Sx1280 = 0x0020,
    Sx1281 = 0x0021,

    // Gen 3 multi-band
    Lr1110 = 0x0030,
    Lr1120 = 0x0031,
    Lr1121 = 0x0032,

    // Gen 4 multi-band
    Lr2021 = 0x0040,
}

impl RadioChipId {
    /// Wire-form u16. Use this when encoding `GET_INFO`.
    pub const fn as_u16(self) -> u16 {
        self as u16
    }

    /// Parse a wire u16 into a known chip. Returns `None` for unassigned
    /// values; hosts receiving an unknown value should treat the device
    /// as unusable.
    pub const fn from_u16(v: u16) -> Option<Self> {
        Some(match v {
            0x0000 => Self::Unknown,
            0x0001 => Self::Sx1261,
            0x0002 => Self::Sx1262,
            0x0003 => Self::Sx1268,
            0x0004 => Self::Llcc68,
            0x0010 => Self::Sx1272,
            0x0011 => Self::Sx1276,
            0x0012 => Self::Sx1277,
            0x0013 => Self::Sx1278,
            0x0014 => Self::Sx1279,
            0x0020 => Self::Sx1280,
            0x0021 => Self::Sx1281,
            0x0030 => Self::Lr1110,
            0x0031 => Self::Lr1120,
            0x0032 => Self::Lr1121,
            0x0040 => Self::Lr2021,
            _ => return None,
        })
    }
}

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

    #[test]
    fn all_assigned_values_roundtrip() {
        let all = [
            RadioChipId::Unknown,
            RadioChipId::Sx1261,
            RadioChipId::Sx1262,
            RadioChipId::Sx1268,
            RadioChipId::Llcc68,
            RadioChipId::Sx1272,
            RadioChipId::Sx1276,
            RadioChipId::Sx1277,
            RadioChipId::Sx1278,
            RadioChipId::Sx1279,
            RadioChipId::Sx1280,
            RadioChipId::Sx1281,
            RadioChipId::Lr1110,
            RadioChipId::Lr1120,
            RadioChipId::Lr1121,
            RadioChipId::Lr2021,
        ];
        for id in all {
            assert_eq!(RadioChipId::from_u16(id.as_u16()), Some(id));
        }
    }

    #[test]
    fn unassigned_values_reject() {
        for bad in [0x0005u16, 0x0015, 0x0022, 0x0033, 0x0041, 0xFFFF] {
            assert_eq!(RadioChipId::from_u16(bad), None);
        }
    }

    #[test]
    fn canonical_values_from_spec() {
        // Direct quote from §8 — any renumbering breaks the wire contract.
        assert_eq!(RadioChipId::Sx1262.as_u16(), 0x0002);
        assert_eq!(RadioChipId::Llcc68.as_u16(), 0x0004);
        assert_eq!(RadioChipId::Sx1280.as_u16(), 0x0020);
        assert_eq!(RadioChipId::Lr2021.as_u16(), 0x0040);
    }
}