diff options
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | artnet.c | 94 | ||||
-rw-r--r-- | artnet.h | 9 | ||||
-rw-r--r-- | monster.cfg | 9 | ||||
-rw-r--r-- | sacn.c | 113 | ||||
-rw-r--r-- | sacn.h | 2 |
6 files changed, 169 insertions, 66 deletions
@@ -138,7 +138,8 @@ A normal channel that is part of a wide channel can not be mapped individually. #### Known bugs / problems -Currently, no keep-alive frames are sent and the minimum inter-frame-time is disregarded. +The minimum inter-frame-time is disregarded, as the packet rate is determined by the rate of incoming +channel events. ### The `sacn` backend @@ -186,11 +187,10 @@ A normal channel that is part of a wide channel can not be mapped individually. #### Known bugs / problems -No keepalive frames are sent at this time. This will be implemented in the near future. - The DMX start code of transmitted and received universes is fixed as `0`. -The limit on packet transmission rate mandated by section 6.6.1 of the sACN specification is disregarded. +The (upper) limit on packet transmission rate mandated by section 6.6.1 of the sACN specification is disregarded. +The rate of packet transmission is influenced by the rate of incoming mapped events on the instance. Universe synchronization is currently not supported, though this feature may be implemented in the future. @@ -13,7 +13,7 @@ static uint8_t default_net = 0; static size_t artnet_fds = 0; -static int* artnet_fd = NULL; +static artnet_descriptor* artnet_fd = NULL; static int artnet_listener(char* host, char* port){ int fd = -1, status, yes = 1, flags; @@ -81,14 +81,17 @@ static int artnet_listener(char* host, char* port){ } //store fd - artnet_fd = realloc(artnet_fd, (artnet_fds + 1) * sizeof(int)); + artnet_fd = realloc(artnet_fd, (artnet_fds + 1) * sizeof(artnet_descriptor)); if(!artnet_fd){ fprintf(stderr, "Failed to allocate memory\n"); return -1; } fprintf(stderr, "ArtNet backend interface %zu bound to %s port %s\n", artnet_fds, host, port); - artnet_fd[artnet_fds] = fd; + artnet_fd[artnet_fds].fd = fd; + artnet_fd[artnet_fds].output_instances = 0; + artnet_fd[artnet_fds].output_instance = NULL; + artnet_fd[artnet_fds].last_frame = NULL; artnet_fds++; return 0; } @@ -279,6 +282,36 @@ static channel* artnet_channel(instance* inst, char* spec){ return mm_channel(inst, chan_a, 1); } +static int artnet_transmit(instance* inst){ + size_t u; + artnet_instance_data* data = (artnet_instance_data*) inst->impl; + //output frame + artnet_pkt frame = { + .magic = {'A', 'r', 't', '-', 'N', 'e', 't', 0x00}, + .opcode = htobe16(OpDmx), + .version = htobe16(ARTNET_VERSION), + .sequence = data->data.seq++, + .port = 0, + .universe = data->uni, + .net = data->net, + .length = htobe16(512), + .data = {0} + }; + memcpy(frame.data, data->data.out, 512); + + if(sendto(artnet_fd[data->fd_index].fd, &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ + fprintf(stderr, "Failed to output ArtNet frame for instance %s: %s\n", inst->name, strerror(errno)); + } + + //update last frame timestamp + for(u = 0; u < artnet_fd[data->fd_index].output_instances; u++){ + if(artnet_fd[data->fd_index].output_instance[u].label == inst->ident){ + artnet_fd[data->fd_index].last_frame[u] = mm_timestamp(); + } + } + return 0; +} + static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t u, mark = 0; artnet_instance_data* data = (artnet_instance_data*) inst->impl; @@ -310,23 +343,7 @@ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v) } if(mark){ - //output frame - artnet_pkt frame = { - .magic = {'A', 'r', 't', '-', 'N', 'e', 't', 0x00}, - .opcode = htobe16(OpDmx), - .version = htobe16(ARTNET_VERSION), - .sequence = data->data.seq++, - .port = 0, - .universe = data->uni, - .net = data->net, - .length = htobe16(512), - .data = {0} - }; - memcpy(frame.data, data->data.out, 512); - - if(sendto(artnet_fd[data->fd_index], &frame, sizeof(frame), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ - fprintf(stderr, "Failed to output ArtNet frame for instance %s: %s\n", inst->name, strerror(errno)); - } + return artnet_transmit(inst); } return 0; @@ -393,7 +410,8 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ } static int artnet_handle(size_t num, managed_fd* fds){ - size_t u; + size_t u, c; + uint64_t timestamp = mm_timestamp(); ssize_t bytes_read; char recv_buf[ARTNET_RECV_BUF]; artnet_instance_id inst_id = { @@ -401,6 +419,19 @@ static int artnet_handle(size_t num, managed_fd* fds){ }; instance* inst = NULL; artnet_pkt* frame = (artnet_pkt*) recv_buf; + + //transmit keepalive frames + for(u = 0; u < artnet_fds; u++){ + for(c = 0; c < artnet_fd[u].output_instances; c++){ + if(timestamp - artnet_fd[u].last_frame[c] >= ARTNET_KEEPALIVE_INTERVAL){ + inst = mm_instance_find(BACKEND_NAME, artnet_fd[u].output_instance[c].label); + if(inst){ + artnet_transmit(inst); + } + } + } + } + if(!num){ //early exit return 0; @@ -476,11 +507,26 @@ static int artnet_start(){ goto bail; } } + + //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_instance_id)); + artnet_fd[data->fd_index].last_frame = realloc(artnet_fd[data->fd_index].last_frame, (artnet_fd[data->fd_index].output_instances + 1) * sizeof(uint64_t)); + + if(!artnet_fd[data->fd_index].output_instance || !artnet_fd[data->fd_index].last_frame){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } + artnet_fd[data->fd_index].output_instance[artnet_fd[data->fd_index].output_instances] = id; + artnet_fd[data->fd_index].last_frame[artnet_fd[data->fd_index].output_instances] = 0; + + artnet_fd[data->fd_index].output_instances++; + } } fprintf(stderr, "ArtNet backend registering %zu descriptors to core\n", artnet_fds); for(u = 0; u < artnet_fds; u++){ - if(mm_manage_fd(artnet_fd[u], BACKEND_NAME, 1, (void*) u)){ + if(mm_manage_fd(artnet_fd[u].fd, BACKEND_NAME, 1, (void*) u)){ goto bail; } } @@ -505,7 +551,9 @@ static int artnet_shutdown(){ free(inst); for(p = 0; p < artnet_fds; p++){ - close(artnet_fd[p]); + close(artnet_fd[p].fd); + free(artnet_fd[p].output_instance); + free(artnet_fd[p].last_frame); } free(artnet_fd); @@ -14,7 +14,7 @@ static int artnet_shutdown(); #define ARTNET_PORT "6454" #define ARTNET_VERSION 14 #define ARTNET_RECV_BUF 4096 -#define ARTNET_KEEPALIVE_INTERVAL 15e5 +#define ARTNET_KEEPALIVE_INTERVAL 2000 #define MAP_COARSE 0x0200 #define MAP_FINE 0x0400 @@ -50,6 +50,13 @@ typedef union /*_artnet_instance_id*/ { uint64_t label; } artnet_instance_id; +typedef struct /*_artnet_fd*/ { + int fd; + size_t output_instances; + artnet_instance_id* output_instance; + uint64_t* last_frame; +} artnet_descriptor; + #pragma pack(push, 1) typedef struct /*_artnet_hdr*/ { uint8_t magic[8]; diff --git a/monster.cfg b/monster.cfg index 236bd9a..8412e8c 100644 --- a/monster.cfg +++ b/monster.cfg @@ -5,6 +5,13 @@ name = MIDIMonster name = sACN source bind = 0.0.0.0 +[backend artnet] +bind = 0.0.0.0 + +[artnet art] +universe = 1 +dest = 10.2.2.255 + [evdev in] input = /dev/input/event14 @@ -19,3 +26,5 @@ in.EV_ABS.ABS_X > midi.cc0.0 in.EV_ABS.ABS_Y > midi.cc0.1 in.EV_ABS.ABS_X > sacn.1+2 in.EV_ABS.ABS_Y > sacn.3 +in.EV_ABS.ABS_X > art.1+2 +in.EV_ABS.ABS_Y > art.3 @@ -126,6 +126,7 @@ static int sacn_listener(char* host, char* port, uint8_t fd_flags){ global_cfg.fd[global_cfg.fds].flags = fd_flags; global_cfg.fd[global_cfg.fds].universes = 0; global_cfg.fd[global_cfg.fds].universe = NULL; + global_cfg.fd[global_cfg.fds].last_frame = NULL; global_cfg.fds++; return 0; } @@ -323,6 +324,55 @@ static channel* sacn_channel(instance* inst, char* spec){ return mm_channel(inst, chan_a, 1); } +static int sacn_transmit(instance* inst){ + size_t u; + sacn_instance_data* data = (sacn_instance_data*) inst->impl; + sacn_data_pdu pdu = { + .root = { + .preamble_size = htobe16(0x10), + .postamble_size = 0, + .magic = { 0 }, //memcpy'd + .flags = htobe16(0x7000 | 0x026e), + .vector = htobe32(ROOT_E131_DATA), + .sender_cid = { 0 }, //memcpy'd + .frame_flags = htobe16(0x7000 | 0x0258), + .frame_vector = htobe32(FRAME_E131_DATA) + }, + .data = { + .source_name = "", //memcpy'd + .priority = data->xmit_prio, + .sync_addr = 0, + .sequence = data->data.last_seq++, + .options = 0, + .universe = htobe16(data->uni), + .flags = htobe16(0x7000 | 0x0205), + .vector = DMP_SET_PROPERTY, + .format = 0xA1, + .startcode_offset = 0, + .address_increment = htobe16(1), + .channels = htobe16(513), + .data = { 0 } //memcpy'd + } + }; + + memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); + memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); + memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); + memcpy((((uint8_t*)pdu.data.data) + 1), data->data.out, 512); + + if(sendto(global_cfg.fd[data->fd_index].fd, &pdu, sizeof(pdu), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ + fprintf(stderr, "Failed to output sACN frame for instance %s: %s\n", inst->name, strerror(errno)); + } + + //update last transmit timestamp + for(u = 0; u < global_cfg.fd[data->fd_index].universes; u++){ + if(global_cfg.fd[data->fd_index].universe[u] == data->uni){ + global_cfg.fd[data->fd_index].last_frame[u] = mm_timestamp(); + } + } + return 0; +} + static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t u, mark = 0; sacn_instance_data* data = (sacn_instance_data*) inst->impl; @@ -358,42 +408,7 @@ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ //send packet if required if(mark){ - sacn_data_pdu pdu = { - .root = { - .preamble_size = htobe16(0x10), - .postamble_size = 0, - .magic = { 0 }, //memcpy'd - .flags = htobe16(0x7000 | 0x026e), - .vector = htobe32(ROOT_E131_DATA), - .sender_cid = { 0 }, //memcpy'd - .frame_flags = htobe16(0x7000 | 0x0258), - .frame_vector = htobe32(FRAME_E131_DATA) - }, - .data = { - .source_name = "", //memcpy'd - .priority = data->xmit_prio, - .sync_addr = 0, - .sequence = data->data.last_seq++, - .options = 0, - .universe = htobe16(data->uni), - .flags = htobe16(0x7000 | 0x0205), - .vector = DMP_SET_PROPERTY, - .format = 0xA1, - .startcode_offset = 0, - .address_increment = htobe16(1), - .channels = htobe16(513), - .data = { 0 } //memcpy'd - } - }; - - memcpy(pdu.root.magic, SACN_PDU_MAGIC, sizeof(pdu.root.magic)); - memcpy(pdu.root.sender_cid, global_cfg.cid, sizeof(pdu.root.sender_cid)); - memcpy(pdu.data.source_name, global_cfg.source_name, sizeof(pdu.data.source_name)); - memcpy((((uint8_t*)pdu.data.data) + 1), data->data.out, 512); - - if(sendto(global_cfg.fd[data->fd_index].fd, &pdu, sizeof(pdu), 0, (struct sockaddr*) &data->dest_addr, data->dest_len) < 0){ - fprintf(stderr, "Failed to output sACN frame for instance %s: %s\n", inst->name, strerror(errno)); - } + sacn_transmit(inst); } return 0; @@ -526,7 +541,8 @@ static void sacn_discovery(size_t fd){ } static int sacn_handle(size_t num, managed_fd* fds){ - size_t u; + size_t u, c; + uint64_t timestamp = mm_timestamp(); ssize_t bytes_read; char recv_buf[SACN_RECV_BUF]; instance* inst = NULL; @@ -543,7 +559,21 @@ static int sacn_handle(size_t num, managed_fd* fds){ sacn_discovery(u); } } - global_cfg.last_announce = mm_timestamp(); + global_cfg.last_announce = timestamp; + } + + //check for keepalive frames + for(u = 0; u < global_cfg.fds; u++){ + for(c = 0; c < global_cfg.fd[u].universes; c++){ + if(timestamp - global_cfg.fd[u].last_frame[c] >= SACN_KEEPALIVE_INTERVAL){ + instance_id.fields.fd_index = u; + instance_id.fields.uni = global_cfg.fd[u].universe[c]; + inst = mm_instance_find(BACKEND_NAME, instance_id.label); + if(inst){ + sacn_transmit(inst); + } + } + } } //early exit @@ -664,6 +694,12 @@ static int sacn_start(){ fprintf(stderr, "sACN backend registering %zu descriptors to core\n", global_cfg.fds); for(u = 0; u < global_cfg.fds; u++){ + //allocate memory for storing last frame transmission timestamp + global_cfg.fd[u].last_frame = calloc(global_cfg.fd[u].universes, sizeof(uint64_t)); + if(!global_cfg.fd[u].last_frame){ + fprintf(stderr, "Failed to allocate memory\n"); + goto bail; + } if(mm_manage_fd(global_cfg.fd[u].fd, BACKEND_NAME, 1, (void*) u)){ goto bail; } @@ -692,6 +728,7 @@ static int sacn_shutdown(){ for(p = 0; p < global_cfg.fds; p++){ close(global_cfg.fd[p].fd); free(global_cfg.fd[p].universe); + free(global_cfg.fd[p].last_frame); } free(global_cfg.fd); fprintf(stderr, "sACN backend shut down\n"); @@ -13,6 +13,7 @@ static int sacn_shutdown(); #define SACN_PORT "5568" #define SACN_RECV_BUF 8192 +#define SACN_KEEPALIVE_INTERVAL 2000 #define SACN_DISCOVERY_TIMEOUT 9000 #define SACN_PDU_MAGIC "ASC-E1.17\0\0\0" @@ -59,6 +60,7 @@ typedef struct /*_sacn_socket*/ { uint8_t flags; size_t universes; uint16_t* universe; + uint64_t* last_frame; } sacn_fd; #pragma pack(push, 1) |