AdaFruit.IO LoRa Windows 10 IoT Core Field Gateway

This project is now live on github.com, sample Arduino with Dragino LoRa Shield for Arduino, MakerFabs Maduino, Dragino LoRa Mini Dev, M2M Low power Node and Netduino with Elecrow LoRa RFM95 Shield clients uploaded in the next couple of days.

AdaFruit.IO.LoRaScreenShot
While building this AdaFruit.IO LoRa field gateway, and sample clients I revisited my RFM9XLoRa-Net library a couple of times adding functionality and renaming constants to make it more consistent. I made many of the default values public so they could be used in the field gateway config file.
The bare minimum configuration is

{
“AdaFruitIOUserName”: “——“,
“AdaFruitIOApiKey”: “——“,
“AdaFruitIOGroupName”: “——”
“Address”: “——“,
“Frequency”: 915000000.0
}

So far battery life and wireless communications range for the Arduino clients is looking pretty good.

ArduinoUnoR3DraginoLoRa

Windows 10 IoT Core LoRa library

I have a pair of Windows 10 IoT Core nRF24L01 field gateway projects, one for AdaFruit.IO and the other for Azure IoTHub (Including Azure IoT Central). I use these field gateways for small scale indoor and outdoor deployments.

For larger systems e.g a school campus I was looking for something with a bit more range (line of site + in building penetration) and clients with lower power consumption (suitable for long term battery or solar power).

Other makers had had success with RFM69(proprietary) and RFM9X (LoRA) based devices and shields/hats so I had a look at both technologies.

To kick things off I purchased

I then did some searching and downloaded two commonly used libraries

Initially I trialled the emmellsoft Windows 10 IoT Core Dragino.LoRa code on a couple of Raspberry PI devices.

RPIDraginoP2P

After updating the Windows 10 Min/Max versions, plus the NuGet packages and setting the processor type to ARM the code compiled, downloaded and ran which was a pretty good start.

I could see messages being transmitted and received by the two devices

Packet RSSI: -33, RSSI: -91, SNR: 8, Length: 5
Message Received: CRC OK, Rssi=-91, PacketRssi=-33, PacketSnr=8, Buffer:[55, ff, 00, aa, 01], 2018-07-30 09:27:48
Successfully sent in 110 milliseconds.
Packet RSSI: -15, RSSI: -100, SNR: 9.2, Length: 5
Message Received: CRC OK, Rssi=-100, PacketRssi=-15, PacketSnr=9.2, Buffer:[55, ff, 00, aa, 02], 2018-07-30 09:27:53
Successfully sent in 36 milliseconds.
Packet RSSI: -35, RSSI: -101, SNR: 9, Length: 5
Message Received: CRC OK, Rssi=-101, PacketRssi=-35, PacketSnr=9, Buffer:[55, ff, 00, aa, 03], 2018-07-30 09:27:58
Successfully sent in 36 milliseconds.

I added my first attempt at device configuration for New Zealand (based on EU settings) in Dragino.LoRa\Radio\TransceiverSettings.cs

public static readonly TransceiverSettings ANZ915 = new TransceiverSettings(
             RadioModemKind.Lora,
             915000000,
             BandWidth.BandWidth_125_00_kHz,
             SpreadingFactor.SF7,
             CodingRate.FourOfFive,
             8,
             true,
             false,
             LoraSyncWord.Public);

The LoraSyncWord.Public would turn out to be a problem later!

Then I modified the sender and receiver sample application MainPage.xaml.cs files to reference my settings

private static TransceiverSettings GetRadioSettings()
{
   // *********************************************************************************************
   // #1/2. YOUR EDITING IS REQUIRED HERE!
   //
   // Choose transeiver settings:
   // *********************************************************************************************

   return TransceiverSettings.Standard.ANZ915;
}

I modified one of the RadioHead sample Arduino applications (centre frequency) and deployed it to a LoRa MiniDev device. I could see messages getting sent but they were not getting received by the RPI(s).

So I dumped the registers for the SX127X device in the HopeRF RFM95 module on both devices,

DraginoLoraMinDev

From the device on RPI Hat

SX1276/77/78/79 detected, starting.
1-85
2-1A
3-B
4-0
5-52
6-E4
7-C0
8-0
9-85
A-9
B-2B
C-23
D-0
E-80
F-0
10-0
11-0
12-0
13-0
14-0
15-0
16-0
17-0
18-4
19-0
1A-0
1B-42
1C-0
1D-72
1E-74
1F-9F
20-0
21-8
22-1
23-FF
24-0
25-0
26-4
27-0
28-0
29-0
2A-0
2B-0
2C-9
2D-50
2E-14
2F-45
30-55
31-C3
32-5
33-27
34-1C
35-A
36-3
37-A
38-42
39-34
The LoRa transceiver is initiated successfully.

I printed out the Radiohead and emmellsoft registers then manually compared them using the SX1275 datasheet for reference.

