Add dynamic UART/BLE host connect for serve, dashboard, and WS.

Serve starts without a required port; REST/WS/UI can scan and switch transports, with BLE live ticks and UART-only OTA.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-23 22:40:28 +02:00
co-authored by Cursor
parent 92a13d7fd0
commit aa67edfe94
18 changed files with 1573 additions and 404 deletions
+51 -12
View File
@@ -37,15 +37,6 @@ func openBLE(name string, settle time.Duration) (*bleLink, error) {
return nil, fmt.Errorf("enable bluetooth adapter: %w (check BlueZ / permissions)", err)
}
svcUUID, err := bluetooth.ParseUUID(bleServiceUUID)
if err != nil {
return nil, err
}
chrUUID, err := bluetooth.ParseUUID(bleCharUUID)
if err != nil {
return nil, err
}
log.Printf("BLE scanning for %q…", name)
var foundAddr bluetooth.Address
var found bool
@@ -53,7 +44,7 @@ func openBLE(name string, settle time.Duration) (*bleLink, error) {
time.Sleep(15 * time.Second)
_ = adapter.StopScan()
}()
err = adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
err := adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
if result.LocalName() != name {
return
}
@@ -67,9 +58,57 @@ func openBLE(name string, settle time.Duration) (*bleLink, error) {
if !found {
return nil, fmt.Errorf("device %q not found (check DIP8=BLE and advertising)", name)
}
return connectBLEDevice(foundAddr, settle)
}
log.Printf("BLE connecting…")
device, err := adapter.Connect(foundAddr, bluetooth.ConnectionParams{})
func openBLEAddress(addrStr string, settle time.Duration) (*bleLink, error) {
adapter := bluetooth.DefaultAdapter
if err := adapter.Enable(); err != nil {
return nil, fmt.Errorf("enable bluetooth adapter: %w (check BlueZ / permissions)", err)
}
if mac, err := bluetooth.ParseMAC(addrStr); err == nil {
var addr bluetooth.Address
addr.Set(mac.String())
return connectBLEDevice(addr, settle)
}
// Fallback: scan until address string matches
log.Printf("BLE resolving address %s via scan…", addrStr)
var foundAddr bluetooth.Address
var found bool
go func() {
time.Sleep(15 * time.Second)
_ = adapter.StopScan()
}()
err := adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
if result.Address.String() != addrStr {
return
}
foundAddr = result.Address
found = true
_ = a.StopScan()
})
if err != nil {
return nil, fmt.Errorf("scan for address: %w", err)
}
if !found {
return nil, fmt.Errorf("BLE address %s not found", addrStr)
}
return connectBLEDevice(foundAddr, settle)
}
func connectBLEDevice(addr bluetooth.Address, settle time.Duration) (*bleLink, error) {
svcUUID, err := bluetooth.ParseUUID(bleServiceUUID)
if err != nil {
return nil, err
}
chrUUID, err := bluetooth.ParseUUID(bleCharUUID)
if err != nil {
return nil, err
}
adapter := bluetooth.DefaultAdapter
log.Printf("BLE connecting to %s…", addr.String())
device, err := adapter.Connect(addr, bluetooth.ConnectionParams{})
if err != nil {
return nil, fmt.Errorf("connect: %w", err)
}