.NET Core RAK811 LoRaWAN library Part3

The massive refactor

After refactoring my RAK3172 device library I have applied a similar approach to code on my RAK811 device library. My test-rig is a RaspberryPI 3B with a PI Supply RAK811 pHat and external antenna.

PI Supply RAK811 LoRaWAN pHat

In the new code a Thread reads lines of text from the SerialPort and processes them, checking for command responses, failures and downlink messages.

Unlike most of the devices I have worked with the RAK811 Join and Send commands are synchronous so return once the process has completed. The RAK811 responses also have quite a few empty, null prefixed or null suffixed lines which is a bit odd.

public void SerialPortProcessor()
{
	string line;

	while (CommandProcessResponses)
	{
		try
		{
#if DIAGNOSTICS
			Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine before");
#endif
			line = SerialDevice.ReadLine().Trim('\0').Trim();
#if DIAGNOSTICS
			Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine after:{line}");
#endif
			// consume empty lines
			if (String.IsNullOrWhiteSpace(line))
			{
				continue;
			}

			// Consume the response from set work mode
			if (line.StartsWith("?LoRa (R)") || line.StartsWith("RAK811 ") || line.StartsWith("UART1 ") || line.StartsWith("UART3 ") || line.StartsWith("LoRa work mode"))
			{
				continue;
			}

			// See if device successfully joined network
			if (line.StartsWith("OK Join Success"))
			{
				OnJoinCompletion?.Invoke(true);

				CommandResponseExpectedEvent.Set();

				continue;
			}

			if (line.StartsWith("at+recv="))
			{
				string[] payloadFields = line.Split("=,:".ToCharArray());

				byte port = byte.Parse(payloadFields[1]);
				int rssi = int.Parse(payloadFields[2]);
				int snr = int.Parse(payloadFields[3]);
				int length = int.Parse(payloadFields[4]);

				if (this.OnMessageConfirmation != null)
				{
					OnMessageConfirmation?.Invoke(rssi, snr);
				}
				if (length > 0)
				{
					string payload = payloadFields[5];

					if (this.OnReceiveMessage != null)
					{
						OnReceiveMessage.Invoke(port, rssi, snr, payload);
					}
				}
				continue;
			}

			switch (line)
			{
				case "OK":
				case "Initialization OK":
				case "OK Wake Up":
				case "OK Sleep":
					CommandResult = Result.Success;
					break;

				case "ERROR: 1":
					CommandResult = Result.ATCommandUnsuported;
					break;
				case "ERROR: 2":
					CommandResult = Result.ATCommandInvalidParameter;
					break;
				case "ERROR: 3": //There is an error when reading or writing flash.
				case "ERROR: 4": //There is an error when reading or writing through IIC.
					CommandResult = Result.ErrorReadingOrWritingFlash;
					break;
				case "ERROR: 5": //There is an error when sending through UART
					CommandResult = Result.ATCommandInvalidParameter;
					break;
				case "ERROR: 41": //The BLE works in an invalid state, so that it can’t be operated.
					CommandResult = Result.ResponseInvalid;
					break;
				case "ERROR: 80":
					CommandResult = Result.LoRaBusy;
					break;
				case "ERROR: 81":
					CommandResult = Result.LoRaServiceIsUnknown;
					break;
				case "ERROR: 82":
					CommandResult = Result.LoRaParameterInvalid;
					break;
				case "ERROR: 83":
					CommandResult = Result.LoRaFrequencyInvalid;
					break;
				case "ERROR: 84":
					CommandResult = Result.LoRaDataRateInvalid;
					break;
				case "ERROR: 85":
					CommandResult = Result.LoRaFrequencyAndDataRateInvalid;
					break;
				case "ERROR: 86":
					CommandResult = Result.LoRaDeviceNotJoinedNetwork;
					break;
				case "ERROR: 87":
					CommandResult = Result.LoRaPacketToLong;
					break;
				case "ERROR: 88":
					CommandResult = Result.LoRaServiceIsClosedByServer;
					break;
				case "ERROR: 89":
					CommandResult = Result.LoRaRegionUnsupported;
					break;
				case "ERROR: 90":
					CommandResult = Result.LoRaDutyCycleRestricted;
					break;
				case "ERROR: 91":
					CommandResult = Result.LoRaNoValidChannelFound;
					break;
				case "ERROR: 92":
					CommandResult = Result.LoRaNoFreeChannelFound;
					break;
				case "ERROR: 93":
					CommandResult = Result.StatusIsError;
					break;
				case "ERROR: 94":
					CommandResult = Result.LoRaTransmitTimeout;
					break;
				case "ERROR: 95":
					CommandResult = Result.LoRaRX1Timeout;
					break;
				case "ERROR: 96":
					CommandResult = Result.LoRaRX2Timeout;
					break;
				case "ERROR: 97":
					CommandResult = Result.LoRaRX1ReceiveError;
					break;
				case "ERROR: 98":
					CommandResult = Result.LoRaRX2ReceiveError;
					break;
				case "ERROR: 99":
					CommandResult = Result.LoRaJoinFailed;
					break;
				case "ERROR: 100":
					CommandResult = Result.LoRaDownlinkRepeated;
					break;
				case "ERROR: 101":
					CommandResult = Result.LoRaPayloadSizeNotValidForDataRate;
					break;
				case "ERROR: 102":
					CommandResult = Result.LoRaTooManyDownlinkFramesLost;
					break;
				case "ERROR: 103":
					CommandResult = Result.LoRaAddressFail;
					break;
				case "ERROR: 104":
					CommandResult = Result.LoRaMicVerifyError;
					break;
				default:
					CommandResult = Result.ResponseInvalid;
					break;
			}
		}
		catch (TimeoutException)
		{
			// Intentionally ignored, not certain this is a good idea
		}

		CommandResponseExpectedEvent.Set();
	}
}

