From 046cd444704b9b457dd85cc2d22e1ec61b9cc184 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 20 Feb 2022 00:09:43 +0100 Subject: [PATCH] DMX: Daemon --- MQTT | 3 + dmx/daemon/Makefile | 24 ++++ dmx/daemon/burrow-dmxd.c | 265 +++++++++++++++++++++++++++++++++++++++ dmx/firmware/interface.h | 4 +- 4 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 dmx/daemon/Makefile create mode 100644 dmx/daemon/burrow-dmxd.c diff --git a/MQTT b/MQTT index a3b0a03..c2f2c0a 100644 --- a/MQTT +++ b/MQTT @@ -51,6 +51,9 @@ burrow/heating/water/active 0/1 burrow/heating/error [error code from message 0500:006b in decimal] burrow/heating/clock yyyy-mm-ddThh:mm +burrow/lights/catarium/top 0-255 [warm] 0-255 [cold] +burrow/lights/catarium/bottom 0-255 [warm] 0-255 [cold] + bsb/stats/* bsb/frame hex dump of raw frames received diff --git a/dmx/daemon/Makefile b/dmx/daemon/Makefile new file mode 100644 index 0000000..9ac9ef9 --- /dev/null +++ b/dmx/daemon/Makefile @@ -0,0 +1,24 @@ +PC=pkg-config +UCW_CFLAGS := $(shell $(PC) --cflags libucw) +UCW_LIBS := $(shell $(PC) --libs libucw) +USB_CFLAGS := $(shell $(PC) --cflags libusb-1.0) +USB_LIBS := $(shell $(PC) --libs libusb-1.0) +MOSQUITTO_CFLAGS := $(shell $(PC) --cflags libmosquitto) +MOSQUITTO_LIBS := $(shell $(PC) --libs libmosquitto) + +CFLAGS=-O2 -Wall -Wextra -Wno-sign-compare -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes $(UCW_CFLAGS) $(USB_CFLAGS) $(MOSQUITTO_CFLAGS) +LDLIBS=$(UCW_LIBS) $(USB_LIBS) $(MOSQUITTO_LIBS) -lpthread + +all: burrow-dmxd + +burrow-dmxd: burrow-dmxd.o + +burrow-dmxd.o: burrow-dmxd.c ../firmware/interface.h + +install: burrow-dmxd + install burrow-dmxd /usr/local/sbin/ + +clean: + rm -f *.o burrow-dmxd + +.PHONY: all install clean diff --git a/dmx/daemon/burrow-dmxd.c b/dmx/daemon/burrow-dmxd.c new file mode 100644 index 0000000..5634537 --- /dev/null +++ b/dmx/daemon/burrow-dmxd.c @@ -0,0 +1,265 @@ +/* + * Daemon for DMX512 over USB (custom USB peripheral) + * + * (c) 2020--2022 Martin Mares + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef unsigned char byte; +typedef uint32_t u32; +typedef unsigned int uint; + +#include "../firmware/interface.h" + +static mtx_t light_mutex; +static cnd_t light_cond; +static byte light_pwm[4]; +static bool light_refresh; + +/*** MQTT ***/ + +static struct mosquitto *mosq; +static bool mqtt_connected; + +static void mqtt_publish(const char *topic, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + if (mqtt_connected) { + char m[256]; + int l = vsnprintf(m, sizeof(m), fmt, args); + int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true); + if (err != MOSQ_ERR_SUCCESS) + msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err); + } + + va_end(args); +} + +static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status) +{ + if (!status) { + msg(L_DEBUG, "MQTT: Connection established"); + mqtt_connected = true; + mqtt_publish("status/dmx", "ok"); + if (mosquitto_subscribe(mosq, NULL, "burrow/lights/catarium/#", 1) != MOSQ_ERR_SUCCESS) + die("Mosquitto: subscribe failed"); + } else if (mqtt_connected) { + msg(L_DEBUG, "MQTT: Connection lost"); + mqtt_connected = false; + } +} + +static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message) +{ + msg(L_DEBUG, "MQTT(%d): %s", level, message); +} + +static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m) +{ + char val[256]; + if (m->payloadlen >= sizeof(val) - 1) { + msg(L_ERROR, "Invalid value for topic %s", m->topic); + return; + } + memcpy(val, m->payload, m->payloadlen); + val[m->payloadlen] = 0; + msg(L_DEBUG, "MQTT < %s %s", m->topic, val); + + int index; + if (!strcmp(m->topic, "burrow/lights/catarium/top")) + index = 0; + else if (!strcmp(m->topic, "burrow/lights/catarium/bottom")) + index = 1; + else + return; + + int warm, cold; + if (sscanf(val, "%d%d", &warm, &cold) != 2 || + !(warm >= 0 && warm < 256 && cold >= 0 && cold < 256)) { + msg(L_ERROR, "Invalid value of %s: %s", m->topic, val); + return; + } + + mtx_lock(&light_mutex); + light_pwm[2*index] = warm; + light_pwm[2*index + 1] = cold; + light_refresh = 1; + cnd_broadcast(&light_cond); + mtx_unlock(&light_mutex); +} + +static void mqtt_init(void) +{ + mosquitto_lib_init(); + + mosq = mosquitto_new("dmxd", 1, NULL); + if (!mosq) + die("Mosquitto: Initialization failed"); + + mosquitto_connect_callback_set(mosq, mqtt_conn_callback); + mosquitto_log_callback_set(mosq, mqtt_log_callback); + mosquitto_message_callback_set(mosq, mqtt_msg_callback); + + if (mosquitto_will_set(mosq, "status/dmx", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS) + die("Mosquitto: Unable to set will"); + + if (mosquitto_tls_set(mosq, "/etc/burrow-mqtt/ca.crt", NULL, "/etc/burrow-mqtt/client.crt", "/etc/burrow-mqtt/client.key", NULL) != MOSQ_ERR_SUCCESS) + die("Mosquitto: Unable to set TLS parameters"); + + if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS) + die("Mosquitto: Unable to connect"); + + if (mosquitto_loop_start(mosq)) + die("Mosquitto: Cannot start service thread"); +} + +/*** USB ***/ + +static struct libusb_context *usb_ctxt; +static struct libusb_device_handle *devh; + +static void usb_error(const char *msg, ...) +{ + va_list args; + va_start(args, msg); + ucw_vmsg(L_ERROR, msg, args); + va_end(args); + + if (devh) { + libusb_close(devh); + devh = NULL; + } +} + +static void open_device(void) +{ + int err; + libusb_device **devlist; + ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist); + if (devn < 0) + die("Cannot enumerate USB devices: error %d", (int) devn); + + for (ssize_t i=0; ilevels &= ~(1U << L_DEBUG); + + mtx_init(&light_mutex, mtx_plain); + cnd_init(&light_cond); + + mqtt_init(); + init_usb(); + + bool need_resend = true; + for (;;) { + if (!devh) { + msg(L_INFO, "Waiting for device to appear..."); + while (!devh) { + sleep(5); + open_device(); + } + need_resend = true; + } + + mtx_lock(&light_mutex); + while (!need_resend && !light_refresh) + cnd_wait(&light_cond, &light_mutex); + + byte packet[5]; + int len = sizeof(packet); + packet[0] = 0; + memcpy(packet + 1, light_pwm, 4); + + light_refresh = 0; + need_resend = 0; + mtx_unlock(&light_mutex); + + msg(L_DEBUG, "Sending DMX packet"); + + int err, transferred; + if (err = libusb_bulk_transfer(devh, 0x01, packet, len, &transferred, 1000)) + usb_error("USB transfer failed: error %d", err); + else if (transferred != len) + usb_error("USB short transfer: %d out of %d bytes", transferred, len); + } +} diff --git a/dmx/firmware/interface.h b/dmx/firmware/interface.h index 1954fb5..f402061 100644 --- a/dmx/firmware/interface.h +++ b/dmx/firmware/interface.h @@ -12,7 +12,5 @@ * Endpoints: * * 0x01 = bulk endpoint - * FIXME - * Used for sending frames to BSB. Accepts BSB frames. Sender address and CRC - * will be recalculated. + * Accepts DMX frames to send. */ -- 2.39.2