In our two previous Pycom articles we covered Building a LoRaWAN Nano Gateway to The Things Network, and creating Your First LoRaWAN Node on The Things Network. In these articles we used the mighty powerful Pycom LoPy (part of the Pycom range) to build two nodes of a LoRaWAN network connected to The Things Network. This gave us full use of LoRaWAN's features including its incredible range and frugal power consumption. But we didn't close the loop; we couldn't take information from one device, make a decision based on that information, then tell other devices what to do. For this ability to coordinate devices we need a different kind of tool, one that "plugs in" to TTN, a tool like Node-RED.
Node-RED calls itself "flow-based programming for the Internet of Things". If you've never heard of Node-RED, jump to their home page and watch an introductory video. The method of connecting blocks into a graphical flow diagram looks simple, but it is POWERFUL! Connecting to our LoRaWAN devices via The Things Network is only one of many, many features. Want to control your devices based on the weather forecast? Sure. Want to track the location of something using a mapping site? Yep. There are no limits!
IoT Changes Everything
As developers and experimenters with IoT, there's a fundamental shift in how devices work. We used to write code on tiny computers to do something useful. If things changed, we usually had to go get the device, change the program and deploy it again.
Using IoT, we leave the control logic up to a platform like Node-RED. A sensor device only has one job to do: measure and report. An actuator device only has one job: make something happen. Now if the system changes, we can go to our control platform (Node-RED) and change how the system works. The devices don't have to be reprogrammed!
The Farmer Scenario
In this tutorial we're going to take what we've developed, one gateway and one node, and build an end-to-end application. Here's an example of how this simple scenario can do some useful work:
A farmer has cattle in a distant paddock, say 10km away. There's no water available to the animals so the farmer installs a trough and fills it from a pump and pipe. The pump runs off a battery and solar panels but has no communications.
In the days before IoT, the farmer rigged the pump to run 10 minutes a day to fill the trough. If anything were to go wrong the cattle would have no source of water so the farmer had to check the trough daily. Usually this was a big waste of time. If the cattle weren't in that paddock, the trough overflowed.
The farmer hears about LoRaWAN and decides to give it a go. She installs a battery powered Trough Monitor and gets an alert on her phone if the monitor detects a low water level. This is great! No wasted trips to the back paddock! The farmer then wonders how to make the pump run only when it needs to. Another LoRaWAN device is installed at the pump and now the system is under closed-loop control. Low water starts the pump. If the water level doesn't rise, the farmer is alerted to go fix the problem.
This is the power of the Internet of Things.
Understanding All the Moving Parts
The Farmer Scenario Diagram
Our simplified "Farmer Scenario" has these parts:
- A LoPy acting as both a sensor (water level measurement) and an actuator (pump controller) and having only LoRa communications
- Another Pycom LoPy acting as a LoRaWAN gateway, connecting LoRa radio messages to The Things Network via the Internet
- The Things Network (web site) as the endpoint for all our LoRaWAN communications. A message coming from any LoRa device will emerge from TTN. A message going back to any LoRa device must be sent through TTN.
- Node-RED running on any computer connected to the Internet for integration of information from all sources, decision making, and sending control messages out to various locations including our LoRa devices.
If we assume we already have the LoRaWAN Nano Gateway and LoRaWAN Node built from the prior tutorials, as well as The Things Network set up with a Gateway, an Application and a Device, then we need to add:
- Changes to the Node code to simulate water level and a pump
NOTE: There's no logic to decide under what conditions to start/stop the pump. Node-RED has control. - Changes to The Things Network Application to convert LoRaWAN's byte data to/from JSON*
- Installing Node-RED on a computer
- Adding The Things Network Node-RED Application Nodes to Node-RED
- Building a flow on Node-RED to control our application
* JSON stands for JavaScript Object Notation. It's a standard way to send and receive information as text, eg: { "humidity": 12, "temperature": 6, "led" : 1 }
New Node Code
Our new code makes our node simulate monitoring a water level and having run/stop control of a water pump. This code replaces the node_abp.py code from Your First LoRaWAN Node on The Things Network.
from network import LoRa import struct import socket import pycom import time LORA_FREQUENCY = 916800000 LORA_NODE_DR = 5 COLOUR_WHITE = 0xFFFFFF COLOUR_BLACK = 0x000000 COLOUR_RED = 0xFF0000 COLOUR_GREEN = 0x00FF00 COLOUR_BLUE = 0x0000FF pycom.heartbeat(False) pycom.rgbled(COLOUR_BLACK) dev_addr = struct.unpack(">l", binascii.unhexlify('AAAAAAAA'))[0] nwk_swkey = binascii.unhexlify('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') app_swkey = binascii.unhexlify('CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC') lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915) for i in range(0, 72): lora.remove_channel(i) lora.add_channel(0, frequency=LORA_FREQUENCY, dr_min=0, dr_max=5) lora.add_channel(1, frequency=LORA_FREQUENCY, dr_min=0, dr_max=5) lora.add_channel(2, frequency=LORA_FREQUENCY, dr_min=0, dr_max=5) lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey)) s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) s.setsockopt(socket.SOL_LORA, socket.SO_DR, LORA_NODE_DR) s.setblocking(False) pump_running = False water_level = 0 empty_level = 0 full_level = 5 def check_downlink_messages(): global pump_running # We need to tell Python to use the variable # defined outside this function downlink_message, port = s.recvfrom(256) # See if a downlink message arrived if not downlink_message: # If there was no message, get out now return if downlink_message[0]: # The first byte is non-zero print("Starting the pump") pump_running = True else: print("Stopping the pump") pump_running = False def send_level(): print('Sending water level: {}'.format(water_level)) uplink_message = bytes([water_level]) s.send(uplink_message) def adjust_water_level(): global water_level # We need to tell Python to use the variable # defined outside this function if pump_running: if water_level < full_level: # Can't be overfilled water_level += 1 # Water level rises else: if water_level > empty_level: # Can't be less than empty water_level -= 1 # Water level drops while(True): # Code at the indent happens every 10 seconds adjust_water_level() send_level() for i in range (10): # 10 x 1 second delay # Code at this indent happens every second check_downlink_messages() if pump_running: # At any time the pump might be stopped pycom.rgbled(COLOUR_GREEN) else: pycom.rgbled(COLOUR_RED) time.sleep(1) # 1 second delay
You might need to adjust these lines:
LORA_FREQUENCY = 916800000 LORA_NODE_DR = 5
Also, you'll need to insert your own security credentials at these lines:
dev_addr = struct.unpack(">l", binascii.unhexlify('AAAAAAAA'))[0] nwk_swkey = binascii.unhexlify('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') app_swkey = binascii.unhexlify('CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC')
The code is as it was, up to the line s.setblocking(False)
. From there I've added four variables, three function definitions (def) and a revised while(True):
loop. I hope the code comments are sufficient to aid understanding.
If your gateway isn't up and running, you need it now. Your node is good to go!
Changes to The Things Network
Check that your gateway is showing connected in The Things Network. With that confirmed, you should be able to jump to the Application you already have and check your device is alive and data is coming in. If not, ensure you can see data from the node appearing in REPL box for the gateway. If there's nothing there you'll need to restart your node/gateway. Once data is visible through the gateway, reset frame counters in the Overview for your device. Just waiting helps too.
Application Data at The Things Network
We want to change the bytes of data coming in (payload: 00) to something more meaningful before it arrives at Node-RED, so on the Application page, click Payload Formats.
Payload Formats at The Things Network
Here we can add some JavaScript code. decoder, converter and validator are run on uplink messages arriving from LoRaWAN devices. encoder is the reverse, it acts on downlink messages headed back to the devices. With only a few lines of code here, we can change the terse binary numbers into lovely, human-readable JSON. Here's the code for each. Paste the code over what's there and click save payload functions at the bottom of the page after each one.
function Decoder(bytes, port) { var decoded = {}; decoded.water_level = bytes[0]; return decoded; } function Converter(decoded, port) { return decoded; } function Validator(converted, port) { // Ditch the data if outside range 0-10 if (converted.water_level > 5 || converted.water_level < 0) { return false; } return true; } function Encoder(object, port) { var bytes = []; if (object.pump_running === 0 || object.pump_running === 1) { bytes[0] = object.pump_running ? 1 : 0; } // There is no separate converter and validator function for this direction. Return an empty array to drop the message. // https://www.thethingsnetwork.org/docs/devices/uno/quick-start.html return bytes; }
With that code in place, you'll see on the Data tab that the nasty byte data is being interpreted as water_level.
Application data being transformed by The Things Network
We're done with TTN. We'll keep it open to check on things, but there shouldn't be any more changes.
Installing Node-RED on Raspberry Pi
If you're into IoT you're likely interested in Raspberry Pi. We're going to run Node-RED on a Raspberry Pi for this article. If you're not into it, no problem, have a look for a tutorial on running Node-RED on a PC/Mac instead. There are hosted services too, like FRED, which has a free option. A Pi is perfect because it's a computer you can leave running permanently for the cost of only a few watts.
Prior to the current release of Raspbian, Node-RED was installed by default. Raspbian 9 (Stretch) lists Node-RED under Recommended Software (Raspbian menu, Preferences, Recommended Software). Once in the Recommended Software dialog, search 'node'. Check the box and hit OK to install.
Installing Node-RED on Raspbian 9
Once installed you'll find Node-RED on the menu at: Raspbian menu, Programming, Node-RED. If you're new to Linux you'll find the result underwhelming. Just a second! The console tells us to use a web browser and go to the address given. Mine in the screenshot below instructs me to go to http://192.168.15.81:1880
Starting Node-RED
And there you will find Node-RED!
A new Flow in Node-RED
Just before we play with Node-RED, go back to the console (text screen) that opened and note the correct way to stop it. Open a terminal and type node-red-stop. You can restart it from the terminal or the menu.
Stopping Node-RED at the terminal
Setting Up Node-RED
Node-RED can play nicely with The Things Network but needs an add-in for that.
Installing the TTN add-in for Node-RED
- Go to the "hamburger" menu in the upper right corner and select Settings
- A menu pops out from which you can select Palette on the left tabs
- At the top change tabs to Install
- In the search box type ttn
- In the search results, click the Install button next to node-red-contrib-ttn
- Click Close after installation
Building a Node-RED Flow
Having learned only a little of Node-RED myself, I can only cover enough to get this basic flow to work. So, here's a bare-metal intro!
See also: node-red-contrib-ttn documentation and quick start guide.
Building a new Flow in Node-RED
All the actions associated with one scenario (TTN Application?) are built into one flow. The gridded area in the middle is where we add and connect nodes to do work. The available types of nodes are listed on the left, categorised under input, output, function, social, etc. On the right we have properties of the nodes (info) as well a debug tab that we'll use like the REPL box in Atom.
The TTN Uplink node
You can see the input nodes have a square connector on the right side so their outputs can be linked to the inputs of other nodes. The first thing we want to do is see the water_level data from our Pycom node device. From the inputs, drag a ttn uplink node onto the flow. Double-clicking it will bring up a property sheet for it on the right.
Configuring the TTN Application in Node-RED
The first thing we'll need to do is configure a TTN app. Configuring your application from their documentation is the authority. I created an extra TTN access key for Node-RED. Because we only have one Node device we don't need to even specify a device to get data from TTN. All of the application's data comes to this one node!
Get Water Level node properties in Node-RED
By linking a debug (output) node, we can print all the data coming in to the debug tab on the right. When you have the two nodes configured and connected, hit the Deploy button in the top right.
Debugging application data in Node-RED
Got the basics? I hope that much worked for you.
The Farmer Scenario in Node-RED
If so, we need to add and connect the following node types per the diagram above.
- A functionnode from the function group, with code:
if (msg.payload.water_level === 0) { msg.payload.pump_running = 1 } if (msg.payload.water_level === 5) { msg.payload.pump_running = 0 } return msg
- Two inject nodes from the input group, one with JSON:
{"pump_running":1}
, the other with JSON:{"pump_running":0}
- Another debug node from the output group
- A ttn downlink node from the output group
Checking it Over
You should have a fully functional system now. The LED on your LoPy Node should turn green when the pump is running and red when it's off. The Atom REPL attached to the Node should display:
Sending water level: 4 Stopping the pump Sending water level: 3 Sending water level: 2 Sending water level: 1 Sending water level: 0 Sending water level: 0 Starting the pump Sending water level: 1 Starting the pump Sending water level: 2 Sending water level: 3 Starting the pump Sending water level: 4 Sending water level: 5 Sending water level: 5 Stopping the pump Sending water level: 4 Stopping the pump Sending water level: 3 Sending water level: 2
The Gateway Pycom device should be outputting normal gateway things, like:
[ 25276.029] Pull rsp [ 25277.004] Sent downlink packet scheduled on 1981.570, at 923.299 Mhz using SF12BW500: b'`\x17\x10\x04&\x00\xb1\x01\x02\xc0k\xbb\x01\xd8' [ 25277.144] Pull ack [ 25285.007] Received packet: {"rxpk": [{"data": "QBcQBCYAhwECjVSiLVI=", "time": "2018-07-05T05:24:36.713347Z", "chan":0, "tmst": 1989572330, "stat": 1, "modu": "LORA", "lsnr": 5.0, "rssi": -22, "rfch": 0, "codr": "4/5", "freq": 916.7999,"datr": "SF7BW125", "size": 14}]} [ 25285.027] Push ack [ 25287.151] Push ack [ 25295.011] Received packet: {"rxpk": [{"data": "QBcQBCYAiAEChhYKw3c=", "time": "2018-07-05T05:24:46.717314Z", "chan":0, "tmst": 1999576222, "stat": 1, "modu": "LORA", "lsnr": 6.0, "rssi": -22, "rfch": 0, "codr": "4/5", "freq": 916.7999,"datr": "SF7BW125", "size": 14}]} [ 25295.031] Push ack [ 25302.155] Pull ack [ 25305.016] Received packet: {"rxpk": [{"data": "QBcQBCYAiQECP77Mrok=", "time": "2018-07-05T05:24:56.719345Z", "chan":0, "tmst": 2009578108, "stat": 1, "modu": "LORA", "lsnr": 6.0, "rssi": -22, "rfch": 0, "codr": "4/5", "freq": 916.7999,"datr": "SF7BW125", "size": 14}]} [ 25305.030] Push ack [ 25327.143] Pull ack
The Data tab on your TTN application should be printing messages going in both directions:
Application Data in its final form on The Things Network
The Debug panel in Node-RED should likewise give you a view into what's going on at that end.
Node-RED debug panel showing all data
And there you have it. I'm excited! Now that we can build a LoRaWAN IoT system from end-to-end I'm going to be building LoRawAN IoT networks at work and at home. I'm not kidding, this stuff is awsome!
EDIT: You might have noticed I diagrammed notifications to a smartphone but didn't implement it. I'll leave that as a challenge. :)