diff --git a/plugins/usbdmx/AnymauDMX.cpp b/plugins/usbdmx/AnymauDMX.cpp index 240ce2f979..92408f4fe2 100644 --- a/plugins/usbdmx/AnymauDMX.cpp +++ b/plugins/usbdmx/AnymauDMX.cpp @@ -109,7 +109,8 @@ bool SynchronousAnymauDMX::Init() { return true; } -bool SynchronousAnymauDMX::SendDMX(const DmxBuffer &buffer) { +bool SynchronousAnymauDMX::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender.get() ? m_sender->SendDMX(buffer) : false; } @@ -135,7 +136,7 @@ class AnymaAsyncUsbSender : public AsyncUsbSender { return ok ? usb_handle : NULL; } - bool PerformTransfer(const DmxBuffer &buffer) { + bool PerformTransfer(const DmxBuffer &buffer, unsigned int portId) { m_adaptor->FillControlSetup( m_control_setup_buffer, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | @@ -173,7 +174,8 @@ bool AsynchronousAnymauDMX::Init() { return m_sender->Init(); } -bool AsynchronousAnymauDMX::SendDMX(const DmxBuffer &buffer) { +bool AsynchronousAnymauDMX::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender->SendDMX(buffer); } } // namespace usbdmx diff --git a/plugins/usbdmx/AnymauDMX.h b/plugins/usbdmx/AnymauDMX.h index 26bf8cc621..5b46166eb4 100644 --- a/plugins/usbdmx/AnymauDMX.h +++ b/plugins/usbdmx/AnymauDMX.h @@ -85,7 +85,7 @@ class SynchronousAnymauDMX: public AnymauDMX { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; @@ -110,7 +110,7 @@ class AsynchronousAnymauDMX : public AnymauDMX { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; diff --git a/plugins/usbdmx/AsyncUsbSender.cpp b/plugins/usbdmx/AsyncUsbSender.cpp index 22e8a12f89..1ee8fb2c24 100644 --- a/plugins/usbdmx/AsyncUsbSender.cpp +++ b/plugins/usbdmx/AsyncUsbSender.cpp @@ -31,27 +31,29 @@ using ola::usb::LibUsbAdaptor; AsyncUsbSender::AsyncUsbSender(LibUsbAdaptor *adaptor, libusb_device *usb_device) - : AsyncUsbTransceiverBase(adaptor, usb_device), - m_pending_tx(false) { + : AsyncUsbTransceiverBase(adaptor, usb_device) { } AsyncUsbSender::~AsyncUsbSender() { m_adaptor->Close(m_usb_handle); } -bool AsyncUsbSender::SendDMX(const DmxBuffer &buffer) { +bool AsyncUsbSender::SendDMX(const DmxBuffer &buffer, unsigned int portId) { if (!m_usb_handle) { OLA_WARN << "AsyncUsbSender hasn't been initialized"; return false; } ola::thread::MutexLocker locker(&m_mutex); if (m_transfer_state == IDLE) { - PerformTransfer(buffer); + PerformTransfer(buffer, portId); } else { // Buffer incoming data so we can send it when the outstanding transfers // complete. - m_pending_tx = true; - m_tx_buffer.Set(buffer); + if (m_tx_buffers.count(portId)) { + m_tx_buffers[portId].Set(buffer); + } else { + m_tx_buffers.insert(std::pair(portId, buffer)); + } } return true; } @@ -77,9 +79,10 @@ void AsyncUsbSender::TransferComplete(struct libusb_transfer *transfer) { PostTransferHook(); - if ((m_transfer_state == IDLE) && m_pending_tx) { - m_pending_tx = false; - PerformTransfer(m_tx_buffer); + if ((m_transfer_state == IDLE) && m_tx_buffers.size()) { + std::map::iterator it = m_tx_buffers.begin(); + PerformTransfer(it->second, it->first); + m_tx_buffers.erase(it->first); } } } // namespace usbdmx diff --git a/plugins/usbdmx/AsyncUsbSender.h b/plugins/usbdmx/AsyncUsbSender.h index 24a35b5ade..8b33430236 100644 --- a/plugins/usbdmx/AsyncUsbSender.h +++ b/plugins/usbdmx/AsyncUsbSender.h @@ -22,6 +22,7 @@ #define PLUGINS_USBDMX_ASYNCUSBSENDER_H_ #include +#include #include "AsyncUsbTransceiverBase.h" #include "libs/usb/LibUsbAdaptor.h" @@ -59,7 +60,7 @@ class AsyncUsbSender: public AsyncUsbTransceiverBase { * @param buffer the DMX data to send. * @returns the value of PerformTransfer(). */ - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); /** * @brief Called from the libusb callback when the asynchronous transfer @@ -78,7 +79,8 @@ class AsyncUsbSender: public AsyncUsbTransceiverBase { * FillControlTransfer() / FillBulkTransfer() as appropriate and then call * SubmitTransfer(). */ - virtual bool PerformTransfer(const DmxBuffer &buffer) = 0; + virtual bool PerformTransfer(const DmxBuffer &buffer, + unsigned int portId) = 0; /** * @brief Called when the transfer completes. @@ -92,11 +94,10 @@ class AsyncUsbSender: public AsyncUsbTransceiverBase { * @brief Check if there is a pending transfer. * @returns true if there is a transfer in progress, false otherwise. */ - bool TransferPending() const { return m_pending_tx; } + bool TransferPending() const { return (bool)(m_tx_buffers.size()); } private: - DmxBuffer m_tx_buffer; // GUARDED_BY(m_mutex); - bool m_pending_tx; // GUARDED_BY(m_mutex); + std::map m_tx_buffers; // GUARDED_BY(m_mutex); DISALLOW_COPY_AND_ASSIGN(AsyncUsbSender); }; diff --git a/plugins/usbdmx/DMXCProjectsNodleU1.cpp b/plugins/usbdmx/DMXCProjectsNodleU1.cpp index 15408668bb..f0d52eb47a 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1.cpp +++ b/plugins/usbdmx/DMXCProjectsNodleU1.cpp @@ -54,6 +54,7 @@ static const int CONFIGURATION = 1; static const int INTERFACE = 0; static const unsigned int DATABLOCK_SIZE = 33; +static const unsigned int DATABLOCK_MAX_SIZE = 64; /* * @brief Send chosen mode to the DMX device @@ -104,13 +105,31 @@ libusb_device_handle *OpenDMXCProjectsNodleU1Widget( return NULL; } - // this device only has one configuration - ret_code = adaptor->SetConfiguration(usb_handle, CONFIGURATION); - if (ret_code) { - OLA_WARN << "Nodle set config failed, with libusb error code " - << adaptor->ErrorCodeToString(ret_code); - adaptor->Close(usb_handle); - return NULL; + // If we are connected to a RP2040-based device, we cannot + // "Set the configuration", because the additional CDC ACM interface might + // be running that would block changing the USB device's config + bool rp2040 = false; + + libusb_device_descriptor descriptor; + ola::usb::LibUsbAdaptor::DeviceInformation info; + if (!(adaptor->GetDeviceDescriptor(usb_device, &descriptor)) && + (adaptor->GetDeviceInfo(usb_device, descriptor, &info)) && + (info.serial.find("RP2040_", 0) != std::string::npos)) + { + OLA_INFO << "Found a RP2040-based device, " + << "will skip (re) setting the USB configuration"; + rp2040 = true; + } + + if (!rp2040) { + // this device only has one configuration + ret_code = adaptor->SetConfiguration(usb_handle, CONFIGURATION); + if (ret_code) { + OLA_WARN << "Nodle set config failed, with libusb error code " + << adaptor->ErrorCodeToString(ret_code); + adaptor->Close(usb_handle); + return NULL; + } } if (adaptor->ClaimInterface(usb_handle, INTERFACE)) { @@ -263,12 +282,19 @@ SynchronousDMXCProjectsNodleU1::SynchronousDMXCProjectsNodleU1( libusb_device *usb_device, PluginAdaptor *plugin_adaptor, const string &serial, - unsigned int mode) - : DMXCProjectsNodleU1(adaptor, usb_device, plugin_adaptor, serial, mode), + unsigned int mode, + unsigned int ins, + unsigned int outs) + : DMXCProjectsNodleU1(adaptor, usb_device, plugin_adaptor, serial, mode, + ins, outs), m_usb_device(usb_device) { + OLA_DEBUG << "SynchronousDMXCProjectsNodleU1 CTOR"; } bool SynchronousDMXCProjectsNodleU1::Init() { + + OLA_DEBUG << "SynchronousDMXCProjectsNodleU1 INIT"; + libusb_device_handle *usb_handle = OpenDMXCProjectsNodleU1Widget( m_adaptor, m_usb_device); @@ -301,7 +327,8 @@ bool SynchronousDMXCProjectsNodleU1::Init() { return true; } -bool SynchronousDMXCProjectsNodleU1::SendDMX(const DmxBuffer &buffer) { +bool SynchronousDMXCProjectsNodleU1::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { if (m_sender.get()) { return m_sender->SendDMX(buffer); } else { @@ -402,7 +429,7 @@ class DMXCProjectsNodleU1AsyncUsbSender : public AsyncUsbSender { return handle; } - bool PerformTransfer(const DmxBuffer &buffer); + bool PerformTransfer(const DmxBuffer &buffer, unsigned int portId = 0); void PostTransferHook(); @@ -412,15 +439,16 @@ class DMXCProjectsNodleU1AsyncUsbSender : public AsyncUsbSender { // This tracks where we are in m_tx_buffer. A value of 0 means we're at the // start of a DMX frame. unsigned int m_buffer_offset; - uint8_t m_packet[DATABLOCK_SIZE]; + unsigned int m_portId; + uint8_t m_packet[DATABLOCK_MAX_SIZE]; bool ContinueTransfer(); bool SendInitialChunk(const DmxBuffer &buffer); - bool SendChunk() { + bool SendChunk(unsigned int size) { FillInterruptTransfer(WRITE_ENDPOINT, m_packet, - DATABLOCK_SIZE, URB_TIMEOUT_MS); + size, URB_TIMEOUT_MS); return (SubmitTransfer() == 0); } @@ -428,8 +456,9 @@ class DMXCProjectsNodleU1AsyncUsbSender : public AsyncUsbSender { }; bool DMXCProjectsNodleU1AsyncUsbSender::PerformTransfer( - const DmxBuffer &buffer) { + const DmxBuffer &buffer, unsigned int portId) { if (m_buffer_offset == 0) { + m_portId = portId; return SendInitialChunk(buffer); } // Otherwise we're part way through a transfer, do nothing. @@ -452,32 +481,61 @@ void DMXCProjectsNodleU1AsyncUsbSender::PostTransferHook() { } bool DMXCProjectsNodleU1AsyncUsbSender::ContinueTransfer() { - unsigned int length = 32; + if (!m_portId) { + unsigned int length = 32; + + m_packet[0] = m_buffer_offset / 32; + + m_tx_buffer.GetRange(m_buffer_offset, m_packet + 1, &length); + memset(m_packet + 1 + length, 0, 32 - length); + m_buffer_offset += length; + return (SendChunk(DATABLOCK_SIZE) == 0); + } else { + unsigned int length = 60; - m_packet[0] = m_buffer_offset / 32; + m_packet[0] = 32; + m_packet[1] = ((m_portId << 4) & 0xF0) | ((m_buffer_offset / 60) & 0x0F); - m_tx_buffer.GetRange(m_buffer_offset, m_packet + 1, &length); - memset(m_packet + 1 + length, 0, 32 - length); - m_buffer_offset += length; - return (SendChunk() == 0); + m_tx_buffer.GetRange(m_buffer_offset, m_packet + 2, &length); + m_buffer_offset += length; + return (SendChunk(length + 2) == 0); + } } bool DMXCProjectsNodleU1AsyncUsbSender::SendInitialChunk( const DmxBuffer &buffer) { - unsigned int length = 32; + OLA_DEBUG << "SendInitialChunk for portId " << m_portId; + + // Copy the incoming buffer to our m_tx_buffer m_tx_buffer.SetRange(0, buffer.GetRaw(), buffer.Size()); - m_packet[0] = 0; - m_tx_buffer.GetRange(0, m_packet + 1, &length); - memset(m_packet + 1 + length, 0, 32 - length); + if (!m_portId) { + unsigned int length = 32; - unsigned int slots_sent = length; - if (slots_sent < m_tx_buffer.Size()) { - // There are more frames to send. - m_buffer_offset = slots_sent; + m_packet[0] = 0; + m_tx_buffer.GetRange(0, m_packet + 1, &length); + memset(m_packet + 1 + length, 0, 32 - length); + + unsigned int slots_sent = length; + if (slots_sent < m_tx_buffer.Size()) { + // There are more frames to send. + m_buffer_offset = slots_sent; + } + return (SendChunk(DATABLOCK_SIZE) == 0); + } else { + unsigned int length = 60; + m_packet[0] = 32; // Command code for advanced data (universe1-15) + m_packet[1] = m_portId << 4 & 0xF0; // Universe are the first 4 bits, offset the remaining 4 + m_tx_buffer.GetRange(0, m_packet + 2, &length); + + unsigned int slots_sent = length; + if (slots_sent < m_tx_buffer.Size()) { + // There are more frames to send. + m_buffer_offset = slots_sent; + } + return (SendChunk(slots_sent + 2) == 0); } - return (SendChunk() == 0); } // AsynchronousDMXCProjectsNodleU1 @@ -488,8 +546,13 @@ AsynchronousDMXCProjectsNodleU1::AsynchronousDMXCProjectsNodleU1( libusb_device *usb_device, PluginAdaptor *plugin_adaptor, const string &serial, - unsigned int mode) - : DMXCProjectsNodleU1(adaptor, usb_device, plugin_adaptor, serial, mode) { + unsigned int mode, + unsigned int ins, + unsigned int outs) + : DMXCProjectsNodleU1(adaptor, usb_device, plugin_adaptor, serial, mode, + ins, outs) { + OLA_DEBUG << "AsynchronousDMXCProjectsNodleU1 CTOR"; + if (mode & OUTPUT_ENABLE_MASK) { // output port active m_sender.reset(new DMXCProjectsNodleU1AsyncUsbSender(m_adaptor, usb_device, mode)); @@ -505,6 +568,9 @@ AsynchronousDMXCProjectsNodleU1::AsynchronousDMXCProjectsNodleU1( bool AsynchronousDMXCProjectsNodleU1::Init() { bool ok = true; + + OLA_DEBUG << "AsynchronousDMXCProjectsNodleU1 INIT"; + if (m_sender.get()) { ok &= m_sender->Init(); } @@ -522,8 +588,9 @@ bool AsynchronousDMXCProjectsNodleU1::Init() { return ok; } -bool AsynchronousDMXCProjectsNodleU1::SendDMX(const DmxBuffer &buffer) { - return m_sender.get() ? m_sender->SendDMX(buffer) : false; +bool AsynchronousDMXCProjectsNodleU1::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { + return m_sender.get() ? m_sender->SendDMX(buffer, portId) : false; } void AsynchronousDMXCProjectsNodleU1::SetDmxCallback( diff --git a/plugins/usbdmx/DMXCProjectsNodleU1.h b/plugins/usbdmx/DMXCProjectsNodleU1.h index dac59391f2..90a977a4ec 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1.h +++ b/plugins/usbdmx/DMXCProjectsNodleU1.h @@ -46,10 +46,14 @@ class DMXCProjectsNodleU1: public SimpleWidget { libusb_device *usb_device, PluginAdaptor *plugin_adaptor, const std::string &serial, - unsigned int mode) + unsigned int mode, + unsigned int ins, + unsigned int outs) : SimpleWidget(adaptor, usb_device), m_serial(serial), m_mode(mode), + m_ins(ins), + m_outs(outs), m_plugin_adaptor(plugin_adaptor) { } @@ -69,6 +73,22 @@ class DMXCProjectsNodleU1: public SimpleWidget { return m_mode; } + /** + * @brief Get the number of inputs suppported by this widget. + * @returns The number of inputs. + */ + unsigned int Ins() const { + return m_ins; + } + + /** + * @brief Get the number of outouts suppported by this widget. + * @returns The number of outouts. + */ + unsigned int Outs() const { + return m_outs; + } + virtual void SetDmxCallback(Callback0 *callback) = 0; virtual const DmxBuffer &GetDmxInBuffer() = 0; @@ -81,9 +101,12 @@ class DMXCProjectsNodleU1: public SimpleWidget { private: std::string m_serial; + std::string m_product; protected: unsigned int m_mode; + unsigned int m_ins; + unsigned int m_outs; PluginAdaptor *m_plugin_adaptor; }; @@ -99,6 +122,7 @@ class SynchronousDMXCProjectsNodleU1: public DMXCProjectsNodleU1 { * @param adaptor the LibUsbAdaptor to use. * @param usb_device the libusb_device to use for the widget. * @param plugin_adaptor the PluginAdaptor used to execute callbacks + * @param product the product name of this widget * @param serial the serial number of this widget * @param mode the send/receive mode to be used by the widget. */ @@ -106,11 +130,13 @@ class SynchronousDMXCProjectsNodleU1: public DMXCProjectsNodleU1 { libusb_device *usb_device, PluginAdaptor *plugin_adaptor, const std::string &serial, - unsigned int mode); + unsigned int mode, + unsigned int ins, + unsigned int outs); bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); void SetDmxCallback(Callback0 *callback); const DmxBuffer &GetDmxInBuffer(); @@ -133,6 +159,7 @@ class AsynchronousDMXCProjectsNodleU1 : public DMXCProjectsNodleU1 { * @param adaptor the LibUsbAdaptor to use. * @param usb_device the libusb_device to use for the widget. * @param plugin_adaptor the PluginAdaptor used to execute callbacks + * @param product the product name of this widget * @param serial the serial number of this widget * @param mode the send/receive mode to be used by the widget. */ @@ -140,11 +167,13 @@ class AsynchronousDMXCProjectsNodleU1 : public DMXCProjectsNodleU1 { libusb_device *usb_device, PluginAdaptor *plugin_adaptor, const std::string &serial, + unsigned int ins, + unsigned int outs, unsigned int mode); bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); void SetDmxCallback(Callback0 *callback); const DmxBuffer &GetDmxInBuffer(); diff --git a/plugins/usbdmx/DMXCProjectsNodleU1Device.cpp b/plugins/usbdmx/DMXCProjectsNodleU1Device.cpp index 531dd94653..465d753dd6 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1Device.cpp +++ b/plugins/usbdmx/DMXCProjectsNodleU1Device.cpp @@ -36,28 +36,63 @@ DMXCProjectsNodleU1Device::DMXCProjectsNodleU1Device( PluginAdaptor *plugin_adaptor) : Device(owner, device_name), m_device_id(device_id), - m_out_port(), - m_in_port() { - unsigned int mode = widget->Mode(); - - if (mode & DMXCProjectsNodleU1::OUTPUT_ENABLE_MASK) { // output port active - m_out_port.reset(new GenericOutputPort(this, 0, widget)); - } - - if (mode & DMXCProjectsNodleU1::INPUT_ENABLE_MASK) { // input port active - m_in_port.reset(new DMXCProjectsNodleU1InputPort(this, 0, plugin_adaptor, - widget)); - } + m_widget(widget), + m_plugin_adaptor(plugin_adaptor) { } bool DMXCProjectsNodleU1Device::StartHook() { - if (m_out_port.get()) { - AddPort(m_out_port.release()); + unsigned int mode = m_widget->Mode(); + unsigned int ins = m_widget->Ins(); + unsigned int outs = m_widget->Outs(); + + bool ok = true; + + OLA_DEBUG << "StartHook, will create " << ins << " INs and " << outs << " OUTs"; + + if (ins != 1) { + for (unsigned i = 0; i < ins; i++) { + OLA_DEBUG << "IN " << i; + DMXCProjectsNodleU1InputPort *port = new DMXCProjectsNodleU1InputPort( + this, i, m_plugin_adaptor, + m_widget); + if (!AddPort(port)) { + OLA_DEBUG << "FAILED"; + delete port; + ok = false; + } + } + } else if (mode & DMXCProjectsNodleU1::INPUT_ENABLE_MASK) { + // input port active + DMXCProjectsNodleU1InputPort *port = new DMXCProjectsNodleU1InputPort( + this, 0, m_plugin_adaptor, + m_widget); + if (!AddPort(port)) { + delete port; + ok = false; + } } - if (m_in_port.get()) { - AddPort(m_in_port.release()); + + if (outs != 1) { + for (unsigned i = 0; i < outs; i++) { + OLA_DEBUG << "OUT " << i; + GenericOutputPort *port = new GenericOutputPort(this, i, m_widget); + if (!AddPort(port)) { + OLA_DEBUG << "FAILED"; + delete port; + ok = false; + } + } + } else if (mode & DMXCProjectsNodleU1::OUTPUT_ENABLE_MASK) { + // output port active + GenericOutputPort *port = new GenericOutputPort(this, 0, m_widget); + if (!AddPort(port)) { + delete port; + ok = false; + } } - return true; + + OLA_DEBUG << "StartHook will return " << ok; + return ok; } } // namespace usbdmx } // namespace plugin diff --git a/plugins/usbdmx/DMXCProjectsNodleU1Device.h b/plugins/usbdmx/DMXCProjectsNodleU1Device.h index 842644487c..031d6183ab 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1Device.h +++ b/plugins/usbdmx/DMXCProjectsNodleU1Device.h @@ -24,6 +24,7 @@ #include #include #include "ola/base/Macro.h" +#include "ola/Logging.h" #include "olad/Device.h" #include "plugins/usbdmx/DMXCProjectsNodleU1.h" @@ -56,13 +57,15 @@ class DMXCProjectsNodleU1Device: public Device { return m_device_id; } + bool AllowMultiPortPatching() const { return true; } + protected: bool StartHook(); private: const std::string m_device_id; - std::auto_ptr m_out_port; - std::auto_ptr m_in_port; + DMXCProjectsNodleU1 *m_widget; + PluginAdaptor*m_plugin_adaptor; DISALLOW_COPY_AND_ASSIGN(DMXCProjectsNodleU1Device); }; diff --git a/plugins/usbdmx/DMXCProjectsNodleU1Factory.cpp b/plugins/usbdmx/DMXCProjectsNodleU1Factory.cpp index 88bfdf7d11..fa2ac03445 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1Factory.cpp +++ b/plugins/usbdmx/DMXCProjectsNodleU1Factory.cpp @@ -33,15 +33,27 @@ namespace usbdmx { const uint16_t DMXCProjectsNodleU1Factory::VENDOR_ID = 0x16d0; const uint16_t DMXCProjectsNodleU1Factory::PRODUCT_ID = 0x0830; +// Those IDs are not officially registered or "free for everyone" +// but there seems to be one clone using them. +// See also: https://github.com/mcallegari/qlcplus/blob/3b69452d0679333c6e60ff0928c20f2107ccc81f/plugins/hid/hiddmxdevice.h#L34 +const uint16_t DMXCProjectsNodleU1Factory::VENDOR_ID_FX5 = 0x16c0; +const uint16_t DMXCProjectsNodleU1Factory::PRODUCT_ID_FX5 = 0x088b; + bool DMXCProjectsNodleU1Factory::DeviceAdded( WidgetObserver *observer, libusb_device *usb_device, const struct libusb_device_descriptor &descriptor) { - if (descriptor.idVendor != VENDOR_ID || descriptor.idProduct != PRODUCT_ID) { + OLA_DEBUG << "Factory, DeviceAdded"; + if ( + ((descriptor.idVendor != VENDOR_ID) && + (descriptor.idVendor != VENDOR_ID_FX5)) || + ((descriptor.idProduct != PRODUCT_ID) && + (descriptor.idProduct != PRODUCT_ID_FX5)) + ) { return false; } - OLA_INFO << "Found a new Nodle U1 device"; + OLA_INFO << "Found a new Nodle U1 or clone device"; ola::usb::LibUsbAdaptor::DeviceInformation info; if (!m_adaptor->GetDeviceInfo(usb_device, descriptor, &info)) { return false; @@ -49,6 +61,16 @@ bool DMXCProjectsNodleU1Factory::DeviceAdded( OLA_INFO << "Nodle U1 serial: " << info.serial; + // Check if it's a rp2040-dongle that supports multiple universes + unsigned int ins = 1; // Input universes + unsigned int outs = 1; // Output universes + if (info.serial.find('RP2040_') != std::string::npos) { + // The rp2040-dongle supports at most 8 inputs or 16 outputs + // However, inputs are not yet implemented + ins = 0; + outs = 16; + } + if (m_preferences->SetDefaultValue( "nodle-" + info.serial + "-mode", UIntValidator(DMXCProjectsNodleU1::NODLE_MIN_MODE, @@ -68,12 +90,12 @@ bool DMXCProjectsNodleU1Factory::DeviceAdded( DMXCProjectsNodleU1 *widget = NULL; if (FLAGS_use_async_libusb) { widget = new AsynchronousDMXCProjectsNodleU1(m_adaptor, usb_device, - m_plugin_adaptor, info.serial, - mode); + m_plugin_adaptor, info.serial, + mode, ins, outs); } else { widget = new SynchronousDMXCProjectsNodleU1(m_adaptor, usb_device, m_plugin_adaptor, info.serial, - mode); + mode, ins, outs); } return AddWidget(observer, widget); } diff --git a/plugins/usbdmx/DMXCProjectsNodleU1Factory.h b/plugins/usbdmx/DMXCProjectsNodleU1Factory.h index 7df0b280a4..1b09084a42 100644 --- a/plugins/usbdmx/DMXCProjectsNodleU1Factory.h +++ b/plugins/usbdmx/DMXCProjectsNodleU1Factory.h @@ -59,6 +59,8 @@ class DMXCProjectsNodleU1Factory : static const uint16_t VENDOR_ID; static const uint16_t PRODUCT_ID; + static const uint16_t VENDOR_ID_FX5; + static const uint16_t PRODUCT_ID_FX5; DISALLOW_COPY_AND_ASSIGN(DMXCProjectsNodleU1Factory); }; diff --git a/plugins/usbdmx/EurolitePro.cpp b/plugins/usbdmx/EurolitePro.cpp index 31781ed1e5..d9a569a48d 100644 --- a/plugins/usbdmx/EurolitePro.cpp +++ b/plugins/usbdmx/EurolitePro.cpp @@ -184,7 +184,8 @@ bool SynchronousEurolitePro::Init() { return true; } -bool SynchronousEurolitePro::SendDMX(const DmxBuffer &buffer) { +bool SynchronousEurolitePro::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender.get() ? m_sender->SendDMX(buffer) : false; } @@ -213,7 +214,7 @@ class EuroliteProAsyncUsbSender : public AsyncUsbSender { return ok ? usb_handle : NULL; } - bool PerformTransfer(const DmxBuffer &buffer) { + bool PerformTransfer(const DmxBuffer &buffer, unsigned int port) { CreateFrame(buffer, m_tx_frame); FillBulkTransfer(ENDPOINT, m_tx_frame, EUROLITE_PRO_FRAME_SIZE, URB_TIMEOUT_MS); @@ -241,7 +242,8 @@ bool AsynchronousEurolitePro::Init() { return m_sender->Init(); } -bool AsynchronousEurolitePro::SendDMX(const DmxBuffer &buffer) { +bool AsynchronousEurolitePro::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender->SendDMX(buffer); } } // namespace usbdmx diff --git a/plugins/usbdmx/EurolitePro.h b/plugins/usbdmx/EurolitePro.h index 8b8516cb96..4ec4cee51d 100644 --- a/plugins/usbdmx/EurolitePro.h +++ b/plugins/usbdmx/EurolitePro.h @@ -85,7 +85,7 @@ class SynchronousEurolitePro: public EurolitePro { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; @@ -110,7 +110,7 @@ class AsynchronousEurolitePro: public EurolitePro { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; diff --git a/plugins/usbdmx/GenericOutputPort.cpp b/plugins/usbdmx/GenericOutputPort.cpp index fdff9ba6ee..399a5f7cf1 100644 --- a/plugins/usbdmx/GenericOutputPort.cpp +++ b/plugins/usbdmx/GenericOutputPort.cpp @@ -33,11 +33,13 @@ GenericOutputPort::GenericOutputPort(Device *parent, WidgetInterface *widget) : BasicOutputPort(parent, id), m_widget(widget) { + OLA_DEBUG << "NEW GENERICOUTPUTPORT ID: " << id; + m_description = "Output " + std::to_string(id); } bool GenericOutputPort::WriteDMX(const DmxBuffer &buffer, OLA_UNUSED uint8_t priority) { - m_widget->SendDMX(buffer); + m_widget->SendDMX(buffer, this->PortId()); return true; } } // namespace usbdmx diff --git a/plugins/usbdmx/GenericOutputPort.h b/plugins/usbdmx/GenericOutputPort.h index 8cdc03b42a..e18c1bb6d4 100644 --- a/plugins/usbdmx/GenericOutputPort.h +++ b/plugins/usbdmx/GenericOutputPort.h @@ -51,10 +51,11 @@ class GenericOutputPort: public BasicOutputPort { bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); - std::string Description() const { return ""; } + std::string Description() const { return m_description; } private: class WidgetInterface* const m_widget; + std::string m_description; DISALLOW_COPY_AND_ASSIGN(GenericOutputPort); }; diff --git a/plugins/usbdmx/JaRuleDevice.h b/plugins/usbdmx/JaRuleDevice.h index c2cc4af0d6..be987019c5 100644 --- a/plugins/usbdmx/JaRuleDevice.h +++ b/plugins/usbdmx/JaRuleDevice.h @@ -53,6 +53,10 @@ class JaRuleDevice: public Device { return m_device_id; } + bool AllowMultiPortPatching() const { + return true; + } + protected: bool StartHook(); diff --git a/plugins/usbdmx/ScanlimeFadecandy.cpp b/plugins/usbdmx/ScanlimeFadecandy.cpp index 375539cba4..e2938a85da 100644 --- a/plugins/usbdmx/ScanlimeFadecandy.cpp +++ b/plugins/usbdmx/ScanlimeFadecandy.cpp @@ -275,7 +275,8 @@ bool SynchronousScanlimeFadecandy::Init() { return true; } -bool SynchronousScanlimeFadecandy::SendDMX(const DmxBuffer &buffer) { +bool SynchronousScanlimeFadecandy::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender.get() ? m_sender->SendDMX(buffer) : false; } @@ -290,7 +291,7 @@ class FadecandyAsyncUsbSender : public AsyncUsbSender { libusb_device_handle* SetupHandle(); - bool PerformTransfer(const DmxBuffer &buffer); + bool PerformTransfer(const DmxBuffer &buffer, unsigned int portId); private: fadecandy_packet m_data_packets[PACKETS_PER_UPDATE]; @@ -312,7 +313,8 @@ libusb_device_handle* FadecandyAsyncUsbSender::SetupHandle() { return usb_handle; } -bool FadecandyAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer) { +bool FadecandyAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer, + unsigned int portId) { UpdatePacketsWithDMX(m_data_packets, buffer); // We do a single bulk transfer of the entire data, rather than one transfer // for each 64 bytes. @@ -338,7 +340,8 @@ bool AsynchronousScanlimeFadecandy::Init() { return m_sender->Init(); } -bool AsynchronousScanlimeFadecandy::SendDMX(const DmxBuffer &buffer) { +bool AsynchronousScanlimeFadecandy::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender->SendDMX(buffer); } } // namespace usbdmx diff --git a/plugins/usbdmx/ScanlimeFadecandy.h b/plugins/usbdmx/ScanlimeFadecandy.h index b88deada4f..41601e2fae 100644 --- a/plugins/usbdmx/ScanlimeFadecandy.h +++ b/plugins/usbdmx/ScanlimeFadecandy.h @@ -86,7 +86,7 @@ class SynchronousScanlimeFadecandy: public ScanlimeFadecandy { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; @@ -111,7 +111,7 @@ class AsynchronousScanlimeFadecandy : public ScanlimeFadecandy { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; diff --git a/plugins/usbdmx/Sunlite.cpp b/plugins/usbdmx/Sunlite.cpp index 621ef4462b..0ea4c9dfb6 100644 --- a/plugins/usbdmx/Sunlite.cpp +++ b/plugins/usbdmx/Sunlite.cpp @@ -158,7 +158,8 @@ bool SynchronousSunlite::Init() { return true; } -bool SynchronousSunlite::SendDMX(const DmxBuffer &buffer) { +bool SynchronousSunlite::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender.get() ? m_sender->SendDMX(buffer) : false; } @@ -183,7 +184,7 @@ class SunliteAsyncUsbSender : public AsyncUsbSender { return ok ? usb_handle : NULL; } - bool PerformTransfer(const DmxBuffer &buffer) { + bool PerformTransfer(const DmxBuffer &buffer, unsigned int portId) { UpdatePacket(buffer, m_packet); FillBulkTransfer(ENDPOINT, m_packet, SUNLITE_PACKET_SIZE, TIMEOUT); return (SubmitTransfer() == 0); @@ -209,7 +210,8 @@ bool AsynchronousSunlite::Init() { return m_sender->Init(); } -bool AsynchronousSunlite::SendDMX(const DmxBuffer &buffer) { +bool AsynchronousSunlite::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender->SendDMX(buffer); } } // namespace usbdmx diff --git a/plugins/usbdmx/Sunlite.h b/plugins/usbdmx/Sunlite.h index 1db1461d64..87bbf6d5a6 100644 --- a/plugins/usbdmx/Sunlite.h +++ b/plugins/usbdmx/Sunlite.h @@ -63,7 +63,7 @@ class SynchronousSunlite: public Sunlite { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; @@ -86,7 +86,7 @@ class AsynchronousSunlite: public Sunlite { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; diff --git a/plugins/usbdmx/UsbDmxPlugin.cpp b/plugins/usbdmx/UsbDmxPlugin.cpp index 012c372c45..d02b43f1b7 100644 --- a/plugins/usbdmx/UsbDmxPlugin.cpp +++ b/plugins/usbdmx/UsbDmxPlugin.cpp @@ -92,7 +92,8 @@ string UsbDmxPlugin::Description() const { "----------------------------\n" "\n" "This plugin supports various USB DMX devices including the \n" -"Anyma uDMX, DMXControl Projects e.V. Nodle U1, Eurolite, Fadecandy, " +"Anyma uDMX, DMXControl Projects e.V. Nodle U1 (and clones like Digital \n" +"Enlightenment USB DMX or Frank Sievertsen FX5), Eurolite, Fadecandy, \n" "Sunlite USBDMX2 and Velleman K8062.\n" "\n" "--- Config file : ola-usbdmx.conf ---\n" diff --git a/plugins/usbdmx/VellemanK8062.cpp b/plugins/usbdmx/VellemanK8062.cpp index 72c8f49426..fa8d8f5884 100644 --- a/plugins/usbdmx/VellemanK8062.cpp +++ b/plugins/usbdmx/VellemanK8062.cpp @@ -320,7 +320,8 @@ bool SynchronousVellemanK8062::Init() { return true; } -bool SynchronousVellemanK8062::SendDMX(const DmxBuffer &buffer) { +bool SynchronousVellemanK8062::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender.get() ? m_sender->SendDMX(buffer) : false; } @@ -351,7 +352,7 @@ class VellemanAsyncUsbSender : public AsyncUsbSender { return handle; } - bool PerformTransfer(const DmxBuffer &buffer); + bool PerformTransfer(const DmxBuffer &buffer, unsigned int portId); void PostTransferHook(); @@ -379,7 +380,8 @@ class VellemanAsyncUsbSender : public AsyncUsbSender { DISALLOW_COPY_AND_ASSIGN(VellemanAsyncUsbSender); }; -bool VellemanAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer) { +bool VellemanAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer, + unsigned int portId) { if (m_buffer_offset == 0) { return SendInitialChunk(buffer); } @@ -401,7 +403,7 @@ void VellemanAsyncUsbSender::PostTransferHook() { } else { // No pending transfer. The widget only actually sends a frame once the // next frame begins, so kick off the next frame here. - PerformTransfer(m_tx_buffer); + PerformTransfer(m_tx_buffer, 0); } } } @@ -508,7 +510,8 @@ bool AsynchronousVellemanK8062::Init() { return m_sender->Init(); } -bool AsynchronousVellemanK8062::SendDMX(const DmxBuffer &buffer) { +bool AsynchronousVellemanK8062::SendDMX(const DmxBuffer &buffer, + unsigned int portId) { return m_sender->SendDMX(buffer); } } // namespace usbdmx diff --git a/plugins/usbdmx/VellemanK8062.h b/plugins/usbdmx/VellemanK8062.h index c20be290c3..30d2578929 100644 --- a/plugins/usbdmx/VellemanK8062.h +++ b/plugins/usbdmx/VellemanK8062.h @@ -63,7 +63,7 @@ class SynchronousVellemanK8062: public VellemanK8062 { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; @@ -86,7 +86,7 @@ class AsynchronousVellemanK8062 : public VellemanK8062 { bool Init(); - bool SendDMX(const DmxBuffer &buffer); + bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0); private: std::auto_ptr m_sender; diff --git a/plugins/usbdmx/Widget.h b/plugins/usbdmx/Widget.h index 19fb99046b..7686a82632 100644 --- a/plugins/usbdmx/Widget.h +++ b/plugins/usbdmx/Widget.h @@ -54,7 +54,7 @@ class WidgetInterface { * @param buffer The DmxBuffer containing the data to send. * @returns true if the data was sent, false otherwise. */ - virtual bool SendDMX(const DmxBuffer &buffer) = 0; + virtual bool SendDMX(const DmxBuffer &buffer, unsigned int portId = 0) = 0; }; /**