I found the 3 registers which contain the MSB, ISB and LSB for the centre frequency weren’t being calculated correctly (checked this by changing the frequency to 434MHz and comparing the register values to the worked example in the datasheet).

I manually “brute forced” the centre frequency registers in LoRaTransceiver.cs Init() and the RPI could then detect a signal but couldn’t decode the messages.

I went back to the Register dumps and found the SyncWord (odd name as it is a byte) were different. After updating the RPI settings the devices could exchange packets..

const double RH_RF95_FXOSC = 32000000.0;
const double RH_RF95_FSTEP = RH_RF95_FXOSC / 524288.0;
long frf = (long)(Settings.Frequency / RH_RF95_FSTEP);

byte[] bytes = BitConverter.GetBytes(frf);

byte[] x6 = { bytes[2] };
RegisterManager.WriteRegister(6, x6);
byte[] x7 = { bytes[1] };
RegisterManager.WriteRegister(7, x7);
byte[] x8 = { bytes[0] };
RegisterManager.WriteRegister(8, x8);

RegisterManager.Write(new LoraRegisterSyncWord(Settings.LoraSyncWord.Value));

This was not a long term solution, lots of code, and register setting changes with limited explanation…

Azure Meetup-Budget tank of 91 IoT

The premise of my Azure Meetup presentation was could you build an interesting project on a rainy weekend afternoon with a constrained budget (tank of 91 octane petrol) and minimal soldering .

Budget

Our family car is a VW Passat V6 4Motion which has a 62 Litre tank. The driver usually doesn’t usually stop to fill up until the fuel light has been on for a bit which helped.

PetrolReceipt

Based on the most recent receipt the budget was NZD132.

Where possible I purchased parts locally (the tech equivalent of food miles) or on special.

My bill of materials (prices as at 2018-06) was on budget.

The devDuino V2.2 and nRF24L01 module were USD26.20 approx. NZD37.50 (including freight) from elecrow.

Tradeoffs

I powered my Raspberry PI with a spare cellphone charger (make sure it can supply enough current to reliably power the device).

The devDuino V2.has an ATSHA204A which provides a guaranteed unique 72-bit serial number (makes it harder to screw up provisioning devices in the field).

I use a 32G MicroSD rather than a 16G MicroSD card as I have had issued with 16G cards getting corrupted by more recent upgrades (possibly running out of space?)

The Raspberry PI shield requires a simple modification to enable interrupt driven operation.

My sample devDuino V2.2 client uses an external temperature and humidity sensor, modifying this code to use the onboard temperature sensor an MCP9700 will be covered in another post.

The devDuino V2 is a little bit cheaper USD15.99 NZD37.31, has the same onboard temperature sensor as the V2.2 but no unique serial number chip.

The devDuino V4.0 has an onboard HTU21D temperature + humidity sensor but no unique serial number and the batteries are expensive.

The code and deployment instructions for the nRF24L01 field gateway applications for AdaFruit.IO and Azure IoT Hub/Azure IoT Central are available on hackster.IO.

RPiWithnRF24Plate

AdaFruit.IO has free and USD10.00/month options which work well for many hobbyist projects.

AdaFruitIO

Azure Meetup Christchurch notes

For the people who came to my Azure meetup session this evening

Sources of sensors and development boards

http://www.adafruit.com
http://www.elecrow.com (watering kits)
http://www.ingenuitymicro.com (NZ based dev boards)
http://www.netduino.com (.NetMF development boards)
http://www.makerfabs.com
http://www.seeedstudio.com
http://www.tindie.com

nRF24Shields for RPI devices
http://www.tindie.com/products/ceech/new-raspberry-pi-to-nrf24l01-shield/

nRF24Shields for *duino devices in AU
embeddedcoolness.com

Raspberry PI Source in CHC
http://www.wavetech.co.nz

RFM69 & LoRa Modules
http://www.wisen.com.au

local sensor and device resellers quick turnaround
http://www.mindkits.co.nz
http://www.nicegear.co.nz

http://www.diyelectricskateboard.com

The watch development platform
http://www.hexiwear.com

http://www.gowifi.co.nz (Antennas & other wireless kit based in Rangiora)

my projects
http://www.hackster.io/KiwiBryn
io.adafruit.com/BrynHLewis/dashboards/home-environment

Wireless field gateway protocol V2

I have now built a couple of nRF2L01P field gateways (for AdaFriut.IO & Azure IoT Hubs) which run as a background tasks on Windows 10 IoT Core on RaspberyPI). I have also written several clients which run on Arduino, devDuino, Netduino, and Seeeduino devices.

I have tried to keep the protocol simple (telemetry only) to deploy and it will be used in high school student projects in the next couple of weeks.

To make the payload smaller the first byte of the message now specifies the message type in the top nibble and the length of the device unique identifier in the bottom nibble.

0 = Echo

The message is displayed by the field gateway as text & hexadecimal.

