Improve dashboard master config and separate slave deadzone updates.
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>
This commit is contained in:
parent
85aeab85c0
commit
80fb9cf55e
@ -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`.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body x-data="dashboard()" x-init="connect()">
|
||||
@ -145,9 +151,10 @@
|
||||
<div class="row g-4">
|
||||
<section class="col-lg-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Master</div>
|
||||
<div class="card-header">Master (BMA456 lokal)</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-2" x-text="'UART ' + (state.serial_port || '—')"></p>
|
||||
<p class="text-muted small mb-3">Deadzone nur am Master — Slaves bleiben unverändert.</p>
|
||||
<template x-if="!state.uart_connected">
|
||||
<div class="alert alert-warning py-2 mb-2"
|
||||
x-text="state.serial_error || 'UART disconnected — reconnecting…'"></div>
|
||||
@ -156,7 +163,7 @@
|
||||
<div class="alert alert-danger py-2" x-text="state.serial_error || 'Serial error'"></div>
|
||||
</template>
|
||||
<template x-if="state.master?.ok">
|
||||
<dl class="row mb-3">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-5 text-muted">Version</dt>
|
||||
<dd class="col-7" x-text="state.master.version"></dd>
|
||||
<dt class="col-5 text-muted">Git</dt>
|
||||
@ -164,21 +171,38 @@
|
||||
<dt class="col-5 text-muted">Deadzone</dt>
|
||||
<dd class="col-7" x-text="state.master.deadzone != null ? state.master.deadzone + ' LSB' : '—'"></dd>
|
||||
</dl>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center" x-show="state.uart_connected">
|
||||
<input type="number" class="form-control form-control-sm dz-input"
|
||||
min="0" max="4095" placeholder="LSB"
|
||||
x-model.number="masterDz"
|
||||
:disabled="busy">
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setDeadzone(0, masterDz)"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
Master setzen
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="state.master && !state.master.ok">
|
||||
<div class="alert alert-warning py-2" x-text="state.master.error"></div>
|
||||
<div class="alert alert-warning py-2 mb-0" x-text="state.master.error"></div>
|
||||
</template>
|
||||
|
||||
<div class="config-block">
|
||||
<h6 class="text-secondary mb-2">Konfiguration Master</h6>
|
||||
<label class="form-label text-muted small mb-1" for="master-dz-input">
|
||||
Accel Deadzone (LSB)
|
||||
</label>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-end">
|
||||
<input id="master-dz-input" type="number"
|
||||
class="form-control form-control-sm config-input"
|
||||
min="0" max="4095" step="1"
|
||||
placeholder="z. B. 100"
|
||||
x-model.number="masterDz"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="readMasterDeadzone()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
Lesen
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setMasterDeadzone()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
Setzen
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-muted small mt-2 mb-0" x-show="!state.uart_connected">
|
||||
UART nicht verbunden — Eingabe gesperrt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -200,7 +224,8 @@
|
||||
<span class="badge bg-secondary" x-text="(state.clients || []).length + ' registered'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<p class="text-muted small px-3 pt-2 mb-0">Slaves per ESP-NOW — Master-Deadzone bleibt separat.</p>
|
||||
<div class="card-body p-0 pt-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table pp-table table-hover">
|
||||
<thead>
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user