diff --git a/goTool/README.md b/goTool/README.md index 39f8219..a219caf 100644 --- a/goTool/README.md +++ b/goTool/README.md @@ -62,8 +62,9 @@ The dashboard can configure nodes using the same UART commands as the CLI: | UI action | CLI equivalent | |-----------|------------------| -| Master / slave deadzone | `deadzone -set -value N -client ID` | -| Alle Slaves | `deadzone -set -value N -all` | +| Nur Master | `deadzone -set -value N -client 0` | +| Einzelner Slave | `deadzone -set -value N -client ID` | +| Alle Slaves | per-slave ESP-NOW (Master bleibt unverändert; CLI `-all` setzt auch den Master) | | Unicast test | `unicast-test -client ID` | HTTP API (used by the web UI): `GET/POST /api/deadzone`, `POST /api/unicast-test`. diff --git a/goTool/api_serve.go b/goTool/api_serve.go index 12e6752..20228e7 100644 --- a/goTool/api_serve.go +++ b/goTool/api_serve.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "net/http" "strconv" @@ -21,6 +22,8 @@ type deadzoneAPIRequest struct { 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 { @@ -84,12 +87,30 @@ func serveDeadzonePost(w http.ResponseWriter, r *http.Request, link *managedSeri writeJSON(w, http.StatusBadRequest, deadzoneAPIResponse{Error: "invalid JSON"}) return } - resp, err := link.AccelDeadzone(&pb.AccelDeadzoneRequest{ + 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, @@ -105,6 +126,36 @@ func serveDeadzonePost(w http.ResponseWriter, r *http.Request, link *managedSeri }) } +// 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 { diff --git a/goTool/webui/index.html b/goTool/webui/index.html index d57d6eb..35e9429 100644 --- a/goTool/webui/index.html +++ b/goTool/webui/index.html @@ -118,6 +118,12 @@ } .btn-sm { font-size: 0.8rem; } .dz-input { width: 5.5rem; } + .config-input { max-width: 8rem; } + .config-block { + border-top: 1px solid var(--pp-border); + margin-top: 1rem; + padding-top: 1rem; + } @@ -145,9 +151,10 @@
-
Master
+
Master (BMA456 lokal)

+

Deadzone nur am Master — Slaves bleiben unverändert.

+ +
+
Konfiguration Master
+ +
+ + + +
+

+ UART nicht verbunden — Eingabe gesperrt. +

+
@@ -200,7 +224,8 @@
-
+

Slaves per ESP-NOW — Master-Deadzone bleibt separat.

+
@@ -307,7 +332,7 @@ this.configMsgOk = ok; setTimeout(() => { this.configMsg = ''; }, 5000); }, - async setDeadzone(clientId, deadzone) { + async setDeadzone(clientId, deadzone, opts = {}) { if (deadzone == null || deadzone < 0) { this.flash('Ungültiger Deadzone-Wert', false); return; @@ -321,12 +346,19 @@ write: true, deadzone: deadzone, client_id: clientId, - all_clients: false + all_clients: !!opts.allClients, + slaves_only: !!opts.slavesOnly }) }); const data = await r.json(); if (!r.ok || !data.success) { - this.flash(data.error || `Deadzone für Client ${clientId} fehlgeschlagen`, false); + const err = data.error || + (opts.slavesOnly ? 'Deadzone für Slaves fehlgeschlagen' : `Deadzone für Client ${clientId} fehlgeschlagen`); + this.flash(err, false); + return; + } + if (opts.slavesOnly) { + this.flash(`Slaves: Deadzone ${data.deadzone} LSB (${data.slaves_updated} per ESP-NOW, Master unverändert)`, true); return; } const who = clientId === 0 ? 'Master' : `Slave ${clientId}`; @@ -337,35 +369,33 @@ this.busy = false; } }, - async setDeadzoneAll(deadzone) { - if (deadzone == null || deadzone < 0) { - this.flash('Ungültiger Deadzone-Wert', false); - return; - } + async readMasterDeadzone() { this.busy = true; try { - const r = await fetch('/api/deadzone', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - write: true, - deadzone: deadzone, - client_id: 0, - all_clients: true - }) - }); + const r = await fetch('/api/deadzone?client_id=0'); const data = await r.json(); if (!r.ok || !data.success) { - this.flash(data.error || 'Deadzone für alle Slaves fehlgeschlagen', false); + this.flash(data.error || 'Master-Deadzone lesen fehlgeschlagen', false); return; } - this.flash(`Alle Slaves: Deadzone ${data.deadzone} LSB (${data.slaves_updated} per ESP-NOW)`, true); + this.masterDz = data.deadzone; + this.flash(`Master: Deadzone ${data.deadzone} LSB`, true); } catch (e) { this.flash(String(e), false); } finally { this.busy = false; } }, + async setMasterDeadzone() { + await this.setDeadzone(0, this.masterDz); + }, + async setDeadzoneAll(deadzone) { + if (deadzone == null || deadzone < 0) { + this.flash('Ungültiger Deadzone-Wert', false); + return; + } + await this.setDeadzone(0, deadzone, { allClients: true, slavesOnly: true }); + }, async unicastTest(clientId) { this.busy = true; try {