This is a re-mix of my original The Things Network Node for TTNmapper.org project.
The original project uses a smartphone to map The Things Network (TTN) coverage on TTN Mapper; this version adds an Adafruit Ultimate GPS FeatherWing to the node which then can be used standalone: it will transmit its coordinates through TTN and the TTN Mapper Integration does the rest!
The Adafruit Feather/FeatherWings building blocks are easy to use, it doesn't take more than a couple of hours to get your node online (a bit more if you decide to print the enclosure!).
The Adafruit LoRa Feather and the OLED and GPS FeatherWings are almost ready for TTN! There are two things which we need to look at.
First, the Button A
of the FeatherWing is routed to Digital #9
pin of the M0 micro-controller. But this pin is also Analog A7
which can be used to measure battery voltage! The Arduino sketch is currently not using the buttons, but to avoid any issue we re-route Button A
to Digital #10
.
The other point is more important: the LMIC(LoraMAC-in-C) Arduino library which is used to communicate with TTN needs access to the DIO1
pin of the RFM95 radio chip. The Feather exposes this pin (labeled IO1
on the board) but it is not routed to the M0 micro-controller. We need to route IO1
to a free Digital pin; we will use Digital #11
.
Enough for the reading, let's assemble the electronics!
Solder the headers first.
- Short female headers on the micro-controller Feather. I am using 7mm female headers on the Feather, if you are using taller headers the device won't fit in the 3D printed enclosure.
- Male/Female headers on the GPS FeatherWing (you may have to shorten the male pins as they are quite long)
- Male headers on the OLED FeatherWing. Pull out the pin corresponding to
Button A
(see pictures).
If you go with an external antenna, solder the uFL connector under the Feather board, otherwise solder a 82mm wire (for the 868Mhz frequency plan). For more details see Antenna Options on the Adafruit website.
Last but not least, we need to solder two bridges to re-route Button A and DIO1. This can easily be done on the OLED FeatherWing:
Stack the GPS and the Display on top of the micro-controller and you are done!
You can print an enclosure or jump directly to the Software paragraph to test your node.
EnclosureIf you have (access to) a 3D printer, I designed a simple enclosure to protect the boards and secure the antennas.
The STL and the STEP source files are available on YouMagine. It is a 3 parts assembly, no screw needed. It is easy to print, just don't use a material which is too brittle.
Ready to map:
Before compiling the small Arduino sketch, we need to ensure we have the environment and the required libraries in place -- I am referring to the Adafruit website for most parts as they are well documented:
- Arduino IDE setup (Support for Adafruit boards)
- SAMD Support (Support for the M0 micro-controller)
- Adafruit OLED libraries: install the Adafruit SSD1306 and GFX libraries (the FeatherOLED library is not needed)
- Adafruit GPS libraries: install the Adafruit GPS library
- Arduino-LMIC library: download and install this library just like you did for the other ones.
- Adafruit ZeroTimer and ASFcore libraries: easy interface to the timers; download and install as the other ones!
It is recommended to slow down the speed of the SPI bus from 10MHz to 8MHz. In the source code of the Arduino-LMIC library. Edit src/hal/hal.cpp line 77 and change 10E6 to 8E6 -- from:
static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0);
to
static const SPISettings settings(8E6, MSBFIRST, SPI_MODE0);
By default, the Arduino-LMIC library is configured for an RFM95 radio using the European 868MHz frequency plan. If you use the US 915MHz frequency plan, you will need to edit src/lmic/config.h and change:
#define CFG_eu868 1
//#define CFG_us915 1
Into:
//#define CFG_eu868 1
#define CFG_us915 1
Configuring, compiling and running the scriptNote: if you have re-routed DIO1
to a different pin than Digital #11
as suggested above, you will need to update the pin mappings in ttn_mapper.cpp.
Other than that, the ttn-mapper script only needs to be configured with your Application and Device keys.
In the TTN Console:
- Create an Application if you don't already have one
- Register a new Device in this Application. As the RFM95 chips doesn't have unique id/serial, let the TTN console assign you an EUI for your Device.
- In the Device Overview screen take note of the Device EUI, Application EUI and App Key.
In the Arduino IDE, open the ttn-mapper
sketch:
- Rename
ttn_secrets_template.h
intottn_secrets.h
- Fill in the fields with the data from the console. For the Device EUI and Application EUI you need to reverse the byte order! The App Key is copied as-is.
That's it!
Compile and upload the sketch. After reset, you should see the TTN logo, the device will then join the network and send a packet with its location every minute.
The display shows:
- Top line - S: number of packets sent
- Top line - C: number of confirmed sent packets (this does not mean it has been received by a gateway, just that the send operation is completed)
- Top line - E: error count (should be 0!)
- Middle lines: last action issued or event received with its timestamp
- Bottom line - UTC time (from the GPS)
- Bottom line - F: GPS Fix quality (0: no fix, 1: GPS, 2: DGPS)
- Bottom line - S: Number of satellites
- Bottom line - Voltage
You should now see the packets on the TTN console:
Once the node joins the network it will send one packet every minute. You can adjust this interval in ttn_mapper.cpp, but you have to respect the LoRaWan/TTN duty cycle!
// Send packet interval (in seconds) -- respect duty cycle!
const uint8_t send_packet_interval = 60;
TTN Mapper integrationTo actually start mapping gateways coverage, you still need to perform two actions: decode the payload and enable the TTN Mapper integration.
The node sends the data in binary format, which needs to be transformed in JSON before reaching TTN Mapper. This is done in the Payload Formats screen of your application:
Use the following script:
function Decoder(bytes, port) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var decoded = {};
if (port === 2) {
// The port should match the port configured in `ttn_mapper.cpp`
var i = 0;
decoded.latitude = (bytes[i++]<<24) + (bytes[i++]<<16) + (bytes[i++]<<8) + bytes[i++];
decoded.latitude = (decoded.latitude / 1e7) - 90;
decoded.longitude = (bytes[i++]<<24) + (bytes[i++]<<16) + (bytes[i++]<<8) + bytes[i++];
decoded.longitude = (decoded.longitude / 1e7) - 180;
decoded.altitude = (bytes[i++]<<8) + bytes[i++];
decoded.altitude = decoded.altitude - 0x7fff;
decoded.hdop = (bytes[i++]<<8) + bytes[i++];
decoded.hdop = decoded.hdop /10.0;
if (bytes.length >= 14){
decoded.voltage = ((bytes[i++]<<8)>>>0) + bytes[i++];
decoded.voltage /= 100.0;
}
}
return decoded;
}
The ttn-mapper script sends its payload on port 2 to differentiate from other data. If you want to use another port, you need to update the decoder script and ttn_mapper.cpp.
// Port used for the message
// (can be used in the 'Port filter' of the TTN Mapper integration)
const u1_t port = 2;
Now that the decoder is in place, you should see decoded data on the TTN console:
Last step is to create and enable the TTN Mapper integration. Just follow the simple steps described in the TTN help article.
Additional PayloadIf ADD_PAYLOAD
is defined in ttn_mapper.cpp (default) the node will also send its battery voltage in the payload.
// Uncomment the following line to send additional data with the payload
// (Battery voltage)
#define ADD_PAYLOAD
Should you prefer a smaller payload, comment out the ADD_PAYLOAD
define in the source.
Happy gateway mapping!
Comments