/* 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);
}