From 460d2dfbc42aebb6ab574543f4b185207fe27a60 Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Sun, 15 Mar 2020 15:59:55 -0400 Subject: [PATCH 1/7] adding reliable datagram mode --- adafruit_rfm9x.py | 189 +++++++++++++++++++++++++++++++++------------- 1 file changed, 138 insertions(+), 51 deletions(-) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index b362146..a4312b5 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -30,6 +30,7 @@ * Author(s): Tony DiCola, Jerry Needell """ import time +import random import digitalio from micropython import const @@ -226,6 +227,12 @@ def warn(msg, **kwargs): # 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 @@ -271,11 +278,8 @@ class RFM9x: delivery need to be implemented at an application level. """ - # Global buffer to hold data sent and received with the chip. This must be - # at least as large as the FIFO on the chip (256 bytes)! Keep this on the - # class level to ensure only one copy ever exists (with the trade-off that - # this is NOT re-entrant or thread safe code by design). - _BUFFER = bytearray(10) + # Global buffer for SPI commands + _BUFFER = bytearray(4) class _RegisterBits: # Class to simplify access to the many configuration bits avaialable @@ -395,6 +399,27 @@ def __init__(self, spi, cs, reset, frequency, *, preamble_length=8, self.frequency_mhz = frequency # Set TX power to low defaut, 13 dB. self.tx_power = 13 + #initialize timeouts and delays delays + self.ack_wait = .5 + self.receive_timeout = .5 + self.xmit_timeout = 2. + self.ack_retries = 5 + self.ack_delay = None + #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 + #destination address - default is broadcast + self.destination = _RH_BROADCAST_ADDRESS + #ID - contains seq count for reliable datagram mode + self.identifier = 0 + #flags - identifies ack/reetry packet for reliable datagram mode + self.flags = 0 + #initialize last RSSI reading + self.last_rssi = 0. # pylint: disable=no-member # Reconsider pylint: disable when this can be tested @@ -646,17 +671,16 @@ def enable_crc(self, val): self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xfb ) - def send(self, data, timeout=2., keep_listening=False, - tx_header=(_RH_BROADCAST_ADDRESS, _RH_BROADCAST_ADDRESS, 0, 0)): + def send(self, data, keep_listening=False, tx_header=None): """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 tx_header defaults to using the Broadcast addresses. It may be overidden - by specifying a 4-tuple of bytes containing (To,From,ID,Flags) - The timeout is just to prevent a hang (arbitrarily set to 2 seconds) + The tx_header defaults to using the initialized attributes: + (destination,node,identifier,flags) + It may be overidden by specifying a 4-tuple of bytes containing (To,From,ID,Flags) 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. + automatically after the packet is sent. The default setting is False """ # 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 @@ -664,20 +688,29 @@ def send(self, data, timeout=2., keep_listening=False, # buffer be within an expected range of bounds. Disable this check. # pylint: disable=len-as-condition assert 0 < len(data) <= 252 - assert len(tx_header) == 4, "tx header must be 4-tuple (To,From,ID,Flags)" + if tx_header is not None: + assert len(tx_header) == 4, "tx header must be 4-tuple (To,From,ID,Flags)" # 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(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. - # Write header bytes. - self._write_u8(_RH_RF95_REG_00_FIFO, tx_header[0]) # Header: To - self._write_u8(_RH_RF95_REG_00_FIFO, tx_header[1]) # Header: From - self._write_u8(_RH_RF95_REG_00_FIFO, tx_header[2]) # Header: Id - self._write_u8(_RH_RF95_REG_00_FIFO, tx_header[3]) # Header: Flags + # Combine header and data to form payload + payload = bytearray(4) + if tx_header is None: #use attributes + payload[0] = self.destination + payload[1] = self.node + payload[2] = self.identifier + payload[3] = self.flags + else: #use header passed as argument + payload[0] = tx_header[0] + payload[1] = tx_header[1] + payload[2] = tx_header[2] + payload[3] = tx_header[3] + payload = payload + data # Write payload. - self._write_from(_RH_RF95_REG_00_FIFO, data) + self._write_from(_RH_RF95_REG_00_FIFO, payload) # Write payload and header length. - self._write_u8(_RH_RF95_REG_22_PAYLOAD_LENGTH, len(data) + 4) + self._write_u8(_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 @@ -685,7 +718,7 @@ def send(self, data, timeout=2., keep_listening=False, start = time.monotonic() timed_out = False while not timed_out and not self.tx_done: - if (time.monotonic() - start) >= timeout: + if (time.monotonic() - start) >= self.xmit_timeout: timed_out = True # Listen again if necessary and return the result packet. if keep_listening: @@ -698,65 +731,119 @@ def send(self, data, timeout=2., keep_listening=False, if timed_out: raise RuntimeError('Timeout during packet send') - - - def receive(self, timeout=0.5, keep_listening=True, with_header=False, - rx_filter=_RH_BROADCAST_ADDRESS): - """Wait to receive a packet from the receiver. Will wait for up to timeout_s amount of - seconds for a packet to be received and decoded. If a packet is found the payload bytes + def send_with_ack(self, data): + """Reliabe Datagram mode: + Send a packet with data and wait for an ACK response. + The packet header is automatically generated. + If enabled, the packet tranmsiion 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 + self.send(data, keep_listening=True) + # 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 = self.receive(timeout=self.ack_wait, with_header=True) + if ack_packet is not None: + if ack_packet[3] & _RH_FLAGS_ACK: + # check the ID + if ack_packet[2] == self.identifier: + got_ack = True + break + # pause before next retry -- random delay + if not got_ack: + # delay by random amount before next try + time.sleep(self.ack_wait + self.ack_wait * random.random()) + retries_remaining = retries_remaining - 1 + # set retry flag in packet header + self.flags |= _RH_FLAGS_RETRY + self.flags = 0 # clear flags + return got_ack + + #pylint: disable=too-many-branches + def receive(self, keep_listening=True, with_header=False, with_ack=False, timeout=None): + """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 timeout is None it is not used ( for use with interrupts) + 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. - A 4-byte header must be prepended to the data for compatibilty with the + All packets must have a 4 byte header A 4-byte header for compatibilty with the RadioHead library. - The header consists of a 4 bytes (To,From,ID,Flags). The default setting will accept - any incomming packet and strip the header before returning the packet to the caller. + 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]. - rx_fliter may be set to reject any "non-broadcast" packets that do not contain the - specfied "To" value in the header. - if rx_filter is set to 0xff (_RH_BROADCAST_ADDRESS) or if the "To" field (packet[[0]) - is equal to 0xff then the packet will be accepted and returned to the caller. - If rx_filter is not 0xff and packet[0] does not match rx_filter then - the packet is ignored and None is returned. + 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: - # Make sure we are listening for packets. - self.listen() - # Wait for the rx done interrupt. This is not ideal and will + # 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() start = time.monotonic() + timed_out = False while not timed_out and not self.rx_done: if (time.monotonic() - start) >= timeout: timed_out = True # Payload ready is set, a packet is in the FIFO. packet = None + # save last RSSI reading + self.last_rssi = self.rssi + # Enter idle mode to stop receiving other packets. + self.idle() if not timed_out: if self.enable_crc and self.crc_error: warn("CRC error, packet ignored") else: - # Grab the length of the received packet and check it has at least 5 - # bytes to indicate the 4 byte header and at least 1 byte of user data. - length = self._read_u8(_RH_RF95_REG_13_RX_NB_BYTES) - if length < 5: - packet = None - else: - # Have a good packet, grab it from the FIFO. - # Reset the fifo read ptr to the beginning of the packet. + # Read the data from the FIFO. + # Read the length of the FIFO. + fifo_length = self._read_u8(_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(_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) - packet = bytearray(length) + packet = bytearray(fifo_length) # Read the packet. self._read_into(_RH_RF95_REG_00_FIFO, packet) - if (rx_filter != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS - and packet[0] != rx_filter): + # Clear interrupt. + self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + if fifo_length < 5: + packet = None + else: + if (self.node != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS + and packet[0] != self.node): packet = None - elif not with_header: # skip the header if not wanted + #send ACK unless this was an ACK or a broadcast + elif with_ack and ((packet[3]&_RH_FLAGS_ACK) == 0) \ + and (packet[0] != _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 = bytes("!", "UTF-8") + self.send(data, tx_header=(packet[1], packet[0], + packet[2], packet[3]|_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[3]&_RH_FLAGS_RETRY): + packet = None + else: # save the packet identifier for this source + self.seen_ids[packet[1]] = packet[2] + if not with_header and packet is not None: # skip the header if not wanted packet = packet[4:] # Listen again if necessary and return the result packet. if keep_listening: From cccbd09207439d30a0018ccb8f921141354212d3 Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Sun, 12 Apr 2020 15:13:27 -0400 Subject: [PATCH 2/7] cleanup - format with black --- adafruit_rfm9x.py | 455 +++++++++++++++++++++++++--------------------- 1 file changed, 243 insertions(+), 212 deletions(-) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index a4312b5..f69caf3 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -37,9 +37,14 @@ try: from warnings import warn except ImportError: + def warn(msg, **kwargs): "Issue a warning to stdout." - print("%s: %s" % ("Warning" if kwargs.get("cat") is None else kwargs["cat"].__name__, msg)) + print( + "%s: %s" + % ("Warning" if kwargs.get("cat") is None else kwargs["cat"].__name__, msg) + ) + import adafruit_bus_device.spi_device as spidev @@ -51,178 +56,178 @@ def warn(msg, **kwargs): # pylint: disable=bad-whitespace # 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_02_RESERVED = const(0x02) -_RH_RF95_REG_03_RESERVED = const(0x03) -_RH_RF95_REG_04_RESERVED = const(0x04) -_RH_RF95_REG_05_RESERVED = 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) -_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_REG_00_FIFO = const(0x00) +_RH_RF95_REG_01_OP_MODE = const(0x01) +_RH_RF95_REG_02_RESERVED = const(0x02) +_RH_RF95_REG_03_RESERVED = const(0x03) +_RH_RF95_REG_04_RESERVED = const(0x04) +_RH_RF95_REG_05_RESERVED = 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) +_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_REG_01_OP_MODE 0x01 -_RH_RF95_LONG_RANGE_MODE = const(0x80) -_RH_RF95_ACCESS_SHARED_REG = const(0x40) -_RH_RF95_MODE = const(0x07) -_RH_RF95_MODE_SLEEP = const(0x00) -_RH_RF95_MODE_STDBY = const(0x01) -_RH_RF95_MODE_FSTX = const(0x02) -_RH_RF95_MODE_TX = const(0x03) -_RH_RF95_MODE_FSRX = const(0x04) -_RH_RF95_MODE_RXCONTINUOUS = const(0x05) -_RH_RF95_MODE_RXSINGLE = const(0x06) -_RH_RF95_MODE_CAD = const(0x07) +_RH_RF95_LONG_RANGE_MODE = const(0x80) +_RH_RF95_ACCESS_SHARED_REG = const(0x40) +_RH_RF95_MODE = const(0x07) +_RH_RF95_MODE_SLEEP = const(0x00) +_RH_RF95_MODE_STDBY = const(0x01) +_RH_RF95_MODE_FSTX = const(0x02) +_RH_RF95_MODE_TX = const(0x03) +_RH_RF95_MODE_FSRX = const(0x04) +_RH_RF95_MODE_RXCONTINUOUS = const(0x05) +_RH_RF95_MODE_RXSINGLE = const(0x06) +_RH_RF95_MODE_CAD = const(0x07) # RH_RF95_REG_09_PA_CONFIG 0x09 -_RH_RF95_PA_SELECT = const(0x80) -_RH_RF95_MAX_POWER = const(0x70) -_RH_RF95_OUTPUT_POWER = const(0x0f) +_RH_RF95_PA_SELECT = const(0x80) +_RH_RF95_MAX_POWER = const(0x70) +_RH_RF95_OUTPUT_POWER = const(0x0F) # RH_RF95_REG_0A_PA_RAMP 0x0a -_RH_RF95_LOW_PN_TX_PLL_OFF = const(0x10) -_RH_RF95_PA_RAMP = const(0x0f) -_RH_RF95_PA_RAMP_3_4MS = const(0x00) -_RH_RF95_PA_RAMP_2MS = const(0x01) -_RH_RF95_PA_RAMP_1MS = const(0x02) -_RH_RF95_PA_RAMP_500US = const(0x03) -_RH_RF95_PA_RAMP_250US = const(0x04) -_RH_RF95_PA_RAMP_125US = const(0x05) -_RH_RF95_PA_RAMP_100US = const(0x06) -_RH_RF95_PA_RAMP_62US = const(0x07) -_RH_RF95_PA_RAMP_50US = const(0x08) -_RH_RF95_PA_RAMP_40US = const(0x09) -_RH_RF95_PA_RAMP_31US = const(0x0a) -_RH_RF95_PA_RAMP_25US = const(0x0b) -_RH_RF95_PA_RAMP_20US = const(0x0c) -_RH_RF95_PA_RAMP_15US = const(0x0d) -_RH_RF95_PA_RAMP_12US = const(0x0e) -_RH_RF95_PA_RAMP_10US = const(0x0f) +_RH_RF95_LOW_PN_TX_PLL_OFF = const(0x10) +_RH_RF95_PA_RAMP = const(0x0F) +_RH_RF95_PA_RAMP_3_4MS = const(0x00) +_RH_RF95_PA_RAMP_2MS = const(0x01) +_RH_RF95_PA_RAMP_1MS = const(0x02) +_RH_RF95_PA_RAMP_500US = const(0x03) +_RH_RF95_PA_RAMP_250US = const(0x04) +_RH_RF95_PA_RAMP_125US = const(0x05) +_RH_RF95_PA_RAMP_100US = const(0x06) +_RH_RF95_PA_RAMP_62US = const(0x07) +_RH_RF95_PA_RAMP_50US = const(0x08) +_RH_RF95_PA_RAMP_40US = const(0x09) +_RH_RF95_PA_RAMP_31US = const(0x0A) +_RH_RF95_PA_RAMP_25US = const(0x0B) +_RH_RF95_PA_RAMP_20US = const(0x0C) +_RH_RF95_PA_RAMP_15US = const(0x0D) +_RH_RF95_PA_RAMP_12US = const(0x0E) +_RH_RF95_PA_RAMP_10US = const(0x0F) # RH_RF95_REG_0B_OCP 0x0b -_RH_RF95_OCP_ON = const(0x20) -_RH_RF95_OCP_TRIM = const(0x1f) +_RH_RF95_OCP_ON = const(0x20) +_RH_RF95_OCP_TRIM = const(0x1F) # RH_RF95_REG_0C_LNA 0x0c -_RH_RF95_LNA_GAIN = const(0xe0) -_RH_RF95_LNA_BOOST = const(0x03) -_RH_RF95_LNA_BOOST_DEFAULT = const(0x00) -_RH_RF95_LNA_BOOST_150PC = const(0x11) +_RH_RF95_LNA_GAIN = const(0xE0) +_RH_RF95_LNA_BOOST = const(0x03) +_RH_RF95_LNA_BOOST_DEFAULT = const(0x00) +_RH_RF95_LNA_BOOST_150PC = const(0x11) # RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 -_RH_RF95_RX_TIMEOUT_MASK = const(0x80) -_RH_RF95_RX_DONE_MASK = const(0x40) -_RH_RF95_PAYLOAD_CRC_ERROR_MASK = const(0x20) -_RH_RF95_VALID_HEADER_MASK = const(0x10) -_RH_RF95_TX_DONE_MASK = const(0x08) -_RH_RF95_CAD_DONE_MASK = const(0x04) -_RH_RF95_FHSS_CHANGE_CHANNEL_MASK = const(0x02) -_RH_RF95_CAD_DETECTED_MASK = const(0x01) +_RH_RF95_RX_TIMEOUT_MASK = const(0x80) +_RH_RF95_RX_DONE_MASK = const(0x40) +_RH_RF95_PAYLOAD_CRC_ERROR_MASK = const(0x20) +_RH_RF95_VALID_HEADER_MASK = const(0x10) +_RH_RF95_TX_DONE_MASK = const(0x08) +_RH_RF95_CAD_DONE_MASK = const(0x04) +_RH_RF95_FHSS_CHANGE_CHANNEL_MASK = const(0x02) +_RH_RF95_CAD_DETECTED_MASK = const(0x01) # RH_RF95_REG_12_IRQ_FLAGS 0x12 -_RH_RF95_RX_TIMEOUT = const(0x80) -_RH_RF95_RX_DONE = const(0x40) -_RH_RF95_PAYLOAD_CRC_ERROR = const(0x20) -_RH_RF95_VALID_HEADER = const(0x10) -_RH_RF95_TX_DONE = const(0x08) -_RH_RF95_CAD_DONE = const(0x04) -_RH_RF95_FHSS_CHANGE_CHANNEL = const(0x02) -_RH_RF95_CAD_DETECTED = const(0x01) +_RH_RF95_RX_TIMEOUT = const(0x80) +_RH_RF95_RX_DONE = const(0x40) +_RH_RF95_PAYLOAD_CRC_ERROR = const(0x20) +_RH_RF95_VALID_HEADER = const(0x10) +_RH_RF95_TX_DONE = const(0x08) +_RH_RF95_CAD_DONE = const(0x04) +_RH_RF95_FHSS_CHANGE_CHANNEL = const(0x02) +_RH_RF95_CAD_DETECTED = const(0x01) # RH_RF95_REG_18_MODEM_STAT 0x18 -_RH_RF95_RX_CODING_RATE = const(0xe0) -_RH_RF95_MODEM_STATUS_CLEAR = const(0x10) -_RH_RF95_MODEM_STATUS_HEADER_INFO_VALID = const(0x08) -_RH_RF95_MODEM_STATUS_RX_ONGOING = const(0x04) -_RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED = const(0x02) -_RH_RF95_MODEM_STATUS_SIGNAL_DETECTED = const(0x01) +_RH_RF95_RX_CODING_RATE = const(0xE0) +_RH_RF95_MODEM_STATUS_CLEAR = const(0x10) +_RH_RF95_MODEM_STATUS_HEADER_INFO_VALID = const(0x08) +_RH_RF95_MODEM_STATUS_RX_ONGOING = const(0x04) +_RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED = const(0x02) +_RH_RF95_MODEM_STATUS_SIGNAL_DETECTED = const(0x01) # RH_RF95_REG_1C_HOP_CHANNEL 0x1c -_RH_RF95_PLL_TIMEOUT = const(0x80) -_RH_RF95_RX_PAYLOAD_CRC_IS_ON = const(0x40) -_RH_RF95_FHSS_PRESENT_CHANNEL = const(0x3f) +_RH_RF95_PLL_TIMEOUT = const(0x80) +_RH_RF95_RX_PAYLOAD_CRC_IS_ON = const(0x40) +_RH_RF95_FHSS_PRESENT_CHANNEL = const(0x3F) # RH_RF95_REG_1D_MODEM_CONFIG1 0x1d -_RH_RF95_BW = const(0xc0) -_RH_RF95_BW_125KHZ = const(0x00) -_RH_RF95_BW_250KHZ = const(0x40) -_RH_RF95_BW_500KHZ = const(0x80) -_RH_RF95_BW_RESERVED = const(0xc0) -_RH_RF95_CODING_RATE = const(0x38) -_RH_RF95_CODING_RATE_4_5 = const(0x00) -_RH_RF95_CODING_RATE_4_6 = const(0x08) -_RH_RF95_CODING_RATE_4_7 = const(0x10) -_RH_RF95_CODING_RATE_4_8 = const(0x18) -_RH_RF95_IMPLICIT_HEADER_MODE_ON = const(0x04) -_RH_RF95_RX_PAYLOAD_CRC_ON = const(0x02) -_RH_RF95_LOW_DATA_RATE_OPTIMIZE = const(0x01) +_RH_RF95_BW = const(0xC0) +_RH_RF95_BW_125KHZ = const(0x00) +_RH_RF95_BW_250KHZ = const(0x40) +_RH_RF95_BW_500KHZ = const(0x80) +_RH_RF95_BW_RESERVED = const(0xC0) +_RH_RF95_CODING_RATE = const(0x38) +_RH_RF95_CODING_RATE_4_5 = const(0x00) +_RH_RF95_CODING_RATE_4_6 = const(0x08) +_RH_RF95_CODING_RATE_4_7 = const(0x10) +_RH_RF95_CODING_RATE_4_8 = const(0x18) +_RH_RF95_IMPLICIT_HEADER_MODE_ON = const(0x04) +_RH_RF95_RX_PAYLOAD_CRC_ON = const(0x02) +_RH_RF95_LOW_DATA_RATE_OPTIMIZE = const(0x01) # RH_RF95_REG_1E_MODEM_CONFIG2 0x1e -_RH_RF95_DETECTION_OPTIMIZE = const(0x31) -_RH_RF95_DETECTION_THRESHOLD = const(0x37) -_RH_RF95_SPREADING_FACTOR = const(0xf0) -_RH_RF95_SPREADING_FACTOR_64CPS = const(0x60) -_RH_RF95_SPREADING_FACTOR_128CPS = const(0x70) -_RH_RF95_SPREADING_FACTOR_256CPS = const(0x80) -_RH_RF95_SPREADING_FACTOR_512CPS = const(0x90) -_RH_RF95_SPREADING_FACTOR_1024CPS = const(0xa0) -_RH_RF95_SPREADING_FACTOR_2048CPS = const(0xb0) -_RH_RF95_SPREADING_FACTOR_4096CPS = const(0xc0) -_RH_RF95_TX_CONTINUOUS_MOE = const(0x08) -_RH_RF95_AGC_AUTO_ON = const(0x04) -_RH_RF95_SYM_TIMEOUT_MSB = const(0x03) +_RH_RF95_DETECTION_OPTIMIZE = const(0x31) +_RH_RF95_DETECTION_THRESHOLD = const(0x37) +_RH_RF95_SPREADING_FACTOR = const(0xF0) +_RH_RF95_SPREADING_FACTOR_64CPS = const(0x60) +_RH_RF95_SPREADING_FACTOR_128CPS = const(0x70) +_RH_RF95_SPREADING_FACTOR_256CPS = const(0x80) +_RH_RF95_SPREADING_FACTOR_512CPS = const(0x90) +_RH_RF95_SPREADING_FACTOR_1024CPS = const(0xA0) +_RH_RF95_SPREADING_FACTOR_2048CPS = const(0xB0) +_RH_RF95_SPREADING_FACTOR_4096CPS = const(0xC0) +_RH_RF95_TX_CONTINUOUS_MOE = const(0x08) +_RH_RF95_AGC_AUTO_ON = const(0x04) +_RH_RF95_SYM_TIMEOUT_MSB = const(0x03) # RH_RF95_REG_4D_PA_DAC 0x4d -_RH_RF95_PA_DAC_DISABLE = const(0x04) -_RH_RF95_PA_DAC_ENABLE = const(0x07) +_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) +_RH_RF95_FSTEP = _RH_RF95_FXOSC / 524288 # RadioHead specific compatibility constants. _RH_BROADCAST_ADDRESS = const(0xFF) @@ -234,12 +239,12 @@ def warn(msg, **kwargs): _RH_FLAGS_RETRY = const(0x40) # User facing constants: -SLEEP_MODE = 0b000 +SLEEP_MODE = 0b000 STANDBY_MODE = 0b001 -FS_TX_MODE = 0b010 -TX_MODE = 0b011 -FS_RX_MODE = 0b100 -RX_MODE = 0b101 +FS_TX_MODE = 0b010 +TX_MODE = 0b011 +FS_RX_MODE = 0b100 +RX_MODE = 0b101 # pylint: enable=bad-whitespace @@ -249,6 +254,7 @@ def warn(msg, **kwargs): # the warning to work around the error. # pylint: disable=too-many-instance-attributes + class RFM9x: """Interface to a RFM95/6/7/8 LoRa radio module. Allows sending and receivng bytes of data in long range LoRa mode at a support board frequency @@ -350,13 +356,21 @@ def __set__(self, obj, val): bw_bins = (7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000) - def __init__(self, spi, cs, reset, frequency, *, preamble_length=8, - high_power=True, baudrate=5000000): + def __init__( + self, + spi, + cs, + reset, + frequency, + *, + preamble_length=8, + high_power=True, + baudrate=5000000 + ): 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) + self._device = spidev.SPIDevice(spi, cs, baudrate=baudrate, polarity=0, phase=0) # Setup reset as a digital input (default state for reset line according # to the datasheet). This line is pulled low as an output quickly to # trigger a reset. Note that reset MUST be done like this and set as @@ -368,7 +382,9 @@ def __init__(self, spi, cs, reset, frequency, *, preamble_length=8, # throw a nicer message to indicate possible wiring problems. version = self._read_u8(_RH_RF95_REG_42_VERSION) if version != 18: - raise RuntimeError('Failed to find rfm9x with expected version -- check wiring') + 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. @@ -376,7 +392,7 @@ def __init__(self, spi, cs, reset, frequency, *, preamble_length=8, time.sleep(0.01) self.long_range_mode = True if self.operation_mode != SLEEP_MODE or not self.long_range_mode: - raise RuntimeError('Failed to configure radio for LoRa mode, check wiring!') + 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 @@ -399,27 +415,27 @@ def __init__(self, spi, cs, reset, frequency, *, preamble_length=8, self.frequency_mhz = frequency # Set TX power to low defaut, 13 dB. self.tx_power = 13 - #initialize timeouts and delays delays - self.ack_wait = .5 - self.receive_timeout = .5 - self.xmit_timeout = 2. + # initialize timeouts and delays delays + self.ack_wait = 0.5 + self.receive_timeout = 0.5 + self.xmit_timeout = 2.0 self.ack_retries = 5 self.ack_delay = None - #initialize sequence number counter for reliabe datagram mode + # initialize sequence number counter for reliabe datagram mode self.sequence_number = 0 - #create seen Ids list + # create seen Ids list self.seen_ids = bytearray(256) - #initialize packet header - #node address - default is broadcast + # initialize packet header + # node address - default is broadcast self.node = _RH_BROADCAST_ADDRESS - #destination address - default is broadcast + # destination address - default is broadcast self.destination = _RH_BROADCAST_ADDRESS - #ID - contains seq count for reliable datagram mode + # ID - contains seq count for reliable datagram mode self.identifier = 0 - #flags - identifies ack/reetry packet for reliable datagram mode + # flags - identifies ack/reetry packet for reliable datagram mode self.flags = 0 - #initialize last RSSI reading - self.last_rssi = 0. + # initialize last RSSI reading + self.last_rssi = 0.0 # pylint: disable=no-member # Reconsider pylint: disable when this can be tested @@ -431,7 +447,7 @@ def _read_into(self, address, buf, length=None): length = len(buf) with self._device as device: self._BUFFER[0] = address & 0x7F # Strip out top bit to set 0 - # value (read). + # value (read). device.write(self._BUFFER, end=1) device.readinto(buf, end=length) @@ -448,7 +464,7 @@ def _write_from(self, address, buf, length=None): length = len(buf) with self._device as device: self._BUFFER[0] = (address | 0x80) & 0xFF # Set top bit to 1 to - # indicate a write. + # indicate a write. device.write(self._BUFFER, end=1) device.write(buf, end=length) @@ -457,7 +473,7 @@ def _write_u8(self, address, val): # 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. + # indicate a write. self._BUFFER[1] = val & 0xFF device.write(self._BUFFER, end=2) @@ -467,7 +483,7 @@ def reset(self): self._reset.switch_to_output(value=False) time.sleep(0.0001) # 100 us self._reset.switch_to_input(pull=digitalio.Pull.UP) - time.sleep(0.005) # 5 ms + time.sleep(0.005) # 5 ms def idle(self): """Enter idle standby mode.""" @@ -523,7 +539,7 @@ def frequency_mhz(self): @frequency_mhz.setter def frequency_mhz(self, val): if val < 240 or val > 960: - raise RuntimeError('frequency_mhz must be between 240 and 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 # Extract byte values and update registers. @@ -554,7 +570,7 @@ def tx_power(self, val): val = int(val) if self.high_power: if val < 5 or val > 23: - raise RuntimeError('tx_power must be between 5 and 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: @@ -583,7 +599,7 @@ def signal_bandwidth(self): 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(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xf0) >> 4 + bw_id = (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xF0) >> 4 if bw_id >= len(self.bw_bins): current_bandwidth = 500000 else: @@ -600,7 +616,7 @@ def signal_bandwidth(self, val): bw_id = 9 self._write_u8( _RH_RF95_REG_1D_MODEM_CONFIG1, - (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0f) | (bw_id << 4) + (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0F) | (bw_id << 4), ) @property @@ -609,7 +625,7 @@ def coding_rate(self): 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(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0e) >> 1 + cr_id = (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0x0E) >> 1 denominator = cr_id + 4 return denominator @@ -620,7 +636,7 @@ def coding_rate(self, val): cr_id = denominator - 4 self._write_u8( _RH_RF95_REG_1D_MODEM_CONFIG1, - (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xf1) | (cr_id << 1) + (self._read_u8(_RH_RF95_REG_1D_MODEM_CONFIG1) & 0xF1) | (cr_id << 1), ) @property @@ -629,25 +645,21 @@ def spreading_factor(self): 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(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xf0) >> 4 + sf_id = (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xF0) >> 4 return sf_id @spreading_factor.setter def spreading_factor(self, val): # Set spreading factor (set to 7 to match RadioHead Sf128). val = min(max(val, 6), 12) - self._write_u8( - _RH_RF95_DETECTION_OPTIMIZE, 0xc5 if val == 6 else 0xc3 - ) - self._write_u8( - _RH_RF95_DETECTION_THRESHOLD, 0x0c if val == 6 else 0x0a - ) + self._write_u8(_RH_RF95_DETECTION_OPTIMIZE, 0xC5 if val == 6 else 0xC3) + self._write_u8(_RH_RF95_DETECTION_THRESHOLD, 0x0C if val == 6 else 0x0A) self._write_u8( _RH_RF95_REG_1E_MODEM_CONFIG2, ( - (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0x0f) | - ((val << 4) & 0xf0) - ) + (self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0x0F) + | ((val << 4) & 0xF0) + ), ) @property @@ -663,12 +675,12 @@ def enable_crc(self, val): if val: self._write_u8( _RH_RF95_REG_1E_MODEM_CONFIG2, - self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) | 0x04 + self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) | 0x04, ) else: self._write_u8( _RH_RF95_REG_1E_MODEM_CONFIG2, - self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xfb + self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xFB, ) def send(self, data, keep_listening=False, tx_header=None): @@ -696,12 +708,12 @@ def send(self, data, keep_listening=False, tx_header=None): self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. # Combine header and data to form payload payload = bytearray(4) - if tx_header is None: #use attributes + if tx_header is None: # use attributes payload[0] = self.destination payload[1] = self.node payload[2] = self.identifier payload[3] = self.flags - else: #use header passed as argument + else: # use header passed as argument payload[0] = tx_header[0] payload[1] = tx_header[1] payload[2] = tx_header[2] @@ -724,12 +736,12 @@ def send(self, data, keep_listening=False, tx_header=None): if keep_listening: self.listen() else: - # Enter idle mode to stop receiving other packets. + # Enter idle mode to stop receiving other packets. self.idle() # Clear interrupt. self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) if timed_out: - raise RuntimeError('Timeout during packet send') + raise RuntimeError("Timeout during packet send") def send_with_ack(self, data): """Reliabe Datagram mode: @@ -742,7 +754,7 @@ def send_with_ack(self, data): else: retries_remaining = 1 got_ack = False - self.sequence_number = (self.sequence_number + 1)&0xff + self.sequence_number = (self.sequence_number + 1) & 0xFF while not got_ack and retries_remaining: self.identifier = self.sequence_number self.send(data, keep_listening=True) @@ -765,11 +777,13 @@ def send_with_ack(self, data): retries_remaining = retries_remaining - 1 # set retry flag in packet header self.flags |= _RH_FLAGS_RETRY - self.flags = 0 # clear flags + self.flags = 0 # clear flags return got_ack - #pylint: disable=too-many-branches - def receive(self, keep_listening=True, with_header=False, with_ack=False, timeout=None): + # pylint: disable=too-many-branches + def receive( + self, keep_listening=True, with_header=False, with_ack=False, timeout=None + ): """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). @@ -814,7 +828,7 @@ def receive(self, keep_listening=True, with_header=False, with_ack=False, timeou fifo_length = self._read_u8(_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 + if fifo_length > 0: # read and clear the FIFO if anything in it current_addr = self._read_u8(_RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR) self._write_u8(_RH_RF95_REG_0D_FIFO_ADDR_PTR, current_addr) packet = bytearray(fifo_length) @@ -825,31 +839,48 @@ def receive(self, keep_listening=True, with_header=False, with_ack=False, timeou if fifo_length < 5: packet = None else: - if (self.node != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS - and packet[0] != self.node): + if ( + self.node != _RH_BROADCAST_ADDRESS + and packet[0] != _RH_BROADCAST_ADDRESS + and packet[0] != self.node + ): packet = None - #send ACK unless this was an ACK or a broadcast - elif with_ack and ((packet[3]&_RH_FLAGS_ACK) == 0) \ - and (packet[0] != _RH_BROADCAST_ADDRESS): + # send ACK unless this was an ACK or a broadcast + elif ( + with_ack + and ((packet[3] & _RH_FLAGS_ACK) == 0) + and (packet[0] != _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 + # send ACK packet to sender data = bytes("!", "UTF-8") - self.send(data, tx_header=(packet[1], packet[0], - packet[2], packet[3]|_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[3]&_RH_FLAGS_RETRY): + self.send( + data, + tx_header=( + packet[1], + packet[0], + packet[2], + packet[3] | _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[3] & _RH_FLAGS_RETRY + ): packet = None - else: # save the packet identifier for this source + else: # save the packet identifier for this source self.seen_ids[packet[1]] = packet[2] - if not with_header and packet is not None: # skip the header if not wanted + if ( + not with_header and packet is not None + ): # skip the header if not wanted packet = packet[4:] # Listen again if necessary and return the result packet. if keep_listening: self.listen() else: - # Enter idle mode to stop receiving other packets. + # Enter idle mode to stop receiving other packets. self.idle() # Clear interrupt. self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) From f9696de73c40fa3dabe27b283d140cdd489b1d20 Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Sun, 12 Apr 2020 15:34:51 -0400 Subject: [PATCH 3/7] reran black --- adafruit_rfm9x.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index e7fc096..f69caf3 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -742,6 +742,7 @@ def send(self, data, keep_listening=False, tx_header=None): self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) if timed_out: raise RuntimeError("Timeout during packet send") + def send_with_ack(self, data): """Reliabe Datagram mode: Send a packet with data and wait for an ACK response. From 9c74587b7328d3ec85647d40de8e6b4f1c8eecd2 Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Sun, 12 Apr 2020 16:50:02 -0400 Subject: [PATCH 4/7] incorporate rfm69 updates --- adafruit_rfm9x.py | 117 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 35 deletions(-) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index f69caf3..9d89c8a 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -401,26 +401,35 @@ def __init__( self._write_u8(_RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0x00) # Set mode idle self.idle() - # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. - self.signal_bandwidth = 125000 - self.coding_rate = 5 - self.spreading_factor = 7 - # Default to disable CRC checking on incoming packets. - self.enable_crc = False - # Note no sync word is set for LoRa mode either! - self._write_u8(_RH_RF95_REG_26_MODEM_CONFIG3, 0x00) # Preamble lsb? - # Set preamble length (default 8 bytes to match radiohead). - self.preamble_length = preamble_length # Set frequency self.frequency_mhz = frequency - # Set TX power to low defaut, 13 dB. - self.tx_power = 13 + # Set preamble length (default 8 bytes to match radiohead). + self.preamble_length = preamble_length + # set radio configuration parameters + self._configure_radio() + # initialize last RSSI reading + self.last_rssi = 0.0 + """The RSSI of the last received packet. Stored when the packet was received. + This instataneous RSSI 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 @@ -428,14 +437,39 @@ def __init__( # 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 - # initialize last RSSI reading - self.last_rssi = 0.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. + """ + + def _configure_radio(self): + # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. + self.signal_bandwidth = 125000 + self.coding_rate = 5 + self.spreading_factor = 7 + # Default to disable CRC checking on incoming packets. + self.enable_crc = False + # Note no sync word is set for LoRa mode either! + self._write_u8(_RH_RF95_REG_26_MODEM_CONFIG3, 0x00) # Preamble lsb? + # Set transmit power to 13 dBm, a safe value any module supports. + self.tx_power = 13 # pylint: disable=no-member # Reconsider pylint: disable when this can be tested @@ -683,16 +717,28 @@ def enable_crc(self, val): self._read_u8(_RH_RF95_REG_1E_MODEM_CONFIG2) & 0xFB, ) - def send(self, data, keep_listening=False, tx_header=None): + def send( + self, + data, + *, + keep_listening=False, + destination=None, + node=None, + identifier=None, + flags=None + ): """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 tx_header defaults to using the initialized attributes: + The header defaults to using the initialized attributes: (destination,node,identifier,flags) - It may be overidden by specifying a 4-tuple of bytes containing (To,From,ID,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 + 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 @@ -700,24 +746,28 @@ def send(self, data, keep_listening=False, tx_header=None): # buffer be within an expected range of bounds. Disable this check. # pylint: disable=len-as-condition assert 0 < len(data) <= 252 - if tx_header is not None: - assert len(tx_header) == 4, "tx header must be 4-tuple (To,From,ID,Flags)" # 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(_RH_RF95_REG_0D_FIFO_ADDR_PTR, 0x00) # FIFO starts at 0. # Combine header and data to form payload payload = bytearray(4) - if tx_header is None: # use attributes + if destination is None: # use attribute payload[0] = self.destination + else: # use kwarg + payload[0] = destination + if node is None: # use attribute payload[1] = self.node + else: # use kwarg + payload[1] = node + if identifier is None: # use attribute payload[2] = self.identifier + else: # use kwarg + payload[2] = identifier + if flags is None: # use attribute payload[3] = self.flags - else: # use header passed as argument - payload[0] = tx_header[0] - payload[1] = tx_header[1] - payload[2] = tx_header[2] - payload[3] = tx_header[3] + else: # use kwarg + payload[3] = flags payload = payload + data # Write payload. self._write_from(_RH_RF95_REG_00_FIFO, payload) @@ -740,8 +790,7 @@ def send(self, data, keep_listening=False, tx_header=None): self.idle() # Clear interrupt. self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) - if timed_out: - raise RuntimeError("Timeout during packet send") + return not timed_out def send_with_ack(self, data): """Reliabe Datagram mode: @@ -782,7 +831,7 @@ def send_with_ack(self, data): # pylint: disable=too-many-branches def receive( - self, keep_listening=True, with_header=False, with_ack=False, timeout=None + self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None ): """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 @@ -858,12 +907,10 @@ def receive( data = bytes("!", "UTF-8") self.send( data, - tx_header=( - packet[1], - packet[0], - packet[2], - packet[3] | _RH_FLAGS_ACK, - ), + destination=packet[1], + node=packet[0], + identifier=packet[2], + flags=(packet[3] | _RH_FLAGS_ACK), ) # reject Retries if we have seen this idetifier from this source before if (self.seen_ids[packet[1]] == packet[2]) and ( From 82c359e59532535b6936412e441a2d84acc3a16f Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Mon, 13 Apr 2020 07:52:45 -0400 Subject: [PATCH 5/7] add examples --- adafruit_rfm9x.py | 2 +- examples/rfm9x_header.py | 36 +++++++++ examples/rfm9x_node1.py | 62 ++++++++++++++++ examples/rfm9x_node1_ack.py | 64 ++++++++++++++++ examples/rfm9x_node1_bonnet.py | 124 +++++++++++++++++++++++++++++++ examples/rfm9x_node2.py | 60 +++++++++++++++ examples/rfm9x_node2_ack.py | 55 ++++++++++++++ examples/rfm9x_rpi_interrupt.py | 2 +- examples/rfm9x_rpi_simpletest.py | 65 ++++++++++++++++ examples/rfm9x_simpletest.py | 2 +- 10 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 examples/rfm9x_header.py create mode 100644 examples/rfm9x_node1.py create mode 100644 examples/rfm9x_node1_ack.py create mode 100644 examples/rfm9x_node1_bonnet.py create mode 100644 examples/rfm9x_node2.py create mode 100644 examples/rfm9x_node2_ack.py create mode 100644 examples/rfm9x_rpi_simpletest.py diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index 9d89c8a..35ba5a0 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -410,7 +410,7 @@ def __init__( # initialize last RSSI reading self.last_rssi = 0.0 """The RSSI of the last received packet. Stored when the packet was received. - This instataneous RSSI value may not be accurate once the + This instantaneous RSSI value may not be accurate once the operating mode has been changed. """ # initialize timeouts and delays delays diff --git a/examples/rfm9x_header.py b/examples/rfm9x_header.py new file mode 100644 index 0000000..f78c1c7 --- /dev/null +++ b/examples/rfm9x_header.py @@ -0,0 +1,36 @@ +# Example to display raw packets including header +# Author: Jerry Needell +# +import board +import busio +import digitalio +import adafruit_rfm9x + +# 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. +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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# Wait to receive packets. +print("Waiting for packets...") +# initialize flag and timer +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("RSSI: {0}".format(rfm9x.last_rssi)) + # send reading after any packet received diff --git a/examples/rfm9x_node1.py b/examples/rfm9x_node1.py new file mode 100644 index 0000000..1377a92 --- /dev/null +++ b/examples/rfm9x_node1.py @@ -0,0 +1,62 @@ +# Example to send a packet periodically between addressed nodes +# Author: Jerry Needell +# +import time +import board +import busio +import digitalio +import adafruit_rfm9x + + +# set the time interval (seconds) for sending packets +transmit_interval = 10 + +# 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. +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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# enable CRC checking +rfm9x.enable_crc = True +# set node addresses +rfm9x.node = 1 +rfm9x.destination = 2 +# initialize counter +counter = 0 +# send a broadcast message from my_node with ID = counter +rfm9x.send( + bytes("Startup message {} from node {}".format(counter, rfm9x.node), "UTF-8") +) + +# Wait to receive packets. +print("Waiting for packets...") +now = time.monotonic() +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("Received RSSI: {0}".format(rfm9x.last_rssi)) + if time.monotonic() - now > transmit_interval: + now = time.monotonic() + counter = counter + 1 + # send a mesage to destination_node from my_node + rfm9x.send( + bytes( + "message number {} from node {}".format(counter, rfm9x.node), "UTF-8" + ), + keep_listening=True, + ) + button_pressed = None diff --git a/examples/rfm9x_node1_ack.py b/examples/rfm9x_node1_ack.py new file mode 100644 index 0000000..8ab3434 --- /dev/null +++ b/examples/rfm9x_node1_ack.py @@ -0,0 +1,64 @@ +# Example to send a packet periodically between addressed nodes with ACK +# Author: Jerry Needell +# +import time +import board +import busio +import digitalio +import adafruit_rfm9x + +# set the time interval (seconds) for sending packets +transmit_interval = 10 + +# 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. +# set GPIO pins as necessary -- this example is for Raspberry Pi +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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# enable CRC checking +rfm9x.enable_crc = True +# set delay before sending ACK +rfm9x.ack_delay = 0.1 +# set node addresses +rfm9x.node = 1 +rfm9x.destination = 2 +# initialize counter +counter = 0 +ack_failed_counter = 0 +# send startup message from my_node +rfm9x.send_with_ack(bytes("startup message from node {}".format(rfm9x.node), "UTF-8")) + +# Wait to receive packets. +print("Waiting for packets...") +# initialize flag and timer +time_now = time.monotonic() +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_ack=True, with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("RSSI: {0}".format(rfm9x.last_rssi)) + # send reading after any packet received + if time.monotonic() - time_now > transmit_interval: + # reset timeer + time_now = time.monotonic() + counter += 1 + # send a mesage to destination_node from my_node + if not rfm9x.send_with_ack( + bytes("message from node node {} {}".format(rfm9x.node, counter), "UTF-8") + ): + ack_failed_counter += 1 + print(" No Ack: ", counter, ack_failed_counter) diff --git a/examples/rfm9x_node1_bonnet.py b/examples/rfm9x_node1_bonnet.py new file mode 100644 index 0000000..74bcfe7 --- /dev/null +++ b/examples/rfm9x_node1_bonnet.py @@ -0,0 +1,124 @@ +# Example to send a packet periodically between addressed nodes +# Author: Jerry Needell +# +import board +import busio +import digitalio + +# Import the SSD1306 module. +import adafruit_ssd1306 +import adafruit_rfm9x + +# Button A +btnA = digitalio.DigitalInOut(board.D5) +btnA.direction = digitalio.Direction.INPUT +btnA.pull = digitalio.Pull.UP + +# Button B +btnB = digitalio.DigitalInOut(board.D6) +btnB.direction = digitalio.Direction.INPUT +btnB.pull = digitalio.Pull.UP + +# Button C +btnC = digitalio.DigitalInOut(board.D12) +btnC.direction = digitalio.Direction.INPUT +btnC.pull = digitalio.Pull.UP + +# Create the I2C interface. +i2c = busio.I2C(board.SCL, board.SDA) + +# 128x32 OLED Display +reset_pin = digitalio.DigitalInOut(board.D4) +display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin) +# Clear the display. +display.fill(0) +display.show() +width = display.width +height = display.height + + +# set the time interval (seconds) for sending packets +transmit_interval = 10 + +# 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. +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 + +# Attempt to set up the rfm9x Module +try: + rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + display.text("rfm9x: Detected", 0, 0, 1) +except RuntimeError: + # Thrown on version mismatch + display.text("rfm9x: ERROR", 0, 0, 1) + +display.show() + +# enable CRC checking +rfm9x.enable_crc = True + +# set node addresses +rfm9x.node = 1 +rfm9x.destination = 2 +# initialize counter +counter = 0 +# send a broadcast message from my_node with ID = counter +rfm9x.send( + bytes("Startup message {} from node {}".format(counter, rfm9x.node), "UTF-8") +) + +# Wait to receive packets. +print("Waiting for packets...") +button_pressed = None +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("Received RSSI: {0}".format(rfm9x.last_rssi)) + # Check buttons + if not btnA.value: + button_pressed = "A" + # Button A Pressed + display.fill(0) + display.text("AAA", width - 85, height - 7, 1) + display.show() + if not btnB.value: + button_pressed = "B" + # Button B Pressed + display.fill(0) + display.text("BBB", width - 75, height - 7, 1) + display.show() + if not btnC.value: + button_pressed = "C" + # Button C Pressed + display.fill(0) + display.text("CCC", width - 65, height - 7, 1) + display.show() + # send reading after any button pressed + if button_pressed is not None: + counter = counter + 1 + # send a mesage to destination_node from my_node + rfm9x.send( + bytes( + "message number {} from node {} button {}".format( + counter, rfm9x.node, button_pressed + ), + "UTF-8", + ), + keep_listening=True, + ) + button_pressed = None diff --git a/examples/rfm9x_node2.py b/examples/rfm9x_node2.py new file mode 100644 index 0000000..c3724cd --- /dev/null +++ b/examples/rfm9x_node2.py @@ -0,0 +1,60 @@ +# Example to send a packet periodically between addressed nodes +# Author: Jerry Needell +# +import time +import board +import busio +import digitalio +import adafruit_rfm9x + +# 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. +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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# enable CRC checking +rfm9x.enable_crc = True +# set node addresses +rfm9x.node = 2 +rfm9x.destination = 1 +# initialize counter +counter = 0 +# send a broadcast message from my_node with ID = counter +rfm9x.send(bytes("startup message from node {} ".format(rfm9x.node), "UTF-8")) + +# Wait to receive packets. +print("Waiting for packets...") +# initialize flag and timer +time_now = time.monotonic() +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("Received RSSI: {0}".format(rfm9x.last_rssi)) + # send reading after any packet received + counter = counter + 1 + # after 10 messages send a response to destination_node from my_node with ID = counter&0xff + if counter % 10 == 0: + time.sleep(0.5) # brief delay before responding + rfm9x.identifier = counter & 0xFF + rfm9x.send( + bytes( + "message number {} from node {} ".format(counter, rfm9x.node), + "UTF-8", + ), + keep_listening=True, + ) diff --git a/examples/rfm9x_node2_ack.py b/examples/rfm9x_node2_ack.py new file mode 100644 index 0000000..8c026f4 --- /dev/null +++ b/examples/rfm9x_node2_ack.py @@ -0,0 +1,55 @@ +# Example to receive addressed packed with ACK and send a response +# Author: Jerry Needell +# +import time +import board +import busio +import digitalio +import adafruit_rfm9x + +# 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. +# set GPIO pins as necessary - this example is for Raspberry Pi +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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# enable CRC checking +rfm9x.enable_crc = True +# set delay before transmitting ACK (seconds) +rfm9x.ack_delay = 0.1 +# set node addresses +rfm9x.node = 2 +rfm9x.destination = 1 +# initialize counter +counter = 0 +ack_failed_counter = 0 + +# Wait to receive packets. +print("Waiting for packets...") +while True: + # Look for a new packet: only accept if addresses to my_node + packet = rfm9x.receive(with_ack=True, with_header=True) + # 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("Received (raw header):", [hex(x) for x in packet[0:4]]) + print("Received (raw payload): {0}".format(packet[4:])) + print("RSSI: {0}".format(rfm9x.last_rssi)) + # send response 2 sec after any packet received + time.sleep(2) + counter += 1 + # send a mesage to destination_node from my_node + if not rfm9x.send_with_ack( + bytes("response from node {} {}".format(rfm9x.node, counter), "UTF-8") + ): + ack_failed_counter += 1 + print(" No Ack: ", counter, ack_failed_counter) diff --git a/examples/rfm9x_rpi_interrupt.py b/examples/rfm9x_rpi_interrupt.py index 6943c0c..384cdb9 100644 --- a/examples/rfm9x_rpi_interrupt.py +++ b/examples/rfm9x_rpi_interrupt.py @@ -24,7 +24,7 @@ def rfm9x_callback(rfm9x_irq): # Print out the raw bytes of the packet: print("Received (raw bytes): {0}".format(packet)) print([hex(x) for x in packet]) - print("RSSI: {0}".format(rfm9x.rssi)) + print("RSSI: {0}".format(rfm9x.last_rssi)) # Define radio parameters. diff --git a/examples/rfm9x_rpi_simpletest.py b/examples/rfm9x_rpi_simpletest.py new file mode 100644 index 0000000..558147d --- /dev/null +++ b/examples/rfm9x_rpi_simpletest.py @@ -0,0 +1,65 @@ +# Simple demo of sending and recieving data with the RFM95 LoRa radio. +# Author: Tony DiCola +import board +import busio +import digitalio + +import adafruit_rfm9x + + +# 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 +rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) + +# Note that the radio is configured in LoRa mode so you can't control sync +# word, encryption, frequency deviation, or other settings! + +# You can however adjust the transmit power (in dB). The default is 13 dB but +# high power radios like the RFM95 can go up to 23 dB: +rfm9x.tx_power = 23 + +# Send a packet. Note you can only send a packet up to 252 bytes in length. +# This is a limitation of the radio packet size, so if you need to send larger +# amounts of data you will need to break it into smaller send calls. Each send +# call will wait for the previous one to finish before continuing. +rfm9x.send(bytes("Hello world!\r\n", "utf-8")) +print("Sent Hello World message!") + +# Wait to receive packets. Note that this library can't receive data at a fast +# rate, in fact it can only receive and process one 252 byte packet at a time. +# This means you should only use this for low bandwidth scenarios, like sending +# and receiving a single message at a time. +print("Waiting for packets...") + +while True: + packet = rfm9x.receive() + # Optionally change the receive timeout from its default of 0.5 seconds: + # packet = rfm9x.receive(timeout=5.0) + # If no packet was received during the timeout then None is returned. + if packet is None: + # Packet has not been received + print("Received nothing! Listening again...") + else: + # Received a packet! + # Print out the raw bytes of the packet: + print("Received (raw bytes): {0}".format(packet)) + # And decode to ASCII text and print it too. Note that you always + # receive raw bytes and need to convert to a text format like ASCII + # if you intend to do string processing on your data. Make sure the + # sending side is sending ASCII data before you try to decode! + packet_text = str(packet, "ascii") + print("Received (ASCII): {0}".format(packet_text)) + # Also read the RSSI (signal strength) of the last received message and + # print it. + rssi = rfm9x.last_rssi + print("Received signal strength: {0} dB".format(rssi)) diff --git a/examples/rfm9x_simpletest.py b/examples/rfm9x_simpletest.py index 9f63a3a..f7001e8 100644 --- a/examples/rfm9x_simpletest.py +++ b/examples/rfm9x_simpletest.py @@ -71,5 +71,5 @@ print("Received (ASCII): {0}".format(packet_text)) # Also read the RSSI (signal strength) of the last received message and # print it. - rssi = rfm9x.rssi + rssi = rfm9x.last_rssi print("Received signal strength: {0} dB".format(rssi)) From a8ae892dead11fab578e9f4b8f7870d5ee3bf73b Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Mon, 13 Apr 2020 09:04:46 -0400 Subject: [PATCH 6/7] fix a few comments --- adafruit_rfm9x.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index 35ba5a0..4bf1b39 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -280,8 +280,8 @@ class RFM9x: 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. Features like addressing and guaranteed - delivery need to be implemented at an application level. + to match RadioHead's defaults and assumes that each packet contains a + 4 byte header compatible with RadioHead's implementation. """ # Global buffer for SPI commands @@ -793,10 +793,10 @@ def send( return not timed_out def send_with_ack(self, data): - """Reliabe Datagram mode: + """Reliable Datagram mode: Send a packet with data and wait for an ACK response. The packet header is automatically generated. - If enabled, the packet tranmsiion will be retried on failure + If enabled, the packet transmission will be retried on failure """ if self.ack_retries: retries_remaining = self.ack_retries @@ -839,7 +839,7 @@ def receive( 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 A 4-byte header for compatibilty with the + 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. From b1c287b8b43559bebda52b7a9ef980feeff0e561 Mon Sep 17 00:00:00 2001 From: jerryneedell Date: Tue, 14 Apr 2020 05:59:18 -0400 Subject: [PATCH 7/7] add to initial docstring --- adafruit_rfm9x.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index 4bf1b39..fc33388 100644 --- a/adafruit_rfm9x.py +++ b/adafruit_rfm9x.py @@ -282,6 +282,10 @@ class RFM9x: 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