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:
brentperteet
2026-07-06 17:32:00 -05:00
parent b602b762c9
commit 299ec3875d
19 changed files with 608 additions and 27 deletions

View File

@@ -42,10 +42,14 @@ public sealed class BleSerialClient : IAsyncDisposable
{
await DisconnectAsync().ConfigureAwait(false);
// Add a timeout to prevent hanging indefinitely when device is not available
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(10));
var device = await _adapter.ConnectToKnownDeviceAsync(
deviceId,
new ConnectParameters(autoConnect: false, forceBleTransport: true),
cancellationToken).ConfigureAwait(false);
timeoutCts.Token).ConfigureAwait(false);
try
{
@@ -53,7 +57,7 @@ public sealed class BleSerialClient : IAsyncDisposable
try { await device.RequestMtuAsync(247).ConfigureAwait(false); }
catch { /* not supported on this platform */ }
var service = await device.GetServiceAsync(serviceUuid, cancellationToken).ConfigureAwait(false)
var service = await device.GetServiceAsync(serviceUuid, timeoutCts.Token).ConfigureAwait(false)
?? throw new InvalidOperationException($"Service {serviceUuid} not found on {device.Name}.");
var notifyChar = await service.GetCharacteristicAsync(notifyUuid).ConfigureAwait(false)
@@ -66,12 +70,16 @@ public sealed class BleSerialClient : IAsyncDisposable
? CharacteristicWriteType.WithoutResponse
: CharacteristicWriteType.WithResponse;
Console.WriteLine($"BLE: Subscribing to notifications on {notifyUuid}");
notifyChar.ValueUpdated += OnValueUpdated;
await notifyChar.StartUpdatesAsync(cancellationToken).ConfigureAwait(false);
await notifyChar.StartUpdatesAsync(timeoutCts.Token).ConfigureAwait(false);
Console.WriteLine($"BLE: Successfully subscribed to notifications");
_device = device;
_notifyChar = notifyChar;
_writeChar = writeChar;
Console.WriteLine($"BLE: Connection complete. Device={device.Name}, CanWrite={writeChar.CanWrite}, CanNotify={notifyChar.Properties.HasFlag(CharacteristicPropertyType.Notify)}");
}
catch
{
@@ -87,7 +95,10 @@ public sealed class BleSerialClient : IAsyncDisposable
public async Task WriteAsync(string text, CancellationToken cancellationToken = default)
{
var writeChar = _writeChar ?? throw new InvalidOperationException("Not connected.");
await writeChar.WriteAsync(Encoding.ASCII.GetBytes(text), cancellationToken).ConfigureAwait(false);
var bytes = Encoding.ASCII.GetBytes(text);
Console.WriteLine($"BLE TX: {text.Replace("\r", "\\r").Replace("\n", "\\n")} ({bytes.Length} bytes)");
await writeChar.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
Console.WriteLine("BLE TX: Write completed");
}
public async Task DisconnectAsync()
@@ -117,13 +128,18 @@ public sealed class BleSerialClient : IAsyncDisposable
private void OnValueUpdated(object? sender, CharacteristicUpdatedEventArgs e)
{
var bytes = e.Characteristic.Value;
Console.WriteLine($"BLE RX: {bytes?.Length ?? 0} bytes");
if (bytes is null || bytes.Length == 0)
return;
List<string> lines = new();
lock (_rxBuffer)
{
_rxBuffer.Append(Encoding.ASCII.GetString(bytes));
var text = Encoding.ASCII.GetString(bytes);
Console.WriteLine($"BLE RX decoded: {text.Replace("\r", "\\r").Replace("\n", "\\n")}");
_rxBuffer.Append(text);
var buffered = _rxBuffer.ToString();
int newline;
while ((newline = buffered.IndexOf('\n')) >= 0)
@@ -133,12 +149,25 @@ public sealed class BleSerialClient : IAsyncDisposable
if (line.Length > 0)
lines.Add(line);
}
// If no newlines found but we have data, treat each notification as a complete message
// This handles devices that send complete messages per notification without line endings
if (lines.Count == 0 && buffered.Length > 0)
{
lines.Add(buffered.Trim());
buffered = "";
}
_rxBuffer.Clear();
_rxBuffer.Append(buffered);
}
Console.WriteLine($"BLE RX: {lines.Count} complete lines");
foreach (var line in lines)
{
Console.WriteLine($"BLE RX line: {line}");
LineReceived?.Invoke(this, line);
}
}
private void OnDeviceDisconnected(object? sender, DeviceEventArgs e) => HandleDisconnect(e.Device);