Ви не увійшли.
Сторінки 1



// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
ESP8266 «Котел» (opentherm_mqtt.ino) — документація
Плата виконує роль шлюзу: спілкується з котлом по протоколу OpenTherm, а з Delphi-застосунком — через MQTT (брокер CloudAMQP).
1. Підключення пінів (NodeMCU)
Пін GPIO Призначення Напрямок
D7 GPIO13 OpenTherm-адаптер — IN вхід (переривання)
D8 GPIO15 OpenTherm-адаптер — OUT вихід
D5 GPIO14 Реле насоса вихід (RELAY_ON=LOW / RELAY_OFF=HIGH)
D3 GPIO0 Кнопка скидання WiFi (вбудована FLASH-кнопка) вхід, INPUT_PULLUP
3.3V / GND — Живлення OpenTherm-адаптера та реле —
⚠️ D3 (GPIO0) зайнятий кнопкою WiFi-reset — не використовуйте його для реле чи інших периферійних пристроїв (це "strapping"-пін, критичний для завантаження ESP8266).
2. Загальна логіка роботи
setup()
├─ connectWiFi() — WiFiManager: підключення / точка доступу "Boiler"
├─ ot.begin(...) — ініціалізація бібліотеки OpenTherm
├─ pinMode(RELAYPUMP, OUTPUT) — реле в безпечний стан (вимкнено)
└─ mqtt.setServer/setCallback
loop() (виконується постійно)
├─ mqtt.loop() — обробка вхідних MQTT-повідомлень
├─ якщо минув OT_INTERVAL (1с) → otCycle() — обмін з котлом
│ └─ раз на кілька циклів → readExtParamsStep()
└─ якщо минув PUB_INTERVAL → publishState() — публікація стану в MQTT
3. Опис функцій
connectWiFi()
Піднімає WiFi через WiFiManager. Якщо при старті утримати кнопку WiFi-reset (D3/GPIO0) — скидає збережені налаштування мережі й відкриває точку доступу "Boiler" для повторного налаштування.
connectMQTT()
Підключається до брокера (goose.rmq2.cloudamqp.com), публікує online (LWT — offline при розриві), підписується на всі командні топіки (set/enable, set/temp, set/room_temp, pump/set, set/curve_mode) і одразу публікує поточні retained-стани (pump, curve_mode).
mqttCallback(topic, payload, length)
Єдина точка прийому команд від Delphi. За назвою топіка визначає, яке поле змінити:
Топік Дія
home/boiler/set/enable boiler.enabled = ...
home/boiler/set/temp boiler.setTemp (перевірка діапазону 20–80°C)
home/boiler/set/room_temp boiler.roomSetTemp (діапазон 5–30°C)
home/boiler/pump/set setPump(msg == "1")
home/boiler/set/curve_mode вмикає/вимикає режим «Крива опалення»
home/sensor/temp / temp_o кеш кімнатної/зовнішньої температури (для кривої)
setPump(bool on)
Керує реле насоса: digitalWrite(RELAYPUMP, ...), зберігає стан у pumpState, публікує підтверджений статус у home/boiler/pump (retained).
otCycle()
Головний цикл обміну з котлом по OpenTherm (виконується раз на секунду):
Встановлює статус (CH увімкнено/вимкнено, чи є помилка, чи горить пальник).
Розраховує й надсилає уставку температури теплоносія — вручну (boiler.setTemp) або автоматично за кривою (computeHeatingCurve), якщо активний curveMode.
Надсилає уставку кімнатної температури.
Читає поточну температуру подачі й обратки.
Читає код і прапорці несправності (ASF-флаги).
computeHeatingCurve(desired, in, out)
Погодозалежний розрахунок температури теплоносія за квадратичною апроксимацією класичного «температурного графіка», з поправками на бажану кімнатну температуру та фактичне відхилення термостата.
readExtParamsStep()
Читає розширені параметри OpenTherm по одному за виклик (не всі одразу), щоб не блокувати loop() на кілька секунд: тиск у системі (ID18), температура вихлопу, зовнішня температура котла (ID27), рівень модуляції пальника (ID17), макс./мін. потужність (ID15).
publishState()
Публікує повний стан котла в MQTT:
окремі топіки (temp, temp_return, flame, fault_code, fault_flags) — для швидкого доступу;
один великий JSON у home/boiler/status — всі поля разом (enabled, temp, set_temp, room_set, pump, розширені OpenTherm- параметри, curve_mode, curve_calc_temp).
handleInterrupt()
Апаратний обробник переривання OpenTherm-бібліотеки (ICACHE_RAM_ATTR — виконується з RAM для швидкодії, обов'язково для переривань на ESP8266).
4. MQTT-топіки (підсумково)
ESP → Delphi (публікує, retained): home/boiler/status (JSON), home/boiler/temp, home/boiler/temp_return, home/boiler/flame, home/boiler/pump, home/boiler/curve_mode, home/boiler/online
Delphi → ESP (команди, підписка): home/boiler/set/enable, home/boiler/set/temp, home/boiler/set/room_temp, home/boiler/pump/set, home/boiler/set/curve_mode




// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
ESP8266 «Котел» (opentherm_mqtt.ino) — документація
Плата виконує роль шлюзу: спілкується з котлом по протоколу OpenTherm, а з Delphi-застосунком — через MQTT (брокер CloudAMQP).
1. Підключення пінів (NodeMCU)
Пін GPIO Призначення Напрямок
D7 GPIO13 OpenTherm-адаптер — IN вхід (переривання)
D8 GPIO15 OpenTherm-адаптер — OUT вихід
D5 GPIO14 Реле насоса вихід (RELAY_ON=LOW / RELAY_OFF=HIGH)
D3 GPIO0 Кнопка скидання WiFi (вбудована FLASH-кнопка) вхід, INPUT_PULLUP
3.3V / GND — Живлення OpenTherm-адаптера та реле
2. Загальна логіка роботи
setup()
├─ connectWiFi() — WiFiManager: підключення / точка доступу "Boiler"
├─ ot.begin(...) — ініціалізація бібліотеки OpenTherm
├─ pinMode(RELAYPUMP, OUTPUT) — реле в безпечний стан (вимкнено)
└─ mqtt.setServer/setCallback
loop() (виконується постійно)
├─ mqtt.loop() — обробка вхідних MQTT-повідомлень
├─ якщо минув OT_INTERVAL (1с) → otCycle() — обмін з котлом
│ └─ раз на кілька циклів → readExtParamsStep()
3. Опис функцій
connectWiFi()
Піднімає WiFi через WiFiManager. Якщо при старті утримати кнопку WiFi-reset (D3/GPIO0) — скидає збережені налаштування мережі й відкриває точку доступу "Boiler" для повторного налаштування.
connectMQTT()
Підключається до брокера (goose.rmq2.cloudamqp.com), публікує online (LWT — offline при розриві), підписується на всі командні топіки (set/enable, set/temp, set/room_temp, pump/set, set/curve_mode) і одразу публікує поточні retained-стани (pump, curve_mode).
mqttCallback(topic, payload, length)
Єдина точка прийому команд від Delphi. За назвою топіка визначає, яке поле змінити:
Топік Дія
home/boiler/set/enable boiler.enabled = ...
home/boiler/set/temp boiler.setTemp (перевірка діапазону 20–80°C)
home/boiler/set/room_temp boiler.roomSetTemp (діапазон 5–30°C)
home/boiler/pump/set setPump(msg == "1")
home/boiler/set/curve_mode вмикає/вимикає режим «Крива опалення»
home/sensor/temp / temp_o кеш кімнатної/зовнішньої температури (для кривої)
setPump(bool on)
Керує реле насоса: digitalWrite(RELAYPUMP, ...), зберігає стан у pumpState, публікує підтверджений статус у home/boiler/pump (retained).
otCycle()
Головний цикл обміну з котлом по OpenTherm (виконується раз на секунду):
Встановлює статус (CH увімкнено/вимкнено, чи є помилка, чи горить пальник).
Розраховує й надсилає уставку температури теплоносія — вручну (boiler.setTemp) або автоматично за кривою (computeHeatingCurve), якщо активний curveMode.
Надсилає уставку кімнатної температури.
Читає поточну температуру подачі й обратки.
Читає код і прапорці несправності (ASF-флаги).
computeHeatingCurve(desired, in, out)
Погодозалежний розрахунок температури теплоносія за квадратичною апроксимацією класичного «температурного графіка», з поправками на бажану кімнатну температуру та фактичне відхилення термостата.
readExtParamsStep()
Читає розширені параметри OpenTherm по одному за виклик (не всі одразу), щоб не блокувати loop() на кілька секунд: тиск у системі (ID18), температура вихлопу, зовнішня температура котла (ID27), рівень модуляції пальника (ID17), макс./мін. потужність (ID15).
publishState()
Публікує повний стан котла в MQTT:
окремі топіки (temp, temp_return, flame, fault_code, fault_flags) — для швидкого доступу;
один великий JSON у home/boiler/status — всі поля разом (enabled, temp, set_temp, room_set, pump, розширені OpenTherm- параметри, curve_mode, curve_calc_temp).
handleInterrupt()
Апаратний обробник переривання OpenTherm-бібліотеки (ICACHE_RAM_ATTR — виконується з RAM для швидкодії, обов'язково для переривань на ESP8266).
4. MQTT-топіки (підсумково)
ESP → Delphi (публікує, retained): home/boiler/status (JSON), home/boiler/temp, home/boiler/temp_return, home/boiler/flame, home/boiler/pump, home/boiler/curve_mode, home/boiler/online
Delphi → ESP (команди, підписка): home/boiler/set/enable, home/boiler/set/temp, home/boiler/set/room_temp, home/boiler/pump/set, home/boiler/set/curve_mode
Прикладний код:
unit OTTypes;
{ ============================================================
Типы и топики для OpenTherm MQTT проекта
Delphi 13 Florence — FMX Android
============================================================ }
interface
uses
System.SysUtils, System.Generics.Collections;
const
TOPIC_SENSOR_TEMPDHT= 'home/sensor/temperature';
TOPIC_SENSOR_HUM = 'home/sensor/humidity';
TOPIC_SENSOR_TEMPDHT_O= 'home/sensor/temperature_o';
TOPIC_SENSOR_HUM_O = 'home/sensor/humidity_o';
TOPIC_SENSOR_TEMPDS = 'home/sensor/temperature_ds';
TOPIC_SENSOR_STATUS = 'home/sensor/status';
//Топики датчика газа MQ-2 ---
TOPIC_SENSOR_GAS = 'home/sensor/gas'; // сырое значение АЦП (0-1023)
TOPIC_SENSOR_GAS_ALARM = 'home/sensor/gas_alarm'; // 0/1 - превышен порог
// ---- Лампа (реле на 2-му ESP8266, тому ж, що і DHT) ----
TOPIC_SENSOR_LAMP = 'home/sensor/lamp'; // READ — ESP публікує стан
TOPIC_SENSOR_LAMP_SET = 'home/sensor/lamp/set'; // WRITE — Delphi надсилає команду
// ---- Насос (реле на 2-му ESP8266, тому ж, що і DHT) ----
TOPIC_SENSOR_PUMP = 'home/sensor/pump'; // READ — ESP публікує стан
TOPIC_SENSOR_PUMP_SET= 'home/sensor/pump/set'; // WRITE — Delphi надсилає команду
// ---- Режим (термостатopenTherm ESP8266, тому ж, що і ) ----
TOPIC_SENSOR_MODEON = 'home/sensor/modeon'; // READ — ESP публікує стан
TOPIC_SENSOR_MODEON_SET = 'home/sensor/modeon/set'; // WRITE — Delphi надсилає команду
// ---- MQTT топики (READ — ESP публикует) ----
TOPIC_STATUS = 'home/boiler/status';
TOPIC_TEMP = 'home/boiler/temp';
TOPIC_TEMP_RETURN = 'home/boiler/temp_return';
TOPIC_FLAME = 'home/boiler/flame';
TOPIC_ONLINE = 'home/boiler/online';
TOPIC_FAULT_CODE = 'home/boiler/fault_code'; // OEM fault code (0-255), напр. 38
TOPIC_FAULT_FLAGS = 'home/boiler/fault_flags'; // біти ASF (сервіс/блок./тиск/полум'я/тяга/перегрів)
TOPIC_BOILER_PUMP = 'home/boiler/pump'; // READ — ESP публікує стан
TOPIC_BOILER_PUMP_SET = 'home/boiler/pump/set'; // WRITE — Delphi надсилає команду
// ---- MQTT топики (WRITE — Delphi пишет) ----
TOPIC_SET_ENABLE = 'home/boiler/set/enable';
TOPIC_SET_TEMP = 'home/boiler/set/temp';
TOPIC_SET_ROOM = 'home/boiler/set/room_temp';
// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
TOPIC_SET_CURVE_MODE = 'home/boiler/set/curve_mode'; // WRITE — 0/1
TOPIC_CURVE_MODE = 'home/boiler/curve_mode'; // READ — поточний стан (retained)
// ---- Лимиты ----
TEMP_BOILER_MIN = 20.0;
TEMP_BOILER_MAX = 80.0;
TEMP_ROOM_MIN = 5.0;
TEMP_ROOM_MAX = 30.0;
// ---- Термостат кімнати (гістерезис) ----
// Котел вимикається, коли кімнатна темп. (з DHT, ESP8266 №2) досягає
// уставки (RoomSet), і вмикається знову, коли впаде на цю величину
// нижче уставки. Без гістерезису котел клацав би вкл/викл щосекунди
// на самій межі уставки.
OnOffTempBoiler = 1.5;
// ---- График — максимум точек в истории ----
HISTORY_MAX_POINTS = 60; // 60 точек × 5 сек = 5 минут истории
type
{ Запись истории температуры }
TTemperaturePoint = record
Time : TDateTime;
Value : Double;
end;
{ Полное состояние котла }
TBoilerState = record
Online : Boolean;
end;
{ Полное состояние датчики та реле }
TSensorRelayState = record
end;
{ Расписание — одна запись }
TScheduleEntry = record
DayOfWeek : Integer; // 1=Пн .. 7=Вс
TimeStart : TTime;
TempTarget : Double;
Enabled : Boolean;
end;
TScheduleList = TList<TScheduleEntry>;
{ Утилиты }
function FerroliFaultText(ACode: Integer): string;
implementation
uses
System.JSON;
procedure TBoilerState.Reset;
begin
Online := False;
OutsideTempSource := 'off';
end;
procedure TSensorRelayState.Reset;
begin
end;
function TempToColor(ATemp: Double): Cardinal;
begin
if ATemp < 30 then
Result := $FF4FC3F7 // синий
else if ATemp < 50 then
Result := $FF81C784 // зелёный
else if ATemp < 65 then
Result := $FFFFB74D // оранжевый
else
Result := $FFEF5350; // красный
end;
function FormatTemp(ATemp: Double): string;
begin
Result := FormatFloat('0.0', ATemp);
end;
function BoilerStateFromJSON(const AJSON: string): TBoilerState;
var
J: TJSONObject;
V: TJSONValue;
begin
Result.Reset;
J := TJSONObject.ParseJSONValue(AJSON) as TJSONObject;
if J = nil then Exit;
try
// ---- Крива опалення ----
if J.TryGetValue('curve_mode', V) then Result.CurveMode := V.GetValue<Boolean>;
if J.TryGetValue('curve_calc_temp', V) then Result.CurveCalcTemp := V.GetValue<Double>;
if J.TryGetValue('outside_temp_source', V) then Result.OutsideTempSource := V.GetValue<string>;
Result.LastUpdate := Now;
finally
J.Free;
end;
end;
function SensorRelayFromJSON(const AJSON: string): TSensorRelayState;
var
J: TJSONObject;
V: TJSONValue;
begin
Result.Reset;
J := TJSONObject.ParseJSONValue(AJSON) as TJSONObject;
if J = nil then Exit;
try
if J.TryGetValue('temperature', V) then Result.temperature := V.GetValue<Double>;
finally
J.Free;
end;
end;
// ------------------------------------------------------------------
// ASF flags (Data-ID 5, старший байт) — стандартні OpenTherm біти,
// однакові для будь-якого котла.
// ------------------------------------------------------------------
function ASFFlagsText(AFlags: Integer): string;
var Parts: TArray<string>;
begin
SetLength(Parts, 0);
if (AFlags and $01) <> 0 then Parts := Parts + ['потрібне сервісне обслуговування'];
if Length(Parts) = 0 then
Result := ''
else
Result := string.Join(', ', Parts);
end;
// ------------------------------------------------------------------
// OEM fault code (Data-ID 5, молодший байт) → текст помилки Ferroli.
//
// !!! УВАГА: OpenTherm гарантує лише те, що котёл віддає своє власне
// (виробниче) число коду помилки в цьому байті — специфікація НЕ
// визначає, що це число обов'язково збігається з тим "Fxx", яке котёл
// друкує на дисплеї. У більшості виробників (в т.ч. зазвичай Ferroli)
// це число дійсно дорівнює номеру з дисплея (тобто код 38 у цьому
// байті = "F38" на панелі), але це потрібно ОБОВ'ЯЗКОВО перевірити
// на своєму котлі: спровокуйте (чи дочекайтесь) відому помилку і
// звірте faultCode із MQTT з тим, що показує панель котла.
//
// Таблицю нижче потрібно доповнити з сервісного мануалу саме на
// Ferroli Bluesens 4 (розділ "Codici di errore" / "Fault codes") —
// нижче лише каркас із кількома типовими для газових котлів кодами.
// ------------------------------------------------------------------
function FerroliFaultText(ACode: Integer): string;
begin
case ACode of
0: Result := 'Помилок немає';
04: Result := 'Проблемы с дымоходом Детальніше';
05: Result := 'Неисправность вентилятор Детальніше';
08: Result := 'Перегрев теплообменника Детальніше';
10: Result := 'Несправність датчика температури подачі (NTC)';
11: Result := 'Неисправность датчика температуры ГВС';
12: Result := 'Несправність датчика тиску води / пресостата';
13: Result := 'Недостатній тиск повітря (несправність димовідведення)';
14: Result := 'Неисправность датчика температуры на выходе ГВС';
20: Result := 'Несправність датчика зворотної води (NTC)';
34: Result := 'Низкое напряжение';
35: Result := 'Паразитне полум''я / несправність контролю полум''я';
37: Result := 'Низкое давление в системе отопления';
38: Result := 'Втрата полум''я під час роботи';
39: Result := 'Потеря связи с выносной метеостанцией';
40: Result := 'Низький тиск води в системі опалення';
41: Result := 'Перегрів теплоносія / спрацював запобіжний термостат';
else
Result := Format('Код помилки %d — див. сервісний мануал Ferroli Bluesens', [ACode]);
end;
end;
end.




ESP8266 «Котел» (opentherm_mqtt.ino) — документація
Плата виконує роль шлюзу: спілкується з котлом по протоколу OpenTherm, а з Delphi-застосунком — через MQTT (брокер CloudAMQP).
// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
1. Підключення пінів (NodeMCU)
Пін GPIO Призначення Напрямок
D7 GPIO13 OpenTherm-адаптер — IN вхід (переривання)
D8 GPIO15 OpenTherm-адаптер — OUT вихід
D5 GPIO14 Реле насоса вихід (RELAY_ON=LOW / RELAY_OFF=HIGH)
D3 GPIO0 Кнопка скидання WiFi (вбудована FLASH-кнопка) вхід, INPUT_PULLUP
3.3V / GND — Живлення OpenTherm-адаптера та реле
2. Загальна логіка роботи
setup()
├─ connectWiFi() — WiFiManager: підключення / точка доступу "Boiler"
├─ ot.begin(...) — ініціалізація бібліотеки OpenTherm
├─ pinMode(RELAYPUMP, OUTPUT) — реле в безпечний стан (вимкнено)
└─ mqtt.setServer/setCallback
loop() (виконується постійно)
├─ mqtt.loop() — обробка вхідних MQTT-повідомлень
├─ якщо минув OT_INTERVAL (1с) → otCycle() — обмін з котлом
│ └─ раз на кілька циклів → readExtParamsStep()
3. Опис функцій
connectWiFi()
Піднімає WiFi через WiFiManager. Якщо при старті утримати кнопку WiFi-reset (D3/GPIO0) — скидає збережені налаштування мережі й відкриває точку доступу "Boiler" для повторного налаштування.
connectMQTT()
Підключається до брокера (goose.rmq2.cloudamqp.com), публікує online (LWT — offline при розриві), підписується на всі командні топіки (set/enable, set/temp, set/room_temp, pump/set, set/curve_mode) і одразу публікує поточні retained-стани (pump, curve_mode).
mqttCallback(topic, payload, length)
Єдина точка прийому команд від Delphi. За назвою топіка визначає, яке поле змінити:
Топік Дія
home/boiler/set/enable boiler.enabled = ...
home/boiler/set/temp boiler.setTemp (перевірка діапазону 20–80°C)
home/boiler/set/room_temp boiler.roomSetTemp (діапазон 5–30°C)
home/boiler/pump/set setPump(msg == "1")
home/boiler/set/curve_mode вмикає/вимикає режим «Крива опалення»
home/sensor/temp / temp_o кеш кімнатної/зовнішньої температури (для кривої)
setPump(bool on)
Керує реле насоса: digitalWrite(RELAYPUMP, ...), зберігає стан у pumpState, публікує підтверджений статус у home/boiler/pump (retained).
otCycle()
Головний цикл обміну з котлом по OpenTherm (виконується раз на секунду):
Встановлює статус (CH увімкнено/вимкнено, чи є помилка, чи горить пальник).
Розраховує й надсилає уставку температури теплоносія — вручну (boiler.setTemp) або автоматично за кривою (computeHeatingCurve), якщо активний curveMode.
Надсилає уставку кімнатної температури.
Читає поточну температуру подачі й обратки.
Читає код і прапорці несправності (ASF-флаги).
computeHeatingCurve(desired, in, out)
Погодозалежний розрахунок температури теплоносія за квадратичною апроксимацією класичного «температурного графіка», з поправками на бажану кімнатну температуру та фактичне відхилення термостата.
readExtParamsStep()
Читає розширені параметри OpenTherm по одному за виклик (не всі одразу), щоб не блокувати loop() на кілька секунд: тиск у системі (ID18), температура вихлопу, зовнішня температура котла (ID27), рівень модуляції пальника (ID17), макс./мін. потужність (ID15).
publishState()
Публікує повний стан котла в MQTT:
окремі топіки (temp, temp_return, flame, fault_code, fault_flags) — для швидкого доступу;
один великий JSON у home/boiler/status — всі поля разом (enabled, temp, set_temp, room_set, pump, розширені OpenTherm- параметри, curve_mode, curve_calc_temp).
handleInterrupt()
Апаратний обробник переривання OpenTherm-бібліотеки (ICACHE_RAM_ATTR — виконується з RAM для швидкодії, обов'язково для переривань на ESP8266).
4. MQTT-топіки (підсумково)
ESP → Delphi (публікує, retained): home/boiler/status (JSON), home/boiler/temp, home/boiler/temp_return, home/boiler/flame, home/boiler/pump, home/boiler/curve_mode, home/boiler/online
Delphi → ESP (команди, підписка): home/boiler/set/enable, home/boiler/set/temp, home/boiler/set/room_temp, home/boiler/pump/set, home/boiler/set/curve_mode
прикладний код :
unit OTTypes;
{ ============================================================
Типы и топики для OpenTherm MQTT проекта
Delphi 13 Florence — FMX Android
============================================================ }
interface
uses
System.SysUtils, System.Generics.Collections;
const
TOPIC_SENSOR_TEMPDHT= 'home/sensor/temperature';
TOPIC_SENSOR_HUM = 'home/sensor/humidity';
TOPIC_SENSOR_TEMPDHT_O= 'home/sensor/temperature_o';
TOPIC_SENSOR_HUM_O = 'home/sensor/humidity_o';
TOPIC_SENSOR_TEMPDS = 'home/sensor/temperature_ds';
TOPIC_SENSOR_STATUS = 'home/sensor/status';
//Топики датчика газа MQ-2 ---
TOPIC_SENSOR_GAS = 'home/sensor/gas'; // сырое значение АЦП (0-1023)
TOPIC_SENSOR_GAS_ALARM = 'home/sensor/gas_alarm'; // 0/1 - превышен порог
// ---- Лампа (реле на 2-му ESP8266, тому ж, що і DHT) ----
TOPIC_SENSOR_LAMP = 'home/sensor/lamp'; // READ — ESP публікує стан
TOPIC_SENSOR_LAMP_SET = 'home/sensor/lamp/set'; // WRITE — Delphi надсилає команду
// ---- Насос (реле на 2-му ESP8266, тому ж, що і DHT) ----
TOPIC_SENSOR_PUMP = 'home/sensor/pump'; // READ — ESP публікує стан
TOPIC_SENSOR_PUMP_SET= 'home/sensor/pump/set'; // WRITE — Delphi надсилає команду
// ---- Режим (термостатopenTherm ESP8266, тому ж, що і ) ----
TOPIC_SENSOR_MODEON = 'home/sensor/modeon'; // READ — ESP публікує стан
TOPIC_SENSOR_MODEON_SET = 'home/sensor/modeon/set'; // WRITE — Delphi надсилає команду
// ---- MQTT топики (READ — ESP публикует) ----
TOPIC_STATUS = 'home/boiler/status';
TOPIC_TEMP = 'home/boiler/temp';
TOPIC_TEMP_RETURN = 'home/boiler/temp_return';
TOPIC_FLAME = 'home/boiler/flame';
TOPIC_ONLINE = 'home/boiler/online';
TOPIC_FAULT_CODE = 'home/boiler/fault_code'; // OEM fault code (0-255), напр. 38
TOPIC_FAULT_FLAGS = 'home/boiler/fault_flags'; // біти ASF (сервіс/блок./тиск/полум'я/тяга/перегрів)
TOPIC_BOILER_PUMP = 'home/boiler/pump'; // READ — ESP публікує стан
TOPIC_BOILER_PUMP_SET = 'home/boiler/pump/set'; // WRITE — Delphi надсилає команду
// ---- MQTT топики (WRITE — Delphi пишет) ----
TOPIC_SET_ENABLE = 'home/boiler/set/enable';
TOPIC_SET_TEMP = 'home/boiler/set/temp';
TOPIC_SET_ROOM = 'home/boiler/set/room_temp';
// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
TOPIC_SET_CURVE_MODE = 'home/boiler/set/curve_mode'; // WRITE — 0/1
TOPIC_CURVE_MODE = 'home/boiler/curve_mode'; // READ — поточний стан (retained)
// ---- Лимиты ----
TEMP_BOILER_MIN = 20.0;
TEMP_BOILER_MAX = 80.0;
TEMP_ROOM_MIN = 5.0;
TEMP_ROOM_MAX = 30.0;
// ---- Термостат кімнати (гістерезис) ----
// Котел вимикається, коли кімнатна темп. (з DHT, ESP8266 №2) досягає
// уставки (RoomSet), і вмикається знову, коли впаде на цю величину
// нижче уставки. Без гістерезису котел клацав би вкл/викл щосекунди
// на самій межі уставки.
OnOffTempBoiler = 1.5;
// ---- График — максимум точек в истории ----
HISTORY_MAX_POINTS = 60; // 60 точек × 5 сек = 5 минут истории
type
{ Запись истории температуры }
TTemperaturePoint = record
Time : TDateTime;
Value : Double;
end;
{ Полное состояние котла }
TBoilerState = record
Online : Boolean;
Enabled : Boolean;
Flame : Boolean;
CHActive : Boolean;
Fault : Boolean;
FaultCode : Integer; // OEM fault code (0-255) — те саме число, що котёл
// показує на дисплеї як Fxx (0 = помилки немає)
FaultFlags : Integer; // байт ASF-флагів, див. ASFFlagsText()
TempCurrent : Double; // текущая темп. котла
TempReturn : Double; // темп. обратки
TempSet : Double; // уставка котла
RoomSet : Double; // уставка комнаты
pump : Boolean; // стан насос (реле на тому ж ESP8266, що насос)
LastUpdate : TDateTime;
// ---- Додаткові OpenTherm Data-ID (читаються рідше, раз в ~10-15 сек) ----
ChPressure : Double; // ID18 — тиск води в системі опалення, бар
TExhaust : Double; // ID33 — температура вихлопних газів котла, °C
TOutside : Double; // ID27 — зовнішня температура (датчик на котлі), °C
HasOutsideSensor: Boolean; // чи котёл дав валідну відповідь на ID27
RelModLevel : Double; // ID17 — відносний рівень модуляції пальника, %
MaxTSetUB : Double; // ID50 — верхня межа кривої нагріву (uboundary), °C
MaxTSetLB : Double; // ID50 — нижня межа кривої нагріву (lboundary), °C
MaxCapacity : Double; // ID15 — максимальна потужність котла, кВт
MinModLevel : Double; // ID15 — мінімальний рівень модуляції котла, %
MaxTSetLimit : Double; // ID57 — максимальна установка температури котла, °C
ExtParamsUpdate : TDateTime; // час останнього оновлення додаткових параметрів
// ---- Режим "Крива опалення" (погодозалежне регулювання) ----
CurveMode : Boolean; // True = котёл сам рахує температуру теплоносія за кривою
CurveCalcTemp : Double; // розрахована за кривою температура контуру опалення, °C
OutsideTempSource : string; // 'id27' | 'external' | 'none' | 'off' — реальне джерело зовн. темп.
procedure Reset;
end;
{ Полное состояние датчики та реле }
TSensorRelayState = record
temperature : Double; // кімнатна темп. з DHT (окремий ESP8266 №1)
Humidity : Double; // вологість з DHT
temperature_o : Double; // кімнатна темп. з DHT (окремий ESP8266 №1)
Humidity_o : Double; // вологість з DHT
Gas : Double; // газ
temperature_ds : Double; // ds18b20 температура
SensorOnline: Boolean; // онлайн-статус датчика (окремий від котла)
LampOn : Boolean; // стан лампи (реле на тому ж ESP8266, що DHT)
pumpOn : Boolean; // стан насос (реле на тому ж ESP8266, що насос)
ModeOn : Boolean; // стан режим (реле на тому ж ESP8266, що режим насос)
gas_alarm : Boolean;
LastUpdate : TDateTime;
procedure Reset;
end;
{ Расписание — одна запись }
TScheduleEntry = record
DayOfWeek : Integer; // 1=Пн .. 7=Вс
TimeStart : TTime;
TempTarget : Double;
Enabled : Boolean;
end;
TScheduleList = TList<TScheduleEntry>;
{ Утилиты }
function ASFFlagsText(AFlags: Integer): string;
function FerroliFaultText(ACode: Integer): string;
implementation
uses
System.JSON;
procedure TBoilerState.Reset;
begin
Online := False;
Enabled := False;
Flame := False;
CHActive := False;
Fault := False;
pump := False;
FaultCode := 0;
FaultFlags := 0;
TempCurrent := 0;
TempReturn := 0;
TempSet := 60;
RoomSet := 21;
LastUpdate := 0;
ChPressure := 0;
TExhaust := 0;
TOutside := 0;
HasOutsideSensor:= False;
RelModLevel := 0;
MaxTSetUB := 0;
MaxTSetLB := 0;
MaxCapacity := 0;
MinModLevel := 0;
MaxTSetLimit := 0;
ExtParamsUpdate := 0;
CurveMode := False;
CurveCalcTemp := 0;
OutsideTempSource := 'off';
end;
procedure TSensorRelayState.Reset;
begin
end;
function TempToColor(ATemp: Double): Cardinal;
begin
end;
function FormatTemp(ATemp: Double): string;
begin
Result := FormatFloat('0.0', ATemp);
end;
function BoilerStateFromJSON(const AJSON: string): TBoilerState;
var
J: TJSONObject;
V: TJSONValue;
begin
Result.Reset;
J := TJSONObject.ParseJSONValue(AJSON) as TJSONObject;
if J = nil then Exit;
try
if J.TryGetValue('enabled', V) then Result.Enabled := V.GetValue<Boolean>;
// ---- Додаткові OpenTherm параметри (ID 18,33,27,17,50,15,57) ----
if J.TryGetValue('ext_params_valid', V) and V.GetValue<Boolean> then
Result.ExtParamsUpdate := Now;
// ---- Крива опалення ----
if J.TryGetValue('curve_mode', V) then Result.CurveMode := V.GetValue<Boolean>;
if J.TryGetValue('curve_calc_temp', V) then Result.CurveCalcTemp := V.GetValue<Double>;
if J.TryGetValue('outside_temp_source', V) then Result.OutsideTempSource := V.GetValue<string>;
Result.LastUpdate := Now;
finally
J.Free;
end;
end;
function SensorRelayFromJSON(const AJSON: string): TSensorRelayState;
var
J: TJSONObject;
V: TJSONValue;
begin
Result.Reset;
J := TJSONObject.ParseJSONValue(AJSON) as TJSONObject;
if J = nil then Exit;
try
if J.TryGetValue('temperature', V) then Result.temperature := V.GetValue<Double>;
if J.TryGetValue('gas', V) then Result.gas := V.GetValue<Double>;
if J.TryGetValue('temperature_ds', V) then Result.temperature_ds:= V.GetValue<Double>;
if J.TryGetValue('temperature_o', V) then Result.temperature_o := V.GetValue<Double>;
if J.TryGetValue('status', V) then Result.SensorOnline:= V.GetValue<Boolean>;
if J.TryGetValue('lamp', V) then Result.LampOn := V.GetValue<Boolean>;
if J.TryGetValue('pumpon', V) then Result.pumpOn := V.GetValue<Boolean>;
if J.TryGetValue('modeon', V) then Result.ModeOn := V.GetValue<Boolean>;
if J.TryGetValue('gas_alarm', V) then Result.gas_alarm := V.GetValue<Boolean>;
Result.LastUpdate := Now;
finally
J.Free;
end;
end;
// ------------------------------------------------------------------
// ASF flags (Data-ID 5, старший байт) — стандартні OpenTherm біти,
// однакові для будь-якого котла.
// ------------------------------------------------------------------
function ASFFlagsText(AFlags: Integer): string;
var Parts: TArray<string>;
begin
SetLength(Parts, 0);
if (AFlags and $01) <> 0 then Parts := Parts + ['потрібне сервісне обслуговування'];
if (AFlags and $02) <> 0 then Parts := Parts + ['блокування (потрібен ручний скид)'];
if (AFlags and $04) <> 0 then Parts := Parts + ['низький тиск води'];
if (AFlags and $08) <> 0 then Parts := Parts + ['несправність полум''я/газу'];
if (AFlags and $10) <> 0 then Parts := Parts + ['несправність тяги/повітря'];
if (AFlags and $20) <> 0 then Parts := Parts + ['перегрів води'];
if Length(Parts) = 0 then
Result := ''
else
Result := string.Join(', ', Parts);
end;
// ------------------------------------------------------------------
// OEM fault code (Data-ID 5, молодший байт) → текст помилки Ferroli.
//
// !!! УВАГА: OpenTherm гарантує лише те, що котёл віддає своє власне
// (виробниче) число коду помилки в цьому байті — специфікація НЕ
// визначає, що це число обов'язково збігається з тим "Fxx", яке котёл
// друкує на дисплеї. У більшості виробників (в т.ч. зазвичай Ferroli)
// це число дійсно дорівнює номеру з дисплея (тобто код 38 у цьому
// байті = "F38" на панелі), але це потрібно ОБОВ'ЯЗКОВО перевірити
// на своєму котлі: спровокуйте (чи дочекайтесь) відому помилку і
// звірте faultCode із MQTT з тим, що показує панель котла.
//
// Таблицю нижче потрібно доповнити з сервісного мануалу саме на
// Ferroli Bluesens 4 (розділ "Codici di errore" / "Fault codes") —
// нижче лише каркас із кількома типовими для газових котлів кодами.
// ------------------------------------------------------------------
function FerroliFaultText(ACode: Integer): string;
begin
case ACode of
0: Result := 'Помилок немає';
04: Result := 'Проблемы с дымоходом Детальніше';
05: Result := 'Неисправность вентилятор Детальніше';
08: Result := 'Перегрев теплообменника Детальніше';
10: Result := 'Несправність датчика температури подачі (NTC)';
11: Result := 'Неисправность датчика температуры ГВС';
12: Result := 'Несправність датчика тиску води / пресостата';
13: Result := 'Недостатній тиск повітря (несправність димовідведення)';
14: Result := 'Неисправность датчика температуры на выходе ГВС';
20: Result := 'Несправність датчика зворотної води (NTC)';
34: Result := 'Низкое напряжение';
35: Result := 'Паразитне полум''я / несправність контролю полум''я';
37: Result := 'Низкое давление в системе отопления';
38: Result := 'Втрата полум''я під час роботи';
39: Result := 'Потеря связи с выносной метеостанцией';
40: Result := 'Низький тиск води в системі опалення';
41: Result := 'Перегрів теплоносія / спрацював запобіжний термостат';
else
Result := Format('Код помилки %d — див. сервісний мануал Ferroli Bluesens', [ACode]);
end;
end;
end.
Сторінки 1