src/node/main.c: ctrl_enum_cb was discarding menu_count and menu_items, causing empty dropdowns for all MENU/INTEGER_MENU controls. Added a menu item pool (MAX_MENU_POOL=128 items) to Ctrl_Build; the callback now copies items into the pool and sets menu_count/menu_items on the control. docs/protocol.md: add missing sections — str8 primitive, ENUM_DEVICES, ENUM_CONTROLS (with control type/flag tables and menu item notes), GET_CONTROL, and SET_CONTROL schemas. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
640 lines
19 KiB
C
640 lines
19 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include "config.h"
|
|
#include "discovery.h"
|
|
#include "transport.h"
|
|
#include "protocol.h"
|
|
#include "media_ctrl.h"
|
|
#include "v4l2_ctrl.h"
|
|
#include "error.h"
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Device enumeration
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
/* Entity type flag for a V4L2 I/O interface entity */
|
|
#define MEDIA_ENT_F_IO_V4L 0x10001u
|
|
|
|
#define MAX_VIDEO_NODES 32
|
|
#define MAX_MEDIA_DEVICES 8
|
|
#define MAX_VNODES_PER_MD 8
|
|
#define MAX_CONTROLS 256
|
|
|
|
/* 5 ("/dev/") + NAME_MAX (255) + 1 (NUL) = 261; round up */
|
|
#define DEV_PATH_MAX 264
|
|
|
|
struct VNode {
|
|
char path[DEV_PATH_MAX];
|
|
uint32_t dev_major;
|
|
uint32_t dev_minor;
|
|
uint32_t device_caps; /* V4L2_CAP_* from VIDIOC_QUERYCAP cap.device_caps */
|
|
int claimed;
|
|
char card[32]; /* VIDIOC_QUERYCAP card name, empty if unavailable */
|
|
};
|
|
|
|
struct MNode {
|
|
char path[DEV_PATH_MAX];
|
|
char entity_name[32];
|
|
uint32_t entity_type;
|
|
uint32_t entity_flags;
|
|
uint32_t device_caps;
|
|
uint8_t is_capture;
|
|
int vnode_index; /* index into VNode array */
|
|
};
|
|
|
|
struct MediaDev {
|
|
char path[DEV_PATH_MAX];
|
|
char driver[16];
|
|
char model[32];
|
|
char bus_info[32];
|
|
struct MNode vnodes[MAX_VNODES_PER_MD];
|
|
int vnode_count;
|
|
};
|
|
|
|
struct Device_List {
|
|
struct VNode vnodes[MAX_VIDEO_NODES];
|
|
int vnode_count;
|
|
struct MediaDev media[MAX_MEDIA_DEVICES];
|
|
int media_count;
|
|
};
|
|
|
|
static int scan_video_nodes(struct Device_List *dl) {
|
|
DIR *d = opendir("/dev");
|
|
if (!d) { return -1; }
|
|
|
|
struct dirent *ent;
|
|
while ((ent = readdir(d)) != NULL && dl->vnode_count < MAX_VIDEO_NODES) {
|
|
if (strncmp(ent->d_name, "video", 5) != 0) { continue; }
|
|
const char *suffix = ent->d_name + 5;
|
|
int numeric = (*suffix != '\0');
|
|
for (const char *p = suffix; *p; p++) {
|
|
if (*p < '0' || *p > '9') { numeric = 0; break; }
|
|
}
|
|
if (!numeric) { continue; }
|
|
|
|
struct VNode *v = &dl->vnodes[dl->vnode_count];
|
|
snprintf(v->path, sizeof(v->path), "/dev/%s", ent->d_name);
|
|
|
|
struct stat st;
|
|
if (stat(v->path, &st) != 0 || !S_ISCHR(st.st_mode)) { continue; }
|
|
v->dev_major = (uint32_t)major(st.st_rdev);
|
|
v->dev_minor = (uint32_t)minor(st.st_rdev);
|
|
v->claimed = 0;
|
|
v->card[0] = '\0';
|
|
|
|
/* Try to get card name */
|
|
int fd = open(v->path, O_RDONLY | O_NONBLOCK);
|
|
if (fd >= 0) {
|
|
struct v4l2_capability cap;
|
|
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
|
|
strncpy(v->card, (const char *)cap.card, sizeof(v->card) - 1);
|
|
v->card[sizeof(v->card) - 1] = '\0';
|
|
/* use per-node caps when available, fall back to physical caps */
|
|
v->device_caps = (cap.capabilities & V4L2_CAP_DEVICE_CAPS)
|
|
? cap.device_caps : cap.capabilities;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
dl->vnode_count++;
|
|
}
|
|
closedir(d);
|
|
return 0;
|
|
}
|
|
|
|
struct Entity_Cb_State {
|
|
struct Device_List *dl;
|
|
struct MediaDev *md;
|
|
};
|
|
|
|
static void entity_callback(const struct Media_Entity *entity, void *userdata) {
|
|
struct Entity_Cb_State *state = userdata;
|
|
struct Device_List *dl = state->dl;
|
|
struct MediaDev *md = state->md;
|
|
|
|
if (entity->dev_major == 0) { return; }
|
|
if (md->vnode_count >= MAX_VNODES_PER_MD) { return; }
|
|
|
|
/* Find matching video node by device number */
|
|
for (int i = 0; i < dl->vnode_count; i++) {
|
|
if (dl->vnodes[i].dev_major != entity->dev_major) { continue; }
|
|
if (dl->vnodes[i].dev_minor != entity->dev_minor) { continue; }
|
|
|
|
dl->vnodes[i].claimed = 1;
|
|
|
|
struct MNode *mn = &md->vnodes[md->vnode_count++];
|
|
strncpy(mn->path, dl->vnodes[i].path, sizeof(mn->path) - 1);
|
|
mn->path[sizeof(mn->path) - 1] = '\0';
|
|
strncpy(mn->entity_name, entity->name, sizeof(mn->entity_name) - 1);
|
|
mn->entity_name[sizeof(mn->entity_name) - 1] = '\0';
|
|
mn->entity_type = entity->type;
|
|
mn->entity_flags = entity->flags;
|
|
mn->device_caps = dl->vnodes[i].device_caps;
|
|
mn->is_capture = (mn->device_caps & V4L2_CAP_VIDEO_CAPTURE) ? 1 : 0;
|
|
mn->vnode_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void scan_media_devices(struct Device_List *dl) {
|
|
DIR *d = opendir("/dev");
|
|
if (!d) { return; }
|
|
|
|
struct dirent *ent;
|
|
while ((ent = readdir(d)) != NULL && dl->media_count < MAX_MEDIA_DEVICES) {
|
|
if (strncmp(ent->d_name, "media", 5) != 0) { continue; }
|
|
const char *suffix = ent->d_name + 5;
|
|
int numeric = (*suffix != '\0');
|
|
for (const char *p = suffix; *p; p++) {
|
|
if (*p < '0' || *p > '9') { numeric = 0; break; }
|
|
}
|
|
if (!numeric) { continue; }
|
|
|
|
struct MediaDev *md = &dl->media[dl->media_count];
|
|
snprintf(md->path, sizeof(md->path), "/dev/%s", ent->d_name);
|
|
md->vnode_count = 0;
|
|
|
|
struct Media_Ctrl *ctrl;
|
|
if (!APP_IS_OK(media_ctrl_open(md->path, &ctrl))) { continue; }
|
|
|
|
struct Media_Device_Info info;
|
|
if (APP_IS_OK(media_ctrl_get_info(ctrl, &info))) {
|
|
strncpy(md->driver, info.driver, sizeof(md->driver) - 1);
|
|
strncpy(md->model, info.model, sizeof(md->model) - 1);
|
|
strncpy(md->bus_info, info.bus_info, sizeof(md->bus_info) - 1);
|
|
md->driver[sizeof(md->driver) - 1] = '\0';
|
|
md->model[sizeof(md->model) - 1] = '\0';
|
|
md->bus_info[sizeof(md->bus_info) - 1] = '\0';
|
|
}
|
|
|
|
struct Entity_Cb_State state = { .dl = dl, .md = md };
|
|
media_ctrl_enum_entities(ctrl, entity_callback, &state);
|
|
media_ctrl_close(ctrl);
|
|
|
|
dl->media_count++;
|
|
}
|
|
closedir(d);
|
|
}
|
|
|
|
static void build_device_list(struct Device_List *dl) {
|
|
memset(dl, 0, sizeof(*dl));
|
|
scan_video_nodes(dl);
|
|
scan_media_devices(dl);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Control enumeration helpers
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
#define MAX_MENU_POOL 128 /* total menu items across all controls */
|
|
|
|
struct Ctrl_Build {
|
|
struct Proto_Control_Info items[MAX_CONTROLS];
|
|
char names[MAX_CONTROLS][32];
|
|
struct Proto_Menu_Item menu_pool[MAX_MENU_POOL];
|
|
char menu_names[MAX_MENU_POOL][32];
|
|
int menu_pool_used;
|
|
int count;
|
|
};
|
|
|
|
static void ctrl_enum_cb(
|
|
const struct V4l2_Ctrl_Desc *desc,
|
|
uint32_t menu_count, const struct V4l2_Menu_Item *menu_items,
|
|
void *userdata)
|
|
{
|
|
struct Ctrl_Build *b = userdata;
|
|
if (b->count >= MAX_CONTROLS) { return; }
|
|
|
|
int i = b->count++;
|
|
strncpy(b->names[i], desc->name, 31);
|
|
b->names[i][31] = '\0';
|
|
|
|
b->items[i].id = desc->id;
|
|
b->items[i].type = (uint8_t)desc->type;
|
|
b->items[i].flags = desc->flags;
|
|
b->items[i].name = b->names[i];
|
|
b->items[i].min = desc->min;
|
|
b->items[i].max = desc->max;
|
|
b->items[i].step = desc->step;
|
|
b->items[i].default_val = desc->default_value;
|
|
b->items[i].current_val = desc->current_value;
|
|
b->items[i].menu_count = 0;
|
|
b->items[i].menu_items = NULL;
|
|
|
|
if (menu_count > 0 && menu_items) {
|
|
int avail = MAX_MENU_POOL - b->menu_pool_used;
|
|
uint8_t mc = (menu_count > (uint32_t)avail) ? (uint8_t)avail : (uint8_t)menu_count;
|
|
if (mc > 0) {
|
|
b->items[i].menu_items = &b->menu_pool[b->menu_pool_used];
|
|
b->items[i].menu_count = mc;
|
|
for (uint8_t j = 0; j < mc; j++) {
|
|
int slot = b->menu_pool_used + j;
|
|
strncpy(b->menu_names[slot], menu_items[j].name, 31);
|
|
b->menu_names[slot][31] = '\0';
|
|
b->menu_pool[slot].index = menu_items[j].index;
|
|
b->menu_pool[slot].name = b->menu_names[slot];
|
|
b->menu_pool[slot].int_value = menu_items[j].value;
|
|
}
|
|
b->menu_pool_used += mc;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Node state
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
struct Node {
|
|
struct Config *config;
|
|
struct Transport_Server *server;
|
|
struct Discovery *discovery;
|
|
struct Device_List devices;
|
|
};
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Request handlers
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
static void handle_enum_devices(struct Node *node,
|
|
struct Transport_Conn *conn, uint16_t request_id)
|
|
{
|
|
/* Build Proto_Media_Device_Info array */
|
|
struct Proto_Video_Node_Info vnodes[MAX_MEDIA_DEVICES * MAX_VNODES_PER_MD];
|
|
struct Proto_Media_Device_Info mdevs[MAX_MEDIA_DEVICES];
|
|
int vnode_offset = 0;
|
|
|
|
for (int i = 0; i < node->devices.media_count; i++) {
|
|
struct MediaDev *md = &node->devices.media[i];
|
|
mdevs[i].path = md->path;
|
|
mdevs[i].driver = md->driver;
|
|
mdevs[i].model = md->model;
|
|
mdevs[i].bus_info = md->bus_info;
|
|
mdevs[i].video_node_count = (uint8_t)md->vnode_count;
|
|
mdevs[i].video_nodes = &vnodes[vnode_offset];
|
|
|
|
for (int j = 0; j < md->vnode_count; j++) {
|
|
struct MNode *mn = &md->vnodes[j];
|
|
vnodes[vnode_offset + j].path = mn->path;
|
|
vnodes[vnode_offset + j].entity_name = mn->entity_name;
|
|
vnodes[vnode_offset + j].entity_type = mn->entity_type;
|
|
vnodes[vnode_offset + j].entity_flags= mn->entity_flags;
|
|
vnodes[vnode_offset + j].device_caps = mn->device_caps;
|
|
vnodes[vnode_offset + j].pad_flags = 0;
|
|
vnodes[vnode_offset + j].is_capture = mn->is_capture;
|
|
}
|
|
vnode_offset += md->vnode_count;
|
|
}
|
|
|
|
/* Build standalone list */
|
|
struct Proto_Standalone_Device_Info standalone[MAX_VIDEO_NODES];
|
|
int standalone_count = 0;
|
|
for (int i = 0; i < node->devices.vnode_count; i++) {
|
|
if (node->devices.vnodes[i].claimed) { continue; }
|
|
standalone[standalone_count].path = node->devices.vnodes[i].path;
|
|
standalone[standalone_count].name = node->devices.vnodes[i].card;
|
|
standalone_count++;
|
|
}
|
|
|
|
struct App_Error e = proto_write_enum_devices_response(conn,
|
|
request_id, PROTO_STATUS_OK,
|
|
mdevs, (uint16_t)node->devices.media_count,
|
|
standalone, (uint16_t)standalone_count);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
|
}
|
|
|
|
static void handle_enum_controls(struct Node *node,
|
|
struct Transport_Conn *conn,
|
|
const uint8_t *payload, uint32_t length)
|
|
{
|
|
struct Proto_Enum_Controls_Req req;
|
|
struct App_Error e = proto_read_enum_controls_req(payload, length, &req);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, 0,
|
|
PROTO_STATUS_INVALID_PARAMS, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
/* Resolve device_index to a path across media-owned + standalone nodes */
|
|
const char *path = NULL;
|
|
int idx = (int)req.device_index;
|
|
for (int i = 0; i < node->devices.media_count && path == NULL; i++) {
|
|
struct MediaDev *md = &node->devices.media[i];
|
|
if (idx < md->vnode_count) {
|
|
path = md->vnodes[idx].path;
|
|
} else {
|
|
idx -= md->vnode_count;
|
|
}
|
|
}
|
|
if (path == NULL) {
|
|
/* Check standalone */
|
|
for (int i = 0; i < node->devices.vnode_count; i++) {
|
|
if (node->devices.vnodes[i].claimed) { continue; }
|
|
if (idx == 0) { path = node->devices.vnodes[i].path; break; }
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
if (path == NULL) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_NOT_FOUND, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
struct V4l2_Ctrl_Handle *handle;
|
|
e = v4l2_ctrl_open(path, &handle);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_ERROR, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
struct Ctrl_Build build = { .count = 0 };
|
|
v4l2_ctrl_enumerate(handle, ctrl_enum_cb, &build);
|
|
v4l2_ctrl_close(handle);
|
|
|
|
e = proto_write_enum_controls_response(conn,
|
|
req.request_id, PROTO_STATUS_OK,
|
|
build.items, (uint16_t)build.count);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
|
}
|
|
|
|
static void handle_get_control(struct Node *node,
|
|
struct Transport_Conn *conn,
|
|
const uint8_t *payload, uint32_t length)
|
|
{
|
|
struct Proto_Get_Control_Req req;
|
|
struct App_Error e = proto_read_get_control_req(payload, length, &req);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, 0,
|
|
PROTO_STATUS_INVALID_PARAMS, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
/* Same device resolution as enum_controls */
|
|
const char *path = NULL;
|
|
int idx = (int)req.device_index;
|
|
for (int i = 0; i < node->devices.media_count && path == NULL; i++) {
|
|
struct MediaDev *md = &node->devices.media[i];
|
|
if (idx < md->vnode_count) {
|
|
path = md->vnodes[idx].path;
|
|
} else {
|
|
idx -= md->vnode_count;
|
|
}
|
|
}
|
|
if (path == NULL) {
|
|
for (int i = 0; i < node->devices.vnode_count; i++) {
|
|
if (node->devices.vnodes[i].claimed) { continue; }
|
|
if (idx == 0) { path = node->devices.vnodes[i].path; break; }
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
if (path == NULL) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_NOT_FOUND, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
struct V4l2_Ctrl_Handle *handle;
|
|
e = v4l2_ctrl_open(path, &handle);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_ERROR, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
int32_t value;
|
|
e = v4l2_ctrl_get(handle, req.control_id, &value);
|
|
v4l2_ctrl_close(handle);
|
|
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_ERROR, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
e = proto_write_get_control_response(conn,
|
|
req.request_id, PROTO_STATUS_OK, value);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); }
|
|
}
|
|
|
|
static void handle_set_control(struct Node *node,
|
|
struct Transport_Conn *conn,
|
|
const uint8_t *payload, uint32_t length)
|
|
{
|
|
struct Proto_Set_Control_Req req;
|
|
struct App_Error e = proto_read_set_control_req(payload, length, &req);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, 0,
|
|
PROTO_STATUS_INVALID_PARAMS, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
const char *path = NULL;
|
|
int idx = (int)req.device_index;
|
|
for (int i = 0; i < node->devices.media_count && path == NULL; i++) {
|
|
struct MediaDev *md = &node->devices.media[i];
|
|
if (idx < md->vnode_count) {
|
|
path = md->vnodes[idx].path;
|
|
} else {
|
|
idx -= md->vnode_count;
|
|
}
|
|
}
|
|
if (path == NULL) {
|
|
for (int i = 0; i < node->devices.vnode_count; i++) {
|
|
if (node->devices.vnodes[i].claimed) { continue; }
|
|
if (idx == 0) { path = node->devices.vnodes[i].path; break; }
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
if (path == NULL) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_NOT_FOUND, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
struct V4l2_Ctrl_Handle *handle;
|
|
e = v4l2_ctrl_open(path, &handle);
|
|
if (!APP_IS_OK(e)) {
|
|
proto_write_control_response(conn, req.request_id,
|
|
PROTO_STATUS_ERROR, NULL, 0);
|
|
return;
|
|
}
|
|
|
|
e = v4l2_ctrl_set(handle, req.control_id, req.value);
|
|
v4l2_ctrl_close(handle);
|
|
|
|
uint16_t status = APP_IS_OK(e) ? PROTO_STATUS_OK : PROTO_STATUS_ERROR;
|
|
proto_write_control_response(conn, req.request_id, status, NULL, 0);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Transport callbacks
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
static void on_frame(struct Transport_Conn *conn,
|
|
struct Transport_Frame *frame, void *userdata)
|
|
{
|
|
struct Node *node = userdata;
|
|
|
|
if (frame->message_type == PROTO_MSG_CONTROL_REQUEST) {
|
|
struct Proto_Request_Header hdr;
|
|
struct App_Error e = proto_read_request_header(
|
|
frame->payload, frame->payload_length, &hdr);
|
|
if (!APP_IS_OK(e)) {
|
|
free(frame->payload);
|
|
return;
|
|
}
|
|
|
|
switch (hdr.command) {
|
|
case PROTO_CMD_ENUM_DEVICES:
|
|
handle_enum_devices(node, conn, hdr.request_id);
|
|
break;
|
|
case PROTO_CMD_ENUM_CONTROLS:
|
|
handle_enum_controls(node, conn,
|
|
frame->payload, frame->payload_length);
|
|
break;
|
|
case PROTO_CMD_GET_CONTROL:
|
|
handle_get_control(node, conn,
|
|
frame->payload, frame->payload_length);
|
|
break;
|
|
case PROTO_CMD_SET_CONTROL:
|
|
handle_set_control(node, conn,
|
|
frame->payload, frame->payload_length);
|
|
break;
|
|
default:
|
|
proto_write_control_response(conn, hdr.request_id,
|
|
PROTO_STATUS_UNKNOWN_CMD, NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(frame->payload);
|
|
}
|
|
|
|
static void on_connect(struct Transport_Conn *conn, void *userdata) {
|
|
(void)conn; (void)userdata;
|
|
printf("peer connected\n");
|
|
}
|
|
|
|
static void on_disconnect(struct Transport_Conn *conn, void *userdata) {
|
|
(void)conn; (void)userdata;
|
|
printf("peer disconnected\n");
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Config schema
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
static const struct Config_Flag_Def function_flag_defs[] = {
|
|
{ "source", DISCOVERY_FLAG_SOURCE },
|
|
{ "relay", DISCOVERY_FLAG_RELAY },
|
|
{ "sink", DISCOVERY_FLAG_SINK },
|
|
{ "controller", DISCOVERY_FLAG_CONTROLLER },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static const struct Config_Def schema[] = {
|
|
{ "node", "name", CONFIG_STRING, "unnamed:0", NULL },
|
|
{ "node", "site_id", CONFIG_UINT16, "0", NULL },
|
|
{ "node", "tcp_port", CONFIG_UINT16, "8000", NULL },
|
|
{ "node", "function", CONFIG_FLAGS, "source", function_flag_defs },
|
|
{ "discovery", "interval_ms", CONFIG_UINT32, "5000", NULL },
|
|
{ "discovery", "timeout_intervals", CONFIG_UINT32, "3", NULL },
|
|
{ "transport", "max_connections", CONFIG_UINT32, "16", NULL },
|
|
{ "transport", "max_payload", CONFIG_UINT32, "16777216", NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Entry point
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
static void usage(void) {
|
|
fprintf(stderr,
|
|
"usage: video-node <config-file>\n"
|
|
" video-node --defaults\n");
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 2) { usage(); return 1; }
|
|
|
|
struct Node node;
|
|
memset(&node, 0, sizeof(node));
|
|
|
|
/* Load config */
|
|
struct App_Error e;
|
|
if (strcmp(argv[1], "--defaults") == 0) {
|
|
e = config_defaults(&node.config, schema);
|
|
} else {
|
|
e = config_load(&node.config, argv[1], schema);
|
|
}
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); return 1; }
|
|
|
|
uint16_t tcp_port = config_get_u16(node.config, "node", "tcp_port");
|
|
uint16_t site_id = config_get_u16(node.config, "node", "site_id");
|
|
uint32_t func = config_get_flags(node.config, "node", "function");
|
|
const char *name = config_get_str(node.config, "node", "name");
|
|
uint32_t interval = config_get_u32(node.config, "discovery", "interval_ms");
|
|
uint32_t timeout_i = config_get_u32(node.config, "discovery", "timeout_intervals");
|
|
uint32_t max_conn = config_get_u32(node.config, "transport", "max_connections");
|
|
uint32_t max_pay = config_get_u32(node.config, "transport", "max_payload");
|
|
|
|
printf("node: %s port=%u site=%u\n", name, tcp_port, site_id);
|
|
|
|
/* Enumerate devices */
|
|
printf("scanning devices...\n");
|
|
build_device_list(&node.devices);
|
|
printf("found %d media device(s), %d video node(s)\n",
|
|
node.devices.media_count, node.devices.vnode_count);
|
|
|
|
/* Start transport server */
|
|
struct Transport_Server_Config srv_cfg = {
|
|
.port = tcp_port,
|
|
.max_connections = (int)max_conn,
|
|
.max_payload = max_pay,
|
|
.on_frame = on_frame,
|
|
.on_connect = on_connect,
|
|
.on_disconnect = on_disconnect,
|
|
.userdata = &node,
|
|
};
|
|
|
|
e = transport_server_create(&node.server, &srv_cfg);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); return 1; }
|
|
|
|
e = transport_server_start(node.server);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); return 1; }
|
|
|
|
/* Start discovery */
|
|
struct Discovery_Config disc_cfg = {
|
|
.site_id = site_id,
|
|
.tcp_port = tcp_port,
|
|
.function_flags = (uint16_t)func,
|
|
.name = name,
|
|
.interval_ms = interval,
|
|
.timeout_intervals= timeout_i,
|
|
.on_peer_found = NULL,
|
|
.on_peer_lost = NULL,
|
|
};
|
|
|
|
e = discovery_create(&node.discovery, &disc_cfg);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); return 1; }
|
|
|
|
e = discovery_start(node.discovery);
|
|
if (!APP_IS_OK(e)) { app_error_print(&e); return 1; }
|
|
|
|
printf("ready\n");
|
|
pause();
|
|
return 0;
|
|
}
|