2 * Daemon for Neopixel Rainbow Indicators over USB
4 * (c) 2022 Martin Mares <mj@ucw.cz>
10 #include <ucw/string.h>
11 #include <ucw/strtonum.h>
12 #include <ucw/unaligned.h>
26 #include <mosquitto.h>
28 typedef unsigned char byte;
30 typedef unsigned int uint;
32 #include "../firmware/interface.h"
34 static mtx_t led_mutex;
35 static cnd_t led_cond;
36 static bool led_refresh;
42 static struct led leds[NPIX_NUM_LEDS];
43 static double led_brightness = 1;
47 static struct mosquitto *mosq;
48 static bool mqtt_connected;
50 static void mqtt_publish(const char *topic, const char *fmt, ...)
57 int l = vsnprintf(m, sizeof(m), fmt, args);
58 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
59 if (err != MOSQ_ERR_SUCCESS)
60 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
66 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
69 msg(L_DEBUG, "MQTT: Connection established");
70 mqtt_connected = true;
71 mqtt_publish("status/rainbow", "ok");
72 if (mosquitto_subscribe(mosq, NULL, "burrow/lights/rainbow/#", 1) != MOSQ_ERR_SUCCESS)
73 die("Mosquitto: subscribe failed");
74 } else if (mqtt_connected) {
75 msg(L_DEBUG, "MQTT: Connection lost");
76 mqtt_connected = false;
80 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
82 msg(L_DEBUG, "MQTT(%d): %s", level, message);
85 static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m)
88 if (m->payloadlen >= sizeof(val) - 1) {
89 msg(L_ERROR, "Invalid value for topic %s", m->topic);
92 memcpy(val, m->payload, m->payloadlen);
93 val[m->payloadlen] = 0;
94 msg(L_DEBUG, "MQTT < %s %s", m->topic, val);
96 static const char px[] = "burrow/lights/rainbow/";
97 if (!str_has_prefix(m->topic, px))
100 const char *top = m->topic + strlen(px);
101 if (!strcmp(top, "brightness")) {
102 double b = atof(val);
103 if (!(b >= 0 && b <= 1)) {
104 msg(L_ERROR, "Invalid value of %s: %s", m->topic, val);
107 mtx_lock(&led_mutex);
110 cnd_broadcast(&led_cond);
111 mtx_unlock(&led_mutex);
116 if (str_to_uint(&index, m->topic + strlen(px), NULL, 10 | STN_WHOLE) || index >= NPIX_NUM_LEDS) {
117 msg(L_ERROR, "Unsupported topic: %s", m->topic);
122 if (sscanf(val, "%lf%lf%lf", &r, &g, &b) != 3 || !(r >= 0 && r <= 1) || !(g >= 0 && g <= 1) || !(b >= 0 && b <= 1)) {
123 msg(L_ERROR, "Invalid value of %s: %s", m->topic, val);
127 mtx_lock(&led_mutex);
132 cnd_broadcast(&led_cond);
133 mtx_unlock(&led_mutex);
136 static void mqtt_init(void)
138 mosquitto_lib_init();
140 mosq = mosquitto_new("rainbowd", 1, NULL);
142 die("Mosquitto: Initialization failed");
144 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
145 mosquitto_log_callback_set(mosq, mqtt_log_callback);
146 mosquitto_message_callback_set(mosq, mqtt_msg_callback);
148 if (mosquitto_will_set(mosq, "status/rainbow", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
149 die("Mosquitto: Unable to set will");
151 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)
152 die("Mosquitto: Unable to set TLS parameters");
154 if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
155 die("Mosquitto: Unable to connect");
157 if (mosquitto_loop_start(mosq))
158 die("Mosquitto: Cannot start service thread");
163 static struct libusb_context *usb_ctxt;
164 static struct libusb_device_handle *devh;
166 static void usb_error(const char *msg, ...)
170 ucw_vmsg(L_ERROR, msg, args);
179 static void open_device(void)
182 libusb_device **devlist;
183 ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
185 die("Cannot enumerate USB devices: error %d", (int) devn);
187 for (ssize_t i=0; i<devn; i++) {
188 struct libusb_device_descriptor desc;
189 libusb_device *dev = devlist[i];
190 if (!libusb_get_device_descriptor(dev, &desc)) {
191 if (desc.idVendor == NPIX_USB_VENDOR && desc.idProduct == NPIX_USB_PRODUCT) {
192 msg(L_INFO, "Found NPIX device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
194 if (err = libusb_open(dev, &devh)) {
195 usb_error("Cannot open device: error %d", err);
198 libusb_reset_device(devh);
199 if (err = libusb_claim_interface(devh, 0)) {
200 usb_error("Cannot claim interface: error %d", err);
210 libusb_free_device_list(devlist, 1);
213 static void init_usb(void)
216 if (err = libusb_init(&usb_ctxt))
217 die("Cannot initialize libusb: error %d", err);
218 // libusb_set_debug(usb_ctxt, 3);
224 static byte npix_packet[3*NPIX_NUM_LEDS];
226 static int npix_build_packet(void)
228 byte *pkt = npix_packet;
230 for (int i=0; i < NPIX_NUM_LEDS; i++) {
231 *pkt++ = (int)(leds[i].r * led_brightness * 255);
232 *pkt++ = (int)(leds[i].g * led_brightness * 255);
233 *pkt++ = (int)(leds[i].b * led_brightness * 255);
236 return pkt - npix_packet;
241 static int use_daemon;
242 static int use_debug;
244 static struct opt_section options = {
246 OPT_HELP("A daemon for controlling lights via NPIX512"),
248 OPT_HELP("Options:"),
249 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
250 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
257 int main(int argc UNUSED, char **argv)
260 opt_parse(&options, argv+1);
263 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
264 log_set_default_stream(ls);
267 log_default_stream()->levels &= ~(1U << L_DEBUG);
269 mtx_init(&led_mutex, mtx_plain);
275 bool need_resend = true;
278 msg(L_INFO, "Waiting for device to appear...");
286 mtx_lock(&led_mutex);
287 while (!need_resend && !led_refresh)
288 cnd_wait(&led_cond, &led_mutex);
290 int len = npix_build_packet();
293 mtx_unlock(&led_mutex);
295 msg(L_DEBUG, "Sending NPIX packet");
297 int err, transferred;
298 if (err = libusb_bulk_transfer(devh, 0x01, npix_packet, len, &transferred, 1000))
299 usb_error("USB transfer failed: error %d", err);
300 else if (transferred != len)
301 usb_error("USB short transfer: %d out of %d bytes", transferred, len);