From 54e3ae9191d8a66f2edecf6ea8f172dc2fbe0f63 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 20 Feb 2022 16:22:04 +0100 Subject: [PATCH] Indicators: Daemon --- indicators/daemon/Makefile | 24 +++ indicators/daemon/burrow-rainbowd.c | 287 ++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 indicators/daemon/Makefile create mode 100644 indicators/daemon/burrow-rainbowd.c diff --git a/indicators/daemon/Makefile b/indicators/daemon/Makefile new file mode 100644 index 0000000..779acf2 --- /dev/null +++ b/indicators/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 -lm + +all: burrow-rainbowd + +burrow-rainbowd: burrow-rainbowd.o + +burrow-rainbowd.o: burrow-rainbowd.c ../firmware/interface.h + +install: burrow-rainbowd + install burrow-rainbowd /usr/local/sbin/ + +clean: + rm -f *.o burrow-rainbowd + +.PHONY: all install clean diff --git a/indicators/daemon/burrow-rainbowd.c b/indicators/daemon/burrow-rainbowd.c new file mode 100644 index 0000000..4da1b8a --- /dev/null +++ b/indicators/daemon/burrow-rainbowd.c @@ -0,0 +1,287 @@ +/* + * Daemon for Neopixel Indicators over USB + * + * (c) 2022 Martin Mares + */ + +#include +#include +#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 led_mutex; +static cnd_t led_cond; +static bool led_refresh; + +struct led { + byte r, g, b; +}; + +static struct led leds[NPIX_NUM_LEDS]; + +/*** 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/rainbow", "ok"); + if (mosquitto_subscribe(mosq, NULL, "burrow/lights/rainbow/#", 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); + + static const char px[] = "burrow/lights/rainbow/"; + if (!str_has_prefix(m->topic, px)) + return; + + uint index; + if (str_to_uint(&index, m->topic + strlen(px), NULL, STN_DEC | STN_WHOLE) || index >= NPIX_NUM_LEDS) { + msg(L_ERROR, "Unsupported topic: %s", m->topic); + return; + } + + uint r, g, b; + if (sscanf(val, "%u%u%u", &r, &g, &b) != 3 || r >= 256 || g >= 256 || b >= 256) { + msg(L_ERROR, "Invalid value of %s: %s", m->topic, val); + return; + } + + mtx_lock(&led_mutex); + leds[index].r = r; + leds[index].g = g; + leds[index].b = b; + led_refresh = 1; + cnd_broadcast(&led_cond); + mtx_unlock(&led_mutex); +} + +static void mqtt_init(void) +{ + mosquitto_lib_init(); + + mosq = mosquitto_new("rainbowd", 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/rainbow", 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(&led_mutex, mtx_plain); + cnd_init(&led_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(&led_mutex); + while (!need_resend && !led_refresh) + cnd_wait(&led_cond, &led_mutex); + + int len = npix_build_packet(); + led_refresh = 0; + need_resend = 0; + mtx_unlock(&led_mutex); + + msg(L_DEBUG, "Sending NPIX packet"); + + int err, transferred; + if (err = libusb_bulk_transfer(devh, 0x01, npix_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); + } +} -- 2.39.2