]> mj.ucw.cz Git - ursary.git/blob - dmx.c
Lights: Long press = full power
[ursary.git] / dmx.c
1 /*
2  *      DMX512 over USB (custom USB peripheral)
3  *
4  *      (c) 2020 Martin Mares <mj@ucw.cz>
5  */
6
7 #define LOCAL_DEBUG
8
9 #include <ucw/lib.h>
10 #include <ucw/mainloop.h>
11 #include <ucw/stkstring.h>
12
13 #include <stdio.h>
14 #include <string.h>
15
16 #include "ursaryd.h"
17 #include "usb.h"
18 #include "dmx-interface.h"
19
20 static libusb_device_handle *dmx_dev;
21 static bool dmx_iface_claimed;
22
23 static void dmx_error(int usb_err, char *text);
24
25 static struct libusb_transfer *dmx_write_xfer;
26 static bool dmx_write_pending;
27 static void dmx_sched_write(void);
28
29 #define DMX_NUM_CHANNELS 4
30 static byte dmx_pwm_state[DMX_NUM_CHANNELS];
31 static bool dmx_pwm_dirty;
32
33 static void dmx_write_done(struct libusb_transfer *xfer)
34 {
35   int len = xfer->actual_length;
36   DBG("DMX: Write done: status %d, length %d", xfer->status, len);
37
38   if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
39     return dmx_error(0, stk_printf("DMX write failed with status %d", xfer->status));
40   if (len < xfer->length)
41     msg(L_ERROR, "DMX partial write: %d out of %d", len, xfer->length);
42
43   dmx_write_pending = 0;
44   dmx_sched_write();
45 }
46
47 static void dmx_sched_write(void)
48 {
49   if (!dmx_dev || dmx_write_pending || !dmx_pwm_dirty)
50     return;
51
52   DBG("DMX: Submitting write");
53   dmx_write_pending = 1;
54   dmx_pwm_dirty = 0;
55
56   struct libusb_transfer *xfer = dmx_write_xfer;
57   byte *pkt = xfer->buffer;
58   pkt[0] = 0;
59   memcpy(pkt+1, dmx_pwm_state, DMX_NUM_CHANNELS);
60   xfer->length = 1 + DMX_NUM_CHANNELS;
61
62   int err;
63   if ((err = libusb_submit_transfer(xfer)) < 0)
64     dmx_error(err, "Cannot submit transfer");
65 }
66
67 void dmx_set_pwm(uint index, uint val)
68 {
69   ASSERT(index < DMX_NUM_CHANNELS);
70   ASSERT(val < 256);
71   if (dmx_pwm_state[index] != val)
72     {
73       dmx_pwm_state[index] = val;
74       dmx_pwm_dirty = 1;
75       dmx_sched_write();
76     }
77 }
78
79 static void dmx_write_init(void)
80 {
81   DBG("DMX: Write init");
82
83   dmx_write_xfer = libusb_alloc_transfer(0);
84   libusb_fill_bulk_transfer(dmx_write_xfer, dmx_dev, 0x01, xmalloc(1+DMX_NUM_CHANNELS), 0, dmx_write_done, NULL, 1000);
85
86   dmx_pwm_dirty = 1;
87   dmx_sched_write();
88 }
89
90 static struct main_timer dmx_connect_timer;
91 static struct main_hook dmx_error_hook;
92
93 static void dmx_connect(struct main_timer *t)
94 {
95   timer_del(t);
96   msg(L_DEBUG, "Looking for DMX interface");
97   int err;
98
99   libusb_device **dev_list;
100   libusb_device *found_dev = NULL;
101   ssize_t len = libusb_get_device_list(usb_ctx, &dev_list);
102   for (ssize_t i=0; i < len; i++)
103     {
104       libusb_device *dev = dev_list[i];
105       struct libusb_device_descriptor desc;
106       if (libusb_get_device_descriptor(dev, &desc) >= 0 &&
107           desc.idVendor == DMX_USB_VENDOR &&
108           desc.idProduct == DMX_USB_PRODUCT)
109         {
110           msg(L_INFO, "DMX found at bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
111           if (found_dev)
112             {
113               msg(L_ERROR, "Multiple DMX devices found. Using the first one.");
114               break;
115             }
116           found_dev = libusb_ref_device(dev);
117         }
118     }
119   libusb_free_device_list(dev_list, 1);
120
121   if (!found_dev)
122     {
123       msg(L_INFO, "No DMX device found");
124       timer_add_rel(t, 5000);
125       return;
126     }
127
128   DBG("Initializing DMX");
129
130   if ((err = libusb_open(found_dev, &dmx_dev)) < 0)
131     return dmx_error(err, "libusb_open failed");
132
133   if ((err = libusb_claim_interface(dmx_dev, 0)) < 0)
134     return dmx_error(err, "libusb_claim_interface failed");
135   dmx_iface_claimed = 1;
136
137   dmx_write_init();
138 }
139
140 static int dmx_error_handler(struct main_hook *h)
141 {
142   DBG("DMX: Entered error handling hook");
143   hook_del(h);
144
145   if (dmx_dev)
146     {
147       if (dmx_write_xfer)
148         {
149           if (dmx_write_pending)
150             {
151               DBG("DMX: Cancelling pending write");
152               libusb_cancel_transfer(dmx_write_xfer);
153               dmx_write_pending = 0;
154             }
155           DBG("DMX: Tearing down write xfer");
156           xfree(dmx_write_xfer->buffer);
157           libusb_free_transfer(dmx_write_xfer);
158           dmx_write_xfer = NULL;
159         }
160       if (dmx_iface_claimed)
161         {
162           DBG("DMX: Unclaiming interface");
163           libusb_release_interface(dmx_dev, 0);
164           dmx_iface_claimed = 0;
165         }
166       DBG("DMX: Resetting device");
167       libusb_reset_device(dmx_dev);
168       libusb_close(dmx_dev);
169       dmx_dev = NULL;
170     }
171
172   DBG("DMX: Scheduling rescan after error");
173   timer_add_rel(&dmx_connect_timer, 3000);
174
175   return HOOK_IDLE;
176 }
177
178 static void dmx_error(int usb_err, char *text)
179 {
180   if (usb_err)
181     msg(L_ERROR, "DMX: %s: error %d (%s)", text, usb_err, libusb_error_name(usb_err));
182   else
183     msg(L_ERROR, "DMX: %s", text);
184
185   DBG("DMX: Scheduling error handling hook");
186   hook_add(&dmx_error_hook);
187 }
188
189 bool dmx_is_ready(void)
190 {
191   return !!dmx_dev;
192 }
193
194 void dmx_init(void)
195 {
196   // Prepare error handling hook
197   dmx_error_hook.handler = dmx_error_handler;
198
199   // Schedule search for DMX USB device
200   dmx_connect_timer.handler = dmx_connect;
201   timer_add_rel(&dmx_connect_timer, 100);
202 }