NmeaParser/src/NmeaParser.Shared/NmeaDevice.cs

269 lines
7.3 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Copyright (c) 2014 Morten Nielsen
//
// Licensed under the Microsoft Public License (Ms-PL) (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://opensource.org/licenses/Ms-PL.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Windows.Foundation;
namespace NmeaParser
{
/// <summary>
/// A generic abstract NMEA device
/// </summary>
public abstract class NmeaDevice : IDisposable
{
private object m_lockObject = new object();
private string m_message = "";
private Stream m_stream;
System.Threading.CancellationTokenSource m_cts;
TaskCompletionSource<bool> closeTask;
/// <summary>
/// Initializes a new instance of the <see cref="NmeaDevice"/> class.
/// </summary>
protected NmeaDevice()
{
}
/// <summary>
/// Opens the device connection.
/// </summary>
/// <returns></returns>
public async Task OpenAsync()
{
lock (m_lockObject)
{
if (IsOpen) return;
IsOpen = true;
}
m_cts = new System.Threading.CancellationTokenSource();
m_stream = await OpenStreamAsync();
StartParser();
MultiPartMessageCache.Clear();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "_")]
private void StartParser()
{
var token = m_cts.Token;
System.Diagnostics.Debug.WriteLine("Starting parser...");
var _ = Task.Run(async () =>
{
var stream = m_stream;
byte[] buffer = new byte[1024];
while (!token.IsCancellationRequested)
{
int readCount = 0;
try
{
readCount = await stream.ReadAsync(buffer, 0, 1024, token).ConfigureAwait(false);
}
catch { }
if (token.IsCancellationRequested)
break;
if (readCount > 0)
{
OnData(buffer.Take(readCount).ToArray());
}
await Task.Delay(10, token);
}
if (closeTask != null)
closeTask.SetResult(true);
});
}
/// <summary>
/// Creates the stream the NmeaDevice is working on top off.
/// </summary>
/// <returns></returns>
protected abstract Task<Stream> OpenStreamAsync();
/// <summary>
/// Closes the device.
/// </summary>
/// <returns></returns>
public async Task CloseAsync()
{
if (m_cts != null)
{
closeTask = new TaskCompletionSource<bool>();
if (m_cts != null)
m_cts.Cancel();
m_cts = null;
}
await closeTask.Task;
await CloseStreamAsync(m_stream);
MultiPartMessageCache.Clear();
m_stream = null;
lock (m_lockObject)
IsOpen = false;
}
/// <summary>
/// Closes the stream the NmeaDevice is working on top off.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns></returns>
protected abstract Task CloseStreamAsync(Stream stream);
private void OnData(byte[] data)
{
var nmea = System.Text.Encoding.UTF8.GetString(data, 0, data.Length);
string line = null;
lock (m_lockObject)
{
m_message += nmea;
var lineEnd = m_message.IndexOf("\n", StringComparison.Ordinal);
if (lineEnd > -1)
{
line = m_message.Substring(0, lineEnd).Trim();
m_message = m_message.Substring(lineEnd + 1);
}
}
if (!string.IsNullOrEmpty(line))
ProcessMessage(line);
}
[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);
if (msg != null)
OnMessageReceived(msg);
}
catch { }
}
private void OnMessageReceived(Nmea.NmeaMessage msg)
{
var args = new NmeaMessageReceivedEventArgs(msg);
var multi = msg as IMultiPartMessage;
if (multi != null)
{
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();
}
}
}
if (MessageReceived != null)
{
MessageReceived(this, args);
}
}
private Dictionary<string, Dictionary<int, Nmea.NmeaMessage>> MultiPartMessageCache
= new Dictionary<string,Dictionary<int,Nmea.NmeaMessage>>();
/// <summary>
/// Occurs when an NMEA message is received.
/// </summary>
public event EventHandler<NmeaMessageReceivedEventArgs> MessageReceived;
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (m_stream != null)
{
if (m_cts != null)
{
m_cts.Cancel();
m_cts = null;
}
CloseStreamAsync(m_stream);
if (disposing && m_stream != null)
m_stream.Dispose();
m_stream = null;
}
}
/// <summary>
/// Gets a value indicating whether this device is open.
/// </summary>
/// <value>
/// <c>true</c> if this instance is open; otherwise, <c>false</c>.
/// </value>
public bool IsOpen { get; private set; }
}
/// <summary>
/// Event argument for the <see cref="NmeaDevice.MessageReceived" />
/// </summary>
public sealed class NmeaMessageReceivedEventArgs : EventArgs
{
internal NmeaMessageReceivedEventArgs(Nmea.NmeaMessage message) {
Message = message;
}
/// <summary>
/// Gets the nmea message.
/// </summary>
/// <value>
/// The nmea message.
/// </value>
public Nmea.NmeaMessage Message { get; private set; }
/// <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; }
}
}