Consuming a UDPStreamer feed so far meant either linking MARTe2 (UDPSClient) or writing Go (Common/Client/go/udpsprotocol). Common/Client/c fills the gap for plain C/C++ integrators: two files depending on nothing but libc and BSD sockets, covering the whole receive path — CONNECT, fragment reassembly, CONFIG/DATA decoding with dequantisation, keepalives and silence-triggered reconnect. No threads are spawned; udps_client_poll() does all the work and runs every callback, so it drops into an existing event loop unsynchronised. Verified against run_udp_producer.sh at 1 Msps: unicast (120 MiB, no loss) and multicast with 12-fragment cycles (116k datagrams, no loss). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
261 lines
8.4 KiB
C
261 lines
8.4 KiB
C
/**
|
|
* @file udps_dump.c
|
|
* @brief Example UDPS client: connects to a UDPStreamer and prints what arrives.
|
|
*
|
|
* Build with the Makefile in the parent directory, then for a unicast stream:
|
|
*
|
|
* ./udps_dump --host 127.0.0.1 --port 44500
|
|
*
|
|
* or, for a multicast one:
|
|
*
|
|
* ./udps_dump --host 127.0.0.1 --port 44500 \
|
|
* --multicast 239.0.0.1 --iface 127.0.0.1
|
|
*
|
|
* Ctrl-C prints a summary of what was received.
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "udps_client.h"
|
|
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
static volatile sig_atomic_t g_stop = 0;
|
|
|
|
static void on_sigint(int sig) {
|
|
(void)sig;
|
|
g_stop = 1;
|
|
}
|
|
|
|
typedef struct {
|
|
double print_interval; /**< Seconds between frame printouts. */
|
|
double last_print;
|
|
uint64_t frames;
|
|
uint64_t max_frames;
|
|
} dump_state_t;
|
|
|
|
static double now_wall(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
|
|
}
|
|
|
|
static const char *time_mode_name(uint8_t m) {
|
|
switch (m) {
|
|
case UDPS_TIME_PACKET: return "packet";
|
|
case UDPS_TIME_FULL_ARRAY: return "full-array";
|
|
case UDPS_TIME_FIRST_SAMPLE: return "first-sample";
|
|
case UDPS_TIME_LAST_SAMPLE: return "last-sample";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
static const char *publish_mode_name(uint8_t m) {
|
|
switch (m) {
|
|
case UDPS_PUBLISH_STRICT: return "strict";
|
|
case UDPS_PUBLISH_ACCUMULATE: return "accumulate";
|
|
case UDPS_PUBLISH_DECIMATE: return "decimate";
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
static void on_config(const udps_signal_t *sigs, uint32_t n, uint8_t mode,
|
|
void *user) {
|
|
uint32_t i;
|
|
(void)user;
|
|
printf("\nCONFIG: %u signal(s), publish mode %s\n", (unsigned)n,
|
|
publish_mode_name(mode));
|
|
printf(" %-3s %-24s %-8s %-10s %-8s %-10s %s\n", "#", "name", "type",
|
|
"shape", "unit", "rate[Hz]", "time-mode");
|
|
for (i = 0u; i < n; i++) {
|
|
char shape[32];
|
|
const udps_signal_t *s = &sigs[i];
|
|
if (s->num_cols > 1u) {
|
|
snprintf(shape, sizeof shape, "%ux%u", (unsigned)s->num_rows,
|
|
(unsigned)s->num_cols);
|
|
} else {
|
|
snprintf(shape, sizeof shape, "%u",
|
|
(unsigned)udps_signal_num_elements(s));
|
|
}
|
|
printf(" %-3u %-24s %-8s %-10s %-8s %-10.6g %s%s\n", (unsigned)i,
|
|
s->name, udps_type_name(s->type_code), shape,
|
|
(s->unit[0] != '\0') ? s->unit : "-", s->sampling_rate,
|
|
time_mode_name(s->time_mode),
|
|
(s->quant_type != UDPS_QUANT_NONE) ? " (quantised)" : "");
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void on_data(const udps_frame_t *f, void *user) {
|
|
dump_state_t *st = (dump_state_t *)user;
|
|
uint32_t i;
|
|
double now;
|
|
|
|
st->frames++;
|
|
now = now_wall();
|
|
if ((now - st->last_print) < st->print_interval) {
|
|
return; /* Streams run far faster than a terminal can be read. */
|
|
}
|
|
st->last_print = now;
|
|
|
|
printf("\nframe #%lu t=%.6f samples=%u (%lu frames so far)\n",
|
|
(unsigned long)f->counter, f->recv_time, (unsigned)f->num_samples,
|
|
(unsigned long)st->frames);
|
|
for (i = 0u; i < f->num_signals; i++) {
|
|
const double *v = f->values[i].values;
|
|
uint32_t cnt = f->values[i].count;
|
|
double lo, hi;
|
|
uint32_t k;
|
|
if (cnt == 0u) {
|
|
continue;
|
|
}
|
|
lo = hi = v[0];
|
|
for (k = 1u; k < cnt; k++) {
|
|
if (v[k] < lo) {
|
|
lo = v[k];
|
|
}
|
|
if (v[k] > hi) {
|
|
hi = v[k];
|
|
}
|
|
}
|
|
printf(" %-24s n=%-6u first=%-12.6g last=%-12.6g min=%-12.6g max=%-12.6g %s\n",
|
|
f->signals[i].name, (unsigned)cnt, v[0], v[cnt - 1u], lo, hi,
|
|
f->signals[i].unit);
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void on_event(udps_event_t ev, const char *detail, void *user) {
|
|
(void)user;
|
|
switch (ev) {
|
|
case UDPS_EVENT_CONNECTED:
|
|
printf("[connected to %s]\n", detail ? detail : "");
|
|
break;
|
|
case UDPS_EVENT_DISCONNECTED:
|
|
printf("[disconnected: %s]\n", detail ? detail : "");
|
|
break;
|
|
case UDPS_EVENT_ERROR:
|
|
fprintf(stderr, "[error] %s\n", detail ? detail : "");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void usage(const char *argv0) {
|
|
printf("Usage: %s --host ADDR --port N [options]\n"
|
|
"\n"
|
|
" --host ADDR server address (default 127.0.0.1)\n"
|
|
" --port N server UDP port, or TCP control port in multicast\n"
|
|
" mode (default 44500)\n"
|
|
" --multicast GROUP join GROUP for data instead of unicast\n"
|
|
" --iface ADDR local interface address for the multicast join\n"
|
|
" --data-port N multicast data port (default: --port + 1)\n"
|
|
" --silence SEC reconnect after SEC without data (default 1, 0 off)\n"
|
|
" --interval SEC seconds between printouts (default 1)\n"
|
|
" --frames N exit after N frames (default: run until Ctrl-C)\n"
|
|
" --help this text\n",
|
|
argv0);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
udps_client_config_t cfg;
|
|
udps_client_t *cli;
|
|
dump_state_t st;
|
|
udps_stats_t stats;
|
|
struct sigaction sa;
|
|
const char *host = "127.0.0.1";
|
|
int i;
|
|
|
|
udps_client_config_init(&cfg);
|
|
cfg.server_port = 44500u;
|
|
|
|
memset(&st, 0, sizeof st);
|
|
st.print_interval = 1.0;
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
const char *a = argv[i];
|
|
const char *next = (i + 1 < argc) ? argv[i + 1] : NULL;
|
|
if (strcmp(a, "--help") == 0) {
|
|
usage(argv[0]);
|
|
return 0;
|
|
}
|
|
if (next == NULL) {
|
|
fprintf(stderr, "missing value for %s\n", a);
|
|
return 2;
|
|
}
|
|
if (strcmp(a, "--host") == 0) {
|
|
host = next;
|
|
} else if (strcmp(a, "--port") == 0) {
|
|
cfg.server_port = (uint16_t)atoi(next);
|
|
} else if (strcmp(a, "--multicast") == 0) {
|
|
cfg.multicast_group = next;
|
|
} else if (strcmp(a, "--iface") == 0) {
|
|
cfg.interface_addr = next;
|
|
} else if (strcmp(a, "--data-port") == 0) {
|
|
cfg.data_port = (uint16_t)atoi(next);
|
|
} else if (strcmp(a, "--silence") == 0) {
|
|
cfg.silence_timeout_s = atof(next);
|
|
} else if (strcmp(a, "--interval") == 0) {
|
|
st.print_interval = atof(next);
|
|
} else if (strcmp(a, "--frames") == 0) {
|
|
st.max_frames = (uint64_t)strtoull(next, NULL, 10);
|
|
} else {
|
|
fprintf(stderr, "unknown option %s\n", a);
|
|
usage(argv[0]);
|
|
return 2;
|
|
}
|
|
i++;
|
|
}
|
|
cfg.server_addr = host;
|
|
|
|
cli = udps_client_create(&cfg);
|
|
if (cli == NULL) {
|
|
fprintf(stderr, "could not create client for %s:%u\n", host,
|
|
(unsigned)cfg.server_port);
|
|
return 1;
|
|
}
|
|
udps_client_set_callbacks(cli, on_config, on_data, on_event, &st);
|
|
|
|
memset(&sa, 0, sizeof sa);
|
|
sa.sa_handler = on_sigint;
|
|
(void)sigaction(SIGINT, &sa, NULL);
|
|
(void)sigaction(SIGTERM, &sa, NULL);
|
|
|
|
printf("listening to %s:%u%s%s ... (Ctrl-C to stop)\n", host,
|
|
(unsigned)cfg.server_port,
|
|
cfg.multicast_group ? " via multicast " : "",
|
|
cfg.multicast_group ? cfg.multicast_group : "");
|
|
|
|
while (!g_stop && (st.max_frames == 0u || st.frames < st.max_frames)) {
|
|
/* All the work — connecting, receiving, decoding, reconnecting — and
|
|
* every callback happens inside this call. */
|
|
(void)udps_client_poll(cli, 200);
|
|
}
|
|
|
|
udps_client_stats(cli, &stats);
|
|
printf("\n--- summary ---\n"
|
|
"packets %lu\n"
|
|
"bytes %.1f MiB\n"
|
|
"frames %lu\n"
|
|
"configs %lu\n"
|
|
"gaps %lu (datagrams lost)\n"
|
|
"dropped %lu (fragments)\n"
|
|
"reconnects %lu\n",
|
|
(unsigned long)stats.packets_received,
|
|
(double)stats.bytes_received / (1024.0 * 1024.0),
|
|
(unsigned long)stats.frames_delivered,
|
|
(unsigned long)stats.config_updates,
|
|
(unsigned long)stats.counter_gaps,
|
|
(unsigned long)stats.fragments_dropped,
|
|
(unsigned long)stats.reconnects);
|
|
|
|
udps_client_destroy(cli);
|
|
return 0;
|
|
}
|