Files
powerpods/goTool/api_battery_uv.go
T
skrueckenandCursor 7f28a48a10 Add runtime UV protection toggle and treat near-zero sense as external power.
Per-pod NVS enable (default off) via UART/ESP-NOW/dashboard, and skip UV latch when LiPo ADC reads ~0 mV so USB/external supply still boots.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-01 22:51:17 +01:00

141 lines
3.6 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"powerpod/gotool/pb"
)
type batteryUvAPIRequest struct {
Write bool `json:"write"`
Enabled bool `json:"enabled"`
ClientID uint32 `json:"client_id"`
AllClients bool `json:"all_clients"`
// SlavesOnly: with all_clients, push to ESP-NOW slaves only (master NVS unchanged).
SlavesOnly bool `json:"slaves_only"`
}
type batteryUvAPIResponse struct {
ClientID uint32 `json:"client_id"`
Success bool `json:"success"`
Enabled bool `json:"enabled"`
Latched bool `json:"latched"`
SlavesUpdated uint32 `json:"slaves_updated"`
Error string `json:"error,omitempty"`
}
func mountBatteryUvAPI(mux *http.ServeMux, link *managedSerial) {
mux.HandleFunc("/api/battery-uv", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
serveBatteryUvGet(w, r, link)
case http.MethodPost:
serveBatteryUvPost(w, r, link)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})
}
func serveBatteryUvGet(w http.ResponseWriter, r *http.Request, link *managedSerial) {
clientID, err := parseUintQuery(r, "client_id", 0)
if err != nil {
writeJSON(w, http.StatusBadRequest, batteryUvAPIResponse{Error: err.Error()})
return
}
resp, err := link.BatteryUv(&pb.BatteryUvRequest{
Write: false,
ClientId: clientID,
})
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, batteryUvAPIResponse{
ClientID: clientID,
Error: err.Error(),
})
return
}
writeJSON(w, http.StatusOK, batteryUvAPIResponse{
ClientID: resp.GetClientId(),
Success: resp.GetSuccess(),
Enabled: resp.GetEnabled(),
Latched: resp.GetLatched(),
SlavesUpdated: resp.GetSlavesUpdated(),
})
}
func serveBatteryUvPost(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body batteryUvAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, batteryUvAPIResponse{Error: "invalid JSON"})
return
}
if body.AllClients && body.SlavesOnly {
updated, err := applyBatteryUvToSlaves(link, body.Enabled)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, batteryUvAPIResponse{
Enabled: body.Enabled,
Error: err.Error(),
})
return
}
writeJSON(w, http.StatusOK, batteryUvAPIResponse{
Success: updated > 0,
Enabled: body.Enabled,
SlavesUpdated: updated,
})
return
}
resp, err := link.BatteryUv(&pb.BatteryUvRequest{
Write: true,
Enabled: body.Enabled,
ClientId: body.ClientID,
AllClients: body.AllClients,
})
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, batteryUvAPIResponse{
ClientID: body.ClientID,
Enabled: body.Enabled,
Error: err.Error(),
})
return
}
writeJSON(w, http.StatusOK, batteryUvAPIResponse{
ClientID: resp.GetClientId(),
Success: resp.GetSuccess(),
Enabled: resp.GetEnabled(),
Latched: resp.GetLatched(),
SlavesUpdated: resp.GetSlavesUpdated(),
})
}
func applyBatteryUvToSlaves(link *managedSerial, enabled bool) (uint32, error) {
clients, err := link.listClients()
if err != nil {
return 0, err
}
var updated uint32
for _, c := range clients {
resp, err := link.BatteryUv(&pb.BatteryUvRequest{
Write: true,
Enabled: enabled,
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("UV protection not applied to any slave")
}
return updated, nil
}