node-red-contrib-myhome-bticino-v2
Version:
Control Bticino / Legrand MyHome components from Node-RED
331 lines (316 loc) • 18.9 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('myhome-scenario',{
category: 'Bticino MyHome',
color: '#a6bbcf',
defaults: {
name: { value: '', required: true },
buslevel: { value: 'private_riser', required: false},
scenariotype: { value: 'CEN+', required: true },
scenarioid: { value: '', required: true, validate: RED.validators.number() },
topic: { value: '', required: true},
gateway: { type: 'myhome-gateway', required: true },
skipevents: { value: false, required: false },
rules: {value:[{
buttonFrom: 0,
buttonTo: 31,
onStartPress:false,
onEndShortPress:true,
onEndLongPress:true,
onDuringLongPress:false,
minLongPressDuration:1000
}]},
outputs: {value:1}
},
inputs: 1,
inputLabels: function () {
let inputLabel = this._("common.input-label-simple") + ": 'xx(:yyyy)':";
inputLabel += "\n " + this._('mh-scenario.config.inputlabel-simple-xx');
inputLabel += "\n " + this._('mh-scenario.config.inputlabel-simple-yyyy');
return inputLabel;
},
outputs: 1,
outputLabels: function(index) {
let rule = this.rules[index-1];
let label = '';
if (rule) {
if (rule.buttonFrom == rule.buttonTo) {
label = this._('mh-scenario.config.outputlabel-btn-from' , {'from' : rule.buttonFrom});
} else {
label = this._('mh-scenario.config.outputlabel-btn-from-to' , {'from' : rule.buttonFrom, 'to' : rule.buttonTo});
}
let onEvents = [];
if (rule.onStartPress) {
if (rule.scenariotype === 'CEN') {
onEvents.push (' - ' + this._('mh-scenario.config.add-outputs-startpress-any'));
} else {
onEvents.push (' - ' + this._('mh-scenario.config.add-outputs-startpress-long'));
}
}
if (rule.onEndShortPress) {
onEvents.push (' - ' + this._('mh-scenario.config.outputlabel-btn-shortpress'));
}
if (rule.onEndLongPress) {
onEvents.push (' - ' + this._('mh-scenario.config.outputlabel-btn-longpress' , {'duration' : (rule.minLongPressDuration/1000).toFixed(1).toString()}) );
}
if (rule.onDuringLongPress) {
onEvents.push (' - ' + this._('mh-scenario.config.outputlabel-btn-longpresswhile') );
}
if (onEvents.length) {
label += ' ' + this._('mh-scenario.config.outputlabel-btn-on') + '\n' + onEvents.join ('\n');
}
} else {
label = this._('mh-scenario.config.outputlabel-main');
}
return label;
},
icon: 'font-awesome/fa-magic',
paletteLabel: 'MH Scenario',
label: function() {
// Force default values for newly added fields (backward compatibility with prior releases : when never edited before, update/add new fields)
// v2.2.1 Added BUS management
if (this.buslevel === undefined) {this.buslevel = 'private_riser'}
// Return the label itself
return this.name || 'MH Scenario';
},
oneditprepare: function() {
let scope = this;
$('#node-input-topic').on('change', function() {
let topicName = $(this).val();
let topicInfo = scope._('common.topic-info' , {'topicName': (topicName) ? topicName : 'topic'});
$('#node-info-topic').prop('title' , topicInfo);
});
// When scenario type is changed from CEN <> CEN+, update labels for 'Start press'
$('#node-input-scenariotype').on('change', function() {
let onStartLabel = '';
if ($('#node-input-scenariotype').val() === "CEN") {
onStartLabel = scope._('mh-scenario.config.add-outputs-startpress-any');
} else {
onStartLabel = scope._('mh-scenario.config.add-outputs-startpress-long');
}
let rules = $('#node-input-rule-container').editableList('items');
rules.each (function (i) {
let rule = $(this);
rule.find("label[for^='node-input-onStartPress_']").text(onStartLabel);
});
});
// Styles used in button range container
$("style").append("#node-input-rule-container .buttonrule-fromtolabel {display:inline-block; width:100px; margin-left: 20px;}");
$("style").append("#node-input-rule-container .buttonrule-checkbox {display:inline-block; width:22px; margin-left:0px; vertical-align:baseline;}");
$("style").append("#node-input-rule-container .buttonrule-select {width:60px; height: 23px; padding: 2px 2px; margin-left: 25px; margin-right: 35px; text-align: center;}");
$("style").append("#node-input-rule-container .buttonrule-checkbox-label {width: auto; margin-bottom: 2px;}");
// Output order can be updated to keep linked junctions correctly connected. To do so, a reserved item must contains
// outputs re-directions (if outputs are a,b,c,d, after configuring, new outputs are z,a,c, then 'node-input-outputs'
// must now be {"0":"1", "1":"-1", "2":"2", "3":"-1", "someRandomNo":"0"}.
var outputOrderIndexOnOpen = {};
function rebuildOutputIndexes (rulesUI) {
// Build new outputs : assume all which were defined before are 'deleted' (if not, they will be re-assigned right after)
let orderedRulesIndex = {'0':'0'}; // first output is always static, afterwards always index using i+1 therefore
Object.entries(outputOrderIndexOnOpen).forEach ((ruleIndex) => {orderedRulesIndex[ruleIndex[1]+1] = -1;});
// Parse each rule : if it existed before, build index transfer (Initial -> To), if new just indicate new index 'usage'
rulesUI.each(function(i) {
let ruleUI = $(this);
let curRowID = ruleUI.find("[id^=node-input-buttonFrom_]").attr('id').split("_").pop(); // row id is appended to each item, so gather it after the '_'
if (outputOrderIndexOnOpen.hasOwnProperty(curRowID)) {
// This rule existed on open, build index .initial -> .toCurrent
orderedRulesIndex[outputOrderIndexOnOpen[curRowID]+1] = i+1;
} else {
// This is a new rule : just add its final index using its rowID
orderedRulesIndex[curRowID] = i+1;
}
// Re-number them in UI (output number)
ruleUI.find(".node-input-rule-index").html(i+2);
});
$("#node-input-outputs").val(JSON.stringify(orderedRulesIndex));
}
$("#node-input-rule-container").css('min-height','350px').css('min-width','450px').editableList({
addButton: scope._('mh-scenario.config.add-outputs-btn-addnew'),
header: $("<div style='background:#EFEFEF; display:grid; grid-template-columns:135px 1fr 90px 30px'>"
+"<div style='margin-left: 30px; display: inline-grid; margin-top: 5px; font-weight:bold'>" + scope._('mh-scenario.config.add-outputs-range') + "</div>"
+"<div style='display: inline-grid; margin-top: 5px; font-weight:bold'>" + scope._('mh-scenario.config.add-outputs-newflow') + "</div>"
+"<div style='display: inline-grid; justify-self: end; margin-top: 5px; font-weight:bold'>" + scope._('mh-scenario.config.add-outputs-nr') + "</div>"
+"</div>"),
addItem: function (container, i, rules) {
let uniqueRowID = Math.floor((0x99999-0x10000)*Math.random()).toString()
if (!rules.hasOwnProperty('rule')) {
rules.rule = {};
} else {
// Rule existed before (=on load, keep its initial index)
outputOrderIndexOnOpen[uniqueRowID] = i;
}
var rule = rules.rule;
// When object is new, apply default values
if (!rule.hasOwnProperty('buttonFrom')) {
rule.buttonFrom = '0';
}
if (!rule.hasOwnProperty('buttonTo')) {
rule.buttonTo = '31';
}
if (!rule.hasOwnProperty('onEndShortPress')) {
rule.onEndShortPress = true;
}
if (!rule.hasOwnProperty('onEndLongPress')) {
rule.onEndLongPress = true;
}
if (!rule.hasOwnProperty('minLongPressDuration')) {
rule.minLongPressDuration = 1000;
}
// ROW 1 : 'From label' & checkbox 'Start press'
let row1 = $('<div/>').appendTo(container);
row1.append('<span class="buttonrule-fromtolabel">' + scope._('mh-scenario.config.add-outputs-btn-from') + '</span>');
let field_onStartPress = $('<input/>',{id:"node-input-onStartPress_" + uniqueRowID,type:"checkbox", class:"buttonrule-checkbox"}).appendTo(row1);
row1.append('<label class="buttonrule-checkbox-label" for="node-input-onStartPress_' + uniqueRowID + '">' + scope._('mh-scenario.config.add-outputs-startpress') + '</label>');
// ROW1 : output info (rightest part)
let finalspan = $('<span/>',{style:"float: right;margin-top: 6px;"}).appendTo(row1);
finalspan.append('<i class="fa fa-sign-out"></i><span class="node-input-rule-index">'+(i+2)+'</span>');
// ROW 2 : 'From list' & checkbox 'End short press'
let row2 = $('<div/>').appendTo(container);
let field_buttonFrom = $('<select/>',{id:"node-input-buttonFrom_" + uniqueRowID, class:"buttonrule-select"}).appendTo(row2);
let field_onEndShortPress = $('<input/>',{id:"node-input-onEndShortPress_" + uniqueRowID, type:"checkbox", class:"buttonrule-checkbox"}).appendTo(row2);
row2.append('<label class="buttonrule-checkbox-label" for="node-input-onEndShortPress_' + uniqueRowID + '">' + scope._('mh-scenario.config.add-outputs-shortpress') + '</label>');
// ROW 3 : 'To label' & checkbox 'End long press after [xx] seconds'
let row3 = $('<div/>').appendTo(container);
row3.append('<span class="buttonrule-fromtolabel">' + scope._('mh-scenario.config.add-outputs-btn-to') + '</span>');
let field_onEndLongPress = $('<input/>',{id:"node-input-onEndLongPress_" + uniqueRowID, type:"checkbox", class:"buttonrule-checkbox"}).appendTo(row3);
row3.append('<label class="buttonrule-checkbox-label" for="node-input-onEndLongPress_' + uniqueRowID + '" style="width:unset";>' + scope._('mh-scenario.config.add-outputs-longpress') + ' </label>');
let field_minLongPressDuration = $('<input/>',{id:"node-input-minLongPressDuration_" + uniqueRowID, type:"text",style:"height:23px; width:55px; padding: 2px 2px;"}).appendTo(row3);
row3.append('<label class="buttonrule-checkbox-label" for="node-input-minLongPressDuration_' + uniqueRowID + '">  ms</label>');
// ROW 4 : 'To list' & checkbox 'During long press'
let row4 = $('<div/>').appendTo(container);
let field_buttonTo = $('<select/>',{id:"node-input-buttonTo_" + uniqueRowID, class:"buttonrule-select"}).appendTo(row4);
let field_onDuringLongPress = $('<input/>',{id:"node-input-onDuringLongPress_" + uniqueRowID, type:"checkbox", class:"buttonrule-checkbox"}).appendTo(row4);
row4.append('<label class="buttonrule-checkbox-label" for="node-input-onDuringLongPress_' + uniqueRowID + '">' + scope._('mh-scenario.config.add-outputs-longpresswhile') + '</label>');
// Build all 0-10 choices to both fields inputs
for (let i = 0; i <= 31; i++) {
field_buttonFrom.append($("<option></option>").val(i.toString()).text(i.toString()));
field_buttonTo.append($("<option></option>").val(i.toString()).text(i.toString()));
}
// Events : When From & To selector is changed, ensure range remains OK
field_buttonFrom.on ("change", function() {
if (parseInt(field_buttonFrom.val()) > parseInt(field_buttonTo.val())) {
field_buttonTo.val(field_buttonFrom.val());
}
});
field_buttonTo.on ("change", function() {
if (parseInt(field_buttonFrom.val()) > parseInt(field_buttonTo.val())) {
field_buttonFrom.val(field_buttonTo.val());
}
});
// Copy memory info back on fields
field_buttonFrom.val (rule.buttonFrom.toString());
field_buttonTo.val (rule.buttonTo.toString());
field_minLongPressDuration.val (rule.minLongPressDuration.toString());
// Checkboxes
field_onStartPress.prop ('checked', rule.onStartPress);
field_onEndShortPress.prop ('checked', rule.onEndShortPress);
field_onEndLongPress.prop ('checked', rule.onEndLongPress);
field_onDuringLongPress.prop ('checked', rule.onDuringLongPress);
// Update outputs (number of outputs and how the changed since onLoad)
rebuildOutputIndexes ($("#node-input-rule-container").editableList('items'));
},
removeItem: function (rule) {
// Update outputs (number of outputs and how the changed since onLoad)
rebuildOutputIndexes ($("#node-input-rule-container").editableList('items'));
},
sortItems: function (rules) {
// Update outputs (number of outputs and how the changed since onLoad)
rebuildOutputIndexes (rules);
},
sortable: true,
removable: true
});
// Add all previously saved rules back to UI List
for (let i = 0 ; i < this.rules.length ; i++) {
$("#node-input-rule-container").editableList('addItem',{rule:this.rules[i], i:i});
}
},
oneditresize: function(size) {
let rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
let height = size.height;
for (let i = 0 ; i<rows.length ; i++) {
height -= $(rows[i]).outerHeight(true);
}
let editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 30;
$("#node-input-rule-container").editableList('height',height);
},
oneditsave: function() {
let rules = $("#node-input-rule-container").editableList('items');
let node = this;
node.rules = [];
rules.each (function (i) {
let rule = $(this);
let savedRule = {};
savedRule.buttonFrom = parseInt(rule.find("[id^=node-input-buttonFrom_]").val());
savedRule.buttonTo = parseInt(rule.find("[id^=node-input-buttonTo_]").val());
savedRule.onStartPress = rule.find("[id^=node-input-onStartPress_]").prop("checked");
savedRule.onEndShortPress = rule.find("[id^=node-input-onEndShortPress_]").prop("checked");
savedRule.onEndLongPress = rule.find("[id^=node-input-onEndLongPress_]").prop("checked");
savedRule.onDuringLongPress = rule.find("[id^=node-input-onDuringLongPress_]").prop("checked");
savedRule.minLongPressDuration = parseInt(rule.find("[id^=node-input-minLongPressDuration_]").val());
node.rules.push (savedRule);
});
},
});
</script>
<script type="text/x-red" data-template-name="myhome-scenario">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]mh-scenario.config.name-placeholder"/>
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-terminal"></i> <span data-i18n="common.topic"></span></label>
<input type="text" id="node-input-topic" data-i18n="[placeholder]mh-scenario.config.topic-placeholder"/>
<i id="node-info-topic" class="fa fa-info-circle" title=""></i>
</div>
<div class="form-row">
<label for="node-input-gateway"><i class="fa fa-server"></i> <span data-i18n="common.gateway"></span></label>
<input type="text" id="node-input-gateway"/>
</div>
<div style="border-top-style: solid ;border-width: 1px; border-color: #CCCCCC; height: 10px;"></div>
<div class="form-row">
<label for="node-input-scenarioid"><i class="fa fa-magic"></i> <span data-i18n="mh-scenario.config.scenarioid"></span></label>
<div style="display: inline-block; position: relative; width: 70%; height: 20px;">
<select id="node-input-buslevel" style="width:110px">
<option value="private_riser" data-i18n="common.riser-private"></option>
<option value="01" data-i18n="common.riser-localbus-01">[Local bus 1]</option>
<option value="02" data-i18n="common.riser-localbus-02">[Local bus 2]</option>
<option value="03" data-i18n="common.riser-localbus-03">[Local bus 3]</option>
<option value="04" data-i18n="common.riser-localbus-04">[Local bus 4]</option>
<option value="05" data-i18n="common.riser-localbus-05">[Local bus 5]</option>
<option value="06" data-i18n="common.riser-localbus-06">[Local bus 6]</option>
<option value="07" data-i18n="common.riser-localbus-07">[Local bus 7]</option>
<option value="08" data-i18n="common.riser-localbus-08">[Local bus 8]</option>
<option value="09" data-i18n="common.riser-localbus-09">[Local bus 9]</option>
<option value="10" data-i18n="common.riser-localbus-10">[Local bus 10]</option>
<option value="11" data-i18n="common.riser-localbus-11">[Local bus 11]</option>
<option value="12" data-i18n="common.riser-localbus-12">[Local bus 12]</option>
<option value="13" data-i18n="common.riser-localbus-13">[Local bus 13]</option>
<option value="14" data-i18n="common.riser-localbus-14">[Local bus 14]</option>
<option value="15" data-i18n="common.riser-localbus-15">[Local bus 15]</option>
</select>
<select id="node-input-scenariotype" style="width:65px">
<option value="CEN">CEN</option>
<option value="CEN+">CEN+</option>
</select>
<div style="position: absolute; left: 183px; right: 0px; top:0px;">
<input type="text" id="node-input-scenarioid" style="width:100%" data-i18n="[placeholder]mh-scenario.config.scenarioid-placeholder"/>
</div>
</div>
</div>
</div>
<div class="form-row">
<label for="node-input-skipevents"><i class="fa fa-ban"></i> <span data-i18n="common.option-skipevents"></span></label>
<label for="node-input-skipevents" style="width:70%">
<input type="checkbox" id="node-input-skipevents" style="display:inline-block; margin-left:0px; width:22px; vertical-align:baseline;"/><span data-i18n="common.option-skipevents-checkbox"></span>
<i class="fa fa-info-circle" data-i18n="[title]common.option-skipevents-info"></i>
</label>
</div>
<div class="form-row">
<i class="fa fa-sign-out"></i> <b><span data-i18n="mh-scenario.config.additional-output-title"></b>
</div>
<div class="form-row node-input-rule-container-row" style="width:100%">
<input type="hidden" id="node-input-outputs"/>
<ol id="node-input-rule-container"></ol>
</div>
</script>