From b2f52aad89fee047fb95ed3b3d887f9760ba8f9e Mon Sep 17 00:00:00 2001 From: johan Date: Sat, 23 Nov 2013 11:14:27 +0100 Subject: [PATCH] Lightpack device added based on hidapi Former-commit-id: 4d3d9c01b169991757587a67479094186d52e6e8 --- dependencies/build/hidapi/CMakeLists.txt | 4 +- dependencies/build/hidapi/hid-libusb.c | 30 +- libsrc/hyperion/CMakeLists.txt | 12 +- libsrc/hyperion/Hyperion.cpp | 10 + .../device/LedDeviceLightpack-hidapi.cpp | 270 ++++++++++++++++++ .../device/LedDeviceLightpack-hidapi.h | 107 +++++++ libsrc/hyperion/device/LedDeviceLightpack.cpp | 2 +- 7 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 libsrc/hyperion/device/LedDeviceLightpack-hidapi.cpp create mode 100644 libsrc/hyperion/device/LedDeviceLightpack-hidapi.h diff --git a/dependencies/build/hidapi/CMakeLists.txt b/dependencies/build/hidapi/CMakeLists.txt index 8aa655d3..976ce80b 100644 --- a/dependencies/build/hidapi/CMakeLists.txt +++ b/dependencies/build/hidapi/CMakeLists.txt @@ -1,13 +1,11 @@ project(hidapi) #add libusb and pthreads -find_package(UDev REQUIRED) find_package(libusb-1.0 REQUIRED) find_package(Threads REQUIRED) include_directories( ../../include/hidapi - ${UDEV_INCLUDE_DIR} ${LIBUSB_1_INCLUDE_DIRS}) add_library(hidapi-libusb hid-libusb.c) @@ -15,4 +13,4 @@ add_library(hidapi-libusb hid-libusb.c) target_link_libraries(hidapi-libusb ${LIBUSB_1_LIBRARIES} #apt-get install libusb-1.0-0-dev ${CMAKE_THREAD_LIBS_INIT} - ${UDEV_LIBRARIES}) # apt-get install libudev-dev +) diff --git a/dependencies/build/hidapi/hid-libusb.c b/dependencies/build/hidapi/hid-libusb.c index 6c1d2471..e01e881b 100644 --- a/dependencies/build/hidapi/hid-libusb.c +++ b/dependencies/build/hidapi/hid-libusb.c @@ -20,7 +20,7 @@ files located at the root of the source distribution. These files may also be found in the public source code repository located at: - http://github.com/signal11/hidapi . + http://github.com/signal11/hidapi . ********************************************************/ #define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */ @@ -167,9 +167,9 @@ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur } else if (num_bytes == 4) { return (rpt[cur+4] * 0x01000000 + - rpt[cur+3] * 0x00010000 + - rpt[cur+2] * 0x00000100 + - rpt[cur+1] * 0x00000001); + rpt[cur+3] * 0x00010000 + + rpt[cur+2] * 0x00000100 + + rpt[cur+1] * 0x00000001); } else return 0; @@ -180,7 +180,7 @@ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur Usage and Usage Page that it finds in the descriptor. The return value is 0 on success and -1 on failure. */ static int get_usage(uint8_t *report_descriptor, size_t size, - unsigned short *usage_page, unsigned short *usage) + unsigned short *usage_page, unsigned short *usage) { unsigned int i = 0; int size_code; @@ -473,7 +473,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, /* Check the VID/PID against the arguments */ if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { + (product_id == 0x0 || product_id == dev_pid)) { struct hid_device_info *tmp; /* VID/PID match. Create the record. */ @@ -617,7 +617,7 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { + cur_dev->product_id == product_id) { if (serial_number) { if (wcscmp(serial_number, cur_dev->serial_number) == 0) { path_to_open = cur_dev->path; @@ -744,9 +744,9 @@ static void *read_thread(void *param) /* Break out of this loop only on fatal error.*/ if (res != LIBUSB_ERROR_BUSY && - res != LIBUSB_ERROR_TIMEOUT && - res != LIBUSB_ERROR_OVERFLOW && - res != LIBUSB_ERROR_INTERRUPTED) { + res != LIBUSB_ERROR_TIMEOUT && + res != LIBUSB_ERROR_OVERFLOW && + res != LIBUSB_ERROR_INTERRUPTED) { break; } } @@ -863,23 +863,23 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) endpoint. */ int is_interrupt = (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) - == LIBUSB_TRANSFER_TYPE_INTERRUPT; + == LIBUSB_TRANSFER_TYPE_INTERRUPT; int is_output = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_OUT; + == LIBUSB_ENDPOINT_OUT; int is_input = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_IN; + == LIBUSB_ENDPOINT_IN; /* Decide whether to use it for intput or output. */ if (dev->input_endpoint == 0 && - is_interrupt && is_input) { + is_interrupt && is_input) { /* Use this endpoint for INPUT */ dev->input_endpoint = ep->bEndpointAddress; dev->input_ep_max_packet_size = ep->wMaxPacketSize; } if (dev->output_endpoint == 0 && - is_interrupt && is_output) { + is_interrupt && is_output) { /* Use this endpoint for OUTPUT */ dev->output_endpoint = ep->bEndpointAddress; } diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index ef129371..fdd33b6b 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -4,13 +4,10 @@ SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/hyperion) SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/hyperion) #add libusb and pthreads (required for the Lighpack usb device) -find_package(UDev REQUIRED) find_package(libusb-1.0 REQUIRED) -find_package(Threads REQUIRED) include_directories( - ${UDEV_INCLUDE_DIR} - ${LIBUSB_1_INCLUDE_DIRS}) + ${LIBUSB_1_INCLUDE_DIRS}) # for Lightpack device # Group the headers that go through the MOC compiler SET(Hyperion_QT_HEADERS @@ -40,6 +37,7 @@ SET(Hyperion_HEADERS ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd8806.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack.h ${CURRENT_SOURCE_DIR}/device/LedDeviceMultiLightpack.h + ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack-hidapi.h ) SET(Hyperion_SOURCES @@ -64,6 +62,7 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/device/LedDeviceAdalight.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceMultiLightpack.cpp + ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack-hidapi.cpp ) set(Hyperion_RESOURCES @@ -85,7 +84,6 @@ add_library(hyperion target_link_libraries(hyperion hyperion-utils serialport + hidapi-libusb ${QT_LIBRARIES} - ${LIBUSB_1_LIBRARIES} #apt-get install libusb-1.0-0-dev - ${CMAKE_THREAD_LIBS_INIT} - ${UDEV_LIBRARIES}) # apt-get install libudev-dev +) diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index dbfd123d..9f742544 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -20,6 +20,7 @@ #include "device/LedDeviceWs2801.h" #include "device/LedDeviceAdalight.h" #include "device/LedDeviceLightpack.h" +#include "device/LedDeviceLightpack-hidapi.h" #include "device/LedDeviceMultiLightpack.h" #include "LinearColorSmoothing.h" @@ -94,6 +95,15 @@ LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) device = deviceLightpack; } + else if (type == "lightpack-hidapi") + { + const std::string output = deviceConfig.get("output", "").asString(); + + LedDeviceLightpackHidapi* deviceLightpack = new LedDeviceLightpackHidapi(); + deviceLightpack->open(output); + + device = deviceLightpack; + } else if (type == "multi-lightpack") { LedDeviceMultiLightpack* deviceLightpack = new LedDeviceMultiLightpack(); diff --git a/libsrc/hyperion/device/LedDeviceLightpack-hidapi.cpp b/libsrc/hyperion/device/LedDeviceLightpack-hidapi.cpp new file mode 100644 index 00000000..e23f7148 --- /dev/null +++ b/libsrc/hyperion/device/LedDeviceLightpack-hidapi.cpp @@ -0,0 +1,270 @@ +// stl includes +#include +#include +#include + +// Local Hyperion includes +#include "LedDeviceLightpack-hidapi.h" + +// from USB_ID.h (http://code.google.com/p/light-pack/source/browse/CommonHeaders/USB_ID.h) +#define USB_OLD_VENDOR_ID 0x03EB +#define USB_OLD_PRODUCT_ID 0x204F +#define USB_VENDOR_ID 0x1D50 +#define USB_PRODUCT_ID 0x6022 + +#define LIGHTPACK_INTERFACE 0 + +// from commands.h (http://code.google.com/p/light-pack/source/browse/CommonHeaders/commands.h) +// Commands to device, sends it in first byte of data[] +enum COMMANDS{ + CMD_UPDATE_LEDS = 1, + CMD_OFF_ALL, + CMD_SET_TIMER_OPTIONS, + CMD_SET_PWM_LEVEL_MAX_VALUE, /* deprecated */ + CMD_SET_SMOOTH_SLOWDOWN, + CMD_SET_BRIGHTNESS, + + CMD_NOP = 0x0F +}; + +// from commands.h (http://code.google.com/p/light-pack/source/browse/CommonHeaders/commands.h) +enum DATA_VERSION_INDEXES{ + INDEX_FW_VER_MAJOR = 1, + INDEX_FW_VER_MINOR +}; + +LedDeviceLightpackHidapi::LedDeviceLightpackHidapi() : + LedDevice(), + _deviceHandle(nullptr), + _serialNumber(""), + _firmwareVersion({-1,-1}), + _ledCount(-1), + _bitsPerChannel(-1), + _ledBuffer() +{ +} + +LedDeviceLightpackHidapi::~LedDeviceLightpackHidapi() +{ + if (_deviceHandle != nullptr) + { + hid_close(_deviceHandle); + _deviceHandle = nullptr; + } + + // TODO: Should be called to avoid memory loss, but only at the end of the application + //hid_exit(); +} + +int LedDeviceLightpackHidapi::open(const std::string & serialNumber) +{ + // initialize the usb context + int error = hid_init(); + if (error != 0) + { + std::cerr << "Error while initializing the hidapi context" << std::endl; + return -1; + } + std::cout << "Hidapi initialized" << std::endl; + + // retrieve the list of usb devices + hid_device_info * deviceList = hid_enumerate(0x0, 0x0); + + // iterate the list of devices + for (hid_device_info * deviceInfo = deviceList; deviceInfo != nullptr; deviceInfo = deviceInfo->next) + { + // try to open and initialize the device + error = testAndOpen(deviceInfo, serialNumber); + + if (error == 0) + { + // a device was sucessfully opened. break from list + break; + } + } + + // free the device list + hid_free_enumeration(deviceList); + + if (_deviceHandle == nullptr) + { + if (_serialNumber.empty()) + { + std::cerr << "No Lightpack device has been found" << std::endl; + } + else + { + std::cerr << "No Lightpack device has been found with serial " << _serialNumber << std::endl; + } + } + + return _deviceHandle == nullptr ? -1 : 0; +} + +int LedDeviceLightpackHidapi::testAndOpen(hid_device_info *device, const std::string & requestedSerialNumber) +{ + if ((device->vendor_id == USB_VENDOR_ID && device->product_id == USB_PRODUCT_ID) || + (device->vendor_id == USB_OLD_VENDOR_ID && device->product_id == USB_OLD_PRODUCT_ID)) + { + std::cout << "Found a lightpack device. Retrieving more information..." << std::endl; + + // get the serial number + std::string serialNumber = ""; + if (device->serial_number != nullptr) + { + // the serial number needs to be converted to a char array instead of wchar + size_t size = wcslen(device->serial_number); + serialNumber.resize(size, '.'); + for (size_t i = 0; i < size; ++i) + { + int c = wctob(device->serial_number[i]); + if (c != EOF) + { + serialNumber[i] = c; + } + } + } + else + { + std::cerr << "No serial number for Lightpack device" << std::endl; + } + + std::cout << "Lightpack device found: path=" << device->path << " serial=" << serialNumber << std::endl; + + // check if this is the device we are looking for + if (requestedSerialNumber.empty() || requestedSerialNumber == serialNumber) + { + // This is it! + _deviceHandle = hid_open_path(device->path); + + if (_deviceHandle != nullptr) + { + _serialNumber = serialNumber; + + std::cout << "Lightpack device successfully opened" << std::endl; + + // get the firmware version + uint8_t buffer[256]; + buffer[0] = 0; // report id + int error = hid_get_feature_report(_deviceHandle, buffer, sizeof(buffer)); + if (error < 4) + { + std::cerr << "Unable to retrieve firmware version number from Lightpack device" << std::endl; + } + else + { + _firmwareVersion.majorVersion = buffer[INDEX_FW_VER_MAJOR+1]; + _firmwareVersion.minorVersion = buffer[INDEX_FW_VER_MINOR+1]; + } + + // FOR TESTING PURPOSE: FORCE MAJOR VERSION TO 6 + _firmwareVersion.majorVersion = 6; + + // disable smoothing of the chosen device + disableSmoothing(); + + // determine the number of leds + if (_firmwareVersion.majorVersion == 4) + { + _ledCount = 8; + } + else + { + _ledCount = 10; + } + + // determine the bits per channel + if (_firmwareVersion.majorVersion == 6) + { + // maybe also or version 7? The firmware suggest this is only for 6... (2013-11-13) + _bitsPerChannel = 12; + } + else + { + _bitsPerChannel = 8; + } + + // set the led buffer size (repport id + command + 6 bytes per led) + _ledBuffer = std::vector(2 + _ledCount * 6, 0); + _ledBuffer[0] = 0x0; // report id + _ledBuffer[1] = CMD_UPDATE_LEDS; + + // return success + std::cout << "Lightpack device opened: path=" << device->path << " serial=" << _serialNumber << " version=" << _firmwareVersion.majorVersion << "." << _firmwareVersion.minorVersion << std::endl; + return 0; + } + else + { + std::cerr << "Unable to open Lightpack device. Searching for other device" << std::endl; + } + } + } + + return -1; +} + +int LedDeviceLightpackHidapi::write(const std::vector &ledValues) +{ + return write(ledValues.data(), ledValues.size()); +} + +int LedDeviceLightpackHidapi::write(const ColorRgb * ledValues, int size) +{ + int count = std::min(_ledCount, size); + + for (int i = 0; i < count ; ++i) + { + const ColorRgb & color = ledValues[i]; + + // copy the most significant bits of the rgb values to the first three bytes + // offset 1 to accomodate for the report id and command byte + _ledBuffer[6*i+2] = color.red; + _ledBuffer[6*i+3] = color.green; + _ledBuffer[6*i+4] = color.blue; + + // leave the next three bytes on zero... + // 12-bit values having zeros in the lowest 4 bits which is almost correct, but it saves extra + // switches to determine what to do and some bit shuffling + } + + int error = writeBytes(_ledBuffer.data(), _ledBuffer.size()); + return error >= 0 ? 0 : error; +} + +int LedDeviceLightpackHidapi::switchOff() +{ + unsigned char buf[2] = {0x0, CMD_OFF_ALL}; + return writeBytes(buf, sizeof(buf)) == sizeof(buf); +} + +const std::string &LedDeviceLightpackHidapi::getSerialNumber() const +{ + return _serialNumber; +} + +int LedDeviceLightpackHidapi::getLedCount() const +{ + return _ledCount; +} + +int LedDeviceLightpackHidapi::writeBytes(uint8_t *data, int size) +{ +// std::cout << "Writing " << size << " bytes: "; +// for (int i = 0; i < size ; ++i) printf("%02x ", data[i]); +// std::cout << std::endl; + + int error = hid_send_feature_report(_deviceHandle, data, size); + if (error == size) + { + return 0; + } + + std::cerr << "Unable to write " << size << " bytes to Lightpack device(" << error << ")" << std::endl; + return error; +} + +int LedDeviceLightpackHidapi::disableSmoothing() +{ + unsigned char buf[2] = {CMD_SET_SMOOTH_SLOWDOWN, 0}; + return writeBytes(buf, sizeof(buf)) == sizeof(buf); +} diff --git a/libsrc/hyperion/device/LedDeviceLightpack-hidapi.h b/libsrc/hyperion/device/LedDeviceLightpack-hidapi.h new file mode 100644 index 00000000..7b25dfb9 --- /dev/null +++ b/libsrc/hyperion/device/LedDeviceLightpack-hidapi.h @@ -0,0 +1,107 @@ +#pragma once + +// stl includes +#include +#include +#include + +// libusb include +#include + +// Hyperion includes +#include + +/// +/// LedDevice implementation for a lightpack device (http://code.google.com/p/light-pack/) +/// +class LedDeviceLightpackHidapi : public LedDevice +{ +public: + /// + /// Constructs the LedDeviceLightpack + /// + LedDeviceLightpackHidapi(); + + /// + /// Destructor of the LedDevice; closes the output device if it is open + /// + virtual ~LedDeviceLightpackHidapi(); + + /// + /// Opens and configures the output device + /// + /// @return Zero on succes else negative + /// + int open(const std::string & serialNumber = ""); + + /// + /// Writes the RGB-Color values to the leds. + /// + /// @param[in] ledValues The RGB-color per led + /// + /// @return Zero on success else negative + /// + virtual int write(const std::vector& ledValues); + + /// + /// Writes the RGB-Color values to the leds. + /// + /// @param[in] ledValues Array of RGB values + /// @param[in] size The number of RGB values + /// + /// @return Zero on success else negative + /// + int write(const ColorRgb * ledValues, int size); + + /// + /// Switch the leds off + /// + /// @return Zero on success else negative + /// + virtual int switchOff(); + + /// Get the serial of the Lightpack + const std::string & getSerialNumber() const; + + /// Get the number of leds + int getLedCount() const; + +private: + /// + /// Test if the device is a (or the) lightpack we are looking for + /// + /// @return Zero on succes else negative + /// + int testAndOpen(hid_device_info * device, const std::string & requestedSerialNumber); + + /// write bytes to the device + int writeBytes(uint8_t *data, int size); + + /// Disable the internal smoothing on the Lightpack device + int disableSmoothing(); + + struct Version + { + int majorVersion; + int minorVersion; + }; + +private: + /// libusb device handle + hid_device * _deviceHandle; + + /// device serial number + std::string _serialNumber; + + /// firmware version of the device + Version _firmwareVersion; + + /// the number of leds of the device + int _ledCount; + + /// the number of bits per channel + int _bitsPerChannel; + + /// buffer for led data + std::vector _ledBuffer; +}; diff --git a/libsrc/hyperion/device/LedDeviceLightpack.cpp b/libsrc/hyperion/device/LedDeviceLightpack.cpp index 1dd262ff..0deac582 100644 --- a/libsrc/hyperion/device/LedDeviceLightpack.cpp +++ b/libsrc/hyperion/device/LedDeviceLightpack.cpp @@ -279,7 +279,7 @@ int LedDeviceLightpack::writeBytes(uint8_t *data, int size) int error = libusb_control_transfer(_deviceHandle, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE, - LIBUSB_REQUEST_SET_CONFIGURATION, + 0x09, (2 << 8), 0x00, data, size, 1000);