After a lot of testing I think my thread based approach works reliably. Initially, I was having some signal strength issues because I had forgotten to configure the external antenna. I need to add some validation to the metrics and payload field unpacking (though I’m not certain what todo if they are the wrong format).

.NET Core RAK3172 LoRaWAN library Part5

The massive refactor

After getting Activation By Personalisation(ABP) and Over The Air Activation(OTAA) working on my RAK3172 test rig I was looking at the code and SerialDataReceivedEventHandler was really ugly.

Raspberry Pi3 with Grove Base Hat and RAK3172 Breakout (using UART2)

After some experimentation in the BreakOutSerial project I decided to reimplement the RAK3172 command processing. In the new code a Thread reads lines of text from the SerialPort and processes them. I have replaced the Join and Send(Confirmed) methods with ones that block only while the command are sent to the RAK3172. Then, when completed the OnJoinCompletion or OnMessagesConfirmation event handlers are called.

private Result SendCommand(string command)
{
	if (command == null)
	{
		throw new ArgumentNullException(nameof(command));
	}

	if (command == string.Empty)
	{
		throw new ArgumentException($"command cannot be empty", nameof(command));
	}

	serialDevice.WriteLine(command);

	this.CommandResponseExpectedEvent.Reset();

	if (!this.CommandResponseExpectedEvent.WaitOne(CommandTimeoutDefaultmSec, false))
	{
		return Result.Timeout;
	}

	return CommandResult;
}

