RAK811/RAK4200 AS923 Join Channels

When running an application which used my TinyCLR V2 RAK811 Module library on a FezDuino with a modified RAK811 LPWAN Evaluation Board(EVB) most join attempts on my Things Industries(TTI) instance would fail. This was a bit odd as connecting to The Things Network(TTN) was pretty reliable.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811LoRaWanDeviceClient starting
12:00:12 Region AS923
12:00:12 ADR On
12:00:12 Unconfirmed
12:00:12 OTAA
12:00:13 Join start Timeout:30Sec
Join failed 26
The thread '<No Name>' (0x1) has exited with code 0 (0x0).
Done.

In TTI end device live data tab I could see the the joins attempts were failing with “Uplink channel Not found”

The Things Industries device live data tab “uplink channel not found” failures
The Things Industries device live data tab “uplink channel not found” detail

Initially I assumed this was an issue with my configuration of the RAKwireless RAK7258 gateway in my office that I was using for testing. After some discussions with a helpful TTI support person they suggested that I try disabling all bar the first two channels the RAK811 module was configured to use then see if worked.

I modified the intialise method of my TinyCLR V2 RAK811 Module library to disable all bar the first two channels

result = SendCommand("OK", "at+set_config=lora:ch_mask:2:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
   Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:2:0 {result}");
#endif
   return result;
}

result = SendCommand("OK", "at+set_config=lora:ch_mask:3:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
   Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:3:0 {result}");
#endif
   return result;
}

result = SendCommand("OK", "at+set_config=lora:ch_mask:4:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
   Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:4:0 {result}");
#endif
   return result;
}

result = SendCommand("OK", "at+set_config=lora:ch_mask:5:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
    Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:5:0 {result}");
#endif
   return result;
}

result = SendCommand("OK", "at+set_config=lora:ch_mask:6:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
   Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:6:0 {result}");
#endif
   return result;
}

result = SendCommand("OK", "at+set_config=lora:ch_mask:7:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
   Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:ch_mask:7:0 {result}");
#endif
   return result;
}

After modifying the code my Fezduino joined reliably.

