Initial stab at merging multi-sentence messages into a single easier-to-use message

This commit is contained in:
Morten 2020-01-15 17:38:21 -08:00
parent 4e8ad28503
commit be31b94f9c
4 changed files with 77 additions and 68 deletions

View file

@ -18,19 +18,14 @@ using System.Text;
namespace NmeaParser
{
interface IMultiPartMessage : System.Collections.IEnumerable
public interface IMultiSentenceMessage : System.Collections.IEnumerable
{
/// <summary>
/// Total number of messages of this type in this cycle
/// </summary>
int TotalMessages { get; }
/// <summary>
/// Message number
/// </summary>
int MessageNumber { get; }
bool TryAppend(string[] values);
bool IsComplete { get; }
IEnumerable<string> SerializeParts();
}
interface IMultiPartMessage<T> : IMultiPartMessage, IEnumerable<T>
{
public interface IMultiSentenceMessage<T> : IMultiSentenceMessage, IEnumerable<T>
{
}
}

View file

@ -24,8 +24,13 @@ namespace NmeaParser.Nmea
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gsv")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
[NmeaMessageType("--GSV")]
public class Gsv : NmeaMessage, IMultiPartMessage<SatelliteVehicle>
public class Gsv : NmeaMessage, IMultiSentenceMessage<SatelliteVehicle>
{
private readonly List<SatelliteVehicle> svs = new List<SatelliteVehicle>();
private int lastMessageNumber = 0;
private int totalMessages;
private readonly int firstMessageNumber;
/// <summary>
/// Initializes a new instance of the <see cref="Gsv"/> class.
/// </summary>
@ -36,9 +41,10 @@ namespace NmeaParser.Nmea
if (message == null || message.Length < 3)
throw new ArgumentException("Invalid GSV", "message");
TotalMessages = int.Parse(message[0], CultureInfo.InvariantCulture);
MessageNumber = int.Parse(message[1], CultureInfo.InvariantCulture);
totalMessages = int.Parse(message[0], CultureInfo.InvariantCulture);
firstMessageNumber = int.Parse(message[1], CultureInfo.InvariantCulture);
SVsInView = int.Parse(message[2], CultureInfo.InvariantCulture);
AppendParts(message);
List<SatelliteVehicle> svs = new List<SatelliteVehicle>();
for (int i = 3; i < message.Length - 3; i += 4)
@ -48,18 +54,37 @@ namespace NmeaParser.Nmea
else
svs.Add(new SatelliteVehicle(message, i));
}
this.SVs = svs.ToArray();
this.SVs = svs.AsReadOnly();
}
/// <summary>
/// Total number of messages of this type in this cycle
/// </summary>
public int TotalMessages { get; }
bool IMultiSentenceMessage.TryAppend(string[] values) => AppendParts(values);
bool IMultiSentenceMessage.IsComplete => firstMessageNumber == 1 && lastMessageNumber == totalMessages;
/// <summary>
/// Message number
/// </summary>
public int MessageNumber { get; }
private bool AppendParts(string[] message)
{
int msgCount = int.Parse(message[0], CultureInfo.InvariantCulture);
int msgNumber = int.Parse(message[1], CultureInfo.InvariantCulture);
var satellites = int.Parse(message[2], CultureInfo.InvariantCulture);
if (msgCount != totalMessages || msgNumber != lastMessageNumber + 1 || satellites != SVsInView)
return false; // Messages do not match
for (int i = 3; i < message.Length - 3; i += 4)
{
if (message[i].Length == 0)
continue;
else
svs.Add(new SatelliteVehicle(message, i));
}
lastMessageNumber = msgNumber;
this.SVs = svs.AsReadOnly();
return true;
}
IEnumerable<string> IMultiSentenceMessage.SerializeParts()
{
throw new NotImplementedException();
}
/// <summary>
/// Total number of SVs in view
@ -69,7 +94,7 @@ namespace NmeaParser.Nmea
/// <summary>
/// Satellite vehicles in this message part.
/// </summary>
public IReadOnlyList<SatelliteVehicle> SVs { get; }
public IReadOnlyList<SatelliteVehicle> SVs { get; private set; }
/// <summary>
/// Returns an enumerator that iterates through the collection.

View file

@ -92,10 +92,10 @@ namespace NmeaParser.Nmea
/// Invalid nmea message: Missing starting character '$'
/// or checksum failure
/// </exception>
public static NmeaMessage Parse(string message)
public static NmeaMessage Parse(string message, IMultiSentenceMessage? previousSentence = null)
{
if (string.IsNullOrEmpty(message))
throw new ArgumentNullException("message");
throw new ArgumentNullException(nameof(message));
int checksum = -1;
if (message[0] != '$')
@ -120,6 +120,13 @@ namespace NmeaParser.Nmea
string[] parts = message.Split(new char[] { ',' });
string MessageType = parts[0].Substring(1);
string[] MessageParts = parts.Skip(1).ToArray();
if(previousSentence is NmeaMessage pmsg && pmsg.MessageType == MessageType)
{
if (previousSentence.TryAppend(MessageParts))
{
return pmsg;
}
}
if (messageTypes.ContainsKey(MessageType))
{
return (NmeaMessage)messageTypes[MessageType].Invoke(new object[] { MessageType, MessageParts });

View file

@ -55,8 +55,8 @@ namespace NmeaParser
m_cts = new CancellationTokenSource();
m_stream = await OpenStreamAsync();
StartParser(m_cts.Token);
MultiPartMessageCache.Clear();
lock (m_lockObject)
_lastMultiMessage = null;
lock (m_lockObject)
{
IsOpen = true;
m_isOpening = false;
@ -132,7 +132,7 @@ namespace NmeaParser
await m_ParserTask;
if (m_stream != null)
await CloseStreamAsync(m_stream);
MultiPartMessageCache.Clear();
_lastMultiMessage = null;
m_stream = null;
lock (m_lockObject)
{
@ -167,58 +167,41 @@ namespace NmeaParser
}
foreach(var line in lines)
ProcessMessage(line);
}
}
private IMultiSentenceMessage? _lastMultiMessage;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="Must silently handle invalid/corrupt input")]
private void ProcessMessage(string p)
{
try
{
var msg = NmeaParser.Nmea.NmeaMessage.Parse(p);
var msg = NmeaParser.Nmea.NmeaMessage.Parse(p, _lastMultiMessage);
if(msg is IMultiSentenceMessage multi)
{
if (!multi.IsComplete)
{
_lastMultiMessage = multi; //Keep it around until next time
return;
}
_lastMultiMessage = null;
}
if (msg != null)
OnMessageReceived(msg);
}
catch { }
}
private void OnMessageReceived(Nmea.NmeaMessage msg)
private void OnMessageReceived(Nmea.NmeaMessage msg)
{
if (msg == null)
return;
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));
MessageReceived?.Invoke(this, new NmeaMessageReceivedEventArgs(msg));
}
private readonly 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.
@ -289,10 +272,9 @@ namespace NmeaParser
/// </summary>
public sealed class NmeaMessageReceivedEventArgs : EventArgs
{
internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message, IReadOnlyList<Nmea.NmeaMessage>? messageParts)
internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message)
{
Message = message;
MessageParts = messageParts;
}
/// <summary>
@ -309,7 +291,7 @@ namespace NmeaParser
/// <value>
/// <c>true</c> if this instance is multi part; otherwise, <c>false</c>.
/// </value>
public bool IsMultipart => Message is IMultiPartMessage;
public bool IsMultipart => Message is IMultiSentenceMessage;
/// <summary>
/// Gets the message parts if this is a multi-part message and all message parts has been received.