diff --git a/adafruit_rfm9x.py b/adafruit_rfm9x.py index 89c5501..fc33388 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 @@ -231,6 +232,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 @@ -273,15 +280,16 @@ 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. + 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 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 @@ -397,6 +405,65 @@ def __init__( self._write_u8(_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 + # 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 instantaneous 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 + 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. + """ + + def _configure_radio(self): # Defaults set modem config to RadioHead compatible Bw125Cr45Sf128 mode. self.signal_bandwidth = 125000 self.coding_rate = 5 @@ -405,11 +472,7 @@ def __init__( 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. + # Set transmit power to 13 dBm, a safe value any module supports. self.tx_power = 13 # pylint: disable=no-member @@ -661,19 +724,25 @@ def enable_crc(self, val): def send( self, data, - timeout=2.0, + *, keep_listening=False, - tx_header=(_RH_BROADCAST_ADDRESS, _RH_BROADCAST_ADDRESS, 0, 0), + 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 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 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 @@ -681,20 +750,33 @@ def send( # 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)" # 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 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 kwarg + payload[3] = flags + 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 @@ -702,7 +784,7 @@ def send( 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: @@ -712,74 +794,138 @@ def send( 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): + """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 + 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, - timeout=0.5, - keep_listening=True, - with_header=False, - rx_filter=_RH_BROADCAST_ADDRESS, + self, *, keep_listening=True, with_header=False, with_ack=False, timeout=None ): - """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 + """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 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) + # Clear interrupt. + self._write_u8(_RH_RF95_REG_12_IRQ_FLAGS, 0xFF) + if fifo_length < 5: + packet = None + else: if ( - rx_filter != _RH_BROADCAST_ADDRESS + self.node != _RH_BROADCAST_ADDRESS and packet[0] != _RH_BROADCAST_ADDRESS - and packet[0] != rx_filter + 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, + 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 ( + 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: 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))