Démarrer votre électroménager en fonction de votre production Solaire

Hello tout le monde, j’espère que vous allez bien.

ça fait quelque temps que je travaille avec mon pote GPT sur un “petit” flow pour lancer automatiquement notre LV (Lave-vaisselle) lorsqu’il est prêt en fonction de la prod Solaire de la maison. Avec notif telegram et tout ce qu’il faut pour interagir.

Je vous partage donc le fruit de mon travail, que j’ai pris le temps de variabiliser pour normalement faciliter ça réutilisation.

PS: c’est mon premier « gros projet » sur Node-red donc n’hésitez pas à faire des commentaires/critiques, si certaines choses peuvent être simplifiées par exemple.

Pré-requis & intégrations

  • Home Assistant avec intégrations :
    • Home Connect (ou équivalent) :
      • select.… (programmes LV), button.… (start), optionnel switch.… de power.
      • sensor d’état …operation_state (Run/InProgress/Finished) — pour l’annulation si démarrage manuel.
    • Solcast :
      • sensor.solcast_pv_forecast_previsions_pour_aujourd_hui
      • sensor.solcast_pv_forecast_previsions_pour_demain
      • attribut attributes.detailedForecast (pas 30 min, champ pv_estimate).
    • Énergie :
      • sensor.…grid_power (production onduleur)
      • sensor.consommation_global_maison_power_a (export négatif → surplus)
    • Telegram bot configuré dans Node-RED.
    • NFC Tag : tag_id_eco défini.
  • Node-RED avec :
    • node-red-contrib-home-assistant-websocket
    • node-red-contrib-telegrambot

Vue d’ensemble :

Le flow gère 3 usages :

  1. Scan NFC → question “urgent ?” → soit démarrage rapide (45/65°C), soit mise en attente solaire.

  2. Attente solaire → déclenchement auto si surplus + forecast OK (toutes les 10 min), ou envoi d’une estimation du prochain créneau si ça ne part pas “now”.

  3. Annulations intelligentes → si re-scan du tag pendant l’attente, ou démarrage manuel du LV, l’attente est désarmée + notif.

Tout est variabilisé via flow.lv_cfg (entités, device_id, seuils, chats Telegram).

Parcours clés

  • Scan NFC
    • Porte ouverte/unavailable ? → reload + notifs dédiées.
    • Sinon clavier OUI65/OUI45/NON.
  • Réponse
    • OUI65/OUI45 → select + start + notif.
    • NON ou timeout 2 min → “mise en attente ECO” → test NOW :
      • Si OK → select ECO + start + reset & notif “on part”.
      • Sinon → notif d’attente enrichie avec prochain créneau (today filtré sur t >= now, premier pas ≥ min_first_kw, somme ≥ kwh_window) ; si rien aujourd’hui → passe à demain.
  • Toutes les 10 min (si en attente)
    • Si production brute ET surplus export ET forecast OK → lancement auto (select ECO + start) + reset & notif.
  • Annulations
    • Re-scan du tag pendant attente → annule + notif.
    • Démarrage manuel (op_state → Run) pendant attente → annule + notif.
  • Timeout 24h
    • Si rien ne s’est déclenché → question Telegram : “lancer maintenant ?” ou “attendre 24h”.
    • OUI → lance ECO + reset & notif.
    • NON → prolonge la fenêtre + notif.

Comment ça marche :

Groupes & blocs (à quoi ils servent)

  • :green_square: Chargement variables

    • cfg_inject_init → initialise au démarrage.

    • cfg_set_flow → crée flow.lv_cfg (chats Telegram, entités HA, device_id Home Connect, seuils).

  • :green_square: Logique de lancement Initial

    • Envoi du message initial

      • evt_tag_scan → écoute l’évènement HA tag_scanned.

      • fn_filter_tag → filtre le bon tag et, si attente en cours, transforme un re-scan en annulation (flag cancel) + message dédié.

      • sw_cancel_or_flow → si cancel=true, route direct vers Telegram ; sinon, suite logique.

      • st_door_open + sw_door → vérifie porte ouverte/unavailable/fermée.

      • fn_unavailable + “reload config LV” (groupe jaune) → corrige les indispos (reload + notif).

      • fn_route_telegram_inline → prépare le clavier inline (OUI65/OUI45/NON) + envoi tg_inline.

      • fn_save_init_msg → mémorise chatId/messageId pour supprimer le clavier ensuite.

    • Réponse message initial

      • evt_cb → écoute callback Telegram (OUI65/45/NON).

      • trig_answer → 2 min max sinon timeout → force NON (éco).

      • b0fccce953940838 / b96d3812aab56ece (et équivalents) → supprime le clavier inline dans Telegram.

    • Lancement Rapide

      • prep_ha_quick65/45 + select.select_option + button.press → lance programme Quick.

      • fn_notif_quick65/45 → envoie notif.

    • Set attente ECO (chat variabilisé)

      • pose global.lv_en_attente = true + message “en attente”.
  • :green_square: Lancement direct OU message estimation

    • NOW check (juste après “Set attente ECO”) :

      • st_power_now + fn_surplus_nowsurplus instantané ≥ seuil (export négatif).

      • st_solcast_today_now + fn_test_now → somme forecast prochaines 4h ≥ seuil kWh.

      • Si OK → prep_ha_eco_start_immediate + select ECO + button press + fn_reset_wait_notif (reset flags + notif “on part maintenant”).

      • Sinon → branche estimation :

        • st_solcast_today + fn_next_window_todaycherche la prochaine fenêtre aujourd’hui en imposant ≥ 1.5 kW sur le 1er pas + ≥ kWh sur 4h, en ignorant les pas déjà passés.

        • sw_need_tomorrow → si rien aujourd’hui → st_solcast_tomorrow + fn_next_window_tomorrow.

        • fn_send_notif_wait + tg_sender → notif “en attente”, avec date/heure du créneau (ou info “pas de créneau clair dans 48h”).

  • :green_square: Lancement solaire Automatique (périodique toutes 10 min)

    • st_inverter + fn_prod_over → production PV brute > seuil.

    • fn_wait_flag_gate → ne continue que si lv_en_attente=true.

    • st_house_export + fn_surplus_periodicsurplus export ≥ seuil → surplusOK → global.

    • st_solcast_today_periodic + fn_sum_forecast → somme forecast 4h (variabilisée).

    • fn_test_conditionslancement auto si surplusOK && forecastOK.

    • prep_ha_eco_periodicselect ECO + button press + fn_reset_wait_notif (reset + notif).

  • :green_square: Echec lancement 24h (timeout)

    • fn_24h_timeout_gate → surveille si lv_en_attente > 24h.

    • fn_24h_message → envoie clavier Telegram “lancer maintenant ? / attendre encore 24h”.

    • evt_cb_24h + fn_filter_cb_24h + sw_24h_choice → traite la réponse.

    • OUIprep_ha_eco_manualselect ECO + button press + fn_reset_wait_notif_24h (reset + notif).

    • NONfn_extend_24h (repousse le timer) + notif ; suppression clavier gérée aussi.

  • :green_square: Lancement manuel, annulation attente

    • server-state-changed (op_state) → si passe à Run (ou équivalent), et lv_en_attente=trueSi attente en cours ⇒ annuler + notif.
  • :green_square: Reset flag attente + notif

    • Petit helper commun (fn_reset_wait_notif) → désarme l’attente et envoie la notif “ECO démarre” (utilisé par les démarrages auto/immédiats).

