Керування Vaillant (BAI, без кімнатного регулятора) через ebusd + Home Assistant — досвід і кілька знахідок
Ділюсь власною збіркою — можливо, комусь стане в пригоді, і заодно кілька технічних знахідок, яких не бачив у цій темі раніше.
Залізо і топологія
- Одноплатник на Armbian (домашній сервер, все на Docker).
- Котел — Vaillant BAI (atmoTEC/turboTEC-класу, проточний, без бака-накопичувача — важливо для подальшого).
- До шини підключений не USB-адаптер, а eBUS-to-LAN шлюз (TCP, окрема залізка на власній IP в локалці), тому в ebusd вказую --device=ens:свій іп замість звичного /dev/ttyUSB0.
- Кімнатного регулятора (Calormatic/VRT) немає взагалі — керування повністю "мозком" з боку сервера.
ebusd (docker, john30/ebusd:latest)
Робочий рядок запуску (те, що реально стабільно крутиться):
docker run -d --name ebusd --restart unless-stopped --net=host \
-v /opt/ebusd/config:/etc/ebusd/config \
john30/ebusd:latest \
--device=ens:192.168.31.239:9999 \
--scanconfig \
--configpath=https://ebus.github.io/ \
--latency=50000 \
--pollinterval=5 \
--mqtthost=127.0.0.1 --mqttport=1883 --mqttjson \
--mqtttopic=ebusd --mqttclientid=ebusd \
--mqttchanges --mqttretain \
--mqttint=/etc/ebusd/config/mqtt-hassio.cfg \
--mqttverbose --mqttvar=filter-direction=r\|u\|^w \
--httpport=8889 --enabledefine \
--accesslevel=install
Ключове: --accesslevel=install — без цього запис (SetMode) на котел мовчки не проходив, хоча читання працювало. Це та поправка, яка нарешті "оживила" запис.
--mqttint=.../mqtt-hassio.cfg дає одразу MQTT autodiscovery в Home Assistant — влітає ~1450+ ентіті без жодної ручної роботи.
Котел на шині коректно ідентифікується:
address 08: slave #11, scanned "MF=Vaillant;ID=BAI00;SW=5011;HW=1303", loaded "vaillant/bai.308523.inc", "vaillant/08.bai.csv"
Автоматика: setmode-sync.py (systemd-сервіс, не таймер!)
Спочатку я планував "таймер systemd раз на 60с запускає скрипт", але зрештою прийшов до кращої схеми: один персистентний Python-процес з власним while True циклом і адаптивним інтервалом:
- 2 секунди, поки є активність (горить пальник, або є витрата ГВП, або хоч один тумблер керування увімкнений);
- 60 секунд, коли все в спокої.
Логіка вибору джерела температури подачі (пріоритет зверху вниз):
1. Ручний тумблер — бере значення прямо з input_number повзунка в HA.
2. Passthrough (за замовчуванням) — просто зчитує поточне FlowTempDesired/StorageTempDesired з самого котла й пише те саме значення назад. Це потрібно, бо (як і в темі вже писали) котел "забуває" SetMode десь за кілька хвилин, якщо йому не нагадувати — тож навіть коли нічого змінювати не треба, скрипт все одно підтверджує поточне значення.
Формат payload, який летить у MQTT (ebusd/bai/SetMode/set):
auto;{flowtemp};{hwctemp};{hwctemp};0;0;0;0;0;0;0;0
Раніше в скрипті була ще й погодозалежна крива (формула на основі вуличної температури з Open-Meteo API), схожа на ту, що зустрічав тут silvan (2021) — але в мене виявилась мертвим кодом (тумблер для неї видалили з дашборда, а логіка лишилась), тому нещодавно прибрав.
Юніт (для тих, хто зустрічав такий самий патерн з таймером — не потрібен):
[Unit]
Description=Sync desired boiler heating/DHW setpoints via ebus SetMode
After=docker.service mosquitto.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/bin/setmode-sync.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Restart=always тут повністю замінює будь-який зовнішній таймер — процес сам себе підтримує.
Знахідка 1 — installer/expert-параметри: не access-level, а, схоже, розбіжність CSV під точну прошивку
Пробував дістатись до FlowsetHwcMax (d.78), PartloadHwcKW (d.77), HwcTempMax (d.20), HcPumpMode, SecondPumpMode, ValveMode (d.70) — усі повертають ERR: invalid position in decode. Спочатку думав, що це через --accesslevel=install — підняв до --accesslevel=* (максимум) і перезапустив контейнер. Результат той самий: котел відповідає тим самим одним нульовим байтом, скільки не піднімай рівень у ebusd.
Покопав глибше й знайшов конкретнішу причину. У самому bai.308523.inc рівень доступу на читання цих полів взагалі порожній (не обмежений!) — обмежений лише запис (install, який у нас якраз є):
r,,,PartloadHwcKW,d.77 ... ← читання: рівень не вказано
w,,install,PartloadHwcKW,d.77 ... ← запис: install
Тобто справа не в access-level. Дістав точний product ID нашого котла з шини:
ebusctl scan 08
→ 08;Vaillant;BAI00;5011;1303;21;21;39;0010026149;3100;005218;N5
Product ID 0010026149, SW=5011, HW=1303. Звірив із vaillant/08.bai.csv — там ~20 умовних правил [Scan_id_product='...']!load,bai.XXXXXXX.inc під конкретні списки product ID. Перевірив повний список ID у правилі, яке реально підхопив наш ebusd (bai.308523.inc):
0010004276, 0010004277, 0010004279-4283, 0010004285-4292,
0010004336-4340, 0010005466-5469, 0010010392-10394, 0010010400,
0020051714-0020051717, 174
Нашого 0010026149 там немає (і в сусідніх правилах теж немає) — котел підхопив цей файл через останнє безумовне правило-заглушку в кінці CSV, а не через справжній збіг. Тобто під нашу точну прошивку в публічному репозиторії конфігурацій просто немає окремого файлу.
Про всяк випадок перевірив і гіпотезу "може, дані з'являються лише під час активного розбору ГВП" — опитував ці поля кожні 4с ~2 хв, поки реально текла гаряча вода (пальник підтверджено горів 18 з 20 замірів). Без змін — та сама помилка незалежно від стану пальника.
Підсумок: найімовірніше пояснення зараз — розбіжність формату повідомлення саме для нашої ревізії заліза/прошивки, а не апаратний захист рівня доступу, як я думав спочатку. Але остаточно підтвердити це можна тільки маючи точний CSV під 0010026149/SW=5011/HW=1303 — якщо у когось є такий, чи є спосіб його дістати/згенерувати — дуже цікаво.
Знахідка 2 — перегрів ГВП при малому потоці (миття рук vs миття посуду)
Реальний кейс: при повному відкритті крана (миття посуду) все нормально, а при слабкому струмені (миття рук) вода раптово стає обпікаючо гарячою, пальник ніби "смикається".
Розібрав по історії Status01.temp (первинний контур):
- фон ~40-44°C у спокої;
- при слабкому потоці стрибок аж до 80.5°C — саме в ту секунду насос перейшов у overrun (пост-охолодження після аварійного вимкнення пальника).
- лічильник TempDiffFailure (спрацювання захисту від різниці температур) = 190 за весь час роботи котла — тобто це хронічно, не разовий збій.
Причина: мінімальна потужність пальника цього котла (немодулюючий/слабко модулюючий) все одно більша, ніж треба для маленького потоку → первинний контур перегрівається швидше, ніж встигає стекти вода → спрацьовує захист → пальник аварійно вимикається-вмикається → "пробка" перегрітої води долітає до крана.
Програмного рішення нема (бо все, що могло б це пом'якшити — PartloadHwcKW, FlowsetHwcMax — з тієї ж installer-категорії, див. вище). Реальне рішення — термостатичний антиопіковий змішувальний клапан (½", під ваш цільовий діапазон температур, наприклад ESBE VTA322 20-43°C) на виході ГВП з котла, до розгалуження по кранах.
Корисні ентіті для тих, хто теж на BAI-серії
- Status01 — комбінований пакет: flow/return/outdoor/hotwater/storage температури + стан насоса (off/on/overrun/hwc) в одному повідомленні.
- HwcImpellorSwitch — датчик протоку ГВП (0/1), зручно для детекції "хтось відкрив кран".
- TempDiffFailure / TempGradientFailure — лічильники спрацювань захисту, добрий індикатор хронічних проблем.
- StorageTemp показує -13.50;cutoff, якщо бака немає фізично — зручний спосіб програмно перевірити тип встановлення (проточний/з баком).
---
Якщо цікаво — можу викласти повний setmode-sync.py без комерційної/особистої частини (шляхи, IP приберу). Питання і критика вітаються, особливо якщо хтось впізнає product ID 0010026149 чи має точний CSV/сервісну документацію під SW=5011/HW=1303.