summaryrefslogtreecommitdiff
path: root/cethcan/ttydmx.c
diff options
context:
space:
mode:
Diffstat (limited to 'cethcan/ttydmx.c')
-rw-r--r--cethcan/ttydmx.c279
1 files changed, 279 insertions, 0 deletions
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;
+}