.NET nanoFramework RAK2305 – RAK4630 Basic connectivity issue

After much trial and error, I couldn’t get a RAK2305 WisBlock Wifi Interface Module running the .NET nanoFramework plugged into the IO Slot of RAK5005 Base Board to send RUIV3 AT Commands to a RAK4631 Module.

RA2305, RAK5005, RAK4631 test rig

When I requested the version information with “AT+VER=?” the RAK4630 Module did not respond with version information.

RA2305, RAK5005, RAK4631 firmware version request failure

I reviewed the schematics of the RAK2305 WisBlock Wifi Interface Module, RAK5005 Base Board and RAK4630 Module

RAK2305 ESP32 schematic

The RAK2305 WisBlock Wifi Interface Module has the TX0 pin is connected to pin 11, RX0 is connected to pin 12, TX1 pin (Gpio.IO21) is connected to pin 34, and the RX1 pin (Gpio.IO21) is connected to pin 33 of the IO Slot connector (crossover TX & RX).

RAK5005 schematic

On the RAK5005 Base Board the RAK2305 WisBlock Wifi Interface Module is plugged into the IO Extension Slot (BTB40_F) with TX pin 33 and RX pin 34.

RAK5005 schematic 2
RAK4361 schematic 1

The RAK4630 Module is plugged into the CPU slot (BTB40_F) of the RAK5005 Base Board with TX pin 33 and RX pin 34. The RAK4630 Module UART2_TX pin is connected to 33, and the UART2_RX is connected to 34 on the CPU slot.

I then read the RAK4630 AT Command documentation to see if I could enable AT Commands on the second serial port

I tried AT+ATM which didn’t work, and I had to reflash the device to get the UART0(USB port) to work. The TXD0 & RXD0 on the RAK4630 Module & RAK2305 WisBlock Wifi Interface Modules are connected by the RAK5005 Base Board. The .NET nanoFramework uses TXDO & RXD0 for debugging so I couldn’t connect to the first serial port on the RAK4630 Module

I had a look at the RAK4360 RAK Unified Interface (RUI) code to see if I could modify it so UART1 responded to AT Commands but I’m not certain this would work.

This is a longish post about failure, it took many hours to explore all the different approaches which was way longer than I should have spent. For why see “sunk cost fallacy”

.NET nanoFramework RAK2305 – RAK4200 Library Usage (AS923 Sorted)

This post covers the usage of my RAK4200LoRaWAN-NetNF library with a RAK3205 WisBlock Wifi Interface Module on a RAK4200 Evaluation Board. This post was delayed because of the issue covered in .NET nanoFramework RAK2305 – RAK4200 Library Usage AS923 Issue. After posting in the RAKWireless formus RAKWireless support very quickly provided updated RAK4200 firmware which fixed the issue.

RAK2305 RAK4200 Evaluation Board based test rig

The RAK4200LoRaWANDeviceClient now reliably joins The Things Network, then sends and receives messages.

When I initially deployed the RAK4200LoRaWANDeviceClient the RAK4200LoRaWAN-NetNF library failed in the OtaaInitialise method. I think this was caused by the “at+set_config=lora:work_mode:0” command rebooting the RAK4200 Module. I have commented out the code but may move it to a standalone method if required.

// Set the Working mode to LoRaWAN, not/never going todo P2P with this library.
#if DIAGNOSTICS
Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:work_mode:0");
#endif
Result result = SendCommand("Initialization OK", "at+set_config=lora:work_mode:0", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
         Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} at+set_config=lora:work_mode:0 failed {result}");
#endif
	return result;
}

I think it would be reasonable to assume that the device is in the correct mode (the default after a reset to factory) on startup so I removed the LoRa® network work mode configuration code.

.NET nanoFramework RAK2305 – RAK4200 Library AS923 Issue

This post was going to be about how to the use my RAK4200LoRaWAN-NetNF library with a RAK3205 WisBlock Wifi Interface Module and RAK4200 Evaluation Board but there was a problem…

RAK2305 RAK4200 Evaluation Board based test rig

When I ran the RAK4200LoRaWANDeviceClient the first couple of join attempts failed which was odd as my sparkfun ESP32 thing plus with RAK4200 Breakout Board setup was very reliable.

Visual Studio Debug output for RAK4200LoRaWANDeviceClient Join failure
The Things Network RAK4200LoRaWANDeviceClient application Join failure

When I looked at The Things Network “Live data” tab the RAK4200 Module on the RAK4200 Evaluation Board wasn’t using the LoRaWAN AS923 Join-Request channels 923.20 & 923.40 MHz.

AS923 Join Channels