private void SerialPortProcessor()
{
	string line;

	while (CommandProcessResponses)
	{
		try
		{
#if DIAGNOSTICS
			Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine before");
#endif
			line = serialDevice.ReadLine();
#if DIAGNOSTICS
			Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine after:{line}");
#endif

			// See if device successfully joined network
			if (line.StartsWith("+EVT:JOINED"))
			{
				OnJoinCompletion?.Invoke(true);

				continue;
			}

			// See if device failed ot join network
			if (line.StartsWith("+EVT:JOIN FAILED"))
			{
				OnJoinCompletion?.Invoke(false);

				continue;
			}

			// Applicable only if confirmed messages enabled 
			if (line.StartsWith("+EVT:SEND CONFIRMED OK"))
			{
				OnMessageConfirmation?.Invoke();

				continue;
			}

			// Check for A/B/C downlink message
			if (line.StartsWith("+EVT:RX_1") || line.StartsWith("+EVT:RX_2") || line.StartsWith("+EVT:RX_3") || line.StartsWith("+EVT:RX_C"))
			{
				// TODO beef up validation, nto certain what todo if borked
				string[] metricsFields= line.Split(' ', ',');

				int rssi = int.Parse(metricsFields[3]);
				int snr = int.Parse(metricsFields[6]);

				line = serialDevice.ReadLine();

#if DIAGNOSTICS
				Debug.WriteLine($" {DateTime.UtcNow:HH:mm:ss} UNICAST :{line}");
#endif
				line = serialDevice.ReadLine();
#if DIAGNOSTICS
				Debug.WriteLine($" {DateTime.UtcNow:HH:mm:ss} Payload:{line}");
#endif
				// TODO beef up validation, nto certain what todo if borked
				string[] payloadFields = line.Split(':');

				byte port = byte.Parse(payloadFields[1]);
				string payload = payloadFields[2];

				OnReceiveMessage?.Invoke(port, rssi, snr, payload);

				continue;
			}

#if DIAGNOSTICS
           Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine Result");
#endif
			line = serialDevice.ReadLine();
#if DIAGNOSTICS
             Debug.WriteLine($" {DateTime.UtcNow:hh:mm:ss} ReadLine Result:{line}");
#endif
			switch (line)
			{
				case "OK":
					CommandResult = Result.Success;
					break;
				case "AT_ERROR":
					CommandResult = Result.AtError;
					break;
				case "AT_PARAM_ERROR":
					CommandResult = Result.ParameterError;
					break;
				case "AT_BUSY_ERROR":
					CommandResult = Result.BusyError;
					break;
				case "AT_TEST_PARAM_OVERFLOW":
					CommandResult = Result.ParameterOverflow;
					break;
				case "AT_NO_NETWORK_JOINED":
					CommandResult = Result.NotJoined;
					break;
				case "AT_RX_ERROR":
					CommandResult = Result.ReceiveError;
					break;
				case "AT_DUTYCYLE_RESTRICTED":
					CommandResult = Result.DutyCycleRestricted;
					break;
				default:
					CommandResult = Result.Undefined;
					break;
			}

			CommandResponseExpectedEvent.Set();
		}
		catch (TimeoutException)
		{
			// Intentionally ignored, not certain this is a good idea
		}
	}
}

After a lot of testing I think my thread based approach works reliably. I also had to modify the code to shutdown the command processor thread and free any non managed resources.

/// <summary>
/// Ensures unmanaged serial port and thread resources are released in a "responsible" manner.
/// </summary>
public void Dispose()
{
	CommandProcessResponses = false;

	if (CommandResponsesProcessorThread != null)
	{
		CommandResponsesProcessorThread.Join();
		CommandResponsesProcessorThread = null;
	}

	if (serialDevice != null)
	{
		serialDevice.Dispose();
		serialDevice = null;
	}
}

I need to add some validation to the metrics and payload field unpacking (though I’m not certain what todo if they are the wrong format) and review the handling of multi-line event messages.

.NET Core RAK3172 LoRaWAN library Part4

Starting again with Threads

After getting Activation By Personalisation(ABP) and Over The Air Activation(OTAA) working on my RAK3172 test rig I was looking at the code and SerialDataReceivedEventHandler was really ugly.

Raspberry Pi3 with Grove Base Hat and RAK3172 Breakout (using UART2)

After some experimentation in the BreakOutSerial project I decided to reimplement the RAK3172 command processing. In the new code a Thread reads lines of text from the SerialPort and processes them. I have replaced the Join and Send(Confirmed) methods with ones that block only while the command are sent to the RAK3172. Then, when completed the OnJoinCompletion or OnMessagesConfirmation event handlers are called.

private Result SendCommand(string command)
{
   if (command == null)
   {
      throw new ArgumentNullException(nameof(command));
   }

   if (command == string.Empty)
   {
      throw new ArgumentException($"command invalid length cannot be empty", nameof(command));
    }

   serialDevice.ReadTimeout = (int)CommandTimeoutDefault.TotalMilliseconds;
   serialDevice.WriteLine(command);

   this.atExpectedEvent.Reset();

   if (!this.atExpectedEvent.WaitOne((int)CommandTimeoutDefault.TotalMilliseconds, false))
      return Result.Timeout;

   return result;
}

