1use super::spi;
20
21pub const SYNC_BYTE_0: u8 = 0xa5;
23
24pub const SYNC_BYTE_1: u8 = 0xc0;
26
27pub const HEAD_METADATA: usize = 9;
29
30pub const TAIL_METADATA: usize = 14;
32
33pub const METRIC_LEN: usize = 2;
35
36pub const BUFFER_LEN: usize = 4096;
38
39pub const MAX_PAYLOAD: usize = 255;
41
42pub const LORA_MULTI_MODEM_MAX: u8 = 15;
44
45pub const LORA_STD_MODEM: u8 = 16;
47
48pub const FSK_MODEM: u8 = 17;
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum Modem {
54 LoraMulti(u8),
56 LoraStandard,
58 Fsk,
60 Unknown(u8),
62}
63
64impl Modem {
65 #[must_use]
85 pub const fn of(id: u8) -> Modem {
86 match id {
87 0..=LORA_MULTI_MODEM_MAX => Modem::LoraMulti(id),
88 LORA_STD_MODEM => Modem::LoraStandard,
89 FSK_MODEM => Modem::Fsk,
90 other => Modem::Unknown(other),
91 }
92 }
93
94 #[must_use]
100 pub const fn is_lora(&self) -> bool {
101 matches!(self, Modem::LoraMulti(_) | Modem::LoraStandard)
102 }
103}
104
105#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
107pub struct Quality {
108 pub crc_error: bool,
110 pub sync_error: bool,
112 pub header_error: bool,
114 pub timing_set: bool,
116}
117
118impl Quality {
119 #[must_use]
126 pub const fn is_clean(&self) -> bool {
127 !self.crc_error && !self.sync_error && !self.header_error
128 }
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
133pub struct Packet<'a> {
134 pub payload: &'a [u8],
136 pub channel: u8,
138 pub modem: Modem,
140 pub datarate: u8,
142 pub coding_rate: u8,
144 pub crc_enabled: bool,
146 pub quality: Quality,
148 pub frequency_offset: i32,
150 pub snr_average: i8,
152 pub rssi_channel: u8,
154 pub rssi_signal: u8,
156 pub timestamp: u32,
158 pub payload_crc: u16,
160 pub timing_metrics: u8,
162 pub checksum_ok: bool,
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub enum RxError {
169 NotAPacket,
171 Truncated {
173 needs: usize,
175 has: usize,
177 },
178}
179
180impl core::fmt::Display for RxError {
181 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182 match self {
183 RxError::NotAPacket => f.write_str("these bytes do not begin a packet"),
184 RxError::Truncated { needs, has } => write!(
185 f,
186 "the packet needs {needs} bytes and the buffer has {has} left"
187 ),
188 }
189 }
190}
191
192#[must_use]
216pub const fn packet_len(payload_len: usize, metrics: u8) -> usize {
217 HEAD_METADATA + payload_len + TAIL_METADATA + (METRIC_LEN * metrics as usize)
218}
219
220pub fn parse(buffer: &[u8]) -> Result<(Packet<'_>, usize), RxError> {
254 if buffer.len() < 2 || buffer[0] != SYNC_BYTE_0 || buffer[1] != SYNC_BYTE_1 {
255 return Err(RxError::NotAPacket);
256 }
257 if buffer.len() < HEAD_METADATA {
258 return Err(RxError::Truncated {
259 needs: HEAD_METADATA,
260 has: buffer.len(),
261 });
262 }
263
264 let payload_len = buffer[2] as usize;
266 let tail_at = payload_len;
267
268 let counted = tail_at + 21;
270 if buffer.len() <= counted {
271 return Err(RxError::Truncated {
272 needs: counted + 1,
273 has: buffer.len(),
274 });
275 }
276 let metrics = buffer[counted];
277
278 let needs = packet_len(payload_len, metrics);
279 if buffer.len() < needs {
280 return Err(RxError::Truncated {
281 needs,
282 has: buffer.len(),
283 });
284 }
285
286 let head = buffer[4];
287 let status = buffer[tail_at + 9];
288
289 let offset = i32::from(buffer[6])
291 | (i32::from(buffer[7]) << 8)
292 | (i32::from(spi::field(buffer[8], 0, 4)) << 16);
293 let frequency_offset = if offset >= (1 << 19) {
294 offset - (1 << 20)
295 } else {
296 offset
297 };
298
299 let timestamp = u32::from(buffer[tail_at + 15])
300 | (u32::from(buffer[tail_at + 16]) << 8)
301 | (u32::from(buffer[tail_at + 17]) << 16)
302 | (u32::from(buffer[tail_at + 18]) << 24);
303
304 let payload_crc = u16::from(buffer[tail_at + 19]) | (u16::from(buffer[tail_at + 20]) << 8);
305
306 let written = buffer[needs - 1];
308 let summed = buffer[..needs - 1]
309 .iter()
310 .fold(0u8, |sum, byte| sum.wrapping_add(*byte));
311
312 let packet = Packet {
313 payload: &buffer[HEAD_METADATA..HEAD_METADATA + payload_len],
314 channel: buffer[3],
315 modem: Modem::of(buffer[5]),
316 datarate: spi::field(head, 4, 4),
317 coding_rate: spi::field(head, 1, 3),
318 crc_enabled: spi::field(head, 0, 1) == 1,
319 quality: Quality {
320 crc_error: spi::field(status, 0, 1) == 1,
321 sync_error: spi::field(status, 2, 1) == 1,
322 header_error: spi::field(status, 3, 1) == 1,
323 timing_set: spi::field(status, 4, 1) == 1,
324 },
325 frequency_offset,
326 snr_average: buffer[tail_at + 10] as i8,
327 rssi_channel: buffer[tail_at + 11],
328 rssi_signal: buffer[tail_at + 12],
329 timestamp,
330 payload_crc,
331 timing_metrics: metrics,
332 checksum_ok: written == summed,
333 };
334
335 Ok((packet, needs))
336}
337
338pub fn packets(buffer: &[u8]) -> impl Iterator<Item = Packet<'_>> {
367 let mut at = 0usize;
368 core::iter::from_fn(move || match parse(buffer.get(at..)?) {
369 Ok((packet, took)) => {
370 at += took;
371 Some(packet)
372 }
373 Err(_) => None,
374 })
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 fn written(
383 payload: &[u8],
384 channel: u8,
385 modem: u8,
386 head: u8,
387 status: u8,
388 metrics: u8,
389 ) -> Vec<u8> {
390 let mut buffer = vec![
391 SYNC_BYTE_0,
392 SYNC_BYTE_1,
393 payload.len() as u8,
394 channel,
395 head,
396 modem,
397 0,
398 0,
399 0,
400 ];
401 buffer.extend_from_slice(payload);
402 buffer.extend_from_slice(&[0u8; TAIL_METADATA]);
403 buffer.extend_from_slice(&vec![0u8; METRIC_LEN * metrics as usize]);
404
405 let tail_at = payload.len();
407 buffer[tail_at + 9] = status;
408 buffer[tail_at + 21] = metrics;
409
410 let last = buffer.len() - 1;
412 let sum = buffer[..last]
413 .iter()
414 .fold(0u8, |sum, byte| sum.wrapping_add(*byte));
415 buffer[last] = sum;
416 buffer
417 }
418
419 #[test]
420 fn a_packet_reads_back_the_way_the_chip_wrote_it() {
421 let buffer = written(b"21.5", 2, 3, 0b0111_0001, 0, 0);
422 let (packet, took) = parse(&buffer).expect("it begins with the sync word");
423
424 assert_eq!(packet.payload, b"21.5");
425 assert_eq!(packet.channel, 2);
426 assert_eq!(packet.modem, Modem::LoraMulti(3));
427 assert_eq!(packet.datarate, 7);
428 assert_eq!(packet.coding_rate, 0);
429 assert!(packet.crc_enabled);
430 assert!(packet.quality.is_clean());
431 assert!(packet.checksum_ok);
432 assert_eq!(took, packet_len(4, 0));
433 }
434
435 #[test]
436 fn the_modem_and_the_frequency_offset_are_different_bytes() {
437 let mut buffer = written(b"x", 0, 7, 0, 0, 0);
439 buffer[6] = 0x34;
440 buffer[7] = 0x12;
441 buffer[8] = 0x00;
442 let last = buffer.len() - 1;
443 buffer[last] = buffer[..last]
444 .iter()
445 .fold(0u8, |sum, byte| sum.wrapping_add(*byte));
446
447 let (packet, _) = parse(&buffer).expect("a packet");
448 assert_eq!(packet.modem, Modem::LoraMulti(7), "the modem is byte five");
449 assert_eq!(
450 packet.frequency_offset, 0x1234,
451 "the offset starts at byte six"
452 );
453 }
454
455 #[test]
456 fn a_carrier_below_the_channel_reads_as_a_negative_offset() {
457 let mut buffer = written(b"x", 0, 0, 0, 0, 0);
459 buffer[6] = 0xff;
460 buffer[7] = 0xff;
461 buffer[8] = 0x0f;
462 let last = buffer.len() - 1;
463 buffer[last] = buffer[..last]
464 .iter()
465 .fold(0u8, |sum, byte| sum.wrapping_add(*byte));
466
467 let (packet, _) = parse(&buffer).expect("a packet");
468 assert_eq!(packet.frequency_offset, -1);
469 }
470
471 #[test]
472 fn the_tail_is_addressed_from_the_payload_length() {
473 for payload in [&b"x"[..], &b"a much longer payload than the first one"[..]] {
475 let mut buffer = written(payload, 0, 0, 0, 0, 0);
476 let tail_at = payload.len();
477 buffer[tail_at + 10] = 0xf6; buffer[tail_at + 11] = 200;
479 buffer[tail_at + 12] = 180;
480 buffer[tail_at + 15] = 0x78;
481 buffer[tail_at + 16] = 0x56;
482 buffer[tail_at + 17] = 0x34;
483 buffer[tail_at + 18] = 0x12;
484 let last = buffer.len() - 1;
485 buffer[last] = buffer[..last]
486 .iter()
487 .fold(0u8, |sum, byte| sum.wrapping_add(*byte));
488
489 let (packet, _) = parse(&buffer).expect("a packet");
490 assert_eq!(packet.snr_average, -10);
491 assert_eq!(packet.rssi_channel, 200);
492 assert_eq!(packet.rssi_signal, 180);
493 assert_eq!(packet.timestamp, 0x1234_5678);
494 }
495 }
496
497 #[test]
498 fn timestamp_metrics_move_where_the_next_packet_starts() {
499 let mut buffer = written(b"one", 0, 0, 0, 0, 4);
502 let first_len = buffer.len();
503 assert_eq!(first_len, packet_len(3, 4));
504
505 buffer.extend_from_slice(&written(b"two", 0, 0, 0, 0, 0));
506
507 let read: Vec<_> = packets(&buffer)
508 .map(|packet| packet.payload.to_vec())
509 .collect();
510 assert_eq!(read, [b"one".to_vec(), b"two".to_vec()]);
511
512 let (first, took) = parse(&buffer).expect("a packet");
513 assert_eq!(first.timing_metrics, 4);
514 assert_eq!(took, first_len);
515 }
516
517 #[test]
518 fn what_does_not_begin_with_the_sync_word_is_not_a_packet() {
519 assert_eq!(parse(&[]), Err(RxError::NotAPacket));
520 assert_eq!(parse(&[0u8; 64]), Err(RxError::NotAPacket));
521 assert_eq!(parse(&[0xa5, 0x00, 0, 0]), Err(RxError::NotAPacket));
522 }
523
524 #[test]
525 fn a_packet_cut_short_says_how_much_it_needed() {
526 let buffer = written(b"a longer payload than the buffer holds", 0, 0, 0, 0, 0);
527 let cut = &buffer[..20];
528 let Err(RxError::Truncated { has, .. }) = parse(cut) else {
529 panic!("a cut packet is truncated");
530 };
531 assert_eq!(has, 20);
532
533 assert_eq!(
535 parse(&[SYNC_BYTE_0, SYNC_BYTE_1, 4]),
536 Err(RxError::Truncated {
537 needs: HEAD_METADATA,
538 has: 3
539 })
540 );
541 }
542
543 #[test]
544 fn the_error_flags_are_read_apart() {
545 let buffer = written(b"x", 0, 0, 0, 0b0000_0001, 0);
547 let crc = parse(&buffer).expect("a packet").0.quality;
548 assert!(crc.crc_error);
549 assert!(!crc.sync_error);
550 assert!(!crc.is_clean());
551
552 let buffer = written(b"x", 0, 0, 0, 0b0000_0100, 0);
553 let sync = parse(&buffer).expect("a packet").0.quality;
554 assert!(sync.sync_error);
555 assert!(!sync.crc_error);
556
557 let buffer = written(b"x", 0, 0, 0, 0b0000_1000, 0);
558 let header = parse(&buffer).expect("a packet").0.quality;
559 assert!(header.header_error);
560
561 let buffer = written(b"x", 0, 0, 0, 0b0001_0000, 0);
562 let timed = parse(&buffer).expect("a packet").0.quality;
563 assert!(timed.timing_set);
564 assert!(timed.is_clean(), "a timestamp is not a failure");
565 }
566
567 #[test]
568 fn a_packet_the_bus_corrupted_is_told_apart_from_one_the_air_did() {
569 let good = written(b"21.5", 1, 0, 0b0111_0001, 0, 0);
570 let (packet, _) = parse(&good).expect("a packet");
571 assert!(packet.checksum_ok, "the chip and the bus agree");
572 assert!(packet.quality.is_clean(), "and the air was clean");
573
574 let mut corrupted = good.clone();
576 corrupted[10] ^= 0x01;
577 let (packet, _) = parse(&corrupted).expect("a packet");
578 assert!(!packet.checksum_ok);
579 assert!(
580 packet.quality.is_clean(),
581 "the receiver still reports a clean reception"
582 );
583 }
584
585 #[test]
586 fn the_demodulators_are_told_apart() {
587 for id in 0..=LORA_MULTI_MODEM_MAX {
588 assert_eq!(Modem::of(id), Modem::LoraMulti(id));
589 assert!(Modem::of(id).is_lora());
590 }
591 assert_eq!(Modem::of(LORA_STD_MODEM), Modem::LoraStandard);
592 assert!(Modem::of(LORA_STD_MODEM).is_lora());
593
594 assert_eq!(Modem::of(FSK_MODEM), Modem::Fsk);
595 assert!(!Modem::of(FSK_MODEM).is_lora());
596
597 assert_eq!(Modem::of(200), Modem::Unknown(200));
598 assert!(!Modem::of(200).is_lora());
599 }
600
601 #[test]
602 fn the_buffer_is_walked_until_it_stops_being_packets() {
603 let mut buffer = Vec::new();
604 for payload in [&b"one"[..], &b"two"[..], &b"three"[..]] {
605 buffer.extend_from_slice(&written(payload, 0, 0, 0, 0, 0));
606 }
607 buffer.extend_from_slice(&[0u8; 64]);
608
609 let read: Vec<_> = packets(&buffer)
610 .map(|packet| packet.payload.to_vec())
611 .collect();
612 assert_eq!(read, [b"one".to_vec(), b"two".to_vec(), b"three".to_vec()]);
613
614 assert_eq!(packets(&[]).count(), 0);
615 }
616
617 #[test]
618 fn a_packet_takes_its_payload_plus_its_metadata() {
619 assert_eq!(packet_len(0, 0), HEAD_METADATA + TAIL_METADATA);
620 assert_eq!(packet_len(255, 0), HEAD_METADATA + 255 + TAIL_METADATA);
621 assert_eq!(packet_len(4, 3), HEAD_METADATA + 4 + TAIL_METADATA + 6);
622 assert!(packet_len(MAX_PAYLOAD, 0) < BUFFER_LEN);
623 }
624}