package transport import ( "context" "errors" "fmt" "sync" "time" "tinygo.org/x/bluetooth" ) var ( NusServiceUUID = bluetooth.NewUUID([16]byte{0x6e, 0x40, 0x00, 0x01, 0xb5, 0xa3, 0xf3, 0x93, 0xe0, 0xa9, 0xe5, 0x0e, 0x24, 0xdc, 0xca, 0x9e}) NusRxUUID = bluetooth.NewUUID([16]byte{0x6e, 0x40, 0x00, 0x02, 0xb5, 0xa3, 0xf3, 0x93, 0xe0, 0xa9, 0xe5, 0x0e, 0x24, 0xdc, 0xca, 0x9e}) ) type RealBleTransport struct { adapter *bluetooth.Adapter device bluetooth.Device rxChar bluetooth.DeviceCharacteristic connected bool mu sync.Mutex address bluetooth.Address found bool } func NewBleTransport() *RealBleTransport { return &RealBleTransport{ adapter: bluetooth.DefaultAdapter, } } func (t *RealBleTransport) Detect(ctx context.Context) (bool, error) { t.mu.Lock() if t.found { t.mu.Unlock() return true, nil } t.mu.Unlock() fmt.Println("[BLE] Iniciando Scan...") if err := t.adapter.Enable(); err != nil { return false, err } foundChan := make(chan bluetooth.Address, 1) err := t.adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) { if result.LocalName() == "KVMote" { adapter.StopScan() foundChan <- result.Address } }) if err != nil { return false, err } select { case addr := <-foundChan: t.mu.Lock() t.address = addr t.found = true t.mu.Unlock() fmt.Println("[BLE] Encontrado!") return true, nil case <-ctx.Done(): t.adapter.StopScan() return false, ctx.Err() case <-time.After(5 * time.Second): t.adapter.StopScan() return false, errors.New("não encontrado") } } func (t *RealBleTransport) Connect(ctx context.Context) error { t.mu.Lock() addr := t.address t.mu.Unlock() fmt.Println("[BLE] Conectando...") device, err := t.adapter.Connect(addr, bluetooth.ConnectionParams{}) if err != nil { return err } t.mu.Lock() t.device = device t.mu.Unlock() fmt.Println("[BLE] Buscando Serviço...") services, err := device.DiscoverServices([]bluetooth.UUID{NusServiceUUID}) if err != nil || len(services) == 0 { return errors.New("serviço não encontrado") } fmt.Println("[BLE] Buscando RX...") chars, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{NusRxUUID}) if err != nil || len(chars) == 0 { return errors.New("característica não encontrada") } t.mu.Lock() t.rxChar = chars[0] t.connected = true t.mu.Unlock() fmt.Println("[BLE] PRONTO!") t.Send([]byte{'H'}) return nil } func (t *RealBleTransport) Disconnect() error { t.mu.Lock() defer t.mu.Unlock() if !t.connected { return nil } // Proteção contra crash no Windows go func() { defer func() { recover() }() t.device.Disconnect() }() t.connected = false return nil } func (t *RealBleTransport) IsConnected() bool { t.mu.Lock() defer t.mu.Unlock() return t.connected } func (t *RealBleTransport) Send(data []byte) error { t.mu.Lock() defer t.mu.Unlock() if !t.connected { return nil } _, err := t.rxChar.WriteWithoutResponse(data) return err } func (t *RealBleTransport) SendLossy(data []byte) error { return t.Send(data) } func (t *RealBleTransport) DeviceLabel() string { return "KVMote (BLE)" } func (t *RealBleTransport) ClipboardConfig() (int, int, bool) { return 65536, 5, true }