# 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…

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.

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";

```

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.

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

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

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.

# Cayenne Low Power Payload (LPP) Encoder

Reducing the size of message payloads is important for LoRa/LoRaWAN communications, as it reduces power consumption and bandwidth usage. One of the more common formats is myDevices Cayenne Low Power Payload(LPP) which is based on the IPSO Alliance Smart Objects Guidelines and is natively supported by The Things Network(TTN).

``` private enum DataType : byte
{
DigitalInput = 0, // 1 byte
DigitialOutput = 1, // 1 byte
AnalogInput = 2, // 2 bytes, 0.01 signed
AnalogOutput = 3, // 2 bytes, 0.01 signed
Luminosity = 101, // 2 bytes, 1 lux unsigned
Presence = 102, // 1 byte, 1
Temperature = 103, // 2 bytes, 0.1°C signed
RelativeHumidity = 104, // 1 byte, 0.5% unsigned
Accelerometer = 113, // 2 bytes per axis, 0.001G
BarometricPressure = 115, // 2 bytes 0.1 hPa Unsigned
Gyrometer = 134, // 2 bytes per axis, 0.01 °/s
Gps = 136, // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m
}
```

My implementation was “inspired” by the myDevices C/C++ sample code. The first step was to allocate a buffer to store the byte encoded values. I pre allocated the buffer to try and reduce the impacts of garbage collection. The code uses a manually incremented index into the buffer for performance reasons, plus the inconsistent support of System.Collections.Generic and Language Integrated Query(LINQ) on my three embedded platforms. The maximum length message that can be sent is limited by coding rate, duty cycle and bandwidth of the LoRa channel.

```public Encoder(byte bufferSize)
{
if ((bufferSize < BufferSizeMinimum) || ( bufferSize > BufferSizeMaximum))
{
throw new ArgumentException(\$"BufferSize must be between {BufferSizeMinimum} and {BufferSizeMaximum}", "bufferSize");
}

buffer = new byte[bufferSize];
}
```

For a simple data types like a digital input a single byte (True or False ) is used. The channel parameter is included so that multiple values of the same data type can be included in a message.

```public void DigitalInputAdd(byte channel, bool value)
{
if ((index + DigitalInputSize) > buffer.Length)
{
throw new ApplicationException("DigitalInputAdd insufficent buffer capacity");
}

buffer[index++] = channel;
buffer[index++] = (byte)DataType.DigitalInput;
// I know this is fugly but it works on all platforms
if (value)
{
buffer[index++] = 1;
}
else
{
buffer[index++] = 0;
}
}
```

For more complex data types like a Global Positioning System(GPS) location (Latitude, Longitude and Altitude) the values are converted to 32bit signed integers and only 3 of the 4 bytes are used.

```public void GpsAdd(byte channel, float latitude, float longitude, float meters)
{
if ((index + GpsSize) > buffer.Length)
{
throw new ApplicationException("GpsAdd insufficent buffer capacity");
}

int lat = (int)(latitude * 10000);
int lon = (int)(longitude * 10000);
int alt = (int)(meters * 100);

buffer[index++] = channel;
buffer[index++] = (byte)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;
}
```

Before the message can be sent it needs to be converted to its Binary Coded Decimal(BCD) representation and all formatting characters removed.

```public string Bcd()
{
StringBuilder payloadBcd = new StringBuilder(BitConverter.ToString(buffer, 0, index));

}
```

The implementation had to be revised a couple of times so It would work with desktop and GHI Electronics TinyCLRV2 powered devices. There maybe some modifications required as I port it to nanoFramework and Wilderness Labs Meadow devices.

# nRF24L01-TinyCLR V2 RC2 on Github

The source code of RC2 of my port GHI Electronics TinyCLR-0SV2RC1 nRF24L01 library is live on GitHub. The sample application now supports Fezduino (with embeddedcoolness.com or other Arduino shield), Fezportal and the SC2010 Dev board (with mikroe nrf24C Click, mikroe nRF24S Click or mikroenRF24T Click) .

The application has gained four compile time configuration options

• TINYCLR_V2_SC20100DEV_MIKROBUS_1
• TINYCLR_V2_SC20100DEV_MIKROBUS_2
• TINYCLR_V2_FEZDUINO
• TINYCLR_V2_FEZPORTAL

These options configure the chip enable, chip selected and interrupt pins.

```//---------------------------------------------------------------------------------
// Copyright (c) May 2020, devMobile Software
//
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//
// Need one of TINYCLR_V2_SC20100DEV_MIKROBUS_1/TINYCLR_V2_SC20100DEV_MIKROBUS_2/TINYCLR_V2_FEZDUINO/TINYCLR_V2_FEZPORTAL defined
//---------------------------------------------------------------------------------
namespace devMobile.IoT.FieldGateway.TinyCLRV2nRF24Client
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

