RadioHead Header
Every packet transmitted via the CircuitPython RFM9x Library contains a 4 byte header compatible with the Arduino RadioHead Library.
https://www.airspayce.com/mikem/arduino/RadioHead/classRHGenericDriver.html
Each message sent and received by a RadioHead driver includes 4 headers:
- TO - the node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)
- FROM -the node address of the sending node
- ID - a message ID, distinct (over short time scales) for each message sent by a particular node
- FLAGS - a bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least significant 4 bits are reserved for applications.
For basic usage, the TO and FROM addresses are set to "Broadcast" with a value of 255 (0xff) and the ID and FLAGS parameters are ignored. With these settings any packet received will be accepted.
Node Addressing
In many applications, it will be desirable to limit packets received to those originating at specified nodes and to direct responses to particular nodes. This can be controlled by setting the "node" and "destination" attributes for the rfm9x instance created in your CircuitPython code. https://circuitpython.readthedocs.io/projects/rfm9x/en/latest/api.html
This example demonstrates the usage of the RadioHead Header settings to specify the address of the transmitting and receiving nodes.
This script is to be run on the board designated as Node 1. It will transmit a packet to Node 2 every 10 seconds
# 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.RFM9X_CS) RESET = digitalio.DigitalInOut(board.RFM9X_RST) # 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, )
This other script is to be run on the board designated as Node 2. It will report the packets received from Node 1 and send a reply to Node 1 after every 10th packet received.
# 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.RFM9X_CS) RESET = digitalio.DigitalInOut(board.RFM9X_RST) # 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, )
Reliable Datagram
During basic transmissions or the above address examples, there is no way to confirm if a packet was actually received by the destination node.
The "Reliable Datagram" mode attempts to provide that reassurance.
When invoked, this mode requires that every addressed packet (non-Broadcast) be acknowledged by sending a specially formatted ACK packet in response as described for the RadioHead Library https://www.airspayce.com/mikem/arduino/RadioHead/classRHReliableDatagram.html
An ack consists of a message with:
- TO set to the from address of the original message
- FROM set to this node address
- ID set to the ID of the original message
- FLAGS with the RH_FLAGS_ACK bit set
- 1 octet of payload containing ASCII '!' (since some drivers cannot handle 0 length payloads)
Instead of the standard send()
function use the send_with_ack()
function. this configures the sending program to send the packet then wait for the responding ACK packet.
At the receiving node, the extra parameter with_ack=True
must be passed to the receive()
function so it generates the ACK packet in response.
This example sets up a "Reliable Datagram" exchange between Nodes 1 and 2. Node 1 sends a packet to Node 2 every 10 seconds and waits for an ACK.
This script is executed by Node 1
# 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.RFM9X_CS) RESET = digitalio.DigitalInOut(board.RFM9X_RST) # 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)
This one is executed by Node 2. It acknowledges each packet and sends a response packet after a 2 second delay. Node 1 will acknowledge the response packet.
# 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.RFM9X_CS) RESET = digitalio.DigitalInOut(board.RFM9X_RST) # 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)
CRC Checking
The above examples also enable CRC checking.
if a packet fails the CRC check, it will be ignored. A warning is presented to the REPL if it is open, but the packet is not returned. This helps avoid having to deal with corrupted packets. When used with the "Reliable DataGram" mode, the packet will not be ACK'ed so it will be resent.
# enable CRC checking rfm9x.enable_crc = True
If a CRC error occurs, it is reported to the REPL as a warning - no Exception is generated.
UserWarning: CRC error, packet ignored
warn("CRC error, packet ignored")
Page last edited May 08, 2024
Text editor powered by tinymce.