diff --git a/family/how-to/t610-access.md b/family/how-to/t610-access.md index c9705fd2..7a91dbb2 100644 --- a/family/how-to/t610-access.md +++ b/family/how-to/t610-access.md @@ -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 состояния **возвращаются сами через ~40–60 с** (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_ && ha store reload && ha apps install local_/log.log` | логи запуска | **⚠️ ПИТФОЛЛ: `database.db` — это НЕ SQLite, а JSON Lines** (по одному JSON-объекту на строку, расширение `.db` обманчиво). diff --git a/family/plans/t610-addons-deployment.md b/family/plans/t610-addons-deployment.md index 680da4c8..1c7fc9c8 100644 --- a/family/plans/t610-addons-deployment.md +++ b/family/plans/t610-addons-deployment.md @@ -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//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//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_`). Скрипт: `~/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 (~40–60 с после старта HA). До этого момента реле = `unknown`. Поэтому `unknown` после рестарта исчезает не сразу, и защиту `not_from` возвращать НЕ надо (она ломала первое нажатие). См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑тер. - [ ] ⚠️ Незакреплённое: `sensor.0xa4c138f8da8bc478_voltage/energy/power/current` (11 hex-сущностей розетки Насос обратки) — проживут ли под новым `friendly_name` или создадутся дубли; проверить после шага 8. ## Связанные заметки