Esempio n. 1
0
 /// <summary>
 /// A standard filter rule. When the filter matches
 /// </summary>
 /// <param name="sourceName">Name of the source (Nmea stream name) for which the filter applies or * for all</param>
 /// <param name="talkerId">TalkerId for which the rule applies or <see cref="TalkerId.Any"/></param>
 /// <param name="sentenceId">SentenceId for which the rule applies or <see cref="SentenceId.Any"/></param>
 /// <param name="sinks">Where to send the message when the filter matches</param>
 /// <param name="rawMessagesOnly">The filter matches raw messages only. This is the default, because otherwise known message
 /// types would be implicitly duplicated on forwarding</param>
 /// <param name="continueAfterMatch">True to continue processing after a match, false to stop processing this message</param>
 public FilterRule(string sourceName, TalkerId talkerId, SentenceId sentenceId, IEnumerable <string> sinks, bool rawMessagesOnly = true,
                   bool continueAfterMatch = false)
 {
     _rawMessagesOnly   = rawMessagesOnly;
     SourceName         = sourceName;
     TalkerId           = talkerId;
     SentenceId         = sentenceId;
     Sinks              = sinks;
     ContinueAfterMatch = continueAfterMatch;
 }
Esempio n. 2
0
 /// <summary>
 /// A standard filter rule. When the filter matches
 /// </summary>
 /// <param name="sourceName">Name of the source (Nmea stream name) for which the filter applies or * for all</param>
 /// <param name="talkerId">TalkerId for which the rule applies or <see cref="TalkerId.Any"/></param>
 /// <param name="sentenceId">SentenceId for which the rule applies or <see cref="SentenceId.Any"/></param>
 /// <param name="sinks">Where to send the message when the filter matches</param>
 /// <param name="forwardingAction">When the message is about to be sent, this method is called that can modify the
 /// message. First arg is the source of the message, second the designated sink.</param>
 /// <param name="rawMessagesOnly">The filter matches raw messages only. This is the default, because otherwise known message
 /// types would be implicitly duplicated on forwarding</param>
 /// <param name="continueAfterMatch">True to continue processing after a match, false to stop processing this message</param>
 public FilterRule(string sourceName, TalkerId talkerId, SentenceId sentenceId, IEnumerable <string> sinks,
                   Func <NmeaSinkAndSource, NmeaSinkAndSource, NmeaSentence, NmeaSentence> forwardingAction, bool rawMessagesOnly = true,
                   bool continueAfterMatch = false)
 {
     _rawMessagesOnly   = rawMessagesOnly;
     SourceName         = sourceName;
     TalkerId           = talkerId;
     SentenceId         = sentenceId;
     Sinks              = sinks;
     ForwardingAction   = forwardingAction;
     ContinueAfterMatch = continueAfterMatch;
 }
