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:
+191
-2
@@ -291,6 +291,69 @@
|
||||
Setzen
|
||||
</button>
|
||||
</div>
|
||||
<h6 class="text-secondary mb-2 mt-3">Tap-Erkennung (BMA456)</h6>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-sens">sens (0–15)</label>
|
||||
<input id="tap-sens" type="number" class="form-control form-control-sm"
|
||||
min="0" max="15" x-model.number="masterTapCfg.tap_sens_thres"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-max-gest">max_gest (×5ms)</label>
|
||||
<input id="tap-max-gest" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.max_gest_dur"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-shock">shock (×5ms)</label>
|
||||
<input id="tap-shock" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.tap_shock_dur"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-quiet">quiet (×5ms)</label>
|
||||
<input id="tap-quiet" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.quite_time_after_gest"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-wait">wait (0|1)</label>
|
||||
<input id="tap-wait" type="number" class="form-control form-control-sm"
|
||||
min="0" max="1" x-model.number="masterTapCfg.wait_for_timeout"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-axis">axis (0=X…2=Z)</label>
|
||||
<input id="tap-axis" type="number" class="form-control form-control-sm"
|
||||
min="0" max="2" x-model.number="masterTapCfg.axis_sel"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center mt-2 small">
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_single"
|
||||
:disabled="busy || !state.uart_connected"> Single
|
||||
</label>
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_double"
|
||||
:disabled="busy || !state.uart_connected"> Double
|
||||
</label>
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_triple"
|
||||
:disabled="busy || !state.uart_connected"> Triple
|
||||
</label>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="readMasterTapConfig()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
Lesen
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setMasterTapConfig()"
|
||||
: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>
|
||||
@@ -313,6 +376,12 @@
|
||||
:disabled="busy || !state.uart_connected">
|
||||
Alle Slaves
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="setTapConfigAll()"
|
||||
:disabled="busy || !state.uart_connected"
|
||||
title="Master-Tap-Config an alle Slaves (ESP-NOW)">
|
||||
Tap-Cfg alle
|
||||
</button>
|
||||
<span class="badge bg-secondary" x-text="(state.clients || []).length + ' registered'"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -348,6 +417,7 @@
|
||||
<th>Ver</th>
|
||||
<th>Status</th>
|
||||
<th>Deadzone</th>
|
||||
<th>Tap-Cfg</th>
|
||||
<th>Accel (LSB)</th>
|
||||
<th>Akku</th>
|
||||
<th>Accel</th>
|
||||
@@ -358,7 +428,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-if="!(state.clients || []).length">
|
||||
<tr><td colspan="11" class="text-muted text-center py-4">No clients</td></tr>
|
||||
<tr><td colspan="12" class="text-muted text-center py-4">No clients</td></tr>
|
||||
</template>
|
||||
<template x-for="c in (state.clients || [])" :key="c.id + c.mac">
|
||||
<tr>
|
||||
@@ -371,6 +441,15 @@
|
||||
x-text="c.available ? 'available' : 'inactive'"></span>
|
||||
</td>
|
||||
<td x-text="c.deadzone != null ? c.deadzone : '—'"></td>
|
||||
<td class="small">
|
||||
<span x-text="formatTapCfg(c.tap_config)" :title="tapCfgTitle(c.tap_config)"></span>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm ms-1 py-0 px-1"
|
||||
@click="setTapConfig(c.id, masterTapCfg)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
title="Master-Tap-Config an diesen Slave">
|
||||
Set
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<span class="accel"
|
||||
:class="accelCellClass(c)"
|
||||
@@ -655,7 +734,20 @@
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
masterDz: 100,
|
||||
masterDzSeeded: false,
|
||||
masterLogLevel: 0,
|
||||
masterTapCfg: {
|
||||
tap_sens_thres: 5,
|
||||
max_gest_dur: 100,
|
||||
tap_shock_dur: 6,
|
||||
quite_time_after_gest: 60,
|
||||
wait_for_timeout: 0,
|
||||
axis_sel: 2,
|
||||
enable_single: true,
|
||||
enable_double: true,
|
||||
enable_triple: true
|
||||
},
|
||||
masterTapCfgSeeded: false,
|
||||
allDz: 100,
|
||||
allTapSingle: false,
|
||||
allTapDouble: false,
|
||||
@@ -719,8 +811,13 @@
|
||||
this.state = msg;
|
||||
this.preserveBatteryInState(prev, this.state);
|
||||
this.syncTapDisplay(msg.clients || []);
|
||||
if (msg.master?.deadzone != null) {
|
||||
if (!this.masterDzSeeded && msg.master?.deadzone != null) {
|
||||
this.masterDz = msg.master.deadzone;
|
||||
this.masterDzSeeded = true;
|
||||
}
|
||||
if (!this.masterTapCfgSeeded && msg.master?.tap_config) {
|
||||
this.applyMasterTapCfg(msg.master.tap_config);
|
||||
this.masterTapCfgSeeded = true;
|
||||
}
|
||||
for (const c of (msg.clients || [])) {
|
||||
if (c.deadzone != null && this.slaveDz[c.id] == null) {
|
||||
@@ -1124,6 +1221,10 @@
|
||||
this.flash(`Slaves: Deadzone ${data.deadzone} LSB (${data.slaves_updated} per ESP-NOW, Master unverändert)`, true);
|
||||
return;
|
||||
}
|
||||
if (clientId === 0) {
|
||||
this.masterDz = data.deadzone;
|
||||
this.masterDzSeeded = true;
|
||||
}
|
||||
const who = clientId === 0 ? 'Master' : `Slave ${clientId}`;
|
||||
this.flash(`${who}: Deadzone ${data.deadzone} LSB gesetzt`, true);
|
||||
} catch (e) {
|
||||
@@ -1142,6 +1243,7 @@
|
||||
return;
|
||||
}
|
||||
this.masterDz = data.deadzone;
|
||||
this.masterDzSeeded = true;
|
||||
this.flash(`Master: Deadzone ${data.deadzone} LSB`, true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
@@ -1152,6 +1254,93 @@
|
||||
async setMasterDeadzone() {
|
||||
await this.setDeadzone(0, this.masterDz);
|
||||
},
|
||||
formatTapCfg(tc) {
|
||||
if (!tc) return '—';
|
||||
return `s${tc.tap_sens_thres}`;
|
||||
},
|
||||
tapCfgTitle(tc) {
|
||||
if (!tc) return '';
|
||||
const kinds = [tc.enable_single && 'S', tc.enable_double && 'D', tc.enable_triple && 'T'].filter(Boolean).join('/');
|
||||
return `sens=${tc.tap_sens_thres} max_gest=${tc.max_gest_dur} shock=${tc.tap_shock_dur} quiet=${tc.quite_time_after_gest} wait=${tc.wait_for_timeout} axis=${tc.axis_sel} (${kinds || 'aus'})`;
|
||||
},
|
||||
applyMasterTapCfg(data) {
|
||||
if (!data) return;
|
||||
this.masterTapCfg = {
|
||||
tap_sens_thres: data.tap_sens_thres,
|
||||
max_gest_dur: data.max_gest_dur,
|
||||
tap_shock_dur: data.tap_shock_dur,
|
||||
quite_time_after_gest: data.quite_time_after_gest,
|
||||
wait_for_timeout: data.wait_for_timeout,
|
||||
axis_sel: data.axis_sel,
|
||||
enable_single: data.enable_single,
|
||||
enable_double: data.enable_double,
|
||||
enable_triple: data.enable_triple
|
||||
};
|
||||
this.masterTapCfgSeeded = true;
|
||||
},
|
||||
async setTapConfig(clientId, cfg, opts = {}) {
|
||||
if (!cfg) {
|
||||
this.flash('Keine Tap-Config', false);
|
||||
return;
|
||||
}
|
||||
this.busy = true;
|
||||
try {
|
||||
const r = await fetch('/api/tap-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
write: true,
|
||||
client_id: clientId,
|
||||
all_clients: !!opts.allClients,
|
||||
slaves_only: !!opts.slavesOnly,
|
||||
...cfg
|
||||
})
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.success) {
|
||||
const err = data.error ||
|
||||
(opts.slavesOnly ? 'Tap-Config für Slaves fehlgeschlagen' : `Tap-Config für Client ${clientId} fehlgeschlagen`);
|
||||
this.flash(err, false);
|
||||
return;
|
||||
}
|
||||
if (opts.slavesOnly) {
|
||||
this.flash(`Slaves: Tap-Config gesetzt (${data.slaves_updated} per ESP-NOW, Master unverändert)`, true);
|
||||
return;
|
||||
}
|
||||
const who = clientId === 0 ? 'Master' : `Slave ${clientId}`;
|
||||
this.flash(`${who}: Tap-Config gesetzt (sens=${data.tap_sens_thres})`, true);
|
||||
if (clientId === 0) {
|
||||
this.applyMasterTapCfg(data);
|
||||
}
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
async readMasterTapConfig() {
|
||||
this.busy = true;
|
||||
try {
|
||||
const r = await fetch('/api/tap-config?client_id=0');
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.success) {
|
||||
this.flash(data.error || 'Master-Tap-Config lesen fehlgeschlagen', false);
|
||||
return;
|
||||
}
|
||||
this.applyMasterTapCfg(data);
|
||||
this.flash(`Master: Tap-Config gelesen (sens=${data.tap_sens_thres})`, true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
async setMasterTapConfig() {
|
||||
await this.setTapConfig(0, this.masterTapCfg);
|
||||
},
|
||||
async setTapConfigAll() {
|
||||
await this.setTapConfig(0, this.masterTapCfg, { allClients: true, slavesOnly: true });
|
||||
},
|
||||
async readMasterLogLevel() {
|
||||
this.busy = true;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user