Files
obsidian-vault/family/tech/ha-registry-operations.md
T

285 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Реестры Home Assistant — операции (справочник)"
created: '2026-09-16'
updated: '2026-09-16'
type: tech
namespace: family
status: 🟢 Проверено фактами на t610 (HA 2026.9.2). Переиспользуемый справочник — не привязан к Zigbee.
tags:
- home-assistant
- haos
- t610
- registry
- websocket
related:
- '[[family/tech/zigbee-t610-z2m-i-zha]]'
- '[[family/how-to/home-automation]]'
- '[[family/tech/heating-cable-water-inlet]]'
---
# Реестры Home Assistant — операции
> **Справочник по работе с `core.entity_registry` / `core.device_registry` / `core.area_registry`.** Все факты проверены на t610.
> ⚠️ Записи реестра — **только WebSocket**. REST `/api/config/*_registry/list` отдаёт **404**.
---
## 1. Структура `core.entity_registry`
```json
{
"version": 1,
"minor_version": 23,
"key": "core.entity_registry",
"data": {
"entities": [...], // ЖИВЫЕ сущности
"deleted_entities": [...], // АРХИВ удалённых (их HA не грузит, но UI показывает!)
"settings": {...}
}
}
```
> 🔴 **Ключевой факт: `entities` и `deleted_entities` — разные секции одного файла.**
> Призраки живут в `deleted_entities` — поэтому их НЕ видно в `/api/states` и WS-реестре, но они вылезают в UI «Обслуживание» и в ссылках дашбордов.
```bash
jq '.data.entities|length' /config/.storage/core.entity_registry # живое
jq '.data.deleted_entities|length' /config/.storage/core.entity_registry # архив
jq -r '.data.entities[].entity_id' ... # список живых
jq -r '.data.deleted_entities[].entity_id' ... # список архивных
```
---
## 2. WS API — рабочие команды
| Задача | Команда |
|---|---|
| Список сущностей (живых) | `config/entity_registry/list` |
| Список устройств | `config/device_registry/list` |
| Список зон | `config/area_registry/list` |
| Получить сущность | `config/entity_registry/get {entity_id}` |
| Переименовать сущность | `config/entity_registry/update {entity_id, new_entity_id}` |
| Переименовать устройство | `config/device_registry/update {device_id, name_by_user}` |
| Сменить зону устройства | `config/device_registry/update {device_id, area_id}` |
| Удалить запись | `config/entity_registry/remove {entity_id}` |
| Проблемы (Обслуживание) | `repairs/list_issues` |
| Записи интеграций | `config_entries/get` |
> ⚠️ `/api/config/*_registry/list` (REST) → **404**. Только WS.
> ⚠️ `repairs/list_issues` может быть **пуст**, даже когда UI «Обслуживание» что-то показывает — если источник в `deleted_entities`.
---
## 3. 🔴 Переименование сущности
### 3.1. `name_by_user` НЕ переименовывает `entity_id`
Смена имени устройства (`device_registry/update`) меняет только `name_by_user`. HA **не перегенерирует** `entity_id` существующих сущностей — только у новых.
```python
# Шаг 1 — имя устройства
{"type":"config/device_registry/update","device_id": D, "name_by_user": "new_name"}
# Шаг 2 — КАЖДАЯ сущность отдельно
{"type":"config/entity_registry/update","entity_id":"sensor.old","new_entity_id":"sensor.new"}
```
### 3.2. Обход `id_reuse: Identifier values have to increase`
Внутренний счётчик реестра. **Штатный случай, не поломка.** Обход — через промежуточное имя:
```
sensor.old → sensor.tmp_xxx → sensor.new
```
### 3.3. Сироты-автоматизации
Тело удалено из `automations.yaml`, но запись в реестре осталась → сущность `unavailable`.
`config/entity_registry/remove` → если `id_reuse`**сначала** `update` c `disabled_by: "user"`, **затем** `remove`.
> 🔴 **После любого переименования — проверять ссылки:**
> - в `automations.yaml` (все `entity_id:` сверить со `/api/states`);
> - в дашбордах (`lovelace.*`), см. §5;
> - в маппингах `modbus-bridge`, см. [[family/tech/zigbee-t610-z2m-i-zha]] §5.
---
## 4. 🔴 Опции сущности: `options_domain` — важно какой
Опции хранятся по доменам. Пример структуры:
```json
"options": {
"conversation": {"should_expose": false},
"sensor": {"suggested_display_precision": 1},
"sensor.private": {"suggested_unit_of_measurement": "°F"}
}
```
Обновление:
```python
{"type":"config/entity_registry/update",
"entity_id":"sensor.x",
"options_domain":"sensor.private", # ⚠️ ТОЧНЫЙ домен, не общий
"options":{"suggested_unit_of_measurement": None}}
```
> 🔴 **ПИТФОЛЛ: `options_domain: "sensor"` даёт `success: true`, но единицу НЕ меняет.** Проверено фактом на H2000_PRO: заход через `"sensor"` не подействовал, после смены на `"sensor.private"` — сработало.
> **Всегда читать обратно** (`config/entity_registry/get`) и сверять результат в `/api/states`.
### Кейс: датчик показывает °F вместо °C
Причина — ручной override `sensor.private.suggested_unit_of_measurement: "°F"`. Значения тоже конвертируются (55.22 °F = 12.9 °C), поэтому цифры выглядят правдоподобно и ошибку легко не заметить.
**Фикс:** сброс в `null` через `options_domain: "sensor.private"`.
---
## 5. Проверка ссылок дашбордов
```bash
# все ссылки плана этажей (picture-elements)
jq -r '.data.config.views[].sections[].cards[]?
| select(.type=="picture-elements")
| .elements[]? | select(.entity?!=null) | .entity' \
/config/.storage/lovelace.home_plan | sort -u
# затем сверить список со живыми из /api/states
```
> 🔴 **Правило: после сноса призраков — проверять ссылки дашбордов.** План этажей показывает «недоступно» из-за мёртвой ссылки, а не из-за поломки устройства.
Дашборды t610: `home_plan` (storage-mode, `/config/.storage/lovelace.home_plan`, карта `/config/www/floorplan/*.svg`), реестр дашбордов — `lovelace_dashboards`.
---
## 6. Контролируемый снос призраков
**Принцип: только то, что в архиве И не живое. Никогда по grep — только по точному `entity_id`.**
Скрипт `~/tmp-t610/ghost_purge.sh`:
1. строит `live_all` (`.data.entities`) и `ghost_all` (`.data.deleted_entities`);
2. `comm -23` → в архиве и НЕ живое;
3. `grep -viE 'fan|at2|damper|vent|shopping_list'` → исключает вентиляцию (по требованию Alex);
4. dry-run по умолчанию, `--apply` — снос;
5. `--apply` → бэкап `*.bak-ghostpurge-<ts>``jq`-фильтр → отчёт.
**Обязательно после сноса:** рестарт HA (`POST /api/services/homeassistant/restart`), иначе правка `.storage` не подхватится. Проверить: `entities` не изменилось, `deleted_entities` уменьшилось.
```bash
scp ~/tmp-t610/ghost_purge.sh root@192.168.2.176:/tmp/
ssh root@192.168.2.176 'bash /tmp/ghost_purge.sh /config/.storage/core.entity_registry' # dry-run
ssh root@192.168.2.176 'bash /tmp/ghost_purge.sh /config/.storage/core.entity_registry --apply'
```
> ⚠️ **Питфолл `set -e` в bash:** `comm`/`grep` возвращают 1 при пустом результате и валят скрипт молча. Использовать `set -uo pipefail` (без `-e`) + `|| true` на таких строках.
> ⚠️ **На t610 нет `python3`.** Только `jq` 1.8.1 (`/usr/bin/jq`). Скрипты для t610 писать на jq/bash, не на Python.
---
## 7. Питфоллы (сводка)
| # | Питфолл | Обход |
|---|---|---|
| 1 | REST `/api/config/*_registry/list` → 404 | Только WebSocket |
| 2 | `name_by_user` не меняет `entity_id` | Переименовывать устройство И сущности отдельно |
| 3 | `id_reuse: Identifier values have to increase` | Промежуточное имя: `old → tmp → new` |
| 4 | Сирота-автоматизация `unavailable` | `remove`, при `id_reuse` — сначала `disabled_by: user` |
| 5 | `options_domain: "sensor"` не меняет единицу | Точный домен: `"sensor.private"` |
| 6 | Призраки не видны в API, но видны в UI | Они в `deleted_entities`, а не в `entities` |
| 7 | Правка `.storage` без рестарта не действует | `POST /api/services/homeassistant/restart` |
| 8 | После сноса призраков дашборд ругается | Проверить ссылки дашбордов (§5) |
| 9 | Удаление `device_id` сбрасывает `area_id` | Восстановить через `device_registry/update` |
| 10 | На t610 нет `python3` | Скрипты на `jq`/bash |
| 11 | `weather/get_forecast` (ед. ч.) и `weather/forecast` не существуют | Мн. ч.: сервис `weather.get_forecasts` либо WS `weather/subscribe_forecast` (ответ — 2 сообщения) |
| 12 | `weather.get_forecasts()` в Jinja → `'weather' is undefined` | Вызывать как **ДЕЙСТВИЕ** `action: weather.get_forecasts` + `response_variable`; `variables:` — внутри `actions:` ПОСЛЕ вызова. Helper'ы НЕ нужны |
| 13 | WS `render_template` возвращает `null` даже для `{{ 2 + 2 }}` | Шаблоны рендерить через REST `POST /api/template` |
| 14 | `write_file` режет строку `Authorization: Bearer *** | Собирать заголовок из переменных: `P1=Authorization; P2=Bearer; AUTH=*** ${P2} ${TOK}"` |
---
## 8. 🔴 Прогноз погоды и шаблоны (HA 2026.9.2)
### 8.1. Прогноз НЕДОСТУПЕН ИЗ ШАБЛОНА — главное
Проверено четырьмя способами 2026-09-16:
| Способ | Результат |
|---|---|
| атрибут `forecast` у сущности `weather.*` | **`None`** — прогноза в атрибутах нет |
| `weather.get_forecasts(...)` **в Jinja-шаблоне** | **`UndefinedError: 'weather' is undefined`** |
| `weather.get_forecasts` **как ДЕЙСТВИЕ** (`action:` + `response_variable`) | ✅ **работает** — 48 точек |
| сервис `weather.get_forecasts` (мн. число), REST/WS | ✅ работает — 48 ч hourly + 6 дн daily |
| WS `weather/subscribe_forecast` | ✅ работает |
> ✅ **Как брать прогноз в автоматизации (рабочий путь, 0 helper'ов):**
> вызов **действием** с `response_variable`, а вычисления — в `variables:`
> ВНУТРИ `actions:` ПОСЛЕ вызова (верхнеуровневые `variables:` вычисляются до
> действий и `forecast` там ещё нет).
>
> ```yaml
> actions:
> - action: weather.get_forecasts
> target: {entity_id: weather.forecast_laki_dom}
> data: {type: hourly}
> response_variable: wx
> - variables:
> fc_min: >-
> {{ wx['weather.forecast_laki_dom']['forecast'][:24]
> | map(attribute='temperature') | min | default(99) | float(99) }}
> ```
>
> 🔴 **Питфолл (стоил лишней итерации):** вызов сервисов из Jinja-шаблона
> (`{{ weather.get_forecasts(...) }}`) действительно убран → `'weather' is undefined`.
> Но это **не значит**, что прогноз недоступен: как **действие** он работает. Не
> строить обходной слой (`input_number` + обновлятор), не проверив `action:` + `response_variable`.
### 8.2. WS `weather/subscribe_forecast` — стрим прогноза
```python
{"type": "weather/subscribe_forecast",
"entity_id": "weather.forecast_laki_dom",
"forecast_type": "daily"} # ⚠️ именно forecast_type, НЕ type; или "hourly"
```
| Способ | Результат |
|---|---|
| `POST /api/services/weather/get_forecast` (ед. ч., REST) | **400 Bad Request** |
| WS `call_service` → `weather.get_forecast` (ед. ч.) | `not_found` |
| WS `weather/forecast` | `unknown_command` |
| сервис **`weather.get_forecasts`** (мн. ч.) | ✅ работает |
| WS **`weather/subscribe_forecast`** | ✅ работает |
> 🔴 **ПИТФОЛЛ: ответ подписки приходит ДВУМЯ сообщениями** — сначала `result` (с `result: null`), затем отдельное `event` с `forecast`. Клиент, читающий только `result`, вернёт **0 точек**.
> Реализация: `~/tmp-t610/ha_ws.py forecast <entity> <hourly|daily>` (флаг `collect_events=True` → второй `recv()`).
> ⚠️ Версия WS-команды зависит от версии HA — при апгрейде перепроверять.
### 8.3. 🔴 `render_template` по WebSocket НЕ работает
> 🔴 **WS `render_template` возвращает `null` даже для `{{ 2 + 2 }}`** — проверено фактом. Это не ошибка шаблона, а неподдерживаемый метод.
> ✅ **Шаблоны рендерить ТОЛЬКО через REST** `POST /api/template`, тело `{"template": "..."}`. Через REST: `{{ 2 + 2 }}` → `4`, `{{ states('sensor.x') }}` → значение.
>
> ```bash
> jq -n --arg t '{{ 2 + 2 }}' '{template: $t}' > /tmp/tpl.json
> # затем POST /api/template (с t610 изнутри: http://172.30.32.1/api/template)
> ```
### 8.4. ⚠️ Питфолл окружения: `write_file` портит строку с `Bearer`
> ⚠️ При генерации скриптов **`write_file` режет литерал `Authorization: Bearer $TOK`** — строка приходит битой и скрипт не парсится (`unexpected EOF while looking for matching quote`). Обход — собирать заголовок из переменных:
>
> ```bash
> TOK=$(cat /tmp/.hatok)
> P1=Authorization
> P2=Bearer
> AUTH_HDR=*** ${P2} ${TOK}"
> ```
---
## Связанные заметки
- [[family/tech/zigbee-t610-z2m-i-zha]] — Zigbee на ZHA, §13 вычистка призраков
- [[family/how-to/home-automation]] — t610: топология, аддоны, доступ
- [[family/tech/heating-cable-water-inlet]] — практический кейс §8 (прогноз для греющего кабеля)
- [[family/plans/t610-heating-cable-automation]] — план автоматизации на базе §8.1