From 39dfd02d5daa8ce7cf749f6235cf6450b2171214 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 7 Aug 2020 22:34:31 +0200 Subject: Implement detect mode for artnet (#70) --- backends/artnet.c | 128 ++++++++++++++++++++++++++++++++--------------------- backends/artnet.h | 1 + backends/artnet.md | 3 +- backends/sacn.c | 1 - backends/sacn.h | 2 +- backends/sacn.md | 2 +- 6 files changed, 82 insertions(+), 55 deletions(-) diff --git a/backends/artnet.c b/backends/artnet.c index e07ea52..4a7907d 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -9,14 +9,19 @@ #define MAX_FDS 255 -static uint32_t next_frame = 0; -static uint8_t default_net = 0; -static size_t artnet_fds = 0; -static artnet_descriptor* artnet_fd = NULL; +static struct { + uint32_t next_frame; + uint8_t default_net; + size_t fds; + artnet_descriptor* fd; + uint8_t detect; +} global_cfg = { + 0 +}; static int artnet_listener(char* host, char* port){ int fd; - if(artnet_fds >= MAX_FDS){ + if(global_cfg.fds >= MAX_FDS){ LOG("Backend descriptor limit reached"); return -1; } @@ -27,18 +32,19 @@ static int artnet_listener(char* host, char* port){ } //store fd - artnet_fd = realloc(artnet_fd, (artnet_fds + 1) * sizeof(artnet_descriptor)); - if(!artnet_fd){ + global_cfg.fd = realloc(global_cfg.fd, (global_cfg.fds + 1) * sizeof(artnet_descriptor)); + if(!global_cfg.fd){ close(fd); + global_cfg.fds = 0; LOG("Failed to allocate memory"); return -1; } - LOGPF("Interface %" PRIsize_t " bound to %s port %s", artnet_fds, host, port); - artnet_fd[artnet_fds].fd = fd; - artnet_fd[artnet_fds].output_instances = 0; - artnet_fd[artnet_fds].output_instance = NULL; - artnet_fds++; + LOGPF("Interface %" PRIsize_t " bound to %s port %s", global_cfg.fds, host, port); + global_cfg.fd[global_cfg.fds].fd = fd; + global_cfg.fd[global_cfg.fds].output_instances = 0; + global_cfg.fd[global_cfg.fds].output_instance = NULL; + global_cfg.fds++; return 0; } @@ -70,8 +76,8 @@ MM_PLUGIN_API int init(){ } static uint32_t artnet_interval(){ - if(next_frame){ - return next_frame; + if(global_cfg.next_frame){ + return global_cfg.next_frame; } return ARTNET_KEEPALIVE_INTERVAL; } @@ -80,7 +86,7 @@ static int artnet_configure(char* option, char* value){ char* host = NULL, *port = NULL, *fd_opts = NULL; if(!strcmp(option, "net")){ //configure default net - default_net = strtoul(value, NULL, 0); + global_cfg.default_net = strtoul(value, NULL, 0); return 0; } else if(!strcmp(option, "bind")){ @@ -97,6 +103,16 @@ static int artnet_configure(char* option, char* value){ } return 0; } + else if(!strcmp(option, "detect")){ + global_cfg.detect = 0; + if(!strcmp(value, "on")){ + global_cfg.detect = 1; + } + else if(!strcmp(value, "verbose")){ + global_cfg.detect = 2; + } + return 0; + } LOGPF("Unknown backend option %s", option); return 1; @@ -111,7 +127,7 @@ static int artnet_instance(instance* inst){ return 1; } - data->net = default_net; + data->net = global_cfg.default_net; for(u = 0; u < sizeof(data->data.channel) / sizeof(channel); u++){ data->data.channel[u].ident = u; data->data.channel[u].instance = inst; @@ -136,7 +152,7 @@ static int artnet_configure_instance(instance* inst, char* option, char* value){ else if(!strcmp(option, "iface") || !strcmp(option, "interface")){ data->fd_index = strtoul(value, NULL, 0); - if(data->fd_index >= artnet_fds){ + if(data->fd_index >= global_cfg.fds){ LOGPF("Invalid interface configured for instance %s", inst->name); return 1; } @@ -223,7 +239,7 @@ static int artnet_transmit(instance* inst, artnet_output_universe* output){ }; memcpy(frame.data, data->data.out, 512); - if(sendto(artnet_fd[data->fd_index].fd, (uint8_t*) &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ + if(sendto(global_cfg.fd[data->fd_index].fd, (uint8_t*) &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ #ifdef _WIN32 if(WSAGetLastError() != WSAEWOULDBLOCK){ #else @@ -234,8 +250,8 @@ static int artnet_transmit(instance* inst, artnet_output_universe* output){ } //reschedule frame output output->mark = 1; - if(!next_frame || next_frame > ARTNET_SYNTHESIZE_MARGIN){ - next_frame = ARTNET_SYNTHESIZE_MARGIN; + if(!global_cfg.next_frame || global_cfg.next_frame > ARTNET_SYNTHESIZE_MARGIN){ + global_cfg.next_frame = ARTNET_SYNTHESIZE_MARGIN; } return 0; } @@ -279,22 +295,22 @@ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v) if(mark){ //find last frame time - for(u = 0; u < artnet_fd[data->fd_index].output_instances; u++){ - if(artnet_fd[data->fd_index].output_instance[u].label == inst->ident){ + for(u = 0; u < global_cfg.fd[data->fd_index].output_instances; u++){ + if(global_cfg.fd[data->fd_index].output_instance[u].label == inst->ident){ break; } } - frame_delta = mm_timestamp() - artnet_fd[data->fd_index].output_instance[u].last_frame; + frame_delta = mm_timestamp() - global_cfg.fd[data->fd_index].output_instance[u].last_frame; //check output rate limit, request next frame if(frame_delta < ARTNET_FRAME_TIMEOUT){ - artnet_fd[data->fd_index].output_instance[u].mark = 1; - if(!next_frame || next_frame > (ARTNET_FRAME_TIMEOUT - frame_delta)){ - next_frame = (ARTNET_FRAME_TIMEOUT - frame_delta); + global_cfg.fd[data->fd_index].output_instance[u].mark = 1; + if(!global_cfg.next_frame || global_cfg.next_frame > (ARTNET_FRAME_TIMEOUT - frame_delta)){ + global_cfg.next_frame = (ARTNET_FRAME_TIMEOUT - frame_delta); } return 0; } - return artnet_transmit(inst, artnet_fd[data->fd_index].output_instance + u); + return artnet_transmit(inst, global_cfg.fd[data->fd_index].output_instance + u); } return 0; @@ -307,6 +323,11 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ channel_value val; artnet_instance_data* data = (artnet_instance_data*) inst->impl; + if(!data->last_input && global_cfg.detect){ + LOGPF("Valid data on instance %s (Net %d Universe %d): %d channels", inst->name, data->net, data->uni, be16toh(frame->length)); + } + data->last_input = mm_timestamp(); + if(be16toh(frame->length) > 512){ LOGPF("Invalid frame channel count: %d", be16toh(frame->length)); return 1; @@ -366,23 +387,23 @@ static int artnet_handle(size_t num, managed_fd* fds){ artnet_pkt* frame = (artnet_pkt*) recv_buf; //transmit keepalive & synthesized frames - next_frame = 0; - for(u = 0; u < artnet_fds; u++){ - for(c = 0; c < artnet_fd[u].output_instances; c++){ - synthesize_delta = timestamp - artnet_fd[u].output_instance[c].last_frame; - if((artnet_fd[u].output_instance[c].mark + global_cfg.next_frame = 0; + for(u = 0; u < global_cfg.fds; u++){ + for(c = 0; c < global_cfg.fd[u].output_instances; c++){ + synthesize_delta = timestamp - global_cfg.fd[u].output_instance[c].last_frame; + if((global_cfg.fd[u].output_instance[c].mark && synthesize_delta >= ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN) //synthesize next frame || synthesize_delta >= ARTNET_KEEPALIVE_INTERVAL){ //keepalive timeout - inst = mm_instance_find(BACKEND_NAME, artnet_fd[u].output_instance[c].label); + inst = mm_instance_find(BACKEND_NAME, global_cfg.fd[u].output_instance[c].label); if(inst){ - artnet_transmit(inst, artnet_fd[u].output_instance + c); + artnet_transmit(inst, global_cfg.fd[u].output_instance + c); } } //update next_frame - if(artnet_fd[u].output_instance[c].mark - && (!next_frame || next_frame > ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN - synthesize_delta)){ - next_frame = ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN - synthesize_delta; + if(global_cfg.fd[u].output_instance[c].mark + && (!global_cfg.next_frame || global_cfg.next_frame > ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN - synthesize_delta)){ + global_cfg.next_frame = ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN - synthesize_delta; } } } @@ -400,6 +421,9 @@ static int artnet_handle(size_t num, managed_fd* fds){ if(inst && artnet_process_frame(inst, frame)){ LOG("Failed to process frame"); } + else if(!inst && global_cfg.detect > 1){ + LOGPF("Received data for unconfigured universe %d (net %d) on descriptor %" PRIsize_t, frame->universe, frame->net, (((uint64_t) fds[u].impl) & 0xFF)); + } } } } while(bytes_read > 0); @@ -429,7 +453,7 @@ static int artnet_start(size_t n, instance** inst){ .label = 0 }; - if(!artnet_fds){ + if(!global_cfg.fds){ LOG("Failed to start backend: no descriptors bound"); return 1; } @@ -452,23 +476,23 @@ static int artnet_start(size_t n, instance** inst){ //if enabled for output, add to keepalive tracking if(data->dest_len){ - artnet_fd[data->fd_index].output_instance = realloc(artnet_fd[data->fd_index].output_instance, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(artnet_output_universe)); + global_cfg.fd[data->fd_index].output_instance = realloc(global_cfg.fd[data->fd_index].output_instance, (global_cfg.fd[data->fd_index].output_instances + 1) * sizeof(artnet_output_universe)); - if(!artnet_fd[data->fd_index].output_instance){ + if(!global_cfg.fd[data->fd_index].output_instance){ LOG("Failed to allocate memory"); goto bail; } - artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances].label = id.label; - artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances].last_frame = 0; - artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances].mark = 0; + global_cfg.fd[data->fd_index].output_instance[global_cfg.fd[data->fd_index].output_instances].label = id.label; + global_cfg.fd[data->fd_index].output_instance[global_cfg.fd[data->fd_index].output_instances].last_frame = 0; + global_cfg.fd[data->fd_index].output_instance[global_cfg.fd[data->fd_index].output_instances].mark = 0; - artnet_fd[data->fd_index].output_instances++; + global_cfg.fd[data->fd_index].output_instances++; } } - LOGPF("Registering %" PRIsize_t " descriptors to core", artnet_fds); - for(u = 0; u < artnet_fds; u++){ - if(mm_manage_fd(artnet_fd[u].fd, BACKEND_NAME, 1, (void*) u)){ + LOGPF("Registering %" PRIsize_t " descriptors to core", global_cfg.fds); + for(u = 0; u < global_cfg.fds; u++){ + if(mm_manage_fd(global_cfg.fd[u].fd, BACKEND_NAME, 1, (void*) u)){ goto bail; } } @@ -485,11 +509,13 @@ static int artnet_shutdown(size_t n, instance** inst){ free(inst[p]->impl); } - for(p = 0; p < artnet_fds; p++){ - close(artnet_fd[p].fd); - free(artnet_fd[p].output_instance); + for(p = 0; p < global_cfg.fds; p++){ + close(global_cfg.fd[p].fd); + free(global_cfg.fd[p].output_instance); } - free(artnet_fd); + free(global_cfg.fd); + global_cfg.fd = NULL; + global_cfg.fds = 0; LOG("Backend shut down"); return 0; diff --git a/backends/artnet.h b/backends/artnet.h index a517aa0..ecd775e 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -47,6 +47,7 @@ typedef struct /*_artnet_instance_model*/ { socklen_t dest_len; artnet_universe data; size_t fd_index; + uint64_t last_input; } artnet_instance_data; typedef union /*_artnet_instance_id*/ { diff --git a/backends/artnet.md b/backends/artnet.md index 383203d..73f598a 100644 --- a/backends/artnet.md +++ b/backends/artnet.md @@ -9,8 +9,9 @@ Art-Netâ„¢ Designed by and Copyright Artistic Licence Holdings Ltd. | Option | Example value | Default value | Description | |---------------|-----------------------|-----------------------|-----------------------| -| `bind` | `127.0.0.1 6454` | none | Binds a network address to listen for data. This option may be set multiple times, with each interface being assigned an index starting from 0 to be used with the `interface` instance configuration option. At least one interface is required for transmission. | +| `bind` | `127.0.0.1 6454` | none | Binds a network address to listen for data. This option may be set multiple times, with each interface being assigned an index starting from 0 to be used with the `interface` instance configuration option. At least one interface is required for transmission. | | `net` | `0` | `0` | The default net to use | +| `detect` | `on`, `verbose` | `off` | Output additional information on received data packets to help with configuring complex scenarios | #### Instance configuration diff --git a/backends/sacn.c b/backends/sacn.c index 247dfc7..0ea7b58 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -1,5 +1,4 @@ #define BACKEND_NAME "sacn" -#define DEBUG #include #include diff --git a/backends/sacn.h b/backends/sacn.h index 0c44ebc..0f24538 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -40,7 +40,7 @@ typedef struct /*_sacn_universe_model*/ { } sacn_universe; typedef struct /*_sacn_instance_model*/ { - uint32_t last_input; + uint64_t last_input; uint16_t uni; uint8_t realtime; uint8_t xmit_prio; diff --git a/backends/sacn.md b/backends/sacn.md index 3bc5b72..b7686e0 100644 --- a/backends/sacn.md +++ b/backends/sacn.md @@ -11,7 +11,7 @@ containing all write-enabled universes. | `name` | `sACN source` | `MIDIMonster` | sACN source name | | `cid` | `0xAA 0xBB 0xCC` ... | `MIDIMonster` | Source CID (16 bytes) | | `bind` | `0.0.0.0 5568` | none | Binds a network address to listen for data. This option may be set multiple times, with each descriptor being assigned an index starting from 0 to be used with the `interface` instance configuration option. At least one descriptor is required for operation. | -| `detect` | `on`, `verbose` | `off` | Output additional information on incoming and outgoing data packets to help with configuring complex scenarios. | +| `detect` | `on`, `verbose` | `off` | Output additional information on received data packets to help with configuring complex scenarios. | The `bind` configuration value can be extended by the keyword `local` to allow software on the local host to process the sACN output frames from the MIDIMonster (e.g. `bind = 0.0.0.0 5568 local`). -- cgit v1.2.3