Files
Substation/CoyoteDevice.cs
T

136 lines
5.6 KiB
C#
Raw Normal View History

using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;
namespace Substation;
public class CoyoteDevice : IDisposable
{
static readonly Guid ServiceUuid = Guid.Parse("0000180c-0000-1000-8000-00805f9b34fb");
static readonly Guid WriteCharUuid = Guid.Parse("0000150a-0000-1000-8000-00805f9b34fb");
static readonly Guid NotifyCharUuid= Guid.Parse("0000150b-0000-1000-8000-00805f9b34fb");
BluetoothLEDevice? _device;
GattCharacteristic? _write;
GattCharacteristic? _notify;
public bool IsConnected => _write != null;
public int StrengthA { get; private set; }
public int StrengthB { get; private set; }
public string DeviceName { get; private set; } = "";
public event Action<int, int>? StrengthChanged;
public event Action<bool>? ConnectionChanged;
public async Task ConnectAsync(string nameFilter = "47L121000")
{
var selector = BluetoothLEDevice.GetDeviceSelectorFromDeviceName(nameFilter);
var devices = await DeviceInformation.FindAllAsync(selector);
if (devices.Count == 0)
{
selector = BluetoothLEDevice.GetDeviceSelectorFromPairingState(false);
devices = await DeviceInformation.FindAllAsync(selector);
var match = devices.FirstOrDefault(d =>
d.Name.Contains("47L121000", StringComparison.OrdinalIgnoreCase) ||
d.Name.Contains("DG-LAB", StringComparison.OrdinalIgnoreCase) ||
d.Name.Contains("Coyote", StringComparison.OrdinalIgnoreCase));
if (match == null)
throw new InvalidOperationException(
$"Coyote 3.0 not found (filter='{nameFilter}'). Ensure it's powered on and BLE is enabled.");
// fallback path
var fallback = await BluetoothLEDevice.FromIdAsync(match.Id);
await ConnectDeviceAsync(fallback, match.Name);
return;
}
var dev = await BluetoothLEDevice.FromIdAsync(devices[0].Id);
await ConnectDeviceAsync(dev, devices[0].Name);
}
async Task ConnectDeviceAsync(BluetoothLEDevice dev, string name)
{
_device = dev;
DeviceName = name;
_device.ConnectionStatusChanged += OnConnectionStatusChanged;
var svcResult = await dev.GetGattServicesForUuidAsync(ServiceUuid, BluetoothCacheMode.Uncached);
if (svcResult.Status != GattCommunicationStatus.Success || svcResult.Services.Count == 0)
throw new InvalidOperationException($"GATT service {ServiceUuid} not found on {name}.");
var svc = svcResult.Services[0];
var charResult = await svc.GetCharacteristicsAsync();
if (charResult.Status != GattCommunicationStatus.Success)
throw new InvalidOperationException($"Failed to enumerate characteristics on {name}.");
_write = charResult.Characteristics.FirstOrDefault(c => c.Uuid == WriteCharUuid)
?? throw new InvalidOperationException("Write characteristic 0x150A not found.");
_notify = charResult.Characteristics.FirstOrDefault(c => c.Uuid == NotifyCharUuid)
?? throw new InvalidOperationException("Notify characteristic 0x150B not found.");
_notify.ValueChanged += OnNotify;
var subStatus = await _notify.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
if (subStatus != GattCommunicationStatus.Success)
throw new InvalidOperationException("Failed to subscribe to 0x150B notifications.");
await SendBF(200, 200, 128, 128, 128, 128);
ConnectionChanged?.Invoke(true);
}
void OnConnectionStatusChanged(BluetoothLEDevice sender, object args)
{
if (sender.ConnectionStatus == Windows.Devices.Bluetooth.BluetoothConnectionStatus.Disconnected)
{
_write = null;
_notify = null;
ConnectionChanged?.Invoke(false);
}
}
void OnNotify(GattCharacteristic sender, GattValueChangedEventArgs args)
{
var reader = DataReader.FromBuffer(args.CharacteristicValue);
if (reader.UnconsumedBufferLength < 1) return;
var head = reader.ReadByte();
if (head == 0xB1 && reader.UnconsumedBufferLength >= 3)
{
reader.ReadByte();
StrengthA = reader.ReadByte();
StrengthB = reader.ReadByte();
StrengthChanged?.Invoke(StrengthA, StrengthB);
}
}
public async Task SendB0(byte[] frame)
{
if (_write == null) return;
var writer = new DataWriter();
writer.WriteBytes(frame);
var status = await _write.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
if (status != GattCommunicationStatus.Success)
Console.Error.WriteLine($"[BLE] B0 write failed: {status}");
}
public async Task SendBF(byte capA, byte capB, byte freqBalA, byte freqBalB, byte intBalA, byte intBalB)
{
if (_write == null) return;
var frame = BF.Build(capA, capB, freqBalA, freqBalB, intBalA, intBalB);
var writer = new DataWriter();
writer.WriteBytes(frame);
var status = await _write.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
if (status != GattCommunicationStatus.Success)
Console.Error.WriteLine($"[BLE] BF write failed: {status}");
}
public void Dispose()
{
if (_notify != null)
_notify.ValueChanged -= OnNotify;
_device?.Dispose();
}
}