From 6291031a98539bdf51262329b0dc20604c2bad70 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 26 Jul 2020 23:01:35 +0200 Subject: Implement rudimentary detect mode for sACN (#70) --- backends/sacn.md | 1 + 1 file changed, 1 insertion(+) (limited to 'backends/sacn.md') diff --git a/backends/sacn.md b/backends/sacn.md index 598f430..3bc5b72 100644 --- a/backends/sacn.md +++ b/backends/sacn.md @@ -11,6 +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. | 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 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(-) (limited to 'backends/sacn.md') 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 From fec26ba3f193f3e31e08b55dfb3c60aa30eed63f Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 6 Sep 2020 22:45:20 +0200 Subject: Add rate limiting notice to sACN and ArtNet --- TODO | 1 + backend.c | 6 ++++-- backends/artnet.md | 3 +++ backends/sacn.md | 6 +++++- 4 files changed, 13 insertions(+), 3 deletions(-) (limited to 'backends/sacn.md') diff --git a/TODO b/TODO index befa5e6..44336d4 100644 --- a/TODO +++ b/TODO @@ -4,3 +4,4 @@ udp backends may ignore MTU make event collectors threadsafe to stop marshalling data... collect & check backend API version move all connection establishment to _start to be able to hot-stop/start all backends +event deduplication in core? diff --git a/backend.c b/backend.c index 0fb6679..16e095c 100644 --- a/backend.c +++ b/backend.c @@ -94,16 +94,18 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ size_t u, bucket = channelstore_hash(inst, ident); + DBGPF("\tSearching for inst %" PRIu64 " ident %" PRIu64, inst, ident); for(u = 0; u < channels.n[bucket]; u++){ + DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, channels.entry[bucket][u]->instance, channels.entry[bucket][u]->ident); if(channels.entry[bucket][u]->instance == inst && channels.entry[bucket][u]->ident == ident){ - DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (%" PRIsize_t " search steps)\n", ident, inst->name, u); + DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)\n", ident, inst->name, bucket, u); return channels.entry[bucket][u]; } } if(!create){ - DBGPF("Requested unknown channel %" PRIu64 " on instance %s\n", ident, inst->name); + DBGPF("Requested unknown channel %" PRIu64 " (bucket %" PRIsize_t ") on instance %s\n", ident, bucket, inst->name); return NULL; } diff --git a/backends/artnet.md b/backends/artnet.md index 73f598a..bd26014 100644 --- a/backends/artnet.md +++ b/backends/artnet.md @@ -39,3 +39,6 @@ net1.1+2 > net2.5+123 A normal channel that is part of a wide channel can not be mapped individually. #### Known bugs / problems + +When using this backend for output with a fast event source, some events may appear to be lost due to the packet output rate limiting +mandated by the [ArtNet specification](https://artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf) (Section `Refresh rate`). diff --git a/backends/sacn.md b/backends/sacn.md index b7686e0..244b4c4 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 received 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`). @@ -59,3 +59,7 @@ To use multicast input, all networking hardware in the path must support the IGM The Linux kernel limits the number of multicast groups an interface may join to 20. An instance configured for input automatically joins the multicast group for its universe, unless configured in `unicast` mode. This limit can be raised by changing the kernel option in `/proc/sys/net/ipv4/igmp_max_memberships`. + +When using this backend for output with a fast event source, some events may appear to be lost due to the packet output rate limiting +mandated by the E1.31 specification (Section `6.6.1 Transmission rate`). +The rate limiter can be disabled on a per-instance basis using the `realtime` option. -- cgit v1.2.3