️ evert PTNL,GGK and AML,SVM

This commit is contained in:
Joachim Spange 2025-02-04 14:11:35 +01:00
parent 531fe94d0d
commit eb9904e1df
4 changed files with 0 additions and 342 deletions

View file

@ -1,165 +0,0 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * 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.Globalization;
using System.Linq;
namespace NmeaParser.Messages
{
/// <summary>
/// PTNL,GGK: Time, position, position type, DOP
/// </summary>
/// <remarks>
/// <para>
/// 1.: UTC time of position fix, in hhmmss.ss format. Hours must be two numbers, so may be padded. For example, 7 is shown as 07.
/// 2.: UTC date of position fix, in mmddyy format. Day must be two numbers, so may be padded. For example, 8 is shown as 08.
/// 3.: Latitude, in degrees and decimal minutes (dddmm.mmmmmmm)
/// 4.: Direction of latitude: N: North S: South
/// 5.: Longitude, in degrees and decimal minutes (dddmm.mmmmmmm). Should contain three digits of ddd.
/// 6.: Direction of longitude: E: East W: West
/// 7.: GPS Quality indicator:
/// 0: Fix not available or invalid
/// 1: Autonomous GPS fix
/// 2: RTK float solution
/// 3: RTK fix solution
/// 4: Differential, code phase only solution(DGPS)
/// 5: SBAS solution WAAS/EGNOS/MSAS
/// 6: RTK float or RTK location 3D Network solution
/// 7: RTK fixed 3D Network solution
/// 8: RTK float or RTK location 2D in a Network solution
/// 9: RTK fixed 2D Network solution
/// 10: OmniSTAR HP / XP solution
/// 11: OmniSTAR VBS solution
/// 12: Location RTK solution
/// 13: Beacon DGPS
/// 14: CenterPoint RTX
/// 15: xFill
/// 8.: Number of satellites in fix
/// 9.: Dilution of Precision of fix (DOP)
/// 10.: Ellipsoidal height of fix (antenna height above ellipsoid).
/// 11.: M: ellipsoidal height is measured in meters
/// </para>
/// <para>
/// NOTE The PTNL,GGK message is longer than the NMEA-0183 standard of 80 characters
/// NOTE Even if a user-defined geoid model, or an inclined plane is loaded into the receiver, then the height output in the NMEA GGK string is always an ellipsoid height, for example, EHT24.123.
/// </para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ggk")]
[NmeaMessageType("GGK")]
public class Ggk : NmeaMessage
{
/// <summary>
/// Initializes a new instance of the <see cref="Ggk"/> class.
/// </summary>
/// <param name="type">The message type</param>
/// <param name="message">The NMEA message values.</param>
public Ggk(string type, string[] message) : base(type, message)
{
if (message == null || message.Length < 11)
throw new ArgumentException("Invalid Ggk", "message");
UtcTime = StringsToUtcDateTime(message[1], message[0]);
Latitude = StringToLatitude(message[2], message[3]);
Longitude = StringToLongitude(message[4], message[5]);
if (!string.IsNullOrEmpty(message[6]))
Quality = (Ggk.QualityIndicator)int.Parse(message[6], CultureInfo.InvariantCulture);
if (!string.IsNullOrEmpty(message[7]))
NumberOfSatellites = int.Parse(message[7], CultureInfo.InvariantCulture);
DilutionOfPrecision = StringToDouble(message[8]);
EllipsoidalHeightOfFix = StringToDouble(message[9]);
EllipsoidalHeightIsMeasuredInMeters = message[10] == "M";
}
/// <summary>
/// UTC time of position fix, in DateTime format.
/// DateTime(fullYear, month, day, hours, minutes, seconds, milliseconds, DateTimeKind.Utc)
/// </summary>
public DateTime? UtcTime { get; }
/// <summary>
/// Latitude, in degrees and decimal minutes (dddmm.mmmmmmm)
/// </summary>
public double Latitude { get; }
/// <summary>
/// Longitude, in degrees and decimal minutes (dddmm.mmmmmmm)
/// </summary>
public double Longitude { get; }
/// <summary>
/// GPS Quality indicator
/// </summary>
public Ggk.QualityIndicator Quality { get; }
/// <summary>
/// Number of satellites in fix
/// </summary>
public int NumberOfSatellites { get; }
/// <summary>
/// Dilution of Precision of fix (DOP)
/// </summary>
public double DilutionOfPrecision { get; }
/// <summary>
/// Ellipsoidal height of fix (antenna height above ellipsoid). Must start with EHT.
/// </summary>
public double EllipsoidalHeightOfFix { get; }
/// <summary>
/// M: ellipsoidal height is measured in meters
/// </summary>
public bool EllipsoidalHeightIsMeasuredInMeters { get; }
/// <summary>
/// GPS Quality indicator
/// </summary>
public enum QualityIndicator : int
{
/// <summary>Fix not available or invalid</summary>
Invalid = 0,
/// <summary>Autonomous GPS fix</summary>
GpsFix = 1,
/// <summary>RTK float solution</summary>
RtkFloat = 2,
/// <summary>RTK fix solution</summary>
RtkFix = 3,
/// <summary>Differential, code phase only solution(DGPS)</summary>
Ggps = 4,
/// <summary>SBAS solution WAAS/EGNOS/MSAS</summary>
Sbas = 5,
/// <summary>RTK float or RTK location 3D Network solution</summary>
RtkFloatOrLocation3DNetworkSolution = 6,
/// <summary>RTK fixed 3D Network solution</summary>
RtkFixed3DNetworkSolution = 7,
/// <summary>RTK float or RTK location 2D in a Network solution</summary>
RtkFloatOrLocation2DNetworkSolution = 8,
/// <summary>RTK fixed 2D Network solution</summary>
RtkFixed2DNetworkSolution = 9,
/// <summary>OmniSTAR HP / XP solution</summary>
OmistarHpXp = 10,
/// <summary>OmniSTAR VBS solution</summary>
OmniStarVbs = 11,
/// <summary>Location RTK solution</summary>
LocationRtk = 12,
/// <summary>Beacon DGPS</summary>
BeaconDgps = 13,
/// <summary>CenterPoint RTX</summary>
CenterPointRtx = 14,
/// <summary>xFill</summary>
XFill = 15,
}
}
}

