In the first blog post blog post in this series I showed you how can configure the Seeeduino LoRaWAN module to connect it to TheThingsNetwork (TTN). The second post showed you how the LoRaWAN message can be sent to a Google Spreadsheet. Now this post goes one step further and uses the GPS module in the Seeeduino to post the coordinates together with humidity and temperature to a Google Spreadsheet. We then visualize the coordinates on Google Maps. To make it even more interesting decided to replace the TTN backend with the backend of a Swiss startup called Loriot.io. They offer a free service where you have to connect one LoRaWAN gateway in order to participate.
Setting up a Loriot.io Account
Loriot.io is a cool Swiss startup located close to Zurich and they are proving the infrastructure for LoRaWAN applications. Beside their commercial offerings they also have a service which doesn’t cost you anything. But in return you have to contribute to the network by connecting your own LoRaWAN gateway. I’m not going into detail here how to do that but here is some additional information: I configured my existing TTN gatway with a Raspberry Pi Zero W and a IMST IC880A concentrator to forward packages also to the Loriot network. In order to do that I just had to alter the local_conf.json file: Loriot uses a slightly different gateway ID (with FFFF in the middle, the default with TTN is FFFE). I also had to add the Loriot servers to the same configuration file:
{ "gateway_conf": { "gateway_ID": "XXXXXXFFFFXXXXXX", "servers": [ { "server_address": "router.eu.thethings.network", "serv_port_up": 1700, "serv_port_down": 1700, "serv_enabled": true }, { "server_address": "eu1.loriot.io", "serv_port_up": 1780, "serv_port_down": 1780, "serv_enabled": true, "keepalive_interval": 10, "stat_interval": 30, "push_timeout_ms": 100, "forward_crc_valid": true, "forward_crc_error": false, "forward_crc_disabled": false }], "ref_latitude": 0, "ref_longitude": 0, "ref_altitude": 0, "contact_email": "", "description": "ttn-ic880a" } }
After that you can create a free account on the Loriot.io page. Now setup the gateway and place the created ID in the local_conf.json file, assuming that you are using a similar setup like mine (I’m using a semtek packet forwarder). Now you can switch to the “Application” menu and select the “Sample Application”. Now click on “Enroll device”:
Enter your device ID in the form field and click “Enroll OTAA / ABP device”. In the first post I showed you how to figure out the device ID. Now navigate to “Devices” and click on the newly created device. Here you can find the DevEUI, AppEUI and AppKey which you’ll have to use in the next paragraph.
Setting up the Seeeduino Code
In order to have the Seeeduino read out the GPS coordinates and the temperature and humidity we have to install two libraries. Download the TinyGpsPlus library from http://arduiniana.org/libraries/tinygpsplus/ Then install also the DHT library from Adafruit from the library manager under Sketch > Include Library > Library Manager… Menu. Copy the code you previously generated in the Loriot.io interface when adding a new device.
//Download http://arduiniana.org/libraries/tinygpsplus/ #include <TinyGPS++.h> #include <LoRaWan.h> #include "DHT.h" TinyGPSPlus gps; #define DHTPIN A0 // what pin we're connected to #define DHTTYPE DHT11 // DHT 11 DHT dht(DHTPIN, DHTTYPE); char buffer[256]; void setup(void) { SerialUSB.begin(115200); Serial.begin(9600); lora.init(); memset(buffer, 0, 256); lora.getVersion(buffer, 256, 1); SerialUSB.print(buffer); memset(buffer, 0, 256); lora.getId(buffer, 256, 1); SerialUSB.print(buffer); SerialUSB.println("That was the id"); // void setId(char *DevAddr, char *DevEUI, char *AppEUI); lora.setId(NULL, "XXX", "XXX"); // setKey(char *NwkSKey, char *AppSKey, char *AppKey); lora.setKey(NULL, NULL, "XXX"); lora.setDeciveMode(LWOTAA); lora.setDataRate(DR0, EU868); lora.setChannel(0, 868.1); lora.setChannel(1, 868.3); lora.setChannel(2, 868.5); lora.setChannel(3, 867.1); lora.setChannel(4, 867.3); lora.setChannel(5, 867.5); lora.setChannel(6, 867.7); lora.setReceiceWindowFirst(0); lora.setReceiceWindowSecond(869.5, DR3); lora.setAdaptiveDataRate(false); lora.setPower(20); while(!lora.setOTAAJoin(JOIN, 20000)); SerialUSB.println("After OTAA join"); // Start DHT sensor dht.begin(); } void loop(void) { while (Serial.available() > 0) { gps.encode(Serial.read()); } if (gps.altitude.isUpdated()) { SerialUSB.println(gps.altitude.meters()); SerialUSB.print("LAT="); SerialUSB.println(gps.location.lat(), 6); SerialUSB.print("LONG="); SerialUSB.println(gps.location.lng(), 6); SerialUSB.print("ALT="); SerialUSB.println(gps.altitude.meters()); long latEncoded = (gps.location.lat() * 8388606) / 90; long lonEncoded = (gps.location.lng() * 8388606) / 180; SerialUSB.print("Lat encoded: "); SerialUSB.println(latEncoded); SerialUSB.print("Lon encoded: "); SerialUSB.println(lonEncoded); float h = dht.readHumidity(); float t = dht.readTemperature(); SerialUSB.print("Humidity is: "); SerialUSB.print(h); SerialUSB.print(", Temperature: "); SerialUSB.print(t); bool result = false; byte data[10] = {0}; data[0] = h; data[1] = t + 100; data[2] = (byte) (latEncoded >> 16); data[3] = (byte) (latEncoded >> 8); data[4] = (byte) latEncoded; data[5] = (byte) (lonEncoded >> 16); data[6] = (byte) (lonEncoded >> 8); data[7] = (byte) lonEncoded; result = lora.transferPacket(data, 10); //result = lora.transferPacket(data, 10, 10); if(result) { short length; short rssi; memset(buffer, 0, 256); length = lora.receivePacket(buffer, 256, &rssi); if(length) { SerialUSB.print("Length is: "); SerialUSB.println(length); SerialUSB.print("RSSI is: "); SerialUSB.println(rssi); SerialUSB.print("Data is: "); for(unsigned char i = 0; i < length; i ++) { SerialUSB.print("0x"); SerialUSB.print(buffer[i], HEX); SerialUSB.print(" "); } SerialUSB.println(); } delay(1000 * 60 * 5); } } }
Setting Up the Spreadsheet
Log into your Google Docs account and create an new Spreadsheet
Give the Spreadsheet a meaningful name, e.g. LoriotLogger
You might also want to add the column headers:
rssi | seqno | data | humidity | temp | lat | lon | freq | ack | fcnt | dr | bat | port | snr | EUI | cmc | ts |
Copy/paste them from here and insert them before all other rows.
Then go to Tools > Script Editor…
Replace the few lines of code with the code below:
// 2017 by Daniel Eichhorn, https://blog.squix.org // Inspired by https://gist.github.com/bmcbride/7069aebd643944c9ee8b // Create or open an existing Sheet and click Tools > Script editor and enter the code below // 1. Enter sheet name where data is to be written below var SHEET_NAME = "Sheet1"; // 2. Publish > Deploy as web app // - enter Project Version name and click 'Save New Version' // - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously) // 3. Copy the 'Current web app URL' and post this in your form/script action var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service // If you don't want to expose either GET or POST methods you can comment out the appropriate function function doGet(e){ return handleResponse(e); } function doPost(e){ return handleResponse(e); } function handleResponse(e) { Logger.log("arrived in handleResponse"); var jsonData = JSON.parse(e.postData.contents); try { // next set where we write the data - you could write to multiple/alternate destinations var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key")); var sheet = doc.getSheetByName(SHEET_NAME); var nextRow = sheet.getLastRow()+1; // get next row var row = []; var headerRow = []; row.push(jsonData.rssi); row.push(jsonData.seqno); row.push(jsonData.data); var humidityHex = jsonData.data.substring(0, 2); row.push(parseInt(humidityHex, 16)); var tempHex = jsonData.data.substring(2, 4); row.push(parseInt(tempHex, 16) - 100); var latHex = jsonData.data.substring(4, 10); var lonHex = jsonData.data.substring(10, 16); var latEncoded = parseInt(latHex, 16); var lonEncoded = parseInt(lonHex, 16); row.push((latEncoded / 8388606) * 90); row.push((lonEncoded / 8388606) * 180); row.push(jsonData.freq); row.push(jsonData.ack); row.push(jsonData.fcnt); row.push(jsonData.dr); row.push(jsonData.bat); row.push(jsonData.port); row.push(jsonData.snr); row.push(jsonData.EUI); row.push(jsonData.cmd); row.push(jsonData.ts); // more efficient to set values as [][] array than individually sheet.getRange(nextRow, 1, 1, row.length).setValues([row]); // return json success results return ContentService .createTextOutput(JSON.stringify({"result":"success", "row": nextRow})) .setMimeType(ContentService.MimeType.JSON); } catch(e) { // if error return this return ContentService .createTextOutput(JSON.stringify({"result":"error", "error": e})) .setMimeType(ContentService.MimeType.JSON); } }
Publish > Deploy as web app
- enter Project Version name and click ‘Save New Version’
- set security level and enable service (most likely execute as ‘me’ and access ‘anyone, even anonymously)
- Copy the ‘Current web app URL’ and post this in your form/script action
Give permissions to the script:
Copy the URL:
Connecting Loriot with your Google Spreadsheet
Switch again to the Loriot page in your browser. Navigate to the Sample App > Output page:
Click on the “Change button” and select HTTP Push. Now enter the address from Google Docs, when you published the app. Done, Loriot should now send the data to Google Docs! One Note: I was logged into two google accounts at the same time, so my URL generated had an additional “/u/1” and it started with https://script.google.com/macros/u/1/XXX. If your URL looks like this then remove the /u/1/ from the URL.
Starting the Seeduino
Now let’s see if the data arrives!
Hurray!
Visualize the data on Google Maps
Now to the finishing: let’s put the markers on a map! Google Spreadsheets only have very simple maps so navigate to https://www.google.com/maps/d/u/0/.
In the left menu select “Import” and select your Spreadsheet from Google drive.
Now select the “lat” and “lon” column as the coordinate columns and the seqno column for the title. Create the map and you get a wonderful representation of your geo fixes, sent over LoRaWan to Loriot.io, from there to a App Script instance, which inserted the data into a spreadsheet, and from there you imported the data into a map! Wow! If you got it to work you really made it!
Do you have questions about this post? Go over to https://support.squix.org and place it there so everyone can profit!
Hi,
I wish to know why you use “8388606/” in following code:
long latEncoded = (gps.location.lat() * 8388606) / 90;
long lonEncoded = (gps.location.lng() * 8388606) / 180;
and make these signed long in to 3 bytes…
it’s a magic to me