summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lamparter <equinox@diac24.net>2016-05-15 14:03:59 +0200
committerDavid Lamparter <equinox@diac24.net>2016-05-15 14:03:59 +0200
commit58e5252557fb0eea1585cd9624e1da0a730d9a4c (patch)
tree3187f5672dcc156c1574509bde7aacd4742feef2
parent3dc9bd30e3e116cae4ca48c874a5ad571aa726c7 (diff)
cethcan: ttyDMX
-rw-r--r--cethcan/Makefile6
-rw-r--r--cethcan/cethcan.h1
-rw-r--r--cethcan/cethcan.json4
-rw-r--r--cethcan/main.c7
-rw-r--r--cethcan/ttydmx.c279
5 files changed, 292 insertions, 5 deletions
diff --git a/cethcan/Makefile b/cethcan/Makefile
index bc8fab4..933dd77 100644
--- a/cethcan/Makefile
+++ b/cethcan/Makefile
@@ -2,11 +2,11 @@ love: cethcan
.PHONY: love
PKGS="libevent jansson"
-L_CFLAGS=-g -O0 -Wall -Wextra -Wshadow -pedantic -Wno-unused-parameter -Wno-format -std=gnu99 `pkg-config --cflags $(PKGS)` $(CFLAGS)
-L_LDFLAGS=-g `pkg-config --libs $(PKGS)` -lcrypto $(LDFLAGS)
+L_CFLAGS=-g -O0 -pthread -Wall -Wextra -Wshadow -pedantic -Wno-unused-parameter -Wno-format -std=gnu11 `pkg-config --cflags $(PKGS)` $(CFLAGS)
+L_LDFLAGS=-g -pthread `pkg-config --libs $(PKGS)` -lcrypto $(LDFLAGS)
cethcan: main.o can.o ether.o light.o beanctr.o \
- http.o socketcan.o jsonrpc.o rpc.o espnet.o
+ http.o socketcan.o jsonrpc.o rpc.o espnet.o ttydmx.o
gcc $(L_LDFLAGS) -o $@ $^
clean:
diff --git a/cethcan/cethcan.h b/cethcan/cethcan.h
index 5b42f54..4d215f0 100644
--- a/cethcan/cethcan.h
+++ b/cethcan/cethcan.h
@@ -103,6 +103,7 @@ extern int ether_init(json_t *config);
extern int light_init_conf(json_t *config);
extern int bean_init_conf(json_t *config);
extern int espnet_init_conf(json_t *config);
+extern int ttydmx_init_conf(json_t *config);
extern void http_init(void);
#endif /* _CETHCAN_H */
diff --git a/cethcan/cethcan.json b/cethcan/cethcan.json
index 8998550..2357993 100644
--- a/cethcan/cethcan.json
+++ b/cethcan/cethcan.json
@@ -33,8 +33,8 @@
{ "addr": 258, "name": "door.right", "values": [ "open", "closed" ] },
{ "addr": 259, "name": "door.lock", "values": [ "open", "closed" ] }
],
- "espnet": [ {
- "interface": "eth0",
+ "ttydmx": [ {
+ "ttydev": "ttyS2",
"devices": [
{ "baseaddr": 2, "name": "dmx.lounge_buero_ecke" },
{ "baseaddr": 7, "name": "dmx.lounge_midwest" },
diff --git a/cethcan/main.c b/cethcan/main.c
index 2b9154e..8d38757 100644
--- a/cethcan/main.c
+++ b/cethcan/main.c
@@ -81,6 +81,13 @@ int main(int argc, char **argv)
return 1;
}
+ json_t *dmxcfg = json_object_get(config, "ttydmx");
+ for (size_t i = 0; i < json_array_size(dmxcfg); i++) {
+ json_t *c = json_array_get(dmxcfg, i);
+ if (ttydmx_init_conf(c))
+ return 1;
+ }
+
http_init();
json_decref(config);
diff --git a/cethcan/ttydmx.c b/cethcan/ttydmx.c
new file mode 100644
index 0000000..6165ed7
--- /dev/null
+++ b/cethcan/ttydmx.c
@@ -0,0 +1,279 @@
+#include "cethcan.h"
+
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <termio.h>
+#include <linux/serial.h>
+#include <pthread.h>
+#include <stdatomic.h>
+
+struct ttydmx_device;
+
+struct ttydmx_data {
+ size_t len;
+ uint8_t data[];
+};
+
+struct ttydmx_sink {
+ struct ttydmx_sink *next;
+
+ struct can_user *u;
+ int fd;
+ char *ttydev;
+ size_t maxaddr;
+
+ pthread_t pusher;
+ struct ttydmx_data * _Atomic nextdata;
+
+ struct ttydmx_device *devs;
+};
+
+struct ttydmx_device {
+ struct ttydmx_device *next;
+ struct ttydmx_sink *sink;
+
+ char *name;
+ unsigned baseaddr;
+ uint8_t r, g, b;
+};
+
+static struct ttydmx_sink *sinks = NULL, **psinks = &sinks;
+
+struct ttydmx_device *ttydmx_find(const char *name)
+{
+ for (struct ttydmx_sink *sink = sinks; sink; sink = sink->next)
+ for (struct ttydmx_device *d = sink->devs; d; d = d->next)
+ if (!strcmp(d->name, name))
+ return d;
+ return NULL;
+}
+
+int ttydmx_set(struct ttydmx_device *dev, unsigned r, unsigned g, unsigned b)
+{
+ struct ttydmx_sink *sink = dev->sink;
+ struct ttydmx_data *newdata, *prevdata;
+ struct ttydmx_device *walk;
+ bool bump = dev->r != r || dev->g != g || dev->b != b;
+
+ dev->r = r;
+ dev->g = g;
+ dev->b = b;
+
+ newdata = calloc(offsetof(struct ttydmx_data, data[sink->maxaddr]), 1);
+ newdata->len = sink->maxaddr;
+ for (walk = sink->devs; walk; walk = walk->next) {
+ newdata->data[walk->baseaddr + 0] = walk->r;
+ newdata->data[walk->baseaddr + 1] = walk->g;
+ newdata->data[walk->baseaddr + 2] = walk->b;
+ }
+ prevdata = atomic_exchange(&sink->nextdata, newdata);
+ if (prevdata)
+ free(prevdata);
+
+ if (bump)
+ json_bump_longpoll();
+ return 0;
+}
+
+void ttydmx_get(struct ttydmx_device *dev,
+ unsigned *r, unsigned *g, unsigned *b)
+{
+ *r = dev->r;
+ *g = dev->g;
+ *b = dev->b;
+}
+
+static void ttydmx_json_one(struct ttydmx_sink *sink,
+ struct ttydmx_device *dev,
+ json_t *json, enum json_subtype type)
+{
+ json_t *lobj = json_object();
+
+ json_object_set_new(lobj, "klass", json_string("dmxrgb"));
+ json_object_set_new(lobj, "dmxaddr", json_integer(dev->baseaddr));
+
+ json_object_set_new(lobj, "r", json_integer(dev->r));
+ json_object_set_new(lobj, "g", json_integer(dev->g));
+ json_object_set_new(lobj, "b", json_integer(dev->b));
+
+ json_object_set_new(json, dev->name, lobj);
+}
+
+static void ttydmx_json_handler(void *arg, json_t *json,
+ enum json_subtype type)
+{
+ struct ttydmx_sink *sink = arg;
+ for (struct ttydmx_device *d = sink->devs; d; d = d->next)
+ ttydmx_json_one(sink, d, json, type);
+}
+
+static void ttydmx_can_handler(void *arg, struct can_message *msg)
+{
+}
+
+static struct ttydmx_device *ttydmx_add_dev(struct ttydmx_sink *sink,
+ json_t *config)
+{
+ struct ttydmx_device *d;
+
+ if (!json_is_object(config)) {
+ lprintf("ttyDMX device config must be an object/dictionary");
+ return NULL;
+ }
+ if (!json_is_string(json_object_get(config, "name"))) {
+ lprintf("ttyDMX device config must specify str 'name'");
+ return NULL;
+ }
+ if (!json_is_integer(json_object_get(config, "baseaddr"))) {
+ lprintf("ttyDMX device config must specify int 'baseaddr'");
+ return NULL;
+ }
+
+ d = calloc(sizeof(*d), 1);
+ d->sink = sink;
+ d->baseaddr = json_integer_value(json_object_get(config, "baseaddr"));
+ d->name = strdup(json_string_value(json_object_get(config, "name")));
+
+ if (d->baseaddr + 4 > sink->maxaddr)
+ sink->maxaddr = d->baseaddr + 4;
+ return d;
+}
+
+static void *ttydmx_thread(void *arg)
+{
+ struct ttydmx_sink *sink = arg;
+ struct ttydmx_data *data = NULL, *newdata;
+ uint8_t dummy = 0;
+
+ while (1) {
+ newdata = atomic_exchange(&sink->nextdata, NULL);
+ if (newdata) {
+ free(data);
+ data = newdata;
+ }
+
+ ioctl(sink->fd, TIOCSBRK, 0);
+ usleep(100);
+ ioctl(sink->fd, TIOCCBRK, 0);
+ usleep(12);
+ write(sink->fd, data ? data->data : &dummy,
+ data ? data->len : 1);
+ tcdrain(sink->fd);
+ }
+ return NULL;
+}
+
+int ttydmx_init_conf(json_t *config)
+{
+ struct ttydmx_sink *sink;
+ struct ttydmx_device **devp;
+ const char *ttydev;
+ char ttydevfull[PATH_MAX];
+ struct stat st;
+ struct termios termios;
+ struct serial_struct serial;
+
+ if (!json_is_object(config)) {
+ lprintf("ttyDMX config must be an object/dictionary");
+ return 1;
+ }
+ if (!json_is_string(json_object_get(config, "ttydev"))) {
+ lprintf("ttyDMX config must have a string 'ttydev' key");
+ return 1;
+ }
+ if (!json_is_array(json_object_get(config, "devices"))) {
+ lprintf("ttyDMX config must have an array 'devices' key");
+ return 1;
+ }
+
+ ttydev = json_string_value(json_object_get(config, "ttydev"));
+ if (strchr(ttydev, '/'))
+ snprintf(ttydevfull, sizeof(ttydevfull), "%s", ttydev);
+ else
+ snprintf(ttydevfull, sizeof(ttydevfull), "/dev/%s", ttydev);
+
+ if (stat(ttydevfull, &st)) {
+ lprintf("ttyDMX: stat(\"%s\") failed: %s",
+ ttydevfull, strerror(errno));
+ return 1;
+ }
+ if (!S_ISCHR(st.st_mode)) {
+ lprintf("ttyDMX: \"%s\" needs to be a character device!",
+ ttydevfull);
+ return 1;
+ }
+
+ sink = calloc(sizeof(*sink), 1);
+ sink->ttydev = strdup(ttydevfull);
+ sink->fd = open(ttydevfull, O_RDWR | O_NOCTTY);
+ if (sink->fd < 0) {
+ lprintf("ttyDMX: failed to open \"%s\": %s",
+ ttydevfull, strerror(errno));
+ free(sink->ttydev);
+ free(sink);
+ return 1;
+ }
+
+ memset(&serial, 0, sizeof(serial));
+ if (ioctl(sink->fd, TIOCGSERIAL, &serial))
+ goto out_setup_err;
+
+ serial.flags &= ~ASYNC_SPD_MASK;
+ serial.flags |= ASYNC_SPD_CUST;
+ serial.custom_divisor = serial.baud_base / 250000;
+
+ if (ioctl(sink->fd, TIOCSSERIAL, &serial))
+ goto out_setup_err;
+ if (ioctl(sink->fd, TIOCGSERIAL, &serial))
+ goto out_setup_err;
+ lprintf("ttyDMX: \"%s\": divisor: %d, result rate: %d\n",
+ ttydevfull,
+ (int)serial.custom_divisor,
+ (int)serial.baud_base / serial.custom_divisor);
+
+ if (tcgetattr(sink->fd, &termios))
+ goto out_setup_err;
+ cfsetispeed(&termios, B38400);
+ cfsetospeed(&termios, B38400);
+ cfmakeraw(&termios);
+ termios.c_cflag |= CLOCAL | CREAD | CSTOPB;
+ termios.c_cflag &= ~CRTSCTS;
+ if (tcsetattr(sink->fd, TCSANOW, &termios))
+ goto out_setup_err;
+
+ json_t *devcfg = json_object_get(config, "devices");
+ devp = &sink->devs;
+ for (size_t i = 0; i < json_array_size(devcfg); i++) {
+ struct ttydmx_device *dev;
+ json_t *c = json_array_get(devcfg, i);
+ dev = ttydmx_add_dev(sink, c);
+ if (!dev) {
+ close(sink->fd);
+ free(sink->ttydev);
+ free(sink);
+ return 1;
+ }
+ *devp = dev;
+ devp = &dev->next;
+ }
+
+ sink->u = can_register_alloc(sink, ttydmx_can_handler,
+ "ttyDMX[%s]", sink->ttydev);
+ sink->u->json = ttydmx_json_handler;
+
+ if (pthread_create(&sink->pusher, NULL, ttydmx_thread, sink))
+ lprintf("ttyDMX[%s]: failed to start thread!", sink->ttydev);
+
+ *psinks = sink;
+ psinks = &sink->next;
+ return 0;
+
+out_setup_err:
+ lprintf("ttyDMX: setup error on \"%s\": %s",
+ ttydevfull, strerror(errno));
+ close(sink->fd);
+ free(sink->ttydev);
+ free(sink);
+ return 1;
+}