Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Skript läuft plötzlich nicht mehr

NEWS

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    16
    1
    1.9k

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    905

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    25
    1
    2.2k

Skript läuft plötzlich nicht mehr

Geplant Angeheftet Gesperrt Verschoben JavaScript
28 Beiträge 6 Kommentatoren 1.3k Aufrufe 3 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • hotspot_2H hotspot_2

    @asgothian Ok. Ich habe jetzt nochmal von vorne gestartet und reagiere jetzt auf MQTT Push bei Änderungen von den Leistungswerten und schreibe nur das mal in Objekte. Leistung BKW1, Leistung BKW2 und Gesamtleistung.

    Jetzt schau ich mir mal an wie stabil das läuft und dann gehe ich weiter um die Tages-, Monats- und Jahresleistung zu berechnen.

    // ===============================================
    // BKW Live Power (MQTT JSON parse, no-create, sync)
    // - Liest MQTT-JSON aus status.switch:0 (Shelly Plus Plug S)
    // - Netto-Leistung je BKW = max(0, apower - wr_selfconsumption_w)
    // - Gesamtleistung = Summe der Netto-Leistungen
    // - Legt KEINE States an (schreibt nur in vorhandene Objekte)
    // - Nutzt getState/setState (ressourcenschonend)
    // - Schreibt nur bei echter Änderung (mit Toleranz) + bündelt Aggregat
    // ===============================================
    
    // ---------- KONSTANTEN: HIER ANPASSEN ----------
    const MQTT_JSON_BKW1 = 'mqtt.0.shellies.sonstiges.bkw1.status.switch:0';
    const MQTT_JSON_BKW2 = 'mqtt.0.shellies.sonstiges.bkw2.status.switch:0';
    
    // Eigenverbrauch (W) je BKW (Objekte existieren bereits; sonst wird nur gewarnt)
    const SELF_BKW1      = '0_userdata.0.pvundstrom.bkws.1.wr_selfconsumption_w';
    const SELF_BKW2      = '0_userdata.0.pvundstrom.bkws.2.wr_selfconsumption_w';
    
    // Ziel-States (müssen existieren; number, role=value.power, unit=W)
    const OUT_PWR_BKW1   = '0_userdata.0.pvundstrom.bkws.1.power_w';
    const OUT_PWR_BKW2   = '0_userdata.0.pvundstrom.bkws.2.power_w';
    const OUT_PWR_ALL    = '0_userdata.0.pvundstrom.bkws.all.power_w';
    
    // Toleranz & Aggregat-Entprellung
    const EPS_W = 1;              // nur schreiben, wenn Änderung > 1 W
    const AGG_DEBOUNCE_MS = 300;  // bündelt schnelle Mehrfachänderungen
    
    // ---------- interner Aufbau ----------
    const DEVICES = [
      { name: 'bkw1', inJsonId: MQTT_JSON_BKW1, selfId: SELF_BKW1, outId: OUT_PWR_BKW1 },
      { name: 'bkw2', inJsonId: MQTT_JSON_BKW2, selfId: SELF_BKW2, outId: OUT_PWR_BKW2 },
    ];
    
    // ---------- Helpers ----------
    const n = (v, fb=0) => Number.isFinite(Number(v)) ? Number(v) : fb;
    
    const _existCache = new Map();
    function objExists(id) {
      if (_existCache.has(id)) return _existCache.get(id);
      const ok = !!getObject(id);
      _existCache.set(id, ok);
      if (!ok) log(`[BKW] Objekt existiert nicht: ${id}`, 'warn');
      return ok;
    }
    
    // schreibt nur, wenn Ziel existiert UND sich der Wert (mit EPS) geändert hat
    function setChangedNoCreate(id, nextVal, eps = 0, ack = true) {
      if (!objExists(id)) return; // KEINE Anlage
      const cur = getState(id);
      const curVal = cur ? cur.val : undefined;
    
      let same = false;
      if (typeof nextVal === 'number') {
        same = Number.isFinite(Number(curVal)) && Math.abs(Number(curVal) - Number(nextVal)) <= eps;
      } else {
        same = curVal === nextVal;
      }
      if (same) return;
    
      setState(id, nextVal, ack);
    }
    
    // robustes JSON-Parsing für status.switch:0 → apower (W)
    function parseApowerFromState(stateObj) {
      if (!stateObj || typeof stateObj.val !== 'string') return 0;
      try {
        const json = JSON.parse(stateObj.val);
        const ap = Number(json?.apower);
        return Number.isFinite(ap) ? ap : 0;
      } catch (e) {
        const sample = typeof stateObj.val === 'string' ? stateObj.val.slice(0, 120) : '<non-string>';
        log(`[BKW] JSON parse failed (${sample}): ${e.message}`, 'warn');
        return 0;
      }
    }
    
    // Netto-Leistung: max(0, apower - self)
    function computeNetPower(dev) {
      const sJson = getState(dev.inJsonId);
      const sSelf = getState(dev.selfId);
      const ap    = parseApowerFromState(sJson);
      const self  = n(sSelf?.val, 0);
      const net   = Math.max(0, ap - self);
      return { ap, self, net };
    }
    
    // ---------- Aggregat ----------
    let aggTimer = null;
    function scheduleAgg() {
      if (aggTimer) return;
      aggTimer = setTimeout(() => {
        aggTimer = null;
        try {
          let sum = 0;
          for (const d of DEVICES) {
            if (!objExists(d.outId)) continue;
            const s = getState(d.outId);
            sum += s ? n(s.val, 0) : 0;
          }
          setChangedNoCreate(OUT_PWR_ALL, sum, EPS_W, true);
        } catch (e) {
          log(`[BKW] update aggregate failed: ${e && e.message ? e.message : e}`, 'warn');
        }
      }, AGG_DEBOUNCE_MS);
    }
    
    // ---------- Start ----------
    (() => {
      try {
        // Existenz der benutzten IDs einmalig prüfen (ohne Anlegen)
        for (const d of DEVICES) {
          objExists(d.inJsonId);
          objExists(d.selfId);
          objExists(d.outId);
        }
        objExists(OUT_PWR_ALL);
    
        // Initiale Übernahme (rein aus ioBroker-States, kein Gerät-Poll)
        for (const d of DEVICES) {
          if (!objExists(d.inJsonId) || !objExists(d.outId)) continue;
          if (!objExists(d.selfId)) log(`[BKW] Hinweis: Eigenverbrauchs-Objekt fehlt für ${d.name} → wird als 0 W behandelt`, 'warn');
    
          const { net } = computeNetPower(d);
          setChangedNoCreate(d.outId, net, EPS_W, true);
        }
        scheduleAgg();
    
        // Event-Listener: auf Änderungen am JSON ODER am Eigenverbrauch reagieren
        for (const d of DEVICES) {
          // MQTT JSON (status.switch:0)
          if (objExists(d.inJsonId)) {
            on({ id: d.inJsonId, change: 'ne' }, (obj) => {
              try {
                const ap = parseApowerFromState(obj.state);
                const selfS = getState(d.selfId);
                const self = n(selfS?.val, 0);
                const net = Math.max(0, ap - self);
                setChangedNoCreate(d.outId, net, EPS_W, true);
                scheduleAgg();
              } catch (e) {
                log(`[BKW] onJSON ${d.name} failed: ${e && e.message ? e.message : e}`, 'warn');
              }
            });
          }
          // Eigenverbrauch
          if (objExists(d.selfId)) {
            on({ id: d.selfId, change: 'ne' }, () => {
              try {
                const { net } = computeNetPower(d);
                setChangedNoCreate(d.outId, net, EPS_W, true);
                scheduleAgg();
              } catch (e) {
                log(`[BKW] onSelf ${d.name} failed: ${e && e.message ? e.message : e}`, 'warn');
              }
            });
          }
        }
    
        log('[BKW] Live-Power Script gestartet (MQTT JSON, no-create, sync).', 'info');
      } catch (e) {
        log(`[BKW] init failed: ${e && e.message ? e.message : e}`, 'error');
      }
    })();
    
    arteckA Offline
    arteckA Offline
    arteck
    Developer Most Active
    schrieb am zuletzt editiert von
    #21

    @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

    (() => {

    oder

    function S(dev){

    jetzt dich mit javascript auseinander und übernimm nicht blind irgendwelche Klamotten..

    ich hab zwat kein Plan was du in Delphi geschrieben hast aber es scheint nicht viel gewesen zu sein.. oder hast du etwa so units definiert

    uses 
      n, 
      b,
      u;
    

    versteh es nicht falsch das was du machst funktioniert und ist nutzbar aber nicht wartbar wenn ich das lese..

    zigbee hab ich, zwave auch, nuc's genauso und HA auch

    hotspot_2H OliverIOO 2 Antworten Letzte Antwort
    1
    • arteckA arteck

      @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

      (() => {

      oder

      function S(dev){

      jetzt dich mit javascript auseinander und übernimm nicht blind irgendwelche Klamotten..

      ich hab zwat kein Plan was du in Delphi geschrieben hast aber es scheint nicht viel gewesen zu sein.. oder hast du etwa so units definiert

      uses 
        n, 
        b,
        u;
      

      versteh es nicht falsch das was du machst funktioniert und ist nutzbar aber nicht wartbar wenn ich das lese..

      hotspot_2H Offline
      hotspot_2H Offline
      hotspot_2
      schrieb am zuletzt editiert von
      #22

      @arteck Ich verstehe Dich nicht falsch. Ich kann das komplett nachvollziehen was Du sagst.

      Ich "programmiere" gerade auf eine Art und Weise die zwar zum Ergebnis führt in einigen Fällen aber Wartbarkeit, Lesbarer Code und viele andere Aspekte die zum Programmieren gehören werden da nicht berücksichtigt. Es ist halt wirklich sehr zielorientiert, das ist aber nicht das Einzigste und das wird irgendwann zu Problem führen. Für ein paar Codeschnipsel für iobroker kann man das eventuell noch tolerieren aber wenn's größer wird wird das schwierig.

      Und nein, so hätte ich Units nicht definiert und das hat mir auch sofort klar gemacht was Du damit sagen möchtest. Danke dafür.

      Ich bleibe da am Ball, lese mich weiter ein und nutze den nicht ganz optimalen Code als Vehikel um weiter zu kommen.

      Danke für die Analyse das ich den Code zumindest einsetzen kann. Stabiler als vorher läuft er auf jeden Fall mal.

      T 1 Antwort Letzte Antwort
      0
      • hotspot_2H hotspot_2

        @arteck Ich verstehe Dich nicht falsch. Ich kann das komplett nachvollziehen was Du sagst.

        Ich "programmiere" gerade auf eine Art und Weise die zwar zum Ergebnis führt in einigen Fällen aber Wartbarkeit, Lesbarer Code und viele andere Aspekte die zum Programmieren gehören werden da nicht berücksichtigt. Es ist halt wirklich sehr zielorientiert, das ist aber nicht das Einzigste und das wird irgendwann zu Problem führen. Für ein paar Codeschnipsel für iobroker kann man das eventuell noch tolerieren aber wenn's größer wird wird das schwierig.

        Und nein, so hätte ich Units nicht definiert und das hat mir auch sofort klar gemacht was Du damit sagen möchtest. Danke dafür.

        Ich bleibe da am Ball, lese mich weiter ein und nutze den nicht ganz optimalen Code als Vehikel um weiter zu kommen.

        Danke für die Analyse das ich den Code zumindest einsetzen kann. Stabiler als vorher läuft er auf jeden Fall mal.

        T Nicht stören
        T Nicht stören
        ticaki
        schrieb am zuletzt editiert von
        #23

        @hotspot_2
        Du must dem chat bei GPT ein paar Regeln mit geben - vielleicht auch gleich in der Prompt

        • Nur Funktionen benutzen die existieren
        • von Menschen lesbaren Code erzeugen
        • in der Kürze liegt die Würze
        • englische JSDocs hinzufügen
        • deaktivierbare Logausgaben zum debuggen einfügen
        • kommentare nur auf englisch

        ok ich weiß nicht allles- ich hab meinen so oft "an gemeckert", das da ne menge regeln irgendwo gespeichert sind.

        Wenn ich so einen Code bekommen würde wäre mein Kommentar - Was soll den das sein? Buchstabensalat? Bist du unfähig - Nochmal und dieses mal anständig!

        Wenn man einen genervten ärgerlichen Eindruck macht, werden die ergebnisse meist besser - :rolling_on_the_floor_laughing:

        Weather-Warnings Espresense NSPanel-Lovelace-ui Tagesschau

        Spenden

        hotspot_2H 1 Antwort Letzte Antwort
        0
        • T ticaki

          @hotspot_2
          Du must dem chat bei GPT ein paar Regeln mit geben - vielleicht auch gleich in der Prompt

          • Nur Funktionen benutzen die existieren
          • von Menschen lesbaren Code erzeugen
          • in der Kürze liegt die Würze
          • englische JSDocs hinzufügen
          • deaktivierbare Logausgaben zum debuggen einfügen
          • kommentare nur auf englisch

          ok ich weiß nicht allles- ich hab meinen so oft "an gemeckert", das da ne menge regeln irgendwo gespeichert sind.

          Wenn ich so einen Code bekommen würde wäre mein Kommentar - Was soll den das sein? Buchstabensalat? Bist du unfähig - Nochmal und dieses mal anständig!

          Wenn man einen genervten ärgerlichen Eindruck macht, werden die ergebnisse meist besser - :rolling_on_the_floor_laughing:

          hotspot_2H Offline
          hotspot_2H Offline
          hotspot_2
          schrieb am zuletzt editiert von
          #24

          @ticaki Danke für die Tipps. Das werde ich testen und auch hier mal berichten wie dann der Code aussieht.

          Das ChatGPT sehr gut in der Lage ist sich Dinge zu merken hab ich auch schon gemerkt. Ich habe beispielsweise mal erklärt wie ich gerne Objekte in iobroker organisiere im Bereich userdata. Also welche Besamung, wann ich ein Unterordner anlege usw. Das brauche ich nun bei weiteren Javascript Programmierprojekten nicht mehr wiederholen. Das sitzt und alle Objekte werden so wie gewünscht benannt.

          Ich werde jetzt mal eine rauere Gangart pflegen und mal schauen wie dann die Ergebnisse aussehen ;-).

          1 Antwort Letzte Antwort
          0
          • arteckA arteck

            @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

            (() => {

            oder

            function S(dev){

            jetzt dich mit javascript auseinander und übernimm nicht blind irgendwelche Klamotten..

            ich hab zwat kein Plan was du in Delphi geschrieben hast aber es scheint nicht viel gewesen zu sein.. oder hast du etwa so units definiert

            uses 
              n, 
              b,
              u;
            

            versteh es nicht falsch das was du machst funktioniert und ist nutzbar aber nicht wartbar wenn ich das lese..

            OliverIOO Offline
            OliverIOO Offline
            OliverIO
            schrieb am zuletzt editiert von
            #25

            @arteck sagte in Skript läuft plötzlich nicht mehr:

            @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

            (() => {

            Für den Browser ist das eigentlich gar nicht schlecht, für Node ist es unnötig.
            Primär macht man das um im Browser das window Objekt nicht unnötig voll zu machen, da alles innerhalb der Funktion in einem eigenen scope läuft.

            https://developer.mozilla.org/en-US/docs/Glossary/IIFE

            Meine Adapter und Widgets
            TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
            Links im Profil

            hotspot_2H 1 Antwort Letzte Antwort
            0
            • OliverIOO OliverIO

              @arteck sagte in Skript läuft plötzlich nicht mehr:

              @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

              (() => {

              Für den Browser ist das eigentlich gar nicht schlecht, für Node ist es unnötig.
              Primär macht man das um im Browser das window Objekt nicht unnötig voll zu machen, da alles innerhalb der Funktion in einem eigenen scope läuft.

              https://developer.mozilla.org/en-US/docs/Glossary/IIFE

              hotspot_2H Offline
              hotspot_2H Offline
              hotspot_2
              schrieb am zuletzt editiert von hotspot_2
              #26

              Jetzt habe ich mal einen ernsten Ton genutzt und die Regeln übermittelt bestimmt.

              Das Ergebnis sieht für meine Augen schon besser aus.

              // ==================================================
              // BKW Live Power + Daily/Total Energy (no-create, MQTT push)
              // - Reads Shelly MQTT JSON (status.switch:0 → apower)
              // - Net power per BKW = max(0, apower - wr_selfconsumption_w)
              // - Event-based daily energy integration (no polling)
              // - Daily close at 00:00: writes kWh per BKW + total (Grafana), updates TOTAL_KWH
              // - No state creation/history; writes only if targets exist
              // ==================================================
              
              // ---------- CONFIG ----------
              
              // Common prefix for all user states
              const PREFIX = '0_userdata.0.pvundstrom.bkws.';
              
              // MQTT inputs
              const MQTT_JSON_BKW1 = 'mqtt.0.shellies.sonstiges.bkw1.status.switch:0';
              const MQTT_JSON_BKW2 = 'mqtt.0.shellies.sonstiges.bkw2.status.switch:0';
              
              // Self-consumption per BKW (W)
              const SELF_BKW1 = PREFIX + '1.wr_selfconsumption_w';
              const SELF_BKW2 = PREFIX + '2.wr_selfconsumption_w';
              
              // Live power outputs (W)
              const OUT_PWR_BKW1 = PREFIX + '1.power_w';
              const OUT_PWR_BKW2 = PREFIX + '2.power_w';
              const OUT_PWR_ALL  = PREFIX + 'all.power_w';
              
              // Energy states BKW1
              const BKW1_TODAY_Wh      = PREFIX + '1.today_wh';
              const BKW1_TODAY_kWh     = PREFIX + '1.today_kwh';
              const BKW1_LASTTS_MS     = PREFIX + '1.lastts_ms';
              const BKW1_GRAFANA_DAILY = PREFIX + '1.grafana_daily_kwh';
              const BKW1_TOTAL_KWH     = PREFIX + '1.total_kwh';
              
              // Energy states BKW2
              const BKW2_TODAY_Wh      = PREFIX + '2.today_wh';
              const BKW2_TODAY_kWh     = PREFIX + '2.today_kwh';
              const BKW2_LASTTS_MS     = PREFIX + '2.lastts_ms';
              const BKW2_GRAFANA_DAILY = PREFIX + '2.grafana_daily_kwh';
              const BKW2_TOTAL_KWH     = PREFIX + '2.total_kwh';
              
              // Aggregated energy states
              const OUT_TOTAL_TODAY_Wh  = PREFIX + 'all.today_wh';
              const OUT_TOTAL_TODAY_kWh = PREFIX + 'all.today_kwh';
              const OUT_TOTAL_GRAFANA   = PREFIX + 'all.grafana_daily_kwh';
              const OUT_TOTAL_CUM_KWH   = PREFIX + 'all.total_kwh';
              
              const EPS_W = 1;
              const AGG_DEBOUNCE_MS = 300;
              const MAX_DT_MS = 5000;
              const FALLBACK_DT_MS = 1000;
              const ROUND_DISPLAY = 3;
              const RUN_AT_0005 = false;
              
              // Debug switch
              const DEBUG = false;
              function dbg(msg) { if (DEBUG) log('[BKW] ' + msg); }
              
              // ---------- DEVICE MAP ----------
              const DEVICES = [
                {
                  name: 'bkw1',
                  inJsonId: MQTT_JSON_BKW1,
                  selfId:   SELF_BKW1,
                  outId:    OUT_PWR_BKW1,
                  todayWh:  BKW1_TODAY_Wh,
                  todayKWh: BKW1_TODAY_kWh,
                  lastTs:   BKW1_LASTTS_MS,
                  grafDaily:BKW1_GRAFANA_DAILY,
                  totalKWh: BKW1_TOTAL_KWH,
                },
                {
                  name: 'bkw2',
                  inJsonId: MQTT_JSON_BKW2,
                  selfId:   SELF_BKW2,
                  outId:    OUT_PWR_BKW2,
                  todayWh:  BKW2_TODAY_Wh,
                  todayKWh: BKW2_TODAY_kWh,
                  lastTs:   BKW2_LASTTS_MS,
                  grafDaily:BKW2_GRAFANA_DAILY,
                  totalKWh: BKW2_TOTAL_KWH,
                },
                // add more BKWs with same structure if needed
              ];
              
              // ---------- HELPERS ----------
              /**
               * Convert to finite number with fallback.
               * @param {any} value
               * @param {number} fallback
               * @returns {number}
               */
              function toNumberOr(value, fallback) {
                const num = Number(value);
                return Number.isFinite(num) ? num : (Number.isFinite(fallback) ? fallback : 0);
              }
              
              const existsCache = new Map();
              /**
               * Check object existence (cached). Warn once if missing.
               * @param {string} id
               * @returns {boolean}
               */
              function objectExists(id) {
                if (existsCache.has(id)) return existsCache.get(id);
                const ok = !!getObject(id);
                existsCache.set(id, ok);
                if (!ok) log('[BKW] Missing object: ' + id, 'warn');
                return ok;
              }
              
              /**
               * Write only if object exists AND the value changed beyond epsilon.
               * Allows explicit write of zero even within epsilon.
               * @param {string} id
               * @param {any} nextVal
               * @param {number} eps
               * @param {boolean} ack
               */
              function writeIfChanged(id, nextVal, eps, ack) {
                if (!objectExists(id)) return;
                const cur = getState(id);
                const curVal = cur ? cur.val : undefined;
              
                if (typeof nextVal === 'number') {
                  const curNum = Number(curVal);
                  if (Number.isFinite(curNum)) {
                    const withinEps = Math.abs(curNum - nextVal) <= eps;
                    const zeroOverride = nextVal === 0 && curNum > 0;
                    if (withinEps && !zeroOverride) return;
                  }
                } else if (curVal === nextVal) {
                  return;
                }
                setState(id, nextVal, ack);
              }
              
              /**
               * Write only if object exists (no change check).
               * @param {string} id
               * @param {any} nextVal
               * @param {boolean} ack
               */
              function writeIfExists(id, nextVal, ack) {
                if (!objectExists(id)) return;
                setState(id, nextVal, ack);
              }
              
              // ---------- AGGREGATE POWER ----------
              let aggTimer = null;
              /** Debounced update of aggregate power state. */
              function scheduleAggregateUpdate() {
                if (aggTimer) return;
                aggTimer = setTimeout(function () {
                  aggTimer = null;
                  try {
                    let sum = 0;
                    for (let i = 0; i < DEVICES.length; i++) {
                      const d = DEVICES[i];
                      if (!objectExists(d.outId)) continue;
                      const s = getState(d.outId);
                      sum += s ? toNumberOr(s.val, 0) : 0;
                    }
                    writeIfChanged(OUT_PWR_ALL, sum, EPS_W, true);
                  } catch (e) {
                    log('[BKW] aggregate update failed: ' + (e && e.message ? e.message : e), 'warn');
                  }
                }, AGG_DEBOUNCE_MS);
              }
              
              // ---------- ENERGY INTEGRATION ----------
              const lastTsCache = new Map();
              
              /**
               * Integrate device energy based on elapsed time and net power.
               * Updates: lastTs, todayWh, todayKWh and aggregated today totals.
               * @param {{name:string,lastTs:string,todayWh:string,todayKWh:string}} dev
               * @param {number} netPowerW
               */
              function integrateDeviceEnergy(dev, netPowerW) {
                const now = Date.now();
                let last = lastTsCache.get(dev.name);
                if (!Number.isFinite(last)) last = toNumberOr(getState(dev.lastTs)?.val, now);
              
                let dt = now - last;
                if (dt < 0 || dt > MAX_DT_MS) {
                  dt = FALLBACK_DT_MS;
                  dbg('[' + dev.name + '] implausible dt → fallback ' + dt + 'ms');
                }
              
                // If no power, just advance lastTs to keep dt bounded.
                if (netPowerW <= 0) {
                  writeIfExists(dev.lastTs, now, true);
                  lastTsCache.set(dev.name, now);
                  return;
                }
              
                const WhInc = netPowerW * (dt / 3600000);
                let todayWh = toNumberOr(getState(dev.todayWh)?.val, 0) + WhInc;
                if (todayWh < 0) todayWh = 0;
                const todayKWh = todayWh / 1000;
              
                writeIfExists(dev.lastTs, now, true);
                lastTsCache.set(dev.name, now);
                writeIfExists(dev.todayWh, todayWh, true);
                writeIfChanged(dev.todayKWh, Number(todayKWh.toFixed(ROUND_DISPLAY)), 0, true);
              
                // Update aggregated "today"
                let sumWh = 0;
                for (let i = 0; i < DEVICES.length; i++) {
                  sumWh += toNumberOr(getState(DEVICES[i].todayWh)?.val, 0);
                }
                writeIfExists(OUT_TOTAL_TODAY_Wh, sumWh, true);
                writeIfChanged(OUT_TOTAL_TODAY_kWh, Number((sumWh / 1000).toFixed(ROUND_DISPLAY)), 0, true);
              }
              
              // ---------- INIT ----------
              (function init() {
                try {
                  // Check existence (no creation)
                  for (let i = 0; i < DEVICES.length; i++) {
                    const d = DEVICES[i];
                    objectExists(d.inJsonId); objectExists(d.selfId); objectExists(d.outId);
                    objectExists(d.todayWh);  objectExists(d.todayKWh); objectExists(d.lastTs);
                    objectExists(d.grafDaily);objectExists(d.totalKWh);
                  }
                  objectExists(OUT_PWR_ALL);
                  objectExists(OUT_TOTAL_TODAY_Wh);
                  objectExists(OUT_TOTAL_TODAY_kWh);
                  objectExists(OUT_TOTAL_GRAFANA);
                  objectExists(OUT_TOTAL_CUM_KWH);
              
                  // Initial live power & lastTs cache
                  for (let i = 0; i < DEVICES.length; i++) {
                    const d = DEVICES[i];
                    if (!objectExists(d.inJsonId) || !objectExists(d.outId)) continue;
                    if (!objectExists(d.selfId)) log('[BKW] Note: self-consumption missing for ' + d.name + ' → 0 W', 'warn');
              
                    // Inline apower parse + net calculation
                    let ap = 0;
                    const jState = getState(d.inJsonId);
                    if (jState && typeof jState.val === 'string') {
                      try {
                        const j = JSON.parse(jState.val);
                        ap = Number(j && j.apower) || 0;
                      } catch (e) {
                        const sample = typeof jState.val === 'string' ? jState.val.slice(0, 120) : '<non-string>';
                        log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                      }
                    }
                    const self = toNumberOr(getState(d.selfId)?.val, 0);
                    const net = Math.max(0, ap - self);
              
                    writeIfChanged(d.outId, net, EPS_W, true);
              
                    const lastStored = toNumberOr(getState(d.lastTs)?.val, NaN);
                    lastTsCache.set(d.name, Number.isFinite(lastStored) ? lastStored : Date.now());
                  }
                  scheduleAggregateUpdate();
              
                  // Event listeners
                  for (let i = 0; i < DEVICES.length; i++) {
                    const d = DEVICES[i];
              
                    // MQTT JSON changed
                    if (objectExists(d.inJsonId)) {
                      on({ id: d.inJsonId, change: 'ne' }, (obj) => {
                        try {
                          // Inline apower parse
                          let ap = 0;
                          if (obj && obj.state && typeof obj.state.val === 'string') {
                            try {
                              const j = JSON.parse(obj.state.val);
                              ap = Number(j && j.apower) || 0;
                            } catch (e) {
                              const sample = typeof obj.state.val === 'string' ? obj.state.val.slice(0, 120) : '<non-string>';
                              log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                            }
                          }
                          const self = toNumberOr(getState(d.selfId)?.val, 0);
                          const net = Math.max(0, ap - self);
              
                          writeIfChanged(d.outId, net, EPS_W, true);
                          scheduleAggregateUpdate();
                          integrateDeviceEnergy(d, net);
                        } catch (e) {
                          log('[BKW] onJSON ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                        }
                      });
                    }
              
                    // Self-consumption changed
                    if (objectExists(d.selfId)) {
                      on({ id: d.selfId, change: 'ne' }, () => {
                        try {
                          // Inline apower parse + net
                          let ap = 0;
                          const jState = getState(d.inJsonId);
                          if (jState && typeof jState.val === 'string') {
                            try {
                              const j = JSON.parse(jState.val);
                              ap = Number(j && j.apower) || 0;
                            } catch (e) {
                              const sample = typeof jState.val === 'string' ? jState.val.slice(0, 120) : '<non-string>';
                              log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                            }
                          }
                          const self = toNumberOr(getState(d.selfId)?.val, 0);
                          const net = Math.max(0, ap - self);
              
                          writeIfChanged(d.outId, net, EPS_W, true);
                          scheduleAggregateUpdate();
                          integrateDeviceEnergy(d, net);
                        } catch (e) {
                          log('[BKW] onSelf ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                        }
                      });
                    }
                  }
              
                  log('[BKW] Live power + energy script started (MQTT push, no-create).', 'info');
                } catch (e) {
                  log('[BKW] init failed: ' + (e && e.message ? e.message : e), 'error');
                }
              })();
              
              // ---------- DAILY CLOSE ----------
              /**
               * Daily close: write yesterday's kWh (ts = prev day 23:59:59.999),
               * accumulate totals, reset today's counters.
               */
              function dailyClose() {
                try {
                  const tsStartToday = new Date().setHours(0, 0, 0, 0);
                  const tsPrevDayEnd = tsStartToday - 1;
              
                  let totalDayKWh = 0;
                  for (let i = 0; i < DEVICES.length; i++) {
                    const d = DEVICES[i];
                    const dayKWh = toNumberOr(getState(d.todayKWh)?.val, 0);
                    totalDayKWh += dayKWh;
              
                    writeIfExists(d.grafDaily, { val: Number(dayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
              
                    const cum = toNumberOr(getState(d.totalKWh)?.val, 0) + dayKWh;
                    writeIfExists(d.totalKWh, Number(cum.toFixed(ROUND_DISPLAY)), true);
              
                    writeIfExists(d.todayWh, 0, true);
                    writeIfExists(d.todayKWh, 0, true);
              
                    const now = Date.now();
                    writeIfExists(d.lastTs, now, true);
                    lastTsCache.set(d.name, now);
                  }
              
                  writeIfExists(OUT_TOTAL_GRAFANA, { val: Number(totalDayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
              
                  const totalCum = toNumberOr(getState(OUT_TOTAL_CUM_KWH)?.val, 0) + totalDayKWh;
                  writeIfExists(OUT_TOTAL_CUM_KWH, Number(totalCum.toFixed(ROUND_DISPLAY)), true);
              
                  writeIfExists(OUT_TOTAL_TODAY_Wh, 0, true);
                  writeIfExists(OUT_TOTAL_TODAY_kWh, 0, true);
              
                  log('[BKW] Daily close: total=' + totalDayKWh.toFixed(ROUND_DISPLAY) + ' kWh; cum=' + totalCum.toFixed(ROUND_DISPLAY) + ' kWh');
                } catch (e) {
                  log('[BKW] daily close failed: ' + (e && e.message ? e.message : e), 'warn');
                }
              }
              
              // Cron 00:00
              schedule('0 0 * * *', dailyClose);
              
              // Optional 00:05
              if (RUN_AT_0005) {
                schedule('5 0 * * *', function () {
                  try { dailyClose(); log('[BKW] 00:05 refresh executed.'); }
                  catch (e) { log('[BKW] 00:05 error: ' + e, 'warn'); }
                });
              }
              
              hotspot_2H 1 Antwort Letzte Antwort
              0
              • hotspot_2H hotspot_2

                Jetzt habe ich mal einen ernsten Ton genutzt und die Regeln übermittelt bestimmt.

                Das Ergebnis sieht für meine Augen schon besser aus.

                // ==================================================
                // BKW Live Power + Daily/Total Energy (no-create, MQTT push)
                // - Reads Shelly MQTT JSON (status.switch:0 → apower)
                // - Net power per BKW = max(0, apower - wr_selfconsumption_w)
                // - Event-based daily energy integration (no polling)
                // - Daily close at 00:00: writes kWh per BKW + total (Grafana), updates TOTAL_KWH
                // - No state creation/history; writes only if targets exist
                // ==================================================
                
                // ---------- CONFIG ----------
                
                // Common prefix for all user states
                const PREFIX = '0_userdata.0.pvundstrom.bkws.';
                
                // MQTT inputs
                const MQTT_JSON_BKW1 = 'mqtt.0.shellies.sonstiges.bkw1.status.switch:0';
                const MQTT_JSON_BKW2 = 'mqtt.0.shellies.sonstiges.bkw2.status.switch:0';
                
                // Self-consumption per BKW (W)
                const SELF_BKW1 = PREFIX + '1.wr_selfconsumption_w';
                const SELF_BKW2 = PREFIX + '2.wr_selfconsumption_w';
                
                // Live power outputs (W)
                const OUT_PWR_BKW1 = PREFIX + '1.power_w';
                const OUT_PWR_BKW2 = PREFIX + '2.power_w';
                const OUT_PWR_ALL  = PREFIX + 'all.power_w';
                
                // Energy states BKW1
                const BKW1_TODAY_Wh      = PREFIX + '1.today_wh';
                const BKW1_TODAY_kWh     = PREFIX + '1.today_kwh';
                const BKW1_LASTTS_MS     = PREFIX + '1.lastts_ms';
                const BKW1_GRAFANA_DAILY = PREFIX + '1.grafana_daily_kwh';
                const BKW1_TOTAL_KWH     = PREFIX + '1.total_kwh';
                
                // Energy states BKW2
                const BKW2_TODAY_Wh      = PREFIX + '2.today_wh';
                const BKW2_TODAY_kWh     = PREFIX + '2.today_kwh';
                const BKW2_LASTTS_MS     = PREFIX + '2.lastts_ms';
                const BKW2_GRAFANA_DAILY = PREFIX + '2.grafana_daily_kwh';
                const BKW2_TOTAL_KWH     = PREFIX + '2.total_kwh';
                
                // Aggregated energy states
                const OUT_TOTAL_TODAY_Wh  = PREFIX + 'all.today_wh';
                const OUT_TOTAL_TODAY_kWh = PREFIX + 'all.today_kwh';
                const OUT_TOTAL_GRAFANA   = PREFIX + 'all.grafana_daily_kwh';
                const OUT_TOTAL_CUM_KWH   = PREFIX + 'all.total_kwh';
                
                const EPS_W = 1;
                const AGG_DEBOUNCE_MS = 300;
                const MAX_DT_MS = 5000;
                const FALLBACK_DT_MS = 1000;
                const ROUND_DISPLAY = 3;
                const RUN_AT_0005 = false;
                
                // Debug switch
                const DEBUG = false;
                function dbg(msg) { if (DEBUG) log('[BKW] ' + msg); }
                
                // ---------- DEVICE MAP ----------
                const DEVICES = [
                  {
                    name: 'bkw1',
                    inJsonId: MQTT_JSON_BKW1,
                    selfId:   SELF_BKW1,
                    outId:    OUT_PWR_BKW1,
                    todayWh:  BKW1_TODAY_Wh,
                    todayKWh: BKW1_TODAY_kWh,
                    lastTs:   BKW1_LASTTS_MS,
                    grafDaily:BKW1_GRAFANA_DAILY,
                    totalKWh: BKW1_TOTAL_KWH,
                  },
                  {
                    name: 'bkw2',
                    inJsonId: MQTT_JSON_BKW2,
                    selfId:   SELF_BKW2,
                    outId:    OUT_PWR_BKW2,
                    todayWh:  BKW2_TODAY_Wh,
                    todayKWh: BKW2_TODAY_kWh,
                    lastTs:   BKW2_LASTTS_MS,
                    grafDaily:BKW2_GRAFANA_DAILY,
                    totalKWh: BKW2_TOTAL_KWH,
                  },
                  // add more BKWs with same structure if needed
                ];
                
                // ---------- HELPERS ----------
                /**
                 * Convert to finite number with fallback.
                 * @param {any} value
                 * @param {number} fallback
                 * @returns {number}
                 */
                function toNumberOr(value, fallback) {
                  const num = Number(value);
                  return Number.isFinite(num) ? num : (Number.isFinite(fallback) ? fallback : 0);
                }
                
                const existsCache = new Map();
                /**
                 * Check object existence (cached). Warn once if missing.
                 * @param {string} id
                 * @returns {boolean}
                 */
                function objectExists(id) {
                  if (existsCache.has(id)) return existsCache.get(id);
                  const ok = !!getObject(id);
                  existsCache.set(id, ok);
                  if (!ok) log('[BKW] Missing object: ' + id, 'warn');
                  return ok;
                }
                
                /**
                 * Write only if object exists AND the value changed beyond epsilon.
                 * Allows explicit write of zero even within epsilon.
                 * @param {string} id
                 * @param {any} nextVal
                 * @param {number} eps
                 * @param {boolean} ack
                 */
                function writeIfChanged(id, nextVal, eps, ack) {
                  if (!objectExists(id)) return;
                  const cur = getState(id);
                  const curVal = cur ? cur.val : undefined;
                
                  if (typeof nextVal === 'number') {
                    const curNum = Number(curVal);
                    if (Number.isFinite(curNum)) {
                      const withinEps = Math.abs(curNum - nextVal) <= eps;
                      const zeroOverride = nextVal === 0 && curNum > 0;
                      if (withinEps && !zeroOverride) return;
                    }
                  } else if (curVal === nextVal) {
                    return;
                  }
                  setState(id, nextVal, ack);
                }
                
                /**
                 * Write only if object exists (no change check).
                 * @param {string} id
                 * @param {any} nextVal
                 * @param {boolean} ack
                 */
                function writeIfExists(id, nextVal, ack) {
                  if (!objectExists(id)) return;
                  setState(id, nextVal, ack);
                }
                
                // ---------- AGGREGATE POWER ----------
                let aggTimer = null;
                /** Debounced update of aggregate power state. */
                function scheduleAggregateUpdate() {
                  if (aggTimer) return;
                  aggTimer = setTimeout(function () {
                    aggTimer = null;
                    try {
                      let sum = 0;
                      for (let i = 0; i < DEVICES.length; i++) {
                        const d = DEVICES[i];
                        if (!objectExists(d.outId)) continue;
                        const s = getState(d.outId);
                        sum += s ? toNumberOr(s.val, 0) : 0;
                      }
                      writeIfChanged(OUT_PWR_ALL, sum, EPS_W, true);
                    } catch (e) {
                      log('[BKW] aggregate update failed: ' + (e && e.message ? e.message : e), 'warn');
                    }
                  }, AGG_DEBOUNCE_MS);
                }
                
                // ---------- ENERGY INTEGRATION ----------
                const lastTsCache = new Map();
                
                /**
                 * Integrate device energy based on elapsed time and net power.
                 * Updates: lastTs, todayWh, todayKWh and aggregated today totals.
                 * @param {{name:string,lastTs:string,todayWh:string,todayKWh:string}} dev
                 * @param {number} netPowerW
                 */
                function integrateDeviceEnergy(dev, netPowerW) {
                  const now = Date.now();
                  let last = lastTsCache.get(dev.name);
                  if (!Number.isFinite(last)) last = toNumberOr(getState(dev.lastTs)?.val, now);
                
                  let dt = now - last;
                  if (dt < 0 || dt > MAX_DT_MS) {
                    dt = FALLBACK_DT_MS;
                    dbg('[' + dev.name + '] implausible dt → fallback ' + dt + 'ms');
                  }
                
                  // If no power, just advance lastTs to keep dt bounded.
                  if (netPowerW <= 0) {
                    writeIfExists(dev.lastTs, now, true);
                    lastTsCache.set(dev.name, now);
                    return;
                  }
                
                  const WhInc = netPowerW * (dt / 3600000);
                  let todayWh = toNumberOr(getState(dev.todayWh)?.val, 0) + WhInc;
                  if (todayWh < 0) todayWh = 0;
                  const todayKWh = todayWh / 1000;
                
                  writeIfExists(dev.lastTs, now, true);
                  lastTsCache.set(dev.name, now);
                  writeIfExists(dev.todayWh, todayWh, true);
                  writeIfChanged(dev.todayKWh, Number(todayKWh.toFixed(ROUND_DISPLAY)), 0, true);
                
                  // Update aggregated "today"
                  let sumWh = 0;
                  for (let i = 0; i < DEVICES.length; i++) {
                    sumWh += toNumberOr(getState(DEVICES[i].todayWh)?.val, 0);
                  }
                  writeIfExists(OUT_TOTAL_TODAY_Wh, sumWh, true);
                  writeIfChanged(OUT_TOTAL_TODAY_kWh, Number((sumWh / 1000).toFixed(ROUND_DISPLAY)), 0, true);
                }
                
                // ---------- INIT ----------
                (function init() {
                  try {
                    // Check existence (no creation)
                    for (let i = 0; i < DEVICES.length; i++) {
                      const d = DEVICES[i];
                      objectExists(d.inJsonId); objectExists(d.selfId); objectExists(d.outId);
                      objectExists(d.todayWh);  objectExists(d.todayKWh); objectExists(d.lastTs);
                      objectExists(d.grafDaily);objectExists(d.totalKWh);
                    }
                    objectExists(OUT_PWR_ALL);
                    objectExists(OUT_TOTAL_TODAY_Wh);
                    objectExists(OUT_TOTAL_TODAY_kWh);
                    objectExists(OUT_TOTAL_GRAFANA);
                    objectExists(OUT_TOTAL_CUM_KWH);
                
                    // Initial live power & lastTs cache
                    for (let i = 0; i < DEVICES.length; i++) {
                      const d = DEVICES[i];
                      if (!objectExists(d.inJsonId) || !objectExists(d.outId)) continue;
                      if (!objectExists(d.selfId)) log('[BKW] Note: self-consumption missing for ' + d.name + ' → 0 W', 'warn');
                
                      // Inline apower parse + net calculation
                      let ap = 0;
                      const jState = getState(d.inJsonId);
                      if (jState && typeof jState.val === 'string') {
                        try {
                          const j = JSON.parse(jState.val);
                          ap = Number(j && j.apower) || 0;
                        } catch (e) {
                          const sample = typeof jState.val === 'string' ? jState.val.slice(0, 120) : '<non-string>';
                          log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                        }
                      }
                      const self = toNumberOr(getState(d.selfId)?.val, 0);
                      const net = Math.max(0, ap - self);
                
                      writeIfChanged(d.outId, net, EPS_W, true);
                
                      const lastStored = toNumberOr(getState(d.lastTs)?.val, NaN);
                      lastTsCache.set(d.name, Number.isFinite(lastStored) ? lastStored : Date.now());
                    }
                    scheduleAggregateUpdate();
                
                    // Event listeners
                    for (let i = 0; i < DEVICES.length; i++) {
                      const d = DEVICES[i];
                
                      // MQTT JSON changed
                      if (objectExists(d.inJsonId)) {
                        on({ id: d.inJsonId, change: 'ne' }, (obj) => {
                          try {
                            // Inline apower parse
                            let ap = 0;
                            if (obj && obj.state && typeof obj.state.val === 'string') {
                              try {
                                const j = JSON.parse(obj.state.val);
                                ap = Number(j && j.apower) || 0;
                              } catch (e) {
                                const sample = typeof obj.state.val === 'string' ? obj.state.val.slice(0, 120) : '<non-string>';
                                log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                              }
                            }
                            const self = toNumberOr(getState(d.selfId)?.val, 0);
                            const net = Math.max(0, ap - self);
                
                            writeIfChanged(d.outId, net, EPS_W, true);
                            scheduleAggregateUpdate();
                            integrateDeviceEnergy(d, net);
                          } catch (e) {
                            log('[BKW] onJSON ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                          }
                        });
                      }
                
                      // Self-consumption changed
                      if (objectExists(d.selfId)) {
                        on({ id: d.selfId, change: 'ne' }, () => {
                          try {
                            // Inline apower parse + net
                            let ap = 0;
                            const jState = getState(d.inJsonId);
                            if (jState && typeof jState.val === 'string') {
                              try {
                                const j = JSON.parse(jState.val);
                                ap = Number(j && j.apower) || 0;
                              } catch (e) {
                                const sample = typeof jState.val === 'string' ? jState.val.slice(0, 120) : '<non-string>';
                                log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                              }
                            }
                            const self = toNumberOr(getState(d.selfId)?.val, 0);
                            const net = Math.max(0, ap - self);
                
                            writeIfChanged(d.outId, net, EPS_W, true);
                            scheduleAggregateUpdate();
                            integrateDeviceEnergy(d, net);
                          } catch (e) {
                            log('[BKW] onSelf ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                          }
                        });
                      }
                    }
                
                    log('[BKW] Live power + energy script started (MQTT push, no-create).', 'info');
                  } catch (e) {
                    log('[BKW] init failed: ' + (e && e.message ? e.message : e), 'error');
                  }
                })();
                
                // ---------- DAILY CLOSE ----------
                /**
                 * Daily close: write yesterday's kWh (ts = prev day 23:59:59.999),
                 * accumulate totals, reset today's counters.
                 */
                function dailyClose() {
                  try {
                    const tsStartToday = new Date().setHours(0, 0, 0, 0);
                    const tsPrevDayEnd = tsStartToday - 1;
                
                    let totalDayKWh = 0;
                    for (let i = 0; i < DEVICES.length; i++) {
                      const d = DEVICES[i];
                      const dayKWh = toNumberOr(getState(d.todayKWh)?.val, 0);
                      totalDayKWh += dayKWh;
                
                      writeIfExists(d.grafDaily, { val: Number(dayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                
                      const cum = toNumberOr(getState(d.totalKWh)?.val, 0) + dayKWh;
                      writeIfExists(d.totalKWh, Number(cum.toFixed(ROUND_DISPLAY)), true);
                
                      writeIfExists(d.todayWh, 0, true);
                      writeIfExists(d.todayKWh, 0, true);
                
                      const now = Date.now();
                      writeIfExists(d.lastTs, now, true);
                      lastTsCache.set(d.name, now);
                    }
                
                    writeIfExists(OUT_TOTAL_GRAFANA, { val: Number(totalDayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                
                    const totalCum = toNumberOr(getState(OUT_TOTAL_CUM_KWH)?.val, 0) + totalDayKWh;
                    writeIfExists(OUT_TOTAL_CUM_KWH, Number(totalCum.toFixed(ROUND_DISPLAY)), true);
                
                    writeIfExists(OUT_TOTAL_TODAY_Wh, 0, true);
                    writeIfExists(OUT_TOTAL_TODAY_kWh, 0, true);
                
                    log('[BKW] Daily close: total=' + totalDayKWh.toFixed(ROUND_DISPLAY) + ' kWh; cum=' + totalCum.toFixed(ROUND_DISPLAY) + ' kWh');
                  } catch (e) {
                    log('[BKW] daily close failed: ' + (e && e.message ? e.message : e), 'warn');
                  }
                }
                
                // Cron 00:00
                schedule('0 0 * * *', dailyClose);
                
                // Optional 00:05
                if (RUN_AT_0005) {
                  schedule('5 0 * * *', function () {
                    try { dailyClose(); log('[BKW] 00:05 refresh executed.'); }
                    catch (e) { log('[BKW] 00:05 error: ' + e, 'warn'); }
                  });
                }
                
                hotspot_2H Offline
                hotspot_2H Offline
                hotspot_2
                schrieb am zuletzt editiert von
                #27

                So, bin - mein Eindruck - ein deutliches Stück weiter gekommen. Ich habe ein Regelwerk festgelegt das wir nun für die Skript Aktivitäten nutzen und uns auf verschiedene Dinge verständigt (wie z.B. Versionierung usw.). Auch wann es sinnvoll ist Helper (functions) anzulegen und wann nicht.

                Das geht in die richtige Richtung und ich lerne auch was dabei.

                Macht immer mehr Spaß das Ganze.

                /**
                 * BKW Live Power + Daily/Total Energy (MQTT push)
                 * Version: 0.1.0
                 * Changelog:
                 * - 0.1.0 (2025-10-01) Start of versioning; compact event-driven integration; self-consumption in mW; no existence checks
                 *
                 * Summary:
                 * - Reads Shelly MQTT JSON (status.switch:0 → apower in W)
                 * - Net per BKW = max(0, apower_W - self_W); self is provided in mW (integer)
                 * - Integrates energy event-based using full elapsed time (Δt) since last event
                 * - Daily close at 00:00: writes kWh per BKW + total (Grafana) and updates cumulative totals
                 *
                 * Notes:
                 * - Assumes all datapoints exist; no auto-creation, no existence checks
                 * - Rounds kWh once when writing the kWh states
                 */
                
                // ========================== Configuration ==========================
                
                /** Common prefix for all user states */
                const PREFIX = '0_userdata.0.pvundstrom.bkws.';
                
                /** Shelly MQTT inputs (JSON strings with field "apower") */
                const MQTT_JSON_BKW1 = 'mqtt.0.shellies.sonstiges.bkw1.status.switch:0';
                const MQTT_JSON_BKW2 = 'mqtt.0.shellies.sonstiges.bkw2.status.switch:0';
                
                /** WR self-consumption (mW, integer, e.g. 400 → 0.4 W) */
                const SELF_BKW1_mW = PREFIX + '1.wr_selfconsumption_mw';
                const SELF_BKW2_mW = PREFIX + '2.wr_selfconsumption_mw';
                
                /** Live power outputs (W, NET after self-consumption) */
                const OUT_PWR_BKW1 = PREFIX + '1.power_w';
                const OUT_PWR_BKW2 = PREFIX + '2.power_w';
                const OUT_PWR_ALL  = PREFIX + 'all.power_w';
                
                /** Energy states BKW1 */
                const BKW1_TODAY_Wh      = PREFIX + '1.today_wh';
                const BKW1_TODAY_kWh     = PREFIX + '1.today_kwh';
                const BKW1_LASTTS_MS     = PREFIX + '1.lastts_ms';
                const BKW1_GRAFANA_DAILY = PREFIX + '1.grafana_daily_kwh';
                const BKW1_TOTAL_KWH     = PREFIX + '1.total_kwh';
                
                /** Energy states BKW2 */
                const BKW2_TODAY_Wh      = PREFIX + '2.today_wh';
                const BKW2_TODAY_kWh     = PREFIX + '2.today_kwh';
                const BKW2_LASTTS_MS     = PREFIX + '2.lastts_ms';
                const BKW2_GRAFANA_DAILY = PREFIX + '2.grafana_daily_kwh';
                const BKW2_TOTAL_KWH     = PREFIX + '2.total_kwh';
                
                /** Aggregated energy states */
                const OUT_TOTAL_TODAY_Wh  = PREFIX + 'all.today_wh';
                const OUT_TOTAL_TODAY_kWh = PREFIX + 'all.today_kwh';
                const OUT_TOTAL_GRAFANA   = PREFIX + 'all.grafana_daily_kwh';
                const OUT_TOTAL_CUM_KWH   = PREFIX + 'all.total_kwh';
                
                /** Tolerances & formatting */
                const EPS_W         = 1;   // write threshold for live power
                const ROUND_DISPLAY = 3;   // kWh rounding for display/storage
                const RUN_AT_0005   = false; // optional second daily close at 00:05
                
                /** Debug switch */
                const DEBUG = false;
                function dbg(msg) { if (DEBUG) log('[BKW] ' + msg); }
                
                // Device map
                const DEVICES = [
                  {
                    name: 'bkw1',
                    inJsonId: MQTT_JSON_BKW1,
                    selfMwId: SELF_BKW1_mW,
                    outId:    OUT_PWR_BKW1,
                    todayWh:  BKW1_TODAY_Wh,
                    todayKWh: BKW1_TODAY_kWh,
                    lastTs:   BKW1_LASTTS_MS,
                    grafDaily:BKW1_GRAFANA_DAILY,
                    totalKWh: BKW1_TOTAL_KWH,
                  },
                  {
                    name: 'bkw2',
                    inJsonId: MQTT_JSON_BKW2,
                    selfMwId: SELF_BKW2_mW,
                    outId:    OUT_PWR_BKW2,
                    todayWh:  BKW2_TODAY_Wh,
                    todayKWh: BKW2_TODAY_kWh,
                    lastTs:   BKW2_LASTTS_MS,
                    grafDaily:BKW2_GRAFANA_DAILY,
                    totalKWh: BKW2_TOTAL_KWH,
                  },
                ];
                
                // ========================== Lightweight helpers ==========================
                
                /** Number coercion with fallback */
                function toNumber(v, fb = 0) { const n = Number(v); return Number.isFinite(n) ? n : fb; }
                
                /** Changed-only numeric write with epsilon */
                function setIfChangedNumber(id, next, eps = 0, ack = true) {
                  const cur = Number(getState(id)?.val);
                  const within = Number.isFinite(cur) ? Math.abs(cur - next) <= eps : false;
                  if (!within) setState(id, next, ack);
                }
                
                /** Parse Shelly JSON → apower (W), clamped to >=0 */
                function parseApowerW(jsonVal) {
                  let ap = 0;
                  if (typeof jsonVal === 'string') {
                    try { ap = Number(JSON.parse(jsonVal)?.apower) || 0; }
                    catch (e) {
                      const sample = jsonVal.slice(0, 120);
                      log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                    }
                  }
                  if (!Number.isFinite(ap) || ap < 0) ap = 0;
                  return ap;
                }
                
                /** Read self-consumption in W from mW state (>=0) */
                function readSelfW(id) {
                  const mw = toNumber(getState(id)?.val, 0);
                  return mw > 0 ? (mw / 1000) : 0;
                }
                
                // ========================== Aggregate live power ==========================
                let aggTimer = null;
                function scheduleAggregateLive() {
                  if (aggTimer) return;
                  aggTimer = setTimeout(() => {
                    aggTimer = null;
                    try {
                      let sum = 0;
                      for (const d of DEVICES) {
                        const v = toNumber(getState(d.outId)?.val, 0);
                        if (v > 0) sum += v;
                      }
                      if (sum < 0) sum = 0;
                      setIfChangedNumber(OUT_PWR_ALL, sum, EPS_W, true);
                    } catch (e) {
                      log('[BKW] aggregate update failed: ' + (e && e.message ? e.message : e), 'warn');
                    }
                  }, 300); // small debounce for aggregate
                }
                
                // ========================== Energy integration per device ==========================
                const lastTsCache = new Map();
                
                /**
                 * Integrate energy using full Δt since last event; update live power and daily totals.
                 * @param {*} dev device entry from DEVICES
                 * @param {number} apowerW AC power from Shelly (W, >=0)
                 * @param {number} selfW   WR self-consumption (W, >=0)
                 */
                function integrate(dev, apowerW, selfW) {
                  const now = Date.now();
                  let last = lastTsCache.get(dev.name);
                  if (!Number.isFinite(last)) last = toNumber(getState(dev.lastTs)?.val, now);
                
                  // Full elapsed time since last event (no cap), protect against negative
                  let dt = now - last;
                  if (dt < 0) dt = 0;
                
                  // Advance lastTs immediately to avoid Δt growth on next event
                  setState(dev.lastTs, now, true);
                  lastTsCache.set(dev.name, now);
                
                  // Net power (never negative)
                  const P = apowerW > 0 ? apowerW : 0;
                  const S = selfW   > 0 ? selfW   : 0;
                  const netW = Math.max(0, P - S);
                
                  // Live power (changed-only)
                  setIfChangedNumber(dev.outId, netW, EPS_W, true);
                
                  // No energy when net power is zero
                  if (netW <= 0) return;
                
                  // W * (ms → h) = Wh
                  const WhInc = netW * (dt / 3600000);
                  let todayWh = toNumber(getState(dev.todayWh)?.val, 0) + WhInc;
                  if (todayWh < 0) todayWh = 0;
                
                  const todayKWh = todayWh / 1000;
                
                  setState(dev.todayWh,  todayWh, true);
                  setState(dev.todayKWh, Number(todayKWh.toFixed(ROUND_DISPLAY)), true);
                
                  // Update aggregated "today"
                  let sumWh = 0;
                  for (const d2 of DEVICES) {
                    const v = toNumber(getState(d2.todayWh)?.val, 0);
                    if (v > 0) sumWh += v;
                  }
                  if (sumWh < 0) sumWh = 0;
                
                  setState(OUT_TOTAL_TODAY_Wh,  sumWh, true);
                  setState(OUT_TOTAL_TODAY_kWh, Number((sumWh / 1000).toFixed(ROUND_DISPLAY)), true);
                
                  dbg(`[${dev.name}] P=${P}W, self=${S}W, net=${netW}W, dt=${dt}ms, Wh+=${WhInc.toFixed(3)}`);
                }
                
                // ========================== Init & Event binding ==========================
                (function init() {
                  try {
                    // Initialize live power and lastTs cache from current states
                    for (const d of DEVICES) {
                      const ap   = parseApowerW(getState(d.inJsonId)?.val);
                      const self = readSelfW(d.selfMwId);
                      const net  = Math.max(0, ap - self);
                      setIfChangedNumber(d.outId, net, EPS_W, true);
                
                      const lastStored = toNumber(getState(d.lastTs)?.val, NaN);
                      lastTsCache.set(d.name, Number.isFinite(lastStored) ? lastStored : Date.now());
                    }
                    scheduleAggregateLive();
                
                    // Event listeners
                    for (const d of DEVICES) {
                      // MQTT JSON changed or re-written → integrate
                      on({ id: d.inJsonId, change: 'any' }, (obj) => {
                        try {
                          const ap   = parseApowerW(obj?.state?.val);
                          const self = readSelfW(d.selfMwId);
                          integrate(d, ap, self);
                          scheduleAggregateLive();
                        } catch (e) {
                          log('[BKW] onJSON ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                        }
                      });
                
                      // Self-consumption changed (mW) → recompute/integrate at current apower
                      on({ id: d.selfMwId, change: 'ne' }, () => {
                        try {
                          const ap   = parseApowerW(getState(d.inJsonId)?.val);
                          const self = readSelfW(d.selfMwId);
                          integrate(d, ap, self);
                          scheduleAggregateLive();
                        } catch (e) {
                          log('[BKW] onSelf ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                        }
                      });
                    }
                
                    log('[BKW] Live power + energy script started (MQTT push).', 'info');
                  } catch (e) {
                    log('[BKW] init failed: ' + (e && e.message ? e.message : e), 'error');
                  }
                })();
                
                // ========================== Daily close ==========================
                /**
                 * Daily close: write yesterday's kWh (ts = prev day 23:59:59.999),
                 * accumulate totals, reset today's counters and lastTs.
                 */
                function dailyClose() {
                  try {
                    const tsStartToday = new Date().setHours(0, 0, 0, 0);
                    const tsPrevDayEnd = tsStartToday - 1;
                
                    let totalDayKWh = 0;
                
                    for (const d of DEVICES) {
                      let dayKWh = toNumber(getState(d.todayKWh)?.val, 0);
                      if (dayKWh < 0) dayKWh = 0;
                
                      // Grafana daily (fixed timestamp)
                      setState(d.grafDaily, { val: Number(dayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                
                      // Cumulative per device
                      const cum = Math.max(0, toNumber(getState(d.totalKWh)?.val, 0) + dayKWh);
                      setState(d.totalKWh, Number(cum.toFixed(ROUND_DISPLAY)), true);
                
                      // Running totals reset
                      setState(d.todayWh,  0, true);
                      setState(d.todayKWh, 0, true);
                
                      // Refresh lastTs
                      const now = Date.now();
                      setState(d.lastTs, now, true);
                      lastTsCache.set(d.name, now);
                
                      totalDayKWh += dayKWh;
                    }
                
                    if (totalDayKWh < 0) totalDayKWh = 0;
                
                    // Aggregated daily + cumulative
                    setState(OUT_TOTAL_GRAFANA, { val: Number(totalDayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                
                    const totalCum = Math.max(0, toNumber(getState(OUT_TOTAL_CUM_KWH)?.val, 0) + totalDayKWh);
                    setState(OUT_TOTAL_CUM_KWH, Number(totalCum.toFixed(ROUND_DISPLAY)), true);
                
                    // Reset aggregated "today"
                    setState(OUT_TOTAL_TODAY_Wh,  0, true);
                    setState(OUT_TOTAL_TODAY_kWh, 0, true);
                
                    log(`[BKW] Daily close: total=${totalDayKWh.toFixed(ROUND_DISPLAY)} kWh; cum=${totalCum.toFixed(ROUND_DISPLAY)} kWh`);
                  } catch (e) {
                    log('[BKW] daily close failed: ' + (e && e.message ? e.message : e), 'warn');
                  }
                }
                
                // Cron 00:00
                schedule('0 0 * * *', dailyClose);
                
                // Optional 00:05
                if (RUN_AT_0005) {
                  schedule('5 0 * * *', () => {
                    try { dailyClose(); log('[BKW] 00:05 refresh executed.'); }
                    catch (e) { log('[BKW] 00:05 error: ' + e, 'warn'); }
                  });
                }
                
                OliverIOO 1 Antwort Letzte Antwort
                0
                • hotspot_2H hotspot_2

                  So, bin - mein Eindruck - ein deutliches Stück weiter gekommen. Ich habe ein Regelwerk festgelegt das wir nun für die Skript Aktivitäten nutzen und uns auf verschiedene Dinge verständigt (wie z.B. Versionierung usw.). Auch wann es sinnvoll ist Helper (functions) anzulegen und wann nicht.

                  Das geht in die richtige Richtung und ich lerne auch was dabei.

                  Macht immer mehr Spaß das Ganze.

                  /**
                   * BKW Live Power + Daily/Total Energy (MQTT push)
                   * Version: 0.1.0
                   * Changelog:
                   * - 0.1.0 (2025-10-01) Start of versioning; compact event-driven integration; self-consumption in mW; no existence checks
                   *
                   * Summary:
                   * - Reads Shelly MQTT JSON (status.switch:0 → apower in W)
                   * - Net per BKW = max(0, apower_W - self_W); self is provided in mW (integer)
                   * - Integrates energy event-based using full elapsed time (Δt) since last event
                   * - Daily close at 00:00: writes kWh per BKW + total (Grafana) and updates cumulative totals
                   *
                   * Notes:
                   * - Assumes all datapoints exist; no auto-creation, no existence checks
                   * - Rounds kWh once when writing the kWh states
                   */
                  
                  // ========================== Configuration ==========================
                  
                  /** Common prefix for all user states */
                  const PREFIX = '0_userdata.0.pvundstrom.bkws.';
                  
                  /** Shelly MQTT inputs (JSON strings with field "apower") */
                  const MQTT_JSON_BKW1 = 'mqtt.0.shellies.sonstiges.bkw1.status.switch:0';
                  const MQTT_JSON_BKW2 = 'mqtt.0.shellies.sonstiges.bkw2.status.switch:0';
                  
                  /** WR self-consumption (mW, integer, e.g. 400 → 0.4 W) */
                  const SELF_BKW1_mW = PREFIX + '1.wr_selfconsumption_mw';
                  const SELF_BKW2_mW = PREFIX + '2.wr_selfconsumption_mw';
                  
                  /** Live power outputs (W, NET after self-consumption) */
                  const OUT_PWR_BKW1 = PREFIX + '1.power_w';
                  const OUT_PWR_BKW2 = PREFIX + '2.power_w';
                  const OUT_PWR_ALL  = PREFIX + 'all.power_w';
                  
                  /** Energy states BKW1 */
                  const BKW1_TODAY_Wh      = PREFIX + '1.today_wh';
                  const BKW1_TODAY_kWh     = PREFIX + '1.today_kwh';
                  const BKW1_LASTTS_MS     = PREFIX + '1.lastts_ms';
                  const BKW1_GRAFANA_DAILY = PREFIX + '1.grafana_daily_kwh';
                  const BKW1_TOTAL_KWH     = PREFIX + '1.total_kwh';
                  
                  /** Energy states BKW2 */
                  const BKW2_TODAY_Wh      = PREFIX + '2.today_wh';
                  const BKW2_TODAY_kWh     = PREFIX + '2.today_kwh';
                  const BKW2_LASTTS_MS     = PREFIX + '2.lastts_ms';
                  const BKW2_GRAFANA_DAILY = PREFIX + '2.grafana_daily_kwh';
                  const BKW2_TOTAL_KWH     = PREFIX + '2.total_kwh';
                  
                  /** Aggregated energy states */
                  const OUT_TOTAL_TODAY_Wh  = PREFIX + 'all.today_wh';
                  const OUT_TOTAL_TODAY_kWh = PREFIX + 'all.today_kwh';
                  const OUT_TOTAL_GRAFANA   = PREFIX + 'all.grafana_daily_kwh';
                  const OUT_TOTAL_CUM_KWH   = PREFIX + 'all.total_kwh';
                  
                  /** Tolerances & formatting */
                  const EPS_W         = 1;   // write threshold for live power
                  const ROUND_DISPLAY = 3;   // kWh rounding for display/storage
                  const RUN_AT_0005   = false; // optional second daily close at 00:05
                  
                  /** Debug switch */
                  const DEBUG = false;
                  function dbg(msg) { if (DEBUG) log('[BKW] ' + msg); }
                  
                  // Device map
                  const DEVICES = [
                    {
                      name: 'bkw1',
                      inJsonId: MQTT_JSON_BKW1,
                      selfMwId: SELF_BKW1_mW,
                      outId:    OUT_PWR_BKW1,
                      todayWh:  BKW1_TODAY_Wh,
                      todayKWh: BKW1_TODAY_kWh,
                      lastTs:   BKW1_LASTTS_MS,
                      grafDaily:BKW1_GRAFANA_DAILY,
                      totalKWh: BKW1_TOTAL_KWH,
                    },
                    {
                      name: 'bkw2',
                      inJsonId: MQTT_JSON_BKW2,
                      selfMwId: SELF_BKW2_mW,
                      outId:    OUT_PWR_BKW2,
                      todayWh:  BKW2_TODAY_Wh,
                      todayKWh: BKW2_TODAY_kWh,
                      lastTs:   BKW2_LASTTS_MS,
                      grafDaily:BKW2_GRAFANA_DAILY,
                      totalKWh: BKW2_TOTAL_KWH,
                    },
                  ];
                  
                  // ========================== Lightweight helpers ==========================
                  
                  /** Number coercion with fallback */
                  function toNumber(v, fb = 0) { const n = Number(v); return Number.isFinite(n) ? n : fb; }
                  
                  /** Changed-only numeric write with epsilon */
                  function setIfChangedNumber(id, next, eps = 0, ack = true) {
                    const cur = Number(getState(id)?.val);
                    const within = Number.isFinite(cur) ? Math.abs(cur - next) <= eps : false;
                    if (!within) setState(id, next, ack);
                  }
                  
                  /** Parse Shelly JSON → apower (W), clamped to >=0 */
                  function parseApowerW(jsonVal) {
                    let ap = 0;
                    if (typeof jsonVal === 'string') {
                      try { ap = Number(JSON.parse(jsonVal)?.apower) || 0; }
                      catch (e) {
                        const sample = jsonVal.slice(0, 120);
                        log('[BKW] JSON parse failed (' + sample + '): ' + e.message, 'warn');
                      }
                    }
                    if (!Number.isFinite(ap) || ap < 0) ap = 0;
                    return ap;
                  }
                  
                  /** Read self-consumption in W from mW state (>=0) */
                  function readSelfW(id) {
                    const mw = toNumber(getState(id)?.val, 0);
                    return mw > 0 ? (mw / 1000) : 0;
                  }
                  
                  // ========================== Aggregate live power ==========================
                  let aggTimer = null;
                  function scheduleAggregateLive() {
                    if (aggTimer) return;
                    aggTimer = setTimeout(() => {
                      aggTimer = null;
                      try {
                        let sum = 0;
                        for (const d of DEVICES) {
                          const v = toNumber(getState(d.outId)?.val, 0);
                          if (v > 0) sum += v;
                        }
                        if (sum < 0) sum = 0;
                        setIfChangedNumber(OUT_PWR_ALL, sum, EPS_W, true);
                      } catch (e) {
                        log('[BKW] aggregate update failed: ' + (e && e.message ? e.message : e), 'warn');
                      }
                    }, 300); // small debounce for aggregate
                  }
                  
                  // ========================== Energy integration per device ==========================
                  const lastTsCache = new Map();
                  
                  /**
                   * Integrate energy using full Δt since last event; update live power and daily totals.
                   * @param {*} dev device entry from DEVICES
                   * @param {number} apowerW AC power from Shelly (W, >=0)
                   * @param {number} selfW   WR self-consumption (W, >=0)
                   */
                  function integrate(dev, apowerW, selfW) {
                    const now = Date.now();
                    let last = lastTsCache.get(dev.name);
                    if (!Number.isFinite(last)) last = toNumber(getState(dev.lastTs)?.val, now);
                  
                    // Full elapsed time since last event (no cap), protect against negative
                    let dt = now - last;
                    if (dt < 0) dt = 0;
                  
                    // Advance lastTs immediately to avoid Δt growth on next event
                    setState(dev.lastTs, now, true);
                    lastTsCache.set(dev.name, now);
                  
                    // Net power (never negative)
                    const P = apowerW > 0 ? apowerW : 0;
                    const S = selfW   > 0 ? selfW   : 0;
                    const netW = Math.max(0, P - S);
                  
                    // Live power (changed-only)
                    setIfChangedNumber(dev.outId, netW, EPS_W, true);
                  
                    // No energy when net power is zero
                    if (netW <= 0) return;
                  
                    // W * (ms → h) = Wh
                    const WhInc = netW * (dt / 3600000);
                    let todayWh = toNumber(getState(dev.todayWh)?.val, 0) + WhInc;
                    if (todayWh < 0) todayWh = 0;
                  
                    const todayKWh = todayWh / 1000;
                  
                    setState(dev.todayWh,  todayWh, true);
                    setState(dev.todayKWh, Number(todayKWh.toFixed(ROUND_DISPLAY)), true);
                  
                    // Update aggregated "today"
                    let sumWh = 0;
                    for (const d2 of DEVICES) {
                      const v = toNumber(getState(d2.todayWh)?.val, 0);
                      if (v > 0) sumWh += v;
                    }
                    if (sumWh < 0) sumWh = 0;
                  
                    setState(OUT_TOTAL_TODAY_Wh,  sumWh, true);
                    setState(OUT_TOTAL_TODAY_kWh, Number((sumWh / 1000).toFixed(ROUND_DISPLAY)), true);
                  
                    dbg(`[${dev.name}] P=${P}W, self=${S}W, net=${netW}W, dt=${dt}ms, Wh+=${WhInc.toFixed(3)}`);
                  }
                  
                  // ========================== Init & Event binding ==========================
                  (function init() {
                    try {
                      // Initialize live power and lastTs cache from current states
                      for (const d of DEVICES) {
                        const ap   = parseApowerW(getState(d.inJsonId)?.val);
                        const self = readSelfW(d.selfMwId);
                        const net  = Math.max(0, ap - self);
                        setIfChangedNumber(d.outId, net, EPS_W, true);
                  
                        const lastStored = toNumber(getState(d.lastTs)?.val, NaN);
                        lastTsCache.set(d.name, Number.isFinite(lastStored) ? lastStored : Date.now());
                      }
                      scheduleAggregateLive();
                  
                      // Event listeners
                      for (const d of DEVICES) {
                        // MQTT JSON changed or re-written → integrate
                        on({ id: d.inJsonId, change: 'any' }, (obj) => {
                          try {
                            const ap   = parseApowerW(obj?.state?.val);
                            const self = readSelfW(d.selfMwId);
                            integrate(d, ap, self);
                            scheduleAggregateLive();
                          } catch (e) {
                            log('[BKW] onJSON ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                          }
                        });
                  
                        // Self-consumption changed (mW) → recompute/integrate at current apower
                        on({ id: d.selfMwId, change: 'ne' }, () => {
                          try {
                            const ap   = parseApowerW(getState(d.inJsonId)?.val);
                            const self = readSelfW(d.selfMwId);
                            integrate(d, ap, self);
                            scheduleAggregateLive();
                          } catch (e) {
                            log('[BKW] onSelf ' + d.name + ' failed: ' + (e && e.message ? e.message : e), 'warn');
                          }
                        });
                      }
                  
                      log('[BKW] Live power + energy script started (MQTT push).', 'info');
                    } catch (e) {
                      log('[BKW] init failed: ' + (e && e.message ? e.message : e), 'error');
                    }
                  })();
                  
                  // ========================== Daily close ==========================
                  /**
                   * Daily close: write yesterday's kWh (ts = prev day 23:59:59.999),
                   * accumulate totals, reset today's counters and lastTs.
                   */
                  function dailyClose() {
                    try {
                      const tsStartToday = new Date().setHours(0, 0, 0, 0);
                      const tsPrevDayEnd = tsStartToday - 1;
                  
                      let totalDayKWh = 0;
                  
                      for (const d of DEVICES) {
                        let dayKWh = toNumber(getState(d.todayKWh)?.val, 0);
                        if (dayKWh < 0) dayKWh = 0;
                  
                        // Grafana daily (fixed timestamp)
                        setState(d.grafDaily, { val: Number(dayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                  
                        // Cumulative per device
                        const cum = Math.max(0, toNumber(getState(d.totalKWh)?.val, 0) + dayKWh);
                        setState(d.totalKWh, Number(cum.toFixed(ROUND_DISPLAY)), true);
                  
                        // Running totals reset
                        setState(d.todayWh,  0, true);
                        setState(d.todayKWh, 0, true);
                  
                        // Refresh lastTs
                        const now = Date.now();
                        setState(d.lastTs, now, true);
                        lastTsCache.set(d.name, now);
                  
                        totalDayKWh += dayKWh;
                      }
                  
                      if (totalDayKWh < 0) totalDayKWh = 0;
                  
                      // Aggregated daily + cumulative
                      setState(OUT_TOTAL_GRAFANA, { val: Number(totalDayKWh.toFixed(ROUND_DISPLAY)), ts: tsPrevDayEnd }, true);
                  
                      const totalCum = Math.max(0, toNumber(getState(OUT_TOTAL_CUM_KWH)?.val, 0) + totalDayKWh);
                      setState(OUT_TOTAL_CUM_KWH, Number(totalCum.toFixed(ROUND_DISPLAY)), true);
                  
                      // Reset aggregated "today"
                      setState(OUT_TOTAL_TODAY_Wh,  0, true);
                      setState(OUT_TOTAL_TODAY_kWh, 0, true);
                  
                      log(`[BKW] Daily close: total=${totalDayKWh.toFixed(ROUND_DISPLAY)} kWh; cum=${totalCum.toFixed(ROUND_DISPLAY)} kWh`);
                    } catch (e) {
                      log('[BKW] daily close failed: ' + (e && e.message ? e.message : e), 'warn');
                    }
                  }
                  
                  // Cron 00:00
                  schedule('0 0 * * *', dailyClose);
                  
                  // Optional 00:05
                  if (RUN_AT_0005) {
                    schedule('5 0 * * *', () => {
                      try { dailyClose(); log('[BKW] 00:05 refresh executed.'); }
                      catch (e) { log('[BKW] 00:05 error: ' + e, 'warn'); }
                    });
                  }
                  
                  OliverIOO Offline
                  OliverIOO Offline
                  OliverIO
                  schrieb am zuletzt editiert von OliverIO
                  #28

                  @hotspot_2 sagte in Skript läuft plötzlich nicht mehr:

                  Auch wann es sinnvoll ist Helper (functions) anzulegen und wann nicht.
                  Das geht in die richtige Richtung und ich lerne auch was dabei.

                  hardcore sprach-stilisten wollen eine funktion nicht mehr wie 10-20 Zeilen lang + Dokumentation.
                  Nur dann bleibt der code für jemanden 3.wartbar oder auch für dich wenn du nach einem Jahr wieder drauf schauen musst.
                  d.h. Funktionen entsprechend unterteilen.
                  Dann gäbe es noch die Steuerungsschicht (wo nur die Logik hinterlegt ist) und dann die eigentliche Funktionsebene, anhand der die Steuerungsschicht entscheidet wohin es hingeht und welche Ergebnisse wie verarbeitet werden.
                  Aber das ist dann schon sehr Theorie

                  Meine Adapter und Widgets
                  TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                  Links im Profil

                  1 Antwort Letzte Antwort
                  0
                  Antworten
                  • In einem neuen Thema antworten
                  Anmelden zum Antworten
                  • Älteste zuerst
                  • Neuste zuerst
                  • Meiste Stimmen


                  Support us

                  ioBroker
                  Community Adapters
                  Donate

                  322

                  Online

                  32.6k

                  Benutzer

                  82.2k

                  Themen

                  1.3m

                  Beiträge
                  Community
                  Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                  ioBroker Community 2014-2025
                  logo
                  • Anmelden

                  • Du hast noch kein Konto? Registrieren

                  • Anmelden oder registrieren, um zu suchen
                  • Erster Beitrag
                    Letzter Beitrag
                  0
                  • Home
                  • Aktuell
                  • Tags
                  • Ungelesen 0
                  • Kategorien
                  • Unreplied
                  • Beliebt
                  • GitHub
                  • Docu
                  • Hilfe