DMX Lighting Sequence Player

Portland CORE effigy at Burning Man will be using DMX controlled lighting this year.  At least that's the plan, but a low-cost and low-power way to automatically play the lighting sequence (without a PC) is needed.  Here's a little board I made for the purpose.

Click "Read more" for source code and other technical details.

First, the lighting sequence is created using Vixen version 2.  Creating the sequence is pretty simple, just click ranges of time slots and use the toolbar buttons to turn the light on, off, fade up, fade down, etc.  Vixen has lots of little features to create patterns, but so far I've only used the simplest ones.

One nice feature of Vixen, which I knew existed but never tried back on the Hand-Eye Supply float (Tobias did most of the Vixen stuff) is the Sequence Preview.  It takes a photo of your project and then you can define pixels that will overlay the image for each lighting channel.  Here's a screenshot:

Vixen version 3 greatly expands the preview feature and adds lots of new features, but it doesn't use the simple time slots that we need from version 2.  Maybe someday I'll revist this project and make a way to use version 3, but for now it's limited to Vixen 2.

Vixen saves files to its Sequences folder as a ".vix" file.  It's XML format, with a big binary dump of the raw sequence data encoded as base64.  I started reading about Vixen's file format, determined to write a program to play it.  Pretty soon, I found Bill Porter had already done it, and written a very nice tutorial.

I just modified Bill's awesome script.  I've done very little with Python before, but it's a pretty easy language to pick up.  There are lots of tutorials and great documentation online.  But being a Python novice, I probably didn't do everything the best way.  At least it works.  Actually, it apparently only works with Python 2.7, but not Python version 3.  Again, I'm a Python novice....

It turned out Bill's script could not read a .vix file with the image preview.  It finds too many channels, because the channels within the preview get double counted.  Bill used Phython's minidom XML parser with getElementsByTagName() to find all the channels.  I found much better documentation for Python's ElementTree, so I rewrote the script using that to carefully find only the channels in the main section of the file.

I also changed the script's output.  Instead of creating a .cpp file to be compiled directly into Arduino, I had the script output a text file with the data in a format that could easily be read from a SD card.  This way, there's no practical limit to the sequence length.  The script stores the data in a simple format, so Arduino code can just read each line of the file as it plays the sequence.  Here's what the text format looks like:

100
000000000000000000000000000000000000FF000000000000000000000000000000000000000000
331700000000000000000000000000000000F9000300000000000000000000000000000000000000
662E00000000000000000000000000000000F4000600000000000000000000000000000000000000
994500000000000000000000000000000000EF000900000000000000000000000000000000000000
CC5C0E000000000000000000000000000000EA000C00000000000000000000000000000000000000
FF731C000000000000000000000000000000E5001000000000000000000000000000000000000000
FF8B2A000000000000000000000000000000E0001300000000000000000000000000000000000000
BFA238000000000000000000000000000000DB001600000000000000000000000000000000000000
7FB946000000000000000000000000000000D6001900000000000000000000000000000000000000
3FD055000000000000000000000000000000D1001C00000000000000000000000000000000000000
00E76300000080FF00000000000000000000CB002000000000000000000000000000000000000000
00FF710A00007BF605000000000000000000C6002300000000000000000000000000000000000000
00FF7F15000077EE0A000000000000000000C1002600000000000000000000000000000000000000

The first line is the number of milliseconds for each update period.  Then each line has channel 1 in the first 2 characters, channel 2 in the next 2 characters, and so on.

With the data in a simple format, it was pretty easy to write code with Arduino to read it.  The DmxSimple library did all the heavy lifting for transmitting DMX, so only a RS-485 chip needs to be connected to get DMX output.

The SD library and Arduino 1.0's new readBytesUntil() function makes reading the text file pretty easy.  Just a little code was needed to turn the hex digits back to binary.  I suppose I could have made the Python script output binary, but I felt the text file would be much nicer, so anyone using this project could "see" the data by just double clicking the file.