public void SerialPortProcessor()
{
   string line;

   while (true)
   {
      this.serialDevice.ReadTimeout = -1;

      Debug.WriteLine("ReadLine before");
      line = serialDevice.ReadLine();
      Debug.WriteLine($"ReadLine after:{line}");

            // check for +EVT:JOINED
      if (line.StartsWith("+EVT:JOINED"))
      {
            OnJoinCompletion?.Invoke(true);

            continue;
      }

      if (line.StartsWith("+EVT:JOIN FAILED"))
      {
	     OnJoinCompletion?.Invoke(false);

         continue;
      }

      if (line.StartsWith("+EVT:SEND CONFIRMED OK"))
      {
         OnMessageConfirmation?.Invoke();

         continue;
      }

      // Check for A/B/C downlink message
      if (line.StartsWith("+EVT:RX_1") || line.StartsWith("+EVT:RX_2") || line.StartsWith("+EVT:RX_3") || line.StartsWith("+EVT:RX_C"))
      {
         string[] fields1 = line.Split(' ', ',');

         int rssi = int.Parse(fields1[3]);
         int snr = int.Parse(fields1[6]);
 
         line = serialDevice.ReadLine();
         Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss} UNICAST :{line}");

         line = serialDevice.ReadLine();
         Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Payload:{line}");

         string[] fields2 = line.Split(':');

         int port = int.Parse(fields2[1]);
         string payload = fields2[2];

         OnReceiveMessage?.Invoke(port, rssi, snr, payload);

         continue;
      }

      try
      {
         this.serialDevice.ReadTimeout = 3000;

         Debug.WriteLine("ReadLine Result");
         line = serialDevice.ReadLine();
         Debug.WriteLine($"ReadLine Result after:{line}");

         switch (line)
         {
            case "OK":
               result = Result.Success;
               break;
         case "AT_ERROR":
               result = Result.Error;
               break;
         case "AT_PARAM_ERROR":
               result = Result.ParameterError;
               break;
         case "AT_BUSY_ERROR":
               result = Result.BusyError;
               break;
         case "AT_TEST_PARAM_OVERFLOW":
               result = Result.ParameterOverflow;
               break;
         case "AT_NO_NETWORK_JOINED":
               result = Result.NotJoined;
               break;
         case "AT_RX_ERROR":
               result = Result.ReceiveError;
               break;
         case "AT_DUTYCYLE_RESTRICTED":
               result = Result.DutyCycleRestricted;
               break;
         default:
               result = Result.Undefined;
               break;
         }
      }
      catch (TimeoutException) 
      {
         result = Result.Timeout;
      }
   atExpectedEvent.Set();
}

The code is not suitable for production but it confirmed my thread based approach works. I need to add code to shutdown the message processing thread in a controlled way, support for Class B & C devices, replace the OnJoinCompletionHandler timer magic numbers and soak test for 5-7 days.

Visual Studio Displaying RAK3172 device joining network then sending messages

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.

TTN Displaying RAK3172 device joining network then sending messages

RFM69 hat library lockups and corruptions

While doing yet more stress testing I noticed a couple of odd message go past and a long pause every so often when sending a message in the Visual Studio output window.

I have two Arduino devices sending addressed messages every (both individual and broadcast) to the Adafruit RFM69 HCW Radio Bonnet, on my two Windows 10 IoT Core devices every 100mSec. At the same time the windows 10 devices are sending each other a message every 5 seconds.

To help spot the pauses I added some code to mark any events where there was a significant gap. In this case ” is ASCII character for 0x22 the device address

21:10:30.746 Received To 34 a 23 byte message Hello World ---0x22:236 CRC Ok True
21:10:30.918 Received To 153 a 23 byte message Hello World ---0x99:236 CRC Ok True
21:10:31.399 Received To 34 a 23 byte message Hello World ---0x22:237 CRC Ok True
21:10:31.568 Send-hello world RFM69-915-01 09-10-31
21:10:31.580 Send-Done
21:10:31.592 Received To 34 a 33 byte message """"""""""""""""""""""""""""""""" CRC Ok True
RC-------------------------------------------
21:10:32.052 Received To 34 a 23 byte message Hello World ---0x22:238 CRC Ok True
21:10:32.225 Received To 153 a 23 byte message Hello World ---0x99:238 CRC Ok True
21:10:32.705 Received To 34 a 23 byte message Hello World ---0x22:239 CRC Ok True

There were also still some corrupted messages

21:10:30.746 Received To 34 a 23 byte message Hello World ---0x22:236 CRC Ok True
21:10:30.918 Received To 153 a 23 byte message Hello World ---0x99:236 CRC Ok True
21:10:31.399 Received To 34 a 23 byte message Hello World ---0x22:237 CRC Ok True
21:10:31.568 Send-hello world RFM69-915-01 09-10-31
21:10:31.580 Send-Done
21:10:31.592 Received To 34 a 33 byte message """"""""""""""""""""""""""""""""" CRC Ok True
RC-------------------------------------------
21:10:32.052 Received To 34 a 23 byte message Hello World ---0x22:238 CRC Ok True
21:10:32.225 Received To 153 a 23 byte message Hello World ---0x99:238 CRC Ok True
21:10:32.705 Received To 34 a 23 byte message Hello World ---0x22:239 CRC Ok True