The RAK4200 Module on the appeared to be cycling through all the AS923 channels and every so often would use one the join request channels.

Visual Studio Debug output for RAK4200LoRaWANDeviceClient successful Join and Send
The Things Network RAK4200LoRaWANDeviceClient successful Join and Send

The RAK4200 Breakout Board module is running a later firmware version (V3.2.0.16) than the RAK4200 Evaluation Board module (V3.2.0.15) which is most probably the problem.

Visual Studio Debug output for RAK4200 Evaluation Board Version Request
Visual Studio Debug output for RAK4200 Breakout Board Version Request

The RAK811 module (which has been retired) also had similar issues with AS923.

.NET nanoFramework RAK2305 – RAK4200 Basic connectivity

After some experimentation could get a RAK2305 WisBlock Wifi Interface Module running the .NET nanoFramework plugged into the IO Slot of RAK4200 Evaluation Board to send AT Commands to the RAK4200 Module.

RAK4200 EVB with FTDI Adaptor

After reviewing the RAK4200 Evaluation Board and RAK2305 WisBlock Wifi Interface Module schematics I realised that the Universal Asynchronous Receiver-Transmistted(UART) transmit and receive pins had to be reversed the with the nanoFramwork ESP32 specific Configuration.SetPinFunction.

namespace devMobile.IoT.LoRaWAN.nanoFramework.RAK4200
{
	using System;
	using System.Diagnostics;
	using System.IO.Ports;
	using System.Threading;
   using global::nanoFramework.Hardware.Esp32; //need NuGet nanoFramework.Hardware.Esp32

   public class Program
	{
		private static SerialPort _SerialPort;
        private const string SerialPortId = "COM2";

        public static void Main()
		{
			Thread.Sleep(5000);

#if SERIAL_THREADED_READ
			Thread readThread = new Thread(SerialPortProcessor);
#endif

			Debug.WriteLine("devMobile.IoT.LoRaWAN.nanoFramework.RAK4200 BreakoutSerial starting");

			try
			{
            // set GPIO functions for COM2 (this is UART1 on ESP32)
            Configuration.SetPinFunction(Gpio.IO21, DeviceFunction.COM2_TX);
			Configuration.SetPinFunction(Gpio.IO19, DeviceFunction.COM2_RX);

            Debug.Write("Ports:");
				foreach (string port in SerialPort.GetPortNames())
				{
					Debug.Write($" {port}");
				}
				Debug.WriteLine("");

				using (_SerialPort = new SerialPort(SerialPortId))
				{
					// set parameters
					//_SerialPort.BaudRate = 9600;
					_SerialPort.BaudRate = 115200;
					_SerialPort.Parity = Parity.None;
					_SerialPort.DataBits = 8;
					_SerialPort.StopBits = StopBits.One;
					_SerialPort.Handshake = Handshake.None;
					_SerialPort.NewLine = "\r\n";

					//_SerialPort.ReadBufferSize = 128; 
					//_SerialPort.ReadBufferSize = 256; 
					_SerialPort.ReadBufferSize = 512; 
					//_SerialPort.ReadBufferSize = 1024;
					_SerialPort.ReadTimeout = 1000;

					//_SerialPort.WatchChar = '\n'; // May 2022 WatchChar event didn't fire github issue https://github.com/nanoframework/Home/issues/1035

					_SerialPort.DataReceived += SerialDevice_DataReceived;

					_SerialPort.Open();

					_SerialPort.WatchChar = '\n';

					for (int i = 0; i < 5; i++)
					{
						string atCommand;
						atCommand = "at+version";
						//atCommand = "at+set_config=device:uart:1:9600";
						//atCommand = "at+get_config=lora:status";
						//atCommand = "at+get_config=device:status";
						//atCommand = "at+get_config=lora:channel";
						//atCommand = "at+help";
						//atCommand = "at+set_config=device:restart";
						//atCommand = "at+set_config=lora:default_parameters";
						//atCommand = "at+set_config=lora:work_mode:0";
						Debug.WriteLine("");
						Debug.WriteLine($"{i} TX:{atCommand} bytes:{atCommand.Length}--------------------------------");
						_SerialPort.WriteLine(atCommand);

						Thread.Sleep(5000);
					}
				}
				Debug.WriteLine("Done");
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
		}

		private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			SerialPort serialPort = (SerialPort)sender;

			switch (e.EventType)
			{
				case SerialData.Chars:
					break;

				case SerialData.WatchChar:
					string response = serialPort.ReadExisting();
					Debug.Write(response);
					break;
				default:
					Debug.Assert(false, $"e.EventType {e.EventType} unknown");
					break;
			}
		}
	}
}

