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