Fix menu controls: wire up menu items in ctrl_enum_cb; document control commands
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>
This commit is contained in:
125
docs/protocol.md
125
docs/protocol.md
@@ -266,3 +266,128 @@ No Avahi or Bonjour dependency — nodes open a raw UDP multicast socket directl
|
|||||||
`site_id = 0` means "local / unassigned". Both sides of a site-to-site link will independently default to `0`, so a gateway cannot forward announcements across the boundary without rewriting the field — all nodes on both sides would appear identical.
|
`site_id = 0` means "local / unassigned". Both sides of a site-to-site link will independently default to `0`, so a gateway cannot forward announcements across the boundary without rewriting the field — all nodes on both sides would appear identical.
|
||||||
|
|
||||||
The gateway assigns a distinct non-zero `site_id` to each side and rewrites `site_id` in all announcements (and any protocol messages carrying a `site_id`) as they cross the boundary. Receiving an announcement with `site_id = 0` on a cross-site link is a gateway protocol error.
|
The gateway assigns a distinct non-zero `site_id` to each side and rewrites `site_id` in all announcements (and any protocol messages carrying a `site_id`) as they cross the boundary. Receiving an announcement with `site_id = 0` on a cross-site link is a gateway protocol error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serialisation Primitives
|
||||||
|
|
||||||
|
`str8` — a length-prefixed UTF-8 string, not NUL-terminated on the wire:
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------+-------------------+
|
||||||
|
| length: u8 | bytes: length × u8 |
|
||||||
|
+------------+-------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Maximum string length is 255 bytes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Control Commands
|
||||||
|
|
||||||
|
All requests follow the `CONTROL_REQUEST` frame format (request_id + command + command-specific fields). All responses follow the `CONTROL_RESPONSE` frame format (request_id + status + response-specific fields).
|
||||||
|
|
||||||
|
### `ENUM_DEVICES` (0x0003)
|
||||||
|
|
||||||
|
**Request** — no extra fields beyond request_id and command.
|
||||||
|
|
||||||
|
**Response** on status `OK`:
|
||||||
|
|
||||||
|
```
|
||||||
|
+-------------------+
|
||||||
|
| media_count: u16 |
|
||||||
|
+-------------------+
|
||||||
|
× media_count:
|
||||||
|
+------------+------------+-----------+-------------+-----------------+
|
||||||
|
| path: str8 | driver:str8| model:str8| bus_info:str8| vnode_count: u8 |
|
||||||
|
+------------+------------+-----------+-------------+-----------------+
|
||||||
|
× vnode_count:
|
||||||
|
+------------+-----------------+------------------+-------------------+------------------+----------------+------------------+
|
||||||
|
| path: str8 | entity_name:str8| entity_type: u32 | entity_flags: u32 | device_caps: u32 | pad_flags: u8 | is_capture: u8 |
|
||||||
|
+------------+-----------------+------------------+-------------------+------------------+----------------+------------------+
|
||||||
|
+----------------------+
|
||||||
|
| standalone_count: u16 |
|
||||||
|
+----------------------+
|
||||||
|
× standalone_count:
|
||||||
|
+------------+------------+
|
||||||
|
| path: str8 | name: str8 |
|
||||||
|
+------------+------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
`device_caps` carries `V4L2_CAP_*` bits from `VIDIOC_QUERYCAP` (using `device_caps` if `V4L2_CAP_DEVICE_CAPS` is set, otherwise `capabilities`). Notable bits: `0x00000001` = `VIDEO_CAPTURE`, `0x00800000` = `META_CAPTURE`.
|
||||||
|
|
||||||
|
`is_capture` is `1` if `device_caps & V4L2_CAP_VIDEO_CAPTURE`, else `0`.
|
||||||
|
|
||||||
|
### `ENUM_CONTROLS` (0x0004)
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
|
||||||
|
```
|
||||||
|
+--------------------+
|
||||||
|
| device_index: u16 |
|
||||||
|
+--------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** on status `OK`:
|
||||||
|
|
||||||
|
```
|
||||||
|
+---------------+
|
||||||
|
| count: u16 |
|
||||||
|
+---------------+
|
||||||
|
× count:
|
||||||
|
+---------+---------+----------+------------+----------+---------+----------+-------------+-----------------+------------------+
|
||||||
|
| id: u32 | type: u8 | flags: u32 | name: str8 | min: i32 | max: i32 | step: i32 | default: i32 | current: i32 | menu_count: u8 |
|
||||||
|
+---------+---------+----------+------------+----------+---------+----------+-------------+-----------------+------------------+
|
||||||
|
× menu_count:
|
||||||
|
+------------+------------+------------------+
|
||||||
|
| index: u32 | name: str8 | int_value: i64 |
|
||||||
|
+------------+------------+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
`type` values match `V4L2_CTRL_TYPE_*`:
|
||||||
|
|
||||||
|
| Value | Type |
|
||||||
|
|---|---|
|
||||||
|
| `1` | `INTEGER` — use a slider |
|
||||||
|
| `2` | `BOOLEAN` — use a checkbox |
|
||||||
|
| `3` | `MENU` — use a select; `name` of each menu item is the label |
|
||||||
|
| `4` | `BUTTON` — use a button |
|
||||||
|
| `9` | `INTEGER_MENU` — use a select; `int_value` of each menu item is the label |
|
||||||
|
|
||||||
|
`flags` bits (from `V4L2_CTRL_FLAG_*`): `0x0001` = disabled, `0x0002` = grabbed (read-only due to another active control), `0x0004` = read-only.
|
||||||
|
|
||||||
|
For `MENU` and `INTEGER_MENU` controls, set the control value to a menu item's `index` (not its `int_value`). `int_value` is informational display text only.
|
||||||
|
|
||||||
|
Menu items may have non-contiguous `index` values (gaps where the driver returns `EINVAL` for `VIDIOC_QUERYMENU`).
|
||||||
|
|
||||||
|
### `GET_CONTROL` (0x0005)
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
|
||||||
|
```
|
||||||
|
+--------------------+------------------+
|
||||||
|
| device_index: u16 | control_id: u32 |
|
||||||
|
+--------------------+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** on status `OK`:
|
||||||
|
|
||||||
|
```
|
||||||
|
+-------------+
|
||||||
|
| value: i32 |
|
||||||
|
+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SET_CONTROL` (0x0006)
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
|
||||||
|
```
|
||||||
|
+--------------------+------------------+-------------+
|
||||||
|
| device_index: u16 | control_id: u32 | value: i32 |
|
||||||
|
+--------------------+------------------+-------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** — no extra fields beyond request_id and status.
|
||||||
|
|
||||||
|
For `MENU` and `INTEGER_MENU` controls, `value` must be a valid menu item `index` as returned by `ENUM_CONTROLS`.
|
||||||
|
|||||||
@@ -195,9 +195,14 @@ static void build_device_list(struct Device_List *dl) {
|
|||||||
* Control enumeration helpers
|
* Control enumeration helpers
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#define MAX_MENU_POOL 128 /* total menu items across all controls */
|
||||||
|
|
||||||
struct Ctrl_Build {
|
struct Ctrl_Build {
|
||||||
struct Proto_Control_Info items[MAX_CONTROLS];
|
struct Proto_Control_Info items[MAX_CONTROLS];
|
||||||
char names[MAX_CONTROLS][32];
|
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;
|
int count;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,7 +211,6 @@ static void ctrl_enum_cb(
|
|||||||
uint32_t menu_count, const struct V4l2_Menu_Item *menu_items,
|
uint32_t menu_count, const struct V4l2_Menu_Item *menu_items,
|
||||||
void *userdata)
|
void *userdata)
|
||||||
{
|
{
|
||||||
(void)menu_count; (void)menu_items;
|
|
||||||
struct Ctrl_Build *b = userdata;
|
struct Ctrl_Build *b = userdata;
|
||||||
if (b->count >= MAX_CONTROLS) { return; }
|
if (b->count >= MAX_CONTROLS) { return; }
|
||||||
|
|
||||||
@@ -225,6 +229,24 @@ static void ctrl_enum_cb(
|
|||||||
b->items[i].current_val = desc->current_value;
|
b->items[i].current_val = desc->current_value;
|
||||||
b->items[i].menu_count = 0;
|
b->items[i].menu_count = 0;
|
||||||
b->items[i].menu_items = NULL;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user