Files
ulapp/FieldLogger/ViewModels/DeviceScanViewModel.cs
brentperteet 299ec3875d 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>
2026-07-06 17:32:00 -05:00

108 lines
3.3 KiB
C#

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FieldLogger.Models;
using FieldLogger.Services;
using FieldLogger.Services.Ble;
namespace FieldLogger.ViewModels;
[QueryProperty(nameof(KindName), "kind")]
public sealed partial class DeviceScanViewModel : ObservableObject
{
private readonly BleScanner _scanner;
private readonly DeviceConnectionManager _manager;
private CancellationTokenSource? _scanCts;
[ObservableProperty]
private string _kindName = nameof(DeviceKind.Locator);
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ScanButtonText))]
private bool _isScanning;
[ObservableProperty]
private string _title = "Select Device";
[ObservableProperty]
private string _hint = "";
public ObservableCollection<DiscoveredDevice> Devices { get; } = new();
public DeviceKind Kind => Enum.TryParse<DeviceKind>(KindName, out var k) ? k : DeviceKind.Locator;
public string ScanButtonText => IsScanning ? "Scanning…" : "Scan Again";
public DeviceScanViewModel(BleScanner scanner, DeviceConnectionManager manager)
{
_scanner = scanner;
_manager = manager;
_scanner.DeviceDiscovered += OnDeviceDiscovered;
}
partial void OnKindNameChanged(string value)
{
Title = Kind == DeviceKind.Locator ? "Select Locating Receiver" : "Select RTK GPS Receiver";
Hint = Kind == DeviceKind.Locator
? "Looking for UM receivers (UMRX / DT100). Make sure Bluetooth is enabled on the receiver."
: "Looking for Maglink RTK receivers. Make sure the receiver is powered on.";
}
[RelayCommand]
public async Task ScanAsync()
{
if (IsScanning)
return;
Devices.Clear();
IsScanning = true;
_scanCts = new CancellationTokenSource();
try
{
await _scanner.ScanAsync(TimeSpan.FromSeconds(12), _scanCts.Token);
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("Scan Failed", ex.Message, "OK");
}
finally
{
IsScanning = false;
_scanCts?.Dispose();
_scanCts = null;
}
}
public void StopScan() => _scanCts?.Cancel();
[RelayCommand]
private async Task SelectAsync(DiscoveredDevice device)
{
StopScan();
await _manager.UseDeviceAsync(Kind, device.Id, device.Name);
await Shell.Current.GoToAsync("..");
}
private void OnDeviceDiscovered(object? sender, DiscoveredDevice device)
{
// Filter by device name prefix based on device type.
if (Kind == DeviceKind.Locator && !UmReceiverService.IsUmReceiverName(device.Name))
return;
if (Kind == DeviceKind.RtkGps && !MaglinkService.IsMaglinkName(device.Name))
return;
MainThread.BeginInvokeOnMainThread(() =>
{
var existing = Devices.FirstOrDefault(d => d.Id == device.Id);
if (existing is not null)
Devices.Remove(existing);
// Keep the list sorted by signal strength.
var index = 0;
while (index < Devices.Count && Devices[index].Rssi >= device.Rssi)
index++;
Devices.Insert(index, device);
});
}
}