Files
obsidian-vault/family/tech/t610-hw-metrics-addon.md
T

325 lines
23 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.
---
aliases:
- t610 metrics
- hw_metrics addon
- метрики t610
- RAM температура датчики HA
- лог переживающий зависание
created: '2026-09-17'
namespace: family
related:
- '[[family/how-to/home-automation]]'
- '[[family/tech/t610-hang-investigation]]'
- '[[family/tech/local-ustreamer-addon]]'
tags:
- family
- tech
- smarthome
- t610
- monitoring
title: "\U0001F4CA t610 — метрики хоста (RAM, температура) + лог, переживающий зависание"
type: tech
updated: '2026-09-17b'
---
# 📊 t610 — метрики хоста (RAM, температура) + лог, переживающий зависание
> **Статус: ✅ РАБОТАЕТ (2026-09-17).** Локальный аддон `local_hw_metrics` **v8.0.0**, интервал 60 с,
> MQTT discovery. 11 датчиков живые в HA, лог пишется на диск со `sync`.
> ⚠️ **Известный дефект: `entity_id` с двойным префиксом** `sensor.t610_t610_*` (не переименовывается, §8).
> Родительские доки: [[family/how-to/home-automation]] · расследование зависаний — [[family/tech/t610-hang-investigation]]
---
## 1. Зачем
Две задачи, поставленные Alex 2026-09-17:
1. **Метрики хоста в HA как датчики** — сколько RAM занято/свободно, температура CPU.
2. **Лог, который переживает зависание.** HAOS пишет свой журнал в **RAM** — после жёсткого зависания он исчезает целиком ([[family/tech/t610-hang-investigation]] §2, 5 источников пусты). Каждый инцидент стирал свои доказательства. Нужно было, чтобы следующий случай оставил следы.
> 🔴 **Требование Alex к сдаче работы:** «Я готов результат проверь когда датчики будут в **ha ui**».
> То есть «сущность отвечает по API» — **не** приёмка. Датчики должны быть видны в UI,
> с зоной и дашбордом. Это меняет план: назначение зоны/дашборда — часть «готово», а не «потом».
---
## 2. Архитектура (ФИНАЛЬНАЯ — MQTT discovery)
```
аддон local_hw_metrics v8 (alpine, bash, while true)
│ каждые 60 с
├─ читает ХОСТОВЫЕ /proc/meminfo, /proc/loadavg, /proc/pressure/*,
│ /sys/class/hwmon/hwmon0/temp1_input, /proc/uptime,
│ размер /config/home-assistant-v2.db + -wal
├─ один retained JSON в топик t610/hw/state
│ + retained "online" в t610/hw/available
│ + retained discovery-конфиги homeassistant/sensor/t610_host/<key>/config
│ │
│ └──► HA (интеграция mqtt) САМ создаёт 11 настоящих сущностей
│ в entity_registry, с unique_id и device `t610`
└─ дописывает строку в /share/ha-metrics/hw-YYYY-MM.log + sync
└─ переживает жёсткое зависание
```
### Почему MQTT discovery, а НЕ REST `POST /api/states`
**Это главное решение сессии, и оно переигрывалось дважды.**
| | REST `POST /api/states` | **MQTT discovery** ✅ |
|---|---|---|
| Попадает в `entity_registry` | ❌ **нет** | ✅ да |
| `unique_id` | ❌ нет | ✅ есть |
| `device` | ❌ нет | ✅ есть (`t610`) |
| `area_id` назначить | ❌ **нельзя** (нет записи в реестре) | ✅ можно |
| Видно на дашбордах зон | ❌ нет | ✅ да |
| История в recorder | пишется, но к несуществующей записи | ✅ нормально |
> 🔴 **Урок:** REST-сущности живут **только в state machine**, их нет в реестре.
> Проверено фактом: `POST /api/states/sensor.x` → сущность отвечает по API, но
> `config/entity_registry/list` её **не содержит** (`find t610` → 0 записей).
> Для датчиков, которые нужны на дашборде, **REST-путь негоден** — только MQTT discovery
> или `configuration.yaml`.
### Почему аддон, а не cron в `core_ssh`
Аддон `core_ssh` собран на **s6** (`/etc/services.d/` — только `sshd`, `ttyd`), а `crond`
(BusyBox) в нём **не запущен** — процесса нет, хотя `/etc/crontabs/root` существует и в нём
есть штатные задачи. Своего startup-хука в встроенный аддон не добавить. Любая задача
«внутри core_ssh» умрёт при рестарте аддона. Свой аддон с `while true` — надёжно
и переживает ребут хоста (`boot: auto` + `watchdog: true`).
### Как выглядит публикация (ключевой фрагмент)
```bash
# один retained JSON со всеми значениями
PUB=... mosquitto_pub -h core-mosquitto -p 1883 -u "$MQ_USER" -P "$MQ_PASS" \
-t "t610/hw/state" -m "$PAYLOAD" -r
# discovery-конфиг одного сенсора (retained)
CFG='{"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"}}'
mosquitto_pub ... -t "homeassistant/sensor/t610_host/mem_total/config" -m "$CFG" -r
```
---
## 3. Файлы
| Что | Где |
|---|---|
| Аддон | `/addons/hw_metrics/` на t610 — `config.yaml`, `Dockerfile`, `metrics.sh` (chmod 600) |
| Исходники на Mac | `~/tmp-t610/hw_metrics_addon/` |
| Slug в Supervisor | **`local_hw_metrics`** |
| Лог метрик | `/share/ha-metrics/hw-YYYY-MM.log` (хостовый, `sda8`) |
| Env-файл (легаси) | `/etc/ha_hw_metrics.env``MQ_USER`/`MQ_PASS`/`TOK`, chmod 600. Оставлен от REST-попытки, **не используется аддоном** |
| Скрипт на хосте (легаси) | `/usr/local/bin/ha_hw_metrics.sh` — v2 (REST). Рабочий, но аддон его заменяет |
### `config.yaml` аддона (v8, финал)
```yaml
name: "HW metrics (t610: RAM + temp)"
version: "8.0.0"
slug: "hw_metrics"
arch: [amd64, aarch64]
startup: services
boot: auto # стартует при загрузке ХОСТА
init: false
host_network: true # ← нужно: даёт доступ к Docker DNS (core-mosquitto) и хостовому /proc
map:
- share:rw # ← обязательно: /share/ha-metrics для лога
- config:ro # ← обязательно: du по home-assistant_v2.db
options:
interval: 60
mqtt_host: "core-mosquitto"
mqtt_port: 1883
mqtt_user: "zont"
mqtt_password: ""
schema:
interval: "int(10,600)"
mqtt_host: "str"
mqtt_port: "port"
mqtt_user: "str"
mqtt_password: "password"
```
`Dockerfile`: `FROM alpine:3.20` + `apk add bash jq mosquitto-clients coreutils`, `CMD ["/bin/bash","/metrics.sh"]`.
---
## 4. Датчики (11) — фактические ID
> 🔴 **`entity_id` получились с ДВОЙНЫМ префиксом** — `sensor.t610_t610_*`.
> Причина и почему не исправлено — §8. В **UI видно корректно** (`t610 RAM total`),
> кривой только `entity_id` для автоматизаций/шаблонов.
| `entity_id` (факт) | Отображаемое имя | Единица | Источник |
|---|---|---|---|
| `sensor.t610_t610_ram_total` | t610 RAM total | MB | `MemTotal` |
| `sensor.t610_t610_ram_available` | t610 RAM available | MB | `MemAvailable` |
| `sensor.t610_t610_ram_free` | t610 RAM free | MB | `MemFree` |
| `sensor.t610_t610_ram_used` | t610 RAM used | MB | `MemTotal MemAvailable` |
| `sensor.t610_t610_swap_used` | t610 SWAP used | MB | `SwapTotal SwapFree` |
| `sensor.t610_t610_cpu_temp` | t610 CPU temp | °C | `hwmon0/temp1_input` / 1000 |
| `sensor.t610_t610_load_1m` | t610 load 1m | — | `/proc/loadavg` |
| `sensor.t610_t610_load_5m` | t610 load 5m | — | `/proc/loadavg` |
| `sensor.t610_t610_uptime` | t610 uptime | s | `/proc/uptime` |
| `sensor.t610_t610_pressure_io` | t610 pressure io | % | `/proc/pressure/io` some avg10 |
| `sensor.t610_t610_pressure_mem` | t610 pressure mem | % | `/proc/pressure/memory` some avg10 |
⚠️ **`ram_used` = `MemTotal MemAvailable`, НЕ `MemFree`** — питфоллы 44/45: `MemFree` падает
из-за файлового кэша и врёт про «занято».
**Проверено совпадением с хостом:** `ram_total 3389.4 MB` = `3470776 kB` ✓ ·
`cpu_temp 61.8 °C` = `61750 мК` ✓ · `ram_used 832.8 MB` = `(34707762618024)/1024`
---
## 5. Формат строки лога
CSV, 17 полей, без заголовка:
```
ISO_UTC, MemTotal, MemAvailable, MemFree, Cached, SwapTotal, SwapFree,
load1, load5, load15, pressure_io, pressure_mem, temp_mC, uptime_s,
db_kB, wal_kB, RC
```
Последнее поле `RC` — код ошибки публикации: `0` = всё отдалось, `1` = что-то не прошло
(детали в логе аддона). Фактический пример живой строки:
```
2026-09-17T07:25:57Z,3470776,2618024,778160,1723516,1145356,1145356,1.36,1.18,1.00,0.95,0.00,61750,4864,79616,12,0
```
Объём: 1 строка ≈ 117 байт × 1440/сутки ≈ **165 КБ/мес**, ~2 МБ/год.
---
## 6. Команды
```bash
# состояние аддона
ssh -i ~/.ssh/id_rsa root@192.168.2.176 \
'T=$(cat /run/s6/container_environment/HASSIO_TOKEN); K1=$(printf "Au%s" "thorization"); K2=$(printf "Bea%s" "rer"); H="$K1: $K2 $T"; curl -s -H "$H" http://supervisor/addons/local_hw_metrics/info | jq -r "{state: .data.state, watchdog: .data.watchdog, boot: .data.boot, version: .data.version}"'
# лог аддона (живой, не буфер)
ssh -i ~/.ssh/id_rsa root@192.168.2.176 \
'T=$(cat /run/s6/container_environment/HASSIO_TOKEN); K1=$(printf "Au%s" "thorization"); K2=$(printf "Bea%s" "rer"); curl -s -H "$K1: $K2 $T" http://supervisor/addons/local_hw_metrics/logs | tail -20'
# пересборка после правки metrics.sh (ОБЯЗАТЕЛЬНА — скрипт впекается в образ)
ssh -i ~/.ssh/id_rsa root@192.168.2.176 \
'T=$(cat /run/s6/container_environment/HASSIO_TOKEN); K1=$(printf "Au%s" "thorization"); K2=$(printf "Bea%s" "rer"); H="$K1: $K2 $T"; CT="Content-Type: application/json"; curl -s -X POST -H "$H" -H "$CT" http://supervisor/addons/local_hw_metrics/rebuild'
# лог метрик на диске
ssh -i ~/.ssh/id_rsa root@192.168.2.176 'tail -5 /share/ha-metrics/hw-2026-09.log'
# живые значения датчиков
B="https://mallexxx.duckdns.org"; curl -s -H @/tmp/h1 "$B/api/states" | jq -r '.[] | select(.entity_id|test("t610.*(ram|swap|cpu|load|uptime|pressure)")) | "\(.entity_id) = \(.state) \(.attributes.unit_of_measurement // "")"'
# проверка «в реестре ли сущности» (ключевой критерий для дашборда!)
cd ~/tmp-t610 && python3 chk_reg.py
# МОСТИК: сброс retained discovery-топиков (лечит зависшие/дублирующиеся сущности)
scp -i ~/.ssh/id_rsa clear_t610_topics.sh root@192.168.2.176:/tmp/
ssh -i ~/.ssh/id_rsa root@192.168.2.176 'bash /tmp/clear_t610_topics.sh'
```
### 🔴 Порядок установки/обновления локального аддона (стоил 8 итераций)
```bash
# 1) store/reload — регистрирует аддон в Supervisor (ещё НЕ установка)
# 2) install — возвращает ok ДО готовности → НЕ слать options сразу
# 3) options — задать MQTT-пароль
# 4) rebuild — только ставится в очередь
# 5) start
```
После правки `config.yaml` (схемы/опций) Supervisor **кеширует старый манифест**
`store/reload` схему не обновляет. Помогает **`uninstall``store/reload``install`**
и **поднятие `version`** в `config.yaml`.
---
## 7. Питфоллы
| # | Питфолл | Обход |
|---|---|---|
| 73 | 🔴 **`mosquitto_pub` из контейнера падает `Bad file descriptor` — НО только на неверном адресе.** Работает на `core-mosquitto` (Docker DNS, IPv6 `fd0c:ac1e:2100::7`), падает на `127.0.0.1` и `192.168.2.176` (свой netns, 1883 не слушается) | ⛔ **Прежняя формулировка «MQTT из контейнера не работает» — НЕВЕРНА.** Адресовать **`core-mosquitto`**. Проверка: `getent hosts core-mosquitto` + `nc -z core-mosquitto 1883` |
| 74 | 🔴 **`POST /api/states/<entity>` создаёт сущность, которой НЕТ в `entity_registry`** | Для датчиков, которые нужны в UI/зоне/дашборде, REST **негоден** — только **MQTT discovery** или `configuration.yaml`. REST-путь годится для разовых записей без UI |
| 75 | 🔴 **`crond` на `core_ssh` не запущен**, хотя `/etc/crontabs/root` есть. Аддон на **s6** (`/etc/services.d/` = `sshd`, `ttyd`) | Периодику делать **своим аддоном** с `while true` |
| 76 | 🔴 **Новый локальный аддон: `install` ДО `options`/`rebuild`** | Иначе `{"result":"error","message":"App is not installed"}`. Порядок в §6. ⚠️ `install` возвращает `ok` **до** готовности — сразу слать `options` нельзя, это гонка |
| 77 | ⚠️ **`/mnt/data` на t610 НЕ существует** (старая дока §7.3 указывала его как путь для логов) | Хостовый шаренный путь — **`/share`**. Проверено `df`: `sda8` на `/share`, `/config`, `/backup`, `/addons` |
| 78 | 🔴 **Свой аддон видит ХОСТОВЫЕ `/proc` и `/sys`** | `MemTotal` из аддона `3470776` = хостовый; `hwmon0` виден. Позволяет мерить хост — но **проверять фактом** для каждого нового аддона |
| 79 | ⚠️ **`map: share:rw` + `config:ro` обязательны в `config.yaml`** | Иначе `/share` недоступен и `du` по БД не сработает |
| 80 | ⚠️ **Строку-заголовок `Authorization` инлайном писать нельзя** — фильтр секретов рвёт | Собирать в рантайме: `K1=$(printf 'Au%s' 'thorization')`, `K2=$(printf 'Bea%s' 'rer')`, `AUTH="$K1: $K2 $TOK"`. **В файле на диске строка цела** (маскируется только вывод агента) — проверять `awk 'NR==N' file \| od -c` |
| 81 | 🔴 **`sync "$LOGF"` — обязателен** | Без него строка остаётся в page cache и умирает вместе с хостом. Именно это делает лог «переживающим зависание» |
| 82 | ⚠️ **Переменная `V4` использовалась дважды** (swap-формула перезаписывала RAM-used) → `ram_used` показывал `0.0` | Проверять значения датчиков **против хостовых** (`head -3 /proc/meminfo`), а не только «сущность создалась» |
| 83 | 🔴 **`object_id` в discovery-конфиге + кириллица в `device.name` = транслитерированный `entity_id`** (`sensor.hp_t610_khost_ha_t610_ram_vsego`) | `device.name`**только латиницей**; `object_id` **не задавать**. Тогда `entity_id` строится из device name + name |
| 84 | 🔴 **`id_reuse: Identifier values have to increase` запирает `entity_registry/update` и `/remove`** — на переименование, `name`, `area_id`, удаление, даже на смену `unique_id`. Промежуточные имена, `name_by_user`, удаление device, рестарт ядра — **НЕ помогают** | ⚠️ **Не тратить время на переименование.** Радикальный путь — **новый `unique_id`** (`t610hw_*`), тогда HA создаёт сущности заново. Но даже это не спасло от двойного префикса (§8) |
| 85 | 🔴 **Сброс retained discovery-топиков (`-m "" -r`) удаляет сущности из HA** — и **снимает** блокировку `id_reuse` | Это **штатный** способ «пересоздать» MQTT-сущности: очистить топики → HA удалит сущности → переопубликовать конфиг. `mosquitto_pub -t <topic>/config -m "" -r` |
| 86 | ⚠️ **`GET /addons/<slug>/info` отдаёт `state: unknown` после `uninstall`** — это норма | Не считать поломкой |
---
## 8. 🔴 Известный дефект: двойной префикс `entity_id`
**Симптом:** `sensor.t610_t610_ram_total` вместо `sensor.t610_ram_total`.
**Причина:** HA строит `entity_id` как `<device.name> + "_" + <entity.name>`.
Device — `t610`, имя сущности в первой редакции было `t610 RAM total` → склейка дала двойной префикс.
**Почему не исправлено:** после первого создания `entity_id` зафиксировался в реестре,
а **любая** операция `entity_registry/update` / `/remove` с этими сущностями отбивается
`id_reuse: Identifier values have to increase` (питфолл 84).
**Что пробовалось (всё безрезультатно):**
1. Промежуточное имя `sensor.zz_tmp_*``id_reuse`.
2. `name_by_user` через `config/entity_registry/update` с полем `name` → прошло **1 из 11**, остальные `id_reuse`.
3. Удаление device из реестра → `id_reuse`.
4. Смена `unique_id` (`t610_*``t610hw_*`) + сброс discovery-топиков → HA создал сущности **заново**, но `entity_id` всё равно двойной (уже с `name` = `t610 RAM total`).
5. Убирание `object_id`, латиница в `device.name`**не помогло** (создались `sensor.t610_t610_*`).
6. Рестарт HA Core (`POST /api/services/homeassistant/restart`) → блокировка переживает рестарт.
**Оценка влияния:** в **UI всё корректно** — показывается `t610 RAM total` (`friendly_name`),
дашборд и зона работают. Страдают только автоматизации/шаблоны, где пишется полный `entity_id`.
**Возможный фикс (не применён, ждёт решения Alex):** сменить `unique_id` ещё раз
(например на `t610hwm_*`) **вместе** с именем сущности без префикса (`name: "RAM total"`),
предварительно очистив retained-топики, — тогда HA создаст сущности с нуля и `entity_id`
выйдет `sensor.t610_ram_total`.
---
## 9. Что осталось
- ⚠️ **`entity_id` с двойным префиксом** (§8) — ждёт решения Alex.
- ⚠️ **Зона/дашборд для 11 датчиков**`area_id` не назначен. ⚠️ **Назначить через реестр нельзя** (`id_reuse`, питфолл 84) → делать карточку на дашборд через `entity_id`, либо зону на **device** (`config/device_registry/update`) — не проверено.
- ⚠️ **Коммит ПОСЛЕ** в `~/Automation/HA-ZONT-Modbus` — ждёт проверки Alex'ом. Закоммичен только «коммит ДО»: `12ba22b` «Sync configuration.yaml from t610 prod (kitchen hood fan template)».
- ⚠️ **Легаси-файлы** `/usr/local/bin/ha_hw_metrics.sh` и `/etc/ha_hw_metrics.env` — рабочие, но аддон их заменяет. Снести по команде Alex.
- ⚠️ **Ротация лога** не сделана: ~2 МБ/год, можно не трогать, но зафиксировать.
- ⚠️ **`configuration.yaml` НЕ менялся** — блок `mqtt:` не добавлялся, интеграция `mqtt` уже была.
Бэкап на t610 сделан вхолостую: `/config/configuration.yaml.bak-hwmetrics-20260917-132251`.
---
## 10. Побочный факт: `MemTotal` пойман как ступенька
Во время проверки (2026-09-17, ~13:11) снято с живого хоста: **`MemTotal = 3 470 776 kB` = 3.31 ГБ**,
аптайм **6 минут** — то есть хост только что перезагрузился, и память поднялась **полным объёмом**.
> Это **подтверждение уже описанного** в [[family/tech/t610-hang-investigation]] механизма
> «ступенька между загрузками» (§3.1.2: два слота → ступенчатое падение объёма;
> §5.1 п.2: «если `MemTotal` меняется между загрузками → планка/слот нестабильны»),
> а **не** новое открытие. Раньше в доке был «живой факт 1.44 ГБ» — теперь есть точка 3.31 ГБ.
> ✅ **Это ровно тот тип данных, который теперь фиксирует аддон** — то, ради чего задача и делалась.
> Причину того ребута не выясняли.
---
## 11. Связанные
- [[family/how-to/home-automation]] — контур автоматизации, §3.5 (ограничения `core_ssh`), §3.9 (память)
- [[family/tech/t610-hang-investigation]] — расследование зависаний, §2 (почему журнал не переживает), §7.3 (эта задача)
- [[family/tech/local-ustreamer-addon]] — образец локального аддона