diff --git a/ajax/watering.php b/ajax/watering.php index f4c21ba..65d00bf 100644 --- a/ajax/watering.php +++ b/ajax/watering.php @@ -81,12 +81,6 @@ $zonen = [ ["key" => "frontGarden", "label" => "Vorn", "minuten" => 30], ]; -// Was die Automatik je Zone vorhat, fuellt solarMQTT.js aus dem RainStatus. -$planZeilen = ""; -foreach ($zonen as $zone) { - $planZeilen .= "
{$zone['label']}: --
\n"; -} - echo <<
@@ -103,7 +97,9 @@ echo <<
-$planZeilen
+ Startet die Ventilfolge von Hand. Die Regenautomatik läuft davon + unabhängig zum Sonnenaufgang; ob eine Zone dabei schon versorgt + wurde, steht unten bei „Heute“.
diff --git a/js/solar/solarMQTT.js b/js/solar/solarMQTT.js index 573e250..05af272 100644 --- a/js/solar/solarMQTT.js +++ b/js/solar/solarMQTT.js @@ -38,7 +38,6 @@ const solarMQTT = { client.subscribe("Gartenwasser/hinten/LastWatering"); client.subscribe("Gartenwasser/hinten/WateringToday"); client.subscribe("Gartenwasser/hinten/LastWateringDay"); - client.subscribe("Gartenwasser/AutoWatering/#"); }); client.on("error", function (error) { //alert("MQTT Error: " + error); @@ -69,7 +68,7 @@ const solarMQTT = { if (topic === "Gartenwasser/vorn/Timers" || topic === "Gartenwasser/hinten/Timers") { wateringTimersLastSeen[topic] = Date.now(); } - if (topic.startsWith("Gartenwasser/") && (topic.endsWith("/status") || topic.endsWith("/Timers") || topic.endsWith("/LastWatering") || topic.endsWith("/RainStatus") + if (topic.startsWith("Gartenwasser/") && (topic.endsWith("/status") || topic.endsWith("/Timers") || topic.endsWith("/LastWatering") || topic.endsWith("/WateringToday") || topic.endsWith("/LastWateringDay"))) { updateWateringStatus(); } @@ -729,20 +728,20 @@ var chartSettings = { } const wateringTimersLastSeen = {}; - // Eine Tabelle fuer alles: Ventil, Automatik-Zone des Skripts, Beschriftung - // und die Anzeige-Ids im Realtime-SVG. Vorher standen dieselben Zuordnungen - // in drei getrennten Tabellen. + // Eine Tabelle fuer alles: Ventil, Beschriftung und die Anzeige-Ids im + // Realtime-SVG. Vorher standen dieselben Zuordnungen in drei getrennten + // Tabellen. const wateringZones = { troughs: { - topic: "Gartenwasser/vorn", valve: 1, auto: "troege", label: "Tröge", + topic: "Gartenwasser/vorn", valve: 1, label: "Tröge", statusId: "wateringTroughStatus", iconId: "wateringTroughIcon" }, frontGarden: { - topic: "Gartenwasser/vorn", valve: 6, auto: "garten_vorn", label: "Vorn", + topic: "Gartenwasser/vorn", valve: 6, label: "Vorn", statusId: "wateringFrontStatus", iconId: "wateringFrontIcon" }, raisedBeds: { - topic: "Gartenwasser/hinten", valve: 6, auto: "hochbeete", label: "Beete", + topic: "Gartenwasser/hinten", valve: 6, label: "Beete", statusId: "wateringBedsStatus", iconId: "wateringBedsIcon" } }; @@ -854,12 +853,14 @@ var chartSettings = { // "heute" in der Karte. function wateringDayTotal(topic, subTopic, feld, valve, mussHeuteSein) { const data = wateringJson(topic + "/" + subTopic); - if (!data) return { label: "--", day: "" }; + if (!data) return { label: "--", day: "", sekunden: null }; const tag = data.wateringDay || ""; const sekunden = data[feld + valve]; - if (sekunden === undefined) return { label: "--", day: tag }; - if (mussHeuteSein && tag !== wateringDayStamp(new Date())) return { label: "--", day: tag }; - return { label: wateringTimeLabel(Number(sekunden)), day: tag }; + if (sekunden === undefined) return { label: "--", day: tag, sekunden: null }; + if (mussHeuteSein && tag !== wateringDayStamp(new Date())) { + return { label: "--", day: tag, sekunden: null }; + } + return { label: wateringTimeLabel(Number(sekunden)), day: tag, sekunden: Number(sekunden) }; } function wateringAutoActive(topic, mode) { @@ -931,101 +932,35 @@ var chartSettings = { return remaining !== null && wateringSeconds(remaining) < 0; } - // Liest den von auto_watering.py stündlich veröffentlichten Regen-Status - // (retained, siehe Gartenwasser/AutoWatering//RainStatus) und sagt, - // ob für die Zone heute noch eine automatische Bewässerung aussteht. - function wateringAutoPlanned(autoZoneKey) { - const status = wateringJson("Gartenwasser/AutoWatering/" + autoZoneKey + "/RainStatus"); - return !!(status && status.would_water && !status.watered_recently); - } - - // Zeitraeume und Regenmengen in Klartext. "2 T." war zu kryptisch. - function wateringTage(tage) { - const zahl = Number(tage); - const text = Number.isInteger(zahl) ? String(zahl) : String(zahl).replace(".", ","); - return zahl === 1 ? text + " Tag" : text + " Tagen"; - } - - function wateringMm(wert) { - return String(wert).replace(".", ",") + " mm"; - } - - // Sagt in einem Satz, was die Automatik fuer die Zone vorhat und warum. - // Speist sich aus derselben RainStatus-Nachricht wie die Anzeige im SVG. - // Der Titel nennt zusaetzlich die Regel, nach der entschieden wird - die - // Schwellenwerte unterscheiden sich je Zone deutlich. - function wateringPlanText(autoZoneKey) { - const status = wateringJson("Gartenwasser/AutoWatering/" + autoZoneKey + "/RainStatus"); - if (!status) return { text: "keine Planungsdaten", titel: "" }; - - const regel = []; - if (status.threshold_mm !== undefined && status.threshold_days !== undefined) { - regel.push("Bewässert wird, wenn in " + wateringTage(status.threshold_days) - + " weniger als " + wateringMm(status.threshold_mm) + " Regen fallen."); - } - if (status.max_dry_days) { - regel.push("Ebenso, wenn seit " + wateringTage(status.max_dry_days) + " nicht bewässert wurde."); - } - const titel = regel.join(" "); - - if (status.watered_recently) { - const zeitpunkt = status.last_watering - ? " – zuletzt " + String(status.last_watering).replace(/[.]\d{4} /, ". ") - : ""; - return { text: "erledigt" + zeitpunkt, titel: titel }; - } - const regen = status.rain_mm !== undefined && status.threshold_days !== undefined - ? wateringMm(status.rain_mm) + " Regen in " + wateringTage(status.threshold_days) - : ""; - if (!status.would_water) { - return { text: "kein Bedarf" + (regen ? " – " + regen : ""), titel: titel }; - } - if (status.forced_dry) { - const trocken = status.max_dry_days - ? " – seit " + wateringTage(status.max_dry_days) + " nicht bewässert" - : " – lange nicht bewässert"; - return { text: "heute geplant" + trocken, titel: titel }; - } - return { text: "heute geplant" + (regen ? " – nur " + regen : ""), titel: titel }; - } - - // Alle Zonen stammen aus demselben Lauf des Skripts - der erste gefundene - // Zeitstempel steht daher fuer alle. Die Sekunden schneidet slice(0, 16) ab. - function wateringPlanUpdated() { - for (const [, zone] of wateringZoneList) { - const status = wateringJson("Gartenwasser/AutoWatering/" + zone.auto + "/RainStatus"); - if (status && status.updated) return "Planung vom " + String(status.updated).slice(0, 16); - } - return ""; - } - function updateWateringStatus() { wateringZoneList.forEach(([key, zone]) => { const state = wateringLabel(zone.topic, zone.valve); const paused = wateringIsPaused(zone.topic, zone.valve); - const planned = state === "aus" && wateringAutoPlanned(zone.auto); + const heute = wateringDayTotal(zone.topic, "WateringToday", "wateringDurationTodaySecs", zone.valve, true); + // Die Regenautomatik laeuft zum Sonnenaufgang; wenn jemand auf die + // Anzeige schaut, ist die Entscheidung laengst gefallen. Angezeigt wird + // deshalb nicht mehr, was geplant ist, sondern was passiert ist. Der + // Zaehler unterscheidet nicht, wer ausgeloest hat - eine Bewaesserung + // von Hand zaehlt genauso, und das ist so gewollt. + const bewaessert = state === "aus" && heute.sekunden > 0; // Die Anzeigen liegen im Realtime-SVG und bleiben bestehen -> svgElement. const status = svgElement(zone.statusId); const icon = svgElement(zone.iconId); - if (status && icon) updateWateringIndicator(status, icon, zone.label, state, paused, planned); + if (status && icon) updateWateringIndicator(status, icon, zone.label, state, paused, bewaessert); // Die Modal-Elemente werden bei jedem Oeffnen neu erzeugt -> nicht cachen. setzeZonenButton(document.querySelector(`[data-watering-toggle][data-watering-zone='${key}']`), state); - setzeSchild(`[data-watering-state='${key}']`, state, paused, planned); + setzeSchild(`[data-watering-state='${key}']`, state, paused, bewaessert); const history = wateringHistory(zone.topic, zone.valve); setzeZelle(`[data-watering-last='${key}']`, history.dateTime); setzeZelle(`[data-watering-duration='${key}']`, history.duration); - const heute = wateringDayTotal(zone.topic, "WateringToday", "wateringDurationTodaySecs", zone.valve, true); const vortag = wateringDayTotal(zone.topic, "LastWateringDay", "wateringDurationDaySecs", zone.valve, false); setzeZelle(`[data-watering-today='${key}']`, heute.label, heute.day && "Stand: " + heute.day); setzeZelle(`[data-watering-prevday='${key}']`, vortag.label, vortag.day && "Bewässerungstag: " + vortag.day); - const plan = wateringPlanText(zone.auto); - setzeZelle(`[data-watering-plan='${key}']`, plan.text, plan.titel); }); document.querySelectorAll("[data-watering-auto-toggle]").forEach(updateWateringAutoButton); - setzeZelle("[data-watering-plan-updated]", wateringPlanUpdated()); } // Der Knopf zeigt die naechste Aktion, nicht den Zustand: laeuft die Zone, @@ -1039,8 +974,8 @@ var chartSettings = { } // Zustandsschild der Zonenkarte, Farben wie im SVG: laufend gruen, - // pausiert gelb, von der Automatik geplant blau, sonst grau. - function setzeSchild(selektor, state, paused, planned) { + // pausiert gelb, heute schon bewaessert blau, sonst grau. + function setzeSchild(selektor, state, paused, bewaessert) { const schild = document.querySelector(selektor); if (!schild) return; let text = "unbekannt"; @@ -1049,8 +984,8 @@ var chartSettings = { text = paused ? "pausiert " + state : "läuft " + state; farbe = paused ? "bg-warning text-dark" : "bg-success"; } else if (state === "aus") { - text = planned ? "heute geplant" : "aus"; - farbe = planned ? "bg-primary" : "bg-secondary"; + text = bewaessert ? "heute bewässert" : "aus"; + farbe = bewaessert ? "bg-primary" : "bg-secondary"; } if (schild.textContent !== text) schild.textContent = text; const klasse = "badge " + farbe; @@ -1082,23 +1017,23 @@ var chartSettings = { button.classList.toggle("btn-outline-danger", active); } - function updateWateringIndicator(status, icon, name, state, paused, planned) { - // "Aus" bleibt bei nicht geplant stehen; bei geplant ersetzt die - // Stoppuhr (U+23F1, ohne Emoji-Variationsselektor -> einfarbig, folgt - // der Icon-Farbe) den Text, um Platz auf der textPath zu sparen. - // Laeuft im Sekundentakt, aendert sich aber selten - ohne Pruefung + function updateWateringIndicator(status, icon, name, state, paused, bewaessert) { + // "Aus" bleibt stehen, solange die Zone heute noch kein Wasser hatte; + // danach ersetzt das Haekchen (U+2713, ohne Emoji-Variationsselektor -> + // einfarbig, folgt der Icon-Farbe) den Text, um Platz auf der textPath zu + // sparen. Laeuft im Sekundentakt, aendert sich aber selten - ohne Pruefung // wuerde jede Sekunde derselbe Wert geschrieben und das Icon samt // Filter neu gezeichnet. - const text = state === "aus" ? (planned ? "⏱" : "Aus") : state; + const text = state === "aus" ? (bewaessert ? "✓" : "Aus") : state; if (status.textContent !== text) status.textContent = text; - // Geplant-Blau ("#3A6292") entspricht dem Wassertropfen-Icon (id="water"). - const color = state !== "aus" ? (paused ? "#ffc107" : "#20c997") : planned ? "#3A6292" : "#6c757d"; + // Das Blau ("#3A6292") entspricht dem Wassertropfen-Icon (id="water"). + const color = state !== "aus" ? (paused ? "#ffc107" : "#20c997") : bewaessert ? "#3A6292" : "#6c757d"; if (icon.getAttribute("color") !== color) { icon.setAttribute("color", color); icon.setAttribute("fill", color); icon.style.color = color; } - const titel = name + ": " + (state === "aus" && planned ? "Aus (Automatik heute noch geplant)" : state); + const titel = name + ": " + (state === "aus" && bewaessert ? "Aus (heute bereits bewässert)" : state); const titelKnoten = icon.querySelector("title"); if (titelKnoten.textContent !== titel) titelKnoten.textContent = titel; } diff --git a/restricted/automations.php b/restricted/automations.php index e848965..2b389c7 100644 --- a/restricted/automations.php +++ b/restricted/automations.php @@ -45,9 +45,9 @@ function meshDb() * ein langsamer Durchlauf den Termin nicht verschluckt. * ab 16:30 ab da bis Mitternacht wahr. Ausgeloest wird trotzdem nur * einmal, weil nur die steigende Flanke zaehlt; dafuer kostet - * ein verpasster Takt nicht den ganzen Tag. Dieselbe - * Ueberlegung steht hinter den breiten Zeitfenstern in - * auto_watering.py. + * ein verpasster Takt nicht den ganzen Tag. Fuer alles, was + * laufen muss, auch wenn der Termin selbst verpasst wurde, + * ist das die richtige Wahl - etwa die Bewaesserung. * vor 16:30 bis dahin wahr, danach nicht mehr. * * Beim Sonnenauf- und -untergang ist der Wert ein Versatz, der davor oder diff --git a/tools/lageplan.php b/tools/lageplan.php index cc6c81c..60416c5 100644 --- a/tools/lageplan.php +++ b/tools/lageplan.php @@ -59,9 +59,9 @@ const WMS_LAYER = "by_dop20c"; const WMS_SCHAERFE = 3; /** - * Mittelpunkt des Ausschnitts. Dieselben Koordinaten stehen in - * restricted/gartenbewaesserung/auto_watering.py, von wo die Regendaten - * geholt werden - es ist derselbe Ort. + * Mittelpunkt des Ausschnitts. Dieselben Koordinaten stehen als Vorgabe in + * gatherRainData.py im SolarManager, von wo die Regendaten geholt werden - + * es ist derselbe Ort. */ $lat = isset($_GET["lat"]) ? floatval($_GET["lat"]) : 47.5781; $lon = isset($_GET["lon"]) ? floatval($_GET["lon"]) : 10.2628;