├── .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 |
--------------------------------------------------------------------------------