Feather Code Usage

We'll want to start by loading the code onto the Feather sensor nodes. To do this, copy the code below and rename it as code.py.


CircuitPython LoRa Device w/BME280
import time
import busio
import digitalio
import board

# Import LoRa Library
import adafruit_rfm9x

# Import BME280 Sensor Library
import adafruit_bme280

# Device ID

# Delay between sending radio data, in minutes.

# Create library object using our Bus I2C port
i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)

# Define radio frequency, MUST match gateway frequency.

# Define pins connected to the chip, use these if wiring up the breakout according to the guide:
# pylint: disable=c-extension-no-member
CS = digitalio.DigitalInOut(board.RFM9X_CS)
# pylint: disable=c-extension-no-member
RESET = digitalio.DigitalInOut(board.RFM9X_RST)

# Define the onboard LED
LED = digitalio.DigitalInOut(board.D13)
LED.direction = digitalio.Direction.OUTPUT

# 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)

# Set transmit power to max
rfm9x.tx_power = 23

# sensor data
bme280_data = bytearray(8)

while True:
    # Get sensor readings
    temp_val = int(bme280.temperature * 100)
    print("\nTemperature: %0.1f C" % bme280.temperature)
    humid_val = int(bme280.humidity * 100)
    print("Humidity: %0.1f %%" % bme280.humidity)
    pres_val = int(bme280.pressure * 100)
    print("Pressure: %0.1f hPa" % bme280.pressure)

    # Build packet with float data and headers

    # packet header with feather node ID
    bme280_data[0] = FEATHER_ID
    # Temperature data
    bme280_data[1] = (temp_val >> 8) & 0xff
    bme280_data[2] = temp_val & 0xff

    # Humid data
    bme280_data[3] = (humid_val >> 8) & 0xff
    bme280_data[4] = humid_val & 0xff

    # Pressure data
    bme280_data[5] = (pres_val >> 16) & 0xff
    bme280_data[6] = (pres_val >> 8) & 0xff
    bme280_data[7] = pres_val & 0xff

    # Convert bytearray to bytes
    bme280_data_bytes = bytes(bme280_data)
    # Send the packet data
    print('Sending data...')
    LED.value = True
    print('Sent data!')
    LED.value = False

    # Wait to send the packet again
    time.sleep(SENSOR_SEND_DELAY * 60)

We'll also want to set the frequency which we are transmitting on. This value must be the same between the Feathers (nodes) and the Raspberry Pi (gateway), otherwise the gateway will not receive the packet, or visa-versa. 

To do this, set RADIO_FREQ_MHZ to a frequency value in MHz. For example, I'll set it to 905.5MHz.

Download: file
# Define radio frequency, must match device.

We also need a way for the gateway to identify each Feather. To do this, we'll set the FEATHER_ID. If you're preparing the first sensor node, leave this variable alone as its already set to 1.

Download: file
# Device ID

Save the file (CTRL/CMD+S). This will reload and run the code from code.py.

Using the Mu editor, there is a built in REPL/Serial monitor. This is the recommended way to view output from your Python code. Alternatively you can use a terminal program to connect to the Feather's USB serial port.

You should see the following output from the REPL:

Download: file
code.py output:
Temperature: 23.8 C
Humidity: 21.5 %
Pressure: 1005.2 hPa
Sending data..
Data sent!

Whenever the Feather node(s) send data using the radio, they'll blink an onboard LED (located on Pin #13) to show that the packet has been sent.

Feather Code Overview

One of the advantages of using LoRa over another type of transport (like WiFi) is that the radio is only turned on during transmission. Doing this reduces the amount of power consumption by the node.

We're going to build a packet to transmit the data to the gateway. This packet comprises of 7 bytes (the RFM9x library currently supports sending packets <= 252 bytes in length) which will hold all of our required sensor data, along with a unique identifier for our node, set in the code as FEATHER_ID.

The temperature values read by the BME280 are floating point (decimal) values (this is a value like 3.14 instead of 3). To send these values, we'll assemble the packet by sending the values as integers (which will later be re-assembled by the gateway)

