diff --git a/.readthedocs.yaml b/.readthedocs.yaml index fcb7778..ee38fa0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,8 +8,11 @@ # Required version: 2 +sphinx: + configuration: docs/conf.py + build: - os: ubuntu-latest + os: ubuntu-lts-latest tools: python: "3" diff --git a/README.rst b/README.rst index 4ed4e8e..1062103 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,8 @@ Introduction :alt: Discord -.. image:: https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/workflows/Build%20CI/badge.svg - :target: https://github.com/jerryneedell/Adafruit_CircuitPython_RFM/actions +.. image:: https://github.com/adafruit/Adafruit_CircuitPython_RFM/workflows/Build%20CI/badge.svg + :target: https://github.com/adafruit/Adafruit_CircuitPython_RFM/actions :alt: Build Status @@ -90,7 +90,7 @@ Or the following command to update an existing version: Usage Example ============= -See examples in the GitHub Repository. +See examples in the GitHub Repository. Documentation ============= diff --git a/adafruit_rfm/rfm69.py b/adafruit_rfm/rfm69.py index 4ff3c54..7adb0b1 100644 --- a/adafruit_rfm/rfm69.py +++ b/adafruit_rfm/rfm69.py @@ -639,10 +639,11 @@ def fill_fifo(self, payload: ReadableBuffer) -> None: # Write payload to transmit fifo self.write_from(_RF69_REG_00_FIFO, complete_payload) - def read_fifo(self) -> bytearray: + def read_fifo(self) -> Optional[bytearray]: """Read the packet from the FIFO.""" # Read the length of the FIFO. fifo_length = self.read_u8(_RF69_REG_00_FIFO) + packet = None # return None if FIFO empty if fifo_length > 0: # read and clear the FIFO if anything in it packet = bytearray(fifo_length) # read the packet diff --git a/adafruit_rfm/rfm9x.py b/adafruit_rfm/rfm9x.py index d89000a..5cda864 100644 --- a/adafruit_rfm/rfm9x.py +++ b/adafruit_rfm/rfm9x.py @@ -23,7 +23,7 @@ from circuitpython_typing import ReadableBuffer try: - from typing import Literal + from typing import Literal, Optional except ImportError: from typing_extensions import Literal @@ -131,7 +131,7 @@ class RFM9x(RFMSPI): - 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 + - baudrate: Baud rate of the SPI connection, default is 5mhz 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) @@ -169,6 +169,8 @@ class RFM9x(RFMSPI): auto_agc = RFMSPI.RegisterBits(_RF95_REG_26_MODEM_CONFIG3, offset=2, bits=1) + header_mode = RFMSPI.RegisterBits(_RF95_REG_1D_MODEM_CONFIG1, offset=0, bits=1) + low_datarate_optimize = RFMSPI.RegisterBits(_RF95_REG_26_MODEM_CONFIG3, offset=3, bits=1) lna_boost_hf = RFMSPI.RegisterBits(_RF95_REG_0C_LNA, offset=0, bits=2) @@ -424,6 +426,11 @@ def signal_bandwidth(self, val: int) -> None: else: self.write_u8(0x2F, 0x44) self.write_u8(0x30, 0) + # set low_datarate_optimize for signal duration > 16 ms + if 1000 / (self.signal_bandwidth / (1 << self.spreading_factor)) > 16: + self.low_datarate_optimize = 1 + else: + self.low_datarate_optimize = 0 @property def coding_rate(self) -> Literal[5, 6, 7, 8]: @@ -461,14 +468,21 @@ def spreading_factor(self, val: Literal[6, 7, 8, 9, 10, 11, 12]) -> None: if val == 6: self.detection_optimize = 0x5 + self.header_mode = 1 # enable implicit header mode else: self.detection_optimize = 0x3 + self.header_mode = 0 # enable exlicit header mode self.write_u8(_RF95_DETECTION_THRESHOLD, 0x0C if val == 6 else 0x0A) self.write_u8( _RF95_REG_1E_MODEM_CONFIG2, ((self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0x0F) | ((val << 4) & 0xF0)), ) + # set low_datarate_optimize for signal duration > 16 ms + if 1000 / (self.signal_bandwidth / (1 << self.spreading_factor)) > 16: + self.low_datarate_optimize = 1 + else: + self.low_datarate_optimize = 0 @property def enable_crc(self) -> bool: @@ -491,6 +505,16 @@ def enable_crc(self, val: bool) -> None: self.read_u8(_RF95_REG_1E_MODEM_CONFIG2) & 0xFB, ) + @property + def payload_length(self) -> int: + """Must be set when using Implicit Header Mode - required for SF = 6""" + return self.read_u8(_RF95_REG_22_PAYLOAD_LENGTH) + + @payload_length.setter + def payload_length(self, val: int) -> None: + # Set payload length + self.write_u8(_RF95_REG_22_PAYLOAD_LENGTH, val) + @property def crc_error(self) -> bool: """crc status""" @@ -517,10 +541,11 @@ def fill_fifo(self, payload: ReadableBuffer) -> None: # Write payload and header length. self.write_u8(_RF95_REG_22_PAYLOAD_LENGTH, len(payload)) - def read_fifo(self) -> bytearray: + def read_fifo(self) -> Optional[bytearray]: """Read the data from the FIFO.""" # Read the length of the FIFO. fifo_length = self.read_u8(_RF95_REG_13_RX_NB_BYTES) + packet = None # return None if FIFO empty if fifo_length > 0: # read and clear the FIFO if anything in it packet = bytearray(fifo_length) current_addr = self.read_u8(_RF95_REG_10_FIFO_RX_CURRENT_ADDR) diff --git a/adafruit_rfm/rfm9xfsk.py b/adafruit_rfm/rfm9xfsk.py index 4394cde..d10a45b 100644 --- a/adafruit_rfm/rfm9xfsk.py +++ b/adafruit_rfm/rfm9xfsk.py @@ -563,10 +563,11 @@ def fill_fifo(self, payload: ReadableBuffer) -> None: # Write payload to transmit fifo self.write_from(_RF95_REG_00_FIFO, complete_payload) - def read_fifo(self) -> bytearray: + def read_fifo(self) -> Optional[bytearray]: """Read the data from the FIFO.""" # Read the length of the FIFO. fifo_length = self.read_u8(_RF95_REG_00_FIFO) + packet = None # return None if FIFO empty if fifo_length > 0: # read and clear the FIFO if anything in it packet = bytearray(fifo_length) # read the packet diff --git a/adafruit_rfm/rfm_common.py b/adafruit_rfm/rfm_common.py index ed5a55a..43ce081 100644 --- a/adafruit_rfm/rfm_common.py +++ b/adafruit_rfm/rfm_common.py @@ -178,7 +178,7 @@ def __init__( # noqa: PLR0913 """ self.ack_retries = 5 """The number of ACK retries before reporting a failure.""" - self.ack_delay = None + self.ack_delay: float = None """The delay time before attemting to send an ACK. If ACKs are being missed try setting this to .1 or .2. """ @@ -430,7 +430,7 @@ async def asyncio_receive( # noqa: PLR0912 self.crc_error_count += 1 else: packet = self.read_fifo() - if self.radiohead: + if (packet is not None) and self.radiohead: if len(packet) < 5: # reject the packet if it is too small to contain the RAdioHead Header packet = None @@ -503,7 +503,7 @@ async def asyncio_receive_with_ack( # noqa: PLR0912 self.crc_error_count += 1 else: packet = self.read_fifo() - if self.radiohead: + if (packet is not None) and self.radiohead: if len(packet) < 5: # reject the packet if it is too small to contain the RAdioHead Header packet = None diff --git a/docs/conf.py b/docs/conf.py index e474773..5212071 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -112,7 +112,6 @@ import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/examples/rfm_lora_sf_base.py b/examples/rfm_lora_sf_base.py new file mode 100644 index 0000000..29aae15 --- /dev/null +++ b/examples/rfm_lora_sf_base.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +import time + +import board +import busio +import digitalio + +# Define radio parameters. +RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your +# module! Can be a value like 915.0, 433.0, etc. + +# Define pins connected to the chip, use these if wiring up the breakout according to the guide: +CS = digitalio.DigitalInOut(board.CE1) +RESET = digitalio.DigitalInOut(board.D25) + +# Initialize SPI bus. +spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +# Initialze RFM radio +# uncommnet the desired import and rfm initialization depending on the radio boards being used + +# Use rfm9x for two RFM9x radios using LoRa + +from adafruit_rfm import rfm9x + +rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +rfm.radiohead = False # don't appent RadioHead heade +# set spreading factor +rfm.spreading_factor = 7 +print("spreading factor set to :", rfm.spreading_factor) +print("low_datarate_optimize set to: ", rfm.low_datarate_optimize) +# rfm.signal_bandwidth = 500000 +print("signal_bandwidth set to :", rfm.signal_bandwidth) +print("low_datarate_optimize set to: ", rfm.low_datarate_optimize) +if rfm.spreading_factor == 12: + rfm.xmit_timeout = 5 +print("xmit_timeout set to: ", rfm.xmit_timeout) +if rfm.spreading_factor == 12: + rfm.receive_timeout = 5 +elif rfm.spreading_factor > 7: + rfm.receive_timeout = 2 +print("receive_timeout set to: ", rfm.receive_timeout) +rfm.enable_crc = True +# send startup message +message = bytes(f"startup message from base", "UTF-8") +if rfm.spreading_factor == 6: + payload = bytearray(40) + rfm.payload_length = len(payload) + payload[0 : len(message)] = message + rfm.send( + payload, + keep_listening=True, + ) +else: + rfm.send( + message, + keep_listening=True, + ) +# Wait to receive packets. +print("Waiting for packets...") +# initialize flag and timer +# set a delay before sending the echo packet +# avoide multibples of .5 second to minimize chances of node missing +# the packet between receive attempts +transmit_delay = 0.75 +last_transmit_time = 0 +packet_received = False +while True: + if rfm.payload_ready(): + packet_received = False + packet = rfm.receive(timeout=None) + if packet is not None: + # Received a packet! + # Print out the raw bytes of the packet: + print(f"Received (raw payload): {packet}") + print([hex(x) for x in packet]) + print(f"RSSI: {rfm.last_rssi}") + packet_received = True + last_transmit_time = time.monotonic() + if packet_received and ((time.monotonic() - last_transmit_time) > transmit_delay): + # send back the received packet + if rfm.spreading_factor == 6: + payload = bytearray(40) + rfm.payload_length = len(payload) + payload[0 : len(packet)] = packet + rfm.send( + payload, + keep_listening=True, + ) + else: + rfm.send( + packet, + keep_listening=True, + ) + packet_received = False diff --git a/examples/rfm_lora_sf_node.py b/examples/rfm_lora_sf_node.py new file mode 100644 index 0000000..325a4f2 --- /dev/null +++ b/examples/rfm_lora_sf_node.py @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +# Example to send a packet periodically between addressed nodes with ACK +# Author: Jerry Needell +# +import time + +import board +import busio +import digitalio + +# Define radio parameters. +RADIO_FREQ_MHZ = 915.0 # Frequency of the radio in Mhz. Must match your +# module! Can be a value like 915.0, 433.0, etc. + +# Define pins connected to the chip, use these if wiring up the breakout according to the guide: +CS = digitalio.DigitalInOut(board.CE1) +RESET = digitalio.DigitalInOut(board.D25) + +# Initialize SPI bus. +spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +# Initialze RFM radio +# uncommnet the desired import and rfm initialization depending on the radio boards being used + +# Use rfm9x for two RFM9x radios using LoRa + +from adafruit_rfm import rfm9x + +rfm = rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +rfm.radiohead = False # Do not use RadioHead Header +# set spreading factor +rfm.spreading_factor = 7 +print("spreading factor set to :", rfm.spreading_factor) +print("low_datarate_optimize set to: ", rfm.low_datarate_optimize) +# rfm.signal_bandwidth = 500000 +print("signal_bandwidth set to :", rfm.signal_bandwidth) +print("low_datarate_optimize set to: ", rfm.low_datarate_optimize) +if rfm.spreading_factor == 12: + rfm.xmit_timeout = 5 +print("xmit_timeout set to: ", rfm.xmit_timeout) +if rfm.spreading_factor == 12: + rfm.receive_timeout = 5 +elif rfm.spreading_factor > 7: + rfm.receive_timeout = 2 +print("receive_timeout set to: ", rfm.receive_timeout) +rfm.enable_crc = True +# set the time interval (seconds) for sending packets +transmit_interval = 10 + +# initialize counter +counter = 0 +# send startup message from my_node +message = bytes(f"startup message from node", "UTF-8") +if rfm.spreading_factor == 6: + payload = bytearray(40) + rfm.payload_length = len(payload) + payload[0 : len(message)] = message + rfm.send( + payload, + keep_listening=True, + ) +else: + rfm.send( + message, + keep_listening=True, + ) + +# Wait to receive packets. +print("Waiting for packets...") +# initialize flag and timer +last_transmit_time = time.monotonic() +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm.receive() + # If no packet was received during the timeout then None is returned. + if packet is not None: + # Received a packet! + # Print out the raw bytes of the packet: + print(f"Received (raw payload): {packet}") + print([hex(x) for x in packet]) + print(f"RSSI: {rfm.last_rssi}") + # send reading after any packet received + if time.monotonic() - last_transmit_time > transmit_interval: + # reset timeer + last_transmit_time = time.monotonic() + # send a mesage to destination_node from my_node + message = bytes(f"message from node {counter}", "UTF-8") + if rfm.spreading_factor == 6: + payload = bytearray(40) + rfm.payload_length = len(payload) + payload[0 : len(message)] = message + rfm.send( + payload, + keep_listening=True, + ) + else: + rfm.send( + message, + keep_listening=True, + ) + + counter += 1