Created
October 20, 2023 14:48
-
-
Save stas-dovgodko/f21e5ceed0ab944297f9383943fa4e76 to your computer and use it in GitHub Desktop.
Openhab ventilation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let auto = require('openhab-auto'); | |
auto.manager('Ventilation', { | |
label: 'Автоматичне керування температурою вентиляції', | |
groups: ['Ventilation'], | |
tags: ['HVAC', 'Control'] | |
}, 'Ventilation_OutputSetpointTemperature').description('Автоматичне встановлення температури вентиляції відповідно до потреб') | |
.handle(function(event) { | |
// температура входу до рекуператора це міра "прогріву" будинку | |
const inhouse_temp = items.getItem('VentilationUnit_ExchangeTemperature').history.averageSince(time.ZonedDateTime.now().minusMinutes(5)); | |
const outside_temp = parseFloat(items.getItem('Weather_OutsideTemperature').state); | |
let setpoint_temp = parseFloat(items.getItem('HomeHVAC_SetpointTemperature').state); | |
if (isNaN(setpoint_temp)) setpoint_temp = 21; | |
const is_mode_away = (items.getItem('Home_ModeAway').state == 'OPEN'); | |
const is_mode_inbed = (items.getItem('Home_ModeInbed').state == 'OPEN'); | |
let auto_temp = Math.round((setpoint_temp - (inhouse_temp - 2 - setpoint_temp)) * 10) / 10; | |
// не можна допускати температуру вентиляції надто низькою | |
let low_limit = 16; | |
if (outside_temp > 28) { | |
// на вулиці дуже жарко, тож можна більше агресивно охолоджувати | |
low_limit = setpoint_temp - (outside_temp - setpoint_temp) - 2; | |
} else if (outside_temp > 26) { | |
low_limit = setpoint_temp - (outside_temp - setpoint_temp); | |
} else if (is_mode_inbed && (outside_temp < 14)) { | |
low_limit = setpoint_temp; | |
} else if (is_mode_away && (outside_temp < 14)) { | |
low_limit = setpoint_temp - 1; | |
} else if (outside_temp < 14) { | |
low_limit = setpoint_temp + 2; | |
} else if (outside_temp < 18) { | |
low_limit = setpoint_temp; | |
} | |
if (auto_temp < low_limit) auto_temp = low_limit; | |
if (auto_temp < 16) auto_temp = 16; | |
else if (auto_temp > 25) auto_temp = 25; | |
return `${auto_temp} °C`; | |
}, [ | |
triggers.ItemStateUpdateTrigger('Home_ModeAway'), | |
triggers.ItemStateUpdateTrigger('Home_ModeInbed'), | |
triggers.ItemStateUpdateTrigger('HomeHVAC_SetpointTemperature'), | |
triggers.ItemStateUpdateTrigger('Weather_OutsideTemperature'), | |
triggers.ItemStateUpdateTrigger('VentilationUnit_ExchangeTemperature') | |
]) | |
auto.manager('VentilationIntake', { | |
label: 'Автоматичне керування байпасом ГТО', | |
groups: ['Ventilation'], | |
tags: ['HVAC', 'Control'] | |
}, 'Ventilation_IntakeDumperSwitch') | |
.description('Автоматичне вмикання байпасу притоку вентиляції') | |
.handle(function(event) { | |
const inhouse_temp = items.getItem('VentilationUnit_ExchangeTemperature').history.averageSince(time.ZonedDateTime.now().minusMinutes(5)); | |
const setpoint_temp = parseFloat(items.getItem('Ventilation_OutputSetpointTemperature').state); | |
const gto_temp = items.getItem('VentilationHeatPipe_OutputTemperature').history.averageSince(time.ZonedDateTime.now().minusMinutes(30)); | |
const outside_temp = parseFloat(items.getItem('Weather_OutsideTemperature').state); | |
// рахуємо яку температуру граничну на вході рекуператор зможе дотягнути до бажаної приумовному КПД 50% | |
// оскільки нам повітря не вистачає від ГТО то будемо рахувати що якщо це нагрів то хай ще 2 градуси не алектричному догріває | |
// відкриття байпасу ГТО, окрім впливу на температуру, значно підвищує продуктивність ПВУ по повітрю, бо ГТО має значний опір | |
// вважаю що +-2 градуси на 400 кубах то 300вт зайвого тепла/холоду ні на що не вплинуть але повітря хочеться більше, | |
// тож байпас відкривається в проміжку -1+2 градуса від розрахованого диапазону | |
// T1 = (T4 - rate * T2) / (1 - rate); | |
let expected_heater_temp = (setpoint_temp - 4 - 0.5 * inhouse_temp) / (1 - 0.5); | |
// відкриваємо байпас якшо температура на вулиці в дозволеному проміжку | |
if ((outside_temp > (expected_heater_temp - 1)) && (outside_temp < (setpoint_temp + 2))) | |
{ | |
return 'ON'; | |
} else | |
// відкриваємо якщо температура на вулиці вище температури гто але меньше уставки (нагріваємо) | |
if ((outside_temp > gto_temp - 2) && (outside_temp < (setpoint_temp + 2))) | |
{ | |
return 'ON'; | |
//} else | |
// відкриваємо якщо температура на вулиці нижче температури гто та в домі жарко (охолоджуємо) | |
//if ((outside_temp < gto_temp) && (inhouse_temp > setpoint_temp)) | |
//{ | |
// command('Ventilation_IntakeDumperSwitch', 'ON'); | |
} else { | |
return 'OFF'; | |
} | |
}, [ | |
triggers.ItemStateUpdateTrigger('Ventilation_OutputSetpointTemperature'), | |
triggers.ItemStateUpdateTrigger('VentilationUnit_ExchangeTemperature'), | |
triggers.ItemStateUpdateTrigger('VentilationHeatPipe_OutputTemperature'), | |
triggers.ItemStateUpdateTrigger('Weather_OutsideTemperature') | |
]); | |
auto.manager('VentilationMode', { | |
label: 'Автоматичне керування потужністю вентиляції', | |
groups: ['Ventilation'], | |
tags: ['HVAC', 'Control'] | |
}, 'VentilationUnit_OutputMode') | |
.description('Автоматичне керування кількістю повітря відповідно до потреб') | |
.handle(function(event) { | |
const is_hood_working = (items.getItem('Kitchen_HoodOn').state == 'OPEN') ; | |
const is_mode_away = (items.getItem('Home_ModeAway').state == 'OPEN'); | |
const is_mode_guests = (items.getItem('Home_Guests').state == 'ON'); | |
// кухонний зонт має велику продуктивність і гарантовано опрокидує вентиляцію | |
// тож коли він працює вмикаємо лише приток | |
if (is_hood_working) { | |
return 'INTAKE'; | |
} | |
// коли нікого нема вентиляція вмикається на мінімальний режим | |
if (is_mode_away) { | |
// away | |
//auto_temp = '19 °C'; | |
return 'IDLE'; | |
} | |
// якщо вдома гості то вентиляція вмикається на максималку | |
if (is_mode_guests) { | |
// away | |
//auto_temp = '19 °C'; | |
return 'BOOST'; | |
} | |
// дефолтний режим (невеликий проіритет подачі для підпору) | |
return 'MAIN'; | |
}, [ | |
triggers.ItemStateUpdateTrigger('Kitchen_HoodOn'), | |
triggers.ItemStateUpdateTrigger('Home_ModeAway'), | |
triggers.ItemStateUpdateTrigger('Home_Guests') | |
]); | |
// PID регулювання догрівача | |
rules.JSRule({ | |
name: 'VentilationHeater PID rule', | |
triggers: [ | |
triggers.PIDTrigger( | |
'Ventilation_OutputTemperature', | |
'Ventilation_OutputSetpointTemperature', | |
20, // kp | |
1, //0.0004, // ki 0.007 | |
0.01, //0.001, // kd | |
10, // kdTimeConstant | |
5000, // loopTime | |
'VentilationHeater_OutputState_Reset' //commandItem | |
) | |
], | |
execute: (event) => { | |
let d = parseFloat(event.receivedCommand); | |
const v = parseFloat(items.getItem('VentilationHeater_OutputState').state) + (d / 500); | |
if (d < 0) { | |
if (v <= 0) items.getItem('VentilationHeater_OutputState').sendCommand(0) | |
else items.getItem('VentilationHeater_OutputState').sendCommand(Math.round(v*10000.0)/10000.0); | |
} else if (d > 100) { | |
items.getItem('VentilationHeater_OutputState').sendCommand(100); | |
} else { | |
items.getItem('VentilationHeater_OutputState').sendCommand(d); | |
} | |
}, | |
tags: ['Ventilation'] | |
}); | |
rules.JSRule({ | |
name: 'VentilationHeater setpoint rule', | |
description: "VentilationHeater setpoint update", | |
triggers: [ | |
triggers.ItemStateChangeTrigger('Ventilation_OutputSetpointTemperature') | |
], | |
execute: event => { | |
items.getItem('VentilationHeater_OutputState_Reset').sendCommand('RESET'); // to reset PID | |
}, | |
tags: ['Ventilation'] | |
}); | |
rules.JSRule({ | |
name: 'Calculate VPU exchange rate', | |
description: "", | |
triggers: [ | |
triggers.ItemStateUpdateTrigger('VentilationUnit_InputTemperature'), | |
triggers.ItemStateUpdateTrigger('VentilationUnit_OutputTemperature'), | |
triggers.ItemStateUpdateTrigger('VentilationUnit_ExchangeTemperature') | |
], | |
execute: e => { | |
// розрахунок евективності по класичній формулі через температуру потоків | |
// насправді так рахувати не коректно бо це праює лише за умови однакових потоків але най буде | |
const T1 = parseFloat(items.getItem('VentilationUnit_InputTemperature').rawState.toUnit('°C')); | |
const T4 = parseFloat(items.getItem('VentilationUnit_OutputTemperature').rawState.toUnit('°C')); | |
const T2 = parseFloat(items.getItem('VentilationUnit_ExchangeTemperature').rawState.toUnit('°C')); | |
let rate = 0; | |
if (T2 != T1) { | |
rate = Math.round((T4 - T1) * 100 / (T2 - T1)); | |
if (rate < 0) rate = 0; // датчики температури можуть трохи привирать, особливо в байпасі | |
} | |
items.getItem('VentilationUnit_ExchangeRate').postUpdate(`${rate} %`); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment