Comfused... how do I write C++ on Linux for USB -> DMX Enttec Pro clone

787 views
Skip to first unread message

Scott M

unread,
May 27, 2017, 9:16:23 AM5/27/17
to open-lighting
I'm trying  to research how to do something I thought would be trivial - write some C++ code for Linux to open a USB -> DMX gateway and set values to various channels. (There'll be a couple of different Linux platforms involved; one's a raspberry pi, the other is a laptop.)

I'm assuming I need to use libusb, but I've never used it and the documentation isn't very helpful. 

My perhaps overly naive hope is that there's a short sequence of libusb calls to find and open the device, and then some simple call to make to send some relatively simple message to set channel values. And that such code should be portable to any Linux with libusb, because bytes are bytes.

But if it was that simple, sample code should have turned up easily in searches, and it hasn't.  Is there something mysterious about writing code for this?

Using a commercial app or prebuilt raspberry pi image isn't practical in my situation. I'm porting code that computes a complicated (and changing) set of fades, based on a lot of conditions I won't be able to explain to anyone's commercial app. Moment by moment (or at least at rates up to about 25 times a second), I need to be able to set the values of a handful of channels in ways that don't really fit the model of prewritten scripts.

I'm familiar with C++ and moderately comfortable with Linux app development; that's not the issue. 

Any help, but especially sample code, very welcome. TIA.

Peter Newman

unread,
May 27, 2017, 1:56:47 PM5/27/17
to open-lighting
Hi Scott,

We just open /dev/tty whatever for the Enttec DMX USB Pro (which I see you were asking about on IRC). But it should be possible to do so with libusb too.

There are a few options, but I'd suggest the best one is to let us deal with the DMX transmission side of things, and if you just generate the data for the DMX frames. You can then generate the "complicated (and changing) set of fades" sending a frame each time when you need to. There are details of our client API here, to allow you to generate and send DMX to any of the USB, serial or IP interfaces we support:
http://docs.openlighting.org/ola/doc/latest/dmx_cpp_client_tutorial.html

Scott M

unread,
May 28, 2017, 6:35:19 PM5/28/17
to open-lighting
I was going insane looking for that sample...

I wrote something that worked for me, on an ubuntu laptop at least. No claims that it's in any way better than OLA code, but it compiles with a simple
g++ fade.cpp
and is thread safe, and is extendable to allow various devices to be abstracted separately. I'm going to throw it here because I bothered to write it and it was hard to find the relevant details. Absolutely no copyright and anyone can do with it what they please.


/* Sample code. It works from an ubuntu laptop
to a DMXKing USB Pro, which alleges it is Enttec
compliant. No copyright, no warranty,
you get whatever you get. This doesn't cover anything
other than setting channels.
*/

#include <fcntl.h>
#include <unistd.h>
#include <iostream>

#include <cstring>


//mutex wrapper
class Lock {
pthread_mutex_t& mutex_;
public:
static void init(pthread_mutex_t* mutex)
{
#ifndef SINGLE_THREADED
pthread_mutex_init(mutex, 0);
#endif
}
static void destroy(pthread_mutex_t* mutex)
{
#ifndef SINGLE_THREADED
pthread_mutex_destroy(mutex);
#endif
}

Lock(pthread_mutex_t& mutex) : mutex_(mutex)
{
#ifndef SINGLE_THREADED
pthread_mutex_lock(&mutex_);
#endif
}

~Lock()
{
#ifndef SINGLE_THREADED
pthread_mutex_unlock(&mutex_);
#endif
}
};