When I requested the version information with “at+version” the RAK4200 Module responded with version information.

RAK4200 EVB Debug Output

.NET nanoFramework RAK2305 – RAK3172 Library Usage

This post covers the usage of my RAK3172LoRaWAN-NetNF library with a RAK3205 WisBlock Wifi Interface Module on a RAK3172 Evaluation Board.

RAK2305 RAK3172 Evaluation Board based test rig

The first time the RAK3172LoRaWANDeviceClient is run the following preprocessor directives may need to be defined to configure the RAK3172 module.

//---------------------------------------------------------------------------------
//#define ST_STM32F769I_DISCOVERY      // nanoff --target ST_STM32F769I_DISCOVERY --update 
//#define  SPARKFUN_ESP32_THING_PLUS  // nanoff --platform esp32 --serialport COM4 --update
//#define RAK_WISBLOCK_RAK2305 // nanoff --update --target ESP32_PSRAM_REV0 --serialport COM4
#define DEVICE_DEVEUI_SET
//#define FACTORY_RESET
//#define PAYLOAD_BCD
#define PAYLOAD_BYTES
#define OTAA
//#define ABP
//#define CONFIRMED
#define UNCONFIRMED
#define REGION_SET
#define ADR_SET
//#define SLEEP
namespace devMobile.IoT.LoRaWAN
{
Visual Studio Debug output for RAK3172LoRaWANDeviceClient full configuration

Once the RAK3172 Module is the RAK3172LoRaWANDeviceClient can be run with only PAYLOAD_BCD or PAYLOAD_BYTES defined

//---------------------------------------------------------------------------------
//#define ST_STM32F769I_DISCOVERY      // nanoff --target ST_STM32F769I_DISCOVERY --update 
//#define  SPARKFUN_ESP32_THING_PLUS  // nanoff --platform esp32 --serialport COM4 --update
//#define RAK_WISBLOCK_RAK2305 // nanoff --update --target ESP32_PSRAM_REV0 --serialport COM4
//#define DEVICE_DEVEUI_SET
//#define FACTORY_RESET
//#define PAYLOAD_BCD
#define PAYLOAD_BYTES
//#define OTAA
//#define ABP
//#define CONFIRMED
//#define UNCONFIRMED
//#define REGION_SET
//#define ADR_SET
//#define SLEEP
namespace devMobile.IoT.LoRaWAN
{
Visual Studio Debug output for RAK3172LoRaWANDeviceClient minimal configuration

When I initially deployed ran the RAK3172LoRaWANDeviceClient the RAK3172LoRaWAN-NetNF library crashed in the OtaaInitialise method. I think this was caused by the RAKwireless Unified Interface V3(RUIV3) “AT+NWM=1” command rebooting the RAK3172 Module.

// Set the Working mode to LoRaWAN, not/never going todo P2P with this library.
#if DIAGNOSTICS
Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} AT+NWM=1");
#endif
Result result = SendCommand("Current Work Mode: LoRaWAN.", "AT+NWM=1", CommandTimeoutDefault);
if (result != Result.Success)
{
#if DIAGNOSTICS
	Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} AT+NWM=1 failed {result}");
#endif
	return result;
}

I think it would be reasonable to assume that the device is in the correct mode (the default after a reset to factory) on startup so I removed the LoRa® network work mode configuration code.

.NET nanoFramework RAK2305 – RAK3172 Basic connectivity

After some experimentation could get a RAK2305 WisBlock Wifi Interface Module running the .NET nanoFramework plugged into the IO Slot of RAK3172 Evaluation Board to send RUIV3 AT Commands to the RAK3172 Module.

RAK2305 + RAK 3172 EVB with FTDI module for Visual Studio 2022 Connectivity

After reviewing the RAK3172 Evaluation Board and RAK2305 WisBlock Wifi Interface Module schematics I realised that the Universal Asynchronous Receiver-Transmistted(UART) transmit and receive pins had to be reversed the with the nanoFramwork ESP32 specific Configuration.SetPinFunction.

namespace devMobile.IoT.LoRaWAN.nanoFramework.RAK.LoraWAN
{ 
   using System;
   using System.Diagnostics;
   using System.IO.Ports;
   using System.Threading;
   using global::nanoFramework.Hardware.Esp32;

   public class Program
   {
      private static SerialPort _SerialPort;

      private const string SerialPortId = "COM2";

