]> mj.ucw.cz Git - home-hw.git/blob - burrow-bifrost.c
62de296279942d663ca0d5a615bffba3d297d217
[home-hw.git] / burrow-bifrost.c
1 /*
2  *      Daemon for Desktop Notifications over Burrow Rainbow (with Old Norse accent)
3  *
4  *      (c) 2022 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <ucw/lib.h>
8 #include <ucw/log.h>
9 #include <ucw/opt.h>
10
11 #include <errno.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <syslog.h>
15
16 #include <mosquitto.h>
17 #include <systemd/sd-bus.h>
18
19 /*** MQTT ***/
20
21 static struct mosquitto *mosq;
22 static bool mqtt_connected;
23
24 static void mqtt_publish(const char *topic, const char *fmt, ...)
25 {
26         va_list args;
27         va_start(args, fmt);
28
29         if (mqtt_connected) {
30                 char m[256];
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);
35         }
36
37         va_end(args);
38 }
39
40 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
41 {
42         if (!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;
49         }
50 }
51
52 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
53 {
54         msg(L_DEBUG, "MQTT(%d): %s", level, message);
55 }
56
57 static void mqtt_init(void)
58 {
59         mosquitto_lib_init();
60
61         mosq = mosquitto_new("bifrost", 1, NULL);
62         if (!mosq)
63                 die("Mosquitto: Initialization failed");
64
65         mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
66         mosquitto_log_callback_set(mosq, mqtt_log_callback);
67
68         if (mosquitto_will_set(mosq, "status/bifrost", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
69                 die("Mosquitto: Unable to set will");
70
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");
73
74         if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
75                 die("Mosquitto: Unable to connect");
76
77         if (mosquitto_loop_start(mosq))
78                 die("Mosquitto: Cannot start service thread");
79 }
80
81 /*** DBUS ***/
82
83 struct led {
84         int index;
85         double r, g, b;
86 };
87
88 const struct led leds[] = {
89         // Generic programs
90         { 0, 1, 0, 0 },
91         // Chrome
92         { 1, 0, 1, 0 },
93         // Telegram
94         { 2, 0, 0, 1 },
95 };
96
97 #define NUM_LEDS ARRAY_SIZE(leds)
98
99 static uint current_led_mask;
100
101 #if 0
102 static int method_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
103 {
104         int err;
105         uint mask;
106
107         err = sd_bus_message_read(m, "u", &mask);
108         if (err < 0) {
109                 fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-err));
110                 return err;
111         }
112
113         msg(L_INFO, "Urgent windows: %08x", mask);
114
115         return sd_bus_reply_method_return(m, "");
116 }
117 #endif
118
119 static int signal_urgent_windows(sd_bus_message *m, void *userdata UNUSED, sd_bus_error *ret_error UNUSED)
120 {
121         int err;
122         uint mask;
123
124         err = sd_bus_message_read(m, "u", &mask);
125         if (err < 0) {
126                 msg(L_ERROR, "Failed to parse signal parameters: %s\n", strerror(-err));
127                 return err;
128         }
129
130         msg(L_DEBUG, "Urgent windows: %08x", mask);
131
132         for (uint i=0; i < NUM_LEDS; i++) {
133                 if ((current_led_mask ^ mask) & (1U << i)) {
134                         const struct led *led = &leds[i];
135                         char topic[100];
136                         snprintf(topic, sizeof(topic), "burrow/lights/rainbow/%d", led->index);
137                         if (mask & (1 << i)) {
138                                 mqtt_publish(topic, "%.3f %.3f %.3f bifrost", led->r, led->g, led->b);
139                                 current_led_mask |= 1U << i;
140                         } else {
141                                 mqtt_publish(topic, "");
142                                 current_led_mask &= ~(1U << i);
143                         }
144                 }
145         }
146
147         current_led_mask = mask;
148         return 0;
149 }
150
151 static sd_bus *dbus_bus;
152 static sd_bus_slot *dbus_slot;
153
154 static const sd_bus_vtable bifrost_vtable[] = {
155         SD_BUS_VTABLE_START(0),
156 #if 0
157         SD_BUS_METHOD_WITH_ARGS(
158                 "UrgentWindows",
159                 SD_BUS_ARGS("u", mask),
160                 SD_BUS_NO_RESULT,
161                 method_urgent_windows,
162                 SD_BUS_VTABLE_UNPRIVILEGED
163         ),
164 #endif
165         SD_BUS_SIGNAL_WITH_ARGS(
166                 "UrgentWindows",
167                 SD_BUS_ARGS("u", mask),
168                 0
169         ),
170         SD_BUS_VTABLE_END
171 };
172
173 static void dbus_init(void)
174 {
175         int err;
176
177         err = sd_bus_open_user(&dbus_bus);
178         if (err < 0)
179                 die("Cannot connect to system bus: %s", strerror(-err));
180
181         err = sd_bus_add_object_vtable(dbus_bus, &dbus_slot, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", bifrost_vtable, NULL);
182         if (err < 0)
183                 die("Cannot register DBUS object: %s", strerror(-err));
184
185         err = sd_bus_request_name(dbus_bus, "cz.ucw.Bifrost", 0);
186         if (err < 0)
187                 die("Cannot acquire DBUS name: %s", strerror(-err));
188
189         err = sd_bus_match_signal(dbus_bus, NULL, NULL, "/cz/ucw/Bifrost", "cz.ucw.Bifrost", "UrgentWindows", signal_urgent_windows, NULL);
190         if (err < 0)
191                 die("Cannot install DBUS signal match: %s", strerror(-err));
192 }
193
194 /*** Main ***/
195
196 static int use_daemon;
197 static int use_debug;
198
199 static struct opt_section options = {
200         OPT_ITEMS {
201                 OPT_HELP("A daemon bringing desktop notifications to the Rainbow"),
202                 OPT_HELP(""),
203                 OPT_HELP("Options:"),
204                 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
205                 OPT_BOOL(0, "daemon", use_daemon, 0, "\tDaemonize"),
206                 OPT_HELP_OPTION,
207                 OPT_CONF_OPTIONS,
208                 OPT_END
209         }
210 };
211
212 int main(int argc UNUSED, char **argv)
213 {
214         log_init(argv[0]);
215         opt_parse(&options, argv+1);
216
217         if (use_daemon) {
218                 struct log_stream *ls = log_new_syslog("daemon", LOG_PID);
219                 log_set_default_stream(ls);
220         }
221         if (!use_debug)
222                 log_default_stream()->levels &= ~(1U << L_DEBUG);
223
224         mqtt_init();
225         dbus_init();
226
227         for (;;) {
228                 int err = sd_bus_process(dbus_bus, NULL);
229                 if (err < 0)
230                         die("DBUS processing failed: %s", strerror(-err));
231                 if (err > 0)
232                         continue;
233
234                 err = sd_bus_wait(dbus_bus, (uint64_t) -1);
235                 if (err < 0)
236                         die("DBUS wait failed: %s", strerror(-err));
237         }
238 }