The thread ” (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811LoRaWanDeviceClient starting
12:00:12 Region AS923
12:00:12 ADR On
12:00:12 Unconfirmed
12:00:12 OTAA
12:00:13 Join start Timeout:30Sec
12:00:18 Join finish
Temperature : 19.9 °C
Pressure : 1014.0 HPa
Altitude : 143 meters
12:00:19 port:5 payload BCD:0073279C016700C8
12:00:44 Sleep
12:01:44 Wakeup
Temperature : 20.1 °C
Pressure : 1014.0 HPa
Altitude : 143 meters
12:01:44 port:5 payload BCD:0073279C016700C9
12:02:09 Sleep

The Things Industries device live data tab successful join.

After some further discussion with TTI support it looks like the RAK811 module doesn’t send join requests on the frequencies specified for the AS923 region in the LoRaWAN™1.1Regional Parameters.

LoRaWAN Regional Parameters AS923 Join-request frequencies

After confirming the join-request channel issue I went back to the RAKwireless forums with some new terms to search for and found that others were having a similar issue but with RAK4200 modules. My “best guess” is that the TTI implementation is more strict about join-request frequencies than the TTI

floor, ceil, trunc and casting

I left a Wisnode Track Lite RAK7200 outside on the deck for a day and the way the positions “snapped” to a grid caught my attention. Based on the size of my property the grid looked to be roughly 10 x 10 meters

The sample Cayenne Low Power Payload Mbed C code uses a cast which is I think is the same as a floor.

uint8_t CayenneLPP::addGPS(uint8_t channel, float latitude, float longitude, float meters) {
    if ((cursor + LPP_GPS_SIZE) > maxsize) {
        return 0;
    }
    int32_t lat = latitude * 10000;
    int32_t lon = longitude * 10000;
    int32_t alt = meters * 100;
    
    buffer[cursor++] = channel; 
    buffer[cursor++] = LPP_GPS; 
 
    buffer[cursor++] = lat >> 16; 
    buffer[cursor++] = lat >> 8; 
    buffer[cursor++] = lat; 
    buffer[cursor++] = lon >> 16; 
    buffer[cursor++] = lon >> 8; 
    buffer[cursor++] = lon; 
    buffer[cursor++] = alt >> 16; 
    buffer[cursor++] = alt >> 8;
    buffer[cursor++] = alt;
 
    return cursor;
}

“These functions round x downwards to the nearest integer, returning that value as a double. Thus, floor (1.5) is 1.0 and floor (-1.5) is -2.0.”

In the C code the latitude and longitude values are truncated to four decimal places and the altitude to two decimal places. In my C# code I used Math.Round and I wondered what impact that could have…

public void GpsLocationAdd(byte channel, float latitude, float longitude, float altitude)
{
   IsChannelNumberValid(channel);
   IsBfferSizeSufficient(Enumerations.DataType.Gps);

   if ((latitude < Constants.LatitudeMinimum ) || (latitude > Constants.LatitudeMaximum))
   {
      throw new ArgumentException($"Latitude must be between {Constants.LatitudeMinimum} and {Constants.LatitudeMaximum}", "latitude");
   }

   if ((latitude < Constants.LongitudeMinimum) || (latitude > Constants.LongitudeMaximum))
   {
      throw new ArgumentException($"Longitude must be between {Constants.LongitudeMinimum} and {Constants.LongitudeMaximum}", "latitude");
   }

   if ((altitude < Constants.AltitudeMinimum) || (altitude > Constants.AltitudeMaximum))
   {
      throw new ArgumentException($"Altitude must be between {Constants.AltitudeMinimum} and {Constants.AltitudeMaximum}", "altitude");
   }

   int lat = (int)Math.Round(latitude * 10000.0f);
   int lon = (int)Math.Round(longitude * 10000.0f);
   int alt = (int)Math.Round(altitude * 100.0f);

   buffer[index++] = channel;
   buffer[index++] = (byte)Enumerations.DataType.Gps;

   buffer[index++] = (byte)(lat >> 16);
   buffer[index++] = (byte)(lat >> 8);
   buffer[index++] = (byte)lat;
   buffer[index++] = (byte)(lon >> 16);
   buffer[index++] = (byte)(lon >> 8);
   buffer[index++] = (byte)lon;
   buffer[index++] = (byte)(alt >> 16);
   buffer[index++] = (byte)(alt >> 8);
   buffer[index++] = (byte)alt;
}

Using the WGS84 World Geodetic System Distance Calculator to calculate the distance where the Greenwich Meridian and the Equator cross off the coast of Ghana the theoretical maximum error is 15.69m.

I live in Christchurch New Zealand and the theoretical maximum distance is 13.6 m. So, in summary the LPP latitude and longitude values are most probably fine for tracking applications.

MATH131 Numerical methods was useful

Back in 1986 in my second first year at the University of Canterbury I did “MATH131 Numerical Methods” which was a year of looking at why mathematics in FORTRAN, C, and Pascal sometimes didn’t return the result you were expecting…

While testing my GHI Electronics TinyCLR2 RAK Wireless RAK811 LoRaWAN client I noticed the temperature numbers didn’t quite match…

Visual Studio 2019 Debug output window
The Things Network Device Application Data tab

I have implemented my own Cayenne Low Power Payload encoder in C# based on the sample Mbed C code

uint8_t CayenneLPP::addTemperature(uint8_t channel, float celsius) {
    if ((cursor + LPP_TEMPERATURE_SIZE) > maxsize) {
        return 0;
    }
    int16_t val = celsius * 10;
    buffer[cursor++] = channel; 
    buffer[cursor++] = LPP_TEMPERATURE; 
    buffer[cursor++] = val >> 8; 
    buffer[cursor++] = val; 

    return cursor;
}

My translation of that code to C#

public void TemperatureAdd(byte channel, float celsius)
{
   if ((index + TemperatureSize) > buffer.Length)
   {
      throw new ApplicationException("TemperatureAdd insufficent buffer capacity");
   }

   short val = (short)(celsius * 10);

   buffer[index++] = channel;
   buffer[index++] = (byte)DataType.Temperature;
   buffer[index++] = (byte)(val >> 8);
   buffer[index++] = (byte)val;
}

After looking at the code I think the issues was most probably due to the representation of the constant 10(int32), 10.0(double), and 10.0f(single) . To confirm my theory I modified the client to send the temperature with the calculation done with three different constants.

Visual Studio 2019 Debug output window
The Things Network(TTN) Message Queue Telemetry Transport(MQTT) client

After some trial and error I settled on this C# code for my decoder

public void TemperatureAdd(byte channel, float celsius)
{
   if ((index + TemperatureSize) > buffer.Length)
   {
      throw new ApplicationException("TemperatureAdd insufficent buffer capacity");
   }

   short val = (short)(celsius * 10.0f);

   buffer[index++] = channel;
   buffer[index++] = (byte)DataType.Temperature;
   buffer[index++] = (byte)(val >> 8);
   buffer[index++] = (byte)val;
}

I don’t think this is specifically an issue with the TinyCLR V2 just with number type used for the constant.

The Things Network V2 MQTT Client

Another option for I had been looking at for connecting an Azure IoT Hub and The Things Network(TTN) was a Message Queue Telemetry Transport(MQTT) integration.

To trial this approach I build a .Net Core console application which sent message to and received messages from an application running on a GHI Electronics TinyCLRV2 Fezduino with RakWireless Wisduino Evaluation Board(EVB).

The console application uses MQTTNet to connect to TTN. It subscribes to to the TTN application device uplink topic (did try subscribing to the uplink messages for all the devices in the application but this was to noisy), and the downlink message scheduled, sent and acknowledged topics. To send messages to the device I published them on the device downlink topic.

//string uplinktopic = $"{applicationId}/devices/+/up";
string uplinktopic = $"{applicationId}/devices/{deviceId}/up";
await mqttClient.SubscribeAsync(uplinktopic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);

string downlinkAcktopic = $"{applicationId}/devices/{deviceId}/events/down/acks";
await mqttClient.SubscribeAsync(downlinkAcktopic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);

string downlinkScheduledtopic = $"{applicationId}/devices/{deviceId}/events/down/scheduled";
await mqttClient.SubscribeAsync(downlinkScheduledtopic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);

string downlinkSenttopic = $"{applicationId}/devices/{deviceId}/events/down/sent";
await mqttClient.SubscribeAsync(downlinkSenttopic, MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce);

string downlinktopic = $"{applicationId}/devices/{deviceId}/down";

I used the classes from one of my earlier blog posts to deserialise the uplink message payload so I could display a subset of the fields.

MQTTNet based .Net Core console client
Things Network Device Data view

In the TTN Device data tab I could see messages being sent, to and received from from the device.

Visual Studio 2019 Tiny CLR debugger Output

In the Visual Studio 2019 debugger output window I could see messages being sent and received by the Fezduino.

Malformed TTN downlink payload

I had some problems with the downlink messages silently failing as the TTN sample payload JSON was malformed and I had copied it without noticing.

I have a working TTN HTTP Integration (uplink messages only) but have been exploring alternatives using TTN MQTT and Azure IoT Hub AMQP clients.

The next step is to build an Azure IoT Hub client (using native AMQP) then join them together.

TinyCLR OS V2 RC1 RAK811 LoRaWAN library Part2

Nasty OTAA connect

After getting basic connectivity for my RAK811 LPWAN Evaluation Board(EVB) and Fezduino test rig working. I wanted to see if I could get the device connected to The Things Network(TTN) via the RAK7246G LPWAN Developer Gateway on my desk. I had got the EVB configuration sorted with a nanoFramework device so I was confident it should work.

EVB plugged into Fezduino

My Over the Air Activation (OTAA) implementation is very “nasty” I assumed that there would be no timeouts or failures and I only send one BCD message “48656c6c6f204c6f526157414e” which is “hello LoRaWAN”

I configured the RAK811 module for LoRaWAN

// Set the Working mode to LoRaWAN
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:work_mode:0\r\n"));
Debug.WriteLine($"TX: work mode {txByteCount} bytes");
Thread.Sleep(500);

// Read the response
rxByteCount = serialDevice.BytesToRead;
if (rxByteCount > 0)
{
   byte[] rxBuffer = new byte[rxByteCount];
   serialDevice.Read(rxBuffer);
   Debug.WriteLine($"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}
...

Then just sequentially step through the necessary configuration to join the TTN network with a suitable delay after each command is sent.

// Set the Region to AS923
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:region:AS923\r\n"));
Debug.WriteLine($"TX: region {txByteCount} bytes");
Thread.Sleep(500);

// Read the response
rxByteCount = serialDevice.BytesToRead;
if (rxByteCount > 0)
{
   byte[] rxBuffer = new byte[rxByteCount];
   serialDevice.Read(rxBuffer);
   Debug.WriteLine($"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}

// Set the JoinMode
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:join_mode:0\r\n"));
Debug.WriteLine($"TX: join_mode {txByteCount} bytes");
Thread.Sleep(500);

// Read the response
rxByteCount = serialDevice.BytesToRead;
if (rxByteCount > 0)
{
   byte[] rxBuffer = new byte[rxByteCount];
   serialDevice.Read(rxBuffer);
   Debug.WriteLine($"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}

// OTAA set the devEUI
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes($"at+set_config=lora:dev_eui:{DevEui}\r\n"));
Debug.WriteLine($"TX: dev_eui: {txByteCount} bytes");
Thread.Sleep(500);

// Read the response
rxByteCount = serialDevice.BytesToRead;
if (rxByteCount > 0)
{
   byte[] rxBuffer = new byte[rxByteCount];
   serialDevice.Read(rxBuffer);
   Debug.WriteLine($"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}
...

The code is not suitable for production but it confirmed my software and hardware configuration worked.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinOTAA starting
TX: work mode 32 bytes
RX :UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK 

TX: region 33 bytes
RX :OK 

TX: join_mode 32 bytes
RX :OK 

TX: dev_eui: 45 bytes
RX :OK 

TX: app_eui 45 bytes
RX :OK 

TX: app_key 61 bytes
RX :OK 

TX: confirm 30 bytes
RX :OK 

TX: join 9 bytes
RX :OK Join Success

TX: send 43 bytes
RX :OK 

TX: send 43 bytes
RX :OK 

In the Visual Studio 2019 debug output I could see messages getting sent and then after a short delay they were visible in the TTN console.

Successful OTAA Connect TTN logging

I had some issues with TimeSpan.ToString(…) throwing a CLR_E_UNSUPPORTED_INSTRUCTION exception which has been mentioned on the GHI Forums.

I had to modify my code to fix this issue

Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Join start Timeout:{JoinTimeOut:hh:mm:ss}");

// Became

Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} Join Start Timeout {timeout.TotalSeconds} seconds");

I won’t bother with confirming any other functionality as I’m reasonably confident the nanoFramework library (which this code is based on) is working as expected.

TinyCLR OS V2 RC1 RAK811 LoRaWAN library Part1

Basic connectivity

Over the weekend I have been working on a GHI Electronics TinyCLR V2  C# library for my modified RAK811 LPWAN Evaluation Board(EVB) from RAK Wireless. My initial test rig is based on an Fezduino board which has Arduino Uno R3 format socket for the EVB.

Fezduino with RAK Wisnode shield

The code has compile time options for synchronous and asynchronous operation.

   public class Program
   {
      private static UartController serialDevice;
      private const string ATCommand = "at+version\r\n";
#if TINYCLR_V2_FEZDUINO
      private static string SerialPortId = SC20100.UartPort.Uart5;
#endif

      public static void Main()
      {
         Debug.WriteLine("devMobile.IoT.Rak811.ShieldSerial starting");

         try
         {
            serialDevice = UartController.FromName(SerialPortId);

            serialDevice.SetActiveSettings(new UartSetting()
            {
               BaudRate = 9600,
               Parity = UartParity.None,
               StopBits = UartStopBitCount.One,
               Handshaking = UartHandshake.None,
               DataBits = 8
            });

            serialDevice.Enable();

#if SERIAL_ASYNC_READ
            serialDevice.DataReceived += SerialDevice_DataReceived;
#endif

            while (true)
            {
               byte[] txBuffer = UTF8Encoding.UTF8.GetBytes(ATCommand);

               int txByteCount = serialDevice.Write(txBuffer);
               Debug.WriteLine($"TX: {txByteCount} bytes");

#if SERIAL_SYNC_READ
               while( serialDevice.BytesToWrite>0)
               {
                  Debug.WriteLine($" BytesToWrite {serialDevice.BytesToWrite}");
                  Thread.Sleep(100);
               }

               int rxByteCount = serialDevice.BytesToRead;
               if (rxByteCount>0)
               {
                  byte[] rxBuffer = new byte[rxByteCount];

                  serialDevice.Read(rxBuffer);

                  Debug.WriteLine($"RX sync:{rxByteCount} bytes read");
                  String response = UTF8Encoding.UTF8.GetString(rxBuffer);
                  Debug.WriteLine($"RX sync:{response}");
               }
#endif

               Thread.Sleep(20000);
            }
         }
         catch (Exception ex)
         {
            Debug.WriteLine(ex.Message);
         }
      }


#if SERIAL_ASYNC_READ
      private static void SerialDevice_DataReceived(UartController sender, DataReceivedEventArgs e)
      {
         byte[] rxBuffer = new byte[e.Count];

         serialDevice.Read(rxBuffer, 0, e.Count);

         Debug.WriteLine($"RX Async:{e.Count} bytes read");
         String response = UTF8Encoding.UTF8.GetString(rxBuffer);
         Debug.WriteLine($"RX Async:{response}");
      }
#endif
   }

When I first ran the code I noticed the serialDevice.Read timed out before any characters were received.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

I then added code to check the message had been sent and the code worked as expected. I now think, that rather than checking that the characters had been sent the short 100mSec delay was more important.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
 BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
 BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
 BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

I then added code to receive data asynchronously and the response to the version request was received as expected.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
RX Async:1 bytes read
RX Async:O
RX Async:8 bytes read
RX Async:K V3.0.0
RX Async:10 bytes read
RX Async:.13.H.T3

TX: 12 bytes
RX Async:1 bytes read
RX Async:O
RX Async:5 bytes read
RX Async:K V3.
RX Async:9 bytes read
RX Async:0.0.13.H.
RX Async:4 bytes read
RX Async:T3

nanoFramework RAK811 LoRaWAN library Part7

Now with added callbacks

After building a nanoFramework library “inspired” by the RakWireless Arduino library(which has some issues) I figured it would be good to refactor the library to be more asynchronous with event handlers for send confirmation (if configured) and received messages.

If the RAK811 module is initialised, and connects to the network successfully, the application sends “48656c6c6f204c6f526157414e” (“hello LoRaWAN”) every 5 minutes.

STM32F691Discovery with EVB plugged into Arduino headers

The code application code now has a lot more compile time options for network configuration and payload format.

//---------------------------------------------------------------------------------
// Copyright (c) June 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
#define ST_STM32F769I_DISCOVERY      // nanoff --target ST_STM32F769I_DISCOVERY --update 
#define PAYLOAD_BCD
//#define PAYLOAD_BYTES
#define OTAA
//#define ABP
#define CONFIRMED
namespace devMobile.IoT.Rak811LoRaWanDeviceClient
{
   using System;
   using System.Threading;
   using System.Diagnostics;
   using Windows.Devices.SerialCommunication;

   using devMobile.IoT.LoRaWan;

   public class Program
   {
#if ST_STM32F769I_DISCOVERY
      private const string SerialPortId = "COM6";
#endif
#if OTAA
      private const string DevEui = "...";
      private const string AppEui = "...";
      private const string AppKey = "...";
#endif
#if ABP
      private const string DevAddress = "...";
      private const string NwksKey = "...";
      private const string AppsKey = "...";
#endif
      private const string Region = "AS923";
      private static readonly TimeSpan JoinTimeOut = new TimeSpan(0, 0, 10);
      private static readonly TimeSpan SendTimeout = new TimeSpan(0, 0, 10);
      private const byte MessagePort = 1;
#if PAYLOAD_BCD
      private const string PayloadBcd = "48656c6c6f204c6f526157414e"; // Hello LoRaWAN in BCD
#endif
#if PAYLOAD_BYTES
      private static readonly byte[] PayloadBytes = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x4c, 0x6f, 0x52, 0x61, 0x57, 0x41, 0x4e}; // Hello LoRaWAN in bytes
#endif

      public static void Main()
      {
         Result result;

         Debug.WriteLine("devMobile.IoT.Rak811LoRaWanDeviceClient starting");

         Debug.WriteLine($"Ports :{Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector()}");

         try
         {
            using ( Rak811LoRaWanDevice device = new Rak811LoRaWanDevice())
            {
               result = device.Initialise(SerialPortId, 9600, SerialParity.None, 8, SerialStopBitCount.One);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Initialise failed {result}");
                  return;
               }

#if CONFIRMED
               device.OnMessageConfirmation += OnMessageConfirmationHandler;
#endif
               device.OnReceiveMessage += OnReceiveMessageHandler;

               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Region {Region}");
               result = device.Region(Region);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Region failed {result}");
                  return;
               }

               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} ADR On");
               result = device.AdrOn();
               if (result != Result.Success)
               {
                  Debug.WriteLine($"ADR on failed {result}");
                  return;
               }

#if CONFIRMED
               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Confirmed");
               result = device.Confirm(LoRaConfirmType.Confirmed);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Confirm on failed {result}");
                  return;
               }
#else
               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Unconfirmed");
               result = device.Confirm(LoRaConfirmType.Unconfirmed);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Confirm off failed {result}");
                  return;
               }
#endif

#if OTAA
               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} OTAA");
               result = device.OtaaInitialise(DevEui, AppEui, AppKey);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"OTAA Initialise failed {result}");
                  return;
               }
#endif

#if ABP
               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} ABP");
               result = device.AbpInitialise(DevAddress, NwksKey, AppsKey);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"ABP Initialise failed {result}");
                  return;
               }
