Null handling improvements, device lifecycle fixes and improved multipart message parsing

This commit is contained in:
Morten Nielsen 2019-02-19 22:54:36 -08:00
parent c865776975
commit d2c1d2e11f
2 changed files with 69 additions and 73 deletions

View file

@ -106,7 +106,7 @@ namespace NmeaParser
lastLineRead = ReadLine(); //seek forward to first nmea token
AppendToBuffer(lastLineRead);
}
if(groupToken == null)
if(groupToken == null && lastLineRead != null)
{
var values = lastLineRead.Trim().Split(new char[] { ',' });
if (values.Length > 0)

View file

@ -32,9 +32,9 @@ namespace NmeaParser
private object m_lockObject = new object();
private string m_message = "";
private Stream m_stream;
private System.Threading.CancellationTokenSource m_cts;
private TaskCompletionSource<bool> m_closeTask;
private bool m_isOpening;
private CancellationTokenSource m_cts;
private bool m_isOpening;
private Task m_ParserTask;
/// <summary>
/// Initializes a new instance of the <see cref="NmeaDevice"/> class.
@ -54,9 +54,9 @@ namespace NmeaParser
if (IsOpen || m_isOpening) return;
m_isOpening = true;
}
m_cts = new System.Threading.CancellationTokenSource();
m_cts = new CancellationTokenSource();
m_stream = await OpenStreamAsync();
StartParser();
StartParser(m_cts.Token);
MultiPartMessageCache.Clear();
lock (m_lockObject)
{
@ -66,11 +66,10 @@ namespace NmeaParser
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "_")]
private void StartParser()
private void StartParser(CancellationToken token)
{
var token = m_cts.Token;
System.Diagnostics.Debug.WriteLine("Starting parser...");
var _ = Task.Run(async () =>
System.Diagnostics.Debug.WriteLine("Starting parser...");
m_ParserTask = Task.Run(async () =>
{
byte[] buffer = new byte[1024];
while (!token.IsCancellationRequested)
@ -89,8 +88,6 @@ namespace NmeaParser
}
await Task.Delay(50);
}
if (m_closeTask != null)
m_closeTask.SetResult(true);
});
}
@ -129,13 +126,14 @@ namespace NmeaParser
{
if (m_cts != null)
{
m_closeTask = new TaskCompletionSource<bool>();
if (m_cts != null)
m_cts.Cancel();
m_cts = null;
}
await m_closeTask.Task;
await CloseStreamAsync(m_stream);
if (m_ParserTask != null)
await m_ParserTask;
if (m_stream != null)
await CloseStreamAsync(m_stream);
MultiPartMessageCache.Clear();
m_stream = null;
lock (m_lockObject)
@ -183,48 +181,44 @@ namespace NmeaParser
OnMessageReceived(msg);
}
catch { }
}
private void OnMessageReceived(Nmea.NmeaMessage msg)
{
var args = new NmeaMessageReceivedEventArgs(msg);
if (msg is IMultiPartMessage multi)
{
args.IsMultipart = true;
if (MultiPartMessageCache.ContainsKey(msg.MessageType))
{
var dic = MultiPartMessageCache[msg.MessageType];
if (dic.ContainsKey(multi.MessageNumber - 1) && !dic.ContainsKey(multi.MessageNumber))
{
dic[multi.MessageNumber] = msg;
}
else //Something is out of order. Clear cache
MultiPartMessageCache.Remove(msg.MessageType);
}
else if (multi.MessageNumber == 1)
{
MultiPartMessageCache[msg.MessageType] = new Dictionary<int, Nmea.NmeaMessage>(multi.TotalMessages);
MultiPartMessageCache[msg.MessageType][1] = msg;
}
if (MultiPartMessageCache.ContainsKey(msg.MessageType))
{
var dic = MultiPartMessageCache[msg.MessageType];
if (dic.Count == multi.TotalMessages) //We have a full list
{
MultiPartMessageCache.Remove(msg.MessageType);
args.MessageParts = dic.Values.ToArray();
}
private void OnMessageReceived(Nmea.NmeaMessage msg)
{
Nmea.NmeaMessage[] messageParts = null;
if (msg is IMultiPartMessage multi)
{
string messageType = msg.MessageType.Substring(2); //We don't care about the two first characters. Ie GPGSV, GLGSV, GAGSV etc are all part of the same multi-part message
if (MultiPartMessageCache.ContainsKey(messageType))
{
var dic = MultiPartMessageCache[messageType];
if (dic.ContainsKey(multi.MessageNumber - 1) && !dic.ContainsKey(multi.MessageNumber))
{
dic[multi.MessageNumber] = msg;
}
}
}
else //Something is out of order. Clear cache
MultiPartMessageCache.Remove(messageType);
}
else if (multi.MessageNumber == 1)
{
MultiPartMessageCache[messageType] = new Dictionary<int, Nmea.NmeaMessage>(multi.TotalMessages);
MultiPartMessageCache[messageType][1] = msg;
}
if (MultiPartMessageCache.ContainsKey(messageType))
{
var dic = MultiPartMessageCache[messageType];
if (dic.Count == multi.TotalMessages) //We have a full list
{
MultiPartMessageCache.Remove(messageType);
messageParts = dic.Values.ToArray();
}
}
}
MessageReceived?.Invoke(this, new NmeaMessageReceivedEventArgs(msg, messageParts));
}
if (MessageReceived != null)
{
MessageReceived(this, args);
}
}
private Dictionary<string, Dictionary<int, Nmea.NmeaMessage>> MultiPartMessageCache
= new Dictionary<string,Dictionary<int,Nmea.NmeaMessage>>();
private readonly Dictionary<string, Dictionary<int, Nmea.NmeaMessage>> MultiPartMessageCache = new Dictionary<string,Dictionary<int,Nmea.NmeaMessage>>();
/// <summary>
/// Occurs when an NMEA message is received.
@ -295,8 +289,10 @@ namespace NmeaParser
/// </summary>
public sealed class NmeaMessageReceivedEventArgs : EventArgs
{
internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message) {
internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message, IReadOnlyList<Nmea.NmeaMessage> messageParts)
{
Message = message;
MessageParts = messageParts;
}
/// <summary>
@ -305,22 +301,22 @@ namespace NmeaParser
/// <value>
/// The nmea message.
/// </value>
public Nmea.NmeaMessage Message { get; }
/// <summary>
/// Gets a value indicating whether this instance is a multi part message.
/// </summary>
/// <value>
/// <c>true</c> if this instance is multi part; otherwise, <c>false</c>.
/// </value>
public bool IsMultipart { get; internal set; }
/// <summary>
/// Gets the message parts if this is a multi-part message and all message parts has been received.
/// </summary>
/// <value>
/// The message parts.
/// </value>
public IReadOnlyList<Nmea.NmeaMessage> MessageParts { get; internal set; }
public Nmea.NmeaMessage Message { get; }
/// <summary>
/// Gets a value indicating whether this instance is a multi part message.
/// </summary>
/// <value>
/// <c>true</c> if this instance is multi part; otherwise, <c>false</c>.
/// </value>
public bool IsMultipart => Message is IMultiPartMessage;
/// <summary>
/// Gets the message parts if this is a multi-part message and all message parts has been received.
/// </summary>
/// <value>
/// The message parts.
/// </value>
public IReadOnlyList<Nmea.NmeaMessage> MessageParts { get; }
}
}