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:
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type hostAPIResponse struct {
|
||||
Transport string `json:"transport"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Connected bool `json:"connected"`
|
||||
BleName string `json:"ble_name,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type hostConnectRequest struct {
|
||||
Transport string `json:"transport"` // uart|ble
|
||||
Port string `json:"port,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Baud int `json:"baud,omitempty"`
|
||||
}
|
||||
|
||||
type bleScanRequest struct {
|
||||
TimeoutS float64 `json:"timeout_s"`
|
||||
NameFilter string `json:"name_filter"`
|
||||
}
|
||||
|
||||
type bleScanResponse struct {
|
||||
Devices []bleScanDevice `json:"devices"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type serialPortsResponse struct {
|
||||
Ports []string `json:"ports"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func mountHostAPI(mux *http.ServeMux, link *managedHost) {
|
||||
mux.HandleFunc("/api/host", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
st := link.HostStatus()
|
||||
writeJSON(w, http.StatusOK, hostAPIResponse{
|
||||
Transport: st["transport"].(string),
|
||||
Endpoint: st["endpoint"].(string),
|
||||
Connected: st["connected"].(bool),
|
||||
BleName: st["ble_name"].(string),
|
||||
})
|
||||
})
|
||||
|
||||
mux.HandleFunc("/api/host/connect", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
var body hostConnectRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, hostAPIResponse{Error: "invalid JSON"})
|
||||
return
|
||||
}
|
||||
var err error
|
||||
switch body.Transport {
|
||||
case hostTransportUART:
|
||||
err = link.ConnectUART(body.Port, body.Baud)
|
||||
case hostTransportBLE:
|
||||
err = link.ConnectBLE(body.Address, body.Name)
|
||||
default:
|
||||
writeJSON(w, http.StatusBadRequest, hostAPIResponse{Error: "transport must be uart or ble"})
|
||||
return
|
||||
}
|
||||
st := link.HostStatus()
|
||||
resp := hostAPIResponse{
|
||||
Transport: st["transport"].(string),
|
||||
Endpoint: st["endpoint"].(string),
|
||||
Connected: st["connected"].(bool),
|
||||
BleName: st["ble_name"].(string),
|
||||
}
|
||||
if err != nil {
|
||||
resp.Error = err.Error()
|
||||
resp.Connected = false
|
||||
writeJSON(w, http.StatusBadRequest, resp)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/api/host/disconnect", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
_ = link.Disconnect()
|
||||
st := link.HostStatus()
|
||||
writeJSON(w, http.StatusOK, hostAPIResponse{
|
||||
Transport: st["transport"].(string),
|
||||
Endpoint: st["endpoint"].(string),
|
||||
Connected: false,
|
||||
})
|
||||
})
|
||||
|
||||
mux.HandleFunc("/api/serial/ports", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
ports, err := listSerialPorts()
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusOK, serialPortsResponse{Ports: []string{}, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, serialPortsResponse{Ports: ports})
|
||||
})
|
||||
|
||||
mux.HandleFunc("/api/ble/scan", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
var body bleScanRequest
|
||||
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||
timeout := time.Duration(body.TimeoutS * float64(time.Second))
|
||||
if timeout <= 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
// Disconnect active BLE before scan (BlueZ adapter contention).
|
||||
if link.Transport() == hostTransportBLE {
|
||||
_ = link.Disconnect()
|
||||
}
|
||||
devs, err := scanBLEDevices(timeout, body.NameFilter)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusOK, bleScanResponse{Devices: []bleScanDevice{}, Error: err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, bleScanResponse{Devices: devs})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user