Esempio n. 3
0
        /// <summary>
        /// Gets the last sentence of the given type.
        /// Does not return sentences that are part of a group (i.e. GSV, RTE)
        /// </summary>
        /// <param name="id">Sentence Id to query</param>
        /// <returns>The last sentence of that type, or null.</returns>
        public NmeaSentence?GetLastSentence(SentenceId id)
        {
            lock (_lock)
            {
                if (_sentences.TryGetValue(id, out var sentence))
                {
                    return(sentence);
                }

                return(null);
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Constructs a message from a typed sentence
        /// </summary>
        /// <param name="sentence">Sentence to send. It must be valid</param>
        public TalkerSentence(NmeaSentence sentence)
        {
            TalkerId = sentence.TalkerId;
            Id       = sentence.SentenceId;
            var content = sentence.ToNmeaParameterList();

            if (string.IsNullOrWhiteSpace(content) || sentence.Valid == false)
            {
                throw new InvalidOperationException("Input sentence not valid or cannot be encoded");
            }

            _fields = content.Split(new char[] { ',' }, StringSplitOptions.None);
        }
Esempio n. 5
0
        /// <summary>
        /// Gets the last sentence of the given type.
        /// Does not return sentences that are part of a group (i.e. GSV, RTE)
        /// </summary>
        /// <param name="id">Sentence Id to query</param>
        /// <param name="maxAge">Maximum age of the sentence</param>
        /// <returns>The last sentence of that type, or null if none was received within the given timespan.</returns>
        public NmeaSentence?GetLastSentence(SentenceId id, TimeSpan maxAge)
        {
            lock (_lock)
            {
                if (_sentences.TryGetValue(id, out var sentence))
                {
                    if (sentence.Age < maxAge)
                    {
                        return(sentence);
                    }
                }

                return(null);
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Tries to get a sentence of the given type
        /// </summary>
        /// <typeparam name="T">The type of the sentence to query</typeparam>
        /// <param name="id">The sentence id for T</param>
        /// <param name="sentence">Receives the sentence, if any was found</param>
        /// <returns>True on success, false if no such message was received</returns>
        public bool TryGetLastSentence <T>(SentenceId id,
#if NET5_0_OR_GREATER
                                           [NotNullWhen(true)]
#endif
                                           out T sentence)
            where T : NmeaSentence
        {
            var s = GetLastSentence(id);

            if (s is T)
            {
                sentence = (T)s;
                return(true);
            }

            sentence = null !;
            return(false);
        }
Esempio n. 7
0
        /// <summary>
        /// Reads NMEA0183 query sentence from provided string
        /// </summary>
        /// <param name="sentence">NMEA0183 query sentence</param>
        /// <returns>QuerySentence instance</returns>
        /// <remarks><paramref name="sentence"/> does not include new line characters</remarks>
        public static QuerySentence FromSentenceString(string sentence)
        {
            // http://www.tronico.fi/OH6NT/docs/NMEA0183.pdf
            // page 3
            // $XXYYY ...
            const int SentenceLength = 6;

            if (sentence == null)
            {
                throw new ArgumentNullException(nameof(sentence));
            }

            if (sentence.Length != SentenceLength)
            {
                throw new ArgumentException($"Sentence length must be exactly {SentenceLength} characters", nameof(sentence));
            }

            if (sentence[0] != '$')
            {
                throw new ArgumentException("Sentence must start with '$'", nameof(sentence));
            }

            TalkerId requesterId = new TalkerId(sentence[1], sentence[2]);
            TalkerId deviceId    = new TalkerId(sentence[3], sentence[4]);

            if (sentence[5] != 'Q')
            {
                throw new ArgumentException("Valid query sentence must have character 'Q' at index 5");
            }

            if (sentence[6] != ',')
            {
                throw new ArgumentException("Valid query sentence must have character ',' at index 6");
            }

            SentenceId requestedSentence = new SentenceId(sentence[7], sentence[8], sentence[9]);

            return(new QuerySentence(requesterId, deviceId, requestedSentence));
        }
Esempio n. 8
0
 /// <summary>
 /// Constructs NMEA0183 talker identifier
 /// </summary>
 /// <param name="talkerId">NMEA0183 talker identifier of the device sending the sentence</param>
 /// <param name="sentenceId">NMEA0183 sentence identifier</param>
 /// <param name="fields">fields related to the sentence</param>
 public TalkerSentence(TalkerId talkerId, SentenceId sentenceId, IEnumerable <string> fields)
 {
     TalkerId = talkerId;
     Id       = sentenceId;
     _fields  = fields.ToArray();
 }
Esempio n. 9
0
 /// <summary>
 /// Registers sentence identifier as known. Registered sentences are used by <see cref="TryGetTypedValue"/>.
 /// </summary>
 /// <param name="id">NMEA0183 sentence identifier</param>
 /// <param name="producer">Function which produces typed object given <see cref="TalkerSentence"/>.</param>
 public static void RegisterSentence(SentenceId id, Func <TalkerSentence, DateTimeOffset, NmeaSentence> producer)
 {
     s_registeredSentences[id] = producer;
 }
Esempio n. 10
0
        /// <summary>
        /// Reads NMEA0183 talker sentence from provided string
        /// </summary>
        /// <param name="sentence">NMEA0183 talker sentence</param>
        /// <param name="expectedTalkerId">If this is not TalkerId.Any, only messages with this talker id are parsed,
        /// all others are ignored. This reduces workload if a source acts as repeater, but the repeated messages are not needed.</param>
        /// <param name="errorCode">Returns an error code, if the parsing failed</param>
        /// <returns>TalkerSentence instance, or null in case of an error</returns>
        /// <remarks><paramref name="sentence"/> does not include new line characters</remarks>
        public static TalkerSentence?FromSentenceString(string sentence, TalkerId expectedTalkerId, out NmeaError errorCode)
        {
            // $XXY, ...
            const int sentenceHeaderMinLength = 4;

            // http://www.tronico.fi/OH6NT/docs/NMEA0183.pdf page 2
            // defines this as 80 + 1 (for $), but we don't really care if it is something within a reasonable limit.
            const int MaxSentenceLength = 256;

            if (sentence == null)
            {
                throw new ArgumentNullException(nameof(sentence));
            }

            if (sentence.Length < sentenceHeaderMinLength)
            {
                errorCode = NmeaError.MessageToShort;
                return(null);
            }

            if (sentence.Length > MaxSentenceLength)
            {
                errorCode = NmeaError.MessageToLong;
                return(null);
            }

            if (sentence[0] != '$' && sentence[0] != '!')
            {
                // Valid sentences start with $ or ! (for the AIS sentences)
                errorCode = NmeaError.NoSyncByte;
                return(null);
            }

            TalkerId talkerId = new TalkerId(sentence[1], sentence[2]);

            if (expectedTalkerId != TalkerId.Any && expectedTalkerId != talkerId)
            {
                errorCode = NmeaError.None;
                return(null);
            }

            int firstComma = sentence.IndexOf(',', 1);

            if (firstComma == -1)
            {
                errorCode = NmeaError.MessageToShort;
                return(null);
            }

            string sentenceIdString = sentence.Substring(3, firstComma - 3);

            SentenceId sentenceId = new SentenceId(sentenceIdString);

            string[] fields       = sentence.Substring(firstComma + 1).Split(',');
            int      lastFieldIdx = fields.Length - 1;

            // This returns null as the checksum if there was none, or a very big number if the checksum couldn't be parsed
            (int?checksum, string lastField) = GetChecksumAndLastField(fields[lastFieldIdx]);
            fields[lastFieldIdx]             = lastField;

            TalkerSentence result = new TalkerSentence(talkerId, sentenceId, fields);

            if (checksum.HasValue)
            {
                byte realChecksum = CalculateChecksumFromSentenceString(sentence.AsSpan());

                if (realChecksum != checksum.Value)
                {
                    errorCode = NmeaError.InvalidChecksum;
                    return(null);
                }
            }

            errorCode = NmeaError.None;
            return(result);
        }
Esempio n. 11
0
 /// <summary>
 /// Constructs NMEA0183 query sentence
 /// </summary>
 /// <param name="requesterId">Talker identifier of the requester</param>
 /// <param name="deviceId">Talker identifier of the device</param>
 /// <param name="requestedSentence">Requested sentence</param>
 public QuerySentence(TalkerId requesterId, TalkerId deviceId, SentenceId requestedSentence)
 {
     RequesterId       = requesterId;
     DeviceId          = deviceId;
     RequestedSentence = requestedSentence;
 }