Skip to main content

pamoja_radios/
sim.rs

1//! Simulated SX126x and SX127x chips, for running a radio program with no radio attached.
2//!
3//! A [`Chip`] answers its driver over SPI the way the part does. It takes the commands or
4//! the register writes that tune it, puts a frame on the air when it is told to transmit,
5//! and hands a frame over when one arrives while it listens. The program on the other side
6//! says what arrives, with [`Chip::hear`], and reads back what the driver tuned the chip to
7//! and what it sent, with [`Chip::tuning`] and [`Chip::sent`], so a program's radio settings
8//! are checked against what the chip was actually told rather than against the program's
9//! own intent.
10//!
11//! Nothing is timed. A transmission is done as soon as it starts, a reception with a timeout
12//! ends at once when nothing is waiting on the air, and one without a timeout listens until
13//! a frame arrives. The signal levels of a frame are the ones given to [`Chip::hear`],
14//! rounded to the half or whole decibel the chip reports them in.
15//!
16//! # Examples
17//!
18//! ```
19//! use pamoja_lora::budget::Decibels;
20//! use pamoja_lora::region::Region;
21//! use pamoja_radios::radio::{RadioConfig, Reception};
22//! use pamoja_radios::sim::Chip;
23//! use pamoja_radios::sx126x::config::PowerAmplifier;
24//! use pamoja_radios::sx126x::Board;
25//!
26//! // An SX1262 on the bench, and the driver wired to it.
27//! let chip = Chip::sx126x(Board::new(PowerAmplifier::HighPower));
28//! let mut radio = chip.radio();
29//! radio.init()?;
30//!
31//! // Tuned from the plan's DR3 at 868.1 MHz, a reading goes out.
32//! let link = Region::Eu868.plan().link_settings(3).expect("DR3 is a LoRa data rate");
33//! radio.configure(RadioConfig::new(868_100_000, link, 14))?;
34//! radio.transmit(b"21.5")?;
35//! let sent = &chip.sent()[0];
36//! assert_eq!(sent.tuning.frequency_hz, 868_100_000);
37//! assert_eq!(sent.tuning.link.spreading_factor(), 9);
38//! assert_eq!(sent.payload, b"21.5");
39//!
40//! // An answer arrives 2.5 dB under the noise.
41//! chip.hear(b"ok", Decibels::from_db(-109), Decibels::from_tenths(-25));
42//! let mut buffer = [0u8; 255];
43//! let Reception::Frame { len, levels } = radio.receive(&mut buffer, 1_000_000)? else {
44//!     panic!("a frame was waiting");
45//! };
46//! assert_eq!(&buffer[..len], b"ok");
47//! assert_eq!(levels.snr_db, Decibels::from_tenths(-25));
48//! # Ok::<(), pamoja_radios::radio::RadioError<core::convert::Infallible>>(())
49//! ```
50
51use std::collections::{BTreeMap, VecDeque};
52use std::convert::Infallible;
53use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
54use std::vec::Vec;
55
56use embedded_hal::delay::DelayNs;
57use embedded_hal::digital::{self, InputPin, OutputPin};
58use embedded_hal::spi::{self, Operation, SpiDevice};
59use pamoja_lora::budget::{self, Decibels, RADIO_NOISE_FIGURE_DB};
60use pamoja_lora::LinkSettings;
61
62use crate::radio::{Family, Radio, SyncWord};
63use crate::sx126x::command::opcode;
64use crate::sx126x::config::register as sx126x_register;
65use crate::sx126x::irq::Irq;
66use crate::sx127x::irq::IrqFlags;
67use crate::sx127x::register as sx127x_register;
68use crate::sx127x::status::Port;
69use crate::{sx126x, sx127x};
70
71/// A radio driver wired to a simulated chip.
72pub type SimRadio = Radio<ChipSpi, ChipPin, ChipPin, ChipDelay>;
73
74/// What a chip is tuned to: where it sends and listens, how, and how hard.
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct Tuning {
77    /// The carrier frequency in hertz, as the chip's synthesizer steps it: within a hertz of
78    /// the one asked for on an SX126x, and within 61 Hz on an SX127x.
79    pub frequency_hz: u32,
80    /// The spreading factor, bandwidth, coding rate, preamble, header, and CRC.
81    pub link: LinkSettings,
82    /// The output power the amplifier was asked for, in dBm.
83    pub output_dbm: i8,
84    /// The sync word.
85    pub sync_word: SyncWord,
86}
87
88/// A frame a simulated chip put on the air.
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Sent {
91    /// What the chip was tuned to when the frame went out.
92    pub tuning: Tuning,
93    /// The frame's payload.
94    pub payload: Vec<u8>,
95}
96
97/// A simulated SX126x or SX127x, shared between its driver and the program watching it.
98///
99/// Cloning a chip gives another handle on the same part.
100#[derive(Clone, Debug)]
101pub struct Chip {
102    state: Arc<Mutex<State>>,
103}
104
105impl Chip {
106    /// A simulated SX1261, SX1262, SX1268, or LLCC68, out of reset.
107    ///
108    /// # Arguments
109    ///
110    /// * `board` - how the module wires the chip, which the driver [`radio`](Chip::radio)
111    ///   returns is given.
112    ///
113    /// # Returns
114    ///
115    /// The chip.
116    pub fn sx126x(board: sx126x::Board) -> Chip {
117        Chip::with(Model::Sx126x(Sx126xModel::new(board)))
118    }
119
120    /// A simulated SX1276, SX1277, SX1278, or SX1279, out of reset.
121    ///
122    /// # Arguments
123    ///
124    /// * `board` - how the module wires the chip, which the driver [`radio`](Chip::radio)
125    ///   returns is given.
126    ///
127    /// # Returns
128    ///
129    /// The chip.
130    pub fn sx127x(board: sx127x::Board) -> Chip {
131        Chip::with(Model::Sx127x(Sx127xModel::new(board)))
132    }
133
134    fn with(model: Model) -> Chip {
135        Chip {
136            state: Arc::new(Mutex::new(State {
137                model,
138                air: VecDeque::new(),
139                sent: Vec::new(),
140                random: RANDOM_SEED,
141            })),
142        }
143    }
144
145    /// Returns which family the chip belongs to.
146    ///
147    /// # Returns
148    ///
149    /// The family.
150    pub fn family(&self) -> Family {
151        match self.lock().model {
152            Model::Sx126x(_) => Family::Sx126x,
153            Model::Sx127x(_) => Family::Sx127x,
154        }
155    }
156
157    /// Returns a driver wired to the chip, as a board wires a real one.
158    ///
159    /// # Returns
160    ///
161    /// The driver, which has sent nothing yet; [`init`](Radio::init) resets the chip.
162    pub fn radio(&self) -> SimRadio {
163        let spi = ChipSpi {
164            state: Arc::clone(&self.state),
165        };
166        let busy = ChipPin {
167            state: Arc::clone(&self.state),
168        };
169        let reset = ChipPin {
170            state: Arc::clone(&self.state),
171        };
172        match &self.lock().model {
173            Model::Sx126x(model) => Radio::from(sx126x::Sx126x::new(
174                spi,
175                busy,
176                reset,
177                ChipDelay,
178                model.board,
179            )),
180            Model::Sx127x(model) => {
181                Radio::from(sx127x::Sx127x::new(spi, reset, ChipDelay, model.board))
182            }
183        }
184    }
185
186    /// Puts a frame on the air for the chip to receive the next time it listens.
187    ///
188    /// # Arguments
189    ///
190    /// * `payload` - the frame's payload, at most 255 bytes; any more is cut off, as no LoRa
191    ///   frame carries it.
192    /// * `rssi_dbm` - the strength the chip hears the frame at.
193    /// * `snr_db` - the signal-to-noise ratio it hears it with, negative below the noise.
194    pub fn hear(&self, payload: &[u8], rssi_dbm: Decibels, snr_db: Decibels) {
195        let payload = payload[..payload.len().min(usize::from(u8::MAX))].to_vec();
196        self.lock().air.push_back(Arrival {
197            payload,
198            rssi_dbm,
199            snr_db,
200            intact: true,
201        });
202    }
203
204    /// Puts a frame on the air whose CRC fails, which the chip reports and drops.
205    ///
206    /// # Arguments
207    ///
208    /// * `rssi_dbm` - the strength the chip hears the frame at.
209    /// * `snr_db` - the signal-to-noise ratio it hears it with.
210    pub fn hear_corrupt(&self, rssi_dbm: Decibels, snr_db: Decibels) {
211        self.lock().air.push_back(Arrival {
212            payload: Vec::new(),
213            rssi_dbm,
214            snr_db,
215            intact: false,
216        });
217    }
218
219    /// Returns how many frames wait on the air for the chip to receive.
220    ///
221    /// # Returns
222    ///
223    /// The number of frames given to [`hear`](Chip::hear) and not yet received.
224    pub fn waiting(&self) -> usize {
225        self.lock().air.len()
226    }
227
228    /// Returns every frame the chip has put on the air, oldest first.
229    ///
230    /// # Returns
231    ///
232    /// The frames, each with what the chip was tuned to when it went out.
233    pub fn sent(&self) -> Vec<Sent> {
234        self.lock().sent.clone()
235    }
236
237    /// Returns what the chip is tuned to now.
238    ///
239    /// Before its driver configures it, these are the chip's own values out of reset. The
240    /// SX126x has no carrier until it is given one, and reports 0 Hz.
241    ///
242    /// # Returns
243    ///
244    /// The tuning.
245    pub fn tuning(&self) -> Tuning {
246        self.lock().model.tuning()
247    }
248
249    fn lock(&self) -> MutexGuard<'_, State> {
250        self.state.lock().unwrap_or_else(PoisonError::into_inner)
251    }
252}
253
254/// The SPI device of a simulated chip.
255#[derive(Debug)]
256pub struct ChipSpi {
257    state: Arc<Mutex<State>>,
258}
259
260impl spi::ErrorType for ChipSpi {
261    type Error = Infallible;
262}
263
264impl SpiDevice for ChipSpi {
265    fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Infallible> {
266        let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
267        state.transaction(operations);
268        Ok(())
269    }
270}
271
272/// A line of a simulated chip: BUSY, which never holds the driver up, and NRESET, which
273/// resets the chip when it is pulled low and released.
274#[derive(Debug)]
275pub struct ChipPin {
276    state: Arc<Mutex<State>>,
277}
278
279impl digital::ErrorType for ChipPin {
280    type Error = Infallible;
281}
282
283impl InputPin for ChipPin {
284    fn is_high(&mut self) -> Result<bool, Infallible> {
285        Ok(false)
286    }
287
288    fn is_low(&mut self) -> Result<bool, Infallible> {
289        Ok(true)
290    }
291}
292
293impl OutputPin for ChipPin {
294    fn set_low(&mut self) -> Result<(), Infallible> {
295        let mut state = self.state.lock().unwrap_or_else(PoisonError::into_inner);
296        state.model.hold_in_reset();
297        Ok(())
298    }
299
300    fn set_high(&mut self) -> Result<(), Infallible> {
301        Ok(())
302    }
303}
304
305/// The delay a simulated chip's driver waits with, which passes no time.
306#[derive(Clone, Copy, Debug, Default)]
307pub struct ChipDelay;
308
309impl DelayNs for ChipDelay {
310    fn delay_ns(&mut self, _ns: u32) {}
311}
312
313const RANDOM_SEED: u32 = 0x2545_F491;
314
315#[derive(Clone, Debug)]
316struct Arrival {
317    payload: Vec<u8>,
318    rssi_dbm: Decibels,
319    snr_db: Decibels,
320    intact: bool,
321}
322
323#[derive(Debug)]
324struct State {
325    model: Model,
326    air: VecDeque<Arrival>,
327    sent: Vec<Sent>,
328    random: u32,
329}
330
331impl State {
332    fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) {
333        let mut written = Vec::new();
334        let mut answered = false;
335        for operation in operations.iter_mut() {
336            match operation {
337                Operation::Write(bytes) => written.extend_from_slice(bytes),
338                Operation::Read(answer) => {
339                    self.answer(&written, answer);
340                    answered = true;
341                }
342                Operation::Transfer(answer, bytes) => {
343                    written.extend_from_slice(bytes);
344                    self.answer(&written, answer);
345                    answered = true;
346                }
347                Operation::TransferInPlace(bytes) => {
348                    written.extend_from_slice(bytes);
349                    self.answer(&written, bytes);
350                    answered = true;
351                }
352                Operation::DelayNs(_) => {}
353            }
354        }
355        if !answered {
356            self.write(&written);
357        }
358    }
359
360    fn answer(&mut self, written: &[u8], answer: &mut [u8]) {
361        let State {
362            model,
363            air,
364            sent: _,
365            random,
366        } = self;
367        match model {
368            Model::Sx126x(chip) => chip.answer(written, answer, air, random),
369            Model::Sx127x(chip) => chip.read(written, answer, air, random),
370        }
371    }
372
373    fn write(&mut self, written: &[u8]) {
374        let State { model, sent, .. } = self;
375        match model {
376            Model::Sx126x(chip) => chip.command(written, sent),
377            Model::Sx127x(chip) => chip.write(written, sent),
378        }
379    }
380}
381
382#[derive(Debug)]
383enum Model {
384    Sx126x(Sx126xModel),
385    Sx127x(Sx127xModel),
386}
387
388impl Model {
389    fn tuning(&self) -> Tuning {
390        match self {
391            Model::Sx126x(chip) => chip.tuning(),
392            Model::Sx127x(chip) => chip.tuning(),
393        }
394    }
395
396    fn hold_in_reset(&mut self) {
397        match self {
398            Model::Sx126x(chip) => *chip = Sx126xModel::new(chip.board),
399            Model::Sx127x(chip) => *chip = Sx127xModel::new(chip.board),
400        }
401    }
402}
403
404/// Draws the next byte of the chip's fixed noise sequence.
405fn noise_byte(random: &mut u32) -> u8 {
406    let mut x = *random;
407    x ^= x << 13;
408    x ^= x >> 17;
409    x ^= x << 5;
410    *random = x;
411    x.to_le_bytes()[0]
412}
413
414/// The noise a receiver hears in a channel: the thermal floor raised by a radio's noise
415/// figure.
416fn noise_floor(bandwidth_hz: u32) -> Decibels {
417    budget::noise_floor_dbm(bandwidth_hz) + RADIO_NOISE_FIGURE_DB
418}
419
420/// A level in dBm as the number of half decibels below zero the SX126x reports it in.
421fn half_decibels_below(level: Decibels) -> u8 {
422    let halves = (-level.hundredths() + 25).div_euclid(50);
423    halves.clamp(0, i32::from(u8::MAX)) as u8
424}
425
426/// A ratio in dB as the quarter decibels, two's complement, both families report it in.
427fn quarter_decibels(ratio: Decibels) -> u8 {
428    let quarters = (ratio.hundredths() + 12).div_euclid(25);
429    quarters.clamp(i32::from(i8::MIN), i32::from(i8::MAX)) as i8 as u8
430}
431
432/// The strength of the signal itself: the RSSI, lowered by the SNR below the noise.
433fn signal_level(arrival: &Arrival) -> Decibels {
434    if arrival.snr_db < Decibels::ZERO {
435        arrival.rssi_dbm + arrival.snr_db
436    } else {
437        arrival.rssi_dbm
438    }
439}
440
441#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442enum Sx126xMode {
443    Sleep,
444    StandbyRc,
445    StandbyXosc,
446    Fs,
447    Rx { continuous: bool, timeout: bool },
448    Cad,
449}
450
451const SX126X_STATUS_DATA_AVAILABLE: u8 = 0x2;
452const SX126X_STATUS_TIMEOUT: u8 = 0x3;
453const SX126X_STATUS_TX_DONE: u8 = 0x6;
454const SX126X_RX_CONTINUOUS: u32 = 0x00FF_FFFF;
455
456#[derive(Debug)]
457struct Sx126xModel {
458    board: sx126x::Board,
459    mode: Sx126xMode,
460    command_status: u8,
461    packet_type: u8,
462    frequency_word: u32,
463    modulation: [u8; 4],
464    packet: [u8; 6],
465    output_dbm: i8,
466    irq_mask: u16,
467    irq: u16,
468    tx_base: u8,
469    rx_base: u8,
470    buffer: [u8; 256],
471    rx_buffer: [u8; 2],
472    packet_status: [u8; 3],
473    registers: BTreeMap<u16, u8>,
474}
475
476impl Sx126xModel {
477    fn new(board: sx126x::Board) -> Sx126xModel {
478        let mut registers = BTreeMap::new();
479        let [high, low] = sx126x::config::SyncWord::Private.to_bytes();
480        registers.insert(sx126x_register::LORA_SYNC_WORD, high);
481        registers.insert(sx126x_register::LORA_SYNC_WORD + 1, low);
482        Sx126xModel {
483            board,
484            mode: Sx126xMode::StandbyRc,
485            command_status: 0,
486            packet_type: sx126x::config::PacketType::Lora.code(),
487            frequency_word: 0,
488            modulation: [7, sx126x::config::LoraBandwidth::Khz125.code(), 0x01, 0],
489            packet: [0, 8, 0, u8::MAX, 1, 0],
490            output_dbm: 0,
491            irq_mask: 0,
492            irq: 0,
493            tx_base: 0,
494            rx_base: 0,
495            buffer: [0; 256],
496            rx_buffer: [0; 2],
497            packet_status: [0; 3],
498            registers,
499        }
500    }
501
502    fn tuning(&self) -> Tuning {
503        let [spreading_factor, bandwidth, coding_rate, _] = self.modulation;
504        let bandwidth_hz = sx126x::config::LoraBandwidth::from_code(bandwidth)
505            .map_or(0, sx126x::config::LoraBandwidth::hz);
506        let denominator = match coding_rate {
507            2 | 6 => 6,
508            3 => 7,
509            4 | 7 => 8,
510            _ => 5,
511        };
512        let [preamble_high, preamble_low, header, _, crc, _] = self.packet;
513        let mut link = LinkSettings::new(spreading_factor, bandwidth_hz)
514            .with_coding_rate(denominator)
515            .with_preamble(u16::from_be_bytes([preamble_high, preamble_low]));
516        if header != 0 {
517            link = link.implicit_header();
518        }
519        if crc == 0 {
520            link = link.without_crc();
521        }
522        let high = self.register(sx126x_register::LORA_SYNC_WORD);
523        let low = self.register(sx126x_register::LORA_SYNC_WORD + 1);
524        Tuning {
525            frequency_hz: sx126x::config::frequency_from_word(self.frequency_word),
526            link,
527            output_dbm: self.output_dbm,
528            sync_word: SyncWord::from_byte((high & 0xF0) | (low >> 4)),
529        }
530    }
531
532    fn register(&self, address: u16) -> u8 {
533        self.registers.get(&address).copied().unwrap_or(0)
534    }
535
536    fn status(&self) -> u8 {
537        let mode = match self.mode {
538            Sx126xMode::Sleep | Sx126xMode::StandbyRc => 0x2,
539            Sx126xMode::StandbyXosc => 0x3,
540            Sx126xMode::Fs => 0x4,
541            Sx126xMode::Rx { .. } | Sx126xMode::Cad => 0x5,
542        };
543        (mode << 4) | (self.command_status << 1)
544    }
545
546    fn raise(&mut self, events: Irq) {
547        self.irq |= events.bits() & self.irq_mask;
548    }
549
550    fn command(&mut self, written: &[u8], sent: &mut Vec<Sent>) {
551        if self.mode == Sx126xMode::Sleep {
552            self.mode = Sx126xMode::StandbyRc;
553        }
554        let Some((&code, params)) = written.split_first() else {
555            return;
556        };
557        let word = |at: usize| -> u32 {
558            params.get(at..at + 3).map_or(0, |bytes| {
559                u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]])
560            })
561        };
562        let pair = |at: usize| -> u16 {
563            params
564                .get(at..at + 2)
565                .map_or(0, |bytes| u16::from_be_bytes([bytes[0], bytes[1]]))
566        };
567        match code {
568            opcode::SET_SLEEP => {
569                let warm = params.first().is_some_and(|config| config & 0x04 != 0);
570                if !warm {
571                    *self = Sx126xModel::new(self.board);
572                }
573                self.mode = Sx126xMode::Sleep;
574            }
575            opcode::SET_STANDBY => {
576                self.mode = if params.first() == Some(&1) {
577                    Sx126xMode::StandbyXosc
578                } else {
579                    Sx126xMode::StandbyRc
580                };
581            }
582            opcode::SET_FS => self.mode = Sx126xMode::Fs,
583            opcode::SET_TX => self.transmit(sent),
584            opcode::SET_RX => {
585                let timeout = word(0);
586                self.mode = Sx126xMode::Rx {
587                    continuous: timeout == SX126X_RX_CONTINUOUS,
588                    timeout: timeout != 0 && timeout != SX126X_RX_CONTINUOUS,
589                };
590            }
591            opcode::SET_CAD => self.mode = Sx126xMode::Cad,
592            opcode::SET_PACKET_TYPE => {
593                if let Some(&packet_type) = params.first() {
594                    self.packet_type = packet_type;
595                }
596            }
597            opcode::SET_RF_FREQUENCY => {
598                if let Some(bytes) = params.get(..4) {
599                    self.frequency_word =
600                        u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
601                }
602            }
603            opcode::SET_MODULATION_PARAMS => {
604                if let Some(bytes) = params.get(..4) {
605                    self.modulation.copy_from_slice(bytes);
606                }
607            }
608            opcode::SET_PACKET_PARAMS => {
609                if let Some(bytes) = params.get(..6) {
610                    self.packet.copy_from_slice(bytes);
611                }
612            }
613            opcode::SET_TX_PARAMS => {
614                if let Some(&power) = params.first() {
615                    self.output_dbm = power as i8;
616                }
617            }
618            opcode::SET_DIO_IRQ_PARAMS => self.irq_mask = pair(0),
619            opcode::CLEAR_IRQ_STATUS => self.irq &= !pair(0),
620            opcode::SET_BUFFER_BASE_ADDRESS => {
621                if let [tx_base, rx_base, ..] = params {
622                    self.tx_base = *tx_base;
623                    self.rx_base = *rx_base;
624                }
625            }
626            opcode::WRITE_REGISTER => {
627                let address = pair(0);
628                for (offset, &value) in params.iter().skip(2).enumerate() {
629                    let at = address.wrapping_add(offset as u16);
630                    self.registers.insert(at, value);
631                }
632            }
633            opcode::WRITE_BUFFER => {
634                if let Some((&offset, bytes)) = params.split_first() {
635                    for (index, &value) in bytes.iter().enumerate() {
636                        let at = offset.wrapping_add(index as u8);
637                        self.buffer[usize::from(at)] = value;
638                    }
639                }
640            }
641            _ => {}
642        }
643    }
644
645    fn answer(
646        &mut self,
647        written: &[u8],
648        answer: &mut [u8],
649        air: &mut VecDeque<Arrival>,
650        random: &mut u32,
651    ) {
652        if self.mode == Sx126xMode::Sleep {
653            self.mode = Sx126xMode::StandbyRc;
654        }
655        answer.fill(0);
656        let Some(&code) = written.first() else {
657            return;
658        };
659        match code {
660            opcode::GET_STATUS => {
661                if let Some(first) = answer.first_mut() {
662                    *first = self.status();
663                }
664            }
665            opcode::GET_IRQ_STATUS => {
666                self.settle(air);
667                fill(answer, &self.irq.to_be_bytes());
668            }
669            opcode::GET_RX_BUFFER_STATUS => fill(answer, &self.rx_buffer),
670            opcode::GET_PACKET_STATUS => fill(answer, &self.packet_status),
671            opcode::GET_PACKET_TYPE => fill(answer, &[self.packet_type]),
672            opcode::GET_RSSI_INST => {
673                let bandwidth_hz = self.tuning().link.bandwidth_hz();
674                fill(answer, &[half_decibels_below(noise_floor(bandwidth_hz))]);
675            }
676            opcode::READ_REGISTER => {
677                let address = written
678                    .get(1..3)
679                    .map_or(0, |bytes| u16::from_be_bytes([bytes[0], bytes[1]]));
680                for (offset, value) in answer.iter_mut().enumerate() {
681                    let at = address.wrapping_add(offset as u16);
682                    let random_number = sx126x_register::RANDOM_NUMBER;
683                    *value = if (random_number..random_number + 4).contains(&at) {
684                        noise_byte(random)
685                    } else {
686                        self.register(at)
687                    };
688                }
689            }
690            opcode::READ_BUFFER => {
691                let offset = written.get(1).copied().unwrap_or(0);
692                for (index, value) in answer.iter_mut().enumerate() {
693                    *value = self.buffer[usize::from(offset.wrapping_add(index as u8))];
694                }
695            }
696            _ => {}
697        }
698    }
699
700    fn settle(&mut self, air: &mut VecDeque<Arrival>) {
701        match self.mode {
702            Sx126xMode::Rx {
703                continuous,
704                timeout,
705            } => {
706                if let Some(arrival) = air.pop_front() {
707                    self.receive(&arrival);
708                    if !continuous {
709                        self.mode = Sx126xMode::StandbyRc;
710                    }
711                } else if timeout {
712                    self.raise(Irq::TIMEOUT);
713                    self.command_status = SX126X_STATUS_TIMEOUT;
714                    self.mode = Sx126xMode::StandbyRc;
715                }
716            }
717            Sx126xMode::Cad => {
718                self.raise(Irq::CAD_DONE);
719                if !air.is_empty() {
720                    self.raise(Irq::CAD_DETECTED);
721                }
722                self.mode = Sx126xMode::StandbyRc;
723            }
724            _ => {}
725        }
726    }
727
728    fn receive(&mut self, arrival: &Arrival) {
729        for (index, &value) in arrival.payload.iter().enumerate() {
730            let at = self.rx_base.wrapping_add(index as u8);
731            self.buffer[usize::from(at)] = value;
732        }
733        self.rx_buffer = [arrival.payload.len() as u8, self.rx_base];
734        self.packet_status = [
735            half_decibels_below(arrival.rssi_dbm),
736            quarter_decibels(arrival.snr_db),
737            half_decibels_below(signal_level(arrival)),
738        ];
739        self.raise(Irq::PREAMBLE_DETECTED | Irq::HEADER_VALID | Irq::RX_DONE);
740        if !arrival.intact {
741            self.raise(Irq::CRC_ERROR);
742        }
743        self.command_status = SX126X_STATUS_DATA_AVAILABLE;
744    }
745
746    fn transmit(&mut self, sent: &mut Vec<Sent>) {
747        let len = self.packet[3];
748        let payload = (0..len)
749            .map(|index| self.buffer[usize::from(self.tx_base.wrapping_add(index))])
750            .collect();
751        sent.push(Sent {
752            tuning: self.tuning(),
753            payload,
754        });
755        self.raise(Irq::TX_DONE);
756        self.command_status = SX126X_STATUS_TX_DONE;
757        self.mode = Sx126xMode::StandbyRc;
758    }
759}
760
761const SX127X_MODE_MASK: u8 = 0x07;
762const SX127X_MODE_STANDBY: u8 = 0x01;
763const SX127X_MODE_TX: u8 = 0x03;
764const SX127X_MODE_RX_CONTINUOUS: u8 = 0x05;
765const SX127X_MODE_RX_SINGLE: u8 = 0x06;
766const SX127X_MODE_CAD: u8 = 0x07;
767const SX127X_PAGED_FIRST: usize = 0x0D;
768const SX127X_PAGED_LAST: usize = 0x3F;
769const SX127X_IMAGE_CAL_START: u8 = 0x40;
770const SX127X_IMAGE_CAL_RUNNING: u8 = 0x20;
771
772#[derive(Debug)]
773struct Sx127xModel {
774    board: sx127x::Board,
775    lora: [u8; 128],
776    fsk: [u8; 128],
777    fifo: [u8; 256],
778}
779
780impl Sx127xModel {
781    fn new(board: sx127x::Board) -> Sx127xModel {
782        let mut lora = [0u8; 128];
783        let mut fsk = [0u8; 128];
784        for (address, value) in [
785            (sx127x_register::OP_MODE, 0x09),
786            (sx127x_register::FRF_MSB, 0x6C),
787            (sx127x_register::FRF_MID, 0x80),
788            (sx127x_register::FRF_LSB, 0x00),
789            (sx127x_register::PA_CONFIG, 0x4F),
790            (sx127x_register::PA_RAMP, 0x09),
791            (sx127x_register::OCP, 0x2B),
792            (sx127x_register::LNA, 0x20),
793            (sx127x_register::VERSION, sx127x_register::VERSION_SX1276),
794            (sx127x_register::TCXO, 0x09),
795            (sx127x_register::PA_DAC, sx127x::config::PA_DAC_DEFAULT),
796        ] {
797            lora[usize::from(address)] = value;
798        }
799        for (address, value) in [
800            (sx127x_register::FIFO_TX_BASE_ADDR, 0x80),
801            (sx127x_register::MODEM_CONFIG_1, 0x72),
802            (sx127x_register::MODEM_CONFIG_2, 0x70),
803            (sx127x_register::SYMB_TIMEOUT_LSB, 0x64),
804            (sx127x_register::PREAMBLE_LSB, 0x08),
805            (sx127x_register::PAYLOAD_LENGTH, 0x01),
806            (sx127x_register::MAX_PAYLOAD_LENGTH, 0xFF),
807            (sx127x_register::DETECT_OPTIMIZE, 0xC3),
808            (sx127x_register::INVERT_IQ, 0x27),
809            (sx127x_register::DETECTION_THRESHOLD, 0x0A),
810            (sx127x_register::SYNC_WORD, SyncWord::Private.to_byte()),
811            (sx127x_register::INVERT_IQ_2, 0x1D),
812        ] {
813            lora[usize::from(address)] = value;
814        }
815        fsk[usize::from(sx127x_register::IMAGE_CAL)] = 0x82;
816        Sx127xModel {
817            board,
818            lora,
819            fsk,
820            fifo: [0; 256],
821        }
822    }
823
824    fn lora_mode(&self) -> bool {
825        self.lora[usize::from(sx127x_register::OP_MODE)] & sx127x_register::LONG_RANGE_MODE != 0
826    }
827
828    fn slot(&mut self, address: u8) -> &mut u8 {
829        let address = usize::from(address & 0x7F);
830        if (SX127X_PAGED_FIRST..=SX127X_PAGED_LAST).contains(&address) && !self.lora_mode() {
831            &mut self.fsk[address]
832        } else {
833            &mut self.lora[address]
834        }
835    }
836
837    fn lora_register(&self, address: u8) -> u8 {
838        self.lora[usize::from(address & 0x7F)]
839    }
840
841    fn mode(&self) -> u8 {
842        self.lora_register(sx127x_register::OP_MODE) & SX127X_MODE_MASK
843    }
844
845    fn enter(&mut self, mode: u8) {
846        let op_mode = usize::from(sx127x_register::OP_MODE);
847        self.lora[op_mode] = (self.lora[op_mode] & !SX127X_MODE_MASK) | mode;
848    }
849
850    fn tuning(&self) -> Tuning {
851        let frf = u32::from_be_bytes([
852            0,
853            self.lora_register(sx127x_register::FRF_MSB),
854            self.lora_register(sx127x_register::FRF_MID),
855            self.lora_register(sx127x_register::FRF_LSB),
856        ]);
857        let modem_1 = self.lora_register(sx127x_register::MODEM_CONFIG_1);
858        let modem_2 = self.lora_register(sx127x_register::MODEM_CONFIG_2);
859        let bandwidth_hz = sx127x::config::LoraBandwidth::from_code(modem_1 >> 4)
860            .map_or(0, sx127x::config::LoraBandwidth::hz);
861        let preamble = u16::from_be_bytes([
862            self.lora_register(sx127x_register::PREAMBLE_MSB),
863            self.lora_register(sx127x_register::PREAMBLE_LSB),
864        ]);
865        let mut link = LinkSettings::new(modem_2 >> 4, bandwidth_hz)
866            .with_coding_rate(((modem_1 >> 1) & 0x07) + 4)
867            .with_preamble(preamble);
868        if modem_1 & 0x01 != 0 {
869            link = link.implicit_header();
870        }
871        if modem_2 & 0x04 == 0 {
872            link = link.without_crc();
873        }
874        let pa_config = self.lora_register(sx127x_register::PA_CONFIG);
875        let output_power = (pa_config & 0x0F) as i8;
876        let output_dbm = if pa_config & 0x80 != 0 {
877            let high_power = self.lora_register(sx127x_register::PA_DAC) & 0x07 == 0x07;
878            output_power + if high_power { 5 } else { 2 }
879        } else if (pa_config >> 4) & 0x07 == 0x07 {
880            output_power
881        } else {
882            output_power - 4
883        };
884        Tuning {
885            frequency_hz: sx127x::config::frequency_from_word(frf),
886            link,
887            output_dbm,
888            sync_word: SyncWord::from_byte(self.lora_register(sx127x_register::SYNC_WORD)),
889        }
890    }
891
892    fn raise(&mut self, flags: IrqFlags) {
893        let mask = self.lora_register(sx127x_register::IRQ_FLAGS_MASK);
894        self.lora[usize::from(sx127x_register::IRQ_FLAGS)] |= flags.bits() & !mask;
895    }
896
897    fn write(&mut self, written: &[u8], sent: &mut Vec<Sent>) {
898        let Some((&address, values)) = written.split_first() else {
899            return;
900        };
901        let address = address & !sx127x_register::WRITE;
902        for (offset, &value) in values.iter().enumerate() {
903            let at = if address == sx127x_register::FIFO {
904                address
905            } else {
906                address.wrapping_add(offset as u8) & 0x7F
907            };
908            self.write_register(at, value, sent);
909        }
910    }
911
912    fn write_register(&mut self, address: u8, value: u8, sent: &mut Vec<Sent>) {
913        match address {
914            sx127x_register::FIFO => {
915                let pointer = self.lora_register(sx127x_register::FIFO_ADDR_PTR);
916                self.fifo[usize::from(pointer)] = value;
917                self.lora[usize::from(sx127x_register::FIFO_ADDR_PTR)] = pointer.wrapping_add(1);
918            }
919            sx127x_register::OP_MODE => {
920                self.lora[usize::from(address)] = value;
921                if self.lora_mode() && value & SX127X_MODE_MASK == SX127X_MODE_TX {
922                    self.transmit(sent);
923                }
924            }
925            sx127x_register::IRQ_FLAGS if self.lora_mode() => {
926                self.lora[usize::from(address)] &= !value;
927            }
928            sx127x_register::IMAGE_CAL if !self.lora_mode() => {
929                self.fsk[usize::from(address)] =
930                    value & !(SX127X_IMAGE_CAL_START | SX127X_IMAGE_CAL_RUNNING);
931            }
932            sx127x_register::VERSION => {}
933            _ => *self.slot(address) = value,
934        }
935    }
936
937    fn read(
938        &mut self,
939        written: &[u8],
940        answer: &mut [u8],
941        air: &mut VecDeque<Arrival>,
942        random: &mut u32,
943    ) {
944        let address = written.first().copied().unwrap_or(0) & !sx127x_register::WRITE;
945        for (offset, value) in answer.iter_mut().enumerate() {
946            let at = if address == sx127x_register::FIFO {
947                address
948            } else {
949                address.wrapping_add(offset as u8) & 0x7F
950            };
951            *value = self.read_register(at, air, random);
952        }
953    }
954
955    fn read_register(&mut self, address: u8, air: &mut VecDeque<Arrival>, random: &mut u32) -> u8 {
956        if !self.lora_mode() {
957            return *self.slot(address);
958        }
959        match address {
960            sx127x_register::FIFO => {
961                let pointer = self.lora_register(sx127x_register::FIFO_ADDR_PTR);
962                self.lora[usize::from(sx127x_register::FIFO_ADDR_PTR)] = pointer.wrapping_add(1);
963                self.fifo[usize::from(pointer)]
964            }
965            sx127x_register::IRQ_FLAGS => {
966                self.settle(air);
967                self.lora_register(address)
968            }
969            sx127x_register::RSSI_WIDEBAND => noise_byte(random),
970            sx127x_register::RSSI_VALUE => {
971                let tuning = self.tuning();
972                let port = Port::for_frequency(tuning.frequency_hz);
973                let floor = noise_floor(tuning.link.bandwidth_hz());
974                let above = (floor.hundredths() + 50).div_euclid(100) - port.rssi_offset_dbm();
975                above.clamp(0, i32::from(u8::MAX)) as u8
976            }
977            _ => *self.slot(address),
978        }
979    }
980
981    fn settle(&mut self, air: &mut VecDeque<Arrival>) {
982        match self.mode() {
983            SX127X_MODE_RX_CONTINUOUS => {
984                if let Some(arrival) = air.pop_front() {
985                    self.receive(&arrival);
986                }
987            }
988            SX127X_MODE_RX_SINGLE => {
989                if let Some(arrival) = air.pop_front() {
990                    self.receive(&arrival);
991                } else {
992                    self.raise(IrqFlags::RX_TIMEOUT);
993                }
994                self.enter(SX127X_MODE_STANDBY);
995            }
996            SX127X_MODE_CAD => {
997                self.raise(IrqFlags::CAD_DONE);
998                if !air.is_empty() {
999                    self.raise(IrqFlags::CAD_DETECTED);
1000                }
1001                self.enter(SX127X_MODE_STANDBY);
1002            }
1003            _ => {}
1004        }
1005    }
1006
1007    fn receive(&mut self, arrival: &Arrival) {
1008        let base = self.lora_register(sx127x_register::FIFO_RX_BASE_ADDR);
1009        for (index, &value) in arrival.payload.iter().enumerate() {
1010            self.fifo[usize::from(base.wrapping_add(index as u8))] = value;
1011        }
1012        let port = Port::for_frequency(self.tuning().frequency_hz);
1013        let rssi = (arrival.rssi_dbm.hundredths() + 50).div_euclid(100) - port.rssi_offset_dbm();
1014        self.lora[usize::from(sx127x_register::FIFO_RX_CURRENT_ADDR)] = base;
1015        self.lora[usize::from(sx127x_register::RX_NB_BYTES)] = arrival.payload.len() as u8;
1016        self.lora[usize::from(sx127x_register::PKT_SNR_VALUE)] = quarter_decibels(arrival.snr_db);
1017        self.lora[usize::from(sx127x_register::PKT_RSSI_VALUE)] =
1018            rssi.clamp(0, i32::from(u8::MAX)) as u8;
1019        self.raise(IrqFlags::VALID_HEADER | IrqFlags::RX_DONE);
1020        if !arrival.intact {
1021            self.raise(IrqFlags::PAYLOAD_CRC_ERROR);
1022        }
1023    }
1024
1025    fn transmit(&mut self, sent: &mut Vec<Sent>) {
1026        let base = self.lora_register(sx127x_register::FIFO_TX_BASE_ADDR);
1027        let len = self.lora_register(sx127x_register::PAYLOAD_LENGTH);
1028        let payload = (0..len)
1029            .map(|index| self.fifo[usize::from(base.wrapping_add(index))])
1030            .collect();
1031        sent.push(Sent {
1032            tuning: self.tuning(),
1033            payload,
1034        });
1035        self.raise(IrqFlags::TX_DONE);
1036        self.enter(SX127X_MODE_STANDBY);
1037    }
1038}
1039
1040/// Copies an answer into the bytes a query reads, as far as both go.
1041fn fill(answer: &mut [u8], bytes: &[u8]) {
1042    for (slot, &byte) in answer.iter_mut().zip(bytes) {
1043        *slot = byte;
1044    }
1045}
1046
1047#[cfg(test)]
1048mod tests {
1049    use super::*;
1050    use crate::radio::{RadioConfig, Reception};
1051    use crate::sx126x::config::PowerAmplifier;
1052    use crate::sx127x::config::PaOutput;
1053    use pamoja_lora::region::Region;
1054
1055    fn dr3() -> LinkSettings {
1056        Region::Eu868
1057            .plan()
1058            .link_settings(3)
1059            .expect("DR3 is a LoRa data rate")
1060    }
1061
1062    fn carrier(hz: u32, asked: u32) -> bool {
1063        hz.abs_diff(asked) <= 61
1064    }
1065
1066    fn chips() -> [Chip; 2] {
1067        [
1068            Chip::sx126x(sx126x::Board::new(PowerAmplifier::HighPower)),
1069            Chip::sx127x(sx127x::Board::new(PaOutput::PaBoost)),
1070        ]
1071    }
1072
1073    #[test]
1074    fn the_driver_tunes_each_chip_to_what_it_was_asked() {
1075        for chip in chips() {
1076            let mut radio = chip.radio();
1077            radio.init().expect("the chip answers");
1078            let config = RadioConfig::new(868_100_000, dr3(), 14).lorawan_device();
1079            radio.configure(config).expect("a link the chip carries");
1080            let tuning = chip.tuning();
1081            assert!(
1082                carrier(tuning.frequency_hz, 868_100_000),
1083                "{:?}",
1084                chip.family()
1085            );
1086            assert_eq!(tuning.link.spreading_factor(), 9);
1087            assert_eq!(tuning.link.bandwidth_hz(), 125_000);
1088            assert_eq!(tuning.link.coding_rate_denominator(), 5);
1089            assert_eq!(tuning.output_dbm, 14);
1090            assert_eq!(tuning.sync_word, SyncWord::Public);
1091        }
1092    }
1093
1094    #[test]
1095    fn a_transmission_puts_the_payload_on_the_air_as_tuned() {
1096        for chip in chips() {
1097            let mut radio = chip.radio();
1098            radio.init().expect("the chip answers");
1099            radio
1100                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1101                .expect("a link the chip carries");
1102            let airtime = radio.transmit(b"21.5").expect("the frame leaves");
1103            assert_eq!(airtime, dr3().airtime_us(4));
1104            let sent = chip.sent();
1105            assert_eq!(sent.len(), 1);
1106            assert_eq!(sent[0].payload, b"21.5");
1107            assert!(carrier(sent[0].tuning.frequency_hz, 868_100_000));
1108            assert_eq!(sent[0].tuning.link.preamble_symbols(), 8);
1109        }
1110    }
1111
1112    #[test]
1113    fn a_frame_on_the_air_arrives_with_the_levels_it_was_heard_at() {
1114        for chip in chips() {
1115            let mut radio = chip.radio();
1116            radio.init().expect("the chip answers");
1117            radio
1118                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1119                .expect("a link the chip carries");
1120            chip.hear(b"ack", Decibels::from_db(-109), Decibels::from_tenths(-25));
1121            let mut buffer = [0u8; 255];
1122            let reception = radio
1123                .receive(&mut buffer, 1_000_000)
1124                .expect("the chip answers");
1125            let Reception::Frame { len, levels } = reception else {
1126                panic!("a frame was waiting: {reception:?}");
1127            };
1128            assert_eq!(&buffer[..len], b"ack");
1129            assert_eq!(levels.rssi_dbm, Decibels::from_db(-109));
1130            assert_eq!(levels.snr_db, Decibels::from_tenths(-25));
1131            assert_eq!(levels.signal_rssi_dbm, Decibels::from_hundredths(-11_150));
1132            assert_eq!(chip.waiting(), 0);
1133        }
1134    }
1135
1136    #[test]
1137    fn nothing_on_the_air_times_a_reception_out_and_a_bad_crc_is_corrupt() {
1138        for chip in chips() {
1139            let mut radio = chip.radio();
1140            radio.init().expect("the chip answers");
1141            radio
1142                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1143                .expect("a link the chip carries");
1144            let mut buffer = [0u8; 255];
1145            assert_eq!(
1146                radio.receive(&mut buffer, 1_000_000),
1147                Ok(Reception::Timeout)
1148            );
1149            chip.hear_corrupt(Decibels::from_db(-120), Decibels::from_db(-10));
1150            assert_eq!(
1151                radio.receive(&mut buffer, 1_000_000),
1152                Ok(Reception::Corrupt)
1153            );
1154        }
1155    }
1156
1157    #[test]
1158    fn listening_takes_frame_after_frame_until_the_air_is_quiet() {
1159        for chip in chips() {
1160            let mut radio = chip.radio();
1161            radio.init().expect("the chip answers");
1162            radio
1163                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1164                .expect("a link the chip carries");
1165            radio.listen().expect("the chip listens");
1166            let mut buffer = [0u8; 255];
1167            assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1168            chip.hear(b"one", Decibels::from_db(-100), Decibels::from_db(5));
1169            chip.hear(b"two", Decibels::from_db(-101), Decibels::from_db(4));
1170            for expected in [b"one", b"two"] {
1171                let Ok(Some(Reception::Frame { len, .. })) = radio.take_frame(&mut buffer) else {
1172                    panic!("a frame was waiting");
1173                };
1174                assert_eq!(&buffer[..len], expected);
1175            }
1176            assert_eq!(radio.take_frame(&mut buffer), Ok(None));
1177        }
1178    }
1179
1180    #[test]
1181    fn activity_is_detected_only_while_a_frame_is_on_the_air() {
1182        for chip in chips() {
1183            let mut radio = chip.radio();
1184            radio.init().expect("the chip answers");
1185            radio
1186                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1187                .expect("a link the chip carries");
1188            assert_eq!(radio.detect(4), Ok(false));
1189            chip.hear(b"wake", Decibels::from_db(-110), Decibels::from_db(-5));
1190            assert_eq!(radio.detect(4), Ok(true));
1191            assert_eq!(
1192                chip.waiting(),
1193                1,
1194                "detection leaves the frame for the receiver"
1195            );
1196        }
1197    }
1198
1199    #[test]
1200    fn the_noise_gives_a_random_number_and_leaves_the_air_alone() {
1201        for chip in chips() {
1202            let mut radio = chip.radio();
1203            radio.init().expect("the chip answers");
1204            radio
1205                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1206                .expect("a link the chip carries");
1207            chip.hear(b"kept", Decibels::from_db(-100), Decibels::from_db(5));
1208            let first = radio.random().expect("noise");
1209            let second = radio.random().expect("noise");
1210            assert_ne!(first, second);
1211            assert_eq!(chip.waiting(), 1);
1212        }
1213    }
1214
1215    #[test]
1216    fn a_reset_forgets_the_tuning_and_keeps_what_was_sent() {
1217        for chip in chips() {
1218            let mut radio = chip.radio();
1219            radio.init().expect("the chip answers");
1220            radio
1221                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1222                .expect("a link the chip carries");
1223            radio.transmit(b"once").expect("the frame leaves");
1224            radio.init().expect("the chip answers again");
1225            assert_ne!(chip.tuning().frequency_hz, 868_100_000);
1226            assert_eq!(chip.sent().len(), 1);
1227        }
1228    }
1229
1230    #[test]
1231    fn a_warm_sleep_keeps_the_tuning_and_the_next_command_wakes_the_chip() {
1232        for chip in chips() {
1233            let mut radio = chip.radio();
1234            radio.init().expect("the chip answers");
1235            radio
1236                .configure(RadioConfig::new(868_100_000, dr3(), 14))
1237                .expect("a link the chip carries");
1238            radio.sleep().expect("the chip sleeps");
1239            assert!(carrier(chip.tuning().frequency_hz, 868_100_000));
1240            radio
1241                .configure(RadioConfig::new(868_300_000, dr3(), 14))
1242                .expect("the chip wakes");
1243            assert!(carrier(chip.tuning().frequency_hz, 868_300_000));
1244        }
1245    }
1246
1247    #[test]
1248    fn a_cold_sleep_forgets_the_tuning() {
1249        let chip = Chip::sx126x(sx126x::Board::new(PowerAmplifier::HighPower));
1250        let Radio::Sx126x(mut radio) = chip.radio() else {
1251            panic!("an SX126x");
1252        };
1253        radio.init().expect("the chip answers");
1254        let power = radio.tx_power(14);
1255        let config = sx126x::RadioConfig::new(868_100_000, dr3(), power);
1256        radio.configure(config).expect("a link the chip carries");
1257        radio.sleep(false).expect("the chip sleeps");
1258        assert_eq!(chip.tuning().frequency_hz, 0);
1259    }
1260
1261    #[test]
1262    fn the_rfo_output_and_the_high_power_setting_report_their_own_power() {
1263        let rfo = Chip::sx127x(sx127x::Board::new(PaOutput::Rfo));
1264        let mut radio = rfo.radio();
1265        radio.init().expect("the chip answers");
1266        radio
1267            .configure(RadioConfig::new(
1268                433_175_000,
1269                LinkSettings::new(9, 125_000),
1270                10,
1271            ))
1272            .expect("a link the chip carries");
1273        assert_eq!(rfo.tuning().output_dbm, 10);
1274        assert!(carrier(rfo.tuning().frequency_hz, 433_175_000));
1275
1276        let boost = Chip::sx127x(sx127x::Board::new(PaOutput::PaBoost));
1277        let mut radio = boost.radio();
1278        radio.init().expect("the chip answers");
1279        radio
1280            .configure(RadioConfig::new(
1281                915_000_000,
1282                LinkSettings::new(7, 500_000),
1283                20,
1284            ))
1285            .expect("a link the chip carries");
1286        assert_eq!(boost.tuning().output_dbm, 20);
1287        assert_eq!(boost.tuning().link.bandwidth_hz(), 500_000);
1288    }
1289
1290    #[test]
1291    fn a_payload_longer_than_a_frame_is_cut_to_one() {
1292        let chip = Chip::sx126x(sx126x::Board::new(PowerAmplifier::HighPower));
1293        chip.hear(&[7; 300], Decibels::from_db(-100), Decibels::from_db(5));
1294        let mut radio = chip.radio();
1295        radio.init().expect("the chip answers");
1296        radio
1297            .configure(RadioConfig::new(868_100_000, dr3(), 14))
1298            .expect("a link the chip carries");
1299        let mut buffer = [0u8; 255];
1300        let Ok(Reception::Frame { len, .. }) = radio.receive(&mut buffer, 1_000_000) else {
1301            panic!("a frame was waiting");
1302        };
1303        assert_eq!(len, 255);
1304    }
1305}