      public static void Main()
      {
         Debug.WriteLine("devMobile.IoT.LoRaWAN.nanoFramework.RAK.LoraWAN RAK3172/RAK4630 EVB starting");

         try
         {
            // set GPIO functions for COM2 (this is UART1 on RAK2305)
            Configuration.SetPinFunction(Gpio.IO21, DeviceFunction.COM2_TX);
            Configuration.SetPinFunction(Gpio.IO19, DeviceFunction.COM2_RX);

            Debug.Write("Ports:");
            foreach (string port in SerialPort.GetPortNames())
            {
               Debug.Write($" {port}");
            }
            Debug.WriteLine("");

            using (_SerialPort = new SerialPort(SerialPortId))
            {
               // set parameters
               _SerialPort.BaudRate = 115200;
               _SerialPort.Parity = Parity.None;
               _SerialPort.DataBits = 8;
               _SerialPort.StopBits = StopBits.One;
               _SerialPort.Handshake = Handshake.None;
               _SerialPort.NewLine = "\r\n";
               _SerialPort.ReadTimeout = 1000;

               //_SerialPort.WatchChar = '\n'; // May 2022 WatchChar event didn't fire github issue https://github.com/nanoframework/Home/issues/1035

               _SerialPort.DataReceived += SerialDevice_DataReceived;

               _SerialPort.Open();

               _SerialPort.WatchChar = '\n';

               _SerialPort.ReadExisting(); // Running at 115K2 this was necessary


               for (int i = 0; i < 5; i++)
               {
                  string atCommand;
                  atCommand = "AT+VER=?";
                  //atCommand = "AT+SN=?"; // Empty response?
                  //atCommand = "AT+HWMODEL=?";
                  //atCommand = "AT+HWID=?";
                  //atCommand = "AT+DEVEUI=?";
                  //atCommand = "AT+APPEUI=?";
                  //atCommand = "AT+APPKEY=?";
                  //atCommand = "ATR";
                  //atCommand = "AT+SLEEP=4000";
                  //atCommand = "AT+ATM";
                  //atCommand = "AT?";
                  Debug.WriteLine("");
                  Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} {i} TX:{atCommand} bytes:{atCommand.Length}--------------------------------");
                  _SerialPort.WriteLine(atCommand);

                  Thread.Sleep(5000);
               }
            }
            Debug.WriteLine("Done");
         }
         catch (Exception ex)
         {
            Debug.WriteLine(ex.Message);
         }
      }

      private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
      {
         SerialPort serialPort = (SerialPort)sender;

         switch (e.EventType)
         {
            case SerialData.Chars:
               break;

            case SerialData.WatchChar:
               string response = serialPort.ReadExisting();
               Debug.Write(response);
               break;
            default:
               Debug.Assert(false, $"e.EventType {e.EventType} unknown");
               break;
         }
      }
   }
}

When I requested the version information with “AT+VER=?” the RAK3172 Module responded with version information.

.NET nanoFramework RAK3172 Library Usage

After a two week “soak test” using a Sparkfun Thing Plus ESP32 WROOM and RAK3172 Breakout Board completed with no failures, this final post covers the usage of the RAK3172LoRaWAN-NetNF library in a “real-world” application.

Before a factory reset the DevEUI, JoinEUI (was AppEUI), and AppKey were values I had configured earlier

12:02:04 0 TX:AT+DEVEUI=? bytes:11--------------------------------
AT+DEVEUI=A..............1
OK

12:03:05 0 TX:AT+APPEUI=? bytes:11--------------------------------
AT+APPEUI=A..............8
OK

12:04:03 0 TX:AT+APPKEY=? bytes:11--------------------------------
AT+APPKEY=C..............................F
OK

After a factory reset the DevEUI, JoinEUI (was AppEUI), and AppKey were default values

12:00:21 0 TX:AT+DEVEUI=? bytes:11--------------------------------
AT+DEVEUI=0000000000000000
OK

12:01:09 0 TX:AT+APPEUI=? bytes:11--------------------------------
AT+APPEUI=0000000000000000
OK

12:01:48 0 TX:AT+APPKEY=? bytes:11--------------------------------
AT+APPKEY=00000000000000000000000000000000
OK

I then ran the RAK3172LoRaWANDeviceClient with the following preprocessor directives defined to reconfigure the RAK3172 module.

