1ab397767a
Dead code removed: - MusicAnalyzer.BuildWaveFrame (replaced by DrumDetector + BuildMelodyFrame) - MusicAnalyzer.ExtractFeatures simplified to melody-only (removed rhythm band) - MusicAnalyzer.RhythmLow/RhythmHigh constants, TickFeature.RhythmFlux - LiveCapture._liveMaxFlux + _prevRhythmMag (computed, never read) - State.ActualA/ActualB, State.LastSentA/LastSentB (set, never read) - Program.cs StrengthChanged handler (only wrote to dead fields) - Command.Mode property (never referenced) - DrumDetector.FreqKick/Snare/Brass/Silent static arrays (unused) Bugs fixed: - DrumDetector.BuildFrame: freqBytes was byte[16], now byte[4] - CoyoteDevice fallback: use nameFilter param instead of hardcoded string Refactoring: - Server.DoStatus calls BuildStatusPush(null) instead of duplicating - HeatMap: removed redundant colW assignment - Removed unused System.Numerics imports from LiveCapture, MusicAnalyzer
136 lines
5.6 KiB
C#
136 lines
5.6 KiB
C#
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(nameFilter, 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();
|
|
}
|
|
}
|