pamoja.radios

Idiomatic LoRa radio facade.

pamoja.lora works out what a link costs and how far it reaches; this puts a radio chip on the air to match. For the Semtech SX1261, SX1262, and LLCC68, pamoja.radios.sx126x builds the bytes of every command and decodes every answer, and chooses the amplifier setting a regional EIRP ceiling allows behind an antenna. For the SX1276 family, pamoja.radios.sx127x gives the register values and decodes the readings. DutyCycle holds the radio silent for the off time a duty-cycle limit owes after each frame. On a Linux board, LoraRadio opens a module of either family through the kernel's spidev and GPIO character devices and drives it with the same calls. On any platform, SimulatedLoraChip stands in for a module: the radio it gives is the same class, and the chip says what it was tuned to and what it sent.

 1"""Idiomatic LoRa radio facade.
 2
 3``pamoja.lora`` works out what a link costs and how far it reaches; this puts a radio chip
 4on the air to match. For the Semtech SX1261, SX1262, and LLCC68, :mod:`pamoja.radios.sx126x`
 5builds the bytes of every command and decodes every answer, and chooses the amplifier
 6setting a regional EIRP ceiling allows behind an antenna. For the SX1276 family,
 7:mod:`pamoja.radios.sx127x` gives the register values and decodes the readings.
 8:class:`DutyCycle` holds the radio
 9silent for the off time a duty-cycle limit owes after each frame. On a Linux board,
10:class:`LoraRadio` opens a module of either family through the kernel's spidev and GPIO
11character devices and drives it with the same calls. On any platform,
12:class:`SimulatedLoraChip` stands in for a module: the radio it gives is the same class,
13and the chip says what it was tuned to and what it sent.
14"""
15
16from __future__ import annotations
17
18from pamoja._native import (
19    LoraRadio,
20    LoraReception,
21    LoraSentFrame,
22    LoraTuning,
23    SimulatedLoraChip,
24)
25from pamoja._native import RadioDutyCycle as DutyCycle
26
27from . import sx126x, sx127x
28
29__all__ = [
30    "DutyCycle",
31    "LoraRadio",
32    "LoraReception",
33    "LoraSentFrame",
34    "LoraTuning",
35    "SimulatedLoraChip",
36    "sx126x",
37    "sx127x",
38]
DutyCycle = <class 'builtins.RadioDutyCycle'>
class LoraRadio:

A LoRa radio opened on a Linux board or wired to a simulated chip.

def open_sx126x( spi, gpio_chip, busy_line, reset_line, amplifier, *, spi_hz=None, tcxo_volts=None, tcxo_settle_us=None, dio2_rf_switch=False, dc_dc=False, llcc68=False):

Opens an SX1261, SX1262, SX1268, or LLCC68 module and resets it, which takes a few tens of milliseconds.

Raises PamojaError when the platform is not Linux, a device cannot be opened, or no chip answers, and ValueError for an amplifier name or a TCXO voltage the chip has no setting for.

def open_sx127x(spi, gpio_chip, reset_line, output, *, spi_hz=None, tcxo=False):

Opens an SX1276, SX1277, SX1278, or SX1279 module, such as an RFM95W, and resets it into LoRa mode.

Raises PamojaError when the platform is not Linux, a device cannot be opened, or no chip answers, and ValueError for an amplifier output name the chip has no setting for.

def configure( self, /, frequency_hz, link, output_dbm, *, sync_word=None, band_low_hz=None, band_high_hz=None, invert_iq_transmit=False, invert_iq_receive=False):

Tunes the radio to a carrier, a link, and an output power.

The sync word is one byte, 0x34 for a public network such as LoRaWAN and 0x12, the default, for a private one. An SX126x calibrates its receiver for the carrier alone unless a band is given.

def transmit(self, /, payload):

Sends one frame and returns its airtime in microseconds once it has left.

def receive(self, /, timeout_us):

Listens for one frame for up to a timeout in microseconds.

