/*
* Gemeinsame Bausteine der Dashboard-Seiten.
* Wird vor solarMQTT.js / homeMQTT.js / heatMQTT.js / historyMQTT.js geladen.
*/
/** Sekunden als "HH:MM". */
String.prototype.toHHMM = function () {
var sec_num = parseInt(this, 10);
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
if (hours < 10) { hours = "0" + hours; }
if (minutes < 10) { minutes = "0" + minutes; }
return hours + ':' + minutes;
};
function powerToString(power) {
if (Math.abs(power) > 999) {
power = power / 1000;
return power.toPrecision(3) + " kW";
} else {
return Math.round(power) + " W";
}
}
function loadingHTML(msg) {
return "
";
}
/**
* Diagrammdaten holen und eintragen.
* params Funktion, die {FROM, TO} liefert (Zeitraum aendert sich zur Laufzeit)
* sunrise true -> Sonnenstands-Markierungen fuer denselben Zeitraum
* Objekt {FROM, TO} -> fester Zeitraum
* autoUpdate alle 5 Minuten neu laden
* updateMode an chart.update() durchgereicht ("none" = ohne Animation)
*/
async function getData(chart, url, options = {}) {
const {
params = null,
sunrise = false,
autoUpdate = true,
updateMode = undefined,
refreshMs = 5 * 60 * 1000
} = options;
const range = params ? params() : null;
const query = range ? "?FROM=" + range.FROM + "&TO=" + range.TO : "";
try {
chart.options.events = []; // waehrend des Ladens keine Tooltips
const response = await fetch(url + query);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
chart.data = await response.json();
if (sunrise) {
const sunRange = sunrise === true ? range : sunrise;
const response2 = await fetch("ajax/getSunrise.php?FROM=" + sunRange.FROM + "&TO=" + sunRange.TO);
if (!response2.ok) {
throw new Error(`Response status: ${response2.status}`);
}
chart.options.plugins.annotation.annotations = await response2.json();
}
chart.options.events = ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'];
chart.update(updateMode);
} catch (error) {
console.log("getData " + url + ": " + error.message);
}
if (autoUpdate) {
setTimeout(function () { getData(chart, url, options); }, refreshMs);
}
}
/** Meteoblue-Meteogramm einhängen, sofern die Seite einen Platz dafür hat. */
// Mindestbreite, in der das Meteogramm von meteoblue geladen wird. Der
// Anbieter legt sein Bild einmal beim Laden fest und zeichnet es bei einer
// Breitenaenderung nicht neu; unterhalb von etwa 600 px ueberlagern sich
// Wochentage und Stundenachse zu einem unlesbaren Streifen. Auf schmalen
// Schirmen wird es deshalb in dieser Breite geladen und als Ganzes
// verkleinert - dieselbe Ansicht wie am Rechner, nur kleiner. Ist die Karte
// breiter, wird gleich in Kartenbreite geladen und nichts skaliert.
const METEOGRAM_BREITE = 640;
const METEOGRAM_HOEHE = 500;
function mountMeteogram() {
const ziel = document.getElementById("meteogram");
if (!ziel) return;
// loading="lazy": das Meteogramm ist ein vollstaendiges Fremd-Dokument und
// liegt unterhalb der Falz - es wird erst geladen, wenn man hinscrollt.
const stil = window.getComputedStyle(ziel);
const innenBreite = ziel.clientWidth - parseFloat(stil.paddingLeft) - parseFloat(stil.paddingRight);
const ladeBreite = Math.max(METEOGRAM_BREITE, Math.round(innenBreite) || 0);
ziel.innerHTML =
""
+ ""
+ "
"
+ ""
+ "";
const buehne = ziel.querySelector(".meteogram-buehne");
const rahmen = buehne.querySelector("iframe");
// Zuschnitt und Skalierung stehen hier und nicht im Stylesheet: sonst
// haengt die Darstellung an einer womoeglich veralteten solar.css im
// Browser-Cache, und der breit geladene Rahmen ragte ungeschnitten aus
// der Karte heraus.
buehne.style.overflow = "hidden";
rahmen.style.display = "block";
rahmen.style.transformOrigin = "0 0";
let letzteBreite = 0;
const anpassen = function () {
const breite = buehne.clientWidth;
if (breite === 0 || breite === letzteBreite) return;
letzteBreite = breite;
const faktor = Math.min(1, breite / ladeBreite);
rahmen.style.transform = "scale(" + faktor + ")";
buehne.style.height = Math.round(METEOGRAM_HOEHE * faktor) + "px";
};
anpassen();
// Nur auf Breitenaenderungen reagieren - die Hoehe setzt anpassen() selbst,
// sonst riefe sich der Beobachter gegenseitig auf.
if (window.ResizeObserver) {
new ResizeObserver(anpassen).observe(buehne);
} else {
window.addEventListener("resize", anpassen);
}
}
/**
* Wetterkacheln aktualisieren (restricted/weather_cards.html).
* value(name) liefert den Wert zu weatherStation/ - die Seiten legen
* die MQTT-Werte unterschiedlich ab, deshalb der Zugriff über eine Funktion.
*/
function updateWeatherCards(value) {
const windDir = document.getElementById("windDir");
if (!windDir) return; // Seite ohne Wetterkacheln
windDir.style.transform = "rotate(" + (90 + Number(value("windDeg"))) + "deg)";
document.getElementById("windSpd").innerHTML = value("avgWindspeed");
document.getElementById("humidity").innerHTML = value("hum");
document.getElementById("temp").innerHTML = value("tempAmb");
document.getElementById("windDirGust").style.transform = "rotate(" + (90 + Number(value("gustDeg"))) + "deg)";
document.getElementById("windGust").innerHTML = value("maxgust");
document.getElementById("ambPress").innerHTML = Math.round(value("qff") * 10) / 10;
}
/**
* Offenen Reiter in der Adresse merken.
*
* Gilt fuer jede Leiste, die restricted/reiter.php mit "merken" gebaut hat -
* erkennbar an data-reiter-merken. Ohne das landet man nach jedem Neuladen
* wieder im ersten Reiter, obwohl man gerade woanders gearbeitet hat.
*
* In der Adresse steht nur das Kuerzel des Reiters (#kacheln), nicht der Name
* der Leiste: es ist eine Adresse, die man auch von Hand tippt. Zwei merkende
* Leisten auf einer Seite waeren damit nicht zu unterscheiden - dann muesste
* hier der Name davor, und die Adresse waere haesslicher als noetig.
*/
function reiterMerken() {
if (typeof bootstrap === "undefined") return;
document.querySelectorAll("[data-reiter-merken]").forEach(function (leiste) {
const knoepfe = leiste.querySelectorAll("[data-reiter]");
const gewuenscht = location.hash.replace("#", "");
knoepfe.forEach(function (knopf) {
knopf.addEventListener("shown.bs.tab", function () {
history.replaceState(null, "", "#" + knopf.dataset.reiter);
});
if (gewuenscht && knopf.dataset.reiter === gewuenscht) {
new bootstrap.Tab(knopf).show();
}
});
});
}
document.addEventListener("DOMContentLoaded", reiterMerken);