]> mj.ucw.cz Git - home-hw.git/commitdiff
burrow-bifrost: DBUS to Rainbow gateway
authorMartin Mares <mj@ucw.cz>
Sat, 26 Feb 2022 22:42:34 +0000 (23:42 +0100)
committerMartin Mares <mj@ucw.cz>
Sat, 26 Feb 2022 22:42:34 +0000 (23:42 +0100)
rainbow/desktop/Makefile [new file with mode: 0644]
rainbow/desktop/burrow-bifrost.c [new file with mode: 0644]

diff --git a/rainbow/desktop/Makefile b/rainbow/desktop/Makefile
new file mode 100644 (file)
index 0000000..ad3f858
--- /dev/null
@@ -0,0 +1,19 @@
+PC=pkg-config
+UCW_CFLAGS := $(shell $(PC) --cflags libucw)
+UCW_LIBS := $(shell $(PC) --libs libucw)
+MOSQUITTO_CFLAGS := $(shell $(PC) --cflags libmosquitto)
+MOSQUITTO_LIBS := $(shell $(PC) --libs libmosquitto)
+SYSTEMD_CFLAGS := $(shell $(PC) --cflags libsystemd)
+SYSTEMD_LIBS := $(shell $(PC) --libs libsystemd)
+
+CFLAGS=-O2 -Wall -Wextra -Wno-sign-compare -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes $(UCW_CFLAGS) $(SYSTEMD_CFLAGS) $(MOSQUITTO_CFLAGS)
+LDLIBS=$(UCW_LIBS) $(SYSTEMD_LIBS) $(MOSQUITTO_LIBS) -lpthread -lm
+
+all: burrow-bifrost
+
+burrow-bifrost: burrow-bifrost.o
+
+clean:
+       rm -f *.o burrow-bifrost
+
+.PHONY: all clean
diff --git a/rainbow/desktop/burrow-bifrost.c b/rainbow/desktop/burrow-bifrost.c
new file mode 100644 (file)
index 0000000..6a2605b
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ *     Daemon for Desktop Notifications over Burrow Rainbow (with Old Norse accent)
+ *
+ *     (c) 2022 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/log.h>
+#include <ucw/opt.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <mosquitto.h>
+#include <systemd/sd-bus.h>
+
+/*** 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/bifrost", "ok");
+       } 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_init(void)
+{
+       mosquitto_lib_init();
+
+       mosq = mosquitto_new("bifrost", 1, NULL);
+       if (!mosq)
+               die("Mosquitto: Initialization failed");
+
+       mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
+       mosquitto_log_callback_set(mosq, mqtt_log_callback);
+
+       if (mosquitto_will_set(mosq, "status/bifrost", 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");
+}
+
+/*** DBUS ***/
+
+struct led {
+       int index;
+       double r, g, b;
+};
+
+const struct led leds[] = {
+       // Generic programs
+       { 0, 1, 0, 0 },
+       // Chrome
+       { 1, 0, 1, 0 },
+       // Telegram
+       { 2, 0, 0, 1 },
+};
+
+#define NUM_LEDS ARRAY_SIZE(leds)
+
+static uint current_led_mask;
+
+#if 0
+static int method_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
+{
+       int err;
+       uint mask;
+
+       err = sd_bus_message_read(m, "u", &mask);
+       if (err < 0) {
+               fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-err));
+               return err;
+       }
+
+       msg(L_INFO, "Urgent windows: %08x", mask);
+
+       return sd_bus_reply_method_return(m, "");
+}
+#endif
+
+static int signal_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
+{
+       int err;
+       uint mask;
+
+       err = sd_bus_message_read(m, "u", &mask);
+       if (err < 0) {
+               msg(L_ERROR, "Failed to parse signal parameters: %s\n", strerror(-err));
+               return err;
+       }
+
+       msg(L_DEBUG, "Urgent windows: %08x", mask);
+
+       for (uint i=0; i < NUM_LEDS; i++) {
+               if ((current_led_mask ^ mask) & (1U << i)) {
+                       const struct led *led = &leds[i];
+                       char topic[100];
+                       snprintf(topic, sizeof(topic), "burrow/lights/rainbow/%d", led->index);
+                       if (mask & (1 << i)) {
+                               mqtt_publish(topic, "%.3f %.3f %.3f", led->r, led->g, led->b);
+                               current_led_mask |= 1U << i;
+                       } else {
+                               mqtt_publish(topic, "0 0 0");
+                               current_led_mask &= ~(1U << i);
+                       }
+               }
+       }
+
+       current_led_mask = mask;
+       return 0;
+}
+
+static sd_bus *dbus_bus;
+static sd_bus_slot *dbus_slot;
+
+static const sd_bus_vtable bifrost_vtable[] = {
+       SD_BUS_VTABLE_START(0),
+#if 0
+       SD_BUS_METHOD_WITH_ARGS(
+               "UrgentWindows",
+               SD_BUS_ARGS("u", mask),
+               SD_BUS_NO_RESULT,
+               method_urgent_windows,
+               SD_BUS_VTABLE_UNPRIVILEGED
+       ),
+#endif
+       SD_BUS_SIGNAL_WITH_ARGS(
+               "UrgentWindows",
+               SD_BUS_ARGS("u", mask),
+               0
+       ),
+       SD_BUS_VTABLE_END
+};
+
+static void dbus_init(void)
+{
+       int err;
+
+       err = sd_bus_open_user(&dbus_bus);
+       if (err < 0)
+               die("Cannot connect to system bus: %s", strerror(-err));
+
+       err = sd_bus_add_object_vtable(dbus_bus, &dbus_slot, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", bifrost_vtable, NULL);
+       if (err < 0)
+               die("Cannot register DBUS object: %s", strerror(-err));
+
+       err = sd_bus_request_name(dbus_bus, "cz.ucw.Bifrost", 0);
+       if (err < 0)
+               die("Cannot acquire DBUS name: %s", strerror(-err));
+
+       err = sd_bus_match_signal(dbus_bus, NULL, NULL, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", "UrgentWindows", signal_urgent_windows, NULL);
+       if (err < 0)
+               die("Cannot install DBUS signal match: %s", strerror(-err));
+}
+
+/*** Main ***/
+
+static int use_daemon;
+static int use_debug;
+
+static struct opt_section options = {
+       OPT_ITEMS {
+               OPT_HELP("A daemon bringing desktop notifications to the Rainbow"),
+               OPT_HELP(""),
+               OPT_HELP("Options:"),
+               OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
+               OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
+               OPT_HELP_OPTION,
+               OPT_CONF_OPTIONS,
+               OPT_END
+       }
+};
+
+int main(int argc UNUSED, char **argv)
+{
+       log_init(argv[0]);
+       opt_parse(&options, argv+1);
+
+       if (use_daemon) {
+               struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
+               log_set_default_stream(ls);
+       }
+       if (!use_debug)
+               log_default_stream()->levels &= ~(1U << L_DEBUG);
+
+       mqtt_init();
+       dbus_init();
+
+       for (;;) {
+               int err = sd_bus_process(dbus_bus, NULL);
+               if (err < 0)
+                       die("DBUS processing failed: %s", strerror(-err));
+               if (err > 0)
+                       continue;
+
+               err = sd_bus_wait(dbus_bus, (uint64_t) -1);
+               if (err < 0)
+                       die("DBUS wait failed: %s", strerror(-err));
+       }
+}