smart-nodes
Version:
Controls light, shutters and more. Includes common used logic and statistic nodes to control your home.
465 lines (435 loc) • 20.5 kB
HTML
<script type="text/javascript">
(function ()
{
let treeList;
let candidateNodesCount = 0;
let flows = [];
let flowMap = {};
function onEditPrepare(node, targetTypes)
{
if (!node.links)
node.links = [];
const activeSubflow = RED.nodes.subflow(node.z);
treeList = $("<div>")
.css({ width: "100%", height: "100%" })
.appendTo(".node-input-link-row")
.treeList({ autoSelect: false })
.on("treelistitemmouseover", function (e, item)
{
if (item.node)
{
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}
})
.on("treelistitemmouseout", function (e, item)
{
if (item.node)
{
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
}
});
flows = [];
flowMap = {};
if (activeSubflow)
{
flowMap[activeSubflow.id] = {
id: activeSubflow.id,
class: "red-ui-palette-header",
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
expanded: true,
children: []
};
flows.push(flowMap[activeSubflow.id]);
}
else
{
RED.nodes.eachWorkspace(function (ws)
{
if (!ws.disabled)
{
flowMap[ws.id] = {
id: ws.id,
class: "red-ui-palette-header",
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
expanded: true,
children: []
};
flows.push(flowMap[ws.id]);
}
});
}
setTimeout(function ()
{
treeList.treeList("show", node.z);
}, 100);
}
function initTreeList(node, targetTypes)
{
candidateNodesCount = 0;
for (const key in flowMap)
{
flowMap[key].children = [];
}
let candidateNodes = [];
targetTypes.forEach(function (targetType)
{
candidateNodes = candidateNodes.concat(RED.nodes.filterNodes({ type: targetType }));
});
candidateNodes.forEach(function (n)
{
if (flowMap[n.z])
{
const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
if (isChecked)
{
flowMap[n.z].children.push({
id: n.id,
node: n,
label: n.name || n.id,
selected: false,
checkbox: false,
radio: false
});
candidateNodesCount++;
}
}
});
for (const key in flowMap)
{
flowMap[key].children.sort((a, b) => a.label.localeCompare(b.label));
}
const flowsFiltered = flows.filter(function (f) { return f.children.length > 0 });
treeList.treeList("empty");
treeList.treeList("data", flowsFiltered);
}
function resizeNodeList()
{
var rows = $("#dialog-form>div:not(.node-input-link-row)");
var height = $("#dialog-form").height();
for (var i = 0; i < rows.length; i++)
{
height -= $(rows[i]).outerHeight(true);
}
var editorRow = $("#dialog-form>div.node-input-link-row");
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
$(".node-input-link-row").css("height", Math.max(height, 200) + "px");
}
RED.nodes.registerType("smart_mixing-valve", {
category: "Smart Nodes",
paletteLabel: "Mixing Valve",
color: "#3FADB5",
defaults: {
name: { value: "" },
enabled: { value: true },
setpoint: { value: 45 },
time_total: { value: 60 },
time_sampling: { value: 60 },
off_mode: { value: "NOTHING" }, // NOTHING | OPEN | CLOSE
valve_mode: { value: "HEATING" }, // HEATING | COOLING
outputs: { value: 3 },
output_mode: { value: "OPEN_CLOSE" },
precision: { value: 1.0 },
max_change_percent: { value: 1 },
max_change_temp_difference: { value: 20 },
min_change_time: { value: 100 },
links: { value: [], type: "smart_central-control[]" },
alarm_action: { value: "NOTHING" }, // NOTHING | OPEN | CLOSE
critical_temp_max: { value: 100 },
crit_temp_change_percent: { value: 0 },
config_change_date: { value: "" },
},
inputs: 1,
outputs: 1,
outputLabels: function (index)
{
return this.output_mode == "OPEN_CLOSE" ? ["Open", "Close", "Status Position"][index] : "Position";
},
icon: "mixing-valve.png",
label: function ()
{
return this.name || "Mixing Valve";
},
oneditprepare: function ()
{
let node = this;
onEditPrepare(this, ["smart_central-control"]);
initTreeList(node, ["smart_central-control"]);
$("#node-input-output_mode")
.css("max-width", "70%")
.typedInput({
type: "num",
types: [{
value: "output_mode",
options: [
{ value: "OPEN_CLOSE", label: node._("mixing-valve.ui.open_close") },
{ value: "PERCENTAGE", label: node._("mixing-valve.ui.percentage") },
]
}]
});
$("#node-input-output_mode").on("change", function ()
{
if (this.value == "OPEN_CLOSE")
{
$(".only-open-close").show();
node.outputs = 3;
}
else
{
$(".only-open-close").hide();
node.outputs = 1;
}
});
$("#node-input-setpoint")
.css("max-width", "4rem")
.spinner({
min: -30,
max: 100,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-time_total")
.css("max-width", "4rem")
.spinner({
min: 0,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
// value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-time_sampling")
.css("max-width", "4rem")
.spinner({
min: 0,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
// value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-off_mode")
.css("max-width", "70%")
.typedInput({
types: [{
default: "NOTHING",
value: "off_mode",
options: [
{ value: "NOTHING", label: node._("mixing-valve.ui.nothing") },
{ value: "OPEN", label: node._("mixing-valve.ui.open") },
{ value: "CLOSE", label: node._("mixing-valve.ui.close") }
],
}],
});
$("#node-input-valve_mode")
.css("max-width", "70%")
.typedInput({
types: [{
default: "HEATING",
value: "valve_mode",
options: [
{ value: "HEATING", label: node._("mixing-valve.ui.heating") },
{ value: "COOLING", label: node._("mixing-valve.ui.cooling") }
],
}],
});
$("#node-input-alarm_action")
.css("max-width", "70%")
.typedInput({
types: [{
value: "alarm_action",
default: "NOTHING",
options: [
{ value: "NOTHING", label: node._("mixing-valve.ui.no_action") },
{ value: "OPEN", label: node._("mixing-valve.ui.open") },
{ value: "CLOSE", label: node._("mixing-valve.ui.close") }
],
}],
});
$("#node-input-precision")
.css("max-width", "4rem")
.spinner({
min: 0.1,
max: 1.0,
step: 0.1,
change: function (event, ui)
{
var value = parseFloat(this.value);
value = isNaN(value) ? 0.0 : value;
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-max_change_percent")
.css("max-width", "4rem")
.spinner({
min: 1,
max: 100,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-max_change_temp_difference")
.css("max-width", "4rem")
.spinner({
min: 1,
max: 100,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
$("#node-input-min_change_time")
.css("max-width", "4rem")
.spinner({
min: 0,
max: 10000,
step: 100,
change: function (event, ui)
{
var value = parseInt(this.value);
value = isNaN(value) ? 0 : value;
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
// New fields for critical temperature settings
$("#node-input-critical_temp_max")
.css("max-width", "4rem")
.spinner({
min: 0,
max: 100,
step: 1,
change: function (event, ui)
{
var value = parseFloat(this.value);
value = isNaN(value) ? 0.0 : value;
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
if ($("#node-input-critical_temp_max").val() === "")
$("#node-input-critical_temp_max").val("100");
$("#node-input-crit_temp_change_percent")
.css("max-width", "4rem")
.spinner({
min: 0,
max: 100,
step: 1,
change: function (event, ui)
{
var value = parseFloat(this.value);
value = isNaN(value) ? 0.0 : value;
value = Math.max(value, parseFloat($(this).attr("aria-valuemin")));
value = Math.min(value, parseFloat($(this).attr("aria-valuemax")));
if (value !== this.value) $(this).spinner("value", value);
},
});
if ($("#node-input-crit_temp_change_percent").val() === "")
$("#node-input-crit_temp_change_percent").val("0");
},
onadd: function ()
{
this.links = [];
},
oneditsave: function ()
{
let node = this;
node.config_change_date = (new Date()).toISOString();
},
oneditresize: resizeNodeList
});
})();
</script>
<script type="text/html" data-template-name="smart_mixing-valve">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="mixing-valve.ui.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]mixing-valve.ui.name" />
</div>
<div class="form-row">
<input type="checkbox" id="node-input-enabled" style="width: 20px;" />
<span for="node-input-enabled" style="width: 200px;"> <span data-i18n="mixing-valve.ui.enabled"></span></span>
</div>
<div class="form-row">
<label for="node-input-output_mode"><i class="fa fa-hashtag"></i> <span data-i18n="mixing-valve.ui.output_mode"></span></label>
<input id="node-input-output_mode" />
</div>
<div class="form-row">
<label for="node-input-setpoint"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.setpoint"></span></label>
<input id="node-input-setpoint" value="0" /> °C
</div>
<div class="form-row only-open-close">
<label for="node-input-time_total"><i class="fa fa-clock-o"></i> <span data-i18n="mixing-valve.ui.runtime"></span></label>
<input id="node-input-time_total" value="0" /> s
</div>
<div class="form-row">
<label for="node-input-time_sampling"><i class="fa fa-clock-o"></i> <span data-i18n="mixing-valve.ui.sampling"></span></label>
<input id="node-input-time_sampling" value="0" /> s
</div>
<div class="form-row">
<label for="node-input-off_mode"><i class="fa fa-power-off"></i> <span data-i18n="mixing-valve.ui.power_off"></span></label>
<input id="node-input-off_mode" />
</div>
<div class="form-row">
<label for="node-input-valve_mode"><i class="fa fa-fire"></i> <span data-i18n="mixing-valve.ui.mode"></span></label>
<input id="node-input-valve_mode" />
</div>
<div class="form-row">
<label for="node-input-alarm_action"><i class="fa fa-exclamation-triangle"></i> <span data-i18n="mixing-valve.ui.alarm_on"></span></label>
<input id="node-input-alarm_action"/>
</div>
<div class="form-row">
<label for="node-input-precision" style="width: 250px;"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.precision"></span></label>
<input id="node-input-precision" value="1.0" /> °C
</div>
<div class="form-row">
<label for="node-input-max_change_percent" style="width: 250px;"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.max_change_percent"></span></label>
<input id="node-input-max_change_percent" value="2" /> %
</div>
<div class="form-row">
<label for="node-input-max_change_temp_difference" style="width: 250px;"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.max_change_temp_difference"></span></label>
<input id="node-input-max_change_temp_difference" value="20" /> °C
</div>
<div class="form-row only-open-close">
<label for="node-input-min_change_time" style="width: 250px;"><i class="fa fa-sliders"></i> <span data-i18n="mixing-valve.ui.min_change_time"></span></label>
<input id="node-input-min_change_time" value="0" /> ms
</div>
<div class="form-row">
<label for="node-input-critical_temp_max" style="width: 250px;"><i class="fa fa-thermometer-half"></i> <span data-i18n="mixing-valve.ui.critical_temp_max"></span></label>
<input id="node-input-critical_temp_max" value="100"/> °C
</div>
<div class="form-row">
<label for="node-input-crit_temp_change_percent" style="width: 250px;"><i class="fa fa-percent"></i> <span data-i18n="mixing-valve.ui.crit_temp_change_percent"></span></label>
<input id="node-input-crit_temp_change_percent" value="0"/> %
</div>
<hr/>
<span><i class="fa fa-link"></i> <span data-i18n="mixing-valve.ui.controlled_by_central"></span></span>
<div class="form-row node-input-link-row node-input-link-rows"></div>
</script>