From 8ba57955c9776ff9454547e1db9788c233c359e1 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 20 Feb 2022 01:07:22 +0100 Subject: [PATCH] Lights are now controlled via MQTT --- Makefile | 7 ++-- mqtt.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ursaryd.c | 98 ++++++++++++++++++++++++++++--------------- ursaryd.h | 10 ++++- 4 files changed, 198 insertions(+), 38 deletions(-) create mode 100644 mqtt.c diff --git a/Makefile b/Makefile index f4b376a..d7b66d1 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,12 @@ LIBPULSE_LIBS := $(shell $(PC) --libs libpulse) LIBUCW_CFLAGS := $(shell $(PC) --cflags libucw) LIBUCW_LIBS := $(shell $(PC) --libs libucw) -CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2 -LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -lm +CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wno-sign-compare -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2 +LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -lmosquitto all: ursaryd -ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o dmx.o +ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o dmx.o mqtt.o ursaryd.o: ursaryd.c ursaryd.h usb.h mpd.o: mpd.c ursaryd.h @@ -24,6 +24,7 @@ pulse.o: pulse.c ursaryd.h pulse-ucw.o: pulse-ucw.c ursaryd.h usb.o: usb.c ursaryd.h usb.h dmx.o: dmx.c ursaryd.h usb.h dmx-interface.h +mqtt.o: mqtt.c ursaryd.h clean: rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` diff --git a/mqtt.c b/mqtt.c new file mode 100644 index 0000000..a84ea2b --- /dev/null +++ b/mqtt.c @@ -0,0 +1,121 @@ +/* + * Interaction with MQTT + * + * (c) 2022 Martin Mares + */ + +#undef LOCAL_DEBUG + +#include +#include +#include + +#include +#include + +#include + +#include "ursaryd.h" + +static struct mosquitto *mosq; + +int mqtt_pipes[2]; +static struct main_file mqtt_rx_pipe; + +struct mqtt_pipe_msg { + char topic[100]; + char val[100]; +}; + +void mqtt_publish(const char *topic, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + char m[256]; + int l = vsnprintf(m, sizeof(m), fmt, args); + DBG("MQTT > %s %s", topic, m); + if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS) + msg(L_ERROR, "Mosquitto: publish failed"); + 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, subscribing"); + if (mosquitto_subscribe(mosq, NULL, "burrow/lights/catarium/#", 1) != MOSQ_ERR_SUCCESS) + die("Mosquitto: subscribe failed"); + + mqtt_publish("status/ursary", "ok"); + } +} + +static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level UNUSED, const char *message UNUSED) +{ + // msg(L_INFO, "MQTT(%d): %s", level, message); +} + +static void mqtt_msg_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, const struct mosquitto_message *m) +{ + struct mqtt_pipe_msg pm; + + if (snprintf(pm.topic, sizeof(pm.topic), m->topic) >= sizeof(pm.topic)) + { + msg(L_ERROR, "MQTT: Topic %s too long", m->topic); + return; + } + + if (m->payloadlen >= sizeof(pm.val) - 1) + { + msg(L_ERROR, "MQTT: Invalid value for topic %s", m->topic); + return; + } + memcpy(pm.val, m->payload, m->payloadlen); + pm.val[m->payloadlen] = 0; + + careful_write(mqtt_pipes[1], &pm, sizeof(pm)); +} + +static int mqtt_pipe_read_callback(struct main_file *fi) +{ + struct mqtt_pipe_msg pm; + if (careful_read(fi->fd, &pm, sizeof(pm)) <= 0) + die("MQTT pipe read error: %m"); + + DBG("MQTT < %s %s", pm.topic, pm.val); + notify_mqtt(pm.topic, pm.val); + + return HOOK_IDLE; +} + +void mqtt_init(void) +{ + mosquitto_lib_init(); + mosq = mosquitto_new("ursary", 1, NULL); + if (!mosq) + die("Mosquitto: initialization failed"); + + if (pipe(mqtt_pipes) < 0) + die("Cannot create pipes: %m"); + + mqtt_rx_pipe.fd = mqtt_pipes[0]; + mqtt_rx_pipe.read_handler = mqtt_pipe_read_callback; + file_add(&mqtt_rx_pipe); + + 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/ursary", 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: connect failed"); + + if (mosquitto_loop_start(mosq)) + die("Mosquitto: cannot start service thread"); +} diff --git a/ursaryd.c b/ursaryd.c index f9ce3b9..910482c 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -1,7 +1,7 @@ /* - * The Ursary Audio Controls + * The Ursary Control Panel * - * (c) 2014--2020 Martin Mares + * (c) 2014--2022 Martin Mares */ #undef LOCAL_DEBUG @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -489,56 +490,51 @@ static void update_mpd_from_button(int button UNUSED, int on) static bool lights_on[2]; static double lights_brightness[2]; static double lights_temperature[2]; +static timestamp_t lights_last_update[2]; static void update_lights(void) { - if (!dmx_is_ready()) + for (uint ch=0; ch < 2; ch++) { - noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f); - noct_set_button(10, 0); - noct_set_button(11, 0); - return; - } - - for (uint i=0; i<2; i++) - { - uint warm, cold; - if (lights_on[i]) + if (lights_on[ch]) { - noct_set_ring(3-i, RING_MODE_LEFT, lights_brightness[i] * 127); - noct_set_button(11-i, 1); - double r = 2; - double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1); - double t = lights_temperature[i]; - double w = 2*x*(1-t); - double c = 2*x*t; - warm = CLAMP((int)(255*w), 0, 255); - cold = CLAMP((int)(255*c), 0, 255); + noct_set_ring(3-ch, RING_MODE_LEFT, lights_brightness[ch] * 127); + noct_set_button(11-ch, 1); } else { - noct_set_ring(3-i, RING_MODE_LEFT, 0); - noct_set_button(11-i, 0); - warm = cold = 0; + noct_set_ring(3-ch, RING_MODE_LEFT, 0); + noct_set_button(11-ch, 0); } - DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold); - dmx_set_pwm(2*i, warm); - dmx_set_pwm(2*i+1, cold); } } +static void send_lights(int ch) +{ + DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f", ch, lights_on[ch], lights_brightness[ch], lights_temperature[ch]); + double b = lights_on[ch] ? lights_brightness[ch] : 0; + double t = lights_on[ch] ? lights_temperature[ch] : 0; + char topic[100], val[100]; + snprintf(topic, sizeof(topic), "burrow/lights/catarium/%s", (ch ? "top" : "bottom")); + snprintf(val, sizeof(val), "%.3f %.3f", b, t); + mqtt_publish(topic, val); + lights_last_update[ch] = main_get_now(); + update_lights(); +} + static void update_lights_from_rotary(int ch, int delta) { if (lights_on[ch]) lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.); - update_lights(); + send_lights(ch); } static void update_lights_from_slider(int value) { lights_temperature[0] = value / 127.; lights_temperature[1] = value / 127.; - update_lights(); + send_lights(0); + send_lights(1); } static void lights_button_timeout(struct main_timer *t) @@ -548,7 +544,7 @@ static void lights_button_timeout(struct main_timer *t) timer_del(t); lights_on[ch] = 1; lights_brightness[ch] = 1; - update_lights(); + send_lights(ch); } static void update_lights_from_button(int ch, int on) @@ -567,7 +563,7 @@ static void update_lights_from_button(int ch, int on) { timer_del(&lights_button_timer[ch]); lights_on[ch] = !lights_on[ch]; - update_lights(); + send_lights(ch); } } @@ -784,6 +780,41 @@ void notify_touch(int rotary UNUSED, int on UNUSED) schedule_update(); } +void notify_mqtt(const char *topic, const char *val) +{ + const char blc[] = "burrow/lights/catarium/"; + if (str_has_prefix(topic, blc)) + { + topic += strlen(blc); + int ch; + if (!strcmp(topic, "top")) + ch = 1; + else if (!strcmp(topic, "bottom")) + ch = 0; + else + return; + + double b, t; + if (sscanf(val, "%lf %lf", &b, &t) != 2) + return; + + timestamp_t now = main_get_now(); + if (!lights_last_update[ch] || lights_last_update[ch] + 1000 < now) + { + DBG("Received foreign light settings"); + if (!b) + lights_on[ch] = 0; + else + { + lights_on[ch] = 1; + lights_brightness[ch] = b; + lights_temperature[ch] = t; + } + update_lights(); + } + } +} + /*** Main entry point ***/ static int debug; @@ -813,9 +844,10 @@ static void daemon_body(struct daemon_params *dp) usb_init(); noct_init(); - dmx_init(); + // dmx_init(); pulse_init(); mpd_init(); + mqtt_init(); static struct main_signal term_sig = { .signum = SIGTERM, diff --git a/ursaryd.h b/ursaryd.h index d60a426..6250ff8 100644 --- a/ursaryd.h +++ b/ursaryd.h @@ -1,7 +1,7 @@ /* - * The Ursary Audio Controls + * The Ursary Control Panel * - * (c) 2014 Martin Mares + * (c) 2014--2022 Martin Mares */ #include @@ -15,6 +15,7 @@ void schedule_update(void); void notify_rotary(int rotary, int delta); void notify_touch(int rotary, int on); void notify_button(int button, int on); +void notify_mqtt(const char *topic, const char *val); /* nocturn.c */ @@ -134,3 +135,8 @@ void mpd_stop(void); void mpd_pause(int arg); void mpd_next(void); void mpd_prev(void); + +/* mqtt.c */ + +void mqtt_init(void); +void mqtt_publish(const char *topic, const char *fmt, ...); -- 2.39.2