2 * Daemon for DMX512 over USB (custom USB peripheral)
4 * (c) 2020--2022 Martin Mares <mj@ucw.cz>
10 #include <ucw/string.h>
11 #include <ucw/unaligned.h>
25 #include <mosquitto.h>
27 typedef unsigned char byte;
29 typedef unsigned int uint;
31 #include "../firmware/interface.h"
33 static mtx_t light_mutex;
34 static cnd_t light_cond;
35 static double light_brightness[2];
36 static double light_temperature[2];
37 static bool light_refresh;
41 static struct mosquitto *mosq;
42 static bool mqtt_connected;
44 static void mqtt_publish(const char *topic, const char *fmt, ...)
51 int l = vsnprintf(m, sizeof(m), fmt, args);
52 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
53 if (err != MOSQ_ERR_SUCCESS)
54 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
60 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
63 msg(L_DEBUG, "MQTT: Connection established");
64 mqtt_connected = true;
65 mqtt_publish("status/dmx", "ok");
66 if (mosquitto_subscribe(mosq, NULL, "burrow/lights/catarium/#", 1) != MOSQ_ERR_SUCCESS)
67 die("Mosquitto: subscribe failed");
68 } else if (mqtt_connected) {
69 msg(L_DEBUG, "MQTT: Connection lost");
70 mqtt_connected = false;
74 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
76 msg(L_DEBUG, "MQTT(%d): %s", level, message);
79 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
82 if (m->payloadlen >= sizeof(val) - 1) {
83 msg(L_ERROR, "Invalid value for topic %s", m->topic);
86 memcpy(val, m->payload, m->payloadlen);
87 val[m->payloadlen] = 0;
88 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
91 if (!strcmp(m->topic, "burrow/lights/catarium/bottom"))
93 else if (!strcmp(m->topic, "burrow/lights/catarium/top"))
99 if (sscanf(val, "%lf%lf", &bright, &temp) != 2 ||
100 !(bright >= 0 && bright <= 1 && temp >= 0 && temp <= 1)) {
101 msg(L_ERROR, "Invalid value of %s: %s", m->topic, val);
105 mtx_lock(&light_mutex);
106 light_brightness[index] = bright;
107 light_temperature[index] = temp;
109 cnd_broadcast(&light_cond);
110 mtx_unlock(&light_mutex);
113 static void mqtt_init(void)
115 mosquitto_lib_init();
117 mosq = mosquitto_new("dmxd", 1, NULL);
119 die("Mosquitto: Initialization failed");
121 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
122 mosquitto_log_callback_set(mosq, mqtt_log_callback);
123 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
125 if (mosquitto_will_set(mosq, "status/dmx", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
126 die("Mosquitto: Unable to set will");
128 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)
129 die("Mosquitto: Unable to set TLS parameters");
131 if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
132 die("Mosquitto: Unable to connect");
134 if (mosquitto_loop_start(mosq))
135 die("Mosquitto: Cannot start service thread");
140 static struct libusb_context *usb_ctxt;
141 static struct libusb_device_handle *devh;
143 static void usb_error(const char *msg, ...)
147 ucw_vmsg(L_ERROR, msg, args);
156 static void open_device(void)
159 libusb_device **devlist;
160 ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
162 die("Cannot enumerate USB devices: error %d", (int) devn);
164 for (ssize_t i=0; i<devn; i++) {
165 struct libusb_device_descriptor desc;
166 libusb_device *dev = devlist[i];
167 if (!libusb_get_device_descriptor(dev, &desc)) {
168 if (desc.idVendor == DMX_USB_VENDOR && desc.idProduct == DMX_USB_PRODUCT) {
169 msg(L_INFO, "Found DMX device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
171 if (err = libusb_open(dev, &devh)) {
172 usb_error("Cannot open device: error %d", err);
175 libusb_reset_device(devh);
176 if (err = libusb_claim_interface(devh, 0)) {
177 usb_error("Cannot claim interface: error %d", err);
187 libusb_free_device_list(devlist, 1);
190 static void init_usb(void)
193 if (err = libusb_init(&usb_ctxt))
194 die("Cannot initialize libusb: error %d", err);
195 // libusb_set_debug(usb_ctxt, 3);
201 static byte dmx_packet[5];
203 static int dmx_build_packet(void)
205 byte *pkt = dmx_packet;
208 for (int i=0; i < 2; i++) {
210 double x = (exp(r*light_brightness[i]) - 1) / (exp(r) - 1);
211 double t = light_temperature[i];
212 double w = 2*x*(1-t);
214 pkt[2*i + 1] = CLAMP((int)(255*w), 0, 255);
215 pkt[2*i + 2] = CLAMP((int)(255*c), 0, 255);
218 return sizeof(dmx_packet);
224 static int use_daemon;
225 static int use_debug;
227 static struct opt_section options = {
229 OPT_HELP("A daemon for controlling lights via DMX512"),
231 OPT_HELP("Options:"),
232 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
233 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
240 int main(int argc UNUSED, char **argv)
243 opt_parse(&options, argv+1);
246 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
247 log_set_default_stream(ls);
250 log_default_stream()->levels &= ~(1U << L_DEBUG);
252 mtx_init(&light_mutex, mtx_plain);
253 cnd_init(&light_cond);
258 bool need_resend = true;
261 msg(L_INFO, "Waiting for device to appear...");
269 mtx_lock(&light_mutex);
270 while (!need_resend && !light_refresh)
271 cnd_wait(&light_cond, &light_mutex);
273 int len = dmx_build_packet();
276 mtx_unlock(&light_mutex);
278 msg(L_DEBUG, "Sending DMX packet");
280 int err, transferred;
281 if (err = libusb_bulk_transfer(devh, 0x01, dmx_packet, len, &transferred, 1000))
282 usb_error("USB transfer failed: error %d", err);
283 else if (transferred != len)
284 usb_error("USB short transfer: %d out of %d bytes", transferred, len);