-
Notifications
You must be signed in to change notification settings - Fork 230
RFC: Multi-universe support for usbdmx-based devices #1713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 0.10
Are you sure you want to change the base?
Changes from all commits
fe95f41
2f9f7b7
039899c
8f7fef6
8880e98
1fce273
cc866c4
9b69af8
65332f7
79fdaf9
91ada4e
978d147
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<unsigned int, DmxBuffer>(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<unsigned int, DmxBuffer>::iterator it = m_tx_buffers.begin(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if using "the first" entry in the map is the best one to pick here |
||
| PerformTransfer(it->second, it->first); | ||
| m_tx_buffers.erase(it->first); | ||
| } | ||
| } | ||
| } // namespace usbdmx | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| #define PLUGINS_USBDMX_ASYNCUSBSENDER_H_ | ||
|
|
||
| #include <libusb.h> | ||
| #include <map> | ||
|
|
||
| #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<unsigned int, DmxBuffer> m_tx_buffers; // GUARDED_BY(m_mutex); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if a map is the most suitable pick here |
||
|
|
||
| DISALLOW_COPY_AND_ASSIGN(AsyncUsbSender); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know why the Nodle U1 (and clones) are limited to 32 bytes. USB and HID both allow 64 bytes (for |
||
|
|
||
| /* | ||
| * @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,24 +439,26 @@ 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); | ||
| } | ||
|
|
||
| DISALLOW_COPY_AND_ASSIGN(DMXCProjectsNodleU1AsyncUsbSender); | ||
| }; | ||
|
|
||
| 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; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the "legacy" protocol (sending 32 byte long blocks) is used for universe "0", even if the more advanced protocol could be used instead |
||
|
|
||
| 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( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
beware: untested code block. I didn't yet get the USB device to "stall" = to have outstanding transfers at this point.