diff --git a/CHANGELOG.md b/CHANGELOG.md index 5abedea..a584147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Added DTM Sentence. +- Added GST Sentence. +- Added THS Sentence. +- Added TTM Sentence. +- Added VBW Sentence. +- Added VTG Sentence. + +## [v0.2.0-alpha] - 2021-09-11 +[v0.2.0-alpha](https://github.com/TensionDev/NMEA0183/releases/tag/v0.2.0-alpha) + ### Changed - Changed Namespace from TensionDev.NMEA0183 to TensionDev.Maritime.NMEA0183 to reflect possible expansion to other maritime implementations. +### Added +- Added Query Sentence. +- Added MWV Sentence. +- Added ZDA Sentence. + ## [v0.1.0-alpha] - 2021-09-10 [v0.1.0-alpha](https://github.com/TensionDev/NMEA0183/releases/tag/v0.1.0-alpha) diff --git a/NMEA0183/MWVSentence.cs b/NMEA0183/MWVSentence.cs new file mode 100644 index 0000000..09094e5 --- /dev/null +++ b/NMEA0183/MWVSentence.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TensionDev.Maritime.NMEA0183 +{ + /// + /// MWV - Wind Speed and Angle + /// + public class MWVSentence : NMEASentence + { + /// + /// Wind angle, 0 to 359 degrees + /// + public Decimal WindAngleDegrees { get; set; } + + /// + /// Reference, Relative or Theoretical + /// + public WindReferenceEnum WindReference { get; set; } + + /// + /// Wind speed + /// + public Decimal WindSpeed { get; set; } + + /// + /// Wind speed units, K/M/N + /// + public WindSpeedUnitsEnum WindSpeedUnits { get; set; } + + /// + /// Is the data valid? + /// + public Boolean IsDataValid { get; set; } + + public MWVSentence() + { + SentenceIdentifier = "MWV"; + WindReference = WindReferenceEnum.R; + WindSpeedUnits = WindSpeedUnitsEnum.N; + IsDataValid = false; + } + + public override String EncodeSentence() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.AppendFormat("${0}{1},", TalkerIdentifier.ToString(), SentenceIdentifier); + + stringBuilder.AppendFormat("{0},", WindAngleDegrees); + + stringBuilder.AppendFormat("{0},", WindReference.ToString()); + + stringBuilder.AppendFormat("{0},", WindSpeed); + + stringBuilder.AppendFormat("{0},", WindSpeedUnits.ToString()); + + if (IsDataValid) + stringBuilder.Append("A"); + else + stringBuilder.Append("V"); + + Byte checksum = CalculateChecksum(stringBuilder.ToString()); + + stringBuilder.AppendFormat("*{0}\r\n", checksum.ToString("X2")); + + return stringBuilder.ToString(); + } + + protected override void DecodeInternalSentence(String sentence) + { + DecodeTalker(sentence); + + String[] vs = sentence.Split(new char[] { ',', '*' }); + + // Wind angle + WindAngleDegrees = Decimal.Parse(vs[1]); + + // Wind reference + WindReference = (WindReferenceEnum)Enum.Parse(typeof(WindReferenceEnum), vs[2]); + + // Wind speed + WindSpeed = Decimal.Parse(vs[3]); + + // Wind speed units + WindSpeedUnits = (WindSpeedUnitsEnum)Enum.Parse(typeof(WindSpeedUnitsEnum), vs[4]); + + if (vs[5] == "A") + IsDataValid = true; + else + IsDataValid = false; + } + + public enum WindReferenceEnum + { + /// + /// Relative + /// + R, + /// + /// Theoretical + /// + T, + } + + public enum WindSpeedUnitsEnum + { + /// + /// km/h - Kilometres per Hour + /// + K, + /// + /// m/s - Metres per Second + /// + M, + /// + /// Knots - Nautical Mile per Hour + /// + N, + } + } +} diff --git a/NMEA0183/NMEA0183.csproj b/NMEA0183/NMEA0183.csproj index 94c1565..f1c5eaa 100644 --- a/NMEA0183/NMEA0183.csproj +++ b/NMEA0183/NMEA0183.csproj @@ -7,7 +7,7 @@ true true TensionDev.Maritime.NMEA0183 - 0.1.0-alpha + 0.2.0-alpha TensionDev amsga TensionDev TensionDev.Maritime.NMEA0183 @@ -20,8 +20,8 @@ NMEA0183 Initial project release en-SG - 0.1.0.0 - 0.1.0.0 + 0.2.0.0 + 0.2.0.0 true snupkg diff --git a/NMEA0183/NMEASentence.cs b/NMEA0183/NMEASentence.cs index e3f3e76..bc3da9c 100644 --- a/NMEA0183/NMEASentence.cs +++ b/NMEA0183/NMEASentence.cs @@ -46,6 +46,14 @@ public static NMEASentence DecodeSentence(String sentence) nmeaSentence = new HDTSentence(); break; + case "MWV": + nmeaSentence = new MWVSentence(); + break; + + case "ZDA": + nmeaSentence = new ZDASentence(); + break; + default: throw new NotImplementedException("Sentence Identifier not recognised."); } diff --git a/NMEA0183/QuerySentence.cs b/NMEA0183/QuerySentence.cs new file mode 100644 index 0000000..e94a4e8 --- /dev/null +++ b/NMEA0183/QuerySentence.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TensionDev.Maritime.NMEA0183 +{ + /// + /// Q - Query Sentence + /// + public class QuerySentence : NMEASentence + { + /// + /// Listener Identifier + /// + public TalkerIdentifier ListenerIdentifier { get; set; } + + /// + /// Sentence Identifier + /// + public String QuerySentenceIdentifier { get; set; } + + public QuerySentence() + { + SentenceIdentifier = "Q"; + } + + public override String EncodeSentence() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.AppendFormat("${0}{1}{2},", TalkerIdentifier.ToString(), ListenerIdentifier.ToString(), SentenceIdentifier); + + stringBuilder.AppendFormat("{0}", QuerySentenceIdentifier); + + Byte checksum = CalculateChecksum(stringBuilder.ToString()); + + stringBuilder.AppendFormat("*{0}\r\n", checksum.ToString("X2")); + + return stringBuilder.ToString(); + } + + protected override void DecodeInternalSentence(String sentence) + { + DecodeTalker(sentence); + + String[] vs = sentence.Split(new char[] { ',', '*' }); + + ListenerIdentifier = TalkerIdentifier.FromString(vs[0].Substring(3, 2)); + + QuerySentenceIdentifier = vs[1]; + } + } +} diff --git a/NMEA0183/ZDASentence.cs b/NMEA0183/ZDASentence.cs new file mode 100644 index 0000000..029b993 --- /dev/null +++ b/NMEA0183/ZDASentence.cs @@ -0,0 +1,79 @@ +using System; +using System.Text; + +namespace TensionDev.Maritime.NMEA0183 +{ + /// + /// ZDA - Time & Date + /// + public class ZDASentence : NMEASentence + { + /// + /// UTC Date and Time At Position + /// + public DateTime UTCDateTimeAtPosition { get; set; } + + /// + /// Local Time Zone + /// + public TimeSpan LocalTimeZone { get; set; } + + protected TimeSpan DifferenceToUtc { get { return TimeSpan.Zero - LocalTimeZone; } set { LocalTimeZone = TimeSpan.Zero - value; } } + + public ZDASentence() + { + SentenceIdentifier = "ZDA"; + UTCDateTimeAtPosition = DateTime.UtcNow; + LocalTimeZone = TimeZoneInfo.Local.BaseUtcOffset; + } + + public override String EncodeSentence() + { + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.AppendFormat("${0}{1},", TalkerIdentifier.ToString(), SentenceIdentifier); + + stringBuilder.AppendFormat("{0},", UTCDateTimeAtPosition.ToString("HHmmss.FF")); + + stringBuilder.AppendFormat("{0},", UTCDateTimeAtPosition.ToString("dd")); + + stringBuilder.AppendFormat("{0},", UTCDateTimeAtPosition.ToString("MM")); + + stringBuilder.AppendFormat("{0},", UTCDateTimeAtPosition.ToString("yyyy")); + + if (DifferenceToUtc < TimeSpan.Zero) + stringBuilder.Append("-"); + stringBuilder.AppendFormat("{0},{1}", DifferenceToUtc.ToString("hh"), DifferenceToUtc.ToString("mm")); + + Byte checksum = CalculateChecksum(stringBuilder.ToString()); + + stringBuilder.AppendFormat("*{0}\r\n", checksum.ToString("X2")); + + return stringBuilder.ToString(); + } + + protected override void DecodeInternalSentence(String sentence) + { + DecodeTalker(sentence); + + String[] vs = sentence.Split(new char[] { ',', '*' }); + + // UTC Time + String time = vs[1]; + time = time.Insert(4, ":"); + time = time.Insert(2, ":"); + TimeSpan timeSpan = TimeSpan.Parse(time); + + // UTC Date + Int32 day = Int32.Parse(vs[2]); + Int32 month = Int32.Parse(vs[3]); + Int32 year = Int32.Parse(vs[4]); + + UTCDateTimeAtPosition = new DateTime(year, month, day, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds, timeSpan.Milliseconds, DateTimeKind.Utc); + + StringBuilder UTCOffset = new StringBuilder(); + UTCOffset.AppendFormat("{0}:{1}", vs[5], vs[6]); + DifferenceToUtc = TimeSpan.Parse(UTCOffset.ToString()); + } + } +} diff --git a/XUnitTestProjectNMEA0183/UnitTestMWVSentence.cs b/XUnitTestProjectNMEA0183/UnitTestMWVSentence.cs new file mode 100644 index 0000000..6c796c9 --- /dev/null +++ b/XUnitTestProjectNMEA0183/UnitTestMWVSentence.cs @@ -0,0 +1,27 @@ +using System; +using TensionDev.Maritime.NMEA0183; +using Xunit; + +namespace XUnitTestProjectNMEA0183 +{ + public class UnitTestMWVSentence + { + [Fact] + public void MWVDecoding() + { + String sentence = "$WIMWV,214.8,R,0.1,K,A*28"; + + NMEASentence nmeaSentence = NMEASentence.DecodeSentence(sentence); + MWVSentence mwvSentence = nmeaSentence as MWVSentence; + + Assert.NotNull(nmeaSentence); + Assert.NotNull(mwvSentence); + Assert.Equal(TalkerIdentifierEnum.WeatherInstruments, mwvSentence.TalkerIdentifier.TalkerIdentifierEnum); + Assert.Equal(214.8M, mwvSentence.WindAngleDegrees); + Assert.Equal(MWVSentence.WindReferenceEnum.R, mwvSentence.WindReference); + Assert.Equal(0.1M, mwvSentence.WindSpeed); + Assert.Equal(MWVSentence.WindSpeedUnitsEnum.K, mwvSentence.WindSpeedUnits); + Assert.True(mwvSentence.IsDataValid); + } + } +} diff --git a/XUnitTestProjectNMEA0183/UnitTestQuerySentence.cs b/XUnitTestProjectNMEA0183/UnitTestQuerySentence.cs new file mode 100644 index 0000000..6ce37c1 --- /dev/null +++ b/XUnitTestProjectNMEA0183/UnitTestQuerySentence.cs @@ -0,0 +1,26 @@ +using System; +using TensionDev.Maritime.NMEA0183; +using Xunit; + +namespace XUnitTestProjectNMEA0183 +{ + public class UnitTestQuerySentence + { + [Fact] + public void QueryEncoding() + { + String expectedSentence = "$ECGPQ,GGA*2D\r\n"; + + QuerySentence querySentence = new QuerySentence() + { + TalkerIdentifier = new TalkerIdentifier() { TalkerIdentifierEnum = TalkerIdentifierEnum.ElectronicChartDisplayInformationSystem }, + ListenerIdentifier = new TalkerIdentifier() { TalkerIdentifierEnum = TalkerIdentifierEnum.GlobalPositioningSystem }, + QuerySentenceIdentifier = "GGA" + }; + + String sentence = querySentence.EncodeSentence(); + + Assert.Equal(expectedSentence, sentence); + } + } +} diff --git a/XUnitTestProjectNMEA0183/UnitTestZDASentence.cs b/XUnitTestProjectNMEA0183/UnitTestZDASentence.cs new file mode 100644 index 0000000..1cfecb4 --- /dev/null +++ b/XUnitTestProjectNMEA0183/UnitTestZDASentence.cs @@ -0,0 +1,26 @@ +using System; +using TensionDev.Maritime.NMEA0183; +using Xunit; + +namespace XUnitTestProjectNMEA0183 +{ + public class UnitTestZDASentence + { + [Fact] + public void ZDADecoding() + { + String sentence = "$GPZDA,160012.71,11,03,2004,-1,00*7D"; + DateTime dateTimeUTC = new DateTime(2004, 03, 11, 16, 00, 12, 710, DateTimeKind.Utc); + TimeSpan timeZone = new TimeSpan(1, 0, 0); + + NMEASentence nmeaSentence = NMEASentence.DecodeSentence(sentence); + ZDASentence zdaSentence = nmeaSentence as ZDASentence; + + Assert.NotNull(nmeaSentence); + Assert.NotNull(zdaSentence); + Assert.Equal(TalkerIdentifierEnum.GlobalPositioningSystem, zdaSentence.TalkerIdentifier.TalkerIdentifierEnum); + Assert.Equal(dateTimeUTC, zdaSentence.UTCDateTimeAtPosition); + Assert.Equal(timeZone, zdaSentence.LocalTimeZone); + } + } +}