Bump version to 0.1.12

This commit is contained in:
vvzvlad 2023-10-03 17:15:20 +03:00
parent 08f73c1b58
commit 6578d6bf71
3 changed files with 112 additions and 179 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@vvzvlad/node-red-contrib-rn-combined-nodes", "name": "@vvzvlad/node-red-contrib-rn-combined-nodes",
"version": "0.1.11", "version": "0.1.12",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"keywords": [ "keywords": [

View File

@ -4,13 +4,11 @@
color: '#F3B567', color: '#F3B567',
paletteLabel: 'Th-analyzer', paletteLabel: 'Th-analyzer',
defaults: { defaults: {
name: {value:""}, name: {value:""}
room: {value:"", required:true},
zone: {value:"", required:true}
}, },
inputs: 1, inputs: 1,
outputs: 3, outputs: 3,
outputLabels: ["heater","stats"], outputLabels: ["mqtt_debug","state_text","current_temp"],
icon: "font-awesome/fa-snowflake-o", icon: "font-awesome/fa-snowflake-o",
label: function() { label: function() {
return this.name||"Th-analyzer"; return this.name||"Th-analyzer";

View File

@ -6,196 +6,131 @@ module.exports = function(RED) {
var node = this; var node = this;
var context = node.context(); var context = node.context();
function status_reason(msg) {
let heater = msg.payload.heater_status
let window_state = msg.payload.window_state
let control_mode = msg.payload.control_mode
const HEAT_ON_THRESHOLD = 0.2 if (window_state == "open") {
const HEAT_OFF_THRESHOLD = 0.2 context.set('heater_reason', "(открыто окно)")
let debug = ""
function debug_text(text) {
if (text === "return_debug") {
let debug_tmp = debug
debug = ""
return debug_tmp
} }
debug = debug ? `${debug}, ${text}` : text
if (control_mode == "auto") {
context.set('heater_reason', "(поддержание температуры)")
}
if (heater === 1) {
context.set('heater_status', "Включен")
}
if (heater === 0) {
context.set('heater_status', "Выключен")
}
let heater_reason = context.get('heater_reason') || ""
let heater_status = context.get('heater_status') || ""
return heater_status + " " + heater_reason
} }
function predict_temp_change(current_temp, time_interval) { function get_current_temp(msg) {
let last_temp = context.get('last_temp') || null let current_temp = msg.payload.current_temp || "00"
let last_temp_time = context.get('last_temp_time') || null return current_temp
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 analyzer(msg) {
const status_window = 3 * 60 * 60 * 1000
const transitions_window = 3 * 60 * 60 * 1000
const max_temp_diff = 3
function round_to_two_decimal_places(num) {
return Math.round((num + Number.EPSILON) * 100) / 100
}
function update_heater_status(value) {
let status_hist = context.get('status_hist') || []
if (value === null) return
const current_time = new Date().getTime()
status_hist.push({ value, timestamp: current_time })
status_hist = status_hist.filter(item => current_time - item.timestamp <= status_window)
let sum = 0
let total_time = 0
for (let i = 1; i < status_hist.length; i++) {
const time_diff = status_hist[i].timestamp - status_hist[i - 1].timestamp
sum += status_hist[i].value * time_diff
total_time += time_diff
}
context.set('status_hist', status_hist)
return round_to_two_decimal_places(total_time ? sum / total_time : null)
}
function update_temp_diff(current_temp, target_temp) {
let diff_hist = context.get('diff_hist') || []
const current_time = new Date().getTime()
const temp_diff = Math.abs(current_temp - target_temp)
diff_hist.push({ value: temp_diff, timestamp: current_time })
diff_hist = diff_hist.filter(item => current_time - item.timestamp <= status_window)
let sum = 0
let total_time = 0
for (let i = 1; i < diff_hist.length; i++) {
const time_diff = diff_hist[i].timestamp - diff_hist[i - 1].timestamp
sum += diff_hist[i].value * time_diff
total_time += time_diff
}
context.set('diff_hist', diff_hist)
return round_to_two_decimal_places(total_time ? sum / total_time : null)
}
function update_transitions(new_heater_status, new_window_state) {
let transitions = context.get('transitions') || []
let last_heater_status = context.get('last_heater_status')
let last_window_state = context.get('last_window_state')
if (new_heater_status !== last_heater_status && new_window_state === last_window_state) {
const current_time = new Date().getTime()
transitions.push({ timestamp: current_time })
transitions = transitions.filter(item => current_time - item.timestamp <= transitions_window)
context.set('last_heater_status', new_heater_status)
context.set('transitions', transitions)
}
context.set('last_window_state', new_window_state)
return transitions.length * 8
}
const payload = msg.payload
const avg_heater_status = update_heater_status(payload.heater_status)
const avg_temp_diff = update_temp_diff(payload.current_temp, payload.target_temp)
const transitions_count = update_transitions(payload.heater_status, payload.window_state)
let malfunction = 0
if (avg_temp_diff > max_temp_diff && (avg_heater_status > 0.9 || avg_heater_status < 0.1)) malfunction = 1
msg.payload.malfunction = malfunction
msg.payload.avg_temp_diff = avg_temp_diff
msg.payload.avg_heater_status = avg_heater_status
msg.payload.transitions_count = transitions_count
msg.payload.const_max_temp_diff = max_temp_diff
node.status({ text: `malfunction: ${malfunction}, transitions: ${transitions_count}` })
return msg.payload
}
node.on('input', function(msg) { node.on('input', function(msg) {
let topic = msg.topic
let payload = msg.payload
let control_mode = context.get("control_mode") || "auto" //auto, on, off let key = "fake"
let current_temp = context.get("current_temp") let mqtt_debug = { payload: analyzer(msg), topic: `debug/${node.zone}_${node.room}_/th/${key}`}
let target_temp = context.get("target_temp") || 19 let state_text = { payload: status_reason(msg) }
let window_state = context.get("window_state") || "close" //open, close let current_temp = { payload: get_current_temp(msg) }
let window_mode = context.get("window_mode") || "cool" //heat, cool, off
let heater_status = null
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
}
}
debug_text(`W-mode: ${window_mode}`)
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]
}
else return
}
else return
}
if (topic === "window_mode_control" && (payload === "cool" || payload === "heat" || payload === "off")) {
window_mode = payload
context.set("window_mode", window_mode)
}
if (topic === "heat_mode_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, topic: node.zone + "_" + node.room }
if (heater_status === null || typeof heater_status === "undefined") { if (heater_status === null || typeof heater_status === "undefined") {
msg_heater_status = null msg_heater_status = null
} }
node.send([msg_heater_status, msg_extended_stats]); node.send([mqtt_debug, state_text, current_temp]);
}); });