//---------------------------------------------------------------------------------
//#define ST_STM32F769I_DISCOVERY      // nanoff --target ST_STM32F769I_DISCOVERY --update 
#define ESP32_WROOM   // nanoff --target ESP32_REV0 --serialport COM17 --update
#define DEVICE_DEVEUI_SET
//#define FACTORY_RESET
///#define PAYLOAD_BCD
#define PAYLOAD_BYTES
#define OTAA
//#define ABP
//#define CONFIRMED
#define UNCONFIRMED
#define REGION_SET
#define ADR_SET
//#define SLEEP
namespace devMobile.IoT.LoRaWAN
{
...
Visual Studio Debug output for RAK3172LoRaWANDeviceClient full configuration

I could then run the RAK3172LoRaWANDeviceClient with only PAYLOAD_BCD or PAYLOAD_BYTES defined

//---------------------------------------------------------------------------------
//#define ST_STM32F769I_DISCOVERY      // nanoff --target ST_STM32F769I_DISCOVERY --update 
#define ESP32_WROOM   // nanoff --target ESP32_REV0 --serialport COM17 --update
//#define DEVICE_DEVEUI_SET
//#define FACTORY_RESET
///#define PAYLOAD_BCD
#define PAYLOAD_BYTES
//#define OTAA
//#define ABP
//#define CONFIRMED
//#define UNCONFIRMED
//#define REGION_SET
//#define ADR_SET
//#define SLEEP
namespace devMobile.IoT.LoRaWAN
{
...
Visual Studio Debug output for RAK3172LoRaWANDeviceClient minimal configuration
public static void Main()
{
	Result result;

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

	try
	{
		// set GPIO functions for COM2 (this is UART1 on ESP32)
#if ESP32_WROOM
		Configuration.SetPinFunction(Gpio.IO17, DeviceFunction.COM2_TX);
		Configuration.SetPinFunction(Gpio.IO16, DeviceFunction.COM2_RX);
#endif

		Debug.Write("Ports:");
		foreach (string port in SerialPort.GetPortNames())
		{
			Debug.Write($" {port}");
		}
		Debug.WriteLine("");

		using (Rak3172LoRaWanDevice device = new Rak3172LoRaWanDevice())
		{
			result = device.Initialise(SerialPortId, 115200, Parity.None, 8, StopBits.One);
			if (result != Result.Success)
			{
				Debug.WriteLine($"Initialise failed {result}");
				return;
			}

			MessageSendTimer = new Timer(SendMessageTimerCallback, device, Timeout.Infinite, Timeout.Infinite);
					
			device.OnJoinCompletion += OnJoinCompletionHandler;
			device.OnReceiveMessage += OnReceiveMessageHandler;
#if CONFIRMED
			device.OnMessageConfirmation += OnMessageConfirmationHandler;
#endif

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

#if DEVICE_DEVEUI_SET
			Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Device EUI");
			result = device.DeviceEui(Config.devEui);
			if (result != Result.Success)
			{
				Debug.WriteLine($"DeviceEUI set failed {result}");
				return;
			}
#endif

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

#if ADR_SET
			Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} ADR On");
			result = device.AdrOn();
			if (result != Result.Success)
			{
				Debug.WriteLine($"ADR on failed {result}");
				return;
			}
#endif
#if CONFIRMED
			Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Confirmed");
			result = device.UplinkMessageConfirmationOn();
			if (result != Result.Success)
			{
				Debug.WriteLine($"Confirm on failed {result}");
				return;
			}
#endif
#if UNCONFIRMED
			Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Unconfirmed");
			result = device.UplinkMessageConfirmationOff();
			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(Config.JoinEui, Config.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(Config.DevAddress, Config.NwksKey, Config.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 started");

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

One of the major differences between the RAK4200 and RAK3127 libraries is the way a LoRaWAN network join is handled. The RAK4200 library Join method blocks until it succeeds of fails, the RAK3172 library Join method returns immediately then an EventHandler is called with the result.

private static void OnJoinCompletionHandler(bool result)
{
	Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} Join finished:{result}");

	if (result)
	{
		MessageSendTimer.Change(MessageSendTimerDue, MessageSendTimerPeriod);
	}
}

The new RAK Wireless LoRaWAN modules use the RUI3 AT Commands so the RAK3172 library will most probably be retired and uses as the basis for a generic RUI3 library.

.NET nanoFramework RAK3172 Sleep

The RAKwireless RAK3172 module has “AT+SLEEP -Sleep mode command. To see how it worked I modified the BreakOutSerial application to send an AT-SLEEP command.

public static void Main()
{
#if SERIAL_THREADED_READ
	Thread readThread = new Thread(SerialPortProcessor);
#endif

	Debug.WriteLine("devMobile.IoT.LoRaWAN.nanoFramework.RAK3172 BreakoutSerial starting");

	try
	{
		// set GPIO functions for COM2 (this is UART1 on ESP32)
#if ESP32_WROOM
		Configuration.SetPinFunction(Gpio.IO17, DeviceFunction.COM2_TX);
		Configuration.SetPinFunction(Gpio.IO16, DeviceFunction.COM2_RX);
#endif

		Debug.Write("Ports:");
		foreach (string port in SerialPort.GetPortNames())
		{
			Debug.Write($" {port}");
		}
		Debug.WriteLine("");

		using (_SerialPort = new SerialPort(SerialPortId))
		{
			// set parameters
			_SerialPort.BaudRate = 115200;
			_SerialPort.Parity = Parity.None;
			_SerialPort.DataBits = 8;
			_SerialPort.StopBits = StopBits.One;
			_SerialPort.Handshake = Handshake.None;
			_SerialPort.NewLine = "\r\n";
			_SerialPort.ReadTimeout = 1000;

			//_SerialPort.WatchChar = '\n'; // May 2022 WatchChar event didn't fire github issue https://github.com/nanoframework/Home/issues/1035

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

			_SerialPort.Open();

			_SerialPort.WatchChar = '\n';

			_SerialPort.ReadExisting(); // Running at 115K2 this was necessary

#if SERIAL_THREADED_READ
			readThread.Start();
#endif

			for (int i = 0; i < 5; i++)
			{
				string atCommand;
				atCommand = "AT+VER=?";
				//atCommand = "AT+SN=?"; // Empty response?
				//atCommand = "AT+HWMODEL=?";
				//atCommand = "AT+HWID=?";
				//atCommand = "AT+DEVEUI=?";
				//atCommand = "AT+APPEUI=?";
				//atCommand = "AT+APPKEY=?";
				//atCommand = "ATR";
				//atCommand = "AT+SLEEP=4000";
				Debug.WriteLine("");
				Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} {i} TX:{atCommand} bytes:{atCommand.Length}--------------------------------");
				_SerialPort.WriteLine(atCommand);

				Thread.Sleep(5000);
			}
		}
#if SERIAL_THREADED_READ
		_Continue = false;
#endif
		Debug.WriteLine("Done");
	}
	catch (Exception ex)
	{
		Debug.WriteLine(ex.Message);
	}
}

#if SERIAL_ASYNC_READ
private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
	SerialPort serialPort = (SerialPort)sender;

	switch (e.EventType)
	{
		case SerialData.Chars:
			break;

		case SerialData.WatchChar:
			string response = serialPort.ReadExisting();
			//Debug.Write($"{DateTime.UtcNow:hh:mm:ss} RX:{response} bytes:{response.Length}");
			Debug.Write(response);
			break;
		default:
			Debug.Assert(false, $"e.EventType {e.EventType} unknown");
			break;
	}
}
#endif

I then ran the device in the debugger to see how the AT+SLEEP was handled.

BreakoutSerial application executing AT+SLEEP command

There was limited AT+SLEEP -Sleep mode documentation but it looks like the RAK3172 module sleeps, “wakes up” and then replies with “OK”.

Sparkfun Thing Plus ESP32 WROOM + RAK3172 Idle power consumption

Initially the Sleep method didn’t appear to work, the power consumption didn’t change….

private static void SendMessageTimerCallback(object state)
{
	Rak3172LoRaWanDevice device = (Rak3172LoRaWanDevice)state;

#if PAYLOAD_HEX
	Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} port:{MessagePort} payload HEX:{PayloadHex}");
	Result result = device.Send(MessagePort, PayloadHex, SendTimeout);
#endif
#if PAYLOAD_BYTES
	Debug.WriteLine($"{DateTime.UtcNow:hh:mm:ss} port:{MessagePort} payload bytes:{Rak3172LoRaWanDevice.BytesToHex(PayloadBytes)}");
	Result result = device.Send(MessagePort, PayloadBytes, SendTimeout);
#endif
	if (result != Result.Success)
	{
		Debug.WriteLine($"Send failed {result}");
	}

#if SLEEP
	Thread.Sleep(7500); //10000 Works 5000 to short

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

After some debugging and reading this helpful RAK Wireless forum post I added a short delay before sleeping the RAK3172 module and power consumption reduced.

Sparkfun Thing Plus ESP32 WROOM + RAK3172 Sleep mode power consumption

Initially the Sleep method timed out every time it was called. After some more debugging I figured out that I needed a slightly longer delay for the AutoResetEvent.Waitone as it was timing out just before the “OK” was processed.

public Result Sleep(TimeSpan period)
{
	return Sleep(period, SleepExtensionDefault);
}

public Result Sleep(TimeSpan period, TimeSpan extension)
{
#if DIAGNOSTICS
	Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} AT+SLEEP {period.TotalMilliseconds:f0} mSec");
#endif
	Result result = SendCommand("OK", $"AT+SLEEP={period.TotalMilliseconds:f0}", period.Add(extension));
	if (result != Result.Success)
	{
#if DIAGNOSTICS
		Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} AT+SLEEP failed {result}");
#endif
		return result;
	}