1 = Device identifier + Comma separated values (CSV) payload

[0] – Set to 0001, XXXX   Device identifier length

[1]..[1+Device identifier length] – Unique device identifier bytes e.g. Mac address

[1+Device identifier length+1 ]..[31] – CSV payload e.g.  SensorID value, SensorID value

 

Wireless field gateway protocol V1

I’m going to build a number of nRF2L01P field gateways (Netduino Ethernet & Wifi running .NetMF, Raspberry PI running Windows 10 IoT Core, RedBearLab 3200  etc.), clients which run on a variety of hardware (Arduino, devDuino, Netduino, Seeeduino etc.) which, then upload data to a selection of IoT Cloud services (AdaFruit.IO, ThingSpeak, Microsoft IoT Central etc.)

The nRF24L01P is widely supported with messages up to 32 bytes long, low power consumption and 250kbps, 1Mbps and 2Mbps data rates.

The aim is to keep the protocol simple (telemetry only initially) to implement and debug as the client side code will be utilised by high school student projects.

The first byte of the message specifies the message type

0 = Echo

The message is displayed by the field gateway as text & hexadecimal.

1 = Device identifier + Comma separated values (CSV) payload

[0] – Set to 1

[1] – Device identifier length

[2]..[2+Device identifier length] – Unique device identifier bytes e.g. Mac address

[2+Device identifier length+1 ]..[31] – CSV payload e.g.  SensorID value, SensorID value

Overtime I will support more message types and wireless protocols.

 

AdaFruit IO Swagger based desktop HTTP client

Manually building clients for complex RESTful APIs (like AdaFruit.IO) can be a bit tedious so I figured I would try generating a C# http client from the Swagger OpenAPI specification(OAS) metadata.

My initial attempts using the Swagger Editor and NSwag on the AdaFruit.IO public API description didn’t go so well. (for more info see this AdaFruit.IO support forum thread) You may need to manually modify the type of the id field in Data & DataResponse, plus possibly other responses.

After figuring out how to set the API key, my code which uploads simulates three individual feeds and one feed group appears to work reliably.

/*

Copyright ® 2018 Jan devMobile Software, All Rights Reserved

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.

http://www.devmobile.co.nz

 */
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using AdaFruit.IO;

namespace AdaFruit.IO
{
   public partial class Client
   {
      string adaFruitIOApiKey = "yourAPIKey";

      partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url)
      {
         client.DefaultRequestHeaders.Add("X-AIO-Key", adaFruitIOApiKey);
      }
   }
}

namespace devMobile.IoT.Adafruit.IO.Desktop
{
   class Program
   {
      static void Main(string[] args)
      {
         string userName = "YourUserName"; // This is mixed case & case sensitive
         // The feed group and feed key are forced to lower case by UI
         const string feedGroup = "devduinov2-dot-2";
         const string temperatureKey = "t";
         const double temperatureBase = 20.0;
         const double temperatureRange = 10.0;
         const string humidityKey = "h";
         const double humidityBase = 70.0;
         const double humidityRange = 20.0;
         const string batteryVoltageKey = "v";
         const double batteryVoltageBase = 3.00;
         const double batteryVoltageRange = -1.00;
         TimeSpan feedUpdateDelay = new TimeSpan(0, 0, 15);
         TimeSpan groupUpdateDelay = new TimeSpan(0, 0, 30);
         Random random = new Random();

         while (true)
         {
            Client client = new Client();
            double temperature = temperatureBase + random.NextDouble() * temperatureRange;
            double humidity = humidityBase + random.NextDouble() * humidityRange;
            double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;

            Debug.WriteLine("Temperature {0}°C  Humidity {1}%  Battery Voltage {2}V", temperature.ToString("F1"), humidity.ToString("F0"), batteryVoltage.ToString("F2"));

            // First Update the 3 feeds individually
            // Temperature
            Datum temperatureDatum = new Datum()
            {
               Value = temperature.ToString("F1"),
            };
            client.CreateDataAsync(userName, temperatureKey, temperatureDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Humidity
            Datum humidityDatum = new Datum()
            {
               Value = humidity.ToString("F0"),
            };
            client.CreateDataAsync(userName, humidityKey, humidityDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Battery
            Datum batteryDatum = new Datum()
            {
               Value = batteryVoltage.ToString("F2"),
            };
            client.CreateDataAsync(userName, batteryVoltageKey, batteryDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Then update a feed in a group
            Group_feed_data devDuinoData = new Group_feed_data();

            devDuinoData.Feeds.Add(new Anonymous2() { Key = temperatureKey, Value = temperature.ToString("F1")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = humidityKey, Value = humidity.ToString("F0")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = batteryVoltageKey, Value = batteryVoltage.ToString("F2")});

            client.CreateGroupDataAsync(userName, feedGroup, devDuinoData).Wait();
            Task.Delay(groupUpdateDelay).Wait();
         }
      }
   }
}