--- 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-17' --- # 📊 t610 — метрики хоста (RAM, температура) + лог, переживающий зависание > **Статус: ✅ ВНЕДРЕНО И РАБОТАЕТ (2026-09-17).** Локальный аддон `local_hw_metrics`, интервал 60 с. > Ждёт живой проверки Alex'ом (коммит ПОСЛЕ — после неё). > Родительские доки: [[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 источников пусты). Каждый инцидент стирал свои доказательства. Нужно было, чтобы следующий случай оставил следы. --- ## 2. Архитектура ``` аддон local_hw_metrics (alpine, bash, loop) │ каждые 60 с ├─ читает ХОСТОВЫЕ /proc/meminfo, /proc/loadavg, /proc/pressure/*, │ /sys/class/hwmon/hwmon0/temp1_input, /proc/uptime, │ размер /config/home-assistant_v2.db + -wal ├─ публикует 11 сенсоров → POST /api/states/ (HA REST API) └─ дописывает строку в /share/ha-metrics/hw-YYYY-MM.log + sync ▲ └─ переживает жёсткое зависание ``` **Почему аддон, а не 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`). **Почему REST, а не MQTT:** из контейнера `mosquitto_pub` падает с `Bad file descriptor` (питфолл 7 родительской доки, подтверждён ещё раз). Единственный рабочий путь к HA из контейнера — внешний `https://mallexxx.duckdns.org` (проверено: `401` без токена, `200` с токеном). Значит `POST /api/states/` — и **правка `configuration.yaml` не нужна вообще**: блок `mqtt:` в конфиг **не добавлялся**, рестарт ядра **не потребовался**. Это лучше первоначального плана (было: MQTT → 6 сенсоров через `mqtt:`). --- ## 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. Оставлен от первой (MQTT) попытки, не используется | | Скрипт на хосте (легаси) | `/usr/local/bin/ha_hw_metrics.sh` — v2 (REST). Рабочий, но аддон его заменяет | **Версия токена:** `ha_token` лежит **в опциях аддона** (`/data/options.json` внутри контейнера), `tokenlen=183`. Env-файл `/etc/ha_hw_metrics.env` — не нужен, оставлен как след первой попытки. --- ## 4. Датчики (11) | Entity | Единица | Источник | |---|---|---| | `sensor.t610_ram_total` | MB | `MemTotal` | | `sensor.t610_ram_available` | MB | `MemAvailable` | | `sensor.t610_ram_free` | MB | `MemFree` | | `sensor.t610_ram_used` | MB | `MemTotal − MemAvailable` | | `sensor.t610_swap_used` | MB | `SwapTotal − SwapFree` | | `sensor.t610_cpu_temp` | °C | `hwmon0/temp1_input` / 1000 | | `sensor.t610_load_1m` | — | `/proc/loadavg` | | `sensor.t610_load_5m` | — | `/proc/loadavg` | | `sensor.t610_uptime` | s | `/proc/uptime` | | `sensor.t610_pressure_io` | % | `/proc/pressure/io` some avg10 | | `sensor.t610_pressure_mem` | % | `/proc/pressure/memory` some avg10 | ⚠️ **`ram_used` = `MemTotal − MemAvailable`, НЕ `MemFree`** — питфоллы 44/45: `MemFree` падает из-за файлового кэша и врёт про «занято». > ⚠️ **Датчики из REST — `restore_state`-типа.** Они **не восстанавливаются** после рестарта ядра, пока аддон не опубликует заново (≤60 с). Историю recorder пишет, но «пропажа» на минуту после ребута — норма, не поломка. > ⚠️ **Зона/дашборд не назначены.** Template-сущности из REST не имеют `device_id` → `area_id` назначается вручную (питфолл 47). Не сделано. --- ## 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` = все 11 сенсоров отдались, `1` = что-то не прошло (детали в логе аддона). Объём: 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}"' # лог аддона 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' # сущности в HA B="https://mallexxx.duckdns.org"; curl -s -H @/tmp/h1 "$B/api/states/sensor.t610_ram_used" | jq -r '.state' # удалить датчик (если понадобится) curl -s -X DELETE -H @/tmp/h1 "$B/api/states/sensor.t610_ram_used" ``` --- ## 7. Питфоллы (новые) | # | Питфолл | Обход | |---|---|---| | 73 | 🔴 **`mosquitto_pub` из контейнера — `Bad file descriptor`.** Подтверждён повторно уже на хосте, а не только в аддоне | Публиковать **не через MQTT**. Рабочий путь к HA из контейнера — `https://mallexxx.duckdns.org` (внешний, `401` без токена) | | 74 | 🔴 **`POST /api/states/` создаёт сущность напрямую** — блок `mqtt:` в `configuration.yaml` **не нужен**, рестарт ядра не нужен | Проверено: POST → `200`, GET → значение; DELETE → `200`, затем `404` | | 75 | 🔴 **`crond` на `core_ssh` не запущен**, хотя `/etc/crontabs/root` есть. Аддон на **s6** (`/etc/services.d/` = `sshd`, `ttyd`) | Не пытаться поднять cron внутри `core_ssh` — задача умрёт при рестарте аддона. Периодику делать **своим аддоном** с `while true` | | 76 | 🔴 **Новый локальный аддон: сначала `POST /addons//install`, потом `/options`** | Иначе `/options` и `/rebuild` отдают `{"result":"error","message":"App is not installed"}`. Порядок: `store/reload` → `install` → `options` → `rebuild` → `start`. ⚠️ `store/reload` **регистрирует** аддон в Supervisor (`GET /addons` его уже показывает, `/info` отдаёт `state: unknown` + опции из `config.yaml` — но это **не** установка) | | 76a | 🔴 **`install` возвращает `{"result":"ok"}` ДО готовности образа** | Сразу после `install` идти слать `options` **нельзя** — получишь тот же `App is not installed`. Дождаться реального признака готовности, а не ответа API. `rebuild` тоже только ставится в очередь. **Симптом-маркер:** `options`/`rebuild` дают `App is not installed`, хотя `install` только что сказал `ok` → это гонка, а не сломанный манифест | | 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` по БД не сработает | Обязательная секция `map:` для локального аддона | | 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`), а не только «сущность создалась» | --- ## 8. Что осталось - ⚠️ **Живая проверка Alex'ом** — коммит ПОСЛЕ не сделан, ждёт. - ⚠️ **Зона/дашборд для 11 датчиков** не назначены (`area_id` = null, питфолл 47). - ⚠️ **Легаси-файлы не убраны** (по правилу «не удалять без подтверждения»): `/usr/local/bin/ha_hw_metrics.sh` и `/etc/ha_hw_metrics.env` — рабочие, но аддон их заменяет. Снести по команде Alex. - ⚠️ **Ротация лога** — не сделана. 2 МБ/год, можно не трогать, но зафиксировать. --- ## 9. Связанные - [[family/how-to/home-automation]] — контур автоматизации, §3.5 (ограничения `core_ssh`), §3.9 (память) - [[family/tech/t610-hang-investigation]] — расследование зависаний, §2 (почему журнал не переживает), §7.3 (эта задача) - [[family/tech/local-ustreamer-addon]] — образец локального аддона