It looks like if the base station receives a message as it is about to send a message the Rfm69Device_OnTransmit never gets called.

It also looks like every so often the transmitter gets stuck on one of Windows 10 devices effectively jamming the frequency.

Transmit stuck on
16:12:10.193 Received To 34 a 22 byte message Hello World ---0x22:65 CRC Ok True
16:12:10.360 Received To 153 a 22 byte message Hello World ---0x99:65 CRC Ok True
16:12:10.831 Received To 34 a 22 byte message Hello World ---0x22:66 CRC Ok True
16:12:10.998 Received To 153 a 22 byte message Hello World ---0x99:66 CRC Ok True
The thread 0x570 has exited with code 0 (0x0).
16:12:11.484 Send-hello world RFM69-915-01 04-12-11
16:12:11.494 Received To 34 a 22 byte message Hello World ---0x22:67 CRC Ok True
16:12:11.504 Send-Done
The thread 0x3a8 has exited with code 0 (0x0).
16:12:16.554 Send-hello world RFM69-915-01 04-12-16
16:12:16.566 Send-Done
16:12:16.660 Transmit-Done
T--------------------------------------------
16:12:16.736 Received To 153 a 22 byte message Hello World ---0x99:75 CRC Ok True
16:12:17.206 Received To 34 a 22 byte message Hello World ---0x22:76 CRC Ok True
16:12:17.374 Received To 153 a 22 byte message Hello World ---0x99:76 CRC Ok True
16:12:18.011 Received To 153 a 22 byte message Hello World ---0x99:77 CRC Ok True


Transmit stuck 
16:12:07.591 Transmit-Done
16:12:07.880 Received To 153 a 23 byte message Hello World ---0x99:137 CRC Ok True
16:12:08.533 Received To 153 a 23 byte message Hello World ---0x99:138 CRC Ok True
16:12:08.839 Received To 17 a 24 byte message Hello World ----0x11:139 CRC Ok True
16:12:09.186 Received To 153 a 23 byte message Hello World ---0x99:139 CRC Ok True
16:12:09.493 Received To 17 a 24 byte message Hello World ----0x11:140 CRC Ok True
16:12:10.799 Received To 17 a 24 byte message Hello World ----0x11:142 CRC Ok True
The thread 0xc8 has exited with code 0 (0x0).
16:12:12.567 Send-hello world RFM69-915-02 04-12-12
16:12:12.589 Send-Done
16:12:12.681 Transmit-Done
16:12:16.510 Received To 17 a 33 byte message hello world RFM69-915-01 04-12-16 CRC Ok True
16:12:16.576 Received To 153 a 22 byte message Hello World ---0x99:75 CRC Ok True
16:12:17.025 Received To 153 a 23 byte message Hello World ---0x99:151 CRC Ok True
16:12:17.214 Received To 153 a 22 byte message Hello World ---0x99:76 CRC Ok True
16:12:17.331 Received To 17 a 24 byte message Hello World ----0x11:152 CRC Ok True
The thread 0xfa0 has exited with code 0 (0x0).
16:12:17.661 Send-hello world RFM69-915-02 04-12-17
16:12:17.680 Send-Done
16:12:17.772 Transmit-Done
16:12:17.851 Received To 153 a 22 byte message Hello World ---0x99:77 CRC Ok True
16:12:18.331 Received To 153 a 23 byte message Hello World ---0x99:153 CRC Ok True
16:12:18.489 Received To 153 a 22 byte message Hello World ---0x99:78 CRC Ok True
16:12:18.638 Received To 17 a 24 byte message Hello World ----0x11:154 CRC Ok True
16:12:18.985 Received To 153 a 23 byte message Hello World ---0x99:154 CRC Ok True
16:12:19.291 Received To 17 a 24 byte message Hello World ----0x11:155 CRC Ok True
16:12:19.638 Received To 153 a 23 byte message Hello World ---0x99:155 CRC Ok True
16:12:19.944 Received To 17 a 24 byte message Hello World ----0x11:156 CRC Ok True
16:12:20.291 Received To 153 a 23 byte message Hello World ---0x99:156 CRC Ok True
16:12:20.597 Received To 17 a 24 byte message Hello World ----0x11:157 CRC Ok True

