--- /dev/null
+/*
+ * DMX512 over USB (custom USB peripheral)
+ *
+ * (c) 2020 Martin Mares <mj@ucw.cz>
+ */
+
+#define LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/mainloop.h>
+#include <ucw/stkstring.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "ursaryd.h"
+#include "usb.h"
+#include "dmx-interface.h"
+
+static libusb_device_handle *dmx_dev;
+static bool dmx_iface_claimed;
+
+static void dmx_error(int usb_err, char *text);
+
+static struct libusb_transfer *dmx_write_xfer;
+static bool dmx_write_pending;
+static void dmx_sched_write(void);
+
+#define DMX_NUM_CHANNELS 4
+static byte dmx_pwm_state[DMX_NUM_CHANNELS];
+static bool dmx_pwm_dirty;
+
+static void dmx_write_done(struct libusb_transfer *xfer)
+{
+ int len = xfer->actual_length;
+ DBG("DMX: Write done: status %d, length %d", xfer->status, len);
+
+ if (xfer->status != LIBUSB_TRANSFER_COMPLETED)
+ return dmx_error(0, stk_printf("DMX write failed with status %d", xfer->status));
+ if (len < xfer->length)
+ msg(L_ERROR, "DMX partial write: %d out of %d", len, xfer->length);
+
+ dmx_write_pending = 0;
+ dmx_sched_write();
+}
+
+static void dmx_sched_write(void)
+{
+ if (!dmx_dev || dmx_write_pending || !dmx_pwm_dirty)
+ return;
+
+ DBG("DMX: Submitting write");
+ dmx_write_pending = 1;
+ dmx_pwm_dirty = 0;
+
+ struct libusb_transfer *xfer = dmx_write_xfer;
+ byte *pkt = xfer->buffer;
+ pkt[0] = 0;
+ memcpy(pkt+1, dmx_pwm_state, DMX_NUM_CHANNELS);
+ xfer->length = 1 + DMX_NUM_CHANNELS;
+
+ int err;
+ if ((err = libusb_submit_transfer(xfer)) < 0)
+ dmx_error(err, "Cannot submit transfer");
+}
+
+void dmx_set_pwm(uint index, uint val)
+{
+ ASSERT(index < DMX_NUM_CHANNELS);
+ ASSERT(val < 256);
+ if (dmx_pwm_state[index] != val)
+ {
+ dmx_pwm_state[index] = val;
+ dmx_pwm_dirty = 1;
+ dmx_sched_write();
+ }
+}
+
+static void dmx_write_init(void)
+{
+ DBG("DMX: Write init");
+
+ dmx_write_xfer = libusb_alloc_transfer(0);
+ libusb_fill_bulk_transfer(dmx_write_xfer, dmx_dev, 0x01, xmalloc(1+DMX_NUM_CHANNELS), 0, dmx_write_done, NULL, 1000);
+
+ dmx_pwm_dirty = 1;
+ dmx_sched_write();
+}
+
+static struct main_timer dmx_connect_timer;
+static struct main_hook dmx_error_hook;
+
+static void dmx_connect(struct main_timer *t)
+{
+ timer_del(t);
+ msg(L_DEBUG, "Looking for DMX interface");
+ int err;
+
+ libusb_device **dev_list;
+ libusb_device *found_dev = NULL;
+ ssize_t len = libusb_get_device_list(usb_ctx, &dev_list);
+ for (ssize_t i=0; i < len; i++)
+ {
+ libusb_device *dev = dev_list[i];
+ struct libusb_device_descriptor desc;
+ if (libusb_get_device_descriptor(dev, &desc) >= 0 &&
+ desc.idVendor == DMX_USB_VENDOR &&
+ desc.idProduct == DMX_USB_PRODUCT)
+ {
+ msg(L_INFO, "DMX found at bus %d, addr %d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
+ if (found_dev)
+ {
+ msg(L_ERROR, "Multiple DMX devices found. Using the first one.");
+ break;
+ }
+ found_dev = libusb_ref_device(dev);
+ }
+ }
+ libusb_free_device_list(dev_list, 1);
+
+ if (!found_dev)
+ {
+ msg(L_INFO, "No DMX device found");
+ timer_add_rel(t, 5000);
+ return;
+ }
+
+ DBG("Initializing DMX");
+
+ if ((err = libusb_open(found_dev, &dmx_dev)) < 0)
+ return dmx_error(err, "libusb_open failed");
+
+ if ((err = libusb_claim_interface(dmx_dev, 0)) < 0)
+ return dmx_error(err, "libusb_claim_interface failed");
+ dmx_iface_claimed = 1;
+
+ dmx_write_init();
+}
+
+static int dmx_error_handler(struct main_hook *h)
+{
+ DBG("DMX: Entered error handling hook");
+ hook_del(h);
+
+ if (dmx_dev)
+ {
+ if (dmx_write_xfer)
+ {
+ if (dmx_write_pending)
+ {
+ DBG("DMX: Cancelling pending write");
+ libusb_cancel_transfer(dmx_write_xfer);
+ dmx_write_pending = 0;
+ }
+ DBG("DMX: Tearing down write xfer");
+ xfree(dmx_write_xfer->buffer);
+ libusb_free_transfer(dmx_write_xfer);
+ dmx_write_xfer = NULL;
+ }
+ if (dmx_iface_claimed)
+ {
+ DBG("DMX: Unclaiming interface");
+ libusb_release_interface(dmx_dev, 0);
+ dmx_iface_claimed = 0;
+ }
+ DBG("DMX: Resetting device");
+ libusb_reset_device(dmx_dev);
+ libusb_close(dmx_dev);
+ dmx_dev = NULL;
+ }
+
+ DBG("DMX: Scheduling rescan after error");
+ timer_add_rel(&dmx_connect_timer, 3000);
+
+ return HOOK_IDLE;
+}
+
+static void dmx_error(int usb_err, char *text)
+{
+ if (usb_err)
+ msg(L_ERROR, "DMX: %s: error %d (%s)", text, usb_err, libusb_error_name(usb_err));
+ else
+ msg(L_ERROR, "DMX: %s", text);
+
+ DBG("DMX: Scheduling error handling hook");
+ hook_add(&dmx_error_hook);
+}
+
+bool dmx_is_ready(void)
+{
+ return !!dmx_dev;
+}
+
+void dmx_init(void)
+{
+ // Prepare error handling hook
+ dmx_error_hook.handler = dmx_error_handler;
+
+ // Schedule search for DMX USB device
+ dmx_connect_timer.handler = dmx_connect;
+ timer_add_rel(&dmx_connect_timer, 100);
+}
* (c) 2014--2020 Martin Mares <mj@ucw.cz>
*/
-#undef LOCAL_DEBUG
+#define LOCAL_DEBUG
#include <ucw/lib.h>
#include <ucw/clists.h>
#include <ucw/opt.h>
#include <ucw/stkstring.h>
+#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
* Map of all controls
*
* rotary red button green button
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* 0 sink PCH mute use headphones
* 1 - - -
* 2 - - -
- * 3 - - -
+ * 3 desk brightness - desk lights on
* 4 MPD mute MPD play/pause
* 5 Albireo MPV mute MPD stop
* 6 Albireo other mute MPD prev
* 7 other machines mute MPD next
*
* center -
- * slider -
+ * slider light color temperature
*/
#define PCH_SINK "alsa_output.pci-0000_00_1f.3.analog-stereo"
}
}
+/*** Lights ***/
+
+static bool lights_on[2];
+static double lights_brightness[2];
+static double lights_temperature[2];
+
+static void update_lights(void)
+{
+ if (!dmx_is_ready())
+ {
+ noct_set_ring(3, RING_MODE_SINGLE_ON, 0x7f);
+ noct_set_button(11, 0);
+ return;
+ }
+
+ for (uint i=0; i<1; i++)
+ {
+ uint warm, cold;
+ if (lights_on[i])
+ {
+ noct_set_ring(3, RING_MODE_LEFT, lights_brightness[i] * 127);
+ noct_set_button(11, 1);
+ double r = 2;
+ double x = (exp(r*lights_brightness[i]) - 1) / (exp(r) - 1);
+ double t = lights_temperature[i];
+ double w = 2*x*(1-t);
+ double c = 2*x*t;
+ warm = CLAMP((int)(255*w), 0, 255);
+ cold = CLAMP((int)(255*c), 0, 255);
+ }
+ else
+ {
+ noct_set_ring(3, RING_MODE_LEFT, 0);
+ noct_set_button(11, 0);
+ warm = cold = 0;
+ }
+ DBG("Lights[%d]: on=%d bri=%.3f temp=%.3f -> warm=%d cold=%d", i, lights_on[i], lights_brightness[i], lights_temperature[i], warm, cold);
+ dmx_set_pwm(2*i, warm);
+ dmx_set_pwm(2*i+1, cold);
+ }
+}
+
+static void update_lights_from_rotary(int ch, int delta)
+{
+ if (lights_on[ch])
+ lights_brightness[ch] = CLAMP(lights_brightness[ch] + 0.015*delta*abs(delta), 0., 1.);
+ update_lights();
+}
+
+static void update_lights_from_slider(int value)
+{
+ lights_temperature[0] = value / 127.;
+ update_lights();
+}
+
+static void update_lights_from_button(int ch, int on)
+{
+ if (on)
+ {
+ lights_on[ch] = !lights_on[ch];
+ update_lights();
+ }
+}
+
/*** Main update routines ***/
static struct main_timer update_timer;
update_default_sink();
#endif
update_mpd();
+ update_lights();
}
void schedule_update(void)
case 0:
update_sink_from_rotary(delta, PCH_SINK);
break;
+ case 3:
+ update_lights_from_rotary(0, delta);
+ break;
+ case 9:
+ update_lights_from_slider(delta);
+ break;
default:
update_group_from_rotary(rotary, delta);
}
update_default_sink_from_button(button, on);
break;
#endif
+ case 11:
+ update_lights_from_button(0, on);
+ break;
case 12:
update_mpd_from_button(button, on);
break;
}
}
-void notify_touch(int rotary, int on UNUSED)
+void notify_touch(int rotary UNUSED, int on UNUSED)
{
if (!prepare_notify())
return;
+#if 0
// Rotary touches switch meaning of LEDs, this is handled inside display updates
if (rotary >= 4 && rotary < 8)
schedule_update();
+#endif
}
/*** Main entry point ***/
usb_init();
noct_init();
+ dmx_init();
pulse_init();
mpd_init();