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>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user