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>
142 lines
5.7 KiB
C#
142 lines
5.7 KiB
C#
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);
|
|
}
|