From be31b94f9ce5b0dbf11eba1a3b7421a4fbeb6a63 Mon Sep 17 00:00:00 2001 From: Morten Date: Wed, 15 Jan 2020 17:38:21 -0800 Subject: [PATCH] Initial stab at merging multi-sentence messages into a single easier-to-use message --- ...artMessage.cs => IMultiSentenceMessage.cs} | 19 ++---- src/NmeaParser/Nmea/Gsv.cs | 51 +++++++++++---- src/NmeaParser/Nmea/NmeaMessage.cs | 11 +++- src/NmeaParser/NmeaDevice.cs | 64 +++++++------------ 4 files changed, 77 insertions(+), 68 deletions(-) rename src/NmeaParser/{IMultiPartMessage.cs => IMultiSentenceMessage.cs} (72%) diff --git a/src/NmeaParser/IMultiPartMessage.cs b/src/NmeaParser/IMultiSentenceMessage.cs similarity index 72% rename from src/NmeaParser/IMultiPartMessage.cs rename to src/NmeaParser/IMultiSentenceMessage.cs index 0f79943..b825393 100644 --- a/src/NmeaParser/IMultiPartMessage.cs +++ b/src/NmeaParser/IMultiSentenceMessage.cs @@ -18,19 +18,14 @@ using System.Text; namespace NmeaParser { - interface IMultiPartMessage : System.Collections.IEnumerable + public interface IMultiSentenceMessage : System.Collections.IEnumerable { - /// - /// Total number of messages of this type in this cycle - /// - int TotalMessages { get; } - - /// - /// Message number - /// - int MessageNumber { get; } + bool TryAppend(string[] values); + bool IsComplete { get; } + IEnumerable SerializeParts(); } - interface IMultiPartMessage : IMultiPartMessage, IEnumerable - { + + public interface IMultiSentenceMessage : IMultiSentenceMessage, IEnumerable + { } } diff --git a/src/NmeaParser/Nmea/Gsv.cs b/src/NmeaParser/Nmea/Gsv.cs index f4a67b9..50ad9fd 100644 --- a/src/NmeaParser/Nmea/Gsv.cs +++ b/src/NmeaParser/Nmea/Gsv.cs @@ -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 + public class Gsv : NmeaMessage, IMultiSentenceMessage { + private readonly List svs = new List(); + private int lastMessageNumber = 0; + private int totalMessages; + private readonly int firstMessageNumber; + /// /// Initializes a new instance of the class. /// @@ -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 svs = new List(); 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(); } - /// - /// Total number of messages of this type in this cycle - /// - public int TotalMessages { get; } + bool IMultiSentenceMessage.TryAppend(string[] values) => AppendParts(values); + bool IMultiSentenceMessage.IsComplete => firstMessageNumber == 1 && lastMessageNumber == totalMessages; - /// - /// Message number - /// - 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 IMultiSentenceMessage.SerializeParts() + { + throw new NotImplementedException(); + } /// /// Total number of SVs in view @@ -69,7 +94,7 @@ namespace NmeaParser.Nmea /// /// Satellite vehicles in this message part. /// - public IReadOnlyList SVs { get; } + public IReadOnlyList SVs { get; private set; } /// /// Returns an enumerator that iterates through the collection. diff --git a/src/NmeaParser/Nmea/NmeaMessage.cs b/src/NmeaParser/Nmea/NmeaMessage.cs index 14c4ce4..52ebbaf 100644 --- a/src/NmeaParser/Nmea/NmeaMessage.cs +++ b/src/NmeaParser/Nmea/NmeaMessage.cs @@ -92,10 +92,10 @@ namespace NmeaParser.Nmea /// Invalid nmea message: Missing starting character '$' /// or checksum failure /// - 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 }); diff --git a/src/NmeaParser/NmeaDevice.cs b/src/NmeaParser/NmeaDevice.cs index 7ad7b77..992cccb 100644 --- a/src/NmeaParser/NmeaDevice.cs +++ b/src/NmeaParser/NmeaDevice.cs @@ -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(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> MultiPartMessageCache = new Dictionary>(); + //private readonly Dictionary> MultiPartMessageCache = new Dictionary>(); /// /// Occurs when an NMEA message is received. @@ -289,10 +272,9 @@ namespace NmeaParser /// public sealed class NmeaMessageReceivedEventArgs : EventArgs { - internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message, IReadOnlyList? messageParts) + internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message) { Message = message; - MessageParts = messageParts; } /// @@ -309,7 +291,7 @@ namespace NmeaParser /// /// true if this instance is multi part; otherwise, false. /// - public bool IsMultipart => Message is IMultiPartMessage; + public bool IsMultipart => Message is IMultiSentenceMessage; /// /// Gets the message parts if this is a multi-part message and all message parts has been received.