diff --git a/plugins/usbdmx/AsyncPluginImpl.cpp b/plugins/usbdmx/AsyncPluginImpl.cpp index c7bd75bfa..2bd0a68a5 100644 --- a/plugins/usbdmx/AsyncPluginImpl.cpp +++ b/plugins/usbdmx/AsyncPluginImpl.cpp @@ -53,6 +53,7 @@ #include "plugins/usbdmx/ScanlimeFadecandy.h" #include "plugins/usbdmx/ScanlimeFadecandyFactory.h" #include "plugins/usbdmx/ShowJockeyDMXU1Factory.h" +#include "plugins/usbdmx/SiudiFactory.h" #include "plugins/usbdmx/SunliteFactory.h" #include "plugins/usbdmx/VellemanK8062.h" #include "plugins/usbdmx/VellemanK8062Factory.h" @@ -132,6 +133,7 @@ bool AsyncPluginImpl::Start() { m_widget_factories.push_back( new ScanlimeFadecandyFactory(m_usb_adaptor)); m_widget_factories.push_back(new ShowJockeyDMXU1Factory(m_usb_adaptor)); + m_widget_factories.push_back(new SiudiFactory(m_usb_adaptor)); m_widget_factories.push_back(new SunliteFactory(m_usb_adaptor)); m_widget_factories.push_back(new VellemanK8062Factory(m_usb_adaptor)); @@ -233,6 +235,12 @@ bool AsyncPluginImpl::NewWidget(ShowJockeyDMXU1 *widget) { "showjockey-dmx-u1-" + widget->SerialNumber())); } +bool AsyncPluginImpl::NewWidget(Siudi *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Nicolaudie SIUDI", "usbsiudi")); +} + bool AsyncPluginImpl::NewWidget(Sunlite *widget) { return StartAndRegisterDevice( widget, diff --git a/plugins/usbdmx/AsyncPluginImpl.h b/plugins/usbdmx/AsyncPluginImpl.h index 56282281c..7fec41109 100644 --- a/plugins/usbdmx/AsyncPluginImpl.h +++ b/plugins/usbdmx/AsyncPluginImpl.h @@ -85,6 +85,7 @@ class AsyncPluginImpl: public PluginImplInterface, public WidgetObserver { bool NewWidget(ola::usb::JaRuleWidget *widget); bool NewWidget(class ScanlimeFadecandy *widget); bool NewWidget(class ShowJockeyDMXU1 *widget); + bool NewWidget(class Siudi *widget); bool NewWidget(class Sunlite *widget); bool NewWidget(class VellemanK8062 *widget); diff --git a/plugins/usbdmx/Makefile.mk b/plugins/usbdmx/Makefile.mk index daf83a229..536bac3d0 100644 --- a/plugins/usbdmx/Makefile.mk +++ b/plugins/usbdmx/Makefile.mk @@ -49,6 +49,10 @@ plugins_usbdmx_libolausbdmxwidget_la_SOURCES = \ plugins/usbdmx/ShowJockeyDMXU1.h \ plugins/usbdmx/ShowJockeyDMXU1Factory.cpp \ plugins/usbdmx/ShowJockeyDMXU1Factory.h \ + plugins/usbdmx/Siudi.cpp \ + plugins/usbdmx/Siudi.h \ + plugins/usbdmx/SiudiFactory.cpp \ + plugins/usbdmx/SiudiFactory.h \ plugins/usbdmx/Sunlite.cpp \ plugins/usbdmx/Sunlite.h \ plugins/usbdmx/SunliteFactory.cpp \ diff --git a/plugins/usbdmx/README.md b/plugins/usbdmx/README.md index 79d0eb8b8..c65496087 100644 --- a/plugins/usbdmx/README.md +++ b/plugins/usbdmx/README.md @@ -15,6 +15,7 @@ This plugin supports various USB DMX devices including: * FX5 DMX * ShowJockey SJ-DMX-U1 * Sunlite USBDMX2 +* Nicoleaudie Sunlite intelligent USB DMX interface (SIUDI) (also ADJ MyDMX) * Velleman K8062. diff --git a/plugins/usbdmx/Siudi.cpp b/plugins/usbdmx/Siudi.cpp new file mode 100644 index 000000000..996f3e0db --- /dev/null +++ b/plugins/usbdmx/Siudi.cpp @@ -0,0 +1,155 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Siudi.cpp + * The synchronous SIUDI widgets. + * Copyright (C) 2023 Alexander Simon + */ + +#include "plugins/usbdmx/Siudi.h" + +#include +#include + +#include "libs/usb/LibUsbAdaptor.h" +#include "ola/Constants.h" +#include "ola/Logging.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using ola::usb::LibUsbAdaptor; + +namespace { + +static const uint8_t ENDPOINT = 2; +static const unsigned int TIMEOUT = 50; // 50ms is ok +enum {SIUDI_PACKET_SIZE = 512}; + +} // namespace + +// SiudiThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a SIUDI device in a separate thread. + */ +class SiudiThreadedSender: public ThreadedUsbSender { + public: + SiudiThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle); + + bool Start(); + +private: + LibUsbAdaptor* const m_adaptor; + uint8_t m_packet[SIUDI_PACKET_SIZE]; + libusb_device_handle* const m_usb_handle; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); +}; + +SiudiThreadedSender::SiudiThreadedSender( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *usb_handle) + : ThreadedUsbSender(usb_device, usb_handle), + m_adaptor(adaptor), m_usb_handle(usb_handle) { + memset(m_packet, 0x00, SIUDI_PACKET_SIZE); +} + +bool SiudiThreadedSender::Start() { + if (!ThreadedUsbSender::Start()) { + return false; + } + + // Read device info. This call takes about 270 ms. + // Discard the buffer as the format is currently unknown. + uint8_t buf[64]; + int ret = libusb_control_transfer(m_usb_handle, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + 0x3f, 0x00c4, 1, buf, 64, 500); + if (ret != 64) { + OLA_WARN << "Failed to read SIUDI information: " + << (ret < 0 ? LibUsbAdaptor::ErrorCodeToString(ret) : "Short read"); + return false; + } + + // Unstall the endpoint. The original software seems to call this regularly. + ret = libusb_clear_halt(m_usb_handle, ENDPOINT); + if (ret != 0) { + OLA_WARN << "Failed to reset SIUDI endpoint: " + << (ret < 0 ? LibUsbAdaptor::ErrorCodeToString(ret) : "Unknown"); + return false; + } + usleep(10000); + + return true; +} + +bool SiudiThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + for (unsigned int i = 0; i < buffer.Size(); i++) { + m_packet[i] = buffer.Get(i); + } + int transferred; + int r = m_adaptor->BulkTransfer( + handle, ENDPOINT, (unsigned char*) m_packet, + SIUDI_PACKET_SIZE, &transferred, TIMEOUT); + if (transferred != SIUDI_PACKET_SIZE) { + // not sure if this is fatal or not + OLA_WARN << "SIUDI driver failed to transfer all data"; + } + usleep(20000); + return r == 0; +} + +// SynchronousSiudi +// ----------------------------------------------------------------------------- + +SynchronousSiudi::SynchronousSiudi(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : Siudi(adaptor, usb_device) { +} + +bool SynchronousSiudi::Init() { + libusb_device_handle *usb_handle; + + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, 0, &usb_handle); + if (!ok) { + return false; + } + + std::auto_ptr sender( + new SiudiThreadedSender(m_adaptor, m_usb_device, usb_handle)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousSiudi::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/Siudi.h b/plugins/usbdmx/Siudi.h new file mode 100644 index 000000000..22c52e19b --- /dev/null +++ b/plugins/usbdmx/Siudi.h @@ -0,0 +1,76 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Siudi.h + * The synchronous SIUDI widgets. + * Copyright (C) 2023 Alexander Simon + */ + +#ifndef PLUGINS_USBDMX_SIUDI_H_ +#define PLUGINS_USBDMX_SIUDI_H_ + +#include +#include +#include "ola/DmxBuffer.h" +#include "ola/base/Macro.h" +#include "ola/thread/Mutex.h" +#include "plugins/usbdmx/Widget.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +class SiudiThreadedSender; + +/** + * @brief The interface for the Siudi Widgets + */ +class Siudi : public SimpleWidget { + public: + explicit Siudi(ola::usb::LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : SimpleWidget(adaptor, usb_device) { + } +}; + + +/** + * @brief An SIUDI widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousSiudi: public Siudi { + public: + /** + * @brief Create a new SynchronousSiudi. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + SynchronousSiudi(ola::usb::LibUsbAdaptor *adaptor, + libusb_device *usb_device); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousSiudi); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SIUDI_H_ diff --git a/plugins/usbdmx/SiudiFactory.cpp b/plugins/usbdmx/SiudiFactory.cpp new file mode 100644 index 000000000..1d8a0fe1f --- /dev/null +++ b/plugins/usbdmx/SiudiFactory.cpp @@ -0,0 +1,51 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * SiudiFactory.cpp + * The WidgetFactory for SIUDI widgets. + * Copyright (C) 2023 Alexander Simon + */ + +#include "plugins/usbdmx/SiudiFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" + +DECLARE_bool(use_async_libusb); + +namespace ola { +namespace plugin { +namespace usbdmx { + +const uint16_t SiudiFactory::VENDOR_ID = 0x6244; +const uint16_t SiudiFactory::COLD_PRODUCT_ID = 0x0300; +const uint16_t SiudiFactory::HOT_PRODUCT_ID = 0x0301; + +bool SiudiFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor == VENDOR_ID && + descriptor.idProduct == HOT_PRODUCT_ID) { + OLA_INFO << "Found a new Nicoleaudie SIUDI-6 device"; + Siudi *widget = NULL; + widget = new SynchronousSiudi(m_adaptor, usb_device); + return AddWidget(observer, widget); + } + return false; +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/SiudiFactory.h b/plugins/usbdmx/SiudiFactory.h new file mode 100644 index 000000000..720e47bbe --- /dev/null +++ b/plugins/usbdmx/SiudiFactory.h @@ -0,0 +1,62 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * SiudiFactory.h + * The WidgetFactory for SIUDI widgets. + * Copyright (C) 2023 Alexander Simon + */ + +#ifndef PLUGINS_USBDMX_SIUDIFACTORY_H_ +#define PLUGINS_USBDMX_SIUDIFACTORY_H_ + +#include "libs/usb/LibUsbAdaptor.h" +#include "ola/base/Macro.h" +#include "plugins/usbdmx/Siudi.h" +#include "plugins/usbdmx/Siudi.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Creates SunLite widgets. + */ +class SiudiFactory : public BaseWidgetFactory { + public: + explicit SiudiFactory(ola::usb::LibUsbAdaptor *adaptor) + : BaseWidgetFactory("SiudiFactory"), + m_adaptor(adaptor) {} + + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); + + private: + ola::usb::LibUsbAdaptor* const m_adaptor; + + // The product ID for widgets that are missing their firmware. + static const uint16_t COLD_PRODUCT_ID; + // The product ID for widgets with the firmware. + static const uint16_t HOT_PRODUCT_ID; + static const uint16_t VENDOR_ID; + + DISALLOW_COPY_AND_ASSIGN(SiudiFactory); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SIUDIFACTORY_H_ diff --git a/plugins/usbdmx/SyncPluginImpl.cpp b/plugins/usbdmx/SyncPluginImpl.cpp index 99c5a19c3..3b759c444 100644 --- a/plugins/usbdmx/SyncPluginImpl.cpp +++ b/plugins/usbdmx/SyncPluginImpl.cpp @@ -50,6 +50,8 @@ #include "plugins/usbdmx/GenericDevice.h" #include "plugins/usbdmx/ShowJockeyDMXU1.h" #include "plugins/usbdmx/ShowJockeyDMXU1Factory.h" +#include "plugins/usbdmx/Siudi.h" +#include "plugins/usbdmx/SiudiFactory.h" #include "plugins/usbdmx/Sunlite.h" #include "plugins/usbdmx/SunliteFactory.h" #include "plugins/usbdmx/VellemanK8062.h" @@ -81,6 +83,7 @@ SyncPluginImpl::SyncPluginImpl(PluginAdaptor *plugin_adaptor, m_preferences)); m_widget_factories.push_back(new ScanlimeFadecandyFactory(&m_usb_adaptor)); m_widget_factories.push_back(new ShowJockeyDMXU1Factory(&m_usb_adaptor)); + m_widget_factories.push_back(new SiudiFactory(&m_usb_adaptor)); m_widget_factories.push_back(new SunliteFactory(&m_usb_adaptor)); m_widget_factories.push_back(new VellemanK8062Factory(&m_usb_adaptor)); } @@ -192,6 +195,12 @@ bool SyncPluginImpl::NewWidget(ShowJockeyDMXU1 *widget) { "showjockey-dmx-u1-" + widget->SerialNumber())); } +bool SyncPluginImpl::NewWidget(Siudi *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Sunlite SIUDI", "usbsiudi")); +} + bool SyncPluginImpl::NewWidget(Sunlite *widget) { return StartAndRegisterDevice( widget, diff --git a/plugins/usbdmx/SyncPluginImpl.h b/plugins/usbdmx/SyncPluginImpl.h index 92694b546..df01e04c8 100644 --- a/plugins/usbdmx/SyncPluginImpl.h +++ b/plugins/usbdmx/SyncPluginImpl.h @@ -79,6 +79,7 @@ class SyncPluginImpl: public PluginImplInterface, public WidgetObserver { bool NewWidget(ola::usb::JaRuleWidget *widget); bool NewWidget(class ScanlimeFadecandy *widget); bool NewWidget(class ShowJockeyDMXU1 *widget); + bool NewWidget(class Siudi *widget); bool NewWidget(class Sunlite *widget); bool NewWidget(class VellemanK8062 *widget); @@ -90,6 +91,7 @@ class SyncPluginImpl: public PluginImplInterface, public WidgetObserver { void WidgetRemoved(OLA_UNUSED ola::usb::JaRuleWidget *widget) {} void WidgetRemoved(OLA_UNUSED class ScanlimeFadecandy *widget) {} void WidgetRemoved(OLA_UNUSED class ShowJockeyDMXU1 *widget) {} + void WidgetRemoved(OLA_UNUSED class Siudi *widget) {} void WidgetRemoved(OLA_UNUSED class Sunlite *widget) {} void WidgetRemoved(OLA_UNUSED class VellemanK8062 *widget) {} diff --git a/plugins/usbdmx/SynchronizedWidgetObserver.h b/plugins/usbdmx/SynchronizedWidgetObserver.h index 227551f04..0f3bf009d 100644 --- a/plugins/usbdmx/SynchronizedWidgetObserver.h +++ b/plugins/usbdmx/SynchronizedWidgetObserver.h @@ -79,6 +79,10 @@ class SynchronizedWidgetObserver : public WidgetObserver { return DispatchNewWidget(widget); } + bool NewWidget(class Siudi *widget) { + return DispatchNewWidget(widget); + } + bool NewWidget(class Sunlite *widget) { return DispatchNewWidget(widget); } diff --git a/plugins/usbdmx/UsbDmxPlugin.h b/plugins/usbdmx/UsbDmxPlugin.h index 8e7e40346..8d375993d 100644 --- a/plugins/usbdmx/UsbDmxPlugin.h +++ b/plugins/usbdmx/UsbDmxPlugin.h @@ -43,7 +43,7 @@ namespace usbdmx { * - Eurolite DMX USB Pro. * - Eurolite DMX USB Pro MK2. * - Scanlime's Fadecandy. - * - Sunlite. + * - Sunlite USBDMX2 and SIUDI. * - Velleman K8062. */ class UsbDmxPlugin: public ola::Plugin { diff --git a/plugins/usbdmx/WidgetFactory.h b/plugins/usbdmx/WidgetFactory.h index a7a00c5b7..065cc478c 100644 --- a/plugins/usbdmx/WidgetFactory.h +++ b/plugins/usbdmx/WidgetFactory.h @@ -122,6 +122,15 @@ class WidgetObserver { */ virtual bool NewWidget(class ScanlimeFadecandy *widget) = 0; + /** + * @brief Called when a new SIUDI device is added. + * @param widget the new Widget, ownership is not transferred but the object + * may be used until the corresponding WidgetRemoved() call is made. + * @returns true if the widget has been claimed, false if the widget was + * ignored. + */ + virtual bool NewWidget(class Siudi *widget) = 0; + /** * @brief Called when a new Sunlite is added. * @param widget the new Widget, ownership is not transferred but the object