aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2020-02-27 00:33:22 +0100
committercbdev <cb@cbcdn.com>2020-02-27 00:33:22 +0100
commite1fcd4d11cfdbad54470b2cce98d8b749464ec00 (patch)
tree1fc646402cca3934cd7fe87153a5f86fd9edf868
parent1b3878956f02e274c480815774f9c6f39d65117f (diff)
downloadmidimonster-e1fcd4d11cfdbad54470b2cce98d8b749464ec00.tar.gz
midimonster-e1fcd4d11cfdbad54470b2cce98d8b749464ec00.tar.bz2
midimonster-e1fcd4d11cfdbad54470b2cce98d8b749464ec00.zip
Implement OpenPixelControl server mode (8bit)
-rw-r--r--README.md6
-rw-r--r--backends/libmmbackend.c4
-rw-r--r--backends/openpixelcontrol.c220
-rw-r--r--backends/openpixelcontrol.h15
-rw-r--r--backends/openpixelcontrol.md9
5 files changed, 241 insertions, 13 deletions
diff --git a/README.md b/README.md
index 46331f3..4d0b052 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@
<img align="right" src="/MIDIMonster.svg?raw=true&sanitize=true" alt="MIDIMonster Logo" width="20%">
-[![Build Status](https://travis-ci.com/cbdevnet/midimonster.svg?branch=master)](https://travis-ci.com/cbdevnet/midimonster) [![Coverity Scan Build Status](https://scan.coverity.com/projects/15168/badge.svg)](https://scan.coverity.com/projects/15168)
+[![Build Status](https://travis-ci.com/cbdevnet/midimonster.svg?branch=master)](https://travis-ci.com/cbdevnet/midimonster)
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/15168/badge.svg)](https://scan.coverity.com/projects/15168)
+[![IRC Channel](https://static.midimonster.net/hackint-badge.svg)](https://webirc.hackint.org/#irc://irc.hackint.org/#midimonster)
Named for its scary math, the MIDIMonster is a universal control and translation
tool for multi-channel absolute-value-based control and/or bus protocols.
@@ -15,6 +17,7 @@ Currently, the MIDIMonster supports the following protocols:
| ArtNet | Linux, Windows, OSX | Version 4 | [`artnet`](backends/artnet.md)|
| Streaming ACN (sACN / E1.31) | Linux, Windows, OSX | | [`sacn`](backends/sacn.md) |
| OpenSoundControl (OSC) | Linux, Windows, OSX | | [`osc`](backends/osc.md) |
+| OpenPixelControl | Linux, Windows, OSX | 8 Bit & 16 Bit modes | [`openpixelcontrol`](backends/openpixelcontrol.md) |
| evdev input devices | Linux | Virtual output supported | [`evdev`](backends/evdev.md) |
| Open Lighting Architecture | Linux, OSX | | [`ola`](backends/ola.md) |
| MA Lighting Web Remote | Linux, Windows, OSX | GrandMA2 and dot2 (incl. OnPC) | [`maweb`](backends/maweb.md) |
@@ -142,6 +145,7 @@ special information. These documentation files are located in the `backends/` di
* [`loopback` backend documentation](backends/loopback.md)
* [`ola` backend documentation](backends/ola.md)
* [`osc` backend documentation](backends/osc.md)
+* [`openpixelcontrol` backend documentation](backends/openpixelcontrol.md)
* [`lua` backend documentation](backends/lua.md)
* [`maweb` backend documentation](backends/maweb.md)
diff --git a/backends/libmmbackend.c b/backends/libmmbackend.c
index ffa403b..b9513ac 100644
--- a/backends/libmmbackend.c
+++ b/backends/libmmbackend.c
@@ -153,7 +153,11 @@ int mmbackend_socket(char* host, char* port, int socktype, uint8_t listener, uin
int mmbackend_send(int fd, uint8_t* data, size_t length){
ssize_t total = 0, sent;
while(total < length){
+ #ifndef LIBMMBACKEND_TCP_TORTURE
sent = send(fd, data + total, length - total, 0);
+ #else
+ sent = send(fd, data + total, 1, 0);
+ #endif
if(sent < 0){
LOGPF("Failed to send: %s", strerror(errno));
return 1;
diff --git a/backends/openpixelcontrol.c b/backends/openpixelcontrol.c
index 062c015..3a94a12 100644
--- a/backends/openpixelcontrol.c
+++ b/backends/openpixelcontrol.c
@@ -52,6 +52,7 @@ static int openpixel_configure_instance(instance* inst, char* option, char* valu
if(data->dest_fd >= 0){
return 0;
}
+ LOGPF("Failed to connect to server for instance %s", inst->name);
return 1;
}
if(!strcmp(option, "listen")){
@@ -62,9 +63,10 @@ static int openpixel_configure_instance(instance* inst, char* option, char* valu
}
data->listen_fd = mmbackend_socket(host, port, SOCK_STREAM, 1, 0);
- if(data->listen_fd >= 0 && listen(data->listen_fd, SOMAXCONN)){
+ if(data->listen_fd >= 0 && !listen(data->listen_fd, SOMAXCONN)){
return 0;
}
+ LOGPF("Failed to bind server descriptor for instance %s", inst->name);
return 1;
}
else if(!strcmp(option, "mode")){
@@ -103,15 +105,22 @@ static ssize_t openpixel_buffer_find(openpixel_instance_data* data, uint8_t stri
for(n = 0; n < data->buffers; n++){
if(data->buffer[n].strip == strip
&& (data->buffer[n].flags & OPENPIXEL_INPUT) >= input){
+ DBGPF("Using allocated %s buffer for requested strip %d, size %d", input ? "input" : "output", strip, data->buffer[n].bytes);
return n;
}
}
+ DBGPF("Instance has no %s buffer for requested strip %d", input ? "input" : "output", strip);
return -1;
}
-static int openpixel_buffer_extend(openpixel_instance_data* data, uint8_t strip, uint8_t input, uint8_t length){
+static int openpixel_buffer_extend(openpixel_instance_data* data, uint8_t strip, uint8_t input, uint16_t length){
ssize_t buffer = openpixel_buffer_find(data, strip, input);
+
+ //length is in component-channels, round it to the nearest rgb-triplet
+ //this guarantees that any allocated buffer has at least three bytes, which is important to parts of the receive handler
length = (length % 3) ? ((length / 3) + 1) * 3 : length;
+
+ //calculate required buffer length
size_t bytes_required = (data->mode == rgb8) ? length : length * 2;
if(buffer < 0){
//allocate new buffer
@@ -242,7 +251,7 @@ static int openpixel_set(instance* inst, size_t num, channel** c, channel_value*
}
if(strip == 0){
- //update values in all other output strips, dont mark
+ //update values in all other output strips, don't mark
for(p = 0; p < data->buffers; p++){
if(!(data->buffer[p].flags & OPENPIXEL_INPUT)){
//check whether the buffer is large enough
@@ -278,8 +287,204 @@ static int openpixel_set(instance* inst, size_t num, channel** c, channel_value*
return 0;
}
+static int openpixel_client_new(instance* inst, int fd){
+ if(fd < 0){
+ return 1;
+ }
+ openpixel_instance_data* data = (openpixel_instance_data*) inst->impl;
+ size_t u;
+
+ //mark nonblocking
+ #ifdef _WIN32
+ unsigned long flags = 1;
+ if(ioctlsocket(fd, FIONBIO, &flags)){
+ #else
+ int flags = fcntl(fd, F_GETFL, 0);
+ if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){
+ #endif
+ LOGPF("Failed to set client descriptor on %s nonblocking", inst->name);
+ close(fd);
+ return 0;
+ }
+
+ //find a client block
+ for(u = 0; u < data->clients; u++){
+ if(data->client[u].fd <= 0){
+ break;
+ }
+ }
+
+ //if no free slot, make one
+ if(u == data->clients){
+ data->client = realloc(data->client, (data->clients + 1) * sizeof(openpixel_client));
+ if(!data->client){
+ data->clients = 0;
+ LOG("Failed to allocate memory");
+ return 1;
+ }
+ data->clients++;
+ }
+
+ data->client[u].fd = fd;
+ data->client[u].buffer = -1;
+ data->client[u].offset = 0;
+
+ return mm_manage_fd(fd, BACKEND_NAME, 1, inst);
+}
+
+static int openpixel_client_handle(instance* inst, int fd){
+ openpixel_instance_data* data = (openpixel_instance_data*) inst->impl;
+ uint8_t buffer[8192];
+ size_t c = 0, offset = 0, u;
+ ssize_t bytes_left = 0;
+ channel* chan = NULL;
+ channel_value val;
+
+ for(c = 0; c < data->clients; c++){
+ if(data->client[c].fd == fd){
+ break;
+ }
+ }
+
+ if(c == data->clients){
+ LOGPF("Unknown client descriptor signaled on %s", inst->name);
+ return 1;
+ }
+
+ //FIXME might want to read until EAGAIN
+ ssize_t bytes = recv(fd, buffer, sizeof(buffer), 0);
+ if(bytes <= 0){
+ if(bytes < 0){
+ LOGPF("Failed to receive from client: %s", strerror(errno));
+ }
+
+ //close the connection
+ close(fd);
+ data->client[c].fd = -1;
+ //unmanage the fd
+ mm_manage_fd(fd, BACKEND_NAME, 0, NULL);
+ return 0;
+ }
+
+ for(bytes_left = bytes - offset; bytes_left > 0; bytes_left = bytes - offset){
+ if(data->client[c].buffer == -1){
+ //read a header
+ DBGPF("Reading %" PRIsize_t " bytes to header at offset %" PRIsize_t ", header size %" PRIsize_t ", %" PRIsize_t " bytes left", min(sizeof(openpixel_header) - data->client[c].offset, bytes_left), data->client[c].offset, sizeof(openpixel_header), bytes_left);
+ memcpy(((uint8_t*) (&data->client[c].hdr)) + data->client[c].offset, buffer + offset, min(sizeof(openpixel_header) - data->client[c].offset, bytes_left));
+
+ //if done, resolve buffer
+ if(sizeof(openpixel_header) - data->client[c].offset < bytes_left){
+ data->client[c].buffer = openpixel_buffer_find(data, data->client[c].hdr.strip, 1);
+ //if no buffer or mode mismatch, ignore data
+ if(data->client[c].buffer < 0
+ || data->mode != data->client[c].hdr.mode){
+ data->client[c].buffer = -2; //mark for ignore
+ }
+ data->client[c].left = be16toh(data->client[c].hdr.length);
+ data->client[c].offset = 0;
+ }
+ //if not, update client offset
+ else{
+ data->client[c].offset += bytes_left;
+ }
+
+ //update scan offset
+ offset += min(sizeof(openpixel_header) - data->client[c].offset, bytes_left);
+ }
+ else{
+ //read data
+ if(data->client[c].buffer == -2){
+ //ignore data
+ offset += min(data->client[c].left, bytes_left);
+ data->client[c].offset += min(data->client[c].left, bytes_left);
+ data->client[c].left -= min(data->client[c].left, bytes_left);
+ }
+ else{
+ if(data->mode == rgb8){
+ for(u = 0; u < bytes_left; u++){
+ //if over buffer length, ignore
+ if(u + data->client[c].offset >= data->buffer[data->client[c].buffer].bytes){
+ data->client[c].buffer = -2;
+ break;
+ }
+
+ //FIXME if at start of trailing non-multiple of 3, ignore
+
+ //update changed channels
+ if(data->buffer[data->client[c].buffer].data.u8[u + data->client[c].offset] != buffer[offset + u]){
+ data->buffer[data->client[c].buffer].data.u8[u + data->client[c].offset] = buffer[offset + u];
+ chan = mm_channel(inst, ((uint64_t) data->client[c].hdr.strip << 32) | (u + data->client[c].offset + 1), 0);
+ if(chan){
+ //push event
+ val.raw.u64 = buffer[offset + u];
+ val.normalised = (double) buffer[offset + u] / 255.0;
+ if(mm_channel_event(chan, val)){
+ LOG("Failed to push channel event to core");
+ //FIXME err out here
+ }
+ }
+ }
+ }
+
+ //update offsets
+ offset += u;
+ data->client[c].offset += u;
+ data->client[c].left -= u;
+ }
+ else{
+ //TODO byte-order conversion may be on recv boundary
+ //if over buffer length, ignore
+ //skip non-multiple-of 6 trailing data
+ }
+ }
+
+ //end of data, return to reading headers
+ if(data->client[c].left == 0){
+ data->client[c].buffer = -1;
+ data->client[c].offset = 0;
+ data->client[c].left = 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
static int openpixel_handle(size_t num, managed_fd* fds){
- //TODO handle bcast
+ size_t u;
+ instance* inst = NULL;
+ openpixel_instance_data* data = NULL;
+ uint8_t buffer[8192];
+ ssize_t bytes;
+
+ for(u = 0; u < num; u++){
+ inst = (instance*) fds[u].impl;
+ data = (openpixel_instance_data*) inst->impl;
+
+ if(fds[u].fd == data->dest_fd){
+ //destination fd ready to read
+ //since the protocol does not define any responses, the connection was probably closed
+ bytes = recv(data->dest_fd, buffer, sizeof(buffer), 0);
+ if(bytes <= 0){
+ LOGPF("Output descriptor closed on instance %s", inst->name);
+ //unmanage the fd to give the core some rest
+ mm_manage_fd(data->dest_fd, BACKEND_NAME, 0, NULL);
+ }
+ else{
+ LOGPF("Unhandled response data on %s (%" PRIsize_t" bytes)", inst->name, bytes);
+ }
+ }
+ else if(fds[u].fd == data->listen_fd){
+ //listen fd ready to read, accept a new client
+ if(openpixel_client_new(inst, accept(data->listen_fd, NULL, NULL))){
+ return 1;
+ }
+ }
+ else{
+ //handle client input
+ openpixel_client_handle(inst, fds[u].fd);
+ }
+ }
return 0;
}
@@ -323,12 +528,11 @@ static int openpixel_shutdown(size_t n, instance** inst){
//shutdown all clients
for(p = 0; p < data->clients; p++){
- if(data->client_fd[p] >= 0){
- close(data->client_fd[p]);
+ if(data->client[p].fd>= 0){
+ close(data->client[p].fd);
}
}
- free(data->client_fd);
- free(data->bytes_left);
+ free(data->client);
//close all configured fds
if(data->listen_fd >= 0){
diff --git a/backends/openpixelcontrol.h b/backends/openpixelcontrol.h
index f1061ea..658bbf0 100644
--- a/backends/openpixelcontrol.h
+++ b/backends/openpixelcontrol.h
@@ -31,6 +31,18 @@ typedef struct /*_openpixel_hdr*/ {
} openpixel_header;
#pragma pack(pop)
+typedef struct /*_openpixel_client*/ {
+ int fd;
+ ssize_t buffer;
+ openpixel_header hdr;
+ size_t offset;
+ size_t left;
+ union {
+ uint8_t u8[2];
+ uint16_t u16;
+ } boundary;
+} openpixel_client;
+
typedef struct {
enum {
rgb8 = 0,
@@ -43,6 +55,5 @@ typedef struct {
int dest_fd;
int listen_fd;
size_t clients;
- int* client_fd;
- size_t* bytes_left;
+ openpixel_client* client;
} openpixel_instance_data;
diff --git a/backends/openpixelcontrol.md b/backends/openpixelcontrol.md
index ce60278..6dd38bc 100644
--- a/backends/openpixelcontrol.md
+++ b/backends/openpixelcontrol.md
@@ -45,5 +45,10 @@ strip1.blue2 < strip2.green66
#### Known bugs / problems
-If the connection is lost, it is currently not reestablished and may cause exit the MIDIMonster entirely.
-Thisi behaviour may be changed in future releases.
+If the connection is lost, it is currently not reestablished and may cause the MIDIMonster to exit entirely.
+This behaviour may be changed in future releases.
+
+While acting as an OpenPixelControl server, the backend allows multiple clients to connect.
+This may lead to confusing data output when multiple clients are trying to control the same strip.
+
+16 bit server mode is not implemented yet. This will be fixed in a future release.