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:
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