Skip to content
Snippets Groups Projects
enttecdmxdevice.cpp 9.51 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Fadecandy driver for the Enttec DMX USB Pro.
     * 
     * Copyright (c) 2013 Micah Elizabeth Scott
     * 
     * Permission is hereby granted, free of charge, to any person obtaining a copy of
     * this software and associated documentation files (the "Software"), to deal in
     * the Software without restriction, including without limitation the rights to
     * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
     * the Software, and to permit persons to whom the Software is furnished to do so,
     * subject to the following conditions:
     * 
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     * 
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
     * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
     * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     */
    
    #include "enttecdmxdevice.h"
    
    #include <sstream>
    #include <iostream>
    
    EnttecDMXDevice::Transfer::Transfer(EnttecDMXDevice *device, void *buffer, int length)
    
        : transfer(libusb_alloc_transfer(0)), finished(false)
    
        libusb_fill_bulk_transfer(transfer, device->mHandle,
            OUT_ENDPOINT, (uint8_t*) buffer, length, EnttecDMXDevice::completeTransfer, this, 2000);
    
    }
    
    EnttecDMXDevice::Transfer::~Transfer()
    {
    
        libusb_free_transfer(transfer);
    
    EnttecDMXDevice::EnttecDMXDevice(libusb_device *device, bool verbose)
    
        : USBDevice(device, verbose),
    
          mConfigMap(0)
    
        mSerial[0] = '\0';
    
        // Initialize a minimal valid DMX packet
        memset(&mChannelBuffer, 0, sizeof mChannelBuffer);
        mChannelBuffer.start = START_OF_MESSAGE;
        mChannelBuffer.label = SEND_DMX_PACKET;
        mChannelBuffer.data[0] = START_CODE;
        setChannel(1, 0);
    
    
    EnttecDMXDevice::~EnttecDMXDevice()
    
         * If we have pending transfers, cancel them.
         * The Transfer objects themselves will be freed once libusb completes them.
    
         */
    
        for (std::set<Transfer*>::iterator i = mPending.begin(), e = mPending.end(); i != e; ++i) {
            Transfer *fct = *i;
            libusb_cancel_transfer(fct->transfer);
        }
    
    
    bool EnttecDMXDevice::probe(libusb_device *device)
    {
    
        /*
         * Prior to opening the device, all we can do is look for an FT245 device.
         * We'll take a closer look in probeAfterOpening(), once we can see the
         * string descriptors.
         */
    
        libusb_device_descriptor dd;
    
        if (libusb_get_device_descriptor(device, &dd) < 0) {
            // Can't access descriptor?
            return false;
        }
    
        // FTDI FT245
        return dd.idVendor == 0x0403 && dd.idProduct == 0x6001;
    
        libusb_device_descriptor dd;
        int r = libusb_get_device_descriptor(mDevice, &dd);
        if (r < 0) {
            return r;
        }
    
        r = libusb_open(mDevice, &mHandle);
        if (r < 0) {
            return r;
        }
    
        /*
         * Match the manufacturer and product strings! This is the least intrusive way to
         * determine that the attached device is in fact an Enttec DMX USB Pro, since it doesn't
         * have a unique vendor/product ID.
         */
    
    
        if (dd.iManufacturer && dd.iProduct && dd.iSerialNumber) {
    
            char manufacturer[256];
            char product[256];
    
            r = libusb_get_string_descriptor_ascii(mHandle, dd.iManufacturer, (uint8_t*)manufacturer, sizeof manufacturer);
            if (r < 0) {
                return r;
            }
            r = libusb_get_string_descriptor_ascii(mHandle, dd.iProduct, (uint8_t*)product, sizeof product);
            if (r < 0) {
                return r;
            }
    
            mFoundEnttecStrings = !strcmp(manufacturer, "ENTTEC") && !strcmp(product, "DMX USB PRO");
        }
    
        /*
         * Only go further if we have in fact found evidence that this is the right device.
         */
    
        if (mFoundEnttecStrings) {
    
            // Only relevant on linux; try to detach the FTDI driver.
            libusb_detach_kernel_driver(mHandle, 0);
    
            r = libusb_claim_interface(mHandle, 0);
            if (r < 0) {
                return r;
            }
    
            r = libusb_get_string_descriptor_ascii(mHandle, dd.iSerialNumber, (uint8_t*)mSerial, sizeof mSerial);
            if (r < 0) {
                return r;
            }
        }
    
        return 0;
    
    
    bool EnttecDMXDevice::probeAfterOpening()
    {
        // By default, any device is supported by the time we get to opening it.
        return mFoundEnttecStrings;
    }
    
    
    bool EnttecDMXDevice::matchConfiguration(const Value &config)
    {
    
        if (matchConfigurationWithTypeAndSerial(config, "enttec", mSerial)) {
            mConfigMap = findConfigMap(config);
            return true;
        }
    
        return false;
    
    std::string EnttecDMXDevice::getName()
    {
    
        std::ostringstream s;
        s << "Enttec DMX USB Pro";
        if (mSerial[0]) {
            s << " (Serial# " << mSerial << ")";
        }
        return s.str();
    
    }
    
    void EnttecDMXDevice::setChannel(unsigned n, uint8_t value)
    {
    
        if (n >= 1 && n <= 512) {
            unsigned len = std::max<unsigned>(mChannelBuffer.length, n + 1);
            mChannelBuffer.length = len;
            mChannelBuffer.data[n] = value;
            mChannelBuffer.data[len] = END_OF_MESSAGE;
        }
    
    }
    
    void EnttecDMXDevice::submitTransfer(Transfer *fct)
    {
    
        /*
         * Submit a new USB transfer. The Transfer object is guaranteed to be freed eventually.
         * On error, it's freed right away.
         */
    
        int r = libusb_submit_transfer(fct->transfer);
    
        if (r < 0) {
            if (mVerbose && r != LIBUSB_ERROR_PIPE) {
                std::clog << "Error submitting USB transfer: " << libusb_strerror(libusb_error(r)) << "\n";
            }
            delete fct;
        } else {
            mPending.insert(fct);
        }
    
    }
    
    void EnttecDMXDevice::completeTransfer(struct libusb_transfer *transfer)
    {
    
        EnttecDMXDevice::Transfer *fct = static_cast<EnttecDMXDevice::Transfer*>(transfer->user_data);
    
    void EnttecDMXDevice::flush()
    {
        // Erase any finished transfers
    
        std::set<Transfer*>::iterator current = mPending.begin();
        while (current != mPending.end()) {
            std::set<Transfer*>::iterator next = current;
            next++;
    
            Transfer *fct = *current;
            if (fct->finished) {
                mPending.erase(current);
                delete fct;
            }
    
            current = next;
        }
    
    }
    
    void EnttecDMXDevice::writeDMXPacket()
    {
    
        /*
         * Asynchronously write an FTDI packet containing an Enttec packet containing
         * our set of DMX channels.
         *
         * XXX: We should probably throttle this so that we don't send DMX messages
         *      faster than the Enttec device can keep up!
         */
    
        submitTransfer(new Transfer(this, &mChannelBuffer, mChannelBuffer.length + 5));
    
    void EnttecDMXDevice::writeMessage(const OPC::Message &msg)
    
        /*
         * Dispatch an incoming OPC command
         */
    
        switch (msg.command) {
    
            case OPC::SetPixelColors:
    
                opcSetPixelColors(msg);
                writeDMXPacket();
                return;
    
            case OPC::SystemExclusive:
    
                // No relevant SysEx for this device
                return;
        }
    
        if (mVerbose) {
            std::clog << "Unsupported OPC command: " << unsigned(msg.command) << "\n";
        }
    
    void EnttecDMXDevice::opcSetPixelColors(const OPC::Message &msg)
    
        /*
         * Parse through our device's mapping, and store any relevant portions of 'msg'
         * in the framebuffer.
         */
    
        if (!mConfigMap) {
            // No mapping defined yet. This device is inactive.
            return;
        }
    
        const Value &map = *mConfigMap;
        for (unsigned i = 0, e = map.Size(); i != e; i++) {
            opcMapPixelColors(msg, map[i]);
        }
    
    void EnttecDMXDevice::opcMapPixelColors(const OPC::Message &msg, const Value &inst)
    
        /*
         * Parse one JSON mapping instruction, and copy any relevant parts of 'msg'
         * into our framebuffer. This looks for any mapping instructions that we
         * recognize:
         *
    
         *   [ OPC Channel, OPC Pixel, Pixel Color, DMX Channel ]
         */
    
        unsigned msgPixelCount = msg.length() / 3;
    
        if (inst.IsArray() && inst.Size() == 4) {
    
            // Map a range from an OPC channel to our framebuffer
    
            const Value &vChannel = inst[0u];
            const Value &vPixelIndex = inst[1];
            const Value &vPixelColor = inst[2];
            const Value &vDMXChannel = inst[3];
    
            if (vChannel.IsUint() && vPixelIndex.IsUint() && vPixelColor.IsString() && vDMXChannel.IsUint()) {
                unsigned channel = vChannel.GetUint();
                unsigned pixelIndex = vPixelIndex.GetUint();
                const char *pixelColor = vPixelColor.GetString();
                unsigned dmxChannel = vDMXChannel.GetUint();
    
                if (channel != msg.channel || pixelIndex >= msgPixelCount) {
                    return;
                }
    
                const uint8_t *pixel = msg.data + (pixelIndex * 3);
    
                switch (pixelColor[0]) {
    
                    case 'r':
                        setChannel(dmxChannel, pixel[0]);
                        break;
    
                    case 'g':
                        setChannel(dmxChannel, pixel[1]);
                        break;
    
                    case 'b':
                        setChannel(dmxChannel, pixel[2]);
                        break;
    
                    case 'l':
                        setChannel(dmxChannel, (unsigned(pixel[0]) + unsigned(pixel[1]) + unsigned(pixel[2])) / 3);
                        break;
    
        // Still haven't found a match?
    
            std::clog << "Unsupported JSON mapping instruction\n";