Variables & réglages (flow.lv_cfg)

  • telegram
    • default_chat, map (alias → chatId).
  • nfc
    • tag_id_eco (ID du tag).
  • entities
    • door_sensor, op_state, inverter_power, house_export_power, solcast_today, solcast_tomorrow, program_select, start_button, lv_power_switch.
  • home_connect
    • device_id, program_keys (eco50, quick45, quick65).
  • thresholds
    • prod_min_w (production PV brute minimale pour scanner la situation)
    • surplus_w (export minimal absolu pour lancer)
    • window_slots (taille fenêtre forecast — 8 = 4h)
    • kwh_window (kWh mini cumulés dans la fenêtre)
    • min_first_kw (kW minimum uniquement sur le premier pas du créneau)

Flow node Red :

[{"id":"3c9dea7a32a51d36","type":"function","z":"78f0de5247d76061","name":"Reset flag attente + notif","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nglobal.set('lv_en_attente', false);\nglobal.set('lv_en_attente_date', null);\nmsg.payload = { chatId: CHAT, type: 'message', content: 'Le lave-vaisselle démarre maintenant en mode ECO (production solaire optimale).' };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2970,"y":880,"wires":[["74f6ab9ce6edf3d7"]]},{"id":"74f6ab9ce6edf3d7","type":"telegram sender","z":"78f0de5247d76061","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":3210,"y":880,"wires":[[]]},{"id":"91bf6faafc150a81","type":"group","z":"78f0de5247d76061","name":"Lancement manuel, annulation attente","style":{"stroke":"#e3f3d3","label":true,"label-position":"n","color":"#777777","fill":"#e3f3d3"},"nodes":["15c6bb009330f602","065e2b5e4d2cf538","955ad958579af413","8c5980e05e060af8"],"x":14,"y":1879,"w":1432,"h":82},{"id":"15c6bb009330f602","type":"server-state-changed","z":"78f0de5247d76061","g":"91bf6faafc150a81","name":"LV: OperationState → Run/Active/InProgress","server":"680c8e88f3d1cfdd","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["sensor.siemens_lave_vaisselle_alt_cuisine_operation_state"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":210,"y":1920,"wires":[["065e2b5e4d2cf538"]]},{"id":"065e2b5e4d2cf538","type":"switch","z":"78f0de5247d76061","g":"91bf6faafc150a81","name":"État = Run/Active/InProgress ?","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"Run","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":570,"y":1920,"wires":[["955ad958579af413"]]},{"id":"955ad958579af413","type":"function","z":"78f0de5247d76061","g":"91bf6faafc150a81","name":"Si attente en cours ⇒ annuler + notif","func":"if (global.get('lv_en_attente') === true) {\n    global.set('lv_en_attente', false);\n    global.set('lv_en_attente_date', null);\n    const cfg = flow.get('lv_cfg') || {};\n    const map = cfg?.telegram?.map || {};\n    const def = cfg?.telegram?.default_chat;\n    \n    msg.payload = {\n        type: \"message\",\n        content: \"⏹️ Annulation du lancement solaire programmé : le lave-vaisselle vient d’être démarré manuellement.\"\n    };\n    msg.payload.chatId = map.home ?? def;\n    return msg; // envoie la notif\n}\nreturn null; // sinon, on ne fait rien","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":1920,"wires":[["8c5980e05e060af8"]]},{"id":"8c5980e05e060af8","type":"telegram sender","z":"78f0de5247d76061","g":"91bf6faafc150a81","name":"Notif: attente annulée (démarrage manuel)","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":1250,"y":1920,"wires":[[]]},{"id":"680c8e88f3d1cfdd","type":"server","name":"Home Assistant Ex","version":5,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":": ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"default","statusTimeFormat":"h:m","enableGlobalContextStore":false},{"id":"c339d2e4fa3ec3fb","type":"group","z":"78f0de5247d76061","name":"Echec lancement 24h (timout)","style":{"stroke":"#ff0000","fill":"#ffbfbf","label":true,"color":"#ffffff"},"nodes":["0655d0b3db3938c3","4b4e35ae06751ddf","6f9b55df676b3371","4dba24643377b8df","20d401962174c987","8acdda0f0b7a0883","17c8ce98902e4ddf","0427fcf079e92c88","4bfef0bf0df75300","b74062bbaef79a7d","54f8fa11a5bb2a09","69a0f3512b60815a","c58c0cc185cda587"],"x":314,"y":1359,"w":1672,"h":342},{"id":"0655d0b3db3938c3","type":"telegram event","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Réponse Telegram Callback 24h","bot":"edaf7fc8b148893c","event":"callback_query","autoanswer":true,"x":470,"y":1580,"wires":[["4b4e35ae06751ddf"]]},{"id":"4b4e35ae06751ddf","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Filtrer callback 24h","func":"if (['LV_ECO_MANUEL_OUI','LV_ECO_MANUEL_NON'].includes(msg.payload.content)) return msg;\nreturn null;","outputs":1,"noerr":0,"x":730,"y":1580,"wires":[["6f9b55df676b3371","203cfbcad1a22ab1"]]},{"id":"6f9b55df676b3371","type":"switch","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"OUI/NON manuel","property":"payload.content","propertyType":"msg","rules":[{"t":"eq","v":"LV_ECO_MANUEL_OUI","vt":"str"},{"t":"eq","v":"LV_ECO_MANUEL_NON","vt":"str"}],"outputs":2,"x":960,"y":1580,"wires":[["4dba24643377b8df"],["8acdda0f0b7a0883"]]},{"id":"4dba24643377b8df","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Préparer HA params (ECO manuel)","func":"const cfg = flow.get('lv_cfg') || {};\nmsg.ha = {\n  device_id: cfg?.home_connect?.device_id,\n  program_key: cfg?.home_connect?.program_keys?.eco50,\n  program_select: cfg?.entities?.program_select,\n  start_button: cfg?.entities?.start_button\n};\nreturn msg;","outputs":1,"noerr":0,"x":1280,"y":1560,"wires":[["20d401962174c987","69a0f3512b60815a","c58c0cc185cda587"]]},{"id":"20d401962174c987","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Reset flag/date + notif (24h)","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nglobal.set('lv_en_attente', false);\nglobal.set('lv_en_attente_date', null);\nmsg.payload = { chatId: CHAT, type: 'message', content: 'Lave-vaisselle lancé en mode ECO (après 24h sans créneau solaire).' };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1620,"y":1600,"wires":[["17c8ce98902e4ddf"]]},{"id":"8acdda0f0b7a0883","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Remettre date à now (24h)","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nglobal.set('lv_en_attente_date', Date.now());\nmsg.payload = { chatId: CHAT, type: 'message', content: \"OK, on attend encore jusqu'à demain pour tenter le solaire.\" };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1620,"y":1660,"wires":[["17c8ce98902e4ddf"]]},{"id":"17c8ce98902e4ddf","type":"telegram sender","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":1870,"y":1600,"wires":[[]]},{"id":"0427fcf079e92c88","type":"group","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Suppression clavier T","style":{"stroke":"#ff0000","fill":"#ff7f7f","label":true,"label-position":"n","color":"#ffffff"},"nodes":["203cfbcad1a22ab1","8f3a1f87373d7380"],"x":874,"y":1439,"w":572,"h":82},{"id":"203cfbcad1a22ab1","type":"function","z":"78f0de5247d76061","g":"0427fcf079e92c88","name":"Nettoyage clavier telegram","func":"const cfg = flow.get('lv_cfg') || {}; \nconst CHAT = cfg?.telegram?.default_chat || global.get('lv_inital_chatId');\n\nif (msg.payload.content === 'NO_REPLY'){\n  msg.payload = {\n    content: 'Lave-Vaiselle reponse timout, mode éco sélectionné',\n    type: 'editMessageText',\n    options: { reply_markup: {}, chat_id: global.get('lv_inital_chatId') || CHAT, message_id: global.get('lv_inital_messageId') }\n  };\n} else {\n  msg.payload = {\n    content: msg.originalMessage?.message?.text || msg.payload?.content,\n    type: 'editMessageText',\n    options: { reply_markup: {}, chat_id: msg.payload.chatId || CHAT, message_id: msg.payload.messageId }\n  };\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1020,"y":1480,"wires":[["8f3a1f87373d7380"]]},{"id":"8f3a1f87373d7380","type":"telegram sender","z":"78f0de5247d76061","g":"0427fcf079e92c88","name":"Remove keyboard after answer","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":1290,"y":1480,"wires":[[]]},{"id":"4bfef0bf0df75300","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Attente > 24h ?","func":"const attente = global.get('lv_en_attente');\nconst t0 = global.get('lv_en_attente_date');\nif (!attente) return null;\nif (t0 && (Date.now() - t0 > 24*60*60*1000)) { msg.needManual = true; return [msg,null]; }\nreturn [null,msg];","outputs":2,"noerr":0,"x":420,"y":1400,"wires":[["b74062bbaef79a7d"],[]]},{"id":"b74062bbaef79a7d","type":"function","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Préparer msg Telegram 24h","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nmsg.payload = {\n  chatId: CHAT,\n  type: 'message',\n  content: \"Aucune fenêtre solaire n'a été trouvée depuis 24h pour le lave-vaisselle. Voulez-vous le lancer maintenant en mode ECO ?\",\n  options: {\n    reply_markup: JSON.stringify({\n      inline_keyboard: [\n        [{ text: 'OUI (lancer maintenant)', callback_data: 'LV_ECO_MANUEL_OUI' }],\n        [{ text: 'NON (attendre encore 24h)', callback_data: 'LV_ECO_MANUEL_NON' }]\n      ]\n    })\n  }\n};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":1400,"wires":[["54f8fa11a5bb2a09"]]},{"id":"54f8fa11a5bb2a09","type":"telegram sender","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":900,"y":1400,"wires":[[]]},{"id":"69a0f3512b60815a","type":"api-call-service","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Button LV start","server":"680c8e88f3d1cfdd","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.start_button}}"],"labelId":[],"data":"{}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"button","service":"press","target":{"entity_id":"{{ha.start_button}}"},"x":1580,"y":1560,"wires":[[]]},{"id":"c58c0cc185cda587","type":"api-call-service","z":"78f0de5247d76061","g":"c339d2e4fa3ec3fb","name":"Select LV ECO","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.program_select}}"],"labelId":[],"data":"{\"option\":\"{{ha.program_key}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"select","service":"select_option","target":{"entity_id":"{{ha.program_select}}"},"x":1680,"y":1480,"wires":[[]]},{"id":"5d4c93e3dcc23783","type":"group","z":"78f0de5247d76061","name":"Chargement variables","style":{"stroke":"#92d04f","fill":"#e3f3d3","label":true},"nodes":["ffc7d84064c2a13f","2164a286788b49cb"],"x":14,"y":2059,"w":662,"h":82},{"id":"ffc7d84064c2a13f","type":"inject","z":"78f0de5247d76061","g":"5d4c93e3dcc23783","name":"Init/refresh config (flow.lv_cfg)","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.5,"topic":"","x":190,"y":2100,"wires":[["2164a286788b49cb"]]},{"id":"2164a286788b49cb","type":"function","z":"78f0de5247d76061","g":"5d4c93e3dcc23783","name":"Set flow.lv_cfg (toutes les variables)","func":"const cfg = {\n  telegram: {\n    default_chat: -111111111,\n    map: { home: -1111111111, max: 222222222, julie: 333333333 }\n  },\n  nfc: {\n    tag_id_eco: \"\" //exemple:\"2784588e-f926-46d1-fbe9-f9fbda99b55c\"\n  },\n  entities: {\n    door_sensor: \"\",//exemple:binary_sensor.porte_lave_vaisselle\n    op_state: \"\",//exemple:sensor.status_du_lavevaisselle\n    inverter_power: \"\",//exemple:sensor.puissance_solaire_en_W\n    house_export_power: \"\",//exemple:sensor.conso_global_maison (-1000W j'envoie sur le reseau; 1000W j'achete sur le réseau)\n    solcast_today: \"sensor.solcast_pv_forecast_previsions_pour_aujourd_hui\",//prévision de production solaire du jour\n    solcast_tomorrow: \"sensor.solcast_pv_forecast_previsions_pour_demain\",//prévision de production solaire du lendemain\n    program_select: \"\",//exemple:select.programs_lave_vaisselle\n    start_button: \"\",//exemple:button.demarrer_lave_vaissel\n    lv_power_switch: \"\"//exemple:switch.on_off_lave_vaisselle\n  },\n  home_connect: {\n    device_id: \"1d30a453d47b9516c4793ca229a0d258\",//Id de votre LV exemple:3f40c453g47b9526c4793ca227a0d252\n    program_keys: {\n      eco50: \"Dishcare.Dishwasher.Program.Eco50\",// nom des programme \n      quick45: \"Dishcare.Dishwasher.Program.Quick45\",\n      quick65: \"Dishcare.Dishwasher.Program.Quick65\"\n    }\n  },\n  thresholds: {\n    prod_min_w: 1000, //seuil de production solaire minimum\n    surplus_w: 1500, // Seuil de surplus de production solaire (production-consommation reel)\n    window_slots: 8, // nombre de pas (30minutes) pour un cycle éco a utiliser sur le forecast\n    kwh_window: 1.5, // KhW attendu minimum pour un lancement auto\n    min_first_kw: 1.5 // puissance minimum attendu sur le forecast\n  }\n};\nflow.set('lv_cfg', cfg);\nnode.status({fill:'green',shape:'dot',text:'flow.lv_cfg chargé'});\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":2100,"wires":[[]]},{"id":"69b8339b96f4a9b2","type":"group","z":"78f0de5247d76061","name":"Lancement solaire Automatique","style":{"stroke":"#0070c0","fill":"#92d04f","label":true,"color":"#000000"},"nodes":["d2069a9d553413ad","73e31a029923320d","bbf51a40f3db623f","733134b82fd46776","15a2f1bc0768049d","c5cf27191494703e","c9df34cf880c9771","18e6ab76f9e80d23","f2daaf64e0b5dc97","efe5827f0a439b48","5b5d15b92ff4c723","74af5c7a49b9529d","66ee1dcf0ff75429","f1a8aa88a3feb399","cc2f03a63e636eda","44d222c4c0e7078b"],"x":14,"y":1159,"w":2772,"h":182},{"id":"d2069a9d553413ad","type":"inject","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Toutes les 10 min","props":[],"repeat":"600","crontab":"","once":false,"onceDelay":"","topic":"","x":150,"y":1240,"wires":[["73e31a029923320d","4bfef0bf0df75300"]]},{"id":"73e31a029923320d","type":"change","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"entity_id ← inverter_power","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"lv_cfg.entities.inverter_power","tot":"flow"}],"x":400,"y":1240,"wires":[["bbf51a40f3db623f"]]},{"id":"bbf51a40f3db623f","type":"api-current-state","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Puissance onduleur","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{entity_id}}","state_type":"num","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":630,"y":1240,"wires":[["733134b82fd46776"]]},{"id":"733134b82fd46776","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Prod solaire > seuil ?","func":"const cfg = flow.get('lv_cfg') || {};\nconst MIN = cfg?.thresholds?.prod_min_w ?? 1000;\nif (Number(msg.payload.state) > MIN) return msg;\nreturn null;","outputs":1,"noerr":0,"x":860,"y":1240,"wires":[["15a2f1bc0768049d"]]},{"id":"15a2f1bc0768049d","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Flag lv_en_attente actif ?","func":"return (global.get('lv_en_attente') === true) ? msg : null;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1110,"y":1240,"wires":[["c5cf27191494703e","efe5827f0a439b48"]]},{"id":"c5cf27191494703e","type":"change","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"entity_id ← house_export_power","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"lv_cfg.entities.house_export_power","tot":"flow"}],"x":1400,"y":1200,"wires":[["c9df34cf880c9771"]]},{"id":"c9df34cf880c9771","type":"api-current-state","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Puissance export maison","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{entity_id}}","state_type":"num","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":1690,"y":1200,"wires":[["18e6ab76f9e80d23"]]},{"id":"18e6ab76f9e80d23","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Surplus ≥ seuil ?","func":"const cfg = flow.get('lv_cfg') || {};\nconst SURPLUS_W = cfg?.thresholds?.surplus_w ?? 1500;\nlet p = Number(msg.payload.state);\nmsg.surplusOK = p <= -SURPLUS_W;\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1900,"y":1200,"wires":[["f2daaf64e0b5dc97"]]},{"id":"f2daaf64e0b5dc97","type":"change","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"surplusOK → global","rules":[{"t":"set","p":"surplusOK","pt":"global","to":"surplusOK","tot":"msg"}],"x":2120,"y":1200,"wires":[[]]},{"id":"efe5827f0a439b48","type":"change","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"entity_id ← solcast_today","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"lv_cfg.entities.solcast_today","tot":"flow"}],"x":1370,"y":1280,"wires":[["5b5d15b92ff4c723"]]},{"id":"5b5d15b92ff4c723","type":"api-current-state","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Forecast Solcast (today)","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":1610,"y":1280,"wires":[["74af5c7a49b9529d"]]},{"id":"74af5c7a49b9529d","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Sum forecast 4h (variabilisé)","func":"const cfg = flow.get('lv_cfg') || {};\nconst SLOTS = cfg?.thresholds?.window_slots ?? 8;\nconst TH_KWH = cfg?.thresholds?.kwh_window ?? 1.5;\nlet forecast = msg.payload?.attributes?.detailedForecast || [];\nlet now = new Date();\nlet future = forecast\n  .map(s => ({ t: new Date(s.period_start), kw: Number(s.pv_estimate) }))\n  .filter(s => s.t >= now)\n  .slice(0, SLOTS);\nlet sum_kwh = future.reduce((acc, s) => acc + s.kw * 0.5, 0);\nmsg.forecastOK = sum_kwh >= TH_KWH;\nmsg.forecastSum = sum_kwh;\nreturn msg;","outputs":1,"noerr":0,"x":1860,"y":1280,"wires":[["66ee1dcf0ff75429"]]},{"id":"66ee1dcf0ff75429","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Test conditions","func":"let surplusOK = global.get('surplusOK');\nlet forecastOK = msg.forecastOK;\nif (surplusOK && forecastOK){\n  msg.payload = { launch: true, surplus: surplusOK, forecast: forecastOK, forecastSum: msg.forecastSum };\n  return [msg, null];\n}\nmsg.payload = { launch: false, surplus: surplusOK, forecast: forecastOK, forecastSum: msg.forecastSum };\nreturn [null, msg];","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2100,"y":1280,"wires":[["f1a8aa88a3feb399"],[]]},{"id":"f1a8aa88a3feb399","type":"function","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Préparer HA params (ECO périodique)","func":"const cfg = flow.get('lv_cfg') || {};\nmsg.ha = {\n  device_id: cfg?.home_connect?.device_id,\n  program_key: cfg?.home_connect?.program_keys?.eco50,\n  program_select: cfg?.entities?.program_select,\n  start_button: cfg?.entities?.start_button\n};\nreturn msg;","outputs":1,"noerr":0,"x":2370,"y":1280,"wires":[["3c9dea7a32a51d36","cc2f03a63e636eda","44d222c4c0e7078b"]]},{"id":"cc2f03a63e636eda","type":"api-call-service","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Button LV start","server":"680c8e88f3d1cfdd","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.start_button}}"],"labelId":[],"data":"{}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"button","service":"press","target":{"entity_id":"{{ha.start_button}}"},"x":2680,"y":1300,"wires":[[]]},{"id":"44d222c4c0e7078b","type":"api-call-service","z":"78f0de5247d76061","g":"69b8339b96f4a9b2","name":"Select LV ECO","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.program_select}}"],"labelId":[],"data":"{\"option\":\"{{ha.program_key}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"select","service":"select_option","target":{"entity_id":"{{ha.program_select}}"},"x":2680,"y":1260,"wires":[[]]},{"id":"8cd269ab59fe60f1","type":"group","z":"78f0de5247d76061","name":"Logique de lancement Initial","style":{"label":true,"fill":"#c8e7a7"},"nodes":["b6bafb3c37ce5e81","6fa47715c1682e15","1fefa5370ce3d19e"],"x":8,"y":13,"w":2924,"h":800},{"id":"b6bafb3c37ce5e81","type":"group","z":"78f0de5247d76061","g":"8cd269ab59fe60f1","name":"Porte ouverte/unavailable ? reload config","style":{"stroke":"#ffff00","label":true,"label-position":"n","fill":"#ffffbf","color":"#000000"},"nodes":["8b1c0176c7874c19","31c0b68f8220fcc3","076c7db37ae6d743","435ead721c2bb83f","0ad7a7751a574f25","604c629cbcdc6c21","7e04f0b329f032b5"],"x":1414,"y":39,"w":1252,"h":162},{"id":"8b1c0176c7874c19","type":"api-call-service","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Reload config LV","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":["{{device_id}}"],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"homeassistant","service":"reload_config_entry","x":1530,"y":100,"wires":[["31c0b68f8220fcc3"]]},{"id":"31c0b68f8220fcc3","type":"delay","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Attendre 15s","pauseType":"delay","timeout":"15","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":1710,"y":100,"wires":[["076c7db37ae6d743"]]},{"id":"076c7db37ae6d743","type":"api-current-state","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Re-tester porte","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"doorstate","propertyType":"msg","value":"","valueType":"entityState"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1880,"y":100,"wires":[["435ead721c2bb83f"]]},{"id":"435ead721c2bb83f","type":"switch","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Toujours ouverte ?","property":"doorstate","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"},{"t":"eq","v":"off","vt":"str"},{"t":"eq","v":"unavailable","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":2070,"y":100,"wires":[["0ad7a7751a574f25"],[],["7e04f0b329f032b5"]]},{"id":"0ad7a7751a574f25","type":"function","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Notif door always open","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nconst device_id = cfg?.home_connect?.device_id;\nconst unavailable = global.get('lv_unavailable');\nmsg.payload = {\n    chatId: CHAT,\n    type: \"message\",\n    content: \"La porte du lave-vaisselle est toujours détectée comme ouverte après tentative de correction. Veuillez vérifier manuellement !\"\n};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2300,"y":80,"wires":[["604c629cbcdc6c21"]]},{"id":"604c629cbcdc6c21","type":"telegram sender","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Notif Telegram Pb lv","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":2540,"y":80,"wires":[[]]},{"id":"7e04f0b329f032b5","type":"function","z":"78f0de5247d76061","g":"b6bafb3c37ce5e81","name":"Notif unavailable","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nconst device_id = cfg?.home_connect?.device_id;\nconst unavailable = global.get('lv_unavailable');\n\nif (unavailable){\n    global.set('lv_unavailable', false);\n    msg.payload = {\n        chatId: CHAT,\n        type: \"message\",\n        content: \"Lave-vaisselle est toujours indisponible ! Je n'arrive pas à corriger le probleme automatiquement. Il va falloir regarder toi même... désolé !\"\n    };\n    return [msg];\n}\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2310,"y":160,"wires":[["604c629cbcdc6c21"]]},{"id":"6fa47715c1682e15","type":"group","z":"78f0de5247d76061","g":"8cd269ab59fe60f1","name":"Envoi du message initial","style":{"stroke":"#3f93cf","fill":"#bfdbef","label":true,"color":"#000000"},"nodes":["3fda74d6c93f7869","0c06e2b1488115b4","ebc610be0970d247","4778ecda7f0da515","128b748c18df3f75","ff5ae06b16692ba8","fba5ba612985ac28","d6938d5075ddbe18","33ad4d89df6aa0d7","2ce8aaab0edbf670","893308c081f4c29c","6f2d3cced3a91644","05cc1be93695cfe0","1e1f30cc6723d175"],"x":34,"y":39,"w":1292,"h":362},{"id":"3fda74d6c93f7869","type":"function","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Filtrer le bon tag (+ annulation si attente)","func":"const cfg = flow.get('lv_cfg') || {};\nconst TAG = cfg?.nfc?.tag_id_eco;\nif (!msg?.payload?.event?.tag_id) return null;\n\nif (msg.payload.event.tag_id === TAG) {\n  // si attente en cours et re-scan => annule\n  if (global.get('lv_en_attente') === true) {\n    global.set('lv_en_attente', false);\n    global.set('lv_en_attente_date', null);\n    msg.cancel = true;\n    msg.payload = {\n      chatId: 'home',\n      type: 'message',\n      content: '⏹️ Annulation du lancement solaire du lave-vaisselle (nouveau scan du tag).'\n    };\n    return msg;\n  }\n  // sinon, propose urgent/non\n  msg.payload = {\n    chatId: 'home',\n    content: 'Cycle urgent ?',\n    type: 'message',\n    options: {\n      reply_markup: JSON.stringify({\n        inline_keyboard: [\n          [{ text: 'OUI 65°C 🚨', callback_data: 'LV_URGENT_OUI65' }],\n          [{ text: 'OUI 45°C 🚨', callback_data: 'LV_URGENT_OUI45' }],\n          [{ text: 'NON ☀️',  callback_data: 'LV_URGENT_NON'   }]\n        ]\n      })\n    }\n  };\n  return msg;\n}\nreturn null;","outputs":1,"timeout":0,"noerr":0,"x":260,"y":160,"wires":[["0c06e2b1488115b4"]]},{"id":"0c06e2b1488115b4","type":"switch","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Annulation re-scan ?","property":"cancel","propertyType":"msg","rules":[{"t":"true"},{"t":"null"}],"checkall":"true","repair":false,"outputs":2,"x":560,"y":160,"wires":[["ebc610be0970d247"],["4778ecda7f0da515"]]},{"id":"ebc610be0970d247","type":"function","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Routage Telegram (flow.lv_cfg)","func":"const cfg = flow.get('lv_cfg') || {};\nconst map = cfg?.telegram?.map || {};\nconst def = cfg?.telegram?.default_chat;\nlet alias = msg?.payload?.chatId;\nmsg.payload.chatId = (alias && map[alias] != null) ? map[alias] : def;\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":140,"wires":[["2ce8aaab0edbf670"]]},{"id":"4778ecda7f0da515","type":"change","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"entity_id ← door_sensor","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"lv_cfg.entities.door_sensor","tot":"flow"}],"x":570,"y":220,"wires":[["128b748c18df3f75"]]},{"id":"128b748c18df3f75","type":"api-current-state","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Porte ouverte ?","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"doorstate","propertyType":"msg","value":"","valueType":"entityState"}],"for":"","forType":"num","x":540,"y":260,"wires":[["ff5ae06b16692ba8"]]},{"id":"ff5ae06b16692ba8","type":"switch","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Door open/unavailable/closed","property":"doorstate","propertyType":"msg","rules":[{"t":"eq","v":"on","vt":"str"},{"t":"eq","v":"unavailable","vt":"str"},{"t":"eq","v":"off","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":590,"y":320,"wires":[["33ad4d89df6aa0d7"],["d6938d5075ddbe18"],["fba5ba612985ac28"]]},{"id":"fba5ba612985ac28","type":"function","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Routage + clavier inline","func":"const cfg = flow.get('lv_cfg') || {};\nconst map = cfg?.telegram?.map || {};\nconst def = cfg?.telegram?.default_chat;\nmsg.payload.chatId = map.home ?? def;\nmsg.payload.type = 'message';\nmsg.payload.options = {\n  reply_markup: JSON.stringify({\n    inline_keyboard: [\n      [{ text: 'OUI 65°C 🚨', callback_data: 'LV_URGENT_OUI65' }],\n      [{ text: 'OUI 45°C 🚨', callback_data: 'LV_URGENT_OUI45' }],\n      [{ text: 'NON ☀️',  callback_data: 'LV_URGENT_NON'   }]\n    ]\n  })\n};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":360,"wires":[["893308c081f4c29c"]]},{"id":"d6938d5075ddbe18","type":"function","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Notif unavailable + reload","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nconst device_id = cfg?.home_connect?.device_id;\n// 1er coup: prévenir + reload\nglobal.set('lv_unavailable', true);\nmsg.payload = { chatId: CHAT, type: 'message', content: \"Lave-vaisselle indisponible, je recharge la configuration et réessaie.\" };\nmsg.device_id = device_id;\nreturn [msg];","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":320,"wires":[["604c629cbcdc6c21","8b1c0176c7874c19"]]},{"id":"33ad4d89df6aa0d7","type":"change","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"device_id ← flow.device_id","rules":[{"t":"set","p":"device_id","pt":"msg","to":"lv_cfg.home_connect.device_id","tot":"flow"}],"x":900,"y":280,"wires":[["8b1c0176c7874c19"]]},{"id":"2ce8aaab0edbf670","type":"telegram sender","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":870,"y":80,"wires":[[]]},{"id":"893308c081f4c29c","type":"telegram sender","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Send inline keyboard","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":1180,"y":360,"wires":[["dcba79931defae06","6f2d3cced3a91644"]]},{"id":"6f2d3cced3a91644","type":"function","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"save chatId & msgId","func":"global.set('lv_inital_messageId', msg.payload.sentMessageId);\nglobal.set('lv_inital_chatId', msg.payload.chatId);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"x":1200,"y":320,"wires":[[]]},{"id":"05cc1be93695cfe0","type":"server-events","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"NFC Tag scan","server":"680c8e88f3d1cfdd","version":3,"exposeAsEntityConfig":"","eventType":"tag_scanned","eventData":"","waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"}],"x":130,"y":260,"wires":[["3fda74d6c93f7869"]]},{"id":"1e1f30cc6723d175","type":"inject","z":"78f0de5247d76061","g":"6fa47715c1682e15","name":"Debug","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"event_type\":\"tag_scanned\",\"event\":{\"tag_id\":\"2784588e-f926-46d1-fbe9-f9fbda99b55c\",\"name\":\"Lave vaisselle Eco\",\"device_id\":\"\"},\"origin\":\"LOCAL\",\"time_fired\":\"2025-06-22T12:07:19.420560+00:00\",\"context\":{\"id\":\"01JYBRJBKWNCFC1JNDWENVNT29\",\"parent_id\":null,\"user_id\":\"\"}}","payloadType":"json","x":130,"y":100,"wires":[["3fda74d6c93f7869"]]},{"id":"1fefa5370ce3d19e","type":"group","z":"78f0de5247d76061","g":"8cd269ab59fe60f1","name":"Réponse message initial","style":{"label":true,"color":"#000000","fill":"#e3f3d3"},"nodes":["c078702988b89452","7fd684d1283116d3","20902db36045407b","f9f9010447d362d3","d9b7d7472dd99561","dcba79931defae06","0edef25663abb4ea","7aa31bb31468cfb3","312ef4c17ed30ae7"],"x":34,"y":413,"w":2872,"h":374},{"id":"c078702988b89452","type":"telegram event","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Réponse Telegram Callback","bot":"edaf7fc8b148893c","event":"callback_query","autoanswer":true,"x":180,"y":660,"wires":[["7fd684d1283116d3"]]},{"id":"7fd684d1283116d3","type":"function","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Filtrer callback LV","func":"if (['LV_URGENT_OUI65','LV_URGENT_OUI45','LV_URGENT_NON'].includes(msg.payload.content)){\n  msg.LV_REPLY = true;\n  return msg;\n}\nreturn null;","outputs":1,"noerr":0,"x":430,"y":660,"wires":[["20902db36045407b","1ff30512641f101d"]]},{"id":"20902db36045407b","type":"change","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Reset pour trigger","rules":[{"t":"set","p":"reset","pt":"msg","to":"LV_REPLY","tot":"str"}],"x":950,"y":660,"wires":[["dcba79931defae06","d9b7d7472dd99561"]]},{"id":"f9f9010447d362d3","type":"group","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Suppression clavier T","style":{"stroke":"#ff0000","fill":"#ff7f7f","label":true,"label-position":"n","color":"#ffffff"},"nodes":["1ff30512641f101d","642f603a3dcba5ca"],"x":594,"y":519,"w":572,"h":82},{"id":"1ff30512641f101d","type":"function","z":"78f0de5247d76061","g":"f9f9010447d362d3","name":"Nettoyage clavier telegram","func":"const cfg = flow.get('lv_cfg') || {}; \nconst CHAT = cfg?.telegram?.default_chat || global.get('lv_inital_chatId');\n\nif (msg.payload.content === 'NO_REPLY'){\n  msg.payload = {\n    content: 'Lave-Vaiselle reponse timout, mode éco sélectionné',\n    type: 'editMessageText',\n    options: { reply_markup: {}, chat_id: global.get('lv_inital_chatId') || CHAT, message_id: global.get('lv_inital_messageId') }\n  };\n} else {\n  msg.payload = {\n    content: msg.originalMessage?.message?.text || msg.payload?.content,\n    type: 'editMessageText',\n    options: { reply_markup: {}, chat_id: msg.payload.chatId || CHAT, message_id: msg.payload.messageId }\n  };\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":560,"wires":[["642f603a3dcba5ca"]]},{"id":"642f603a3dcba5ca","type":"telegram sender","z":"78f0de5247d76061","g":"f9f9010447d362d3","name":"Remove keyboard after answer","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":1010,"y":560,"wires":[[]]},{"id":"d9b7d7472dd99561","type":"switch","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"OUI65/OUI45/NON/Timeout","property":"payload.content","propertyType":"msg","rules":[{"t":"eq","v":"LV_URGENT_OUI65","vt":"str"},{"t":"eq","v":"LV_URGENT_OUI45","vt":"str"},{"t":"eq","v":"LV_URGENT_NON","vt":"str"},{"t":"eq","v":"NO_REPLY","vt":"str"}],"outputs":4,"x":1560,"y":660,"wires":[["7f42c33fee298f63"],["4e5abeef501236f8"],["f8a5e7ea7725d95e"],["f8a5e7ea7725d95e"]]},{"id":"dcba79931defae06","type":"trigger","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Attendre 2 min réponse","op1":"","op2":"{\"payload.content\":\"NO_REPLY\"}","op1type":"nul","op2type":"json","duration":"2","extend":false,"overrideDelay":false,"units":"min","reset":"LV_REPLY","bytopic":"all","topic":"lv_telegram_wait","outputs":1,"x":1410,"y":560,"wires":[["d9b7d7472dd99561","0d09f7d84f24045e"]]},{"id":"0edef25663abb4ea","type":"group","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Lancement Rapide","style":{"fill":"#bfbfbf","label":true,"color":"#000000"},"nodes":["7f42c33fee298f63","236d8ec2cbb53fac","10483c1078120cd5","4e5abeef501236f8","259986249b9a954b","be1de4647a8b0e95","f8a5e7ea7725d95e","eecbea6f0c3caa96"],"x":1834,"y":539,"w":852,"h":222},{"id":"7f42c33fee298f63","type":"function","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Préparer HA params (Quick65)","func":"const cfg = flow.get('lv_cfg') || {};\nmsg.ha = {\n  device_id: cfg?.home_connect?.device_id,\n  program_key: cfg?.home_connect?.program_keys?.quick65,\n  program_select: cfg?.entities?.program_select,\n  start_button: cfg?.entities?.start_button,\n  power_switch: cfg?.entities?.lv_power_switch\n};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1990,"y":640,"wires":[["10483c1078120cd5","236d8ec2cbb53fac"]]},{"id":"236d8ec2cbb53fac","type":"api-call-service","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Select LV Quick 65°","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.program_select}}"],"labelId":[],"data":"{\"option\":\"{{ha.program_key}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"select","service":"select_option","target":{"entity_id":"{{ha.program_select}}"},"x":2300,"y":580,"wires":[["be1de4647a8b0e95"]]},{"id":"10483c1078120cd5","type":"function","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Notif Quick65","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nmsg.payload = { chatId: CHAT, type: 'message', content: 'Ok, je lance le lave-vaisselle en mode cycle rapide 65°C' };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2280,"y":640,"wires":[["7aa31bb31468cfb3"]]},{"id":"4e5abeef501236f8","type":"function","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Préparer HA params (Quick45)","func":"const cfg = flow.get('lv_cfg') || {};\nmsg.ha = {\n  device_id: cfg?.home_connect?.device_id,\n  program_key: cfg?.home_connect?.program_keys?.quick45,\n  program_select: cfg?.entities?.program_select,\n  start_button: cfg?.entities?.start_button,\n  power_switch: cfg?.entities?.lv_power_switch\n};\nreturn msg;","outputs":1,"noerr":0,"x":1990,"y":680,"wires":[["259986249b9a954b","eecbea6f0c3caa96"]]},{"id":"259986249b9a954b","type":"function","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Notif Quick45","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nmsg.payload = { chatId: CHAT, type: 'message', content: 'Ok, je lance le lave-vaisselle en mode cycle rapide 45°C' };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2280,"y":720,"wires":[["7aa31bb31468cfb3"]]},{"id":"be1de4647a8b0e95","type":"api-call-service","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Button LV start","server":"680c8e88f3d1cfdd","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.start_button}}"],"labelId":[],"data":"{}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"button","service":"press","target":{"entity_id":"{{ha.start_button}}"},"x":2580,"y":600,"wires":[[]]},{"id":"f8a5e7ea7725d95e","type":"function","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Set attente ECO (chat variabilisé)","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat || -4017188715;\nconst messages = [\n  \"🌱 Merci pour ce coup de pouce à la planète ! Elle t’envoie plein d’ondes vertes.\",\n  \"🌎 La Terre te fait un clin d’œil : tu viens de la faire sourire !\",\n  \"🌼 Grâce à toi, le soleil brille un peu plus fort aujourd’hui.\",\n  \"🌞 Bravo ! Les rayons du soleil t’applaudissent.\",\n  \"🌳 Un arbre vient de te faire un high five virtuel !\",\n  \"🍀 Économie d’énergie activée… et la planète te dit merci !\",\n  \"🐝 Les abeilles te saluent depuis les fleurs !\",\n  \"🦔 Un hérisson vient de t’envoyer un petit cœur.\",\n  \"🍃 Chuchotement du vent : ‘Merci humain responsable !’\",\n  \"🌏 Une vague bleue d’amour de la Terre pour toi !\",\n  \"🐦 Un oiseau chante ton nom : tu viens de faire un geste vert !\",\n  \"🪴 Une plante d’intérieur se sent fière de toi.\",\n  \"🌈 Tu viens d’activer le mode arc-en-ciel sur la planète !\",\n  \"☀️ Le soleil te fait coucou et te dit bravo pour ton geste écolo.\",\n  \"🌿 La planète a frissonné de joie, c’était toi ?\",\n  \"🐢 Une tortue te salue lentement mais sûrement pour ton geste !\",\n  \"🌊 Les océans t’applaudissent en douceur.\",\n  \"🦋 Un papillon a battu fièrement des ailes pour toi !\",\n  \"🌾 Un champ de blé danse pour fêter ta décision.\",\n  \"🫧 Une bulle d’air pur s’est formée rien que pour toi !\"\n];\n\nconst message = messages[Math.floor(Math.random() * messages.length)];\n\nglobal.set('lv_en_attente', true);\nglobal.set('lv_en_attente_date', Date.now());\nmsg.payload = { chatId: CHAT, type: 'message', content: message + '\\n Le lave-vaisselle sera lancé automatiquement en mode éco dès que la production solaire le permet !' };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2020,"y":720,"wires":[["09ad6f6d13148857"]]},{"id":"eecbea6f0c3caa96","type":"api-call-service","z":"78f0de5247d76061","g":"0edef25663abb4ea","name":"Select LV Quick 45°","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.program_select}}"],"labelId":[],"data":"{\"option\":\"{{ha.program_key}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"select","service":"select_option","target":{"entity_id":"{{ha.program_select}}"},"x":2300,"y":680,"wires":[["be1de4647a8b0e95"]]},{"id":"7aa31bb31468cfb3","type":"telegram sender","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":2790,"y":640,"wires":[[]]},{"id":"312ef4c17ed30ae7","type":"group","z":"78f0de5247d76061","g":"1fefa5370ce3d19e","name":"Suppression clavier T","style":{"stroke":"#ff0000","fill":"#ff7f7f","label":true,"label-position":"n","color":"#ffffff"},"nodes":["0d09f7d84f24045e","555b03ec42baf895"],"x":1574,"y":439,"w":652,"h":82},{"id":"0d09f7d84f24045e","type":"function","z":"78f0de5247d76061","g":"312ef4c17ed30ae7","name":"Nettoyage clavier telegram","func":"\nmsg.payload = {\n    content: 'Lave-Vaiselle reponse timout, mode éco sélectionné',\n    type: 'editMessageText',\n    options: {\n        reply_markup: {},\n        chat_id: global.get('lv_inital_chatId'),\n        message_id: global.get('lv_inital_messageId')\n    }\n};\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1720,"y":480,"wires":[["555b03ec42baf895"]]},{"id":"555b03ec42baf895","type":"telegram sender","z":"78f0de5247d76061","g":"312ef4c17ed30ae7","name":"Remove keyboard after answer","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":2070,"y":480,"wires":[[]]},{"id":"01761677acd98ed0","type":"group","z":"78f0de5247d76061","name":"Lancement direct OU message estimation","style":{"label":true,"fill":"#addb7b","color":"#000000"},"nodes":["2055ceb897311db1","09ad6f6d13148857","81e8af5a513ece93","19d1482fe4e9504c","3c2823394567ade5","209f16afc26b6d37","3c0f1053bc1415c0","0622dbb6ccdad4e5","c32b9cc3dcdadc5c","c7c6f8b765d8ccc4","840dd51bc38a8454","14f5bbc40322383b","1976e4426eb99bfb","54656bf617092bf3","b0a13648e596727e","3696d9af713c965d","69ae81da6c059574","37ac44071958dc37"],"x":14,"y":819,"w":2652,"h":322},{"id":"2055ceb897311db1","type":"telegram sender","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Telegram sender","bot":"edaf7fc8b148893c","haserroroutput":false,"outputs":1,"x":2530,"y":1100,"wires":[[]]},{"id":"09ad6f6d13148857","type":"change","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"power_entity_id ← house_export_power","rules":[{"t":"set","p":"power_entity_id","pt":"msg","to":"lv_cfg.entities.house_export_power","tot":"flow"}],"x":200,"y":880,"wires":[["81e8af5a513ece93"]]},{"id":"81e8af5a513ece93","type":"api-current-state","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Puissance export maison (now)","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{power_entity_id}}","state_type":"num","blockInputOverrides":true,"outputProperties":[{"property":"powerNow","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":190,"y":940,"wires":[["19d1482fe4e9504c"]]},{"id":"19d1482fe4e9504c","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Surplus ≥ seuil ? (now)","func":"const cfg = flow.get('lv_cfg') || {};\nconst SURPLUS_W = cfg?.thresholds?.surplus_w ?? 1500;\nconst p = Number(msg.powerNow?.state);\nmsg.surplusNowOK = (p <= -SURPLUS_W);\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":940,"wires":[["3c2823394567ade5"]]},{"id":"3c2823394567ade5","type":"change","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"entity_id ← solcast_today","rules":[{"t":"set","p":"forecast_entity_id","pt":"msg","to":"lv_cfg.entities.solcast_today","tot":"flow"}],"x":710,"y":940,"wires":[["209f16afc26b6d37"]]},{"id":"209f16afc26b6d37","type":"api-current-state","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Forecast Solcast (today) — now","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{forecast_entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"solcast_now","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":730,"y":880,"wires":[["3c0f1053bc1415c0"]]},{"id":"3c0f1053bc1415c0","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Test conditions NOW (sum 4h)","func":"const cfg = flow.get('lv_cfg') || {};\nconst SLOTS = cfg?.thresholds?.window_slots ?? 8;\nconst TH_KWH = cfg?.thresholds?.kwh_window ?? 1.5;\nfunction sumNext(ent){\n  if (!ent?.attributes?.detailedForecast) return 0;\n  const now = new Date();\n  const future = ent.attributes.detailedForecast\n    .map(s => ({ t: new Date(s.period_start), kw: Number(s.pv_estimate) }))\n    .filter(s => s.t >= now)\n    .slice(0, SLOTS);\n  return future.reduce((a, s) => a + s.kw * 0.5, 0);\n}\nconst sum = sumNext(msg.solcast_now);\nmsg.forecastNowOK = (sum >= TH_KWH);\nconst ok = !!msg.surplusNowOK || !!msg.forecastNowOK;\nreturn ok ? [msg, null] : [null, msg];","outputs":2,"noerr":0,"x":1030,"y":880,"wires":[["0622dbb6ccdad4e5"],["c7c6f8b765d8ccc4"]]},{"id":"0622dbb6ccdad4e5","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Préparer HA params (ECO)","func":"const cfg = flow.get('lv_cfg') || {};\nmsg.ha = {\n  device_id: cfg?.home_connect?.device_id,\n  program_key: cfg?.home_connect?.program_keys?.eco50,\n  program_select: cfg?.entities?.program_select,\n  start_button: cfg?.entities?.start_button,\n  power_switch: cfg?.entities?.lv_power_switch\n};\nreturn msg;","outputs":1,"noerr":0,"x":1380,"y":880,"wires":[["3c9dea7a32a51d36","c32b9cc3dcdadc5c","37ac44071958dc37"]]},{"id":"c32b9cc3dcdadc5c","type":"api-call-service","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Button LV (start ECO)","server":"680c8e88f3d1cfdd","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.start_button}}"],"labelId":[],"data":"{}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"button","service":"press","target":{"entity_id":"{{ha.start_button}}"},"x":1880,"y":900,"wires":[[]]},{"id":"c7c6f8b765d8ccc4","type":"change","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"entity_id ← solcast_today","rules":[{"t":"set","p":"solcast_today_entity_id","pt":"msg","to":"lv_cfg.entities.solcast_today","tot":"flow"}],"x":1370,"y":940,"wires":[["840dd51bc38a8454"]]},{"id":"840dd51bc38a8454","type":"api-current-state","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Forecast Solcast (aujourd’hui)","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{solcast_today_entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"solcast_today","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":1090,"y":1060,"wires":[["14f5bbc40322383b"]]},{"id":"14f5bbc40322383b","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Prochaine fenêtre solaire (today)","func":"const cfg = flow.get('lv_lv_cfg') || flow.get('lv_cfg') || {};\nconst SLOTS = cfg?.thresholds?.window_slots ?? 8;\nconst TH_KWH = cfg?.thresholds?.kwh_window ?? 1.5;\nconst MIN1 = cfg?.thresholds?.min_first_kw ?? 1.5;\nfunction findNextStart_TODAY(detailed){\n  const now = new Date();\n  const future = detailed\n    .map(s => ({ t: new Date(s.period_start), kw: Number(s.pv_estimate) }))\n    .sort((a,b) => a.t - b.t)\n    .filter(s => s.t >= now);\n  for (let i = 0; i + SLOTS <= future.length; i++){\n    if (future[i].kw < MIN1) continue;\n    let sum_kwh = 0; for (let j = 0; j < SLOTS; j++) sum_kwh += future[i+j].kw * 0.5;\n    if (sum_kwh >= TH_KWH) return { start: future[i].t, sum_kwh };\n  }\n  return null;\n}\nlet nextInfo = null;\nif (msg.solcast_today?.attributes?.detailedForecast){\n  nextInfo = findNextStart_TODAY(msg.solcast_today.attributes.detailedForecast);\n}\nif (nextInfo){\n  const opts = { timeZone: 'Europe/Paris', hour: '2-digit', minute: '2-digit' };\n  const dOpts = { timeZone: 'Europe/Paris', weekday: 'long', day: '2-digit', month: '2-digit' };\n  const h = nextInfo.start.toLocaleTimeString('fr-FR', opts);\n  const d = nextInfo.start.toLocaleDateString('fr-FR', dOpts);\n  msg.payload = msg.payload || {};\n  msg.payload.content = (msg.payload.content||'') + `\\n\\n🔜 Prochain créneau solaire estimé : **${d} à ${h}** (≥ ${MIN1.toFixed(1)} kW au départ, ≈ ${nextInfo.sum_kwh.toFixed(1)} kWh/4h).`;\n  msg._needTomorrow = false;\n} else {\n  msg._needTomorrow = true;\n}\nreturn msg;","outputs":1,"noerr":0,"x":1370,"y":1060,"wires":[["1976e4426eb99bfb"]]},{"id":"1976e4426eb99bfb","type":"switch","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Créneau trouvé ?","property":"_needTomorrow","propertyType":"msg","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","outputs":2,"x":1630,"y":1060,"wires":[["54656bf617092bf3"],["69ae81da6c059574"]]},{"id":"54656bf617092bf3","type":"change","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"entity_id ← solcast_tomorrow","rules":[{"t":"set","p":"solecast_tomorrow_entity_id","pt":"msg","to":"lv_cfg.entities.solcast_tomorrow","tot":"flow"}],"x":1910,"y":1000,"wires":[["b0a13648e596727e"]]},{"id":"b0a13648e596727e","type":"api-current-state","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Forecast Solcast (demain)","server":"680c8e88f3d1cfdd","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","entity_id":"{{solecast_tomorrow_entity_id}}","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"solcast_tomorrow","propertyType":"msg","value":"","valueType":"entity"}],"for":"","forType":"num","x":2190,"y":1000,"wires":[["3696d9af713c965d"]]},{"id":"3696d9af713c965d","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Prochaine fenêtre solaire (tomorrow)","func":"const cfg = flow.get('lv_cfg') || {};\nconst SLOTS = cfg?.thresholds?.window_slots ?? 8;\nconst TH_KWH = cfg?.thresholds?.kwh_window ?? 1.5;\nconst MIN1 = cfg?.thresholds?.min_first_kw ?? 1.5;\nfunction findNextStart(detailed){\n  const series = detailed\n    .map(s => ({ t: new Date(s.period_start), kw: Number(s.pv_estimate) }))\n    .sort((a,b) => a.t - b.t);\n  for (let i = 0; i + SLOTS <= series.length; i++){\n    if (series[i].kw < MIN1) continue;\n    let sum_kwh = 0; for (let j = 0; j < SLOTS; j++) sum_kwh += series[i+j].kw * 0.5;\n    if (sum_kwh >= TH_KWH) return { start: series[i].t, sum_kwh };\n  }\n  return null;\n}\nlet nextInfo = null;\nif (msg.solcast_tomorrow?.attributes?.detailedForecast){\n  nextInfo = findNextStart(msg.solcast_tomorrow.attributes.detailedForecast);\n}\nconst opts = { timeZone:'Europe/Paris', hour:'2-digit', minute:'2-digit' };\nconst dOpts= { timeZone:'Europe/Paris', weekday:'long', day:'2-digit', month:'2-digit' };\nmsg.payload = msg.payload || {};\nif (nextInfo){\n  const h = nextInfo.start.toLocaleTimeString('fr-FR', opts);\n  const d = nextInfo.start.toLocaleDateString('fr-FR', dOpts);\n  msg.payload.content = (msg.payload.content||'') + `\\n\\n🔜 Prochain créneau solaire estimé : **${d} à ${h}** (≥ ${MIN1.toFixed(1)} kW au départ, ≈ ${nextInfo.sum_kwh.toFixed(1)} kWh/4h).`;\n} else {\n  msg.payload.content = (msg.payload.content||'') + `\\n\\nℹ️ Pas de créneau répondant au départ ≥ ${MIN1.toFixed(1)} kW et au cumul ${TH_KWH} kWh dans les 48h.`;\n}\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2490,"y":1000,"wires":[["69ae81da6c059574"]]},{"id":"69ae81da6c059574","type":"function","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Notif attente ECO (chat variabilisé)","func":"const cfg = flow.get('lv_cfg') || {};\nconst CHAT = cfg?.telegram?.default_chat;\nmsg.payload = Object.assign({ chatId: CHAT, type: 'message' }, msg.payload || {});\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2260,"y":1100,"wires":[["2055ceb897311db1"]]},{"id":"37ac44071958dc37","type":"api-call-service","z":"78f0de5247d76061","g":"01761677acd98ed0","name":"Select LV ECO","server":"680c8e88f3d1cfdd","version":7,"debugenabled":true,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":["{{ha.program_select}}"],"labelId":[],"data":"{\"option\":\"{{ha.program_key}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"blockInputOverrides":false,"domain":"select","service":"select_option","target":{"entity_id":"{{ha.program_select}}"},"x":1860,"y":860,"wires":[[]]},{"id":"edaf7fc8b148893c","type":"telegram bot","botname":"Exemple","usernames":"","chatids":"","baseapiurl":"","testenvironment":false,"updatemode":"polling","pollinterval":300,"usesocks":false,"sockshost":"","socksprotocol":"socks5","socksport":6667,"socksusername":"anonymous","sockspassword":"","bothost":"","botpath":"","localbothost":"0.0.0.0","localbotport":8443,"publicbotport":8443,"privatekey":"","certificate":"","useselfsignedcertificate":false,"sslterminated":false,"verboselogging":false},{"id":"efd92b535d2ae13f","type":"global-config","env":[],"modules":{"node-red-contrib-telegrambot":"16.3.2","node-red-contrib-home-assistant-websocket":"0.77.2"}}]