/*
- * MJ's desktop clock daemon
+ * MJ's Workshop Clock Daemon
*
- * (c) 2018 Martin Mares <mj@ucw.cz>
+ * (c) 2023 Martin Mares <mj@ucw.cz>
*/
+#define LOCAL_DEBUG
+
#include <ucw/lib.h>
#include <ucw/log.h>
#include <ucw/opt.h>
#include <libusb-1.0/libusb.h>
#include <mosquitto.h>
-static struct mosquitto *mosq;
+#include "usb-helper.h"
-struct libusb_context *usb_ctxt;
-struct libusb_device_handle *devh;
+struct device {
+ struct usb_dev *usb;
-static libusb_device *find_device(void)
-{
- libusb_device **devlist;
- ssize_t devn = libusb_get_device_list(usb_ctxt, &devlist);
- if (devn < 0) {
- fprintf(stderr, "Cannot enumerate USB devices: error %d\n", (int) devn);
- exit(1);
- }
+ struct libusb_transfer *rx_transfer, *tx_transfer;
+ bool rx_in_flight, tx_in_flight;
- for (ssize_t i=0; i<devn; i++) {
- struct libusb_device_descriptor desc;
- libusb_device *dev = devlist[i];
- if (!libusb_get_device_descriptor(dev, &desc)) {
- if (desc.idVendor == 0x4242 && desc.idProduct == 0x0001) {
- msg(L_INFO, "Found device at usb%d.%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
- // FIXME: Free device list
- return dev;
- }
- }
- }
+ byte rx_buffer[4];
- libusb_free_device_list(devlist, 1);
- fprintf(stderr, "Device not found\n");
- exit(1);
-}
+ struct main_timer clock_timer;
+};
+
+/*** MQTT ***/
+
+static struct mosquitto *mosq;
static void mqtt_publish(const char *topic, const char *fmt, ...)
{
}
}
-static int use_daemon;
+static void mqtt_init(void)
+{
+ mosquitto_lib_init();
+ mosq = mosquitto_new("clock", 1, NULL);
+ if (!mosq)
+ die("Mosquitto: initialization failed");
+
+ mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
+
+ if (mosquitto_will_set(mosq, "status/clock", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
+ die("Mosquitto: unable to set will");
+
+ if (mosquitto_connect_async(mosq, "10.32.184.5", 1883, 60) != MOSQ_ERR_SUCCESS)
+ die("Mosquitto: connect failed");
+
+ if (mosquitto_loop_start(mosq))
+ die("Mosquitto: cannot start service thread");
+}
+
+/*** USB ***/
+
+static void clock_timer_handler(struct main_timer *timer);
+static void rx_submit(struct device *dev);
+
+bool usb_dev_init(struct usb_dev *u)
+{
+ msg(L_INFO, "Connected clock at %s", u->port);
+
+ struct device *dev = xmalloc_zero(sizeof(*dev));
+ u->device = dev;
+ dev->usb = u;
+
+ dev->rx_transfer = libusb_alloc_transfer(0);
+ dev->tx_transfer = libusb_alloc_transfer(0);
+
+ dev->clock_timer.handler = clock_timer_handler;
+ dev->clock_timer.data = dev;
+
+ return true;
+}
+
+void usb_dev_connect(struct usb_dev *u)
+{
+ struct device *dev = u->device;
+ USB_DBG(u, "Connected!");
+
+ timer_add_rel(&dev->clock_timer, 0);
+ rx_submit(dev);
+}
+
+void usb_dev_cancel(struct usb_dev *u)
+{
+ struct device *dev = u->device;
+ USB_DBG(u, "Cancelling transfers");
+
+ if (dev->rx_in_flight)
+ libusb_cancel_transfer(dev->rx_transfer);
+ if (dev->tx_in_flight)
+ libusb_cancel_transfer(dev->tx_transfer);
+
+ timer_del(&dev->clock_timer);
+}
+
+bool usb_dev_in_flight(struct usb_dev *u)
+{
+ struct device *dev = u->device;
+ return dev->rx_in_flight || dev->tx_in_flight;
+}
+
+void usb_dev_cleanup(struct usb_dev *u)
+{
+ struct device *dev = u->device;
+ msg(L_INFO, "Disconnected clock at %s", u->port);
+
+ libusb_free_transfer(dev->rx_transfer);
+ libusb_free_transfer(dev->tx_transfer);
+}
+
+static void tx_callback(struct libusb_transfer *xfer)
+{
+ struct device *dev = xfer->user_data;
+ struct usb_dev *u = dev->usb;
+
+ USB_DBG(u, "Bulk TX done (status=%d, len=%d/%d)", xfer->status, xfer->actual_length, xfer->length);
+ dev->tx_in_flight = false;
+
+ if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
+ usb_error(u, "Bulk TX transfer failed with status %d", xfer->status);
+}
+
+static void clock_timer_handler(struct main_timer *timer)
+{
+ struct device *dev = timer->data;
+ struct usb_dev *u = dev->usb;
+ msg(L_DEBUG, "Clock tick");
+
+ if (dev->tx_in_flight) {
+ msg(L_ERROR, "Transfer overrun");
+ timer_add_rel(timer, 1000);
+ }
+
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+
+ unsigned char req[8] = {
+ tm->tm_hour / 10,
+ tm->tm_hour % 10,
+ tm->tm_min / 10,
+ tm->tm_min % 10,
+ (tm->tm_sec % 2 ? 0xff : 0),
+ };
+
+ libusb_fill_bulk_transfer(dev->tx_transfer, u->devh, 0x01, req, sizeof(req), tx_callback, dev, 5000);
+
+ int err;
+ if (err = libusb_submit_transfer(dev->tx_transfer))
+ usb_error(u, "Cannot submit bulk TX transfer: error %d", err);
+ else
+ dev->tx_in_flight = true;
+
+ timer_add_rel(timer, 1000);
+}
+
+static void rx_callback(struct libusb_transfer *xfer)
+{
+ struct device *dev = xfer->user_data;
+ struct usb_dev *u = dev->usb;
+
+ USB_DBG(u, "Bulk RX done (status=%d, len=%d/%d)", xfer->status, xfer->actual_length, xfer->length);
+ dev->rx_in_flight = false;
+
+ if (xfer->status == LIBUSB_TRANSFER_TIMED_OUT) {
+ // Should not happen
+ rx_submit(dev);
+ return;
+ }
+
+ if (xfer->status != LIBUSB_TRANSFER_COMPLETED) {
+ usb_error(u, "Bulk RX transfer failed with status %d", xfer->status);
+ return;
+ }
+
+ if (xfer->actual_length < 4) {
+ msg(L_ERROR, "Short RX transfer");
+ } else {
+ u32 key = get_u32_be(dev->rx_buffer);
+ msg(L_INFO, "IR key %08x", key);
+ }
+
+ rx_submit(dev);
+}
+
+static void rx_submit(struct device *dev)
+{
+ struct usb_dev *u = dev->usb;
+
+ libusb_fill_bulk_transfer(dev->rx_transfer, u->devh, 0x82, dev->rx_buffer, sizeof(dev->rx_buffer), rx_callback, dev, 0);
+
+ int err;
+ if (err = libusb_submit_transfer(dev->rx_transfer))
+ usb_error(u, "Cannot submit bulk RX transfer: error %d", err);
+ else
+ dev->rx_in_flight = true;
+}
+
+/*** Main loop ***/
+
static int use_debug;
static struct opt_section options = {
OPT_ITEMS {
- OPT_HELP("A daemon for controlling the solid state relay module via MQTT"),
+ OPT_HELP("A daemon for controlling MJ's workshop clock"),
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
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);
- mosquitto_lib_init();
- mosq = mosquitto_new("clock", 1, NULL);
- if (!mosq)
- die("Mosquitto: initialization failed");
-
- mosquitto_connect_callback_set(mosq, mqtt_conn_callback);
-
- if (mosquitto_will_set(mosq, "status/clock", 4, "dead", 0, true) != MOSQ_ERR_SUCCESS)
- die("Mosquitto: unable to set will");
-
- if (mosquitto_connect_async(mosq, "10.32.184.5", 1883, 60) != MOSQ_ERR_SUCCESS)
- die("Mosquitto: connect failed");
-
- if (mosquitto_loop_start(mosq))
- die("Mosquitto: cannot start service thread");
-
- int err;
- if (err = libusb_init(&usb_ctxt))
- die("Cannot initialize libusb: error %d", err);
-
- libusb_device *dev = find_device();
-
- if (err = libusb_open(dev, &devh))
- die("Cannot open device: error %d", err);
- libusb_reset_device(devh);
- if (err = libusb_claim_interface(devh, 0))
- die("Cannot claim interface: error %d", err);
-
- uint cnt = 0;
- for (;;) {
- time_t t = time(NULL);
- struct tm *tm = localtime(&t);
-
- unsigned char req[8] = {
- tm->tm_hour / 10,
- tm->tm_hour % 10,
- (tm->tm_sec % 2 ? 10 : 0xff),
- tm->tm_min / 10,
- tm->tm_min % 10,
- };
- int transferred;
- if (err = libusb_bulk_transfer(devh, 0x01, req, 8, &transferred, 2000))
- die("Transfer failed: error %d", err);
-
- unsigned char resp[64];
- int received;
- if (err = libusb_bulk_transfer(devh, 0x82, resp, 64, &received, 2000))
- die("Receive failed: error %d", err);
- // printf("Received %d bytes\n", received);
- if (received >= 12) {
- int temp = get_u32_be(resp);
- int press = get_u32_be(resp + 4);
- uint cycle = get_u32_be(resp + 8);
- msg(L_DEBUG, "Temperature %d ddegC, pressure %d Pa, cycle %u", temp, press, cycle);
- if (!(cnt % 10) && press) {
- mqtt_publish("burrow/temp/clock", "%.1f %llu", temp / 10., (long long) t);
- mqtt_publish("burrow/pressure/clock", "%d %llu", press, (long long) t);
- }
- }
-
- sleep(1);
- cnt++;
- }
+ mqtt_init();
+ main_init();
+ usb_helper_init(0x4242, 0x0007);
+ main_loop();
return 0;
}