Once assembled, the LoRa packet is sent using the radio. 

Download: file

To modify how frequently the packet is sent, change the variable SENSOR_SEND_DELAY, keeping in mind that the delay is in minutes.

That's it! Then, load the code onto the other feather(s) sensor nodes, changing the FEATHER_ID as you go. 

Pi Code Usage

Next, we'll want to set up the Raspberry Pi with the code to use the RFM9x as a LoRa gateway. Save the code below on your Pi as lora_gateway.py.

Adafruit IO LoRa Gateway

Learn Guide: https://learn.adafruit.com/multi-device-lora-temperature-network

by Brent Rubell for Adafruit Industries
# Import Python System Libraries
import time

# Import Adafruit IO REST client.
from Adafruit_IO import Client

# Import Blinka Libraries
import busio
import board
from digitalio import DigitalInOut, Direction, Pull

# Import SSD1306 module.
import adafruit_ssd1306

# Import RFM9x module
import adafruit_rfm9x

# Define radio frequency, must match device.

# Button A
btnA = DigitalInOut(board.D5)
btnA.direction = Direction.INPUT
btnA.pull = Pull.UP

# Button B
btnB = DigitalInOut(board.D6)
btnB.direction = Direction.INPUT
btnB.pull = Pull.UP

# Button C
btnC = DigitalInOut(board.D12)
btnC.direction = Direction.INPUT
btnC.pull = Pull.UP

# Create the I2C interface.
i2c = busio.I2C(board.SCL, board.SDA)

# 128x32 OLED Display
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x3c)
# Clear the display.
width = display.width
height = display.height

# Configure LoRa Radio
CS = DigitalInOut(board.CE1)
RESET = DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
prev_packet = None

# Set to your Adafruit IO username.
# (go to https://accounts.adafruit.com to find your username)

# Set to your Adafruit IO key.

# Create an instance of the REST client.

# Set up Adafruit IO feeds
temperature_feed_1 = aio.feeds('feather-1-temp')
humidity_feed_1 = aio.feeds('feather-1-humid')
pressure_feed_1 = aio.feeds('feather-1-pressure')

temperature_feed_2 = aio.feeds('feather-2-temp')
humidity_feed_2 = aio.feeds('feather-2-humid')
pressure_feed_2 = aio.feeds('feather-2-pressure')