#endif

               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Join start Timeout:{JoinTimeOut:hh:mm:ss}");
               result = device.Join(JoinTimeOut);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Join failed {result}");
                  return;
               }
               Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Join finish");

               while (true)
               {
#if PAYLOAD_BCD
                  Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Send Timeout:{SendTimeout:hh:mm:ss} port:{MessagePort} payload BCD:{PayloadBcd}");
                  result = device.Send(MessagePort, PayloadBcd, SendTimeout);
#endif
#if PAYLOAD_BYTES
                  Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Send Timeout:{SendTimeout:hh:mm:ss} port:{MessagePort} payload Bytes:{BitConverter.ToString(PayloadBytes)}");
                  result = device.Send(MessagePort, PayloadBytes, SendTimeout);
#endif
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Send failed {result}");
                  }

                  // if we sleep module too soon response is missed
                  Thread.Sleep(new TimeSpan( 0,0,5));

                  Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Sleep");
                  result = device.Sleep();
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Sleep failed {result}");
                     return;
                  }

                  Thread.Sleep(new TimeSpan(0, 5, 0));

                  Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Wakeup");
                  result = device.Wakeup();
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Wakeup failed {result}");
                     return;
                  }
               }
            }
         }
         catch (Exception ex)
         {
            Debug.WriteLine(ex.Message);
         }
      }

      static void OnMessageConfirmationHandler(int rssi, int snr)
      {
         Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Send Confirm RSSI:{rssi} SNR:{snr}");
      }

      static void OnReceiveMessageHandler(int port, int rssi, int snr, string payloadBcd)
      {
         byte[] payloadBytes = Rak811LoRaWanDevice.BcdToByes(payloadBcd);

         Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Receive Message RSSI:{rssi} SNR:{snr} Port:{port} Payload:{payloadBcd} PayLoadBytes:{BitConverter.ToString(payloadBytes)}");
      }
   }
}

