/*
* 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);
}
}
/** Vorgerendertes HTML-Schnipsel in ein Element laden. */
async function getStats(elem_id, url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
document.getElementById(elem_id).innerHTML = await response.text();
} catch (error) {
console.log("getStats " + url + ": " + error.message);
}
}
/**
* Formular des Modals per POST abschicken.
* Der Start/Stop-Knopf der Ladesteuerung schickt nur sich selbst.
*/
function submitFormAjax(event) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
setTimeout(function () { modalEV.hide(); }, 700);
if (xmlhttp.responseText) {
alert(xmlhttp.responseText);
}
}
};
let post = "";
if (event.currentTarget.id == "evStart/Stop") {
post = "evStart/Stop=" + event.currentTarget.innerHTML;
} else {
const form = document.getElementById('modalEV').querySelector('form');
for (const element of Array.from(form.elements)) {
if (!element.name) continue;
if (element.type == "radio" || element.type == "checkbox") {
if (element.checked) {
post += element.name + "=" + encodeURIComponent(element.value) + "&";
}
} else if (element.type == "button") {
if (!element.classList.contains("accordion-button")) {
post += element.name + "=" + encodeURIComponent(element.innerHTML) + "&";
}
} else {
post += element.name + "=" + encodeURIComponent(element.value) + "&";
}
}
}
xmlhttp.open("POST", event.currentTarget.contentURL, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
document.getElementById('modalEV').querySelector('.modal-body').innerHTML = loadingHTML("Änderungen werden übernommen...");
xmlhttp.send(post);
return false;
}
/**
* Schieberegler im Modal mit seiner Beschriftung verbinden.
* label(wert) liefert den anzuzeigenden Text.
*/
function bindModalSlider(slider, span, label) {
slider.oninput = function () {
const min = Number(slider.min);
const max = Number(slider.max);
const percent = (Number(slider.value) - min) * 100 / (max - min);
slider.style.setProperty("--background-size", `${percent}%`);
// Beschriftung mitlaufen lassen, an den Raendern etwas gebremst
let margin = percent;
if (margin < 10) {
margin = margin - 0.4 * margin;
} else if (margin < 85) {
margin = margin - 4;
} else {
margin = margin - 4 - 0.5 * (margin - 85);
}
span.setAttribute('style', 'margin-left:' + margin + '%;');
span.innerHTML = label(slider.value);
};
}
/** Meteoblue-Meteogramm einhängen, sofern die Seite einen Platz dafür hat. */
function mountMeteogram() {
const target = document.getElementById("meteogram");
if (!target) return;
// loading="lazy": das Meteogramm ist ein vollstaendiges Fremd-Dokument und
// liegt unterhalb der Falz - es wird erst geladen, wenn man hinscrollt.
target.innerHTML = "";
}
/**
* 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;
}