Add runtime UV protection toggle and treat near-zero sense as external power.
Per-pod NVS enable (default off) via UART/ESP-NOW/dashboard, and skip UV latch when LiPo ADC reads ~0 mV so USB/external supply still boots. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+134
-1
@@ -383,6 +383,11 @@
|
||||
<dd class="col-7" x-text="formatLipo(state.master?.lipo1)"></dd>
|
||||
<dt class="col-5 text-muted">LiPo 2</dt>
|
||||
<dd class="col-7" x-text="formatLipo(state.master?.lipo2)"></dd>
|
||||
<dt class="col-5 text-muted">UV-Schutz</dt>
|
||||
<dd class="col-7">
|
||||
<span x-text="state.master?.uv_protection == null ? '—' : (state.master.uv_protection ? 'an' : 'aus')"></span>
|
||||
<span class="text-warning small ms-1" x-show="state.master?.uv_latched">· gelatcht</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</template>
|
||||
<template x-if="state.master && !state.master.ok">
|
||||
@@ -512,6 +517,31 @@
|
||||
Setzen
|
||||
</button>
|
||||
</div>
|
||||
<h6 class="text-secondary mb-2 mt-3">UV-Schutz (LiPo)</h6>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox"
|
||||
:checked="!!state.master?.uv_protection"
|
||||
@change="setBatteryUv(0, $event.target.checked)"
|
||||
:disabled="busy || !hostUp()">
|
||||
Master aktiv
|
||||
</label>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="setBatteryUvAll(true)"
|
||||
:disabled="busy || !hostUp()"
|
||||
title="UV-Schutz auf Master + alle Slaves einschalten">
|
||||
Alle an
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm"
|
||||
@click="setBatteryUvAll(false)"
|
||||
:disabled="busy || !hostUp()"
|
||||
title="UV-Schutz auf Master + alle Slaves ausschalten (cleart Latch)">
|
||||
Alle aus
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-muted small mt-1 mb-0">
|
||||
Runtime-NVS pro Pod. Im Energiesparmodus (gelatcht) kein Host-Zugriff.
|
||||
</p>
|
||||
<p class="text-muted small mt-2 mb-0" x-show="!hostUp()">
|
||||
Host nicht verbunden — Eingabe gesperrt.
|
||||
</p>
|
||||
@@ -540,6 +570,12 @@
|
||||
title="Master-Tap-Config an alle Slaves (ESP-NOW)">
|
||||
Tap-Cfg alle
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm"
|
||||
@click="setBatteryUvSlavesOnly(false)"
|
||||
:disabled="busy || !hostUp() || !(state.clients || []).length"
|
||||
title="UV-Schutz nur auf allen Slaves ausschalten">
|
||||
UV Slaves aus
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm"
|
||||
@click="resetClientIdMap()"
|
||||
:disabled="busy || !hostUp()"
|
||||
@@ -611,6 +647,7 @@
|
||||
<th>Tap-Cfg</th>
|
||||
<th>Accel (LSB)</th>
|
||||
<th>Akku</th>
|
||||
<th>UV</th>
|
||||
<th>Accel</th>
|
||||
<th>Tap-Notify</th>
|
||||
<th>Tap</th>
|
||||
@@ -619,7 +656,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-if="!(state.clients || []).length">
|
||||
<tr><td colspan="12" class="text-muted text-center py-4">No clients</td></tr>
|
||||
<tr><td colspan="13" 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>
|
||||
@@ -648,6 +685,14 @@
|
||||
:title="accelTitle(c)"></span>
|
||||
</td>
|
||||
<td class="small" x-text="formatLipoPair(c)" :title="lipoTitle(c)"></td>
|
||||
<td>
|
||||
<label class="tap-toggle mb-0" title="Software-UV-Schutz (NVS auf Slave)">
|
||||
<input type="checkbox"
|
||||
:checked="!!c.uv_protection"
|
||||
@change="setBatteryUv(c.id, $event.target.checked)"
|
||||
:disabled="busy || !hostUp() || !c.available">
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button"
|
||||
class="btn btn-sm"
|
||||
@@ -1783,6 +1828,94 @@
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
patchBatteryUv(clientId, enabled, latched) {
|
||||
if (clientId === 0) {
|
||||
const master = {
|
||||
...(this.state.master || {}),
|
||||
uv_protection: !!enabled,
|
||||
uv_latched: !!latched
|
||||
};
|
||||
this.state = { ...this.state, master };
|
||||
return;
|
||||
}
|
||||
const clients = (this.state.clients || []).map((c) =>
|
||||
c.id === clientId ? { ...c, uv_protection: !!enabled } : c);
|
||||
this.state = { ...this.state, clients };
|
||||
},
|
||||
async setBatteryUv(clientId, enabled) {
|
||||
this.busy = true;
|
||||
try {
|
||||
const r = await fetch('/api/battery-uv', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ write: true, enabled: !!enabled, client_id: clientId })
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.success) {
|
||||
this.flash(data.error || `UV-Schutz ${clientId === 0 ? 'Master' : 'Slave ' + clientId} fehlgeschlagen`, false);
|
||||
return;
|
||||
}
|
||||
this.patchBatteryUv(clientId, data.enabled, data.latched);
|
||||
const who = clientId === 0 ? 'Master' : `Slave ${clientId}`;
|
||||
this.flash(`${who}: UV-Schutz ${data.enabled ? 'an' : 'aus'}`, true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
async setBatteryUvAll(enabled) {
|
||||
this.busy = true;
|
||||
try {
|
||||
const r = await fetch('/api/battery-uv', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ write: true, enabled: !!enabled, all_clients: true })
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.success) {
|
||||
this.flash(data.error || 'UV-Schutz für alle fehlgeschlagen', false);
|
||||
return;
|
||||
}
|
||||
this.patchBatteryUv(0, data.enabled, data.latched);
|
||||
for (const c of (this.state.clients || [])) {
|
||||
this.patchBatteryUv(c.id, !!enabled, false);
|
||||
}
|
||||
this.flash(`UV-Schutz alle ${enabled ? 'an' : 'aus'} (${data.slaves_updated || 0} Slaves)`, true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
async setBatteryUvSlavesOnly(enabled) {
|
||||
this.busy = true;
|
||||
try {
|
||||
const r = await fetch('/api/battery-uv', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
write: true,
|
||||
enabled: !!enabled,
|
||||
all_clients: true,
|
||||
slaves_only: true
|
||||
})
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.success) {
|
||||
this.flash(data.error || 'UV-Schutz Slaves fehlgeschlagen', false);
|
||||
return;
|
||||
}
|
||||
for (const c of (this.state.clients || [])) {
|
||||
this.patchBatteryUv(c.id, !!enabled, false);
|
||||
}
|
||||
this.flash(`UV-Schutz Slaves ${enabled ? 'an' : 'aus'} (${data.slaves_updated || 0})`, true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.busy = false;
|
||||
}
|
||||
},
|
||||
patchLiveStream(enabled) {
|
||||
let clients = this.state.clients || [];
|
||||
if (!enabled) {
|
||||
|
||||
Reference in New Issue
Block a user