Skip to content

Instantly share code, notes, and snippets.

@stas-dovgodko
Created October 20, 2023 14:48
Show Gist options
  • Save stas-dovgodko/f21e5ceed0ab944297f9383943fa4e76 to your computer and use it in GitHub Desktop.
Save stas-dovgodko/f21e5ceed0ab944297f9383943fa4e76 to your computer and use it in GitHub Desktop.
Openhab ventilation
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