UNPKG

node-red-contrib-vib-smart-valve

Version:
333 lines (278 loc) 16.4 kB
<script type="text/javascript"> // Terminal command node-red -v -D logging.console.level=trace RED.nodes.registerType('smart-valve',{ category: 'vib-node', color: '#bdeeff', defaults: { name: {value:""}, topic:{value:""}, mqttSettings: {value: "", type: "smart-valve-settings"}, tempEntity: {value:"",required: true}, tempEntityTopic: {value:"",required: true}, groupId:{value:"1",required: true,validate:function(v) { if (isNaN(v)){ return false; } if (v<0 || v> 100){ return false; } return true; }}, climates:{value:[{entity:"",valveExtTempTopic:""}],validate: function(rules, opt) { return true; }}, cycleDuration: {value:"5"}, offSp: {value:"5",required:true,validate:function(v){ if (isNaN(v)){ return false; } if (v<0 || v> 30){ return false; } return true; }}, spUpdateMode:{value:"spUpdateMode.statechange.startup"}, adjustValveTempMode:{value:"adjustValveTempMode.noAdjust"}, debugInfo:{value:false}, allowOverride:{value:false}, }, inputs:1, outputs:2, outputLabels: ["HomeAssistant Service","Boiler/Scheduler"], icon: "font-awesome/fa-magnet", button: { onclick: function() { let node=this; $.ajax({ url: "smartvalve/" + node.id, type: "POST", data: JSON.stringify({payload:{command:"trigger"}}), contentType: "application/json; charset=utf-8", success: function (resp) { RED.notify(node._("smart-valve trigger sucess", { 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-valve"; }, oneditprepare: function() { var node = this; if (!this.climates) { var climate = { entity:"", entityTopic:"", valveExtTempTopic:"", valveSpTopic:"" }; this.climates = [climate]; } $('#node-input-climate-container').css('min-height','150px').css('min-width','400px').editableList({ removable: true, sortable: false, addItem: function(container,i,opt) { var climate = opt; if ( typeof node.climates[i] === 'undefined'){ node.climates[i]={}; } node.climates[i].ruleIdx=i; if (!climate.hasOwnProperty('entity')) { climate = {entity:"",valveExtTempTopic:"",entityTopic:"",valveSpTopic:""}; } $(container).data('data',i); let fragment = document.createDocumentFragment(); var row1 = $('<div/>',{style:"display:flex; align-items: baseline"}).appendTo(fragment); var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment); var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment); var row4 = $('<div/>',{style:"margin-top:8px;"}).appendTo(fragment); $('<label style="width:20px !important; text-align: left;"><span><i class="fa fa-share-alt"/><b>'+(i+1)+'</b></span></label>').appendTo(row1); $('<label style="text-align: right; margin-right:5px;"><span>Climate:</span></label>').appendTo(row1); $('<input type="text" id="node-input-climateEntity_'+i+'" idx="'+i+'" class="node-input-climateEntity" placeholder="Climate entity" >').appendTo(row1); var row2_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row2); $('<label style="width:20px !important; text-align: left;"><span></span></label>').appendTo(row2_1); $('<label style="text-align: right; margin-right:5px;"><span>Ext temp topic:</span></label>').appendTo(row2_1); $('<input type="text" id="node-input-valveExtTempTopic_'+i+'" idx="'+i+'" class="node-input-valveExtTempTopic" placeholder="Valve Ext temp topic" >').appendTo(row2_1); var row3_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row3); $('<label style="width:20px !important; text-align: left;"><span><i class="fa fa-share-alt"/><b>'+(i+1)+'</b></span></label>').appendTo(row3_1); $('<label style="text-align: right; margin-right:5px;"><span>Entity Topic:</span></label>').appendTo(row3_1); $('<input type="text" id="node-input-climateEntityTopic_'+i+'" idx="'+i+'" class="node-input-climateEntityTopic" placeholder="Entity topic" >').appendTo(row3_1); var row4_1 = $('<div/>', {style:"display:flex;align-items: baseline"}).appendTo(row4); $('<label style="width:20px !important; text-align: left;"><span><i class="fa fa-share-alt"/><b>'+(i+1)+'</b></span></label>').appendTo(row4_1); $('<label style="text-align: right; margin-right:5px;"><span>SetPoint Topic:</span></label>').appendTo(row4_1); $('<input type="text" id="node-input-valveSpTopic_'+i+'" idx="'+i+'" class="node-input-valveSpTopic" placeholder="Sp topic" >').appendTo(row4_1); container[0].appendChild(fragment); $('#node-input-climateEntity_'+i).val(climate.entity); $('#node-input-climateEntityTopic_'+i).val(climate.entityTopic); $('#node-input-valveExtTempTopic_'+i).val(climate.valveExtTempTopic); $('#node-input-valveSpTopic_'+i).val(climate.valveSpTopic); $('#node-input-climateEntity_'+i).keyup(function (event) { ctrl_i=$(event.target).attr('idx'); node.climates[ctrl_i].entity=$('#node-input-climateEntity_'+ctrl_i).val(); }); $('#node-input-valveSpTopic_'+i).keyup(function (event) { ctrl_i=$(event.target).attr('idx'); node.climates[ctrl_i].valveSpTopic=$('#node-input-valveSpTopic_'+ctrl_i).val(); }); $('#node-input-climateEntityTopic_'+i).keyup(function (event) { ctrl_i=$(event.target).attr('idx'); node.climates[ctrl_i].entityTopic=$('#node-input-climateEntityTopic_'+ctrl_i).val(); }); $('#node-input-valveExtTempTopic_'+i).keyup(function (event) { ctrl_i=$(event.target).attr('idx'); node.climates[ctrl_i].valveExtTempTopic=$('#node-input-valveExtTempTopic_'+ctrl_i).val(); }); }, removeItem:function(data) { }, resizeItem: function(row,index) { var originalData = $(row).data('data'); console.log("Resize the row for item:", originalData) }, resize: function() { } }); for (var i=0; i<this.climates.length; i++) { var climate = this.climates[i]; $("#node-input-climate-container").editableList('addItem',climate); } }, oneditsave: function() { var node = this; var climates = $("#node-input-climate-container").editableList('items'); node.climates= []; climates.each(function(i){ var climate = $(this); var r = { entity:climate.find(".node-input-climateEntity").val(), valveExtTempTopic:climate.find(".node-input-valveExtTempTopic").val(), valveSpTopic:climate.find(".node-input-valveSpTopic").val(), entityTopic:climate.find(".node-input-climateEntityTopic").val(), } node.climates.push(r); }); console.log(node.climates); }, oneditresize: function() { } }); </script> <script type="text/html" data-template-name="smart-valve"> <style> ol#node-input-rule-container .red-ui-typedInput-container { flex:1; } </style> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <div class="form-row"> <label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <input type="text" id="node-input-topic" placeholder="Topic"></input> </div> <div class="form-row"> <label for="node-input-mqttSettings"><i class="fa fa-globe"></i> Mqtt</label> <input type="text" id="node-input-mqttSettings"></input> </div> <div class="form-row"> <label for="node-input-topic"><i class="fa fa-tasks"></i> Group id </label> <input type="text" id="node-input-groupId" placeholder="id"></input> </div> <div class="form-row"> <label for="node-input-tempEntity"><i class="fa fa-tasks"></i> Temperature</label> <input type="text" id="node-input-tempEntity" placeholder="Temperature entity"></input> </div> <div class="form-row"> <label for="node-input-tempEntityTopic"><i class="fa fa-tasks"></i> Temperature topic</label> <input type="text" id="node-input-tempEntityTopic" placeholder="Temperature topic"></input> </div> <div class="form-row"> <label for="node-input-offSp"><i class="fa fa-tasks"></i> Off setpoint</label> <input type="text" id="node-input-offSp" style="width:60px !important; text-align: right;" placeholder="temperature"></input> <label for="node-input-offSp">°C</label> </div> <div class="form-row node-input-climate-container-row"> <ol id="node-input-climate-container"></ol> </div> <div class="form-row"> <label style="width:120px !important;" for="node-input-spUpdateMode"><i class="fa fa-play"></i> Update mode </label> <select id="node-input-spUpdateMode"> <option value="spUpdateMode.statechange">when state changes</option> <option value="spUpdateMode.statechange.startup">when state changes + startup</option> <option value="spUpdateMode.cycle">every cycle</option> </select> </div> <div class="form-row"> <label style="width:120px !important;" for="node-input-cycleDuration"><i class="fa fa-tasks"></i> Update cycle</label> <input type="text" id="node-input-cycleDuration" style="width:60px !important; text-align: right;" placeholder="duration"></input> </div> <div class="form-row"> <label for="node-input-allowOverride"><i class="fa fa-chain-broken"></i> Allow manual</label> <span><input type="checkbox" title="allow override mode" id="node-input-allowOverride"></input></span> <label for="node-input-allowOverride2">update</label> </div> <div class="form-row"> <label style="width:120px !important;" for="node-input-adjustValveTempMode"><i class="fa fa-play"></i> Recalibration</label> <select id="node-input-adjustValveTempMode"> <option value="adjustValveTempMode.noAdjust">No</option> <option value="adjustValveTempMode.adjust.startup">Yes</option> </select> </div> <div class="form-row"> <label style="width:120px !important;" for="node-input-debugInfo"><i class="fa fa-tasks"></i> Debug</label> <input type="checkbox" title="send debug information to the console" id="node-input-debugInfo" ></input> <label style="width:120px !important;" for="node-input-debugInfo">info</label> </div> </script> <script type="text/markdown" data-help-name="smart-valve"> # Smart-valve is part of a suite of node - Smart-Scheduler: multi-zonning SmartScheduler, - Smart-Valve: Valve grouping, auto-calibration, manual override, - Smart-Boiler: Boiler OpenTherm, multi valve management. ## Smart-valve This node enables to manage multiple valve (climate) in a same room like one. It support the following features : - External temperature sensor, - Multiple valves updates, - TRV temperture Recalibration based on the external temperature sensor, - Manual update directly on the valve to trigger override message to the scheduler and update the other valve ### Inputs : payload (string):[1|on|trigger] : sp (integer): [0-35] ## Outputs 1. Update home assistant via call service 2. Update of SP to the boiler (smart-Boiler) or override message to the smart-scheduler ### Settings - Name: [string], name of the node and also the name of the group sent to the smart-boiler node - Topic: [string], not used, - Group Id: [integer], used by the smart-boiler node to identify this group of valves, need to be unique - Temperature: [string], is the name of the external temperature sensor entity in home assistant ex: sensor.temp9 - Update mode: [state changed|state changed+startup|every cycle], define how frequently updates are sent to the smart-boiler node - Update cycle: [integer], duration in minute between two cycle. default is 5 - Allow manual updates: [true|false], enable direct set point (target temperature) change on the valve or home assistant. If true when a valve set point is changed all the other valves are updated and a override message is sent to the smart-scheduler node. - Recalibration: [No|Yes|Yes+threshold], enable to adjust the valve (TRV) current temperature based on the external temperature sensor, - Delta threshold: [integer] [0-9], threshold delta between external temperature sensor and the TRV current temperature to trigger recalibration, - Debug: [true|false], send debug info to the node-red console - Climate: each valve entry has 2 field: - climate: [string], home assitant climate entity of the valve ex: climate.kitchen - calibration: [string], home assistant calibration entity of the valve ex: number.kitchen_calibration ### Execution rules - Step 0: update current group setpoint with the sp of the valve, - Cycle : Step 1: identify if manual updates on valve, then update all valves, Step 2: check and execute recalibration Step 3: based on the execution mode, output to the smart-boiler node - On input: Update the requested set-point on each valves