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.
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.
Pingback: .NET Core RAK811 LoRaWAN library Part3 | devMobile's blog