v1.8.13: Labels + Invertiert-Modus für Überschuss-Geräte
- Beschriftung über jedem Eingabefeld (Name, Z2M Name, Schwellwert, etc.) - Checkbox "Invertiert": EIN bei Netzbezug, AUS bei Überschuss - Live-Tab: ↑/↓ Pfeil zeigt normale/invertierte Logik Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,12 +36,17 @@ class SurplusDeviceController:
|
||||
threshold = float(dev.get("threshold_w", 500))
|
||||
hysteresis = float(dev.get("hysteresis_w", 150))
|
||||
min_on_s = float(dev.get("min_on_minutes", 0)) * 60
|
||||
inverted = bool(dev.get("inverted", False))
|
||||
currently_on = self._states.get(name, False)
|
||||
|
||||
if not currently_on and surplus_w >= threshold:
|
||||
# Invertiert: EIN bei Netzbezug (kein Überschuss), AUS bei Überschuss
|
||||
should_turn_on = not currently_on and (surplus_w >= threshold if not inverted else surplus_w < (threshold - hysteresis))
|
||||
should_turn_off = currently_on and (surplus_w < (threshold - hysteresis) if not inverted else surplus_w >= threshold)
|
||||
|
||||
if should_turn_on:
|
||||
self._send(name, True)
|
||||
self._on_since[name] = now
|
||||
elif currently_on and surplus_w < (threshold - hysteresis):
|
||||
elif should_turn_off:
|
||||
on_since = self._on_since.get(name, 0.0)
|
||||
if now - on_since >= min_on_s:
|
||||
self._send(name, False)
|
||||
|
||||
@@ -988,19 +988,31 @@ async function importConfig(input) {
|
||||
|
||||
let surplusDevices = [];
|
||||
|
||||
function field(label, input) {
|
||||
return `<div style="display:flex;flex-direction:column;gap:3px">
|
||||
<span style="font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.05em;white-space:nowrap">${label}</span>
|
||||
${input}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderSurplusDeviceRow(dev) {
|
||||
const id = dev.id || ('sd_' + Math.random().toString(36).slice(2));
|
||||
return `<div class="surplus-device-row" data-id="${esc(id)}">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||||
<input type="text" placeholder="Name" style="flex:1;min-width:110px" value="${esc(dev.name||'')}" data-field="name">
|
||||
<input type="text" placeholder="Z2M Friendly Name" list="z2m-device-list" style="flex:1;min-width:140px" value="${esc(dev.z2m_name||'')}" data-field="z2m_name">
|
||||
<input type="number" placeholder="Ab (W)" title="Schwellwert" style="width:76px" value="${dev.threshold_w||500}" min="0" step="50" data-field="threshold_w">
|
||||
<input type="number" placeholder="Hysterese (W)" title="Hysterese" style="width:90px" value="${dev.hysteresis_w||150}" min="0" step="50" data-field="hysteresis_w">
|
||||
<input type="number" placeholder="Min. Laufzeit (min)" title="Mindest-Laufzeit in Minuten" style="width:110px" value="${dev.min_on_minutes||0}" min="0" step="5" data-field="min_on_minutes">
|
||||
<label style="cursor:pointer;display:flex;align-items:center;gap:4px;font-size:12px;white-space:nowrap">
|
||||
<input type="checkbox" ${dev.enabled!==false?'checked':''} data-field="enabled" style="width:auto;margin:0"> Aktiv
|
||||
</label>
|
||||
<button class="btn btn-secondary" style="padding:3px 8px;font-size:12px;flex-shrink:0" onclick="removeSurplusDevice(this)">✕</button>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
|
||||
${field('Name', `<input type="text" placeholder="Heizstab Keller" style="width:130px" value="${esc(dev.name||'')}" data-field="name">`)}
|
||||
${field('Z2M Friendly Name', `<input type="text" placeholder="heizstab-keller" list="z2m-device-list" style="width:160px" value="${esc(dev.z2m_name||'')}" data-field="z2m_name">`)}
|
||||
${field('Schwellwert (W)', `<input type="number" style="width:90px" value="${dev.threshold_w||500}" min="0" step="50" data-field="threshold_w">`)}
|
||||
${field('Hysterese (W)', `<input type="number" style="width:90px" value="${dev.hysteresis_w||150}" min="0" step="50" data-field="hysteresis_w">`)}
|
||||
${field('Min. Laufzeit (min)', `<input type="number" style="width:90px" value="${dev.min_on_minutes||0}" min="0" step="5" data-field="min_on_minutes">`)}
|
||||
<div style="display:flex;flex-direction:column;gap:6px;padding-bottom:2px">
|
||||
<label style="cursor:pointer;display:flex;align-items:center;gap:4px;font-size:12px;white-space:nowrap">
|
||||
<input type="checkbox" ${dev.enabled!==false?'checked':''} data-field="enabled" style="width:auto;margin:0"> Aktiv
|
||||
</label>
|
||||
<label style="cursor:pointer;display:flex;align-items:center;gap:4px;font-size:12px;white-space:nowrap" title="AUS bei Überschuss, EIN bei Netzbezug">
|
||||
<input type="checkbox" ${dev.inverted?'checked':''} data-field="inverted" style="width:auto;margin:0"> Invertiert
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-secondary" style="padding:3px 8px;font-size:12px;flex-shrink:0;align-self:flex-end" onclick="removeSurplusDevice(this)">✕</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -1044,6 +1056,7 @@ function _collectSurplusDevices() {
|
||||
threshold_w: parseFloat(row.querySelector('[data-field=threshold_w]').value) || 0,
|
||||
hysteresis_w: parseFloat(row.querySelector('[data-field=hysteresis_w]').value) || 0,
|
||||
min_on_minutes: parseFloat(row.querySelector('[data-field=min_on_minutes]').value) || 0,
|
||||
inverted: row.querySelector('[data-field=inverted]').checked,
|
||||
enabled: row.querySelector('[data-field=enabled]').checked,
|
||||
}));
|
||||
}
|
||||
@@ -1086,7 +1099,7 @@ function renderSurplusStatus(surplusData) {
|
||||
return `<div style="display:flex;align-items:center;gap:6px;font-size:12px;padding:4px 0">
|
||||
${dot}
|
||||
<span>${esc(d.name||d.z2m_name)}</span>
|
||||
<span style="color:var(--text-dim);font-size:11px">ab ${d.threshold_w}W</span>
|
||||
<span style="color:var(--text-dim);font-size:11px">${d.inverted ? '↓' : '↑'} ${d.threshold_w}W</span>
|
||||
${info}
|
||||
<span style="margin-left:auto;font-weight:600;color:${on?'var(--green)':'var(--text-dim)'}">${on?'EIN':'AUS'}</span>
|
||||
</div>`;
|
||||
|
||||
Reference in New Issue
Block a user