Phase 4: ZMQ control plane + MQTT health publisher
Phase 4a — control plane через ZMQ REP socket с JSON-командами.
Позволяет операторам менять text overlay'и runtime'ом без рестарта.
Live-validated: команды set_text label-parking → "⚠ ВНИМАНИЕ ⚠"
и set_text label-gate → "Машина у ворот" отрабатывают, текст
обновляется в потоке RTSP без перерывов.
Phase 4b — MQTT health publisher через libmosquitto. Каждые 10с в
composer/<instance>/health публикуется JSON {active,stale,dead,total,
uptime_s} с retain=true. Опционально публикуется HA MQTT discovery
config — четыре сенсора (active/stale/dead/total) появляются в HA
автоматически с expire_after=30s.
Содержимое:
- include/cuframes_composer/overlay.h — cfc_overlay_set_id/get_id/get_type
для lookup'а через control plane.
- include/cuframes_composer/composer.h — cfc_composer_find_overlay(id).
- include/cuframes_composer/control.h — cfc_control_config_t (endpoint +
composer + cuda_ctx для text rebuild в worker thread).
- src/control.c — ZMQ REP socket в фоновом потоке + zmq_poll с 200мс
timeout'ом для проверки stop_flag. JSON-диспатчер для команд ping /
health / set_text / set_visible / list_overlays. cuCtxSetCurrent
на старте worker'а — без этого update_text валится на cuMemAlloc.
- include/cuframes_composer/health.h — cfc_health_config_t (host/port/
user/pass + topic_prefix/instance + interval + publish_discovery).
- src/health.c — mosquitto_connect_async + loop_start + background
thread с publish health каждые N секунд + один разовый publish_discovery
для HA.
- examples/grid_record — флаги --control tcp://0.0.0.0:5599,
--mqtt host[:port], --mqtt-instance NAME, --mqtt-user/--mqtt-pass.
Для text overlay'ев — prefix "id=NAME:" в --text задаёт control-plane ID.
CMakeLists.txt — find_library(zmq), find_library(json-c),
find_library(mosquitto) + linkage всех трёх.
Production TODO: создать отдельного MQTT user'а для composer'а вместо
переиспользования frigate creds (используется только в smoke).
This commit is contained in:
@@ -84,6 +84,14 @@ int cfc_composer_compose(
|
||||
* Порядок добавления = z-order (последний рисуется поверх). Лимит — 64. */
|
||||
int cfc_composer_add_overlay(cfc_composer_t *comp, cfc_overlay_t *ov);
|
||||
|
||||
/* Найти overlay по ID (если был задан через cfc_overlay_set_id). Возвращает
|
||||
* NULL если не найден. Thread-safe — composer держит overlays в массиве,
|
||||
* пока add/destroy не пересекаются с lookup'ом — но control plane вызывает
|
||||
* это из своего потока, draw — из своего; они оба только читают список,
|
||||
* а update полей overlay'я делается через cfc_overlay_update_text и пр.
|
||||
* (содержимое overlay'я под mutex'ом не лежит, нужно лочиться вызывающему). */
|
||||
cfc_overlay_t *cfc_composer_find_overlay(cfc_composer_t *comp, const char *id);
|
||||
|
||||
/* Получить layout статистику по источникам — для debug / health-репортов. */
|
||||
typedef struct cfc_composer_health {
|
||||
int total; /* всего источников */
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/* cuframes-composer — control plane через ZMQ + JSON.
|
||||
*
|
||||
* Слушает JSON-команды на ZMQ REP socket, диспатчит их в composer/overlay.
|
||||
* Используется операторами для:
|
||||
* - изменения text overlay'я live ("NO SIGNAL" → "RECORDING")
|
||||
* - toggle visible/alpha без рестарта
|
||||
* - health-репортов (active/stale/dead) для observability и автоматики
|
||||
*
|
||||
* Протокол:
|
||||
* Запрос: JSON-объект {"cmd": "<команда>", ...}
|
||||
* Ответ: {"ok": true, ...} либо {"error": "<сообщение>"}
|
||||
*
|
||||
* Команды Phase 4a:
|
||||
* {"cmd": "ping"} → {"ok":true,"pong":1}
|
||||
* {"cmd": "health"} → {"ok":true,"active":N,"stale":M,"dead":K}
|
||||
* {"cmd": "list_overlays"} → {"ok":true,"overlays":[{"id":"...","type":N},...]}
|
||||
* {"cmd": "set_text", "id": "...",
|
||||
* "text": "...", "r":255,"g":255,"b":255,"visible":1}
|
||||
* → {"ok":true}
|
||||
* {"cmd": "set_visible", "id": "...",
|
||||
* "visible": 0|1} → {"ok":true}
|
||||
*
|
||||
* Сервер работает в фоновом потоке. REP socket — блокирующий, но мы используем
|
||||
* zmq_poll с таймаутом для проверки stop_flag'а.
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_CONTROL_H
|
||||
#define CUFRAMES_COMPOSER_CONTROL_H
|
||||
|
||||
#include "composer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct cfc_control_config {
|
||||
const char *bind_endpoint; /* "tcp://0.0.0.0:5599" или "ipc:///run/composer.sock" */
|
||||
cfc_composer_t *composer; /* для health-запросов и lookup overlay'ев */
|
||||
CUcontext cuda_ctx; /* push'нется в control thread (для cuMemAlloc в text rebuild) */
|
||||
} cfc_control_config_t;
|
||||
|
||||
typedef struct cfc_control cfc_control_t;
|
||||
|
||||
/* Создать control plane сервер и запустить background thread. */
|
||||
int cfc_control_create(const cfc_control_config_t *cfg, cfc_control_t **out);
|
||||
|
||||
/* Остановить и освободить. */
|
||||
int cfc_control_destroy(cfc_control_t *ctl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_CONTROL_H */
|
||||
@@ -0,0 +1,49 @@
|
||||
/* cuframes-composer — MQTT health publisher для observability.
|
||||
*
|
||||
* Подключается к MQTT-брокеру, периодически публикует JSON со статистикой
|
||||
* композитора (active/stale/dead источников, framerate, uptime).
|
||||
*
|
||||
* Топик: <prefix>/<instance>/health QoS 1, retain=true.
|
||||
* Так же при старте публикует HA discovery config — для автоматического
|
||||
* появления сенсора в Home Assistant. expire_after короткий (30 секунд) —
|
||||
* если поток упал, HA подсветит сенсор «Unavailable» спустя 30с.
|
||||
*
|
||||
* Lifecycle:
|
||||
* create: mosquitto_new + connect_async + начать background thread
|
||||
* loop: каждые interval_sec секунд → mosquitto_publish + mosquitto_loop
|
||||
* destroy: stop_flag + DISCONNECT + cleanup
|
||||
*
|
||||
* Лицензия: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#ifndef CUFRAMES_COMPOSER_HEALTH_H
|
||||
#define CUFRAMES_COMPOSER_HEALTH_H
|
||||
|
||||
#include "composer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct cfc_health_config {
|
||||
const char *host; /* "cctv-mosquitto" / "192.168.88.23" */
|
||||
int port; /* 1883 без TLS */
|
||||
const char *username; /* nullable */
|
||||
const char *password; /* nullable */
|
||||
const char *topic_prefix; /* "composer" (топик: composer/<instance>/health) */
|
||||
const char *instance; /* "cfc-grid" — уникальный ID composer'а */
|
||||
int interval_sec; /* 10 = публикуем раз в 10 секунд */
|
||||
cfc_composer_t *composer; /* читает get_health() */
|
||||
int publish_discovery; /* 1 = опубликовать HA discovery config при старте */
|
||||
} cfc_health_config_t;
|
||||
|
||||
typedef struct cfc_health cfc_health_t;
|
||||
|
||||
int cfc_health_create(const cfc_health_config_t *cfg, cfc_health_t **out);
|
||||
int cfc_health_destroy(cfc_health_t *h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CUFRAMES_COMPOSER_HEALTH_H */
|
||||
@@ -47,6 +47,17 @@ typedef enum cfc_overlay_type {
|
||||
|
||||
typedef struct cfc_overlay cfc_overlay_t;
|
||||
|
||||
/* Назначить overlay'ю короткий ID для lookup через control plane (Phase 4).
|
||||
* ID копируется внутрь overlay'я (макс 31 символ). Без ID overlay тоже работает,
|
||||
* но управлять им runtime'ом нельзя. Можно вызвать после create. */
|
||||
int cfc_overlay_set_id(cfc_overlay_t *ov, const char *id);
|
||||
|
||||
/* Получить ID overlay'я (NULL если не задан). */
|
||||
const char *cfc_overlay_get_id(const cfc_overlay_t *ov);
|
||||
|
||||
/* Получить тип overlay'я. */
|
||||
cfc_overlay_type_t cfc_overlay_get_type(const cfc_overlay_t *ov);
|
||||
|
||||
/* Параметры BORDER overlay'я. */
|
||||
typedef struct cfc_overlay_border_config {
|
||||
int x, y, w, h; /* прямоугольник в full-res пикселях */
|
||||
|
||||
Reference in New Issue
Block a user