The debugging output with the Rak811LoRaWanDevice class diagnostics off

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811LoRaWanDeviceClient starting
Ports :COM5,COM6
12:00:51 Region AS923
12:00:51 ADR On
12:00:51 Confirmed
12:00:52 OTAA
12:00:52 Join start Timeout:00:00:10
12:00:59 Join finish
12:00:59 Send Timeout:00:00:10 port:1 payload BCD:48656c6c6f204c6f526157414e
12:01:02 Send Confirm RSSI:-79 SNR:9
12:01:07 Sleep
12:06:07 Wakeup
12:06:07 Send Timeout:00:00:10 port:1 payload BCD:48656c6c6f204c6f526157414e
12:06:08 Send Confirm RSSI:-65 SNR:8
12:06:13 Sleep
12:11:13 Wakeup
12:11:13 Send Timeout:00:00:10 port:1 payload BCD:48656c6c6f204c6f526157414e
12:11:15 Send Confirm RSSI:-60 SNR:7
12:11:15 Receive Message RSSI:-60 SNR:7 Port:5 Payload:48656c6c6f PayLoadBytes:48-65-6C-6C-6F
12:11:20 Sleep
TTN OTAA Connection + RX&TX

Some commands are quite quick to respond e.g. setting the Region, Sleep, and Wakeup. Others, take quite a while e.g. Join, Send, WorkMode so they have separate timeout configurations.

