Shelly Scripting
Shelly Scripts für erweiterte Automationen
Shelly-Geräte ab der 2. Generation mit aktualisierter Firmware beherbergen eine Script-Engine auf Java-Script-Basis (Details). Shelly-Skripts laufen auf einer modifizierten Version von mJS ab der Generation 2 (Gen2), also der Plus- und Pro-Serie.
Beispiel: Taupunkt-Rechnung mit Sensordate vom H&T
Dieses Script läuft z.B: auf einem Shelly 1 Gen 4. Dieser schaltet den Badlüfter gemäss dem Taupunkt. Feuchte Luft wird abgeführt, bis kein Kondensieren der Feuchtigkeit mehr stattfindet.
Ich möchte Schimmelbildung vorbeugen und ein angenehmes Raumklima schaffen.
Trick: Das Script nimmt die historische Temperatur im Bad, abzüglich einer Sicherheitskorrektur, um gegenüber der Raumtemperatur die Wandtemperatur zu bestimmen.
Features im Script:
- erkennt Duschen
- simuliert Wandtemperatur
- verhindert Kondensation
- verhindert Lüfterflattern
- berücksichtigt reale Badezimmerphysik
- funktioniert mit DeepSleep Sensor
- sehr stabil im Dauerbetrieb
Taupunkt Script für Shelly
/********************
KONFIGURATION
********************/
/* ihre ip des H&T */
let HT_IP = "xxx.xxx.xxx.xxx";
let TEMP_RISE_TRIGGER = 0.4; // Duschen erkennen
let HUMIDITY_END = 65; // Duschen Ende
let WALL_TEMP_OFFSET = 6; // Sicherheitskorrektur Wandtemperatur
let FAN_HYSTERESIS = 1; // Ausschaltpuffer
let FAN_MIN_RUNTIME = 10 * 60 * 1000; // 10 Minuten Mindestlaufzeit
/********************
VARIABLEN
********************/
let tempHistory = [];
let lastTemp = null;
let showerActive = false;
let wallTemp = null;
let fanRunning = false;
let fanStartTime = 0;
/********************
HISTORY SPEICHERN
********************/
function addTempToHistory(temp) {
let now = Date.now();
tempHistory.push({
time: now,
temp: temp
});
let maxAge = 4 * 60 * 60 * 1000;
tempHistory = tempHistory.filter(function(e) {
return now - e.time <= maxAge;
});
}
/********************
MITTELWERT 2h
********************/
function getAverageTempLast2h() {
let now = Date.now();
let start = now - (2 * 60 * 60 * 1000);
let vals = tempHistory.filter(function(e) {
return e.time >= start;
});
if (vals.length === 0) return null;
let sum = 0;
let i;
for (i = 0; i < vals.length; i++) {
sum += vals[i].temp;
}
return sum / vals.length;
}
/********************
TAUPUNKT
********************/
function calculateDewPoint(t, rh) {
let a = 17.27;
let b = 237.7;
let gamma = (a * t) / (b + t) + Math.log(rh / 100.0);
return (b * gamma) / (a - gamma);
}
/********************
LÜFTER STEUERUNG
********************/
function setFan(state) {
if (state && !fanRunning) {
Shelly.call("switch.set", { id: 0, on: true });
fanRunning = true;
fanStartTime = Date.now();
print(">>> Lüfter EIN");
}
if (!state && fanRunning) {
if (Date.now() - fanStartTime < FAN_MIN_RUNTIME) return;
Shelly.call("switch.set", { id: 0, on: false });
fanRunning = false;
print(">>> Lüfter AUS");
}
}
/********************
HAUPTLOGIK
********************/
function processValues(temp, humidity) {
addTempToHistory(temp);
// ----- Duschen erkennen -----
if (lastTemp !== null) {
let rise = temp - lastTemp;
if (!showerActive && rise > TEMP_RISE_TRIGGER) {
let avg = getAverageTempLast2h();
if (avg !== null) {
wallTemp = avg;
showerActive = true;
print(">>> Duschen erkannt – Wandtemp eingefroren:", wallTemp.toFixed(2));
}
}
// Duschen Ende
if (showerActive && humidity < HUMIDITY_END) {
showerActive = false;
wallTemp = null;
print(">>> Duschen beendet");
}
}
lastTemp = temp;
let refTemp = (wallTemp !== null) ? wallTemp : temp;
let dewPoint = calculateDewPoint(temp, humidity);
let switchOnLimit = refTemp - WALL_TEMP_OFFSET;
let switchOffLimit = refTemp - WALL_TEMP_OFFSET - FAN_HYSTERESIS;
/******** DEBUG AUSGABEN ********/
print(
"Temp:", temp.toFixed(2),
"RH:", humidity.toFixed(1),
"Taupunkt:", dewPoint.toFixed(2),
"Referenz:", refTemp.toFixed(2),
"EIN-Grenze:", switchOnLimit.toFixed(2),
"AUS-Grenze:", switchOffLimit.toFixed(2)
);
/******** LÜFTERLOGIK ********/
if (dewPoint >= switchOnLimit) {
setFan(true);
}
if (dewPoint < switchOffLimit) {
setFan(false);
}
}
/********************
H&T DATEN HOLEN
********************/
function Loop() {
Shelly.call(
"HTTP.GET",
{ url: "http://" + HT_IP + "/rpc/Shelly.GetStatus" },
function(result, error_code) {
if (error_code !== 0) {
print(">>> H&T nicht erreichbar");
return;
}
let data = JSON.parse(result.body);
let temp = data["temperature:0"].tC;
let humidity = data["humidity:0"].rh;
processValues(temp, humidity);
}
);
}
/********************
TIMER
********************/
Timer.set(20000, true, Loop);
Beispiel Debug-Ausgaben
print(
"Temp:", temp.toFixed(2),
"RH:", humidity.toFixed(1),
"Taupunkt:", dewPoint.toFixed(2),
"Referenz:", refTemp.toFixed(2),
"EIN-Grenze:", switchOnLimit.toFixed(2),
"AUS-Grenze:", switchOffLimit.toFixed(2)
);
Temp: 24.10
RH: 68.2
Taupunkt: 18.4
Referenz: 23.7
EIN-Grenze: 17.7
AUS-Grenze: 16.7
Anzeigen der historischen Temperatur-Werte
/* anzeigen der letzten 5 Werte */
let start = Math.max(0, tempHistory.length - 5);
let i;
for (i = start; i < tempHistory.length; i++) {
print("Hist Temp:", tempHistory[i].temp);
}
