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
				
					/********************
 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);

				
			
				
					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 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);
}

				
			
Warenkorb