The code is approaching beta and I’ll be testing and fixing bugs for the next couple of days.

nanoFramework RAK811 LoRaWAN library Part6

Inspired by the Arduino Library

After successful proof of concept projects I have build a nanoFramwork library “inspired” by the RakWireless Arduino library.

The initial version only supports my RAK811 LPWAN Evaluation Board(EVB) and STM32F691DISCOVERY based test rig It handles failures, displays error codes/messages, but doesn’t handle all timeouts.

If the RAK811 module is initialised, then connects to the network successfully, the application sends “48656c6c6f204c6f526157414e” (“hello LoRaWAN”) every 20 seconds.

STM32F691Discovery with EVB plugged into Arduino headers

The code application code is now a lot smaller & simpler

   public class Program
   {
#if ST_STM32F769I_DISCOVERY
      private const string SerialPortId = "COM6";
#endif
#if OTAA
      private const string DevEui = "...";
      private const string AppEui = "...";
      private const string AppKey = "...";
#endif
#if ABP
      private const string devAddress = "...";
      private const string nwksKey = "...";
      private const string appsKey = "...";
#endif
      private const byte MessagePort = 1;
      private const string Payload = "48656c6c6f204c6f526157414e"; // Hello LoRaWAN

      public static void Main()
      {
         Result result;

         Debug.WriteLine(" devMobile.IoT.Rak811LoRaWanDeviceClient starting");

         Debug.WriteLine(Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector());

         try
         {
            using ( Rak811LoRaWanDevice device = new Rak811LoRaWanDevice())
            {
               result = device.Initialise(SerialPortId, SerialParity.None, 8, SerialStopBitCount.One);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Initialise failed {result}");
                  return;
               }

               result = device.Region("AS923");
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Region failed {result}");
                  return;
               }

#if OTAA
               result = device.OtaaInitialise(DevEui, AppEui, AppKey);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"OTAA Initialise failed {result}");
                  return;
               }
