From ca7c362a628eeae5280ff1e08d35fb881fc7424c Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sat, 26 Feb 2022 23:42:34 +0100 Subject: [PATCH] burrow-bifrost: DBUS to Rainbow gateway --- rainbow/desktop/Makefile | 19 +++ rainbow/desktop/burrow-bifrost.c | 238 +++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 rainbow/desktop/Makefile create mode 100644 rainbow/desktop/burrow-bifrost.c diff --git a/rainbow/desktop/Makefile b/rainbow/desktop/Makefile new file mode 100644 index 0000000..ad3f858 --- /dev/null +++ b/rainbow/desktop/Makefile @@ -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 index 0000000..6a2605b --- /dev/null +++ b/rainbow/desktop/burrow-bifrost.c @@ -0,0 +1,238 @@ +/* + * Daemon for Desktop Notifications over Burrow Rainbow (with Old Norse accent) + * + * (c) 2022 Martin Mares + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +/*** 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)); + } +} -- 2.39.2