From 76a5de8771d5eaac2eea0dc554fd9cff6531c748 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 12 Apr 2020 00:29:15 +0200 Subject: [PATCH] First attempt at controlling DMX lights --- Makefile | 5 +- dmx-interface.h | 18 +++++ dmx.c | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ ursaryd.c | 87 ++++++++++++++++++++- ursaryd.h | 6 ++ 5 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 dmx-interface.h create mode 100644 dmx.c diff --git a/Makefile b/Makefile index 40de57e..065d8e1 100644 --- a/Makefile +++ b/Makefile @@ -11,11 +11,11 @@ LIBUCW_CFLAGS := $(shell $(PC) --cflags libucw) LIBUCW_LIBS := $(shell $(PC) --libs libucw) CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 $(LIBUCW_CFLAGS) $(LIBUSB_CFLAGS) $(LIBPULSE_CFLAGS) -g2 -LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) +LDLIBS=$(LIBUCW_LIBS) $(LIBUSB_LIBS) $(LIBPULSE_LIBS) -lm all: ursaryd -ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o +ursaryd: ursaryd.o mpd.o nocturn.o pulse.o pulse-ucw.o usb.o dmx.o ursaryd.o: ursaryd.c ursaryd.h usb.h mpd.o: mpd.c ursaryd.h @@ -23,6 +23,7 @@ nocturn.o: nocturn.c ursaryd.h usb.h pulse.o: pulse.c ursaryd.h pulse-ucw.o: pulse-ucw.c ursaryd.h usb.o: usb.c ursaryd.h usb.h +dmx.o: dmx.c ursaryd.h usb.h dmx-interface.h clean: rm -f `find . -name "*~" -or -name "*.[oa]" -or -name "\#*\#" -or -name TAGS -or -name core -or -name .depend -or -name .#*` diff --git a/dmx-interface.h b/dmx-interface.h new file mode 100644 index 0000000..1954fb5 --- /dev/null +++ b/dmx-interface.h @@ -0,0 +1,18 @@ +/* + * DMX512 Gadget -- Interface Definitions + * + * (c) 2020 Martin Mareš + */ + +#define DMX_USB_VENDOR 0x4242 +#define DMX_USB_PRODUCT 0x0005 +#define DMX_USB_VERSION 0x0100 + +/* + * Endpoints: + * + * 0x01 = bulk endpoint + * FIXME + * Used for sending frames to BSB. Accepts BSB frames. Sender address and CRC + * will be recalculated. + */ diff --git a/dmx.c b/dmx.c new file mode 100644 index 0000000..d223819 --- /dev/null +++ b/dmx.c @@ -0,0 +1,202 @@ +/* + * DMX512 over USB (custom USB peripheral) + * + * (c) 2020 Martin Mares + */ + +#define LOCAL_DEBUG + +#include +#include +#include + +#include +#include + +#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); +} diff --git a/ursaryd.c b/ursaryd.c index 8df65df..7aa426e 100644 --- a/ursaryd.c +++ b/ursaryd.c @@ -4,7 +4,7 @@ * (c) 2014--2020 Martin Mares */ -#undef LOCAL_DEBUG +#define LOCAL_DEBUG #include #include @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -27,17 +28,18 @@ * 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" @@ -509,6 +511,70 @@ static void update_mpd_from_button(int button UNUSED, int on) } } +/*** 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; @@ -614,6 +680,7 @@ static void do_update(struct main_timer *t) update_default_sink(); #endif update_mpd(); + update_lights(); } void schedule_update(void) @@ -649,6 +716,12 @@ void notify_rotary(int rotary, int delta) 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); } @@ -673,6 +746,9 @@ void notify_button(int button, int on) 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; @@ -693,14 +769,16 @@ void notify_button(int button, int on) } } -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 ***/ @@ -732,6 +810,7 @@ static void daemon_body(struct daemon_params *dp) usb_init(); noct_init(); + dmx_init(); pulse_init(); mpd_init(); diff --git a/ursaryd.h b/ursaryd.h index 18abb1b..8b74334 100644 --- a/ursaryd.h +++ b/ursaryd.h @@ -36,6 +36,12 @@ enum ring_mode { RING_MODE_SINGLE_OFF, }; +/* dmx.c */ + +void dmx_init(void); +void dmx_set_pwm(uint index, uint val); +bool dmx_is_ready(void); + /* pulse.c */ extern char *pulse_default_sink_name; -- 2.39.2