Add protocol module, video-node binary, query/web CLI tools
- Protocol module: framed binary encoding for control requests/responses (ENUM_DEVICES, ENUM_CONTROLS, GET/SET_CONTROL, STREAM_OPEN/CLOSE) - video-node: scans /dev/media* and /dev/video*, serves V4L2 device topology and controls over TCP; uses UDP discovery for peer announce - query_cli: auto-discovers a node, queries devices and controls - protocol_cli: low-level protocol frame decoder for debugging - dev/web: Express 5 ESM web inspector — live SSE discovery picker, REST bridge to video-node, controls UI with sliders/selects/checkboxes - Makefile: sequential module builds before cli/node to fix make -j races - common.mk: add DEPFLAGS (-MMD -MP) for automatic header dependencies - All module Makefiles: split compile/link, generate .d dependency files - discovery: replace 100ms poll loop with pthread_cond_timedwait; respond to all announcements (not just new peers) for instant re-discovery - ENUM_DEVICES response: carry device_caps (V4L2_CAP_*) per video node so clients can distinguish capture nodes from metadata nodes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,9 @@ struct Discovery {
|
||||
pthread_t announce_thread;
|
||||
pthread_t receive_thread;
|
||||
atomic_int running;
|
||||
atomic_int early_announce; /* set when a new peer is seen */
|
||||
|
||||
pthread_mutex_t announce_mutex;
|
||||
pthread_cond_t announce_cond; /* signaled to wake announce thread early */
|
||||
|
||||
pthread_mutex_t peers_mutex;
|
||||
struct Peer_Entry peers[DISCOVERY_MAX_PEERS];
|
||||
@@ -131,23 +133,26 @@ static void *announce_thread_fn(void *arg) {
|
||||
|
||||
send_announcement(d);
|
||||
|
||||
pthread_mutex_lock(&d->announce_mutex);
|
||||
while (atomic_load(&d->running)) {
|
||||
/* sleep in 100 ms increments; breaks early if a new peer is detected
|
||||
* or if destroy is called */
|
||||
uint32_t elapsed = 0;
|
||||
while (atomic_load(&d->running) && elapsed < d->config.interval_ms) {
|
||||
if (atomic_load(&d->early_announce)) { break; }
|
||||
struct timespec ts = { .tv_sec = 0, .tv_nsec = 100 * 1000000L };
|
||||
nanosleep(&ts, NULL);
|
||||
elapsed += 100;
|
||||
struct timespec abs;
|
||||
clock_gettime(CLOCK_REALTIME, &abs);
|
||||
uint32_t ms = d->config.interval_ms;
|
||||
abs.tv_sec += ms / 1000u;
|
||||
abs.tv_nsec += (long)(ms % 1000u) * 1000000L;
|
||||
if (abs.tv_nsec >= 1000000000L) {
|
||||
abs.tv_sec++;
|
||||
abs.tv_nsec -= 1000000000L;
|
||||
}
|
||||
/* blocks until signaled (new peer / shutdown) or interval elapses */
|
||||
pthread_cond_timedwait(&d->announce_cond, &d->announce_mutex, &abs);
|
||||
|
||||
if (!atomic_load(&d->running)) { break; }
|
||||
|
||||
atomic_store(&d->early_announce, 0);
|
||||
send_announcement(d);
|
||||
check_timeouts(d);
|
||||
}
|
||||
pthread_mutex_unlock(&d->announce_mutex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -228,11 +233,14 @@ static void *receive_thread_fn(void *arg) {
|
||||
|
||||
pthread_mutex_unlock(&d->peers_mutex);
|
||||
|
||||
if (is_new) {
|
||||
atomic_store(&d->early_announce, 1);
|
||||
if (d->config.on_peer_found) {
|
||||
d->config.on_peer_found(&peer_copy, d->config.userdata);
|
||||
}
|
||||
/* respond to every announcement — the sender may be a fresh instance
|
||||
* that doesn't know about us yet even if we already have it in our table */
|
||||
pthread_mutex_lock(&d->announce_mutex);
|
||||
pthread_cond_signal(&d->announce_cond);
|
||||
pthread_mutex_unlock(&d->announce_mutex);
|
||||
|
||||
if (is_new && d->config.on_peer_found) {
|
||||
d->config.on_peer_found(&peer_copy, d->config.userdata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +270,8 @@ struct App_Error discovery_create(struct Discovery **out,
|
||||
inet_pton(AF_INET, DISCOVERY_MULTICAST_GROUP, &d->mcast_addr.sin_addr);
|
||||
|
||||
atomic_init(&d->running, 0);
|
||||
atomic_init(&d->early_announce, 0);
|
||||
pthread_mutex_init(&d->announce_mutex, NULL);
|
||||
pthread_cond_init(&d->announce_cond, NULL);
|
||||
pthread_mutex_init(&d->peers_mutex, NULL);
|
||||
|
||||
*out = d;
|
||||
@@ -326,9 +335,15 @@ struct App_Error discovery_start(struct Discovery *d) {
|
||||
|
||||
void discovery_destroy(struct Discovery *d) {
|
||||
atomic_store(&d->running, 0);
|
||||
/* wake announce thread so it exits without waiting for the full interval */
|
||||
pthread_mutex_lock(&d->announce_mutex);
|
||||
pthread_cond_signal(&d->announce_cond);
|
||||
pthread_mutex_unlock(&d->announce_mutex);
|
||||
close(d->sock);
|
||||
pthread_join(d->announce_thread, NULL);
|
||||
pthread_join(d->receive_thread, NULL);
|
||||
pthread_cond_destroy(&d->announce_cond);
|
||||
pthread_mutex_destroy(&d->announce_mutex);
|
||||
pthread_mutex_destroy(&d->peers_mutex);
|
||||
free(d);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user