Add discovery module: UDP multicast announcements and peer tracking
Sends 6-byte framed announcements to 224.0.0.251:5353 on startup and every interval_ms (default 5s). Receive thread maintains a peer table (max 64 entries); fires on_peer_found for new peers, on_peer_lost when a peer misses timeout_intervals (default 3) consecutive intervals. Own announcements are filtered by name+site_id. SO_REUSEADDR+REUSEPORT allows multiple processes on the same host for testing. discovery_cli: announce <name> <tcp_port> [flags] — prints found/lost events. Also notes future config module in planning.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
ROOT := $(abspath ../..)
|
||||
include $(ROOT)/common.mk
|
||||
|
||||
CLI_BUILD = $(BUILD)/cli
|
||||
COMMON_OBJ = $(BUILD)/common/error.o
|
||||
MEDIA_CTRL_OBJ = $(BUILD)/media_ctrl/media_ctrl.o
|
||||
V4L2_CTRL_OBJ = $(BUILD)/v4l2_ctrl/v4l2_ctrl.o
|
||||
SERIAL_OBJ = $(BUILD)/serial/serial.o
|
||||
TRANSPORT_OBJ = $(BUILD)/transport/transport.o
|
||||
CLI_BUILD = $(BUILD)/cli
|
||||
COMMON_OBJ = $(BUILD)/common/error.o
|
||||
MEDIA_CTRL_OBJ = $(BUILD)/media_ctrl/media_ctrl.o
|
||||
V4L2_CTRL_OBJ = $(BUILD)/v4l2_ctrl/v4l2_ctrl.o
|
||||
SERIAL_OBJ = $(BUILD)/serial/serial.o
|
||||
TRANSPORT_OBJ = $(BUILD)/transport/transport.o
|
||||
DISCOVERY_OBJ = $(BUILD)/discovery/discovery.o
|
||||
|
||||
.PHONY: all clean modules
|
||||
|
||||
all: modules \
|
||||
$(CLI_BUILD)/media_ctrl_cli \
|
||||
$(CLI_BUILD)/v4l2_ctrl_cli \
|
||||
$(CLI_BUILD)/transport_cli
|
||||
$(CLI_BUILD)/transport_cli \
|
||||
$(CLI_BUILD)/discovery_cli
|
||||
|
||||
modules:
|
||||
$(MAKE) -C $(ROOT)/src/modules/common
|
||||
@@ -21,6 +23,7 @@ modules:
|
||||
$(MAKE) -C $(ROOT)/src/modules/v4l2_ctrl
|
||||
$(MAKE) -C $(ROOT)/src/modules/serial
|
||||
$(MAKE) -C $(ROOT)/src/modules/transport
|
||||
$(MAKE) -C $(ROOT)/src/modules/discovery
|
||||
|
||||
$(CLI_BUILD)/media_ctrl_cli: media_ctrl_cli.c $(COMMON_OBJ) $(MEDIA_CTRL_OBJ) | $(CLI_BUILD)
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
@@ -31,6 +34,9 @@ $(CLI_BUILD)/v4l2_ctrl_cli: v4l2_ctrl_cli.c $(COMMON_OBJ) $(V4L2_CTRL_OBJ) | $(C
|
||||
$(CLI_BUILD)/transport_cli: transport_cli.c $(COMMON_OBJ) $(SERIAL_OBJ) $(TRANSPORT_OBJ) | $(CLI_BUILD)
|
||||
$(CC) $(CFLAGS) -o $@ $^ -lpthread
|
||||
|
||||
$(CLI_BUILD)/discovery_cli: discovery_cli.c $(COMMON_OBJ) $(SERIAL_OBJ) $(DISCOVERY_OBJ) | $(CLI_BUILD)
|
||||
$(CC) $(CFLAGS) -o $@ $^ -lpthread
|
||||
|
||||
$(CLI_BUILD):
|
||||
mkdir -p $@
|
||||
|
||||
@@ -38,4 +44,5 @@ clean:
|
||||
rm -f \
|
||||
$(CLI_BUILD)/media_ctrl_cli \
|
||||
$(CLI_BUILD)/v4l2_ctrl_cli \
|
||||
$(CLI_BUILD)/transport_cli
|
||||
$(CLI_BUILD)/transport_cli \
|
||||
$(CLI_BUILD)/discovery_cli
|
||||
|
||||
108
dev/cli/discovery_cli.c
Normal file
108
dev/cli/discovery_cli.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "discovery.h"
|
||||
#include "error.h"
|
||||
|
||||
static void flags_str(uint16_t flags, char *buf, size_t len) {
|
||||
buf[0] = '\0';
|
||||
if (flags & DISCOVERY_FLAG_SOURCE) { strncat(buf, "source ", len - strlen(buf) - 1); }
|
||||
if (flags & DISCOVERY_FLAG_RELAY) { strncat(buf, "relay ", len - strlen(buf) - 1); }
|
||||
if (flags & DISCOVERY_FLAG_SINK) { strncat(buf, "sink ", len - strlen(buf) - 1); }
|
||||
if (flags & DISCOVERY_FLAG_CONTROLLER) { strncat(buf, "controller ", len - strlen(buf) - 1); }
|
||||
/* trim trailing space */
|
||||
size_t l = strlen(buf);
|
||||
if (l > 0 && buf[l - 1] == ' ') { buf[l - 1] = '\0'; }
|
||||
}
|
||||
|
||||
static void on_peer_found(const struct Discovery_Peer *peer, void *userdata) {
|
||||
(void)userdata;
|
||||
char addr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &peer->addr, addr, sizeof(addr));
|
||||
char flags[64];
|
||||
flags_str(peer->function_flags, flags, sizeof(flags));
|
||||
printf("found %-30s %s:%u site=%u [%s]\n",
|
||||
peer->name, addr, peer->tcp_port, peer->site_id, flags);
|
||||
}
|
||||
|
||||
static void on_peer_lost(const struct Discovery_Peer *peer, void *userdata) {
|
||||
(void)userdata;
|
||||
char addr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &peer->addr, addr, sizeof(addr));
|
||||
printf("lost %-30s %s:%u\n", peer->name, addr, peer->tcp_port);
|
||||
}
|
||||
|
||||
static void usage(void) {
|
||||
fprintf(stderr,
|
||||
"usage: discovery_cli <name> <tcp_port> [flags]\n"
|
||||
"\n"
|
||||
" name node name in namespace:instance form, e.g. v4l2:microscope\n"
|
||||
" tcp_port port this node listens on for transport connections\n"
|
||||
" flags comma-separated roles: source,relay,sink,controller\n"
|
||||
" default: source\n"
|
||||
"\n"
|
||||
"Announces this node on the multicast group and prints peers as they\n"
|
||||
"appear and disappear. Press Ctrl-C to exit.\n");
|
||||
}
|
||||
|
||||
static uint16_t parse_flags(const char *s) {
|
||||
uint16_t f = 0;
|
||||
if (strstr(s, "source")) { f |= DISCOVERY_FLAG_SOURCE; }
|
||||
if (strstr(s, "relay")) { f |= DISCOVERY_FLAG_RELAY; }
|
||||
if (strstr(s, "sink")) { f |= DISCOVERY_FLAG_SINK; }
|
||||
if (strstr(s, "controller")) { f |= DISCOVERY_FLAG_CONTROLLER; }
|
||||
return f;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *name = argv[1];
|
||||
uint16_t tcp_port = (uint16_t)atoi(argv[2]);
|
||||
uint16_t flags = argc >= 4 ? parse_flags(argv[3]) : DISCOVERY_FLAG_SOURCE;
|
||||
|
||||
if (strlen(name) == 0 || strlen(name) > DISCOVERY_MAX_NAME_LEN) {
|
||||
fprintf(stderr, "name must be 1–%d characters\n", DISCOVERY_MAX_NAME_LEN);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct Discovery_Config config = {
|
||||
.site_id = 0,
|
||||
.tcp_port = tcp_port,
|
||||
.function_flags = flags,
|
||||
.name = name,
|
||||
.interval_ms = 0, /* use default */
|
||||
.timeout_intervals = 0, /* use default */
|
||||
.on_peer_found = on_peer_found,
|
||||
.on_peer_lost = on_peer_lost,
|
||||
.userdata = NULL,
|
||||
};
|
||||
|
||||
struct Discovery *d;
|
||||
struct App_Error err = discovery_create(&d, &config);
|
||||
if (!APP_IS_OK(err)) {
|
||||
fprintf(stderr, "discovery_create: errno %d\n", err.detail.syscall.err_no);
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = discovery_start(d);
|
||||
if (!APP_IS_OK(err)) {
|
||||
fprintf(stderr, "discovery_start: errno %d\n", err.detail.syscall.err_no);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char flags_buf[64];
|
||||
flags_str(flags, flags_buf, sizeof(flags_buf));
|
||||
printf("announcing %-30s port=%u [%s]\n", name, tcp_port, flags_buf);
|
||||
printf("listening on %s:%d\n\n", DISCOVERY_MULTICAST_GROUP, DISCOVERY_PORT);
|
||||
|
||||
pause();
|
||||
|
||||
discovery_destroy(d);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user