2 * Daemon for Neopixel Rainbow Indicators over USB
4 * (c) 2022 Martin Mares <mj@ucw.cz>
8 #include <ucw/clists.h>
11 #include <ucw/string.h>
12 #include <ucw/strtonum.h>
13 #include <ucw/unaligned.h>
27 #include <mosquitto.h>
29 typedef unsigned char byte;
31 typedef unsigned int uint;
33 #include "../firmware/interface.h"
40 int status; // 0=unknown, 1=running, -1=dead
44 static struct client *null_client;
46 static mtx_t led_mutex;
47 static cnd_t led_cond;
48 static bool led_refresh;
52 struct client *sender;
55 static struct led leds[NPIX_NUM_LEDS];
56 static double led_brightness = 1;
58 static struct client *find_client(const char *name)
60 CLIST_FOR_EACH(struct client *, c, clients)
61 if (!strcmp(c->name, name))
64 struct client *c = xmalloc_zero(sizeof(*c));
65 clist_add_tail(&clients, &c->n);
66 c->name = xstrdup(name);
70 static void led_init(void) {
72 null_client = find_client("");
74 for (uint i=0; i < NPIX_NUM_LEDS; i++)
75 leds[i].sender = null_client;
77 mtx_init(&led_mutex, mtx_plain);
81 static void led_begin_update(void)
86 static void led_end_update(void)
89 cnd_broadcast(&led_cond);
90 mtx_unlock(&led_mutex);
95 static struct mosquitto *mosq;
96 static bool mqtt_connected;
98 static void mqtt_publish(const char *topic, const char *fmt, ...)
103 if (mqtt_connected) {
105 int l = vsnprintf(m, sizeof(m), fmt, args);
106 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
107 if (err != MOSQ_ERR_SUCCESS)
108 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
114 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
117 msg(L_DEBUG, "MQTT: Connection established");
118 mqtt_connected = true;
119 if (mosquitto_subscribe(mosq, NULL, "burrow/lights/rainbow/#", 1) != MOSQ_ERR_SUCCESS)
120 die("Mosquitto: subscribe failed");
121 if (mosquitto_subscribe(mosq, NULL, "status/#", 1) != MOSQ_ERR_SUCCESS)
122 die("Mosquitto: subscribe failed");
123 mqtt_publish("status/rainbow", "ok");
124 } else if (mqtt_connected) {
125 msg(L_DEBUG, "MQTT: Connection lost");
126 mqtt_connected = false;
130 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
132 msg(L_DEBUG, "MQTT(%d): %s", level, message);
135 static void msg_status(const char *topic, const char *key, const char *val)
138 msg(L_ERROR, "Unknown status topic %s", topic);
145 else if (!strcmp(val, "ok"))
151 struct client *c = find_client(key);
154 for (uint i=0; i < NPIX_NUM_LEDS; i++) {
155 struct led *led = &leds[i];
156 if (led->sender == c) {
157 led->r = led->g = led->b = 0;
158 led->sender = null_client;
165 static void msg_brightness(const char *topic, const char *val)
167 double b = atof(val);
168 if (!(b >= 0 && b <= 1)) {
169 msg(L_ERROR, "Invalid value of %s: %s", topic, val);
178 static void msg_rainbow(const char *topic, const char *key, const char *val)
180 if (!strcmp(key, "brightness"))
181 return msg_brightness(topic, val);
184 if (str_to_uint(&index, key, NULL, 10 | STN_WHOLE) || index >= NPIX_NUM_LEDS) {
185 msg(L_ERROR, "Unknown topic: %s", topic);
190 char sender[strlen(val) + 1];
195 } else if (sscanf(val, "%lf%lf%lf %s", &r, &g, &b, sender) < 3 || !(r >= 0 && r <= 1) || !(g >= 0 && g <= 1) || !(b >= 0 && b <= 1)) {
196 msg(L_ERROR, "Invalid value of %s: %s", topic, val);
201 struct led *led = &leds[index];
202 struct client *client = find_client(sender);
203 if (client->status < 0) {
204 msg(L_ERROR, "LED update from a dead client %s", client->name);
209 led->sender = client;
214 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
217 if (m->payloadlen >= sizeof(val) - 1) {
218 msg(L_ERROR, "Invalid value for topic %s", m->topic);
221 memcpy(val, m->payload, m->payloadlen);
222 val[m->payloadlen] = 0;
223 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
225 static const char px_status[] = "status/";
226 static const char px_rainbow[] = "burrow/lights/rainbow/";
227 if (str_has_prefix(m->topic, px_status))
228 msg_status(m->topic, m->topic + strlen(px_status), val);
229 else if (str_has_prefix(m->topic, px_rainbow))
230 msg_rainbow(m->topic, m->topic + strlen(px_rainbow), val);
233 static void mqtt_init(void)
235 mosquitto_lib_init();
237 mosq = mosquitto_new("rainbowd", 1, NULL);
239 die("Mosquitto: Initialization failed");
241 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
242 mosquitto_log_callback_set(mosq, mqtt_log_callback);
243 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
245 if (mosquitto_will_set(mosq, "status/rainbow", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
246 die("Mosquitto: Unable to set will");
248 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)
249 die("Mosquitto: Unable to set TLS parameters");
251 if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
252 die("Mosquitto: Unable to connect");
254 if (mosquitto_loop_start(mosq))
255 die("Mosquitto: Cannot start service thread");
260 static struct libusb_context *usb_ctxt;
261 static struct libusb_device_handle *devh;
263 static void usb_error(const char *msg, ...)
267 ucw_vmsg(L_ERROR, msg, args);
276 static void open_device(void)
279 libusb_device **devlist;
280 ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
282 die("Cannot enumerate USB devices: error %d", (int) devn);
284 for (ssize_t i=0; i<devn; i++) {
285 struct libusb_device_descriptor desc;
286 libusb_device *dev = devlist[i];
287 if (!libusb_get_device_descriptor(dev, &desc)) {
288 if (desc.idVendor == NPIX_USB_VENDOR && desc.idProduct == NPIX_USB_PRODUCT) {
289 msg(L_INFO, "Found NPIX device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
291 if (err = libusb_open(dev, &devh)) {
292 usb_error("Cannot open device: error %d", err);
295 libusb_reset_device(devh);
296 if (err = libusb_claim_interface(devh, 0)) {
297 usb_error("Cannot claim interface: error %d", err);
307 libusb_free_device_list(devlist, 1);
310 static void init_usb(void)
313 if (err = libusb_init(&usb_ctxt))
314 die("Cannot initialize libusb: error %d", err);
315 // libusb_set_debug(usb_ctxt, 3);
321 static byte npix_packet[3*NPIX_NUM_LEDS];
323 static int npix_build_packet(void)
325 byte *pkt = npix_packet;
327 for (int i=0; i < NPIX_NUM_LEDS; i++) {
328 struct led *led = &leds[i];
329 struct client *sender = led->sender;
330 msg(L_DEBUG, "LED #%d: r=%.3f g=%.3f b=%.3f client=%s status=%d", i, led->r, led->g, led->b, sender->name, sender->status);
331 *pkt++ = (int)(led->r * led_brightness * 255);
332 *pkt++ = (int)(led->g * led_brightness * 255);
333 *pkt++ = (int)(led->b * led_brightness * 255);
336 return pkt - npix_packet;
341 static int use_daemon;
342 static int use_debug;
344 static struct opt_section options = {
346 OPT_HELP("A daemon for controlling lights via NPIX512"),
348 OPT_HELP("Options:"),
349 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
350 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
357 int main(int argc UNUSED, char **argv)
360 opt_parse(&options, argv+1);
363 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
364 log_set_default_stream(ls);
367 log_default_stream()->levels &= ~(1U << L_DEBUG);
373 bool need_resend = true;
376 msg(L_INFO, "Waiting for device to appear...");
384 mtx_lock(&led_mutex);
385 while (!need_resend && !led_refresh)
386 cnd_wait(&led_cond, &led_mutex);
388 int len = npix_build_packet();
391 mtx_unlock(&led_mutex);
393 msg(L_DEBUG, "Sending NPIX packet");
395 int err, transferred;
396 if (err = libusb_bulk_transfer(devh, 0x01, npix_packet, len, &transferred, 1000))
397 usb_error("USB transfer failed: error %d", err);
398 else if (transferred != len)
399 usb_error("USB short transfer: %d out of %d bytes", transferred, len);