node-red-smithtek-hysteresis
Version:
Adds hysteresis function to node-red
398 lines (384 loc) • 13.7 kB
JavaScript
module.exports = function (RED) {
"use strict";
function HysteresisNode(config) {
RED.nodes.createNode(this, config);
this.ThresholdType = config.ThresholdType || "fixed";
this.ThresholdRising = config.ThresholdRising;
this.ThresholdFalling = config.ThresholdFalling;
this.TopicThreshold = config.TopicThreshold;
this.TopicCurrent = config.TopicCurrent;
this.ThresholdDeltaRising = config.ThresholdDeltaRising;
this.ThresholdDeltaFalling = config.ThresholdDeltaFalling;
this.InitialMessage = config.InitialMessage;
this.OutRisingType = config.OutRisingType;
this.OutRisingValue = config.OutRisingValue;
this.OutFallingType = config.OutFallingType;
this.OutFallingValue = config.OutFallingValue;
this.OutTopicType = config.OutTopicType;
this.OutTopicValue = config.OutTopicValue || "";
var nodeContext = this.context();
var statusTimeout = null;
if (this.OutRisingType !== "pay") {
if (this.OutRisingType === "num" && !Number.isNaN(this.OutRisingValue)) {
this.OutRisingValue = Number.parseFloat(this.OutRisingValue);
} else if (
this.OutRisingType === "bool" &&
(this.OutRisingValue === "true" || this.OutRisingValue === "false")
) {
this.OutRisingValue === "true" ?
(this.OutRisingValue = true) :
(this.OutRisingValue = false);
} else if (this.OutRisingValue === "null") {
this.OutRisingType = "null";
this.OutRisingValue = null;
} else {
this.OutRisingValue = String(this.OutRisingValue);
}
}
if (this.OutFallingType !== "pay") {
if (
this.OutFallingType === "num" &&
!Number.isNaN(this.OutFallingValue)
) {
this.OutFallingValue = Number.parseFloat(this.OutFallingValue);
} else if (
this.OutFallingType === "bool" &&
(this.OutFallingValue === "true" || this.OutFallingValue === "false")
) {
this.OutFallingValue === "true" ?
(this.OutFallingValue = true) :
(this.OutFallingValue = false);
} else if (this.OutFallingValue === "null") {
this.OutFallingType = "null";
this.OutFallingValue = null;
} else {
this.OutFallingValue = String(this.OutFallingValue);
}
}
// eslint-disable-next-line prefer-const
let node = this;
let TriggerValueRising = "";
let TriggerValueFalling = "";
// if (this.ThresholdType === "fixed") {
// TriggerValueRising = Number.parseFloat(this.ThresholdRising);
// TriggerValueFalling = Number.parseFloat(this.ThresholdFalling);
// }
TriggerValueRising = nodeContext.get("TriggerValueRising") || "";
TriggerValueFalling = nodeContext.get("TriggerValueFalling") || "";
// clear direction flag
node.direction = "";
// Set initial status
setInitialStatus();
function setInitialStatus() {
if ("" === TriggerValueRising && "" === TriggerValueFalling) {
node.status({
fill: "red",
shape: "ring",
text: "Thresholds missing",
});
} else if ("" === TriggerValueRising) {
node.status({
fill: "red",
shape: "ring",
text: "Upper Thresholds missing",
});
} else if ("" === TriggerValueFalling) {
node.status({
fill: "red",
shape: "ring",
text: "Lower Thresholds missing",
});
} else {
node.status({
fill: "yellow",
shape: "ring",
text: TriggerValueFalling + "/--/" + TriggerValueRising,
});
}
}
function setWarningStatus(status) {
if ("upper" === status) {
node.status({
fill: "yellow",
shape: "ring",
text: "Upper Thresholds must equal or greater than Lower Thresholds",
});
} else if ("lower" === status) {
node.status({
fill: "yellow",
shape: "ring",
text: "Lower Thresholds must equal or less than Upper Thresholds",
});
}
statusTimeout = setTimeout(function () {
setInitialStatus();
}, 2000);
}
this.on("input", function (msg) {
if (
Object.prototype.hasOwnProperty.call(msg, "payload") &&
Object.prototype.hasOwnProperty.call(msg.payload, "high")
) {
if (
"" != TriggerValueFalling &&
msg.payload.high < TriggerValueFalling
) {
setWarningStatus("upper");
} else {
TriggerValueRising = msg.payload.high;
nodeContext.set("TriggerValueRising", msg.payload.high);
setInitialStatus();
}
} else if (
Object.prototype.hasOwnProperty.call(msg, "payload") &&
Object.prototype.hasOwnProperty.call(msg.payload, "low")
) {
if ("" != TriggerValueRising && msg.payload.low > TriggerValueRising) {
setWarningStatus("lower");
} else {
TriggerValueFalling = msg.payload.low;
nodeContext.set("TriggerValueFalling", msg.payload.low);
setInitialStatus();
}
} else {
// original msg object
const msgNew = RED.util.cloneMessage(msg);
// set topic
if (this.OutTopicType === "str") {
msgNew.topic = this.OutTopicValue;
}
if (
(Object.prototype.hasOwnProperty.call(msg, "payload") &&
this.ThresholdType === "fixed" &&
!Number.isNaN(msg.payload)) ||
(Object.prototype.hasOwnProperty.call(msg, "payload") &&
this.ThresholdType === "dynamic" &&
msg.topic === this.TopicCurrent &&
TriggerValueRising &&
!Number.isNaN(msg.payload))
) {
const CurrentValue = Number.parseFloat(msg.payload);
// Cover the case where no initial values are known
if (
this.InitialMessage &&
node.direction === "" &&
!Number.isNaN(CurrentValue)
) {
if (CurrentValue >= Number.parseFloat(TriggerValueRising)) {
msgNew.payload =
this.OutRisingType === "pay" ?
msgNew.payload :
this.OutRisingValue;
msgNew.hystdirection = "initial high";
node.send(msgNew);
node.direction = "high";
node.status({
fill: "green",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (initial high band)",
});
} else if (CurrentValue <= TriggerValueFalling) {
msgNew.payload =
this.OutFallingType === "pay" ?
msgNew.payload :
this.OutFallingValue;
msgNew.hystdirection = "initial low";
node.send(msgNew);
node.direction = "low";
node.status({
fill: "blue",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (initial low band)",
});
}
// Last value known. Work as hysteresis
} else if (!Number.isNaN(CurrentValue) && node.LastValue) {
// rising
if (
CurrentValue > node.LastValue &&
CurrentValue >= TriggerValueRising &&
node.direction !== "high"
) {
msgNew.payload =
this.OutRisingType === "pay" ?
msgNew.payload :
this.OutRisingValue;
msgNew.hystdirection = "high";
node.send(msgNew);
node.direction = "high";
node.status({
fill: "green",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (high band)",
});
// falling
} else if (
CurrentValue < node.LastValue &&
CurrentValue <= TriggerValueFalling &&
node.direction !== "low"
) {
msgNew.payload =
this.OutFallingType === "pay" ?
msgNew.payload :
this.OutFallingValue;
msgNew.hystdirection = "low";
node.send(msgNew);
node.direction = "low";
node.status({
fill: "blue",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (low band)",
});
} else if (
CurrentValue > node.LastValue &&
CurrentValue >= TriggerValueRising &&
node.direction === "high"
) {
node.status({
fill: "green",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (high band rising)",
});
} else if (
CurrentValue < node.LastValue &&
CurrentValue >= TriggerValueRising &&
node.direction === "high"
) {
node.status({
fill: "green",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (high band falling)",
});
} else if (
CurrentValue > node.LastValue &&
CurrentValue > TriggerValueFalling &&
CurrentValue < TriggerValueRising &&
node.direction === "high"
) {
node.status({
fill: "green",
shape: "ring",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (high dead band rising)",
});
} else if (
CurrentValue < node.LastValue &&
CurrentValue > TriggerValueFalling &&
CurrentValue < TriggerValueRising &&
node.direction === "high"
) {
node.status({
fill: "green",
shape: "ring",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (high dead band falling)",
});
} else if (
CurrentValue > node.LastValue &&
CurrentValue > TriggerValueFalling &&
CurrentValue < TriggerValueRising &&
node.direction === "low"
) {
node.status({
fill: "blue",
shape: "ring",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (low dead band rising)",
});
} else if (
CurrentValue < node.LastValue &&
CurrentValue > TriggerValueFalling &&
CurrentValue < TriggerValueRising &&
node.direction === "low"
) {
node.status({
fill: "blue",
shape: "ring",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (low dead band falling)",
});
} else if (
CurrentValue > node.LastValue &&
CurrentValue <= TriggerValueFalling &&
node.direction === "low"
) {
node.status({
fill: "blue",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueRising +
" (low band rising)",
});
} else if (
CurrentValue < node.LastValue &&
CurrentValue <= TriggerValueFalling &&
node.direction === "low"
) {
node.status({
fill: "blue",
shape: "dot",
text: TriggerValueFalling +
"/" +
CurrentValue +
"/" +
TriggerValueFalling +
" (low band falling)",
});
}
} else {}
node.LastValue = CurrentValue;
}
}
});
}
RED.nodes.registerType("smithtek_node_red_hysteresis", HysteresisNode);
};