node-red-contrib-rn-combine.../thermostat.js

219 lines
7.5 KiB
JavaScript
Raw Normal View History

2023-10-02 21:33:26 +03:00
module.exports = function(RED) {
2023-10-02 22:32:55 +03:00
function thermostat(config) {
2023-10-02 21:33:26 +03:00
RED.nodes.createNode(this,config);
2023-10-03 16:13:30 +03:00
this.room = config.room;
this.zone = config.zone;
2023-10-02 21:33:26 +03:00
var node = this;
2023-10-03 00:26:45 +03:00
var context = node.context();
2023-10-03 16:09:12 +03:00
2023-10-03 00:26:45 +03:00
const HEAT_ON_THRESHOLD = 0.2
const HEAT_OFF_THRESHOLD = 0.2
let debug = ""
2023-10-03 00:55:42 +03:00
2023-10-03 00:26:45 +03:00
function debug_text(text) {
2023-10-03 00:55:42 +03:00
if (text === "return_debug") {
let debug_tmp = debug
debug = ""
2023-10-03 01:31:10 +03:00
return debug_tmp
2023-10-03 00:55:42 +03:00
}
2023-10-03 00:26:45 +03:00
debug = debug ? `${debug}, ${text}` : text
2023-10-03 00:55:42 +03:00
2023-10-03 00:26:45 +03:00
}
function predict_temp_change(current_temp, time_interval) {
let last_temp = context.get('last_temp') || null
let last_temp_time = context.get('last_temp_time') || null
if (last_temp === null || last_temp_time === null) {
last_temp = current_temp
last_temp_time = new Date().getTime()
context.set('last_temp', last_temp)
context.set('last_temp_time', last_temp_time)
return 0
}
const temp_diff = current_temp - last_temp
const time_diff = (new Date().getTime() - last_temp_time) / 1000
const rate_of_change = temp_diff / time_diff
last_temp = current_temp
last_temp_time = new Date().getTime()
context.set('last_temp', last_temp)
context.set('last_temp_time', last_temp_time)
return current_temp + rate_of_change * time_interval
}
2023-10-03 00:58:52 +03:00
node.on('input', function(msg) {
let topic = msg.topic
let payload = msg.payload
2023-10-03 00:26:45 +03:00
2023-10-03 00:58:52 +03:00
let control_mode = context.get("control_mode") || "auto" //auto, on, off
let current_temp = context.get("current_temp")
let target_temp = context.get("target_temp") || 19
let window_state = context.get("window_state") || "close" //open, close
let window_mode = context.get("window_mode") || "cool" //heat, cool, off
let heater_status = null
function logic() {
if (control_mode === "off") {
2023-10-03 00:26:45 +03:00
heater_status = 0
2023-10-03 00:58:52 +03:00
debug_text("Heater: force/off")
2023-10-03 00:26:45 +03:00
return
}
2023-10-03 00:58:52 +03:00
if (control_mode === "on") {
2023-10-03 00:26:45 +03:00
heater_status = 1
2023-10-03 00:58:52 +03:00
debug_text("Heater: force/on")
2023-10-03 00:26:45 +03:00
return
}
2023-10-03 00:58:52 +03:00
if (control_mode === "auto-predict" && current_temp !== null && target_temp !== null) {
const predicted_temp = predict_temp_change(current_temp, 40)
if (window_state === "open" && window_mode === "cool") {
2023-10-03 00:26:45 +03:00
heater_status = 0
2023-10-03 00:58:52 +03:00
debug_text("Heater: w-open-cool/off")
return
}
if (window_state === "open" && window_mode === "heat") {
heater_status = 1
debug_text("Heater: w-open-heat/on")
return
2023-10-03 00:26:45 +03:00
}
2023-10-03 00:58:52 +03:00
if (window_state === "close") {
if (predicted_temp < target_temp) {
heater_status = 1
debug_text(`Heater: auto-predict/${"on"}`)
} else if (predicted_temp >= target_temp) {
heater_status = 0
debug_text(`Heater: auto-predict/${"off"}`)
}
}
2023-10-03 00:26:45 +03:00
return
}
2023-10-03 00:58:52 +03:00
if (control_mode === "auto" && current_temp !== null && target_temp !== null) {
if (window_state === "open" && window_mode === "cool") {
2023-10-03 00:26:45 +03:00
heater_status = 0
2023-10-03 00:58:52 +03:00
debug_text("Heater: w-open-cool/off")
return
}
if (window_state === "open" && window_mode === "heat") {
heater_status = 1
debug_text("Heater: w-open-heat/on")
return
}
if (window_state === "close") {
if (current_temp <= target_temp - HEAT_ON_THRESHOLD) {
heater_status = 1
debug_text(`Heater: auto/${"on"}`)
} else if (current_temp >= target_temp + HEAT_OFF_THRESHOLD) {
heater_status = 0
debug_text(`Heater: auto/${"off"}`)
} else {
debug_text(`Heater: auto/${"hyst"}`)
}
2023-10-03 00:26:45 +03:00
}
2023-10-03 00:58:52 +03:00
return
2023-10-03 00:26:45 +03:00
}
}
2023-10-03 00:58:52 +03:00
2023-10-03 00:55:42 +03:00
2023-10-03 00:26:45 +03:00
debug_text(`W-mode: ${window_mode}`)
2023-10-03 16:36:12 +03:00
let mqtt_control = topic.split('/')
if (mqtt_control.length === 3 && mqtt_control[0] === 'control') {
if (mqtt_control[2] === node.zone || mqtt_control[2] === node.zone + "_" + node.room || mqtt_control[2] === "all") {
if (mqtt_control[1] === "window_mode_control" || mqtt_control[1] === "heat_mode_control") {
topic = mqtt_control[1]
}
2023-10-03 16:39:56 +03:00
else return
2023-10-03 16:36:12 +03:00
}
2023-10-03 16:39:56 +03:00
else return
2023-10-03 16:36:12 +03:00
}
2023-10-03 00:26:45 +03:00
if (topic === "window_mode_control" && (payload === "cool" || payload === "heat" || payload === "off")) {
window_mode = payload
context.set("window_mode", window_mode)
}
2023-10-03 16:36:12 +03:00
if (topic === "heat_mode_control" && (payload === "on" || payload === "off" || payload === "auto" || payload === "auto-predict")) {
2023-10-03 00:26:45 +03:00
control_mode = payload
context.set("control_mode", control_mode)
}
debug_text(`W-state: ${window_state}`)
if (topic === "window" && (payload === "open" || payload === "close")) {
window_state = payload
context.set("window_state", window_state)
}
if (topic === "target") {
target_temp = parseFloat(msg.payload)
context.set("target_temp", target_temp)
debug_text(`Current: ${current_temp}`)
debug_text(`Target: ${target_temp}`)
}
if (msg.topic === "current") {
current_temp = parseFloat(msg.payload)
context.set("current_temp", current_temp)
debug_text(`Current: ${current_temp}`)
debug_text(`Target: ${target_temp}`)
}
logic()
let extended_stats = {
"control_mode": control_mode,
"current_temp": current_temp,
"target_temp": target_temp,
"window_state": window_state,
"window_mode": window_mode,
"heater_status": heater_status,
"HEAT_ON_THRESHOLD": HEAT_ON_THRESHOLD,
"HEAT_OFF_THRESHOLD": HEAT_OFF_THRESHOLD,
}
node.status({ text: debug_text("return_debug") })
let msg_heater_status = { payload: heater_status }
2023-10-03 16:59:59 +03:00
let msg_extended_stats = { payload: extended_stats, topic: node.zone + "_" + node.room }
2023-10-03 00:26:45 +03:00
if (heater_status === null || typeof heater_status === "undefined") {
msg_heater_status = null
}
node.send([msg_heater_status, msg_extended_stats]);
2023-10-02 21:33:26 +03:00
});
2023-10-03 00:26:45 +03:00
2023-10-02 21:33:26 +03:00
}
2023-10-02 22:53:03 +03:00
RED.nodes.registerType("c-thermostat", thermostat );
2023-10-02 21:33:26 +03:00
}
2023-10-03 00:26:45 +03:00