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:
88
FieldLogger/Services/UmReceiverService.cs
Normal file
88
FieldLogger/Services/UmReceiverService.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using FieldLogger.Models;
|
||||
using FieldLogger.Services.Ble;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FieldLogger.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the connection to an Underground Magnetics locating receiver and
|
||||
/// its push-button data-logging (PBDL) protocol.
|
||||
/// </summary>
|
||||
public sealed class UmReceiverService : IAsyncDisposable
|
||||
{
|
||||
// UM Receiver BLE External Logging API v1.2:
|
||||
// serial port service with notify (RX) and write-no-response (TX) characteristics.
|
||||
public static readonly Guid ServiceUuid = Guid.Parse("554d0000-261f-677e-a6f1-54c57aa996d4");
|
||||
public static readonly Guid NotifyUuid = Guid.Parse("554d0001-261f-677e-a6f1-54c57aa996d4");
|
||||
public static readonly Guid WriteUuid = Guid.Parse("554d0002-261f-677e-a6f1-54c57aa996d4");
|
||||
|
||||
private readonly BleSerialClient _client = new();
|
||||
private readonly ILogger<UmReceiverService> _logger;
|
||||
|
||||
public UmDeviceInfo? DeviceInfo { get; private set; }
|
||||
public bool IsConnected => _client.IsConnected;
|
||||
public string? DeviceName => _client.DeviceName;
|
||||
|
||||
/// <summary>Raised when the operator presses the log button on the receiver.</summary>
|
||||
public event EventHandler<UmLogPacket>? PacketReceived;
|
||||
public event EventHandler<UmDeviceInfo>? DeviceInfoReceived;
|
||||
public event EventHandler? Disconnected;
|
||||
|
||||
public UmReceiverService(ILogger<UmReceiverService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_client.LineReceived += OnLineReceived;
|
||||
_client.Disconnected += (_, _) => Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Device-name filter for scan results (UMRX_* for most brands, DT100_* for Leica).</summary>
|
||||
public static bool IsUmReceiverName(string? name) =>
|
||||
name is not null &&
|
||||
(name.StartsWith("UMRX", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.StartsWith("DT100", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
public async Task ConnectAsync(Guid deviceId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _client.ConnectAsync(deviceId, ServiceUuid, NotifyUuid, WriteUuid, cancellationToken);
|
||||
await EnableLoggingAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Enables push-button data logging. The first enable triggers the info string.</summary>
|
||||
public Task EnableLoggingAsync(CancellationToken cancellationToken = default)
|
||||
=> _client.WriteLineAsync("$UMPBDL,1", cancellationToken);
|
||||
|
||||
public Task DisableLoggingAsync(CancellationToken cancellationToken = default)
|
||||
=> _client.WriteLineAsync("$UMPBDL,0", cancellationToken);
|
||||
|
||||
public Task DisconnectAsync() => _client.DisconnectAsync();
|
||||
|
||||
private void OnLineReceived(object? sender, string line)
|
||||
{
|
||||
_logger.LogDebug("UM rx: {Line}", line);
|
||||
|
||||
// Bare command acknowledgements.
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.Equals("OK", StringComparison.OrdinalIgnoreCase) ||
|
||||
trimmed.Equals("ERROR", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
var packet = UmLogPacket.TryParse(line);
|
||||
if (packet is not null)
|
||||
{
|
||||
PacketReceived?.Invoke(this, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
var info = UmDeviceInfo.TryParse(line);
|
||||
if (info is not null)
|
||||
{
|
||||
DeviceInfo = info;
|
||||
DeviceInfoReceived?.Invoke(this, info);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogWarning("UM receiver sent unrecognized line: {Line}", line);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => _client.DisposeAsync();
|
||||
}
|
||||
Reference in New Issue
Block a user