With 5 PWM output available (DmxSimple uses Timer2, so 2 of the 7 PWM on Teensy2 aren't usable), I added a tiny bit of code to display the first 5 channels on LEDs.  The script can parse up to 256 channels, half of DMX's maximum.  I'm pretty sure that will be plenty of this project.

Here's the Arduino sketch:

 

#include <SD.h>
#include <DmxSimple.h>

const int chipSelect = 0;
char buffer[516];

void setup()
{
  for (int i=0; i<NUM_DIGITAL_PINS; i++) {
    pinMode(i, INPUT_PULLUP);
  }
  analogWrite(4, 0);
  analogWrite(5, 0);
  analogWrite(9, 0);
  analogWrite(15, 0);
  analogWrite(14, 0);
  //Serial.begin(115200);
  DmxSimple.usePin(10);
  DmxSimple.maxChannel(100);
  for (int i=1; i<=100; i++) {
    DmxSimple.write(i, 0);
  }
  // initialize the SD card
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  while (!SD.begin(chipSelect)) {
    delay(250);
  }
  digitalWrite(LED_BUILTIN, LOW);
}

void loop()
{
  play();
  // TODO: would be nice to detect if the SD card is removed
  // and automatically recover, rather than requiring power cycle
}

void play()
{
  File f = SD.open("COREPLAY.TXT");
  if (f) {
    // read the period so we know how fast to play
    long period = f.parseInt();
    //Serial.print("Period is ");
    //Serial.println(period);
    f.readBytesUntil('\n', buffer, sizeof(buffer));
    
    // then read every line and play it
    elapsedMillis msec=0;
    while (f.available()) {
      f.readBytesUntil('\n', buffer, sizeof(buffer));
      //Serial.print("Data: ");
      //Serial.print(buffer);
      int channels = hex2bin(buffer);
      //Serial.print(", ");
      //Serial.print(channels);
      //Serial.println(" channels");
      if (channels > 0) {
        //transmit all the channels with DMX
        DmxSimple.maxChannel(channels);
        for (int i=0; i < channels; i++) {
           DmxSimple.write(i+1, buffer[i]);
        }
        // display the first 5 channels on LEDs
        analogWrite(4, buffer[0]);
        analogWrite(5, buffer[1]);
        analogWrite(9, buffer[2]);
        analogWrite(15, buffer[3]);
        analogWrite(14, buffer[4]);
        // wait for the required period
        while (msec < period) ; // wait
        msec = msec - period;
      }
    }
    f.close();
  }
}


byte hexdigit(char c)
{
  if (c >= '0' && c <= '9') return c - '0';
  if (c >= 'A' && c <= 'F') return c - 'A' + 10;
  return 255;
}


int hex2bin(char *buf)
{
  byte b1, b2;
  int i=0, count=0;
  
  while (1) {
    b1 = hexdigit(buf[i++]);
    if (b1 > 15) break;
    b2 = hexdigit(buf[i++]);
    if (b2 > 15) break;
    buf[count++] = b1 * 16 + b2;
  }
  return count;
}

I'm not actually going to Burning Man this year.  So far, my success rate for building stuff for burners to take and use on the Playa (in my absence) has been pretty low.  Burning Man is a really harsh environment and it's also filled will all sorts of distractions when solving any sort of technical problems.  This year the team has someone who's very good with electronics and he seems comfortable with Python.  Hopefully this little device will be usable and they'll be able to create sequences, convert the file and get it onto the SD card.

EDIT: Here's an image from Jesse doing a test with all the DMX lights.


EDIT: I built a spare board.  They wanted it to be able to play multiple files and randomly choose them, so I added a LCD to show which file is playing.  Hopefully will make troubleshooting easier.

Here's the Python script and revised Arduino code (with randomized multi-file playing and display), in case anyone ever wants to use this controller for their own DMX controlled lighting.

 

AttachmentSize
Vixen2core_python_script.zip973 bytes
CorePlay.zip1.92 KB