1use pamoja_radios::duty::DutyCycle;
16use pamoja_radios::sx126x::command::{self, Command, Query};
17use pamoja_radios::sx126x::config::{
18 self, LoraModulation, LoraPacket, PaConfig, PacketType, PowerAmplifier, RampTime, StandbyMode,
19 SyncWord, TxPower,
20};
21use pamoja_radios::sx126x::irq::Irq;
22use pamoja_radios::sx126x::status::{
23 rssi_inst_dbm, ChipMode, CommandStatus, DeviceErrors, PacketStatus, RxBufferStatus, Status,
24};
25
26use pamoja_radios::sx126x::config::llcc68_supports;
27use pamoja_radios::sx127x::config::{
28 self as sx127x_config, LoraBandwidth as Sx127xBandwidth, LoraModulation as Sx127xModulation,
29 ModulationError, PaOutput, TxPower as Sx127xPower,
30};
31use pamoja_radios::sx127x::irq::IrqFlags;
32use pamoja_radios::sx127x::register::{self as sx127x_register, Mode as Sx127xMode};
33use pamoja_radios::sx127x::status::{
34 rssi_dbm as sx127x_rssi_dbm, ModemStatus as Sx127xModemStatus,
35 PacketStatus as Sx127xPacketStatus, Port,
36};
37
38use pamoja_lora::budget::Decibels;
39
40use crate::lora::{link_budget, settings, PamojaLoraLink, PamojaLoraLinkBudget};
41use crate::{set_last_error, PamojaStatus};
42
43pub const PAMOJA_SX126X_COMMAND_MAX: usize = 10;
45
46pub const PAMOJA_SX126X_RX_CONTINUOUS: u32 = 0xFF_FFFF;
48
49pub const PAMOJA_SX126X_SYNC_WORD_PUBLIC: u16 = 0x3444;
51
52pub const PAMOJA_SX126X_SYNC_WORD_PRIVATE: u16 = 0x1424;
54
55pub const PAMOJA_SX126X_REGISTER_LORA_SYNC_WORD: u16 = 0x0740;
57
58pub const PAMOJA_SX126X_IRQ_TX_DONE: u16 = 1 << 0;
60pub const PAMOJA_SX126X_IRQ_RX_DONE: u16 = 1 << 1;
62pub const PAMOJA_SX126X_IRQ_PREAMBLE_DETECTED: u16 = 1 << 2;
64pub const PAMOJA_SX126X_IRQ_SYNC_WORD_VALID: u16 = 1 << 3;
66pub const PAMOJA_SX126X_IRQ_HEADER_VALID: u16 = 1 << 4;
68pub const PAMOJA_SX126X_IRQ_HEADER_ERROR: u16 = 1 << 5;
70pub const PAMOJA_SX126X_IRQ_CRC_ERROR: u16 = 1 << 6;
72pub const PAMOJA_SX126X_IRQ_CAD_DONE: u16 = 1 << 7;
74pub const PAMOJA_SX126X_IRQ_CAD_DETECTED: u16 = 1 << 8;
76pub const PAMOJA_SX126X_IRQ_TIMEOUT: u16 = 1 << 9;
78pub const PAMOJA_SX126X_IRQ_LR_FHSS_HOP: u16 = 1 << 14;
80pub const PAMOJA_SX126X_IRQ_ALL: u16 = 0x43FF;
82
83pub const PAMOJA_SX126X_ERROR_RC64K_CALIBRATION: u16 = 1 << 0;
85pub const PAMOJA_SX126X_ERROR_RC13M_CALIBRATION: u16 = 1 << 1;
87pub const PAMOJA_SX126X_ERROR_PLL_CALIBRATION: u16 = 1 << 2;
89pub const PAMOJA_SX126X_ERROR_ADC_CALIBRATION: u16 = 1 << 3;
91pub const PAMOJA_SX126X_ERROR_IMAGE_CALIBRATION: u16 = 1 << 4;
93pub const PAMOJA_SX126X_ERROR_XOSC_START: u16 = 1 << 5;
95pub const PAMOJA_SX126X_ERROR_PLL_LOCK: u16 = 1 << 6;
97pub const PAMOJA_SX126X_ERROR_PA_RAMP: u16 = 1 << 8;
99
100const _: () = assert!(PAMOJA_SX126X_COMMAND_MAX == command::MAX_LEN);
103const _: () = assert!(PAMOJA_SX126X_RX_CONTINUOUS == config::RX_CONTINUOUS);
104const _: () =
105 assert!(PAMOJA_SX126X_SYNC_WORD_PUBLIC == u16::from_be_bytes(SyncWord::Public.to_bytes()));
106const _: () =
107 assert!(PAMOJA_SX126X_SYNC_WORD_PRIVATE == u16::from_be_bytes(SyncWord::Private.to_bytes()));
108const _: () = assert!(PAMOJA_SX126X_REGISTER_LORA_SYNC_WORD == config::register::LORA_SYNC_WORD);
109const _: () = assert!(PAMOJA_SX126X_IRQ_TX_DONE == Irq::TX_DONE.bits());
110const _: () = assert!(PAMOJA_SX126X_IRQ_RX_DONE == Irq::RX_DONE.bits());
111const _: () = assert!(PAMOJA_SX126X_IRQ_PREAMBLE_DETECTED == Irq::PREAMBLE_DETECTED.bits());
112const _: () = assert!(PAMOJA_SX126X_IRQ_SYNC_WORD_VALID == Irq::SYNC_WORD_VALID.bits());
113const _: () = assert!(PAMOJA_SX126X_IRQ_HEADER_VALID == Irq::HEADER_VALID.bits());
114const _: () = assert!(PAMOJA_SX126X_IRQ_HEADER_ERROR == Irq::HEADER_ERROR.bits());
115const _: () = assert!(PAMOJA_SX126X_IRQ_CRC_ERROR == Irq::CRC_ERROR.bits());
116const _: () = assert!(PAMOJA_SX126X_IRQ_CAD_DONE == Irq::CAD_DONE.bits());
117const _: () = assert!(PAMOJA_SX126X_IRQ_CAD_DETECTED == Irq::CAD_DETECTED.bits());
118const _: () = assert!(PAMOJA_SX126X_IRQ_TIMEOUT == Irq::TIMEOUT.bits());
119const _: () = assert!(PAMOJA_SX126X_IRQ_LR_FHSS_HOP == Irq::LR_FHSS_HOP.bits());
120const _: () = assert!(PAMOJA_SX126X_IRQ_ALL == Irq::ALL.bits());
121const _: () =
122 assert!(PAMOJA_SX126X_ERROR_RC64K_CALIBRATION == DeviceErrors::RC64K_CALIBRATION.bits());
123const _: () =
124 assert!(PAMOJA_SX126X_ERROR_RC13M_CALIBRATION == DeviceErrors::RC13M_CALIBRATION.bits());
125const _: () = assert!(PAMOJA_SX126X_ERROR_PLL_CALIBRATION == DeviceErrors::PLL_CALIBRATION.bits());
126const _: () = assert!(PAMOJA_SX126X_ERROR_ADC_CALIBRATION == DeviceErrors::ADC_CALIBRATION.bits());
127const _: () =
128 assert!(PAMOJA_SX126X_ERROR_IMAGE_CALIBRATION == DeviceErrors::IMAGE_CALIBRATION.bits());
129const _: () = assert!(PAMOJA_SX126X_ERROR_XOSC_START == DeviceErrors::XOSC_START.bits());
130const _: () = assert!(PAMOJA_SX126X_ERROR_PLL_LOCK == DeviceErrors::PLL_LOCK.bits());
131const _: () = assert!(PAMOJA_SX126X_ERROR_PA_RAMP == DeviceErrors::PA_RAMP.bits());
132
133pub const PAMOJA_SX127X_VERSION: u8 = 0x12;
135
136pub const PAMOJA_SX127X_WRITE: u8 = 0x80;
138
139pub const PAMOJA_SX127X_DIO0_RX_DONE: u8 = 0x00;
141
142pub const PAMOJA_SX127X_DIO0_TX_DONE: u8 = 0x40;
144
145pub const PAMOJA_SX127X_DIO0_CAD_DONE: u8 = 0x80;
147
148pub const PAMOJA_SX127X_PA_DAC_DEFAULT: u8 = 0x84;
150
151pub const PAMOJA_SX127X_PA_DAC_HIGH_POWER: u8 = 0x87;
153
154pub const PAMOJA_SX127X_IMAGE_CAL_START: u8 = 0x40;
156
157pub const PAMOJA_SX127X_IMAGE_CAL_RUNNING: u8 = 0x20;
159
160pub const PAMOJA_SX127X_SYNC_WORD_PUBLIC: u8 = 0x34;
162
163pub const PAMOJA_SX127X_SYNC_WORD_PRIVATE: u8 = 0x12;
165
166pub const PAMOJA_SX127X_LNA_BOOSTED: u8 = 0x23;
168
169pub const PAMOJA_SX127X_TCXO_INPUT_ON: u8 = 0x19;
171
172pub const PAMOJA_SX127X_REG_FIFO: u8 = 0x00;
174
175pub const PAMOJA_SX127X_REG_OP_MODE: u8 = 0x01;
177
178pub const PAMOJA_SX127X_REG_FRF_MSB: u8 = 0x06;
180
181pub const PAMOJA_SX127X_REG_FRF_MID: u8 = 0x07;
183
184pub const PAMOJA_SX127X_REG_FRF_LSB: u8 = 0x08;
186
187pub const PAMOJA_SX127X_REG_PA_CONFIG: u8 = 0x09;
189
190pub const PAMOJA_SX127X_REG_PA_RAMP: u8 = 0x0A;
192
193pub const PAMOJA_SX127X_REG_OCP: u8 = 0x0B;
195
196pub const PAMOJA_SX127X_REG_LNA: u8 = 0x0C;
198
199pub const PAMOJA_SX127X_REG_FIFO_ADDR_PTR: u8 = 0x0D;
201
202pub const PAMOJA_SX127X_REG_FIFO_TX_BASE_ADDR: u8 = 0x0E;
204
205pub const PAMOJA_SX127X_REG_FIFO_RX_BASE_ADDR: u8 = 0x0F;
207
208pub const PAMOJA_SX127X_REG_FIFO_RX_CURRENT_ADDR: u8 = 0x10;
210
211pub const PAMOJA_SX127X_REG_IRQ_FLAGS_MASK: u8 = 0x11;
213
214pub const PAMOJA_SX127X_REG_IRQ_FLAGS: u8 = 0x12;
216
217pub const PAMOJA_SX127X_REG_RX_NB_BYTES: u8 = 0x13;
219
220pub const PAMOJA_SX127X_REG_MODEM_STAT: u8 = 0x18;
222
223pub const PAMOJA_SX127X_REG_PKT_SNR_VALUE: u8 = 0x19;
225
226pub const PAMOJA_SX127X_REG_PKT_RSSI_VALUE: u8 = 0x1A;
228
229pub const PAMOJA_SX127X_REG_RSSI_VALUE: u8 = 0x1B;
231
232pub const PAMOJA_SX127X_REG_HOP_CHANNEL: u8 = 0x1C;
234
235pub const PAMOJA_SX127X_REG_MODEM_CONFIG_1: u8 = 0x1D;
237
238pub const PAMOJA_SX127X_REG_MODEM_CONFIG_2: u8 = 0x1E;
240
241pub const PAMOJA_SX127X_REG_SYMB_TIMEOUT_LSB: u8 = 0x1F;
243
244pub const PAMOJA_SX127X_REG_PREAMBLE_MSB: u8 = 0x20;
246
247pub const PAMOJA_SX127X_REG_PREAMBLE_LSB: u8 = 0x21;
249
250pub const PAMOJA_SX127X_REG_PAYLOAD_LENGTH: u8 = 0x22;
252
253pub const PAMOJA_SX127X_REG_MAX_PAYLOAD_LENGTH: u8 = 0x23;
255
256pub const PAMOJA_SX127X_REG_MODEM_CONFIG_3: u8 = 0x26;
258
259pub const PAMOJA_SX127X_REG_RSSI_WIDEBAND: u8 = 0x2C;
261
262pub const PAMOJA_SX127X_REG_IF_FREQ_2: u8 = 0x2F;
264
265pub const PAMOJA_SX127X_REG_IF_FREQ_1: u8 = 0x30;
267
268pub const PAMOJA_SX127X_REG_DETECT_OPTIMIZE: u8 = 0x31;
270
271pub const PAMOJA_SX127X_REG_INVERT_IQ: u8 = 0x33;
273
274pub const PAMOJA_SX127X_REG_HIGH_BW_OPTIMIZE_1: u8 = 0x36;
276
277pub const PAMOJA_SX127X_REG_DETECTION_THRESHOLD: u8 = 0x37;
279
280pub const PAMOJA_SX127X_REG_SYNC_WORD: u8 = 0x39;
282
283pub const PAMOJA_SX127X_REG_HIGH_BW_OPTIMIZE_2: u8 = 0x3A;
285
286pub const PAMOJA_SX127X_REG_INVERT_IQ_2: u8 = 0x3B;
288
289pub const PAMOJA_SX127X_REG_IMAGE_CAL: u8 = 0x3B;
291
292pub const PAMOJA_SX127X_REG_DIO_MAPPING_1: u8 = 0x40;
294
295pub const PAMOJA_SX127X_REG_DIO_MAPPING_2: u8 = 0x41;
297
298pub const PAMOJA_SX127X_REG_VERSION: u8 = 0x42;
300
301pub const PAMOJA_SX127X_REG_TCXO: u8 = 0x4B;
303
304pub const PAMOJA_SX127X_REG_PA_DAC: u8 = 0x4D;
306
307pub const PAMOJA_SX127X_MODE_SLEEP: u8 = 0;
309
310pub const PAMOJA_SX127X_MODE_STANDBY: u8 = 1;
312
313pub const PAMOJA_SX127X_MODE_FS_TX: u8 = 2;
315
316pub const PAMOJA_SX127X_MODE_TX: u8 = 3;
318
319pub const PAMOJA_SX127X_MODE_FS_RX: u8 = 4;
321
322pub const PAMOJA_SX127X_MODE_RX_CONTINUOUS: u8 = 5;
324
325pub const PAMOJA_SX127X_MODE_RX_SINGLE: u8 = 6;
327
328pub const PAMOJA_SX127X_MODE_CAD: u8 = 7;
330
331pub const PAMOJA_SX127X_IRQ_CAD_DETECTED: u8 = 0x01;
333
334pub const PAMOJA_SX127X_IRQ_FHSS_CHANGE_CHANNEL: u8 = 0x02;
336
337pub const PAMOJA_SX127X_IRQ_CAD_DONE: u8 = 0x04;
339
340pub const PAMOJA_SX127X_IRQ_TX_DONE: u8 = 0x08;
342
343pub const PAMOJA_SX127X_IRQ_VALID_HEADER: u8 = 0x10;
345
346pub const PAMOJA_SX127X_IRQ_PAYLOAD_CRC_ERROR: u8 = 0x20;
348
349pub const PAMOJA_SX127X_IRQ_RX_DONE: u8 = 0x40;
351
352pub const PAMOJA_SX127X_IRQ_RX_TIMEOUT: u8 = 0x80;
354
355pub const PAMOJA_SX127X_IRQ_ALL: u8 = 0xFF;
357
358const _: () = assert!(PAMOJA_SX127X_VERSION == sx127x_register::VERSION_SX1276);
361const _: () = assert!(PAMOJA_SX127X_WRITE == sx127x_register::WRITE);
362const _: () = assert!(PAMOJA_SX127X_DIO0_RX_DONE == sx127x_config::DIO0_RX_DONE);
363const _: () = assert!(PAMOJA_SX127X_DIO0_TX_DONE == sx127x_config::DIO0_TX_DONE);
364const _: () = assert!(PAMOJA_SX127X_DIO0_CAD_DONE == sx127x_config::DIO0_CAD_DONE);
365const _: () = assert!(PAMOJA_SX127X_PA_DAC_DEFAULT == sx127x_config::PA_DAC_DEFAULT);
366const _: () = assert!(PAMOJA_SX127X_PA_DAC_HIGH_POWER == sx127x_config::PA_DAC_HIGH_POWER);
367const _: () = assert!(PAMOJA_SX127X_IMAGE_CAL_START == sx127x_config::IMAGE_CAL_START);
368const _: () = assert!(PAMOJA_SX127X_IMAGE_CAL_RUNNING == sx127x_config::IMAGE_CAL_RUNNING);
369const _: () = assert!(PAMOJA_SX127X_SYNC_WORD_PUBLIC == sx127x_config::SyncWord::Public.to_byte());
370const _: () =
371 assert!(PAMOJA_SX127X_SYNC_WORD_PRIVATE == sx127x_config::SyncWord::Private.to_byte());
372const _: () = assert!(PAMOJA_SX127X_LNA_BOOSTED == sx127x_config::LNA_BOOSTED);
373const _: () = assert!(PAMOJA_SX127X_TCXO_INPUT_ON == sx127x_config::TCXO_INPUT_ON);
374const _: () = assert!(PAMOJA_SX127X_REG_FIFO == sx127x_register::FIFO);
375const _: () = assert!(PAMOJA_SX127X_REG_OP_MODE == sx127x_register::OP_MODE);
376const _: () = assert!(PAMOJA_SX127X_REG_FRF_MSB == sx127x_register::FRF_MSB);
377const _: () = assert!(PAMOJA_SX127X_REG_FRF_MID == sx127x_register::FRF_MID);
378const _: () = assert!(PAMOJA_SX127X_REG_FRF_LSB == sx127x_register::FRF_LSB);
379const _: () = assert!(PAMOJA_SX127X_REG_PA_CONFIG == sx127x_register::PA_CONFIG);
380const _: () = assert!(PAMOJA_SX127X_REG_PA_RAMP == sx127x_register::PA_RAMP);
381const _: () = assert!(PAMOJA_SX127X_REG_OCP == sx127x_register::OCP);
382const _: () = assert!(PAMOJA_SX127X_REG_LNA == sx127x_register::LNA);
383const _: () = assert!(PAMOJA_SX127X_REG_FIFO_ADDR_PTR == sx127x_register::FIFO_ADDR_PTR);
384const _: () = assert!(PAMOJA_SX127X_REG_FIFO_TX_BASE_ADDR == sx127x_register::FIFO_TX_BASE_ADDR);
385const _: () = assert!(PAMOJA_SX127X_REG_FIFO_RX_BASE_ADDR == sx127x_register::FIFO_RX_BASE_ADDR);
386const _: () =
387 assert!(PAMOJA_SX127X_REG_FIFO_RX_CURRENT_ADDR == sx127x_register::FIFO_RX_CURRENT_ADDR);
388const _: () = assert!(PAMOJA_SX127X_REG_IRQ_FLAGS_MASK == sx127x_register::IRQ_FLAGS_MASK);
389const _: () = assert!(PAMOJA_SX127X_REG_IRQ_FLAGS == sx127x_register::IRQ_FLAGS);
390const _: () = assert!(PAMOJA_SX127X_REG_RX_NB_BYTES == sx127x_register::RX_NB_BYTES);
391const _: () = assert!(PAMOJA_SX127X_REG_MODEM_STAT == sx127x_register::MODEM_STAT);
392const _: () = assert!(PAMOJA_SX127X_REG_PKT_SNR_VALUE == sx127x_register::PKT_SNR_VALUE);
393const _: () = assert!(PAMOJA_SX127X_REG_PKT_RSSI_VALUE == sx127x_register::PKT_RSSI_VALUE);
394const _: () = assert!(PAMOJA_SX127X_REG_RSSI_VALUE == sx127x_register::RSSI_VALUE);
395const _: () = assert!(PAMOJA_SX127X_REG_HOP_CHANNEL == sx127x_register::HOP_CHANNEL);
396const _: () = assert!(PAMOJA_SX127X_REG_MODEM_CONFIG_1 == sx127x_register::MODEM_CONFIG_1);
397const _: () = assert!(PAMOJA_SX127X_REG_MODEM_CONFIG_2 == sx127x_register::MODEM_CONFIG_2);
398const _: () = assert!(PAMOJA_SX127X_REG_SYMB_TIMEOUT_LSB == sx127x_register::SYMB_TIMEOUT_LSB);
399const _: () = assert!(PAMOJA_SX127X_REG_PREAMBLE_MSB == sx127x_register::PREAMBLE_MSB);
400const _: () = assert!(PAMOJA_SX127X_REG_PREAMBLE_LSB == sx127x_register::PREAMBLE_LSB);
401const _: () = assert!(PAMOJA_SX127X_REG_PAYLOAD_LENGTH == sx127x_register::PAYLOAD_LENGTH);
402const _: () = assert!(PAMOJA_SX127X_REG_MAX_PAYLOAD_LENGTH == sx127x_register::MAX_PAYLOAD_LENGTH);
403const _: () = assert!(PAMOJA_SX127X_REG_MODEM_CONFIG_3 == sx127x_register::MODEM_CONFIG_3);
404const _: () = assert!(PAMOJA_SX127X_REG_RSSI_WIDEBAND == sx127x_register::RSSI_WIDEBAND);
405const _: () = assert!(PAMOJA_SX127X_REG_IF_FREQ_2 == sx127x_register::IF_FREQ_2);
406const _: () = assert!(PAMOJA_SX127X_REG_IF_FREQ_1 == sx127x_register::IF_FREQ_1);
407const _: () = assert!(PAMOJA_SX127X_REG_DETECT_OPTIMIZE == sx127x_register::DETECT_OPTIMIZE);
408const _: () = assert!(PAMOJA_SX127X_REG_INVERT_IQ == sx127x_register::INVERT_IQ);
409const _: () = assert!(PAMOJA_SX127X_REG_HIGH_BW_OPTIMIZE_1 == sx127x_register::HIGH_BW_OPTIMIZE_1);
410const _: () =
411 assert!(PAMOJA_SX127X_REG_DETECTION_THRESHOLD == sx127x_register::DETECTION_THRESHOLD);
412const _: () = assert!(PAMOJA_SX127X_REG_SYNC_WORD == sx127x_register::SYNC_WORD);
413const _: () = assert!(PAMOJA_SX127X_REG_HIGH_BW_OPTIMIZE_2 == sx127x_register::HIGH_BW_OPTIMIZE_2);
414const _: () = assert!(PAMOJA_SX127X_REG_INVERT_IQ_2 == sx127x_register::INVERT_IQ_2);
415const _: () = assert!(PAMOJA_SX127X_REG_IMAGE_CAL == sx127x_register::IMAGE_CAL);
416const _: () = assert!(PAMOJA_SX127X_REG_DIO_MAPPING_1 == sx127x_register::DIO_MAPPING_1);
417const _: () = assert!(PAMOJA_SX127X_REG_DIO_MAPPING_2 == sx127x_register::DIO_MAPPING_2);
418const _: () = assert!(PAMOJA_SX127X_REG_VERSION == sx127x_register::VERSION);
419const _: () = assert!(PAMOJA_SX127X_REG_TCXO == sx127x_register::TCXO);
420const _: () = assert!(PAMOJA_SX127X_REG_PA_DAC == sx127x_register::PA_DAC);
421const _: () = assert!(PAMOJA_SX127X_MODE_SLEEP == Sx127xMode::Sleep.code());
422const _: () = assert!(PAMOJA_SX127X_MODE_STANDBY == Sx127xMode::Standby.code());
423const _: () = assert!(PAMOJA_SX127X_MODE_FS_TX == Sx127xMode::FsTx.code());
424const _: () = assert!(PAMOJA_SX127X_MODE_TX == Sx127xMode::Tx.code());
425const _: () = assert!(PAMOJA_SX127X_MODE_FS_RX == Sx127xMode::FsRx.code());
426const _: () = assert!(PAMOJA_SX127X_MODE_RX_CONTINUOUS == Sx127xMode::RxContinuous.code());
427const _: () = assert!(PAMOJA_SX127X_MODE_RX_SINGLE == Sx127xMode::RxSingle.code());
428const _: () = assert!(PAMOJA_SX127X_MODE_CAD == Sx127xMode::Cad.code());
429const _: () = assert!(PAMOJA_SX127X_IRQ_CAD_DETECTED == IrqFlags::CAD_DETECTED.bits());
430const _: () =
431 assert!(PAMOJA_SX127X_IRQ_FHSS_CHANGE_CHANNEL == IrqFlags::FHSS_CHANGE_CHANNEL.bits());
432const _: () = assert!(PAMOJA_SX127X_IRQ_CAD_DONE == IrqFlags::CAD_DONE.bits());
433const _: () = assert!(PAMOJA_SX127X_IRQ_TX_DONE == IrqFlags::TX_DONE.bits());
434const _: () = assert!(PAMOJA_SX127X_IRQ_VALID_HEADER == IrqFlags::VALID_HEADER.bits());
435const _: () = assert!(PAMOJA_SX127X_IRQ_PAYLOAD_CRC_ERROR == IrqFlags::PAYLOAD_CRC_ERROR.bits());
436const _: () = assert!(PAMOJA_SX127X_IRQ_RX_DONE == IrqFlags::RX_DONE.bits());
437const _: () = assert!(PAMOJA_SX127X_IRQ_RX_TIMEOUT == IrqFlags::RX_TIMEOUT.bits());
438const _: () = assert!(PAMOJA_SX127X_IRQ_ALL == IrqFlags::ALL.bits());
439
440#[repr(C)]
442#[derive(Clone, Copy, Debug, PartialEq, Eq)]
443pub struct PamojaSx126xCommand {
444 pub bytes: [u8; PAMOJA_SX126X_COMMAND_MAX],
446 pub len: u8,
448}
449
450#[repr(C)]
452#[derive(Clone, Copy, Debug, PartialEq, Eq)]
453pub struct PamojaSx126xQuery {
454 pub command: PamojaSx126xCommand,
456 pub answer_len: u32,
458}
459
460#[repr(C)]
463#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464pub struct PamojaSx126xTxPower {
465 pub pa_duty_cycle: u8,
467 pub hp_max: u8,
469 pub device_sel: u8,
471 pub pa_lut: u8,
473 pub setting_dbm: i8,
475}
476
477#[repr(u8)]
479#[derive(Clone, Copy, Debug, PartialEq, Eq)]
480pub enum PamojaSx126xChipMode {
481 Other = 0,
483 StandbyRc = 2,
485 StandbyXosc = 3,
487 Fs = 4,
489 Rx = 5,
491 Tx = 6,
493}
494
495#[repr(u8)]
497#[derive(Clone, Copy, Debug, PartialEq, Eq)]
498pub enum PamojaSx126xCommandStatus {
499 Other = 0,
501 DataAvailable = 2,
503 Timeout = 3,
505 ProcessingError = 4,
507 ExecutionFailure = 5,
509 TxDone = 6,
511}
512
513#[repr(C)]
515#[derive(Clone, Copy, Debug, PartialEq, Eq)]
516pub struct PamojaSx126xStatus {
517 pub chip_mode: PamojaSx126xChipMode,
519 pub command_status: PamojaSx126xCommandStatus,
521 pub error: bool,
523}
524
525#[repr(C)]
527#[derive(Clone, Copy, Debug, PartialEq, Eq)]
528pub struct PamojaSx126xPacketStatus {
529 pub rssi_centi_dbm: i32,
531 pub snr_centi_db: i32,
533 pub signal_rssi_centi_dbm: i32,
535}
536
537#[repr(C)]
539#[derive(Clone, Copy, Debug, PartialEq, Eq)]
540pub struct PamojaSx126xRxBufferStatus {
541 pub payload_len: u8,
543 pub start: u8,
545}
546
547pub struct PamojaRadioDutyCycle {
553 guard: DutyCycle,
554}
555
556#[no_mangle]
566pub extern "C" fn pamoja_sx126x_frequency_word(frequency_hz: u32) -> u32 {
567 config::frequency_word(frequency_hz)
568}
569
570#[no_mangle]
581pub extern "C" fn pamoja_sx126x_timeout_steps(timeout_us: u64) -> u32 {
582 config::timeout_steps(timeout_us)
583}
584
585#[no_mangle]
596pub extern "C" fn pamoja_sx126x_image_calibration(low_hz: u32, high_hz: u32) -> u16 {
597 u16::from_be_bytes(config::image_calibration(low_hz, high_hz))
598}
599
600#[no_mangle]
610pub extern "C" fn pamoja_sx126x_ramp_time_us(at_least_us: u32) -> u32 {
611 RampTime::at_least(at_least_us).micros()
612}
613
614#[no_mangle]
626pub extern "C" fn pamoja_sx126x_tx_power_for_output(
627 high_power: bool,
628 output_dbm: i8,
629) -> PamojaSx126xTxPower {
630 power_of(TxPower::for_output(amplifier(high_power), output_dbm))
631}
632
633#[no_mangle]
645pub extern "C" fn pamoja_sx126x_tx_power_under_ceiling(
646 high_power: bool,
647 budget: PamojaLoraLinkBudget,
648 eirp_ceiling_centi_dbm: i32,
649) -> PamojaSx126xTxPower {
650 power_of(TxPower::under_ceiling(
651 amplifier(high_power),
652 &link_budget(budget),
653 Decibels::from_hundredths(eirp_ceiling_centi_dbm),
654 ))
655}
656
657#[no_mangle]
663pub extern "C" fn pamoja_sx126x_set_standby() -> PamojaSx126xCommand {
664 command_of(command::set_standby(StandbyMode::Rc))
665}
666
667#[no_mangle]
673pub extern "C" fn pamoja_sx126x_set_packet_type_lora() -> PamojaSx126xCommand {
674 command_of(command::set_packet_type(PacketType::Lora))
675}
676
677#[no_mangle]
687pub extern "C" fn pamoja_sx126x_set_rf_frequency(frequency_hz: u32) -> PamojaSx126xCommand {
688 command_of(command::set_rf_frequency(config::frequency_word(
689 frequency_hz,
690 )))
691}
692
693#[no_mangle]
704pub extern "C" fn pamoja_sx126x_calibrate_image(low_hz: u32, high_hz: u32) -> PamojaSx126xCommand {
705 command_of(command::calibrate_image(config::image_calibration(
706 low_hz, high_hz,
707 )))
708}
709
710#[no_mangle]
721pub extern "C" fn pamoja_sx126x_set_pa_config(power: PamojaSx126xTxPower) -> PamojaSx126xCommand {
722 command_of(command::set_pa_config(tx_power(power).pa))
723}
724
725#[no_mangle]
737pub extern "C" fn pamoja_sx126x_set_tx_params(
738 power: PamojaSx126xTxPower,
739 ramp_us: u32,
740) -> PamojaSx126xCommand {
741 command_of(command::set_tx_params(
742 power.setting_dbm,
743 RampTime::at_least(ramp_us),
744 ))
745}
746
747#[no_mangle]
763pub unsafe extern "C" fn pamoja_sx126x_set_lora_modulation_params(
764 link: PamojaLoraLink,
765 out_command: *mut PamojaSx126xCommand,
766) -> PamojaStatus {
767 if out_command.is_null() {
768 set_last_error("out_command must not be null".to_owned());
769 return PamojaStatus::InvalidArgument;
770 }
771 match LoraModulation::from_link(&settings(link)) {
772 Some(modulation) => {
773 *out_command = command_of(command::set_lora_modulation_params(modulation));
774 PamojaStatus::Ok
775 }
776 None => {
777 set_last_error(format!(
778 "the SX126x has no {} Hz LoRa bandwidth",
779 link.bandwidth_hz
780 ));
781 PamojaStatus::InvalidArgument
782 }
783 }
784}
785
786#[no_mangle]
798pub extern "C" fn pamoja_sx126x_set_lora_packet_params(
799 link: PamojaLoraLink,
800 payload_len: u8,
801 invert_iq: bool,
802) -> PamojaSx126xCommand {
803 command_of(command::set_lora_packet_params(LoraPacket::from_link(
804 &settings(link),
805 payload_len,
806 invert_iq,
807 )))
808}
809
810#[no_mangle]
823pub extern "C" fn pamoja_sx126x_set_dio_irq_params(
824 irq: u16,
825 dio1: u16,
826 dio2: u16,
827 dio3: u16,
828) -> PamojaSx126xCommand {
829 command_of(command::set_dio_irq_params(
830 Irq::from_bits(irq),
831 Irq::from_bits(dio1),
832 Irq::from_bits(dio2),
833 Irq::from_bits(dio3),
834 ))
835}
836
837#[no_mangle]
847pub extern "C" fn pamoja_sx126x_clear_irq_status(irq: u16) -> PamojaSx126xCommand {
848 command_of(command::clear_irq_status(Irq::from_bits(irq)))
849}
850
851#[no_mangle]
862pub extern "C" fn pamoja_sx126x_set_tx(timeout_us: u64) -> PamojaSx126xCommand {
863 command_of(command::set_tx(config::timeout_steps(timeout_us)))
864}
865
866#[no_mangle]
877pub extern "C" fn pamoja_sx126x_set_rx(timeout_us: u64) -> PamojaSx126xCommand {
878 command_of(command::set_rx(config::timeout_steps(timeout_us)))
879}
880
881#[no_mangle]
887pub extern "C" fn pamoja_sx126x_set_rx_continuous() -> PamojaSx126xCommand {
888 command_of(command::set_rx(config::RX_CONTINUOUS))
889}
890
891#[no_mangle]
901pub extern "C" fn pamoja_sx126x_set_sleep(warm_start: bool) -> PamojaSx126xCommand {
902 command_of(command::set_sleep(warm_start, false))
903}
904
905#[no_mangle]
916pub extern "C" fn pamoja_sx126x_write_register_header(address: u16) -> PamojaSx126xCommand {
917 command_of(command::write_register(address))
918}
919
920#[no_mangle]
930pub extern "C" fn pamoja_sx126x_write_buffer_header(offset: u8) -> PamojaSx126xCommand {
931 command_of(command::write_buffer(offset))
932}
933
934#[no_mangle]
940pub extern "C" fn pamoja_sx126x_get_status() -> PamojaSx126xQuery {
941 query_of(command::get_status())
942}
943
944#[no_mangle]
950pub extern "C" fn pamoja_sx126x_get_irq_status() -> PamojaSx126xQuery {
951 query_of(command::get_irq_status())
952}
953
954#[no_mangle]
960pub extern "C" fn pamoja_sx126x_get_rx_buffer_status() -> PamojaSx126xQuery {
961 query_of(command::get_rx_buffer_status())
962}
963
964#[no_mangle]
970pub extern "C" fn pamoja_sx126x_get_packet_status() -> PamojaSx126xQuery {
971 query_of(command::get_packet_status())
972}
973
974#[no_mangle]
980pub extern "C" fn pamoja_sx126x_get_rssi_inst() -> PamojaSx126xQuery {
981 query_of(command::get_rssi_inst())
982}
983
984#[no_mangle]
990pub extern "C" fn pamoja_sx126x_get_device_errors() -> PamojaSx126xQuery {
991 query_of(command::get_device_errors())
992}
993
994#[no_mangle]
1005pub extern "C" fn pamoja_sx126x_read_register(address: u16, len: u8) -> PamojaSx126xQuery {
1006 query_of(command::read_register(address, usize::from(len)))
1007}
1008
1009#[no_mangle]
1020pub extern "C" fn pamoja_sx126x_read_buffer(offset: u8, len: u8) -> PamojaSx126xQuery {
1021 query_of(command::read_buffer(offset, usize::from(len)))
1022}
1023
1024#[no_mangle]
1034pub extern "C" fn pamoja_sx126x_status_from_byte(byte: u8) -> PamojaSx126xStatus {
1035 let status = Status::from_byte(byte);
1036 PamojaSx126xStatus {
1037 chip_mode: chip_mode(status.chip_mode),
1038 command_status: command_status(status.command_status),
1039 error: status.is_error(),
1040 }
1041}
1042
1043#[no_mangle]
1054pub extern "C" fn pamoja_sx126x_irq_from_bytes(high: u8, low: u8) -> u16 {
1055 Irq::from_bytes([high, low]).bits()
1056}
1057
1058#[no_mangle]
1069pub extern "C" fn pamoja_sx126x_device_errors_from_bytes(high: u8, low: u8) -> u16 {
1070 DeviceErrors::from_bytes([high, low]).bits()
1071}
1072
1073#[no_mangle]
1085pub extern "C" fn pamoja_sx126x_packet_status_from_bytes(
1086 rssi_pkt: u8,
1087 snr_pkt: u8,
1088 signal_rssi_pkt: u8,
1089) -> PamojaSx126xPacketStatus {
1090 let status = PacketStatus::from_bytes([rssi_pkt, snr_pkt, signal_rssi_pkt]);
1091 PamojaSx126xPacketStatus {
1092 rssi_centi_dbm: status.rssi_dbm.hundredths(),
1093 snr_centi_db: status.snr_db.hundredths(),
1094 signal_rssi_centi_dbm: status.signal_rssi_dbm.hundredths(),
1095 }
1096}
1097
1098#[no_mangle]
1109pub extern "C" fn pamoja_sx126x_rx_buffer_status_from_bytes(
1110 payload_len: u8,
1111 start: u8,
1112) -> PamojaSx126xRxBufferStatus {
1113 let status = RxBufferStatus::from_bytes([payload_len, start]);
1114 PamojaSx126xRxBufferStatus {
1115 payload_len: status.payload_len,
1116 start: status.start,
1117 }
1118}
1119
1120#[no_mangle]
1130pub extern "C" fn pamoja_sx126x_rssi_inst_centi_dbm(byte: u8) -> i32 {
1131 rssi_inst_dbm(byte).hundredths()
1132}
1133
1134#[no_mangle]
1145pub extern "C" fn pamoja_radio_duty_cycle_new(permille: u32) -> *mut PamojaRadioDutyCycle {
1146 Box::into_raw(Box::new(PamojaRadioDutyCycle {
1147 guard: DutyCycle::new(permille),
1148 }))
1149}
1150
1151#[no_mangle]
1168pub unsafe extern "C" fn pamoja_radio_duty_cycle_transmitted(
1169 guard: *mut PamojaRadioDutyCycle,
1170 started_us: u64,
1171 link: PamojaLoraLink,
1172 payload_len: usize,
1173) -> u64 {
1174 if guard.is_null() {
1175 return 0;
1176 }
1177 (*guard)
1178 .guard
1179 .transmitted(started_us, &settings(link), payload_len)
1180}
1181
1182#[no_mangle]
1198pub unsafe extern "C" fn pamoja_radio_duty_cycle_wait_us(
1199 guard: *const PamojaRadioDutyCycle,
1200 now_us: u64,
1201) -> u64 {
1202 if guard.is_null() {
1203 return u64::MAX;
1204 }
1205 (*guard).guard.wait_us(now_us)
1206}
1207
1208#[no_mangle]
1224pub unsafe extern "C" fn pamoja_radio_duty_cycle_ready(
1225 guard: *const PamojaRadioDutyCycle,
1226 now_us: u64,
1227) -> bool {
1228 !guard.is_null() && (*guard).guard.ready(now_us)
1229}
1230
1231#[no_mangle]
1246pub unsafe extern "C" fn pamoja_radio_duty_cycle_earliest_us(
1247 guard: *const PamojaRadioDutyCycle,
1248) -> u64 {
1249 if guard.is_null() {
1250 return u64::MAX;
1251 }
1252 (*guard).guard.earliest_us()
1253}
1254
1255#[no_mangle]
1264pub unsafe extern "C" fn pamoja_radio_duty_cycle_free(guard: *mut PamojaRadioDutyCycle) {
1265 if !guard.is_null() {
1266 drop(Box::from_raw(guard));
1267 }
1268}
1269
1270fn command_of(command: Command) -> PamojaSx126xCommand {
1272 let bytes = command.as_bytes();
1273 let mut out = [0u8; PAMOJA_SX126X_COMMAND_MAX];
1274 out[..bytes.len()].copy_from_slice(bytes);
1275 PamojaSx126xCommand {
1276 bytes: out,
1277 len: bytes.len() as u8,
1278 }
1279}
1280
1281fn query_of(query: Query) -> PamojaSx126xQuery {
1283 PamojaSx126xQuery {
1284 command: command_of(query.command),
1285 answer_len: query.answer_len as u32,
1286 }
1287}
1288
1289fn amplifier(high_power: bool) -> PowerAmplifier {
1291 if high_power {
1292 PowerAmplifier::HighPower
1293 } else {
1294 PowerAmplifier::LowPower
1295 }
1296}
1297
1298fn power_of(power: TxPower) -> PamojaSx126xTxPower {
1300 PamojaSx126xTxPower {
1301 pa_duty_cycle: power.pa.duty_cycle,
1302 hp_max: power.pa.hp_max,
1303 device_sel: power.pa.device,
1304 pa_lut: power.pa.lut,
1305 setting_dbm: power.setting_dbm,
1306 }
1307}
1308
1309fn tx_power(power: PamojaSx126xTxPower) -> TxPower {
1311 TxPower {
1312 pa: PaConfig {
1313 duty_cycle: power.pa_duty_cycle,
1314 hp_max: power.hp_max,
1315 device: power.device_sel,
1316 lut: power.pa_lut,
1317 },
1318 setting_dbm: power.setting_dbm,
1319 }
1320}
1321
1322fn chip_mode(mode: ChipMode) -> PamojaSx126xChipMode {
1324 if mode == ChipMode::StandbyRc {
1325 PamojaSx126xChipMode::StandbyRc
1326 } else if mode == ChipMode::StandbyXosc {
1327 PamojaSx126xChipMode::StandbyXosc
1328 } else if mode == ChipMode::Fs {
1329 PamojaSx126xChipMode::Fs
1330 } else if mode == ChipMode::Rx {
1331 PamojaSx126xChipMode::Rx
1332 } else if mode == ChipMode::Tx {
1333 PamojaSx126xChipMode::Tx
1334 } else {
1335 PamojaSx126xChipMode::Other
1336 }
1337}
1338
1339fn command_status(status: CommandStatus) -> PamojaSx126xCommandStatus {
1341 if status == CommandStatus::DataAvailable {
1342 PamojaSx126xCommandStatus::DataAvailable
1343 } else if status == CommandStatus::Timeout {
1344 PamojaSx126xCommandStatus::Timeout
1345 } else if status == CommandStatus::ProcessingError {
1346 PamojaSx126xCommandStatus::ProcessingError
1347 } else if status == CommandStatus::ExecutionFailure {
1348 PamojaSx126xCommandStatus::ExecutionFailure
1349 } else if status == CommandStatus::TxDone {
1350 PamojaSx126xCommandStatus::TxDone
1351 } else {
1352 PamojaSx126xCommandStatus::Other
1353 }
1354}
1355
1356#[repr(C)]
1358#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1359pub struct PamojaSx127xTxPower {
1360 pub pa_config: u8,
1362 pub pa_dac: u8,
1364 pub ocp: u8,
1366 pub output_dbm: i8,
1368}
1369
1370#[repr(C)]
1372#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1373pub struct PamojaSx127xModem {
1374 pub modem_config_1: u8,
1376 pub modem_config_2: u8,
1378 pub modem_config_3: u8,
1380 pub detection_optimize: u8,
1383 pub detection_threshold: u8,
1385}
1386
1387#[repr(C)]
1389#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1390pub struct PamojaSx127xPacketStatus {
1391 pub rssi_centi_dbm: i32,
1393 pub snr_centi_db: i32,
1395 pub signal_rssi_centi_dbm: i32,
1397}
1398
1399#[repr(C)]
1401#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1402pub struct PamojaSx127xModemStatus {
1403 pub coding_rate_denominator: u8,
1406 pub clear: bool,
1408 pub header_valid: bool,
1410 pub rx_ongoing: bool,
1412 pub signal_synchronized: bool,
1414 pub signal_detected: bool,
1416}
1417
1418#[repr(C)]
1420#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1421pub struct PamojaSx127xHighBwOptimize {
1422 pub optimize_1: u8,
1424 pub optimize_2: u8,
1426 pub has_optimize_2: bool,
1428}
1429
1430#[repr(C)]
1432#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1433pub struct PamojaSx127xSpuriousReception {
1434 pub automatic_if: bool,
1436 pub has_if_freq_2: bool,
1438 pub if_freq_2: u8,
1440 pub offset_hz: u32,
1442}
1443
1444#[no_mangle]
1455pub extern "C" fn pamoja_sx126x_llcc68_supports(link: PamojaLoraLink) -> bool {
1456 LoraModulation::from_link(&settings(link)).is_some_and(|modulation| {
1457 llcc68_supports(modulation.spreading_factor, modulation.bandwidth)
1458 })
1459}
1460
1461#[no_mangle]
1471pub extern "C" fn pamoja_sx127x_frequency_word(frequency_hz: u32) -> u32 {
1472 sx127x_config::frequency_word(frequency_hz)
1473}
1474
1475#[no_mangle]
1485pub extern "C" fn pamoja_sx127x_frequency_from_word(word: u32) -> u32 {
1486 sx127x_config::frequency_from_word(word)
1487}
1488
1489#[no_mangle]
1499pub extern "C" fn pamoja_sx127x_read_address(address: u8) -> u8 {
1500 sx127x_register::read_address(address)
1501}
1502
1503#[no_mangle]
1513pub extern "C" fn pamoja_sx127x_write_address(address: u8) -> u8 {
1514 sx127x_register::write_address(address)
1515}
1516
1517#[no_mangle]
1527pub extern "C" fn pamoja_sx127x_lora_op_mode(mode: u8) -> u8 {
1528 sx127x_register::lora_op_mode(Sx127xMode::from_op_mode(mode))
1529}
1530
1531#[no_mangle]
1541pub extern "C" fn pamoja_sx127x_fsk_op_mode(mode: u8) -> u8 {
1542 sx127x_register::fsk_op_mode(Sx127xMode::from_op_mode(mode))
1543}
1544
1545#[no_mangle]
1555pub extern "C" fn pamoja_sx127x_mode_from_op_mode(op_mode: u8) -> u8 {
1556 Sx127xMode::from_op_mode(op_mode).code()
1557}
1558
1559#[no_mangle]
1579pub unsafe extern "C" fn pamoja_sx127x_modem(
1580 link: PamojaLoraLink,
1581 frequency_hz: u32,
1582 symbol_timeout: u16,
1583 out_modem: *mut PamojaSx127xModem,
1584) -> PamojaStatus {
1585 if out_modem.is_null() {
1586 set_last_error("out_modem must not be null".to_owned());
1587 return PamojaStatus::InvalidArgument;
1588 }
1589 match sx127x_modulation(link, frequency_hz) {
1590 Ok(modulation) => {
1591 *out_modem = PamojaSx127xModem {
1592 modem_config_1: modulation.modem_config_1(),
1593 modem_config_2: modulation.modem_config_2(symbol_timeout),
1594 modem_config_3: modulation.modem_config_3(),
1595 detection_optimize: modulation.detect_optimize(0),
1596 detection_threshold: modulation.detection_threshold(),
1597 };
1598 PamojaStatus::Ok
1599 }
1600 Err(error) => {
1601 set_last_error(modulation_message(error));
1602 PamojaStatus::InvalidArgument
1603 }
1604 }
1605}
1606
1607#[no_mangle]
1618pub extern "C" fn pamoja_sx127x_symbol_timeout(link: PamojaLoraLink, timeout_us: u64) -> u16 {
1619 sx127x_config::symbol_timeout(&settings(link), timeout_us)
1620}
1621
1622#[no_mangle]
1633pub extern "C" fn pamoja_sx127x_tx_power_for_output(
1634 pa_boost: bool,
1635 output_dbm: i8,
1636) -> PamojaSx127xTxPower {
1637 sx127x_power_of(Sx127xPower::for_output(pa_output(pa_boost), output_dbm))
1638}
1639
1640#[no_mangle]
1652pub extern "C" fn pamoja_sx127x_tx_power_under_ceiling(
1653 pa_boost: bool,
1654 budget: PamojaLoraLinkBudget,
1655 eirp_ceiling_centi_dbm: i32,
1656) -> PamojaSx127xTxPower {
1657 sx127x_power_of(Sx127xPower::under_ceiling(
1658 pa_output(pa_boost),
1659 &link_budget(budget),
1660 Decibels::from_hundredths(eirp_ceiling_centi_dbm),
1661 ))
1662}
1663
1664#[no_mangle]
1674pub extern "C" fn pamoja_sx127x_ocp_register(milliamps: u16) -> u8 {
1675 sx127x_config::ocp_register(milliamps)
1676}
1677
1678#[no_mangle]
1690pub extern "C" fn pamoja_sx127x_invert_iq(receive: bool, transmit: bool) -> u8 {
1691 sx127x_config::invert_iq(receive, transmit)
1692}
1693
1694#[no_mangle]
1704pub extern "C" fn pamoja_sx127x_invert_iq_2(inverted: bool) -> u8 {
1705 sx127x_config::invert_iq_2(inverted)
1706}
1707
1708#[no_mangle]
1725pub unsafe extern "C" fn pamoja_sx127x_high_bw_optimize(
1726 link: PamojaLoraLink,
1727 frequency_hz: u32,
1728 out_optimize: *mut PamojaSx127xHighBwOptimize,
1729) -> PamojaStatus {
1730 if out_optimize.is_null() {
1731 set_last_error("out_optimize must not be null".to_owned());
1732 return PamojaStatus::InvalidArgument;
1733 }
1734 let Some(bandwidth) = sx127x_bandwidth(link) else {
1735 return PamojaStatus::InvalidArgument;
1736 };
1737 let (optimize_1, optimize_2) = sx127x_config::high_bw_optimize(bandwidth, frequency_hz);
1738 *out_optimize = PamojaSx127xHighBwOptimize {
1739 optimize_1,
1740 optimize_2: optimize_2.unwrap_or(0),
1741 has_optimize_2: optimize_2.is_some(),
1742 };
1743 PamojaStatus::Ok
1744}
1745
1746#[no_mangle]
1762pub unsafe extern "C" fn pamoja_sx127x_spurious_reception(
1763 link: PamojaLoraLink,
1764 out_erratum: *mut PamojaSx127xSpuriousReception,
1765) -> PamojaStatus {
1766 if out_erratum.is_null() {
1767 set_last_error("out_erratum must not be null".to_owned());
1768 return PamojaStatus::InvalidArgument;
1769 }
1770 let Some(bandwidth) = sx127x_bandwidth(link) else {
1771 return PamojaStatus::InvalidArgument;
1772 };
1773 let erratum = sx127x_config::spurious_reception(bandwidth);
1774 *out_erratum = PamojaSx127xSpuriousReception {
1775 automatic_if: erratum.automatic_if,
1776 has_if_freq_2: erratum.if_freq_2.is_some(),
1777 if_freq_2: erratum.if_freq_2.unwrap_or(0),
1778 offset_hz: erratum.offset_hz,
1779 };
1780 PamojaStatus::Ok
1781}
1782
1783#[no_mangle]
1793pub extern "C" fn pamoja_sx127x_image_cal_start(current: u8) -> u8 {
1794 sx127x_config::image_cal_start(current)
1795}
1796
1797#[no_mangle]
1808pub extern "C" fn pamoja_sx127x_automatic_if(current: u8, automatic_if: bool) -> u8 {
1809 sx127x_config::automatic_if(current, automatic_if)
1810}
1811
1812#[no_mangle]
1825pub extern "C" fn pamoja_sx127x_packet_status_from_bytes(
1826 pkt_snr: u8,
1827 pkt_rssi: u8,
1828 frequency_hz: u32,
1829) -> PamojaSx127xPacketStatus {
1830 let status =
1831 Sx127xPacketStatus::from_bytes([pkt_snr, pkt_rssi], Port::for_frequency(frequency_hz));
1832 PamojaSx127xPacketStatus {
1833 rssi_centi_dbm: status.rssi_dbm.hundredths(),
1834 snr_centi_db: status.snr_db.hundredths(),
1835 signal_rssi_centi_dbm: status.signal_rssi_dbm.hundredths(),
1836 }
1837}
1838
1839#[no_mangle]
1850pub extern "C" fn pamoja_sx127x_rssi_centi_dbm(byte: u8, frequency_hz: u32) -> i32 {
1851 sx127x_rssi_dbm(byte, Port::for_frequency(frequency_hz)).hundredths()
1852}
1853
1854#[no_mangle]
1864pub extern "C" fn pamoja_sx127x_modem_status_from_byte(byte: u8) -> PamojaSx127xModemStatus {
1865 let status = Sx127xModemStatus::from_byte(byte);
1866 PamojaSx127xModemStatus {
1867 coding_rate_denominator: status.coding_rate_denominator.unwrap_or(0),
1868 clear: status.clear,
1869 header_valid: status.header_valid,
1870 rx_ongoing: status.rx_ongoing,
1871 signal_synchronized: status.signal_synchronized,
1872 signal_detected: status.signal_detected,
1873 }
1874}
1875
1876fn sx127x_modulation(
1878 link: PamojaLoraLink,
1879 frequency_hz: u32,
1880) -> Result<Sx127xModulation, ModulationError> {
1881 let modulation = Sx127xModulation::from_link(&settings(link))?;
1882 if modulation.bandwidth.in_band(frequency_hz) {
1883 Ok(modulation)
1884 } else {
1885 Err(ModulationError::Bandwidth(link.bandwidth_hz))
1886 }
1887}
1888
1889fn sx127x_bandwidth(link: PamojaLoraLink) -> Option<Sx127xBandwidth> {
1891 let bandwidth = Sx127xBandwidth::from_hz(link.bandwidth_hz);
1892 if bandwidth.is_none() {
1893 set_last_error(format!(
1894 "the SX127x has no {} Hz LoRa bandwidth",
1895 link.bandwidth_hz
1896 ));
1897 }
1898 bandwidth
1899}
1900
1901fn modulation_message(error: ModulationError) -> String {
1903 match error {
1904 ModulationError::Bandwidth(hz) => {
1905 format!("the SX127x has no {hz} Hz LoRa bandwidth at this carrier")
1906 }
1907 ModulationError::SpreadingFactor(sf) => format!("the SX127x has no SF{sf}"),
1908 ModulationError::ExplicitHeaderAtSf6 => "SF6 needs an implicit header".to_owned(),
1909 }
1910}
1911
1912fn pa_output(pa_boost: bool) -> PaOutput {
1914 if pa_boost {
1915 PaOutput::PaBoost
1916 } else {
1917 PaOutput::Rfo
1918 }
1919}
1920
1921fn sx127x_power_of(power: Sx127xPower) -> PamojaSx127xTxPower {
1923 PamojaSx127xTxPower {
1924 pa_config: power.pa_config,
1925 pa_dac: power.pa_dac,
1926 ocp: power.ocp,
1927 output_dbm: power.output_dbm,
1928 }
1929}
1930
1931#[cfg(test)]
1932mod tests {
1933 use super::*;
1934 use crate::lora::{pamoja_lora_link_budget_default, pamoja_lora_link_default};
1935
1936 fn bytes(command: PamojaSx126xCommand) -> Vec<u8> {
1937 command.bytes[..usize::from(command.len)].to_vec()
1938 }
1939
1940 #[test]
1941 fn commands_carry_the_datasheet_bytes() {
1942 assert_eq!(
1943 bytes(pamoja_sx126x_set_rf_frequency(868_100_000)),
1944 [0x86, 0x36, 0x41, 0x99, 0x9A]
1945 );
1946 let power = pamoja_sx126x_tx_power_for_output(true, 14);
1947 assert_eq!(
1948 bytes(pamoja_sx126x_set_pa_config(power)),
1949 [0x95, 0x04, 0x07, 0x00, 0x01]
1950 );
1951 assert_eq!(
1952 bytes(pamoja_sx126x_set_tx_params(power, 40)),
1953 [0x8E, 0x0E, 0x02]
1954 );
1955 assert_eq!(
1956 bytes(pamoja_sx126x_set_rx_continuous()),
1957 [0x82, 0xFF, 0xFF, 0xFF]
1958 );
1959 let query = pamoja_sx126x_get_irq_status();
1960 assert_eq!(bytes(query.command), [0x12, 0x00]);
1961 assert_eq!(query.answer_len, 2);
1962 }
1963
1964 #[test]
1965 fn a_bandwidth_the_chip_lacks_is_refused() {
1966 let mut command = PamojaSx126xCommand {
1967 bytes: [0; PAMOJA_SX126X_COMMAND_MAX],
1968 len: 0,
1969 };
1970 let link = pamoja_lora_link_default(7, 125_000);
1971 let status = unsafe { pamoja_sx126x_set_lora_modulation_params(link, &mut command) };
1972 assert_eq!(status, PamojaStatus::Ok);
1973 assert_eq!(bytes(command), [0x8B, 0x07, 0x04, 0x01, 0x00]);
1974
1975 let narrow = pamoja_lora_link_default(7, 203_125);
1976 let status = unsafe { pamoja_sx126x_set_lora_modulation_params(narrow, &mut command) };
1977 assert_eq!(status, PamojaStatus::InvalidArgument);
1978 }
1979
1980 #[test]
1981 fn power_under_a_ceiling_takes_off_the_antenna() {
1982 let whip = PamojaLoraLinkBudget {
1983 transmit_antenna_gain_centi_dbi: 215,
1984 transmit_cable_loss_centi_db: 50,
1985 ..pamoja_lora_link_budget_default()
1986 };
1987 let power = pamoja_sx126x_tx_power_under_ceiling(true, whip, 1_600);
1988 assert_eq!(power.setting_dbm, 14);
1989 assert_eq!(
1990 pamoja_sx126x_tx_power_for_output(false, 15).pa_duty_cycle,
1991 0x06
1992 );
1993 assert_eq!(pamoja_sx126x_ramp_time_us(100), 200);
1994 }
1995
1996 #[test]
1997 fn answers_decode_as_the_datasheet_gives() {
1998 let status = pamoja_sx126x_status_from_byte(0x2C);
1999 assert_eq!(status.chip_mode, PamojaSx126xChipMode::StandbyRc);
2000 assert_eq!(status.command_status, PamojaSx126xCommandStatus::TxDone);
2001 assert!(!status.error);
2002 assert_eq!(
2003 pamoja_sx126x_irq_from_bytes(0x02, 0x62),
2004 PAMOJA_SX126X_IRQ_TIMEOUT
2005 | PAMOJA_SX126X_IRQ_CRC_ERROR
2006 | PAMOJA_SX126X_IRQ_HEADER_ERROR
2007 | PAMOJA_SX126X_IRQ_RX_DONE
2008 );
2009 let packet = pamoja_sx126x_packet_status_from_bytes(0xDB, 0xF6, 0xE0);
2010 assert_eq!(packet.rssi_centi_dbm, -10_950);
2011 assert_eq!(packet.snr_centi_db, -250);
2012 assert_eq!(pamoja_sx126x_rssi_inst_centi_dbm(0xDB), -10_950);
2013 }
2014
2015 #[test]
2016 fn a_duty_cycle_guard_holds_the_radio_silent() {
2017 let guard = pamoja_radio_duty_cycle_new(10);
2018 let link = pamoja_lora_link_default(12, 125_000);
2019 unsafe {
2020 assert_eq!(
2021 pamoja_radio_duty_cycle_transmitted(guard, 0, link, 10),
2022 991_232
2023 );
2024 assert_eq!(pamoja_radio_duty_cycle_wait_us(guard, 0), 99_123_200);
2025 assert!(!pamoja_radio_duty_cycle_ready(guard, 99_000_000));
2026 assert!(pamoja_radio_duty_cycle_ready(guard, 99_123_200));
2027 pamoja_radio_duty_cycle_free(guard);
2028 assert!(!pamoja_radio_duty_cycle_ready(std::ptr::null(), 0));
2029 }
2030 }
2031
2032 #[test]
2033 fn sx127x_register_values_follow_the_datasheet() {
2034 assert_eq!(pamoja_sx127x_frequency_word(868_100_000), 0xD9_0666);
2035 assert_eq!(pamoja_sx127x_frequency_from_word(0x6C_8000), 434_000_000);
2036 assert_eq!(pamoja_sx127x_lora_op_mode(PAMOJA_SX127X_MODE_TX), 0x8B);
2037 assert_eq!(pamoja_sx127x_fsk_op_mode(PAMOJA_SX127X_MODE_STANDBY), 0x09);
2038 assert_eq!(
2039 pamoja_sx127x_mode_from_op_mode(0x8D),
2040 PAMOJA_SX127X_MODE_RX_CONTINUOUS
2041 );
2042 assert_eq!(pamoja_sx127x_write_address(PAMOJA_SX127X_REG_OP_MODE), 0x81);
2043 assert_eq!(pamoja_sx127x_read_address(0x81), PAMOJA_SX127X_REG_OP_MODE);
2044
2045 let mut modem = PamojaSx127xModem::default();
2046 let link = pamoja_lora_link_default(7, 125_000);
2047 assert_eq!(
2048 unsafe { pamoja_sx127x_modem(link, 868_100_000, 0, &mut modem) },
2049 PamojaStatus::Ok
2050 );
2051 assert_eq!(
2052 (
2053 modem.modem_config_1,
2054 modem.modem_config_2,
2055 modem.modem_config_3
2056 ),
2057 (0x72, 0x74, 0x04)
2058 );
2059 assert_eq!(
2060 (modem.detection_optimize, modem.detection_threshold),
2061 (0x03, 0x0A)
2062 );
2063
2064 let power = pamoja_sx127x_tx_power_for_output(true, 20);
2065 assert_eq!(
2066 (power.pa_config, power.pa_dac, power.ocp),
2067 (0xFF, 0x87, 0x31)
2068 );
2069 assert_eq!(pamoja_sx127x_invert_iq(true, true), 0x66);
2070 assert_eq!(pamoja_sx127x_invert_iq_2(false), 0x1D);
2071 let packet = pamoja_sx127x_packet_status_from_bytes(0xF6, 0x30, 868_100_000);
2072 assert_eq!(
2073 (
2074 packet.rssi_centi_dbm,
2075 packet.snr_centi_db,
2076 packet.signal_rssi_centi_dbm
2077 ),
2078 (-10_900, -250, -11_150)
2079 );
2080 assert_eq!(pamoja_sx127x_rssi_centi_dbm(0x30, 433_175_000), -11_600);
2081 assert_eq!(
2082 pamoja_sx127x_modem_status_from_byte(0x30).coding_rate_denominator,
2083 5
2084 );
2085 }
2086
2087 #[test]
2088 fn sx127x_refuses_what_the_chip_cannot_do_and_the_llcc68_limits_hold() {
2089 let mut modem = PamojaSx127xModem::default();
2090 let sf5 = pamoja_lora_link_default(5, 125_000);
2091 assert_eq!(
2092 unsafe { pamoja_sx127x_modem(sf5, 868_100_000, 0, &mut modem) },
2093 PamojaStatus::InvalidArgument
2094 );
2095 let wide = pamoja_lora_link_default(7, 500_000);
2096 assert_eq!(
2097 unsafe { pamoja_sx127x_modem(wide, 169_400_000, 0, &mut modem) },
2098 PamojaStatus::InvalidArgument
2099 );
2100 assert!(pamoja_sx126x_llcc68_supports(pamoja_lora_link_default(
2101 9, 125_000
2102 )));
2103 assert!(!pamoja_sx126x_llcc68_supports(pamoja_lora_link_default(
2104 10, 125_000
2105 )));
2106
2107 let mut erratum = PamojaSx127xSpuriousReception::default();
2108 let narrow = pamoja_lora_link_default(9, 20_833);
2109 assert_eq!(
2110 unsafe { pamoja_sx127x_spurious_reception(narrow, &mut erratum) },
2111 PamojaStatus::Ok
2112 );
2113 assert!(erratum.has_if_freq_2);
2114 assert_eq!((erratum.if_freq_2, erratum.offset_hz), (0x44, 20_830));
2115 let mut optimize = PamojaSx127xHighBwOptimize::default();
2116 assert_eq!(
2117 unsafe { pamoja_sx127x_high_bw_optimize(wide, 915_000_000, &mut optimize) },
2118 PamojaStatus::Ok
2119 );
2120 assert_eq!((optimize.optimize_1, optimize.optimize_2), (0x02, 0x64));
2121 assert!(optimize.has_optimize_2);
2122 }
2123}