Initial commit: FieldLogger MAUI app with Maglink BLE support
Added Maglink RTK GNSS receiver integration with correct BLE UUIDs and device name filtering (ML-* prefix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
11
FieldLogger/Models/DeviceKind.cs
Normal file
11
FieldLogger/Models/DeviceKind.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
/// <summary>The two kinds of BLE devices the app pairs with.</summary>
|
||||
public enum DeviceKind
|
||||
{
|
||||
/// <summary>Underground Magnetics utility locating receiver (UMRX_* / DT100_*).</summary>
|
||||
Locator,
|
||||
|
||||
/// <summary>Maglink RTK GNSS receiver (H11).</summary>
|
||||
RtkGps,
|
||||
}
|
||||
155
FieldLogger/Models/GnssFix.cs
Normal file
155
FieldLogger/Models/GnssFix.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
public enum GnssFixStatus { NoFix = 0, Single = 1, Dgps = 2, RtkFixed = 4, RtkFloat = 5 }
|
||||
|
||||
/// <summary>
|
||||
/// A parsed $GNPOS sentence from the Maglink (H11) RTK receiver.
|
||||
/// </summary>
|
||||
public sealed class GnssFix
|
||||
{
|
||||
public double Latitude { get; init; }
|
||||
public double Longitude { get; init; }
|
||||
public double Altitude { get; init; }
|
||||
public double AltitudeCorrected { get; init; }
|
||||
public GnssFixStatus Status { get; init; }
|
||||
public double Hdop { get; init; }
|
||||
public double Hrms { get; init; }
|
||||
public double Vrms { get; init; }
|
||||
public int SatellitesUsed { get; init; }
|
||||
public int SatellitesVisible { get; init; }
|
||||
public double SpeedKmh { get; init; }
|
||||
public double Heading { get; init; }
|
||||
public double BatteryVoltage { get; init; }
|
||||
public int BatteryPercent { get; init; }
|
||||
public bool NtripConnected { get; init; }
|
||||
public int RtcmSize { get; init; }
|
||||
public double CorrectionAgeSeconds { get; init; }
|
||||
public long UnixTimestamp { get; init; }
|
||||
public double TiltAngle { get; init; }
|
||||
public string RawSentence { get; init; } = "";
|
||||
|
||||
/// <summary>Local UTC time this fix was received by the app.</summary>
|
||||
public DateTime ReceivedUtc { get; init; } = DateTime.UtcNow;
|
||||
|
||||
public bool HasPosition => Status != GnssFixStatus.NoFix;
|
||||
|
||||
public string StatusLabel => Status switch
|
||||
{
|
||||
GnssFixStatus.NoFix => "No Fix",
|
||||
GnssFixStatus.Single => "Single",
|
||||
GnssFixStatus.Dgps => "DGPS",
|
||||
GnssFixStatus.RtkFixed => "RTK Fixed",
|
||||
GnssFixStatus.RtkFloat => "RTK Float",
|
||||
_ => Status.ToString(),
|
||||
};
|
||||
|
||||
/// <summary>Parses a "$GNPOS,..." sentence (checksum must already be validated).</summary>
|
||||
public static GnssFix? TryParse(string sentence)
|
||||
{
|
||||
var body = NmeaSentence.Body(sentence);
|
||||
var f = body.Split(',');
|
||||
if (f.Length < 20 || f[0] != "GNPOS")
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return new GnssFix
|
||||
{
|
||||
Latitude = D(f[1]),
|
||||
Longitude = D(f[2]),
|
||||
Altitude = D(f[3]),
|
||||
AltitudeCorrected = D(f[4]),
|
||||
Status = (GnssFixStatus)I(f[5]),
|
||||
Hdop = D(f[6]),
|
||||
Hrms = D(f[7]),
|
||||
Vrms = D(f[8]),
|
||||
SatellitesUsed = I(f[9]),
|
||||
SatellitesVisible = I(f[10]),
|
||||
SpeedKmh = D(f[11]),
|
||||
Heading = D(f[12]),
|
||||
BatteryVoltage = D(f[13]),
|
||||
BatteryPercent = I(f[14]),
|
||||
NtripConnected = I(f[15]) != 0,
|
||||
RtcmSize = I(f[16]),
|
||||
CorrectionAgeSeconds = D(f[17]),
|
||||
UnixTimestamp = long.Parse(f[18], CultureInfo.InvariantCulture),
|
||||
TiltAngle = D(f[19]),
|
||||
RawSentence = sentence.Trim(),
|
||||
};
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static double D(string s) => double.Parse(s, NumberStyles.Float, CultureInfo.InvariantCulture);
|
||||
private static int I(string s) => int.Parse(s, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parsed $GNDEV device-information sentence from the Maglink receiver.
|
||||
/// </summary>
|
||||
public sealed class GnssDeviceInfo
|
||||
{
|
||||
public string SerialNumber { get; init; } = "";
|
||||
public string PcbVersion { get; init; } = "";
|
||||
public string FirmwareVersion { get; init; } = "";
|
||||
public string Imei { get; init; } = "";
|
||||
public string Imsi { get; init; } = "";
|
||||
public string Iccid { get; init; } = "";
|
||||
|
||||
public static GnssDeviceInfo? TryParse(string sentence)
|
||||
{
|
||||
var f = NmeaSentence.Body(sentence).Split(',');
|
||||
if (f.Length < 7 || f[0] != "GNDEV")
|
||||
return null;
|
||||
|
||||
return new GnssDeviceInfo
|
||||
{
|
||||
SerialNumber = f[1],
|
||||
PcbVersion = f[2],
|
||||
FirmwareVersion = f[3],
|
||||
Imei = f[4],
|
||||
Imsi = f[5],
|
||||
Iccid = f[6],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>NMEA framing helpers ($...*hh checksum).</summary>
|
||||
public static class NmeaSentence
|
||||
{
|
||||
/// <summary>Returns the sentence body between '$' and '*' (or end of string).</summary>
|
||||
public static string Body(string sentence)
|
||||
{
|
||||
var s = sentence.Trim().TrimStart('$');
|
||||
var star = s.IndexOf('*');
|
||||
return star >= 0 ? s[..star] : s;
|
||||
}
|
||||
|
||||
public static byte Checksum(string sentence)
|
||||
{
|
||||
byte checksum = 0;
|
||||
foreach (var c in Body(sentence))
|
||||
checksum ^= (byte)c;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>Validates the trailing *hh checksum. Sentences without a checksum fail validation.</summary>
|
||||
public static bool VerifyChecksum(string sentence)
|
||||
{
|
||||
var star = sentence.LastIndexOf('*');
|
||||
if (star < 0 || star + 3 > sentence.TrimEnd().Length)
|
||||
return false;
|
||||
|
||||
var hex = sentence.Substring(star + 1, 2);
|
||||
if (!byte.TryParse(hex, System.Globalization.NumberStyles.HexNumber,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var expected))
|
||||
return false;
|
||||
|
||||
return Checksum(sentence) == expected;
|
||||
}
|
||||
}
|
||||
23
FieldLogger/Models/Job.cs
Normal file
23
FieldLogger/Models/Job.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using SQLite;
|
||||
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
[Table("jobs")]
|
||||
public sealed class Job
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
public string Notes { get; set; } = "";
|
||||
|
||||
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Reserved for MQTT sync: server-side job identifier once provisioned remotely.</summary>
|
||||
public string? RemoteId { get; set; }
|
||||
|
||||
/// <summary>Reserved for MQTT sync: true once all points have been pushed to the server.</summary>
|
||||
public bool Synced { get; set; }
|
||||
}
|
||||
145
FieldLogger/Models/LoggedPoint.cs
Normal file
145
FieldLogger/Models/LoggedPoint.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using SQLite;
|
||||
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
/// <summary>
|
||||
/// One logged locate point: the full UM receiver PBDL packet joined with the
|
||||
/// GNSS fix that was current when the operator pressed the log button.
|
||||
/// </summary>
|
||||
[Table("points")]
|
||||
public sealed class LoggedPoint
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Indexed]
|
||||
public int JobId { get; set; }
|
||||
|
||||
public DateTime TimestampUtc { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// ---- UM locating receiver (PBDL schema 1) ----
|
||||
public int Mode { get; set; }
|
||||
public int Frequency { get; set; }
|
||||
public int FreqType { get; set; }
|
||||
public int Signal { get; set; }
|
||||
public int GainDb { get; set; }
|
||||
public string DepthRaw { get; set; } = "";
|
||||
public double? DepthMeters { get; set; }
|
||||
public string CurrentRaw { get; set; } = "";
|
||||
public double? CurrentMilliamps { get; set; }
|
||||
public int CompassAngle { get; set; }
|
||||
public int GuidanceArrows { get; set; }
|
||||
public int LdPhase { get; set; }
|
||||
public bool Clipping { get; set; }
|
||||
public int DepthCurrentSetting { get; set; }
|
||||
public int LrArrowStyle { get; set; }
|
||||
public int AudioVolume { get; set; }
|
||||
public int AudioModulation { get; set; }
|
||||
public int AudioSound { get; set; }
|
||||
public double LocatorBatteryVoltage { get; set; }
|
||||
public int LocatorBatteryType { get; set; }
|
||||
public int Backlight { get; set; }
|
||||
public int Utility { get; set; }
|
||||
public bool MenuInUse { get; set; }
|
||||
public string LocatorRawPacket { get; set; } = "";
|
||||
public string LocatorSerialNumber { get; set; } = "";
|
||||
public string LocatorModel { get; set; } = "";
|
||||
|
||||
// ---- GNSS fix (Maglink $GNPOS) ----
|
||||
/// <summary>False when no fresh GNSS fix was available at log time; position fields are then unset.</summary>
|
||||
public bool GpsValid { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public double Altitude { get; set; }
|
||||
public double AltitudeCorrected { get; set; }
|
||||
public int FixStatus { get; set; }
|
||||
public double Hdop { get; set; }
|
||||
public double Hrms { get; set; }
|
||||
public double Vrms { get; set; }
|
||||
public int SatellitesUsed { get; set; }
|
||||
public int SatellitesVisible { get; set; }
|
||||
public double SpeedKmh { get; set; }
|
||||
public double HeadingDegrees { get; set; }
|
||||
public bool NtripConnected { get; set; }
|
||||
public double CorrectionAgeSeconds { get; set; }
|
||||
public long GpsUnixTimestamp { get; set; }
|
||||
public double TiltAngle { get; set; }
|
||||
public double GpsBatteryVoltage { get; set; }
|
||||
public int GpsBatteryPercent { get; set; }
|
||||
public string GpsRawSentence { get; set; } = "";
|
||||
public string GpsSerialNumber { get; set; } = "";
|
||||
|
||||
/// <summary>Reserved for MQTT sync.</summary>
|
||||
public bool Synced { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public GnssFixStatus FixStatusEnum => (GnssFixStatus)FixStatus;
|
||||
|
||||
[Ignore]
|
||||
public UmUtility UtilityEnum => (UmUtility)Utility;
|
||||
|
||||
public static LoggedPoint From(int jobId, UmLogPacket packet, UmDeviceInfo? locatorInfo,
|
||||
GnssFix? fix, GnssDeviceInfo? gpsInfo)
|
||||
{
|
||||
var p = new LoggedPoint
|
||||
{
|
||||
JobId = jobId,
|
||||
TimestampUtc = DateTime.UtcNow,
|
||||
|
||||
Mode = (int)packet.Mode,
|
||||
Frequency = packet.Frequency,
|
||||
FreqType = (int)packet.FreqType,
|
||||
Signal = packet.Signal,
|
||||
GainDb = packet.GainDb,
|
||||
DepthRaw = packet.DepthRaw,
|
||||
DepthMeters = packet.DepthMeters,
|
||||
CurrentRaw = packet.CurrentRaw,
|
||||
CurrentMilliamps = packet.CurrentMilliamps,
|
||||
CompassAngle = packet.CompassAngle,
|
||||
GuidanceArrows = packet.GuidanceArrows,
|
||||
LdPhase = packet.LdPhase,
|
||||
Clipping = packet.Clipping,
|
||||
DepthCurrentSetting = packet.DepthCurrentSetting,
|
||||
LrArrowStyle = packet.LrArrowStyle,
|
||||
AudioVolume = packet.AudioVolume,
|
||||
AudioModulation = packet.AudioModulation,
|
||||
AudioSound = packet.AudioSound,
|
||||
LocatorBatteryVoltage = packet.BatteryVoltage,
|
||||
LocatorBatteryType = (int)packet.BatteryType,
|
||||
Backlight = packet.Backlight,
|
||||
Utility = (int)packet.Utility,
|
||||
MenuInUse = packet.MenuInUse,
|
||||
LocatorRawPacket = packet.RawText,
|
||||
LocatorSerialNumber = locatorInfo?.SerialNumber ?? "",
|
||||
LocatorModel = locatorInfo?.ModelName ?? "",
|
||||
|
||||
GpsSerialNumber = gpsInfo?.SerialNumber ?? "",
|
||||
};
|
||||
|
||||
if (fix is not null)
|
||||
{
|
||||
p.GpsValid = fix.HasPosition;
|
||||
p.Latitude = fix.Latitude;
|
||||
p.Longitude = fix.Longitude;
|
||||
p.Altitude = fix.Altitude;
|
||||
p.AltitudeCorrected = fix.AltitudeCorrected;
|
||||
p.FixStatus = (int)fix.Status;
|
||||
p.Hdop = fix.Hdop;
|
||||
p.Hrms = fix.Hrms;
|
||||
p.Vrms = fix.Vrms;
|
||||
p.SatellitesUsed = fix.SatellitesUsed;
|
||||
p.SatellitesVisible = fix.SatellitesVisible;
|
||||
p.SpeedKmh = fix.SpeedKmh;
|
||||
p.HeadingDegrees = fix.Heading;
|
||||
p.NtripConnected = fix.NtripConnected;
|
||||
p.CorrectionAgeSeconds = fix.CorrectionAgeSeconds;
|
||||
p.GpsUnixTimestamp = fix.UnixTimestamp;
|
||||
p.TiltAngle = fix.TiltAngle;
|
||||
p.GpsBatteryVoltage = fix.BatteryVoltage;
|
||||
p.GpsBatteryPercent = fix.BatteryPercent;
|
||||
p.GpsRawSentence = fix.RawSentence;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
51
FieldLogger/Models/UmDeviceInfo.cs
Normal file
51
FieldLogger/Models/UmDeviceInfo.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Info string the UM receiver sends the first time push-button data logging is enabled.
|
||||
/// </summary>
|
||||
public sealed class UmDeviceInfo
|
||||
{
|
||||
public string Manufacturer { get; init; } = "";
|
||||
public string ModelName { get; init; } = "";
|
||||
public string SerialNumber { get; init; } = "";
|
||||
public string BootloaderVersion { get; init; } = "";
|
||||
public string SoftwareVersion { get; init; } = "";
|
||||
public string ManufactureDate { get; init; } = "";
|
||||
public string CalibrationDate { get; init; } = "";
|
||||
public double HourCount { get; init; }
|
||||
public int PbdlSchema { get; init; }
|
||||
public string RawText { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Parses the 10-field info string (9 parameters followed by "OK").
|
||||
/// Returns null if the line does not match.
|
||||
/// </summary>
|
||||
public static UmDeviceInfo? TryParse(string line)
|
||||
{
|
||||
var fields = line.Trim().Split(',');
|
||||
if (fields.Length < 10 || !fields[^1].Trim().Equals("OK", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
// Serial number is 9 digits; use it to distinguish from other OK-terminated responses.
|
||||
if (fields[2].Trim().Length < 6)
|
||||
return null;
|
||||
|
||||
double.TryParse(fields[7], System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var hours);
|
||||
int.TryParse(fields[8], out var schema);
|
||||
|
||||
return new UmDeviceInfo
|
||||
{
|
||||
Manufacturer = fields[0].Trim(),
|
||||
ModelName = fields[1].Trim(),
|
||||
SerialNumber = fields[2].Trim(),
|
||||
BootloaderVersion = fields[3].Trim(),
|
||||
SoftwareVersion = fields[4].Trim(),
|
||||
ManufactureDate = fields[5].Trim(),
|
||||
CalibrationDate = fields[6].Trim(),
|
||||
HourCount = hours,
|
||||
PbdlSchema = schema,
|
||||
RawText = line.Trim(),
|
||||
};
|
||||
}
|
||||
}
|
||||
141
FieldLogger/Models/UmLogPacket.cs
Normal file
141
FieldLogger/Models/UmLogPacket.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
public enum UmMode { Single = 0, Twin = 1, Null = 2, Sweep = 3, TwinSweep = 4, Omni = 5, TwinOmni = 6 }
|
||||
|
||||
public enum UmFreqType { Active = 0, LdActive = 1, Power = 2, GroupedPower = 3, Cathodic = 4, Sonde = 5, Radio = 6, FaultFind = 7 }
|
||||
|
||||
public enum UmUtility { None = 0, Gas = 1, Power = 2, Communications = 3, Water = 4, Sewer = 5, Fiber = 6, Other = 7 }
|
||||
|
||||
public enum UmBatteryType { NotMeasured = 0, Alkaline = 1, AlkalineError = 2, Lithium = 3 }
|
||||
|
||||
/// <summary>
|
||||
/// A single push-button data-log packet from the UM locating receiver
|
||||
/// (PBDL schema 1, 21 comma-delimited fields).
|
||||
/// </summary>
|
||||
public sealed class UmLogPacket
|
||||
{
|
||||
public UmMode Mode { get; init; }
|
||||
public int Frequency { get; init; }
|
||||
public UmFreqType FreqType { get; init; }
|
||||
public int Signal { get; init; } // 0 - 99
|
||||
public int GainDb { get; init; } // 0 - 145 dB
|
||||
public string DepthRaw { get; init; } = ""; // "X' Y\"" imperial, "X.Yym" metric, "- - -" no depth
|
||||
public string CurrentRaw { get; init; } = ""; // 0 - 999mA
|
||||
public int CompassAngle { get; init; } // 0 - 180 degrees (direction ambiguous)
|
||||
public int GuidanceArrows { get; init; } // -300 to 300, negative = left
|
||||
public int LdPhase { get; init; }
|
||||
public bool Clipping { get; init; }
|
||||
public int DepthCurrentSetting { get; init; } // 0 ON, 1 AUTO
|
||||
public int LrArrowStyle { get; init; } // 0 OFF, 1 Style 1, 2 Style 2
|
||||
public int AudioVolume { get; init; } // 0 - 3
|
||||
public int AudioModulation { get; init; } // 0 AM, 1 FM
|
||||
public int AudioSound { get; init; } // 0 Smooth, 1 Rough
|
||||
public double BatteryVoltage { get; init; }
|
||||
public UmBatteryType BatteryType { get; init; }
|
||||
public int Backlight { get; init; } // 0 Low, 1 Medium, 2 High
|
||||
public UmUtility Utility { get; init; }
|
||||
public bool MenuInUse { get; init; }
|
||||
public string RawText { get; init; } = "";
|
||||
|
||||
public const int FieldCount = 21;
|
||||
|
||||
/// <summary>Depth converted to meters, when the raw string can be interpreted; otherwise null.</summary>
|
||||
public double? DepthMeters => TryParseDepthMeters(DepthRaw);
|
||||
|
||||
/// <summary>Current in mA parsed from the raw string, when possible.</summary>
|
||||
public double? CurrentMilliamps
|
||||
{
|
||||
get
|
||||
{
|
||||
var s = CurrentRaw.Trim().TrimEnd('A', 'a').TrimEnd('m', 'M');
|
||||
return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var v) ? v : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static double? TryParseDepthMeters(string raw)
|
||||
{
|
||||
raw = raw.Trim();
|
||||
if (raw.Length == 0 || raw.Contains("- -"))
|
||||
return null;
|
||||
|
||||
// Metric: "X.Yym" e.g. "1.23m"
|
||||
if (raw.EndsWith("m", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var s = raw[..^1].Trim();
|
||||
if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var meters))
|
||||
return meters;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Imperial: feet' inches" e.g. "3' 7"" or "3' 7'"
|
||||
if (raw.Contains('\''))
|
||||
{
|
||||
var parts = raw.Split('\'', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length >= 1 &&
|
||||
double.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var feet))
|
||||
{
|
||||
double inches = 0;
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var inchStr = parts[1].TrimEnd('"');
|
||||
double.TryParse(inchStr, NumberStyles.Float, CultureInfo.InvariantCulture, out inches);
|
||||
}
|
||||
return (feet + inches / 12.0) * 0.3048;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a 21-field PBDL schema 1 packet. Returns null if the line does not look like a data packet.
|
||||
/// </summary>
|
||||
public static UmLogPacket? TryParse(string line)
|
||||
{
|
||||
var fields = line.Trim().Split(',');
|
||||
if (fields.Length < FieldCount)
|
||||
return null;
|
||||
|
||||
// First field must be a small integer (Mode 0-6) to qualify as a data packet.
|
||||
if (!int.TryParse(fields[0], out var mode) || mode is < 0 or > 6)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return new UmLogPacket
|
||||
{
|
||||
Mode = (UmMode)mode,
|
||||
Frequency = ParseInt(fields[1]),
|
||||
FreqType = (UmFreqType)ParseInt(fields[2]),
|
||||
Signal = ParseInt(fields[3]),
|
||||
GainDb = ParseInt(fields[4]),
|
||||
DepthRaw = fields[5].Trim(),
|
||||
CurrentRaw = fields[6].Trim(),
|
||||
CompassAngle = ParseInt(fields[7]),
|
||||
GuidanceArrows = ParseInt(fields[8]),
|
||||
LdPhase = ParseInt(fields[9]),
|
||||
Clipping = ParseInt(fields[10]) != 0,
|
||||
DepthCurrentSetting = ParseInt(fields[11]),
|
||||
LrArrowStyle = ParseInt(fields[12]),
|
||||
AudioVolume = ParseInt(fields[13]),
|
||||
AudioModulation = ParseInt(fields[14]),
|
||||
AudioSound = ParseInt(fields[15]),
|
||||
BatteryVoltage = ParseDouble(fields[16]),
|
||||
BatteryType = (UmBatteryType)ParseInt(fields[17]),
|
||||
Backlight = ParseInt(fields[18]),
|
||||
Utility = (UmUtility)ParseInt(fields[19]),
|
||||
MenuInUse = ParseInt(fields[20]) != 0,
|
||||
RawText = line.Trim(),
|
||||
};
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ParseInt(string s) => int.Parse(s.Trim(), CultureInfo.InvariantCulture);
|
||||
private static double ParseDouble(string s) => double.Parse(s.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture);
|
||||
}
|
||||
Reference in New Issue
Block a user