Setup-Portal

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.