1use core::fmt;
34
35use embedded_hal::delay::DelayNs;
36use embedded_hal::digital::{InputPin, OutputPin};
37use embedded_hal::spi::SpiDevice;
38use pamoja_lora::budget::Decibels;
39use pamoja_lora::LinkSettings;
40
41use crate::sx126x::{self, Sx126x};
42use crate::sx127x::{self, Sx127x};
43
44const SX127X_LAST_REGISTER: u16 = 0x7F;
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub enum Family {
51 Sx126x,
53 Sx127x,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
74pub enum SyncWord {
75 Public,
77 Private,
79 Custom(u8),
81}
82
83impl SyncWord {
84 pub const fn to_byte(self) -> u8 {
90 match self {
91 SyncWord::Public => 0x34,
92 SyncWord::Private => 0x12,
93 SyncWord::Custom(byte) => byte,
94 }
95 }
96
97 pub const fn from_byte(byte: u8) -> SyncWord {
108 match byte {
109 0x34 => SyncWord::Public,
110 0x12 => SyncWord::Private,
111 other => SyncWord::Custom(other),
112 }
113 }
114
115 pub const fn sx126x(self) -> sx126x::config::SyncWord {
121 match self {
122 SyncWord::Public => sx126x::config::SyncWord::Public,
123 SyncWord::Private => sx126x::config::SyncWord::Private,
124 SyncWord::Custom(byte) => {
125 let high = (byte >> 4) as u16;
126 let low = (byte & 0x0F) as u16;
127 sx126x::config::SyncWord::Custom((high << 12) | 0x0400 | (low << 4) | 0x0004)
128 }
129 }
130 }
131
132 pub const fn sx127x(self) -> sx127x::config::SyncWord {
138 match self {
139 SyncWord::Public => sx127x::config::SyncWord::Public,
140 SyncWord::Private => sx127x::config::SyncWord::Private,
141 SyncWord::Custom(byte) => sx127x::config::SyncWord::Custom(byte),
142 }
143 }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub struct RadioConfig {
169 pub frequency_hz: u32,
171 pub band_hz: (u32, u32),
174 pub link: LinkSettings,
176 pub output_dbm: i8,
178 pub sync_word: SyncWord,
180 pub invert_iq_transmit: bool,
182 pub invert_iq_receive: bool,
184}
185
186impl RadioConfig {
187 pub const fn new(frequency_hz: u32, link: LinkSettings, output_dbm: i8) -> RadioConfig {
199 RadioConfig {
200 frequency_hz,
201 band_hz: (frequency_hz, frequency_hz),
202 link,
203 output_dbm,
204 sync_word: SyncWord::Private,
205 invert_iq_transmit: false,
206 invert_iq_receive: false,
207 }
208 }
209
210 pub const fn with_band(mut self, low_hz: u32, high_hz: u32) -> RadioConfig {
222 self.band_hz = (low_hz, high_hz);
223 self
224 }
225
226 pub const fn with_sync_word(mut self, sync_word: SyncWord) -> RadioConfig {
236 self.sync_word = sync_word;
237 self
238 }
239
240 pub const fn with_inverted_iq(mut self, transmit: bool, receive: bool) -> RadioConfig {
251 self.invert_iq_transmit = transmit;
252 self.invert_iq_receive = receive;
253 self
254 }
255
256 pub const fn lorawan_device(self) -> RadioConfig {
263 self.with_sync_word(SyncWord::Public)
264 .with_inverted_iq(false, true)
265 }
266}
267
268#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
270pub struct SignalLevels {
271 pub rssi_dbm: Decibels,
273 pub snr_db: Decibels,
275 pub signal_rssi_dbm: Decibels,
277}
278
279impl From<sx126x::status::PacketStatus> for SignalLevels {
280 fn from(status: sx126x::status::PacketStatus) -> SignalLevels {
281 SignalLevels {
282 rssi_dbm: status.rssi_dbm,
283 snr_db: status.snr_db,
284 signal_rssi_dbm: status.signal_rssi_dbm,
285 }
286 }
287}
288
289impl From<sx127x::status::PacketStatus> for SignalLevels {
290 fn from(status: sx127x::status::PacketStatus) -> SignalLevels {
291 SignalLevels {
292 rssi_dbm: status.rssi_dbm,
293 snr_db: status.snr_db,
294 signal_rssi_dbm: status.signal_rssi_dbm,
295 }
296 }
297}
298
299#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
301pub enum Reception {
302 Frame {
304 len: usize,
306 levels: SignalLevels,
308 },
309 Timeout,
311 Corrupt,
313}
314
315impl From<sx126x::Reception> for Reception {
316 fn from(reception: sx126x::Reception) -> Reception {
317 match reception {
318 sx126x::Reception::Frame { len, status } => Reception::Frame {
319 len,
320 levels: status.into(),
321 },
322 sx126x::Reception::Timeout => Reception::Timeout,
323 sx126x::Reception::Corrupt => Reception::Corrupt,
324 }
325 }
326}
327
328impl From<sx127x::Reception> for Reception {
329 fn from(reception: sx127x::Reception) -> Reception {
330 match reception {
331 sx127x::Reception::Frame { len, status } => Reception::Frame {
332 len,
333 levels: status.into(),
334 },
335 sx127x::Reception::Timeout => Reception::Timeout,
336 sx127x::Reception::Corrupt => Reception::Corrupt,
337 }
338 }
339}
340
341#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub enum RadioError<E> {
344 Sx126x(sx126x::RadioError<E>),
346 Sx127x(sx127x::RadioError<E>),
348 Address(u16),
350}
351
352impl<E: fmt::Debug> fmt::Display for RadioError<E> {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 match self {
355 RadioError::Sx126x(error) => error.fmt(f),
356 RadioError::Sx127x(error) => error.fmt(f),
357 RadioError::Address(address) => write!(
358 f,
359 "the SX127x has no register at {address:#06x}; its map ends at 0x7F"
360 ),
361 }
362 }
363}
364
365impl<E: fmt::Debug> core::error::Error for RadioError<E> {}
366
367pub enum Radio<SPI, BUSY, RESET, D> {
374 Sx126x(Sx126x<SPI, BUSY, RESET, D>),
376 Sx127x(Sx127x<SPI, RESET, D>),
378}
379
380impl<SPI, BUSY, RESET, D> From<Sx126x<SPI, BUSY, RESET, D>> for Radio<SPI, BUSY, RESET, D> {
381 fn from(radio: Sx126x<SPI, BUSY, RESET, D>) -> Self {
382 Radio::Sx126x(radio)
383 }
384}
385
386impl<SPI, BUSY, RESET, D> From<Sx127x<SPI, RESET, D>> for Radio<SPI, BUSY, RESET, D> {
387 fn from(radio: Sx127x<SPI, RESET, D>) -> Self {
388 Radio::Sx127x(radio)
389 }
390}
391
392impl<SPI, BUSY, RESET, D> Radio<SPI, BUSY, RESET, D> {
393 pub fn family(&self) -> Family {
399 match self {
400 Radio::Sx126x(_) => Family::Sx126x,
401 Radio::Sx127x(_) => Family::Sx127x,
402 }
403 }
404
405 pub fn link(&self) -> Option<LinkSettings> {
412 match self {
413 Radio::Sx126x(radio) => radio.config().map(|config| config.link),
414 Radio::Sx127x(radio) => radio.config().map(|config| config.link),
415 }
416 }
417}
418
419impl<SPI, BUSY, RESET, D> Radio<SPI, BUSY, RESET, D>
420where
421 SPI: SpiDevice,
422 BUSY: InputPin,
423 RESET: OutputPin,
424 D: DelayNs,
425{
426 pub fn init(&mut self) -> Result<(), RadioError<SPI::Error>> {
433 match self {
434 Radio::Sx126x(radio) => radio.init().map_err(RadioError::Sx126x),
435 Radio::Sx127x(radio) => radio.init().map_err(RadioError::Sx127x),
436 }
437 }
438
439 pub fn configure(&mut self, config: RadioConfig) -> Result<(), RadioError<SPI::Error>> {
453 match self {
454 Radio::Sx126x(radio) => {
455 let power = radio.tx_power(config.output_dbm);
456 let (low_hz, high_hz) = config.band_hz;
457 let settings = sx126x::RadioConfig::new(config.frequency_hz, config.link, power)
458 .with_band(low_hz, high_hz)
459 .with_sync_word(config.sync_word.sx126x())
460 .with_inverted_iq(config.invert_iq_transmit, config.invert_iq_receive);
461 radio.configure(settings).map_err(RadioError::Sx126x)
462 }
463 Radio::Sx127x(radio) => {
464 let power = radio.tx_power(config.output_dbm);
465 let settings = sx127x::RadioConfig::new(config.frequency_hz, config.link, power)
466 .with_sync_word(config.sync_word.sx127x())
467 .with_inverted_iq(config.invert_iq_transmit, config.invert_iq_receive);
468 radio.configure(settings).map_err(RadioError::Sx127x)
469 }
470 }
471 }
472
473 pub fn transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
488 match self {
489 Radio::Sx126x(radio) => radio.transmit(payload).map_err(RadioError::Sx126x),
490 Radio::Sx127x(radio) => radio.transmit(payload).map_err(RadioError::Sx127x),
491 }
492 }
493
494 pub fn start_transmit(&mut self, payload: &[u8]) -> Result<u64, RadioError<SPI::Error>> {
509 match self {
510 Radio::Sx126x(radio) => radio.start_transmit(payload).map_err(RadioError::Sx126x),
511 Radio::Sx127x(radio) => radio.start_transmit(payload).map_err(RadioError::Sx127x),
512 }
513 }
514
515 pub fn finish_transmit(&mut self) -> Result<bool, RadioError<SPI::Error>> {
525 match self {
526 Radio::Sx126x(radio) => radio.finish_transmit().map_err(RadioError::Sx126x),
527 Radio::Sx127x(radio) => radio.finish_transmit().map_err(RadioError::Sx127x),
528 }
529 }
530
531 pub fn receive(
548 &mut self,
549 buffer: &mut [u8],
550 timeout_us: u64,
551 ) -> Result<Reception, RadioError<SPI::Error>> {
552 match self {
553 Radio::Sx126x(radio) => radio
554 .receive(buffer, timeout_us)
555 .map(Reception::from)
556 .map_err(RadioError::Sx126x),
557 Radio::Sx127x(radio) => radio
558 .receive(buffer, timeout_us)
559 .map(Reception::from)
560 .map_err(RadioError::Sx127x),
561 }
562 }
563
564 pub fn listen(&mut self) -> Result<(), RadioError<SPI::Error>> {
570 match self {
571 Radio::Sx126x(radio) => radio.listen().map_err(RadioError::Sx126x),
572 Radio::Sx127x(radio) => radio.listen().map_err(RadioError::Sx127x),
573 }
574 }
575
576 pub fn take_frame(
590 &mut self,
591 buffer: &mut [u8],
592 ) -> Result<Option<Reception>, RadioError<SPI::Error>> {
593 match self {
594 Radio::Sx126x(radio) => radio
595 .take_frame(buffer)
596 .map(|taken| taken.map(Reception::from))
597 .map_err(RadioError::Sx126x),
598 Radio::Sx127x(radio) => radio
599 .take_frame(buffer)
600 .map(|taken| taken.map(Reception::from))
601 .map_err(RadioError::Sx127x),
602 }
603 }
604
605 pub fn standby(&mut self) -> Result<(), RadioError<SPI::Error>> {
611 match self {
612 Radio::Sx126x(radio) => radio.standby().map_err(RadioError::Sx126x),
613 Radio::Sx127x(radio) => radio.standby().map_err(RadioError::Sx127x),
614 }
615 }
616
617 pub fn sleep(&mut self) -> Result<(), RadioError<SPI::Error>> {
627 match self {
628 Radio::Sx126x(radio) => radio.sleep(true).map_err(RadioError::Sx126x),
629 Radio::Sx127x(radio) => radio.sleep().map_err(RadioError::Sx127x),
630 }
631 }
632
633 pub fn read_register(&mut self, address: u16) -> Result<u8, RadioError<SPI::Error>> {
649 match self {
650 Radio::Sx126x(radio) => {
651 let mut value = [0u8; 1];
652 radio
653 .read_register(address, &mut value)
654 .map_err(RadioError::Sx126x)?;
655 Ok(value[0])
656 }
657 Radio::Sx127x(radio) => radio
658 .read_register(sx127x_address(address)?)
659 .map_err(RadioError::Sx127x),
660 }
661 }
662
663 pub fn write_register(
676 &mut self,
677 address: u16,
678 value: u8,
679 ) -> Result<(), RadioError<SPI::Error>> {
680 match self {
681 Radio::Sx126x(radio) => radio
682 .write_register(address, &[value])
683 .map_err(RadioError::Sx126x),
684 Radio::Sx127x(radio) => radio
685 .write_register(sx127x_address(address)?, value)
686 .map_err(RadioError::Sx127x),
687 }
688 }
689}
690
691fn sx127x_address<E>(address: u16) -> Result<u8, RadioError<E>> {
692 if address > SX127X_LAST_REGISTER {
693 return Err(RadioError::Address(address));
694 }
695 Ok(address as u8)
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use pamoja_hal::script::{DelayLog, PinScript, SpiScript, SpiStep};
702
703 type ScriptedRadio = Radio<SpiScript, PinScript, PinScript, DelayLog>;
704
705 fn sx126x_radio(steps: Vec<SpiStep>, board: sx126x::Board) -> ScriptedRadio {
706 let mut busy = PinScript::new([]);
707 busy.set_low().expect("a scripted line takes any level");
708 Radio::from(Sx126x::new(
709 SpiScript::new(steps),
710 busy,
711 PinScript::new([]),
712 DelayLog::new(),
713 board,
714 ))
715 }
716
717 fn sx127x_radio(steps: Vec<SpiStep>) -> ScriptedRadio {
718 Radio::from(Sx127x::new(
719 SpiScript::new(steps),
720 PinScript::new([]),
721 DelayLog::new(),
722 sx127x::Board::new(sx127x::config::PaOutput::PaBoost),
723 ))
724 }
725
726 fn done(radio: ScriptedRadio) -> bool {
727 match radio {
728 Radio::Sx126x(radio) => radio.release().0.done(),
729 Radio::Sx127x(radio) => radio.release().0.done(),
730 }
731 }
732
733 fn wrote(address: u8, values: &[u8]) -> [SpiStep; 2] {
734 [
735 SpiStep::write([address | 0x80]),
736 SpiStep::write(values.to_vec()),
737 ]
738 }
739
740 fn read(address: u8, values: &[u8]) -> [SpiStep; 2] {
741 [SpiStep::write([address]), SpiStep::read(values.to_vec())]
742 }
743
744 #[test]
745 fn a_sync_word_byte_takes_each_familys_layout() {
746 assert_eq!(
747 SyncWord::Public.sx126x().to_bytes(),
748 SyncWord::Custom(0x34).sx126x().to_bytes()
749 );
750 assert_eq!(
751 SyncWord::Private.sx126x().to_bytes(),
752 SyncWord::Custom(0x12).sx126x().to_bytes()
753 );
754 assert_eq!(SyncWord::Custom(0x2B).sx126x().to_bytes(), [0x24, 0xB4]);
755 assert_eq!(SyncWord::Custom(0x2B).sx127x().to_byte(), 0x2B);
756 for byte in [0x00, 0x12, 0x2B, 0x34, 0xFF] {
757 assert_eq!(SyncWord::from_byte(byte).to_byte(), byte);
758 }
759 }
760
761 #[test]
762 fn an_sx127x_config_reaches_the_registers_with_the_boards_output() {
763 let mut steps = Vec::new();
764 steps.extend(wrote(0x01, &[0x88]));
765 steps.extend(wrote(0x01, &[0x08]));
766 steps.extend(wrote(0x01, &[0x09]));
767 steps.extend(wrote(0x09, &[0x00]));
768 steps.extend(wrote(0x06, &[0xD9, 0x06, 0x66]));
769 steps.extend(read(0x3B, &[0x82]));
770 steps.extend(wrote(0x3B, &[0x42]));
771 steps.extend(read(0x3B, &[0x22]));
772 steps.extend(read(0x3B, &[0x02]));
773 steps.extend(wrote(0x01, &[0x08]));
774 steps.extend(wrote(0x01, &[0x88]));
775 steps.extend(wrote(0x01, &[0x89]));
776 steps.extend(wrote(0x09, &[0xFC]));
777 steps.extend(wrote(0x4D, &[0x84]));
778 steps.extend(wrote(0x0B, &[0x2B]));
779 steps.extend(wrote(0x1D, &[0x72]));
780 steps.extend(wrote(0x1E, &[0x74]));
781 steps.extend(wrote(0x26, &[0x04]));
782 steps.extend(wrote(0x20, &[0x00, 0x08]));
783 steps.extend(read(0x31, &[0xC3]));
784 steps.extend(wrote(0x31, &[0xC3]));
785 steps.extend(wrote(0x37, &[0x0A]));
786 steps.extend(wrote(0x36, &[0x03]));
787 steps.extend(wrote(0x23, &[0xFF]));
788 steps.extend(wrote(0x39, &[0x34]));
789 let mut radio = sx127x_radio(steps);
790
791 let config =
792 RadioConfig::new(868_100_000, LinkSettings::new(7, 125_000), 14).lorawan_device();
793 radio.configure(config).expect("configures");
794
795 assert_eq!(radio.link(), Some(LinkSettings::new(7, 125_000)));
796 assert!(done(radio));
797 }
798
799 #[test]
800 fn an_sx126x_config_sends_the_commands_and_the_spread_sync_word() {
801 use sx126x::command;
802 use sx126x::config::{self as chip, LoraModulation, PacketType, RampTime, StandbyMode};
803
804 let link = LinkSettings::new(7, 125_000);
805 let board = sx126x::Board::new(chip::PowerAmplifier::HighPower);
806 let power = chip::TxPower::for_output(chip::PowerAmplifier::HighPower, 14);
807 let modulation = LoraModulation::from_link(&link).expect("125 kHz is an SX126x bandwidth");
808 let image = chip::image_calibration(863_000_000, 870_000_000);
809 let steps = vec![
810 SpiStep::write(command::set_standby(StandbyMode::Rc).as_bytes().to_vec()),
811 SpiStep::write(
812 command::set_packet_type(PacketType::Lora)
813 .as_bytes()
814 .to_vec(),
815 ),
816 SpiStep::write(command::calibrate_image(image).as_bytes().to_vec()),
817 SpiStep::write(
818 command::set_rf_frequency(chip::frequency_word(868_100_000))
819 .as_bytes()
820 .to_vec(),
821 ),
822 SpiStep::write(command::set_pa_config(power.pa).as_bytes().to_vec()),
823 SpiStep::write(
824 command::set_tx_params(power.setting_dbm, RampTime::Us40)
825 .as_bytes()
826 .to_vec(),
827 ),
828 SpiStep::write(
829 command::set_lora_modulation_params(modulation)
830 .as_bytes()
831 .to_vec(),
832 ),
833 SpiStep::write([0x0D, 0x07, 0x40]),
834 SpiStep::write([0x24, 0xB4]),
835 ];
836 let mut radio = sx126x_radio(steps, board);
837
838 let config = RadioConfig::new(868_100_000, link, 14)
839 .with_band(863_000_000, 870_000_000)
840 .with_sync_word(SyncWord::Custom(0x2B));
841 radio.configure(config).expect("configures");
842
843 assert_eq!(radio.family(), Family::Sx126x);
844 assert!(done(radio));
845 }
846
847 #[test]
848 fn a_chip_refusal_arrives_wrapped_in_its_family() {
849 let board = sx126x::Board::new(sx126x::config::PowerAmplifier::HighPower).with_llcc68();
850 let mut radio = sx126x_radio(Vec::new(), board);
851 let config = RadioConfig::new(868_100_000, LinkSettings::new(10, 125_000), 14);
852
853 let refused = radio.configure(config);
854
855 assert_eq!(
856 refused,
857 Err(RadioError::Sx126x(sx126x::RadioError::Llcc68 {
858 spreading_factor: 10,
859 bandwidth_hz: 125_000,
860 }))
861 );
862 assert!(refused.expect_err("refused").to_string().contains("LLCC68"));
863 }
864
865 #[test]
866 fn nothing_is_sent_before_a_configuration() {
867 let mut radio = sx127x_radio(Vec::new());
868 assert_eq!(
869 radio.transmit(b"21.5"),
870 Err(RadioError::Sx127x(sx127x::RadioError::NotConfigured))
871 );
872 assert_eq!(radio.link(), None);
873 }
874
875 #[test]
876 fn registers_reach_either_family_and_the_sx127x_map_ends_at_0x7f() {
877 let mut steps = Vec::new();
878 steps.extend(wrote(0x39, &[0x2B]));
879 steps.extend(read(0x39, &[0x2B]));
880 let mut radio = sx127x_radio(steps);
881 radio.write_register(0x39, 0x2B).expect("writes");
882 assert_eq!(radio.read_register(0x39), Ok(0x2B));
883 assert_eq!(
884 radio.write_register(0x80, 0),
885 Err(RadioError::Address(0x80))
886 );
887 assert!(done(radio));
888
889 let board = sx126x::Board::new(sx126x::config::PowerAmplifier::HighPower);
890 let steps = vec![
891 SpiStep::write([0x1D, 0x07, 0x40, 0x00]),
892 SpiStep::read([0x14]),
893 ];
894 let mut radio = sx126x_radio(steps, board);
895 assert_eq!(radio.read_register(0x0740), Ok(0x14));
896 assert!(done(radio));
897 }
898}