Files
powerpods/goTool/api_tap_config.go
T
simonandCursor d7441cb85f Add runtime BMA456 tap config with NVS persistence and goTool UI.
Expose multitap parameters over UART/ESP-NOW (like deadzone), store per node in NVS, and allow get/set from CLI, REST, and the dashboard without poll-overwrite while editing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-26 13:07:13 +02:00

172 lines
4.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"powerpod/gotool/pb"
)
type tapConfigFields struct {
TapSensThres uint32 `json:"tap_sens_thres"`
MaxGestDur uint32 `json:"max_gest_dur"`
TapShockDur uint32 `json:"tap_shock_dur"`
QuiteTimeAfterGest uint32 `json:"quite_time_after_gest"`
WaitForTimeout uint32 `json:"wait_for_timeout"`
AxisSel uint32 `json:"axis_sel"`
EnableSingle bool `json:"enable_single"`
EnableDouble bool `json:"enable_double"`
EnableTriple bool `json:"enable_triple"`
}
type tapConfigAPIResponse struct {
tapConfigFields
ClientID uint32 `json:"client_id"`
Success bool `json:"success"`
SlavesUpdated uint32 `json:"slaves_updated"`
Error string `json:"error,omitempty"`
}
type tapConfigAPIRequest struct {
Write bool `json:"write"`
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"`
tapConfigFields
}
func tapConfigFieldsFromPB(r *pb.TapConfigResponse) tapConfigFields {
return tapConfigFields{
TapSensThres: r.GetTapSensThres(),
MaxGestDur: r.GetMaxGestDur(),
TapShockDur: r.GetTapShockDur(),
QuiteTimeAfterGest: r.GetQuiteTimeAfterGest(),
WaitForTimeout: r.GetWaitForTimeout(),
AxisSel: r.GetAxisSel(),
EnableSingle: r.GetEnableSingle(),
EnableDouble: r.GetEnableDouble(),
EnableTriple: r.GetEnableTriple(),
}
}
func (f tapConfigFields) toPBRequest(write bool, clientID uint32, all bool) *pb.TapConfigRequest {
return &pb.TapConfigRequest{
Write: write,
ClientId: clientID,
AllClients: all,
TapSensThres: f.TapSensThres,
MaxGestDur: f.MaxGestDur,
TapShockDur: f.TapShockDur,
QuiteTimeAfterGest: f.QuiteTimeAfterGest,
WaitForTimeout: f.WaitForTimeout,
AxisSel: f.AxisSel,
EnableSingle: f.EnableSingle,
EnableDouble: f.EnableDouble,
EnableTriple: f.EnableTriple,
}
}
func mountTapConfigAPI(mux *http.ServeMux, link *managedSerial) {
mux.HandleFunc("/api/tap-config", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
serveTapConfigGet(w, r, link)
case http.MethodPost:
serveTapConfigPost(w, r, link)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
})
}
func serveTapConfigGet(w http.ResponseWriter, r *http.Request, link *managedSerial) {
clientID, err := parseUintQuery(r, "client_id", 0)
if err != nil {
writeJSON(w, http.StatusBadRequest, tapConfigAPIResponse{Error: err.Error()})
return
}
resp, err := link.TapConfig(&pb.TapConfigRequest{
Write: false,
ClientId: clientID,
})
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, tapConfigAPIResponse{
ClientID: clientID,
Error: err.Error(),
})
return
}
out := tapConfigAPIResponse{
tapConfigFields: tapConfigFieldsFromPB(resp),
ClientID: resp.GetClientId(),
Success: resp.GetSuccess(),
}
writeJSON(w, http.StatusOK, out)
}
func serveTapConfigPost(w http.ResponseWriter, r *http.Request, link *managedSerial) {
var body tapConfigAPIRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, tapConfigAPIResponse{Error: "invalid JSON"})
return
}
if body.AllClients && body.SlavesOnly {
updated, err := applyTapConfigToSlaves(link, body.tapConfigFields)
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, tapConfigAPIResponse{
tapConfigFields: body.tapConfigFields,
Error: err.Error(),
})
return
}
writeJSON(w, http.StatusOK, tapConfigAPIResponse{
tapConfigFields: body.tapConfigFields,
Success: updated > 0,
SlavesUpdated: updated,
})
return
}
resp, err := link.TapConfig(body.tapConfigFields.toPBRequest(true, body.ClientID, body.AllClients))
if err != nil {
writeJSON(w, http.StatusServiceUnavailable, tapConfigAPIResponse{
ClientID: body.ClientID,
tapConfigFields: body.tapConfigFields,
Error: err.Error(),
})
return
}
writeJSON(w, http.StatusOK, tapConfigAPIResponse{
tapConfigFields: tapConfigFieldsFromPB(resp),
ClientID: resp.GetClientId(),
Success: resp.GetSuccess(),
SlavesUpdated: resp.GetSlavesUpdated(),
})
}
func applyTapConfigToSlaves(link *managedSerial, fields tapConfigFields) (uint32, error) {
clients, err := link.listClients()
if err != nil {
return 0, err
}
var updated uint32
for _, c := range clients {
resp, err := link.TapConfig(fields.toPBRequest(true, c.GetId(), false))
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("tap config not applied to any slave")
}
return updated, nil
}