#endif

#if ABP
               result = device.AbpInitialise(devAddress, nwksKey, appsKey);
               if (result != Result.Success)
               {
                  Debug.WriteLine($"ABP Initialise failed {result}");
                  return;
               }
#endif

               result = device.Join(new TimeSpan(0,0,10));
               if (result != Result.Success)
               {
                  Debug.WriteLine($"Join failed {result}");
                  return;
               }

               while (true)
               {
                  result = device.Send(MessagePort, Payload);
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Send failed {result}");
                  }

                  result = device.Sleep();
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Sleep failed {result}");
                     return;
                  }

                  Thread.Sleep(20000);

                  result = device.Wakeup();
                  if (result != Result.Success)
                  {
                     Debug.WriteLine($"Wakeup failed {result}");
                     return;
                  }
               }
            }
         }
         catch (Exception ex)
         {
            Debug.WriteLine(ex.Message);
         }
      }
   }

I compared the debugging output with confirmations off

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
 devMobile.IoT.Rak811LoRaWanDeviceClient starting
COM5,COM6
01:11:13 lora:work_mode
TX: send 32 bytes 32 via COM6
RX 01:11:14:UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK 

01:11:15 lora:region
TX: send 33 bytes 33 via COM6
RX 01:11:16:OK 

01:11:16 lora:join_mode
TX: send 32 bytes 32 via COM6
RX 01:11:17:OK 

