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/rfm69/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_rfm69


# 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
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set node addresses
rfm69.node = 1
rfm69.destination = 2
# initialize counter
counter = 0
# send a broadcast message from my_node with ID = counter
rfm69.send(
    bytes("Startup message {} from node {}".format(counter, rfm69.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 = rfm69.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(rfm69.last_rssi))
    if time.monotonic() - now > transmit_interval:
        now = time.monotonic()
        counter = counter + 1
        # send a  mesage to destination_node from my_node
        rfm69.send(
            bytes(
                "message number {} from node {}".format(counter, rfm69.node), "UTF-8"
            ),
            keep_listening=True,
        )

This 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_rfm69

# 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
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set node addresses
rfm69.node = 2
rfm69.destination = 1
# initialize counter
counter = 0
# send a broadcast message from my_node with ID = counter
rfm69.send(bytes("startup message from node {} ".format(rfm69.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 = rfm69.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(rfm69.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
            rfm69.identifier = counter & 0xFF
            rfm69.send(
                bytes(
                    "message number {} from node {} ".format(counter, rfm69.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_rfm69

# 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
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set delay before sending ACK
rfm69.ack_delay = 0.1
# set node addresses
rfm69.node = 1
rfm69.destination = 2
# initialize counter
counter = 0
ack_failed_counter = 0
# send startup message from my_node
rfm69.send_with_ack(bytes("startup message from node {}".format(rfm69.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 = rfm69.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(rfm69.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 rfm69.send_with_ack(
            bytes("message from node node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)

And 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_rfm69

# 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
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set delay before transmitting ACK (seconds)
rfm69.ack_delay = 0.1
# set node addresses
rfm69.node = 2
rfm69.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 = rfm69.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(rfm69.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 rfm69.send_with_ack(
            bytes("response from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)

This guide was first published on Apr 15, 2016. It was last updated on Oct 24, 2023.

This page (Advanced CircuitPython RFM69 Library Usage) was last updated on May 26, 2020.

Text editor powered by tinymce.