class Program
{
private const string BaseStationAddress = "Base1";
private const string DeviceAddress = "Dev01";

static void Main()
{
byte messageCount = System.Byte.MaxValue;

try
{

#if TINYCLR_V2_SC20100DEV_MIKROBUS_1
#endif
#if TINYCLR_V2_SC20100DEV_MIKROBUS_2
#endif
#if TINYCLR_V2_FEZDUINO
#endif
#if TINYCLR_V2_FEZPORTAL
#endif

while (true)
{
messageCount -= 1;

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

{
// display as hex
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-RX Hex Length {data.Length} Payload {BitConverter.ToString(data)}");

// Display as Unicode
string unicodeText = Encoding.UTF8.GetString(data);
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-RX Unicode Length {unicodeText.Length} Unicode text {unicodeText}");
}

{
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-TX Succeeded!");
}

{
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-TX failed!");
}
}
}
```

# RFM9X.TinyCLR V2 RC2 on Github

The source code of RC2 of my GHI Electronics TinyCLR-0SV2RC1 RFM9X/SX127X library is live on GitHub. The sample application now supports Fezduino(dragino_LoRa shield for Arduino + others), Fezportal and the SC2010 Dev board (with CascoLogix LoRa Click) . I will add FezFeather and Fezstick support soon.

```//---------------------------------------------------------------------------------
// Copyright (c) March/April 2020, devMobile Software
//
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//
// Need one of TINYCLR_V2_SC20100DEV_MIKROBUS_1/TINYCLR_V2_SC20100DEV_MIKROBUS_2/TINYCLR_V2_FEZDUINO/TINYCLR_V2_FEZPORTAL defined
//---------------------------------------------------------------------------------
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

using devMobile.IoT.Rfm9x;

class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV_MIKROBUS_1 || TINYCLR_V2_SC20100DEV_MIKROBUS_2
const string DeviceName = "SC20100DEVLoRa";
#endif
#if TINYCLR_V2_FEZDUINO
const string DeviceName = "FezduinoLoRa";
#endif
#if TINYCLR_V2_FEZPORTAL
const string DeviceName = "FezportalLoRa";
#endif
const string HostName = "LoRaIoT1";
#endif
const double Frequency = 915000000.0;
byte MessageCount = System.Byte.MaxValue;
#if TINYCLR_V2_SC20100DEV_MIKROBUS_1
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD3, SC20100.GpioPin.PD4, SC20100.GpioPin.PC5);
#endif
#if TINYCLR_V2_SC20100DEV_MIKROBUS_2
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD14, SC20100.GpioPin.PD15, SC20100.GpioPin.PA8);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1);
#endif
#if TINYCLR_V2_FEZPORTAL
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PC13, SC20100.GpioPin.PD4,SC20100.GpioPin.PC2);
#endif

#if DEBUG
rfm9XDevice.RegisterDump();
#endif

#else
#endif
rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

while (true)
{
string messageText = string.Format("Hello from {0} ! {1}", DeviceName, MessageCount);
MessageCount -= 1;

byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
Debug.WriteLine(\$"{DateTime.Now:HH:mm:ss}-TX {messageBytes.Length} byte message {messageText}");
rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
rfm9XDevice.Send(messageBytes);
#endif
}
}

{
try
{
string messageText = UTF8Encoding.UTF8.GetString(e.Data);

#else
#endif
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}

private static void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
{
Debug.WriteLine(\$"{DateTime.Now:HH:mm:ss}-TX Done");
}
}
}
```

The library works but should be treated as late beta.

# TinyCLR OS V2 RC1 LoRa library Part4

## Interrupts Revisited

In my last post I had two approaches for fixing the issue with transmit interrupts. As I was sorting out the configuration for my Fezportal device and SS20100 Dev board(both sockets) with a Cascologix MikroBus LoRa click I had similar issues with both receive and transmit.

I noticed the extra RegIrqFlags 0X58, Receive-Message in the debugging output.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Sending 13 bytes message Hello LoRa 1!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 2!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X58
Received 23 byte message  �LoRaIoT1N3WT 20.5,H 86
Transmit-Done
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
```

This was with the modified device write methods so I tried changing the Serial Peripheral Interface(SPI) configuration.

```public RegisterManager(string spiPortName,  int chipSelectPin, int clockFrequency = 500000)
{
GpioPin chipSelectGpio = GpioController.GetDefault().OpenPin(chipSelectPin);

var settings = new SpiConnectionSettings()
{
ChipSelectType = SpiChipSelectType.Gpio,
ChipSelectLine = chipSelectGpio,
Mode = SpiMode.Mode0,
ClockFrequency = clockFrequency,
ChipSelectActiveState = false,
ChipSelectHoldTime = new TimeSpan(1),
};

SpiController spiController = SpiController.FromName(spiPortName);

rfm9XLoraModem = spiController.GetDevice(settings);
}
```

