13 KiB
aliases, created, namespace, related, tags, title, type, updated
| aliases | created | namespace | related | tags | title | type | updated | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
2026-09-17 | family |
|
|
🧩 HAOS: локальный аддон → датчики в HA UI (skill) | tech | 2026-09-17 |
🧩 HAOS: локальный аддон → датчики в HA UI
Зеркало скилла
haos-local-addon-publish-sensors(категорияdevops). Путь:~/.hermes/hermes-whale/skills/devops/haos-local-addon-publish-sensors/SKILL.md. Дополняет скиллha-automation-debugging. Загружать, когда нужно завести свои данные как сенсоры HA, видимые в UI (метрики хоста, скрейпинг, всё, что не покрыто интеграцией). Практический пример применения — family/tech/t610-hw-metrics-addon.
0. Главное решение: MQTT discovery, НЕ REST
POST /api/states/<entity> |
MQTT discovery ✅ | |
|---|---|---|
В entity_registry |
❌ нет | ✅ да |
unique_id / device |
❌ / ❌ | ✅ / ✅ |
Назначить area_id |
❌ невозможно (нет записи в реестре) | ✅ да |
| Видно на дашбордах зон | ❌ нет | ✅ да |
| История в recorder | пишется, запись осиротевшая | ✅ нормально |
REST-сущности живут только в state machine. Отвечают по /api/states, видны в
Developer Tools → States, но отсутствуют в config/entity_registry/list.
Проверено фактом: POST /api/states/sensor.x → 200, затем find <substr> в реестре → 0.
🔴 Проверять по реестру, а не по
/api/states. «Отвечает по API» ≠ «сущность настоящая». Grep поconfig/entity_registry/listчерез WebSocket — это и есть приёмка. Если пользователь говорит «покажи в UI» — REST уже не подходит.
Следствие: блок mqtt: в configuration.yaml не нужен, если интеграция mqtt
уже настроена (обычно так и есть — проверять /api/config → .components содержит mqtt).
1. MQTT изнутри контейнера — адресовать правильно
mosquitto_pub внутри контейнера аддона работает — но только по правильному адресу:
| Адрес | Результат |
|---|---|
core-mosquitto ✅ |
работает (Docker DNS; резолвится в IPv6 fd0c:ac1e:2100::7) |
127.0.0.1 / localhost ❌ |
Bad file descriptor — у аддона свой netns, на 1883 никто не слушает |
192.168.2.176 (LAN IP хоста) ❌ |
та же ошибка |
getent hosts core-mosquitto # DNS резолвится?
nc -z core-mosquitto 1883 && echo OPEN
⚠️ Неверный питфолл размножается: ранее в доке было записано «MQTT из контейнера не работает вообще». Это был баг адреса, а не MQTT.
Bad file descriptorотmosquitto_pub= неверный хост, а не сломанный клиент.
2. Структура файлов локального аддона
/addons/<dir>/ # chmod 600 каждому файлу
config.yaml # манифест: slug, version, options, schema, map, host_network
Dockerfile # FROM alpine:3.20 + apk add bash jq mosquitto-clients coreutils
<script>.sh # CMD ["/bin/bash", "/script.sh"]
slug: "hw_metrics" # → id аддона становится local_hw_metrics
version: "8.0.0" # ПОДНИМАТЬ при любом изменении манифеста (§4)
startup: services
boot: auto # старт при загрузке ХОСТА
init: false
host_network: true # нужно: Docker DNS + видимость хостовых /proc, /sys
map:
- share:rw # /share для долгоживущих файлов ← ОБЯЗАТЕЛЬНО
- config:ro # /config для чтения БД HA
options:
interval: 60
schema:
interval: "int(10,600)"
✅ Аддон видит ХОСТОВЫЕ
/procи/sys—MemTotalиhwmon0это значения хоста, а не контейнера. Именно это делает возможными метрики хоста. Проверять для каждого аддона (head -2 /proc/meminfo→ сравнить с хостом).
⚠️ На HAOS долгоживущий общий путь —
/share(sda8, рядом с/config,/backup,/addons)./mnt/dataНЕ существует — частое ложное допущение, скопированное с других типов установки HA.
3. Жизненный цикл установки/обновления (порядок обязателен)
S=http://supervisor
# токен: T=$(cat /run/s6/container_environment/HASSIO_TOKEN)
# заголовок собирать в рантайме — фильтр секретов рвёт литералы:
# K1=$(printf 'Au%s' 'thorization'); K2=$(printf 'Bea%s' 'rer'); H="$K1: $K2 $T"
POST $S/store/reload # 1. регистрирует аддон в Supervisor
POST $S/addons/local_<slug>/install # 2. установка
POST $S/addons/local_<slug>/options -d '{"options":{...}}' # 3. опции
POST $S/addons/local_<slug>/rebuild # 4. только в очередь
POST $S/addons/local_<slug>/start # 5. старт
GET $S/addons/local_<slug>/info # проверка
| # | Ловушка | Обход |
|---|---|---|
| 1 | options до install → {"result":"error","message":"App is not installed"} |
Всегда install первым |
| 2 | install отдаёт ok до готовности образа |
Не слать options сразу — это гонка, а не сломанный манифест. rebuild тоже только ставится в очередь |
| 3 | GET /addons/<slug>/info → state: unknown сразу после uninstall |
Норма, не поломка |
| 4 | store/reload показывает аддон в GET /addons с state: unknown + опции |
Регистрация ≠ установка |
4. Supervisor кеширует манифест — поднимать version
После правки config.yaml (новые опции / изменённая схема) Supervisor продолжает отдавать
старую схему — валидация падает с Missing option 'old_key' in root.
POST /store/reload сам по себе это не исправляет.
Рабочая последовательность:
- Поднять
versionвconfig.yaml(например3.0.0→4.0.0). uninstall→store/reload→install.
5. Именование сущностей: что реально определяет entity_id
entity_id = <device.name> + _ + <entity.name>.
| Поле | Эффект |
|---|---|
device.name с кириллицей |
entity_id транслитерируется: device.name="HP t610 (хост HA)" → sensor.hp_t610_khost_ha_t610_ram_vsego |
device.name латиницей ("t610") + name: "RAM total" |
→ sensor.t610_ram_total ✅ |
name: "t610 RAM total" (префикс продублирован) |
→ sensor.t610_t610_ram_total ❌ двойной префикс |
Задан object_id |
НЕ спасает имя — не задавать вообще |
Правила:
device.name— только латиница.- Не задавать
object_id. - Не повторять имя устройства внутри
nameсущности.
Минимальный discovery-payload:
{"name":"RAM total","unique_id":"t610hw_mem_total",
"state_topic":"t610/hw/state","value_template":"{{ value_json.mem_total }}",
"availability_topic":"t610/hw/available",
"payload_available":"online","payload_not_available":"offline",
"unit_of_measurement":"MB","device_class":"data_size","state_class":"measurement",
"device":{"identifiers":["t610_host"],"name":"t610","manufacturer":"HP","model":"t610"}}
Публиковать retained в homeassistant/sensor/<dev_id>/<key>/config.
availability_topicстоит завести: публиковать retainedonlineкаждый цикл — тогда сущности уйдут вunavailableвместо показа устаревших значений, если аддон умер.
6. 🔴 id_reuse — блокировка, с которой нельзя спорить
{"code":"id_reuse","message":"Identifier values have to increase."} блокирует:
config/entity_registry/updateсnew_entity_id(переименование)- тот же вызов с
name/name_by_user - тот же вызов с
area_id config/entity_registry/removeconfig/device_registry/remove- даже смену
unique_id— иногда
Что НЕ работает (всё пробовалось, всё провалилось): промежуточные имена zz_tmp_*,
name_by_user, удаление device, рестарт HA Core
(POST /api/services/homeassistant/restart), «больше выглядящие» ID.
Не тратить на это сессию.
Что РАБОТАЕТ — путь спасения:
- Очистить retained discovery-топик:
mosquitto_pub ... -t "homeassistant/sensor/<dev>/<key>/config" -m "" -r→ HA сама удаляет сущность, и блокировкаid_reuseуходит вместе с ней. - Переопубликовать discovery-конфиг с новым
unique_id→ HA создаёт сущность заново.
⚠️ Публикация пустого сообщения retained удаляет discovery-сущность. Это штатный способ пересоздать сущности — и единственный надёжный выход из
id_reuse.
⚠️
entity_idфиксируется при первом создании. Позднейшее изменениеname/object_idв discovery-payload не переименовывает существующую сущность. Чтобы сменитьentity_id, нужно удалить + пересоздать (шаги 1–2). Именование делать правильно с первого раза.
7. Долгоживущий лог, переживающий зависание
Если цель — диагностика хоста, который намертво вешается:
- HAOS пишет свой журнал в RAM — при жёстком зависании он теряется (
journalctl -b -1пуст). - Файл на
/shareпереживает — но только если сделатьsync:
printf '%s,...\n' "$ROW" >> "$LOGF"
sync "$LOGF" 2>/dev/null || sync # ← без этого строка умрёт в page cache вместе с хостом
Добавлять поле статуса (например RC=0/1), чтобы сбой публикации был виден в самом логе.
8. Чек-лист проверки
GET /addons/local_<slug>/info→state: started,watchdog: true,boot: auto, ожидаемаяversion.- Лог аддона: цикл стартовал, ошибок публикации нет.
- Долгоживущий файл растёт на одну строку за интервал.
- Сущность есть в
config/entity_registry/list(WebSocket) — не только в/api/states. - Значения сенсоров совпадают с хостовой истиной (
head -3 /proc/meminfo,cat /sys/class/hwmon/hwmon0/temp1_input). - Для приёмки в UI: убедиться, что
device+area_idзаданы, чтобы дашборд зоны мог их показать.
Связанные
- family/tech/t610-hw-metrics-addon — практическое применение (аддон
local_hw_metrics) - family/how-to/home-automation — контур автоматизации, §3.5 (ограничения
core_ssh), §7 (питфоллы) - family/tech/local-ustreamer-addon — образец локального аддона