[
    {
        "id": "006ee2924550546b",
        "type": "function",
        "z": "10392fda6f950578",
        "name": "Staging-Controller",
        "func": "const v = Number(msg.payload);\nif (!isNaN(v)) {\n    context.set('sum',   (context.get('sum')   || 0) + v);\n    context.set('count', (context.get('count') || 0) + 1);\n}\nreturn null;   // Die Entscheidung trifft ausschliesslich der Timer",
        "outputs": 6,
        "timeout": 0,
        "noerr": 0,
        "initialize": "// ===== Heizstab Staging-Controller (ein Entscheidungstakt) =====\n// Verhalten:\n//   - ZUSCHALTEN mehrstufig: bei viel Ueberschuss duerfen pro Takt mehrere Stufen an.\n//   - ABWERFEN gestaffelt: pro Takt hoechstens EINE Stufe weg.\n//   - Abwurf, sobald die aktuelle Einspeisung unter MIN_FEEDIN_OFF faellt -> kein bewusster Netzbezug.\n//   - Stufen kumulativ: S1 / S1+S2 / S1+S2+S3.\n\n// ---- Parameter (hier anpassen) ----\nconst P_STAGE        = 4000;   // W pro Stufe (ca.)\nconst ON_MARGIN      = 1000;    // W Rest-Einspeisung, die nach dem Zuschalten noch bleiben muss.\n                               //   Hoeher (z.B. 1000) = mehr Abstand zur Abwurfschwelle -> Hysterese.\nconst MIN_FEEDIN_OFF = 500;    // W Mindest-Einspeisung. Faellt die aktuelle Einspeisung darunter,\n                               //   wird EINE Stufe abgeworfen. So wird kein Netzbezug angesteuert.\nconst EVAL_MS        = 180000;  // Entscheidungstakt in ms (Mittelung + Schaltung).\n                               //   Kuerzer = schnellerer Abwurf bei PV-Einbruch.\nconst FORCE_EVERY    = 15;     // alle N Zyklen alle Relais erneut senden (Selbstheilung)\nconst STUFEN         = 3;\n\ncontext.set('sum', 0);\ncontext.set('count', 0);\ncontext.set('cycle', 0);\n\n// Startzustand definiert: alles AUS\nflow.set('stufe1', 0);\nflow.set('stufe2', 0);\nflow.set('stufe3', 0);\n\n// 6 Outputs: 0=S1 an, 1=S1 aus, 2=S2 an, 3=S2 aus, 4=S3 an, 5=S3 aus\nfunction sendStates(want, sendAll, prev) {\n    const out = [null, null, null, null, null, null];\n    for (let i = 0; i < STUFEN; i++) {\n        if (sendAll || want[i] !== prev[i]) {\n            const idx = i * 2 + (want[i] ? 0 : 1);\n            out[idx] = { payload: want[i] ? 'on' : 'off', topic: 'Stufe ' + (i + 1) };\n        }\n    }\n    node.send(out);\n}\n\n// Beim Start einmal definierten Zustand (alles AUS) an die Shellys senden,\n// damit Modell und Hardware synchron sind.\n// Soll beim Redeploy NICHT geschaltet werden -> diesen setTimeout-Block auskommentieren.\nsetTimeout(function () {\n    sendStates([0, 0, 0], true, [null, null, null]);\n    node.status({ fill: 'grey', shape: 'ring', text: 'Init: alle Stufen AUS' });\n}, 1000);\n\nconst old = context.get('timer');\nif (old) { clearInterval(old); }\n\nconst timer = setInterval(function () {\n    const count = context.get('count') || 0;\n    if (count === 0) { return; }                     // keine Messdaten -> nichts tun\n    const avg = (context.get('sum') || 0) / count;   // W, negativ = Einspeisung\n    context.set('sum', 0);\n    context.set('count', 0);\n\n    const prev = [flow.get('stufe1') || 0, flow.get('stufe2') || 0, flow.get('stufe3') || 0];\n    const n = prev[0] + prev[1] + prev[2];\n\n    // Verfuegbarer PV-Ueberschuss, unabhaengig von der bereits laufenden Heizlast:\n    // gemessenes Netz + bereits aktive Heizlast wieder herausgerechnet.\n    // (Aktuelle Einspeisung waere sAvail - n*P_STAGE bzw. -avg.)\n    const sAvail = -avg + n * P_STAGE;               // positiv = Ueberschuss in W\n\n    // Zielanzahl Stufen bestimmen:\n    //   - Abwurf hat Vorrang und schaltet hoechstens EINE Stufe ab (gestaffelt).\n    //   - sonst: zuschalten, dabei beliebig viele Stufen pro Takt erlaubt.\n    let target = n;\n    if (n > 0 && sAvail < n * P_STAGE + MIN_FEEDIN_OFF) {\n        target = n - 1;                              // Einspeisung < MIN_FEEDIN_OFF -> eine Stufe weg\n    } else {\n        while (target < STUFEN && sAvail >= (target + 1) * P_STAGE + ON_MARGIN) {\n            target++;                                // genug Ueberschuss -> Stufe(n) zuschalten\n        }\n    }\n\n    const want = [target >= 1 ? 1 : 0, target >= 2 ? 1 : 0, target >= 3 ? 1 : 0];\n\n    let cyc = (context.get('cycle') || 0) + 1;\n    context.set('cycle', cyc);\n    const force = (cyc % FORCE_EVERY) === 0;\n\n    sendStates(want, force, prev);\n    flow.set('stufe1', want[0]);\n    flow.set('stufe2', want[1]);\n    flow.set('stufe3', want[2]);\n\n    node.status({\n        fill:  target > 0 ? 'green' : 'grey',\n        shape: 'dot',\n        text:  'Mittel ' + Math.round(avg) + ' W | Ueberschuss ~' + Math.round(sAvail) + ' W | Stufen ' + n + ' -> ' + target\n    });\n}, EVAL_MS);\n\ncontext.set('timer', timer);",
        "finalize": "const timer = context.get('timer');\nif (timer) { clearInterval(timer); }",
        "libs": [],
        "x": 1130,
        "y": 380,
        "wires": [
            [
                "14285bd716c0db9d"
            ],
            [
                "ac16276e94626530"
            ],
            [
                "89f0b8ce580ebf9d"
            ],
            [
                "ae32a8695c7fd4ca"
            ],
            [
                "7676114dc5dcdd8e"
            ],
            [
                "fdc11a45635b9493"
            ]
        ]
    }
]