	return Result.Success;
}

I then disconnected RAK3172 module to see how much power the Sparkfun Thing Plus ESP32 WROOM was using.

The nanoFramework ESP32 support library has a LightSleep and DeepSleep functionality which significantly reduced the power consumption

Sparkfun Thing Plus ESP32 WROOM LightSleep power consumption
Sparkfun Thing Plus ESP32 WROOM DeepSleep power consumption

The Keweisi KWS-MX19 USB Tester DC 4V-30V 0-5A Current Voltage Detector is not a precision laboratory instrument but did show the power consumption of my setup could be reduced by sleeping the RAK3172 module and the Sparkfun Thing Plus ESP32 WROOM.

.NET nanoFramework RAK3172 Factory Reset

The RAKwireless RAK3172 module has “ATR – Restore to Default Parameters” command. To see what settings were “restored” I modified the BreakOutSerial application to send an ATR command to reset the device, then display the DevEUI, JoinEUI, and AppKey.

public static void Main()
{
	Debug.WriteLine("devMobile.IoT.LoRaWAN.nanoFramework.RAK3172 BreakoutSerial starting");

	try
	{
		// set GPIO functions for COM2 (this is UART1 on ESP32)
#if ESP32_WROOM
		Configuration.SetPinFunction(Gpio.IO17, DeviceFunction.COM2_TX);
		Configuration.SetPinFunction(Gpio.IO16, DeviceFunction.COM2_RX);
#endif

		Debug.Write("Ports:");
		foreach (string port in SerialPort.GetPortNames())
		{
			Debug.Write($" {port}");
		}
		Debug.WriteLine("");

		using (_SerialPort = new SerialPort(SerialPortId))
		{
			// set parameters
			_SerialPort.BaudRate = 115200;
			_SerialPort.Parity = Parity.None;
			_SerialPort.DataBits = 8;
			_SerialPort.StopBits = StopBits.One;
			_SerialPort.Handshake = Handshake.None;
			_SerialPort.NewLine = "\r\n";
			_SerialPort.ReadTimeout = 1000;

			//_SerialPort.WatchChar = '\n'; // May 2022 WatchChar event didn't fire github issue https://github.com/nanoframework/Home/issues/1035

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

			_SerialPort.Open();

			_SerialPort.WatchChar = '\n';

			_SerialPort.ReadExisting(); // Running at 115K2 this was necessary

...

			for (int i = 0; i < 5; i++)
			{
				string atCommand;
				atCommand = "AT+VER=?";
				//atCommand = "AT+SN=?"; // Empty response?
				//atCommand = "AT+HWMODEL=?";
				//atCommand = "AT+HWID=?";
				//atCommand = "AT+DEVEUI=?";
				//atCommand = "AT+APPEUI=?";
				//atCommand = "AT+APPKEY=?";
				//atCommand = "ATR";
				//atCommand = "AT+SLEEP=4000";
				Debug.WriteLine("");
				Debug.WriteLine($"{i} TX:{atCommand} bytes:{atCommand.Length}--------------------------------");
				_SerialPort.WriteLine(atCommand);

				Thread.Sleep(5000);
			}
		}
...
		Debug.WriteLine("Done");
	}
	catch (Exception ex)
	{
		Debug.WriteLine(ex.Message);
	}
}

After resetting the device I modified the code to display some of the configuration.

DevEUI after ATR command
JoinEUI after ATR command
AppKey after ATR command

To reconfigure the device I ran the RAK3172LoRaWANDeviceClient application with DEVICE_DEVEUI_SET, OTAA, UNCONFIRMED, REGION_SET and ADR_SET defined. The testrig could then successfully connect to The Things Network and when the device was power cycled the configuration was retained.

public Result FactoryReset()
{
#if DIAGNOSTICS
	Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ATR");
#endif
	Result result = SendCommand("OK", "ATR", CommandTimeoutDefault);
	if (result != Result.Success)
	{
#if DIAGNOSTICS
		Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ATR failed {result}");
#endif
		return result;
	}

	return Result.Success;
}

I have added a “FactoryReset” method to the RAK3172LoRaWANDevice library.

.NET nanoFramework RAK3172 LoRaWAN library ABP Join

After getting my RAKwireless RAK3172 module to reliably connect to The Things Network(TTN) using Over The Air Activation(OTAA) the next step was to built an Activation By Personalisation(ABP) sample application.

SparkFun Thing Plus – ESP32 WROOM, RAK3172 Breakout board

I modified the NetworkJoinOTAA sample(based on the asynchronous version of BreakOutSerial) to send the required sequence of AT commands and displays the responses.

namespace devMobile.IoT.LoRaWAN.nanoFramework.RAK3172
{
	using System;
	using System.Diagnostics;
	using System.IO.Ports;
	using System.Threading;
#if ESP32_WROOM
	using global::nanoFramework.Hardware.Esp32; //need NuGet nanoFramework.Hardware.Esp32
#endif