When I added the ChipSelectHoldTime (even the smallest possible one) the code worked as expected.

```The thread '' (0x2) has exited with code 0 (0x0).
Sending 13 bytes message Hello LoRa 1!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 2!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Received 23 byte message �LoRaIoT1N3WT 20.5,H 87
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 4!
RegIrqFlags 0X08
Transmit-Done```

At this point I have run out of ideas so I will release the code this fix.

# RAK811LoRaWAN.TinyCLR on Github

The source code of my TinyCLR V2 RAK811 Module library is now available on GitHub. My demonstration application uses a FezDuino and a modified RAK811 LPWAN Evaluation Board(EVB).

A sample application which shows how to connect using Over the Air Activation(OTAA) or Activation By Personalisation(ABP) then send and receive byte array/Binary Coded Decimal(BCD) messages .

```//---------------------------------------------------------------------------------
// Copyright (c) July 2020, devMobile Software
//
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//
// must have one of
//    TINYCLR_V2_FEZDUINO or
//    OTAA or ABP
//
// For confirmed messages define CONFIRMED
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rak811LoRaWanDeviceClient
{
using System;
using System.Diagnostics;

using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.Devices.Uart;

using devMobile.IoT.LoRaWan;

public class Program
{
#if TINYCLR_V2_FEZDUINO
private const string SerialPortId = SC20100.UartPort.Uart5;
#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;
private const string PayloadBcd = "48656c6c6f204c6f526157414e"; // Hello LoRaWAN in BCD
#endif
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");

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

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

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

if (result != Result.Success)
{
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");
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)
{
#endif
#endif
if (result != Result.Success)
{
Debug.WriteLine(\$"Send failed {result}");
}

// if we sleep module too soon response is missed

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

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)
{
}

{

}
}
}
```

I noticed (mid July 2020) that when changing from from ABP to OTAA and vice versa I needed to reset the the device “frame counters” in the Things Network console.

# 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.

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");

if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
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");

if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
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");

if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
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");

if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
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.

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.

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();

#endif

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

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

while( serialDevice.BytesToWrite>0)
{
Debug.WriteLine(\$" BytesToWrite {serialDevice.BytesToWrite}");
}

if (rxByteCount>0)
{
byte[] rxBuffer = new byte[rxByteCount];

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

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

{
byte[] rxBuffer = new byte[e.Count];

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:OK V3.0.0.13.H.T3

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

TX: 12 bytes
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:OK V3.0.0.13.H.T3

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

TX: 12 bytes
BytesToWrite 10
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:O
RX Async:K V3.0.0
RX Async:.13.H.T3

TX: 12 bytes
RX Async:O
RX Async:K V3.
RX Async:0.0.13.H.
RX Async:T3
```

# RFM9X.TinyCLR V2 RC1 on Github

The source code of my GHI Electronics TinyCLR-0SV2RC1 RFM9X/SX127X library is live on GitHub. The test harness uses a Fezduino and a dragino technology LoRa shield for Arduino. I will add FezPortal, FezFeather and Fezstick support soon.

```//---------------------------------------------------------------------------------
// Copyright (c) March/April 2020, devMobile Software
//
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
//
// Need one of TINYCLR_V2_SC20100DEV/TINYCLR_V2_FEZDUINO defined
//---------------------------------------------------------------------------------
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

using devMobile.IoT.Rfm9x;

class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
const string DeviceName = "SC20100DEVLoRa";
#endif
#if TINYCLR_V2_FEZDUINO
const string DeviceName = "FezduinoLoRa";
#endif
const string HostName = "LoRaIoT1";
#endif
const double Frequency = 915000000.0;
byte MessageCount = System.Byte.MaxValue;
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1);
#endif

#if DEBUG
rfm9XDevice.RegisterDump();
#endif

#else
#endif
rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

while (true)
{
string messageText = string.Format("Hello from {0} ! {1}", DeviceName, MessageCount);
MessageCount -= 1;

byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
Debug.WriteLine(\$"{DateTime.Now:HH:mm:ss}-TX {messageBytes.Length} byte message {messageText}");
rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
rfm9XDevice.Send(messageBytes);
#endif
}
}

{
try
{
string messageText = UTF8Encoding.UTF8.GetString(e.Data);

#else
#endif
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}

private static void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
{
Debug.WriteLine(\$"{DateTime.Now:HH:mm:ss}-TX Done");
}
}
}
```

The addressing support is pretty basic as my goal was a library that I could extend with optional functionality like tamper detection via signing and privacy via payload encryption, mesh network support etc.

The library works but should be treated as late beta.