├── .gitignore ├── example-controller ├── addons.make ├── bin │ └── data │ │ ├── curves.yml │ │ └── dmx.xml └── src │ ├── main.cpp │ ├── ofApp.cpp │ └── ofApp.h ├── example ├── addons.make ├── bin │ └── data │ │ └── .gitkeep └── src │ ├── main.cpp │ ├── ofApp.cpp │ └── ofApp.h ├── license.md ├── readme.md └── src ├── ofxDmx.cpp └── ofxDmx.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Some general ignore patterns 2 | 3 | # temp & build files 4 | build/ 5 | obj/ 6 | *.o 7 | Debug*/ 8 | Release*/ 9 | *.mode* 10 | *.app/ 11 | *.pyc 12 | .svn/ 13 | *.exe 14 | *.dll 15 | *.lib 16 | 17 | # IDE-specific ignore patterns (e.g. user-specific files) 18 | 19 | # XCode 20 | *.xcodeproj 21 | project.pbxproj 22 | xcshareddata 23 | xcschemes 24 | Project.xcconfig 25 | config.make 26 | openFrameworks-Info.plist 27 | *.pbxuser 28 | *.perspective 29 | *.perspectivev3 30 | *.mode1v3 31 | *.mode2v3 32 | # XCode 4 33 | xcuserdata 34 | *.xcworkspace 35 | 36 | # Code::Blocks 37 | *.depend 38 | *.layout 39 | *.cbTemp 40 | 41 | # Visual Studio 42 | *.sln 43 | *.vcxproj 44 | *.vcxproj.filters 45 | *.vcxproj.user 46 | icon.rc 47 | *.sdf 48 | *.pdf 49 | *.opensdf 50 | *.suo 51 | ipch/ 52 | *.pdb 53 | *.exp 54 | *.ilk 55 | *.aps 56 | 57 | # Eclipse 58 | .metadata 59 | local.properties 60 | .externalToolBuilders 61 | 62 | # codelite 63 | *.session 64 | *.tags 65 | *.workspace.* 66 | 67 | #Linux/OSX 68 | Makefile 69 | 70 | # OS-specific ignore patterns 71 | 72 | # Linux 73 | *~ 74 | # KDE 75 | .directory 76 | 77 | # OSX 78 | .DS_Store 79 | *.swp 80 | *~.nib 81 | # Thumbnails 82 | ._* 83 | 84 | # Windows 85 | # Windows image file caches 86 | Thumbs.db 87 | # Folder config file 88 | Desktop.ini 89 | 90 | # Android 91 | .csettings 92 | 93 | # Packages 94 | # it's better to unpack these files and commit the raw source 95 | # git has its own built in compression methods 96 | *.7z 97 | *.dmg 98 | *.gz 99 | *.iso 100 | *.jar 101 | *.rar 102 | *.tar 103 | *.zip 104 | 105 | # Logs and databases 106 | *.log 107 | *.sql 108 | *.sqlite 109 | -------------------------------------------------------------------------------- /example-controller/addons.make: -------------------------------------------------------------------------------- 1 | ofxDmx 2 | ofxGui 3 | -------------------------------------------------------------------------------- /example-controller/bin/data/curves.yml: -------------------------------------------------------------------------------- 1 | [[0,0],[63,28],[104,71],[128,132],[255,255]] -------------------------------------------------------------------------------- /example-controller/bin/data/dmx.xml: -------------------------------------------------------------------------------- 1 | tty.usbserial-ENS8KBA6 2 | 10 3 | 4 -------------------------------------------------------------------------------- /example-controller/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | #include "ofAppGlutWindow.h" 3 | 4 | int main() { 5 | ofAppGlutWindow window; 6 | ofSetupOpenGL(&window, 800, 800, OF_WINDOW); 7 | ofRunApp(new ofApp()); 8 | } 9 | -------------------------------------------------------------------------------- /example-controller/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | 3 | void ofApp::setup() { 4 | ofSetVerticalSync(true); 5 | 6 | modules = 6; 7 | 8 | panel.setup(); 9 | panel.setPosition(4, 4); 10 | panel.setName("settings 0"); 11 | panel.add(save.set("save", false)); 12 | panel.add(load.set("load", false)); 13 | 14 | for(int module = 1; module <= modules; module++) { 15 | string label = "mod" + ofToString(module); 16 | panel.add(red[module].set("red" +ofToString(module), 0, 0, 1)); 17 | panel.add(green[module].set("green"+ofToString(module), 0, 0, 1)); 18 | panel.add(blue[module].set("blue"+ofToString(module), 0, 0, 1)); 19 | 20 | //panel.add(moduleNum[module].set(label, false)); 21 | } 22 | 23 | load = true; 24 | 25 | dmx.connect(port, modules * channelsPerModule); 26 | dmx.update(true); // black on startup 27 | } 28 | 29 | void ofApp::exit() { 30 | dmx.clear(); 31 | dmx.update(true); // black on shutdown 32 | } 33 | 34 | void ofApp::update() { 35 | if(save) { 36 | panel.saveToFile("settings.xml"); 37 | save = false; 38 | } 39 | if(load) { 40 | if(ofFile::doesFileExist(ofToDataPath("settings.xml"))) { 41 | panel.loadFromFile("settings.xml"); 42 | } 43 | load = false; 44 | } 45 | 46 | int channel = 1; 47 | for(int module = 1; module <= modules; module++) { 48 | dmx.setLevel(channel++, red[module]*255); 49 | dmx.setLevel(channel++, green[module]*255); 50 | dmx.setLevel(channel++, blue[module]*255); 51 | channel++; 52 | } 53 | if(dmx.isConnected()) { 54 | dmx.update(); 55 | } else { 56 | ofSetColor(255); 57 | ofDrawBitmapString("Could not connect to port " + ofToString(port), 250,20); 58 | } 59 | } 60 | 61 | void ofApp::draw() { 62 | ofBackground(0); 63 | ofPushMatrix(); 64 | 65 | ofTranslate(256, 0); 66 | int channel = 1; 67 | for(int module = 1; module <= modules; module++) { 68 | string label = "module " + ofToString(module); 69 | int rc = channel++; 70 | int gc = channel++; 71 | int bc = channel++; 72 | int r = dmx.getLevel(rc); 73 | int g = dmx.getLevel(gc); 74 | int b = dmx.getLevel(bc); 75 | ofSetColor(r, g, b); 76 | ofFill(); 77 | ofDrawRectangle(4, module * 16 + 6, 14, 14); 78 | ofSetColor(255); 79 | ofNoFill(); 80 | ofDrawRectangle(4, module * 16 + 6, 14, 14); 81 | string rs = ofToString(rc) + ":" + ofToString(r); 82 | string gs = ofToString(gc) + ":" + ofToString(g); 83 | string bs = ofToString(bc) + ":" + ofToString(b); 84 | string text = label + " (" + rs + ", " + gs + ", " + bs + ")"; 85 | ofDrawBitmapString(text, 24, module * 16 + 16); 86 | } 87 | 88 | ofPopMatrix(); 89 | 90 | panel.draw(); 91 | } 92 | -------------------------------------------------------------------------------- /example-controller/src/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | 5 | #include "ofxDmx.h" 6 | #include "ofxGui.h" 7 | 8 | class ofApp : public ofBaseApp { 9 | public: 10 | void setup(); 11 | void exit(); 12 | void update(); 13 | void draw(); 14 | 15 | ofxDmx dmx; 16 | ofxPanel panel; 17 | string port; 18 | int modules, channelsPerModule; 19 | ofParameter moduleNum[11]; 20 | 21 | ofParameter red[11], green[11], blue[11]; 22 | ofParameter load, save; 23 | }; 24 | -------------------------------------------------------------------------------- /example/addons.make: -------------------------------------------------------------------------------- 1 | ofxDmx 2 | ofxGui 3 | -------------------------------------------------------------------------------- /example/bin/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kylemcdonald/ofxDmx/fc1397507c7c3f9ac4cddf99c3275f68077b0557/example/bin/data/.gitkeep -------------------------------------------------------------------------------- /example/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | #include "ofAppGlutWindow.h" 3 | 4 | int main() { 5 | ofAppGlutWindow window; 6 | ofSetupOpenGL(&window, 512, 256, OF_WINDOW); 7 | ofRunApp(new ofApp()); 8 | } 9 | -------------------------------------------------------------------------------- /example/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | 3 | void ofApp::setup() { 4 | 5 | dmx.connect("tty.usbserial-EN143965"); // use the name 6 | //dmx.connect(0); // or use a number 7 | 8 | chan1.set("Channel 1", 120, 0, 255); 9 | chan2.set("Channel 2", 120, 0, 255); 10 | chan3.set("Channel 3", 120, 0, 255); 11 | autoCycle.set("AutoCycle", false); 12 | panel.setName("Panel"); 13 | panel.setup(); 14 | panel.add(chan1); 15 | panel.add(chan2); 16 | panel.add(chan3); 17 | panel.add(autoCycle); 18 | } 19 | 20 | void ofApp::update() { 21 | 22 | // use the time to generate a level 23 | if (autoCycle) { 24 | chan1 = 127 + 127 * sin(2 * ofGetElapsedTimef()); 25 | chan2 = 127 + 127 * sin(-2 * ofGetElapsedTimef()); 26 | chan3 = 127 + 127 * sin(1.5 * ofGetElapsedTimef()); 27 | } 28 | 29 | dmx.setLevel(1, chan1); 30 | dmx.setLevel(2, chan2); 31 | dmx.setLevel(3, chan3); 32 | dmx.update(); 33 | } 34 | 35 | void ofApp::draw() { 36 | 37 | ofSetColor(chan1); 38 | ofDrawRectangle(0, 0, ofGetWidth() / 3, ofGetHeight()); 39 | 40 | ofSetColor(chan2); 41 | ofDrawRectangle(ofGetWidth() / 3, 0, ofGetWidth() / 3, ofGetHeight()); 42 | 43 | ofSetColor(chan3); 44 | ofDrawRectangle(2*(ofGetWidth() / 3), 0, ofGetWidth() / 3, ofGetHeight()); 45 | 46 | panel.draw(); 47 | } 48 | -------------------------------------------------------------------------------- /example/src/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | #include "ofxDmx.h" 5 | #include "ofxGui.h" 6 | 7 | class ofApp : public ofBaseApp { 8 | public: 9 | void setup(); 10 | void update(); 11 | void draw(); 12 | 13 | ofxDmx dmx; 14 | int level; 15 | 16 | ofxPanel panel; 17 | ofParameter chan1; 18 | ofParameter chan2; 19 | ofParameter chan3; 20 | ofParameter autoCycle; 21 | }; 22 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | ofxDmx is available under the MIT License. 2 | 3 | https://secure.wikimedia.org/wikipedia/en/wiki/Mit_license 4 | 5 | - - -- 6 | 7 | Copyright (c) 2007-2008 Erik Sjodin, eriksjodin.net 8 | Copyright (c) 2008- ofxDmx developers 9 | 10 | 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: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | 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. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ofxDmx wraps DMX over serial control for [openFrameworks](http://openframeworks.cc/) 2 | 3 | ofxDmx is based on DmxPro from Erik Sjodin, later adapted by Marek Bereza. 4 | 5 | ofxDmx is targeted at the [Enttec DmxPro](http://www.enttec.com/index.php?main_menu=Products&prod=70304&show=description) running the virtual serial port drivers [available from FTDI](http://www.ftdichip.com/Drivers/VCP.htm). 6 | 7 | ## About Channel Numbering 8 | 9 | DMX is an unusual specification in that channels begin at `1`. There is no `0` channel. This means that if you have 24 channels, they will be numbered `1, 2, 3... 24`. They will not be numbered `0,1,2... 23`. Because of this, you have to more careful when you write for loops. This is correct: 10 | 11 | int total = 16; 12 | dmx.connect("tty.usbserial-ENS8KBA6", total); 13 | for(int channel = 1; channel <= total; channel++) { 14 | dmx.setLevel(channel, ...); 15 | } 16 | 17 | Notice that the for loop starts at `1`, and the stopping condition is `channel <= total` rather than the usual `<` operator. 18 | 19 | 20 | ## Enttec USB PRO Mk2 21 | 22 | To run the 2 DMX universes of the [Enttec USB PRO Mk2](http://www.enttec.com/?main_menu=Products&pn=70314&show=description) you need to first activate the second universe with `dmx.activateMk2()` and then specify which universe you address with `dmx.setLevel(channel, value, universe)`. 23 | 24 | 25 | ## Running the Example Projects 26 | 27 | Project files for the examples are not included so you will need to generate the project files for your operating system and development environment using the OF ProjectGenerator which is included with the OpenFrameworks distribution. 28 | 29 | To (re)generate project files for an *existing* project: 30 | 31 | * click the "Import" button in the ProjectGenerator 32 | * navigate the to base folder for the project ie. "example" 33 | * click the "Update" button 34 | 35 | If everything went Ok, you should now be able to open the generated project and build/run the example. 36 | 37 | ## Dev Notes 38 | 39 | Development Notes for updating to include the Enttec OpenDMX module 40 | 41 | http://www.enttec.com/download/examples/OpenDMX.cs 42 | -------------------------------------------------------------------------------- /src/ofxDmx.cpp: -------------------------------------------------------------------------------- 1 | #include "ofxDmx.h" 2 | #include "ofMain.h" 3 | 4 | #define DMX_PRO_HEADER_SIZE 4 5 | #define DMX_PRO_START_MSG 0x7E 6 | #define DMX_START_CODE 0 7 | #define DMX_START_CODE_SIZE 1 8 | #define DMX_PRO_SEND_PACKET 6 // "periodically send a DMX packet" mode 9 | #define DMX_PRO_END_SIZE 1 10 | #define DMX_PRO_END_MSG 0xE7 11 | 12 | // Enttec DMX USB PRO MK2 specific 13 | #define DMX_PRO_SEND_PACKET2 0xA9 // send to universe #2 14 | #define ENTTEC_PRO_ENABLE_API2 0x0D 15 | #define ENTTEC_PRO_PORT_ASS_REQ 0xCB 16 | 17 | ofxDmx::ofxDmx() 18 | :connected(false) 19 | ,needsUpdate(false) { 20 | universes = 1; 21 | } 22 | 23 | ofxDmx::~ofxDmx() { 24 | serial.close(); 25 | connected = false; 26 | } 27 | 28 | bool ofxDmx::connect(int device, unsigned int channels) { 29 | serial.listDevices(); 30 | connected = serial.setup(device, 57600); 31 | setChannels(channels); 32 | return connected; 33 | } 34 | 35 | bool ofxDmx::connect(string device, unsigned int channels) { 36 | serial.listDevices(); 37 | connected = serial.setup(device.c_str(), 57600); 38 | setChannels(channels); 39 | return connected; 40 | } 41 | 42 | bool ofxDmx::isConnected() { 43 | return connected; 44 | } 45 | 46 | void ofxDmx::disconnect() { 47 | serial.close(); 48 | connected = false; 49 | } 50 | 51 | void ofxDmx::setChannels(unsigned int channels) { 52 | levels.resize(ofClamp(channels, 24, 512)); 53 | levels2.resize(ofClamp(channels, 24, 512)); 54 | } 55 | 56 | /* 57 | You can use your own API key, but don't have to! 58 | 59 | ATTENTION: this won't throw an error, if no MK2 is plugged in. 60 | You'll just have to wait and see if your 2nd universe lights 61 | turn on to know if the activation worked. Or watch out for: 62 | LED on device blinks RED if 2nd universe setLevel msg was rejected. 63 | */ 64 | void ofxDmx::activateMk2(unsigned char key0, unsigned char key1, unsigned char key2, unsigned char key3) { 65 | 66 | // step 1: set API -key 67 | unsigned int dataSize = 4; 68 | unsigned int packetSize = DMX_PRO_HEADER_SIZE + dataSize + DMX_PRO_END_SIZE; 69 | vector packet(packetSize); 70 | 71 | // header 72 | packet[0] = DMX_PRO_START_MSG; 73 | packet[1] = ENTTEC_PRO_ENABLE_API2; 74 | packet[2] = dataSize & 0xff; // data length lsb 75 | packet[3] = (dataSize >> 8) & 0xff; // data length msb 76 | 77 | // data = API key, LSB at lowest address 78 | packet[4] = key3; 79 | packet[5] = key2; 80 | packet[6] = key1; 81 | packet[7] = key0; 82 | 83 | // end 84 | packet[packetSize - 1] = DMX_PRO_END_MSG; 85 | 86 | serial.writeBytes(&packet[0], packetSize); 87 | 88 | ofSleepMillis(200); 89 | cout << "MK2: activated with API key" << endl; 90 | 91 | 92 | 93 | 94 | // step 2, enable both ports 95 | dataSize = 2; 96 | packetSize = DMX_PRO_HEADER_SIZE + dataSize + DMX_PRO_END_SIZE; 97 | vector packet2(packetSize); 98 | 99 | // header 100 | packet2[0] = DMX_PRO_START_MSG; 101 | packet2[1] = ENTTEC_PRO_PORT_ASS_REQ; 102 | packet2[2] = dataSize & 0xff; // data length lsb 103 | packet2[3] = (dataSize >> 8) & 0xff; // data length msb 104 | 105 | // data 106 | packet2[4] = 1; 107 | packet2[5] = 1; 108 | 109 | // end 110 | packet2[packetSize - 1] = DMX_PRO_END_MSG; 111 | 112 | serial.writeBytes(&packet2[0], packetSize); 113 | 114 | ofSleepMillis(200); 115 | cout << "MK2: enabled both DMX ports" << endl; 116 | 117 | universes = 2; 118 | 119 | } 120 | 121 | void ofxDmx::update(bool force) { 122 | if(needsUpdate || force) { 123 | needsUpdate = false; 124 | 125 | for (unsigned int i=0; i packet(packetSize); 130 | 131 | // header 132 | packet[0] = DMX_PRO_START_MSG; 133 | if (i==0) packet[1] = DMX_PRO_SEND_PACKET; 134 | else packet[1] = DMX_PRO_SEND_PACKET2; 135 | packet[2] = dataSize & 0xff; // data length lsb 136 | packet[3] = (dataSize >> 8) & 0xff; // data length msb 137 | 138 | // data 139 | packet[4] = DMX_START_CODE; // first data byte 140 | if (i==0) copy(levels.begin(), levels.end(), packet.begin() + 5); 141 | else copy(levels2.begin(), levels2.end(), packet.begin() + 5); 142 | 143 | // end 144 | packet[packetSize - 1] = DMX_PRO_END_MSG; 145 | 146 | serial.writeBytes(&packet[0], packetSize); 147 | } 148 | 149 | #ifdef OFXDMX_SPEW 150 | cout << "@" << ofGetSystemTime() << endl; 151 | for(int i = 0; i < packetSize; i++) { 152 | cout << setw(2) << hex << (int) packet[i]; 153 | if((i + 1) % 8 == 0) { 154 | cout << endl; 155 | } 156 | } 157 | #endif 158 | } 159 | } 160 | 161 | bool ofxDmx::badChannel(unsigned int channel) { 162 | if(channel > levels.size()) { 163 | ofLogError() << "Channel " + ofToString(channel) + " is out of bounds. Only " + ofToString(levels.size()) + " channels are available."; 164 | return true; 165 | } 166 | if(channel == 0) { 167 | ofLogError() << "Channel 0 does not exist. DMX channels start at 1."; 168 | return true; 169 | } 170 | return false; 171 | } 172 | 173 | void ofxDmx::setLevel(unsigned int channel, unsigned char level, unsigned int universe) { 174 | if(badChannel(channel)) { 175 | return; 176 | } 177 | channel--; // convert from 1-initial to 0-initial 178 | if (universe == 1) { 179 | if(level != levels[channel]) { 180 | levels[channel] = level; 181 | needsUpdate = true; 182 | } 183 | } else if (universe == 2) { 184 | if(level != levels2[channel]) { 185 | levels2[channel] = level; 186 | needsUpdate = true; 187 | } 188 | } 189 | } 190 | 191 | void ofxDmx::clear() { 192 | for (int i = 0; i < levels.size(); i++) { 193 | levels[i] = 0; 194 | } 195 | for (int i = 0; i < levels2.size(); i++) { 196 | levels2[i] = 0; 197 | } 198 | } 199 | 200 | unsigned char ofxDmx::getLevel(unsigned int channel) { 201 | if(badChannel(channel)) { 202 | return 0; 203 | } 204 | channel--; // convert from 1-initial to 0-initial 205 | return levels[channel]; 206 | } 207 | -------------------------------------------------------------------------------- /src/ofxDmx.h: -------------------------------------------------------------------------------- 1 | // TODO: support input, and perhaps other devices. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | class ofxDmx { 8 | public: 9 | ofxDmx(); 10 | ~ofxDmx(); 11 | 12 | // connect to the serial port. valid number of channels is 24-512. performance 13 | // is directly related to the number of channels, so use the minimum required. 14 | bool connect(int device = 0, unsigned int channels = 24); 15 | bool connect(string device, unsigned int channels = 24); 16 | void activateMk2(unsigned char key0 = 0xC8, unsigned char key1 = 0xD0, unsigned char key2 = 0x88, unsigned char key3 = 0xAD); 17 | void disconnect(); 18 | 19 | void setLevel(unsigned int channel, unsigned char level, unsigned int universe = 1); 20 | void clear(); 21 | unsigned char getLevel(unsigned int channel); 22 | void update(bool force = false); // send a packet to the dmx controller 23 | 24 | void setChannels(unsigned int channels = 24); // change the number of channels 25 | bool isConnected(); 26 | 27 | private: 28 | int connected; 29 | int universes; 30 | vector levels; 31 | vector levels2; // 2nd universe, only for MK2 32 | ofSerial serial; 33 | bool needsUpdate; 34 | 35 | bool badChannel(unsigned int channel); 36 | }; 37 | --------------------------------------------------------------------------------