Internet-Watchdog
Bei einer abgelegenen PV-Installation bin ich für die Kontrolle auf eine funktionierende Internetverbindung angewiesen. Der dafür eingesetzte 5G-Outdoor-Router „hängt“ alle paar Monate mal und muss manuell neu gestartet werden. Damit ich hier nicht auf Hilfe vor Ort angewiesen bin, habe ich nach dem Motto „Schweizer Jugend forscht“ Abhilfe gebastelt. Dazu reicht ein Shelly Switch (hier ein Shelly 1PM Gen4), ein Gehäuse und ein Kabel. Chat-GPT hat mir im Dialog ein 300 Zeilen langes Script entwickelt, welches bei ausbleibender Internetverbindung den Router für 30 Sekunden vom Netz trennt und wieder startet. Der Shelly-Switch kostet CHF 17.00, das gedruckte Gehäuse ein paar Euro und das Kabel stammt aus der Bastelkiste. Falls jemand die Schaltung nachbauen mag, veröffentliche ich gleich das Script dazu.



// ============================================================
// Internet-Watchdog für Shelly 1PM Gen4
// Zyxel 5G Router / PoE-Netzteil
//
// Normalbetrieb:
// Internetprüfung alle 30 Sekunden
//
// Ausfall:
// Beide Testziele müssen nicht erreichbar sein.
// Erster Power-Cycle nach 10 Minuten.
//
// Power-Cycle:
// Ausgang 30 Sekunden AUS
// danach wieder EIN
// anschliessend 5 Minuten Boot-/Einbuchzeit
//
// Dauerhafter Ausfall:
// Weitere Power-Cycles frühestens alle 15 Minuten.
// Unbegrenzte Anzahl Versuche.
//
// Sobald Internet wieder erreichbar ist:
// Rückkehr zum Normalbetrieb.
// ============================================================
// ---------------- EINSTELLUNGEN ----------------
let CHECK_INTERVAL = 30000; // 30 Sekunden
let FIRST_REBOOT_AFTER = 600000; // 10 Minuten
let POWER_OFF_TIME = 30000; // 30 Sekunden
let BOOT_WAIT = 300000; // 5 Minuten
let RETRY_INTERVAL = 900000; // 15 Minuten
let HTTP_TIMEOUT = 10; // 10 Sekunden
// Zwei unabhängige Internet-Testziele
let URL1 = "https://www.google.com/generate_204";
let URL2 = "https://cp.cloudflare.com/generate_204";
// ---------------- STATUS ----------------
let failureStart = 0;
let lastReboot = 0;
let rebooting = false;
let bootWaiting = false;
let rebootCount = 0;
// ---------------- HTTP-TEST ----------------
function checkURL(url, callback) {
Shelly.call(
"HTTP.GET",
{
url: url,
timeout: HTTP_TIMEOUT
},
function(result, error_code, error_message) {
let ok =
error_code === 0 &&
result !== null &&
result.code >= 200 &&
result.code < 400;
callback(ok);
}
);
}
// ---------------- INTERNET PRÜFEN ----------------
function checkInternet() {
// Während Power-Cycle und Bootphase nichts tun
if (rebooting || bootWaiting) {
return;
}
checkURL(URL1, function(ok1) {
if (ok1) {
internetOK("Google");
return;
}
// Nur wenn Google nicht erreichbar ist,
// wird das zweite unabhängige Ziel geprüft.
checkURL(URL2, function(ok2) {
if (ok2) {
internetOK("Cloudflare");
} else {
internetFailed();
}
});
});
}
// ---------------- INTERNET OK ----------------
function internetOK(source) {
if (failureStart !== 0) {
let duration =
Math.round((Date.now() - failureStart) / 60000);
print(
"Internet wieder OK über " +
source +
". Ausfalldauer ca. " +
duration +
" Minuten."
);
}
// Neue Störung beginnt wieder bei Null
failureStart = 0;
lastReboot = 0;
}
// ---------------- INTERNET AUSGEFALLEN ----------------
function internetFailed() {
let now = Date.now();
// Beginn einer neuen Störung
if (failureStart === 0) {
failureStart = now;
print(
"WARNUNG: Beide Internetziele nicht erreichbar."
);
return;
}
// Noch kein Power-Cycle in dieser Störung
if (lastReboot === 0) {
let outage = now - failureStart;
print(
"Internet seit ca. " +
Math.round(outage / 60000) +
" Minuten nicht erreichbar."
);
if (outage >= FIRST_REBOOT_AFTER) {
powerCycle();
}
return;
}
// Es gab bereits mindestens einen Power-Cycle.
// Nächster frühestens 15 Minuten nach dem letzten.
let sinceReboot = now - lastReboot;
if (sinceReboot >= RETRY_INTERVAL) {
print(
"Internet weiterhin nicht erreichbar. " +
"15 Minuten seit letztem Neustart -> erneuter Power-Cycle."
);
powerCycle();
}
}
// ---------------- POWER-CYCLE ----------------
function powerCycle() {
if (rebooting || bootWaiting) {
return;
}
rebooting = true;
lastReboot = Date.now();
rebootCount++;
print(
"POWER-CYCLE Nr. " +
rebootCount +
": PoE AUS."
);
// Ausgang ausschalten
Shelly.call(
"Switch.Set",
{
id: 0,
on: false
},
function(result, error_code, error_message) {
if (error_code !== 0) {
print(
"FEHLER beim Ausschalten: " +
error_message
);
rebooting = false;
return;
}
// 30 Sekunden stromlos
Timer.set(
POWER_OFF_TIME,
false,
function() {
// Ausgang wieder einschalten
Shelly.call(
"Switch.Set",
{
id: 0,
on: true
},
function(result, error_code, error_message) {
if (error_code !== 0) {
print(
"FEHLER beim Einschalten: " +
error_message
);
rebooting = false;
return;
}
print(
"PoE wieder EIN. " +
"Zyxel erhält jetzt 5 Minuten Boot-/Einbuchzeit."
);
rebooting = false;
bootWaiting = true;
// 5 Minuten lang keine Internetprüfung
Timer.set(
BOOT_WAIT,
false,
function() {
bootWaiting = false;
print(
"Bootzeit beendet. Internetprüfung wird fortgesetzt."
);
// Direkt nach Ende der Bootzeit prüfen
checkInternet();
}
);
}
);
}
);
}
);
}
// ---------------- START ----------------
print("----------------------------------------");
print("Internet-Watchdog gestartet");
print("Prüfintervall: 30 Sekunden");
print("Erster Power-Cycle nach: 10 Minuten");
print("PoE AUS: 30 Sekunden");
print("Boot-/Einbuchzeit: 5 Minuten");
print("Weitere Power-Cycles: alle 15 Minuten");
print("----------------------------------------");
// Erste Prüfung 30 Sekunden nach Scriptstart
Timer.set(
30000,
false,
checkInternet
);
// Danach dauerhaft alle 30 Sekunden
Timer.set(
CHECK_INTERVAL,
true,
checkInternet
);
