parse_rc_ibus/
lib.rs

1//! # parse_rc_ibus
2//!
3//! A crate meant to make parsing FlySky IBUS packets easy.
4//!
5//! FlySky IBUS is a serial communications protocol that contains 14 channels of data and a
6//! checksum. Many of their receivers output this protocol.
7
8#![no_std]
9
10use core::error::Error;
11use core::fmt;
12
13#[cfg(feature = "defmt")]
14use defmt::Format;
15
16#[derive(Debug, Clone, Copy)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct IbusPacket {
19    channels: [u16; 14],
20}
21
22impl IbusPacket {
23    /// Tries to create an IBUS packet from a group of 32 bytes.
24    ///
25    /// Will return an Err value if:
26    /// - The lead byte is not `0x20`
27    /// - The second byte is not `0x40`
28    /// - The checksum fails  
29    pub fn try_from_bytes(bytes: &[u8; 32]) -> Result<Self, ParsingError> {
30        if bytes[0] != 0x20 || bytes[1] != 0x40 {
31            return Err(ParsingError::InvalidPacket);
32        }
33
34        let mut channels = [0u16; 14];
35
36        let mut channels_iter = bytes[2..30].iter();
37        let mut channel_sum = 0u16;
38        for idx in 0..14 {
39            let low_byte = *channels_iter.next().unwrap();
40            let high_byte = *channels_iter.next().unwrap();
41            let channel = ((high_byte as u16) << 8) | low_byte as u16;
42            channel_sum += low_byte as u16 + high_byte as u16;
43            channels[idx] = channel;
44        }
45
46        channel_sum += bytes[1] as u16 + bytes[0] as u16;
47        let calculated_checksum: u16 = (0xFFFF as u16) - channel_sum;
48        let actual_checksum = ((bytes[31] as u16) << 8) | bytes[30] as u16;
49        if calculated_checksum == actual_checksum {
50            Ok(IbusPacket { channels })
51        } else {
52            return Err(ParsingError::FailsChecksum);
53        }
54    }
55
56    /// Gets an individual channel's data, which is a value between 1000 and 2000. There are 14
57    /// channels, but only some of them will have data that changes from 1500 (the
58    /// default for inactive channels). Returns None if you select a channel out of range.
59    /// Starts indexing at 1
60    pub fn get_channel(&self, number: usize) -> Option<&u16> {
61        self.channels.get(number - 1)
62    }
63    pub fn get_all_channels(&self) -> [u16; 14] {
64        self.channels
65    }
66}
67
68#[derive(Debug, Clone, Copy)]
69#[cfg_attr(feature = "defmt", derive(defmt::Format))]
70pub enum ParsingError {
71    InvalidPacket,
72    FailsChecksum,
73}
74
75impl Error for ParsingError {}
76
77impl fmt::Display for ParsingError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            &ParsingError::InvalidPacket => write!(f, "Parsing Error: Packet not valid")?,
81            &ParsingError::FailsChecksum => write!(
82                f,
83                "Parsing Error: Packet fails checksum and should not be used"
84            )?,
85        }
86
87        Ok(())
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn works_with_correct_packet() {
97        let data: [u8; 32] = [
98            0x20, 0x40, 0xDB, 0x05, 0xDC, 0x05, 0x54, 0x05, 0xDC, 0x05, 0xE8, 0x03, 0xD0, 0x07,
99            0xD2, 0x05, 0xE8, 0x03, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05,
100            0xDC, 0x05, 0xDA, 0xF3,
101        ];
102
103        let packet = IbusPacket::try_from_bytes(&data).expect("Should be valid packet");
104
105        assert_eq!(*packet.get_channel(3).unwrap(), 1364u16);
106    }
107
108    #[test]
109    fn fails_invalid_packet() {
110        let data: [u8; 32] = [0x02; 32];
111
112        let packet = IbusPacket::try_from_bytes(&data);
113
114        assert!(matches!(packet, Err(ParsingError::InvalidPacket)));
115    }
116
117    #[test]
118    fn fails_bad_checksum() {
119        let data: [u8; 32] = [
120            0x20, 0x40, 0xDB, 0x05, 0xDC, 0x05, 0x54, 0x05, 0xDC, 0x05, 0xE8, 0x03, 0xD0, 0x07,
121            0xD2, 0x05, 0xE8, 0x03, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05, 0xDC, 0x05,
122            0xDC, 0x05, 0xDA, 0xFF,
123        ];
124
125        let packet = IbusPacket::try_from_bytes(&data);
126
127        assert!(matches!(packet, Err(ParsingError::FailsChecksum)));
128    }
129}