	public class Program
	{
#if ESP32_WROOM
		private const string SerialPortId = "COM2";
#endif
#if ST_STM32F769I_DISCOVERY
		private const string SerialPortId = "COM6";
#endif
		private const string DevEui = "...";
		private const string DevAddress = "...";
		private const string NwksKey = "...";
		private const string AppsKey = "..."; 
		private const byte MessagePort = 1;
		private const string Payload = "A0EEE456D02AFF4AB8BAFD58101D2A2A"; // Hello LoRaWAN

		public static void Main()
		{
			Debug.WriteLine("devMobile.IoT.LoRaWAN.nanoFramework.RAK3172.NetworkJoinABP starting");

			try
			{
				// set GPIO functions for COM2 (this is UART1 on ESP32)
#if ESP32_WROOM
				Configuration.SetPinFunction(Gpio.IO17, DeviceFunction.COM2_TX);
				Configuration.SetPinFunction(Gpio.IO16, DeviceFunction.COM2_RX);
#endif

				Debug.Write("Ports:");
				foreach (string port in SerialPort.GetPortNames())
				{
					Debug.Write($" {port}");
				}
				Debug.WriteLine("");

				using (SerialPort serialPort = new SerialPort(SerialPortId))
				{
					// set parameters
					serialPort.BaudRate = 115200;
					serialPort.Parity = Parity.None;
					serialPort.DataBits = 8;
					serialPort.StopBits = StopBits.One;
					serialPort.Handshake = Handshake.None;
					serialPort.NewLine = "\r\n";
					serialPort.ReadTimeout = 1000;

					serialPort.DataReceived += SerialDevice_DataReceived;

					serialPort.Open();

					serialPort.WatchChar = '\n';

					serialPort.ReadExisting(); // Running at 115K2 this was necessary

					// Set the Device EUI
					Console.WriteLine("Set Device EUI");
					serialPort.WriteLine($"AT+DEVEUI={DevEui}");

					// Set the Working mode to LoRaWAN
					Console.WriteLine("Set Work mode");
					serialPort.WriteLine("AT+NWM=1");

					// Set the Region to AS923
					Console.WriteLine("Set Region");
					serialPort.WriteLine("AT+BAND=8");

					// Set the JoinMode
					Console.WriteLine("Set Join mode");
					serialPort.WriteLine("AT+NJM=0");

					// Set the DevAddress
					Console.WriteLine("Set Device Address");
					serialPort.WriteLine($"AT+DEVADDR={DevAddress}");

					// Set the Network Session Key
					Console.WriteLine("Set NwksKey");
					serialPort.WriteLine($"AT+NWKSKEY={NwksKey}");

					// Set the Application Session Key
					Console.WriteLine("Set AppsKey");
					serialPort.WriteLine($"AT+APPSKEY={AppsKey}");

					// Set the Confirm flag
					Console.WriteLine("Set Confirm off");
					serialPort.WriteLine("AT+CFM=0");

					// Join the network
					Console.WriteLine("Start Join");
					serialPort.WriteLine("AT+JOIN=1:0:10:2");

					// Wait for the +EVT:JOINED

					while (true)
					{
						Console.WriteLine("Sending");
						serialPort.WriteLine($"AT+SEND={MessagePort}:{Payload}");

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

		private static void SerialDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			SerialPort serialPort = (SerialPort)sender;

			switch (e.EventType)
			{
				case SerialData.Chars:
					break;

				case SerialData.WatchChar:
					string response = serialPort.ReadExisting();
					Debug.Write(response);
					break;
				default:
					Debug.Assert(false, $"e.EventType {e.EventType} unknown");
					break;
			}
		}
	}
}

The NetworkJoinABP application assumes that all of the AT commands succeed.

TTN Console live data tab connection process
Visual Studio Output windows displaying connection process and a D2C message
TTN Console live data tab connection process with a couple of D2C messages
Visual Studio Output windows displaying connection process and a couple of C2D messages

The Activation By Personalisation(ABP) sample was a bit more fragile than I expected. The The Things Network(TTN) Live Data Tab updates were often delayed or required a page refresh to be displayed (July 2022).