def listen(self, /):

Starts listening, frame after frame, until another call changes the mode.

def take_frame(self, /):

Takes the frame a listening radio has received, or None when nothing has arrived.

def detect(self, /, symbols):

Listens a few symbols for a LoRa preamble and returns whether one is there, as a relay's scan does, leaving the chip in standby. An SX126x listens over 1, 2, 4, 8, or 16 symbols, rounded down to one of them, and an SX127x over one.

def standby(self, /):

Puts the radio in standby, which stops a transmission or a reception.

def sleep(self, /):

Puts the radio to sleep until the next call wakes it. An SX126x is configured again before its next frame; an SX127x keeps its registers.

def random(self, /):

Draws a random number from the noise the receiver hears, leaving the chip in standby.

Semtech's own drivers draw it the same way, and LoRaWAN 1.0.3 suggests this source for a join nonce on a device that has no other.

def read_register(self, /, address):

Reads one register: a 16-bit address on the SX126x, 0x00 to 0x7F on the SX127x.

def write_register(self, /, address, value):

Writes one register: a 16-bit address on the SX126x, 0x00 to 0x7F on the SX127x.

def close(self, /):

Closes the radio's device files, after any call in progress finishes. Calls after this raise PamojaError.

family

The family of the radio's chip: "Sx126x" or "Sx127x".

class LoraReception:

How a reception ended, with the frame and its signal levels when one arrived.

signal_rssi_dbm

The estimated strength of the LoRa signal itself, in dBm, or None without a frame.

rssi_dbm

The received signal strength averaged over the frame, in dBm, or None without a frame.

payload

The frame's payload, or None without a frame.

outcome

How it ended: "Frame", "Timeout", or "Corrupt".

snr_db

The estimated signal-to-noise ratio, in dB, or None without a frame.

class LoraSentFrame:

A frame a simulated chip put on the air.

tuning

What the chip was tuned to when the frame went out.

payload

The frame's payload.

class LoraTuning:

What a simulated chip is tuned to.

sync_word

The sync word byte.

output_dbm

The output power the amplifier was asked for, in dBm.

frequency_hz

The carrier frequency in hertz, as the chip's synthesizer steps it: within a hertz of the one asked for on an SX126x, and within 61 Hz on an SX127x.

class SimulatedLoraChip:

A simulated SX126x or SX127x, which a LoraRadio drives with no radio attached.

The program says what arrives on the air with hear, and reads back what the chip was tuned to and what it sent with tuning and sent. Nothing is timed: a transmission is done as soon as it starts, and a reception with a timeout ends at once when nothing is waiting.

def sx126x( amplifier, *, tcxo_volts=None, tcxo_settle_us=None, dio2_rf_switch=False, dc_dc=False, llcc68=False):

A simulated SX1261, SX1262, SX1268, or LLCC68, out of reset.

Raises ValueError for an amplifier name or a TCXO voltage the chip has no setting for.

def sx127x(output, *, tcxo=False):

A simulated SX1276, SX1277, SX1278, or SX1279, out of reset.

Raises ValueError for an amplifier output name the chip has no setting for.

def radio(self, /):

Returns a radio wired to the chip, reset as opening a module resets it.

def hear(self, /, payload, rssi_dbm, snr_db):

Puts a frame on the air for the chip to receive the next time it listens, heard at a strength in dBm and a signal-to-noise ratio in dB. A payload past 255 bytes is cut to 255.

Raises ValueError for a level that is not a finite number.

def hear_corrupt(self, /, rssi_dbm, snr_db):

Puts a frame on the air whose CRC fails, which the chip reports and drops.

Raises ValueError for a level that is not a finite number.

def tuning(self, /):

Returns what the chip is tuned to now.

def sent(self, /):

Returns every frame the chip has put on the air, oldest first.

family

The family of the chip: "Sx126x" or "Sx127x".

waiting

How many frames wait on the air for the chip to receive.