File: NmeaSinkAndSource.cs
Web Access
Project: ..\..\..\src\Iot.Device.Bindings\Iot.Device.Bindings.csproj (Iot.Device.Bindings)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Text;
using Iot.Device.Nmea0183.Sentences;
using UnitsNet;
 
namespace Iot.Device.Nmea0183
{
    /// <summary>
    /// This abstract class describes an NMEA data source or data sink.
    /// It can be a connection, a data processor or an output device.
    /// </summary>
    public abstract class NmeaSinkAndSource : IDisposable
    {
        /// <summary>
        /// This is fired when a new position is available
        /// </summary>
        public virtual event PositionUpdate? OnNewPosition;
 
        /// <summary>
        /// This is fired when the time is updated
        /// </summary>
        public virtual event Action<DateTimeOffset>? OnNewTime;
 
        /// <summary>
        /// This is fired on every new sentence
        /// </summary>
        public virtual event Action<NmeaSinkAndSource, NmeaSentence>? OnNewSequence;
 
        /// <summary>
        /// This is fired when a message couldn't be parsed
        /// </summary>
        public virtual event Action<NmeaSinkAndSource, string, NmeaError>? OnParserError;
 
        /// <summary>
        /// Constructs a message sink
        /// </summary>
        /// <param name="interfaceName">Name of the interface (mostly used for logging purposes)</param>
        protected NmeaSinkAndSource(string interfaceName)
        {
            InterfaceName = interfaceName;
        }
 
        /// <summary>
        /// Name of the interface
        /// </summary>
        public string InterfaceName
        {
            get;
        }
 
        /// <summary>
        /// Start receiving messages from this interface.
        /// An implementation should open streams, connect to sockets or create receiver threads, as appropriate.
        /// </summary>
        public abstract void StartDecode();
 
        /// <summary>
        /// Send the message to the device.
        /// From the implementation side, this is where the input data comes in (e.g. from the message dispatcher)
        /// </summary>
        /// <param name="source">Source of message</param>
        /// <param name="sentence">Sentence to send</param>
        public abstract void SendSentence(NmeaSinkAndSource source, NmeaSentence sentence);
 
        /// <summary>
        /// Send the given sentence to the interface.
        /// </summary>
        /// <param name="sentence">Sentence to send</param>
        public void SendSentence(NmeaSentence sentence)
        {
            SendSentence(this, sentence);
        }
 
        /// <summary>
        /// Stops sending or receiving messages from and to this interface.
        /// Any pending messages are discarded.
        /// </summary>
        public abstract void StopDecode();
 
        /// <summary>
        /// Fire an event informing about parser errors
        /// </summary>
        /// <param name="message">The message to write</param>
        /// <param name="error">The kind of error seen</param>
        protected void FireOnParserError(string message, NmeaError error)
        {
            OnParserError?.Invoke(this, message, error);
        }
 
        /// <summary>
        /// Dispose this instance
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                StopDecode();
            }
        }
 
        /// <summary>
        /// Standard dispose method
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        /// <summary>
        /// Forwards the given sentence to listeners, as needed. Use the current instance as source.
        /// </summary>
        /// <param name="typedSequence">The sentences to process</param>
        protected virtual void DispatchSentenceEvents(NmeaSentence? typedSequence)
        {
            DispatchSentenceEvents(this, typedSequence);
        }
 
        /// <summary>
        /// Forwards the given sentence to listeners, as needed.
        /// </summary>
        /// <param name="source">The source from which this event comes (if forwarded)</param>
        /// <param name="typedSequence">The sentences to process</param>
        protected virtual void DispatchSentenceEvents(NmeaSinkAndSource source, NmeaSentence? typedSequence)
        {
            if (typedSequence != null)
            {
                OnNewSequence?.Invoke(source, typedSequence);
            }
 
            if (typedSequence is RecommendedMinimumNavigationInformation rmc && rmc.Valid)
            {
                OnNewPosition?.Invoke(rmc.Position, rmc.TrackMadeGoodInDegreesTrue, rmc.SpeedOverGround);
            }
            else if (typedSequence is TimeDate td)
            {
                if (td.Valid)
                {
                    OnNewTime?.Invoke(td.DateTime);
                }
            }
        }
 
        /// <summary>
        /// Sends a list of messages at once
        /// </summary>
        /// <param name="sentencesToSend">The list of sentences to send</param>
        public virtual void SendSentences(IEnumerable<NmeaSentence> sentencesToSend)
        {
            foreach (var sentence in sentencesToSend)
            {
                SendSentence(sentence);
            }
        }
    }
}