From c16d2d5a90dcdaae435e21890a92e5d5778b5794 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 12:15:01 -0400 Subject: [PATCH 01/37] add config for flake8 --- setup.cfg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..183f209 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[flake8] +ignore = E302, E241, E221, W504, E402 +max-line-length = 120 +exclude = *.png, *.md, *.cfg, *.yml, *.sh, *.json, .gitignore, *.txt From 4cbbb9e601bd6bd6b9a726b269fff5a8f5029b17 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 12:15:55 -0400 Subject: [PATCH 02/37] Creating RadioHead class independent of driver Radiohead should be given a protocol and it will do everything with the given protocol --- lib/configuration/radio_configuration.py | 18 +- lib/pycubed_rfm9x.py | 942 +++++++++++++++++++++++ lib/pycubed_rfm9x_fsk.py | 395 +++++----- lib/radiohead.py | 734 ++++++++++++++++++ 4 files changed, 1888 insertions(+), 201 deletions(-) create mode 100644 lib/pycubed_rfm9x.py create mode 100644 lib/radiohead.py diff --git a/lib/configuration/radio_configuration.py b/lib/configuration/radio_configuration.py index 494b84f..c671b46 100644 --- a/lib/configuration/radio_configuration.py +++ b/lib/configuration/radio_configuration.py @@ -2,16 +2,26 @@ Defines the default settings used to configure the RFM9x satellite """ -CHECKSUM = True -TX_POWER = 23 # dB +PROTOCOL = "fsk" + +# FSK specific BITRATE = 1200 # bits per second -FREQUENCY = 433.0 # MHz FREQUENCY_DEVIATION = 10000 # Hz RX_BANDWIDTH = 25.0 # KHz + +# LoRa specific +SPREADING_FACTOR = 7 +SIGNAL_BANDWIDTH = 125000 +CODING_RATE = 5 + +CHECKSUM = True +TX_POWER = 23 # dB +FREQUENCY = 433.0 # MHz PREAMBLE_LENGTH = 16 # bytes ACK_DELAY = 0.1 # seconds ACK_WAIT = 1 # seconds -RECEIVE_TIMEOUT = 0.5 # seconds +RECEIVE_TIMEOUT = 2.0 # seconds +ACK_RETRIES = 2 # lower b/c TX queue retries as well SATELLITE_ID = 0xAB GROUNDSTATION_ID = 0xBA diff --git a/lib/pycubed_rfm9x.py b/lib/pycubed_rfm9x.py new file mode 100644 index 0000000..378089f --- /dev/null +++ b/lib/pycubed_rfm9x.py @@ -0,0 +1,942 @@ +# SPDX-FileCopyrightText: 2017 Tony DiCola for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +`adafruit_rfm9x` +==================================================== + +CircuitPython module for the RFM95/6/7/8 LoRa 433/915mhz radio modules. This is +adapted from the Radiohead library RF95 code from: +http: www.airspayce.com/mikem/arduino/RadioHead/ + +* Author(s): Tony DiCola, Jerry Needell +""" +import random +import time +import adafruit_bus_device.spi_device as spidev +from micropython import const +import tasko + +HAS_SUPERVISOR = False + +try: + import supervisor + + if hasattr(supervisor, "ticks_ms"): + HAS_SUPERVISOR = True +except ImportError: + pass + +try: + from typing import Optional, Type + from digitalio import DigitalInOut + from busio import SPI + from circuitpython_typing import WriteableBuffer, ReadableBuffer + + try: + from typing import Literal + except ImportError: + from typing_extensions import Literal + +except ImportError: + pass + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/PyCubed-Mini/RFM9x_asyncio.git" + + +class Constants: + # Internal constants: + # Register names (FSK Mode even though we use LoRa instead, from table 85) + _RH_RF95_REG_00_FIFO = const(0x00) + _RH_RF95_REG_01_OP_MODE = const(0x01) + _RH_RF95_REG_06_FRF_MSB = const(0x06) + _RH_RF95_REG_07_FRF_MID = const(0x07) + _RH_RF95_REG_08_FRF_LSB = const(0x08) + _RH_RF95_REG_09_PA_CONFIG = const(0x09) + _RH_RF95_REG_0A_PA_RAMP = const(0x0A) + _RH_RF95_REG_0B_OCP = const(0x0B) + _RH_RF95_REG_0C_LNA = const(0x0C) + _RH_RF95_REG_0D_FIFO_ADDR_PTR = const(0x0D) + _RH_RF95_REG_0E_FIFO_TX_BASE_ADDR = const(0x0E) + _RH_RF95_REG_0F_FIFO_RX_BASE_ADDR = const(0x0F) + _RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR = const(0x10) + _RH_RF95_REG_11_IRQ_FLAGS_MASK = const(0x11) + _RH_RF95_REG_12_IRQ_FLAGS = const(0x12) + _RH_RF95_REG_13_RX_NB_BYTES = const(0x13) + _RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB = const(0x14) + _RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB = const(0x15) + _RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB = const(0x16) + _RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB = const(0x17) + _RH_RF95_REG_18_MODEM_STAT = const(0x18) + _RH_RF95_REG_19_PKT_SNR_VALUE = const(0x19) + _RH_RF95_REG_1A_PKT_RSSI_VALUE = const(0x1A) + _RH_RF95_REG_1B_RSSI_VALUE = const(0x1B) + _RH_RF95_REG_1C_HOP_CHANNEL = const(0x1C) + _RH_RF95_REG_1D_MODEM_CONFIG1 = const(0x1D) + _RH_RF95_REG_1E_MODEM_CONFIG2 = const(0x1E) + _RH_RF95_REG_1F_SYMB_TIMEOUT_LSB = const(0x1F) + _RH_RF95_REG_20_PREAMBLE_MSB = const(0x20) + _RH_RF95_REG_21_PREAMBLE_LSB = const(0x21) + _RH_RF95_REG_22_PAYLOAD_LENGTH = const(0x22) + _RH_RF95_REG_23_MAX_PAYLOAD_LENGTH = const(0x23) + _RH_RF95_REG_24_HOP_PERIOD = const(0x24) + _RH_RF95_REG_25_FIFO_RX_BYTE_ADDR = const(0x25) + _RH_RF95_REG_26_MODEM_CONFIG3 = const(0x26) + + _RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) + _RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) + _RH_RF95_REG_42_VERSION = const(0x42) + + _RH_RF95_REG_4B_TCXO = const(0x4B) + _RH_RF95_REG_4D_PA_DAC = const(0x4D) + _RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) + _RH_RF95_REG_61_AGC_REF = const(0x61) + _RH_RF95_REG_62_AGC_THRESH1 = const(0x62) + _RH_RF95_REG_63_AGC_THRESH2 = const(0x63) + _RH_RF95_REG_64_AGC_THRESH3 = const(0x64) + + _RH_RF95_DETECTION_OPTIMIZE = const(0x31) + _RH_RF95_DETECTION_THRESHOLD = const(0x37) + + _RH_RF95_PA_DAC_DISABLE = const(0x04) + _RH_RF95_PA_DAC_ENABLE = const(0x07) + + # The crystal oscillator frequency of the module + _RH_RF95_FXOSC = 32000000.0 + + # The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 + _RH_RF95_FSTEP = _RH_RF95_FXOSC / 524288 + + # RadioHead specific compatibility constants. + _RH_BROADCAST_ADDRESS = const(0xFF) + + # The acknowledgement bit in the FLAGS + # The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved + # for application layer use. + _RH_FLAGS_ACK = const(0x80) + _RH_FLAGS_RETRY = const(0x40) + + # User facing constants: + SLEEP_MODE = 0b000 + STANDBY_MODE = 0b001 + FS_TX_MODE = 0b010 + TX_MODE = 0b011 + FS_RX_MODE = 0b100 + RX_MODE = 0b101 + # supervisor.ticks_ms() contants + _TICKS_PERIOD = const(1 << 29) + _TICKS_MAX = const(_TICKS_PERIOD - 1) + _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) + +# Disable the too many instance members warning. Pylint has no knowledge +# of the context and is merely guessing at the proper amount of members. This +# is a complex chip which requires exposing many attributes and state. Disable +# the warning to work around the error. +# pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-statements + + +def ticks_diff(ticks1: int, ticks2: int) -> int: + """Compute the signed difference between two ticks values + assuming that they are within 2**28 ticks + """ + diff = (ticks1 - ticks2) & Constants._TICKS_MAX + diff = ((diff + Constants._TICKS_HALFPERIOD) & Constants._TICKS_MAX) - Constants._TICKS_HALFPERIOD + return diff + + +class RFM9x: + """Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and + receiving bytes of data in long range LoRa mode at a support board frequency + (433/915mhz). + + You must specify the following parameters: + - spi: The SPI bus connected to the radio. + - cs: The CS pin DigitalInOut connected to the radio. + - reset: The reset/RST pin DigialInOut connected to the radio. + - frequency: The frequency (in mhz) of the radio module (433/915mhz typically). + + You can optionally specify: + - preamble_length: The length in bytes of the packet preamble (default 8). + - high_power: Boolean to indicate a high power board (RFM95, etc.). Default + is True for high power. + - baudrate: Baud rate of the SPI connection, default is 10mhz but you might + choose to lower to 1mhz if using long wires or a breadboard. + - agc: Boolean to Enable/Disable Automatic Gain Control - Default=False (AGC off) + - crc: Boolean to Enable/Disable Cyclic Redundancy Check - Default=True (CRC Enabled) + Remember this library makes a best effort at receiving packets with pure + Python code. Trying to receive packets too quickly will result in lost data + so limit yourself to simple scenarios of sending and receiving single + packets at a time. + + Also note this library tries to be compatible with raw RadioHead Arduino + library communication. This means the library sets up the radio modulation + to match RadioHead's defaults and assumes that each packet contains a + 4 byte header compatible with RadioHead's implementation. + Advanced RadioHead features like address/node specific packets + or "reliable datagram" delivery are supported however due to the + limitations noted, "reliable datagram" is still subject to missed packets but with it, + sender is notified if a packet has potentially been missed. + """ + + # Global buffer for SPI commands + _BUFFER = bytearray(4) + + class _RegisterBits: + # Class to simplify access to the many configuration bits avaialable + # on the chip's registers. This is a subclass here instead of using + # a higher level module to increase the efficiency of memory usage + # (all of the instances of this bit class will share the same buffer + # used by the parent RFM69 class instance vs. each having their own + # buffer and taking too much memory). + + # Quirk of pylint that it requires public methods for a class. This + # is a decorator class in Python and by design it has no public methods. + # Instead it uses dunder accessors like get and set below. For some + # reason pylint can't figure this out so disable the check. + # pylint: disable=too-few-public-methods + + # Again pylint fails to see the true intent of this code and warns + # against private access by calling the write and read functions below. + # This is by design as this is an internally used class. Disable the + # check from pylint. + # pylint: disable=protected-access + + def __init__(self, address: int, *, offset: int = 0, bits: int = 1) -> None: + assert 0 <= offset <= 7 + assert 1 <= bits <= 8 + assert (offset + bits) <= 8 + self._address = address + self._mask = 0 + for _ in range(bits): + self._mask <<= 1 + self._mask |= 1 + self._mask <<= offset + self._offset = offset + + def __get__(self, obj: "RFM9x", objtype: Type["RFM9x"]) -> int: + reg_value = obj._read_u8(self._address) + return (reg_value & self._mask) >> self._offset + + def __set__(self, obj: "RFM9x", val: int) -> None: + reg_value = obj._read_u8(self._address) + reg_value &= ~self._mask + reg_value |= (val & 0xFF) << self._offset + obj._write_u8(self._address, reg_value) + + operation_mode = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, bits=3) + + low_frequency_mode = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=3, bits=1) + + modulation_type = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=5, bits=2) + + # Long range/LoRa mode can only be set in sleep mode! + long_range_mode = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=7, bits=1) + + output_power = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, bits=4) + + max_power = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3) + + pa_select = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1) + + pa_dac = _RegisterBits(Constants._RH_RF95_REG_4D_PA_DAC, bits=3) + + dio0_mapping = _RegisterBits(Constants._RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2) + + auto_agc = _RegisterBits(Constants._RH_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1) + + low_datarate_optimize = _RegisterBits( + Constants._RH_RF95_REG_26_MODEM_CONFIG3, offset=3, bits=1 + ) + + lna_boost_hf = _RegisterBits(Constants._RH_RF95_REG_0C_LNA, offset=0, bits=2) + + auto_ifon = _RegisterBits(Constants._RH_RF95_DETECTION_OPTIMIZE, offset=7, bits=1) + + detection_optimize = _RegisterBits(Constants._RH_RF95_DETECTION_OPTIMIZE, offset=0, bits=3) + + bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000) + + def __init__( + self, + spi: SPI, + cs: DigitalInOut, + reset: DigitalInOut, + frequency: int, + *, + preamble_length: int = 8, + high_power: bool = True, + baudrate: int = 5000000, + agc: bool = False, + crc: bool = True, + checksum: bool = True + ) -> None: + self.high_power = high_power + # Device support SPI mode 0 (polarity & phase = 0) up to a max of 10mhz. + # Set Default Baudrate to 5MHz to avoid problems + self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0) + # Setup reset as a digital output - initially High + # This line is pulled low as an output quickly to trigger a reset. + self._reset = reset + # initialize Reset High + self._reset.switch_to_output(value=True) + self.reset() + # No device type check! Catch an error from the very first request and + # throw a nicer message to indicate possible wiring problems. + version = self._read_u8(Constants._RH_RF95_REG_42_VERSION) + if version != 18: + raise RuntimeError( + "Failed to find rfm9x with expected version -- check wiring" + ) + + # Set sleep mode, wait 10s and confirm in sleep mode (basic device check). + # Also set long range mode (LoRa mode) as it can only be done in sleep. + self.sleep() + time.sleep(0.01) + self.long_range_mode = True + if self.operation_mode != Constants.SLEEP_MODE or not self.long_range_mode: + raise RuntimeError("Failed to configure radio for LoRa mode, check wiring!") + # clear default setting for access to LF registers if frequency > 525MHz + if frequency > 525: + self.low_frequency_mode = 0 + # Setup entire 256 byte FIFO + self._write_u8(Constants._RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0x00) + self._write_u8(Constants._RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00) + # Set mode idle + self.idle() + # Set frequency + self.frequency_mhz = frequency + # Set preamble length (default 8 bytes to match radiohead). + self.preamble_length = preamble_length + # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. + self.signal_bandwidth = 125000 + self.coding_rate = 5 + self.spreading_factor = 7 + # Default to enable CRC checking on incoming packets. + self.enable_crc = crc + """CRC Enable state""" + # set AGC - Default = False + self.auto_agc = agc + """Automatic Gain Control state""" + # Set transmit power to 13 dBm, a safe value any module supports. + self.tx_power = 13 + # initialize last RSSI reading + self.last_rssi = 0.0 + """The RSSI of the last received packet. Stored when the packet was received. + The instantaneous RSSI value may not be accurate once the + operating mode has been changed. + """ + self.last_snr = 0.0 + """The SNR of the last received packet. Stored when the packet was received. + The instantaneous SNR value may not be accurate once the + operating mode has been changed. + """ + # initialize timeouts and delays delays + self.ack_wait = 0.5 + """The delay time before attempting a retry after not receiving an ACK""" + self.receive_timeout = 0.5 + """The amount of time to poll for a received packet. + If no packet is received, the returned packet will be None + """ + self.xmit_timeout = 2.0 + """The amount of time to wait for the HW to transmit the packet. + This is mainly used to prevent a hang due to a HW issue + """ + self.ack_retries = 5 + """The number of ACK retries before reporting a failure.""" + self.ack_delay = None + """The delay time before attemting to send an ACK. + If ACKs are being missed try setting this to .1 or .2. + """ + # initialize sequence number counter for reliabe datagram mode + self.sequence_number = 0 + # create seen Ids list + self.seen_ids = bytearray(256) + # initialize packet header + # node address - default is broadcast + self.node = Constants._RH_BROADCAST_ADDRESS + """The default address of this Node. (0-255). + If not 255 (0xff) then only packets address to this node will be accepted. + First byte of the RadioHead header. + """ + # destination address - default is broadcast + self.destination = Constants._RH_BROADCAST_ADDRESS + """The default destination address for packet transmissions. (0-255). + If 255 (0xff) then any receiving node should accept the packet. + Second byte of the RadioHead header. + """ + # ID - contains seq count for reliable datagram mode + self.identifier = 0 + """Automatically set to the sequence number when send_with_ack() used. + Third byte of the RadioHead header. + """ + # flags - identifies ack/reetry packet for reliable datagram mode + self.flags = 0 + """Upper 4 bits reserved for use by Reliable Datagram Mode. + Lower 4 bits may be used to pass information. + Fourth byte of the RadioHead header. + """ + self.crc_error_count = 0 + self.checksum = checksum + + # pylint: disable=no-member + # Reconsider pylint: disable when this can be tested + def _read_into( + self, address: int, buf: WriteableBuffer, length: Optional[int] = None + ) -> None: + # Read a number of bytes from the specified address into the provided + # buffer. If length is not specified (the default) the entire buffer + # will be filled. + if length is None: + length = len(buf) + with self._device as device: + self._BUFFER[0] = address & 0x7F # Strip out top bit to set 0 + # value (read). + device.write(self._BUFFER, end=1) + device.readinto(buf, end=length) + + def _read_u8(self, address: int) -> int: + # Read a single byte from the provided address and return it. + self._read_into(address, self._BUFFER, length=1) + return self._BUFFER[0] + + def _write_from( + self, address: int, buf: ReadableBuffer, length: Optional[int] = None + ) -> None: + # Write a number of bytes to the provided address and taken from the + # provided buffer. If no length is specified (the default) the entire + # buffer is written. + if length is None: + length = len(buf) + with self._device as device: + self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to + # indicate a write. + device.write(self._BUFFER, end=1) + device.write(buf, end=length) + + def _write_u8(self, address: int, val: int) -> None: + # Write a byte register to the chip. Specify the 7-bit address and the + # 8-bit value to write to that address. + with self._device as device: + self._BUFFER[0] = ( + address | 0x80 + ) & 0xFF # Set top bit to 1 to indicate a write. + self._BUFFER[1] = val & 0xFF + device.write(self._BUFFER, end=2) + + def reset(self) -> None: + """Perform a reset of the chip.""" + # See section 7.2.2 of the datasheet for reset description. + self._reset.value = False # Set Reset Low + time.sleep(0.0001) # 100 us + self._reset.value = True # set Reset High + time.sleep(0.005) # 5 ms + + def idle(self) -> None: + """Enter idle standby mode.""" + self.operation_mode = Constants.STANDBY_MODE + + def sleep(self) -> None: + """Enter sleep mode.""" + self.operation_mode = Constants.SLEEP_MODE + + def listen(self) -> None: + """Listen for packets to be received by the chip. Use :py:func:`receive` + to listen, wait and retrieve packets as they're available. + """ + self.operation_mode = Constants.RX_MODE + self.dio0_mapping = 0b00 # Interrupt on rx done. + + def transmit(self) -> None: + """Transmit a packet which is queued in the FIFO. This is a low level + function for entering transmit mode and more. For generating and + transmitting a packet of data use :py:func:`send` instead. + """ + self.operation_mode = Constants.TX_MODE + self.dio0_mapping = 0b01 # Interrupt on tx done. + + @property + def preamble_length(self) -> int: + """The length of the preamble for sent and received packets, an unsigned + 16-bit value. Received packets must match this length or they are + ignored! Set to 8 to match the RadioHead RFM95 library. + """ + msb = self._read_u8(Constants._RH_RF95_REG_20_PREAMBLE_MSB) + lsb = self._read_u8(Constants._RH_RF95_REG_21_PREAMBLE_LSB) + return ((msb << 8) | lsb) & 0xFFFF + + @preamble_length.setter + def preamble_length(self, val: int) -> None: + assert 0 <= val <= 65535 + self._write_u8(Constants._RH_RF95_REG_20_PREAMBLE_MSB, (val >> 8) & 0xFF) + self._write_u8(Constants._RH_RF95_REG_21_PREAMBLE_LSB, val & 0xFF) + + @property + def frequency_mhz(self) -> Literal[433.0, 915.0]: + """The frequency of the radio in Megahertz. Only the allowed values for + your radio must be specified (i.e. 433 vs. 915 mhz)! + """ + msb = self._read_u8(Constants._RH_RF95_REG_06_FRF_MSB) + mid = self._read_u8(Constants._RH_RF95_REG_07_FRF_MID) + lsb = self._read_u8(Constants._RH_RF95_REG_08_FRF_LSB) + frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF + frequency = (frf * Constants._RH_RF95_FSTEP) / 1000000.0 + return frequency + + @frequency_mhz.setter + def frequency_mhz(self, val: Literal[433.0, 915.0]) -> None: + if val < 240 or val > 960: + raise RuntimeError("frequency_mhz must be between 240 and 960") + # Calculate FRF register 24-bit value. + frf = int((val * 1000000.0) / Constants._RH_RF95_FSTEP) & 0xFFFFFF + # Extract byte values and update Constants. + msb = frf >> 16 + mid = (frf >> 8) & 0xFF + lsb = frf & 0xFF + self._write_u8(Constants._RH_RF95_REG_06_FRF_MSB, msb) + self._write_u8(Constants._RH_RF95_REG_07_FRF_MID, mid) + self._write_u8(Constants._RH_RF95_REG_08_FRF_LSB, lsb) + + @property + def tx_power(self) -> int: + """The transmit power in dBm. Can be set to a value from 5 to 23 for + high power devices (RFM95/96/97/98, high_power=True) or -1 to 14 for low + power devices. Only integer power levels are actually set (i.e. 12.5 + will result in a value of 12 dBm). + The actual maximum setting for high_power=True is 20dBm but for values > 20 + the PA_BOOST will be enabled resulting in an additional gain of 3dBm. + The actual setting is reduced by 3dBm. + The reported value will reflect the reduced setting. + """ + if self.high_power: + return self.output_power + 5 + return self.output_power - 1 + + @tx_power.setter + def tx_power(self, val: int) -> None: + val = int(val) + if self.high_power: + if val < 5 or val > 23: + raise RuntimeError("tx_power must be between 5 and 23") + # Enable power amp DAC if power is above 20 dB. + # Lower setting by 3db when PA_BOOST enabled - see Data Sheet Section 6.4 + if val > 20: + self.pa_dac = Constants._RH_RF95_PA_DAC_ENABLE + val -= 3 + else: + self.pa_dac = Constants._RH_RF95_PA_DAC_DISABLE + self.pa_select = True + self.output_power = (val - 5) & 0x0F + else: + assert -1 <= val <= 14 + self.pa_select = False + self.max_power = 0b111 # Allow max power output. + self.output_power = (val + 1) & 0x0F + + @property + def rssi(self) -> int: + """The received strength indicator (in dBm) of the last received message.""" + # Read RSSI register and convert to value using formula in datasheet. + # Remember in LoRa mode the payload register changes function to RSSI! + raw_rssi = self._read_u8(Constants._RH_RF95_REG_1A_PKT_RSSI_VALUE) + if self.low_frequency_mode: + raw_rssi -= 157 + else: + raw_rssi -= 164 + return raw_rssi + + @property + def snr(self) -> float: + """The SNR (in dB) of the last received message.""" + # Read SNR 0x19 register and convert to value using formula in datasheet. + # SNR(dB) = PacketSnr [twos complement] / 4 + snr_byte = self._read_u8(Constants._RH_RF95_REG_19_PKT_SNR_VALUE) + if snr_byte > 127: + snr_byte = (256 - snr_byte) * -1 + return snr_byte / 4 + + @property + def signal_bandwidth(self) -> int: + """The signal bandwidth used by the radio (try setting to a higher + value to increase throughput or to a lower value to increase the + likelihood of successfully received payloads). Valid values are + listed in RFM9x.bw_bins.""" + bw_id = (self._read_u8(Constants._RH_RF95_REG_1D_MODEM_CONFIG1) & 0xF0) >> 4 + if bw_id >= len(self.bw_bins): + current_bandwidth = 500000 + else: + current_bandwidth = self.bw_bins[bw_id] + return current_bandwidth + + @signal_bandwidth.setter + def signal_bandwidth(self, val: int) -> None: + # Set signal bandwidth (set to 125000 to match RadioHead Bw125). + for bw_id, cutoff in enumerate(self.bw_bins): + if val <= cutoff: + break + else: + bw_id = 9 + self._write_u8( + Constants._RH_RF95_REG_1D_MODEM_CONFIG1, + (self._read_u8(Constants._RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0F) | (bw_id << 4), + ) + if val >= 500000: + # see Semtech SX1276 errata note 2.3 + self.auto_ifon = True + # see Semtech SX1276 errata note 2.1 + if self.low_frequency_mode: + self._write_u8(0x36, 0x02) + self._write_u8(0x3A, 0x7F) + else: + self._write_u8(0x36, 0x02) + self._write_u8(0x3A, 0x64) + else: + # see Semtech SX1276 errata note 2.3 + self.auto_ifon = False + self._write_u8(0x36, 0x03) + if val == 7800: + self._write_u8(0x2F, 0x48) + elif val >= 62500: + # see Semtech SX1276 errata note 2.3 + self._write_u8(0x2F, 0x40) + else: + self._write_u8(0x2F, 0x44) + self._write_u8(0x30, 0) + + @property + def coding_rate(self) -> Literal[5, 6, 7, 8]: + """The coding rate used by the radio to control forward error + correction (try setting to a higher value to increase tolerance of + short bursts of interference or to a lower value to increase bit + rate). Valid values are limited to 5, 6, 7, or 8.""" + cr_id = (self._read_u8(Constants._RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0E) >> 1 + denominator = cr_id + 4 + return denominator + + @coding_rate.setter + def coding_rate(self, val: Literal[5, 6, 7, 8]) -> None: + # Set coding rate (set to 5 to match RadioHead Cr45). + denominator = min(max(val, 5), 8) + cr_id = denominator - 4 + self._write_u8( + Constants._RH_RF95_REG_1D_MODEM_CONFIG1, + (self._read_u8(Constants._RH_RF95_REG_1D_MODEM_CONFIG1) & 0xF1) | (cr_id << 1), + ) + + @property + def spreading_factor(self) -> Literal[6, 7, 8, 9, 10, 11, 12]: + """The spreading factor used by the radio (try setting to a higher + value to increase the receiver's ability to distinguish signal from + noise or to a lower value to increase the data transmission rate). + Valid values are limited to 6, 7, 8, 9, 10, 11, or 12.""" + sf_id = (self._read_u8(Constants._RH_RF95_REG_1E_MODEM_CONFIG2) & 0xF0) >> 4 + return sf_id + + @spreading_factor.setter + def spreading_factor(self, val: Literal[6, 7, 8, 9, 10, 11, 12]) -> None: + # Set spreading factor (set to 7 to match RadioHead Sf128). + val = min(max(val, 6), 12) + + if val == 6: + self.detection_optimize = 0x5 + else: + self.detection_optimize = 0x3 + + self._write_u8(Constants._RH_RF95_DETECTION_THRESHOLD, 0x0C if val == 6 else 0x0A) + self._write_u8( + Constants._RH_RF95_REG_1E_MODEM_CONFIG2, + ( + (self._read_u8(Constants._RH_RF95_REG_1E_MODEM_CONFIG2) & 0x0F) + | ((val << 4) & 0xF0) + ), + ) + + @property + def enable_crc(self) -> bool: + """Set to True to enable hardware CRC checking of incoming packets. + Incoming packets that fail the CRC check are not processed. Set to + False to disable CRC checking and process all incoming packets.""" + return (self._read_u8(Constants._RH_RF95_REG_1E_MODEM_CONFIG2) & 0x04) == 0x04 + + @enable_crc.setter + def enable_crc(self, val: bool) -> None: + # Optionally enable CRC checking on incoming packets. + if val: + self._write_u8( + Constants._RH_RF95_REG_1E_MODEM_CONFIG2, + self._read_u8(Constants._RH_RF95_REG_1E_MODEM_CONFIG2) | 0x04, + ) + else: + self._write_u8( + Constants._RH_RF95_REG_1E_MODEM_CONFIG2, + self._read_u8(Constants._RH_RF95_REG_1E_MODEM_CONFIG2) & 0xFB, + ) + + def tx_done(self) -> bool: + """Transmit status""" + return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x8) >> 3 + + def rx_done(self) -> bool: + """Receive status""" + return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x40) >> 6 + + def crc_error(self) -> bool: + """crc status""" + return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 + + # pylint: disable=too-many-branches + async def send( + self, + data: ReadableBuffer, + *, + keep_listening: bool = False, + destination: Optional[int] = None, + node: Optional[int] = None, + identifier: Optional[int] = None, + flags: Optional[int] = None, + debug: bool = False + ) -> bool: + """Send a string of data using the transmitter. + You can only send 252 bytes at a time + (limited by chip's FIFO size and appended headers). + This appends a 4 byte header to be compatible with the RadioHead library. + The header defaults to using the initialized attributes: + (destination,node,identifier,flags) + It may be temporarily overidden via the kwargs - destination,node,identifier,flags. + Values passed via kwargs do not alter the attribute settings. + The keep_listening argument should be set to True if you want to start listening + automatically after the packet is sent. The default setting is False. + + Returns: True if success or False if the send timed out. + """ + # Disable pylint warning to not use length as a check for zero. + # This is a puzzling warning as the below code is clearly the most + # efficient and proper way to ensure a precondition that the provided + # buffer be within an expected range of bounds. Disable this check. + # pylint: disable=len-as-condition + assert 0 < len(data) <= 252 + # pylint: enable=len-as-condition + self.idle() # Stop receiving to clear FIFO and keep it clear. + # Fill the FIFO with a packet to send. + self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. + # Combine header and data to form payload + payload = bytearray(5) + payload[0] = len(payload) + len(data) + if destination is None: # use attribute + payload[1] = self.destination + else: # use kwarg + payload[1] = destination + if node is None: # use attribute + payload[2] = self.node + else: # use kwarg + payload[2] = node + if identifier is None: # use attribute + payload[3] = self.identifier + else: # use kwarg + payload[3] = identifier + if flags is None: # use attribute + payload[4] = self.flags + else: # use kwarg + payload[4] = flags + payload = payload + data + + if self.checksum: + payload[0] += 2 + checksum = bsd_checksum(payload) + payload = payload + checksum + + if debug: + print(f"RFM9x: sending {str(payload)}") + + # Write payload. + self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) + # Write payload and header length. + self._write_u8(Constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + # Turn on transmit mode to send out the packet. + self.transmit() + # Wait for tx done interrupt with explicit polling (not ideal but + # best that can be done right now without interrupts). + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.tx_done(): + if ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.tx_done(): + if time.monotonic() - start >= self.xmit_timeout: + timed_out = True + else: + await tasko.sleep(0) + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + # Clear interrupt. + self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return not timed_out + + async def send_with_ack(self, data: ReadableBuffer) -> bool: + """Reliable Datagram mode: + Send a packet with data and wait for an ACK response. + The packet header is automatically generated. + If enabled, the packet transmission will be retried on failure + """ + if self.ack_retries: + retries_remaining = self.ack_retries + else: + retries_remaining = 1 + got_ack = False + self.sequence_number = (self.sequence_number + 1) & 0xFF + while not got_ack and retries_remaining: + self.identifier = self.sequence_number + await self.send(data, keep_listening=True) + # Don't look for ACK from Broadcast message + if self.destination == Constants._RH_BROADCAST_ADDRESS: + got_ack = True + else: + # wait for a packet from our destination + ack_packet = await self.receive(timeout=self.ack_wait, with_header=True) + if ack_packet is not None: + if ack_packet[4] & Constants._RH_FLAGS_ACK: + # check the ID + if ack_packet[3] == self.identifier: + got_ack = True + break + # pause before next retry -- random delay + if not got_ack: + # delay by random amount before next try + await tasko.sleep(self.ack_wait + self.ack_wait * random.random()) + retries_remaining = retries_remaining - 1 + # set retry flag in packet header + self.flags |= Constants._RH_FLAGS_RETRY + self.flags = 0 # clear flags + return got_ack + + async def receive( + self, + *, + keep_listening: bool = True, + with_header: bool = False, + with_ack: bool = False, + timeout: Optional[float] = None + ) -> Optional[bytearray]: + """Wait to receive a packet from the receiver. If a packet is found the payload bytes + are returned, otherwise None is returned (which indicates the timeout elapsed with no + reception). + If keep_listening is True (the default) the chip will immediately enter listening mode + after reception of a packet, otherwise it will fall back to idle mode and ignore any + future reception. + All packets must have a 4-byte header for compatibility with the + RadioHead library. + The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip + the header before returning the packet to the caller. + If with_header is True then the 4 byte header will be returned with the packet. + The payload then begins at packet[4]. + If with_ack is True, send an ACK after receipt (Reliable Datagram mode) + """ + timed_out = False + if timeout is None: + timeout = self.receive_timeout + if timeout is not None: + # Wait for the payload_ready signal. This is not ideal and will + # surely miss or overflow the FIFO when packets aren't read fast + # enough, however it's the best that can be done from Python without + # interrupt supports. + # Make sure we are listening for packets. + self.listen() + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.rx_done(): + if ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.rx_done(): + if time.monotonic() - start >= timeout: + timed_out = True + else: + await tasko.sleep(0) + # Payload ready is set, a packet is in the FIFO. + packet = None + # save last RSSI reading + self.last_rssi = self.rssi + + # save the last SNR reading + self.last_snr = self.snr + + # Enter idle mode to stop receiving other packets. + self.idle() + if not timed_out: + if self.enable_crc and self.crc_error(): + self.crc_error_count += 1 + else: + # Read the data from the FIFO. + # Read the length of the FIFO. + fifo_length = self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) + # Handle if the received packet is too small to include the 4 byte + # RadioHead header and at least one byte of data --reject this packet and ignore it. + if fifo_length > 0: # read and clear the FIFO if anything in it + current_addr = self._read_u8(Constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) + self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) + packet = bytearray(fifo_length) + # Read the packet. + self._read_into(Constants._RH_RF95_REG_00_FIFO, packet) + # Clear interrupt. + self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + if fifo_length < 5: + packet = None + else: + if ( + self.node != Constants._RH_BROADCAST_ADDRESS + and packet[1] != Constants._RH_BROADCAST_ADDRESS + and packet[1] != self.node + ): + packet = None + # send ACK unless this was an ACK or a broadcast + elif ( + with_ack + and ((packet[4] & Constants._RH_FLAGS_ACK) == 0) + and (packet[1] != Constants._RH_BROADCAST_ADDRESS) + ): + # delay before sending Ack to give receiver a chance to get ready + if self.ack_delay is not None: + time.sleep(self.ack_delay) + # send ACK packet to sender (data is b'!') + await self.send( + b"!", + destination=packet[1], + node=packet[0], + identifier=packet[2], + flags=(packet[4] | Constants._RH_FLAGS_ACK), + ) + # reject Retries if we have seen this idetifier from this source before + if (self.seen_ids[packet[1]] == packet[2]) and ( + packet[4] & Constants._RH_FLAGS_RETRY + ): + packet = None + else: # save the packet identifier for this source + self.seen_ids[packet[2]] = packet[3] + if ( + not with_header and packet is not None + ): # skip the header if not wanted + packet = packet[5:] + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + # Clear interrupt. + self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return packet diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 75285cd..9b2db2a 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -31,129 +31,130 @@ pass -# Internal constants: -# Register names - -# FIFO -_RH_RF95_REG_00_FIFO = const(0x00) -# Registers for common settings -_RH_RF95_REG_01_OP_MODE = const(0x01) -_RH_RF95_REG_02_BITRATE_MSB = const(0x02) -_RH_RF95_REG_03_BITRATE_LSB = const(0x03) -_RH_RF95_REG_04_FREQ_DEVIATION_MSB = const(0x04) -_RH_RF95_REG_05_FREQ_DEVIATION_LSB = const(0x05) -_RH_RF95_REG_06_FRF_MSB = const(0x06) -_RH_RF95_REG_07_FRF_MID = const(0x07) -_RH_RF95_REG_08_FRF_LSB = const(0x08) -# Registers for the transmitter -_RH_RF95_REG_09_PA_CONFIG = const(0x09) -_RH_RF95_REG_0A_PA_RAMP = const(0x0A) -_RH_RF95_REG_0B_OCP = const(0x0B) -# Registers for the receiver -_RH_RF95_REG_0C_LNA = const(0x0C) -_RH_RF95_REG_0D_RX_CONFIG = const(0x0D) -_RH_RF95_REG_0E_RSSI_CONFIG = const(0x0E) -_RH_RF95_REG_0F_RSSI_COLLISION = const(0x0F) -_RH_RF95_REG_10_RSSI_THRESH = const(0x10) -_RH_RF95_REG_11_RSSI_VALUE = const(0x11) -_RH_RF95_REG_12_RX_BW = const(0x12) -_RH_RF95_REG_13_AFC_BW = const(0x13) -_RH_RF95_REG_14_OOK_PEAK = const(0x14) -_RH_RF95_REG_15_OOK_FIX = const(0x15) -_RH_RF95_REG_16_OOK_AVG = const(0x16) -# 0x17 to 0x19 - Reserved -_RH_RF95_REG_1A_AFC_FEI = const(0x1A) -_RH_RF95_REG_1B_AFC_MSB = const(0x1B) -_RH_RF95_REG_1C_AFC_LSB = const(0x1C) -_RH_RF95_REG_1D_FEI_MSB = const(0x1D) -_RH_RF95_REG_1E_FEI_LSB = const(0x1E) -_RH_RF95_REG_1F_PREAMBLE_DETECT = const(0x1F) -_RH_RF95_REG_20_RX_TIMEOUT1 = const(0x20) -_RH_RF95_REG_21_RX_TIMEOUT2 = const(0x21) -_RH_RF95_REG_22_RX_TIMEOUT3 = const(0x22) -_RH_RF95_REG_23_RX_DELAY = const(0x23) -# Registers for RC oscillator -_RH_RF95_REG_24_OSC = const(0x24) -# Registers for packet handling -_RH_RF95_REG_25_PREAMBLE_MSB = const(0x25) -_RH_RF95_REG_26_PREAMBLE_LSB = const(0x26) -_RH_RF95_REG_27_SYNC_CONFIG = const(0x27) -_RH_RF95_REG_28_SYNC_VALUE_1 = const(0x28) # Most significant byte -_RH_RF95_REG_29_SYNC_VALUE_2 = const(0x29) -_RH_RF95_REG_2A_SYNC_VALUE_3 = const(0x2A) -_RH_RF95_REG_2B_SYNC_VALUE_4 = const(0x2B) -_RH_RF95_REG_2C_SYNC_VALUE_5 = const(0x2C) -_RH_RF95_REG_2D_SYNC_VALUE_6 = const(0x2D) -_RH_RF95_REG_2E_SYNC_VALUE_7 = const(0x2E) -_RH_RF95_REG_2F_SYNC_VALUE_8 = const(0x2F) -_RH_RF95_REG_30_PKT_CONFIG_1 = const(0x30) -_RH_RF95_REG_31_PKT_CONFIG_2 = const(0x31) -_RH_RF95_REG_32_PAYLOAD_LEN = const(0x32) -_RH_RF95_REG_33_NODE_ADDRESS = const(0x33) -_RH_RF95_REG_34_BROADCAST_ADDRESS = const(0x34) -_RH_RF95_REG_35_FIFO_THRESH = const(0x35) -# Sequencer registers -_RH_RF95_REG_36_SEQ_CONFIG_1 = const(0x36) -_RH_RF95_REG_37_SEQ_CONFIG_2 = const(0x37) -_RH_RF95_REG_38_TIMER_RESOLUTION = const(0x38) -_RH_RF95_REG_39_TIMER1_COEF = const(0x39) -_RH_RF95_REG_3A_TIMER2_COEF = const(0x3A) -# Service registers -_RH_RF95_REG_3B_IMAGE_CAL = const(0x3B) -_RH_RF95_REG_3C_TEMP = const(0x3C) -_RH_RF95_REG_3D_LOW_BATT = const(0x3C) -# Status registers -_RH_RF95_REG_3E_IRQ_FLAGS_1 = const(0x3D) -_RH_RF95_REG_3F_IRQ_FLAGS_2 = const(0x3F) -# IO control registers -_RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) -_RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) -_RH_RF95_REG_42_VERSION = const(0x42) -# Additional registers -_RH_RF95_REG_44_PLL_HOP = const(0x44) -_RH_RF95_REG_4B_TCXO = const(0x4B) -_RH_RF95_REG_4D_PA_DAC = const(0x4D) -_RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) -_RH_RF95_REG_5D_BITRATE_FRAC = const(0x5D) -# Band-specific additional registers -_RH_RF95_REG_61_AGC_REF = const(0x61) -_RH_RF95_REG_62_AGC_THRESH1 = const(0x62) -_RH_RF95_REG_63_AGC_THRESH2 = const(0x63) -_RH_RF95_REG_64_AGC_THRESH3 = const(0x64) -_RH_RF95_REG_70_PLL = const(0x70) - -# PA DAC register options -_RH_RF95_PA_DAC_DISABLE = const(0x04) -_RH_RF95_PA_DAC_ENABLE = const(0x07) - -# The crystal oscillator frequency of the module -_RH_RF95_FXOSC = 32000000.0 - -# The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 -_RH_RF95_FSTEP = _RH_RF95_FXOSC / 524288 - -# RadioHead specific compatibility constants. -_RH_BROADCAST_ADDRESS = const(0xFF) - -# The acknowledgement bit in the FLAGS -# The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved -# for application layer use. -_RH_FLAGS_ACK = const(0x80) -_RH_FLAGS_RETRY = const(0x40) - -# User facing constants: -SLEEP_MODE = 0b000 -STANDBY_MODE = 0b001 -FS_TX_MODE = 0b010 -TX_MODE = 0b011 -FS_RX_MODE = 0b100 -RX_MODE = 0b101 -# supervisor.ticks_ms() contants -_TICKS_PERIOD = const(1 << 29) -_TICKS_MAX = const(_TICKS_PERIOD - 1) -_TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) - -_MAX_FIFO_LENGTH = 66 +class Constants: + # Internal constants: + # Register names + + # FIFO + _RH_RF95_REG_00_FIFO = const(0x00) + # Registers for common settings + _RH_RF95_REG_01_OP_MODE = const(0x01) + _RH_RF95_REG_02_BITRATE_MSB = const(0x02) + _RH_RF95_REG_03_BITRATE_LSB = const(0x03) + _RH_RF95_REG_04_FREQ_DEVIATION_MSB = const(0x04) + _RH_RF95_REG_05_FREQ_DEVIATION_LSB = const(0x05) + _RH_RF95_REG_06_FRF_MSB = const(0x06) + _RH_RF95_REG_07_FRF_MID = const(0x07) + _RH_RF95_REG_08_FRF_LSB = const(0x08) + # Registers for the transmitter + _RH_RF95_REG_09_PA_CONFIG = const(0x09) + _RH_RF95_REG_0A_PA_RAMP = const(0x0A) + _RH_RF95_REG_0B_OCP = const(0x0B) + # Registers for the receiver + _RH_RF95_REG_0C_LNA = const(0x0C) + _RH_RF95_REG_0D_RX_CONFIG = const(0x0D) + _RH_RF95_REG_0E_RSSI_CONFIG = const(0x0E) + _RH_RF95_REG_0F_RSSI_COLLISION = const(0x0F) + _RH_RF95_REG_10_RSSI_THRESH = const(0x10) + _RH_RF95_REG_11_RSSI_VALUE = const(0x11) + _RH_RF95_REG_12_RX_BW = const(0x12) + _RH_RF95_REG_13_AFC_BW = const(0x13) + _RH_RF95_REG_14_OOK_PEAK = const(0x14) + _RH_RF95_REG_15_OOK_FIX = const(0x15) + _RH_RF95_REG_16_OOK_AVG = const(0x16) + # 0x17 to 0x19 - Reserved + _RH_RF95_REG_1A_AFC_FEI = const(0x1A) + _RH_RF95_REG_1B_AFC_MSB = const(0x1B) + _RH_RF95_REG_1C_AFC_LSB = const(0x1C) + _RH_RF95_REG_1D_FEI_MSB = const(0x1D) + _RH_RF95_REG_1E_FEI_LSB = const(0x1E) + _RH_RF95_REG_1F_PREAMBLE_DETECT = const(0x1F) + _RH_RF95_REG_20_RX_TIMEOUT1 = const(0x20) + _RH_RF95_REG_21_RX_TIMEOUT2 = const(0x21) + _RH_RF95_REG_22_RX_TIMEOUT3 = const(0x22) + _RH_RF95_REG_23_RX_DELAY = const(0x23) + # Registers for RC oscillator + _RH_RF95_REG_24_OSC = const(0x24) + # Registers for packet handling + _RH_RF95_REG_25_PREAMBLE_MSB = const(0x25) + _RH_RF95_REG_26_PREAMBLE_LSB = const(0x26) + _RH_RF95_REG_27_SYNC_CONFIG = const(0x27) + _RH_RF95_REG_28_SYNC_VALUE_1 = const(0x28) # Most significant byte + _RH_RF95_REG_29_SYNC_VALUE_2 = const(0x29) + _RH_RF95_REG_2A_SYNC_VALUE_3 = const(0x2A) + _RH_RF95_REG_2B_SYNC_VALUE_4 = const(0x2B) + _RH_RF95_REG_2C_SYNC_VALUE_5 = const(0x2C) + _RH_RF95_REG_2D_SYNC_VALUE_6 = const(0x2D) + _RH_RF95_REG_2E_SYNC_VALUE_7 = const(0x2E) + _RH_RF95_REG_2F_SYNC_VALUE_8 = const(0x2F) + _RH_RF95_REG_30_PKT_CONFIG_1 = const(0x30) + _RH_RF95_REG_31_PKT_CONFIG_2 = const(0x31) + _RH_RF95_REG_32_PAYLOAD_LEN = const(0x32) + _RH_RF95_REG_33_NODE_ADDRESS = const(0x33) + _RH_RF95_REG_34_BROADCAST_ADDRESS = const(0x34) + _RH_RF95_REG_35_FIFO_THRESH = const(0x35) + # Sequencer registers + _RH_RF95_REG_36_SEQ_CONFIG_1 = const(0x36) + _RH_RF95_REG_37_SEQ_CONFIG_2 = const(0x37) + _RH_RF95_REG_38_TIMER_RESOLUTION = const(0x38) + _RH_RF95_REG_39_TIMER1_COEF = const(0x39) + _RH_RF95_REG_3A_TIMER2_COEF = const(0x3A) + # Service registers + _RH_RF95_REG_3B_IMAGE_CAL = const(0x3B) + _RH_RF95_REG_3C_TEMP = const(0x3C) + _RH_RF95_REG_3D_LOW_BATT = const(0x3C) + # Status registers + _RH_RF95_REG_3E_IRQ_FLAGS_1 = const(0x3D) + _RH_RF95_REG_3F_IRQ_FLAGS_2 = const(0x3F) + # IO control registers + _RH_RF95_REG_40_DIO_MAPPING1 = const(0x40) + _RH_RF95_REG_41_DIO_MAPPING2 = const(0x41) + _RH_RF95_REG_42_VERSION = const(0x42) + # Additional registers + _RH_RF95_REG_44_PLL_HOP = const(0x44) + _RH_RF95_REG_4B_TCXO = const(0x4B) + _RH_RF95_REG_4D_PA_DAC = const(0x4D) + _RH_RF95_REG_5B_FORMER_TEMP = const(0x5B) + _RH_RF95_REG_5D_BITRATE_FRAC = const(0x5D) + # Band-specific additional registers + _RH_RF95_REG_61_AGC_REF = const(0x61) + _RH_RF95_REG_62_AGC_THRESH1 = const(0x62) + _RH_RF95_REG_63_AGC_THRESH2 = const(0x63) + _RH_RF95_REG_64_AGC_THRESH3 = const(0x64) + _RH_RF95_REG_70_PLL = const(0x70) + + # PA DAC register options + _RH_RF95_PA_DAC_DISABLE = const(0x04) + _RH_RF95_PA_DAC_ENABLE = const(0x07) + + # The crystal oscillator frequency of the module + _RH_RF95_FXOSC = 32000000.0 + + # The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 + _RH_RF95_FSTEP = _RH_RF95_FXOSC / 524288 + + # RadioHead specific compatibility constants. + _RH_BROADCAST_ADDRESS = const(0xFF) + + # The acknowledgement bit in the FLAGS + # The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved + # for application layer use. + _RH_FLAGS_ACK = const(0x80) + _RH_FLAGS_RETRY = const(0x40) + + # User facing constants: + SLEEP_MODE = 0b000 + STANDBY_MODE = 0b001 + FS_TX_MODE = 0b010 + TX_MODE = 0b011 + FS_RX_MODE = 0b100 + RX_MODE = 0b101 + # supervisor.ticks_ms() contants + _TICKS_PERIOD = const(1 << 29) + _TICKS_MAX = const(_TICKS_PERIOD - 1) + _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) + + _MAX_FIFO_LENGTH = 66 # Disable the too many instance members warning. Pylint has no knowledge # of the context and is merely guessing at the proper amount of members. This @@ -167,8 +168,8 @@ def ticks_diff(ticks1, ticks2): """Compute the signed difference between two ticks values assuming that they are within 2**28 ticks """ - diff = (ticks1 - ticks2) & _TICKS_MAX - diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD + diff = (ticks1 - ticks2) & Constants._TICKS_MAX + diff = ((diff + Constants._TICKS_HALFPERIOD) & Constants._TICKS_MAX) - Constants._TICKS_HALFPERIOD return diff @@ -258,48 +259,48 @@ def __set__(self, obj, val): reg_value |= (val & 0xFF) << self._offset obj._write_u8(self._address, reg_value) - operation_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=0, bits=3) + operation_mode = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=0, bits=3) low_frequency_mode = _RegisterBits( - _RH_RF95_REG_01_OP_MODE, offset=3, bits=1) + Constants._RH_RF95_REG_01_OP_MODE, offset=3, bits=1) - modulation_type = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=5, bits=2) + modulation_type = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=5, bits=2) # Long range mode (LoRa or FSK) can only be set in sleep mode! - long_range_mode = _RegisterBits(_RH_RF95_REG_01_OP_MODE, offset=7, bits=1) + long_range_mode = _RegisterBits(Constants._RH_RF95_REG_01_OP_MODE, offset=7, bits=1) - output_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=0, bits=4) + output_power = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, offset=0, bits=4) - max_power = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3) + max_power = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, offset=4, bits=3) - pa_select = _RegisterBits(_RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1) + pa_select = _RegisterBits(Constants._RH_RF95_REG_09_PA_CONFIG, offset=7, bits=1) - pa_dac = _RegisterBits(_RH_RF95_REG_4D_PA_DAC, offset=0, bits=3) + pa_dac = _RegisterBits(Constants._RH_RF95_REG_4D_PA_DAC, offset=0, bits=3) - dio0_mapping = _RegisterBits(_RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2) + dio0_mapping = _RegisterBits(Constants._RH_RF95_REG_40_DIO_MAPPING1, offset=6, bits=2) - lna_boost_hf = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=0, bits=2) + lna_boost_hf = _RegisterBits(Constants._RH_RF95_REG_0C_LNA, offset=0, bits=2) - lna_gain = _RegisterBits(_RH_RF95_REG_0C_LNA, offset=5, bits=3) + lna_gain = _RegisterBits(Constants._RH_RF95_REG_0C_LNA, offset=5, bits=3) - afc_enable = _RegisterBits(_RH_RF95_REG_0D_RX_CONFIG, offset=4, bits=1) + afc_enable = _RegisterBits(Constants._RH_RF95_REG_0D_RX_CONFIG, offset=4, bits=1) - tx_start_condition = _RegisterBits(_RH_RF95_REG_35_FIFO_THRESH, offset=7, bits=1) - fifo_threshold = _RegisterBits(_RH_RF95_REG_35_FIFO_THRESH, offset=0, bits=6) + tx_start_condition = _RegisterBits(Constants._RH_RF95_REG_35_FIFO_THRESH, offset=7, bits=1) + fifo_threshold = _RegisterBits(Constants._RH_RF95_REG_35_FIFO_THRESH, offset=0, bits=6) modulation_shaping = _RegisterBits( - _RH_RF95_REG_0A_PA_RAMP, offset=6, bits=2) - - packet_format = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=7, bits=1) - dc_free = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=5, bits=2) - crc_on = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=4, bits=1) - crc_auto_clear = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=3, bits=1) - address_filtering = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=1, bits=2) - crc_whitening = _RegisterBits(_RH_RF95_REG_30_PKT_CONFIG_1, offset=0, bits=1) - data_mode = _RegisterBits(_RH_RF95_REG_31_PKT_CONFIG_2, offset=6, bits=1) - - _bw_mantissa = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=3, bits=2) - _bw_exponent = _RegisterBits(_RH_RF95_REG_12_RX_BW, offset=0, bits=3) + Constants._RH_RF95_REG_0A_PA_RAMP, offset=6, bits=2) + + packet_format = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=7, bits=1) + dc_free = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=5, bits=2) + crc_on = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=4, bits=1) + crc_auto_clear = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=3, bits=1) + address_filtering = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=1, bits=2) + crc_whitening = _RegisterBits(Constants._RH_RF95_REG_30_PKT_CONFIG_1, offset=0, bits=1) + data_mode = _RegisterBits(Constants._RH_RF95_REG_31_PKT_CONFIG_2, offset=6, bits=1) + + _bw_mantissa = _RegisterBits(Constants._RH_RF95_REG_12_RX_BW, offset=3, bits=2) + _bw_exponent = _RegisterBits(Constants._RH_RF95_REG_12_RX_BW, offset=0, bits=3) _bw_bins_kHz = (2.5, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25.0, 31.3, 41.7, 50.0, 62.5, 83.3, 100.0, 125.0, 166.7, 200.0, 250.0) _bw_mant_bins = (2, 1, 0, 2, 1, 0, 2, 1, 0, 2, @@ -332,7 +333,7 @@ def __init__( self.reset() # No device type check! Catch an error from the very first request and # throw a nicer message to indicate possible wiring problems. - version = self._read_u8(_RH_RF95_REG_42_VERSION) + version = self._read_u8(Constants._RH_RF95_REG_42_VERSION) if version != 18: raise RuntimeError( "Failed to find rfm9x with expected version -- check wiring" @@ -343,7 +344,7 @@ def __init__( self.sleep() time.sleep(0.01) self.long_range_mode = False # choose FSK instead of LoRA - if self.operation_mode != SLEEP_MODE or self.long_range_mode: + if self.operation_mode != Constants.SLEEP_MODE or self.long_range_mode: raise RuntimeError( "Failed to configure radio for FSK mode, check wiring!") # clear default setting for access to LF registers if frequency > 525MHz @@ -435,17 +436,17 @@ def reset(self): def idle(self): """Enter idle standby mode.""" - self.operation_mode = STANDBY_MODE + self.operation_mode = Constants.STANDBY_MODE def sleep(self): """Enter sleep mode.""" - self.operation_mode = SLEEP_MODE + self.operation_mode = Constants.SLEEP_MODE def listen(self): """Listen for packets to be received by the chip. Use: py: func: `receive` to listen, wait and retrieve packets as they're available. """ - self.operation_mode = RX_MODE + self.operation_mode = Constants.RX_MODE self.dio0_mapping = 0b00 # Interrupt on rx done. def transmit(self): @@ -453,7 +454,7 @@ def transmit(self): function for entering transmit mode and more. For generating and transmitting a packet of data use: py: func: `send` instead. """ - self.operation_mode = TX_MODE + self.operation_mode = Constants.TX_MODE self.dio0_mapping = 0b00 # Interrupt on tx done. @property @@ -461,27 +462,27 @@ def preamble_length(self): """The length of the preamble for sent packets, an unsigned 16-bit value. Default is 0x0003. """ - msb = self._read_u8(_RH_RF95_REG_25_PREAMBLE_MSB) - lsb = self._read_u8(_RH_RF95_REG_26_PREAMBLE_LSB) + msb = self._read_u8(Constants._RH_RF95_REG_25_PREAMBLE_MSB) + lsb = self._read_u8(Constants._RH_RF95_REG_26_PREAMBLE_LSB) return ((msb << 8) | lsb) & 0xFFFF @preamble_length.setter def preamble_length(self, val): val = int(val) assert 0 <= val <= 65535 - self._write_u8(_RH_RF95_REG_25_PREAMBLE_MSB, (val >> 8) & 0xFF) - self._write_u8(_RH_RF95_REG_26_PREAMBLE_LSB, val & 0xFF) + self._write_u8(Constants._RH_RF95_REG_25_PREAMBLE_MSB, (val >> 8) & 0xFF) + self._write_u8(Constants._RH_RF95_REG_26_PREAMBLE_LSB, val & 0xFF) @property def frequency_mhz(self): """The frequency of the radio in Megahertz. Only the allowed values for your radio must be specified(i.e. 433 vs. 915 mhz)! """ - msb = self._read_u8(_RH_RF95_REG_06_FRF_MSB) - mid = self._read_u8(_RH_RF95_REG_07_FRF_MID) - lsb = self._read_u8(_RH_RF95_REG_08_FRF_LSB) + msb = self._read_u8(Constants._RH_RF95_REG_06_FRF_MSB) + mid = self._read_u8(Constants._RH_RF95_REG_07_FRF_MID) + lsb = self._read_u8(Constants._RH_RF95_REG_08_FRF_LSB) frf = ((msb << 16) | (mid << 8) | lsb) & 0xFFFFFF - frequency = (frf * _RH_RF95_FSTEP) / 1000000.0 + frequency = (frf * Constants._RH_RF95_FSTEP) / 1000000.0 return frequency @frequency_mhz.setter @@ -489,69 +490,69 @@ def frequency_mhz(self, val): if val < 240 or val > 960: raise RuntimeError("frequency_mhz must be between 240 and 960") # Calculate FRF register 24-bit value. - frf = int((val * 1000000.0) / _RH_RF95_FSTEP) & 0xFFFFFF + frf = int((val * 1000000.0) / Constants._RH_RF95_FSTEP) & 0xFFFFFF # Extract byte values and update registers. msb = frf >> 16 mid = (frf >> 8) & 0xFF lsb = frf & 0xFF - self._write_u8(_RH_RF95_REG_06_FRF_MSB, msb) - self._write_u8(_RH_RF95_REG_07_FRF_MID, mid) - self._write_u8(_RH_RF95_REG_08_FRF_LSB, lsb) + self._write_u8(Constants._RH_RF95_REG_06_FRF_MSB, msb) + self._write_u8(Constants._RH_RF95_REG_07_FRF_MID, mid) + self._write_u8(Constants._RH_RF95_REG_08_FRF_LSB, lsb) @property def bitrate(self): - msb = self._read_u8(_RH_RF95_REG_02_BITRATE_MSB) - lsb = self._read_u8(_RH_RF95_REG_03_BITRATE_LSB) - frac = self._read_u8(_RH_RF95_REG_5D_BITRATE_FRAC) & 0x0F + msb = self._read_u8(Constants._RH_RF95_REG_02_BITRATE_MSB) + lsb = self._read_u8(Constants._RH_RF95_REG_03_BITRATE_LSB) + frac = self._read_u8(Constants._RH_RF95_REG_5D_BITRATE_FRAC) & 0x0F int_part = ((msb << 8) | lsb) & 0xFFFF - br = _RH_RF95_FXOSC / (int_part + (frac / 16)) + br = Constants._RH_RF95_FXOSC / (int_part + (frac / 16)) return br @bitrate.setter def bitrate(self, val): - br = _RH_RF95_FXOSC / val + br = Constants._RH_RF95_FXOSC / val int_part = int(br) frac_part = int(16 * (br % 1)) & 0x0F msb = (int_part >> 8) & 0xFF lsb = int_part & 0xFF - self._write_u8(_RH_RF95_REG_02_BITRATE_MSB, msb) - self._write_u8(_RH_RF95_REG_03_BITRATE_LSB, lsb) - self._write_u8(_RH_RF95_REG_5D_BITRATE_FRAC, frac_part) + self._write_u8(Constants._RH_RF95_REG_02_BITRATE_MSB, msb) + self._write_u8(Constants._RH_RF95_REG_03_BITRATE_LSB, lsb) + self._write_u8(Constants._RH_RF95_REG_5D_BITRATE_FRAC, frac_part) @property def frequency_deviation(self): - msb = self._read_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB) & 0x3F - lsb = self._read_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB) + msb = self._read_u8(Constants._RH_RF95_REG_04_FREQ_DEVIATION_MSB) & 0x3F + lsb = self._read_u8(Constants._RH_RF95_REG_05_FREQ_DEVIATION_LSB) - fd = (((msb << 8) | lsb) & 0xFFFF) * _RH_RF95_FSTEP + fd = (((msb << 8) | lsb) & 0xFFFF) * Constants._RH_RF95_FSTEP return fd @frequency_deviation.setter def frequency_deviation(self, val): - val = int(val / _RH_RF95_FSTEP) + val = int(val / Constants._RH_RF95_FSTEP) msb = (val >> 8) & 0x3F lsb = val & 0xFF - self._write_u8(_RH_RF95_REG_04_FREQ_DEVIATION_MSB, msb) - self._write_u8(_RH_RF95_REG_05_FREQ_DEVIATION_LSB, lsb) + self._write_u8(Constants._RH_RF95_REG_04_FREQ_DEVIATION_MSB, msb) + self._write_u8(Constants._RH_RF95_REG_05_FREQ_DEVIATION_LSB, lsb) @property def frequency_error(self): """ The frequency error """ - msb = self._read_u8(_RH_RF95_REG_1D_FEI_MSB) - lsb = self._read_u8(_RH_RF95_REG_1E_FEI_LSB) + msb = self._read_u8(Constants._RH_RF95_REG_1D_FEI_MSB) + lsb = self._read_u8(Constants._RH_RF95_REG_1E_FEI_LSB) fei_value = twos_comp( ((msb << 8) | lsb) & 0xFFFF, 16) - f_error = fei_value * _RH_RF95_FSTEP + f_error = fei_value * Constants._RH_RF95_FSTEP return f_error @property @@ -559,8 +560,8 @@ def afc_value(self): """ The automatic frequency correction value """ - msb = self._read_u8(_RH_RF95_REG_1B_AFC_MSB) - lsb = self._read_u8(_RH_RF95_REG_1C_AFC_LSB) + msb = self._read_u8(Constants._RH_RF95_REG_1B_AFC_MSB) + lsb = self._read_u8(Constants._RH_RF95_REG_1C_AFC_LSB) afc = twos_comp( ((msb << 8) | lsb) & 0xFFFF, @@ -580,7 +581,7 @@ def tx_power(self): The actual setting is reduced by 3dBm. """ if self.high_power: - if self.pa_dac & 0x07 == _RH_RF95_PA_DAC_ENABLE: + if self.pa_dac & 0x07 == Constants._RH_RF95_PA_DAC_ENABLE: return self.output_power + 5 + 3 else: return self.output_power + 5 @@ -596,10 +597,10 @@ def tx_power(self, val): # Enable power amp DAC if power is above 20 dB. # Lower setting by 3db when PA_BOOST enabled - see Data Sheet Section 6.4 if val > 20: - self.pa_dac = _RH_RF95_PA_DAC_ENABLE + self.pa_dac = Constants._RH_RF95_PA_DAC_ENABLE val -= 3 else: - self.pa_dac = _RH_RF95_PA_DAC_DISABLE + self.pa_dac = Constants._RH_RF95_PA_DAC_DISABLE self.pa_select = True self.output_power = (val - 5) & 0x0F else: @@ -612,7 +613,7 @@ def tx_power(self, val): def rssi(self): """The received strength indicator (in dBm) of the last received message.""" # Read RSSI register and convert to value using formula in datasheet. - raw_rssi = self._read_u8(_RH_RF95_REG_11_RSSI_VALUE) + raw_rssi = self._read_u8(Constants._RH_RF95_REG_11_RSSI_VALUE) return -raw_rssi / 2 @property @@ -633,7 +634,7 @@ def rx_bandwidth(self): else: raise ValueError(f"RX bandwidth mantissa {mant_binary} invalid") - rxbw = _RH_RF95_FXOSC / (mant * (2**(exp + 2))) + rxbw = Constants._RH_RF95_FXOSC / (mant * (2**(exp + 2))) return rxbw / 1000 @rx_bandwidth.setter @@ -649,19 +650,19 @@ def rx_bandwidth(self, val): def tx_done(self): """Transmit status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b1000) >> 3 + return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b1000) >> 3 def rx_done(self): """Receive status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0100) >> 2 + return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0100) >> 2 def crc_ok(self): """crc status""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0010) >> 1 + return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & 0b0010) >> 1 def fifo_empty(self): """True when FIFO is empty""" - return (self._read_u8(_RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 + return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 class Radiohead: diff --git a/lib/radiohead.py b/lib/radiohead.py new file mode 100644 index 0000000..754d218 --- /dev/null +++ b/lib/radiohead.py @@ -0,0 +1,734 @@ +import random +import time +import tasko +from lib.pycubed_rfm9x import (Constants as LoRa_Constants, + RFM9x as LoRa_RFM9x) +from lib.pycubed_rfm9x_fsk import (Constants as FSK_Constants, + RFM9x as FSK_RFM9x) +from lib.configuration import radio_configuration as rf_config + +HAS_SUPERVISOR = False + +try: + import supervisor + + if hasattr(supervisor, "ticks_ms"): + HAS_SUPERVISOR = True +except ImportError: + pass + +try: + from typing import Optional + from circuitpython_typing import ReadableBuffer + +except ImportError: + pass + + +class Radiohead: + + def __init__(self, + protocol, + tx_spi, + tx_cs, + tx_reset, + rx_spi=None, + rx_cs=None, + rx_reset=None, + rxtx_switch=None, + checksum=True + ): + self.protocol = protocol + + self.tx_device = self.initialize_radio(tx_spi, + tx_cs, + tx_reset) + + if rx_spi and rx_cs and rx_reset: + self.rx_device = self.initialize_radio(rx_spi, + rx_cs, + rx_reset) + self.separate_rx = True + else: + self.rx_device = self.tx_device + self.separate_rx = False + + self.choose_constants() + + self.rxtx_switch = rxtx_switch + + self.ack_wait = 0.5 + """The delay time before attempting a retry after not receiving an ACK""" + + self.receive_timeout = 0.5 + """The amount of time to poll for a received packet. + If no packet is received, the returned packet will be None + """ + + self.xmit_timeout = 2.0 + """The amount of time to wait for the HW to transmit the packet. + This is mainly used to prevent a hang due to a HW issue + """ + + self.ack_retries = 5 + """The number of ACK retries before reporting a failure.""" + + self.ack_delay = None + """The delay time before attemting to send an ACK. + If ACKs are being missed try setting this to .1 or .2. + """ + + # initialize sequence number counter for reliabe datagram mode + self.sequence_number = 0 + + # create seen Ids list + self.seen_ids = bytearray(256) + + # initialize packet header + # node address - default is broadcast + self.node = self.constants._RH_BROADCAST_ADDRESS + """The default address of this Node. (0-255). + If not 255 (0xff) then only packets address to this node will be accepted. + First byte of the RadioHead header. + """ + + # destination address - default is broadcast + self.destination = self.constants._RH_BROADCAST_ADDRESS + """The default destination address for packet transmissions. (0-255). + If 255 (0xff) then any receiving node should accept the packet. + Second byte of the RadioHead header. + """ + + # ID - contains seq count for reliable datagram mode + self.identifier = 0 + """Automatically set to the sequence number when send_with_ack() used. + Third byte of the RadioHead header. + """ + + # flags - identifies ack/reetry packet for reliable datagram mode + self.flags = 0 + """Upper 4 bits reserved for use by Reliable Datagram Mode. + Lower 4 bits may be used to pass information. + Fourth byte of the RadioHead header. + """ + + self.checksum = checksum + self.checksum_error_count = 0 + + self.last_rssi = 0.0 + """The RSSI of the last received packet. Stored when the packet was received. + The instantaneous RSSI value may not be accurate once the + operating mode has been changed. + """ + + def ticks_diff(self, ticks1, ticks2): + """Compute the signed difference between two ticks values + assuming that they are within 2**28 ticks + """ + diff = (ticks1 - ticks2) & self.constants._TICKS_MAX + diff = ((diff + self.constants._TICKS_HALFPERIOD) & self.constants._TICKS_MAX) - self.constants._TICKS_HALFPERIOD + return diff + + def choose_constants(self): + if self.protocol == "fsk": + self.constants = FSK_Constants + elif self.protocol == "lora": + self.constants = LoRa_Constants + + def initialize_radio(self, spi, cs, reset): + if self.protocol == "fsk": + rfm_device = FSK_RFM9x(spi, cs, reset, rf_config.FREQUENCY) + elif self.protocol == "lora": + rfm_device = LoRa_RFM9x(spi, cs, reset, rf_config.FREQUENCY) + else: + raise RuntimeError(f"unrecognized radio protocol: {self.protocol}") + return rfm_device + + def listen(self): + if self.separate_rx: + self.tx_device.idle() + self.rx_device.listen() + if self.rxtx_switch: + self.rxtx_switch.receive() + + def idle(self): + self.tx_device.idle() + if self.separate_rx: + self.rx_device.idle() + if self.rxtx_switch: + self.rxtx_switch.idle() + + def transmit(self): + if self.separate_rx: + self.rx_device.idle() + self.tx_device.transmit() + if self.rxtx_switch: + self.rxtx_switch.transmit() + + """ + =========================================================================== + FSK Specific Functions + =========================================================================== + """ + async def fsk_send( + self, + data, + *, + keep_listening=False, + destination=None, + node=None, + identifier=None, + flags=None, + debug=False + ): + """Send a string of data using the transmitter. + You can only send 57 bytes at a time + (limited by chip's FIFO size and appended headers). + This prepends a 1 byte length to be compatible with the RFM9X fsk packet handler, + and 4 byte header to be compatible with the RadioHead library. + The header defaults to using the initialized attributes: + (destination, node, identifier, flags) + It may be temporarily overidden via the kwargs - destination, node, identifier, flags. + Values passed via kwargs do not alter the attribute settings. + The keep_listening argument should be set to True if you want to start listening + automatically after the packet is sent. The default setting is False. + + Returns: True if success or False if the send timed out. + """ + # Disable pylint warning to not use length as a check for zero. + # This is a puzzling warning as the below code is clearly the most + # efficient and proper way to ensure a precondition that the provided + # buffer be within an expected range of bounds. Disable this check. + # pylint: disable=len-as-condition + assert 0 < len(data) <= 57 # TODO: Allow longer packets, see pg 76 + # pylint: enable=len-as-condition + self.idle() # Stop receiving to clear FIFO and keep it clear. + + # Combine header and data to form payload + payload = bytearray(5) + payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) + if destination is None: # use attribute + payload[1] = self.destination + else: # use kwarg + payload[1] = destination + if node is None: # use attribute + payload[2] = self.node + else: # use kwarg + payload[2] = node + if identifier is None: # use attribute + payload[3] = self.identifier + else: # use kwarg + payload[3] = identifier + if flags is None: # use attribute + payload[4] = self.flags + else: # use kwarg + payload[4] = flags + + payload = payload + data + + if self.checksum: + payload[0] += 2 + checksum = bsd_checksum(payload) + payload = payload + checksum + + # Write payload. + if debug: + print(f"RFM9X: Sending {str(payload)}") + self.tx_device._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) + + # Turn on transmit mode to send out the packet. + self.transmit() + # Wait for tx done interrupt with explicit polling (not ideal but + # best that can be done right now without interrupts). + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.tx_device.tx_done(): + if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.tx_device.tx_done(): + if time.monotonic() - start >= self.xmit_timeout: + timed_out = True + else: + await tasko.sleep(0) + + # Done transmitting - change modes (interrupt automatically cleared on mode change) + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + return not timed_out + + async def fsk_receive( + self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False + ): + """Wait to receive a packet from the receiver. If a packet is found the payload bytes + are returned, otherwise None is returned(which indicates the timeout elapsed with no + reception). + If keep_listening is True (the default) the chip will immediately enter listening mode + after reception of a packet, otherwise it will fall back to idle mode and ignore any + future reception. + All packets must have a 4-byte header for compatibilty with the + RadioHead library. + The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip + the header before returning the packet to the caller. + If with_header is True then the 4 byte header will be returned with the packet. + The payload then begins at packet[4]. + If with_ack is True, send an ACK after receipt(Reliable Datagram mode) + """ + + if timeout is None: + timeout = self.receive_timeout + + # get starting time + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + else: + start = time.monotonic() + + packet = None + # Make sure we are listening for packets (and not transmitting). + self.listen() + + while True: + # check for valid packets + if self.rx_device.rx_done(): + # save last RSSI reading + self.last_rssi = self.rx_device.rssi + # Enter idle mode to stop receiving other packets. + self.idle() + # read packet + packet = await self._process_packet(with_header=with_header, with_ack=with_ack, debug=debug) + if packet is not None: + break # packet valid - return it + # packet invalid - continue listening + self.listen() + + # check if we have timed out + if ((HAS_SUPERVISOR and (self.constants.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000)) or + (not HAS_SUPERVISOR and (time.monotonic() - start >= timeout))): + # timed out + if debug: + print("RFM9X: RX timed out") + break + + await tasko.sleep(0) + + # Exit + if keep_listening: + self.listen() + else: + self.idle() + + return packet + + async def fsk_process_packet(self, with_header=False, with_ack=False, debug=False): + + # Read the data from the radio FIFO + packet = bytearray(self.constants._MAX_FIFO_LENGTH) + packet_length = self.rx_device._read_until_flag(self.constants._RH_RF95_REG_00_FIFO, + packet, + self.rx_device.fifo_empty) + + # Reject if the received packet is too small to include the 1 byte length, the + # 4 byte RadioHead header and at least one byte of data + if packet_length < 6: + if debug: + print(f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {str(packet)})") + return None + + # Reject if the length recorded in the packet doesn't match the amount of data we got + internal_packet_length = packet[0] + if internal_packet_length != packet_length - 1: + if debug: + print( + f"RFM9X: Received packet length ({packet_length}) " + + f"does not match transmitted packet length ({internal_packet_length}), " + + f"packet = {str(packet)}") + return None + + packet = packet[:packet_length] + # Reject if the packet does not pass the checksum + if self.checksum: + if not bsd_checksum(packet[:-2]) == packet[-2:]: + if debug: + print( + f"RFM9X: Checksum failed, packet = {str(packet)}, bsd_checksum(packet[:-2])" + + f" = {bsd_checksum(packet[:-2])}, packet[-2:] = {packet[-2:]}") + self.checksum_error_count += 1 + return None + else: + # passed the checksum - remove it before continuing + packet = packet[:-2] + + # Reject if the packet wasn't sent to my address + if (self.node != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.node): + if debug: + print( + "RFM9X: Incorrect Address " + + f"(packet address = {packet[1]} != my address = {self.node}), " + + f"packet = {str(packet)}") + return None + + # send ACK unless this was an ACK or a broadcast + if (with_ack and + ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) and + (packet[1] != self.constants._RH_BROADCAST_ADDRESS)): + # delay before sending Ack to give receiver a chance to get ready + if self.ack_delay is not None: + await tasko.sleep(self.ack_delay) + # send ACK packet to sender (data is b'!') + if debug: + print("RFM9X: Sending ACK") + await self.send( + b"!", + destination=packet[2], + node=packet[1], + identifier=packet[3], + flags=(packet[4] | self.constants._RH_FLAGS_ACK), + ) + # reject this packet if its identifier was the most recent one from its source + # TODO: Make sure identifiers are being changed for each packet + if (self.seen_ids[packet[2]] == packet[3]) and ( + packet[4] & self.constants._RH_FLAGS_RETRY): + if debug: + print(f"RFM9X: dropping retried packet, packet = {str(packet)}") + return None + else: # save the packet identifier for this source + self.seen_ids[packet[2]] = packet[3] + + if (not with_header): # skip the header if not wanted + packet = packet[5:] + + if debug: + print(f"RFM9X: Received {str(packet)}") + + return packet + + """ + =========================================================================== + LoRa Specific Functions + =========================================================================== + """ + async def LoRa_send( + self, + data: ReadableBuffer, + *, + keep_listening: bool = False, + destination: Optional[int] = None, + node: Optional[int] = None, + identifier: Optional[int] = None, + flags: Optional[int] = None, + debug: bool = False + ) -> bool: + """Send a string of data using the transmitter. + You can only send 252 bytes at a time + (limited by chip's FIFO size and appended headers). + This appends a 4 byte header to be compatible with the RadioHead library. + The header defaults to using the initialized attributes: + (destination,node,identifier,flags) + It may be temporarily overidden via the kwargs - destination,node,identifier,flags. + Values passed via kwargs do not alter the attribute settings. + The keep_listening argument should be set to True if you want to start listening + automatically after the packet is sent. The default setting is False. + + Returns: True if success or False if the send timed out. + """ + # Disable pylint warning to not use length as a check for zero. + # This is a puzzling warning as the below code is clearly the most + # efficient and proper way to ensure a precondition that the provided + # buffer be within an expected range of bounds. Disable this check. + # pylint: disable=len-as-condition + assert 0 < len(data) <= 252 + # pylint: enable=len-as-condition + self.idle() # Stop receiving to clear FIFO and keep it clear. + # Fill the FIFO with a packet to send. + self._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. + # Combine header and data to form payload + payload = bytearray(5) + payload[0] = len(payload) + len(data) + if destination is None: # use attribute + payload[1] = self.destination + else: # use kwarg + payload[1] = destination + if node is None: # use attribute + payload[2] = self.node + else: # use kwarg + payload[2] = node + if identifier is None: # use attribute + payload[3] = self.identifier + else: # use kwarg + payload[3] = identifier + if flags is None: # use attribute + payload[4] = self.flags + else: # use kwarg + payload[4] = flags + payload = payload + data + + if self.checksum: + payload[0] += 2 + checksum = bsd_checksum(payload) + payload = payload + checksum + + if debug: + print(f"RFM9x: sending {str(payload)}") + + # Write payload. + self._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) + # Write payload and header length. + self._write_u8(self.constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + # Turn on transmit mode to send out the packet. + self.transmit() + # Wait for tx done interrupt with explicit polling (not ideal but + # best that can be done right now without interrupts). + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.tx_done(): + if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.tx_done(): + if time.monotonic() - start >= self.xmit_timeout: + timed_out = True + else: + await tasko.sleep(0) + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + # Clear interrupt. + self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return not timed_out + + async def LoRa_receive( + self, + *, + keep_listening: bool = True, + with_header: bool = False, + with_ack: bool = False, + timeout: Optional[float] = None + ) -> Optional[bytearray]: + """Wait to receive a packet from the receiver. If a packet is found the payload bytes + are returned, otherwise None is returned (which indicates the timeout elapsed with no + reception). + If keep_listening is True (the default) the chip will immediately enter listening mode + after reception of a packet, otherwise it will fall back to idle mode and ignore any + future reception. + All packets must have a 4-byte header for compatibility with the + RadioHead library. + The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip + the header before returning the packet to the caller. + If with_header is True then the 4 byte header will be returned with the packet. + The payload then begins at packet[4]. + If with_ack is True, send an ACK after receipt (Reliable Datagram mode) + """ + timed_out = False + if timeout is None: + timeout = self.receive_timeout + if timeout is not None: + # Wait for the payload_ready signal. This is not ideal and will + # surely miss or overflow the FIFO when packets aren't read fast + # enough, however it's the best that can be done from Python without + # interrupt supports. + # Make sure we are listening for packets. + self.listen() + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.rx_done(): + if self.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.rx_done(): + if time.monotonic() - start >= timeout: + timed_out = True + else: + await tasko.sleep(0) + # Payload ready is set, a packet is in the FIFO. + packet = None + # save last RSSI reading + self.last_rssi = self.rssi + + # save the last SNR reading + self.last_snr = self.snr + + # Enter idle mode to stop receiving other packets. + self.idle() + if not timed_out: + if self.enable_crc and self.crc_error(): + self.crc_error_count += 1 + else: + # Read the data from the FIFO. + # Read the length of the FIFO. + fifo_length = self._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) + # Handle if the received packet is too small to include the 4 byte + # RadioHead header and at least one byte of data --reject this packet and ignore it. + if fifo_length > 0: # read and clear the FIFO if anything in it + current_addr = self._read_u8(self.constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) + self._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) + packet = bytearray(fifo_length) + # Read the packet. + self._read_into(self.constants._RH_RF95_REG_00_FIFO, packet) + # Clear interrupt. + self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + if fifo_length < 5: + packet = None + else: + if ( + self.node != self.constants._RH_BROADCAST_ADDRESS + and packet[1] != self.constants._RH_BROADCAST_ADDRESS + and packet[1] != self.node + ): + packet = None + # send ACK unless this was an ACK or a broadcast + elif ( + with_ack + and ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) + and (packet[1] != self.constants._RH_BROADCAST_ADDRESS) + ): + # delay before sending Ack to give receiver a chance to get ready + if self.ack_delay is not None: + time.sleep(self.ack_delay) + # send ACK packet to sender (data is b'!') + await self.send( + b"!", + destination=packet[1], + node=packet[0], + identifier=packet[2], + flags=(packet[4] | self.constants._RH_FLAGS_ACK), + ) + # reject Retries if we have seen this idetifier from this source before + if (self.seen_ids[packet[1]] == packet[2]) and ( + packet[4] & self.constants._RH_FLAGS_RETRY + ): + packet = None + else: # save the packet identifier for this source + self.seen_ids[packet[2]] = packet[3] + if ( + not with_header and packet is not None + ): # skip the header if not wanted + packet = packet[5:] + # Listen again if necessary and return the result packet. + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + # Clear interrupt. + self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return packet + + """ + =========================================================================== + Wrapper Functions + =========================================================================== + """ + + async def send( + self, + data: ReadableBuffer, + *, + keep_listening: bool = False, + destination: Optional[int] = None, + node: Optional[int] = None, + identifier: Optional[int] = None, + flags: Optional[int] = None, + debug: bool = False + ) -> bool: + if self.protocol == "fsk": + self.fsk_send(data, + keep_listening, + destination, + node, + identifier, + flags, + debug) + elif self.protocol == "LoRa": + self.LoRa_send(data, + keep_listening, + destination, + identifier, + flags, + debug) + + async def fsk_send_with_ack(self, data, debug=False): + """Reliable Datagram mode: + Send a packet with data and wait for an ACK response. + The packet header is automatically generated. + If enabled, the packet transmission will be retried on failure + """ + if self.ack_retries: + retries_remaining = self.ack_retries + else: + retries_remaining = 1 + got_ack = False + self.sequence_number = (self.sequence_number + 1) & 0xFF + while not got_ack and retries_remaining: + self.identifier = self.sequence_number + await self.send(data, keep_listening=True, debug=debug) + # Don't look for ACK from Broadcast message + if self.destination == self.constants._RH_BROADCAST_ADDRESS: + got_ack = True + else: + # wait for a packet from our destination + ack_packet = await self.receive( + timeout=self.ack_wait, with_header=True, debug=debug) + if ack_packet is not None: + if ack_packet[4] & self.constants._RH_FLAGS_ACK: + # check the ID + if ack_packet[3] == self.identifier: + got_ack = True + break + if debug: + print(f"Invalid ACK packet {str(ack_packet)}") + # pause before next retry -- random delay + if not got_ack: + # delay by random amount before next try + await tasko.sleep(self.ack_wait * random.random()) + if debug: + print(f"No ACK, retrying send - retries remaining: {retries_remaining}") + retries_remaining = retries_remaining - 1 + # set retry flag in packet header + self.flags |= self.constants._RH_FLAGS_RETRY + self.flags = 0 # clear flags + return got_ack + + async def receive( + self, + *, + keep_listening: bool = True, + with_header: bool = False, + with_ack: bool = False, + timeout: Optional[float] = None + ) -> Optional[bytearray]: + if self.protocol == "fsk": + self.fsk_receive(keep_listening, with_header, with_ack, timeout) + elif self.protocol == "lora": + self.LoRa_receive(keep_listening, with_ack, with_header, timeout) + + +def bsd_checksum(bytedata): + """Very simple, not secure, but fast 2 byte checksum""" + checksum = 0 + + for b in bytedata: + checksum = (checksum >> 1) + ((checksum & 1) << 15) + checksum += b + checksum &= 0xffff + return bytes([checksum >> 8, checksum & 0xff]) From e163ecff7c15e66e23154a5bdcc32a2cd80139b0 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 12:24:45 -0400 Subject: [PATCH 03/37] updating gs_setup to use Radiohead class --- gs_setup.py | 56 +++++++++-------------------------------------------- 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/gs_setup.py b/gs_setup.py index a8a5888..4cf1017 100644 --- a/gs_setup.py +++ b/gs_setup.py @@ -6,58 +6,20 @@ import busio import digitalio from lib import pycubed_rfm9x_fsk +from lib import radiohead from lib.configuration import radio_configuration as rf_config from shell_utils import bold, normal -def initialize_rfm9x(spi, cs, reset): - """ - Initialize the radio - uses lib/configuration/radio_configuration to configure with defaults - """ - - rfm_device = pycubed_rfm9x_fsk.RFM9x( - spi, - cs, - reset, - rf_config.FREQUENCY, - ) - - # configure to match satellite - rfm_device.tx_power = rf_config.TX_POWER - rfm_device.bitrate = rf_config.BITRATE - rfm_device.frequency_deviation = rf_config.FREQUENCY_DEVIATION - rfm_device.rx_bandwidth = rf_config.RX_BANDWIDTH - rfm_device.preamble_length = rf_config.PREAMBLE_LENGTH - - return rfm_device - - -def initialize_radiohead(tx_device, rx_device=None, rxtx_switch=None): - - rh = pycubed_rfm9x_fsk.Radiohead( - tx_device, - rx_device=rx_device, - rxtx_switch=rxtx_switch, - checksum=rf_config.CHECKSUM - ) - - rh.ack_delay = rf_config.ACK_DELAY - rh.ack_wait = rf_config.ACK_WAIT - rh.receive_timeout = rf_config.RECEIVE_TIMEOUT - rh.node = rf_config.GROUNDSTATION_ID - rh.destination = rf_config.SATELLITE_ID - - return rh - - def initialize_radio(tx_spi, tx_cs, tx_reset, rx_spi=None, rx_cs=None, rx_reset=None, rxtx_switch=None): - tx_device = initialize_rfm9x(tx_spi, tx_cs, tx_reset) - rx_device = None - if rx_spi and rx_cs and rx_reset: - rx_device = initialize_rfm9x(rx_spi, rx_cs, rx_reset) - - return initialize_radiohead(tx_device, rx_device=rx_device, rxtx_switch=rxtx_switch) - + return radiohead.Radiohead(rf_config.PROTOCOL, + tx_spi, + tx_cs, + tx_reset, + rx_spi, + rx_cs, + rx_reset, + rxtx_switch) def satellite_spi_config(): # pocketqube From dec925f3a3c45c42df38e229b442fc3be100a884 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 12:25:17 -0400 Subject: [PATCH 04/37] removing unused code from driver --- lib/pycubed_rfm9x.py | 255 ------------------------- lib/pycubed_rfm9x_fsk.py | 402 --------------------------------------- 2 files changed, 657 deletions(-) diff --git a/lib/pycubed_rfm9x.py b/lib/pycubed_rfm9x.py index 378089f..848a32f 100644 --- a/lib/pycubed_rfm9x.py +++ b/lib/pycubed_rfm9x.py @@ -685,258 +685,3 @@ def rx_done(self) -> bool: def crc_error(self) -> bool: """crc status""" return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 - - # pylint: disable=too-many-branches - async def send( - self, - data: ReadableBuffer, - *, - keep_listening: bool = False, - destination: Optional[int] = None, - node: Optional[int] = None, - identifier: Optional[int] = None, - flags: Optional[int] = None, - debug: bool = False - ) -> bool: - """Send a string of data using the transmitter. - You can only send 252 bytes at a time - (limited by chip's FIFO size and appended headers). - This appends a 4 byte header to be compatible with the RadioHead library. - The header defaults to using the initialized attributes: - (destination,node,identifier,flags) - It may be temporarily overidden via the kwargs - destination,node,identifier,flags. - Values passed via kwargs do not alter the attribute settings. - The keep_listening argument should be set to True if you want to start listening - automatically after the packet is sent. The default setting is False. - - Returns: True if success or False if the send timed out. - """ - # Disable pylint warning to not use length as a check for zero. - # This is a puzzling warning as the below code is clearly the most - # efficient and proper way to ensure a precondition that the provided - # buffer be within an expected range of bounds. Disable this check. - # pylint: disable=len-as-condition - assert 0 < len(data) <= 252 - # pylint: enable=len-as-condition - self.idle() # Stop receiving to clear FIFO and keep it clear. - # Fill the FIFO with a packet to send. - self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. - # Combine header and data to form payload - payload = bytearray(5) - payload[0] = len(payload) + len(data) - if destination is None: # use attribute - payload[1] = self.destination - else: # use kwarg - payload[1] = destination - if node is None: # use attribute - payload[2] = self.node - else: # use kwarg - payload[2] = node - if identifier is None: # use attribute - payload[3] = self.identifier - else: # use kwarg - payload[3] = identifier - if flags is None: # use attribute - payload[4] = self.flags - else: # use kwarg - payload[4] = flags - payload = payload + data - - if self.checksum: - payload[0] += 2 - checksum = bsd_checksum(payload) - payload = payload + checksum - - if debug: - print(f"RFM9x: sending {str(payload)}") - - # Write payload. - self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) - # Write payload and header length. - self._write_u8(Constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) - # Turn on transmit mode to send out the packet. - self.transmit() - # Wait for tx done interrupt with explicit polling (not ideal but - # best that can be done right now without interrupts). - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.tx_done(): - if ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: - timed_out = True - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.tx_done(): - if time.monotonic() - start >= self.xmit_timeout: - timed_out = True - else: - await tasko.sleep(0) - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - # Clear interrupt. - self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - return not timed_out - - async def send_with_ack(self, data: ReadableBuffer) -> bool: - """Reliable Datagram mode: - Send a packet with data and wait for an ACK response. - The packet header is automatically generated. - If enabled, the packet transmission will be retried on failure - """ - if self.ack_retries: - retries_remaining = self.ack_retries - else: - retries_remaining = 1 - got_ack = False - self.sequence_number = (self.sequence_number + 1) & 0xFF - while not got_ack and retries_remaining: - self.identifier = self.sequence_number - await self.send(data, keep_listening=True) - # Don't look for ACK from Broadcast message - if self.destination == Constants._RH_BROADCAST_ADDRESS: - got_ack = True - else: - # wait for a packet from our destination - ack_packet = await self.receive(timeout=self.ack_wait, with_header=True) - if ack_packet is not None: - if ack_packet[4] & Constants._RH_FLAGS_ACK: - # check the ID - if ack_packet[3] == self.identifier: - got_ack = True - break - # pause before next retry -- random delay - if not got_ack: - # delay by random amount before next try - await tasko.sleep(self.ack_wait + self.ack_wait * random.random()) - retries_remaining = retries_remaining - 1 - # set retry flag in packet header - self.flags |= Constants._RH_FLAGS_RETRY - self.flags = 0 # clear flags - return got_ack - - async def receive( - self, - *, - keep_listening: bool = True, - with_header: bool = False, - with_ack: bool = False, - timeout: Optional[float] = None - ) -> Optional[bytearray]: - """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned (which indicates the timeout elapsed with no - reception). - If keep_listening is True (the default) the chip will immediately enter listening mode - after reception of a packet, otherwise it will fall back to idle mode and ignore any - future reception. - All packets must have a 4-byte header for compatibility with the - RadioHead library. - The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip - the header before returning the packet to the caller. - If with_header is True then the 4 byte header will be returned with the packet. - The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt (Reliable Datagram mode) - """ - timed_out = False - if timeout is None: - timeout = self.receive_timeout - if timeout is not None: - # Wait for the payload_ready signal. This is not ideal and will - # surely miss or overflow the FIFO when packets aren't read fast - # enough, however it's the best that can be done from Python without - # interrupt supports. - # Make sure we are listening for packets. - self.listen() - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.rx_done(): - if ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: - timed_out = True - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.rx_done(): - if time.monotonic() - start >= timeout: - timed_out = True - else: - await tasko.sleep(0) - # Payload ready is set, a packet is in the FIFO. - packet = None - # save last RSSI reading - self.last_rssi = self.rssi - - # save the last SNR reading - self.last_snr = self.snr - - # Enter idle mode to stop receiving other packets. - self.idle() - if not timed_out: - if self.enable_crc and self.crc_error(): - self.crc_error_count += 1 - else: - # Read the data from the FIFO. - # Read the length of the FIFO. - fifo_length = self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) - # Handle if the received packet is too small to include the 4 byte - # RadioHead header and at least one byte of data --reject this packet and ignore it. - if fifo_length > 0: # read and clear the FIFO if anything in it - current_addr = self._read_u8(Constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) - self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) - packet = bytearray(fifo_length) - # Read the packet. - self._read_into(Constants._RH_RF95_REG_00_FIFO, packet) - # Clear interrupt. - self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - if fifo_length < 5: - packet = None - else: - if ( - self.node != Constants._RH_BROADCAST_ADDRESS - and packet[1] != Constants._RH_BROADCAST_ADDRESS - and packet[1] != self.node - ): - packet = None - # send ACK unless this was an ACK or a broadcast - elif ( - with_ack - and ((packet[4] & Constants._RH_FLAGS_ACK) == 0) - and (packet[1] != Constants._RH_BROADCAST_ADDRESS) - ): - # delay before sending Ack to give receiver a chance to get ready - if self.ack_delay is not None: - time.sleep(self.ack_delay) - # send ACK packet to sender (data is b'!') - await self.send( - b"!", - destination=packet[1], - node=packet[0], - identifier=packet[2], - flags=(packet[4] | Constants._RH_FLAGS_ACK), - ) - # reject Retries if we have seen this idetifier from this source before - if (self.seen_ids[packet[1]] == packet[2]) and ( - packet[4] & Constants._RH_FLAGS_RETRY - ): - packet = None - else: # save the packet identifier for this source - self.seen_ids[packet[2]] = packet[3] - if ( - not with_header and packet is not None - ): # skip the header if not wanted - packet = packet[5:] - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - # Clear interrupt. - self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - return packet diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 9b2db2a..8e75d99 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -663,405 +663,3 @@ def crc_ok(self): def fifo_empty(self): """True when FIFO is empty""" return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 - - -class Radiohead: - - def __init__(self, - tx_device, - rx_device=None, - rxtx_switch=None, - checksum=True - ): - """ - """ - - self.tx_device = tx_device - - if rx_device is not None: - self.rx_device = rx_device - self.separate_rx = True - else: - self.rx_device = tx_device - self.separate_rx = False - - self.rxtx_switch = rxtx_switch - - self.ack_wait = 0.5 - """The delay time before attempting a retry after not receiving an ACK""" - - self.receive_timeout = 0.5 - """The amount of time to poll for a received packet. - If no packet is received, the returned packet will be None - """ - - self.xmit_timeout = 2.0 - """The amount of time to wait for the HW to transmit the packet. - This is mainly used to prevent a hang due to a HW issue - """ - - self.ack_retries = 5 - """The number of ACK retries before reporting a failure.""" - - self.ack_delay = None - """The delay time before attemting to send an ACK. - If ACKs are being missed try setting this to .1 or .2. - """ - - # initialize sequence number counter for reliabe datagram mode - self.sequence_number = 0 - - # create seen Ids list - self.seen_ids = bytearray(256) - - # initialize packet header - # node address - default is broadcast - self.node = _RH_BROADCAST_ADDRESS - """The default address of this Node. (0-255). - If not 255 (0xff) then only packets address to this node will be accepted. - First byte of the RadioHead header. - """ - - # destination address - default is broadcast - self.destination = _RH_BROADCAST_ADDRESS - """The default destination address for packet transmissions. (0-255). - If 255 (0xff) then any receiving node should accept the packet. - Second byte of the RadioHead header. - """ - - # ID - contains seq count for reliable datagram mode - self.identifier = 0 - """Automatically set to the sequence number when send_with_ack() used. - Third byte of the RadioHead header. - """ - - # flags - identifies ack/reetry packet for reliable datagram mode - self.flags = 0 - """Upper 4 bits reserved for use by Reliable Datagram Mode. - Lower 4 bits may be used to pass information. - Fourth byte of the RadioHead header. - """ - - self.checksum = checksum - self.checksum_error_count = 0 - - self.last_rssi = 0.0 - """The RSSI of the last received packet. Stored when the packet was received. - The instantaneous RSSI value may not be accurate once the - operating mode has been changed. - """ - - def listen(self): - if self.separate_rx: - self.tx_device.idle() - self.rx_device.listen() - if self.rxtx_switch: - self.rxtx_switch.receive() - - def idle(self): - self.tx_device.idle() - if self.separate_rx: - self.rx_device.idle() - if self.rxtx_switch: - self.rxtx_switch.idle() - - def transmit(self): - if self.separate_rx: - self.rx_device.idle() - self.tx_device.transmit() - if self.rxtx_switch: - self.rxtx_switch.transmit() - - # pylint: disable=too-many-branches - async def send( - self, - data, - *, - keep_listening=False, - destination=None, - node=None, - identifier=None, - flags=None, - debug=False - ): - """Send a string of data using the transmitter. - You can only send 57 bytes at a time - (limited by chip's FIFO size and appended headers). - This prepends a 1 byte length to be compatible with the RFM9X fsk packet handler, - and 4 byte header to be compatible with the RadioHead library. - The header defaults to using the initialized attributes: - (destination, node, identifier, flags) - It may be temporarily overidden via the kwargs - destination, node, identifier, flags. - Values passed via kwargs do not alter the attribute settings. - The keep_listening argument should be set to True if you want to start listening - automatically after the packet is sent. The default setting is False. - - Returns: True if success or False if the send timed out. - """ - # Disable pylint warning to not use length as a check for zero. - # This is a puzzling warning as the below code is clearly the most - # efficient and proper way to ensure a precondition that the provided - # buffer be within an expected range of bounds. Disable this check. - # pylint: disable=len-as-condition - assert 0 < len(data) <= 57 # TODO: Allow longer packets, see pg 76 - # pylint: enable=len-as-condition - self.idle() # Stop receiving to clear FIFO and keep it clear. - - # Combine header and data to form payload - payload = bytearray(5) - payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) - if destination is None: # use attribute - payload[1] = self.destination - else: # use kwarg - payload[1] = destination - if node is None: # use attribute - payload[2] = self.node - else: # use kwarg - payload[2] = node - if identifier is None: # use attribute - payload[3] = self.identifier - else: # use kwarg - payload[3] = identifier - if flags is None: # use attribute - payload[4] = self.flags - else: # use kwarg - payload[4] = flags - - payload = payload + data - - if self.checksum: - payload[0] += 2 - checksum = bsd_checksum(payload) - payload = payload + checksum - - # Write payload. - if debug: - print(f"RFM9X: Sending {str(payload)}") - self.tx_device._write_from(_RH_RF95_REG_00_FIFO, payload) - - # Turn on transmit mode to send out the packet. - self.transmit() - # Wait for tx done interrupt with explicit polling (not ideal but - # best that can be done right now without interrupts). - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.tx_device.tx_done(): - if ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: - timed_out = True - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.tx_device.tx_done(): - if time.monotonic() - start >= self.xmit_timeout: - timed_out = True - else: - await tasko.sleep(0) - - # Done transmitting - change modes (interrupt automatically cleared on mode change) - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - return not timed_out - - async def send_with_ack(self, data, debug=False): - """Reliable Datagram mode: - Send a packet with data and wait for an ACK response. - The packet header is automatically generated. - If enabled, the packet transmission will be retried on failure - """ - if self.ack_retries: - retries_remaining = self.ack_retries - else: - retries_remaining = 1 - got_ack = False - self.sequence_number = (self.sequence_number + 1) & 0xFF - while not got_ack and retries_remaining: - self.identifier = self.sequence_number - await self.send(data, keep_listening=True, debug=debug) - # Don't look for ACK from Broadcast message - if self.destination == _RH_BROADCAST_ADDRESS: - got_ack = True - else: - # wait for a packet from our destination - ack_packet = await self.receive( - timeout=self.ack_wait, with_header=True, debug=debug) - if ack_packet is not None: - if ack_packet[4] & _RH_FLAGS_ACK: - # check the ID - if ack_packet[3] == self.identifier: - got_ack = True - break - if debug: - print(f"Invalid ACK packet {str(ack_packet)}") - # pause before next retry -- random delay - if not got_ack: - # delay by random amount before next try - await tasko.sleep(self.ack_wait * random.random()) - if debug: - print(f"No ACK, retrying send - retries remaining: {retries_remaining}") - retries_remaining = retries_remaining - 1 - # set retry flag in packet header - self.flags |= _RH_FLAGS_RETRY - self.flags = 0 # clear flags - return got_ack - - async def receive( - self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False - ): - """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned(which indicates the timeout elapsed with no - reception). - If keep_listening is True (the default) the chip will immediately enter listening mode - after reception of a packet, otherwise it will fall back to idle mode and ignore any - future reception. - All packets must have a 4-byte header for compatibilty with the - RadioHead library. - The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip - the header before returning the packet to the caller. - If with_header is True then the 4 byte header will be returned with the packet. - The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt(Reliable Datagram mode) - """ - - if timeout is None: - timeout = self.receive_timeout - - # get starting time - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - else: - start = time.monotonic() - - packet = None - # Make sure we are listening for packets (and not transmitting). - self.listen() - - while True: - # check for valid packets - if self.rx_device.rx_done(): - # save last RSSI reading - self.last_rssi = self.rx_device.rssi - # Enter idle mode to stop receiving other packets. - self.idle() - # read packet - packet = await self._process_packet(with_header=with_header, with_ack=with_ack, debug=debug) - if packet is not None: - break # packet valid - return it - # packet invalid - continue listening - self.listen() - - # check if we have timed out - if ((HAS_SUPERVISOR and (ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000)) or - (not HAS_SUPERVISOR and (time.monotonic() - start >= timeout))): - # timed out - if debug: - print("RFM9X: RX timed out") - break - - await tasko.sleep(0) - - # Exit - if keep_listening: - self.listen() - else: - self.idle() - - return packet - - async def _process_packet(self, with_header=False, with_ack=False, debug=False): - - # Read the data from the radio FIFO - packet = bytearray(_MAX_FIFO_LENGTH) - packet_length = self.rx_device._read_until_flag(_RH_RF95_REG_00_FIFO, packet, self.rx_device.fifo_empty) - - # Reject if the received packet is too small to include the 1 byte length, the - # 4 byte RadioHead header and at least one byte of data - if packet_length < 6: - if debug: - print(f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {str(packet)})") - return None - - # Reject if the length recorded in the packet doesn't match the amount of data we got - internal_packet_length = packet[0] - if internal_packet_length != packet_length - 1: - if debug: - print( - f"RFM9X: Received packet length ({packet_length}) " + - f"does not match transmitted packet length ({internal_packet_length}), " + - f"packet = {str(packet)}") - return None - - packet = packet[:packet_length] - # Reject if the packet does not pass the checksum - if self.checksum: - if not bsd_checksum(packet[:-2]) == packet[-2:]: - if debug: - print( - f"RFM9X: Checksum failed, packet = {str(packet)}, bsd_checksum(packet[:-2])" + - f" = {bsd_checksum(packet[:-2])}, packet[-2:] = {packet[-2:]}") - self.checksum_error_count += 1 - return None - else: - # passed the checksum - remove it before continuing - packet = packet[:-2] - - # Reject if the packet wasn't sent to my address - if (self.node != _RH_BROADCAST_ADDRESS and - packet[1] != _RH_BROADCAST_ADDRESS and - packet[1] != self.node): - if debug: - print( - "RFM9X: Incorrect Address " + - f"(packet address = {packet[1]} != my address = {self.node}), " + - f"packet = {str(packet)}") - return None - - # send ACK unless this was an ACK or a broadcast - if (with_ack and - ((packet[4] & _RH_FLAGS_ACK) == 0) and - (packet[1] != _RH_BROADCAST_ADDRESS)): - # delay before sending Ack to give receiver a chance to get ready - if self.ack_delay is not None: - await tasko.sleep(self.ack_delay) - # send ACK packet to sender (data is b'!') - if debug: - print("RFM9X: Sending ACK") - await self.send( - b"!", - destination=packet[2], - node=packet[1], - identifier=packet[3], - flags=(packet[4] | _RH_FLAGS_ACK), - ) - # reject this packet if its identifier was the most recent one from its source - # TODO: Make sure identifiers are being changed for each packet - if (self.seen_ids[packet[2]] == packet[3]) and ( - packet[4] & _RH_FLAGS_RETRY): - if debug: - print(f"RFM9X: dropping retried packet, packet = {str(packet)}") - return None - else: # save the packet identifier for this source - self.seen_ids[packet[2]] = packet[3] - - if (not with_header): # skip the header if not wanted - packet = packet[5:] - - if debug: - print(f"RFM9X: Received {str(packet)}") - - return packet - - -def bsd_checksum(bytedata): - """Very simple, not secure, but fast 2 byte checksum""" - checksum = 0 - - for b in bytedata: - checksum = (checksum >> 1) + ((checksum & 1) << 15) - checksum += b - checksum &= 0xffff - return bytes([checksum >> 8, checksum & 0xff]) From 9e7493f67a6f6607f13ff7e37fc233708ae54a5e Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 13:57:48 -0400 Subject: [PATCH 05/37] initialize with default values --- lib/radiohead.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/radiohead.py b/lib/radiohead.py index 754d218..1b75e9d 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -138,10 +138,30 @@ def choose_constants(self): def initialize_radio(self, spi, cs, reset): if self.protocol == "fsk": rfm_device = FSK_RFM9x(spi, cs, reset, rf_config.FREQUENCY) + rfm_device.bitrate = rf_config.BITRATE + rfm_device.frequency_deviation = rf_config.FREQUENCY_DEVIATION + rfm_device.rx_bandwidth = rf_config.RX_BANDWIDTH + elif self.protocol == "lora": rfm_device = LoRa_RFM9x(spi, cs, reset, rf_config.FREQUENCY) + rfm_device.spreading_factor = rf_config.SPREADING_FACTOR + rfm_device.coding_rate = rf_config.CODING_RATE + rfm_device.signal_bandwidth = rf_config.SIGNAL_BANDWIDTH + else: raise RuntimeError(f"unrecognized radio protocol: {self.protocol}") + + rfm_device.dio0 = self.radio_DIO0 + + rfm_device.tx_power = rf_config.TX_POWER + rfm_device.preamble_length = rf_config.PREAMBLE_LENGTH + rfm_device.ack_delay = rf_config.ACK_DELAY + rfm_device.ack_wait = rf_config.ACK_WAIT + rfm_device.ack_retries = rf_config.ACK_RETRIES + rfm_device.receive_timeout = rf_config.RECEIVE_TIMEOUT + rfm_device.node = rf_config.SATELLITE_ID + rfm_device.destination = rf_config.GROUNDSTATION_ID + return rfm_device def listen(self): From c6c66a72fe287bb5c9915dccd68bd13b07d16339 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:04:48 -0400 Subject: [PATCH 06/37] rh config --- gs_setup.py | 24 ++++++++++++++++-------- lib/radiohead.py | 8 -------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gs_setup.py b/gs_setup.py index 4cf1017..6bdf2d3 100644 --- a/gs_setup.py +++ b/gs_setup.py @@ -12,14 +12,22 @@ def initialize_radio(tx_spi, tx_cs, tx_reset, rx_spi=None, rx_cs=None, rx_reset=None, rxtx_switch=None): - return radiohead.Radiohead(rf_config.PROTOCOL, - tx_spi, - tx_cs, - tx_reset, - rx_spi, - rx_cs, - rx_reset, - rxtx_switch) + rh = radiohead.Radiohead(rf_config.PROTOCOL, + tx_spi, + tx_cs, + tx_reset, + rx_spi, + rx_cs, + rx_reset, + rxtx_switch) + + rh.ack_delay = rf_config.ACK_DELAY + rh.ack_wait = rf_config.ACK_WAIT + rh.receive_timeout = rf_config.RECEIVE_TIMEOUT + rh.node = rf_config.GROUNDSTATION_ID + rh.destination = rf_config.SATELLITE_ID + + return rh def satellite_spi_config(): # pocketqube diff --git a/lib/radiohead.py b/lib/radiohead.py index 1b75e9d..ebab55e 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -151,16 +151,8 @@ def initialize_radio(self, spi, cs, reset): else: raise RuntimeError(f"unrecognized radio protocol: {self.protocol}") - rfm_device.dio0 = self.radio_DIO0 - rfm_device.tx_power = rf_config.TX_POWER rfm_device.preamble_length = rf_config.PREAMBLE_LENGTH - rfm_device.ack_delay = rf_config.ACK_DELAY - rfm_device.ack_wait = rf_config.ACK_WAIT - rfm_device.ack_retries = rf_config.ACK_RETRIES - rfm_device.receive_timeout = rf_config.RECEIVE_TIMEOUT - rfm_device.node = rf_config.SATELLITE_ID - rfm_device.destination = rf_config.GROUNDSTATION_ID return rfm_device From 33a452ce25ae031c88fbe216770cf1f57dc4b77d Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:07:09 -0400 Subject: [PATCH 07/37] fsk_send_with_ack -> send_wth_ack --- lib/radiohead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index ebab55e..2d61c24 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -679,7 +679,7 @@ async def send( flags, debug) - async def fsk_send_with_ack(self, data, debug=False): + async def send_with_ack(self, data, debug=False): """Reliable Datagram mode: Send a packet with data and wait for an ACK response. The packet header is automatically generated. From 50b5378598f3839c9649d0ade953876ac1b6e385 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:09:49 -0400 Subject: [PATCH 08/37] update arguments --- lib/radiohead.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 2d61c24..8d2959b 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -727,10 +727,11 @@ async def receive( keep_listening: bool = True, with_header: bool = False, with_ack: bool = False, - timeout: Optional[float] = None + timeout: Optional[float] = None, + debug: bool = False, ) -> Optional[bytearray]: if self.protocol == "fsk": - self.fsk_receive(keep_listening, with_header, with_ack, timeout) + self.fsk_receive(keep_listening, with_header, with_ack, timeout, debug=debug) elif self.protocol == "lora": self.LoRa_receive(keep_listening, with_ack, with_header, timeout) From 2aec7b6ad3828d83b9c0a049de7906674d400463 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:11:49 -0400 Subject: [PATCH 09/37] receive uses keyword args --- lib/radiohead.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 8d2959b..84386ce 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -731,9 +731,16 @@ async def receive( debug: bool = False, ) -> Optional[bytearray]: if self.protocol == "fsk": - self.fsk_receive(keep_listening, with_header, with_ack, timeout, debug=debug) + self.fsk_receive(keep_listening=keep_listening, + with_header=with_header, + with_ack=with_ack, + timeout=timeout, + debug=debug) elif self.protocol == "lora": - self.LoRa_receive(keep_listening, with_ack, with_header, timeout) + self.LoRa_receive(keep_listening=keep_listening, + with_ack=with_ack, + with_header=with_header, + timeout=timeout) def bsd_checksum(bytedata): From 1621267e2aa0d807a2f5e42ca39ce367551ec76d Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:17:45 -0400 Subject: [PATCH 10/37] type annotations and awaits --- lib/radiohead.py | 58 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 84386ce..19e6f5c 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -186,13 +186,13 @@ async def fsk_send( self, data, *, - keep_listening=False, - destination=None, - node=None, - identifier=None, - flags=None, - debug=False - ): + keep_listening: bool = False, + destination: Optional[int] = None, + node: Optional[int] = None, + identifier: Optional[int] = None, + flags: Optional[int] = None, + debug: bool = False + ) -> bool: """Send a string of data using the transmitter. You can only send 57 bytes at a time (limited by chip's FIFO size and appended headers). @@ -664,20 +664,20 @@ async def send( debug: bool = False ) -> bool: if self.protocol == "fsk": - self.fsk_send(data, - keep_listening, - destination, - node, - identifier, - flags, - debug) + return await self.fsk_send(data, + keep_listening, + destination, + node, + identifier, + flags, + debug) elif self.protocol == "LoRa": - self.LoRa_send(data, - keep_listening, - destination, - identifier, - flags, - debug) + return await self.LoRa_send(data, + keep_listening, + destination, + identifier, + flags, + debug) async def send_with_ack(self, data, debug=False): """Reliable Datagram mode: @@ -731,16 +731,16 @@ async def receive( debug: bool = False, ) -> Optional[bytearray]: if self.protocol == "fsk": - self.fsk_receive(keep_listening=keep_listening, - with_header=with_header, - with_ack=with_ack, - timeout=timeout, - debug=debug) + return await self.fsk_receive(keep_listening=keep_listening, + with_header=with_header, + with_ack=with_ack, + timeout=timeout, + debug=debug) elif self.protocol == "lora": - self.LoRa_receive(keep_listening=keep_listening, - with_ack=with_ack, - with_header=with_header, - timeout=timeout) + return await self.LoRa_receive(keep_listening=keep_listening, + with_ack=with_ack, + with_header=with_header, + timeout=timeout) def bsd_checksum(bytedata): From d013cb14fcd53bd6a0f782bc43558e3fca5954ad Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:20:42 -0400 Subject: [PATCH 11/37] using keyword args for send functions --- lib/radiohead.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 19e6f5c..a8a581b 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -665,19 +665,20 @@ async def send( ) -> bool: if self.protocol == "fsk": return await self.fsk_send(data, - keep_listening, - destination, - node, - identifier, - flags, - debug) + keep_listening=keep_listening, + destination=destination, + node=node, + identifier=identifier, + flags=flags, + debug=debug) elif self.protocol == "LoRa": return await self.LoRa_send(data, - keep_listening, - destination, - identifier, - flags, - debug) + keep_listening=keep_listening, + destination=destination, + node=node, + identifier=identifier, + flags=identifier, + debug=debug) async def send_with_ack(self, data, debug=False): """Reliable Datagram mode: From 5bed26d976e79c57a819c3f63821076d5e042aed Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:23:41 -0400 Subject: [PATCH 12/37] switching to LoRa protocol --- lib/configuration/radio_configuration.py | 2 +- lib/radiohead.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/configuration/radio_configuration.py b/lib/configuration/radio_configuration.py index c671b46..8eede42 100644 --- a/lib/configuration/radio_configuration.py +++ b/lib/configuration/radio_configuration.py @@ -2,7 +2,7 @@ Defines the default settings used to configure the RFM9x satellite """ -PROTOCOL = "fsk" +PROTOCOL = "lora" # FSK specific BITRATE = 1200 # bits per second diff --git a/lib/radiohead.py b/lib/radiohead.py index a8a581b..b45e02f 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -671,7 +671,7 @@ async def send( identifier=identifier, flags=flags, debug=debug) - elif self.protocol == "LoRa": + elif self.protocol == "lora": return await self.LoRa_send(data, keep_listening=keep_listening, destination=destination, From 374788cf46ecb1f2e22a5d821fde72f0abbd5854 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:35:17 -0400 Subject: [PATCH 13/37] LoRa config stuff included --- shell_utils.py | 77 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/shell_utils.py b/shell_utils.py index 7d3e6a4..c2c6e96 100644 --- a/shell_utils.py +++ b/shell_utils.py @@ -1,3 +1,5 @@ +from .lib.configuration import radio_configuration as rf_config + """ A collection of functions for getting and validating inputs """ @@ -102,33 +104,68 @@ def manually_configure_radio(radio): def manually_configure_rfm9x(device): - device.frequency_mhz = set_param_from_input_range(device.frequency_mhz, f"Frequency (currently {device.frequency_mhz} MHz)", - [240.0, 960.0], allow_default=True) - device.tx_power = set_param_from_input_discrete(device.tx_power, f"Power (currently {device.tx_power} dB)", - [f"{i}" for i in range(5, 24)], allow_default=True) - device.bitrate = set_param_from_input_range(device.bitrate, f"Bitrate (currently {device.bitrate} bps)", - [500, 300000], allow_default=True) - device.frequency_deviation = set_param_from_input_range(device.frequency_deviation, f"Frequency deviation (currently {device.frequency_deviation})", - [600, 200000], allow_default=True) - device.rx_bandwidth = set_param_from_input_discrete(device.rx_bandwidth, f"Receiver filter bandwidth (single-sided, currently {device.rx_bandwidth})", - [f"{device._bw_bins_kHz[i]}" for i in range(len(device._bw_bins_kHz))], allow_default=True, type=float) - device.lna_gain = set_param_from_input_discrete(device.lna_gain, f"LNA Gain - [max = 1, min = 6] (currently {device.lna_gain})", - [f"{i}" for i in range(1, 7)], allow_default=True) - device.preamble_length = set_param_from_input_range(device.preamble_length, f"Preamble length (currently {device.preamble_length})", - [3, 2**16], allow_default=True) - device.afc_enable = set_param_from_input_discrete(device.afc_enable, f"Enable automatic frequency calibration (AFC) (currently {device.afc_enable})", - ["0", "1"], allow_default=True) + device.frequency_mhz = set_param_from_input_range(device.frequency_mhz, + f"Frequency (currently {device.frequency_mhz} MHz)", + [240.0, 960.0], + allow_default=True) + device.tx_power = set_param_from_input_discrete(device.tx_power, + f"Power (currently {device.tx_power} dB)", + [f"{i}" for i in range(5, 24)], + allow_default=True) + device.lna_gain = set_param_from_input_discrete(device.lna_gain, + f"LNA Gain - [max = 1, min = 6] (currently {device.lna_gain})", + [f"{i}" for i in range(1, 7)], + allow_default=True) + device.preamble_length = set_param_from_input_range(device.preamble_length, + f"Preamble length (currently {device.preamble_length})", + [3, 2**16], + allow_default=True) + if rf_config.PROTOCOL == "fsk": + device.bitrate = set_param_from_input_range(device.bitrate, + f"Bitrate (currently {device.bitrate} bps)", + [500, 300000], + allow_default=True) + device.frequency_deviation = set_param_from_input_range(device.frequency_deviation, + f"Frequency deviation (currently {device.frequency_deviation})", + [600, 200000], + allow_default=True) + device.rx_bandwidth = set_param_from_input_discrete(device.rx_bandwidth, + f"Receiver filter bandwidth (single-sided, currently {device.rx_bandwidth})", + [f"{device._bw_bins_kHz[i]}" for i in range(len(device._bw_bins_kHz))], + allow_default=True, type=float) + device.afc_enable = set_param_from_input_discrete(device.afc_enable, + f"Enable automatic frequency calibration (AFC) (currently {device.afc_enable})", + ["0", "1"], + allow_default=True) + if rf_config.PROTOCOL == "lora": + device.coding_rate = set_param_from_input_discrete(device.coding_rate, + f"coding_rate currently {device.coding_rate}", + [f"{i}" for i in range(5, 9)], + allow_default=True) + device.spreading_factor = set_param_from_input_discrete(device.spreading_factor, + f"current spreading factor {device.spreading_factor}", + [f"{i}" for i in range(6, 13)], + allow_default=True) + device.signal_bandwidth = set_param_from_input_discrete(device.signal_bandwidth, + f"signal bandwidth currently {device.signal_bandwidth}", + ["7800", "10400", "15600", "20800", "31250", "41700", "62500", "125000", "250000"], + allow_default=True) def print_rfm9x_configuration(device): print(f"\tFrequency = {device.frequency_mhz} MHz") print(f"\tPower = {device.tx_power} dBm") - print(f"\tBitrate = {device.bitrate} Hz") - print(f"\tFrequency Deviation = {device.frequency_deviation}") - print(f"\tRX filter bandwidth = {device.rx_bandwidth}") print(f"\tLNA Gain [max = 1, min = 6] = {device.lna_gain}") print(f"\tPreamble Length = {device.preamble_length}") - print(f"\tAFC enabled = {device.afc_enable}") + if rf_config.PROTOCOL == "fsk": + print(f"\tBitrate = {device.bitrate} Hz") + print(f"\tFrequency Deviation = {device.frequency_deviation}") + print(f"\tRX filter bandwidth = {device.rx_bandwidth}") + print(f"\tAFC enabled = {device.afc_enable}") + elif rf_config.PROTOCOL == "lora": + print(f"\Spreading Factor = {device.spreading_factor}") + print(f"\tCoding Rate = {device.coding_rate}") + print(f"\tsignal_bandwidth = {device.signal_bandwidth}") def print_radio_configuration(radio): From 07c18e7846d65906deff93c3e9f34c630aec1081 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:36:11 -0400 Subject: [PATCH 14/37] import properly --- shell_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell_utils.py b/shell_utils.py index c2c6e96..2e65cda 100644 --- a/shell_utils.py +++ b/shell_utils.py @@ -1,4 +1,4 @@ -from .lib.configuration import radio_configuration as rf_config +from lib.configuration import radio_configuration as rf_config """ A collection of functions for getting and validating inputs From 9680707ba3a40c2fa3e0dc3caaad59de1680ffe2 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:37:33 -0400 Subject: [PATCH 15/37] add lna gain to LoRa driver --- lib/pycubed_rfm9x.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pycubed_rfm9x.py b/lib/pycubed_rfm9x.py index 848a32f..4ccaf63 100644 --- a/lib/pycubed_rfm9x.py +++ b/lib/pycubed_rfm9x.py @@ -253,6 +253,8 @@ def __set__(self, obj: "RFM9x", val: int) -> None: lna_boost_hf = _RegisterBits(Constants._RH_RF95_REG_0C_LNA, offset=0, bits=2) + lna_gain = _RegisterBits(Constants._RH_RF95_REG_0C_LNA, offset=5, bits=3) + auto_ifon = _RegisterBits(Constants._RH_RF95_DETECTION_OPTIMIZE, offset=7, bits=1) detection_optimize = _RegisterBits(Constants._RH_RF95_DETECTION_OPTIMIZE, offset=0, bits=3) From 271b77ee292c2fb7578df7772d1cb9100bed4399 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:41:26 -0400 Subject: [PATCH 16/37] update LoRa to use internal rx/tx devices --- lib/radiohead.py | 8 ++++---- shell_utils.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index b45e02f..608a422 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -503,14 +503,14 @@ async def LoRa_send( timed_out = False if HAS_SUPERVISOR: start = supervisor.ticks_ms() - while not timed_out and not self.tx_done(): + while not timed_out and not self.tx_device.tx_done(): if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: timed_out = True else: await tasko.sleep(0) else: start = time.monotonic() - while not timed_out and not self.tx_done(): + while not timed_out and not self.tx_device.tx_done(): if time.monotonic() - start >= self.xmit_timeout: timed_out = True else: @@ -560,14 +560,14 @@ async def LoRa_receive( timed_out = False if HAS_SUPERVISOR: start = supervisor.ticks_ms() - while not timed_out and not self.rx_done(): + while not timed_out and not self.rx_device.rx_done(): if self.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: timed_out = True else: await tasko.sleep(0) else: start = time.monotonic() - while not timed_out and not self.rx_done(): + while not timed_out and not self.rx_device.rx_done(): if time.monotonic() - start >= timeout: timed_out = True else: diff --git a/shell_utils.py b/shell_utils.py index 2e65cda..9704773 100644 --- a/shell_utils.py +++ b/shell_utils.py @@ -163,7 +163,7 @@ def print_rfm9x_configuration(device): print(f"\tRX filter bandwidth = {device.rx_bandwidth}") print(f"\tAFC enabled = {device.afc_enable}") elif rf_config.PROTOCOL == "lora": - print(f"\Spreading Factor = {device.spreading_factor}") + print(f"\tSpreading Factor = {device.spreading_factor}") print(f"\tCoding Rate = {device.coding_rate}") print(f"\tsignal_bandwidth = {device.signal_bandwidth}") From d01647ca21d4c355575d95b36294ee51ac3ace84 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:45:15 -0400 Subject: [PATCH 17/37] updating LoRa radiohead to use internal devices --- lib/radiohead.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 608a422..a7d9ece 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -121,6 +121,13 @@ def __init__(self, operating mode has been changed. """ + self.last_snr = 0.0 + """The SNR of the last received packet. Stored when the packet was received. + The instantaneous SNR value may not be accurate once the + operating mode has been changed. + """ + + def ticks_diff(self, ticks1, ticks2): """Compute the signed difference between two ticks values assuming that they are within 2**28 ticks @@ -575,10 +582,10 @@ async def LoRa_receive( # Payload ready is set, a packet is in the FIFO. packet = None # save last RSSI reading - self.last_rssi = self.rssi + self.last_rssi = self.rx_device.rssi # save the last SNR reading - self.last_snr = self.snr + self.last_snr = self.rx_device.snr # Enter idle mode to stop receiving other packets. self.idle() From 9cf2aea03f65c8d1363bff2a20bac6b8a1be6365 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:49:35 -0400 Subject: [PATCH 18/37] missed a few functions to be made internal --- lib/radiohead.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index a7d9ece..4547c53 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -469,7 +469,7 @@ async def LoRa_send( # pylint: enable=len-as-condition self.idle() # Stop receiving to clear FIFO and keep it clear. # Fill the FIFO with a packet to send. - self._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. + self.tx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. # Combine header and data to form payload payload = bytearray(5) payload[0] = len(payload) + len(data) @@ -500,9 +500,9 @@ async def LoRa_send( print(f"RFM9x: sending {str(payload)}") # Write payload. - self._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) + self.tx_device._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) # Write payload and header length. - self._write_u8(self.constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + self.tx_device._write_u8(self.constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) # Turn on transmit mode to send out the packet. self.transmit() # Wait for tx done interrupt with explicit polling (not ideal but @@ -529,7 +529,7 @@ async def LoRa_send( # Enter idle mode to stop receiving other packets. self.idle() # Clear interrupt. - self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + self.tx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return not timed_out async def LoRa_receive( @@ -590,36 +590,36 @@ async def LoRa_receive( # Enter idle mode to stop receiving other packets. self.idle() if not timed_out: - if self.enable_crc and self.crc_error(): - self.crc_error_count += 1 + if self.rx_device.enable_crc and self.rx_device.crc_error(): + self.rx_device.crc_error_count += 1 else: # Read the data from the FIFO. # Read the length of the FIFO. - fifo_length = self._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) + fifo_length = self.rx_device._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) # Handle if the received packet is too small to include the 4 byte # RadioHead header and at least one byte of data --reject this packet and ignore it. if fifo_length > 0: # read and clear the FIFO if anything in it - current_addr = self._read_u8(self.constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) - self._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) + current_addr = self.rx_device._read_u8(self.constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) + self.rx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) packet = bytearray(fifo_length) # Read the packet. - self._read_into(self.constants._RH_RF95_REG_00_FIFO, packet) + self.rx_device._read_into(self.constants._RH_RF95_REG_00_FIFO, packet) # Clear interrupt. - self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + self.rx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) if fifo_length < 5: packet = None else: if ( - self.node != self.constants._RH_BROADCAST_ADDRESS - and packet[1] != self.constants._RH_BROADCAST_ADDRESS - and packet[1] != self.node + self.node != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.node ): packet = None # send ACK unless this was an ACK or a broadcast elif ( - with_ack - and ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) - and (packet[1] != self.constants._RH_BROADCAST_ADDRESS) + with_ack and + ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) and + (packet[1] != self.constants._RH_BROADCAST_ADDRESS) ): # delay before sending Ack to give receiver a chance to get ready if self.ack_delay is not None: @@ -650,7 +650,7 @@ async def LoRa_receive( # Enter idle mode to stop receiving other packets. self.idle() # Clear interrupt. - self._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + self.rx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return packet """ From 8cd23ba59e84f74597c184b5cdddc6acc0ae7b57 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 14:53:56 -0400 Subject: [PATCH 19/37] adding debugging to LoRa receive --- lib/radiohead.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 4547c53..ea21bd4 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -538,7 +538,8 @@ async def LoRa_receive( keep_listening: bool = True, with_header: bool = False, with_ack: bool = False, - timeout: Optional[float] = None + timeout: Optional[float] = None, + debug: bool = True ) -> Optional[bytearray]: """Wait to receive a packet from the receiver. If a packet is found the payload bytes are returned, otherwise None is returned (which indicates the timeout elapsed with no @@ -577,6 +578,8 @@ async def LoRa_receive( while not timed_out and not self.rx_device.rx_done(): if time.monotonic() - start >= timeout: timed_out = True + if debug: + print("RFM9X: RX timed out") else: await tasko.sleep(0) # Payload ready is set, a packet is in the FIFO. From 033d00fa75998df7f7900640ad705e7666fafa4e Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 15:38:16 -0400 Subject: [PATCH 20/37] more debugging --- lib/radiohead.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index ea21bd4..09b5583 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -322,7 +322,7 @@ async def fsk_receive( # Enter idle mode to stop receiving other packets. self.idle() # read packet - packet = await self._process_packet(with_header=with_header, with_ack=with_ack, debug=debug) + packet = await self.fsk_process_packet(with_header=with_header, with_ack=with_ack, debug=debug) if packet is not None: break # packet valid - return it # packet invalid - continue listening @@ -571,6 +571,8 @@ async def LoRa_receive( while not timed_out and not self.rx_device.rx_done(): if self.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: timed_out = True + if debug: + print("RFM9X: RX timed out") else: await tasko.sleep(0) else: From 01494badfb86e4fe463bbbf79d1266ed6a2f1ed0 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 16:17:08 -0400 Subject: [PATCH 21/37] debugging tools --- lib/radiohead.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 09b5583..897657d 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -127,7 +127,6 @@ def __init__(self, operating mode has been changed. """ - def ticks_diff(self, ticks1, ticks2): """Compute the signed difference between two ticks values assuming that they are within 2**28 ticks @@ -601,6 +600,7 @@ async def LoRa_receive( # Read the data from the FIFO. # Read the length of the FIFO. fifo_length = self.rx_device._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) + print(fifo_length) # Handle if the received packet is too small to include the 4 byte # RadioHead header and at least one byte of data --reject this packet and ignore it. if fifo_length > 0: # read and clear the FIFO if anything in it @@ -619,6 +619,7 @@ async def LoRa_receive( packet[1] != self.constants._RH_BROADCAST_ADDRESS and packet[1] != self.node ): + print(f"node: {self.node} != {self.constants._RH_BROADCAST_ADDRESS} != {packet[1]}") packet = None # send ACK unless this was an ACK or a broadcast elif ( From 203d481025ed369a77087a9789080558bfdda996 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Tue, 27 Jun 2023 17:31:48 -0400 Subject: [PATCH 22/37] LoRa works, small bug --- lib/radiohead.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 897657d..2820ce5 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -600,7 +600,6 @@ async def LoRa_receive( # Read the data from the FIFO. # Read the length of the FIFO. fifo_length = self.rx_device._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) - print(fifo_length) # Handle if the received packet is too small to include the 4 byte # RadioHead header and at least one byte of data --reject this packet and ignore it. if fifo_length > 0: # read and clear the FIFO if anything in it @@ -619,7 +618,6 @@ async def LoRa_receive( packet[1] != self.constants._RH_BROADCAST_ADDRESS and packet[1] != self.node ): - print(f"node: {self.node} != {self.constants._RH_BROADCAST_ADDRESS} != {packet[1]}") packet = None # send ACK unless this was an ACK or a broadcast elif ( @@ -633,13 +631,13 @@ async def LoRa_receive( # send ACK packet to sender (data is b'!') await self.send( b"!", - destination=packet[1], - node=packet[0], - identifier=packet[2], + destination=packet[2], + node=packet[1], + identifier=packet[3], flags=(packet[4] | self.constants._RH_FLAGS_ACK), ) # reject Retries if we have seen this idetifier from this source before - if (self.seen_ids[packet[1]] == packet[2]) and ( + if (self.seen_ids[packet[2]] == packet[3]) and ( packet[4] & self.constants._RH_FLAGS_RETRY ): packet = None @@ -690,7 +688,7 @@ async def send( destination=destination, node=node, identifier=identifier, - flags=identifier, + flags=flags, debug=debug) async def send_with_ack(self, data, debug=False): From 1fc29397cc712b845a9e1f9e7250ff6f12e522fd Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 13:52:10 -0400 Subject: [PATCH 23/37] consolidating the send function --- lib/pycubed_rfm9x.py | 9 +++ lib/pycubed_rfm9x_fsk.py | 6 ++ lib/radiohead.py | 133 ++++++++++++++++++++++++++++++++++----- 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/lib/pycubed_rfm9x.py b/lib/pycubed_rfm9x.py index 4ccaf63..ebf86c7 100644 --- a/lib/pycubed_rfm9x.py +++ b/lib/pycubed_rfm9x.py @@ -687,3 +687,12 @@ def rx_done(self) -> bool: def crc_error(self) -> bool: """crc status""" return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 + + def write_payload(self, payload) -> bool: + # Write payload. + self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) + # Write payload and header length. + self._write_u8(Constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + + def check_data(self, data): + assert 0 < len(data) <= 241 diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 8e75d99..1ce4259 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -663,3 +663,9 @@ def crc_ok(self): def fifo_empty(self): """True when FIFO is empty""" return (self._read_u8(Constants._RH_RF95_REG_3F_IRQ_FLAGS_2) & (0b1 << 6)) >> 6 + + def write_payload(self, payload): + self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) + + def check_data(self, data): + assert 0 < len(data) <= 57 diff --git a/lib/radiohead.py b/lib/radiohead.py index 2820ce5..f932387 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -663,6 +663,34 @@ async def LoRa_receive( =========================================================================== """ + # async def send( + # self, + # data: ReadableBuffer, + # *, + # keep_listening: bool = False, + # destination: Optional[int] = None, + # node: Optional[int] = None, + # identifier: Optional[int] = None, + # flags: Optional[int] = None, + # debug: bool = False + # ) -> bool: + # if self.protocol == "fsk": + # return await self.fsk_send(data, + # keep_listening=keep_listening, + # destination=destination, + # node=node, + # identifier=identifier, + # flags=flags, + # debug=debug) + # elif self.protocol == "lora": + # return await self.LoRa_send(data, + # keep_listening=keep_listening, + # destination=destination, + # node=node, + # identifier=identifier, + # flags=flags, + # debug=debug) + async def send( self, data: ReadableBuffer, @@ -674,22 +702,95 @@ async def send( flags: Optional[int] = None, debug: bool = False ) -> bool: - if self.protocol == "fsk": - return await self.fsk_send(data, - keep_listening=keep_listening, - destination=destination, - node=node, - identifier=identifier, - flags=flags, - debug=debug) - elif self.protocol == "lora": - return await self.LoRa_send(data, - keep_listening=keep_listening, - destination=destination, - node=node, - identifier=identifier, - flags=flags, - debug=debug) + """Send a string of data using the transmitter. + You can only send 57 bytes at a time + (limited by chip's FIFO size and appended headers). + This prepends a 1 byte length to be compatible with the RFM9X fsk packet handler, + and 4 byte header to be compatible with the RadioHead library. + The header defaults to using the initialized attributes: + (destination, node, identifier, flags) + It may be temporarily overidden via the kwargs - destination, node, identifier, flags. + Values passed via kwargs do not alter the attribute settings. + The keep_listening argument should be set to True if you want to start listening + automatically after the packet is sent. The default setting is False. + + Returns: True if success or False if the send timed out. + """ + # Disable pylint warning to not use length as a check for zero. + # This is a puzzling warning as the below code is clearly the most + # efficient and proper way to ensure a precondition that the provided + # buffer be within an expected range of bounds. Disable this check. + # pylint: disable=len-as-condition + self.tx_device.check_data(data) + + # pylint: enable=len-as-condition + self.idle() # Stop receiving to clear FIFO and keep it clear. + if self.protocol == "lora": + # tells device that FIFO should start at 0. + # register is used different in fsk + self.tx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) + + # Combine header and data to form payload + payload = bytearray(5) + payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) + if destination is None: # use attribute + payload[1] = self.destination + else: # use kwarg + payload[1] = destination + if node is None: # use attribute + payload[2] = self.node + else: # use kwarg + payload[2] = node + if identifier is None: # use attribute + payload[3] = self.identifier + else: # use kwarg + payload[3] = identifier + if flags is None: # use attribute + payload[4] = self.flags + else: # use kwarg + payload[4] = flags + + payload = payload + data + + if self.checksum: + payload[0] += 2 + checksum = bsd_checksum(payload) + payload = payload + checksum + + if debug: + print(f"RFM9X: Sending {str(payload)}") + + # Write payload. + self.tx_device.write_payload(payload) + + # Turn on transmit mode to send out the packet. + self.transmit() + # Wait for tx done interrupt with explicit polling (not ideal but + # best that can be done right now without interrupts). + timed_out = False + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + while not timed_out and not self.tx_device.tx_done(): + if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: + timed_out = True + else: + await tasko.sleep(0) + else: + start = time.monotonic() + while not timed_out and not self.tx_device.tx_done(): + if time.monotonic() - start >= self.xmit_timeout: + timed_out = True + else: + await tasko.sleep(0) + # Done transmitting - change modes (interrupt automatically cleared on mode change) + if keep_listening: + self.listen() + else: + # Enter idle mode to stop receiving other packets. + self.idle() + if self.protocol == "lora": + self.tx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return not timed_out async def send_with_ack(self, data, debug=False): """Reliable Datagram mode: From e6f1d4e527de7005ba3991a259631755911d2441 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 14:57:11 -0400 Subject: [PATCH 24/37] removes unused send functions --- lib/radiohead.py | 216 ----------------------------------------------- 1 file changed, 216 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index f932387..c20c231 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -188,99 +188,6 @@ def transmit(self): FSK Specific Functions =========================================================================== """ - async def fsk_send( - self, - data, - *, - keep_listening: bool = False, - destination: Optional[int] = None, - node: Optional[int] = None, - identifier: Optional[int] = None, - flags: Optional[int] = None, - debug: bool = False - ) -> bool: - """Send a string of data using the transmitter. - You can only send 57 bytes at a time - (limited by chip's FIFO size and appended headers). - This prepends a 1 byte length to be compatible with the RFM9X fsk packet handler, - and 4 byte header to be compatible with the RadioHead library. - The header defaults to using the initialized attributes: - (destination, node, identifier, flags) - It may be temporarily overidden via the kwargs - destination, node, identifier, flags. - Values passed via kwargs do not alter the attribute settings. - The keep_listening argument should be set to True if you want to start listening - automatically after the packet is sent. The default setting is False. - - Returns: True if success or False if the send timed out. - """ - # Disable pylint warning to not use length as a check for zero. - # This is a puzzling warning as the below code is clearly the most - # efficient and proper way to ensure a precondition that the provided - # buffer be within an expected range of bounds. Disable this check. - # pylint: disable=len-as-condition - assert 0 < len(data) <= 57 # TODO: Allow longer packets, see pg 76 - # pylint: enable=len-as-condition - self.idle() # Stop receiving to clear FIFO and keep it clear. - - # Combine header and data to form payload - payload = bytearray(5) - payload[0] = len(payload) + len(data) - 1 # first byte is length to meet semtech FSK requirements (pg 74) - if destination is None: # use attribute - payload[1] = self.destination - else: # use kwarg - payload[1] = destination - if node is None: # use attribute - payload[2] = self.node - else: # use kwarg - payload[2] = node - if identifier is None: # use attribute - payload[3] = self.identifier - else: # use kwarg - payload[3] = identifier - if flags is None: # use attribute - payload[4] = self.flags - else: # use kwarg - payload[4] = flags - - payload = payload + data - - if self.checksum: - payload[0] += 2 - checksum = bsd_checksum(payload) - payload = payload + checksum - - # Write payload. - if debug: - print(f"RFM9X: Sending {str(payload)}") - self.tx_device._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) - - # Turn on transmit mode to send out the packet. - self.transmit() - # Wait for tx done interrupt with explicit polling (not ideal but - # best that can be done right now without interrupts). - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.tx_device.tx_done(): - if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: - timed_out = True - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.tx_device.tx_done(): - if time.monotonic() - start >= self.xmit_timeout: - timed_out = True - else: - await tasko.sleep(0) - - # Done transmitting - change modes (interrupt automatically cleared on mode change) - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - return not timed_out async def fsk_receive( self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False @@ -435,101 +342,6 @@ async def fsk_process_packet(self, with_header=False, with_ack=False, debug=Fals LoRa Specific Functions =========================================================================== """ - async def LoRa_send( - self, - data: ReadableBuffer, - *, - keep_listening: bool = False, - destination: Optional[int] = None, - node: Optional[int] = None, - identifier: Optional[int] = None, - flags: Optional[int] = None, - debug: bool = False - ) -> bool: - """Send a string of data using the transmitter. - You can only send 252 bytes at a time - (limited by chip's FIFO size and appended headers). - This appends a 4 byte header to be compatible with the RadioHead library. - The header defaults to using the initialized attributes: - (destination,node,identifier,flags) - It may be temporarily overidden via the kwargs - destination,node,identifier,flags. - Values passed via kwargs do not alter the attribute settings. - The keep_listening argument should be set to True if you want to start listening - automatically after the packet is sent. The default setting is False. - - Returns: True if success or False if the send timed out. - """ - # Disable pylint warning to not use length as a check for zero. - # This is a puzzling warning as the below code is clearly the most - # efficient and proper way to ensure a precondition that the provided - # buffer be within an expected range of bounds. Disable this check. - # pylint: disable=len-as-condition - assert 0 < len(data) <= 252 - # pylint: enable=len-as-condition - self.idle() # Stop receiving to clear FIFO and keep it clear. - # Fill the FIFO with a packet to send. - self.tx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. - # Combine header and data to form payload - payload = bytearray(5) - payload[0] = len(payload) + len(data) - if destination is None: # use attribute - payload[1] = self.destination - else: # use kwarg - payload[1] = destination - if node is None: # use attribute - payload[2] = self.node - else: # use kwarg - payload[2] = node - if identifier is None: # use attribute - payload[3] = self.identifier - else: # use kwarg - payload[3] = identifier - if flags is None: # use attribute - payload[4] = self.flags - else: # use kwarg - payload[4] = flags - payload = payload + data - - if self.checksum: - payload[0] += 2 - checksum = bsd_checksum(payload) - payload = payload + checksum - - if debug: - print(f"RFM9x: sending {str(payload)}") - - # Write payload. - self.tx_device._write_from(self.constants._RH_RF95_REG_00_FIFO, payload) - # Write payload and header length. - self.tx_device._write_u8(self.constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) - # Turn on transmit mode to send out the packet. - self.transmit() - # Wait for tx done interrupt with explicit polling (not ideal but - # best that can be done right now without interrupts). - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.tx_device.tx_done(): - if self.ticks_diff(supervisor.ticks_ms(), start) >= self.xmit_timeout * 1000: - timed_out = True - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.tx_device.tx_done(): - if time.monotonic() - start >= self.xmit_timeout: - timed_out = True - else: - await tasko.sleep(0) - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - # Clear interrupt. - self.tx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - return not timed_out async def LoRa_receive( self, @@ -663,34 +475,6 @@ async def LoRa_receive( =========================================================================== """ - # async def send( - # self, - # data: ReadableBuffer, - # *, - # keep_listening: bool = False, - # destination: Optional[int] = None, - # node: Optional[int] = None, - # identifier: Optional[int] = None, - # flags: Optional[int] = None, - # debug: bool = False - # ) -> bool: - # if self.protocol == "fsk": - # return await self.fsk_send(data, - # keep_listening=keep_listening, - # destination=destination, - # node=node, - # identifier=identifier, - # flags=flags, - # debug=debug) - # elif self.protocol == "lora": - # return await self.LoRa_send(data, - # keep_listening=keep_listening, - # destination=destination, - # node=node, - # identifier=identifier, - # flags=flags, - # debug=debug) - async def send( self, data: ReadableBuffer, From a55dee71ce458bfcc38b1d89e71a672facfa4159 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 15:00:43 -0400 Subject: [PATCH 25/37] pycubed_rfm0x -> pycubedrfm9x_lora --- gs_setup.py | 1 - lib/{pycubed_rfm9x.py => pycubed_rfm9x_lora.py} | 0 lib/radiohead.py | 2 +- 3 files changed, 1 insertion(+), 2 deletions(-) rename lib/{pycubed_rfm9x.py => pycubed_rfm9x_lora.py} (100%) diff --git a/gs_setup.py b/gs_setup.py index 6bdf2d3..bd4b9d7 100644 --- a/gs_setup.py +++ b/gs_setup.py @@ -5,7 +5,6 @@ import board import busio import digitalio -from lib import pycubed_rfm9x_fsk from lib import radiohead from lib.configuration import radio_configuration as rf_config from shell_utils import bold, normal diff --git a/lib/pycubed_rfm9x.py b/lib/pycubed_rfm9x_lora.py similarity index 100% rename from lib/pycubed_rfm9x.py rename to lib/pycubed_rfm9x_lora.py diff --git a/lib/radiohead.py b/lib/radiohead.py index c20c231..2e20cb3 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -1,7 +1,7 @@ import random import time import tasko -from lib.pycubed_rfm9x import (Constants as LoRa_Constants, +from lib.pycubed_rfm9x_lora import (Constants as LoRa_Constants, RFM9x as LoRa_RFM9x) from lib.pycubed_rfm9x_fsk import (Constants as FSK_Constants, RFM9x as FSK_RFM9x) From 347a7ea46ef43c4b121b8e79de605d8823782ab1 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 15:12:02 -0400 Subject: [PATCH 26/37] some repo cleanup --- gs_setup.py | 1 - lib/pycubed_rfm9x_fsk.py | 2 -- lib/pycubed_rfm9x_lora.py | 2 -- lib/radiohead.py | 2 +- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/gs_setup.py b/gs_setup.py index bd4b9d7..223f6fd 100644 --- a/gs_setup.py +++ b/gs_setup.py @@ -7,7 +7,6 @@ import digitalio from lib import radiohead from lib.configuration import radio_configuration as rf_config -from shell_utils import bold, normal def initialize_radio(tx_spi, tx_cs, tx_reset, rx_spi=None, rx_cs=None, rx_reset=None, rxtx_switch=None): diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 1ce4259..8a0c474 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -15,11 +15,9 @@ ==================================================== Modified to use FSK in 2022 by Jacob Willis """ -import random import time import adafruit_bus_device.spi_device as spidev from micropython import const -import tasko HAS_SUPERVISOR = False diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index ebf86c7..1a29ff5 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -12,11 +12,9 @@ * Author(s): Tony DiCola, Jerry Needell """ -import random import time import adafruit_bus_device.spi_device as spidev from micropython import const -import tasko HAS_SUPERVISOR = False diff --git a/lib/radiohead.py b/lib/radiohead.py index 2e20cb3..e260883 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -2,7 +2,7 @@ import time import tasko from lib.pycubed_rfm9x_lora import (Constants as LoRa_Constants, - RFM9x as LoRa_RFM9x) + RFM9x as LoRa_RFM9x) from lib.pycubed_rfm9x_fsk import (Constants as FSK_Constants, RFM9x as FSK_RFM9x) from lib.configuration import radio_configuration as rf_config From bd82ac5394209c64a903780b0b3bc2788442730b Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 15:30:54 -0400 Subject: [PATCH 27/37] consolidating receive function --- lib/configuration/radio_configuration.py | 2 + lib/radiohead.py | 180 +++++++++++++++++++++-- 2 files changed, 171 insertions(+), 11 deletions(-) diff --git a/lib/configuration/radio_configuration.py b/lib/configuration/radio_configuration.py index 8eede42..ce074a5 100644 --- a/lib/configuration/radio_configuration.py +++ b/lib/configuration/radio_configuration.py @@ -2,6 +2,7 @@ Defines the default settings used to configure the RFM9x satellite """ +# either "lora" or "fsk" PROTOCOL = "lora" # FSK specific @@ -14,6 +15,7 @@ SIGNAL_BANDWIDTH = 125000 CODING_RATE = 5 +# shared between lora/fsk CHECKSUM = True TX_POWER = 23 # dB FREQUENCY = 433.0 # MHz diff --git a/lib/radiohead.py b/lib/radiohead.py index e260883..3b24011 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -618,6 +618,27 @@ async def send_with_ack(self, data, debug=False): self.flags = 0 # clear flags return got_ack + # async def receive( + # self, + # *, + # keep_listening: bool = True, + # with_header: bool = False, + # with_ack: bool = False, + # timeout: Optional[float] = None, + # debug: bool = False, + # ) -> Optional[bytearray]: + # if self.protocol == "fsk": + # return await self.fsk_receive(keep_listening=keep_listening, + # with_header=with_header, + # with_ack=with_ack, + # timeout=timeout, + # debug=debug) + # elif self.protocol == "lora": + # return await self.LoRa_receive(keep_listening=keep_listening, + # with_ack=with_ack, + # with_header=with_header, + # timeout=timeout) + async def receive( self, *, @@ -627,17 +648,154 @@ async def receive( timeout: Optional[float] = None, debug: bool = False, ) -> Optional[bytearray]: - if self.protocol == "fsk": - return await self.fsk_receive(keep_listening=keep_listening, - with_header=with_header, - with_ack=with_ack, - timeout=timeout, - debug=debug) - elif self.protocol == "lora": - return await self.LoRa_receive(keep_listening=keep_listening, - with_ack=with_ack, - with_header=with_header, - timeout=timeout) + """Wait to receive a packet from the receiver. If a packet is found the payload bytes + are returned, otherwise None is returned(which indicates the timeout elapsed with no + reception). + If keep_listening is True (the default) the chip will immediately enter listening mode + after reception of a packet, otherwise it will fall back to idle mode and ignore any + future reception. + All packets must have a 4-byte header for compatibilty with the + RadioHead library. + The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip + the header before returning the packet to the caller. + If with_header is True then the 4 byte header will be returned with the packet. + The payload then begins at packet[4]. + If with_ack is True, send an ACK after receipt(Reliable Datagram mode) + """ + + if timeout is None: + timeout = self.receive_timeout + + # get starting time + if HAS_SUPERVISOR: + start = supervisor.ticks_ms() + else: + start = time.monotonic() + + packet = None + # Make sure we are listening for packets (and not transmitting). + self.listen() + + while True: + # check for valid packets + if self.rx_device.rx_done(): + # save last RSSI reading + self.last_rssi = self.rx_device.rssi + + if self.protocol == "lora": + self.last_snr = self.rx_device.snr + + # Enter idle mode to stop receiving other packets. + self.idle() + # read packet + packet = await self.process_packet(with_header=with_header, with_ack=with_ack, debug=debug) + if packet is not None: + break # packet valid - return it + # packet invalid - continue listening + self.listen() + + # check if we have timed out + if ((HAS_SUPERVISOR and (self.constants.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000)) or + (not HAS_SUPERVISOR and (time.monotonic() - start >= timeout))): + # timed out + if debug: + print("RFM9X: RX timed out") + break + + await tasko.sleep(0) + + # Exit + if keep_listening: + self.listen() + else: + self.idle() + + return packet + + async def process_packet(self, with_header=False, with_ack=False, debug=False): + + # Read the data from the radio FIFO + packet = bytearray(self.constants._MAX_FIFO_LENGTH) + packet_length = self.rx_device._read_until_flag(self.constants._RH_RF95_REG_00_FIFO, + packet, + self.rx_device.fifo_empty) + + # Reject if the received packet is too small to include the 1 byte length, the + # 4 byte RadioHead header and at least one byte of data + if packet_length < 6: + if debug: + print(f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {str(packet)})") + return None + + # Reject if the length recorded in the packet doesn't match the amount of data we got + internal_packet_length = packet[0] + if internal_packet_length != packet_length - 1: + if debug: + print( + f"RFM9X: Received packet length ({packet_length}) " + + f"does not match transmitted packet length ({internal_packet_length}), " + + f"packet = {str(packet)}") + return None + + packet = packet[:packet_length] + # Reject if the packet does not pass the checksum + if self.checksum: + if not bsd_checksum(packet[:-2]) == packet[-2:]: + if debug: + print( + f"RFM9X: Checksum failed, packet = {str(packet)}, bsd_checksum(packet[:-2])" + + f" = {bsd_checksum(packet[:-2])}, packet[-2:] = {packet[-2:]}") + self.checksum_error_count += 1 + return None + else: + # passed the checksum - remove it before continuing + packet = packet[:-2] + + # Reject if the packet wasn't sent to my address + if (self.node != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.constants._RH_BROADCAST_ADDRESS and + packet[1] != self.node): + if debug: + print( + "RFM9X: Incorrect Address " + + f"(packet address = {packet[1]} != my address = {self.node}), " + + f"packet = {str(packet)}") + return None + + # send ACK unless this was an ACK or a broadcast + if (with_ack and + ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) and + (packet[1] != self.constants._RH_BROADCAST_ADDRESS)): + # delay before sending Ack to give receiver a chance to get ready + if self.ack_delay is not None: + await tasko.sleep(self.ack_delay) + # send ACK packet to sender (data is b'!') + if debug: + print("RFM9X: Sending ACK") + await self.send( + b"!", + destination=packet[2], + node=packet[1], + identifier=packet[3], + flags=(packet[4] | self.constants._RH_FLAGS_ACK), + ) + # reject this packet if its identifier was the most recent one from its source + # TODO: Make sure identifiers are being changed for each packet + if (self.seen_ids[packet[2]] == packet[3]) and ( + packet[4] & self.constants._RH_FLAGS_RETRY): + if debug: + print(f"RFM9X: dropping retried packet, packet = {str(packet)}") + return None + else: # save the packet identifier for this source + self.seen_ids[packet[2]] = packet[3] + + if (not with_header): # skip the header if not wanted + packet = packet[5:] + + if debug: + print(f"RFM9X: Received {str(packet)}") + + return packet def bsd_checksum(bytedata): From 2224e67181df19ecc1853c4f296dbc1bf628e5cf Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 15:59:19 -0400 Subject: [PATCH 28/37] update length with driver function --- lib/pycubed_rfm9x_fsk.py | 5 +++++ lib/pycubed_rfm9x_lora.py | 5 +++++ lib/radiohead.py | 4 +--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 8a0c474..3ab456a 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -667,3 +667,8 @@ def write_payload(self, payload): def check_data(self, data): assert 0 < len(data) <= 57 + + def get_packet_length(self, packet): + return self._read_until_flag(Constants._RH_RF95_REG_00_FIFO, + packet, + self.fifo_empty) diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index 1a29ff5..29b7972 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -128,6 +128,8 @@ class Constants: _TICKS_MAX = const(_TICKS_PERIOD - 1) _TICKS_HALFPERIOD = const(_TICKS_PERIOD // 2) + _MAX_FIFO_LENGTH = 258 + # Disable the too many instance members warning. Pylint has no knowledge # of the context and is merely guessing at the proper amount of members. This # is a complex chip which requires exposing many attributes and state. Disable @@ -694,3 +696,6 @@ def write_payload(self, payload) -> bool: def check_data(self, data): assert 0 < len(data) <= 241 + + def get_packet_length(self, packet): + return self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) diff --git a/lib/radiohead.py b/lib/radiohead.py index 3b24011..86c4a53 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -716,9 +716,7 @@ async def process_packet(self, with_header=False, with_ack=False, debug=False): # Read the data from the radio FIFO packet = bytearray(self.constants._MAX_FIFO_LENGTH) - packet_length = self.rx_device._read_until_flag(self.constants._RH_RF95_REG_00_FIFO, - packet, - self.rx_device.fifo_empty) + packet_length = self.rx_device.get_packet_length(packet) # Reject if the received packet is too small to include the 1 byte length, the # 4 byte RadioHead header and at least one byte of data From e1d19860a05114e19743fcf4c00169b1f061a9d5 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 16:17:42 -0400 Subject: [PATCH 29/37] updating packet getting sequence --- lib/pycubed_rfm9x_fsk.py | 10 ++++++---- lib/pycubed_rfm9x_lora.py | 15 +++++++++++++-- lib/radiohead.py | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index 3ab456a..efb39cf 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -668,7 +668,9 @@ def write_payload(self, payload): def check_data(self, data): assert 0 < len(data) <= 57 - def get_packet_length(self, packet): - return self._read_until_flag(Constants._RH_RF95_REG_00_FIFO, - packet, - self.fifo_empty) + def get_packet_length(self): + packet = bytearray(Constants._MAX_FIFO_LENGTH) + packet_length = self._read_until_flag(Constants._RH_RF95_REG_00_FIFO, + packet, + self.fifo_empty) + return packet[:packet_length] diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index 29b7972..f29afce 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -697,5 +697,16 @@ def write_payload(self, payload) -> bool: def check_data(self, data): assert 0 < len(data) <= 241 - def get_packet_length(self, packet): - return self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) + def get_packet(self): + # get length of data in FIFO + fifo_length = self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) + # get start address of last packet in FIFO + current_addr = self._read_u8(Constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) + # write to the pointer where we need to get data + self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) + # create a packet of fifo_length size + packet = bytearray(fifo_length) + # Read the packet. + self._read_into(Constants._RH_RF95_REG_00_FIFO, packet) + + return packet diff --git a/lib/radiohead.py b/lib/radiohead.py index 86c4a53..f8bafea 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -715,8 +715,8 @@ async def receive( async def process_packet(self, with_header=False, with_ack=False, debug=False): # Read the data from the radio FIFO - packet = bytearray(self.constants._MAX_FIFO_LENGTH) - packet_length = self.rx_device.get_packet_length(packet) + packet = self.rx_device.get_packet() + packet_length = len(packet) # Reject if the received packet is too small to include the 1 byte length, the # 4 byte RadioHead header and at least one byte of data From 4e4799cf6c34e00cbd10f0234c874321b8c3ca0f Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 16:30:46 -0400 Subject: [PATCH 30/37] updates fsk with correct function name --- lib/pycubed_rfm9x_fsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pycubed_rfm9x_fsk.py b/lib/pycubed_rfm9x_fsk.py index efb39cf..d62a08d 100644 --- a/lib/pycubed_rfm9x_fsk.py +++ b/lib/pycubed_rfm9x_fsk.py @@ -668,7 +668,7 @@ def write_payload(self, payload): def check_data(self, data): assert 0 < len(data) <= 57 - def get_packet_length(self): + def get_packet(self): packet = bytearray(Constants._MAX_FIFO_LENGTH) packet_length = self._read_until_flag(Constants._RH_RF95_REG_00_FIFO, packet, From 255ad1c5ede8ec5b71581682a353b42365ff7c27 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 28 Jun 2023 17:04:51 -0400 Subject: [PATCH 31/37] packet already proper length --- lib/radiohead.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index f8bafea..75b6208 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -735,7 +735,6 @@ async def process_packet(self, with_header=False, with_ack=False, debug=False): f"packet = {str(packet)}") return None - packet = packet[:packet_length] # Reject if the packet does not pass the checksum if self.checksum: if not bsd_checksum(packet[:-2]) == packet[-2:]: From 12ae3c8b1dbfc0c997e05919eac531b544f9eb86 Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Thu, 29 Jun 2023 09:35:21 -0400 Subject: [PATCH 32/37] remove unused functions --- lib/radiohead.py | 309 +---------------------------------------------- 1 file changed, 1 insertion(+), 308 deletions(-) diff --git a/lib/radiohead.py b/lib/radiohead.py index 75b6208..2fda44d 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -185,293 +185,7 @@ def transmit(self): """ =========================================================================== - FSK Specific Functions - =========================================================================== - """ - - async def fsk_receive( - self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None, debug=False - ): - """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned(which indicates the timeout elapsed with no - reception). - If keep_listening is True (the default) the chip will immediately enter listening mode - after reception of a packet, otherwise it will fall back to idle mode and ignore any - future reception. - All packets must have a 4-byte header for compatibilty with the - RadioHead library. - The header consists of 4 bytes(To, From, ID, Flags). The default setting will strip - the header before returning the packet to the caller. - If with_header is True then the 4 byte header will be returned with the packet. - The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt(Reliable Datagram mode) - """ - - if timeout is None: - timeout = self.receive_timeout - - # get starting time - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - else: - start = time.monotonic() - - packet = None - # Make sure we are listening for packets (and not transmitting). - self.listen() - - while True: - # check for valid packets - if self.rx_device.rx_done(): - # save last RSSI reading - self.last_rssi = self.rx_device.rssi - # Enter idle mode to stop receiving other packets. - self.idle() - # read packet - packet = await self.fsk_process_packet(with_header=with_header, with_ack=with_ack, debug=debug) - if packet is not None: - break # packet valid - return it - # packet invalid - continue listening - self.listen() - - # check if we have timed out - if ((HAS_SUPERVISOR and (self.constants.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000)) or - (not HAS_SUPERVISOR and (time.monotonic() - start >= timeout))): - # timed out - if debug: - print("RFM9X: RX timed out") - break - - await tasko.sleep(0) - - # Exit - if keep_listening: - self.listen() - else: - self.idle() - - return packet - - async def fsk_process_packet(self, with_header=False, with_ack=False, debug=False): - - # Read the data from the radio FIFO - packet = bytearray(self.constants._MAX_FIFO_LENGTH) - packet_length = self.rx_device._read_until_flag(self.constants._RH_RF95_REG_00_FIFO, - packet, - self.rx_device.fifo_empty) - - # Reject if the received packet is too small to include the 1 byte length, the - # 4 byte RadioHead header and at least one byte of data - if packet_length < 6: - if debug: - print(f"RFM9X: Incomplete message (packet_length = {packet_length} < 6, packet = {str(packet)})") - return None - - # Reject if the length recorded in the packet doesn't match the amount of data we got - internal_packet_length = packet[0] - if internal_packet_length != packet_length - 1: - if debug: - print( - f"RFM9X: Received packet length ({packet_length}) " + - f"does not match transmitted packet length ({internal_packet_length}), " + - f"packet = {str(packet)}") - return None - - packet = packet[:packet_length] - # Reject if the packet does not pass the checksum - if self.checksum: - if not bsd_checksum(packet[:-2]) == packet[-2:]: - if debug: - print( - f"RFM9X: Checksum failed, packet = {str(packet)}, bsd_checksum(packet[:-2])" + - f" = {bsd_checksum(packet[:-2])}, packet[-2:] = {packet[-2:]}") - self.checksum_error_count += 1 - return None - else: - # passed the checksum - remove it before continuing - packet = packet[:-2] - - # Reject if the packet wasn't sent to my address - if (self.node != self.constants._RH_BROADCAST_ADDRESS and - packet[1] != self.constants._RH_BROADCAST_ADDRESS and - packet[1] != self.node): - if debug: - print( - "RFM9X: Incorrect Address " + - f"(packet address = {packet[1]} != my address = {self.node}), " + - f"packet = {str(packet)}") - return None - - # send ACK unless this was an ACK or a broadcast - if (with_ack and - ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) and - (packet[1] != self.constants._RH_BROADCAST_ADDRESS)): - # delay before sending Ack to give receiver a chance to get ready - if self.ack_delay is not None: - await tasko.sleep(self.ack_delay) - # send ACK packet to sender (data is b'!') - if debug: - print("RFM9X: Sending ACK") - await self.send( - b"!", - destination=packet[2], - node=packet[1], - identifier=packet[3], - flags=(packet[4] | self.constants._RH_FLAGS_ACK), - ) - # reject this packet if its identifier was the most recent one from its source - # TODO: Make sure identifiers are being changed for each packet - if (self.seen_ids[packet[2]] == packet[3]) and ( - packet[4] & self.constants._RH_FLAGS_RETRY): - if debug: - print(f"RFM9X: dropping retried packet, packet = {str(packet)}") - return None - else: # save the packet identifier for this source - self.seen_ids[packet[2]] = packet[3] - - if (not with_header): # skip the header if not wanted - packet = packet[5:] - - if debug: - print(f"RFM9X: Received {str(packet)}") - - return packet - - """ - =========================================================================== - LoRa Specific Functions - =========================================================================== - """ - - async def LoRa_receive( - self, - *, - keep_listening: bool = True, - with_header: bool = False, - with_ack: bool = False, - timeout: Optional[float] = None, - debug: bool = True - ) -> Optional[bytearray]: - """Wait to receive a packet from the receiver. If a packet is found the payload bytes - are returned, otherwise None is returned (which indicates the timeout elapsed with no - reception). - If keep_listening is True (the default) the chip will immediately enter listening mode - after reception of a packet, otherwise it will fall back to idle mode and ignore any - future reception. - All packets must have a 4-byte header for compatibility with the - RadioHead library. - The header consists of 4 bytes (To,From,ID,Flags). The default setting will strip - the header before returning the packet to the caller. - If with_header is True then the 4 byte header will be returned with the packet. - The payload then begins at packet[4]. - If with_ack is True, send an ACK after receipt (Reliable Datagram mode) - """ - timed_out = False - if timeout is None: - timeout = self.receive_timeout - if timeout is not None: - # Wait for the payload_ready signal. This is not ideal and will - # surely miss or overflow the FIFO when packets aren't read fast - # enough, however it's the best that can be done from Python without - # interrupt supports. - # Make sure we are listening for packets. - self.listen() - timed_out = False - if HAS_SUPERVISOR: - start = supervisor.ticks_ms() - while not timed_out and not self.rx_device.rx_done(): - if self.ticks_diff(supervisor.ticks_ms(), start) >= timeout * 1000: - timed_out = True - if debug: - print("RFM9X: RX timed out") - else: - await tasko.sleep(0) - else: - start = time.monotonic() - while not timed_out and not self.rx_device.rx_done(): - if time.monotonic() - start >= timeout: - timed_out = True - if debug: - print("RFM9X: RX timed out") - else: - await tasko.sleep(0) - # Payload ready is set, a packet is in the FIFO. - packet = None - # save last RSSI reading - self.last_rssi = self.rx_device.rssi - - # save the last SNR reading - self.last_snr = self.rx_device.snr - - # Enter idle mode to stop receiving other packets. - self.idle() - if not timed_out: - if self.rx_device.enable_crc and self.rx_device.crc_error(): - self.rx_device.crc_error_count += 1 - else: - # Read the data from the FIFO. - # Read the length of the FIFO. - fifo_length = self.rx_device._read_u8(self.constants._RH_RF95_REG_13_RX_NB_BYTES) - # Handle if the received packet is too small to include the 4 byte - # RadioHead header and at least one byte of data --reject this packet and ignore it. - if fifo_length > 0: # read and clear the FIFO if anything in it - current_addr = self.rx_device._read_u8(self.constants._RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) - self.rx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) - packet = bytearray(fifo_length) - # Read the packet. - self.rx_device._read_into(self.constants._RH_RF95_REG_00_FIFO, packet) - # Clear interrupt. - self.rx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - if fifo_length < 5: - packet = None - else: - if ( - self.node != self.constants._RH_BROADCAST_ADDRESS and - packet[1] != self.constants._RH_BROADCAST_ADDRESS and - packet[1] != self.node - ): - packet = None - # send ACK unless this was an ACK or a broadcast - elif ( - with_ack and - ((packet[4] & self.constants._RH_FLAGS_ACK) == 0) and - (packet[1] != self.constants._RH_BROADCAST_ADDRESS) - ): - # delay before sending Ack to give receiver a chance to get ready - if self.ack_delay is not None: - time.sleep(self.ack_delay) - # send ACK packet to sender (data is b'!') - await self.send( - b"!", - destination=packet[2], - node=packet[1], - identifier=packet[3], - flags=(packet[4] | self.constants._RH_FLAGS_ACK), - ) - # reject Retries if we have seen this idetifier from this source before - if (self.seen_ids[packet[2]] == packet[3]) and ( - packet[4] & self.constants._RH_FLAGS_RETRY - ): - packet = None - else: # save the packet identifier for this source - self.seen_ids[packet[2]] = packet[3] - if ( - not with_header and packet is not None - ): # skip the header if not wanted - packet = packet[5:] - # Listen again if necessary and return the result packet. - if keep_listening: - self.listen() - else: - # Enter idle mode to stop receiving other packets. - self.idle() - # Clear interrupt. - self.rx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - return packet - - """ - =========================================================================== - Wrapper Functions + Main Functions =========================================================================== """ @@ -618,27 +332,6 @@ async def send_with_ack(self, data, debug=False): self.flags = 0 # clear flags return got_ack - # async def receive( - # self, - # *, - # keep_listening: bool = True, - # with_header: bool = False, - # with_ack: bool = False, - # timeout: Optional[float] = None, - # debug: bool = False, - # ) -> Optional[bytearray]: - # if self.protocol == "fsk": - # return await self.fsk_receive(keep_listening=keep_listening, - # with_header=with_header, - # with_ack=with_ack, - # timeout=timeout, - # debug=debug) - # elif self.protocol == "lora": - # return await self.LoRa_receive(keep_listening=keep_listening, - # with_ack=with_ack, - # with_header=with_header, - # timeout=timeout) - async def receive( self, *, From 0c79c06c67369de8b64eab7a3c977592314c41de Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Thu, 29 Jun 2023 12:22:41 -0400 Subject: [PATCH 33/37] clear interrupt after LoRa read --- lib/pycubed_rfm9x_lora.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index f29afce..a35aa09 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -708,5 +708,7 @@ def get_packet(self): packet = bytearray(fifo_length) # Read the packet. self._read_into(Constants._RH_RF95_REG_00_FIFO, packet) + # Clear interrupt. + self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return packet From 8fdf62ec0742874bf9e7ac497059c59890241f2a Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Wed, 5 Jul 2023 11:20:29 -0400 Subject: [PATCH 34/37] A few cleanup changes --- lib/pycubed_rfm9x_lora.py | 2 +- lib/radiohead.py | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index a35aa09..655e290 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -688,7 +688,7 @@ def crc_error(self) -> bool: """crc status""" return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 - def write_payload(self, payload) -> bool: + def write_payload(self, payload) -> None: # Write payload. self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) # Write payload and header length. diff --git a/lib/radiohead.py b/lib/radiohead.py index 2fda44d..9330658 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -201,7 +201,6 @@ async def send( debug: bool = False ) -> bool: """Send a string of data using the transmitter. - You can only send 57 bytes at a time (limited by chip's FIFO size and appended headers). This prepends a 1 byte length to be compatible with the RFM9X fsk packet handler, and 4 byte header to be compatible with the RadioHead library. @@ -214,14 +213,8 @@ async def send( Returns: True if success or False if the send timed out. """ - # Disable pylint warning to not use length as a check for zero. - # This is a puzzling warning as the below code is clearly the most - # efficient and proper way to ensure a precondition that the provided - # buffer be within an expected range of bounds. Disable this check. - # pylint: disable=len-as-condition self.tx_device.check_data(data) - # pylint: enable=len-as-condition self.idle() # Stop receiving to clear FIFO and keep it clear. if self.protocol == "lora": # tells device that FIFO should start at 0. From 096dd8f48afd97282335fc2e11bdb30dbe9fe96d Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Mon, 17 Jul 2023 12:02:06 -0400 Subject: [PATCH 35/37] removing all register I/O from radiohead --- lib/pycubed_rfm9x_lora.py | 16 ++++++++++++++++ lib/radiohead.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index 655e290..26c9c57 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -712,3 +712,19 @@ def get_packet(self): self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return packet + + def write_fifo_start(self): + try: + self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) + return True + except Exception as e: + print(f"failed to write fifo start: {e}") + return False + + def reset_irq_flags(self): + try: + self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + return True + except Exception as e: + print(f"failed to reset irq flags: {e}") + return False diff --git a/lib/radiohead.py b/lib/radiohead.py index 9330658..3f10e89 100644 --- a/lib/radiohead.py +++ b/lib/radiohead.py @@ -219,7 +219,7 @@ async def send( if self.protocol == "lora": # tells device that FIFO should start at 0. # register is used different in fsk - self.tx_device._write_u8(self.constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) + self.tx_device.write_fifo_start() # Combine header and data to form payload payload = bytearray(5) @@ -280,7 +280,7 @@ async def send( # Enter idle mode to stop receiving other packets. self.idle() if self.protocol == "lora": - self.tx_device._write_u8(self.constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + self.tx_device.reset_irq_flags() return not timed_out async def send_with_ack(self, data, debug=False): From 05de346d49ea5ddf5cd51fff012c604e7f4d4c5b Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Mon, 17 Jul 2023 12:05:15 -0400 Subject: [PATCH 36/37] type annotating and try catching --- lib/pycubed_rfm9x_lora.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/pycubed_rfm9x_lora.py b/lib/pycubed_rfm9x_lora.py index 26c9c57..84fea5b 100644 --- a/lib/pycubed_rfm9x_lora.py +++ b/lib/pycubed_rfm9x_lora.py @@ -688,16 +688,21 @@ def crc_error(self) -> bool: """crc status""" return (self._read_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS) & 0x20) >> 5 - def write_payload(self, payload) -> None: - # Write payload. - self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) - # Write payload and header length. - self._write_u8(Constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + def write_payload(self, payload) -> bool: + try: + # Write payload. + self._write_from(Constants._RH_RF95_REG_00_FIFO, payload) + # Write payload and header length. + self._write_u8(Constants._RH_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) + return True + except Exception as e: + print(f"failed to write payload: {e}") + return False def check_data(self, data): assert 0 < len(data) <= 241 - def get_packet(self): + def get_packet(self) -> bytearray: # get length of data in FIFO fifo_length = self._read_u8(Constants._RH_RF95_REG_13_RX_NB_BYTES) # get start address of last packet in FIFO @@ -713,7 +718,7 @@ def get_packet(self): return packet - def write_fifo_start(self): + def write_fifo_start(self) -> bool: try: self._write_u8(Constants._RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) return True @@ -721,7 +726,7 @@ def write_fifo_start(self): print(f"failed to write fifo start: {e}") return False - def reset_irq_flags(self): + def reset_irq_flags(self) -> bool: try: self._write_u8(Constants._RH_RF95_REG_12_IRQ_FLAGS, 0xFF) return True From 024ac8f8448d63816b8a57eda97e6675d5afee9f Mon Sep 17 00:00:00 2001 From: tomyyyD Date: Thu, 23 May 2024 09:45:40 -0400 Subject: [PATCH 37/37] update hw config --- gs_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gs_setup.py b/gs_setup.py index 223f6fd..594342c 100644 --- a/gs_setup.py +++ b/gs_setup.py @@ -68,7 +68,7 @@ def pi_spi_config(): def rpigs_tx_spi_config(): spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) - cs = digitalio.DigitalInOut(board.D7) + cs = digitalio.DigitalInOut(board.D8) reset = digitalio.DigitalInOut(board.D25) return spi, cs, reset