]> mj.ucw.cz Git - home-hw.git/blob - clock/host/burrow-clock.c
Clock: Non-experimental USB ID
[home-hw.git] / clock / host / burrow-clock.c
1 /*
2  *      MJ's Workshop Clock Daemon
3  *
4  *      (c) 2023 Martin Mares <mj@ucw.cz>
5  */
6
7 #define LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/log.h>
11 #include <ucw/opt.h>
12 #include <ucw/unaligned.h>
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stdint.h>
17 #include <syslog.h>
18 #include <unistd.h>
19 #include <time.h>
20
21 #include <libusb-1.0/libusb.h>
22 #include <mosquitto.h>
23
24 #include "usb-helper.h"
25
26 struct device {
27         struct usb_dev *usb;
28
29         struct libusb_transfer *rx_transfer, *tx_transfer;
30         bool rx_in_flight, tx_in_flight;
31
32         byte rx_buffer[4];
33
34         struct main_timer clock_timer;
35 };
36
37 /*** MQTT ***/
38
39 static struct mosquitto *mosq;
40
41 static void mqtt_publish(const char *topic, const char *fmt, ...)
42 {
43         va_list args;
44         va_start(args, fmt);
45         char m[256];
46         int l = vsnprintf(m, sizeof(m), fmt, args);
47         if (mosquitto_publish(mosq, NULL, topic, l, m, 0, true) != MOSQ_ERR_SUCCESS)
48                 msg(L_ERROR, "Mosquitto: publish failed");
49         va_end(args);
50 }
51
52 static void mqtt_publish_ephemeral(const char *topic, const char *fmt, ...)
53 {
54         va_list args;
55         va_start(args, fmt);
56         char m[256];
57         int l = vsnprintf(m, sizeof(m), fmt, args);
58         if (mosquitto_publish(mosq, NULL, topic, l, m, 0, false) != MOSQ_ERR_SUCCESS)
59                 msg(L_ERROR, "Mosquitto: publish failed");
60         va_end(args);
61 }
62
63 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
64 {
65         msg(L_DEBUG, "MQTT(%d): %s", level, message);
66 }
67
68 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
69 {
70         if (!status) {
71                 msg(L_DEBUG, "MQTT: Connection established");
72                 mqtt_publish("status/clock", "ok");
73         }
74 }
75
76 static void mqtt_init(void)
77 {
78         mosquitto_lib_init();
79         mosq = mosquitto_new("clock", 1, NULL);
80         if (!mosq)
81                 die("Mosquitto: initialization failed");
82
83         mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
84         mosquitto_log_callback_set(mosq, mqtt_log_callback);
85
86         if (mosquitto_will_set(mosq, "status/clock", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
87                 die("Mosquitto: unable to set will");
88
89         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)
90                 die("Mosquitto: Unable to set TLS parameters");
91
92         if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
93                 die("Mosquitto: Unable to connect");
94
95         if (mosquitto_loop_start(mosq))
96                 die("Mosquitto: cannot start service thread");
97 }
98
99 /*** IR remote ***/
100
101 static const struct {
102         u32 code;
103         const char * const name;
104 } key_table[] = {
105         { 0x4bc0ab54, "1" },
106         { 0x4bc06b94, "2" },
107         { 0x4bc0eb14, "3" },
108         { 0x4bc01be4, "4" },
109         { 0x4bc09b64, "5" },
110         { 0x4bc05ba4, "6" },
111         { 0x4bc0db24, "7" },
112         { 0x4bc03bc4, "8" },
113         { 0x4bc0bb44, "9" },
114         { 0x4bc0fb04, ">10" },
115         { 0x4bc07b84, "10/0" },
116         { 0x4bc07986, "clr" },
117         { 0x4b2010ef, "play-mode" },
118         { 0x4b20817e, "group/search" },
119         { 0x4bc0926d, "memory" },
120         { 0x4b20629d, "enter" },
121         { 0x4b2051ae, "band" },
122         { 0x4b90cb34, "fm-mode" },
123         { 0x4bc000ff, "preset+" },
124         { 0x4bc0807f, "preset-" },
125         { 0x4b9002fd, "tuning-up" },
126         { 0x4b90827d, "tuning-down" },
127         { 0x4b207887, "input-up" },
128         { 0x4b20f807, "input-down" },
129         { 0x4bc0a956, "dimmer" },
130         { 0x4bc040bf, "volume-up" },
131         { 0x4bc0c03f, "volume-down" },
132         { 0x4b20aa55, "display" },
133         { 0x4bc0a05f, "muting" },
134         { 0x4bc0f807, "pause" },
135         { 0x4bc0d827, "play" },
136         { 0x4bc038c7, "stop" },
137         { 0x4bc0d12e, "rewind" },
138         { 0x4bc051ae, "ffwd" },
139         { 0x4bc07887, "prev-song" },
140         { 0x4bc0b847, "next-song" },
141         { 0x4b20ea15, "random" },
142         { 0x4b206a95, "repeat" },
143         { 0x4b2048b7, "scroll" },
144         { 0x4b904ab5, "playlist-up" },
145         { 0x4b90ca35, "playlist-down" },
146         { 0x4b900af5, "album-up" },
147         { 0x4b908a75, "album-down" },
148         { 0x4b905aa5, "menu" },
149         { 0x4b90da25, "select" },
150         { 0x4b9041be, "list-up" },
151         { 0x4b90c13e, "list-down" },
152 };
153
154 static void report_ir_key(u32 key_code)
155 {
156         for (uint i = 0; i < (uint) ARRAY_SIZE(key_table); i++) {
157                 if (key_table[i].code == key_code) {
158                         msg(L_DEBUG, "IR key %08x: %s", key_code, key_table[i].name);
159                         mqtt_publish_ephemeral("burrow/control/catarium-ir", key_table[i].name);
160                         return;
161                 }
162         }
163
164         msg(L_WARN, "IR key %08x not recognized", key_code);
165 }
166
167 /*** USB ***/
168
169 static void clock_timer_handler(struct main_timer *timer);
170 static void rx_submit(struct device *dev);
171
172 bool usb_dev_init(struct usb_dev *u)
173 {
174         msg(L_INFO, "Connected clock at %s", u->port);
175
176         struct device *dev = xmalloc_zero(sizeof(*dev));
177         u->device = dev;
178         dev->usb = u;
179
180         dev->rx_transfer = libusb_alloc_transfer(0);
181         dev->tx_transfer = libusb_alloc_transfer(0);
182
183         dev->clock_timer.handler = clock_timer_handler;
184         dev->clock_timer.data = dev;
185
186         return true;
187 }
188
189 void usb_dev_connect(struct usb_dev *u)
190 {
191         struct device *dev = u->device;
192         USB_DBG(u, "Connected!");
193
194         timer_add_rel(&dev->clock_timer, 0);
195         rx_submit(dev);
196 }
197
198 void usb_dev_cancel(struct usb_dev *u)
199 {
200         struct device *dev = u->device;
201         USB_DBG(u, "Cancelling transfers");
202
203         if (dev->rx_in_flight)
204                 libusb_cancel_transfer(dev->rx_transfer);
205         if (dev->tx_in_flight)
206                 libusb_cancel_transfer(dev->tx_transfer);
207
208         timer_del(&dev->clock_timer);
209 }
210
211 bool usb_dev_in_flight(struct usb_dev *u)
212 {
213         struct device *dev = u->device;
214         return dev->rx_in_flight || dev->tx_in_flight;
215 }
216
217 void usb_dev_cleanup(struct usb_dev *u)
218 {
219         struct device *dev = u->device;
220         msg(L_INFO, "Disconnected clock at %s", u->port);
221
222         libusb_free_transfer(dev->rx_transfer);
223         libusb_free_transfer(dev->tx_transfer);
224 }
225
226 static void tx_callback(struct libusb_transfer *xfer)
227 {
228         struct device *dev = xfer->user_data;
229         struct usb_dev *u = dev->usb;
230
231         USB_DBG(u, "Bulk TX done (status=%d, len=%d/%d)", xfer->status, xfer->actual_length, xfer->length);
232         dev->tx_in_flight = false;
233
234         if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
235                 usb_error(u, "Bulk TX transfer failed with status %d", xfer->status);
236 }
237
238 static void clock_timer_handler(struct main_timer *timer)
239 {
240         struct device *dev = timer->data;
241         struct usb_dev *u = dev->usb;
242         msg(L_DEBUG, "Clock tick");
243
244         if (dev->tx_in_flight) {
245                 msg(L_ERROR, "Transfer overrun");
246                 timer_add_rel(timer, 1000);
247         }
248
249         time_t t = time(NULL);
250         struct tm *tm = localtime(&t);
251
252         unsigned char req[8] = {
253                 tm->tm_hour / 10,
254                 tm->tm_hour % 10,
255                 tm->tm_min / 10,
256                 tm->tm_min % 10,
257                 (tm->tm_sec % 2 ? 0xff : 0),
258         };
259
260         libusb_fill_bulk_transfer(dev->tx_transfer, u->devh, 0x01, req, sizeof(req), tx_callback, dev, 5000);
261
262         int err;
263         if (err = libusb_submit_transfer(dev->tx_transfer))
264                 usb_error(u, "Cannot submit bulk TX transfer: error %d", err);
265         else
266                 dev->tx_in_flight = true;
267
268         timer_add_rel(timer, 1000);
269 }
270
271 static void rx_callback(struct libusb_transfer *xfer)
272 {
273         struct device *dev = xfer->user_data;
274         struct usb_dev *u = dev->usb;
275
276         USB_DBG(u, "Bulk RX done (status=%d, len=%d/%d)", xfer->status, xfer->actual_length, xfer->length);
277         dev->rx_in_flight = false;
278
279         if (xfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
280                 // Should not happen
281                 rx_submit(dev);
282                 return;
283         }
284
285         if (xfer->status != LIBUSB_TRANSFER_COMPLETED) {
286                 usb_error(u, "Bulk RX transfer failed with status %d", xfer->status);
287                 return;
288         }
289
290         if (xfer->actual_length < 4) {
291                 msg(L_ERROR, "Short RX transfer");
292         } else {
293                 u32 key = get_u32_be(dev->rx_buffer);
294                 report_ir_key(key);
295         }
296
297         rx_submit(dev);
298 }
299
300 static void rx_submit(struct device *dev)
301 {
302         struct usb_dev *u = dev->usb;
303
304         libusb_fill_bulk_transfer(dev->rx_transfer, u->devh, 0x82, dev->rx_buffer, sizeof(dev->rx_buffer), rx_callback, dev, 0);
305
306         int err;
307         if (err = libusb_submit_transfer(dev->rx_transfer))
308                 usb_error(u, "Cannot submit bulk RX transfer: error %d", err);
309         else
310                 dev->rx_in_flight = true;
311 }
312
313 /*** Main loop ***/
314
315 static int use_debug;
316
317 static struct opt_section options = {
318         OPT_ITEMS {
319                 OPT_HELP("A daemon for controlling MJ's workshop clock"),
320                 OPT_HELP(""),
321                 OPT_HELP("Options:"),
322                 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
323                 OPT_HELP_OPTION,
324                 OPT_CONF_OPTIONS,
325                 OPT_END
326         }
327 };
328
329 int main(int argc UNUSED, char **argv)
330 {
331         log_init(argv[0]);
332         opt_parse(&options, argv+1);
333
334         if (!use_debug)
335                 log_default_stream()->levels &= ~(1U << L_DEBUG);
336
337         mqtt_init();
338         main_init();
339         usb_helper_init(0x4242, 0x000d);
340
341         main_loop();
342         return 0;
343 }