Skript – Backupbereitstellung
Hier kannst du das aktuelle Geräteskript nachträglich kopieren oder als Datei herunterladen.
Copy-Paste-Inhalt
//final_mqtt_telemetry_conf
// === BKW Telemetry (persist + global config + ack) ===========================
// Version: 2025-10-09
// Hochgeladen: 2025-10-16
// Änderungen:
// - Reboot-Fix: Shelly.reboot() -> Shelly.call("Shelly.Reboot", {})
// - Guard-Checks & Logging
// - Fail-safe: switch:0 bei Boot & MQTT-Connect einschalten, falls OFF
// - MQTT-Status-Flag: "forced_on" nur senden, wenn wirklich connected
// - Gentle startup Delay auf 3000 ms erhöht
// ============================================================================
// ---- config & constants -----------------------------------------------------
let CFG = { interval_s: 10 }; // Default-Sendeintervall (Sek.)
const K_INT = "bkw_interval_s"; // Persistenz-Key im KVS
const PUBLISH_QOS = 1;
const RETAIN_STATUS = true;
const FAIL_REBOOT_THRESHOLD = 30; // nach n aufeinanderfolgenden Publish-FAILs rebooten
const MIN_INTERVAL_S = 3;
const MAX_INTERVAL_S = 3600;
const SWITCH_ID = 0; // Shelly Plug S = switch:0
const ENFORCE_ON_AT_BOOT = true; // Beim Start sicher einschalten
let di = Shelly.getDeviceInfo();
let DEVICE_ID = di.id;
function tTelemetry() { return "bkw/" + DEVICE_ID + "/telemetry"; }
function tConfigDev() { return "bkw/config/" + DEVICE_ID; }
function tConfigAll() { return "bkw/config/_all"; }
function tStatus() { return "bkw/status/" + DEVICE_ID; }
function tAck() { return "bkw/config_ack/" + DEVICE_ID; }
let tmr = null;
let failCount = 0;
let mqttConnected = false; // Tracke MQTT-Verbindung, um Marker sicher zu senden
// ---- helpers ----------------------------------------------------------------
function clampInterval(n) {
n = Number(n);
if (!isFinite(n)) n = CFG.interval_s || 10;
return Math.max(MIN_INTERVAL_S, Math.min(n, MAX_INTERVAL_S));
}
function schedule(ms) {
if (tmr) Timer.clear(tmr);
tmr = Timer.set(ms, true, tick);
}
function persistInterval() {
Shelly.call("KVS.Set", { key: K_INT, value: CFG.interval_s }, function () { });
}
function publishAck(source) {
let ack = {
interval_s: CFG.interval_s,
source: source,
ts_utc: (new Date()).toISOString()
};
let ok = MQTT.publish(tAck(), JSON.stringify(ack), PUBLISH_QOS, false);
if (!ok) print("ACK publish failed");
}
function applyInterval(newVal, source) {
let old = CFG.interval_s;
CFG.interval_s = clampInterval(newVal);
if (CFG.interval_s !== old) {
print("Interval set to", CFG.interval_s, "s (src:", source, ")");
persistInterval();
publishAck(source);
schedule(1000); // schnell erste Messung nach Änderung
}
}
// Reboot sauber per RPC (Fix)
function rebootDevice(reason) {
print("Reboot requested:", reason);
Shelly.call("Shelly.Reboot", {}, function (res, code, msg) {
if (code !== 0) print("Reboot RPC failed:", code, msg || "");
});
}
// Fail-safe: Schalter einschalten, falls OFF
function ensureSwitchOn(reason) {
try {
let s = Shelly.getComponentStatus("switch:0") || {};
// Gen2 liefert "output" (boolean); einige Firmwares kennen "state".
let isOn = (s.output === true) || (s.state === true);
if (!isOn && ENFORCE_ON_AT_BOOT) {
Shelly.call("Switch.Set", { id: SWITCH_ID, on: true }, function (res, code, msg) {
if (code === 0) {
print("Switch was OFF → forced ON (reason:", reason, ")");
// Nur Marker senden, wenn MQTT wirklich verbunden ist (sonst verpufft er)
if (mqttConnected) {
MQTT.publish(tStatus(), "forced_on", PUBLISH_QOS, RETAIN_STATUS);
}
} else {
print("Switch.Set failed:", code, msg || "");
}
});
}
} catch (e) {
print("ensureSwitchOn error:", e);
}
}
// ---- measurement & publish --------------------------------------------------
function measureOnce() {
// switch:0 liefert bei Plugs Leistung/Spannung/Strom/Energie
let s = Shelly.getComponentStatus("switch:0") || {};
let payload = {
device_id: DEVICE_ID,
ts_utc: (new Date()).toISOString(),
power_w: (s.apower !== undefined) ? s.apower : null,
voltage_v: (s.voltage !== undefined) ? s.voltage : null,
current_a: (s.current !== undefined) ? s.current : null,
energy_wh: (s.aenergy && s.aenergy.total !== undefined) ? s.aenergy.total : null
};
// Publish Telemetry
let ok = MQTT.publish(tTelemetry(), JSON.stringify(payload), PUBLISH_QOS, false);
print("PUB", tTelemetry(), ok ? "OK" : "FAIL", JSON.stringify(payload));
return ok;
}
// ---- main tick / backoff ----------------------------------------------------
function tick() {
let ok = measureOnce();
failCount = ok ? 0 : (failCount + 1);
if (!ok && failCount >= FAIL_REBOOT_THRESHOLD) {
print("Too many publish FAILs, rebooting device…");
Timer.set(2000, false, function () { rebootDevice("too_many_publish_fails"); });
return;
}
if (!ok) {
// Exponentieller-ish Backoff bis max. 60s
let nextMs = Math.min(60000, 1000 * failCount);
schedule(nextMs);
} else {
// Normales Intervall, innerhalb MIN/MAX clampen
schedule(clampInterval(CFG.interval_s) * 1000);
}
}
// ---- boot: Persistenz laden -------------------------------------------------
Shelly.call("KVS.Get", { key: K_INT }, function (res, code) {
if (code === 0 && res && res.value !== undefined) {
applyInterval(res.value, "kvs");
} else {
// KVS leer -> Default übernehmen & persistieren
persistInterval();
}
});
// ---- boot: Switch absichern -------------------------------------------------
ensureSwitchOn("boot");
// ---- MQTT connect: status + config-subscribe -------------------------------
MQTT.setConnectHandler(function () {
mqttConnected = true;
print("MQTT connected");
// Sicherstellen, dass eingeschaltet ist (z. B. nach WLAN/MQTT-Reconnect)
ensureSwitchOn("mqtt_connect");
// retained Online-Status setzen
MQTT.publish(tStatus(), "online", PUBLISH_QOS, RETAIN_STATUS);
// Per-Device Konfiguration
MQTT.subscribe(tConfigDev(), function (topic, payload) {
try {
let c = JSON.parse(payload);
if (typeof c.interval_s === "number") applyInterval(c.interval_s, "device");
} catch (e) {
print("Config parse error (device):", e);
}
});
// Globale (Fleet-weite) Konfiguration
MQTT.subscribe(tConfigAll(), function (topic, payload) {
try {
let c = JSON.parse(payload);
if (typeof c.interval_s === "number") applyInterval(c.interval_s, "global");
} catch (e) {
print("Config parse error (global):", e);
}
});
failCount = 0;
schedule(1000); // direkt nach Connect eine Messung
});
// ---- MQTT disconnect: Flag zurücksetzen ------------------------------------
MQTT.setDisconnectHandler(function () {
mqttConnected = false;
print("MQTT disconnected");
});
// ---- gentle startup ---------------------------------------------------------
schedule(3000); //falls wir gerade "forced_on" gesetzt haben
Tipp: In der Shelly-Oberfläche unter Scripts das bestehende Skript stoppen, Code ersetzen, speichern und wieder starten.