Always show master deadzone input with read/set controls; apply bulk slave changes via slaves_only without changing the master's BMA456. Co-authored-by: Cursor <cursoragent@cursor.com>
200 lines
5.2 KiB
Go
200 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"powerpod/gotool/pb"
|
|
)
|
|
|
|
type deadzoneAPIResponse struct {
|
|
Deadzone uint32 `json:"deadzone"`
|
|
ClientID uint32 `json:"client_id"`
|
|
Success bool `json:"success"`
|
|
SlavesUpdated uint32 `json:"slaves_updated"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type deadzoneAPIRequest struct {
|
|
Write bool `json:"write"`
|
|
Deadzone uint32 `json:"deadzone"`
|
|
ClientID uint32 `json:"client_id"`
|
|
AllClients bool `json:"all_clients"`
|
|
// SlavesOnly: with all_clients, push to ESP-NOW slaves only (master BMA456 unchanged).
|
|
SlavesOnly bool `json:"slaves_only"`
|
|
}
|
|
|
|
type unicastAPIRequest struct {
|
|
ClientID uint32 `json:"client_id"`
|
|
Seq uint32 `json:"seq"`
|
|
}
|
|
|
|
type unicastAPIResponse struct {
|
|
Success bool `json:"success"`
|
|
Seq uint32 `json:"seq"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func mountServeAPI(mux *http.ServeMux, link *managedSerial) {
|
|
mux.HandleFunc("/api/deadzone", func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
serveDeadzoneGet(w, r, link)
|
|
case http.MethodPost:
|
|
serveDeadzonePost(w, r, link)
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
})
|
|
mux.HandleFunc("/api/unicast-test", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
serveUnicastTest(w, r, link)
|
|
})
|
|
}
|
|
|
|
func serveDeadzoneGet(w http.ResponseWriter, r *http.Request, link *managedSerial) {
|
|
clientID, err := parseUintQuery(r, "client_id", 0)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, deadzoneAPIResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
resp, err := link.AccelDeadzone(&pb.AccelDeadzoneRequest{
|
|
Write: false,
|
|
ClientId: clientID,
|
|
})
|
|
if err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, deadzoneAPIResponse{
|
|
ClientID: clientID,
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, deadzoneAPIResponse{
|
|
Deadzone: resp.GetDeadzone(),
|
|
ClientID: resp.GetClientId(),
|
|
Success: resp.GetSuccess(),
|
|
})
|
|
}
|
|
|
|
func serveDeadzonePost(w http.ResponseWriter, r *http.Request, link *managedSerial) {
|
|
var body deadzoneAPIRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, deadzoneAPIResponse{Error: "invalid JSON"})
|
|
return
|
|
}
|
|
if body.AllClients && body.SlavesOnly {
|
|
updated, err := applyDeadzoneToSlaves(link, body.Deadzone)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, deadzoneAPIResponse{
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, deadzoneAPIResponse{
|
|
Deadzone: body.Deadzone,
|
|
Success: updated > 0,
|
|
SlavesUpdated: updated,
|
|
})
|
|
return
|
|
}
|
|
|
|
req := &pb.AccelDeadzoneRequest{
|
|
Write: true,
|
|
Deadzone: body.Deadzone,
|
|
ClientId: body.ClientID,
|
|
AllClients: body.AllClients,
|
|
}
|
|
// client_id 0 without all_clients: master BMA456 only (same as CLI -client 0).
|
|
resp, err := link.AccelDeadzone(req)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, deadzoneAPIResponse{
|
|
ClientID: body.ClientID,
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, deadzoneAPIResponse{
|
|
Deadzone: resp.GetDeadzone(),
|
|
ClientID: resp.GetClientId(),
|
|
Success: resp.GetSuccess(),
|
|
SlavesUpdated: resp.GetSlavesUpdated(),
|
|
})
|
|
}
|
|
|
|
// applyDeadzoneToSlaves sets deadzone on each registered slave via per-client UART/ESP-NOW.
|
|
// Does not change the master's local BMA456 (use client_id 0 for that).
|
|
func applyDeadzoneToSlaves(link *managedSerial, deadzone uint32) (uint32, error) {
|
|
clients, err := link.listClients()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var updated uint32
|
|
for _, c := range clients {
|
|
resp, err := link.AccelDeadzone(&pb.AccelDeadzoneRequest{
|
|
Write: true,
|
|
Deadzone: deadzone,
|
|
ClientId: c.GetId(),
|
|
})
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if resp.GetSuccess() {
|
|
updated++
|
|
}
|
|
}
|
|
if len(clients) == 0 {
|
|
return 0, fmt.Errorf("no slaves registered")
|
|
}
|
|
if updated == 0 {
|
|
return 0, fmt.Errorf("deadzone not applied to any slave")
|
|
}
|
|
return updated, nil
|
|
}
|
|
|
|
func serveUnicastTest(w http.ResponseWriter, r *http.Request, link *managedSerial) {
|
|
var body unicastAPIRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, unicastAPIResponse{Error: "invalid JSON"})
|
|
return
|
|
}
|
|
if body.ClientID == 0 {
|
|
writeJSON(w, http.StatusBadRequest, unicastAPIResponse{Error: "client_id required"})
|
|
return
|
|
}
|
|
if body.Seq == 0 {
|
|
body.Seq = 1
|
|
}
|
|
resp, err := link.EspnowUnicastTest(body.ClientID, body.Seq)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, unicastAPIResponse{Error: err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, unicastAPIResponse{
|
|
Success: resp.GetSuccess(),
|
|
Seq: resp.GetSeq(),
|
|
})
|
|
}
|
|
|
|
func parseUintQuery(r *http.Request, key string, def uint32) (uint32, error) {
|
|
s := r.URL.Query().Get(key)
|
|
if s == "" {
|
|
return def, nil
|
|
}
|
|
v, err := strconv.ParseUint(s, 10, 32)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint32(v), nil
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|