Add Debug page and fix BLE communication issues
Major changes: - Added Debug tab with real-time message logging and hex viewer - Implemented timed data logging ($UMTDL command) per API v1.3 - Fixed BLE message parsing to handle notifications without line endings - Added Schema 2 support (22-field timed logging packets) - Updated Maglink BLE UUIDs (fff0/fff1/fff2) - Added connection timeout (10s) to prevent hanging on unavailable devices - Added connection status display and manual push-button logging trigger - Improved retry logging with attempt numbers and delays - Added console window allocation for Windows debug output - Added BLE disconnect on app close Bug fixes: - Fixed device name filtering for Maglink (ML-* prefix) - Fixed RtkGps enum reference in DeviceScanViewModel - Fixed DebugMessage namespace (moved to Models) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
24
FieldLogger/Models/DebugMessage.cs
Normal file
24
FieldLogger/Models/DebugMessage.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace FieldLogger.Models;
|
||||
|
||||
public class DebugMessage
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Direction { get; set; } = "";
|
||||
public string RawMessage { get; set; } = "";
|
||||
public string Type { get; set; } = "";
|
||||
public string? ParsedData { get; set; }
|
||||
public string TypeColor { get; set; } = "Gray";
|
||||
public bool HasParsedData => !string.IsNullOrEmpty(ParsedData);
|
||||
|
||||
public string HexBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(RawMessage))
|
||||
return "[empty]";
|
||||
|
||||
var bytes = System.Text.Encoding.ASCII.GetBytes(RawMessage);
|
||||
return BitConverter.ToString(bytes).Replace("-", " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,16 @@ public enum UmUtility { None = 0, Gas = 1, Power = 2, Communications = 3, Water
|
||||
|
||||
public enum UmBatteryType { NotMeasured = 0, Alkaline = 1, AlkalineError = 2, Lithium = 3 }
|
||||
|
||||
public enum UmLogType { PushButton = 0, TimeBased = 1 }
|
||||
|
||||
/// <summary>
|
||||
/// A single push-button data-log packet from the UM locating receiver
|
||||
/// (PBDL schema 1, 21 comma-delimited fields).
|
||||
/// A single data-log packet from the UM locating receiver.
|
||||
/// Schema 1 (push-button): 21 fields
|
||||
/// Schema 2 (timed logging): 22 fields with LogType as first field
|
||||
/// </summary>
|
||||
public sealed class UmLogPacket
|
||||
{
|
||||
public UmLogType? LogType { get; init; } // Schema 2 only
|
||||
public UmMode Mode { get; init; }
|
||||
public int Frequency { get; init; }
|
||||
public UmFreqType FreqType { get; init; }
|
||||
@@ -39,7 +43,8 @@ public sealed class UmLogPacket
|
||||
public bool MenuInUse { get; init; }
|
||||
public string RawText { get; init; } = "";
|
||||
|
||||
public const int FieldCount = 21;
|
||||
public const int Schema1FieldCount = 21;
|
||||
public const int Schema2FieldCount = 22;
|
||||
|
||||
/// <summary>Depth converted to meters, when the raw string can be interpreted; otherwise null.</summary>
|
||||
public double? DepthMeters => TryParseDepthMeters(DepthRaw);
|
||||
@@ -90,23 +95,45 @@ public sealed class UmLogPacket
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a 21-field PBDL schema 1 packet. Returns null if the line does not look like a data packet.
|
||||
/// Parses either Schema 1 (21 fields, push-button) or Schema 2 (22 fields, timed logging).
|
||||
/// 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 Schema 2 (22 fields with LogType)
|
||||
if (fields.Length >= Schema2FieldCount)
|
||||
{
|
||||
if (int.TryParse(fields[0], out var logType) && logType is 0 or 1)
|
||||
{
|
||||
if (int.TryParse(fields[1], out var mode) && mode is >= 0 and <= 6)
|
||||
{
|
||||
return ParseSchema2(fields, (UmLogType)logType, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try Schema 1 (21 fields without LogType)
|
||||
if (fields.Length >= Schema1FieldCount)
|
||||
{
|
||||
if (int.TryParse(fields[0], out var mode) && mode is >= 0 and <= 6)
|
||||
{
|
||||
return ParseSchema1(fields, line);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static UmLogPacket? ParseSchema1(string[] fields, string line)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new UmLogPacket
|
||||
{
|
||||
Mode = (UmMode)mode,
|
||||
LogType = null, // Schema 1 doesn't have LogType
|
||||
Mode = (UmMode)ParseInt(fields[0]),
|
||||
Frequency = ParseInt(fields[1]),
|
||||
FreqType = (UmFreqType)ParseInt(fields[2]),
|
||||
Signal = ParseInt(fields[3]),
|
||||
@@ -136,6 +163,44 @@ public sealed class UmLogPacket
|
||||
}
|
||||
}
|
||||
|
||||
private static UmLogPacket? ParseSchema2(string[] fields, UmLogType logType, string line)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Schema 2 has LogType at index 0, then same fields as Schema 1 shifted by 1
|
||||
return new UmLogPacket
|
||||
{
|
||||
LogType = logType,
|
||||
Mode = (UmMode)ParseInt(fields[1]),
|
||||
Frequency = ParseInt(fields[2]),
|
||||
FreqType = (UmFreqType)ParseInt(fields[3]),
|
||||
Signal = ParseInt(fields[4]),
|
||||
GainDb = ParseInt(fields[5]),
|
||||
DepthRaw = fields[6].Trim(),
|
||||
CurrentRaw = fields[7].Trim(),
|
||||
CompassAngle = ParseInt(fields[8]),
|
||||
GuidanceArrows = ParseInt(fields[9]),
|
||||
LdPhase = ParseInt(fields[10]),
|
||||
Clipping = ParseInt(fields[11]) != 0,
|
||||
DepthCurrentSetting = ParseInt(fields[12]),
|
||||
LrArrowStyle = ParseInt(fields[13]),
|
||||
AudioVolume = ParseInt(fields[14]),
|
||||
AudioModulation = ParseInt(fields[15]),
|
||||
AudioSound = ParseInt(fields[16]),
|
||||
BatteryVoltage = ParseDouble(fields[17]),
|
||||
BatteryType = (UmBatteryType)ParseInt(fields[18]),
|
||||
Backlight = ParseInt(fields[19]),
|
||||
Utility = (UmUtility)ParseInt(fields[20]),
|
||||
MenuInUse = ParseInt(fields[21]) != 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