2 * MJ's Workshop Clock Daemon
4 * (c) 2023 Martin Mares <mj@ucw.cz>
12 #include <ucw/unaligned.h>
21 #include <libusb-1.0/libusb.h>
22 #include <mosquitto.h>
24 #include "usb-helper.h"
29 struct libusb_transfer *rx_transfer, *tx_transfer;
30 bool rx_in_flight, tx_in_flight;
34 struct main_timer clock_timer;
39 static struct mosquitto *mosq;
41 static void mqtt_publish(const char *topic, const char *fmt, ...)
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");
52 static void mqtt_publish_ephemeral(const char *topic, const char *fmt, ...)
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");
63 static void mqtt_log_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int level, const char *message)
65 msg(L_DEBUG, "MQTT(%d): %s", level, message);
68 static void mqtt_conn_callback(struct mosquitto *mosq UNUSED, void *obj UNUSED, int status)
71 msg(L_DEBUG, "MQTT: Connection established");
72 mqtt_publish("status/clock", "ok");
76 static void mqtt_init(void)
79 mosq = mosquitto_new("clock", 1, NULL);
81 die("Mosquitto: initialization failed");
83 mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
84 mosquitto_log_callback_set(mosq, mqtt_log_callback);
86 if (mosquitto_will_set(mosq, "status/clock", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
87 die("Mosquitto: unable to set will");
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");
92 if (mosquitto_connect_async(mosq, "burrow-mqtt", 8883, 60) != MOSQ_ERR_SUCCESS)
93 die("Mosquitto: Unable to connect");
95 if (mosquitto_loop_start(mosq))
96 die("Mosquitto: cannot start service thread");
101 static const struct {
103 const char * const name;
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" },
154 static void report_ir_key(u32 key_code)
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);
164 msg(L_WARN, "IR key %08x not recognized", key_code);
169 static void clock_timer_handler(struct main_timer *timer);
170 static void rx_submit(struct device *dev);
172 bool usb_dev_init(struct usb_dev *u)
174 msg(L_INFO, "Connected clock at %s", u->port);
176 struct device *dev = xmalloc_zero(sizeof(*dev));
180 dev->rx_transfer = libusb_alloc_transfer(0);
181 dev->tx_transfer = libusb_alloc_transfer(0);
183 dev->clock_timer.handler = clock_timer_handler;
184 dev->clock_timer.data = dev;
189 void usb_dev_connect(struct usb_dev *u)
191 struct device *dev = u->device;
192 USB_DBG(u, "Connected!");
194 timer_add_rel(&dev->clock_timer, 0);
198 void usb_dev_cancel(struct usb_dev *u)
200 struct device *dev = u->device;
201 USB_DBG(u, "Cancelling transfers");
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);
208 timer_del(&dev->clock_timer);
211 bool usb_dev_in_flight(struct usb_dev *u)
213 struct device *dev = u->device;
214 return dev->rx_in_flight || dev->tx_in_flight;
217 void usb_dev_cleanup(struct usb_dev *u)
219 struct device *dev = u->device;
220 msg(L_INFO, "Disconnected clock at %s", u->port);
222 libusb_free_transfer(dev->rx_transfer);
223 libusb_free_transfer(dev->tx_transfer);
226 static void tx_callback(struct libusb_transfer *xfer)
228 struct device *dev = xfer->user_data;
229 struct usb_dev *u = dev->usb;
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;
234 if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
235 usb_error(u, "Bulk TX transfer failed with status %d", xfer->status);
238 static void clock_timer_handler(struct main_timer *timer)
240 struct device *dev = timer->data;
241 struct usb_dev *u = dev->usb;
242 msg(L_DEBUG, "Clock tick");
244 if (dev->tx_in_flight) {
245 msg(L_ERROR, "Transfer overrun");
246 timer_add_rel(timer, 1000);
249 time_t t = time(NULL);
250 struct tm *tm = localtime(&t);
252 unsigned char req[8] = {
257 (tm->tm_sec % 2 ? 0xff : 0),
260 libusb_fill_bulk_transfer(dev->tx_transfer, u->devh, 0x01, req, sizeof(req), tx_callback, dev, 5000);
263 if (err = libusb_submit_transfer(dev->tx_transfer))
264 usb_error(u, "Cannot submit bulk TX transfer: error %d", err);
266 dev->tx_in_flight = true;
268 timer_add_rel(timer, 1000);
271 static void rx_callback(struct libusb_transfer *xfer)
273 struct device *dev = xfer->user_data;
274 struct usb_dev *u = dev->usb;
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;
279 if (xfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
285 if (xfer->status != LIBUSB_TRANSFER_COMPLETED) {
286 usb_error(u, "Bulk RX transfer failed with status %d", xfer->status);
290 if (xfer->actual_length < 4) {
291 msg(L_ERROR, "Short RX transfer");
293 u32 key = get_u32_be(dev->rx_buffer);
300 static void rx_submit(struct device *dev)
302 struct usb_dev *u = dev->usb;
304 libusb_fill_bulk_transfer(dev->rx_transfer, u->devh, 0x82, dev->rx_buffer, sizeof(dev->rx_buffer), rx_callback, dev, 0);
307 if (err = libusb_submit_transfer(dev->rx_transfer))
308 usb_error(u, "Cannot submit bulk RX transfer: error %d", err);
310 dev->rx_in_flight = true;
315 static int use_debug;
317 static struct opt_section options = {
319 OPT_HELP("A daemon for controlling MJ's workshop clock"),
321 OPT_HELP("Options:"),
322 OPT_BOOL('d', "debug", use_debug, 0, "\tLog debugging messages"),
329 int main(int argc UNUSED, char **argv)
332 opt_parse(&options, argv+1);
335 log_default_stream()->levels &= ~(1U << L_DEBUG);
339 usb_helper_init(0x4242, 0x000d);