Files
cuframes-composer/docs/ru/user.md
T
gx e76360dbc4 docs: руководства пользователя / разработчика / operations (RU+EN)
Полный комплект документации к Phase 11b:

  docs/ru/user.md        — для админа инсталляции (motion-mode, PTZ,
                            templates.json, mqtt_overlays.json, ZMQ verbs)
  docs/ru/developer.md   — архитектура (Cell / Layout / Decoration),
                            как добавить новый Cell/Decoration, ABI shim,
                            algorithms (best-fit + asymmetric hysteresis)
  docs/ru/operations.md  — build (host + jammy + incremental bake),
                            deploy, logs/telemetry, troubleshooting
                            (broken pipe, MQTT-overlay, motion-mode)
  docs/en/*.md           — английская версия всех трёх
  README.md              — переписан с overview + ссылками на docs/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 22:02:47 +01:00

471 lines
20 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.
# cfc-grid — руководство пользователя
> Аудитория: администратор инсталляции. Кто настраивает камеры, layout'ы,
> overlay'и, смотрит RTSP-поток на TV или в браузере. Не правит C++ код.
>
> Если ты разработчик и хочешь добавить новый тип ячейки/декорации/виджета —
> см. [developer.md](developer.md).
## 1. Что это
**cfc-grid** — CUDA-композитор, собирающий N камер в один RTSP-поток
`rtsp://192.168.88.23:554/cfc-grid` (1920×1080, H.264, NVENC). Раскладка
выбирается автоматически по движению (от Frigate) или вручную через
ONVIF-presets с TV.
Параллельно с видеокадром поверх рисуются:
- **Borders** — серые рамки 2 px вокруг каждой ячейки
- **Labels** — подпись `имя prio=N` в углу каждой камеры
- **Detection boxes** — рамки объектов от Frigate, повторяющие позицию
камеры при смене layout
- **MQTT-overlays** — текстовые поля привязанные к топикам MQTT
(температура, статусы, чаты)
## 2. Архитектура одной фразой
```
cuframes-pub-* (на камеру)
↓ shared VMM
cfc-grid (composer) ── ZMQ control ──┐
↓ pipe (H.264) │
cfc-grid-ffmpeg (relay) ─→ mediamtx ─┴─→ TV / VLC / Frigate / ...
ONVIF discovery от cctv-onvif
```
Кадры через cuframes идут zero-copy (один VMM-буфер, разделяемый между
publisher'ом и composer'ом). Композитор берёт NV12-поверхность,
ресайзит/блитит в свой output, добавляет декорации, отдаёт NVENC,
NVENC пишет H.264 в pipe, `cfc-grid-ffmpeg` транскодирует pipe → RTSP
к mediamtx.
## 3. Motion-mode — основной режим работы
### 3.1 Что происходит
На каждом кадре композитор:
1. Берёт `last_motion_ms` для каждой камеры (обновляется из Frigate MQTT
`frigate/events`)
2. Считает «активными» те у кого `(now - last_motion_ms) < motion_ttl_ms`
(по умолчанию **45 секунд**)
3. Сортирует активных по `priority` (число; больше = главнее)
4. Выбирает template из `templates.json` по правилу **best-fit**:
минимальный template с `nb_camera_cells >= количество_активных`
5. Если в template'е больше camera-cells, чем активных — лишние
заполняются остальными drawable камерами из pool (по приоритету)
6. Применяет **asymmetric hysteresis**: рост числа активных переключает
layout мгновенно, уменьшение — ждёт 3 секунды (чтобы не мелькало)
### 3.2 Что значит «drawable»
Камера **исключается** из pool если её `cfc_source_state_t` =
`CONNECTING`, `DISCONNECTED` или `DEAD` (cuframes publisher молчит
дольше `dead_threshold_ms`, по умолчанию 5 секунд).
`STALE` (кадры приходят редко) — считается, рисуется последний доступный
кадр.
### 3.3 Manual override через PTZ
В TV в ONVIF PTZ-presets отображаются имена templates (`tpl_1, tpl_3,
tpl_4, ..., tpl_16`). Нажатие `GotoPreset` или movement-кнопок:
- Применяет выбранный layout мгновенно
- **Замораживает** motion-mode на 60 секунд
- По истечении — возвращается в auto-режим
ContinuousMove (стрелки): pan/tilt циклируют по списку presets, zoom-in
=`tpl_1` (full screen), zoom-out=`tpl_16` (4×4 grid).
## 4. templates.json — раскладка экрана
### 4.1 Схема
```json
{
"version": 1,
"grid_cols": 8,
"grid_rows": 8,
"templates": [
{
"name": "tpl_N",
"_desc": "Описание",
"priority": 0,
"cells": [
{
"col": 0, "row": 0,
"cs": 4, "rs": 4,
"role": "camera",
"order": 0
},
{
"col": 6, "row": 0,
"cs": 2, "rs": 6,
"role": "widget",
"widget": "temp_chart"
}
]
}
]
}
```
**Грид: 8×8 микроячеек** (240×135 px каждая = 16:9 на output 1920×1080).
Любой квадрат N×N микроячеек тоже 16:9.
| Поле | Значение |
|---|---|
| `col`, `row` | Top-left угол cell в микроячейках (0..7) |
| `cs`, `rs` | Размер в микроячейках |
| `role` | `camera` либо `widget` |
| `order` | Для `camera`: порядок placement'а активных (0 = главная, обычно крупнейшая cell) |
| `widget` | Для `widget`: имя placeholder'а (текст подписи) |
### 4.2 Best-fit selection
Композитор выбирает template для текущего числа активных:
```
candidates = [t for t in templates if t.nb_camera_cells >= n_active]
pick = min(candidates, key=lambda t: t.nb_camera_cells - n_active)
# При равенстве: больший priority побеждает
```
Если активных больше чем cell'ов в самом большом template — берётся
самый большой, лишние камеры обрезаются (низший priority вылетает).
### 4.3 Встроенные templates
По умолчанию 9 templates в `/opt/templates.json`:
| Имя | Cells | Описание |
|---|---|---|
| `tpl_1` | 1 cam | Одна камера во весь экран |
| `tpl_3` | 3 cam + 2 widgets | Главная 1440×810 слева + 2 превью справа + 2 widget |
| `tpl_4` | 4 cam | Quad 2×2, 960×540 каждая |
| `tpl_5` | 5 cam + 1 widget | Главная + 4 превью справа стопкой + widget снизу |
| `tpl_6` | 6 cam + 1 widget | Главная + 3 правые + 2 нижние + widget |
| `tpl_7` | 7 cam + 1 widget | Главная + 3 правые + 3 нижние + widget |
| `tpl_8` | 8 cam (1+3+4) | Главная + 3 правые + 4 в нижней строке |
| `tpl_9` | 9 cam + 2 widgets | 3×3 главных + widget справа + widget снизу |
| `tpl_16` | 16 cam | 4×4 grid, 480×270 каждая |
Подробности — см. `docker/templates.json` в репо.
### 4.4 Как добавить свой template
1. Открыть `docker/templates.json` (или подмонтированный override)
2. Добавить блок в `"templates": [...]` по схеме выше
3. Перезапустить cfc-grid (либо `docker exec cfc-grid sh -c 'kill -HUP 1'`
когда добавим hot-reload в Phase 12), либо вызвать ZMQ:
```bash
mosquitto_pub -h 192.168.88.23 -p 5599 ... # пока нет CLI, см. operations.md
```
### 4.5 Координатная математика
Каждая микроячейка = `1920/8 = 240 px` ширина, `1080/8 = 135 px` высота
(aspect 16:9). Cell `{col=2, row=4, cs=4, rs=2}`:
- pixel x = `2 * 240 = 480`
- pixel y = `4 * 135 = 540`
- pixel w = `4 * 240 = 960`
- pixel h = `2 * 135 = 270`
Aspect cell = `cs/rs * 16/9`. Для 16:9 cell'ов **cs == rs**.
## 5. mqtt_overlays.json — text overlay'и из MQTT
### 5.1 Схема
```json
{
"version": 1,
"overlays": [
{
"id": "temp_outside",
"topic": "zigbee2mqtt/Температура на улице",
"json_field": "temperature",
"format": "%+.1f°C",
"anchor": "right-bottom",
"margin_x": 32, "margin_y": 24,
"pixel_size": 32,
"color": [255, 255, 255],
"alpha": 230,
"bg_alpha": 160,
"bg_y": 16, "bg_u": 128, "bg_v": 128,
"bg_pad": 10,
"placeholder": "—",
"font_path": "/fonts/DejaVuSans-Bold.ttf"
}
]
}
```
| Поле | Описание |
|---|---|
| `id` | Уникальный идентификатор overlay'я (используется ZMQ для lookup'а) |
| `topic` | MQTT topic для subscribe (через `cctv-mosquitto`) |
| `json_field` | Если payload JSON — имя поля для извлечения значения; **пусто = raw payload как строка** |
| `format` | `printf`-стиль для отформатированного значения (например `"%+.1f°C"`, `"%s"`) |
| `anchor` | Якорь позиционирования: `right-bottom`, `right-top`, `left-bottom`, `left-top`, `center` |
| `margin_x`, `margin_y` | Отступ от ближайшего края экрана (px) |
| `pixel_size` | Размер шрифта в пикселях |
| `color` | RGB цвет текста |
| `alpha` | Общая непрозрачность текста (0..255) |
| `bg_alpha` | Непрозрачность подложки (0..255); **0 = без фона** |
| `bg_y`, `bg_u`, `bg_v` | BT.709 limited-range цвет подложки (Y=16..235, UV=16..240); default чёрный |
| `bg_pad` | Отступ подложки вокруг текста (px) |
| `placeholder` | Что показывать пока не пришло MQTT-сообщение; пусто = "—" |
| `font_path` | Путь к шрифту (.ttf/.otf) в контейнере |
### 5.2 Поддерживаемые символы
Шрифт `DejaVuSans-Bold.ttf` — стандартный из пакета `fonts-dejavu`.
**Покрывает Basic Multilingual Plane** (latin, cyrillic, базовые
символы), включая:
- `` (U+276F), `✎` (U+270E), `➤` (U+27A4), `→` (U+2192)
- `★` (U+2605), `▶` (U+25B6), `✉` (U+2709)
**Emoji из Supplementary Multilingual Plane (>U+10000)** — например
`🗣` (U+1F5E3), `🤖` (U+1F916), `💬` (U+1F4AC) — **не рендерятся**:
шрифт не содержит таких глифов. Рисуется placeholder-квадрат.
Чтобы добавить color emoji — нужно подключить Noto Color Emoji и
расширить рендерер для COLR/CPAL/SBIX (см. developer.md).
### 5.3 Примеры
**Только число**: payload = `"23.5"` (raw), `json_field: ""`, `format: "%s°"`
**JSON с полем**: payload = `{"temperature": 23.5, "humidity": 45}`,
`json_field: "temperature"`, `format: "%+.1f°C"`
**Несколько overlay'ев** в правом-верхнем углу столбиком: первый с
`margin_y: 24`, второй с `margin_y: 72`, третий с `margin_y: 120`.
### 5.4 Как добавить
1. Открыть `docker/mqtt_overlays.json` (либо подмонтированный override)
2. Добавить блок в массив `"overlays": [...]`
3. Перезапустить cfc-grid
Hot-reload через ZMQ — Phase 12 (`reload_overlays` verb).
## 6. CLI флаги composer'а
В compose override `docker/cctv/cuframes-composer/docker-compose.override.yml`:
```yaml
command:
- "--out=/out/grid.h264" # named pipe для ffmpeg-relay
- "--fps=25"
- "--bitrate=6000" # kbps
- "--width=1920"
- "--height=1080"
- "--intra-refresh" # вместо IDR-burst'ов (low-latency)
- "--control=tcp://0.0.0.0:5599" # ZMQ control plane
- "--mqtt=cctv-mosquitto:1883" # MQTT для health-публикации
- "--mqtt-instance=cfc-grid"
- "--mqtt-user=composer"
- "--mqtt-pass=${COMPOSER_MQTT_PASSWORD}"
# Источники (камеры) — повторяемое
- "--source=cam-parking,frigate=parking_overview,priority=100,zones=parking_zone:canopy:private_area"
- "--source=cam-back_yard,frigate=back_yard,priority=70"
# ...
- "--motion-mode" # включить auto-layout
- "--motion-ttl=45000" # ms
- "--templates=/opt/templates.json"
- "--mqtt-overlays=/opt/mqtt_overlays.json"
# Frigate motion-driver и detection boxes
- "--frigate-mqtt=cctv-mosquitto:1883"
- "--frigate-topic=frigate/events"
- "--detection-cell=parking,parking_overview,0,0,960,540,640,480,parking_zone:canopy:private_area"
```
### 6.1 `--source` синтаксис
```
--source=<cuframes_key>,frigate=<camera>,priority=<N>[,zones=<z1>:<z2>:...]
```
- `cuframes_key` — имя cuframes-publisher'а (например `cam-parking`)
- `frigate=NAME` — имя камеры в Frigate (для матчинга motion-событий)
- `priority=N` — целое, больше = главнее
- `zones=...` — опциональный whitelist Frigate zones; motion засчитывается
только если `event.current_zones` пересекается со списком (отсев
street-флуда)
### 6.2 `--detection-cell` синтаксис
```
--detection-cell=<key>,<frigate_camera>,<x>,<y>,<w>,<h>,<detect_w>,<detect_h>[,zones]
```
- `key` — произвольный идентификатор overlay'я для логов
- `frigate_camera` — имя в Frigate (для матчинга event.camera)
- `x,y,w,h` — initial geometry (composer пересчитает динамически)
- `detect_w,detect_h` — разрешение детектора Frigate (например 640×480)
- `zones` — whitelist для bbox
## 7. ZMQ control plane
Default endpoint: `tcp://192.168.88.23:5599`. Все verb'ы — JSON request/reply.
### 7.1 Список verbs
| Команда | Параметры | Что делает |
|---|---|---|
| `ping` | — | Health-check |
| `health` | — | `{total, active, stale, dead}` по pool'у |
| `set_text` | `id, text, r, g, b, x, y, visible` | Обновить текстовый overlay (для CLI `--text=...`) |
| `set_visible` | `id, visible` | Скрыть/показать overlay |
| `list_overlays` | — | Список overlay'ев |
| `set_layout` | `name` | Применить named template (manual override на 60s в motion-mode) |
| `list_layouts` | — | Список доступных templates с cells |
| `get_layout` | — | Имя текущего template'а |
| `set_motion_mode` | `on, ttl_ms` | Включить/выключить motion-режим |
| `get_motion_mode` | — | Состояние motion-mode |
| `get_template` | `name` | Полный JSON template'а |
| `reload_templates` | `path?` | Перезагрузить templates из файла (default — последний путь) |
### 7.2 Пример
```bash
# Python
python3 <<EOF
import zmq, json
s = zmq.Context().socket(zmq.REQ)
s.connect("tcp://192.168.88.23:5599")
s.send_json({"cmd": "list_layouts"})
print(json.dumps(s.recv_json(), indent=2, ensure_ascii=False))
EOF
```
```bash
# Force layout
echo '{"cmd":"set_layout","name":"tpl_4"}' | \
nc -q1 192.168.88.23 5599 # ! REP socket требует ZMQ framing, не голый TCP
```
Голый `nc` **не работает** — REP socket ожидает ZMQ wire-protocol. Используй
`zmq` Python/Go/JS либо `mosquitto_pub` через RPC-bridge (Phase 12).
## 8. ONVIF и PTZ
Сервис `cctv-onvif` биндится на host network, отвечает на WS-Discovery
multicast (239.255.255.250:3702) и SOAP-запросы по HTTP `:8085`.
### 8.1 Добавление в TV
В клиенте (TV / IP-CamViewer / Synology / Frigate):
- ONVIF host: `192.168.88.23`
- Port: `8085`
- User / Password: пусто (auth не настроен)
WS-Discovery в LAN 192.168.88.0/24 найдёт устройство `cfc-grid (Goldix)`.
RTSP-URL автоматически — `rtsp://192.168.88.23:554/cfc-grid`.
### 8.2 PTZ presets
Список (`GetPresets`): `tpl_1, tpl_3, tpl_4, tpl_5, tpl_6, tpl_7, tpl_8,
tpl_9, tpl_16`.
GotoPreset(name) → composer применяет template + замораживает motion-mode
на 60 секунд → auto-возврат.
### 8.3 PTZ movement (ContinuousMove)
| Команда | Действие |
|---|---|
| Pan right / Tilt down | Следующий template в списке |
| Pan left / Tilt up | Предыдущий |
| Zoom in (+) | `tpl_1` (full screen) |
| Zoom out () | `tpl_16` (4×4 grid) |
## 9. Где смотреть RTSP
| Способ | URL |
|---|---|
| VLC / mpv / ffplay | `rtsp://192.168.88.23:554/cfc-grid` |
| Браузер (HLS) | `http://192.168.88.23:8888/cfc-grid` |
| WebRTC | `http://192.168.88.23:8889/cfc-grid` |
| OBS / FFmpeg input | `rtsp://192.168.88.23:554/cfc-grid` |
| ONVIF клиенты | через WS-Discovery (см. §8) |
## 10. Известные ограничения
- **Color emoji не рендерятся** (нужен Noto Color Emoji + COLR/CPAL
поддержка в text renderer — Phase 12)
- **Hot-reload `mqtt_overlays.json`** — нет ZMQ verb'а, нужен restart
cfc-grid
- **Per-overlay broker** — все MQTT-overlay'и используют общий
broker (тот что задан как `--mqtt`); подписку на сторонний broker
отдельно — нет
- **Widget rendering** — placeholder (тёмный rect + label), реальные
виджеты (graph, chat) — Phase 12+
- **HA Assist в MQTT** — архитектурное ограничение HA (см. operations.md
§Troubleshooting)
## 11. FAQ
**В: На TV вижу старые имена layouts (`quad`, `dual_horizontal`).
Что делать?**
О: TV закешировал ONVIF-presets. В клиенте удали камеру и добавь
заново — он перечитает `GetPresets` с актуальными именами.
**В: Камера парковки в DEAD, но в logs показывает active=3.
Почему?**
О: `cfc_composer_get_health` показывает **pool-wide** state, а
motion-active считает по `last_motion_ms` независимо от source state.
DEAD исключается на этапе `is_camera_drawable()` в `compose_motion_relayout`.
**В: PTZ нажал на TV, рамка переключилась, через минуту вернулась
обратно.**
О: Это by design — `set_layout` в motion-mode замораживает auto на
60 секунд (`manual_override_duration_ms`). Чтобы зафиксировать template
надолго — выключи motion-mode целиком через ZMQ:
```json
{"cmd": "set_motion_mode", "on": 0}
```
**В: Хочу подложку у text overlay'я разного цвета.**
О: Поля `bg_y/bg_u/bg_v` в JSON принимают BT.709 limited-range. Чтобы
получить красный — Y≈80, U≈90, V≈240. Для голубого — Y≈170, U≈170, V≈100.
Калькулятор: https://www.rapidtables.com/convert/color/rgb-to-yuv.html
(использовать BT.709 limited).
**В: При motion на 5 камерах layout не появляется, остаётся quad.**
О: Проверь `docker logs cfc-grid | grep "loaded N templates"` — там
должно быть ≥5 (есть `tpl_5..tpl_8`, `tpl_9`, `tpl_16`). Если нет —
templates.json не подгрузился (проверь syntax через `jq` либо
`python3 -m json.tool`).
**В: Frigate-bbox рисуется не на той камере.**
О: Проверь `--detection-cell` — там должен быть `frigate_camera`
который совпадает с `event.after.camera`. Composer связывает
detbox-overlay с pool-entry по `frigate_camera` (см.
`cfc_composer::pool::by_frigate_camera`).
## 12. Куда дальше
- [developer.md](developer.md) — внутреннее устройство, расширение
- [operations.md](operations.md) — build, deploy, troubleshooting
- README репо: краткий overview