[2026-09-14] eagle: family/how-to/t610-access.md family/plans/t610-addons-deployment.md

This commit is contained in:
Alexey Martemyanov
2026-09-14 13:31:42 +06:00
parent c002ac520b
commit 3c4c9eec5a
2 changed files with 90 additions and 9 deletions
+27 -5
View File
@@ -15,10 +15,11 @@
> - **`area_id`** (зона устройства) — теряется → проставить заново по эталону;
> - **ссылки на `entity_id`** в автоматизациях/дашбордах — не обновляются автоматически, даже если реестр переименован (грепать `[a-z_]+\.0x[0-9a-f]{16}` **везде**, включая `platform: state`-триггеры).
> Подробно: [[family/plans/t610-addons-deployment]] §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ».
> 🔑 **z2m не публикует состояние пассивно** — реле остаются `unknown` до первого события. Живость проверять по **`lastSeen`** в `database.db`. После рестарта HA — физически нажать кнопки.
> 🔑 **⚠️ `unknown` после рестарта HA ВОЗВРАЩАЕТСЯ — проверено экспериментом 2026-09-14.** Зафиксировано → `homeassistant.restart` → снято: всё, что было `on`/`off`, стало `unknown` (`office_table_light_switch`, `smart_light_office`, `heating_cable_plug`, `boiler_controller_power`). **`retain: true`, `cache_state`, `cache_state_persistent`, `cache_state_send_on_startup` в z2m стоят — но retained на топиках устройств не публикуется** (контроль: `zigbee2mqtt/bridge/state` retained **есть** → брокер умеет).
> ✅ **РЕШЕНИЕ (применено 2026-09-14): `not_from: [unavailable, unknown]` убран из триггеров кнопок** в `automations.yaml`. Первое нажатие срабатывает, проверено на живом. Бэкап `/config/automations.yaml.bak-20260914-131541`.
> 🔑 **Кэш состояний z2m лежит в `/homeassistant/zigbee2mqtt/state.json`** (НЕ в `/config/zigbee2mqtt` и не в `/addon_configs` — искать `find / -name state.json`). Плоский JSON `{ieee: {param: value}}`, пишется при каждом событии. Полное описание и таблица «до/после»: [[family/plans/t610-addons-deployment]] §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис.
> 🔑 **z2m не публикует состояние пассивно** — реле остаются `unknown` до первого события. Живость проверять по **`lastSeen`** в `database.db`. После рестарта HA состояния **возвращаются сами через ~4060 с** (birth-message), принудительно нажимать кнопки НЕ нужно.
> 🔑 **⚠️ `unknown` после рестарта HA — РАЗОБРАНО И РЕШЕНО (2026-09-14).** При рестарте всё, что было `on`/`off`, становится `unknown` — но **состояния возвращаются САМИ через ~40–60 с**, без физических нажатий. Механизм: z2m слушает `homeassistant.status_topic` (`homeassistant/status`); когда HA стартует, z2m видит birth-message и **переопубликовывает состояния всех устройств**. `retain: true` + `cache_state*` для этого **НЕ нужны** (retained на топиках устройств фактически не публикуетсяконтроль: `zigbee2mqtt/bridge/state` retained есть, значит дело не в брокере; причины: `cache_state_send_on_startup` отдаёт состояние, пока HA ещё не подписан).
> ✅ **РЕШЕНИЕ (применено 2026-09-14): `not_from: [unavailable, unknown]` убран из триггеров кнопок** в `automations.yaml` — иначе первое нажатие после рестарта блокировалось. Проверено на живом, оба канала. Бэкап `/config/automations.yaml.bak-20260914-131541`. Полностью: [[family/plans/t610-addons-deployment]] §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑тер.
> ⚠️ **Окно нестабильности:** между «HA поднялся» и приходом birth-message (десятки секунд) реле = `unknown` — нажатие в это окно может не сработать.
> 🔑 **Кэш состояний z2m лежит в `/homeassistant/zigbee2mqtt/state.json`** (НЕ в `/config/zigbee2mqtt` и не в `/addon_configs` — искать `find / -name state.json`). Плоский JSON `{ieee: {param: value}}`, пишется при каждом событии. Таблица «до/после»: [[family/plans/t610-addons-deployment]] §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис/③‑тер.
## Основное
@@ -245,6 +246,27 @@ ttyACM0 → /sys/devices/pci0000:00/0000:00:15.3/0000:04:00.0/usb3/3-1/3-1:1.0
**Различать только по `by-path`** (адрес шины) — ровно та же проблема, что была на TrueNAS, где алиасы `ttyZONT`/`ttyVent` делались udev-правилами по адресу шины ([[family/how-to/zont-modbus-bridge-udev-race-protection]]).
**✅ Доказано на живом t610 (2026-09-14):** в `/dev/serial/by-id/` для двух CH340 существует **ровно ОДИН симлинк**`usb-1a86_USB_Serial-if00-port0 → ttyUSB1` (занял тот, кто зарегистрировался последним). **Ссылки на `ttyUSB0` через by-id нет вообще.** Значит by-id не просто «неоднозначен» — он физически не может адресовать второй адаптер.
```
by-id:
usb-1a86_USB_Serial-if00-port0 -> ../../ttyUSB1 ← один на два CH340
usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00 -> ../../ttyACM0
```
**Проверка серийников (доказывает причину):**
```bash
for d in 1-3 1-4; do
echo -n "$d serial: "; cat /sys/bus/usb/devices/$d/serial 2>/dev/null || echo '(ПУСТО)'
done
# 1-3 serial: (ПУСТО) ← CH340 в порту 3
# 1-4 serial: (ПУСТО) ← CH340 в порту 4
```
> 🔴 **ОПАСНОСТЬ ТИХОГО СБОЯ: перепутывание кабелей.** CH340 №1 и №2 **физически неотличимы** — одинаковые VID:PID, название, отсутствуют серийники, стоят в одном корпусе. Если переткнуть кабели местами (ZONT ← порт 4, вентиляция ← порт 3), *оба аддона поднимутся без ошибок*, но будут работать **не с теми шинами**. Внешне это никак не проявится: Modbus-трафик пойдёт в чужой порт. Диагностируется только по аномальному трафику или по отсутствию ответов устройств.
>
> **Правило:** перед любым перетыканием записать, какой адаптер в каком порту, и сверить с картой ниже. Проверка «кто на шине» без физического вмешательства невозможна, если шина молчит (нет эталонного трафика для сравнения).
**Zigbee-координатор** — единственный из трёх, у кого есть уникальный серийник (`535A000001`), поэтому его by-id стабилен и проброс по by-id безопасен.
### Различия by-path на t610 vs TrueNAS
@@ -344,7 +366,7 @@ ha apps uninstall local_<slug> && ha store reload && ha apps install local_<slug
| `database.db` | база z2m (устройства, endpoints, keys) |
| `configuration.yaml` | конфиг z2m: `network_key`, `pan_id`, `ext_pan_id`, `channel`, `serial`, `mqtt`, **секция `devices:` с `friendly_name`** |
| `coordinator_backup.json` | бэкап координатора |
| `state.json` | ⚠️ **в этой папке его НЕТ.** Живой кэш состояний: **`/homeassistant/zigbee2mqtt/state.json`** (искать `find / -name state.json`) — плоский JSON `{ieee: {param: value}}`, пишется при каждом событии; z2m переопубликовывает его при старте (`cache_state_send_on_startup: true`). Содержит `state_l1`/`state_l2` (TS0002) и `state_left`/`state_right` (TS0012) — именно из него видно «запомненный на момент перезагрузки» стейт реле. |
| `state.json` | ⚠️ **в этой папке его НЕТ.** Живой кэш состояний: **`/homeassistant/zigbee2mqtt/state.json`** (искать `find / -name state.json`) — плоский JSON `{ieee: {param: value}}`, пишется при каждом событии. Содержит `state_l1`/`state_l2` (TS0002) и `state_left`/`state_right` (TS0012) — именно из него видно «запомненный на момент перезагрузки» стейт реле. ⚠️ **Отдаётся в HA не мгновенно:** при старте HA публикуется в момент, когда HA ещё не подписан, поэтому реально состояние доходит через **birth-message** (`homeassistant/status`) — z2m видит старт HA и переопубликовывает. Задержка ~40–60 с. |
| `log/<ts>/log.log` | логи запуска |
**⚠️ ПИТФОЛЛ: `database.db` — это НЕ SQLite, а JSON Lines** (по одному JSON-объекту на строку, расширение `.db` обманчиво).
+63 -4
View File
@@ -23,7 +23,7 @@ related:
> **modbus.host исправлен: `127.0.0.1` → `192.168.2.176`** (mbusd в отдельном контейнере — `127.0.0.1` изнутри HA его не видел).
> **modbus-bridge: HTTP 404 → 200.** Опрашивал HA по hex-`entity_id`; правился `modbus_ha_bridge.py` + `data/config.template.tmpl` + **обязательный rebuild аддона**.
> ⚠️ **ЕДИНСТВЕННЫЙ ОСТАВШИЙСЯ БЛОКЕР (физика, не конфиг): 32 заслонки вентиляции `unavailable`.** mbusd работает (TCP отвечает), но шина **не отвечает** — exception **0x0B** (GATEWAY TARGET DEVICE FAILED TO RESPOND). Вывод: **CH340 #2 воткнут в USB, но линии A/B вентиляционной шины к нему не подключены / шина обесточена.** За Alex.
> ⚠️ **`unknown` после рестарта HA — ПРОВЕРЕНО ЭКСПЕРИМЕНТОМ 2026-09-14, воспроизводится.** Реле/кнопки слетают в `unknown` при каждой перезагрузке HA; `retain: true` + `cache_state*` в z2m стоят, но не помогают. ✅ **ФИКС ПРИМЕНЁН: `not_from: [unavailable, unknown]` убран из триггеров** (`automations.yaml`, 2 шт.) — кнопка теперь срабатывает **с первого нажатия**, проверено на живом (l1 и l2, вкл+выкл). ⏳ Осталось выяснить, доходит ли cached-стейт из `state.json` до HA до первого события от устройства. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис.
> ⚠️ **`unknown` после рестарта HA — ПРОВЕРЕНО ЭКСПЕРИМЕНТОМ 2026-09-14.** Реле/кнопки слетают в `unknown` при каждой перезагрузке HA, но **состояния возвращаются сами через ~40–60 с** (без нажатий) — их переопубликовывает z2m, увидев birth-message HA (`homeassistant/status`). `retain` для этого НЕ нужен и фактически не публикуется. ✅ **ФИКС ПРИМЕНЁН: `not_from: [unavailable, unknown]` убран из триггеров** (`automations.yaml`, 2 шт.) — кнопка срабатывает **с первого нажатия**, проверено на живом (l1 и l2, вкл+выкл). Полностью: §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑тер.
> **✅ ЗОНЫ ВОССТАНОВЛЕНЫ (18 устройств):** при переносе `area_id` теряются так же, как `device_id` (устройства ре-регистрируются) → все 14 zigbee были **без зон**. Проставлены по эталону с TrueNAS по zigbee-адресу. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ①.
> **✅ office-переключатель управляет светом:** устранены hex-`entity_id` **в триггерах** автоматизаций (`switch.0xa4c13873b5c1575b_l1/l2` → человеческие) + реле выведены из `unknown` физическим нажатием. Цепочка `light → switch_as_x → z2m → реле` проверена. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ②③.
> Сервисы: z2m (14 устройств), mbusd (502), modbus-bridge (MQTT + HA-опрос), MQTT-интеграция в HA.
@@ -962,7 +962,34 @@ mosquitto_pub … -t 'zigbee2mqtt/office_table_light_switch/set' -m '{"state_l1"
> "0xa4c13873b5c1575b": { "state_l1": "ON", "state_l2": "ON" },
> "0xa4c1381186ed1a32": { "state_left": "OFF", "state_right": "OFF" }
> ```
> При старте z2m с `cache_state_send_on_startup: true` эти значения **должны** переопубликовываться. **Остаётся открытым:** приходит ли cached-стейт к HA **до** того, как устройство пришлёт своё (тогда `unknown` не появится вовсе и `not_from` можно вернуть в узком виде). Проверяется так: рестарт HA + снятие состояния каждые 2 с → видно последовательность `unknown`→cached `ON` или `unknown`→`ON`(от устройства). **Эксперимент не доведён.**
> При старте z2m с `cache_state_send_on_startup: true` эти значения **должны** переопубликовываться. **Остаётся открытым:** приходит ли cached-стейт к HA **до** того, как устройство пришлёт своё (тогда `unknown` не появится вовсе и `not_from` можно вернуть в узком виде).
##### ③‑тер ✅ РЕШЕНО (2026-09-14, конец сессии): состояние восстанавливает **birth-message**, а не `retain`
**Эксперимент доведён.** Рестарт HA + снятие состояния каждые 2 с:
```
13:21:02 knopka_l1=on svet_left=off ← до рестарта
13:21:11 knopka_l1=NO_ANSWER (HA лежит)
13:22:06 knopka_l1=null (HA поднимается, сущности ещё не загружены)
13:22:34 knopka_l1=unknown ← HA поднялся, состояния НЕТ
13:23:35 knopka_l1=unknown ← держится
↓ ещё ~40 с ожидания + задержка на подписку MQTT
knopka_l1=on svet_left=off ← ✅ СОСТОЯНИЯ ВОССТАНОВИЛИСЬ САМИ
```
**Итог: после рестарта `unknown` ДЕРЖИТСЯ несколько десятков секунд, а затем состояния приходят сами — БЕЗ физического нажатия кнопок.** После этого нажатие срабатывает с первого раза.
**🔑 Механизм — `status_topic` / birth-message, а НЕ retained:**
- z2m имеет `homeassistant.status_topic: "homeassistant/status"` (`bridge/info → config.homeassistant`).
- Когда HA стартует, он публикует во `homeassistant/status` сообщение `online`.
- z2m это видит → **переопубликовывает состояния всех устройств** → HA их ловит → `unknown` уходит.
**Почему `retain` не спасал:** retained на топиках устройств фактически не публикуется, а `cache_state_send_on_startup` отдаёт состояние в момент, когда HA ещё **не подписался** на MQTT → сообщение улетает в пустоту. Birth-message закрывает ровно этот разрыв: z2m узнаёт о старте HA и повторяет publish **в нужный момент**.
> ⚠️ **Практическое следствие:** в окне между «HA поднялся» и «z2m увидел birth-message» (десятки секунд) реле могут быть `unknown`. Если нажать кнопку ровно в это окно — может не сработать. Ждать ~40–60 с после рестарта.
> ✅ Значит `not_from: [unknown]` возвращать **не нужно** и **вредно** (он блокировал первое настоящее нажатие) — состояния приходят сами, фантомного переключения при старте не происходит, т.к. `unknown` не является «переключением» от `on`/`off`.
> ℹ️ Датчики (t°/освещённость/радар) выходят из `unknown` сами за секунды-минуты — страдают только **реле и кнопки**.
> ℹ️ Проверка живости узла без нажатия — `get`-запрос: `mosquitto_pub … -t 'zigbee2mqtt/<name>/get' -m '{"state_l1":""}'` → устройство отвечает текущим состоянием.
@@ -977,6 +1004,38 @@ mosquitto_pub … -t 'zigbee2mqtt/office_table_light_switch/set' -m '{"state_l1"
**Итог по всем трём:** `light.smart_light_office_left/right`, `switch.*_office_table_light_switch_l1/l2`, `switch.smart_light_office_left/right` — все корректны, цепочка управления проверена. Зоны проставлены (18 устройств). Розетка котельной видна (её не было в UI именно из-за `area_id = null`).
##### ④ 🔬 Рецепт диагностики «состояние не доходит до HA» (переиспользуемый)
Порядок проверок (сверху вниз — от простого к сложному), всё через bash+jq/curl, **без python**:
```bash
# 0) HA-токен — надёжно через файл-конфиг curl (маскировщик ломает инлайн Bearer)
printf 'header = "Authorization: Bearer %s"\n' "$(cat /tmp/ha_token.txt | tr -d '\n\r')" > /tmp/curl.auth
curl -s -o /dev/null -w 'HA: HTTP %{http_code}\n' -K /tmp/curl.auth http://192.168.2.176/api/ # 200 = ок
# 1) состояние сущностей ДО (снимок)
curl -s -K /tmp/curl.auth http://192.168.2.176/api/states \
| jq -r '[.[]|select(.entity_id|test("^switch\\.(office_table_light_switch_l[12]|smart_light_office_(left|right))$"))]|.[]|"\(.entity_id) = \(.state)"'
# 2) проверить, жив ли узел физически (lastSeen в database.db — JSON-lines, читать построчно!)
# обновляется по ЛЮБЫМ пакетам → зелёный флаг, даже когда HA показывает unknown
# 3) проверить, публикует ли устройство вообще (listener + get-запрос)
# подписка в background, затем: mosquitto_pub -t 'zigbee2mqtt/<name>/get' -m '{"state_l1":""}'
# → устройство отвечает текущим состоянием (доказательство, что реле живо и отвечает)
# 4) есть ли retained: подписаться и смотреть, что прилетает СРАЗУ (с timestamp).
# ⚠️ Флаг -R (retained-only) в mosquitto_sub аддона ВРЁТ — не использовать!
# Надёжно: timeout 4 mosquitto_sub … -v | while read l; do echo "$(date +%H:%M:%S) | $l"; done
# 5) причины «unknown держится»: HA ещё не подписан на MQTT / z2m не увидел birth-message.
# Ждать 40–60 с после рестарта HA — состояния приходят сами.
```
> 🔑 **Главный вывод:** состояние восстанавливается не через `retain`, а через **birth-message** (`homeassistant/status`) — z2m переопубликовывает состояния при старте HA. `retain: true` в z2m можно оставить, но полагаться на него нельзя.
> ℹ️ После правок реестров **обновить страницу в браузере (Ctrl+Shift+R)** — UI держит кэш и может не показать новые зоны сразу.
### Этап 4 — проверка и отключение TrueNAS
@@ -1020,9 +1079,9 @@ mosquitto_pub … -t 'zigbee2mqtt/office_table_light_switch/set' -m '{"state_l1"
- [x]**ВЫПОЛНЕНО 2026-09-14 (конец сессии) — ЗОНЫ восстановлены у 18 устройств.** `area_id` теряется при ре-регистрации устройств (как `device_id`) — все 14 zigbee были без зон. Проставлены по эталону с TrueNAS по `identifiers[0][1]` (`zigbee2mqtt_<ieee>`). Скрипт: `~/tmp-t610/etap3-fix/set_all_areas.jq`.
- [x]**ИСПРАВЛЕНО 2026-09-14 (конец сессии) — office-переключатель:** hex-`entity_id` в **триггерах** автоматизаций (`switch.0xa4c13873b5c1575b_l1/l2``switch.office_table_light_switch_l1/l2`); реле выведены из `unknown` нажатием кнопок. Цепочка управления светом проверена на живом.
- [x]**УСТАНОВЛЕНО 2026-09-14 (конец сессии) — z2m не публикует состояние пассивно:** реле «залипают» в `unknown` до первого события. Живость проверять по `lastSeen` в `database.db` (JSON-lines, читать построчно).
- [x]**ПРОВЕРЕНО ЭКСПЕРИМЕНТОМ 2026-09-14 — `unknown` возвращается после рестарта HA.** Зафиксировано состояние → рестарт → состояние снято: всё `on`/`off` слетело в `unknown`. `retain: true` + `cache_state*` в z2m стоят, но retained на топиках устройств фактически не публикуется (контроль: `bridge/state` retained есть). Питфолл: `mosquitto_sub -R` в аддоне врёт. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис.
- [x]**РАЗОБРАНО 2026-09-14 — `unknown` после рестарта HA: восстанавливается САМ за ~40–60 с.** Эксперимент доведён: рестарт HA → `unknown` держится десятки секунд → состояния приходят без нажатий. Механизм — **birth-message** (`homeassistant/status`): z2m видит старт HA и переопубликовывает состояния. `retain`/`cache_state*` для этого не нужны (retained на топиках устройств фактически не публикуется; контроль: `bridge/state` retained есть). Питфолл: `mosquitto_sub -R` в аддоне врёт.`not_from` убран из триггеров — возвращать НЕ нужно. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑тер и ④.
- [x]**ВЫПОЛНЕНО 2026-09-14 (конец сессии, после объяснения Alex) — `not_from: [unavailable, unknown]` УБРАН** из триггеров `office_pass_switch_table`/`office_pass_switch_main`. Кнопка срабатывает **с первого нажатия после рестарта**; проверено на живом toggle'ом через z2m на обоих каналах. Бэкап `/config/automations.yaml.bak-20260914-131541`.
- [ ] **ОТКРЫТО (низкий приоритет):** выяснить, доходит ли cached-стейт из `/homeassistant/zigbee2mqtt/state.json` до HA **до** первого события от устройства. Если да — `unknown` после рестарта исчезнет вовсе, и защиту `not_from` можно вернуть в узком виде (только против фантомного переключения). Метод: рестарт HA + снятие состояния каждые 2 с.
- [x] **ЗАКРЫТО 2026-09-14:** эксперимент проведён. Cached-стейт из `/homeassistant/zigbee2mqtt/state.json` доходит до HA **не мгновенно**, а через birth-message (~4060 с после старта HA). До этого момента реле = `unknown`. Поэтому `unknown` после рестарта исчезает не сразу, и защиту `not_from` возвращать НЕ надо (она ломала первое нажатие). См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑тер.
- [ ] ⚠️ Незакреплённое: `sensor.0xa4c138f8da8bc478_voltage/energy/power/current` (11 hex-сущностей розетки Насос обратки) — проживут ли под новым `friendly_name` или создадутся дубли; проверить после шага 8.
## Связанные заметки