This guide is deprecated and is no longer possible! Single-channel packet forwarders no longer work after the Things Network migration to The Things Stack v3. For more information about this decision, visit: https://www.thethingsnetwork.org/forum/t/single-channel-packet-forwarders-scpf-are-deprecated-and-not-supported/31117
Do not use this code if you have a RFM69HCW Radio

Now that we've set up our Things Network application and device, we're going to move on to installing TinyLoRa onto our Raspberry Pi. To do this, enter the following into your terminal to install the library system-wide:

sudo pip3 install adafruit-circuitpython-tinylora

Code

Unlike sending data to another device, we're going to be sending data from our device (the Pi) to a gateway (check the gateway map to find one near you).

While we don't have any sensors hooked up to our radio, we'll send the Raspberry Pi's CPU utilization to The Things Network.

Below is an example of using TinyLoRa to send data to The Things Network. Save this as radio_lorawan.py on your Raspberry Pi. 

# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
Example for using the RFM9x Radio with Raspberry Pi and LoRaWAN

Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi
Author: Brent Rubell for Adafruit Industries
"""
import threading
import time
import subprocess
import busio
from digitalio import DigitalInOut, Direction, Pull
import board
# Import thte SSD1306 module.
import adafruit_ssd1306
# Import Adafruit TinyLoRa
from adafruit_tinylora.adafruit_tinylora import TTN, TinyLoRa

# 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
reset_pin = 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

# TinyLoRa Configuration
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
cs = DigitalInOut(board.CE1)
irq = DigitalInOut(board.D22)
rst = DigitalInOut(board.D25)

# TTN Device Address, 4 Bytes, MSB
devaddr = bytearray([0x00, 0x00, 0x00, 0x00])
# TTN Network Key, 16 Bytes, MSB
nwkey = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# TTN Application Key, 16 Bytess, MSB
app = bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
# Initialize ThingsNetwork configuration
ttn_config = TTN(devaddr, nwkey, app, country='US')
# Initialize lora object
lora = TinyLoRa(spi, cs, irq, rst, ttn_config)
# 2b array to store sensor data
data_pkt = bytearray(2)
# time to delay periodic packet sends (in seconds)
data_pkt_delay = 5.0


def send_pi_data_periodic():
    threading.Timer(data_pkt_delay, send_pi_data_periodic).start()
    print("Sending periodic data...")
    send_pi_data(CPU)
    print('CPU:', CPU)

def send_pi_data(data):
    # Encode float as int
    data = int(data * 100)
    # Encode payload as bytes
    data_pkt[0] = (data >> 8) & 0xff
    data_pkt[1] = data & 0xff
    # Send data packet
    lora.send_data(data_pkt, len(data_pkt), lora.frame_counter)
    lora.frame_counter += 1
    display.fill(0)
    display.text('Sent Data to TTN!', 15, 15, 1)
    print('Data sent!')
    display.show()
    time.sleep(0.5)

while True:
    packet = None
    # draw a box to clear the image
    display.fill(0)
    display.text('RasPi LoRaWAN', 35, 0, 1)

    # read the raspberry pi cpu load
    cmd = "top -bn1 | grep load | awk '{printf \"%.1f\", $(NF-2)}'"
    CPU = subprocess.check_output(cmd, shell = True )
    CPU = float(CPU)

    if not btnA.value:
        # Send Packet
        send_pi_data(CPU)
    if not btnB.value:
        # Display CPU Load
        display.fill(0)
        display.text('CPU Load %', 45, 0, 1)
        display.text(str(CPU), 60, 15, 1)
        display.show()
        time.sleep(0.1)
    if not btnC.value:
        display.fill(0)
        display.text('* Periodic Mode *', 15, 0, 1)
        display.show()
        time.sleep(0.5)
        send_pi_data_periodic()


    display.show()
    time.sleep(.1)

You'll also want to download the font file, font5x8.bin, and copy it into the same directory as the script:

Setting up the code for The Things Network

While we can send data to The Things Network, and our gateway might notice it, it isn't registered to a device yet. To register your device with The Things Network using ABP, you'll need to set three unique identifiers in radio_lorawan.py: the Network Session Key, the Device Address, and the Application Session Key.

Navigate to the Device Overview page for your Raspberry Pi device.

 

Make sure the Activation Method is set to ABP

Before adding the unique identifiers to our code, we'll need to first expand them by clicking the <> icon.

These are your keys. We're going to enter them into our code, but we need to be careful - the keys on the Things Network console use parentheses or curly braces { } instead of brackets [ ]

First, copy the Device Address from the TTN console to the devaddr variable in the code. 

Then, remove the braces { } from the device address.

A device address copied from The Things Network console would look like: { 0x26, 0x02, 0x1F, 0x07 }. In the code, it'd look like: devaddr = bytearray([0x26, 0x02, 0x1F, 0x07]).

Then, copy the Network Session Key from the TTN console to the nwkey variable in the code. Make sure to modify the code to remove the parentheses/curly braces { }. 

Finally, copy the Application Session Key from the TTN console to the app variable in the code. Make sure to modify the code to remove the parentheses/curly braces { }. 

That's all for now - we're ready to run our code! 

Usage

Enter the following into the terminal and press enter:

python3 radio_lorawan.py

The OLED should display the operating mode (LoRaWAN).

Press Button A to send this data to the The Things Network application you built earlier.

Press Button B to view the Raspberry Pi's CPU utilization percentage.

Pressing Button C will periodically send a packet to the things network. You can modify this value (in seconds) by changing the data_pkt_delay variable in the code.

Checking Data on The Things Network Console

We want to make sure the data has been received on the other end. To do this, we'll navigate to the The Things Network Console and select the application. From the menu on the right hand side of the page, click Data.

If everything worked correctly, you'll see the payload from your device streaming into the page, in real time. 

Neat, right? But while we received a payload, we still don't understand what it means. It's been sent to The Things Network and decoded on the client (Gateway) side, so it's not an AES-encrypted payload anymore. It's just not readable by humans.

Decoding the Payload

If you're sending packets in strange formats or encodings (like we are!), The Things Network Console has a programmable data decoder to decode the packets, and assign useful labels to the data.

Copy and paste the decoder script below into the decoder's integrated text editor and click save

// TinyLoRa - Raspberry Pi CPU Load Decoder
function Decoder(bytes, port) {
  var decoded = {};

  // Decode bytes to int
  var CPU_Load = (bytes[0] << 8) | bytes[1];

  // Decode int to float
  decoded.CPU_Load = CPU_Load / 100;

  return decoded;
}

Then, click the data tab. Next to the raw payload data, you should see the decoded data for the CPU load.

This guide was first published on Jan 18, 2019. It was last updated on Jan 18, 2019.

This page (Usage) was last updated on Sep 28, 2023.

Text editor powered by tinymce.