smart-nodes
Version:
250 lines (202 loc) • 8.74 kB
JavaScript
module.exports = function (RED)
{
"use strict";
function HysteresisNode(config)
{
const node = this;
RED.nodes.createNode(node, config);
// ###################
// # Class constants #
// ###################
// #######################
// # Global help objects #
// #######################
const smart_context = require("../persistence.js")(RED);
const helper = require("../smart_helper.js");
// #####################
// # persistent values #
// #####################
var node_settings = {
last_value: null,
last_result: null,
last_message: null,
setpoint: parseFloat(config.setpoint ?? 10),
hysteresis: parseFloat(config.hysteresis ?? 1),
min: parseFloat(config.min ?? 10),
max: parseFloat(config.max ?? 20)
};
if (config.save_state)
{
// load old saved values
node_settings = Object.assign(node_settings, smart_context.get(node.id));
switch (node_settings.last_result)
{
case true:
node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Load last state: Higher" });
break;
case false:
node.status({ fill: "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Load last state: Lower" });
break;
default:
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": No last state available" });
break;
}
}
else
{
// delete old saved values
smart_context.del(node.id);
}
// ##################
// # Dynamic config #
// ##################
let out_higher = helper.evaluateNodeProperty(RED, config.out_higher, config.out_higher_type);
let out_lower = helper.evaluateNodeProperty(RED, config.out_lower, config.out_lower_type);
let send_only_change = helper.evaluateNodeProperty(RED, config.send_only_change, "bool");
let outputs = helper.evaluateNodeProperty(RED, config.outputs, "num");
let mode = config.mode || "HYSTERESIS";
// ##################
// # Runtime values #
// ##################
// ###############
// # Node events #
// ###############
node.on("input", function (msg)
{
handleTopic(msg);
setStatus();
if (config.save_state)
smart_context.set(node.id, node_settings);
});
node.on("close", function ()
{
});
// #####################
// # Private functions #
// #####################
// This is the main function which handles all topics that was received.
let handleTopic = msg =>
{
let real_topic = helper.getTopicName(msg.topic);
let value = parseFloat(msg.payload);
if (isNaN(value))
{
// helper.warn(this, "Invalid payload: " + msg.payload);
return;
}
switch (real_topic)
{
case "setpoint":
node_settings.setpoint = value;
break;
case "hysteresis":
node_settings.hysteresis = value;
break;
case "min":
node_settings.min = value;
break;
case "max":
node_settings.max = value;
break;
default:
node_settings.last_value = value
break;
}
// Always calculate new result
let result = getResult(node_settings.last_value);
let out_msg = null;
// No change, nothing to send
if (result === null)
return;
// Get custom output message
if (result)
out_msg = createMessage(out_higher, config.out_higher_type, msg, node_settings.last_value);
else
out_msg = createMessage(out_lower, config.out_lower_type, msg, node_settings.last_value);
// Overwrite automatic values, if not already defined
if (typeof out_msg.payload === "undefined")
out_msg.payload = result ?? node_settings.last_result;
// Separate outputs if needed
if (outputs == 2)
{
if (result)
out_msg = [out_msg, null];
else
out_msg = [null, out_msg];
}
// Send only if needed
if (send_only_change == false || node_settings.last_result != result)
node.send(out_msg);
node_settings.last_result = result;
node_settings.last_message = out_msg;
}
let getResult = value =>
{
if (mode == "HYSTERESIS")
{
if (value >= node_settings.setpoint + node_settings.hysteresis && node_settings.last_result !== true)
return true;
if (value <= node_settings.setpoint - node_settings.hysteresis && node_settings.last_result !== false)
return false;
}
else if (mode == "MIN_MAX")
{
if (value >= node_settings.max && node_settings.last_result !== true)
return true;
if (value <= node_settings.min && node_settings.last_result !== false)
return false;
}
if (send_only_change)
return null;
return node_settings.last_result;
}
let setStatus = () =>
{
if (mode == "HYSTERESIS")
{
if (node_settings.last_result == null)
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": ❓ S: " + node_settings.setpoint + " - H: " + node_settings.hysteresis + " - V: " + (node_settings.last_value?.toFixed(2) ?? "null") });
else
node.status({ fill: node_settings.last_result ? "green" : "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": " + (node_settings.last_result ? "⬆️" : "⬇️") + " S: " + node_settings.setpoint + " - H: " + node_settings.hysteresis + " - V: " + node_settings.last_value?.toFixed(2) });
}
else if (mode == "MIN_MAX")
{
if (node_settings.last_result == null)
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": ❓ Min: " + node_settings.min + " - Max: " + node_settings.max + " - V: " + (node_settings.last_value?.toFixed(2) ?? "null") });
else
node.status({ fill: node_settings.last_result ? "green" : "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": " + (node_settings.last_result ? "⬆️" : "⬇️") + " Min: " + node_settings.min + " - Max: " + node_settings.max + " - V: " + node_settings.last_value?.toFixed(2) });
}
}
let createMessage = (out_msg, out_type, msg, value) =>
{
return helper.cloneObject(out_type == "ORIGINAL" ? msg : out_msg, {
smart_info: {
last_result: node_settings.last_result,
hysteresis: node_settings.hysteresis,
setpoint: node_settings.setpoint,
last_value: node_settings.last_value,
value: value
}
});
};
if (config.save_state && config.resend_on_start && node_settings.last_result !== null && node_settings.last_message !== null)
{
setTimeout(() =>
{
if (outputs == 2)
{
if (node_settings.last_result)
node.send([helper.cloneObject(node_settings.last_message), null]);
else
node.send([null, helper.cloneObject(node_settings.last_message)]);
}
else
{
node.send(helper.cloneObject(node_settings.last_message));
}
}, 10000);
}
setStatus();
}
RED.nodes.registerType("smart_hysteresis", HysteresisNode);
}