uf_crsf/packets/
gps_time.rs

1use crate::packets::CrsfPacket;
2use crate::packets::PacketType;
3use crate::CrsfParsingError;
4
5/// Represents a GPS Time packet.
6///
7/// This frame is needed for synchronization with a GPS time pulse.
8/// The maximum offset of time is +/-10ms.
9#[derive(Clone, Debug, PartialEq)]
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11pub struct GpsTime {
12    pub year: i16,
13    pub month: u8,
14    pub day: u8,
15    pub hour: u8,
16    pub minute: u8,
17    pub second: u8,
18    pub millisecond: u16,
19}
20
21impl GpsTime {
22    pub fn new(
23        year: i16,
24        month: u8,
25        day: u8,
26        hour: u8,
27        minute: u8,
28        second: u8,
29        millisecond: u16,
30    ) -> Result<Self, CrsfParsingError> {
31        Ok(Self {
32            year,
33            month,
34            day,
35            hour,
36            minute,
37            second,
38            millisecond,
39        })
40    }
41}
42
43impl CrsfPacket for GpsTime {
44    const PACKET_TYPE: PacketType = PacketType::GpsTime;
45    const MIN_PAYLOAD_SIZE: usize = size_of::<i16>() + 5 * size_of::<u8>() + size_of::<u16>();
46
47    fn to_bytes(&self, buffer: &mut [u8]) -> Result<usize, CrsfParsingError> {
48        self.validate_buffer_size(buffer)?;
49        buffer[0..2].copy_from_slice(&self.year.to_be_bytes());
50        buffer[2] = self.month;
51        buffer[3] = self.day;
52        buffer[4] = self.hour;
53        buffer[5] = self.minute;
54        buffer[6] = self.second;
55        buffer[7..9].copy_from_slice(&self.millisecond.to_be_bytes());
56        Ok(Self::MIN_PAYLOAD_SIZE)
57    }
58
59    fn from_bytes(data: &[u8]) -> Result<Self, CrsfParsingError> {
60        if data.len() != Self::MIN_PAYLOAD_SIZE {
61            return Err(CrsfParsingError::InvalidPayloadLength);
62        }
63
64        Ok(Self {
65            year: i16::from_be_bytes(data[0..2].try_into().unwrap()),
66            month: data[2],
67            day: data[3],
68            hour: data[4],
69            minute: data[5],
70            second: data[6],
71            millisecond: u16::from_be_bytes(data[7..9].try_into().unwrap()),
72        })
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_gps_time_to_bytes() {
82        assert_eq!(GpsTime::MIN_PAYLOAD_SIZE, 9);
83        let gps_time = GpsTime {
84            year: 2024,
85            month: 10,
86            day: 27,
87            hour: 12,
88            minute: 34,
89            second: 56,
90            millisecond: 789,
91        };
92
93        let mut buffer = [0u8; GpsTime::MIN_PAYLOAD_SIZE];
94        gps_time.to_bytes(&mut buffer).unwrap();
95
96        let expected_bytes: [u8; GpsTime::MIN_PAYLOAD_SIZE] =
97            [0x07, 0xe8, 0x0a, 0x1b, 0x0c, 0x22, 0x38, 0x03, 0x15];
98
99        assert_eq!(buffer, expected_bytes);
100    }
101
102    #[test]
103    fn test_gps_time_from_bytes() {
104        let data: [u8; GpsTime::MIN_PAYLOAD_SIZE] =
105            [0x07, 0xe8, 0x0a, 0x1b, 0x0c, 0x22, 0x38, 0x03, 0x15];
106
107        let gps_time = GpsTime::from_bytes(&data).unwrap();
108
109        assert_eq!(
110            gps_time,
111            GpsTime {
112                year: 2024,
113                month: 10,
114                day: 27,
115                hour: 12,
116                minute: 34,
117                second: 56,
118                millisecond: 789,
119            }
120        );
121    }
122
123    #[test]
124    fn test_gps_time_round_trip() {
125        let gps_time = GpsTime {
126            year: 2024,
127            month: 10,
128            day: 27,
129            hour: 12,
130            minute: 34,
131            second: 56,
132            millisecond: 789,
133        };
134
135        let mut buffer = [0u8; GpsTime::MIN_PAYLOAD_SIZE];
136        gps_time.to_bytes(&mut buffer).unwrap();
137
138        let round_trip_gps_time = GpsTime::from_bytes(&buffer).unwrap();
139
140        assert_eq!(gps_time, round_trip_gps_time);
141    }
142
143    #[test]
144    fn test_edge_cases() {
145        let gps_time = GpsTime {
146            year: -1,
147            month: 1,
148            day: 1,
149            hour: 0,
150            minute: 0,
151            second: 0,
152            millisecond: 65535,
153        };
154
155        let mut buffer = [0u8; GpsTime::MIN_PAYLOAD_SIZE];
156        gps_time.to_bytes(&mut buffer).unwrap();
157        let round_trip_gps_time = GpsTime::from_bytes(&buffer).unwrap();
158        assert_eq!(gps_time, round_trip_gps_time);
159    }
160
161    #[test]
162    fn test_to_bytes_buffer_too_small() {
163        let gps_time = GpsTime {
164            year: -1,
165            month: 1,
166            day: 1,
167            hour: 0,
168            minute: 0,
169            second: 0,
170            millisecond: 65535,
171        };
172        let mut buffer: [u8; 8] = [0; 8];
173        let result = gps_time.to_bytes(&mut buffer);
174        assert!(matches!(result, Err(CrsfParsingError::BufferOverflow)));
175    }
176
177    #[test]
178    fn test_from_bytes_too_small() {
179        let data: [u8; 8] = [0; 8];
180        let result = GpsTime::from_bytes(&data);
181        assert_eq!(result, Err(CrsfParsingError::InvalidPayloadLength));
182    }
183}