diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 4f91c7f..b80f9a1 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -2,7 +2,10 @@
"permissions": {
"allow": [
"Bash(go build:*)",
- "Bash(git:*)"
+ "Bash(git:*)",
+ "Bash(cmd.exe *)",
+ "Bash(go env *)",
+ "Bash(go install *)"
]
}
}
diff --git a/S3/KVMote_ESP32S3.ino b/S3/KVMote_ESP32S3/KVMote_ESP32S3.ino
similarity index 99%
rename from S3/KVMote_ESP32S3.ino
rename to S3/KVMote_ESP32S3/KVMote_ESP32S3.ino
index 9f48eb5..5948e72 100644
--- a/S3/KVMote_ESP32S3.ino
+++ b/S3/KVMote_ESP32S3/KVMote_ESP32S3.ino
@@ -263,6 +263,7 @@ void setup() {
// BLE — NUS
BLEDevice::init("KVMote");
+
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
diff --git a/frontend/dist/index.html b/frontend/dist/index.html
index 6292410..ec091a1 100644
--- a/frontend/dist/index.html
+++ b/frontend/dist/index.html
@@ -162,16 +162,11 @@
-
-
-
-
-
-
+
+
diff --git a/internal/transport/ble_windows.go b/internal/transport/ble_windows.go
index 22587c8..c1cf9ca 100644
--- a/internal/transport/ble_windows.go
+++ b/internal/transport/ble_windows.go
@@ -4,12 +4,24 @@ import (
"context"
"errors"
"fmt"
+ "os"
"sync"
"time"
"tinygo.org/x/bluetooth"
)
+func bleLog(msg string) {
+ line := fmt.Sprintf("[BLE] %s | %s\n", time.Now().Format("15:04:05.000"), msg)
+ fmt.Print(line)
+ f, err := os.OpenFile("kvmote_debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ return
+ }
+ defer f.Close()
+ f.WriteString(line)
+}
+
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})
@@ -45,15 +57,19 @@ func (t *RealBleTransport) Detect(ctx context.Context) (bool, error) {
}
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
- }
+ go func() {
+ t.adapter.Scan(func(adapter *bluetooth.Adapter, result bluetooth.ScanResult) {
+ hasUUID := result.HasServiceUUID(NusServiceUUID)
+ bleLog(fmt.Sprintf("visto: nome=%q hasNUS=%v addr=%s", result.LocalName(), hasUUID, result.Address.String()))
+ if result.LocalName() == "KVMote" || hasUUID {
+ adapter.StopScan()
+ select {
+ case foundChan <- result.Address:
+ default:
+ }
+ }
+ })
+ }()
select {
case addr := <-foundChan:
@@ -77,24 +93,65 @@ func (t *RealBleTransport) Connect(ctx context.Context) error {
addr := t.address
t.mu.Unlock()
- fmt.Println("[BLE] Conectando...")
- device, err := t.adapter.Connect(addr, bluetooth.ConnectionParams{})
+ var device bluetooth.Device
+ var services []bluetooth.DeviceService
+ var err error
+
+ for cycle := 1; cycle <= 3; cycle++ {
+ if cycle > 1 {
+ bleLog(fmt.Sprintf("Reconectando (ciclo %d)...", cycle))
+ time.Sleep(1 * time.Second)
+ }
+
+ bleLog(fmt.Sprintf("Conectando (ciclo %d)...", cycle))
+ device, err = t.adapter.Connect(addr, bluetooth.ConnectionParams{})
+ if err != nil {
+ bleLog(fmt.Sprintf(" Connect erro: %v", err))
+ continue
+ }
+
+ t.mu.Lock()
+ t.device = device
+ t.mu.Unlock()
+
+ // Delay crucial para BT 4.0 estabilizar a conexão antes de pedir serviços
+ bleLog("Aguardando estabilização (2s)...")
+ time.Sleep(2 * time.Second)
+
+ bleLog("DiscoverServices...")
+ services, err = device.DiscoverServices(nil)
+ if err != nil {
+ bleLog(fmt.Sprintf(" DiscoverServices erro: %v", err))
+ // Tenta mais uma vez após um pequeno delay antes de desistir do ciclo
+ time.Sleep(1 * time.Second)
+ bleLog("DiscoverServices (retry)...")
+ services, err = device.DiscoverServices(nil)
+ if err != nil {
+ bleLog(fmt.Sprintf(" DiscoverServices retry erro: %v", err))
+ go func() { defer func() { recover() }(); device.Disconnect() }()
+ continue
+ }
+ }
+ break
+ }
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")
+ return fmt.Errorf("erro ao descobrir serviços: %w", err)
}
- fmt.Println("[BLE] Buscando RX...")
- chars, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{NusRxUUID})
+ bleLog(fmt.Sprintf("Serviços encontrados: %d", len(services)))
+ nusIdx := -1
+ for i, s := range services {
+ bleLog(fmt.Sprintf(" [%d] %s", i, s.UUID().String()))
+ if s.UUID() == NusServiceUUID {
+ nusIdx = i
+ }
+ }
+ if nusIdx < 0 {
+ return errors.New("serviço NUS não encontrado")
+ }
+
+ bleLog("Buscando característica RX...")
+ chars, err := services[nusIdx].DiscoverCharacteristics([]bluetooth.UUID{NusRxUUID})
if err != nil || len(chars) == 0 {
return errors.New("característica não encontrada")
}