Feature: EMS-Konfiguration im Web-UI (v1.5.1)
- Modal zeigt EMS-Felder nur bei KATHREIN_WALLBOX (toggleEmsSection) - openModal lädt ems_min_pv/timeout/target_hour/phases aus Gerätekonfig - saveInverter speichert EMS-Parameter in inverters.json - main.py übergibt EMS-Konfig aus inv_cfg an EmsController() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -188,7 +188,12 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
|||||||
inv_id, inverter.name, host, interval)
|
inv_id, inverter.name, host, interval)
|
||||||
elif inverter.protocol == "kathrein":
|
elif inverter.protocol == "kathrein":
|
||||||
reader = WallboxReader(host=host, port=port)
|
reader = WallboxReader(host=host, port=port)
|
||||||
ems = EmsController()
|
ems = EmsController(
|
||||||
|
min_pv_power=inv_cfg.get("ems_min_pv", 1400),
|
||||||
|
pv_timeout_h=inv_cfg.get("ems_timeout", 4.0),
|
||||||
|
target_hour=inv_cfg.get("ems_target_hour", 6),
|
||||||
|
phases=inv_cfg.get("ems_phases", 3),
|
||||||
|
)
|
||||||
log.info("[%s] Poll-Loop: %s @ %s:%s (Kathrein EMS) alle %ds",
|
log.info("[%s] Poll-Loop: %s @ %s:%s (Kathrein EMS) alle %ds",
|
||||||
inv_id, inverter.name, host, port, interval)
|
inv_id, inverter.name, host, port, interval)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -226,6 +226,26 @@
|
|||||||
<input type="text" id="modal-prefix" placeholder="growatt/wechselrichter1"></div>
|
<input type="text" id="modal-prefix" placeholder="growatt/wechselrichter1"></div>
|
||||||
<div class="field"><label>Abfrageintervall (Sekunden)</label>
|
<div class="field"><label>Abfrageintervall (Sekunden)</label>
|
||||||
<input type="number" id="modal-interval" value="30" min="5"></div>
|
<input type="number" id="modal-interval" value="30" min="5"></div>
|
||||||
|
|
||||||
|
<div id="ems-section" style="display:none; border-top:1px solid var(--border); margin-top:14px; padding-top:14px;">
|
||||||
|
<div style="font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.06em; color:var(--accent); margin-bottom:12px;">EMS — Ladesteuerung</div>
|
||||||
|
<div class="field"><label>Mindest-PV-Überschuss (W)</label>
|
||||||
|
<input type="number" id="ems-min-pv" value="1400" min="100" step="100">
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Mindestleistung für PV-Laden (min. 6A × 230V = 1380W)</div></div>
|
||||||
|
<div class="field"><label>Timeout ohne PV (Stunden)</label>
|
||||||
|
<input type="number" id="ems-timeout" value="4" min="0.5" max="24" step="0.5">
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Nach dieser Zeit ohne Überschuss startet Zwangsladen</div></div>
|
||||||
|
<div class="field"><label>Vollladung bis (Uhr)</label>
|
||||||
|
<input type="number" id="ems-target-hour" value="6" min="0" max="23">
|
||||||
|
<div style="font-size:11px;color:var(--muted);margin-top:3px">Zielzeit für vollgeladenes Auto (z.B. 6 = 06:00 Uhr)</div></div>
|
||||||
|
<div class="field"><label>Anzahl Phasen</label>
|
||||||
|
<select id="ems-phases">
|
||||||
|
<option value="1">1-phasig</option>
|
||||||
|
<option value="2">2-phasig</option>
|
||||||
|
<option value="3" selected>3-phasig</option>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn" onclick="closeModal()">Abbrechen</button>
|
<button class="btn" onclick="closeModal()">Abbrechen</button>
|
||||||
<button class="btn btn-primary" onclick="saveInverter()">Speichern</button>
|
<button class="btn btn-primary" onclick="saveInverter()">Speichern</button>
|
||||||
@@ -437,20 +457,31 @@ function renderInverterList() {
|
|||||||
el.innerHTML = cards + `<div class="add-btn" onclick="openModal()">+ Gerät hinzufügen</div>`;
|
el.innerHTML = cards + `<div class="add-btn" onclick="openModal()">+ Gerät hinzufügen</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleEmsSection(model) {
|
||||||
|
document.getElementById("ems-section").style.display =
|
||||||
|
model === "KATHREIN_WALLBOX" ? "" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
async function openModal(invId) {
|
async function openModal(invId) {
|
||||||
await loadModels();
|
await loadModels();
|
||||||
const modal = document.getElementById("modal-backdrop");
|
const modal = document.getElementById("modal-backdrop");
|
||||||
|
const modelSel = document.getElementById("modal-model");
|
||||||
|
modelSel.onchange = () => toggleEmsSection(modelSel.value);
|
||||||
if (invId) {
|
if (invId) {
|
||||||
const inv = invertersList.find(i => i.id === invId) || {};
|
const inv = invertersList.find(i => i.id === invId) || {};
|
||||||
document.getElementById("modal-title").textContent = "Gerät bearbeiten";
|
document.getElementById("modal-title").textContent = "Gerät bearbeiten";
|
||||||
document.getElementById("modal-id").value = inv.id || "";
|
document.getElementById("modal-id").value = inv.id || "";
|
||||||
document.getElementById("modal-name").value = inv.name || "";
|
document.getElementById("modal-name").value = inv.name || "";
|
||||||
document.getElementById("modal-model").value = inv.inverter_model || "MIC_1500_TL_X";
|
modelSel.value = inv.inverter_model || "MIC_1500_TL_X";
|
||||||
document.getElementById("modal-ip").value = inv.modbus_ip || "";
|
document.getElementById("modal-ip").value = inv.modbus_ip || "";
|
||||||
document.getElementById("modal-port").value = inv.modbus_port || 502;
|
document.getElementById("modal-port").value = inv.modbus_port || 502;
|
||||||
document.getElementById("modal-addr").value = inv.modbus_address || 1;
|
document.getElementById("modal-addr").value = inv.modbus_address || 1;
|
||||||
document.getElementById("modal-prefix").value = inv.mqtt_topic_prefix || "";
|
document.getElementById("modal-prefix").value = inv.mqtt_topic_prefix || "";
|
||||||
document.getElementById("modal-interval").value = inv.update_interval || 30;
|
document.getElementById("modal-interval").value = inv.update_interval || 30;
|
||||||
|
document.getElementById("ems-min-pv").value = inv.ems_min_pv ?? 1400;
|
||||||
|
document.getElementById("ems-timeout").value = inv.ems_timeout ?? 4;
|
||||||
|
document.getElementById("ems-target-hour").value = inv.ems_target_hour ?? 6;
|
||||||
|
document.getElementById("ems-phases").value = inv.ems_phases ?? 3;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("modal-title").textContent = "Gerät hinzufügen";
|
document.getElementById("modal-title").textContent = "Gerät hinzufügen";
|
||||||
document.getElementById("modal-id").value = "";
|
document.getElementById("modal-id").value = "";
|
||||||
@@ -460,7 +491,12 @@ async function openModal(invId) {
|
|||||||
document.getElementById("modal-addr").value = "1";
|
document.getElementById("modal-addr").value = "1";
|
||||||
document.getElementById("modal-prefix").value = "growatt/wechselrichter" + (invertersList.length + 1);
|
document.getElementById("modal-prefix").value = "growatt/wechselrichter" + (invertersList.length + 1);
|
||||||
document.getElementById("modal-interval").value = "30";
|
document.getElementById("modal-interval").value = "30";
|
||||||
|
document.getElementById("ems-min-pv").value = "1400";
|
||||||
|
document.getElementById("ems-timeout").value = "4";
|
||||||
|
document.getElementById("ems-target-hour").value = "6";
|
||||||
|
document.getElementById("ems-phases").value = "3";
|
||||||
}
|
}
|
||||||
|
toggleEmsSection(modelSel.value);
|
||||||
modal.classList.add("open");
|
modal.classList.add("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,16 +513,23 @@ async function saveInverter() {
|
|||||||
const r = await fetchJSON(api("api/new-id"), {method: "POST"});
|
const r = await fetchJSON(api("api/new-id"), {method: "POST"});
|
||||||
id = r.id;
|
id = r.id;
|
||||||
}
|
}
|
||||||
|
const model = document.getElementById("modal-model").value;
|
||||||
const inv = {
|
const inv = {
|
||||||
id,
|
id,
|
||||||
name: document.getElementById("modal-name").value.trim() || "Wechselrichter",
|
name: document.getElementById("modal-name").value.trim() || "Wechselrichter",
|
||||||
inverter_model: document.getElementById("modal-model").value,
|
inverter_model: model,
|
||||||
modbus_ip: document.getElementById("modal-ip").value.trim(),
|
modbus_ip: document.getElementById("modal-ip").value.trim(),
|
||||||
modbus_port: parseInt(document.getElementById("modal-port").value),
|
modbus_port: parseInt(document.getElementById("modal-port").value),
|
||||||
modbus_address: parseInt(document.getElementById("modal-addr").value),
|
modbus_address: parseInt(document.getElementById("modal-addr").value),
|
||||||
mqtt_topic_prefix: document.getElementById("modal-prefix").value.trim(),
|
mqtt_topic_prefix: document.getElementById("modal-prefix").value.trim(),
|
||||||
update_interval: parseInt(document.getElementById("modal-interval").value),
|
update_interval: parseInt(document.getElementById("modal-interval").value),
|
||||||
};
|
};
|
||||||
|
if (model === "KATHREIN_WALLBOX") {
|
||||||
|
inv.ems_min_pv = parseInt(document.getElementById("ems-min-pv").value);
|
||||||
|
inv.ems_timeout = parseFloat(document.getElementById("ems-timeout").value);
|
||||||
|
inv.ems_target_hour = parseInt(document.getElementById("ems-target-hour").value);
|
||||||
|
inv.ems_phases = parseInt(document.getElementById("ems-phases").value);
|
||||||
|
}
|
||||||
const idx = invertersList.findIndex(i => i.id === id);
|
const idx = invertersList.findIndex(i => i.id === id);
|
||||||
if (idx >= 0) invertersList[idx] = inv; else invertersList.push(inv);
|
if (idx >= 0) invertersList[idx] = inv; else invertersList.push(inv);
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user