def pkt_int_to_float(pkt_val_1, pkt_val_2, pkt_val_3=None):
    """Convert packet data to float.
    if pkt_val_3 is None:
        float_val = pkt_val_1 << 8 | pkt_val_2
        float_val = pkt_val_1 << 16 | pkt_val_2 << 8 | pkt_val_3
    return float_val/100

while True:
    packet = None
    # draw a box to clear the image
    display.text('Adafruit.IO LoRa GTWY', 0, 0, 1)

    # check for packet rx
    packet = rfm9x.receive()
    if packet is None or prev_packet:
        display.text('- Waiting for PKT -', 10, 20, 1)
        prev_packet = packet
        print('> New Packet!')
        # Decode packet
        temp_val = pkt_int_to_float(packet[1], packet[2])
        humid_val = pkt_int_to_float(packet[3], packet[4])
        pres_val = pkt_int_to_float(packet[5], packet[6], packet[7])

        # Display packet information
        print('Device ID: LoRa Feather #', packet[0])
        print("Temp: %0.2f C" % temp_val)
        print("Humid: %0.2f %% " % humid_val)
        print("Pressure: %0.2f hPa" % pres_val)

        # Send to Feather 1 feeds
        if packet[0] == 0x01:
            display.text('Feather #1 Data RX''d!', 15, 0, 1)
            display.text('Sending to IO...', 0, 20, 1)
            aio.send(temperature_feed_1.key, temp_val)
            aio.send(humidity_feed_1.key, humid_val)
            aio.send(pressure_feed_1.key, pres_val)
            display.text('Sent!', 100, 20, 1)
        # Send to Feather 2 feeds
        if packet[0] == 0x02:
            display.text('Feather #2 Data RX''d!', 15, 0, 1)
            display.text('Sending to IO...', 0, 20, 1)
            aio.send(temperature_feed_2.key, temp_val)
            aio.send(humidity_feed_2.key, humid_val)
            aio.send(pressure_feed_2.key, pres_val)
            display.text('Sent!', 100, 20, 1)


We'll need to install the Adafruit IO Python library to forward the messages by the LoRa radio to Adafruit IO. To do this, enter the following in your terminal:

sudo pip3 install adafruit-io

Once the Adafruit IO library is installed, we'll need to set the username and the key in the code:

Set the ADAFRUIT_IO_USERNAME to your Adafruit IO Username.

Then, set the ADAFRUIT_IO_KEY to your Adafruit IO Key.

And save the file.

Now that the Adafruit IO username and password are configured, run the code by typing the following into your terminal:

python3 rfm9x_gateway.py

The display should show Adafruit IO GTWY and - Waiting for PKT -. This is the idle state, the gateway is listening for a new packet from the Feather node.

When a new packet is received, the display will change to show that a new packet from the feather was received. It'll also forward the packet to Adafruit IO.

The terminal will change when a new packet is received:

Download: file
- New Packet! -
Device ID: LoRa Feather # 1
Temp: 22.83 C
Humid: 17.90 %
Pressure: 370.11 hPa
- New Packet! -
Device ID: LoRa Feather # 2
Temp: 22.89 C
Humid: 10.48 %
Pressure: 370.11 hPa

To verify Adafruit IO has received the data, navigate to the monitor page on Adafruit IO. This page shows a live-view of all incoming data (and error messages). 

Your Adafruit IO Dashboard should look like the following as it receives data from the LoRa devices:

If you want this code to run when the Pi boots up, you'll need to make an entry in rc.local.

To do this, type the following into the terminal

sudo nano /etc/rc.local

Then, add the following line of code the file

(sleep 30; python3 home/pi/rfm9x_gateway.py) &

and save the file.

Reboot the Pi by entering the following into the terminal

sudo shutdown -r now

When the Pi restarts, it'll run the gateway code.

Congrats! You've built your first LoRa network, and forwarded data to the internet.

Code Overview

Since the network topology can seem complicated at a glance, we'll take it piece by piece and explain how the code relates.

First, the gateway receives a new packet.

Download: file
packet = rfm9x.receive()

Then, it checks if the packet's value is empty or a retransmission. If it's a new packet value, it'll decode the packet.

Since we converted the float sensor value into an integer for transmission, and know the order of the packet, we can decode the temperature, humidity and air pressure. We also know the first byte of the packet (the header) is the the Feather's unique identifier. 

Download: file
# Decode packet
feather_id = packet[0]
temp_val = pkt_int_to_float(packet[1], packet[2])
humid_val = pkt_int_to_float(packet[3], packet[4])
pres_val = pkt_int_to_float(packet[5], packet[6])

Now that the data has been decoded, we're going to tackle the second role of the Radio Bonnet - sending the data as a transmission to Adafruit IO.

If the decoded feather_id is 1, we'll send  the values to Adafruit IO feeds temperature_feed_1humidity_feed_1, and pressure_feed_1.

Download: file
if feather_id is 1:
  display.text('Feather #1 Data RX''d!', 15, 0, 1)
  display.text('Sending to IO...', 0, 20, 1)
  aio.send(temperature_feed_1.key, temp_val)
  aio.send(humidity_feed_1.key, humid_val)
  aio.send(pressure_feed_1.key, pres_val)
  display.text('Sent!', 100, 20, 1)

We have code for detecting a second feather, but if you have a third (or fourth!), you'll want to add another elif statement identical to the second one.

This guide was first published on Feb 05, 2019. It was last updated on Feb 05, 2019.
This page (Using with Adafruit IO) was last updated on Oct 24, 2020.