File: ProprietaryMessage.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.Globalization;
 
namespace Iot.Device.Nmea0183.Sentences
{
    /// <summary>
    /// Proprietary message used to pass NMEA2000 messages over NMEA0183, only supported
    /// by some converters and for some messages, for instance engine parameters.
    /// The messages are usually not fully documented, but the SeaSmart (v1.6.0) protocol
    /// specification may help (and some trying around)
    /// </summary>
    public abstract class ProprietaryMessage : NmeaSentence
    {
        /// <summary>
        /// This sentence's id
        /// </summary>
        public static SentenceId Id => new SentenceId("DIN");
        private static bool Matches(SentenceId sentence) => Id == sentence;
 
        /// <summary>
        /// Checks this message has the correct talker id
        /// </summary>
        /// <param name="sentence">The sentence to check</param>
        /// <returns>True if this input sentence matches this message type (but be careful that this message
        /// type needs further division by arguments)</returns>
        protected static bool Matches(TalkerSentence sentence) => Matches(sentence.Id);
 
        /// <summary>
        /// Creates a default message of this type
        /// </summary>
        protected ProprietaryMessage()
            : base(TalkerId.Proprietary, Id, DateTimeOffset.UtcNow)
        {
        }
 
        /// <summary>
        /// Used to create a message while decoding, see base class implementation
        /// </summary>
        protected ProprietaryMessage(TalkerId talker, SentenceId id, DateTimeOffset time)
            : base(talker, id, time)
        {
        }
 
        /// <summary>
        /// The hex identifier of this message type (first field of a PCDIN message)
        /// </summary>
        public abstract int Identifier
        {
            get;
        }
 
        private static UInt32 InverseEndianness(UInt32 value)
        {
            return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
                   (value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
        }
 
        /// <summary>
        /// Decodes a value from a longer hex string (PRDIN messages contain one blob of stringly-typed hex numbers)
        /// </summary>
        /// <param name="input">Input string</param>
        /// <param name="start">Start offset of required number</param>
        /// <param name="length">Length of required number. Must be 2, 4 or 8</param>
        /// <param name="inverseEndianness">True to inverse the endianness of the number (reverse the partial string)</param>
        /// <param name="value">The output value</param>
        /// <returns>True on success, false otherwise</returns>
        /// <exception cref="ArgumentException">Length is not 2, 4 or 8</exception>
        /// <remarks>
        /// Other erroneous inputs don't throw an exception but return false, e.g. string shorter than expected or
        /// value is not a hex number. This is to prevent an exception in case of a malformed message.
        /// </remarks>
        protected bool ReadFromHexString(string input, int start, int length, bool inverseEndianness, out int value)
        {
            if (length != 2 && length != 4 && length != 8)
            {
                throw new ArgumentException("Length must be 2, 4, or 8", nameof(length));
            }
 
            if (input.Length < start + length)
            {
                value = 0;
                return false;
            }
 
            // length is given in characters here, not in bytes
            string part = input.Substring(start, length);
 
            if (!UInt32.TryParse(part, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out UInt32 result))
            {
                value = 0;
                return false;
            }
 
            if (length == 8 && inverseEndianness)
            {
                result = InverseEndianness(result);
            }
            else if (length == 4 && inverseEndianness)
            {
                result = result >> 8 | ((result & 0xFF) << 8);
            }
 
            value = (int)result;
            return true;
        }
    }
}