View file

@ -188,12 +188,6 @@ namespace NmeaParser.Messages
string[] parts = message.Split(new char[] { ',' });
string MessageType = parts[0].Substring(1);
if (MessageType is "PTNL" or "AML") {
// PTNL is parent to e.g. AVR, GGK etc.
// AML is parent to e.g. SVT, SV, SVP etc
MessageType = parts[1];
parts = parts.Skip(1).ToArray();
}
if (MessageType == string.Empty)
throw new ArgumentException("Missing NMEA Message Type");
string[] MessageParts = parts.Skip(1).ToArray();
@ -310,68 +304,6 @@ namespace NmeaParser.Messages
return TimeSpan.Zero;
}
/// <summary>
/// Parse from mmddyy + hhmmss.ss to DateTime (used in e.g. Ggk)
/// https://receiverhelp.trimble.com/alloy-gnss/en-us/NMEA-0183messages_PTNL_GGK.html
/// </summary>
internal static DateTime? StringsToUtcDateTime(string dateField, string timeField)
{
if (string.IsNullOrWhiteSpace(dateField) || string.IsNullOrWhiteSpace(timeField))
return null;
// 2. Parse date (mmddyy)
// - Typically the year is 2 digits; you must decide how to handle the century.
// - For example, if year < 80 => 20xx; else => 19xx
if (dateField.Length < 6) return null;
if (!int.TryParse(dateField.Substring(0, 2), out int month)) return null; // mm
if (!int.TryParse(dateField.Substring(2, 2), out int day)) return null; // dd
if (!int.TryParse(dateField.Substring(4, 2), out int year)) return null; // yy
int fullYear = (year < 80) ? (2000 + year) : (1900 + year);
// 3. Parse time (hhmmss.ss)
// - The fractional seconds might be optional or might have variable length.
// - Parse hours, minutes, and then the part after the decimal.
// - Time can be "hhmmss", or "hhmmss.sss", etc.
// Example: "102939.00" => hh=10, mm=29, ss=39, fraction=0
if (timeField.Length < 6) return null;
if (!int.TryParse(timeField.Substring(0, 2), out int hours)) return null; // hh
if (!int.TryParse(timeField.Substring(2, 2), out int minutes)) return null; // mm
// The seconds part can have a decimal fraction
// So we split around the decimal point:
double secondsDouble;
if (timeField.Length > 4)
{
string secString = timeField.Substring(4); // "39.00 in example above"
if (!double.TryParse(secString,
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out secondsDouble))
{
return null;
}
}
else
{
secondsDouble = 0.0;
}
int seconds = (int)secondsDouble;
int milliseconds = (int)((secondsDouble - seconds) * 1000.0);
// 4. Construct the UTC DateTime
try
{
var result = new DateTime(fullYear, month, day, hours, minutes, seconds, milliseconds, DateTimeKind.Utc);
return result;
}
catch
{
// If the date/time is invalid (e.g. month=13, day=32), we return null or handle differently
return null;
}
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>

View file

@ -1,76 +0,0 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * 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;
namespace NmeaParser.Messages
{
/// <summary>
/// AML Sound Velocity (m/s), Temperature (C)
/// AML Oceanographic Svm sensor
/// </summary>
/// <remarks>
/// <para>
/// 1.: Sound Velocity m/s
/// 2.: Unit of temperature, TC = Celcius
/// 3.: Temperature
/// 4.: Label, SN: Serial number
/// 5.: Device serial number
/// </para>
/// </remarks>
[NmeaMessageType("SVM")]
public class Svm : NmeaMessage
{
/// <summary>
/// Initializes a new instance of the <see cref="Svm"/> class.
/// </summary>
/// <param name="type">The message type</param>
/// <param name="message">The NMEA message values.</param>
public Svm(string type, string[] message) : base(type, message)
{
if (message == null || message.Length < 5)
throw new ArgumentException("Invalid Svm", "message");
SoundVelocity = StringToDouble(message[0]);
TemperatureUnit = message[1];
Temperature = StringToDouble(message[2]);
IsSerialNumber = message[3] == "SN";
SerialNumber = StringToDouble(message[4]);
}
/// <summary>
/// Sound Velocity m/s
/// </summary>
public double SoundVelocity { get; }
/// <summary>
/// Temperature Unit
/// </summary>
public string TemperatureUnit { get; }
/// <summary>
/// Temperature
/// </summary>
public double Temperature { get; }
/// <summary>
/// String indicating Serial number
/// </summary>
public bool IsSerialNumber { get; }
/// <summary>
/// Serial Number
/// </summary>
public double SerialNumber { get; }
}
}

View file

@ -85,8 +85,6 @@ namespace NmeaParser.Tests
{
var msg = NmeaMessage.Parse(line, previousMessage as IMultiSentenceMessage);
Assert.IsNotNull(msg);
if (line.IndexOf("PTNL,") > 0) continue; // TODO PTNL
if (line.IndexOf("AML,") > 0) continue; // TODO AML
var idx = line.IndexOf('*');
if (idx >= 0)
{
@ -126,20 +124,6 @@ namespace NmeaParser.Tests
Assert.ThrowsException<ArgumentException>(() => NmeaMessage.Parse(input, ignoreChecksum: false));
}
[TestMethod]
public void TestAmlSvm()
{
string input = "$AML,SVM,1468.951,TC,15.753,SN,168753*0F";
var msg = NmeaMessage.Parse(input);
Assert.IsInstanceOfType(msg, typeof(Svm));
Svm svm = (Svm)msg;
Assert.AreEqual(1468.951, svm.SoundVelocity);
Assert.AreEqual("TC", svm.TemperatureUnit);
Assert.AreEqual(15.753, svm.Temperature);
Assert.IsTrue(svm.IsSerialNumber);
Assert.AreEqual(168753, svm.SerialNumber);
}
[TestMethod]
public void TestDPT()
{
@ -366,23 +350,6 @@ namespace NmeaParser.Tests
Assert.AreEqual('M', ptlna.SlopeDistanceUnits);
}
[TestMethod]
public void TestPtnlGgk()
{
string input = "$PTNL,GGK,133703.14,012925,6104.64373420,N,01027.91199999,E,2,08,1.0,0.0,M,*0B";
var msg = NmeaMessage.Parse(input);
Assert.IsInstanceOfType(msg, typeof(Ggk));
Ggk ggk = (Ggk)msg;
Assert.AreEqual(new DateTime(2025, 1, 29, 13, 37, 3, 140, DateTimeKind.Utc), ggk.UtcTime);
Assert.AreEqual(61.07739557, ggk.Latitude);
Assert.AreEqual(10.465199999833333, ggk.Longitude);
Assert.AreEqual(Ggk.QualityIndicator.RtkFloat, ggk.Quality);
Assert.AreEqual(8, ggk.NumberOfSatellites);
Assert.AreEqual(1, ggk.DilutionOfPrecision);
Assert.AreEqual(0, ggk.EllipsoidalHeightOfFix);
Assert.IsTrue(ggk.EllipsoidalHeightIsMeasuredInMeters);
}
[TestMethod]
public void TestPgrme()
{