//opens the serial device and provides basic support for sending label 6 commands.
//For Enttec pro and clones (tested on a clone).
class DMXEnttecProEtc {
protected:
pthread_mutex_t mutex_;
int fd_;
unsigned short channels_;
unsigned char buf_[608]; //extra, for future extensions

static const unsigned char Offset_DataLength_LSB = 2;
static const unsigned char Offset_DataLength_MSB = 3;
static const unsigned char Offset_StartCode = 4; //where startcode lives
//c is 1..512
unsigned char* startOfChannels(const unsigned short c = 0)
{return buf_ + Offset_StartCode + c;}

void closeDevice_locked()
{
channels_ = 0;
if (fd_ == -1)
return;
close(fd_);
fd_ = -1;
}
public:
static const unsigned short MaxChannels = 512;
void closeDevice()
{
Lock _(mutex_);
closeDevice_locked();
}
bool openDevice(const char* filename, const unsigned char startCode = 0)
{
Lock _(mutex_);
closeDevice_locked();
fd_ = open(filename, O_RDWR);
if (fd_ == -1)
{
//std::cerr << "Failed " << errno << '\n';
return false;
}

//build incomplete message for label 6

buf_[0] = 0x7E; //Enttec start packet byte
buf_[1] = 6; //label for setting channel values
//data starts here
buf_[4] = startCode; //start code of 0 for dimmers, etc
return true;
}
DMXEnttecProEtc(const char* filename, unsigned char startCode = 0) : fd_(-1), channels_(0)
{
Lock::init(&mutex_);
openDevice(filename, startCode);
}
inline bool isOpen() const {return fd_ != -1;}
inline ~DMXEnttecProEtc()
{
if (fd_ != -1)
send(); //get any final stuff out
closeDevice();
Lock::destroy(&mutex_);
}

bool setChannel(unsigned short channelNum, const unsigned char value)
{
if (channelNum == 0 || channelNum > MaxChannels)
return false;
Lock _(mutex_);
if (channelNum > channels_)
{
channels_ = channelNum;
const unsigned short len = channels_ + 1;
//the offset of 1 accounts for the startCode
buf_[Offset_DataLength_LSB] = len & 0xff;
buf_[Offset_DataLength_MSB] = len >> 8;
}
*startOfChannels(channelNum) = value;
return true;
}

bool setChannels(const unsigned short channelNum,
const unsigned short channelCount,
const unsigned char *value)
{
if (channelCount == 0)
return true;
const unsigned short highChannel = channelNum + channelCount - 1;
if (channelNum == 0 || highChannel > MaxChannels)
return false;
Lock _(mutex_);
if (highChannel > channels_)
{
channels_ = highChannel;
const unsigned short len = highChannel + 1;
//the offset of 1 accounts for the startCode
buf_[Offset_DataLength_LSB] = len & 0xff;
buf_[Offset_DataLength_MSB] = len >> 8;
}
memcpy(startOfChannels(channelNum), value, channelCount);
return true;
}
bool send()
{
Lock _(mutex_);
if (channels_ == 0 || !isOpen())
return false;
unsigned char* at = startOfChannels(channels_ + 1);
*at = 0xE7; //Enttec end packet byte
const int size = 1 + (int)(at - buf_);
return size == write(fd_, buf_, size);
}
};

class DMXDevice {
protected:
DMXEnttecProEtc& dmxer_;
const unsigned short firstChannel_;
const unsigned short channelCount_;

DMXDevice(DMXEnttecProEtc& dmxer, const unsigned short firstChannel, const unsigned short chanCount)
: dmxer_(dmxer), firstChannel_(firstChannel), channelCount_(chanCount)
{ }

unsigned short nextFreeChannel() const {return firstChannel_ + channelCount_;}

bool setDeviceChannel(const unsigned short i, const unsigned char value) const
{
if (i >= channelCount_)
return false;
return dmxer_.setChannel(firstChannel_ + i, value);
}

bool setDeviceChannels(const unsigned short i, const unsigned short count,
const unsigned char* value) const
{
if (count == 0)
return true;
if (i + count - 1 >= channelCount_)
return false;
return dmxer_.setChannels(firstChannel_ + i, count, value);
}
public:

//Sends the universe, not just this device
bool send() const {return dmxer_.send();}

};

