I wrote an Arduino wrapper for my LoRaWAN implementation. This post shows how to use it in an experimental application.
Note that even though the wrapper is very easy to use, you should not use it unless you know what you are doing. This includes knowing if you are allowed to do this where you live, and how much trouble you might get into if you interfere with other radio users.
Lately it’s been hot in Britain. Too hot. I want to know how hot it is.
To do this I will run a sensor in my home that measures temperature and humidity every 30 minutes. The sensor will be mains powered and will use LoRaWAN to push the data to The Things Network (TTN). I will then collect the data and store it in a CSV file on my PC.
For this project I’m using an Adafruit Metro (ATMEGA328) configured for 3V3 operation, a Semtech SX1272MB2DAS shield, and a DHT11 sensor. The Semtech shield has a CE mark and comes with an antenna.
The hardware plugs together like so:
For a gateway I have a Multitech Conduit AP. If you are lucky you might not need one.
I wrote the following ino to meet my application requirements:
Compiling in the Arduino environment produces the following message:
There’s still a bit of code space for extra features.
RAM usage seems OK but keep in mind that ldl.process() will use the stack to store the downstream frame, various keys, and a shadow instance of the session parameters. You probably need at least 500 bytes free to ensure the wheels don’t fall off.
Here the preprocessor is used to include the ArduinoLDL header.
Defining DEBUG_LEVEL before the include will enable a set of inline diagnostic messages. Level 1 gives a summary of state transitions, level 2 also includes parameters.
The DHT11 library header is needed to interface with the temperature and humidity sensor.
Mandatory LoRaWAN Parameters
These byte strings are required to connect to TTN. TTN has plenty of resource explaining the purpose of these strings and where they come from so I won’t explain here.
These aren’t the values I actually used. What you see here is just filler.
The PROGMEM attribute is a quirky thing needed on AVR to ensure strings end up in flash memory rather than SRAM.
The constructor caches the settings but doesn’t perform any initialisation. Initialisation is done later in setup() by ldl.begin().
The two-step pattern is slightly awkward solution to two problems:
- The static object is constructed before the Arduino environment is initialised (so you should avoid touching the Arduino interfaces)
- The core implementation needs a pointer to the ArduinoLDL instance (which is not possible to know in the constructor)
Here an instance of the sensor object is constructed by specifying the pin the sensor is connected to, and the hardware variant.
This code is used in loop() to generate a push event every 30 minutes based on the Arduino millisecond counter.
This function is called once from Arduino main() after the environment is initialised but before loop() is called.
Serial must be initialised to support the optional ArduinoLDL diagnostic messages. DHT and ArduinoLDL instances must also be initialised here by calling their begin() methods.
After initialisation the rate and power settings can be adjusted. I recommend using the lowest transmit power and spreading factor you can get away with.
The meaning of these integers changes depending on which region you specify in the ArduinoLDL constructor. You can read about it in the LoRaWAN Regional Parameters companion specification.
Loop is called repeatedly from Arduino main().
Three things happen at the base of the loop:
First there is a check for if the application timer has expired. If it has, the push flag is set and push_timer is reset to the next interval.
Next there is a check for if ArduinoLDL is ready. ArduinoLDL must be ready before it will initiate a join or send data. ArduinoLDL is ready when:
- chip initialisation is complete, and
- there are no active operations (e.g. transmit/receive in progress), and
- a channel is available
Finally ldl.process() is called to make ArduinoLDL process events.
Drilling into the ldl.ready() branch:
When ArduinoLDL is ready but not joined to the network, it will attempt to join using ‘over the air activation’.
If ldl.otaa() completes successfully, ldl.joined() will return true, which will open up this branch:
If the push flag is set, the firmware reads the sensor and sends the data using the unconfirmed data service on port 1.
The push flag is cleared after initiating the send. This ensures the send will not occur again until ArduinoLDL is ready and the application timer has expired.
Serial Monitor and TTN Console
Running the sensor for two hours will produce serial monitor output which looks like this:
You should also be able to see activity on the TTN console:
Collecting Data from TTN
I had originally planned to use the TTN HTTP integration to POST upstream messages to a Google Sheet. This didn’t work as expected and proved too difficult to debug in the time available.
I decided instead to use the MQTT interface. One useful feature of this protocol is that the client can receive messages from behind NAT. This means you can get started with something simple running on your PC, like this program I wrote in Ruby:
The program subscribes to upstream messages from all devices in my ‘environment-sensor’ application. Received messages are unpacked and printed to stdout in CSV format.
Running the program and the sensor at the same time should produce output that looks like this:
And that’s it.