module.exports = function(RED) { function thermostat(config) { RED.nodes.createNode(this,config); var node = this; var context = node.context(); const HEAT_ON_THRESHOLD = 0.2 const HEAT_OFF_THRESHOLD = 0.2 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 let debug = "" function debug_text(text) { if (text === "return_debug") return debug debug = debug ? `${debug}, ${text}` : text } 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 } function logic() { if (control_mode === "off") { heater_status = 0 debug_text("Heater: force/off") return } if (control_mode === "on") { heater_status = 1 debug_text("Heater: force/on") return } 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") { heater_status = 0 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 (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"}`) } } return } if (control_mode === "auto" && current_temp !== null && target_temp !== null) { if (window_state === "open" && window_mode === "cool") { heater_status = 0 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"}`) } } return } } node.on('input', function(msg) { let topic = msg.topic let payload = msg.payload debug_text(`W-mode: ${window_mode}`) if (topic === "window_mode_control" && (payload === "cool" || payload === "heat" || payload === "off")) { window_mode = payload context.set("window_mode", window_mode) } if (topic === "control" && (payload === "on" || payload === "off" || payload === "auto" || payload === "auto-predict")) { 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 } let msg_extended_stats = { payload: extended_stats } if (heater_status === null || typeof heater_status === "undefined") { msg_heater_status = null } node.send([msg_heater_status, msg_extended_stats]); }); } RED.nodes.registerType("c-thermostat", thermostat ); }