I teach kids how to code and needed a telemetry field gateway which was reasonably priced (both devices + field gateway hardware), which would work across a campus (couple of city blocks), had low power consumption (solar powered devices) and had good in building penetration (selection of indoor & outdoor sensors).
After looking at several cloud IoT services, Adafruit.IO looked like it had sufficient flexibility and a reasonable pricing model. The security model (Feb 2018) with a shared API Key will have some challenges in wider scale deployments
Initially, I was going to use the Adafruit.IO MQTT interface but have found that getting an HTTP endpoint (fine for telemetry) white-listed in a school environment is easier.
While reading the Adafruit.IO API documents I noticed that they have published a swagger definition of the API so I used NSwag to generate a C# client. I added a partial class with a single method to put an HTTP Header with the Adafruit.IO API Key (see Adafruit.IO.Swagger\APIKey.cs for details) on every request.
Based on feedback from users prebuilt installers are now available from GitHub. There are eight different configurations to build and test so there maybe some delays before updated installers for your hardware are available.
All you have to do is download latest installer file for your shield locally then open the "Device portal" on your device.
- Navigate to Apps\Apps Manager "Add" to install
- Then "Start" and "Stop" the application which will create config file template
- Navigate to Apps\File Explorer to download config file from \User Folders\LocalAppData\AdafruitIO.IoTCore.FieldGateway.LoRa...\LocalState\
- Modify config file putting in your Adafruit.IO user name, API key and Group, string, and changing frequency etc. as required.
- Then use File Explorer to upload config file
- Navigate to Apps\Apps Manager to start application and/or set application to "Startup" automatically
Currently the codebase supports (tested with) these Raspberry Pi shields:
- Dragino LoRa GPS Hat for Raspberry Pi
- Elecrow LoRa RFM95 IoT Board for RPI
- M2M 1 Channel LoRaWan Gateway Shield for Raspberry PI
- Lora/LoraWan Shield for Raspberry PI Zero and PI 3 (needs a spacer on PI3)
- Uputronics Raspberry PI Zero LoRa Expansion Board
- Uputronics Raspberry Pi+ LoRa Expansion Board
The core field gateway code is roughly 510 Lines long and uses my .Net RFM9X library (with payload addressing enabled). The packets have a simple header which has to & from addresses and the payload. For ease of student development/debugging the payload is comma delimited ASCII, rather than a packed binary protocol.(A future enhancement)
I have roughly a dozen sample clients (working on a couple more LowPower Lab clients) on my blog and I will publish them on Hackster.IO over the next couple of weeks.
- Arduino using Dragino, elecrow and MakerFabs shields
- Netduino using Dragino, elecrow and MakerFabs shields
- Maduino
- IoTNet
- IoTMCU915
- Adafruit Feather M0
- Elecrow 32u4 with LoRa RFM95 IoT Board
- M2M Low power LoRaWan Node Model A328
- M2M Low power LoRaWan Node Model B1284
- Wisen Whisper Node LoRa
- Dragino LoRa Mini Dev
- Arduino Nano with EasySensors Arduino Nano shield RFM95
- Arduino MKR WAN 1300
- LowPowerLab MoteinoM0
The application logs diagnostic information to the ETW logging.
Once the application is configured and tested you can use the Device Portal\Apps Manager to set the program to run on device startup.
The application supports the following configuration settings. The minimal configuration file is
{
"AdaFruitIOUserName": "Your username",
"AdaFruitIOApiKey": "Your API key",
"AdaFruitIOGroupName": "YourGroupName",
"Address": "LoRaIoT2",
"Frequency": 433000000.0
}
The software has been tested with 433MHz & 915MHz devices, it should work fine with 868MHz etc.
Pay attention to your local ISM band regulations and ensure that you are compliant with them, especially frequency, power levels and duty cyclerequirements.
There are a lot of very specialised LoRa settings which can cause problems if used incorrectly, modify these with care.
private class ApplicationSettings
{
[JsonProperty("AdaFruitIOBaseUrl", Required = Required.DisallowNull)]
public string AdaFruitIOBaseUrl { get; set; }
[JsonProperty("AdaFruitIOUserName", Required = Required.Always)]
public string AdaFruitIOUserName { get; set; }
[JsonProperty("AdaFruitIOApiKey", Required = Required.Always)]
public string AdaFruitIOApiKey { get; set; }
[JsonProperty("AdaFruitIOGroupName", Required = Required.Always)]
public string AdaFruitIOGroupName { get; set; }
// LoRa configuration parameters
[JsonProperty("Address", Required = Required.Always)]
public string Address { get; set; }
[DefaultValue(Rfm9XDevice.FrequencyDefault)]
[JsonProperty("Frequency", DefaultValueHandling = DefaultValueHandling.Populate)]
public double Frequency { get; set; }
// RegPaConfig
[DefaultValue(Rfm9XDevice.PABoostDefault)]
[JsonProperty("PABoost", DefaultValueHandling = DefaultValueHandling.Populate)]
public bool PABoost { get; set; }
[DefaultValue(Rfm9XDevice.RegPAConfigMaxPowerDefault)]
[JsonProperty("MaxPower", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte MaxPower { get; set; }
[DefaultValue(Rfm9XDevice.RegPAConfigOutputPowerDefault)]
[JsonProperty("OutputPower", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte OutputPower { get; set; }
// RegOcp
[DefaultValue(Rfm9XDevice.RegOcpDefault)]
[JsonProperty("OCPOn", DefaultValueHandling = DefaultValueHandling.Populate)]
public bool OCPOn { get; set; }
[DefaultValue(Rfm9XDevice.RegOcpOcpTrimDefault)]
[JsonProperty("OCPTrim", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte OCPTrim { get; set; }
// RegLna
[DefaultValue(Rfm9XDevice.LnaGainDefault)]
[JsonProperty("LNAGain", DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(StringEnumConverter))]
public Rfm9XDevice.RegLnaLnaGain LnaGain { get; set; }
[DefaultValue(Rfm9XDevice.LnaBoostDefault)]
[JsonProperty("LNABoost", DefaultValueHandling = DefaultValueHandling.Populate)]
public bool LNABoost { get; set; }
// RegModemConfig1
[DefaultValue(Rfm9XDevice.RegModemConfigBandwidthDefault)]
[JsonProperty("Bandwidth", DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(StringEnumConverter))]
public Rfm9XDevice.RegModemConfigBandwidth Bandwidth { get; set; }
[DefaultValue(Rfm9XDevice.RegModemConfigCodingRateDefault)]
[JsonProperty("codingRate", DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(StringEnumConverter))]
public Rfm9XDevice.RegModemConfigCodingRate CodingRate { get; set; }
[DefaultValue(Rfm9XDevice.RegModemConfigImplicitHeaderModeOnDefault)]
[JsonProperty("ImplicitHeaderModeOn", DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(StringEnumConverter))]
public Rfm9XDevice.RegModemConfigImplicitHeaderModeOn ImplicitHeaderModeOn { get; set; }
// RegModemConfig2SpreadingFactor
[DefaultValue(Rfm9XDevice.RegModemConfig2SpreadingFactorDefault)]
[JsonProperty("SpreadingFactor", DefaultValueHandling = DefaultValueHandling.Populate)]
public Rfm9XDevice.RegModemConfig2SpreadingFactor SpreadingFactor { get; set; }
[DefaultValue(Rfm9XDevice.SymbolTimeoutDefault)]
[JsonProperty("SymbolTimeout", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte SymbolTimeout { get; set; }
[DefaultValue(Rfm9XDevice.PreambleLengthDefault)]
[JsonProperty("PreambleLength", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte PreambleLength { get; set; }
[DefaultValue(Rfm9XDevice.PayloadLengthDefault)]
[JsonProperty("PayloadLength", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte PayloadLength { get; set; }
[DefaultValue(Rfm9XDevice.PayloadMaxLengthDefault)]
[JsonProperty("PayloadMaxLength", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte PayloadMaxLength { get; set; }
[DefaultValue(Rfm9XDevice.FreqHoppingPeriodDefault)]
[JsonProperty("freqHoppingPeriod", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte FreqHoppingPeriod { get; set; }
[DefaultValue(Rfm9XDevice.LowDataRateOptimizeDefault)]
[JsonProperty("LowDataRateOptimize", DefaultValueHandling = DefaultValueHandling.Populate)]
public bool LowDataRateOptimize { get; set; }
[DefaultValue(Rfm9XDevice.AgcAutoOnDefault)]
[JsonProperty("AgcAutoOn", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte AgcAutoOn { get; set; }
[DefaultValue(Rfm9XDevice.ppmCorrectionDefault)]
[JsonProperty("PPMCorrection", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte PpmCorrection { get; set; }
[DefaultValue(Rfm9XDevice.RegDetectOptimizeDectionOptimizeDefault)]
[JsonProperty("DetectionOptimize", DefaultValueHandling = DefaultValueHandling.Populate)]
public Rfm9XDevice.RegDetectOptimizeDectionOptimize DetectionOptimize { get; set; }
[DefaultValue(Rfm9XDevice.InvertIqDefault)]
[JsonProperty("InvertIQ", DefaultValueHandling = DefaultValueHandling.Populate)]
public bool InvertIQ { get; set; }
[DefaultValue(Rfm9XDevice.RegisterDetectionThresholdDefault)]
[JsonProperty("DetectionThreshold", DefaultValueHandling = DefaultValueHandling.Populate)]
public Rfm9XDevice.RegisterDetectionThreshold DetectionThreshold { get; set; }
[DefaultValue(Rfm9XDevice.RegSyncWordDefault)]
[JsonProperty("SyncWord", DefaultValueHandling = DefaultValueHandling.Populate)]
public byte SyncWord { get; set; }
}
Future planned enhancements include packed binary payloads, cloud to device messaging (text+Binary+BCD & push+queued), automagic device provisioning, OTA crypto, support for other hardware platforms, e.g. Dragonboard 410C, and automated updates.
Comments