]> mj.ucw.cz Git - home-hw.git/commitdiff
DMX: Daemon
authorMartin Mares <mj@ucw.cz>
Sat, 19 Feb 2022 23:09:43 +0000 (00:09 +0100)
committerMartin Mares <mj@ucw.cz>
Sat, 19 Feb 2022 23:09:43 +0000 (00:09 +0100)
MQTT
dmx/daemon/Makefile [new file with mode: 0644]
dmx/daemon/burrow-dmxd.c [new file with mode: 0644]
dmx/firmware/interface.h

diff --git a/MQTT b/MQTT
index a3b0a03e6c12338ff02944bb594397a7fbc2018d..c2f2c0add02d89921afb3dcf52c44a7d63fbda03 100644 (file)
--- 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 (file)
index 0000000..9ac9ef9
--- /dev/null
@@ -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 (file)
index 0000000..5634537
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ *     Daemon for DMX512 over USB (custom USB peripheral)
+ *
+ *     (c) 2020--2022 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/log.h>
+#include <ucw/opt.h>
+#include <ucw/string.h>
+#include <ucw/unaligned.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <threads.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <libusb.h>
+#include <mosquitto.h>
+
+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; i<devn; i++) {
+               struct libusb_device_descriptor desc;
+               libusb_device *dev = devlist[i];
+               if (!libusb_get_device_descriptor(dev, &desc)) {
+                       if (desc.idVendor == DMX_USB_VENDOR && desc.idProduct == DMX_USB_PRODUCT) {
+                               msg(L_INFO, "Found DMX device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
+
+                               if (err = libusb_open(dev, &devh)) {
+                                       usb_error("Cannot open device: error %d", err);
+                                       goto out;
+                               }
+                               libusb_reset_device(devh);
+                               if (err = libusb_claim_interface(devh, 0)) {
+                                       usb_error("Cannot claim interface: error %d", err);
+                                       goto out;
+                               }
+
+                               goto out;
+                       }
+               }
+       }
+
+out:
+       libusb_free_device_list(devlist, 1);
+}
+
+static void init_usb(void)
+{
+       int err;
+       if (err = libusb_init(&usb_ctxt))
+               die("Cannot initialize libusb: error %d", err);
+       // libusb_set_debug(usb_ctxt, 3);
+       open_device();
+}
+
+/*** Protocol ***/
+
+static int use_daemon;
+static int use_debug;
+
+static struct opt_section options = {
+       OPT_ITEMS {
+               OPT_HELP("A daemon for controlling lights via DMX512"),
+               OPT_HELP(""),
+               OPT_HELP("Options:"),
+               OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
+               OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
+               OPT_HELP_OPTION,
+               OPT_CONF_OPTIONS,
+               OPT_END
+       }
+};
+
+int main(int argc UNUSED, char **argv)
+{
+       log_init(argv[0]);
+       opt_parse(&options, argv+1);
+
+       if (use_daemon) {
+               struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
+               log_set_default_stream(ls);
+       }
+       if (!use_debug)
+               log_default_stream()->levels &= ~(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);
+       }
+}
index 1954fb5aef933f89198940df1a4c5833da2de39d..f4020610d5f07a1683755216168d40707593165f 100644 (file)
@@ -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.
  */