UNPKG

node-red-contrib-vib-smart-boiler

Version:

Smart boiler node to control multiple thermostat

510 lines (441 loc) 24.4 kB
<script type="text/javascript"> // Terminal command node-red -v -D logging.console.level=trace RED.nodes.registerType('smart-boiler',{ category: 'vib-node', color: '#bdeeff', defaults: { name: {value:""}, mqttSettings: {value: "", type: "smart-boiler-settings"}, boilerCurrentTemperatureSetTopic:{value:""}, boilerCurrentTemperatureKey:{value:""}, boilerSpSetTopic:{value:""}, boilerSpKey:{value:""}, boilerLeadingDeviceSetTopic:{value:""}, boilerLeadingDeviceIdSetTopic:{value:""}, boilerLeadingDeviceKey:{value:""}, boilerSwCentralHeatingSetTopic:{value:""}, boilerSwCentralHeatingKey:{value:""}, boilerEntityID:{value:""}, mqttUpdates:{value:false}, debugInfo:{value:"none"}, boilerSecurity:{value:false}, triggerMode:{value:"triggerMode.statechange.startup",required: true}, cycleDuration: {value:60,required: true,validate:function(v) { if (isNaN(v)){ return false; } if (v<0 || v> 600){ return false; } return true; }}, outputUpdates:{value:true}, defaultSp:{value:5,required: true, validate:function(v) { if (isNaN(v)){ return false; } if (v<0 || v> 35){ //this.warn("overrideSp need to be >0 et <= 35") return false; } return true; }}, defaultTemp:{value:10,required: true, validate:function(v) { if (isNaN(v)){ return false; } if (v<0 || v> 35){ //this.warn("overrideSp need to be >0 et <= 35") return false; } return true; }}, maxDurationSinceLastInput:{value:"5",required: true, validate:function(v) { if (isNaN(v)){ return false; } if (v<0 || v> 120){ //this.warn("overrideSp need to be >0 et <= 35") return false; } return true; }} }, inputs:1, outputs:1, icon: "font-awesome/fa-fire", button: { onclick: function() { let node=this; $.ajax({ url: "smartboiler/" + node.id, type: "POST", data: JSON.stringify({payload:{command:"trigger"}}), contentType: "application/json; charset=utf-8", success: function (resp) { RED.notify(node._("smart-boiler trigger success", { label: "ss" }), { type: "success", id: "inject", timeout: 2000 }); }, error: function (jqXHR, textStatus, errorThrown) { if (jqXHR.status == 404) { RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error"); } else if (jqXHR.status == 500) { RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error"); } else if (jqXHR.status == 0) { RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error"); } else { RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error"); } } }); } }, label: function() { return this.name||"smart-boiler"; }, oneditprepare: function() { var node = this; $("#node-input-boilerEntityID").on('change', function() { console.log("boilerEntityID changed, updating topics"); node.boilerEntityID = $("#node-input-boilerEntityID").val(); let prefix = (node.mqttSettings && node.mqttSettings.mqttRootPath) ? node.mqttSettings.mqttRootPath : "homeassistant"; if ($("#node-input-boilerSpSetTopic").val().length==0){ let setpointTopic = prefix + "/" + node.boilerEntityID + "/setpoint-temperature/set"; $("#node-input-boilerSpSetTopic").val(setpointTopic); } if ($("#node-input-boilerSwCentralHeatingSetTopic").val().length==0){ let swCentralHeatingTopic = prefix + "/" + node.boilerEntityID + "/swCentralHeating/set"; $("#node-input-boilerSwCentralHeatingSetTopic").val(swCentralHeatingTopic); } if ($("#node-input-boilerCurrentTemperatureSetTopic").val().length==0){ let tempTopic = prefix + "/" + node.boilerEntityID + "/current-temperature/state"; $("#node-input-boilerCurrentTemperatureSetTopic").val(tempTopic); } if ($("#node-input-boilerLeadingDeviceSetTopic").val().length==0){ let leadingDeviceTopic = prefix + "/" + node.boilerEntityID + "/leadingDevice/state"; $("#node-input-boilerLeadingDeviceSetTopic").val(leadingDeviceTopic); } if ($("#node-input-boilerLeadingDeviceIdSetTopic").val().length==0){ let leadingDeviceIdTopic = prefix + "/" + node.boilerEntityID + "/leadingDeviceId/state"; $("#node-input-boilerLeadingDeviceIdSetTopic").val(leadingDeviceIdTopic); } }); }, oneditsave: function() { var node = this; }, oneditresize: function() { } }); </script> <script type="text/html" data-template-name="smart-boiler"> <style> .form-row label { display: inline-block; width: 180px !important; white-space: nowrap; } .form-row input[type="text"] { width: 250px !important; } .form-row input[type="checkbox"] { width: 120px !important; } </style> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag">&nbsp;</i> Name</label> <input type="text" title="node & home asssitant device name" id="node-input-name" placeholder="Name"> </div> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag">&nbsp;</i> Boiler entity</label> <input type="text" title="node & home asssitant device name" id="node-input-boilerEntityID" placeholder="Smart boiler ID "> </div> <div class="form-row"> <label for="node-input-mqttSettings"><i class="fa fa-wrench">&nbsp;</i> MQTT</label> <input type="text" id="node-input-mqttSettings"></input> </div> <div class="form-row"> <label for="node-input-mqttUpdates"><i class="fa fa-tasks">&nbsp;</i> Mqtt updates</label> <input type="checkbox" title="" id="node-input-mqttUpdates"></span> </div> <div class="form-row"> <label for="node-input-outputUpdates"><i class="fa fa-tasks">&nbsp;</i> Output updates</label> <input type="checkbox" title="" id="node-input-outputUpdates"> </div> <div class="form-row"> <span><i class="fa fa-tasks">&nbsp;</i>Current temperature</span> </div> <div class="form-row"> <label for="node-input-boilerCurrentTemperatureSetTopic">Set</label> <input type="text" id="node-input-boilerCurrentTemperatureSetTopic" placeholder="mqtt topic"></input> </div> <div class="form-row"> <label for="node-input-boilerCurrentTemperatureKey">Key</label> <input type="text" id="node-input-boilerCurrentTemperatureKey" placeholder="mqtt key"></input> </div> <div class="form-row"> <span><i class="fa fa-tasks">&nbsp;</i>Set point temperature</span> </div> <div class="form-row"> <label for="node-input-boilerSpSetTopic">Set</label> <input type="text" id="node-input-boilerSpSetTopic" placeholder="mqtt topic"></input> </div> <div class="form-row"> <label for="node-input-boilerSpKey">Key</label> <input type="text" id="node-input-boilerSpKey" placeholder="mqtt key"></input> </div> <div class="form-row"> <span><i class="fa fa-tasks">&nbsp;</i>Leading Device</span> </div> <div class="form-row"> <label for="node-input-boilerLeadingDeviceSetTopic">Set name</label> <input type="text" title="" id="node-input-boilerLeadingDeviceSetTopic" placeholder="mqtt topic"> </div> <div class="form-row"> <label for="node-input-boilerLeadingDeviceIdSetTopic">Set Id</label> <input type="text" title="" id="node-input-boilerLeadingDeviceIdSetTopic" placeholder="mqtt topic"> </div> <div class="form-row"> <label for="node-input-boilerLeadingDeviceKey">Key</label> <input type="text" title="" id="node-input-boilerLeadingDeviceKey" placeholder="mqtt topic"> </div> <div class="form-row"> <span><i class="fa fa-tasks">&nbsp;</i>Central Heating Switch</span> </div> <div class="form-row"> <label for="node-input-boilerSwCentralHeatingSetTopic">Set</label> <input type="text" title="" id="node-input-boilerSwCentralHeatingSetTopic" placeholder="mqtt topic"> </div> <div class="form-row"> <label for="node-input-boilerSwCentralHeatingKey">Key</label> <input type="text" title="" id="node-input-boilerSwCentralHeatingKey" placeholder="mqtt topic"> </div> <div class="form-row"> <span><i class="fa fa-tasks">&nbsp;</i>Update</span> </div> <div class="form-row"> <label for="node-input-cycleDuration"><i class="fa fa-tasks">&nbsp;</i>Cycle</label> <input type="text" id="node-input-cycleDuration" style="width:60px !important; text-align: right;" placeholder="duration"> <span>s</span> </div> <div class="form-row"> <label for="node-input-triggerMode"><i class="fa fa-play">&nbsp;</i> Trigger mode </label> <select id="node-input-triggerMode"> <option value="triggerMode.statechange">When state changes</option> <option value="triggerMode.statechange.startup">When state changes + startup</option> <option value="triggerMode.everyCycle">Every cycle</option> </select> </div> <div class="form-row"> <label for="node-input-maxDurationSinceLastInput"><i class="fa fa-tasks">&nbsp;</i> Max duration</label> <input type="text" id="node-input-maxDurationSinceLastInput" style="width:60px !important; text-align: right;" placeholder="duration"> <span> min since last input</span> </div> <div class="form-row"> <label for="node-input-boilerSecurity"><i class="fa fa-tasks">&nbsp;</i>Security</label> <input type="checkbox" title="" id="node-input-boilerSecurity" placeholder=""> </div> <div class="form-row"> <label for="node-input-defaultTemp"><i class="fa fa-tasks"></i> Default temp</label> <input type="text" id="node-input-defaultTemp" style="width:60px !important; text-align: right;" placeholder="duration"> <span> °C</span> </div> <div class="form-row"> <label for="node-input-defaultSp"><i class="fa fa-tasks"></i> Default set point</label> <input type="text" id="node-input-defaultSp" style="width:60px !important; text-align: right;" placeholder="duration"> <span> °C</span> </div> <div class="form-row"> <label for="node-input-debugInfo"><i class="fa fa-bug">&nbsp;</i> Debug Level</label> <select id="node-input-debugInfo"> <option value="none">None</option> <option value="error">Error</option> <option value="warn">Warning</option> <option value="info">Info</option> <option value="debug">Debug</option> </select> </div> </script> <script type="text/html" data-help-name="smart-boiler"> <!-- ╔══════════════════════════════════════════════════════════════════════════════╗ ║ SMART-BOILER NODE ║ ╚══════════════════════════════════════════════════════════════════════════════╝ OVERVIEW: --------- This node is part of an intelligent heating control system consisting of three main components: • Smart-Scheduler: Manages multiple schedules and temperature set-points • Smart-Valve: Controls and groups individual valves by room/zone • Smart-Boiler: Central controller that determines optimal boiler temperature based on all connected valve states PURPOSE: -------- Smart-Boiler aggregates data from multiple Smart-Valve nodes to intelligently control the central heating boiler, ensuring efficient heating by prioritizing the valve with the greatest heating demand. INPUT MESSAGE FORMAT: -------------------- msg.payload = { command: "set", // Required setpoint: [number], // Set-point (target temperature) in °C temperature: [number], // Current valve temperature in °C requestedBy: [string], // Valve description/ID (e.g., "Valve kitchen") groupId: [number] // Group ID } Example: msg.payload = { command: "set", setpoint: 23, temperature: 20, requestedBy: "Valve kitchen", groupId: 1 } VALVE PROCESSING: ---------------- • Each input message creates or updates a valve entry in the internal stack • Valves are evaluated on each update cycle ACTIVE VALVE SELECTION LOGIC: ----------------------------- A valve is considered ACTIVE when: sp > temp (heating required) The boiler is controlled by the valve with the HIGHEST temperature gap: gap = sp - temp Example Scenario: • Valve A: sp=19°C, temp=21°C → INACTIVE (sp < temp) • Valve B: sp=23°C, temp=20°C → gap=3°C (ACTIVE) • Valve C: sp=24°C, temp=19°C → gap=5°C (ACTIVE - CONTROLS BOILER) Result: Valve C drives the boiler with sp=24°C, temp=19°C FALLBACK RULES: -------------- • No active valves: The highest set-point among inactive valves is used as the boiler reference • Input timeout: If no input received within max duration, a safety message is sent with default values to prevent continuous heating OUTPUT MODES: ------------ 1. MQTT Output (Recommended): - Set-point topic: Publishes boiler target temperature - Temperature topic: Publishes current reference temperature - Leading device topic: Publishes name of controlling valve 2. Standard Node Output: msg.payload = { sp: [number], temp: [number], name: [string], id: [string], groupId: [number], lastupdate: [ISO 8601 timestamp] } CONFIGURATION PARAMETERS: ------------------------ ┌─────────────────────────────────┬──────────────────────────────────────────┐ │ Parameter │ Description │ ├─────────────────────────────────┼──────────────────────────────────────────┤ │ MQTT │ MQTT broker connection configuration │ │ MQTT updates │ Enable direct MQTT publishing │ │ Output updates │ Enable standard node output │ │ Set point topic │ MQTT topic for boiler set-point │ │ Temperature topic │ MQTT topic for boiler current temp │ │ Leading Device topic │ MQTT topic for active valve name │ │ Update cycle duration │ Interval for valve stack evaluation │ │ Max duration since last input │ Timeout before safety mode activation │ │ Default temp │ Safety mode default temperature │ │ Default set point │ Safety mode default set-point │ └─────────────────────────────────┴──────────────────────────────────────────┘ SAFETY FEATURES: --------------- • Watchdog timer prevents endless heating if valve communication fails • Configurable default values ensure safe operation during failures • Timeout triggers automatic fallback to safe temperature settings </pre> --> <div style="font-family: monospace;"></div> <h2 style="border: 2px solid #333; padding: 10px; text-align: center; background-color: #f0f0f0;">SMART-BOILER NODE</h2> <h3>OVERVIEW:</h3> <p>This node is part of an intelligent heating control system consisting of three main components:</p> <ul> <li><strong>Smart-Scheduler:</strong> Manages multiple schedules and temperature set-points</li> <li><strong>Smart-Valve:</strong> Controls and groups individual valves by room/zone</li> <li><strong>Smart-Boiler:</strong> Central controller that determines optimal boiler temperature based on all connected valve states</li> </ul> <h3>PURPOSE:</h3> <p>Smart-Boiler aggregates data from multiple Smart-Valve nodes to intelligently control the central heating boiler, ensuring efficient heating by prioritizing the valve with the greatest heating demand.</p> <h3>INPUT MESSAGE FORMAT:</h3> <pre><code>msg.payload = { command: "set", // Required setpoint: [number], // Set-point (target temperature) in °C temperature: [number], // Current valve temperature in °C requestedBy: [string], // Valve description/ID (e.g., "Valve kitchen") groupId: [number] // Group ID } Example: msg.payload = { command: "set", setpoint: 23, temperature: 20, requestedBy: "Valve kitchen", groupId: 1 }</code></pre> <h3>VALVE PROCESSING:</h3> <ul> <li>Each input message creates or updates a valve entry in the internal stack</li> <li>Valves are evaluated on each update cycle</li> </ul> <h3>BUSINESS RULES:</h3> <h4>Valve Activation Condition:</h4> <p>A valve is considered <strong>ACTIVE</strong> when: <code>sp &gt; temp</code> (heating required)</p> <p>The boiler is controlled by the valve with the <strong>HIGHEST</strong> temperature gap:</p> <pre><code>gap = sp - temp</code></pre> <h4>Target Conditions on Input:</h4> <ul> <li><strong>Valid input requires:</strong> <code>msg.payload.command === "set"</code> AND <code>msg.topic</code> matches configured pattern</li> <li><strong>Message processing:</strong> Each input creates or updates a valve entry in the internal stack (liveStack)</li> <li><strong>Stack management:</strong> Valve entries are indexed by <code>requestedBy</code> (valve name/ID)</li> <li><strong>Cycle trigger:</strong> Every valid input triggers an evaluation cycle to determine the active valve</li> <li><strong>Timestamp tracking:</strong> Each valve entry records <code>lastupdate</code> for timeout monitoring</li> </ul> <h4>Example Scenario:</h4> <ul> <li>Valve A: sp=19°C, temp=21°C → <strong>INACTIVE</strong> (sp &lt; temp)</li> <li>Valve B: sp=23°C, temp=20°C → gap=3°C (<strong>ACTIVE</strong>)</li> <li>Valve C: sp=24°C, temp=19°C → gap=5°C (<strong>ACTIVE - CONTROLS BOILER</strong>)</li> </ul> <p><strong>Result:</strong> Valve C drives the boiler with sp=24°C, temp=19°C</p> <h3>FALLBACK RULES:</h3> <ul> <li><strong>No active valves:</strong> The highest set-point among inactive valves is used as the boiler reference</li> <li><strong>Input timeout:</strong> If no input received within max duration, a safety message is sent with default values to prevent continuous heating</li> </ul> <h3>OUTPUT MODES:</h3> <h4>1. MQTT Output (Recommended):</h4> <ul> <li>Set-point topic: Publishes boiler target temperature</li> <li>Temperature topic: Publishes current reference temperature</li> <li>Leading device topic: Publishes name of controlling valve</li> </ul> <h4>2. Standard Node Output:</h4> <pre><code>msg.payload = { sp: [number], temp: [number], name: [string], id: [string], groupId: [number], lastupdate: [ISO 8601 timestamp] }</code></pre> <h3>CONFIGURATION PARAMETERS:</h3> <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse; width: 100%;"> <thead> <tr style="background-color: #e0e0e0;"> <th>Parameter</th> <th>Description</th> </tr> </thead> <tbody> <tr><td>MQTT</td><td>MQTT broker connection configuration</td></tr> <tr><td>MQTT updates</td><td>Enable direct MQTT publishing</td></tr> <tr><td>Output updates</td><td>Enable standard node output</td></tr> <tr><td>Set point topic</td><td>MQTT topic for boiler set-point</td></tr> <tr><td>Temperature topic</td><td>MQTT topic for boiler current temp</td></tr> <tr><td>Leading Device topic</td><td>MQTT topic for active valve name</td></tr> <tr><td>Update cycle duration</td><td>Interval for valve stack evaluation</td></tr> <tr><td>Max duration since last input</td><td>Timeout before safety mode activation</td></tr> <tr><td>Default temp</td><td>Safety mode default temperature</td></tr> <tr><td>Default set point</td><td>Safety mode default set-point</td></tr> </tbody> </table> <h3>SAFETY FEATURES:</h3> <ul> <li>Watchdog timer prevents endless heating if valve communication fails</li> <li>Configurable default values ensure safe operation during failures</li> <li>Timeout triggers automatic fallback to safe temperature settings</li> <li><strong>State Persistence:</strong> The node automatically saves the complete valve stack and boiler state to <code>~/.node-red/.node-red-state/boiler-{node-id}.json</code>. This includes all connected valves, their temperatures, setpoints, and the active valve selection. State is restored on Node-RED restarts and flow redeployments, ensuring seamless operation continuity.</li> </ul> </div> </script>