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>
141 lines
4.0 KiB
Go
141 lines
4.0 KiB
Go
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})
|
|
})
|
|
}
|