//Support for a basic n Channel dimmer
template <unsigned short CHAN>
class DimmerDMX : public DMXDevice {
protected:
public:

DimmerDMX(DMXEnttecProEtc& dmxer, const unsigned short firstChan)
: DMXDevice(dmxer, firstChan, CHAN)
{
setOff(); //turn off on construction
}

bool setOff() const
{
unsigned char values[CHAN];
memset(values, 0, sizeof values);
setDeviceChannels(0, CHAN, values);
return send();
}
DimmerDMX(const char* filename) : DMXEnttecProEtc(filename)
{
setOff();
}
//Leave alone on deconstruction
~DimmerDMX() {}

bool setOn(const unsigned char valueAll = 255) const
{
unsigned char values[CHAN];
memset(values, valueAll, sizeof values);
setDeviceChannels(0, CHAN, values);
return send();
}
};


//A 7 channel 3 color par from a cheap Chinese manufacturer who was too
// shy to give a company name. This class doesn't handle everything the
// hardware does, as we're only interested in proving the parent class works.
class ColorFloodDMX : public DMXDevice {
static const unsigned char ChannelBrightness = 0;
static const unsigned char ChannelR = 1;
static const unsigned char ChannelG = 2;
static const unsigned char ChannelB = 3;
static const unsigned char ChannelBlink = 4;
static const unsigned char ChannelMode = 5;
static const unsigned char ChannelData = 6;
public:
ColorFloodDMX(DMXEnttecProEtc& dmxer, const unsigned short firstChan)
: DMXDevice(dmxer, firstChan, 7)
{
setDeviceChannel(ChannelData, 0); //make sure we claim last channel
setOff(); //turn off on construction
}
//go dark on deconstruction
~ColorFloodDMX() {setOff();}
//no brightness, no color, no blink. And send it.
bool setOff() const
{
return setColors(0,0,0,0) && send();
}

//turns off strobe and sets colors and brightness.
//Does not send.
bool setColors(
const unsigned char r,
const unsigned char g,
const unsigned char b,
const unsigned char brightness = 255) const
{
setDeviceChannel(ChannelBrightness, brightness);
setDeviceChannel(ChannelR, r);
setDeviceChannel(ChannelG, g);
setDeviceChannel(ChannelB, b);
setDeviceChannel(ChannelBlink, 0);
setDeviceChannel(ChannelMode, 0);
}
//Turns on strobe. rate is in undefined units, but 0 is slowish
// and 247 is "fast".
bool setStrobe(unsigned char rate) const //0 to 247
{
setDeviceChannel(ChannelBlink, rate % 248 + 8);
}
//stop strobing
bool clearStrobe() const
{
setDeviceChannel(ChannelBlink, 0);
}
//If you called setColors(), this just rewrites the colors.
//It leaves strobing alone.
bool changeColors(unsigned char r, unsigned char g, unsigned char b,
unsigned char brightness = 255) const
{
setDeviceChannel(ChannelBrightness, brightness);
setDeviceChannel(ChannelR, r);
setDeviceChannel(ChannelG, g);
return setDeviceChannel(ChannelB, b);
}
};


//test code. Use sudo.
int main(int argc, char** argv)
{
//My DMXKing usb device showed up as /dev/ttyUSB0.
//Milage may vary.
DMXEnttecProEtc dmx("/dev/ttyUSB0");
if (!dmx.isOpen())
return EXIT_FAILURE;

ColorFloodDMX d(dmx, 1); //Par on channels 1-7
DimmerDMX<4> d2(dmx, 8); //Dimmer on 8-11
d2.setOn();
d.setColors(255,255,255,255);
d.setStrobe(0); //slow
dmx.send();
usleep(8000 * 1000);
d.clearStrobe();
dmx.send();

//steady fade up from off to bright
for (int i = 0; i <= 255; ++i)
{
//std::cerr << i << '\n';
//adjust for predominance of blue
if (!d.changeColors(255,255,129,i))
std::cerr << "Write failed\n";
dmx.send();
usleep(1000 * 1000 / 40);
}
d.setStrobe(247); //fast
d2.setOff();
dmx.send();

usleep(3000 * 1000);
}

Peter Newman

unread,
May 29, 2017, 6:56:02 PM5/29/17
to open-lighting
Sorry to hear that Scott.

There's also some general API notes on our website:

As well as some C++ specific API info:

From the looks of your code, you should just be able to write an alternative DMXEnttecProEtc class that talks to OLA if you wanted.
Reply all
Reply to author
Forward
0 new messages