01:11:18 lora:dev_eui
TX: send 45 bytes 45 via COM6
RX 01:11:19:OK 

01:11:19 lora:app_eui
TX: send 45 bytes 45 via COM6
RX 01:11:20:OK 

01:11:21 lora:app_key
TX: send 61 bytes 61 via COM6
RX 01:11:22:OK 

01:11:22 join
TX: send 9 bytes 9 via COM6
RX 01:11:29:OK Join Success

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK 

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK 
at+recv=1,-54,9,5:48656c6c6f

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK 
at+recv=2,-51,7,5:48656c6c6f

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6

Then with confirmations on (note the at+recv=0,-59,7,0) and received messages (at+recv=23,-53,8,5:48656c6c6f)

devMobile.IoT.Rak811LoRaWanDeviceClient starting
COM5,COM6
01:20:54 lora:work_mode
TX: send 32 bytes 32 via COM6
RX 01:20:56:UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK 

01:20:56 lora:region
TX: send 33 bytes 33 via COM6
RX 01:20:57:OK 

01:20:58 lora:join_mode
TX: send 32 bytes 32 via COM6
RX 01:20:59:OK 

01:20:59 lora:dev_eui
TX: send 45 bytes 45 via COM6
RX 01:21:00:OK 

01:21:01 lora:app_eui
TX: send 45 bytes 45 via COM6
RX 01:21:02:OK 

01:21:02 lora:app_key
TX: send 61 bytes 61 via COM6
RX 01:21:03:OK 

01:21:04 join
TX: send 9 bytes 9 via COM6
RX 01:21:11:OK Join Success

01:21:11 lora:confirm
TX: send 30 bytes 30 via COM6
RX 01:21:12:OK 

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK 
at+recv=23,-53,8,5:48656c6c6f

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK 
at+recv=0,-59,7,0

In the Visual Studio 2019 debug output I could see the responses to the AT Commands and especially the lack of handling of downlink messages and confirmations from the network.

The next step is to implement timeouts for when operations fail or the module doesn’t respond. Then extend the code to support the receiving of messages as a class A device (missing for the RAK arduino library). I wonder how this will work for when the module is configured as a class C device which can receive messages at any time.

Some commands are quite quick to respond e.g. setting the Region, Sleep, and Wakeup so are most probably ok running synchronously. Other commands can take quite a while e.g. Join, Send, WorkMode so maybe these need to be asynchronous (along with the receiving of confirmations and messages ).

The code is not suitable for production but it confirmed my new approach worked.

nanoFramework RAK811 LoRaWAN library Part5

Nasty ABP connect

After a successful Over The Air Activation(OTAA) with my RAK811 LPWAN Evaluation Board(EVB) and STM32F691DISCOVERY based test rig. I figured for completeness an Activation by Personalization (ABP) would be a good.

My ABP implementation is based on my OTAA one so is pretty “nasty”. Again, I assumed that there would be no timeouts or failures and I only send one message BCD “48656c6c6f204c6f526157414e” (“hello LoRaWAN”) every 20 seconds.

STM32F691Discovery with EVB plugged into Arduino headers

I created a new ABP device

Things Network ABP configuration

Then I configured the RAK811 module for LoRaWAN

// Set the Working mode to LoRaWAN
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:work_mode:0rn");
Debug.WriteLine($"TX: work_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   string response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX sync:{response}");
}

Then sequentially stepped through the necessary configuration to join the The Things Network(TTN) network

// Set the JoinMode to ABP
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:join_mode:1\r\n");
Debug.WriteLine($"TX: join_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   String response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX :{response}");
}