Then as rfm69Device.SetMode(Rfm69HcwDevice.RegOpModeMode.Receive) hasn’t been called no messages are received until another message is sent.

It looks like a timing issue around access to the message fifo (I have that in a critical section) so I need todo some more debugging. Maybe purging the receive buffer

byte regPacketConfig2 = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegPacketConfig2);
			regPacketConfig2 |= 0b00000100;
			RegisterManager.WriteByte((byte)Rfm69HcwDevice.Registers.RegPacketConfig2, regPacketConfig2);

The adfruit.io RFM69 shield has DIO2 which can be used for automode operation which might remove some of the synchronisation issues I am encountering.

RFM69 hat library h WWWWWWWWoo

Again, while doing some stress testing I noticed an odd message go past in the Visual Studio output window. I had multiple devices sending addressed messages (both individual and broadcast) to the Adafruit RFM69 HCW Radio Bonnet, on my Windows 10 IoT Core device while it was sending a message every 5 seconds.

Received From 102 a 15 byte message Hello World:161
23:42:33.343 RegIrqFlags2 01100110
23:42:33.356 RegIrqFlags1 11011001
23:42:33.374 Address 0X99 10011001
Received From 153 a 15 byte message Hello World:106
23:42:33.761 RegIrqFlags2 01100110
23:42:33.774 RegIrqFlags1 11011001
23:42:33.791 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:162
The thread 0xd20 has exited with code 0 (0x0).
23:42:34.500 RegIrqFlags2 01100110
23:42:34.501 Send-hello world 11:42:34 PM
23:42:34.520 RegIrqFlags1 11011001
23:42:34.545 Send-Done
23:42:34.551 Address 0X10 00010000
Received From 16 a 15 byte message h    WWWWWWWWoo
23:42:34.686 RegIrqFlags2 00001000
23:42:34.701 RegIrqFlags1 10110000
23:42:34.715 Transmit-Done
Transmit-Done
23:42:34.902 RegIrqFlags2 01100110
23:42:34.915 RegIrqFlags1 11011001
23:42:34.931 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:163
23:42:35.626 RegIrqFlags2 01100110
23:42:35.640 RegIrqFlags1 11011001
23:42:35.659 Address 0X99 10011001
Received From 153 a 15 byte message Hello World:108
23:42:36.042 RegIrqFlags2 01100110
23:42:36.055 RegIrqFlags1 11011001
23:42:36.073 Address 0X66 01100110

The RegIrqFlags2 CrcOk (bit 1) was set and the message was corrupt.

RegIrqFlags2 bit flags from SX1231 datasheet

I have added code to check the CRC on inbound messages if this functionality is enabled. So the library can be used with CRCs disabled I have added a flag to the OnDataReceivedEventArgs class to indicate whether the CRC on the inbound message was OK.

