-
Autor
Bekanntermaßen bieten Shelly Skripte erheblich mehr Einsatzmöglichkeiten, als es Standardkonfigurationen erlauben. Soviel zum entscheidenden Vorteil.
Der Nachteil von Skripten besteht in einer mitunter mangelhaften Ausfallsicherheit, soll heißen: Ein Skript kann, aus welchen Gründen auch immer, inaktiv sein - also ruhen. Wenn ein Skript nicht ruhen darf, es aber tut, dann ist es hilfreich, automatisiert darüber informiert zu werden. Dafür habe ich ein Skript geschrieben, welches bei Bedarf über Nichtaktivitäten von Skripten via callmebot.com informiert. Ich nutze dafür den Messengerdienst Signal, es können aber afaik auch andere Messenger genutzt werden.
Das Skript bietet insbesondere eine Funktion checkScripts() , welche praktikabel per Zeitpläne (schedule jobs) aufgerufen werden sollte - bspw. alle 30 Minuten. Diese Funktion kann je nach Parameter lokal oder remote (besonders zweckmäßig) bestimmte Skripte auf Inaktivität prüfen. Falls der remote Host nicht erreichbar ist oder mindestens eines der zu prüfenden Skripte inaktiv ist, wird eine Messenger Nachricht via callmebot.com gesendet.
Dies habe ich bisher auf einer Shelly Pille entwickelt und getestet.
Optionales Vorhaben: Falls Internet nicht verfügbar, können solche Meldungen im nichtflüchtigen Speicher protokolliert und zeitversetzt bei Bedarf angeschaut werden.
Beispiel einer solchen Nachricht
Erstellen eines geeigneten Schedule Jobs via tools.eichelsdoerfer.net
Da mit Shelly Bordmitteln ein benötigter Job nicht erstellbar ist, habe ich dafür eine Website zusammengestellt, welche das Anlegen oder ändern nützlicher Jobs unterstützt.
https://tools.eichelsdoerfer.net/schedjob.html
Beispiel eines hier geeigneten Jobs
Dieser Screenshot zeigt das Anlegen bzw. Ändern eines Zeitplans zum regelmäßigen Aufruf der Skriptfunktion checkScripts().
Mein noch folgendes Skript hat die Skript Id 4. Der Job ruft die Funktion checkScripts() alle 5 Minuten auf. Diese relativ hohe Abfragefrequenz ist ausschließlich dem Testen geschuldet. Niemand dürfte daran interessiert sein, alle 5 Minuten eine Nachricht zu erhalten, welche denselben Ausfall mittteilt. Der timespec Wert kann gerne auch völlig anders gestaltet werden. Dieser ist auch via Shelly WebUI leicht abänderbar. Nur der Aufruf von checkScripts() kann nicht via WebUI eingebaut werden.
Die Parameter des Funktionsaufrufs - am Beispiel erläutert
- IP Adresse des remote Skripthosts - wichtig(!) in Hochkommata gesetzt
Wird hier 'local' oder 'localhost' oder '127.0.0.1' eingesetzt, fragt das Skript unmittelbar lokal ab, andernfalls per HTTP.Get. Im Beispiel hat der Host die IP Adresse 172.16.3.23. - Die Id des zu prüfenden (remote) Skripts. Hier ist nur eine Id (1) verwendet. Stattdessen kann auch eine Liste (Array) genutzt werden, bspw. [1,3,4] womit die Skripts mit den Ids 1, 3 und 4 auf Inaktivität geprüft werden.
Welche Skripte zu prüfen sind, kann nur der Anwender entscheiden. - Optionaler Zeitversatz in Sekunden, hier 10s. Er bietet die Möglichkeit, zur selben Zeit mehrere Aufrufe von checkScripts() zu nutzen, welche das tatsächliche Prüfen unterschiedlich zeitversetzt durchführen. Dies kann in mehreren Jobs oder auch in einem Job mit mehreren calls erfolgen. Bei geeigneter Planung kann dieser optionale Zeitversatz den Abbruch meines Skripts wegen zu vieler gleichzeitiger RPC Aufrufe verhindern. Hier sollten wenige Sekunden völlig ausreichen. Fehlt dieser Parameter, wird die Prüfung sofort abgearbeitet.
Bsp: checkScripts('172.16.3.23', 1)
Das Skript (zum Prüfen von Skripts
)
// GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
// More information: https://www.gnu.org/licenses/gpl-3.0.txt
// ABSOLUTELY NO WARRANTY!!!
// Author: Gerhard Eichelsdörfer alias eiche
// Version 2026-01-31_01
// This script may sends messages via callmebot.com.
const Src = "Die+Pille:+",
HttpCode = "https://signal.callmebot.com/signal/send.php?phone=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&text=",
Msg = "Inaktive+Skripte:";
function d2(n) {return n>9 ? n.toString() : "0" + n;}
function sendMsg(msg) {
let d = new Date();
let t = d.getFullYear()+"-"+d2(d.getMonth()+1)+"-"+d2(d.getDate())+"+"+d2(d.getHours())+":"+d2(d.getMinutes());
Shelly.call("HTTP.Get", {url:HttpCode+Src+t+"+"+msg},
function(res, errc, errm) {
if(errc) {console.log(errc, errm); return;}
});
}
// analyze the result of method Script.List
function analyze(res, id, msg){
if(id.length===undefined) id = [id];
let i, j, n = id.length, l = res.length, d = false;
for(i=0; i<n; ++i) {
for(j=0; j<l && res[j].id!==id[i]; ++j);
if(j>=l) {
console.log("missing script:"+id[i]);
msg += (d ? ",+" : "+")+"missing+script:"+id[i];
d = true;
} else {
//print(JSON.stringify(res[j]));
if(!res[j].running) {msg += (d ? ",+" : "+") + res[j].name; d = true;}
}
}
print(msg);
if(d) sendMsg(msg);
}
// checkLocalScripts sends a message if one or more local scripts in the id list are not running
// the parameter may be a single number or an array of numbers
// call examples: checkLocalScripts(2), checkLocalScripts([1,2])
function checkLocalScripts(id) {
print("local");
Shelly.call("Script.List", {}, function(res, errc, errm, id) {
if(errc) {console.log(errc, errm); return;}
//print(JSON.stringify(res));
analyze(res.scripts, id, Msg); // do the remainder
}, id);
}
// checkRemoteScript sends a message if one or more remote scripts in the id list are not running
// the id parameter may be a single number or an array of numbers
// call examples: checkRemoteScript("<IP address>", 2), checkRemoteScript("<IP address>", [1,2])
function checkRemoteScripts(ip, id) {
print("remote");
Shelly.call("HTTP.Get", {url:"http://" + ip + "/rpc/Script.List"}, function(res, errc, errm, p) {
let ip = p[0], id = p[1];
if(errc) {
console.log(errc, errm);
if(errc===-104) sendMsg("Host+"+ip+"+is+not+reachable.");
return;
}
if(res.code!==200) return;
//print(res.body);
res = JSON.parse(res.body).scripts;
//print(JSON.stringify(res));
analyze(res, id, "Host+"+ip+"+"+Msg); // do the remainder
}, [ip,id]);
}
// checkScripts is the most usable function in this script.
// Parameters:
// ip: IP address of the scripts host
// id: single id of the checked script or an array of such ids
// dt: time offset in seconds after the call - optional - if missing, the check will be proceed immediately
// call examples:
// checkScripts("local", 1, 5);
// checkScripts("172.16.3.23", 1, 3);
function checkScripts(ip, id, dt) {
let local = ip==="local" || ip==="localhost" || ip==="127.0.0.1";
if(dt===undefined || dt<=0) local ? checkLocalScripts(id) : checkRemoteScripts(ip,id);
else Timer.set(dt*1000, false, function(p) {
p[0] ? checkLocalScripts(p[2]) : checkRemoteScripts(p[1],p[2]);
}, [local,ip,id]);
}
Alles anzeigen
Man benötigt einen kostenfreien Account auf callmebot.com. Damit muss der Wert von HttpCode für eigene Zwecke angepasst werden.
Das Skript gibt im Console Fenster wenige Informationen aus, was leicht abgestellt werden kann.
Hinweis zu den via callmebot.com übertragenen Texten
Darin darf kein Leerzeichen stehen. Stattdessen muss jeweils ein Pluszeichen vorhanden sein.
Anlass
In unserem alten, kleinen Wintergarten hängt ein Heizkörper, der normalerweise verlässlich via Shelly Plus 1 und Skript vor dem Einfrieren - Zieltemperatur 5°C - geschützt wird. Dieser Shelly verlor in letzter Zeit wiederholt die WLAN Verbindung, trotz guter Feldstärke - vermutlich ein Hardwaredefekt. Der Ersatz Shelly Plus 1 tut bisher verlässlich seine Dienste. Ich möchte aber darüber informiert werden, wenn hier ein Problem vorliegen sollte. Solches erledigt obiges Skript in Kombination eines Schedule Jobs - derzeit auf einer Shelly Pille.
Nebenbemerkung: Die alten, üblichen, mechanischen Thermostate sind wenig geeignet, da sie nicht für solch niedrige Zieltemperaturen gebaut sind. Dies verursachte früher leider oftmals einen zu warmen Heizkörper, also verschwendete Energie. Ein dort montierter TRV fiel aus. Dies veranlasste mich dazu, eine verlässlichere Implementation via Skript zu entwickeln, die standalone nutzbar ist.
Edit:
Das Skript kann auch ohne bereits vorhandenen callmebot.com Account getestet werden. Die Ausgaben im Console Fenster zeigen, ob ein inaktives Skript gefunden wurde. Steht hinter der Ausgabe "... Inaktive+Skripte:" nichts, wurden keine der zu prüfenden Skripte als inaktiv gefunden und bei einer remote Prüfung wurde kein Problem festgestellt.
Btw, falls ein zu prüfendes Skript (dessen Id) nicht vorhanden ist, wird die Meldung "missing script:<Id>" ausgegeben und gesendet.