2 * Daemon for Desktop Notifications over Burrow Rainbow (with Old Norse accent)
4 * (c) 2022 Martin Mares <mj@ucw.cz>
16 #include <mosquitto.h>
17 #include <systemd/sd-bus.h>
21 static struct mosquitto *mosq;
22 static bool mqtt_connected;
24 static void mqtt_publish(const char *topic, const char *fmt, ...)
31 int l = vsnprintf(m, sizeof(m), fmt, args);
32 int err = mosquitto_publish(mosq, NULL, topic, l, m, 0, true);
33 if (err != MOSQ_ERR_SUCCESS)
34 msg(L_ERROR, "Mosquitto: Publish failed, error=%d", err);
40 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
43 msg(L_DEBUG, "MQTT: Connection established");
44 mqtt_connected = true;
45 mqtt_publish("status/bifrost", "ok");
46 } else if (mqtt_connected) {
47 msg(L_DEBUG, "MQTT: Connection lost");
48 mqtt_connected = false;
52 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
54 msg(L_DEBUG, "MQTT(%d): %s", level, message);
57 static void mqtt_init(void)
61 mosq = mosquitto_new("bifrost", 1, NULL);
63 die("Mosquitto: Initialization failed");
65 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
66 mosquitto_log_callback_set(mosq, mqtt_log_callback);
68 if (mosquitto_will_set(mosq, "status/bifrost", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
69 die("Mosquitto: Unable to set will");
71 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)
72 die("Mosquitto: Unable to set TLS parameters");
74 if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
75 die("Mosquitto: Unable to connect");
77 if (mosquitto_loop_start(mosq))
78 die("Mosquitto: Cannot start service thread");
88 const struct led leds[] = {
97 #define NUM_LEDS ARRAY_SIZE(leds)
99 static uint current_led_mask;
102 static int method_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
107 err = sd_bus_message_read(m, "u", &mask);
109 fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-err));
113 msg(L_INFO, "Urgent windows: %08x", mask);
115 return sd_bus_reply_method_return(m, "");
119 static int signal_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
124 err = sd_bus_message_read(m, "u", &mask);
126 msg(L_ERROR, "Failed to parse signal parameters: %s\n", strerror(-err));
130 msg(L_DEBUG, "Urgent windows: %08x", mask);
132 for (uint i=0; i < NUM_LEDS; i++) {
133 if ((current_led_mask ^ mask) & (1U << i)) {
134 const struct led *led = &leds[i];
136 snprintf(topic, sizeof(topic), "burrow/lights/rainbow/%d", led->index);
137 if (mask & (1 << i)) {
138 mqtt_publish(topic, "%.3f %.3f %.3f", led->r, led->g, led->b);
139 current_led_mask |= 1U << i;
141 mqtt_publish(topic, "0 0 0");
142 current_led_mask &= ~(1U << i);
147 current_led_mask = mask;
151 static sd_bus *dbus_bus;
152 static sd_bus_slot *dbus_slot;
154 static const sd_bus_vtable bifrost_vtable[] = {
155 SD_BUS_VTABLE_START(0),
157 SD_BUS_METHOD_WITH_ARGS(
159 SD_BUS_ARGS("u", mask),
161 method_urgent_windows,
162 SD_BUS_VTABLE_UNPRIVILEGED
165 SD_BUS_SIGNAL_WITH_ARGS(
167 SD_BUS_ARGS("u", mask),
173 static void dbus_init(void)
177 err = sd_bus_open_user(&dbus_bus);
179 die("Cannot connect to system bus: %s", strerror(-err));
181 err = sd_bus_add_object_vtable(dbus_bus, &dbus_slot, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", bifrost_vtable, NULL);
183 die("Cannot register DBUS object: %s", strerror(-err));
185 err = sd_bus_request_name(dbus_bus, "cz.ucw.Bifrost", 0);
187 die("Cannot acquire DBUS name: %s", strerror(-err));
189 err = sd_bus_match_signal(dbus_bus, NULL, NULL, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", "UrgentWindows", signal_urgent_windows, NULL);
191 die("Cannot install DBUS signal match: %s", strerror(-err));
196 static int use_daemon;
197 static int use_debug;
199 static struct opt_section options = {
201 OPT_HELP("A daemon bringing desktop notifications to the Rainbow"),
203 OPT_HELP("Options:"),
204 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
205 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
212 int main(int argc UNUSED, char **argv)
215 opt_parse(&options, argv+1);
218 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
219 log_set_default_stream(ls);
222 log_default_stream()->levels &= ~(1U << L_DEBUG);
228 int err = sd_bus_process(dbus_bus, NULL);
230 die("DBUS processing failed: %s", strerror(-err));
234 err = sd_bus_wait(dbus_bus, (uint64_t) -1);
236 die("DBUS wait failed: %s", strerror(-err));