private readonly Object Rfm9XRegFifoLock = new object();
...
private void ProcessPayloadReady(RegIrqFlags1 irqFlags1, RegIrqFlags2 irqFlags2)
{
	byte? address = null;
	byte numberOfBytes;
	byte[] messageBytes;

	lock (Rfm9XRegFifoLock)
	{
		// Read the length of the buffer if variable length packets
		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			numberOfBytes = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
		else
		{
			numberOfBytes = PayloadLength;
		}

		// Remove the address from start of the payload
		if (AddressingEnabled)
		{
			address = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);

			Debug.WriteLine("{0:HH:mm:ss.fff} Address 0X{1:X2} {2}", DateTime.Now, address, Convert.ToString((byte)address, 2).PadLeft(8, '0'));
			numberOfBytes--;
		}

		// Allocate a buffer for the payload and read characters from the Fifo
		messageBytes = new byte[numberOfBytes];

		for (int i = 0; i < numberOfBytes; i++)
		{
			messageBytes[i] = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
	}
...
public void SendMessage(byte[] messageBytes)
{
#region Guard conditions
#endregion

	lock (Rfm9XRegFifoLock)
	{
		SetMode(RegOpModeMode.StandBy);

		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			RegisterManager.WriteByte((byte)Registers.RegFifo, (byte)messageBytes.Length);
		}

		foreach (byte b in messageBytes)
		{
			this.RegisterManager.WriteByte((byte)Registers.RegFifo, b);
		}

		SetMode(RegOpModeMode.Transmit);
	}
}

I can most probably reduce the duration which I hold the lock for but that will require some more stress testing.

RFM69 hat library Hello Woooooooo

While doing some stress testing I noticed an odd message go past in the Visual Studio output window. I had multiple devices sending addressed messages (both individual and broadcast) to the Adafruit RFM69 HCW Radio Bonnet, on my Windows 10 IoT Core device while it was sending a message every 5 seconds.

Received From 153 a 13 byte message Hello World:7
18:43:56.544 RegIrqFlags2 01100110
18:43:56.558 RegIrqFlags1 11011001
18:43:56.575 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:162
The thread 0x254 has exited with code 0 (0x0).
18:43:57.699 Send-hello world 6:43:57 PM
18:43:57.699 RegIrqFlags2 01100110
18:43:57.731 RegIrqFlags1 10000000
18:43:57.747 Address 0X66 01100110
18:43:57.765 Send-Done
Received From 102 a 15 byte message Hello Woooooooo
18:43:57.987 RegIrqFlags2 00001000
18:43:58.003 RegIrqFlags1 10110000
18:43:58.017 Transmit-Done
Transmit-Done
18:43:58.825 RegIrqFlags2 01100110
18:43:58.838 RegIrqFlags1 11011001
18:43:58.857 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:164
18:43:59.966 RegIrqFlags2 01100110
18:43:59.979 RegIrqFlags1 11011001
18:43:59.998 Address 0X66 01100110

The odd thing was that the RegIrqFlags2 CrcOk (bit 1) was set but the message was still corrupt.

RegIrqFlags2 bit flags from SX1231 datasheet

After looking at the code I think the problem was the reading of the received message bytes from the device FIFO and the writing of bytes of message to be transmitted into the device FIFO overlapped. To stop this occurring again I have added code to synchronise access (using a Lock) to the FIFO.

private readonly Object Rfm9XRegFifoLock = new object();
...
private void ProcessPayloadReady(RegIrqFlags1 irqFlags1, RegIrqFlags2 irqFlags2)
{
	byte? address = null;
	byte numberOfBytes;
	byte[] messageBytes;

	lock (Rfm9XRegFifoLock)
	{
		// Read the length of the buffer if variable length packets
		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			numberOfBytes = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
		else
		{
			numberOfBytes = PayloadLength;
		}

		// Remove the address from start of the payload
		if (AddressingEnabled)
		{
			address = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);

			Debug.WriteLine("{0:HH:mm:ss.fff} Address 0X{1:X2} {2}", DateTime.Now, address, Convert.ToString((byte)address, 2).PadLeft(8, '0'));
			numberOfBytes--;
		}

		// Allocate a buffer for the payload and read characters from the Fifo
		messageBytes = new byte[numberOfBytes];

		for (int i = 0; i < numberOfBytes; i++)
		{
			messageBytes[i] = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
	}
...
public void SendMessage(byte[] messageBytes)
{
#region Guard conditions
#endregion

	lock (Rfm9XRegFifoLock)
	{
		SetMode(RegOpModeMode.StandBy);

		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			RegisterManager.WriteByte((byte)Registers.RegFifo, (byte)messageBytes.Length);
		}

		foreach (byte b in messageBytes)
		{
			this.RegisterManager.WriteByte((byte)Registers.RegFifo, b);
		}

		SetMode(RegOpModeMode.Transmit);
	}
}

The code has been running for a day without any corrupted messages so the lock appears to be working. I can most probably reduce the duration which I hold the lock for but that will require some more stress testing.