#include #include #include #include #include #include #include #include #include "discovery.h" #include "transport.h" #include "protocol.h" #include "error.h" /* ------------------------------------------------------------------------- * Discovery — wait for first matching node * ------------------------------------------------------------------------- */ struct Discovery_Wait { struct Discovery_Peer peer; atomic_int found; }; static void on_peer_found(const struct Discovery_Peer *peer, void *userdata) { struct Discovery_Wait *w = userdata; if (atomic_load(&w->found)) { return; } w->peer = *peer; atomic_store(&w->found, 1); } static int wait_for_node(struct Discovery_Peer *peer_out, const char *self_name, int timeout_ms) { struct Discovery_Wait w; memset(&w, 0, sizeof(w)); atomic_init(&w.found, 0); struct Discovery_Config cfg = { .site_id = 0, .tcp_port = 0, .function_flags = 0, .name = self_name, .interval_ms = 2000, .timeout_intervals= 3, .on_peer_found = on_peer_found, .userdata = &w, }; struct Discovery *disc; struct App_Error e = discovery_create(&disc, &cfg); if (!APP_IS_OK(e)) { app_error_print(&e); return -1; } e = discovery_start(disc); if (!APP_IS_OK(e)) { app_error_print(&e); discovery_destroy(disc); return -1; } printf("waiting for a node (timeout %d ms)...\n", timeout_ms); int elapsed = 0; while (!atomic_load(&w.found) && elapsed < timeout_ms) { usleep(100000); elapsed += 100; } discovery_destroy(disc); if (!atomic_load(&w.found)) { fprintf(stderr, "no node found within %d ms\n", timeout_ms); return -1; } *peer_out = w.peer; return 0; } /* ------------------------------------------------------------------------- * Response handling — semaphore-based synchronisation * ------------------------------------------------------------------------- */ struct Query_State { sem_t sem; uint16_t last_request_id; int done; }; /* ------------------------------------------------------------------------- * ENUM_DEVICES callbacks * ------------------------------------------------------------------------- */ static void on_media_device( const char *path, uint8_t path_len, const char *driver, uint8_t driver_len, const char *model, uint8_t model_len, const char *bus_info, uint8_t bus_info_len, uint8_t vcount, void *ud) { (void)ud; printf(" media %.*s driver=%.*s model=%.*s bus=%.*s (%u video node(s))\n", (int)path_len, path, (int)driver_len, driver, (int)model_len, model, (int)bus_info_len, bus_info, (unsigned)vcount); } static void caps_str(uint32_t caps, char *buf, size_t len) { /* Build a compact comma-separated list of the relevant V4L2_CAP_* bits. */ static const struct { uint32_t bit; const char *name; } flags[] = { { 0x00000001u, "video-capture" }, { 0x00000002u, "video-output" }, { 0x00000010u, "vbi-capture" }, { 0x00800000u, "meta-capture" }, { 0x04000000u, "streaming" }, }; buf[0] = '\0'; size_t pos = 0; for (size_t i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) { if (!(caps & flags[i].bit)) { continue; } int n = snprintf(buf + pos, len - pos, "%s%s", pos ? "," : "", flags[i].name); if (n < 0 || (size_t)n >= len - pos) { break; } pos += (size_t)n; } } static void on_video_node( const char *path, uint8_t path_len, const char *ename, uint8_t ename_len, uint32_t etype, uint32_t eflags, uint32_t dcaps, uint8_t pflags, uint8_t is_capture, void *ud) { (void)eflags; (void)pflags; (void)ud; char caps[128]; caps_str(dcaps, caps, sizeof(caps)); printf(" video %.*s entity=%.*s type=0x%08x caps=[%s]%s\n", (int)path_len, path, (int)ename_len, ename, etype, caps, is_capture ? " [capture]" : ""); } static void on_standalone( const char *path, uint8_t path_len, const char *name, uint8_t name_len, void *ud) { (void)ud; printf(" standalone %.*s card=%.*s\n", (int)path_len, path, (int)name_len, name); } /* ------------------------------------------------------------------------- * ENUM_CONTROLS callbacks * ------------------------------------------------------------------------- */ static void on_control( uint32_t id, uint8_t type, uint32_t flags, const char *name, uint8_t name_len, int32_t min, int32_t max, int32_t step, int32_t default_val, int32_t current_val, uint8_t menu_count, void *ud) { (void)flags; (void)ud; printf(" ctrl id=0x%08x type=%u %.*s" " min=%d max=%d step=%d default=%d current=%d", id, type, (int)name_len, name, min, max, step, default_val, current_val); if (menu_count) { printf(" (%u menu items)", (unsigned)menu_count); } printf("\n"); } /* ------------------------------------------------------------------------- * Frame handler * ------------------------------------------------------------------------- */ struct Frame_State { struct Query_State *q; uint16_t pending_cmd; /* command we expect a response for */ uint16_t device_count; /* flat video node count from ENUM_DEVICES */ }; static void on_frame(struct Transport_Conn *conn, struct Transport_Frame *frame, void *userdata) { (void)conn; struct Frame_State *fs = userdata; if (frame->message_type == PROTO_MSG_CONTROL_RESPONSE) { switch (fs->pending_cmd) { case PROTO_CMD_ENUM_DEVICES: { struct Proto_Response_Header hdr; struct App_Error e = proto_read_enum_devices_response( frame->payload, frame->payload_length, &hdr, on_media_device, on_video_node, on_standalone, NULL, NULL); if (!APP_IS_OK(e)) { app_error_print(&e); } else if (hdr.status != PROTO_STATUS_OK) { fprintf(stderr, "ENUM_DEVICES failed: status=%u\n", hdr.status); } break; } case PROTO_CMD_ENUM_CONTROLS: { struct Proto_Response_Header hdr; struct App_Error e = proto_read_enum_controls_response( frame->payload, frame->payload_length, &hdr, on_control, NULL, NULL); if (!APP_IS_OK(e)) { app_error_print(&e); } else if (hdr.status != PROTO_STATUS_OK) { fprintf(stderr, "ENUM_CONTROLS failed: status=%u\n", hdr.status); } break; } default: break; } sem_post(&fs->q->sem); } free(frame->payload); } static void on_disconnect(struct Transport_Conn *conn, void *userdata) { (void)conn; (void)userdata; printf("disconnected\n"); } /* ------------------------------------------------------------------------- * Entry point * ------------------------------------------------------------------------- */ static void usage(void) { fprintf(stderr, "usage: query_cli [--timeout ms] [--controls device_index]\n" "\n" " Discovers a video node on the LAN and queries its devices.\n" " --timeout ms discovery timeout in ms (default 5000)\n" " --controls idx also enumerate controls for device at index idx\n"); } int main(int argc, char **argv) { int timeout_ms = 5000; int query_controls = -1; /* -1 = don't query controls */ for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--timeout") == 0 && i + 1 < argc) { timeout_ms = atoi(argv[++i]); } else if (strcmp(argv[i], "--controls") == 0 && i + 1 < argc) { query_controls = atoi(argv[++i]); } else if (strcmp(argv[i], "--help") == 0) { usage(); return 0; } } /* Discover a node */ struct Discovery_Peer peer; if (wait_for_node(&peer, "query_cli:0", timeout_ms) != 0) { return 1; } char addr_str[INET_ADDRSTRLEN]; struct in_addr in = { .s_addr = peer.addr }; inet_ntop(AF_INET, &in, addr_str, sizeof(addr_str)); printf("found node: %s addr=%s port=%u\n", peer.name, addr_str, peer.tcp_port); /* Set up connection state */ struct Query_State q; sem_init(&q.sem, 0, 0); struct Frame_State fs = { .q = &q, .pending_cmd = 0, .device_count = 0 }; struct Transport_Conn *conn; struct App_Error e = transport_connect(&conn, addr_str, peer.tcp_port, 16 * 1024 * 1024, on_frame, on_disconnect, &fs); if (!APP_IS_OK(e)) { app_error_print(&e); return 1; } /* ENUM_DEVICES */ printf("\ndevices:\n"); fs.pending_cmd = PROTO_CMD_ENUM_DEVICES; e = proto_write_enum_devices(conn, 1); if (!APP_IS_OK(e)) { app_error_print(&e); return 1; } sem_wait(&q.sem); /* ENUM_CONTROLS for a specific device if requested */ if (query_controls >= 0) { printf("\ncontrols for device %d:\n", query_controls); fs.pending_cmd = PROTO_CMD_ENUM_CONTROLS; e = proto_write_enum_controls(conn, 2, (uint16_t)query_controls); if (!APP_IS_OK(e)) { app_error_print(&e); return 1; } sem_wait(&q.sem); } transport_conn_close(conn); sem_destroy(&q.sem); return 0; }