diff --git a/common/utils/DmxBuffer.cpp b/common/utils/DmxBuffer.cpp index 9ff3c2c205..a664e2f8c2 100644 --- a/common/utils/DmxBuffer.cpp +++ b/common/utils/DmxBuffer.cpp @@ -261,6 +261,11 @@ void DmxBuffer::Get(uint8_t *data, unsigned int *length) const { void DmxBuffer::GetRange(unsigned int slot, uint8_t *data, unsigned int *length) const { + if (slot >= m_length) { + *length = 0; + return; + } + if (m_data) { *length = min(*length, m_length - slot); memcpy(data, m_data + slot, *length); diff --git a/plugins/usbdmx/AnymaDevice.cpp b/plugins/usbdmx/AnymaDevice.cpp deleted file mode 100644 index e42b4b8b56..0000000000 --- a/plugins/usbdmx/AnymaDevice.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - * - * AnymaDevice.cpp - * The Anyma usb driver - * Copyright (C) 2010 Simon Newton - */ - -#include -#include -#include - -#include "ola/Logging.h" -#include "plugins/usbdmx/AnymaDevice.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -using std::string; - -const char AnymaDevice::EXPECTED_MANUFACTURER[] = "www.anyma.ch"; -const char AnymaDevice::EXPECTED_PRODUCT[] = "uDMX"; - - -/** - * New AnymaDevice. - * @param owner the plugin that owns this device - * @param usb_device a USB device - * @param usb_handle a claimed handle to the device. Ownership is transferred. - * @param serial the serial number, may be empty. - */ -AnymaDevice::AnymaDevice(ola::AbstractPlugin *owner, - libusb_device *usb_device, - libusb_device_handle *usb_handle, - const string &serial) - : UsbDevice(owner, "Anyma USB Device", usb_device), - m_output_port(new AnymaOutputPort(this, 0, usb_handle, serial)) { -} - - -/* - * Start this device. - */ -bool AnymaDevice::StartHook() { - if (!m_output_port->Start()) { - delete m_output_port; - m_output_port = NULL; - return false; - } - AddPort(m_output_port); - return true; -} - - -/* - * Get the device id - */ -string AnymaDevice::DeviceId() const { - if (m_output_port) { - return "anyma-" + m_output_port->SerialNumber(); - } else { - return ""; - } -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/AnymaOutputPort.h b/plugins/usbdmx/AnymaOutputPort.h deleted file mode 100644 index 5b0ecde930..0000000000 --- a/plugins/usbdmx/AnymaOutputPort.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - * - * AnymaOutputPort.h - * The output port for a Anyma device. - * Copyright (C) 2010 Simon Newton - * - * It takes around 21ms to send one universe of data. so we do this in a - * separate thread. - */ - -#ifndef PLUGINS_USBDMX_ANYMAOUTPUTPORT_H_ -#define PLUGINS_USBDMX_ANYMAOUTPUTPORT_H_ - -#include -#include -#include -#include "ola/DmxBuffer.h" -#include "ola/thread/Thread.h" -#include "olad/Port.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -class AnymaDevice; - -class AnymaOutputPort: public BasicOutputPort, ola::thread::Thread { - public: - AnymaOutputPort(AnymaDevice *parent, - unsigned int id, - libusb_device_handle *usb_handle, - const std::string &serial); - ~AnymaOutputPort(); - std::string SerialNumber() const { return m_serial; } - - bool Start(); - void *Run(); - - bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); - std::string Description() const { return ""; } - - private: - static const unsigned int URB_TIMEOUT_MS = 500; - static const unsigned int UDMX_SET_CHANNEL_RANGE = 0x0002; - - bool m_term; - std::string m_serial; - libusb_device_handle *m_usb_handle; - DmxBuffer m_buffer; - ola::thread::Mutex m_data_mutex; - ola::thread::Mutex m_term_mutex; - - bool SendDMX(const DmxBuffer &buffer_old); -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_ANYMAOUTPUTPORT_H_ diff --git a/plugins/usbdmx/AnymaWidget.cpp b/plugins/usbdmx/AnymaWidget.cpp new file mode 100644 index 0000000000..5e1115ea94 --- /dev/null +++ b/plugins/usbdmx/AnymaWidget.cpp @@ -0,0 +1,181 @@ +/* + * 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. + * + * AnymaWidget.cpp + * The synchronous and asynchronous Anyma widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/AnymaWidget.h" + +#include +#include + +#include "ola/Logging.h" +#include "ola/Constants.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::string; + +namespace { + +static const unsigned int URB_TIMEOUT_MS = 500; +static const unsigned int UDMX_SET_CHANNEL_RANGE = 0x0002; + +} // namespace + +// AnymaThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a Anyma device in a separate thread. + */ +class AnymaThreadedSender: public ThreadedUsbSender { + public: + AnymaThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle) + : ThreadedUsbSender(usb_device, handle), + m_adaptor(adaptor) { + } + + private: + LibUsbAdaptor* const m_adaptor; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); +}; + +bool AnymaThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + int r = m_adaptor->ControlTransfer( + handle, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | + LIBUSB_ENDPOINT_OUT, // bmRequestType + UDMX_SET_CHANNEL_RANGE, // bRequest + buffer.Size(), // wValue + 0, // wIndex + const_cast(buffer.GetRaw()), // data + buffer.Size(), // wLength + URB_TIMEOUT_MS); // timeout + // Sometimes we get PIPE errors here, those are non-fatal + return r > 0 || r == LIBUSB_ERROR_PIPE; +} + + +// SynchronousAnymaWidget +// ----------------------------------------------------------------------------- + +SynchronousAnymaWidget::SynchronousAnymaWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const string &serial) + : AnymaWidget(adaptor, serial), + m_usb_device(usb_device) { +} + +bool SynchronousAnymaWidget::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 AnymaThreadedSender(m_adaptor, m_usb_device, usb_handle)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousAnymaWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} + +// AnymaAsyncUsbSender +// ----------------------------------------------------------------------------- +class AnymaAsyncUsbSender : public AsyncUsbSender { + public: + AnymaAsyncUsbSender(LibUsbAdaptor *adaptor, libusb_device *usb_device) + : AsyncUsbSender(adaptor, usb_device) { + m_control_setup_buffer = + new uint8_t[LIBUSB_CONTROL_SETUP_SIZE + DMX_UNIVERSE_SIZE]; + } + + ~AnymaAsyncUsbSender() { + CancelTransfer(); + delete[] m_control_setup_buffer; + } + + libusb_device_handle* SetupHandle() { + libusb_device_handle *usb_handle; + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, 0, &usb_handle); + return ok ? usb_handle : NULL; + } + + bool PerformTransfer(const DmxBuffer &buffer) { + m_adaptor->FillControlSetup( + m_control_setup_buffer, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | + LIBUSB_ENDPOINT_OUT, // bmRequestType + UDMX_SET_CHANNEL_RANGE, // bRequest + buffer.Size(), // wValue + 0, // wIndex + buffer.Size()); // wLength + + unsigned int length = DMX_UNIVERSE_SIZE; + buffer.Get(m_control_setup_buffer + LIBUSB_CONTROL_SETUP_SIZE, &length); + + FillControlTransfer(m_control_setup_buffer, URB_TIMEOUT_MS); + return SubmitTransfer() == 0; + } + + private: + uint8_t *m_control_setup_buffer; + + DISALLOW_COPY_AND_ASSIGN(AnymaAsyncUsbSender); +}; + +// AsynchronousAnymaWidget +// ----------------------------------------------------------------------------- + +AsynchronousAnymaWidget::AsynchronousAnymaWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const string &serial) + : AnymaWidget(adaptor, serial) { + m_sender.reset(new AnymaAsyncUsbSender(m_adaptor, usb_device)); +} + +bool AsynchronousAnymaWidget::Init() { + return m_sender->Init(); +} + +bool AsynchronousAnymaWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender->SendDMX(buffer); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/AnymaWidget.h b/plugins/usbdmx/AnymaWidget.h new file mode 100644 index 0000000000..3d1a4c5a86 --- /dev/null +++ b/plugins/usbdmx/AnymaWidget.h @@ -0,0 +1,121 @@ +/* + * 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. + * + * AnymaWidget.h + * The synchronous and asynchronous Anyma widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_ANYMAWIDGET_H_ +#define PLUGINS_USBDMX_ANYMAWIDGET_H_ + +#include +#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 { + +/** + * @brief The base class for Anyma Widgets. + */ +class AnymaWidget: public BaseWidget { + public: + /** + * @brief Create a new AnymaWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param serial the serial number of the widget. + */ + AnymaWidget(LibUsbAdaptor *adaptor, + const std::string &serial) + : BaseWidget(adaptor), + m_serial(serial) {} + + virtual ~AnymaWidget() {} + + /** + * @brief Get the serial number of this widget. + * @returns The serial number of the widget. + */ + std::string SerialNumber() const { + return m_serial; + } + + private: + std::string m_serial; +}; + +/** + * @brief An Anyma widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousAnymaWidget: public AnymaWidget { + public: + /** + * @brief Create a new SynchronousAnymaWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + SynchronousAnymaWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + libusb_device* const m_usb_device; + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousAnymaWidget); +}; + +/** + * @brief An Anyma widget that uses asynchronous libusb operations. + */ +class AsynchronousAnymaWidget : public AnymaWidget { + public: + /** + * @brief Create a new AsynchronousAnymaWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + AsynchronousAnymaWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousAnymaWidget); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_ANYMAWIDGET_H_ diff --git a/plugins/usbdmx/AnymaWidgetFactory.cpp b/plugins/usbdmx/AnymaWidgetFactory.cpp new file mode 100644 index 0000000000..40c23a0abd --- /dev/null +++ b/plugins/usbdmx/AnymaWidgetFactory.cpp @@ -0,0 +1,90 @@ +/* + * 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. + * + * AnymaWidgetFactory.cpp + * The WidgetFactory for Anyma widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/AnymaWidgetFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" +#include "plugins/usbdmx/AnymaWidget.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" + +DECLARE_bool(use_async_libusb); + + +namespace ola { +namespace plugin { +namespace usbdmx { + +const char AnymaWidgetFactory::EXPECTED_MANUFACTURER[] = "www.anyma.ch"; +const char AnymaWidgetFactory::EXPECTED_PRODUCT[] = "uDMX"; +const uint16_t AnymaWidgetFactory::PRODUCT_ID = 0x05DC; +const uint16_t AnymaWidgetFactory::VENDOR_ID = 0x16C0; + + +bool AnymaWidgetFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor != VENDOR_ID || descriptor.idProduct != PRODUCT_ID || + HasDevice(usb_device)) { + return false; + } + + OLA_INFO << "Found a new Anyma device"; + LibUsbAdaptor::DeviceInformation info; + if (!m_adaptor->GetDeviceInfo(usb_device, descriptor, &info)) { + return false; + } + + if (!m_adaptor->CheckManufacturer(EXPECTED_MANUFACTURER, info.manufacturer)) { + return false; + } + + if (!m_adaptor->CheckProduct(EXPECTED_PRODUCT, info.product)) { + return false; + } + + // Some Anyma devices don't have serial numbers. Since there isn't another + // good way to uniquely identify a USB device, we only support one of these + // types of devices per host. + if (info.serial.empty()) { + if (m_missing_serial_number) { + OLA_WARN << "Failed to read serial number or serial number empty. " + << "We can only support one device without a serial number."; + return false; + } else { + OLA_WARN << "Failed to read serial number from " << info.manufacturer + << " : " << info.product + << " the device probably doesn't have one"; + m_missing_serial_number = true; + } + } + + AnymaWidget *widget = NULL; + if (FLAGS_use_async_libusb) { + widget = new AsynchronousAnymaWidget(m_adaptor, usb_device, info.serial); + } else { + widget = new SynchronousAnymaWidget(m_adaptor, usb_device, info.serial); + } + return AddWidget(observer, usb_device, widget); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/AnymaDevice.h b/plugins/usbdmx/AnymaWidgetFactory.h similarity index 52% rename from plugins/usbdmx/AnymaDevice.h rename to plugins/usbdmx/AnymaWidgetFactory.h index c7596892ca..55baa7cec4 100644 --- a/plugins/usbdmx/AnymaDevice.h +++ b/plugins/usbdmx/AnymaWidgetFactory.h @@ -13,45 +13,48 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * AnymaDevice.h - * Interface for the Anyma device - * Copyright (C) 2010 Simon Newton + * AnymaWidgetFactory.h + * The WidgetFactory for Anyma widgets. + * Copyright (C) 2014 Simon Newton */ -#ifndef PLUGINS_USBDMX_ANYMADEVICE_H_ -#define PLUGINS_USBDMX_ANYMADEVICE_H_ +#ifndef PLUGINS_USBDMX_ANYMAWIDGETFACTORY_H_ +#define PLUGINS_USBDMX_ANYMAWIDGETFACTORY_H_ -#include -#include -#include "plugins/usbdmx/UsbDevice.h" -#include "plugins/usbdmx/AnymaOutputPort.h" +#include "ola/base/Macro.h" +#include "plugins/usbdmx/WidgetFactory.h" namespace ola { namespace plugin { namespace usbdmx { -/* - * A Anyma device +/** + * @brief Creates Anyma widgets. */ -class AnymaDevice: public UsbDevice { +class AnymaWidgetFactory : public BaseWidgetFactory { public: - AnymaDevice(ola::AbstractPlugin *owner, - libusb_device *usb_device, - libusb_device_handle *usb_handle, - const std::string &serial); + explicit AnymaWidgetFactory(class LibUsbAdaptor *adaptor) + : m_missing_serial_number(false), + m_adaptor(adaptor) { + } + + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); - std::string DeviceId() const; + private: + bool m_missing_serial_number; + class LibUsbAdaptor *m_adaptor; static const char EXPECTED_MANUFACTURER[]; static const char EXPECTED_PRODUCT[]; + static const uint16_t PRODUCT_ID; + static const uint16_t VENDOR_ID; - protected: - bool StartHook(); - - private: - AnymaOutputPort *m_output_port; + DISALLOW_COPY_AND_ASSIGN(AnymaWidgetFactory); }; } // namespace usbdmx } // namespace plugin } // namespace ola -#endif // PLUGINS_USBDMX_ANYMADEVICE_H_ +#endif // PLUGINS_USBDMX_ANYMAWIDGETFACTORY_H_ diff --git a/plugins/usbdmx/AsyncPluginImpl.cpp b/plugins/usbdmx/AsyncPluginImpl.cpp new file mode 100644 index 0000000000..862f2cb18c --- /dev/null +++ b/plugins/usbdmx/AsyncPluginImpl.cpp @@ -0,0 +1,359 @@ +/* + * 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. + * + * AsyncPluginImpl.cpp + * The asynchronous libusb implementation. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/AsyncPluginImpl.h" + +#include +#include +#include + +#include + +#include "ola/Logging.h" +#include "ola/StringUtils.h" +#include "ola/stl/STLUtils.h" +#include "olad/PluginAdaptor.h" + +#include "plugins/usbdmx/AnymaWidget.h" +#include "plugins/usbdmx/AnymaWidgetFactory.h" +#include "plugins/usbdmx/EuroliteProWidgetFactory.h" +#include "plugins/usbdmx/FadecandyWidget.h" +#include "plugins/usbdmx/FadecandyWidgetFactory.h" +#include "plugins/usbdmx/GenericDevice.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/LibUsbThread.h" +#include "plugins/usbdmx/SunliteWidgetFactory.h" +#include "plugins/usbdmx/VellemanWidget.h" +#include "plugins/usbdmx/VellemanWidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +namespace { + +/** + * @brief Called by libusb when a USB device is added / removed. + */ +#ifdef OLA_LIBUSB_HAS_HOTPLUG_API +int hotplug_callback(OLA_UNUSED struct libusb_context *ctx, + struct libusb_device *dev, + libusb_hotplug_event event, + void *user_data) { + AsyncPluginImpl *plugin = reinterpret_cast(user_data); + plugin->HotPlugEvent(dev, event); + return 0; +} +#endif +} // namespace + +AsyncPluginImpl::AsyncPluginImpl(PluginAdaptor *plugin_adaptor, + Plugin *plugin, + unsigned int debug_level) + : m_plugin_adaptor(plugin_adaptor), + m_plugin(plugin), + m_debug_level(debug_level), + m_widget_observer(this, plugin_adaptor), + m_context(NULL), + m_use_hotplug(false), + m_scan_timeout(ola::thread::INVALID_TIMEOUT) { +} + +AsyncPluginImpl::~AsyncPluginImpl() { + STLDeleteElements(&m_widget_factories); +} + +bool AsyncPluginImpl::Start() { + if (libusb_init(&m_context)) { + OLA_WARN << "Failed to init libusb"; + return false; + } + + OLA_DEBUG << "libusb debug level set to " << m_debug_level; + libusb_set_debug(m_context, m_debug_level); + + m_use_hotplug = HotplugSupported(); + OLA_INFO << "HotplugSupported returned " << m_use_hotplug; + if (m_use_hotplug) { +#ifdef OLA_LIBUSB_HAS_HOTPLUG_API + m_usb_thread.reset(new LibUsbHotplugThread( + m_context, hotplug_callback, this)); +#else + OLA_FATAL << "Mismatch between m_use_hotplug and " + " OLA_LIBUSB_HAS_HOTPLUG_API"; + return false; +#endif + } else { + m_usb_thread.reset(new LibUsbSimpleThread(m_context)); + } + m_usb_adaptor.reset(new AsyncronousLibUsbAdaptor(m_usb_thread.get())); + + // Setup the factories. + m_widget_factories.push_back(new AnymaWidgetFactory(m_usb_adaptor.get())); + m_widget_factories.push_back( + new EuroliteProWidgetFactory(m_usb_adaptor.get())); + m_widget_factories.push_back(new SunliteWidgetFactory(m_usb_adaptor.get())); + m_widget_factories.push_back(new VellemanWidgetFactory(m_usb_adaptor.get())); + + // If we're using hotplug, this starts the hotplug thread. + if (!m_usb_thread->Init()) { + STLDeleteElements(&m_widget_factories); + m_usb_adaptor.reset(); + m_usb_thread.reset(); + return false; + } + + if (!m_use_hotplug) { + // Either we don't support hotplug or the setup failed. + // As poor man's hotplug, we call libusb_get_device_list periodically to + // check for new devices. + m_scan_timeout = m_plugin_adaptor->RegisterRepeatingTimeout( + TimeInterval(5, 0), + NewCallback(this, &AsyncPluginImpl::ScanUSBDevices)); + + // Call it immediately now. + ScanUSBDevices(); + } + + return true; +} + +bool AsyncPluginImpl::Stop() { + if (m_scan_timeout != ola::thread::INVALID_TIMEOUT) { + m_plugin_adaptor->RemoveTimeout(m_scan_timeout); + m_scan_timeout = ola::thread::INVALID_TIMEOUT; + } + + m_usb_thread->Shutdown(); + + USBDeviceToFactoryMap::iterator iter = m_device_factory_map.begin(); + for (; iter != m_device_factory_map.end(); ++iter) { + iter->second->DeviceRemoved(this, iter->first); + } + m_device_factory_map.clear(); + + m_usb_thread.reset(); + m_usb_adaptor.reset(); + STLDeleteElements(&m_widget_factories); + + libusb_exit(m_context); + m_context = NULL; + return true; +} + +#ifdef OLA_LIBUSB_HAS_HOTPLUG_API +void AsyncPluginImpl::HotPlugEvent(struct libusb_device *usb_device, + libusb_hotplug_event event) { + OLA_INFO << "Got USB hotplug event for " << usb_device << " : " + << (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED ? "add" : "del"); + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + USBDeviceAdded(usb_device); + } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + USBDeviceRemoved(usb_device); + } +} +#endif + +bool AsyncPluginImpl::NewWidget(AnymaWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Anyma USB Device", + "anyma-" + widget->SerialNumber())); +} + +bool AsyncPluginImpl::NewWidget(EuroliteProWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "EurolitePro USB Device", + "eurolite-" + widget->SerialNumber())); +} + +bool AsyncPluginImpl::NewWidget(FadecandyWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Fadecandy USB Device", + "fadecandy-" + widget->SerialNumber())); +} + +bool AsyncPluginImpl::NewWidget(SunliteWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Sunlite USBDMX2 Device", "usbdmx2")); +} + +bool AsyncPluginImpl::NewWidget(VellemanWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Velleman USB Device", "velleman")); +} + +void AsyncPluginImpl::WidgetRemoved(AnymaWidget *widget) { + RemoveWidget(widget); +} + +void AsyncPluginImpl::WidgetRemoved(EuroliteProWidget *widget) { + RemoveWidget(widget); +} + +void AsyncPluginImpl::WidgetRemoved(FadecandyWidget *widget) { + RemoveWidget(widget); +} + +void AsyncPluginImpl::WidgetRemoved(SunliteWidget *widget) { + RemoveWidget(widget); +} + +void AsyncPluginImpl::WidgetRemoved(VellemanWidget *widget) { + RemoveWidget(widget); +} + +/** + * @brief Check if this platform supports hotplug. + * @returns true if hotplug is supported and enabled on this platform, false + * otherwise. + */ +bool AsyncPluginImpl::HotplugSupported() { +#ifdef OLA_LIBUSB_HAS_HOTPLUG_API + return libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; +#else + return false; +#endif +} + +/** + * @brief Signal a new USB device has been added. + * + * This can be called from either the libusb thread or the main thread. However + * only one of those will be active at once, so we can avoid locking. + */ +bool AsyncPluginImpl::USBDeviceAdded(libusb_device *usb_device) { + struct libusb_device_descriptor descriptor; + libusb_get_device_descriptor(usb_device, &descriptor); + + WidgetFactories::iterator iter = m_widget_factories.begin(); + for (; iter != m_widget_factories.end(); ++iter) { + if ((*iter)->DeviceAdded(&m_widget_observer, usb_device, descriptor)) { + STLReplacePtr(&m_device_factory_map, usb_device, *iter); + return true; + } + } + return false; +} + +/** + * @brief Signal a USB device has been removed. + * + * This can be called from either the libusb thread or the main thread. However + * only one of those will be active at once, so we can avoid locking. + */ +void AsyncPluginImpl::USBDeviceRemoved(libusb_device *usb_device) { + WidgetFactory *factory = STLLookupAndRemovePtr( + &m_device_factory_map, usb_device); + if (factory) { + factory->DeviceRemoved(&m_widget_observer, usb_device); + } +} + +/* + * @brief Signal widget / device addition. + * @param widget The widget that was added. + * @param device The new olad device that uses this new widget. + * + * This is run within the main thread. + */ +bool AsyncPluginImpl::StartAndRegisterDevice(Widget *widget, Device *device) { + if (!device->Start()) { + delete device; + return false; + } + + Device *old_device = STLReplacePtr(&m_widget_device_map, widget, device); + if (old_device) { + m_plugin_adaptor->UnregisterDevice(old_device); + old_device->Stop(); + delete old_device; + } + m_plugin_adaptor->RegisterDevice(device); + return true; +} + +/* + * @brief Signal widget removal. + * @param widget The widget that was removed. + * + * This is run within the main thread. + */ +void AsyncPluginImpl::RemoveWidget(Widget *widget) { + Device *device = STLLookupAndRemovePtr(&m_widget_device_map, widget); + if (device) { + m_plugin_adaptor->UnregisterDevice(device); + device->Stop(); + delete device; + } +} + +/* + * If hotplug isn't supported, this is called periodically to checked for + * USB devices that have been added or removed. + * + * This is run within the main thread, since the libusb thread only runs if at + * least one USB device is used. + */ +bool AsyncPluginImpl::ScanUSBDevices() { + OLA_INFO << "Scanning USB devices...."; + std::set current_device_ids; + + libusb_device **device_list; + size_t device_count = libusb_get_device_list(m_context, &device_list); + + OLA_INFO << "Got " << device_count << " devices"; + for (unsigned int i = 0; i < device_count; i++) { + libusb_device *usb_device = device_list[i]; + + USBDeviceID device_id(libusb_get_bus_number(usb_device), + libusb_get_device_address(usb_device)); + + current_device_ids.insert(device_id); + + if (!STLContains(m_seen_usb_devices, device_id)) { + OLA_INFO << " " << usb_device; + bool claimed = USBDeviceAdded(usb_device); + STLReplace(&m_seen_usb_devices, device_id, claimed ? usb_device : NULL); + } + } + libusb_free_device_list(device_list, 1); // unref devices + + USBDeviceIDs::iterator iter = m_seen_usb_devices.begin(); + while (iter != m_seen_usb_devices.end()) { + if (!STLContains(current_device_ids, iter->first)) { + if (iter->second) { + USBDeviceRemoved(iter->second); + } + m_seen_usb_devices.erase(iter++); + } else { + iter++; + } + } + return true; +} + +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/AsyncPluginImpl.h b/plugins/usbdmx/AsyncPluginImpl.h new file mode 100644 index 0000000000..7d3623e19d --- /dev/null +++ b/plugins/usbdmx/AsyncPluginImpl.h @@ -0,0 +1,137 @@ +/* + * 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. + * + * AsyncPluginImpl.h + * The asynchronous libusb implementation. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_ASYNCPLUGINIMPL_H_ +#define PLUGINS_USBDMX_ASYNCPLUGINIMPL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "ola/base/Macro.h" +#include "ola/thread/Thread.h" +#include "plugins/usbdmx/PluginImplInterface.h" +#include "plugins/usbdmx/SyncronizedWidgetObserver.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { + +class Device; + +namespace plugin { +namespace usbdmx { + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) +#define OLA_LIBUSB_HAS_HOTPLUG_API +#endif + +/** + * @brief The asynchronous libusb implementation. + */ +class AsyncPluginImpl: public PluginImplInterface, public WidgetObserver { + public: + /** + * @brief Create a new AsyncPluginImpl. + * @param plugin_adaptor The PluginAdaptor to use, ownership is not + * transferred. + * @param plugin The parent Plugin object which is used when creating + * devices. + * @param debug_level the debug level to use for libusb. + */ + AsyncPluginImpl(PluginAdaptor *plugin_adaptor, + Plugin *plugin, + unsigned int debug_level); + ~AsyncPluginImpl(); + + bool Start(); + bool Stop(); + + #ifdef OLA_LIBUSB_HAS_HOTPLUG_API + /** + * @brief Called when a USB hotplug event occurs. + * @param dev the libusb_device the event occurred for. + * @param event indicates if the device was added or removed. + * + * This can be called from either the thread that called + * Start(), or from the libusb thread. It can't be called from both threads at + * once though, since the libusb thread is only started once the initial call + * to libusb_hotplug_register_callback returns. + */ + void HotPlugEvent(struct libusb_device *dev, + libusb_hotplug_event event); + #endif + + bool NewWidget(class AnymaWidget *widget); + bool NewWidget(class EuroliteProWidget *widget); + bool NewWidget(class FadecandyWidget *widget); + bool NewWidget(class SunliteWidget *widget); + bool NewWidget(class VellemanWidget *widget); + + void WidgetRemoved(class AnymaWidget *widget); + void WidgetRemoved(class EuroliteProWidget *widget); + void WidgetRemoved(class FadecandyWidget *widget); + void WidgetRemoved(class SunliteWidget *widget); + void WidgetRemoved(class VellemanWidget *widget); + + private: + typedef std::vector WidgetFactories; + typedef std::map USBDeviceToFactoryMap; + typedef std::map WidgetToDeviceMap; + typedef std::pair USBDeviceID; + typedef std::map USBDeviceIDs; + + PluginAdaptor* const m_plugin_adaptor; + Plugin* const m_plugin; + const unsigned int m_debug_level; + + SyncronizedWidgetObserver m_widget_observer; + + libusb_context *m_context; + bool m_use_hotplug; + std::auto_ptr m_usb_thread; + std::auto_ptr m_usb_adaptor; + + WidgetFactories m_widget_factories; + USBDeviceToFactoryMap m_device_factory_map; + WidgetToDeviceMap m_widget_device_map; + + // Members used if hotplug is not supported + ola::thread::timeout_id m_scan_timeout; + USBDeviceIDs m_seen_usb_devices; + + bool HotplugSupported(); + bool USBDeviceAdded(libusb_device *device); + void USBDeviceRemoved(libusb_device *device); + + bool StartAndRegisterDevice(class Widget *widget, Device *device); + void RemoveWidget(class Widget *widget); + + bool ScanUSBDevices(); + + DISALLOW_COPY_AND_ASSIGN(AsyncPluginImpl); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_ASYNCPLUGINIMPL_H_ diff --git a/plugins/usbdmx/AsyncUsbSender.cpp b/plugins/usbdmx/AsyncUsbSender.cpp new file mode 100644 index 0000000000..ffac1f37ac --- /dev/null +++ b/plugins/usbdmx/AsyncUsbSender.cpp @@ -0,0 +1,151 @@ +/* + * 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. + * + * AsyncUsbSender.cpp + * A Asynchronous DMX USB sender. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/AsyncUsbSender.h" + +#include "ola/Logging.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +namespace { + +/* + * Called by libusb when the transfer completes. + */ +void AsyncCallback(struct libusb_transfer *transfer) { + AsyncUsbSender *widget = reinterpret_cast( + transfer->user_data); + widget->TransferComplete(transfer); +} +} // namespace + +AsyncUsbSender::AsyncUsbSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : m_adaptor(adaptor), + m_usb_device(usb_device), + m_usb_handle(NULL), + m_transfer_state(IDLE) { + m_transfer = m_adaptor->AllocTransfer(0); + m_adaptor->RefDevice(usb_device); +} + +AsyncUsbSender::~AsyncUsbSender() { + CancelTransfer(); + m_adaptor->Close(m_usb_handle); + m_adaptor->UnrefDevice(m_usb_device); +} + +bool AsyncUsbSender::Init() { + m_usb_handle = SetupHandle(); + return m_usb_handle ? true : false; +} + +bool AsyncUsbSender::SendDMX(const DmxBuffer &buffer) { + if (!m_usb_handle) { + OLA_WARN << "AsynchronousAnymaWidget hasn't been initialized"; + return false; + } + ola::thread::MutexLocker locker(&m_mutex); + if (m_transfer_state != IDLE) { + return true; + } + + PerformTransfer(buffer); + return true; +} + +void AsyncUsbSender::CancelTransfer() { + if (!m_transfer) { + return; + } + + bool canceled = false; + while (1) { + ola::thread::MutexLocker locker(&m_mutex); + if (m_transfer_state == IDLE || m_transfer_state == DISCONNECTED) { + break; + } + if (!canceled) { + m_adaptor->CancelTransfer(m_transfer); + canceled = true; + } + } + + m_adaptor->FreeTransfer(m_transfer); + m_transfer = NULL; +} + +void AsyncUsbSender::FillControlTransfer(unsigned char *buffer, + unsigned int timeout) { + m_adaptor->FillControlTransfer(m_transfer, m_usb_handle, buffer, + &AsyncCallback, this, timeout); +} + +void AsyncUsbSender::FillBulkTransfer(unsigned char endpoint, + unsigned char *buffer, + int length, + unsigned int timeout) { + m_adaptor->FillBulkTransfer(m_transfer, m_usb_handle, endpoint, buffer, + length, &AsyncCallback, this, timeout); +} + +void AsyncUsbSender::FillInterruptTransfer(unsigned char endpoint, + unsigned char *buffer, + int length, + unsigned int timeout) { + m_adaptor->FillInterruptTransfer(m_transfer, m_usb_handle, endpoint, buffer, + length, &AsyncCallback, this, timeout); +} + +int AsyncUsbSender::SubmitTransfer() { + int ret = m_adaptor->SubmitTransfer(m_transfer); + if (ret) { + OLA_WARN << "libusb_submit_transfer returned " << libusb_error_name(ret); + if (ret == LIBUSB_ERROR_NO_DEVICE) { + m_transfer_state = DISCONNECTED; + } + return false; + } + m_transfer_state = IN_PROGRESS; + return ret; +} + +void AsyncUsbSender::TransferComplete(struct libusb_transfer *transfer) { + if (transfer != m_transfer) { + OLA_WARN << "Mismatched libusb transfer: " << transfer << " != " + << m_transfer; + return; + } + + if (transfer->status != LIBUSB_TRANSFER_COMPLETED) { + OLA_WARN << "Transfer returned " << transfer->status; + } + + ola::thread::MutexLocker locker(&m_mutex); + m_transfer_state = transfer->status == LIBUSB_TRANSFER_NO_DEVICE ? + DISCONNECTED : IDLE; + PostTransferHook(); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/AsyncUsbSender.h b/plugins/usbdmx/AsyncUsbSender.h new file mode 100644 index 0000000000..8231163cf7 --- /dev/null +++ b/plugins/usbdmx/AsyncUsbSender.h @@ -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. + * + * AsyncUsbSender.h + * A Asynchronous DMX USB sender. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_ASYNCUSBSENDER_H_ +#define PLUGINS_USBDMX_ASYNCUSBSENDER_H_ + +#include + +#include "ola/DmxBuffer.h" +#include "ola/base/Macro.h" +#include "ola/thread/Mutex.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief A base class that send DMX data asynchronously. + * + * This encapsulates much of the asynchronous libusb logic. Subclasses should + * implement the SetupHandle() and PerformTransfer() methods. + */ +class AsyncUsbSender { + public: + /** + * @brief Create a new AsynchronousAnymaWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + AsyncUsbSender(class LibUsbAdaptor* const adaptor, + libusb_device *usb_device); + + /** + * @brief Destructor + */ + virtual ~AsyncUsbSender(); + + /** + * @brief Initialize the sender. + * @returns true if SetupHandle() returned a valid handle, false otherwise. + */ + bool Init(); + + /** + * @brief Send on frame of DMX data. + * @param buffer the DMX data to send. + * @returns the value of PerformTransfer(). + */ + bool SendDMX(const DmxBuffer &buffer); + + /** + * @brief Called from the libusb callback when the asynchronous transfer + * completes. + * @param transfer the completed transfer. + */ + void TransferComplete(struct libusb_transfer *transfer); + + protected: + /** + * @brief The LibUsbAdaptor passed in the constructor. + */ + class LibUsbAdaptor* const m_adaptor; + + /** + * @brief The libusb_device passed in the constructor. + */ + libusb_device* const m_usb_device; + + /** + * @brief Open the device handle. + * @returns A valid libusb_device_handle or NULL if the device could not be + * opened. + */ + virtual libusb_device_handle* SetupHandle() = 0; + + /** + * @brief Perform the DMX transfer. + * @param buffer the DMX buffer to send. + * @returns true if the transfer was scheduled, false otherwise. + * + * This method is implemented by the subclass. The subclass should call + * FillControlTransfer() / FillBulkTransfer() as appropriate and then call + * SubmitTransfer(). + */ + virtual bool PerformTransfer(const DmxBuffer &buffer) = 0; + + + virtual void PostTransferHook() {} + + /** + * @brief Cancel any pending transfers. + */ + void CancelTransfer(); + + /** + * @brief Fill a control transfer. + * @param buffer passed to libusb_fill_control_transfer. + * @param timeout passed to libusb_fill_control_transfer. + */ + void FillControlTransfer(unsigned char *buffer, unsigned int timeout); + + /** + * @brief Fill a bulk transfer. + */ + void FillBulkTransfer(unsigned char endpoint, unsigned char *buffer, + int length, unsigned int timeout); + + /** + * @brief Fill a interrupt transfer. + */ + void FillInterruptTransfer(unsigned char endpoint, unsigned char *buffer, + int length, unsigned int timeout); + + /** + * @brief Submit the transfer for tx. + * @returns the result of libusb_submit_transfer(). + */ + int SubmitTransfer(); + + private: + enum TransferState { + IDLE, + IN_PROGRESS, + DISCONNECTED, + }; + + libusb_device_handle *m_usb_handle; + struct libusb_transfer *m_transfer; + + TransferState m_transfer_state; // GUARDED_BY(m_mutex); + ola::thread::Mutex m_mutex; + + DISALLOW_COPY_AND_ASSIGN(AsyncUsbSender); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_ASYNCUSBSENDER_H_ diff --git a/plugins/usbdmx/EuroliteProDevice.cpp b/plugins/usbdmx/EuroliteProDevice.cpp deleted file mode 100644 index 70e42b2c56..0000000000 --- a/plugins/usbdmx/EuroliteProDevice.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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. - * - * EuroliteProDevice.cpp - * The EurolitePro usb driver - * Copyright (C) 2011 Simon Newton & Harry F - * Eurolite Pro USB DMX ArtNo. 51860120 - */ - -#include - -#include "ola/Logging.h" -#include "plugins/usbdmx/EuroliteProDevice.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -using std::string; - -/* - * Start this device. - */ -bool EuroliteProDevice::StartHook() { - m_output_port = new EuroliteProOutputPort(this, 0, m_usb_device); - if (!m_output_port->Start()) { - delete m_output_port; - m_output_port = NULL; - return false; - } - AddPort(m_output_port); - return true; -} - - -/* - * Get the device id - */ -string EuroliteProDevice::DeviceId() const { - if (m_output_port) { - return "eurolite-" + m_output_port->SerialNumber(); - } else { - return ""; - } -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/EuroliteProOutputPort.cpp b/plugins/usbdmx/EuroliteProOutputPort.cpp deleted file mode 100644 index fd1e1ecbcf..0000000000 --- a/plugins/usbdmx/EuroliteProOutputPort.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* - * 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. - * - * EuroliteProOutputPort.cpp - * Thread for the EurolitePro Output Port - * Copyright (C) 2011 Simon Newton & Harry F - * Eurolite Pro USB DMX ArtNo. 51860120 - */ - -#include -#include - -#include "ola/Constants.h" -#include "ola/Logging.h" -#include "plugins/usbdmx/EuroliteProOutputPort.h" -#include "plugins/usbdmx/EuroliteProDevice.h" -#include "plugins/usbdmx/LibUsbUtils.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -using std::string; - -const char EuroliteProOutputPort::EXPECTED_MANUFACTURER[] = "Eurolite"; -const char EuroliteProOutputPort::EXPECTED_PRODUCT[] = "Eurolite DMX512 Pro"; -const uint8_t EuroliteProOutputPort::DMX_LABEL; -const unsigned char EuroliteProOutputPort::ENDPOINT; -const unsigned int EuroliteProOutputPort::UDMX_SET_CHANNEL_RANGE; -const unsigned int EuroliteProOutputPort::URB_TIMEOUT_MS; - -/* - * Create a new EuroliteProOutputPort object - */ -EuroliteProOutputPort::EuroliteProOutputPort(EuroliteProDevice *parent, - unsigned int id, - libusb_device *usb_device) - : BasicOutputPort(parent, id), - m_term(false), - m_serial(""), - m_usb_device(usb_device), - m_usb_handle(NULL) { -} - - -/* - * Cleanup - */ -EuroliteProOutputPort::~EuroliteProOutputPort() { - { - ola::thread::MutexLocker locker(&m_term_mutex); - m_term = true; - } - Join(); -} - - -/* - * Start this thread - */ -bool EuroliteProOutputPort::Start() { - libusb_device_handle *usb_handle; - struct libusb_device_descriptor device_descriptor; - libusb_get_device_descriptor(m_usb_device, &device_descriptor); - - if (libusb_open(m_usb_device, &usb_handle)) { - OLA_WARN << "Failed to open Eurolite usb device"; - return false; - } - - string data; - if (!GetDescriptorString(usb_handle, - device_descriptor.iManufacturer, - &data)) { - OLA_INFO << "Failed to get manufacturer name"; - libusb_close(usb_handle); - return false; - } - - if (data != EXPECTED_MANUFACTURER) { - OLA_INFO << "Manufacturer mismatch: " << EXPECTED_MANUFACTURER << " != " << - data; - libusb_close(usb_handle); - return false; - } - - if (!GetDescriptorString(usb_handle, - device_descriptor.iProduct, - &data)) { - OLA_INFO << "Failed to get product name"; - libusb_close(usb_handle); - return false; - } - - if (data != EXPECTED_PRODUCT) { - OLA_INFO << "Product mismatch: " << EXPECTED_PRODUCT << " != " << data; - libusb_close(usb_handle); - return false; - } - - bool ok = LocateInterface(); - - if (!ok) { - libusb_close(usb_handle); - return false; - } - - // The Eurolite doesn't have a serial number, so instead we use the device & - // bus number. - // TODO(simon): check if this supports the SERIAL NUMBER label and use that - // instead. - - // There is no Serialnumber--> work around: bus+device number - int bus_number = libusb_get_bus_number(m_usb_device); - int device_address = libusb_get_device_address(m_usb_device); - - OLA_INFO << "Bus_number: " << bus_number << ", Device_address: " << - device_address; - - std::ostringstream str; - str << bus_number << "-" << device_address; - m_serial = str.str(); - - int error = libusb_claim_interface(usb_handle, m_interface_number); - - if (error) { - if (error == LIBUSB_ERROR_BUSY) { - OLA_WARN << "Eurolite device in use by another program"; - } else { - OLA_WARN << "Failed to claim Eurolite usb interface, error: " << error; - } - libusb_close(usb_handle); - return false; - } - - m_usb_handle = usb_handle; - bool ret = ola::thread::Thread::Start(); - if (!ret) { - OLA_WARN << "pthread create failed"; - libusb_release_interface(m_usb_handle, m_interface_number); - libusb_close(usb_handle); - return false; - } - return true; -} - - -/* - * The main loop for the sender thread. - */ -void *EuroliteProOutputPort::Run() { - DmxBuffer buffer; - - if (!m_usb_handle) - return NULL; - - while (1) { - { - ola::thread::MutexLocker locker(&m_term_mutex); - if (m_term) - break; - } - - { - ola::thread::MutexLocker locker(&m_data_mutex); - buffer.Set(m_buffer); - } - - if (buffer.Size()) { - if (!SendDMX(buffer)) { - OLA_WARN << "Send bufferfailed, stopping thread..."; - break; - } - } else { - // sleep for a bit - usleep(40000); - } - } - libusb_release_interface(m_usb_handle, m_interface_number); - libusb_close(m_usb_handle); - return NULL; -} - - -/* - * Store the data in the shared buffer - */ -bool EuroliteProOutputPort::WriteDMX(const DmxBuffer &buffer, - uint8_t priority) { - ola::thread::MutexLocker locker(&m_data_mutex); - m_buffer.Set(buffer); - return true; - (void) priority; -} - - -/* - * Send the dmx out the widget - * @return true on success, false on failure - */ -bool EuroliteProOutputPort::SendDMX(const DmxBuffer &buffer) { - uint8_t usb_data[FRAME_SIZE]; - unsigned int frame_size = buffer.Size(); - - // header - usb_data[0] = 0x7E; // Start message delimiter - usb_data[1] = DMX_LABEL; // Label - usb_data[4] = DMX512_START_CODE; - buffer.Get(usb_data + 5, &frame_size); - usb_data[2] = (DMX_UNIVERSE_SIZE + 1) & 0xff; // Data length LSB. - usb_data[3] = ((DMX_UNIVERSE_SIZE + 1) >> 8); // Data length MSB - memset(usb_data + 5 + frame_size, 0, DMX_UNIVERSE_SIZE - frame_size); - usb_data[FRAME_SIZE - 1] = 0xE7; // End message delimiter - - int transferred = 0; - int ret = libusb_bulk_transfer( - m_usb_handle, - ENDPOINT, - usb_data, - FRAME_SIZE, - &transferred, - URB_TIMEOUT_MS); - - if (ret) - OLA_INFO << "return code was: " << ret << ", transferred bytes " << - transferred; - return ret == 0; -} - - -/** - * Find the interface with the endpoint we're after. Usually this is interface - * 1 but we check them all just in case. - */ -bool EuroliteProOutputPort::LocateInterface() { - struct libusb_config_descriptor *device_config; - if (libusb_get_config_descriptor(m_usb_device, 0, &device_config) != 0) { - OLA_WARN << "Failed to get device config descriptor"; - return false; - } - - OLA_DEBUG << static_cast(device_config->bNumInterfaces) << - " interfaces found"; - for (unsigned int i = 0; i < device_config->bNumInterfaces; i++) { - const struct libusb_interface *interface = &device_config->interface[i]; - for (int j = 0; j < interface->num_altsetting; j++) { - const struct libusb_interface_descriptor *iface_descriptor = - &interface->altsetting[j]; - for (uint8_t k = 0; k < iface_descriptor->bNumEndpoints; k++) { - const struct libusb_endpoint_descriptor *endpoint = - &iface_descriptor->endpoint[k]; - OLA_DEBUG << "Interface " << i << ", altsetting " << j << ", endpoint " - << static_cast(k) << ", endpoint address 0x" << std::hex << - static_cast(endpoint->bEndpointAddress); - if (endpoint->bEndpointAddress == ENDPOINT) { - OLA_INFO << "Using interface " << i; - m_interface_number = i; - libusb_free_config_descriptor(device_config); - return true; - } - } - } - } - libusb_free_config_descriptor(device_config); - return false; -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/EuroliteProOutputPort.h b/plugins/usbdmx/EuroliteProOutputPort.h deleted file mode 100644 index 92370e1d9f..0000000000 --- a/plugins/usbdmx/EuroliteProOutputPort.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - * - * EuroliteProOutputPort.h - * The output port for a EurolitePro device. - * Copyright (C) 2011 Simon Newton & Harry F - * Eurolite Pro USB DMX ArtNo. 51860120 - */ - -#ifndef PLUGINS_USBDMX_EUROLITEPROOUTPUTPORT_H_ -#define PLUGINS_USBDMX_EUROLITEPROOUTPUTPORT_H_ - -#include -#include -#include "ola/DmxBuffer.h" -#include "ola/thread/Thread.h" -#include "olad/Port.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -class EuroliteProOutputPort: public BasicOutputPort, ola::thread::Thread { - public: - EuroliteProOutputPort(class EuroliteProDevice *parent, - unsigned int id, - libusb_device *usb_device); - ~EuroliteProOutputPort(); - std::string SerialNumber() const { return m_serial; } - - bool Start(); - void *Run(); - - bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); - std::string Description() const { return ""; } - - private: - static const unsigned int URB_TIMEOUT_MS = 500; - static const unsigned int UDMX_SET_CHANNEL_RANGE = 0x0002; - static const unsigned char ENDPOINT = 0x02; - static const char EXPECTED_MANUFACTURER[]; - static const char EXPECTED_PRODUCT[]; - static const uint8_t DMX_LABEL = 6; - - bool m_term; - int m_interface_number; - std::string m_serial; - - libusb_device *m_usb_device; - libusb_device_handle *m_usb_handle; - DmxBuffer m_buffer; - ola::thread::Mutex m_data_mutex; - ola::thread::Mutex m_term_mutex; - - bool SendDMX(const DmxBuffer &buffer_old); - - bool LocateInterface(); - - // 513 + header + code + size(2) + footer - enum { FRAME_SIZE = 518 }; -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_EUROLITEPROOUTPUTPORT_H_ diff --git a/plugins/usbdmx/EuroliteProWidget.cpp b/plugins/usbdmx/EuroliteProWidget.cpp new file mode 100644 index 0000000000..572b8de30a --- /dev/null +++ b/plugins/usbdmx/EuroliteProWidget.cpp @@ -0,0 +1,247 @@ +/* + * 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. + * + * EuroliteProWidget.cpp + * The synchronous and asynchronous EurolitePro widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/EuroliteProWidget.h" + +#include +#include + +#include "ola/Constants.h" +#include "ola/Logging.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::string; + +namespace { + +// Why is this so long? +static const unsigned int URB_TIMEOUT_MS = 500; +static const uint8_t DMX_LABEL = 6; +static const unsigned char ENDPOINT = 0x02; +enum { EUROLITE_PRO_FRAME_SIZE = 518 }; + +/* + * Create a Eurolite Pro message to match the supplied DmxBuffer. + */ +void CreateFrame( + const DmxBuffer &buffer, + uint8_t frame[EUROLITE_PRO_FRAME_SIZE]) { + unsigned int frame_size = buffer.Size(); + + // header + frame[0] = 0x7E; // Start message delimiter + frame[1] = DMX_LABEL; // Label + frame[4] = DMX512_START_CODE; + buffer.Get(frame + 5, &frame_size); + frame[2] = (DMX_UNIVERSE_SIZE + 1) & 0xff; // Data length LSB. + frame[3] = ((DMX_UNIVERSE_SIZE + 1) >> 8); // Data length MSB + memset(frame + 5 + frame_size, 0, DMX_UNIVERSE_SIZE - frame_size); + // End message delimiter + + frame[EUROLITE_PRO_FRAME_SIZE - 1] = 0xE7; +} + +/** + * Find the interface with the endpoint we're after. Usually this is interface + * 1 but we check them all just in case. + */ +bool LocateInterface(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + int *interface_number) { + struct libusb_config_descriptor *device_config; + if (adaptor->GetConfigDescriptor(usb_device, 0, &device_config) != 0) { + OLA_WARN << "Failed to get device config descriptor"; + return false; + } + + OLA_DEBUG << static_cast(device_config->bNumInterfaces) + << " interfaces found"; + for (unsigned int i = 0; i < device_config->bNumInterfaces; i++) { + const struct libusb_interface *interface = &device_config->interface[i]; + for (int j = 0; j < interface->num_altsetting; j++) { + const struct libusb_interface_descriptor *iface_descriptor = + &interface->altsetting[j]; + for (uint8_t k = 0; k < iface_descriptor->bNumEndpoints; k++) { + const struct libusb_endpoint_descriptor *endpoint = + &iface_descriptor->endpoint[k]; + OLA_DEBUG << "Interface " << i << ", altsetting " << j << ", endpoint " + << static_cast(k) << ", endpoint address 0x" << std::hex + << static_cast(endpoint->bEndpointAddress); + if (endpoint->bEndpointAddress == ENDPOINT) { + OLA_INFO << "Using interface " << i; + *interface_number = i; + adaptor->FreeConfigDescriptor(device_config); + return true; + } + } + } + } + OLA_WARN << "Failed to locate endpoint for EurolitePro device."; + adaptor->FreeConfigDescriptor(device_config); + return false; +} +} // namespace + +// EuroliteProThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a EurolitePro device in a separate thread. + */ +class EuroliteProThreadedSender: public ThreadedUsbSender { + public: + EuroliteProThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle); + + private: + LibUsbAdaptor* const m_adaptor; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); +}; + +EuroliteProThreadedSender::EuroliteProThreadedSender( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *usb_handle) + : ThreadedUsbSender(usb_device, usb_handle), + m_adaptor(adaptor) { +} + +bool EuroliteProThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + uint8_t frame[EUROLITE_PRO_FRAME_SIZE]; + CreateFrame(buffer, frame); + + int transferred; + int r = m_adaptor->BulkTransfer(handle, ENDPOINT, frame, + EUROLITE_PRO_FRAME_SIZE, &transferred, + URB_TIMEOUT_MS); + if (transferred != EUROLITE_PRO_FRAME_SIZE) { + // not sure if this is fatal or not + OLA_WARN << "EurolitePro driver failed to transfer all data"; + } + return r == 0; +} + +// SynchronousEuroliteProWidget +// ----------------------------------------------------------------------------- + +SynchronousEuroliteProWidget::SynchronousEuroliteProWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const string &serial) + : EuroliteProWidget(adaptor, serial), + m_usb_device(usb_device) { +} + +bool SynchronousEuroliteProWidget::Init() { + libusb_device_handle *usb_handle; + + int interface_number; + if (!LocateInterface(m_adaptor, m_usb_device, &interface_number)) { + return false; + } + + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, interface_number, &usb_handle); + if (!ok) { + return false; + } + + std::auto_ptr sender( + new EuroliteProThreadedSender(m_adaptor, m_usb_device, usb_handle)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousEuroliteProWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} + +// EuroliteProAsyncUsbSender +// ----------------------------------------------------------------------------- +class EuroliteProAsyncUsbSender : public AsyncUsbSender { + public: + EuroliteProAsyncUsbSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : AsyncUsbSender(adaptor, usb_device) { + } + + ~EuroliteProAsyncUsbSender() { + CancelTransfer(); + } + + libusb_device_handle* SetupHandle() { + int interface_number; + if (!LocateInterface(m_adaptor, m_usb_device, &interface_number)) { + return NULL; + } + + libusb_device_handle *usb_handle; + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, 0, &usb_handle); + return ok ? usb_handle : NULL; + } + + bool PerformTransfer(const DmxBuffer &buffer) { + CreateFrame(buffer, m_tx_frame); + FillBulkTransfer(ENDPOINT, m_tx_frame, EUROLITE_PRO_FRAME_SIZE, + URB_TIMEOUT_MS); + return SubmitTransfer() == 0; + } + + private: + uint8_t m_tx_frame[EUROLITE_PRO_FRAME_SIZE]; + + DISALLOW_COPY_AND_ASSIGN(EuroliteProAsyncUsbSender); +}; + +// AsynchronousEuroliteProWidget +// ----------------------------------------------------------------------------- + +AsynchronousEuroliteProWidget::AsynchronousEuroliteProWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const string &serial) + : EuroliteProWidget(adaptor, serial) { + m_sender.reset(new EuroliteProAsyncUsbSender(m_adaptor, usb_device)); +} + +bool AsynchronousEuroliteProWidget::Init() { + return m_sender->Init(); +} + +bool AsynchronousEuroliteProWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender->SendDMX(buffer); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/EuroliteProWidget.h b/plugins/usbdmx/EuroliteProWidget.h new file mode 100644 index 0000000000..a6d2041aab --- /dev/null +++ b/plugins/usbdmx/EuroliteProWidget.h @@ -0,0 +1,121 @@ +/* + * 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. + * + * EuroliteProWidget.h + * The synchronous and asynchronous EurolitePro widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_EUROLITEPROWIDGET_H_ +#define PLUGINS_USBDMX_EUROLITEPROWIDGET_H_ + +#include +#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 EuroliteProThreadedSender; + +/** + * @brief The EurolitePro Widget. + */ +class EuroliteProWidget : public BaseWidget { + public: + /** + * @brief Create a new EuroliteProWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param serial the serial number of the widget. + */ + EuroliteProWidget(LibUsbAdaptor *adaptor, + const std::string &serial) + : BaseWidget(adaptor), + m_serial(serial) {} + + /** + * @brief Get the serial number of this widget. + * @returns The serial number of the widget. + */ + std::string SerialNumber() const { + return m_serial; + } + + private: + std::string m_serial; +}; + + +/** + * @brief An EurolitePro widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousEuroliteProWidget: public EuroliteProWidget { + public: + /** + * @brief Create a new SynchronousEuroliteProWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + SynchronousEuroliteProWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + libusb_device* const m_usb_device; + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousEuroliteProWidget); +}; + +/** + * @brief An EurolitePro widget that uses asynchronous libusb operations. + */ +class AsynchronousEuroliteProWidget: public EuroliteProWidget { + public: + /** + * @brief Create a new AsynchronousEuroliteProWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + AsynchronousEuroliteProWidget(class LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousEuroliteProWidget); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_EUROLITEPROWIDGET_H_ diff --git a/plugins/usbdmx/EuroliteProWidgetFactory.cpp b/plugins/usbdmx/EuroliteProWidgetFactory.cpp new file mode 100644 index 0000000000..46333a8b8d --- /dev/null +++ b/plugins/usbdmx/EuroliteProWidgetFactory.cpp @@ -0,0 +1,86 @@ +/* + * 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. + * + * EuroliteProWidgetFactory.cpp + * The WidgetFactory for EurolitePro widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/EuroliteProWidgetFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" + +DECLARE_bool(use_async_libusb); + +namespace ola { +namespace plugin { +namespace usbdmx { + +const char EuroliteProWidgetFactory::EXPECTED_MANUFACTURER[] = "Eurolite"; +const char EuroliteProWidgetFactory::EXPECTED_PRODUCT[] = "Eurolite DMX512 Pro"; +const uint16_t EuroliteProWidgetFactory::PRODUCT_ID = 0xfa63; +const uint16_t EuroliteProWidgetFactory::VENDOR_ID = 0x04d; + + +bool EuroliteProWidgetFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor != VENDOR_ID || descriptor.idProduct != PRODUCT_ID || + HasDevice(usb_device)) { + return false; + } + + OLA_INFO << "Found a new EurolitePro device"; + LibUsbAdaptor::DeviceInformation info; + if (!m_adaptor->GetDeviceInfo(usb_device, descriptor, &info)) { + return false; + } + + if (!m_adaptor->CheckManufacturer(EXPECTED_MANUFACTURER, info.manufacturer)) { + return false; + } + + if (!m_adaptor->CheckProduct(EXPECTED_PRODUCT, info.product)) { + return false; + } + + // The Eurolite doesn't have a serial number, so instead we use the device & + // bus number. + // TODO(simon): check if this supports the SERIAL NUMBER label and use that + // instead. + + // There is no Serialnumber--> work around: bus+device number + int bus_number = libusb_get_bus_number(usb_device); + int device_address = libusb_get_device_address(usb_device); + + std::ostringstream serial_str; + serial_str << bus_number << "-" << device_address; + + EuroliteProWidget *widget = NULL; + if (FLAGS_use_async_libusb) { + widget = new AsynchronousEuroliteProWidget(m_adaptor, usb_device, + serial_str.str()); + } else { + widget = new SynchronousEuroliteProWidget(m_adaptor, usb_device, + serial_str.str()); + } + return AddWidget(observer, usb_device, widget); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/EuroliteProWidgetFactory.h b/plugins/usbdmx/EuroliteProWidgetFactory.h new file mode 100644 index 0000000000..aacb38010c --- /dev/null +++ b/plugins/usbdmx/EuroliteProWidgetFactory.h @@ -0,0 +1,60 @@ +/* + * 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. + * + * EuroliteProWidgetFactory.h + * The WidgetFactory for EurolitePro widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_EUROLITEPROWIDGETFACTORY_H_ +#define PLUGINS_USBDMX_EUROLITEPROWIDGETFACTORY_H_ + +#include "ola/base/Macro.h" +#include "plugins/usbdmx/EuroliteProWidget.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Creates EurolitePro widgets. + */ +class EuroliteProWidgetFactory + : public BaseWidgetFactory { + public: + explicit EuroliteProWidgetFactory(class LibUsbAdaptor *adaptor) + : m_adaptor(adaptor) {} + + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); + + private: + class LibUsbAdaptor *m_adaptor; + + static const uint16_t PRODUCT_ID; + static const uint16_t VENDOR_ID; + static const char EXPECTED_MANUFACTURER[]; + static const char EXPECTED_PRODUCT[]; + + + DISALLOW_COPY_AND_ASSIGN(EuroliteProWidgetFactory); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_EUROLITEPROWIDGETFACTORY_H_ diff --git a/plugins/usbdmx/FadecandyWidget.cpp b/plugins/usbdmx/FadecandyWidget.cpp new file mode 100644 index 0000000000..1fbee31476 --- /dev/null +++ b/plugins/usbdmx/FadecandyWidget.cpp @@ -0,0 +1,327 @@ +/* + * 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. + * + * FadecandyWidget.cpp + * The synchronous and asynchronous Fadecandy widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/FadecandyWidget.h" + +#include +#include +#include +#include +#include + +#include "ola/Constants.h" +#include "ola/Logging.h" +#include "ola/StringUtils.h" +#include "ola/util/Utils.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::string; + +namespace { + +static const unsigned char ENDPOINT = 0x01; +// 2s is a really long time. Can we reduce this? +static const unsigned int URB_TIMEOUT_MS = 2000; +static const int INTERFACE = 0; + +// A data frame. +static const uint8_t TYPE_FRAMEBUFFER = 0x00; +// The color lookup table +static const uint8_t TYPE_LUT = 0x40; +// The initial setup message. +static const uint8_t CONFIG_MESSAGE = 0x80; +// The final packet in a set. +static const uint8_t FINAL = 0x20; + +// Options used in the first data byte of the config message. +static const uint8_t OPTION_NO_DITHERING = 0x01; +static const uint8_t OPTION_NO_INTERPOLATION = 0x02; +// static const uint8_t OPTION_NO_ACTIVITY_LED = 0x03; +// static const uint8_t OPTION_LED_CONTROL = 0x04; + +// Each 'packet' is 63 bytes, or 21 RGB pixels. +enum { SLOTS_PER_PACKET = 63 }; +static const uint8_t PACKETS_PER_UPDATE = 25; + +PACK( +struct fadecandy_packet { + uint8_t type; + uint8_t data[SLOTS_PER_PACKET]; + + void Reset() { + type = 0; + memset(data, 0, sizeof(data)); + } + + fadecandy_packet() { + Reset(); + } +}); + +bool InitializeWidget(LibUsbAdaptor *adaptor, + libusb_device_handle *usb_handle) { + // Set the fadecandy configuration. + fadecandy_packet packet; + packet.type = CONFIG_MESSAGE; + packet.data[0] = OPTION_NO_DITHERING | OPTION_NO_INTERPOLATION; + + // packet.data[0] = OPTION_NO_ACTIVITY_LED; // Manual control of LED + // packet.data[0] |= OPTION_LED_CONTROL; // Manual LED state + + int bytes_sent = 0; + adaptor->BulkTransfer(usb_handle, ENDPOINT, + reinterpret_cast(&packet), + sizeof(packet), &bytes_sent, URB_TIMEOUT_MS); + OLA_INFO << "Config transferred " << bytes_sent << " bytes"; + + // Build the Look Up Table + uint16_t lut[3][257]; + memset(&lut, 0, sizeof(lut)); + for (unsigned int channel = 0; channel < 3; channel++) { + for (unsigned int value = 0; value < 257; value++) { + // Fadecandy Python Example + // lut[channel][value] = std::min( + // static_cast(std::numeric_limits::max()), + // int(pow(value / 256.0, 2.2) * + // (std::numeric_limits::max() + 1))); + // 1:1 + lut[channel][value] = std::min( + static_cast(std::numeric_limits::max()), + (value << 8)); + OLA_DEBUG << "Generated LUT for channel " << channel << " value " + << value << " with val " << lut[channel][value]; + } + } + + OLA_INFO << "LUT size " << (sizeof(lut) / 2); + unsigned int index = 0; + packet.Reset(); + + // Transfer the lookup table. + for (unsigned int channel = 0; channel < 3; channel++) { + for (unsigned int value = 0; value < 257; value++) { + unsigned int entry = (channel * 257) + value; + unsigned int packet_entry = entry % 31; + OLA_DEBUG << "Working on channel " << channel << " value " << value + << " (" << IntToHexString(value) << ") with entry " << entry + << ", packet entry " << packet_entry << " with val " + << IntToHexString(lut[channel][value]); + ola::utils::SplitUInt16(lut[channel][value], + &packet.data[packet_entry + 1], + &packet.data[packet_entry]); + if ((packet_entry == 30) || (entry == ((3*257) - 1))) { + packet.type = TYPE_LUT | index; + if (entry == ((3*257) - 1)) { + OLA_DEBUG << "Setting final flag on packet"; + packet.type = FINAL; + } + packet.type = TYPE_LUT | index; + packet.data[0] = 0; // Reserved + + // Send the data + int lut_txed = 0; + + // TODO(Peter): Fix the calculations and transmit this + // m_adaptor->BulkTransfer(usb_handle, + // ENDPOINT, + // packet, + // sizeof(packet), + // &lut_txed, + // URB_TIMEOUT_MS); + + OLA_INFO << "LUT packet " << index << " transferred " << lut_txed + << " bytes"; + + // Get ready for the next packet + index++; + packet.Reset(); + } + } + } + return true; +} + +void UpdatePacketsWithDMX(fadecandy_packet packets[PACKETS_PER_UPDATE], + const DmxBuffer &buffer) { + for (unsigned int packet_index = 0; packet_index < PACKETS_PER_UPDATE; + packet_index++) { + packets[packet_index].Reset(); + + unsigned int dmx_offset = packet_index * SLOTS_PER_PACKET; + unsigned int slots_in_packet = SLOTS_PER_PACKET; + buffer.GetRange(dmx_offset, packets[packet_index].data, + &slots_in_packet); + + packets[packet_index].type = TYPE_FRAMEBUFFER | packet_index; + if (packet_index == PACKETS_PER_UPDATE - 1) { + packets[packet_index].type |= FINAL; + } + } +} + +} // namespace + +// FadecandyThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a Fadecandy device in a separate thread. + */ +class FadecandyThreadedSender: public ThreadedUsbSender { + public: + FadecandyThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle) + : ThreadedUsbSender(usb_device, handle), + m_adaptor(adaptor) { + } + + private: + LibUsbAdaptor* const m_adaptor; + fadecandy_packet m_data_packets[PACKETS_PER_UPDATE]; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); +}; + +bool FadecandyThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + UpdatePacketsWithDMX(m_data_packets, buffer); + + int bytes_sent = 0; + // We do a single bulk transfer of the entire data, rather than one transfer + // for each 64 bytes. + int r = m_adaptor->BulkTransfer( + handle, ENDPOINT, + reinterpret_cast(&m_data_packets), + sizeof(m_data_packets), &bytes_sent, + URB_TIMEOUT_MS); + return r == 0; +} + +// SynchronousFadecandyWidget +// ----------------------------------------------------------------------------- + +SynchronousFadecandyWidget::SynchronousFadecandyWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial) + : FadecandyWidget(adaptor, serial), + m_usb_device(usb_device) { +} + +bool SynchronousFadecandyWidget::Init() { + libusb_device_handle *usb_handle; + + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, INTERFACE, &usb_handle); + if (!ok) { + return false; + } + + if (!InitializeWidget(m_adaptor, usb_handle)) { + m_adaptor->Close(usb_handle); + return false; + } + + std::auto_ptr sender( + new FadecandyThreadedSender(m_adaptor, m_usb_device, usb_handle)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousFadecandyWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} + +// FadecandyAsyncUsbSender +// ----------------------------------------------------------------------------- +class FadecandyAsyncUsbSender : public AsyncUsbSender { + public: + FadecandyAsyncUsbSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : AsyncUsbSender(adaptor, usb_device) { + } + + libusb_device_handle* SetupHandle(); + + bool PerformTransfer(const DmxBuffer &buffer); + + private: + fadecandy_packet m_data_packets[PACKETS_PER_UPDATE]; + + DISALLOW_COPY_AND_ASSIGN(FadecandyAsyncUsbSender); +}; + +libusb_device_handle* FadecandyAsyncUsbSender::SetupHandle() { + libusb_device_handle *usb_handle; + if (!m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, INTERFACE, &usb_handle)) { + return NULL; + } + + if (!InitializeWidget(m_adaptor, usb_handle)) { + m_adaptor->Close(usb_handle); + return NULL; + } + return usb_handle; +} + +bool FadecandyAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer) { + UpdatePacketsWithDMX(m_data_packets, buffer); + // We do a single bulk transfer of the entire data, rather than one transfer + // for each 64 bytes. + FillBulkTransfer(ENDPOINT, + reinterpret_cast(&m_data_packets), + sizeof(m_data_packets), + URB_TIMEOUT_MS); + return SubmitTransfer() == 0; +} + +// AsynchronousFadecandyWidget +// ----------------------------------------------------------------------------- + +AsynchronousFadecandyWidget::AsynchronousFadecandyWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial) + : FadecandyWidget(adaptor, serial) { + m_sender.reset(new FadecandyAsyncUsbSender(m_adaptor, usb_device)); +} + +bool AsynchronousFadecandyWidget::Init() { + return m_sender->Init(); +} + +bool AsynchronousFadecandyWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender->SendDMX(buffer); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/FadecandyWidget.h b/plugins/usbdmx/FadecandyWidget.h new file mode 100644 index 0000000000..63e6e56ea8 --- /dev/null +++ b/plugins/usbdmx/FadecandyWidget.h @@ -0,0 +1,123 @@ +/* + * 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. + * + * FadecandyWidget.h + * The synchronous and asynchronous Fadecandy widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_FADECANDYWIDGET_H_ +#define PLUGINS_USBDMX_FADECANDYWIDGET_H_ + +#include +#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 { + +/** + * @brief The interface for the Fadecandy Widgets. + * + * Fadecandy devices have 8 physical ports. Each port can drive 64 RGB pixels. + * Ideally this means we'd model each Fadecandy port as an OLA port, but that + * introduces syncronization issues, since the underlying protocol models all 8 + * ports as a flat pixel array. For now we just expose the first 170 pixels. + * + * See https://github.com/scanlime/fadecandy/blob/master/README.md for more + * information on Fadecandy devices. + */ +class FadecandyWidget: public BaseWidget { + public: + FadecandyWidget(LibUsbAdaptor *adaptor, + const std::string &serial) + : BaseWidget(adaptor), + m_serial(serial) { + } + + /** + * @brief Get the serial number of this widget. + * @returns The serial number of the widget. + */ + std::string SerialNumber() const { + return m_serial; + } + + private: + std::string m_serial; +}; + +/** + * @brief An Fadecandy widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousFadecandyWidget: public FadecandyWidget { + public: + /** + * @brief Create a new SynchronousFadecandyWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + SynchronousFadecandyWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + libusb_device* const m_usb_device; + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousFadecandyWidget); +}; + +/** + * @brief An Fadecandy widget that uses asynchronous libusb operations. + */ +class AsynchronousFadecandyWidget : public FadecandyWidget { + public: + /** + * @brief Create a new AsynchronousFadecandyWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + * @param serial the serial number of the widget. + */ + AsynchronousFadecandyWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + const std::string &serial); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousFadecandyWidget); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_FADECANDYWIDGET_H_ diff --git a/plugins/usbdmx/FadecandyWidgetFactory.cpp b/plugins/usbdmx/FadecandyWidgetFactory.cpp new file mode 100644 index 0000000000..7865aebc41 --- /dev/null +++ b/plugins/usbdmx/FadecandyWidgetFactory.cpp @@ -0,0 +1,91 @@ +/* + * 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. + * + * FadecandyWidgetFactory.cpp + * The WidgetFactory for Fadecandy widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/FadecandyWidgetFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" +#include "plugins/usbdmx/FadecandyWidget.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" + +DECLARE_bool(use_async_libusb); + + +namespace ola { +namespace plugin { +namespace usbdmx { + +const char FadecandyWidgetFactory::EXPECTED_MANUFACTURER[] = "scanlime"; +const char FadecandyWidgetFactory::EXPECTED_PRODUCT[] = "Fadecandy"; +const uint16_t FadecandyWidgetFactory::PRODUCT_ID = 0x1D50; +const uint16_t FadecandyWidgetFactory::VENDOR_ID = 0x607A; + + +bool FadecandyWidgetFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor != VENDOR_ID || descriptor.idProduct != PRODUCT_ID || + HasDevice(usb_device)) { + return false; + } + + OLA_INFO << "Found a new Fadecandy device"; + LibUsbAdaptor::DeviceInformation info; + if (!m_adaptor->GetDeviceInfo(usb_device, descriptor, &info)) { + return false; + } + + if (!m_adaptor->CheckManufacturer(EXPECTED_MANUFACTURER, info.manufacturer)) { + return false; + } + + if (!m_adaptor->CheckProduct(EXPECTED_PRODUCT, info.product)) { + return false; + } + + // Fadecandy devices may be missing serial numbers. Since there isn't another + // good way to uniquely identify a USB device, we only support one of these + // types of devices per host. + if (info.serial.empty()) { + if (m_missing_serial_number) { + OLA_WARN << "Failed to read serial number or serial number empty. " + << "We can only support one device without a serial number."; + return false; + } else { + OLA_WARN << "Failed to read serial number from " << info.manufacturer + << " : " << info.product + << " the device probably doesn't have one"; + m_missing_serial_number = true; + } + } + + FadecandyWidget *widget = NULL; + if (FLAGS_use_async_libusb) { + widget = new AsynchronousFadecandyWidget(m_adaptor, usb_device, + info.serial); + } else { + widget = new SynchronousFadecandyWidget(m_adaptor, usb_device, info.serial); + } + return AddWidget(observer, usb_device, widget); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/FadecandyWidgetFactory.h b/plugins/usbdmx/FadecandyWidgetFactory.h new file mode 100644 index 0000000000..a440ff63ed --- /dev/null +++ b/plugins/usbdmx/FadecandyWidgetFactory.h @@ -0,0 +1,60 @@ +/* + * 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. + * + * FadecandyWidgetFactory.h + * The WidgetFactory for Fadecandy / FadeCandy widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_FADECANDYWIDGETFACTORY_H_ +#define PLUGINS_USBDMX_FADECANDYWIDGETFACTORY_H_ + +#include "ola/base/Macro.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Creates Fadecandy widgets. + */ +class FadecandyWidgetFactory : public BaseWidgetFactory { + public: + explicit FadecandyWidgetFactory(class LibUsbAdaptor *adaptor) + : m_missing_serial_number(false), + m_adaptor(adaptor) { + } + + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); + + private: + bool m_missing_serial_number; + class LibUsbAdaptor *m_adaptor; + + static const char EXPECTED_MANUFACTURER[]; + static const char EXPECTED_PRODUCT[]; + static const uint16_t PRODUCT_ID; + static const uint16_t VENDOR_ID; + + DISALLOW_COPY_AND_ASSIGN(FadecandyWidgetFactory); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_FADECANDYWIDGETFACTORY_H_ diff --git a/plugins/usbdmx/VellemanDevice.cpp b/plugins/usbdmx/GenericDevice.cpp similarity index 58% rename from plugins/usbdmx/VellemanDevice.cpp rename to plugins/usbdmx/GenericDevice.cpp index 9ab068df66..3616314111 100644 --- a/plugins/usbdmx/VellemanDevice.cpp +++ b/plugins/usbdmx/GenericDevice.cpp @@ -13,36 +13,32 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * VellemanDevice.cpp - * The Velleman usb driver - * Copyright (C) 2010 Simon Newton + * GenericDevice.cpp + * A Generic device that creates a single port. + * Copyright (C) 2014 Simon Newton */ -#include -#include -#include +#include "plugins/usbdmx/GenericDevice.h" -#include "ola/Logging.h" -#include "plugins/usbdmx/VellemanDevice.h" -#include "plugins/usbdmx/VellemanOutputPort.h" +#include +#include "plugins/usbdmx/Widget.h" +#include "plugins/usbdmx/GenericOutputPort.h" namespace ola { namespace plugin { namespace usbdmx { +GenericDevice::GenericDevice(ola::AbstractPlugin *owner, + Widget *widget, + const std::string &device_name, + const std::string &device_id) + : Device(owner, device_name), + m_device_id(device_id), + m_port(new GenericOutputPort(this, 0, widget)) { +} -/* - * Start this device. - */ -bool VellemanDevice::StartHook() { - VellemanOutputPort *output_port = new VellemanOutputPort(this, - 0, - m_usb_device); - if (!output_port->Start()) { - delete output_port; - return false; - } - AddPort(output_port); +bool GenericDevice::StartHook() { + AddPort(m_port.release()); return true; } } // namespace usbdmx diff --git a/plugins/usbdmx/GenericDevice.h b/plugins/usbdmx/GenericDevice.h new file mode 100644 index 0000000000..7728d3de6f --- /dev/null +++ b/plugins/usbdmx/GenericDevice.h @@ -0,0 +1,68 @@ +/* + * 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. + * + * GenericDevice.h + * A Generic device that creates a single port. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_GENERICDEVICE_H_ +#define PLUGINS_USBDMX_GENERICDEVICE_H_ + +#include +#include +#include "ola/base/Macro.h" +#include "olad/Device.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief An Generic device. + * + * This simple generic device creates a single output port around a Widget. + */ +class GenericDevice: public Device { + public: + /** + * @brief Create a new GenericDevice. + * @param owner The plugin this device belongs to + * @param widget The widget to use for this device. + * @param device_name The name of the device. + * @param device_id The id of the device. + */ + GenericDevice(ola::AbstractPlugin *owner, + class Widget *widget, + const std::string &device_name, + const std::string &device_id); + + std::string DeviceId() const { + return m_device_id; + } + + protected: + bool StartHook(); + + private: + const std::string m_device_id; + std::auto_ptr m_port; + + DISALLOW_COPY_AND_ASSIGN(GenericDevice); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_GENERICDEVICE_H_ diff --git a/plugins/usbdmx/SunliteDevice.cpp b/plugins/usbdmx/GenericOutputPort.cpp similarity index 60% rename from plugins/usbdmx/SunliteDevice.cpp rename to plugins/usbdmx/GenericOutputPort.cpp index 36b76be780..dd6cc304b8 100644 --- a/plugins/usbdmx/SunliteDevice.cpp +++ b/plugins/usbdmx/GenericOutputPort.cpp @@ -13,34 +13,31 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * SunliteDevice.cpp - * The USBDMX2 device - * Copyright (C) 2010 Simon Newton + * GenericOutputPort.cpp + * A Generic output port that uses a widget. + * Copyright (C) 2014 Simon Newton */ -#include -#include +#include "plugins/usbdmx/GenericOutputPort.h" #include "ola/Logging.h" -#include "plugins/usbdmx/SunliteDevice.h" -#include "plugins/usbdmx/SunliteOutputPort.h" +#include "olad/Device.h" +#include "plugins/usbdmx/Widget.h" namespace ola { namespace plugin { namespace usbdmx { -/* - * Start this device. - */ -bool SunliteDevice::StartHook() { - SunliteOutputPort *output_port = new SunliteOutputPort(this, - 0, - m_usb_device); - if (!output_port->Start()) { - delete output_port; - return false; - } - AddPort(output_port); +GenericOutputPort::GenericOutputPort(Device *parent, + unsigned int id, + Widget *widget) + : BasicOutputPort(parent, id), + m_widget(widget) { +} + +bool GenericOutputPort::WriteDMX(const DmxBuffer &buffer, + OLA_UNUSED uint8_t priority) { + m_widget->SendDMX(buffer); return true; } } // namespace usbdmx diff --git a/plugins/usbdmx/GenericOutputPort.h b/plugins/usbdmx/GenericOutputPort.h new file mode 100644 index 0000000000..221a39cd77 --- /dev/null +++ b/plugins/usbdmx/GenericOutputPort.h @@ -0,0 +1,64 @@ +/* + * 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. + * + * GenericOutputPort.h + * A Generic output port that uses a widget. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_GENERICOUTPUTPORT_H_ +#define PLUGINS_USBDMX_GENERICOUTPUTPORT_H_ + +#include +#include "ola/base/Macro.h" +#include "olad/Port.h" + +namespace ola { + +class Device; + +namespace plugin { +namespace usbdmx { + +class Widget; + +/** + * @brief A thin wrapper around a Widget so that it can operate as a Port. + */ +class GenericOutputPort: public BasicOutputPort { + public: + /** + * @brief Create a new GenericOutputPort. + * @param parent The parent device for this port. + * @param id The port id. + * @param widget The widget to use to send DMX frames. + */ + GenericOutputPort(Device *parent, + unsigned int id, + class Widget *widget); + + bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); + + std::string Description() const { return ""; } + + private: + class Widget* const m_widget; + + DISALLOW_COPY_AND_ASSIGN(GenericOutputPort); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_GENERICOUTPUTPORT_H_ diff --git a/plugins/usbdmx/LibUsbAdaptor.cpp b/plugins/usbdmx/LibUsbAdaptor.cpp new file mode 100644 index 0000000000..f8b45257a6 --- /dev/null +++ b/plugins/usbdmx/LibUsbAdaptor.cpp @@ -0,0 +1,366 @@ +/* + * 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. + * + * LibUsbAdaptor.cpp + * The wrapper around libusb calls. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/LibUsbAdaptor.h" + +#include +#include +#include + +#include "plugins/usbdmx/LibUsbThread.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::string; + +namespace { + +/** + * @brief A wrapper around libusb_get_string_descriptor_ascii. + */ +bool GetStringDescriptorAscii(libusb_device_handle *usb_handle, + uint8_t desc_index, + string *data) { + enum { buffer_size = 32 }; // static arrays FTW! + unsigned char buffer[buffer_size]; + int r = libusb_get_string_descriptor_ascii( + usb_handle, + desc_index, + buffer, + buffer_size); + + if (r <= 0) { + OLA_INFO << "libusb_get_string_descriptor_ascii failed: " + << libusb_error_name(r); + return false; + } + data->assign(reinterpret_cast(buffer)); + return true; +} + +/** + * @brief A wrapper around libusb_open. + */ +bool Open(libusb_device *usb_device, + libusb_device_handle **usb_handle) { + int r = libusb_open(usb_device, usb_handle); + if (r) { + OLA_WARN << "Failed to open libusb device: " << usb_device << ": " + << libusb_error_name(r);; + return false; + } + return true; +} + +bool OpenHandleAndClaimInterface(libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle) { + if (!Open(usb_device, usb_handle)) { + return false; + } + + int r = libusb_claim_interface(*usb_handle, 0); + if (r) { + OLA_WARN << "Failed to claim interface " << interface + << " on device: " << usb_device << ": " + << libusb_error_name(r); + libusb_close(*usb_handle); + return false; + } + return true; +} +} // namespace + +// LibUsbAdaptor +// ---------------------------------------------------------------------------- + +bool LibUsbAdaptor::GetDeviceInfo( + struct libusb_device *usb_device, + const struct libusb_device_descriptor &device_descriptor, + DeviceInformation *device_info) { + // Since the calls on the handle are syncronous, we don't bother adding the + // handle to the thread. + libusb_device_handle *usb_handle; + if (!Open(usb_device, &usb_handle)) { + return false; + } + + if (!GetStringDescriptorAscii(usb_handle, device_descriptor.iManufacturer, + &device_info->manufacturer)) { + OLA_INFO << "Failed to get manufacturer name"; + } + + if (!GetStringDescriptorAscii(usb_handle, device_descriptor.iProduct, + &device_info->product)) { + OLA_INFO << "Failed to get product name"; + } + + if (!GetStringDescriptorAscii(usb_handle, device_descriptor.iSerialNumber, + &device_info->serial)) { + OLA_WARN << "Failed to read serial number, the device probably doesn't " + << "have one"; + } + + libusb_close(usb_handle); + return true; +} + +bool LibUsbAdaptor::CheckManufacturer(const string &expected, + const string &actual) { + if (expected != actual) { + OLA_WARN << "Manufacturer mismatch: " << expected << " != " << actual; + return false; + } + return true; +} + +bool LibUsbAdaptor::CheckProduct(const string &expected, const string &actual) { + if (expected != actual) { + OLA_WARN << "Product mismatch: " << expected << " != " << actual; + return false; + } + return true; +} + +// BaseLibUsbAdaptor +// ---------------------------------------------------------------------------- +libusb_device* BaseLibUsbAdaptor::RefDevice(libusb_device *dev) { + return libusb_ref_device(dev); +} + +void BaseLibUsbAdaptor::UnrefDevice(libusb_device *dev) { + libusb_unref_device(dev); +} + +int BaseLibUsbAdaptor::SetConfiguration(libusb_device_handle *dev, + int configuration) { + return libusb_set_configuration(dev, configuration); +} + +int BaseLibUsbAdaptor::ClaimInterface(libusb_device_handle *dev, + int interface_number) { + return libusb_claim_interface(dev, interface_number); +} + +int BaseLibUsbAdaptor::DetachKernelDriver(libusb_device_handle *dev, + int interface_number) { + if (libusb_kernel_driver_active(dev, interface_number)) { + return libusb_detach_kernel_driver(dev, interface_number); + } else { + return 0; + } +} + +int BaseLibUsbAdaptor::GetActiveConfigDescriptor( + libusb_device *dev, + struct libusb_config_descriptor **config) { + return libusb_get_active_config_descriptor(dev, config); +} + +int BaseLibUsbAdaptor::GetConfigDescriptor( + libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config) { + return libusb_get_config_descriptor(dev, config_index, config); +} + +void BaseLibUsbAdaptor::FreeConfigDescriptor( + struct libusb_config_descriptor *config) { + libusb_free_config_descriptor(config); +} + +struct libusb_transfer* BaseLibUsbAdaptor::AllocTransfer(int iso_packets) { + return libusb_alloc_transfer(iso_packets); +} + +void BaseLibUsbAdaptor::FreeTransfer(struct libusb_transfer *transfer) { + return libusb_free_transfer(transfer); +} + +int BaseLibUsbAdaptor::SubmitTransfer(struct libusb_transfer *transfer) { + return libusb_submit_transfer(transfer); +} + +int BaseLibUsbAdaptor::CancelTransfer(struct libusb_transfer *transfer) { + return libusb_cancel_transfer(transfer); +} + +void BaseLibUsbAdaptor::FillControlSetup(unsigned char *buffer, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + uint16_t wLength) { + return libusb_fill_control_setup(buffer, bmRequestType, bRequest, wValue, + wIndex, wLength); +} + +void BaseLibUsbAdaptor::FillControlTransfer( + struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char *buffer, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) { + return libusb_fill_control_transfer(transfer, dev_handle, buffer, callback, + user_data, timeout); +} + +void BaseLibUsbAdaptor::FillBulkTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) { + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, + length, callback, user_data, timeout); +} + +void BaseLibUsbAdaptor::FillInterruptTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) { + libusb_fill_interrupt_transfer(transfer, dev_handle, endpoint, buffer, + length, callback, user_data, timeout); +} + +int BaseLibUsbAdaptor::ControlTransfer( + libusb_device_handle *dev_handle, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout) { + return libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, + wIndex, data, wLength, timeout); +} + +int BaseLibUsbAdaptor::BulkTransfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout) { + return libusb_bulk_transfer(dev_handle, endpoint, data, length, transferred, + timeout); +} + +int BaseLibUsbAdaptor::InterruptTransfer(libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout) { + return libusb_interrupt_transfer(dev_handle, endpoint, data, length, + actual_length, timeout); +} + + +// SyncronousLibUsbAdaptor +// ----------------------------------------------------------------------------- +bool SyncronousLibUsbAdaptor::OpenDevice(libusb_device *usb_device, + libusb_device_handle **usb_handle) { + return Open(usb_device, usb_handle); +} + +bool SyncronousLibUsbAdaptor::OpenDeviceAndClaimInterface( + libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle) { + return OpenHandleAndClaimInterface(usb_device, interface, usb_handle); +} + +void SyncronousLibUsbAdaptor::Close(libusb_device_handle *usb_handle) { + libusb_close(usb_handle); +} + +// AsyncronousLibUsbAdaptor +// ----------------------------------------------------------------------------- +bool AsyncronousLibUsbAdaptor::OpenDevice(libusb_device *usb_device, + libusb_device_handle **usb_handle) { + bool ok = Open(usb_device, usb_handle); + if (ok) { + m_thread->OpenHandle(); + } + return ok; +} + +bool AsyncronousLibUsbAdaptor::OpenDeviceAndClaimInterface( + libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle) { + bool ok = OpenHandleAndClaimInterface(usb_device, interface, usb_handle); + if (ok) { + m_thread->OpenHandle(); + } + return ok; +} + +void AsyncronousLibUsbAdaptor::Close(libusb_device_handle *handle) { + m_thread->CloseHandle(handle); +} + +int AsyncronousLibUsbAdaptor::ControlTransfer( + OLA_UNUSED libusb_device_handle *dev_handle, + OLA_UNUSED uint8_t bmRequestType, + OLA_UNUSED uint8_t bRequest, + OLA_UNUSED uint16_t wValue, + OLA_UNUSED uint16_t wIndex, + OLA_UNUSED unsigned char *data, + OLA_UNUSED uint16_t wLength, + OLA_UNUSED unsigned int timeout) { + OLA_WARN << "libusb_control_transfer in an AsyncronousLibUsbAdaptor"; + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +int AsyncronousLibUsbAdaptor::BulkTransfer( + OLA_UNUSED struct libusb_device_handle *dev_handle, + OLA_UNUSED unsigned char endpoint, + OLA_UNUSED unsigned char *data, + OLA_UNUSED int length, + OLA_UNUSED int *transferred, + OLA_UNUSED unsigned int timeout) { + OLA_WARN << "libusb_bulk_transfer in an AsyncronousLibUsbAdaptor"; + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +int AsyncronousLibUsbAdaptor::InterruptTransfer( + OLA_UNUSED libusb_device_handle *dev_handle, + OLA_UNUSED unsigned char endpoint, + OLA_UNUSED unsigned char *data, + OLA_UNUSED int length, + OLA_UNUSED int *actual_length, + OLA_UNUSED unsigned int timeout) { + OLA_WARN << "libusb_interrupt_transfer in an AsyncronousLibUsbAdaptor"; + return LIBUSB_ERROR_NOT_SUPPORTED; +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/LibUsbAdaptor.h b/plugins/usbdmx/LibUsbAdaptor.h new file mode 100644 index 0000000000..1708781893 --- /dev/null +++ b/plugins/usbdmx/LibUsbAdaptor.h @@ -0,0 +1,549 @@ +/* + * 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. + * + * LibUsbAdaptor.h + * The wrapper around libusb calls. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_LIBUSBADAPTOR_H_ +#define PLUGINS_USBDMX_LIBUSBADAPTOR_H_ + +#include +#include + +#include "ola/base/Macro.h" + +namespace ola { +namespace plugin { +namespace usbdmx { +/** + * @brief Wraps calls to libusb so we can test the code. + */ +class LibUsbAdaptor { + public: + struct DeviceInformation { + std::string manufacturer; + std::string product; + std::string serial; + }; + + virtual ~LibUsbAdaptor() {} + + // Device handling and enumeration + + /** + * @brief Wraps libusb_ref_device. + * @param dev the device to reference + * @returns the same device + */ + virtual libusb_device* RefDevice(libusb_device *dev) = 0; + + /** + * @brief Wraps libusb_unref_device. + * @param dev the device to unreference. + */ + virtual void UnrefDevice(libusb_device *dev) = 0; + + /** + * @brief Open a libusb device. + * @param usb_device The usb device to open. + * @param[out] usb_handle the new device handle. + * @returns true if the device was opened, false otherwise. + */ + virtual bool OpenDevice(libusb_device *usb_device, + libusb_device_handle **usb_handle) = 0; + + /** + * @brief Open a libusb device and claim an interface. + * @param usb_device The usb device to open. + * @param interface the interface index to claim. + * @param[out] usb_handle the new device handle. + * @returns true if the device was opened and the interface claimed, + * false otherwise. + */ + virtual bool OpenDeviceAndClaimInterface( + libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle) = 0; + + /** + * @brief Close a libusb handle. + * @param usb_handle the handle to close. + */ + virtual void Close(libusb_device_handle *usb_handle) = 0; + + /** + * @brief Wraps libusb_set_configuration. + * @param dev a device handle + * @param configuration the bConfigurationValue of the configuration you + * wish to activate, or -1 if you wish to put the device in unconfigured state + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not + * exist. + * @returns LIBUSB_ERROR_BUSY if interfaces are currently claimed + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns another LIBUSB_ERROR code on other failure + */ + virtual int SetConfiguration(libusb_device_handle *dev, + int configuration) = 0; + + /** + * @brief Wraps libusb_claim_interface. + * @param dev a device handle + * @param interface_number the bInterfaceNumber of the interface you + * wish to claim + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist + * @returns LIBUSB_ERROR_BUSY if another program or driver has claimed the + * interface + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns a LIBUSB_ERROR code on other failure + */ + virtual int ClaimInterface(libusb_device_handle *dev, + int interface_number) = 0; + + /** + * @brief Detatch a kernel driver. + * @param dev a device handle + * @param interface_number the interface to detach the driver from + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * @returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * @returns another LIBUSB_ERROR code on other failure + * + */ + virtual int DetachKernelDriver(libusb_device_handle *dev, + int interface_number) = 0; + + // USB descriptors + + /** + * @brief Wraps libusb_get_active_config_descriptor. + * @param dev a device + * @param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state + * @returns another LIBUSB_ERROR code on error + */ + virtual int GetActiveConfigDescriptor( + libusb_device *dev, + struct libusb_config_descriptor **config) = 0; + + /** + * @brief Wraps libusb_get_config_descriptor. + * @param dev a device + * @param config_index the index of the configuration you wish to retrieve + * @param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * @returns another LIBUSB_ERROR code on error + */ + virtual int GetConfigDescriptor(libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config) = 0; + + /** + * @brief Wraps busb_free_config_descriptor. + * @param config the configuration descriptor to free + */ + virtual void FreeConfigDescriptor( + struct libusb_config_descriptor *config) = 0; + + // Asynchronous device I/O + + /** + * @brief Wraps libusb_alloc_transfer + * @param iso_packets number of isochronous packet descriptors to allocate + * @returns a newly allocated transfer, or NULL on error + */ + virtual struct libusb_transfer* AllocTransfer(int iso_packets) = 0; + + /** + * @brief Wraps libusb_free_transfer. + * @param transfer the transfer to free + */ + virtual void FreeTransfer(struct libusb_transfer *transfer) = 0; + + /** + * @brief Wraps libusb_submit_transfer. + * @param transfer the transfer to submit + * @returns 0 on success + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns LIBUSB_ERROR_BUSY if the transfer has already been submitted. + * @returns LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported + * by the operating system. + * @returns another LIBUSB_ERROR code on other failure + */ + virtual int SubmitTransfer(struct libusb_transfer *transfer) = 0; + + /** + * @brief Wraps libusb_cancel_transfer + * @param transfer the transfer to cancel + * @returns 0 on success + * @returns LIBUSB_ERROR_NOT_FOUND if the transfer is already complete or + * cancelled. + * @returns a LIBUSB_ERROR code on failure + */ + virtual int CancelTransfer(struct libusb_transfer *transfer) = 0; + + /** + * @brief Wraps libusb_fill_control_setup + * @param buffer buffer to output the setup packet into + * This pointer must be aligned to at least 2 bytes boundary. + * @param bmRequestType the request type field for the setup packet + * @param bRequest the request field for the setup packet + * @param wValue the value field for the setup packet + * @param wIndex the index field for the setup packet + * @param wLength the length field for the setup packet. The data buffer + * should be at least this size. + */ + virtual void FillControlSetup(unsigned char *buffer, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + uint16_t wLength) = 0; + + /** + * @brief Wraps libusb_fill_control_transfer + * @param transfer the transfer to populate + * @param dev_handle handle of the device that will handle the transfer + * @param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * This pointer must be aligned to at least 2 bytes boundary. + * @param callback callback function to be invoked on transfer completion + * @param user_data user data to pass to callback function + * @param timeout timeout for the transfer in milliseconds + */ + virtual void FillControlTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char *buffer, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) = 0; + + /** + * @brief Wraps libusb_fill_bulk_transfer. + * @param transfer the transfer to populate + * @param dev_handle handle of the device that will handle the transfer + * @param endpoint address of the endpoint where this transfer will be sent + * @param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * This pointer must be aligned to at least 2 bytes boundary. + * @param length length of data buffer + * @param callback callback function to be invoked on transfer completion + * @param user_data user data to pass to callback function + * @param timeout timeout for the transfer in milliseconds + */ + virtual void FillBulkTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) = 0; + + /** + * @brief Wraps libusb_fill_interrupt_transfer. + * @param transfer the transfer to populate + * @param dev_handle handle of the device that will handle the transfer + * @param endpoint address of the endpoint where this transfer will be sent + * @param buffer data buffer + * @param length length of data buffer + * @param callback callback function to be invoked on transfer completion + * @param user_data user data to pass to callback function + * @param timeout timeout for the transfer in milliseconds + */ + virtual void FillInterruptTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout) = 0; + + // Synchronous device I/O + + /** + * @brief Wraps libusb_control_transfer(). + * @param dev_handle a handle for the device to communicate with + * @param bmRequestType the request type field for the setup packet + * @param bRequest the request field for the setup packet + * @param wValue the value field for the setup packet + * @param wIndex the index field for the setup packet + * @param data a suitably-sized data buffer for either input or output + * (depending on direction bits within bmRequestType) + * @param wLength the length field for the setup packet. The data buffer + * should be at least this size. + * @param timeout timeout (in millseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * @returns on success, the number of bytes actually transferred + * + */ + virtual int ControlTransfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout) = 0; + + /** + * @brief Wraps libusb_bulk_transfer. + * @returns 0 on success (and populates transferred) + * @returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates + * transferred) + * @returns LIBUSB_ERROR_PIPE if the endpoint halted + * @returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns another LIBUSB_ERROR code on other failures + */ + virtual int BulkTransfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout) = 0; + + /** + * @brief Wraps libusb_interrupt_transfer + * @returns 0 on success (and populates transferred) + * @returns LIBUSB_ERROR_TIMEOUT if the transfer timed out + * @returns LIBUSB_ERROR_PIPE if the endpoint halted + * @returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * @returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * @returns another LIBUSB_ERROR code on other error + */ + virtual int InterruptTransfer(libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout) = 0; + + // Static helper methods. + + /** + * @brief Fetch the manufacturer, product and serial strings from a device. + * @param usb_device The USB device to get information for. + * @param device_descriptor The descriptor to use + * @param[out] device_info The DeviceInformation struct to populate. + * @returns true if we fetched the information, false otherwise. + */ + static bool GetDeviceInfo( + struct libusb_device *usb_device, + const struct libusb_device_descriptor &device_descriptor, + DeviceInformation *device_info); + + /** + * @brief Check if the manufacturer string matches the expected value. + * @param expected The expected manufacturer string. + * @param actual The actual manufacturer string. + * @returns true if the strings matched, false otherwise. + */ + static bool CheckManufacturer(const std::string &expected, + const std::string &actual); + + /** + * @brief Check if the product string matches the expected value. + * @param expected The expected product string. + * @param actual The actual product string. + * @returns true if the strings matched, false otherwise. + */ + static bool CheckProduct(const std::string &expected, + const std::string &actual); +}; + +/** + * @brief The base LibUsbAdaptor that passes most called through to libusb. + */ +class BaseLibUsbAdaptor : public LibUsbAdaptor { + public: + // Device handling and enumeration + libusb_device* RefDevice(libusb_device *dev); + + void UnrefDevice(libusb_device *dev); + + int SetConfiguration(libusb_device_handle *dev, int configuration); + + int ClaimInterface(libusb_device_handle *dev, int interface_number); + + int DetachKernelDriver(libusb_device_handle *dev, int interface_number); + + // USB descriptors + int GetActiveConfigDescriptor( + libusb_device *dev, + struct libusb_config_descriptor **config); + + int GetConfigDescriptor(libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config); + + void FreeConfigDescriptor(struct libusb_config_descriptor *config); + + // Asynchronous device I/O + struct libusb_transfer* AllocTransfer(int iso_packets); + + void FreeTransfer(struct libusb_transfer *transfer); + + int SubmitTransfer(struct libusb_transfer *transfer); + + int CancelTransfer(struct libusb_transfer *transfer); + + void FillControlSetup(unsigned char *buffer, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + uint16_t wLength); + + void FillControlTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char *buffer, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout); + + void FillBulkTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout); + + void FillInterruptTransfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *buffer, + int length, + libusb_transfer_cb_fn callback, + void *user_data, + unsigned int timeout); + + // Synchronous device I/O + int ControlTransfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout); + + int BulkTransfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout); + + int InterruptTransfer(libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout); +}; + +/** + * @brief A LibUsbAdaptor for use with Syncronous widgets. + * + * When using syncronous mode, we don't have the requirement of interacting + * with a LibUsbThread. + */ +class SyncronousLibUsbAdaptor : public BaseLibUsbAdaptor { + public: + SyncronousLibUsbAdaptor() {} + + bool OpenDevice(libusb_device *usb_device, + libusb_device_handle **usb_handle); + + bool OpenDeviceAndClaimInterface(libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle); + + void Close(libusb_device_handle *usb_handle); + + private: + DISALLOW_COPY_AND_ASSIGN(SyncronousLibUsbAdaptor); +}; + +/** + * @brief A LibUsbAdaptor for use with Asyncronous widgets. + * + * Asyncronous mode requires notifying the LibUsbThread when handles are opened + * and closed. + */ +class AsyncronousLibUsbAdaptor : public BaseLibUsbAdaptor { + public: + explicit AsyncronousLibUsbAdaptor(class LibUsbThread *thread) + : m_thread(thread) { + } + + bool OpenDevice(libusb_device *usb_device, + libusb_device_handle **usb_handle); + + bool OpenDeviceAndClaimInterface(libusb_device *usb_device, + int interface, + libusb_device_handle **usb_handle); + + void Close(libusb_device_handle *usb_handle); + + int ControlTransfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout); + + int BulkTransfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *transferred, + unsigned int timeout); + + int InterruptTransfer(libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout); + + private: + class LibUsbThread *m_thread; + + DISALLOW_COPY_AND_ASSIGN(AsyncronousLibUsbAdaptor); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_LIBUSBADAPTOR_H_ diff --git a/plugins/usbdmx/LibUsbThread.cpp b/plugins/usbdmx/LibUsbThread.cpp new file mode 100644 index 0000000000..a56c154272 --- /dev/null +++ b/plugins/usbdmx/LibUsbThread.cpp @@ -0,0 +1,124 @@ +/* + * 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. + * + * AsyncPluginImpl.cpp + * The asynchronous libusb implementation. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/LibUsbThread.h" + +#include "ola/Logging.h" +#include "ola/StringUtils.h" +#include "ola/stl/STLUtils.h" + + +namespace ola { +namespace plugin { +namespace usbdmx { + +// LibUsbThread +// ----------------------------------------------------------------------------- + +void *LibUsbThread::Run() { + OLA_INFO << "----libusb event thread is running"; + while (1) { + { + ola::thread::MutexLocker locker(&m_term_mutex); + if (m_term) + break; + } + libusb_handle_events(m_context); + } + OLA_INFO << "----libusb thread exiting"; + return NULL; +} + +void LibUsbThread::LaunchThread() { + OLA_INFO << "-- Starting libusb thread"; + Start(); +} + +void LibUsbThread::JoinThread() { + OLA_INFO << "-- Stopping libusb thread"; + Join(); + m_term = false; +} + +// LibUsbHotplugThread +// ----------------------------------------------------------------------------- + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) +LibUsbHotplugThread::LibUsbHotplugThread(libusb_context *context, + libusb_hotplug_callback_fn callback_fn, + void *user_data) + : LibUsbThread(context), + m_hotplug_handle(0), + m_callback_fn(callback_fn), + m_user_data(user_data) { +} + +bool LibUsbHotplugThread::Init() { + int rc = libusb_hotplug_register_callback( + NULL, + static_cast(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + m_callback_fn, m_user_data, &m_hotplug_handle); + + if (LIBUSB_SUCCESS != rc) { + OLA_WARN << "Error creating a hotplug callback"; + return false; + } + LaunchThread(); + return true; +} + +void LibUsbHotplugThread::Shutdown() { + SetTerminate(); + libusb_hotplug_deregister_callback(Context(), m_hotplug_handle); + JoinThread(); +} + +void LibUsbHotplugThread::CloseHandle(libusb_device_handle *handle) { + libusb_close(handle); +} + +#endif + +// LibUsbSimpleThread +// ----------------------------------------------------------------------------- + +void LibUsbSimpleThread::OpenHandle() { + m_device_count++; + if (m_device_count == 1) { + LaunchThread(); + } +} + +void LibUsbSimpleThread::CloseHandle(libusb_device_handle *handle) { + if (m_device_count == 1) { + SetTerminate(); + } + libusb_close(handle); + if (m_device_count == 1) { + JoinThread(); + } + m_device_count--; +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/LibUsbThread.h b/plugins/usbdmx/LibUsbThread.h new file mode 100644 index 0000000000..e764c592a7 --- /dev/null +++ b/plugins/usbdmx/LibUsbThread.h @@ -0,0 +1,204 @@ +/* + * 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. + * + * AsyncPluginImpl.h + * The asynchronous libusb implementation. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_LIBUSBTHREAD_H_ +#define PLUGINS_USBDMX_LIBUSBTHREAD_H_ + +#include + +#include "ola/base/Macro.h" +#include "ola/thread/Thread.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief The base class for the dedicated libusb thread. + * + * Asynchronous I/O for libusb requires either i) a dedicated thread ii) + * integration with the i/o event loop. From the libusb documentation, i) has + * the advantage that it works on Windows, so we do that. + * + * However, there is no easy way to interrupt libusb_handle_events(). Instead + * we use either libusb_close (for the non-hotplug case) or + * libusb_hotplug_deregister_callback() (for the hotplug case) to wake + * libusb_handle_events(). + * + * Both these techniques have require care to avoid deadlocks / race + * conditions. For the non-hotplug case, it's imperative that libusb_open() and + * libusb_close() are paired with calls to OpenHandle() and CloseHandle(). + * + * http://libusb.sourceforge.net/api-1.0/group__asyncio.html covers both + * approaches. + */ +class LibUsbThread : private ola::thread::Thread { + public: + /** + * @brief Base constructor + * @param context the libusb context to use. + */ + explicit LibUsbThread(libusb_context *context) + : m_context(context), + m_term(false) { + } + + /** + * @brief Destructor. + */ + virtual ~LibUsbThread() {} + + /** + * @brief Initialize the thread. + */ + virtual bool Init() { + return true; + } + + /** + * @brief Shutdown the thread. + */ + virtual void Shutdown() {} + + /** + * @brief The entry point to the libusb thread. + * + * Don't call this directly. It's executed when the thread starts. + */ + void *Run(); + + /** + * @brief This must be called whenever libusb_open() is called. + */ + virtual void OpenHandle() = 0; + + /** + * @brief This must be called whenever libusb_close() is called. + */ + virtual void CloseHandle(libusb_device_handle *handle) = 0; + + protected: + /** + * @brief Indicate that the libusb thread should terminate. + * + * This doesn't wake up libusb_handle_events(), it simply sets m_term to + * true. + */ + void SetTerminate() { + ola::thread::MutexLocker locker(&m_term_mutex); + m_term = true; + } + + /** + * @brief Start the libusb thread. + */ + void LaunchThread(); + + /** + * @brief Join the libusb thread. + */ + void JoinThread(); + + /** + * @brief Return the libusb_context this thread uses. + * @returns A libusb_context. + */ + libusb_context* Context() const { return m_context; } + + private: + libusb_context *m_context; + bool m_term; // GUARDED_BY(m_term_mutex) + ola::thread::Mutex m_term_mutex; +}; + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) + +/** + * @brief The hotplug version of the LibUsbThread. + */ +class LibUsbHotplugThread : public LibUsbThread { + public: + /** + * @brief Create a new LibUsbHotplugThread.o + * @param context the libusb context to use. + * @param callback_fn The callback function to run when hotplug events occur. + * @param user_data User data to pass to the callback function. + * + * The thread is started in Init(). When the object is + * destroyed, the handle is de-registered as part of the thread shutdown + * sequence. + */ + LibUsbHotplugThread(libusb_context *context, + libusb_hotplug_callback_fn callback_fn, + void *user_data); + + bool Init(); + + void Shutdown(); + + void OpenHandle() {} + + void CloseHandle(libusb_device_handle *handle); + + private: + libusb_hotplug_callback_handle m_hotplug_handle; + libusb_hotplug_callback_fn m_callback_fn; + void *m_user_data; + + DISALLOW_COPY_AND_ASSIGN(LibUsbHotplugThread); +}; + +#endif + +/** + * @brief The non-hotplug version of LibUsbThread. + * + * The libusb thread is only run when one of more handles are open. Otherwise + * there is no way to interrupt libusb_handle_events(). See the libusb Async + * documentation at http://libusb.sourceforge.net/api-1.0/group__asyncio.html + * for more information. + */ +class LibUsbSimpleThread : public LibUsbThread { + public: + /** + * @brief Create a new LibUsbHotplugThread.o + * @param context the libusb context to use. + * + * The thread is starts as soon as this object is created. When the object is + * destroyed, the handle is de-registered as part of the thread shutdown + * sequence. + */ + explicit LibUsbSimpleThread(libusb_context *context) + : LibUsbThread(context), + m_device_count(0) { + } + + void OpenHandle(); + void CloseHandle(libusb_device_handle *handle); + + private: + unsigned int m_device_count; + + DISALLOW_COPY_AND_ASSIGN(LibUsbSimpleThread); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_LIBUSBTHREAD_H_ diff --git a/plugins/usbdmx/LibUsbUtils.cpp b/plugins/usbdmx/LibUsbUtils.cpp deleted file mode 100644 index cb117e5b92..0000000000 --- a/plugins/usbdmx/LibUsbUtils.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - * - * LibUsbUtils.cpp - * libusb Util functions. - * Copyright (C) 2014 Peter Newman - */ - -#include - -#include "ola/Logging.h" -#include "plugins/usbdmx/LibUsbUtils.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -using std::string; - -/** - * Return a string descriptor. - * @param usb_handle the usb handle to the device - * @param desc_index the index of the descriptor - * @param data where to store the output string - * @returns true if we got the value, false otherwise - */ -bool GetDescriptorString(libusb_device_handle *usb_handle, - uint8_t desc_index, - string *data) { - enum { buffer_size = 32 }; // static arrays FTW! - unsigned char buffer[buffer_size]; - int r = libusb_get_string_descriptor_ascii(usb_handle, - desc_index, - buffer, - buffer_size); - - if (r <= 0) { - OLA_INFO << "libusb_get_string_descriptor_ascii returned " << r; - return false; - } - data->assign(reinterpret_cast(buffer)); - return true; -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/LibUsbUtils.h b/plugins/usbdmx/LibUsbUtils.h deleted file mode 100644 index 5a0dfda226..0000000000 --- a/plugins/usbdmx/LibUsbUtils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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. - * - * LibUsbUtils.h - * libusb Util functions. - * Copyright (C) 2014 Peter Newman - */ - -#ifndef PLUGINS_USBDMX_LIBUSBUTILS_H_ -#define PLUGINS_USBDMX_LIBUSBUTILS_H_ - -#include - -#include - -namespace ola { -namespace plugin { -namespace usbdmx { - -bool GetDescriptorString(libusb_device_handle *usb_handle, - uint8_t desc_index, - std::string *data); -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_LIBUSBUTILS_H_ diff --git a/plugins/usbdmx/Makefile.mk b/plugins/usbdmx/Makefile.mk index c9a448b442..068bad5dc9 100644 --- a/plugins/usbdmx/Makefile.mk +++ b/plugins/usbdmx/Makefile.mk @@ -3,31 +3,54 @@ if USE_LIBUSB lib_LTLIBRARIES += plugins/usbdmx/libolausbdmx.la plugins_usbdmx_libolausbdmx_la_SOURCES = \ - plugins/usbdmx/AnymaDevice.cpp \ - plugins/usbdmx/AnymaDevice.h \ - plugins/usbdmx/AnymaOutputPort.cpp \ - plugins/usbdmx/AnymaOutputPort.h \ - plugins/usbdmx/EuroliteProDevice.cpp \ - plugins/usbdmx/EuroliteProDevice.h \ - plugins/usbdmx/EuroliteProOutputPort.cpp \ - plugins/usbdmx/EuroliteProOutputPort.h \ - plugins/usbdmx/LibUsbUtils.cpp \ - plugins/usbdmx/LibUsbUtils.h \ + plugins/usbdmx/AnymaWidget.cpp \ + plugins/usbdmx/AnymaWidget.h \ + plugins/usbdmx/AnymaWidgetFactory.cpp \ + plugins/usbdmx/AnymaWidgetFactory.h \ + plugins/usbdmx/AsyncPluginImpl.cpp \ + plugins/usbdmx/AsyncPluginImpl.h \ + plugins/usbdmx/AsyncUsbSender.cpp \ + plugins/usbdmx/AsyncUsbSender.h \ + plugins/usbdmx/EuroliteProWidget.cpp \ + plugins/usbdmx/EuroliteProWidget.h \ + plugins/usbdmx/EuroliteProWidgetFactory.cpp \ + plugins/usbdmx/EuroliteProWidgetFactory.h \ + plugins/usbdmx/FadecandyWidget.cpp \ + plugins/usbdmx/FadecandyWidget.h \ + plugins/usbdmx/FadecandyWidgetFactory.cpp \ + plugins/usbdmx/FadecandyWidgetFactory.h \ plugins/usbdmx/FirmwareLoader.h \ - plugins/usbdmx/SunliteDevice.cpp \ - plugins/usbdmx/SunliteDevice.h \ + plugins/usbdmx/GenericDevice.cpp \ + plugins/usbdmx/GenericDevice.h \ + plugins/usbdmx/GenericOutputPort.cpp \ + plugins/usbdmx/GenericOutputPort.h \ + plugins/usbdmx/LibUsbAdaptor.cpp \ + plugins/usbdmx/LibUsbAdaptor.h \ + plugins/usbdmx/LibUsbThread.cpp \ + plugins/usbdmx/LibUsbThread.h \ + plugins/usbdmx/PluginImplInterface.h \ plugins/usbdmx/SunliteFirmware.h \ plugins/usbdmx/SunliteFirmwareLoader.cpp \ plugins/usbdmx/SunliteFirmwareLoader.h \ - plugins/usbdmx/SunliteOutputPort.cpp \ - plugins/usbdmx/SunliteOutputPort.h \ - plugins/usbdmx/UsbDevice.h \ + plugins/usbdmx/SunliteWidget.cpp \ + plugins/usbdmx/SunliteWidget.h \ + plugins/usbdmx/SunliteWidgetFactory.cpp \ + plugins/usbdmx/SunliteWidgetFactory.h \ + plugins/usbdmx/SyncPluginImpl.cpp \ + plugins/usbdmx/SyncPluginImpl.h \ + plugins/usbdmx/SyncronizedWidgetObserver.cpp \ + plugins/usbdmx/SyncronizedWidgetObserver.h \ + plugins/usbdmx/ThreadedUsbSender.cpp \ + plugins/usbdmx/ThreadedUsbSender.h \ plugins/usbdmx/UsbDmxPlugin.cpp \ plugins/usbdmx/UsbDmxPlugin.h \ - plugins/usbdmx/VellemanDevice.cpp \ - plugins/usbdmx/VellemanDevice.h \ - plugins/usbdmx/VellemanOutputPort.cpp \ - plugins/usbdmx/VellemanOutputPort.h + plugins/usbdmx/VellemanWidget.cpp \ + plugins/usbdmx/VellemanWidget.h \ + plugins/usbdmx/VellemanWidgetFactory.cpp \ + plugins/usbdmx/VellemanWidgetFactory.h \ + plugins/usbdmx/Widget.h \ + plugins/usbdmx/WidgetFactory.h + plugins_usbdmx_libolausbdmx_la_CXXFLAGS = $(COMMON_CXXFLAGS) $(libusb_CFLAGS) plugins_usbdmx_libolausbdmx_la_LIBADD = $(libusb_LIBS) \ common/libolacommon.la diff --git a/plugins/usbdmx/EuroliteProDevice.h b/plugins/usbdmx/PluginImplInterface.h similarity index 53% rename from plugins/usbdmx/EuroliteProDevice.h rename to plugins/usbdmx/PluginImplInterface.h index 1a6d8e477d..c66aaab52d 100644 --- a/plugins/usbdmx/EuroliteProDevice.h +++ b/plugins/usbdmx/PluginImplInterface.h @@ -13,44 +13,47 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * EuroliteProDevice.h - * Interface for the EurolitePro device - * Copyright (C) 2011 Simon Newton & Harry F - * Eurolite Pro USB DMX ArtNo. 51860120 + * PluginImplInterface.h + * The interface for the various implementations of the USBDMX plugin. + * Copyright (C) 2014 Simon Newton */ -#ifndef PLUGINS_USBDMX_EUROLITEPRODEVICE_H_ -#define PLUGINS_USBDMX_EUROLITEPRODEVICE_H_ +#ifndef PLUGINS_USBDMX_PLUGINIMPLINTERFACE_H_ +#define PLUGINS_USBDMX_PLUGINIMPLINTERFACE_H_ #include +#include #include -#include "plugins/usbdmx/UsbDevice.h" -#include "plugins/usbdmx/EuroliteProOutputPort.h" +#include +#include +#include "ola/plugin_id.h" +#include "olad/Plugin.h" +#include "ola/io/Descriptor.h" namespace ola { namespace plugin { namespace usbdmx { -/* - * A EurolitePro device +/** + * @brief The interface for an implementation of the USB DMX plugin. */ -class EuroliteProDevice: public UsbDevice { +class PluginImplInterface { public: - EuroliteProDevice(ola::AbstractPlugin *owner, - libusb_device *usb_device) - : UsbDevice(owner, "EurolitePro USB Device", usb_device), - m_output_port(NULL) { - } - - std::string DeviceId() const; + virtual ~PluginImplInterface() {} - protected: - bool StartHook(); + /** + * @brief Start the implementation. + * @returns true if successful, false otherwise. + */ + virtual bool Start() = 0; - private: - EuroliteProOutputPort *m_output_port; + /** + * @brief Stop the implementation. + * @returns true if successful, false otherwise. + */ + virtual bool Stop() = 0; }; } // namespace usbdmx } // namespace plugin } // namespace ola -#endif // PLUGINS_USBDMX_EUROLITEPRODEVICE_H_ +#endif // PLUGINS_USBDMX_PLUGINIMPLINTERFACE_H_ diff --git a/plugins/usbdmx/README.md b/plugins/usbdmx/README.md new file mode 100644 index 0000000000..38533a5a91 --- /dev/null +++ b/plugins/usbdmx/README.md @@ -0,0 +1,97 @@ +USBDMX Plugin +=============================================================================== + +This plugin uses [libusb](http://libusb.info/) to communicate with USB devices. + +libusb supports synchronous and asynchronous transfers. The initial version of +this plugin used the synchronous interface, and spawned a thread for every USB +device present. + +The new version of the plugin uses the asynchronous mode of operation and a +single thread for the libusb completion handling. + +You can opt-in to the new asynchronous mode by passing the --use-async-libusb +flag to olad. Assuming we don't find any problems, at some point this will +become the default and the synchronous implementation will be removed. + +The rest of this file explains how the plugin is constructed and is aimed at +developers wishing to add support for a new USB Device. It assumes the reader +has an understanding of libusb. + +Terminology +------------------------------------------------------------------------------- + +*USB Device*, is the physical USB device attached to the host. + +*DMX512 Interface*, the physical socket on the USB Device the user plugins the +DMX512 cable into. Currently all the USB Devices supported by the plugin +contain a single DMX512 Interface. + +*libusb\_device*, is the libusb structure that represents a USB device. + +*Widget*, is OLA's internal object representation of a USB Device. Widgets use +the corresponding libusb\_device to communicate with the USB Device. + +*Port*, the software representation of a DMX512 Interface. In OLA, users +associate a Port with a Universe. + +*Device*, the software representation of a USB Device. Contains one or more +Ports. + +Code Concepts & Structure +------------------------------------------------------------------------------- + +USB Devices are represented as Widgets, this allows us to de-couple the Widget +code from OLA's Port representation (remember, prefer composition over +inheritance). Since all the USB devices we support so +far have a single DMX512 interface, each specific Widget (e.g. AnymaWidget) +derives from the Widget class. This isn't strictly necessary, it just means we +can avoid code duplication by using the GenericDevice and GenericPort classes. +If in the future, multi-interface USB devices are supported, they shouldn't +inherit from the Widget class. + +GenericPort wraps a Widget into a Port object, so it can show up in olad. +GenericDevice creates a Device with a single GenericPort. + +For each type of USB Device, we create a common base class, e.g. AnymaWidget. +This enables the WidgetObserver class (see below) to know what +name it should give the resultant Device. + +Then for each type of USB Device, we create a synchronous and asynchronous +version of the Widget. So you end up with something like: + +* Widget + * AnymaWidget + * SynchronousAnymaWidget + * AsynchronousAnymaWidget + * SunliteWidget + * SynchronousSunliteWidget + * AsynchronousSunliteWidget + +Each type of USB Device has an associated factory. When a new libusb\_device is +discovered, each factory has the opportunity to claim the new device. Typically +factories will check the vendor and product ID and, if they recognize the +device, create a Widget to represent it. + +If a factory creates a new Widget, it needs to notify the WidgetObserver. The +WidgetObserver can then go ahead and use the newly created Widget to setup an +OLA Device & Port. + + +Adding Support for a new USB Device +------------------------------------------------------------------------------- + +Adding support for a new USB device should be reasonably straightforward. This +guide assumes the new USB Device has a single DMX512 Interface. + +1. Create the FooWidget.{h,cpp} files. + - Create the FooWidget base class, and a synchronous and asynchronous + implementation of the Foo Widget. +2. Create the FooWidgetFactory.{h,cpp} files. + - Write the DeviceAdded() method, to detect the new USB Device and create + either a synchronous or asynchronous widget, depending on the + --use-async-libusb flag. +3. Extend the WidgetObserver with new NewWidget() and WidgetRemoved() removed + methods for the new FooWidget. +4. Implement the new NewWidget() and WidgetRemoved() methods in both the + SyncPluginImpl and AsyncPluginImpl. diff --git a/plugins/usbdmx/SunliteDevice.h b/plugins/usbdmx/SunliteDevice.h deleted file mode 100644 index 567c72a079..0000000000 --- a/plugins/usbdmx/SunliteDevice.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - * - * SunliteDevice.h - * Interface for the Sunlite device - * Copyright (C) 2010 Simon Newton - */ - -#ifndef PLUGINS_USBDMX_SUNLITEDEVICE_H_ -#define PLUGINS_USBDMX_SUNLITEDEVICE_H_ - -#include -#include -#include "plugins/usbdmx/UsbDevice.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -/* - * A Sunlite device - */ -class SunliteDevice: public UsbDevice { - public: - SunliteDevice(ola::AbstractPlugin *owner, - libusb_device *usb_device) - : UsbDevice(owner, "Sunlite USB Device", usb_device) { - } - - std::string DeviceId() const { return "usbdmx2"; } - - protected: - bool StartHook(); -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_SUNLITEDEVICE_H_ diff --git a/plugins/usbdmx/SunliteFirmwareLoader.h b/plugins/usbdmx/SunliteFirmwareLoader.h index 9ed62924b8..c16f59181d 100644 --- a/plugins/usbdmx/SunliteFirmwareLoader.h +++ b/plugins/usbdmx/SunliteFirmwareLoader.h @@ -25,6 +25,7 @@ #define PLUGINS_USBDMX_SUNLITEFIRMWARELOADER_H_ #include +#include "ola/base/Macro.h" #include "plugins/usbdmx/FirmwareLoader.h" namespace ola { @@ -46,6 +47,8 @@ class SunliteFirmwareLoader: public FirmwareLoader { static const uint8_t UPLOAD_REQUEST_TYPE = 0x40; static const uint8_t UPLOAD_REQUEST = 0xa0; static const unsigned int UPLOAD_TIMEOUT = 300; // ms + + DISALLOW_COPY_AND_ASSIGN(SunliteFirmwareLoader); }; } // namespace usbdmx } // namespace plugin diff --git a/plugins/usbdmx/SunliteOutputPort.cpp b/plugins/usbdmx/SunliteOutputPort.cpp deleted file mode 100644 index 3024fb9ad2..0000000000 --- a/plugins/usbdmx/SunliteOutputPort.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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. - * - * SunliteOutputPort.cpp - * Thread for the Sunlite USBDMX2 Output Port - * Copyright (C) 2010 Simon Newton - * - * See the comments in SunliteOutputPort.h - */ - -#include -#include - -#include "ola/Logging.h" -#include "plugins/usbdmx/SunliteOutputPort.h" -#include "plugins/usbdmx/SunliteDevice.h" - - -namespace ola { -namespace plugin { -namespace usbdmx { - - -/* - * Create a new SunliteOutputPort object - */ -SunliteOutputPort::SunliteOutputPort(SunliteDevice *parent, - unsigned int id, - libusb_device *usb_device) - : BasicOutputPort(parent, id), - m_term(false), - m_new_data(false), - m_usb_device(usb_device), - m_usb_handle(NULL) { - InitPacket(); -} - - -/* - * Cleanup - */ -SunliteOutputPort::~SunliteOutputPort() { - { - ola::thread::MutexLocker locker(&m_term_mutex); - m_term = true; - } - Join(); -} - - -/* - * Start this thread - */ -bool SunliteOutputPort::Start() { - libusb_device_handle *usb_handle; - - if (libusb_open(m_usb_device, &usb_handle)) { - OLA_WARN << "Failed to open Sunlite usb device"; - return false; - } - - if (libusb_claim_interface(usb_handle, 0)) { - OLA_WARN << "Failed to claim Sunlite usb device"; - libusb_close(usb_handle); - return false; - } - - m_usb_handle = usb_handle; - bool ret = ola::thread::Thread::Start(); - if (!ret) { - OLA_WARN << "pthread create failed"; - libusb_release_interface(m_usb_handle, 0); - libusb_close(usb_handle); - return false; - } - return true; -} - - -/* - * Run this thread - */ -void *SunliteOutputPort::Run() { - DmxBuffer buffer; - bool new_data; - - if (!m_usb_handle) - return NULL; - - while (true) { - { - ola::thread::MutexLocker locker(&m_term_mutex); - if (m_term) - break; - } - - { - ola::thread::MutexLocker locker(&m_data_mutex); - buffer.Set(m_buffer); - new_data = m_new_data; - m_new_data = false; - } - - if (new_data) { - if (!SendDMX(buffer)) { - OLA_WARN << "Send failed, stopping thread..."; - break; - } - } else { - // sleep for a bit - usleep(40000); - } - } - libusb_release_interface(m_usb_handle, 0); - libusb_close(m_usb_handle); - return NULL; -} - - -/* - * Store the data in the shared buffer - */ -bool SunliteOutputPort::WriteDMX(const DmxBuffer &buffer, uint8_t priority) { - ola::thread::MutexLocker locker(&m_data_mutex); - m_buffer.Set(buffer); - m_new_data = true; - return true; - (void) priority; -} - - -/* - * Setup the packet we send to the device. - * - * The packet is divided into 26 chunks of 32 bytes each. Each chunk contains - * the data for 20 channels (except the last one which has 12 channels of data) - */ -void SunliteOutputPort::InitPacket() { - // zero everything - memset(m_packet, 0, SUNLITE_PACKET_SIZE); - - for (unsigned int chunk = 0; chunk < CHUNKS_PER_PACKET; ++chunk) { - unsigned int i = chunk * CHUNK_SIZE; // index into the packet - unsigned int channel = chunk * CHANNELS_PER_CHUNK; - - m_packet[i] = 0x80; - m_packet[i+1] = channel / 2; - m_packet[i+2] = 0x84; - m_packet[i+7] = channel / 2 + 2; - m_packet[i+8] = 0x84; - m_packet[i+13] = channel / 2 + 4; - if (chunk < CHUNKS_PER_PACKET - 1) { - m_packet[i+14] = 0x84; - m_packet[i+19] = channel / 2 + 6; - m_packet[i+20] = 0x84; - m_packet[i+25] = channel / 2 + 8; - m_packet[i+26] = 0x04; - m_packet[i+31] = 0x00; - } else { - // the last m_packet is short - m_packet[i+14] = 0x04; - } - } -} - - -/* - * Send DMX to the widget - */ -bool SunliteOutputPort::SendDMX(const DmxBuffer &buffer) { - for (unsigned int i = 0; i < buffer.Size(); i++) - m_packet[(i / CHANNELS_PER_CHUNK) * CHUNK_SIZE + - ((i / 4) % 5) * 6 + 3 + (i % 4)] = buffer.Get(i); - - int transferred; - int r = libusb_bulk_transfer( - m_usb_handle, - ENDPOINT, - (unsigned char*) m_packet, - SUNLITE_PACKET_SIZE, - &transferred, - TIMEOUT); - if (transferred != SUNLITE_PACKET_SIZE) - // not sure if this is fatal or not - OLA_WARN << "Sunlite driver failed to transfer all data"; - return r == 0; -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/SunliteOutputPort.h b/plugins/usbdmx/SunliteOutputPort.h deleted file mode 100644 index 7cee3542bd..0000000000 --- a/plugins/usbdmx/SunliteOutputPort.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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. - * - * SunliteOutputPort.h - * The output port for a Sunlite USBDMX2 device. - * Copyright (C) 2010 Simon Newton - * - * It takes around 11ms to complete the transfer to the device so we use a - * a separate thread for the writes. The time to acquire the lock, copy the - * buffer & release is 1-2 uS. - */ - -#ifndef PLUGINS_USBDMX_SUNLITEOUTPUTPORT_H_ -#define PLUGINS_USBDMX_SUNLITEOUTPUTPORT_H_ - -#include -#include -#include -#include "ola/DmxBuffer.h" -#include "ola/thread/Thread.h" -#include "olad/Port.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -class SunliteDevice; - -class SunliteOutputPort: public BasicOutputPort, ola::thread::Thread { - public: - SunliteOutputPort(SunliteDevice *parent, - unsigned int id, - libusb_device *usb_device); - ~SunliteOutputPort(); - - bool Start(); - void *Run(); - - bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); - std::string Description() const { return ""; } - - private: - enum {SUNLITE_PACKET_SIZE = 0x340}; - - static const unsigned int CHUNKS_PER_PACKET = 26; - static const unsigned int CHANNELS_PER_CHUNK = 20; - static const unsigned int CHUNK_SIZE = 32; - static const uint8_t ENDPOINT = 1; - static const unsigned int TIMEOUT = 50; // 50ms is ok - - bool m_term; - bool m_new_data; - uint8_t m_packet[SUNLITE_PACKET_SIZE]; - libusb_device *m_usb_device; - libusb_device_handle *m_usb_handle; - DmxBuffer m_buffer; - ola::thread::Mutex m_data_mutex; - ola::thread::Mutex m_term_mutex; - - void InitPacket(); - bool SendDMX(const DmxBuffer &buffer); -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_SUNLITEOUTPUTPORT_H_ diff --git a/plugins/usbdmx/SunliteWidget.cpp b/plugins/usbdmx/SunliteWidget.cpp new file mode 100644 index 0000000000..ca637eb006 --- /dev/null +++ b/plugins/usbdmx/SunliteWidget.cpp @@ -0,0 +1,215 @@ +/* + * 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. + * + * SunliteWidget.cpp + * The synchronous and asynchronous Sunlite widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/SunliteWidget.h" + +#include + +#include "ola/Constants.h" +#include "ola/Logging.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +namespace { + +static const unsigned int CHUNKS_PER_PACKET = 26; +static const unsigned int CHANNELS_PER_CHUNK = 20; +static const unsigned int CHUNK_SIZE = 32; +static const uint8_t ENDPOINT = 1; +static const unsigned int TIMEOUT = 50; // 50ms is ok +enum {SUNLITE_PACKET_SIZE = 0x340}; + +/* + * Initialize a USBDMX2 packet + */ +void InitPacket(uint8_t packet[SUNLITE_PACKET_SIZE]) { + memset(packet, 0, SUNLITE_PACKET_SIZE); + + // The packet is divided into 26 chunks of 32 bytes each. Each chunk contains + // the data for 20 channels (except the last one which has 12 channels of + // data). + for (unsigned int chunk = 0; chunk < CHUNKS_PER_PACKET; ++chunk) { + unsigned int i = chunk * CHUNK_SIZE; // index into the packet + unsigned int channel = chunk * CHANNELS_PER_CHUNK; + + packet[i] = 0x80; + packet[i + 1] = channel / 2; + packet[i + 2] = 0x84; + packet[i + 7] = channel / 2 + 2; + packet[i + 8] = 0x84; + packet[i + 13] = channel / 2 + 4; + if (chunk < CHUNKS_PER_PACKET - 1) { + packet[i + 14] = 0x84; + packet[i + 19] = channel / 2 + 6; + packet[i + 20] = 0x84; + packet[i + 25] = channel / 2 + 8; + packet[i + 26] = 0x04; + packet[i + 31] = 0x00; + } else { + // the last chunk is short + packet[i + 14] = 0x04; + } + } +} + +/* + * Update a USBDMX2 packet to match the supplied DmxBuffer. + */ +void UpdatePacket(const DmxBuffer &buffer, + uint8_t packet[SUNLITE_PACKET_SIZE]) { + for (unsigned int i = 0; i < buffer.Size(); i++) { + packet[(i / CHANNELS_PER_CHUNK) * CHUNK_SIZE + + ((i / 4) % 5) * 6 + 3 + (i % 4)] = buffer.Get(i); + } +} + +} // namespace + +// SunliteThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a Sunlite device in a separate thread. + */ +class SunliteThreadedSender: public ThreadedUsbSender { + public: + SunliteThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle); + + private: + LibUsbAdaptor* const m_adaptor; + uint8_t m_packet[SUNLITE_PACKET_SIZE]; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); +}; + +SunliteThreadedSender::SunliteThreadedSender( + LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *usb_handle) + : ThreadedUsbSender(usb_device, usb_handle), + m_adaptor(adaptor) { + InitPacket(m_packet); +} + +bool SunliteThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + UpdatePacket(buffer, m_packet); + int transferred; + int r = m_adaptor->BulkTransfer(handle, ENDPOINT, (unsigned char*) m_packet, + SUNLITE_PACKET_SIZE, &transferred, TIMEOUT); + if (transferred != SUNLITE_PACKET_SIZE) { + // not sure if this is fatal or not + OLA_WARN << "Sunlite driver failed to transfer all data"; + } + return r == 0; +} + +// SynchronousSunliteWidget +// ----------------------------------------------------------------------------- + +SynchronousSunliteWidget::SynchronousSunliteWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : SunliteWidget(adaptor), + m_usb_device(usb_device) { +} + +bool SynchronousSunliteWidget::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 SunliteThreadedSender(m_adaptor, m_usb_device, usb_handle)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousSunliteWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} + +// SunliteAsyncUsbSender +// ----------------------------------------------------------------------------- +class SunliteAsyncUsbSender : public AsyncUsbSender { + public: + SunliteAsyncUsbSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : AsyncUsbSender(adaptor, usb_device) { + InitPacket(m_packet); + } + + ~SunliteAsyncUsbSender() { + CancelTransfer(); + } + + libusb_device_handle* SetupHandle() { + libusb_device_handle *usb_handle; + bool ok = m_adaptor->OpenDeviceAndClaimInterface( + m_usb_device, 0, &usb_handle); + return ok ? usb_handle : NULL; + } + + bool PerformTransfer(const DmxBuffer &buffer) { + UpdatePacket(buffer, m_packet); + FillBulkTransfer(ENDPOINT, m_packet, SUNLITE_PACKET_SIZE, TIMEOUT); + return SubmitTransfer() == 0; + } + + private: + uint8_t m_packet[SUNLITE_PACKET_SIZE]; + + DISALLOW_COPY_AND_ASSIGN(SunliteAsyncUsbSender); +}; + +// AsynchronousSunliteWidget +// ----------------------------------------------------------------------------- + +AsynchronousSunliteWidget::AsynchronousSunliteWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : SunliteWidget(adaptor) { + m_sender.reset(new SunliteAsyncUsbSender(m_adaptor, usb_device)); +} + +bool AsynchronousSunliteWidget::Init() { + return m_sender->Init(); +} + +bool AsynchronousSunliteWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender->SendDMX(buffer); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/SunliteWidget.h b/plugins/usbdmx/SunliteWidget.h new file mode 100644 index 0000000000..f82568fd11 --- /dev/null +++ b/plugins/usbdmx/SunliteWidget.h @@ -0,0 +1,99 @@ +/* + * 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. + * + * SunliteWidget.h + * The synchronous and asynchronous Sunlite widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_SUNLITEWIDGET_H_ +#define PLUGINS_USBDMX_SUNLITEWIDGET_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 SunliteThreadedSender; + +/** + * @brief The interface for the Sunlite Widgets + */ +class SunliteWidget : public BaseWidget { + public: + explicit SunliteWidget(LibUsbAdaptor *adaptor) + : BaseWidget(adaptor) { + } +}; + + +/** + * @brief An Sunlite widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousSunliteWidget: public SunliteWidget { + public: + /** + * @brief Create a new SynchronousSunliteWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + SynchronousSunliteWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + libusb_device* const m_usb_device; + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousSunliteWidget); +}; + +/** + * @brief An Sunlite widget that uses asynchronous libusb operations. + */ +class AsynchronousSunliteWidget: public SunliteWidget { + public: + /** + * @brief Create a new AsynchronousSunliteWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + AsynchronousSunliteWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousSunliteWidget); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SUNLITEWIDGET_H_ diff --git a/plugins/usbdmx/SunliteWidgetFactory.cpp b/plugins/usbdmx/SunliteWidgetFactory.cpp new file mode 100644 index 0000000000..01b9cd04f8 --- /dev/null +++ b/plugins/usbdmx/SunliteWidgetFactory.cpp @@ -0,0 +1,70 @@ +/* + * 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. + * + * SunliteWidgetFactory.cpp + * The WidgetFactory for SunLite widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/SunliteWidgetFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" +#include "plugins/usbdmx/SunliteFirmwareLoader.h" + +DECLARE_bool(use_async_libusb); + +namespace ola { +namespace plugin { +namespace usbdmx { + +const uint16_t SunliteWidgetFactory::EMPTY_PRODUCT_ID = 0x2000; +const uint16_t SunliteWidgetFactory::FULL_PRODUCT_ID = 0x2001; +const uint16_t SunliteWidgetFactory::VENDOR_ID = 0x0962; + +bool SunliteWidgetFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor == VENDOR_ID && + descriptor.idProduct == EMPTY_PRODUCT_ID) { + OLA_INFO << "New empty SunliteDevice"; + // TODO(simon): Make this async. + SunliteFirmwareLoader loader(usb_device); + loader.LoadFirmware(); + return true; + } else if (descriptor.idVendor == VENDOR_ID && + descriptor.idProduct == FULL_PRODUCT_ID && + !HasDevice(usb_device)) { + OLA_INFO << "Found a new Sunlite device"; + SunliteWidget *widget = NULL; + if (FLAGS_use_async_libusb) { + widget = new AsynchronousSunliteWidget(m_adaptor, usb_device); + } else { + widget = new SynchronousSunliteWidget(m_adaptor, usb_device); + } + return AddWidget(observer, usb_device, widget); + } + return false; +} + +void SunliteWidgetFactory::DeviceRemoved(WidgetObserver *observer, + libusb_device *device) { + // TODO(simon): once firmware loading is async, cancel the load here. + BaseWidgetFactory::DeviceRemoved(observer, device); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/SunliteWidgetFactory.h b/plugins/usbdmx/SunliteWidgetFactory.h new file mode 100644 index 0000000000..5e68be83ab --- /dev/null +++ b/plugins/usbdmx/SunliteWidgetFactory.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. + * + * SunliteWidgetFactory.h + * The WidgetFactory for SunLite widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_SUNLITEWIDGETFACTORY_H_ +#define PLUGINS_USBDMX_SUNLITEWIDGETFACTORY_H_ + +#include "ola/base/Macro.h" +#include "plugins/usbdmx/SunliteWidget.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Creates SunLite widgets. + */ +class SunliteWidgetFactory : public BaseWidgetFactory { + public: + explicit SunliteWidgetFactory(class LibUsbAdaptor *adaptor) + : m_adaptor(adaptor) {} + + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); + + void DeviceRemoved(WidgetObserver *observer, + libusb_device *device); + + private: + class LibUsbAdaptor* const m_adaptor; + + // The product ID for widgets that are missing their firmware. + static const uint16_t EMPTY_PRODUCT_ID; + // The product ID for widgets with the firmware. + static const uint16_t FULL_PRODUCT_ID; + static const uint16_t VENDOR_ID; + + DISALLOW_COPY_AND_ASSIGN(SunliteWidgetFactory); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SUNLITEWIDGETFACTORY_H_ diff --git a/plugins/usbdmx/SyncPluginImpl.cpp b/plugins/usbdmx/SyncPluginImpl.cpp new file mode 100644 index 0000000000..cfbc54c25d --- /dev/null +++ b/plugins/usbdmx/SyncPluginImpl.cpp @@ -0,0 +1,202 @@ +/* + * 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. + * + * SyncPluginImpl.cpp + * The synchronous implementation of the USB DMX plugin. + * Copyright (C) 2006 Simon Newton + */ + +#include "plugins/usbdmx/SyncPluginImpl.h" + +#include +#include +#include + +#include +#include +#include + +#include "ola/Callback.h" +#include "ola/Logging.h" +#include "ola/stl/STLUtils.h" +#include "olad/PluginAdaptor.h" + +#include "plugins/usbdmx/AnymaWidget.h" +#include "plugins/usbdmx/AnymaWidgetFactory.h" +#include "plugins/usbdmx/EuroliteProWidget.h" +#include "plugins/usbdmx/EuroliteProWidgetFactory.h" +#include "plugins/usbdmx/FadecandyWidget.h" +#include "plugins/usbdmx/FadecandyWidgetFactory.h" +#include "plugins/usbdmx/GenericDevice.h" +#include "plugins/usbdmx/SunliteWidget.h" +#include "plugins/usbdmx/SunliteWidgetFactory.h" +#include "plugins/usbdmx/VellemanWidget.h" +#include "plugins/usbdmx/VellemanWidgetFactory.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::pair; +using std::string; +using std::vector; + +SyncPluginImpl::SyncPluginImpl(PluginAdaptor *plugin_adaptor, + Plugin *plugin, + unsigned int debug_level) + : m_plugin_adaptor(plugin_adaptor), + m_plugin(plugin), + m_debug_level(debug_level), + m_context(NULL) { + m_widget_factories.push_back(new AnymaWidgetFactory(&m_usb_adaptor)); + m_widget_factories.push_back(new EuroliteProWidgetFactory(&m_usb_adaptor)); + m_widget_factories.push_back(new SunliteWidgetFactory(&m_usb_adaptor)); + m_widget_factories.push_back(new VellemanWidgetFactory(&m_usb_adaptor)); +} + +SyncPluginImpl::~SyncPluginImpl() { + STLDeleteElements(&m_widget_factories); +} + +bool SyncPluginImpl::Start() { + if (libusb_init(&m_context)) { + OLA_WARN << "Failed to init libusb"; + return false; + } + + OLA_DEBUG << "libusb debug level set to " << m_debug_level; + libusb_set_debug(m_context, m_debug_level); + + unsigned int devices_claimed = ScanForDevices(); + if (devices_claimed != m_devices.size()) { + // This indicates there is firmware loading going on, schedule a callback + // to check for 'new' devices once the firmware has loaded. + + m_plugin_adaptor->RegisterSingleTimeout( + 3500, + NewSingleCallback(this, &SyncPluginImpl::ReScanForDevices)); + } + return true; +} + +bool SyncPluginImpl::Stop() { + WidgetToDeviceMap::iterator iter; + for (iter = m_devices.begin(); iter != m_devices.end(); ++iter) { + m_plugin_adaptor->UnregisterDevice(iter->second); + iter->second->Stop(); + delete iter->second; + delete iter->first; + } + m_devices.clear(); + m_registered_devices.clear(); + + libusb_exit(m_context); + + return true; +} + +bool SyncPluginImpl::NewWidget(AnymaWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Anyma USB Device", + "anyma-" + widget->SerialNumber())); +} + +bool SyncPluginImpl::NewWidget(EuroliteProWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "EurolitePro USB Device", + "eurolite-" + widget->SerialNumber())); +} + +bool SyncPluginImpl::NewWidget(FadecandyWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "FadeCandy USB Device", + "fadecandy-" + widget->SerialNumber())); +} + +bool SyncPluginImpl::NewWidget(SunliteWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Sunlite USBDMX2 Device", "usbdmx2")); +} + +bool SyncPluginImpl::NewWidget(VellemanWidget *widget) { + return StartAndRegisterDevice( + widget, + new GenericDevice(m_plugin, widget, "Velleman USB Device", "velleman")); +} + +/* + * @brief Look for USB devices. + */ +unsigned int SyncPluginImpl::ScanForDevices() { + libusb_device **device_list; + size_t device_count = libusb_get_device_list(m_context, &device_list); + unsigned int claimed_device_count = 0; + + for (unsigned int i = 0; i < device_count; i++) { + if (CheckDevice(device_list[i])) { + claimed_device_count++; + } + } + libusb_free_device_list(device_list, 1); // unref devices + return claimed_device_count; +} + +bool SyncPluginImpl::CheckDevice(libusb_device *usb_device) { + struct libusb_device_descriptor device_descriptor; + libusb_get_device_descriptor(usb_device, &device_descriptor); + + pair bus_dev_id(libusb_get_bus_number(usb_device), + libusb_get_device_address(usb_device)); + + if (STLContains(m_registered_devices, bus_dev_id)) { + return false; + } + + WidgetFactories::iterator iter = m_widget_factories.begin(); + for (; iter != m_widget_factories.end(); ++iter) { + if ((*iter)->DeviceAdded(this, usb_device, device_descriptor)) { + m_registered_devices.insert(bus_dev_id); + return true; + } + } + return false; +} + +void SyncPluginImpl::ReScanForDevices() { + ScanForDevices(); +} + +/* + * @brief Signal widget / device addition. + * @param widget The widget that was added. + * @param device The new olad device that uses this new widget. + */ +bool SyncPluginImpl::StartAndRegisterDevice(Widget *widget, Device *device) { + if (!device->Start()) { + delete device; + return false; + } + + STLReplace(&m_devices, widget, device); + m_plugin_adaptor->RegisterDevice(device); + return true; +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/SyncPluginImpl.h b/plugins/usbdmx/SyncPluginImpl.h new file mode 100644 index 0000000000..fd06262c96 --- /dev/null +++ b/plugins/usbdmx/SyncPluginImpl.h @@ -0,0 +1,108 @@ +/* + * 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. + * + * SyncPluginImpl.h + * The synchronous implementation of the USB DMX plugin. + * Copyright (C) 2010 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_SYNCPLUGINIMPL_H_ +#define PLUGINS_USBDMX_SYNCPLUGINIMPL_H_ + +#include +#include +#include +#include +#include +#include + +#include "ola/base/Macro.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/PluginImplInterface.h" +#include "plugins/usbdmx/WidgetFactory.h" + +namespace ola { + +class Device; + +namespace plugin { +namespace usbdmx { + +/** + * @brief The legacy implementation. + * + * This implementation spawns a thread for each dongle, and then uses + * synchronous calls to libusb. + * + * This does not support hotplug. + */ +class SyncPluginImpl: public PluginImplInterface, public WidgetObserver { + public: + /** + * @brief Create a new SyncPluginImpl. + * @param plugin_adaptor The PluginAdaptor to use, ownership is not + * transferred. + * @param plugin The parent Plugin object which is used when creating + * devices. + * @param debug_level the debug level to use for libusb. + */ + SyncPluginImpl(PluginAdaptor *plugin_adaptor, + Plugin *plugin, + unsigned int debug_level); + + ~SyncPluginImpl(); + + bool Start(); + bool Stop(); + + bool NewWidget(class AnymaWidget *widget); + bool NewWidget(class EuroliteProWidget *widget); + bool NewWidget(class FadecandyWidget *widget); + bool NewWidget(class SunliteWidget *widget); + bool NewWidget(class VellemanWidget *widget); + + void WidgetRemoved(OLA_UNUSED class AnymaWidget *widget) {} + void WidgetRemoved(OLA_UNUSED class EuroliteProWidget *widget) {} + void WidgetRemoved(OLA_UNUSED class FadecandyWidget *widget) {} + void WidgetRemoved(OLA_UNUSED class SunliteWidget *widget) {} + void WidgetRemoved(OLA_UNUSED class VellemanWidget *widget) {} + + private: + typedef std::vector WidgetFactories; + typedef std::map WidgetToDeviceMap; + + PluginAdaptor* const m_plugin_adaptor; + Plugin* const m_plugin; + const unsigned int m_debug_level; + SyncronousLibUsbAdaptor m_usb_adaptor; + WidgetFactories m_widget_factories; + + libusb_context *m_context; + + WidgetToDeviceMap m_devices; + std::set > m_registered_devices; + + unsigned int ScanForDevices(); + void ReScanForDevices(); + bool CheckDevice(libusb_device *device); + + bool StartAndRegisterDevice(class Widget *widget, Device *device); + + DISALLOW_COPY_AND_ASSIGN(SyncPluginImpl); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SYNCPLUGINIMPL_H_ diff --git a/plugins/usbdmx/SyncronizedWidgetObserver.cpp b/plugins/usbdmx/SyncronizedWidgetObserver.cpp new file mode 100644 index 0000000000..4b98e29bd1 --- /dev/null +++ b/plugins/usbdmx/SyncronizedWidgetObserver.cpp @@ -0,0 +1,81 @@ +/* + * 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. + * + * SyncronizedWidgetObserver.cpp + * Transfers widget add/remove events to another thread. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/SyncronizedWidgetObserver.h" + +#include "ola/Callback.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using ola::thread::Thread; + +SyncronizedWidgetObserver::SyncronizedWidgetObserver( + WidgetObserver *observer, + ola::io::SelectServerInterface *ss) + : m_observer(observer), + m_ss(ss), + m_main_thread_id(Thread::Self()) { +} + +template +bool SyncronizedWidgetObserver::DispatchNewWidget(WidgetClass*widget) { + if (Thread::Self() == m_main_thread_id) { + return m_observer->NewWidget(widget); + } else { + AddFuture f; + m_ss->Execute( + NewSingleCallback( + this, &SyncronizedWidgetObserver::HandleNewWidget, + widget, &f)); + return f.Get(); + } +} + +template +void SyncronizedWidgetObserver::DispatchWidgetRemoved(WidgetClass *widget) { + if (Thread::Self() == m_main_thread_id) { + m_observer->WidgetRemoved(widget); + } else { + RemoveFuture f; + m_ss->Execute( + NewSingleCallback( + this, &SyncronizedWidgetObserver::HandleWidgetRemoved, + widget, &f)); + f.Get(); + } +} + +template +void SyncronizedWidgetObserver::HandleNewWidget(WidgetClass*widget, + AddFuture *f) { + f->Set(m_observer->NewWidget(widget)); +} + +template +void SyncronizedWidgetObserver::HandleWidgetRemoved(WidgetClass *widget, + RemoveFuture *f) { + m_observer->WidgetRemoved(widget); + f->Set(); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/SyncronizedWidgetObserver.h b/plugins/usbdmx/SyncronizedWidgetObserver.h new file mode 100644 index 0000000000..fb8b0c0631 --- /dev/null +++ b/plugins/usbdmx/SyncronizedWidgetObserver.h @@ -0,0 +1,115 @@ +/* + * 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. + * + * SyncronizedWidgetObserver.h + * Transfers widget add/remove events to another thread. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_SYNCRONIZEDWIDGETOBSERVER_H_ +#define PLUGINS_USBDMX_SYNCRONIZEDWIDGETOBSERVER_H_ + +#include "plugins/usbdmx/WidgetFactory.h" + +#include "ola/io/SelectServerInterface.h" +#include "ola/thread/Future.h" +#include "ola/thread/Thread.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Transfers widget add/remove events to another thread. + * + * The SyncronizedWidgetObserver ensures that all widget add/removed events are + * handled in the thread that created the SyncronizedWidgetObserver object. + */ +class SyncronizedWidgetObserver : public WidgetObserver { + public: + /** + * @brief Create a new SyncronizedWidgetObserver. + * @param observer the observer to notify on add/remove events. + * @param ss The ss to use the schedule events on. + */ + SyncronizedWidgetObserver(WidgetObserver *observer, + ola::io::SelectServerInterface *ss); + + bool NewWidget(class AnymaWidget *widget) { + return DispatchNewWidget(widget); + } + + bool NewWidget(class EuroliteProWidget *widget) { + return DispatchNewWidget(widget); + } + + bool NewWidget(class FadecandyWidget *widget) { + return DispatchNewWidget(widget); + } + + bool NewWidget(class SunliteWidget *widget) { + return DispatchNewWidget(widget); + } + + bool NewWidget(class VellemanWidget *widget) { + return DispatchNewWidget(widget); + } + + void WidgetRemoved(class AnymaWidget *widget) { + DispatchWidgetRemoved(widget); + } + + void WidgetRemoved(class EuroliteProWidget *widget) { + DispatchWidgetRemoved(widget); + } + + void WidgetRemoved(class FadecandyWidget *widget) { + DispatchWidgetRemoved(widget); + } + + void WidgetRemoved(class SunliteWidget *widget) { + DispatchWidgetRemoved(widget); + } + + void WidgetRemoved(class VellemanWidget *widget) { + DispatchWidgetRemoved(widget); + } + + private: + typedef ola::thread::Future AddFuture; + typedef ola::thread::Future RemoveFuture; + + WidgetObserver* const m_observer; + ola::io::SelectServerInterface* const m_ss; + const ola::thread::ThreadId m_main_thread_id; + + template + bool DispatchNewWidget(WidgetClass *widget); + + template + void DispatchWidgetRemoved(WidgetClass *widget); + + template + void HandleNewWidget(WidgetClass *widget, AddFuture *f); + + template + void HandleWidgetRemoved(WidgetClass *widget, RemoveFuture *f); + + DISALLOW_COPY_AND_ASSIGN(SyncronizedWidgetObserver); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_SYNCRONIZEDWIDGETOBSERVER_H_ diff --git a/plugins/usbdmx/AnymaOutputPort.cpp b/plugins/usbdmx/ThreadedUsbSender.cpp similarity index 54% rename from plugins/usbdmx/AnymaOutputPort.cpp rename to plugins/usbdmx/ThreadedUsbSender.cpp index 2a77aabc5d..caff8887ca 100644 --- a/plugins/usbdmx/AnymaOutputPort.cpp +++ b/plugins/usbdmx/ThreadedUsbSender.cpp @@ -13,57 +13,38 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * AnymaOutputPort.cpp - * Thread for the Anyma Output Port - * Copyright (C) 2010 Simon Newton + * ThreadedUsbSender.cpp + * Send DMX data over USB from a dedicated thread. + * Copyright (C) 2014 Simon Newton */ -#include -#include -#include +#include "plugins/usbdmx/ThreadedUsbSender.h" +#include #include "ola/Logging.h" -#include "plugins/usbdmx/AnymaOutputPort.h" -#include "plugins/usbdmx/AnymaDevice.h" - namespace ola { namespace plugin { namespace usbdmx { -using std::string; - - -/* - * Create a new AnymaOutputPort object - */ -AnymaOutputPort::AnymaOutputPort(AnymaDevice *parent, - unsigned int id, - libusb_device_handle *usb_handle, - const string &serial) - : BasicOutputPort(parent, id), - m_term(false), - m_serial(serial), +ThreadedUsbSender::ThreadedUsbSender(libusb_device *usb_device, + libusb_device_handle *usb_handle) + : m_term(false), + m_usb_device(usb_device), m_usb_handle(usb_handle) { + libusb_ref_device(usb_device); } - -/* - * Cleanup - */ -AnymaOutputPort::~AnymaOutputPort() { +ThreadedUsbSender::~ThreadedUsbSender() { { ola::thread::MutexLocker locker(&m_term_mutex); m_term = true; } Join(); + libusb_unref_device(m_usb_device); } - -/* - * Start this thread - */ -bool AnymaOutputPort::Start() { +bool ThreadedUsbSender::Start() { bool ret = ola::thread::Thread::Start(); if (!ret) { OLA_WARN << "Failed to start sender thread"; @@ -74,11 +55,7 @@ bool AnymaOutputPort::Start() { return true; } - -/* - * Run this thread - */ -void *AnymaOutputPort::Run() { +void *ThreadedUsbSender::Run() { DmxBuffer buffer; if (!m_usb_handle) return NULL; @@ -96,7 +73,7 @@ void *AnymaOutputPort::Run() { } if (buffer.Size()) { - if (!SendDMX(buffer)) { + if (!TransmitBuffer(m_usb_handle, buffer)) { OLA_WARN << "Send failed, stopping thread..."; break; } @@ -110,35 +87,11 @@ void *AnymaOutputPort::Run() { return NULL; } - -/* - * Store the data in the shared buffer - */ -bool AnymaOutputPort::WriteDMX(const DmxBuffer &buffer, uint8_t priority) { +bool ThreadedUsbSender::SendDMX(const DmxBuffer &buffer) { + // Store the new data in the shared buffer. ola::thread::MutexLocker locker(&m_data_mutex); m_buffer.Set(buffer); return true; - (void) priority; -} - - -/* - * Send the dmx out the widget - * @return true on success, false on failure - */ -bool AnymaOutputPort::SendDMX(const DmxBuffer &buffer) { - int r = libusb_control_transfer(m_usb_handle, - LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | - LIBUSB_ENDPOINT_OUT, - UDMX_SET_CHANNEL_RANGE, - buffer.Size(), - 0, - // the suck - const_cast(buffer.GetRaw()), - buffer.Size(), - URB_TIMEOUT_MS); - // Sometimes we get PIPE errors here, those are non-fatal - return r > 0 || r == LIBUSB_ERROR_PIPE; } } // namespace usbdmx } // namespace plugin diff --git a/plugins/usbdmx/ThreadedUsbSender.h b/plugins/usbdmx/ThreadedUsbSender.h new file mode 100644 index 0000000000..4771a95fca --- /dev/null +++ b/plugins/usbdmx/ThreadedUsbSender.h @@ -0,0 +1,102 @@ +/* + * 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. + * + * ThreadedUsbSender.h + * Send DMX data over USB from a dedicated thread. + * Copyright (C) 2010 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_THREADEDUSBSENDER_H_ +#define PLUGINS_USBDMX_THREADEDUSBSENDER_H_ + +#include +#include "ola/base/Macro.h" +#include "ola/DmxBuffer.h" +#include "ola/thread/Thread.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Send DMX data using libusb, from a separate thread. + * + * The synchronous libusb calls can sometimes take a while to complete, I've + * seen cases of up to 21ms. + * To avoid blocking the main thread, we need to perform the libusb transfer + * calls in a separate thread. This class contains all the thread management + * code, leaving the subclass to implement TransmitBuffer(), which performs the + * actual transfer. + * + * ThreadedUsbSender can be used as a building block for synchronous widgets. + */ +class ThreadedUsbSender: private ola::thread::Thread { + public: + /** + * @brief Create a new ThreadedUsbSender. + * @param usb_device The usb_device to use. The ThreadedUsbSender takes a ref + * on the device, while the ThreadedUsbSender object exists. + * @param usb_handle The handle to use for the DMX transfer. + */ + ThreadedUsbSender(libusb_device *usb_device, + libusb_device_handle *usb_handle); + virtual ~ThreadedUsbSender(); + + /** + * @brief Start the new thread. + * @returns true if the thread is running, false otherwise. + */ + bool Start(); + + /** + * @brief Entry point for the new thread. + * @returns NULL. + */ + void *Run(); + + /** + * @brief Buffer a DMX frame for sending. + * @param buffer the DmxBuffer to send. + * + * This should be called in the main thread. + */ + bool SendDMX(const DmxBuffer &buffer); + + protected: + /** + * @brief Perform the DMX transfer. + * @param handle the libusb_device_handle to use for the transfer. + * @param buffer The DmxBuffer to transfer. + * @returns true if the transfer was completed, false otherwise. + * + * This is called from the sender thread. + */ + virtual bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) = 0; + + private: + bool m_term; + libusb_device* const m_usb_device; + libusb_device_handle* const m_usb_handle; + DmxBuffer m_buffer; + ola::thread::Mutex m_data_mutex; + ola::thread::Mutex m_term_mutex; + + DISALLOW_COPY_AND_ASSIGN(ThreadedUsbSender); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_THREADEDUSBSENDER_H_ diff --git a/plugins/usbdmx/UsbDevice.h b/plugins/usbdmx/UsbDevice.h deleted file mode 100644 index 1154b0d7cb..0000000000 --- a/plugins/usbdmx/UsbDevice.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - * - * UsbDevice.h - * Interface for the generic usb device - * Copyright (C) 2010 Simon Newton - */ - -#ifndef PLUGINS_USBDMX_USBDEVICE_H_ -#define PLUGINS_USBDMX_USBDEVICE_H_ - -#include -#include -#include "olad/Device.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -/* - * A Usb device, this is just like the generic Device class but it has a - * Start() method as well to do the USB setup. - */ -class UsbDevice: public ola::Device { - public: - UsbDevice(ola::AbstractPlugin *owner, - const std::string &name, - libusb_device *device) - : Device(owner, name), - m_usb_device(device) { - libusb_ref_device(device); - } - virtual ~UsbDevice() { - libusb_unref_device(m_usb_device); - } - - protected: - libusb_device *m_usb_device; -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_USBDEVICE_H_ diff --git a/plugins/usbdmx/UsbDmxPlugin.cpp b/plugins/usbdmx/UsbDmxPlugin.cpp index 4e26d7a266..e2a31c84e2 100644 --- a/plugins/usbdmx/UsbDmxPlugin.cpp +++ b/plugins/usbdmx/UsbDmxPlugin.cpp @@ -14,44 +14,29 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * UsbDmxPlugin.cpp - * The UsbDmx plugin for ola + * A plugin that uses libusb to communicate with USB devices. * Copyright (C) 2006 Simon Newton */ -#include -#include -#include +#include "plugins/usbdmx/UsbDmxPlugin.h" #include -#include -#include -#include "ola/Callback.h" #include "ola/Logging.h" -#include "ola/StringUtils.h" -#include "ola/io/Descriptor.h" -#include "olad/PluginAdaptor.h" +#include "ola/base/Flags.h" #include "olad/Preferences.h" +#include "plugins/usbdmx/AsyncPluginImpl.h" +#include "plugins/usbdmx/PluginImplInterface.h" +#include "plugins/usbdmx/SyncPluginImpl.h" -#include "plugins/usbdmx/AnymaDevice.h" -#include "plugins/usbdmx/EuroliteProDevice.h" -#include "plugins/usbdmx/FirmwareLoader.h" -#include "plugins/usbdmx/LibUsbUtils.h" -#include "plugins/usbdmx/SunliteDevice.h" -#include "plugins/usbdmx/SunliteFirmwareLoader.h" -#include "plugins/usbdmx/UsbDevice.h" -#include "plugins/usbdmx/UsbDmxPlugin.h" -#include "plugins/usbdmx/VellemanDevice.h" - +DEFINE_default_bool(use_async_libusb, false, + "Use the asyncronous libusb calls."); namespace ola { namespace plugin { namespace usbdmx { -using ola::io::DeviceDescriptor; -using std::pair; using std::string; -using std::vector; const char UsbDmxPlugin::PLUGIN_NAME[] = "USB"; const char UsbDmxPlugin::PLUGIN_PREFIX[] = "usbdmx"; @@ -60,197 +45,54 @@ int UsbDmxPlugin::LIBUSB_DEFAULT_DEBUG_LEVEL = 0; int UsbDmxPlugin::LIBUSB_MAX_DEBUG_LEVEL = 3; -/* - * Called by libusb when a new descriptor is created. - */ -void libusb_fd_added(int fd, short events, void *data) { // NOLINT(runtime/int) - UsbDmxPlugin *plugin = static_cast(data); - - OLA_INFO << "USB new FD: " << fd; - plugin->AddDeviceDescriptor(fd); - (void) events; +UsbDmxPlugin::UsbDmxPlugin(PluginAdaptor *plugin_adaptor) + : Plugin(plugin_adaptor) { } +UsbDmxPlugin::~UsbDmxPlugin() {} -/* - * Called by libusb when a descriptor is no longer needed. - */ -void libusb_fd_removed(int fd, void *data) { - UsbDmxPlugin *plugin = static_cast(data); - OLA_INFO << "USB rm FD: " << fd; - plugin->RemoveDeviceDescriptor(fd); -} - - -/* - * Start the plugin - */ bool UsbDmxPlugin::StartHook() { - if (libusb_init(NULL)) { - OLA_WARN << "Failed to init libusb"; - return false; + if (m_impl.get()) { + return true; } unsigned int debug_level; if (!StringToInt(m_preferences->GetValue(LIBUSB_DEBUG_LEVEL_KEY) , - &debug_level)) + &debug_level)) { debug_level = LIBUSB_DEFAULT_DEBUG_LEVEL; - - OLA_DEBUG << "libusb debug level set to " << debug_level; - libusb_set_debug(NULL, debug_level); - - if (LoadFirmware()) { - // we loaded firmware for at least one device, set up a callback to run in - // a couple of seconds to re-scan for devices - m_plugin_adaptor->RegisterSingleTimeout( - 3500, - NewSingleCallback(this, &UsbDmxPlugin::FindDevices)); } - FindDevices(); - - /* - if (!libusb_pollfds_handle_timeouts(m_usb_context)) { - OLA_FATAL << "libusb doesn't have timeout support, BAD"; - return false; - } - - libusb_set_pollfd_notifiers(m_usb_context, - &libusb_fd_added, - &libusb_fd_removed, - this); - const libusb_pollfd **pollfds = libusb_get_pollfds(m_usb_context); - while (*pollfds) { - OLA_WARN << "poll fd " << (*pollfds)->fd; - AddDeviceDescriptor((*pollfds)->fd); - pollfds++; + std::auto_ptr impl; + if (FLAGS_use_async_libusb) { + impl.reset( + new AsyncPluginImpl(m_plugin_adaptor, this, debug_level)); + } else { + impl.reset( + new SyncPluginImpl(m_plugin_adaptor, this, debug_level)); } - */ - return true; -} - - -/* - * Load firmware onto devices if required. - * @returns true if we loaded firmware for one or more devices - */ -bool UsbDmxPlugin::LoadFirmware() { - libusb_device **device_list; - size_t device_count = libusb_get_device_list(NULL, &device_list); - FirmwareLoader *loader; - bool loaded = false; - - for (unsigned int i = 0; i < device_count; i++) { - libusb_device *usb_device = device_list[i]; - loader = NULL; - struct libusb_device_descriptor device_descriptor; - libusb_get_device_descriptor(usb_device, &device_descriptor); - - if (device_descriptor.idVendor == 0x0962 && - device_descriptor.idProduct == 0x2000) { - loader = new SunliteFirmwareLoader(usb_device); - } - - if (loader) { - loader->LoadFirmware(); - loaded = true; - delete loader; - } - } - libusb_free_device_list(device_list, 1); // unref devices - return loaded; -} - - -/* - * Find known devices & register them - */ -void UsbDmxPlugin::FindDevices() { - libusb_device **device_list; - size_t device_count = libusb_get_device_list(NULL, &device_list); - - for (unsigned int i = 0; i < device_count; i++) { - libusb_device *usb_device = device_list[i]; - struct libusb_device_descriptor device_descriptor; - libusb_get_device_descriptor(usb_device, &device_descriptor); - UsbDevice *device = NULL; - - pair bus_dev_id(libusb_get_bus_number(usb_device), - libusb_get_device_address(usb_device)); - - if ((m_registered_devices.find(bus_dev_id) != m_registered_devices.end())) - continue; - - if (device_descriptor.idVendor == 0x10cf && - device_descriptor.idProduct == 0x8062) { - OLA_INFO << "Found a Velleman USB device"; - device = new VellemanDevice(this, usb_device); - } else if (device_descriptor.idVendor == 0x0962 && - device_descriptor.idProduct == 0x2001) { - OLA_INFO << "Found a Sunlite device"; - device = new SunliteDevice(this, usb_device); - } else if (device_descriptor.idVendor == 0x16C0 && - device_descriptor.idProduct == 0x05DC) { - OLA_INFO << "Found an Anyma device"; - device = NewAnymaDevice(usb_device, device_descriptor); - } else if (device_descriptor.idVendor == 0x04d8 && - device_descriptor.idProduct == 0xfa63) { - OLA_INFO << "Found a EUROLITE device"; - device = new EuroliteProDevice(this, usb_device); - } else { - OLA_DEBUG << "Found an unknown device, skipping. VID: " - << device_descriptor.idVendor << ", PID: " - << device_descriptor.idProduct; - } - - if (device) { - if (!device->Start()) { - delete device; - continue; - } - m_registered_devices.insert(bus_dev_id); - m_devices.push_back(device); - m_plugin_adaptor->RegisterDevice(device); - } + if (impl->Start()) { + m_impl.reset(impl.release()); + return true; + } else { + return false; } - libusb_free_device_list(device_list, 1); // unref devices } - -/* - * Stop the plugin - * @return true on success, false on failure - */ bool UsbDmxPlugin::StopHook() { - vector::iterator iter; - for (iter = m_devices.begin(); iter != m_devices.end(); ++iter) { - m_plugin_adaptor->UnregisterDevice(*iter); - (*iter)->Stop(); - delete *iter; - } - m_devices.clear(); - m_registered_devices.clear(); - - libusb_exit(NULL); - - if (!m_descriptors.empty()) { - OLA_WARN << "libusb is still using descriptor, this is a bug"; + if (m_impl.get()) { + m_impl->Stop(); } return true; } - -/* - * Return the description for this plugin - */ string UsbDmxPlugin::Description() const { return "USB DMX Plugin\n" "----------------------------\n" "\n" "This plugin supports various USB DMX devices including the \n" -"Anyma uDMX, Sunlite USBDMX2 & Velleman K8062.\n" +"Anyma uDMX, Eurolite, Fadecandy, Sunlite USBDMX2 & Velleman K8062.\n" "\n" "--- Config file : ola-usbdmx.conf ---\n" "\n" @@ -260,10 +102,6 @@ string UsbDmxPlugin::Description() const { "\n"; } - -/* - * Default to sensible values - */ bool UsbDmxPlugin::SetDefaultPreferences() { if (!m_preferences) return false; @@ -278,143 +116,6 @@ bool UsbDmxPlugin::SetDefaultPreferences() { return true; } - - -bool UsbDmxPlugin::AddDeviceDescriptor(int fd) { - vector::const_iterator iter = m_descriptors.begin(); - for (; iter != m_descriptors.end(); ++iter) { - if ((*iter)->ReadDescriptor() == fd) - return true; - } - DeviceDescriptor *socket = new DeviceDescriptor(fd); - socket->SetOnData(NewCallback(this, &UsbDmxPlugin::SocketReady)); - m_plugin_adaptor->AddReadDescriptor(socket); - m_descriptors.push_back(socket); - return true; -} - - -bool UsbDmxPlugin::RemoveDeviceDescriptor(int fd) { - vector::iterator iter = m_descriptors.begin(); - for (; iter != m_descriptors.end(); ++iter) { - if ((*iter)->ReadDescriptor() == fd) { - m_plugin_adaptor->RemoveReadDescriptor(*iter); - delete *iter; - m_descriptors.erase(iter); - return true; - } - } - return false; -} - - -/* - * Called when there is activity on one of our sockets. - */ -void UsbDmxPlugin::SocketReady() { - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - libusb_handle_events_timeout(NULL, &tv); -} - - -/** - * Create a new AnymaDevice. Some Anyma devices don't have serial numbers, so - * we can only support one of those. - */ -UsbDevice* UsbDmxPlugin::NewAnymaDevice( - libusb_device *usb_device, - const struct libusb_device_descriptor &device_descriptor) { - libusb_device_handle *usb_handle; - if (libusb_open(usb_device, &usb_handle)) { - OLA_WARN << "Failed to open Anyma usb device"; - return NULL; - } - - USBDeviceInformation info; - GetDeviceInfo(usb_handle, device_descriptor, &info); - - if (!MatchManufacturer(AnymaDevice::EXPECTED_MANUFACTURER, - info.manufacturer)) { - libusb_close(usb_handle); - return NULL; - } - - if (!MatchProduct(AnymaDevice::EXPECTED_PRODUCT, info.product)) { - libusb_close(usb_handle); - return NULL; - } - - if (info.serial.empty()) { - if (m_anyma_devices_missing_serial_numbers) { - OLA_WARN << "Failed to read serial number or serial number empty. " - << "We can only support one device without a serial number."; - return NULL; - } else { - OLA_WARN << "Failed to read serial number from " << info.manufacturer - << " : " << info.product - << " the device probably doesn't have one"; - m_anyma_devices_missing_serial_numbers = true; - } - } - - if (libusb_claim_interface(usb_handle, 0)) { - OLA_WARN << "Failed to claim Anyma usb device"; - libusb_close(usb_handle); - return NULL; - } - return new AnymaDevice(this, usb_device, usb_handle, info.serial); -} - - -/** - * Get the Manufacturer, Product and Serial number strings for a device. - */ -void UsbDmxPlugin::GetDeviceInfo( - libusb_device_handle *usb_handle, - const struct libusb_device_descriptor &device_descriptor, - USBDeviceInformation *device_info) { - if (!GetDescriptorString(usb_handle, device_descriptor.iManufacturer, - &device_info->manufacturer)) - OLA_INFO << "Failed to get manufacturer name"; - - if (!GetDescriptorString(usb_handle, device_descriptor.iProduct, - &device_info->product)) - OLA_INFO << "Failed to get product name"; - - if (!GetDescriptorString(usb_handle, device_descriptor.iSerialNumber, - &device_info->serial)) - OLA_WARN << "Failed to read serial number, the device probably doesn't " - << "have one"; -} - - -/** - * Check if the manufacturer string matches the expected value. Log a message - * if it doesn't. - */ -bool UsbDmxPlugin::MatchManufacturer(const string &expected, - const string &actual) { - if (expected != actual) { - OLA_WARN << "Manufacturer mismatch: " << expected << " != " << actual; - return false; - } - return true; -} - - -/** - * Check if the manufacturer string matches the expected value. Log a message - * if it doesn't. - */ -bool UsbDmxPlugin::MatchProduct(const string &expected, const string &actual) { - if (expected != actual) { - OLA_WARN << "Product mismatch: " << expected << " != " << actual; - return false; - } - return true; -} } // namespace usbdmx } // namespace plugin } // namespace ola diff --git a/plugins/usbdmx/UsbDmxPlugin.h b/plugins/usbdmx/UsbDmxPlugin.h index bb2f29c8f6..84b289f251 100644 --- a/plugins/usbdmx/UsbDmxPlugin.h +++ b/plugins/usbdmx/UsbDmxPlugin.h @@ -14,76 +14,61 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * UsbDmxPlugin.h - * Interface for the usbdmx plugin class + * A plugin that uses libusb to communicate with USB devices. * Copyright (C) 2010 Simon Newton */ #ifndef PLUGINS_USBDMX_USBDMXPLUGIN_H_ #define PLUGINS_USBDMX_USBDMXPLUGIN_H_ -#include +#include #include -#include -#include +#include "ola/base/Macro.h" #include "ola/plugin_id.h" #include "olad/Plugin.h" -#include "ola/io/Descriptor.h" namespace ola { namespace plugin { - namespace usbdmx { +/** + * @brief A plugin that uses libusb to communicate with USB devices. + * + * This plugin supports a number of USB dongles including + * - Anyma + * - Eurolite + * - Sunlite + * - Velleman + */ class UsbDmxPlugin: public ola::Plugin { public: - explicit UsbDmxPlugin(PluginAdaptor *plugin_adaptor) - : Plugin(plugin_adaptor), - m_anyma_devices_missing_serial_numbers(false) { - } + /** + * @brief Create a new UsbDmxPlugin. + * @param plugin_adaptor The PluginAdaptor to use, ownership is not + * transferred. + */ + explicit UsbDmxPlugin(PluginAdaptor *plugin_adaptor); + ~UsbDmxPlugin(); std::string Name() const { return PLUGIN_NAME; } std::string Description() const; ola_plugin_id Id() const { return OLA_PLUGIN_USBDMX; } std::string PluginPrefix() const { return PLUGIN_PREFIX; } - bool AddDeviceDescriptor(int fd); - bool RemoveDeviceDescriptor(int fd); - void SocketReady(); - private: - struct USBDeviceInformation { - std::string manufacturer; - std::string product; - std::string serial; - }; - - bool m_anyma_devices_missing_serial_numbers; - std::vector m_devices; // list of our devices - std::vector m_descriptors; - std::set > m_registered_devices; + std::auto_ptr m_impl; bool StartHook(); - bool LoadFirmware(); - void FindDevices(); bool StopHook(); bool SetDefaultPreferences(); - class UsbDevice* NewAnymaDevice( - struct libusb_device *usb_device, - const struct libusb_device_descriptor &device_descriptor); - - void GetDeviceInfo( - struct libusb_device_handle *usb_handle, - const struct libusb_device_descriptor &device_descriptor, - USBDeviceInformation *device_info); - bool MatchManufacturer(const std::string &expected, - const std::string &actual); - bool MatchProduct(const std::string &expected, const std::string &actual); static const char PLUGIN_NAME[]; static const char PLUGIN_PREFIX[]; static const char LIBUSB_DEBUG_LEVEL_KEY[]; static int LIBUSB_DEFAULT_DEBUG_LEVEL; static int LIBUSB_MAX_DEBUG_LEVEL; + + DISALLOW_COPY_AND_ASSIGN(UsbDmxPlugin); }; } // namespace usbdmx } // namespace plugin diff --git a/plugins/usbdmx/VellemanOutputPort.cpp b/plugins/usbdmx/VellemanOutputPort.cpp deleted file mode 100644 index 751213b8e0..0000000000 --- a/plugins/usbdmx/VellemanOutputPort.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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. - * - * VellemanOutputPort.cpp - * Thread for the Velleman Output Port - * Copyright (C) 2010 Simon Newton - * - * See the comments in VellemanOutputPort.h - */ - -#include -#include -#include -#include - -#include "ola/Logging.h" -#include "plugins/usbdmx/VellemanOutputPort.h" -#include "plugins/usbdmx/VellemanDevice.h" - - -namespace ola { -namespace plugin { -namespace usbdmx { - -using std::string; - - -/* - * Create a new VellemanOutputPort object - */ -VellemanOutputPort::VellemanOutputPort(VellemanDevice *parent, - unsigned int id, - libusb_device *usb_device) - : BasicOutputPort(parent, id), - m_term(false), - m_chunk_size(8), // the standard unit uses 8 - m_usb_device(usb_device), - m_usb_handle(NULL) { -} - - -/* - * Cleanup - */ -VellemanOutputPort::~VellemanOutputPort() { - { - ola::thread::MutexLocker locker(&m_term_mutex); - m_term = true; - } - Join(); -} - - -/* - * Start this thread - */ -bool VellemanOutputPort::Start() { - libusb_device_handle *usb_handle; - libusb_config_descriptor *config; - - if (libusb_open(m_usb_device, &usb_handle)) { - OLA_WARN << "Failed to open Velleman usb device"; - return false; - } - - if (libusb_kernel_driver_active(usb_handle, 0)) { - if (libusb_detach_kernel_driver(usb_handle, 0)) { - OLA_WARN << "Failed to detach kernel driver"; - libusb_close(usb_handle); - return false; - } - } - - // this device only has one configuration - int ret_code = libusb_set_configuration(usb_handle, CONFIGURATION); - if (ret_code) { - OLA_WARN << "Velleman set config failed, with libusb error code " << - ret_code; - libusb_close(usb_handle); - return false; - } - - if (libusb_get_active_config_descriptor(m_usb_device, &config)) { - OLA_WARN << "Could not get active config descriptor"; - libusb_close(usb_handle); - return false; - } - - // determine the max packet size, see - // http://opendmx.net/index.php/Velleman_K8062_Upgrade - if (config && - config->interface && - config->interface->altsetting && - config->interface->altsetting->endpoint) { - uint16_t max_packet_size = - config->interface->altsetting->endpoint->wMaxPacketSize; - OLA_DEBUG << "Velleman K8062 max packet size is " << max_packet_size; - if (max_packet_size == UPGRADED_CHUNK_SIZE) - // this means the upgrade is present - m_chunk_size = max_packet_size; - } - libusb_free_config_descriptor(config); - - if (libusb_claim_interface(usb_handle, INTERFACE)) { - OLA_WARN << "Failed to claim Velleman usb device"; - libusb_close(usb_handle); - return false; - } - - m_usb_handle = usb_handle; - bool ret = ola::thread::Thread::Start(); - if (!ret) { - OLA_WARN << "pthread create failed"; - libusb_release_interface(m_usb_handle, INTERFACE); - libusb_close(usb_handle); - return false; - } - return true; -} - - -/* - * Run this thread - */ -void *VellemanOutputPort::Run() { - DmxBuffer buffer; - if (!m_usb_handle) - return NULL; - - while (1) { - { - ola::thread::MutexLocker locker(&m_term_mutex); - if (m_term) - break; - } - - { - ola::thread::MutexLocker locker(&m_data_mutex); - buffer.Set(m_buffer); - } - - if (buffer.Size()) { - if (!SendDMX(buffer)) { - OLA_WARN << "Send failed, stopping thread..."; - break; - } - } else { - // sleep for a bit - usleep(40000); - } - } - libusb_release_interface(m_usb_handle, 0); - libusb_close(m_usb_handle); - return NULL; -} - - -/* - * Store the data in the shared buffer - */ -bool VellemanOutputPort::WriteDMX(const DmxBuffer &buffer, uint8_t priority) { - ola::thread::MutexLocker locker(&m_data_mutex); - m_buffer.Set(buffer); - return true; - (void) priority; -} - - -/** - * Return the port description - */ -string VellemanOutputPort::Description() const { - if (m_chunk_size == UPGRADED_CHUNK_SIZE) - return "VX8062"; - else - return "K8062"; -} - - -/* - * Send the dmx out the widget - * @return true on success, false on failure - */ -bool VellemanOutputPort::SendDMX(const DmxBuffer &buffer) { - unsigned char usb_data[m_chunk_size]; - unsigned int size = buffer.Size(); - const uint8_t *data = buffer.GetRaw(); - unsigned int i = 0; - unsigned int n; - - // this could be up to 254 for the standard interface but then the shutdown - // process gets wacky. Limit it to 100 for the standard and 255 for the - // extended. - unsigned int max_compressed_channels = m_chunk_size == UPGRADED_CHUNK_SIZE ? - 254 : 100; - unsigned int compressed_channel_count = m_chunk_size - 2; - unsigned int channel_count = m_chunk_size - 1; - - memset(usb_data, 0, sizeof(usb_data)); - - if (m_chunk_size == UPGRADED_CHUNK_SIZE && size <= m_chunk_size - 2) { - // if the upgrade is present and we can fit the data in a single packet - // use the 7 message type - usb_data[0] = 7; - usb_data[1] = size; // number of channels in packet - memcpy(usb_data + 2, data, std::min(size, m_chunk_size - 2)); - } else { - // otherwise use 4 to signal the start of frame - for (n = 0; - n < max_compressed_channels && n < size - compressed_channel_count - && !data[n]; - n++) { - } - usb_data[0] = 4; - usb_data[1] = n + 1; // include start code - memcpy(usb_data + 2, data + n, compressed_channel_count); - i += n + compressed_channel_count; - } - - if (!SendDataChunk(usb_data)) - return false; - - while (i < size - channel_count) { - for (n = 0; - n < max_compressed_channels && n + i < size - compressed_channel_count - && !data[i + n]; - n++) { - } - if (n) { - // we have leading zeros - usb_data[0] = 5; - usb_data[1] = n; - memcpy(usb_data + 2, data + i + n, compressed_channel_count); - i += n + compressed_channel_count; - } else { - usb_data[0] = 2; - memcpy(usb_data + 1, data + i, channel_count); - i += channel_count; - } - if (!SendDataChunk(usb_data)) - return false; - } - - // send the last channels - if (m_chunk_size == UPGRADED_CHUNK_SIZE) { - // if running in extended mode we can use the 6 message type to send - // everything at once. - usb_data[0] = 6; - usb_data[1] = size - i; - memcpy(usb_data + 2, data + i, size - i); - if (!SendDataChunk(usb_data)) - return false; - - } else { - // else we use the 3 message type to send one at a time - for (; i != size; i++) { - usb_data[0] = 3; - usb_data[1] = data[i]; - if (!SendDataChunk(usb_data)) - return false; - } - } - return true; -} - - -/* - * Send 8 bytes to the usb device - * @returns false if there was an error, true otherwise - */ -bool VellemanOutputPort::SendDataChunk(uint8_t *usb_data) { - int transferred; - int ret = libusb_interrupt_transfer( - m_usb_handle, - ENDPOINT, - reinterpret_cast(usb_data), - m_chunk_size, - &transferred, - URB_TIMEOUT_MS); - if (ret) - OLA_INFO << "USB return code was " << ret << ", transferred " << - transferred; - return ret == 0; -} -} // namespace usbdmx -} // namespace plugin -} // namespace ola diff --git a/plugins/usbdmx/VellemanOutputPort.h b/plugins/usbdmx/VellemanOutputPort.h deleted file mode 100644 index 1901b08d97..0000000000 --- a/plugins/usbdmx/VellemanOutputPort.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - * - * VellemanOutputPort.h - * The output port for a Velleman 8062 device. - * Copyright (C) 2010 Simon Newton - * - * Because this interface is so slow we run the output in a separate thread. It - * takes around 8ms to respond to an urb and in the worst case we send 74 urbs - * per universe. - * - * It would be interesting to see if you can pipeline the urbs to improve the - * performance. - */ - -#ifndef PLUGINS_USBDMX_VELLEMANOUTPUTPORT_H_ -#define PLUGINS_USBDMX_VELLEMANOUTPUTPORT_H_ - -#include -#include -#include -#include "ola/DmxBuffer.h" -#include "ola/thread/Thread.h" -#include "olad/Port.h" - -namespace ola { -namespace plugin { -namespace usbdmx { - -class VellemanDevice; - -class VellemanOutputPort: public BasicOutputPort, ola::thread::Thread { - public: - VellemanOutputPort(VellemanDevice *parent, - unsigned int id, - libusb_device *usb_device); - ~VellemanOutputPort(); - - bool Start(); - void *Run(); - - bool WriteDMX(const DmxBuffer &buffer, uint8_t priority); - std::string Description() const; - - private: - static const unsigned char ENDPOINT = 0x01; - // 25ms seems to be about the shortest we can go - static const unsigned int URB_TIMEOUT_MS = 25; - static const int CONFIGURATION = 1; - static const int INTERFACE = 0; - static const unsigned int UPGRADED_CHUNK_SIZE = 64; - - bool m_term; - unsigned int m_chunk_size; - libusb_device *m_usb_device; - libusb_device_handle *m_usb_handle; - DmxBuffer m_buffer; - ola::thread::Mutex m_data_mutex; - ola::thread::Mutex m_term_mutex; - - bool SendDMX(const DmxBuffer &buffer_old); - bool SendDataChunk(uint8_t *usb_data); -}; -} // namespace usbdmx -} // namespace plugin -} // namespace ola -#endif // PLUGINS_USBDMX_VELLEMANOUTPUTPORT_H_ diff --git a/plugins/usbdmx/VellemanWidget.cpp b/plugins/usbdmx/VellemanWidget.cpp new file mode 100644 index 0000000000..bd711905e9 --- /dev/null +++ b/plugins/usbdmx/VellemanWidget.cpp @@ -0,0 +1,488 @@ +/* + * 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. + * + * VellemanWidget.cpp + * The synchronous and asynchronous Velleman widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/VellemanWidget.h" + +#include +#include +#include +#include + +#include "ola/Logging.h" +#include "ola/Constants.h" +#include "ola/StringUtils.h" +#include "plugins/usbdmx/AsyncUsbSender.h" +#include "plugins/usbdmx/LibUsbAdaptor.h" +#include "plugins/usbdmx/ThreadedUsbSender.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +using std::string; + +namespace { + +static const unsigned char ENDPOINT = 0x01; +// 25ms seems to be about the shortest we can go +static const unsigned int URB_TIMEOUT_MS = 25; +static const int CONFIGURATION = 1; +static const int INTERFACE = 0; +static const unsigned int DEFAULT_CHUNK_SIZE = 8; +static const unsigned int UPGRADED_CHUNK_SIZE = 64; +static const unsigned int HEADER_SIZE = 2; + +// Message types +// Length: 8 or 64 for the extended version. +// Data: [2] [slot N] [slot N +1] [slot N + 2] ... [slot N + 6] +static const uint8_t INTERMEDIATE_FRAME_MSG = 2; + +// Length: 8 or 64 for the extended version. +// Data: [3] [slot N] [undef] [undef] [undef] ... +static const uint8_t SINGLE_SLOT_MSG = 3; + +// This must be used to indicate a new DMX512 frame. +// Length: 8 or 64 for the extended version. +// Data: [4] [number of leading 0s] [slot N] [slot N + 1] [slot N + 2] ... +static const uint8_t BREAK_MSG = 4; + +// Length: 8 or 64 for the extended version. +// [5] [number of leading 0s] [slot N] [slot N + 1] [slot N + 2] ... +static const uint8_t INTERMEDIATE_COMPRESSED_FRAME_MSG = 5; + +// Length: 64, only defined for the extended version. +// Data: [4] [data size] [slot 0] [slot 1] [slot 2] ... +static const uint8_t VARIABLE_FRAME_CONTINUATION_MSG = 6; + +// Length: 64, only defined for the extended version. +// Data: [4] [data size] [slot 0] [slot 1] [slot 2] ... +static const uint8_t FULL_FRAME_MSG = 7; + +/* + * @brief Attempt to open a handle to a Velleman widget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use. + * @param[out] The chunk size of the device, this determines if the enhanced + * firmware is present. + * @returns A libusb_device_handle of NULL if it failed. + */ +libusb_device_handle *OpenVellemenWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + unsigned int *chunk_size) { + libusb_config_descriptor *config; + if (adaptor->GetActiveConfigDescriptor(usb_device, &config)) { + OLA_WARN << "Could not get active config descriptor"; + return NULL; + } + + // determine the max packet size, see + // http://opendmx.net/index.php/Velleman_K8062_Upgrade + // The standard size is 8. + *chunk_size = DEFAULT_CHUNK_SIZE; + if (config && + config->interface && + config->interface->altsetting && + config->interface->altsetting->endpoint) { + uint16_t max_packet_size = + config->interface->altsetting->endpoint->wMaxPacketSize; + OLA_DEBUG << "Velleman K8062 max packet size is " << max_packet_size; + if (max_packet_size == UPGRADED_CHUNK_SIZE) + // this means the upgrade is present + *chunk_size = max_packet_size; + } + adaptor->FreeConfigDescriptor(config); + + libusb_device_handle *usb_handle; + bool ok = adaptor->OpenDevice(usb_device, &usb_handle); + if (!ok) { + return NULL; + } + + int ret_code = adaptor->DetachKernelDriver(usb_handle, INTERFACE); + if (ret_code != 0 && ret_code != LIBUSB_ERROR_NOT_FOUND) { + OLA_WARN << "Failed to detach kernel driver"; + adaptor->Close(usb_handle); + return NULL; + } + + // this device only has one configuration + ret_code = adaptor->SetConfiguration(usb_handle, CONFIGURATION); + if (ret_code) { + OLA_WARN << "Velleman set config failed, with libusb error code " + << ret_code; + adaptor->Close(usb_handle); + return NULL; + } + + if (adaptor->ClaimInterface(usb_handle, INTERFACE)) { + OLA_WARN << "Failed to claim Velleman usb device"; + adaptor->Close(usb_handle); + return NULL; + } + return usb_handle; +} + +/** + * @brief Count the number of leading 0s in a block of data. + * ma + */ +unsigned int CountLeadingZeros(const uint8_t *data, unsigned int data_length, + unsigned int chunk_size) { + unsigned int leading_zero_count = 0; + + // This could be up to 254 for the standard interface but then the shutdown + // process gets wacky. Limit it to 100 for the standard and 255 for the + // extended. + unsigned int max_leading_zeros = chunk_size == UPGRADED_CHUNK_SIZE ? + 254 : 100; + unsigned int rest_of_chunk = chunk_size - 2; + + while (leading_zero_count < max_leading_zeros && + leading_zero_count + rest_of_chunk < data_length && + data[leading_zero_count] == 0) { + leading_zero_count++; + } + return leading_zero_count; +} + +} // namespace + +// VellemanThreadedSender +// ----------------------------------------------------------------------------- + +/* + * Sends messages to a Velleman device in a separate thread. + */ +class VellemanThreadedSender: public ThreadedUsbSender { + public: + VellemanThreadedSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device, + libusb_device_handle *handle, + unsigned int chunk_size) + : ThreadedUsbSender(usb_device, handle), + m_adaptor(adaptor), + m_chunk_size(chunk_size) { + } + + private: + LibUsbAdaptor* const m_adaptor; + const unsigned int m_chunk_size; + + bool TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer); + bool SendDataChunk(libusb_device_handle *handle, + uint8_t *usb_data, + unsigned int chunk_size); +}; + +bool VellemanThreadedSender::TransmitBuffer(libusb_device_handle *handle, + const DmxBuffer &buffer) { + unsigned char usb_data[m_chunk_size]; + const unsigned int size = buffer.Size(); + const uint8_t *data = buffer.GetRaw(); + unsigned int i = 0; + + unsigned int compressed_channel_count = m_chunk_size - 2; + unsigned int channel_count = m_chunk_size - 1; + + memset(usb_data, 0, sizeof(usb_data)); + + if (m_chunk_size == UPGRADED_CHUNK_SIZE && size <= m_chunk_size - 2) { + // if the upgrade is present and we can fit the data in a single packet + // use FULL_FRAME_MSG. + usb_data[0] = FULL_FRAME_MSG; + usb_data[1] = size; // number of channels in packet + memcpy(usb_data + HEADER_SIZE, data, std::min(size, m_chunk_size - 2)); + } else { + unsigned int leading_zero_count = CountLeadingZeros( + data, size, m_chunk_size); + usb_data[0] = BREAK_MSG; + usb_data[1] = leading_zero_count + 1; // include start code + memcpy(usb_data + HEADER_SIZE, data + leading_zero_count, + compressed_channel_count); + i += leading_zero_count + compressed_channel_count; + } + + if (!SendDataChunk(handle, usb_data, m_chunk_size)) + return false; + + while (i < size - channel_count) { + unsigned int leading_zero_count = CountLeadingZeros( + data + i, size - i, m_chunk_size); + if (leading_zero_count) { + // we have leading zeros + usb_data[0] = INTERMEDIATE_COMPRESSED_FRAME_MSG; + usb_data[1] = leading_zero_count; + memcpy(usb_data + HEADER_SIZE, data + i + leading_zero_count, + compressed_channel_count); + i += leading_zero_count + compressed_channel_count; + } else { + usb_data[0] = INTERMEDIATE_FRAME_MSG; + memcpy(usb_data + 1, data + i, channel_count); + i += channel_count; + } + if (!SendDataChunk(handle, usb_data, m_chunk_size)) + return false; + } + + // send the last channels + if (m_chunk_size == UPGRADED_CHUNK_SIZE) { + // if running in extended mode we can use the 6 message type to send + // everything at once. + usb_data[0] = VARIABLE_FRAME_CONTINUATION_MSG; + usb_data[1] = size - i; + memcpy(usb_data + HEADER_SIZE, data + i, size - i); + if (!SendDataChunk(handle, usb_data, m_chunk_size)) + return false; + + } else { + // else we use the 3 message type to send one at a time + for (; i != size; i++) { + usb_data[0] = SINGLE_SLOT_MSG; + usb_data[1] = data[i]; + if (!SendDataChunk(handle, usb_data, m_chunk_size)) + return false; + } + } + return true; +} + +bool VellemanThreadedSender::SendDataChunk(libusb_device_handle *handle, + uint8_t *usb_data, + unsigned int chunk_size) { + int transferred; + int ret = m_adaptor->InterruptTransfer(handle, + ENDPOINT, reinterpret_cast(usb_data), + chunk_size, &transferred, URB_TIMEOUT_MS); + if (ret) { + OLA_INFO << "USB return code was " << ret << ", transferred " + << transferred; + } + return ret == 0; +} + +// SynchronousVellemanWidget +// ----------------------------------------------------------------------------- + +SynchronousVellemanWidget::SynchronousVellemanWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : VellemanWidget(adaptor), + m_usb_device(usb_device) { +} + +bool SynchronousVellemanWidget::Init() { + unsigned int chunk_size = DEFAULT_CHUNK_SIZE; + libusb_device_handle *usb_handle = OpenVellemenWidget( + m_adaptor, m_usb_device, &chunk_size); + + if (!usb_handle) { + return false; + } + + std::auto_ptr sender( + new VellemanThreadedSender(m_adaptor, m_usb_device, usb_handle, + chunk_size)); + if (!sender->Start()) { + return false; + } + m_sender.reset(sender.release()); + return true; +} + +bool SynchronousVellemanWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender.get() ? m_sender->SendDMX(buffer) : false; +} + +// VellemanAsyncUsbSender +// ----------------------------------------------------------------------------- +class VellemanAsyncUsbSender : public AsyncUsbSender { + public: + VellemanAsyncUsbSender(LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : AsyncUsbSender(adaptor, usb_device), + m_chunk_size(DEFAULT_CHUNK_SIZE), + m_buffer_offset(0) { + } + + ~VellemanAsyncUsbSender() { + CancelTransfer(); + } + + libusb_device_handle* SetupHandle() { + return OpenVellemenWidget(m_adaptor, m_usb_device, &m_chunk_size); + } + + bool PerformTransfer(const DmxBuffer &buffer); + + void PostTransferHook() { + if (m_buffer_offset < m_tx_buffer.Size()) { + ContinueTransfer(); + } else if (m_buffer_offset >= m_tx_buffer.Size()) { + m_buffer_offset = 0; + m_tx_buffer.Reset(); + } + } + + private: + // These are set once we known the type of device we're talking to. + unsigned int m_chunk_size; + + DmxBuffer m_tx_buffer; + // This tracks where were are in m_tx_buffer. A value of 0 means we're at the + // state of a DMX frame. + unsigned int m_buffer_offset; + + bool ContinueTransfer(); + + bool SendInitialChunk(const DmxBuffer &buffer); + bool SendIntermediateChunk(); + bool SendSingleSlotChunk(); + + bool SendChunk(uint8_t *usb_data, unsigned int data_length) { + FormatData(&std::cout, usb_data, data_length); + FillInterruptTransfer(ENDPOINT, usb_data, data_length, URB_TIMEOUT_MS); + return SubmitTransfer() == 0; + } + + DISALLOW_COPY_AND_ASSIGN(VellemanAsyncUsbSender); +}; + +bool VellemanAsyncUsbSender::PerformTransfer(const DmxBuffer &buffer) { + if (m_buffer_offset == 0) { + return SendInitialChunk(buffer); + } + // Otherwise we're part way through a transfer. + return ContinueTransfer(); +} + +bool VellemanAsyncUsbSender::SendInitialChunk(const DmxBuffer &buffer) { + unsigned char usb_data[m_chunk_size]; + unsigned int length = m_chunk_size - HEADER_SIZE; + + if (m_chunk_size == UPGRADED_CHUNK_SIZE && + buffer.Size() <= m_chunk_size - HEADER_SIZE) { + // If the upgrade is present and we can fit the data in a single chunk + // use the FULL_FRAME_MSG message type. + usb_data[0] = FULL_FRAME_MSG; + usb_data[1] = buffer.Size(); // number of slots in the frame. + buffer.Get(usb_data + HEADER_SIZE, &length); + memset(usb_data + HEADER_SIZE + length, 0, + m_chunk_size - length - HEADER_SIZE); + } else { + // Otherwise use BREAK_MSG to signal the start of frame. + unsigned int leading_zero_count = CountLeadingZeros( + buffer.GetRaw(), buffer.Size(), m_chunk_size); + usb_data[0] = BREAK_MSG; + usb_data[1] = leading_zero_count + 1; // include start code + buffer.GetRange(leading_zero_count, usb_data + HEADER_SIZE, &length); + memset(usb_data + HEADER_SIZE + length, 0, + m_chunk_size - length - HEADER_SIZE); + + unsigned int slots_sent = leading_zero_count + length; + if (slots_sent < buffer.Size()) { + // There are more frames to send. + m_tx_buffer.Set(buffer); + m_buffer_offset = slots_sent; + } + } + return SendChunk(usb_data, m_chunk_size) == 0; +} + +bool VellemanAsyncUsbSender::SendIntermediateChunk() { + unsigned char usb_data[m_chunk_size]; + + // Intermediate frame. + unsigned int zeros = CountLeadingZeros( + m_tx_buffer.GetRaw() + m_buffer_offset, + m_tx_buffer.Size() - m_buffer_offset, + m_chunk_size); + + unsigned int length = m_chunk_size - 1; + if (zeros) { + // we have leading zeros + usb_data[0] = INTERMEDIATE_COMPRESSED_FRAME_MSG; + usb_data[1] = zeros; + length--; + m_tx_buffer.GetRange(m_buffer_offset + zeros, usb_data + HEADER_SIZE, + &length); + m_buffer_offset += zeros + length; + } else { + usb_data[0] = INTERMEDIATE_FRAME_MSG; + m_tx_buffer.GetRange(m_buffer_offset, usb_data + 1, &length); + memset(usb_data + 1 + length, 0, m_chunk_size - length - 1); + m_buffer_offset += length; + } + return SendChunk(usb_data, m_chunk_size) == 0; +} + +bool VellemanAsyncUsbSender::SendSingleSlotChunk() { + unsigned char usb_data[m_chunk_size]; + memset(usb_data, 0, m_chunk_size); + + usb_data[0] = SINGLE_SLOT_MSG; + usb_data[1] = m_tx_buffer.Get(m_buffer_offset); + m_buffer_offset++; + return SendChunk(usb_data, m_chunk_size) == 0; +} + +bool VellemanAsyncUsbSender::ContinueTransfer() { + if (m_buffer_offset + m_chunk_size + 1 < m_tx_buffer.Size()) { + return SendIntermediateChunk(); + } + + if (m_chunk_size == UPGRADED_CHUNK_SIZE) { + // If running in extended mode we can use the 6 message type to send + // everything at once. + unsigned char usb_data[m_chunk_size]; + unsigned int length = m_chunk_size - HEADER_SIZE; + + usb_data[0] = VARIABLE_FRAME_CONTINUATION_MSG; + usb_data[1] = m_tx_buffer.Size() - m_buffer_offset; + m_tx_buffer.GetRange(m_buffer_offset, usb_data + HEADER_SIZE, &length); + memset(usb_data + HEADER_SIZE + length, 0, + m_chunk_size - length - HEADER_SIZE); + return SendChunk(usb_data, m_chunk_size) == 0; + } else { + // The trailing slots are sendt individually. + return SendSingleSlotChunk(); + } +} + +// AsynchronousVellemanWidget +// ----------------------------------------------------------------------------- + +AsynchronousVellemanWidget::AsynchronousVellemanWidget( + LibUsbAdaptor *adaptor, + libusb_device *usb_device) + : VellemanWidget(adaptor) { + m_sender.reset(new VellemanAsyncUsbSender(m_adaptor, usb_device)); +} + +bool AsynchronousVellemanWidget::Init() { + return m_sender->Init(); +} + +bool AsynchronousVellemanWidget::SendDMX(const DmxBuffer &buffer) { + return m_sender->SendDMX(buffer); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/VellemanWidget.h b/plugins/usbdmx/VellemanWidget.h new file mode 100644 index 0000000000..e29827f8ad --- /dev/null +++ b/plugins/usbdmx/VellemanWidget.h @@ -0,0 +1,98 @@ +/* + * 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. + * + * VellemanWidget.h + * The synchronous and asynchronous Velleman widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_VELLEMANWIDGET_H_ +#define PLUGINS_USBDMX_VELLEMANWIDGET_H_ + +#include +#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 { + +/** + * @brief The interface for the Velleman Widgets + */ +class VellemanWidget: public BaseWidget { + public: + explicit VellemanWidget(LibUsbAdaptor *adaptor) + : BaseWidget(adaptor) { + } +}; + +/** + * @brief An Velleman widget that uses synchronous libusb operations. + * + * Internally this spawns a new thread to avoid blocking SendDMX() calls. + */ +class SynchronousVellemanWidget: public VellemanWidget { + public: + /** + * @brief Create a new SynchronousVellemanWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + SynchronousVellemanWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + libusb_device* const m_usb_device; + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(SynchronousVellemanWidget); +}; + +/** + * @brief An Velleman widget that uses asynchronous libusb operations. + */ +class AsynchronousVellemanWidget : public VellemanWidget { + public: + /** + * @brief Create a new AsynchronousVellemanWidget. + * @param adaptor the LibUsbAdaptor to use. + * @param usb_device the libusb_device to use for the widget. + */ + AsynchronousVellemanWidget(LibUsbAdaptor *adaptor, + libusb_device *usb_device); + + bool Init(); + + bool SendDMX(const DmxBuffer &buffer); + + private: + std::auto_ptr m_sender; + + DISALLOW_COPY_AND_ASSIGN(AsynchronousVellemanWidget); +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_VELLEMANWIDGET_H_ diff --git a/plugins/usbdmx/VellemanWidgetFactory.cpp b/plugins/usbdmx/VellemanWidgetFactory.cpp new file mode 100644 index 0000000000..f8eb2308d6 --- /dev/null +++ b/plugins/usbdmx/VellemanWidgetFactory.cpp @@ -0,0 +1,56 @@ +/* + * 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. + * + * VellemanWidgetFactory.cpp + * The WidgetFactory for Velleman widgets. + * Copyright (C) 2014 Simon Newton + */ + +#include "plugins/usbdmx/VellemanWidgetFactory.h" + +#include "ola/Logging.h" +#include "ola/base/Flags.h" +#include "plugins/usbdmx/VellemanWidget.h" + +DECLARE_bool(use_async_libusb); + +namespace ola { +namespace plugin { +namespace usbdmx { + +const uint16_t VellemanWidgetFactory::VENDOR_ID = 0x10cf; +const uint16_t VellemanWidgetFactory::PRODUCT_ID = 0x8062; + +bool VellemanWidgetFactory::DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) { + if (descriptor.idVendor != VENDOR_ID || descriptor.idProduct != PRODUCT_ID || + HasDevice(usb_device)) { + return false; + } + + OLA_INFO << "Found a new Velleman device"; + VellemanWidget *widget = NULL; + if (FLAGS_use_async_libusb) { + widget = new AsynchronousVellemanWidget(m_adaptor, usb_device); + } else { + widget = new SynchronousVellemanWidget(m_adaptor, usb_device); + } + return AddWidget(observer, usb_device, widget); +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola diff --git a/plugins/usbdmx/VellemanDevice.h b/plugins/usbdmx/VellemanWidgetFactory.h similarity index 50% rename from plugins/usbdmx/VellemanDevice.h rename to plugins/usbdmx/VellemanWidgetFactory.h index 5a40ba9357..0bd008e910 100644 --- a/plugins/usbdmx/VellemanDevice.h +++ b/plugins/usbdmx/VellemanWidgetFactory.h @@ -13,38 +13,44 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * VellemanDevice.h - * Interface for the Velleman device - * Copyright (C) 2010 Simon Newton + * VellemanWidgetFactory.h + * The WidgetFactory for Velleman widgets. + * Copyright (C) 2014 Simon Newton */ -#ifndef PLUGINS_USBDMX_VELLEMANDEVICE_H_ -#define PLUGINS_USBDMX_VELLEMANDEVICE_H_ +#ifndef PLUGINS_USBDMX_VELLEMANWIDGETFACTORY_H_ +#define PLUGINS_USBDMX_VELLEMANWIDGETFACTORY_H_ -#include -#include -#include "plugins/usbdmx/UsbDevice.h" +#include "ola/base/Macro.h" +#include "plugins/usbdmx/WidgetFactory.h" namespace ola { namespace plugin { namespace usbdmx { -/* - * A Velleman device +/** + * @brief Creates Velleman widgets. */ -class VellemanDevice: public UsbDevice { +class VellemanWidgetFactory : public BaseWidgetFactory { public: - VellemanDevice(ola::AbstractPlugin *owner, - libusb_device *usb_device) - : UsbDevice(owner, "Velleman USB Device", usb_device) { + explicit VellemanWidgetFactory(class LibUsbAdaptor *adaptor) + : m_adaptor(adaptor) { } - std::string DeviceId() const { return "velleman"; } + bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor); + + private: + class LibUsbAdaptor* const m_adaptor; + + static const uint16_t VENDOR_ID; + static const uint16_t PRODUCT_ID; - protected: - bool StartHook(); + DISALLOW_COPY_AND_ASSIGN(VellemanWidgetFactory); }; } // namespace usbdmx } // namespace plugin } // namespace ola -#endif // PLUGINS_USBDMX_VELLEMANDEVICE_H_ +#endif // PLUGINS_USBDMX_VELLEMANWIDGETFACTORY_H_ diff --git a/plugins/usbdmx/Widget.h b/plugins/usbdmx/Widget.h new file mode 100644 index 0000000000..95ffabb9b0 --- /dev/null +++ b/plugins/usbdmx/Widget.h @@ -0,0 +1,73 @@ +/* + * 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. + * + * Widget.h + * A generic USB Widget. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_WIDGET_H_ +#define PLUGINS_USBDMX_WIDGET_H_ + +#include "ola/DmxBuffer.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief The interface for an generic USB Widget. + */ +class Widget { + public: + virtual ~Widget() {} + + /** + * @brief Initialize the widget. + * @returns true if this widget was initialized correctly, false otherwise. + */ + virtual bool Init() = 0; + + /** + * @brief Send DMX data from this widget + * @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; +}; + +/** + * @brief A base widget class. + * + * This holds a pointer to a LibUsbAdaptor so we don't have to duplicate that + * code in all the Widgets. + */ +class BaseWidget : public Widget { + public: + /** + * @brief Create a new BaseWidget. + * @param adaptor the LibUsbAdaptor to use. + */ + explicit BaseWidget(class LibUsbAdaptor *adaptor) + : m_adaptor(adaptor) { + } + + protected: + class LibUsbAdaptor* const m_adaptor; +}; +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_WIDGET_H_ diff --git a/plugins/usbdmx/WidgetFactory.h b/plugins/usbdmx/WidgetFactory.h new file mode 100644 index 0000000000..5cd80a681d --- /dev/null +++ b/plugins/usbdmx/WidgetFactory.h @@ -0,0 +1,257 @@ +/* + * 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. + * + * WidgetFactory.h + * Creates USB Widgets. + * Copyright (C) 2014 Simon Newton + */ + +#ifndef PLUGINS_USBDMX_WIDGETFACTORY_H_ +#define PLUGINS_USBDMX_WIDGETFACTORY_H_ + +#include +#include +#include "ola/Logging.h" +#include "ola/base/Macro.h" +#include "ola/stl/STLUtils.h" + +namespace ola { +namespace plugin { +namespace usbdmx { + +/** + * @brief Receives notifications when Widgets are added or removed. + * + * Classes implementing the WidgetObserver can be used with WidgetFactories to + * receive notifcations when widgets are added or removed. + * + * On adding a new Widget, the appropriate NewWidget() method is called. The + * observer can mark a widget as in-use by returning true. + * + * When widgets are removed, the appropriate WidgetRemoved() method is called. + * The observer must not access the removed widget object once the call to + * WidgetRemoved() completes. + */ +class WidgetObserver { + public: + virtual ~WidgetObserver() {} + + /** + * @brief Called when a new AnymaWidget 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 AnymaWidget *widget) = 0; + + /** + * @brief Called when a new EuroliteProWidget 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 EuroliteProWidget *widget) = 0; + + /** + * @brief Called when a new FadecandyWidget 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 FadecandyWidget *widget) = 0; + + /** + * @brief Called when a new SunliteWidget 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 SunliteWidget *widget) = 0; + + /** + * @brief Called when a new VellemanWidget 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 VellemanWidget *widget) = 0; + + /** + * @brief Called when an AnymaWidget is removed. + * @param widget the Widget that has been removed. + * + * It is an error to use the widget once this call completes. + */ + virtual void WidgetRemoved(class AnymaWidget *widget) = 0; + + /** + * @brief Called when a EuroliteProWidget is removed. + * @param widget the Widget that has been removed. + * + * It is an error to use the widget once this call completes. + */ + virtual void WidgetRemoved(class EuroliteProWidget *widget) = 0; + + /** + * @brief Called when a FadecandyWidget is removed. + * @param widget the Widget that has been removed. + * + * It is an error to use the widget once this call completes. + */ + virtual void WidgetRemoved(class FadecandyWidget *widget) = 0; + + /** + * @brief Called when a SunliteWidget is removed. + * @param widget the Widget that has been removed. + * + * It is an error to use the widget once this call completes. + */ + virtual void WidgetRemoved(class SunliteWidget *widget) = 0; + + /** + * @brief Called when a VellemanWidget is removed. + * @param widget the Widget that has been removed. + * + * It is an error to use the widget once this call completes. + */ + virtual void WidgetRemoved(class VellemanWidget *widget) = 0; +}; + +/** + * @brief Creates new Widget objects to represent DMX USB hardware. + * + * WidgetFactories are called when new USB devices are located. By inspecting + * the device's vendor and product ID, they may choose to create a new Widget + * object. The WidgetFactory then calls the WidgetObserver object to indicate a + * new Widget has been added. + * + * When a USB device is removed, the factory that created a Widget from the + * device has it's DeviceRemoved() method called. The factory should then + * invoke WidgetRemoved on the observer object. + */ +class WidgetFactory { + public: + virtual ~WidgetFactory() {} + + /** + * @brief Called when a new USB device is added. + * @param observer The WidgetObserver to notify if this results in a new + * widget. + * @param usb_device the libusb_device that was added. + * @param descriptor the libusb_device_descriptor that corresponds to the + * usb_device. + * @returns True if this factory has claimed the usb_device, false otherwise. + */ + virtual bool DeviceAdded( + WidgetObserver *observer, + libusb_device *usb_device, + const struct libusb_device_descriptor &descriptor) = 0; + + /** + * @brief Called when a USB device is removed. + * @param observer The WidgetObserver to notify if this action results in a + * widget removal. + * @param usb_device the libusb_device that was removed. + */ + virtual void DeviceRemoved(WidgetObserver *observer, + libusb_device *usb_device) = 0; +}; + +/** + * @brief A partial implementation of WidgetFactory. + * + * This handles the mapping of libusb_devices to widgets and notifying the + * observer when widgets are added or removed. + */ +template +class BaseWidgetFactory : public WidgetFactory { + public: + BaseWidgetFactory() {} + + void DeviceRemoved(WidgetObserver *observer, + libusb_device *device); + + protected: + /** + * @brief Check if this factory is already using this device. + * @param usb_device The libusb_device to check for. + * @returns true if we already have a widget registered for this device, + * false otherwise. + */ + bool HasDevice(libusb_device *usb_device) { + return STLContains(m_widget_map, usb_device); + } + + /** + * @brief Initialize a widget and notify the observer. + * @param observer The WidgetObserver to notify of the new widget. + * @param usb_device the libusb_device associated with the widget. + * @param widget the new Widget, ownership is transferred. + * @returns True if the widget was added, false otherwise. + */ + bool AddWidget(WidgetObserver *observer, libusb_device *usb_device, + WidgetType *widget); + + private: + typedef std::map WidgetMap; + + WidgetMap m_widget_map; + + DISALLOW_COPY_AND_ASSIGN(BaseWidgetFactory); +}; + +template +bool BaseWidgetFactory::AddWidget(WidgetObserver *observer, + libusb_device *usb_device, + WidgetType *widget) { + if (!widget->Init()) { + delete widget; + return false; + } + + if (!observer->NewWidget(widget)) { + delete widget; + return false; + } + + WidgetType *old_widget = STLReplacePtr(&m_widget_map, usb_device, widget); + if (old_widget) { + // This should never happen. + OLA_WARN << "Widget conflict for " << usb_device; + observer->WidgetRemoved(old_widget); + delete old_widget; + } + return true; +} + +template +void BaseWidgetFactory::DeviceRemoved(WidgetObserver *observer, + libusb_device *usb_device) { + OLA_INFO << "Removing device " << usb_device; + WidgetType *widget = STLLookupAndRemovePtr(&m_widget_map, usb_device); + if (widget) { + observer->WidgetRemoved(widget); + delete widget; + } +} +} // namespace usbdmx +} // namespace plugin +} // namespace ola +#endif // PLUGINS_USBDMX_WIDGETFACTORY_H_