// Set the device address
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:dev_addr:{devAddress}\r\n");
Debug.WriteLine($"TX: dev_addr {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
   {
   String response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX :{response}");
   }
...

After making a few fixes to my code and tweaking some settings I could see data in the TTN Console.

ABP Device data uplink

The code is not suitable for production but it confirmed my software and hardware configuration worked.

In the Visual Studio 2019 debug output I could see the AT Command responses from were getting truncated in odd ways so I need to be careful how they are processed.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinABP starting
COM5,COM6
TX: work_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX :UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:ABP, Class: A
Initialization OK 

TX: region 33 bytes to output stream.
TX: 33 bytes via COM6
RX :OK 

TX: join_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX :OK 

TX: dev_addr 38 bytes to output stream.
TX: 38 bytes via COM6
RX :OK 

TX: nwks_key 62 bytes to output stream.
TX: 62 bytes via COM6
RX :OK 

TX: apps_key 62 bytes to output stream.
TX: 62 bytes via COM6
RX :OK 

TX: confirm 30 bytes to output stream.
TX: 30 bytes via COM6
RX :OK 

TX: join 9 bytes to output stream.
TX: 9 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :OK Jo
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :in Su
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :ccess
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :
OK 
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX :
OK 

The next step is to get rework the code to process responses to the AT commands in a smarter way and extract error codes when an operation fails.

nanoFramework RAK811 LoRaWAN library Part4

Nasty OTAA connect

After getting basic connectivity for my RAK811 LPWAN Evaluation Board(EVB) and STM32F691DISCOVERY test rig sorted. I wanted to see if I could get the device connected to The Things Network(TTN) via the RAK7246G LPWAN Developer Gateway which was on my desk. I had got the EVB configuration sorted with an Arduino Uno R3 device so I was confident it should work.

STM32F691Discovery with EVB plugged into Arduino headers

My Over the Air Activation(OTAA) implementation is pretty “nasty” I assumed that there would be no timeouts or failures and I only send one BCD message “48656c6c6f204c6f526157414e” which is “hello LoRaWAN”

I configured the RAK811 module for LoRaWAN

// Set the Working mode to LoRaWAN
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:work_mode:0\r\n");
Debug.WriteLine($"TX: work_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   string response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX sync:{response}");
}

Then just sequentially stepped through the necessary configuration to join the TTN network

// Set the Region to AS923
bytesWritten = outputDataWriter.WriteString("at+set_config=lora:region:AS923\r\n");
Debug.WriteLine($"TX: region {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   String response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX sync:{response}");
}

// Set the JoinMode
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:join_mode:0\r\n");
Debug.WriteLine($"TX: join_mode {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   String response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX sync:{response}");
}

// OTAA set the devEUI
bytesWritten = outputDataWriter.WriteString($"at+set_config=lora:dev_eui:{devEui}\r\n");
Debug.WriteLine($"TX: dev_eui {outputDataWriter.UnstoredBufferLength} bytes to output stream.");
txByteCount = outputDataWriter.Store();
Debug.WriteLine($"TX: {txByteCount} bytes via {serialDevice.PortName}");

// Read the response
bytesRead = inputDataReader.Load(128);
if (bytesRead > 0)
{
   String response = inputDataReader.ReadString(bytesRead);
   Debug.WriteLine($"RX sync:{response}");
}
...

The code is not suitable for production but it confirmed my software and hardware configuration worked.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinOTAA starting
COM5,COM6
TX: work_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX sync:UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK 

TX: region 33 bytes to output stream.
TX: 33 bytes via COM6
RX sync:OK 

TX: join_mode 32 bytes to output stream.
TX: 32 bytes via COM6
RX sync:OK 

TX: dev_eui 45 bytes to output stream.
TX: 45 bytes via COM6
RX sync:OK 

TX: app_eui 45 bytes to output stream.
TX: 45 bytes via COM6
RX sync:OK 

TX: app_key 61 bytes to output stream.
TX: 61 bytes via COM6
RX sync:OK 

TX: confirm 30 bytes to output stream.
TX: 30 bytes via COM6
RX sync:OK 

TX: join 9 bytes to output stream.
TX: 9 bytes via COM6
RX sync:
RX sync:
RX sync:
RX sync:
RX sync:OK Join Success

TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
TX: send 43 bytes to output stream.
TX: 43 bytes via COM6
RX sync:OK 
at+recv=0,-59,9,0

In the Visual Studio 2019 debug out put I could see messages getting sent and then after a short delay they were visible in the TTN console.

I then modified the confirmed flag and in the TTN console I could see how they were processed differently.

Confirmed messages
Unconfirmed messages

I could receive messages but as the RAK 811 module can be configured to be a Class C device there didn’t appear to be a way to receive a message without sending one which seemed a bit odd.

The next step is to get Authentication By Personalisation(ABP) working.