using FieldLogger.Models; using FieldLogger.Services.Ble; using Microsoft.Extensions.Logging; namespace FieldLogger.Services; /// /// Manages the connection to an Underground Magnetics locating receiver and /// its push-button data-logging (PBDL) protocol. /// 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 _logger; public UmDeviceInfo? DeviceInfo { get; private set; } public bool IsConnected => _client.IsConnected; public string? DeviceName => _client.DeviceName; /// Raised when the operator presses the log button on the receiver. public event EventHandler? PacketReceived; public event EventHandler? DeviceInfoReceived; public event EventHandler? Disconnected; public UmReceiverService(ILogger logger) { _logger = logger; _client.LineReceived += OnLineReceived; _client.Disconnected += (_, _) => Disconnected?.Invoke(this, EventArgs.Empty); } /// Device-name filter for scan results (UMRX_* for most brands, DT100_* for Leica). 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); } /// Enables push-button data logging. The first enable triggers the info string. 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(); }