├── .gitignore ├── LICENSE ├── README.md ├── box_1 ├── Box 1.png ├── Hookup_Diagram.png ├── box_1_assembly_instructions.pdf ├── box_1_src │ └── box_1_src.ino ├── faceplate_model.STL ├── faceplate_model_blank.STL └── faceplate_template.pdf ├── test_tools └── usb_test │ ├── README.md │ ├── handshake.png │ ├── ping.png │ └── usb_test_sketch │ └── usb_test_sketch.ino └── user_projects └── nunchuk ├── README.md └── nunchuk_src ├── WiiLib.h └── nunchuk_src.ino /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Electronic Theatre Controls, Inc., http://www.etcconnect.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # #lighthack 2 | This repository contains the source and documentation for **#lighthack**, an educational project designed by ETC users and employees to engage with the DIY community. 3 | 4 | #lighthack is a community-supported initiative, and your fellow #lighthackers are your first resource for help! 5 | 6 | The #lighthack project is centered around Arduino-based hardware devices that users assemble from scratch. The Arduino connects via a USB COM port and then communicates via OSC with ETC's Eos software. 7 | 8 | Eos supports this functionality on consoles running Windows 7 embedded or higher and ETCnomad for Windows in software version 2.6.1 and newer, and on ETCnomad for Mac in software version 2.7.0 and newer. 9 | 10 | _Support for XPe-based consoles or XP computers is not planned for this project. If you do not know the version of embedded Windows your console uses, [check this link](https://support.etcconnect.com/ETC/Consoles/General/What_Version_of_Windows_is_my_Console_Running) for identifying information._ 11 | 12 | ## test tools/usb_test 13 | This is the first Arduino sketch to load - it helps to ensure that the Arduino is communicating successfully with Eos. This is also a good point to return to if you want to re-confirm the Arduino-Eos connection later on. 14 | 15 | ## #lighthack box 1 16 | **#lighthack box 1** is a kit project with two encoders, three buttons and a 2x20 display. This kit is available for purchase directly from ETC's [US](https://shop.etcconnect.com/) and [EU](https://shop.etcconnect.eu/) online shops, but you can also find all of the parts directly from suppliers online. This project is located in the box_1 subfolder. This folder contains an assembly instructions document which also contains the parts list. It also contains the Arduino sketch and other supporting documentation. 17 | 18 | [Supported OSC Commands](https://github.com/ETCLabs/EosSyncLib/blob/master/Supported%20OSC%20Commands.pdf) 19 | 20 | ## About this ETCLabs Project 21 | #lighthack projects are designed to interact with ETC products, but they are not official ETC hardware or software. For challenges using, integrating, compiling, or modifying items in this project, we encourage posting on the [Issues page](https://github.com/ETCLabs/lighthack/issues) or on the [#lighthack forum](https://community.etcconnect.com/etclabs/f/lighthack). Again, #lighthack is a community-supported initiative, and the #lighthacking community is the best place to ask for help! 22 | 23 | ## Dependencies 24 | Requires the Arduino OSC parsing library maintained by the great folks over at [CNMAT](https://github.com/CNMAT/OSC). The assembly instructions documents contain details on how to add this to your Arduino project. 25 | 26 | -------------------------------------------------------------------------------- /box_1/Box 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/Box 1.png -------------------------------------------------------------------------------- /box_1/Hookup_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/Hookup_Diagram.png -------------------------------------------------------------------------------- /box_1/box_1_assembly_instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/box_1_assembly_instructions.pdf -------------------------------------------------------------------------------- /box_1/box_1_src/box_1_src.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Electronic Theatre Controls, Inc., http://www.etcconnect.com 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | 22 | /******************************************************************************* 23 | 24 | Electronic Theatre Controls 25 | 26 | lighthack - Box 1 27 | 28 | (C) 2017-2018 by ETC 29 | 30 | 31 | This code implements a Pan/Tilt module using two encoders and three 32 | buttons. The two encoders function as pan and tilt controllers with one 33 | button being reserved for controlling fine/coarse mode. The other two 34 | buttons are assigned to Next and Last which make it easy to switch between 35 | channels. 36 | 37 | ******************************************************************************* 38 | 39 | NOTE: UPDATE VERSION_STRING IN DEFINITIONS BELOW WHEN VERSION NUMBER CHANGES 40 | 41 | Revision History 42 | 43 | yyyy-mm-dd Vxx By_Who Comment 44 | 45 | 2017-07-21 1.0.0.1 Ethan Oswald Massey Original creation 46 | 47 | 2017-10-19 1.0.0.2 Sam Kearney Fix build errors on some 48 | Arduino platforms. Change 49 | OSC subscribe parameters 50 | 51 | 2017-10-24 1.0.0.3 Sam Kearney Add ability to scale encoder 52 | output 53 | 54 | 2017-11-22 1.0.0.4 Hans Hinrichsen Add splash msg before Eos 55 | connects 56 | 57 | 2017-12-07 1.0.0.5 Hans Hinrichsen Added timeout to disconnect 58 | and show splash screen again 59 | 60 | 2018-10-25 2.0.0.0 Richard Thompson Generalised to support other 61 | ETC consoles 62 | 63 | 2018-10-25 2.0.0.1 Richard Thompson Add basic support for ColorSource 64 | 65 | ******************************************************************************/ 66 | 67 | /******************************************************************************* 68 | Includes 69 | ******************************************************************************/ 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #ifdef BOARD_HAS_USB_SERIAL 77 | #include 78 | SLIPEncodedUSBSerial SLIPSerial(thisBoardsSerialUSB); 79 | #else 80 | #include 81 | SLIPEncodedSerial SLIPSerial(Serial); 82 | #endif 83 | #include 84 | #include 85 | 86 | /******************************************************************************* 87 | Macros and Constants 88 | ******************************************************************************/ 89 | #define LCD_CHARS 16 90 | #define LCD_LINES 2 // Currently assume at least 2 lines 91 | 92 | #define NEXT_BTN 6 93 | #define LAST_BTN 7 94 | #define SHIFT_BTN 8 95 | 96 | #define SUBSCRIBE ((int32_t)1) 97 | #define UNSUBSCRIBE ((int32_t)0) 98 | 99 | #define EDGE_DOWN ((int32_t)1) 100 | #define EDGE_UP ((int32_t)0) 101 | 102 | #define FORWARD 0 103 | #define REVERSE 1 104 | 105 | // Change these values to switch which direction increase/decrease pan/tilt 106 | #define PAN_DIR FORWARD 107 | #define TILT_DIR FORWARD 108 | 109 | // Use these values to make the encoder more coarse or fine. 110 | // This controls the number of wheel "ticks" the device sends to the console 111 | // for each tick of the encoder. 1 is the default and the most fine setting. 112 | // Must be an integer. 113 | #define PAN_SCALE 1 114 | #define TILT_SCALE 1 115 | 116 | #define SIG_DIGITS 3 // Number of significant digits displayed 117 | 118 | #define OSC_BUF_MAX_SIZE 512 119 | 120 | const String HANDSHAKE_QUERY = "ETCOSC?"; 121 | const String HANDSHAKE_REPLY = "OK"; 122 | 123 | //See displayScreen() below - limited to 10 chars (after 6 prefix chars) 124 | #define VERSION_STRING "2.0.0.1" 125 | 126 | #define BOX_NAME_STRING "box1" 127 | 128 | // Change these values to alter how long we wait before sending an OSC ping 129 | // to see if Eos is still there, and then finally how long before we 130 | // disconnect and show the splash screen 131 | // Values are in milliseconds 132 | #define PING_AFTER_IDLE_INTERVAL 2500 133 | #define TIMEOUT_AFTER_IDLE_INTERVAL 5000 134 | 135 | /******************************************************************************* 136 | Local Types 137 | ******************************************************************************/ 138 | enum WHEEL_TYPE { TILT, PAN }; 139 | enum WHEEL_MODE { COARSE, FINE }; 140 | 141 | struct Encoder 142 | { 143 | uint8_t pinA; 144 | uint8_t pinB; 145 | int pinAPrevious; 146 | int pinBPrevious; 147 | float pos; 148 | uint8_t direction; 149 | }; 150 | struct Encoder panWheel; 151 | struct Encoder tiltWheel; 152 | 153 | enum ConsoleType 154 | { 155 | ConsoleNone, 156 | ConsoleEos, 157 | ConsoleCobalt, 158 | ConsoleColorSource 159 | }; 160 | 161 | /******************************************************************************* 162 | Global Variables 163 | ******************************************************************************/ 164 | 165 | // initialize the library with the numbers of the interface pins 166 | LiquidCrystal lcd(12, 11, 5, 4, 3, 2); 167 | 168 | bool updateDisplay = false; 169 | ConsoleType connectedToConsole = ConsoleNone; 170 | unsigned long lastMessageRxTime = 0; 171 | bool timeoutPingSent = false; 172 | 173 | /******************************************************************************* 174 | Local Functions 175 | ******************************************************************************/ 176 | 177 | /******************************************************************************* 178 | Issues all our subscribes to Eos. When subscribed, Eos will keep us updated 179 | with the latest values for a given parameter. 180 | 181 | Parameters: none 182 | 183 | Return Value: void 184 | 185 | ******************************************************************************/ 186 | void issueEosSubscribes() 187 | { 188 | // Add a filter so we don't get spammed with unwanted OSC messages from Eos 189 | OSCMessage filter("/eos/filter/add"); 190 | filter.add("/eos/out/param/*"); 191 | filter.add("/eos/out/ping"); 192 | SLIPSerial.beginPacket(); 193 | filter.send(SLIPSerial); 194 | SLIPSerial.endPacket(); 195 | 196 | // subscribe to Eos pan & tilt updates 197 | OSCMessage subPan("/eos/subscribe/param/pan"); 198 | subPan.add(SUBSCRIBE); 199 | SLIPSerial.beginPacket(); 200 | subPan.send(SLIPSerial); 201 | SLIPSerial.endPacket(); 202 | 203 | OSCMessage subTilt("/eos/subscribe/param/tilt"); 204 | subTilt.add(SUBSCRIBE); 205 | SLIPSerial.beginPacket(); 206 | subTilt.send(SLIPSerial); 207 | SLIPSerial.endPacket(); 208 | } 209 | 210 | /******************************************************************************* 211 | Given a valid OSCMessage (relevant to Pan/Tilt), we update our Encoder struct 212 | with the new position information. 213 | 214 | Parameters: 215 | msg - The OSC message we will use to update our internal data 216 | addressOffset - Unused (allows for multiple nested roots) 217 | 218 | Return Value: void 219 | 220 | ******************************************************************************/ 221 | void parseFloatPanUpdate(OSCMessage& msg, int addressOffset) 222 | { 223 | panWheel.pos = msg.getOSCData(0)->getFloat(); 224 | updateDisplay = true; 225 | } 226 | 227 | void parseFloatTiltUpdate(OSCMessage& msg, int addressOffset) 228 | { 229 | tiltWheel.pos = msg.getOSCData(0)->getFloat(); 230 | updateDisplay = true; 231 | } 232 | 233 | void parseEos(OSCMessage& msg, int addressOffset) 234 | { 235 | // If we don't think we're connected, reconnect and subscribe 236 | if (connectedToConsole != ConsoleEos) 237 | { 238 | issueEosSubscribes(); 239 | connectedToConsole = ConsoleEos; 240 | updateDisplay = true; 241 | } 242 | 243 | if (!msg.route("/out/param/pan", parseFloatPanUpdate, addressOffset)) 244 | msg.route("/out/param/tilt", parseFloatTiltUpdate, addressOffset); 245 | } 246 | 247 | /******************************************************************************/ 248 | 249 | void parseCobalt(OSCMessage& msg, int addressOffset) 250 | { 251 | // Cobalt doesn't currently send anything other than ping 252 | connectedToConsole = ConsoleCobalt; 253 | updateDisplay = true; 254 | } 255 | 256 | void parseColorSource(OSCMessage& msg, int addressOffset) 257 | { 258 | // ColorSource doesn't currently send anything other than ping 259 | connectedToConsole = ConsoleColorSource; 260 | updateDisplay = true; 261 | } 262 | 263 | /******************************************************************************* 264 | Given an unknown OSC message we check to see if it's a handshake message. 265 | If it's a handshake we issue a subscribe, otherwise we begin route the OSC 266 | message to the appropriate function. 267 | 268 | Parameters: 269 | msg - The OSC message of unknown importance 270 | 271 | Return Value: void 272 | 273 | ******************************************************************************/ 274 | void parseOSCMessage(String& msg) 275 | { 276 | // check to see if this is the handshake string 277 | if (msg.indexOf(HANDSHAKE_QUERY) != -1) 278 | { 279 | // handshake string found! 280 | SLIPSerial.beginPacket(); 281 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 282 | SLIPSerial.endPacket(); 283 | 284 | // An Eos would do nothing until subscribed 285 | // Let Eos know we want updates on some things 286 | issueEosSubscribes(); 287 | 288 | updateDisplay = true; 289 | } 290 | else 291 | { 292 | // prepare the message for routing by filling an OSCMessage object with our message string 293 | OSCMessage oscmsg; 294 | oscmsg.fill((uint8_t*)msg.c_str(), (int)msg.length()); 295 | // route pan/tilt messages to the relevant update function 296 | 297 | // Try the various OSC routes 298 | if (oscmsg.route("/eos", parseEos)) 299 | return; 300 | if (oscmsg.route("/cobalt", parseCobalt)) 301 | return; 302 | if (oscmsg.route("/cs", parseColorSource)) 303 | return; 304 | } 305 | } 306 | 307 | /******************************************************************************* 308 | Updates the display with the latest pan and tilt positions. 309 | 310 | Parameters: none 311 | 312 | Return Value: void 313 | 314 | ******************************************************************************/ 315 | void displayStatus() 316 | { 317 | lcd.clear(); 318 | 319 | switch (connectedToConsole) 320 | { 321 | case ConsoleNone: 322 | { 323 | // display a splash message before the Eos connection is open 324 | lcd.setCursor(0, 0); 325 | lcd.print(BOX_NAME_STRING " v" VERSION_STRING); 326 | lcd.setCursor(0, 1); 327 | lcd.print("Waiting..."); 328 | } break; 329 | 330 | case ConsoleEos: 331 | { 332 | // put the cursor at the begining of the first line 333 | lcd.setCursor(0, 0); 334 | lcd.print("Pan: "); 335 | lcd.print(panWheel.pos, SIG_DIGITS); 336 | 337 | // put the cursor at the begining of the second line 338 | lcd.setCursor(0, 1); 339 | lcd.print("Tilt: "); 340 | lcd.print(tiltWheel.pos, SIG_DIGITS); 341 | } break; 342 | 343 | case ConsoleCobalt: 344 | { 345 | lcd.setCursor(7, 0); 346 | lcd.print("Cobalt"); 347 | lcd.setCursor(0, 1); 348 | lcd.print("Pan"); 349 | lcd.setCursor(12, 1); 350 | lcd.print("Tilt"); 351 | } break; 352 | 353 | case ConsoleColorSource: 354 | { 355 | lcd.setCursor(2, 0); 356 | lcd.print("ColorSource"); 357 | lcd.setCursor(0, 1); 358 | lcd.print("Pan"); 359 | lcd.setCursor(12, 1); 360 | lcd.print("Tilt"); 361 | } break; 362 | 363 | } 364 | 365 | updateDisplay = false; 366 | } 367 | 368 | /******************************************************************************* 369 | Initializes a given encoder struct to the requested parameters. 370 | 371 | Parameters: 372 | encoder - Pointer to the encoder we will be initializing 373 | pinA - Where the A pin is connected to the Arduino 374 | pinB - Where the B pin is connected to the Arduino 375 | direction - Determines if clockwise or counterclockwise is "forward" 376 | 377 | Return Value: void 378 | 379 | ******************************************************************************/ 380 | void initEncoder(struct Encoder* encoder, uint8_t pinA, uint8_t pinB, uint8_t direction) 381 | { 382 | encoder->pinA = pinA; 383 | encoder->pinB = pinB; 384 | encoder->pos = 0; 385 | encoder->direction = direction; 386 | 387 | pinMode(pinA, INPUT_PULLUP); 388 | pinMode(pinB, INPUT_PULLUP); 389 | 390 | encoder->pinAPrevious = digitalRead(pinA); 391 | encoder->pinBPrevious = digitalRead(pinB); 392 | } 393 | 394 | /******************************************************************************* 395 | Checks if the encoder has moved by comparing the previous state of the pins 396 | with the current state. If they are different, we know there is movement. 397 | In the event of movement we update the current state of our pins. 398 | 399 | Parameters: 400 | encoder - Pointer to the encoder we will be checking for motion 401 | 402 | Return Value: 403 | encoderMotion - Returns the 0 if the encoder has not moved 404 | 1 for forward motion 405 | -1 for reverse motion 406 | 407 | ******************************************************************************/ 408 | int8_t updateEncoder(struct Encoder* encoder) 409 | { 410 | int8_t encoderMotion = 0; 411 | int pinACurrent = digitalRead(encoder->pinA); 412 | int pinBCurrent = digitalRead(encoder->pinB); 413 | 414 | // has the encoder moved at all? 415 | if (encoder->pinAPrevious != pinACurrent) 416 | { 417 | // Since it has moved, we must determine if the encoder has moved forwards or backwards 418 | encoderMotion = (encoder->pinAPrevious == encoder->pinBPrevious) ? -1 : 1; 419 | 420 | // If we are in reverse mode, flip the direction of the encoder motion 421 | if (encoder->direction == REVERSE) 422 | encoderMotion = -encoderMotion; 423 | } 424 | encoder->pinAPrevious = pinACurrent; 425 | encoder->pinBPrevious = pinBCurrent; 426 | 427 | return encoderMotion; 428 | } 429 | 430 | /******************************************************************************* 431 | Sends a message to Eos informing them of a wheel movement. 432 | 433 | Parameters: 434 | type - the type of wheel that's moving (i.e. pan or tilt) 435 | ticks - the direction and intensity of the movement 436 | 437 | Return Value: void 438 | 439 | ******************************************************************************/ 440 | 441 | void sendOscMessage(const String &address, float value) 442 | { 443 | OSCMessage msg(address.c_str()); 444 | msg.add(value); 445 | SLIPSerial.beginPacket(); 446 | msg.send(SLIPSerial); 447 | SLIPSerial.endPacket(); 448 | } 449 | 450 | void sendEosWheelMove(WHEEL_TYPE type, float ticks) 451 | { 452 | String wheelMsg("/eos/wheel"); 453 | 454 | if (digitalRead(SHIFT_BTN) == LOW) 455 | wheelMsg.concat("/fine"); 456 | else 457 | wheelMsg.concat("/coarse"); 458 | 459 | if (type == PAN) 460 | wheelMsg.concat("/pan"); 461 | else if (type == TILT) 462 | wheelMsg.concat("/tilt"); 463 | else 464 | // something has gone very wrong 465 | return; 466 | 467 | sendOscMessage(wheelMsg, ticks); 468 | } 469 | 470 | void sendCobaltWheelMove(WHEEL_TYPE type, float ticks) 471 | { 472 | String wheelMsg("/cobalt/param"); 473 | 474 | if (type == PAN) 475 | wheelMsg.concat("/pan/wheel"); 476 | else if (type == TILT) 477 | wheelMsg.concat("/tilt/wheel"); 478 | else 479 | // something has gone very wrong 480 | return; 481 | 482 | if (digitalRead(SHIFT_BTN) != LOW) 483 | ticks = ticks * 16; 484 | 485 | sendOscMessage(wheelMsg, ticks); 486 | } 487 | 488 | void sendColorSourceWheelMove(WHEEL_TYPE type, float ticks) 489 | { 490 | String wheelMsg("/cs/param"); 491 | 492 | if (type == PAN) 493 | wheelMsg.concat("/pan/wheel"); 494 | else if (type == TILT) 495 | wheelMsg.concat("/tilt/wheel"); 496 | else 497 | // something has gone very wrong 498 | return; 499 | 500 | if (digitalRead(SHIFT_BTN) != LOW) 501 | ticks = ticks * 2; 502 | 503 | sendOscMessage(wheelMsg, ticks); 504 | } 505 | 506 | /******************************************************************************/ 507 | 508 | void sendWheelMove(WHEEL_TYPE type, float ticks) 509 | { 510 | switch (connectedToConsole) 511 | { 512 | default: 513 | case ConsoleEos: 514 | sendEosWheelMove(type, ticks); 515 | break; 516 | case ConsoleCobalt: 517 | sendCobaltWheelMove(type, ticks); 518 | break; 519 | case ConsoleColorSource: 520 | sendColorSourceWheelMove(type, ticks); 521 | break; 522 | } 523 | } 524 | 525 | /******************************************************************************* 526 | Sends a message to the console informing them of a key press. 527 | 528 | Parameters: 529 | down - whether a key has been pushed down (true) or released (false) 530 | key - the OSC key name that has moved 531 | 532 | Return Value: void 533 | 534 | ******************************************************************************/ 535 | void sendKeyPress(bool down, const String &key) 536 | { 537 | String keyAddress; 538 | switch (connectedToConsole) 539 | { 540 | default: 541 | case ConsoleEos: 542 | keyAddress = "/eos/key/" + key; 543 | break; 544 | case ConsoleCobalt: 545 | keyAddress = "/cobalt/key/" + key; 546 | break; 547 | case ConsoleColorSource: 548 | keyAddress = "/cs/key/" + key; 549 | break; 550 | } 551 | OSCMessage keyMsg(keyAddress.c_str()); 552 | 553 | if (down) 554 | keyMsg.add(EDGE_DOWN); 555 | else 556 | keyMsg.add(EDGE_UP); 557 | 558 | SLIPSerial.beginPacket(); 559 | keyMsg.send(SLIPSerial); 560 | SLIPSerial.endPacket(); 561 | } 562 | 563 | /******************************************************************************* 564 | Checks the status of all the relevant buttons (i.e. Next & Last) 565 | 566 | NOTE: This does not check the shift key. The shift key is used in tandem with 567 | the encoder to determine coarse/fine mode and thus does not report directly. 568 | 569 | Parameters: none 570 | 571 | Return Value: void 572 | 573 | ******************************************************************************/ 574 | 575 | void checkButtons() 576 | { 577 | // OSC configuration 578 | const int keyCount = 2; 579 | const int keyPins[2] = {NEXT_BTN, LAST_BTN}; 580 | const String keyNames[4] = { 581 | "NEXT", "LAST", 582 | "soft6", "soft4" 583 | }; 584 | 585 | static int keyStates[2] = {HIGH, HIGH}; 586 | 587 | // Eos and Cobalt buttons are the same 588 | // ColorSource is different 589 | int firstKey = (connectedToConsole == ConsoleColorSource) ? 2 : 0; 590 | 591 | // Loop over the buttons 592 | for (int keyNum = 0; keyNum < keyCount; ++keyNum) 593 | { 594 | // Has the button state changed 595 | if (digitalRead(keyPins[keyNum]) != keyStates[keyNum]) 596 | { 597 | // Notify console of this key press 598 | if (keyStates[keyNum] == LOW) 599 | { 600 | sendKeyPress(false, keyNames[firstKey + keyNum]); 601 | keyStates[keyNum] = HIGH; 602 | } 603 | else 604 | { 605 | sendKeyPress(true, keyNames[firstKey + keyNum]); 606 | keyStates[keyNum] = LOW; 607 | } 608 | } 609 | } 610 | } 611 | 612 | /******************************************************************************* 613 | Here we setup our encoder, lcd, and various input devices. We also prepare 614 | to communicate OSC with Eos by setting up SLIPSerial. Once we are done with 615 | setup() we pass control over to loop() and never call setup() again. 616 | 617 | NOTE: This function is the entry function. This is where control over the 618 | Arduino is passed to us (the end user). 619 | 620 | Parameters: none 621 | 622 | Return Value: void 623 | 624 | ******************************************************************************/ 625 | void setup() 626 | { 627 | SLIPSerial.begin(115200); 628 | // This is a hack around an Arduino bug. It was taken from the OSC library 629 | //examples 630 | #ifdef BOARD_HAS_USB_SERIAL 631 | while (!SerialUSB); 632 | #else 633 | while (!Serial); 634 | #endif 635 | 636 | // This is necessary for reconnecting a device because it needs some time 637 | // for the serial port to open. The handshake message may have been sent 638 | // from the console before #lighthack was ready 639 | 640 | SLIPSerial.beginPacket(); 641 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 642 | SLIPSerial.endPacket(); 643 | 644 | // If it's an Eos, request updates on some things 645 | issueEosSubscribes(); 646 | 647 | initEncoder(&panWheel, A0, A1, PAN_DIR); 648 | initEncoder(&tiltWheel, A3, A4, TILT_DIR); 649 | 650 | lcd.begin(LCD_CHARS, LCD_LINES); 651 | lcd.clear(); 652 | 653 | pinMode(NEXT_BTN, INPUT_PULLUP); 654 | pinMode(LAST_BTN, INPUT_PULLUP); 655 | pinMode(SHIFT_BTN, INPUT_PULLUP); 656 | 657 | displayStatus(); 658 | } 659 | 660 | /******************************************************************************* 661 | Here we service, monitor, and otherwise control all our peripheral devices. 662 | First, we retrieve the status of our encoders and buttons and update Eos. 663 | Next, we check if there are any OSC messages for us. 664 | Finally, we update our display (if an update is necessary) 665 | 666 | NOTE: This function is our main loop and thus this function will be called 667 | repeatedly forever 668 | 669 | Parameters: none 670 | 671 | Return Value: void 672 | 673 | ******************************************************************************/ 674 | void loop() 675 | { 676 | static String curMsg; 677 | int size; 678 | // get the updated state of each encoder 679 | int32_t panMotion = updateEncoder(&panWheel); 680 | int32_t tiltMotion = updateEncoder(&tiltWheel); 681 | 682 | // Scale the result by a scaling factor 683 | panMotion *= PAN_SCALE; 684 | tiltMotion *= TILT_SCALE; 685 | 686 | // check for next/last updates 687 | checkButtons(); 688 | 689 | // now update our wheels 690 | if (tiltMotion != 0) 691 | sendWheelMove(TILT, tiltMotion); 692 | 693 | if (panMotion != 0) 694 | sendWheelMove(PAN, panMotion); 695 | 696 | // Then we check to see if any OSC commands have come from Eos 697 | // and update the display accordingly. 698 | size = SLIPSerial.available(); 699 | if (size > 0) 700 | { 701 | // Fill the msg with all of the available bytes 702 | while (size--) 703 | curMsg += (char)(SLIPSerial.read()); 704 | } 705 | if (SLIPSerial.endofPacket()) 706 | { 707 | parseOSCMessage(curMsg); 708 | lastMessageRxTime = millis(); 709 | // We only care about the ping if we haven't heard recently 710 | // Clear flag when we get any traffic 711 | timeoutPingSent = false; 712 | curMsg = String(); 713 | } 714 | 715 | if (lastMessageRxTime > 0) 716 | { 717 | unsigned long diff = millis() - lastMessageRxTime; 718 | //We first check if it's been too long and we need to time out 719 | if (diff > TIMEOUT_AFTER_IDLE_INTERVAL) 720 | { 721 | connectedToConsole = ConsoleNone; 722 | lastMessageRxTime = 0; 723 | updateDisplay = true; 724 | timeoutPingSent = false; 725 | } 726 | 727 | //It could be the console is sitting idle. Send a ping once to 728 | // double check that it's still there, but only once after 2.5s have passed 729 | if (!timeoutPingSent && diff > PING_AFTER_IDLE_INTERVAL) 730 | { 731 | OSCMessage ping("/eos/ping"); 732 | ping.add(BOX_NAME_STRING "_hello"); // This way we know who is sending the ping 733 | SLIPSerial.beginPacket(); 734 | ping.send(SLIPSerial); 735 | SLIPSerial.endPacket(); 736 | timeoutPingSent = true; 737 | } 738 | } 739 | 740 | if (updateDisplay) 741 | displayStatus(); 742 | } 743 | -------------------------------------------------------------------------------- /box_1/faceplate_model.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/faceplate_model.STL -------------------------------------------------------------------------------- /box_1/faceplate_model_blank.STL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/faceplate_model_blank.STL -------------------------------------------------------------------------------- /box_1/faceplate_template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/box_1/faceplate_template.pdf -------------------------------------------------------------------------------- /test_tools/usb_test/README.md: -------------------------------------------------------------------------------- 1 | # USB Test Sketch 2 | This folder contains a troubleshooting tool to verify the line of communication between an Arduino and the Eos software. Once connected with Eos, it sends a series of OSC "ping" messages which are visible in the Eos diagnostics tab. Follow these steps to use it: 3 | 4 | 1. Burn the sketch to your Arduino using the Arduino IDE. 5 | 2. Start the Eos Family software, and connect your device to the machine running Eos (if not the same machine). 6 | 3. Navigate to the Eos diagnostics tab ([Tab & 99]). You should see log messages about an OSC handshake completing: 7 | 8 | ![handshake messages](https://github.com/ETCLabs/OSCHardware/raw/master/test_tools/usb_test/handshake.png) 9 | 10 | 4. On the diagnostics tab, click the button labeled "Incoming OSC (off)" to turn on display of OSC messages. You should see a ping message coming from your device once per second with an incrementing number argument: 11 | 12 | ![ping messages](https://github.com/ETCLabs/OSCHardware/raw/master/test_tools/usb_test/ping.png) 13 | 14 | If this doesn't work, there is a non-hardware-related problem. Make sure the Arduino USB drivers are installed on your target machine. Check the Device Manager to make sure the Arduino is being recognized correctly. 15 | -------------------------------------------------------------------------------- /test_tools/usb_test/handshake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/test_tools/usb_test/handshake.png -------------------------------------------------------------------------------- /test_tools/usb_test/ping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ETCLabs/lighthack/7dfe49752f77a17b0e079df7ed93c3a911a31318/test_tools/usb_test/ping.png -------------------------------------------------------------------------------- /test_tools/usb_test/usb_test_sketch/usb_test_sketch.ino: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Electronic Theatre Controls, Inc., http://www.etcconnect.com 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | 22 | /******************************************************************************* 23 | * 24 | * Electronic Theatre Controls 25 | * 26 | * lighthack - USB Test Sketch 27 | * 28 | * (c) 2017 by ETC 29 | * 30 | * 31 | * This code provides a minimal OSC/USB implementation designed for 32 | * troubleshooting issues with the other DIY modules. It does not interface 33 | * with any hardware; it simply attempts to connect with Eos. Upon doing so, 34 | * it begins periodically sending /eos/ping commands which can then be viewed 35 | * in the Eos application to verify that the OSC pipe is working. 36 | * 37 | ******************************************************************************* 38 | * 39 | * Revision History 40 | * 41 | * yyyy-mm-dd Vxx By_Who Comment 42 | * 43 | * 2017-10-20 V1.0.0.1 Sam Kearney Original creation 44 | * 45 | ******************************************************************************/ 46 | 47 | /******************************************************************************* 48 | * Includes 49 | ******************************************************************************/ 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #ifdef BOARD_HAS_USB_SERIAL 57 | #include 58 | SLIPEncodedUSBSerial SLIPSerial(thisBoardsSerialUSB); 59 | #else 60 | #include 61 | SLIPEncodedSerial SLIPSerial(Serial); 62 | #endif 63 | #include 64 | 65 | /******************************************************************************* 66 | * Macros and Constants 67 | ******************************************************************************/ 68 | const String HANDSHAKE_QUERY = "ETCOSC?"; 69 | const String HANDSHAKE_REPLY = "OK"; 70 | 71 | /******************************************************************************* 72 | * Local Types 73 | ******************************************************************************/ 74 | 75 | /******************************************************************************* 76 | * Global Variables 77 | ******************************************************************************/ 78 | 79 | /******************************************************************************* 80 | * Local Functions 81 | ******************************************************************************/ 82 | 83 | /******************************************************************************* 84 | * Given an unknown OSC message we check to see if it's a handshake message. 85 | * The handshake message must be replied to for Eos to recognize this device 86 | * as an OSC device. 87 | * 88 | * Parameters: 89 | * msg - The OSC message of unknown importance 90 | * 91 | * Return Value: void 92 | * 93 | ******************************************************************************/ 94 | void parseOSCMessage(String& msg) 95 | { 96 | // check to see if this is the handshake string 97 | if (msg.indexOf(HANDSHAKE_QUERY) != -1) 98 | { 99 | // handshake string found! 100 | SLIPSerial.beginPacket(); 101 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 102 | SLIPSerial.endPacket(); 103 | } 104 | } 105 | 106 | /******************************************************************************* 107 | * Here we prepare to communicate OSC with Eos by setting up SLIPSerial. Once 108 | * we are done with setup() we pass control over to loop() and never call 109 | * setup() again. 110 | * 111 | * Parameters: none 112 | * 113 | * Return Value: void 114 | * 115 | ******************************************************************************/ 116 | void setup() 117 | { 118 | SLIPSerial.begin(115200); 119 | // This is a hack around an Arduino bug. It was taken from the OSC library 120 | // examples 121 | #ifdef BOARD_HAS_USB_SERIAL 122 | while (!SerialUSB); 123 | #else 124 | while (!Serial); 125 | #endif 126 | 127 | // this is necessary for reconnecting a device because it needs some time 128 | // for the serial port to open, but meanwhile the handshake message was 129 | // sent from Eos 130 | SLIPSerial.beginPacket(); 131 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 132 | SLIPSerial.endPacket(); 133 | } 134 | 135 | /******************************************************************************* 136 | * Main loop: manage OSC I/O. Send a ping command to Eos every second. 137 | * 138 | * Parameters: none 139 | * 140 | * Return Value: void 141 | * 142 | ******************************************************************************/ 143 | void loop() 144 | { 145 | static String curMsg; 146 | static unsigned long lastTimeSent; 147 | static int32_t pingNum; 148 | unsigned long curTime; 149 | int size; 150 | 151 | // Check to see if any OSC commands have come from Eos that we need to respond to. 152 | size = SLIPSerial.available(); 153 | if (size > 0) 154 | { 155 | // Fill the msg with all of the available bytes 156 | while (size--) 157 | curMsg += (char)(SLIPSerial.read()); 158 | } 159 | if (SLIPSerial.endofPacket()) 160 | { 161 | parseOSCMessage(curMsg); 162 | curMsg = String(); 163 | } 164 | 165 | // Send a ping every second. 166 | curTime = millis(); 167 | if (curTime - lastTimeSent > 1000) 168 | { 169 | OSCMessage ping("/eos/ping"); 170 | ping.add(pingNum++); 171 | SLIPSerial.beginPacket(); 172 | ping.send(SLIPSerial); 173 | SLIPSerial.endPacket(); 174 | lastTimeSent = curTime; 175 | } 176 | } 177 | 178 | -------------------------------------------------------------------------------- /user_projects/nunchuk/README.md: -------------------------------------------------------------------------------- 1 | # Lighthack Nunchuk Assembly Instructions 2 | 3 | YouTube Video 6 | 7 | ## Parts List 8 | 9 | | **ETC PN** | **Vendor PN** | **Qty** | **Description** | 10 | | --- | --- | --- | --- | 11 | | 4201B9001 | Mouser 782-A000066 | 1 | Arduino Uno R3 | 12 | | W6378 | | 1 | USB Cable, A to B | 13 | | | Adafruit 342 | 1 | Nunchuk Remote Controller Attachment For Nintendo Wii | 14 | | | Adafruit 345 | 1 | Nunchucky (Wii Nunchuck breakout adapter) | 15 | | 4201B7001 | | 1 | Wire Solid Core 22AWG/0.65mm2 2-3'/60-90cm | 16 | | 4201A4001 | Hammond 1591U | 1 | Enclosure with lid and screws | 17 | 18 | ## Tools 19 | 20 | - Wire strippers and cutters 21 | - Soldering iron and solder 22 | 23 | ## Software 24 | 25 | - Arduino Integrated Development Environment (IDE). Download from [https://www.arduino.cc/en/Main/Software](https://www.arduino.cc/en/Main/Software) 26 | - Arduino sketch for Nunchuk. Download from [https://github.com/ETCLabs/lighthack](https://github.com/ETCLabs/lighthack) 27 | - Arduino OSC library. Download from [https://github.com/CNMAT/OSC](https://github.com/CNMAT/OSC) 28 | - Arduino to Eos Test Application. Download from [https://github.com/ETCLabs/lighthack](https://github.com/ETCLabs/lighthack) 29 | 30 | ## Test the Software 31 | 32 | It is recommended that you test the Arduino before starting the assembly of the enclosure. 33 | 34 | 1. Download the source code from https://github.com/ETCLabs/lighthack, and extract the .zip file (or clone the repository if you're git-savvy!). 35 | 36 | 1. Open the Arduino USB Test sketch (lighthackmaster\test\_tools\usb\_test\usb\_test\_sketch\usb\_test\_sketch.ino) in the Arduino IDE. 37 | 1. Before the sketch will compile, we need to add additional code (called a "library") so that the Arduino knows how to speak OSC. Download the library as a .zip from [https://github.com/CNMAT/OSC](https://github.com/CNMAT/OSC). 38 | 1. In the Arduino IDE, select **Sketch > Include Library >Add .ZIP Library...** and select the OSC zip file you downloaded 39 | 1. Press the check mark to verify your sketch. 40 | ![](https://i.imgur.com/uMSuBz5.png) 41 | 1. Connect the Arduino to your computer using the USB cable. 42 | 1. The Arduino IDE will automatically detect your Arduino Uno and select it. You can verify this in the Tools menu: 43 | ![](https://i.imgur.com/vm8c9vk.png) 44 | 1. Press the arrow to upload your sketch to the Arduino. 45 | ![](https://i.imgur.com/RToec6g.png) 46 | 1. After the sketch has been loaded, the Arduino's lights should have the L and ON lights solid and the TX light blinking once per second: 47 | ![](https://i.imgur.com/4AtZWOc.png) 48 | 1. Launch Eos software, open Tab 99 Diagnostics, and look for messages that read: 49 | ``` 50 | OSC USB Device Handshake Complete [OK] 51 | OSC USB Device Handshake Initiated [ETCOSC?] 52 | ``` 53 | 54 | 55 | The TX and RX lights should blink in sequence once per second. 56 | Press `Incoming OSC` and you should see repeating messages of `[OSC Packet] /eos/ping` and a number counting up 57 | 58 | These messages indicate that your Arduino and Eos are talking to each other. 59 | 60 | The RX LED on your Arduino should also start blinking. 61 | 62 | 11. Exit the Eos software. 63 | 1. Now, load the main nunchuk.ino code. If you've added the OSC library properly, the verification will be successful. 64 | 1. Press the arrow to upload your sketch to the Arduino. 65 | 1. After the sketch has been loaded, start Eos again and look for the same Handshake messages. Note that in this mode, the Arduino does not send the repeating Ping messages, and the Arduino's TX/RX LEDs will only blink once. 66 | 67 | If text does not appear, follow the troubleshooting steps at [https://github.com/ETCLabs/lighthack/blob/master/box\_1/box\_1\_assembly\_instructions.pdf](https://github.com/ETCLabs/lighthack/blob/master/box_1/box_1_assembly_instructions.pdf) 68 | 69 | ## Adding the Nunchuk 70 | 71 | Connect the `3.3v` and `Gnd` pins to the respective Arduino Pins using the jumper wire and soldering iron. Connect Nunchuk `Data` to `SDA` (Analogue 4 on Arduino Uno) and `Clk` to `SCL` (Analogue 5 on Arduino Uno) 72 | ![Breakout Adaptor](https://i.imgur.com/DKtQkd5.jpg) 73 | ![Wiring Diagram](https://i.imgur.com/ZD6JGy8.png) 74 | Plug in the Nunchuk, and then powerup the Arduino 75 | 76 | ## Usage 77 | 78 | Press Z as ETC Eos "Next" and C as "Last". 79 | 80 | Use joystick to adjust Pan/Tilt on moving fixtures 81 | 82 | Hold down both Z & C for "fine" mode of movement 83 | 84 | ## Guide to Arduino LEDs 85 | 86 | ![](https://i.imgur.com/3if1ke8.png) 87 | -------------------------------------------------------------------------------- /user_projects/nunchuk/nunchuk_src/WiiLib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Nunchuck -- Use a Wii Nunchuck 3 | * Tim Hirzel http://www.growdown.com 4 | * 5 | notes on Wii Nunchuck Behavior. 6 | This library provides an improved derivation of rotation angles from the nunchuck accelerometer data. 7 | The biggest different over existing libraries (that I know of ) is the full 360 degrees of Roll data 8 | from teh combination of the x and z axis accelerometer data using the math library atan2. 9 | 10 | It is accurate with 360 degrees of roll (rotation around axis coming out of the c button, the front of the wii), 11 | and about 180 degrees of pitch (rotation about the axis coming out of the side of the wii). (read more below) 12 | 13 | In terms of mapping the wii position to angles, its important to note that while the Nunchuck 14 | sense Pitch, and Roll, it does not sense Yaw, or the compass direction. This creates an important 15 | disparity where the nunchuck only works within one hemisphere. At a result, when the pitch values are 16 | less than about 10, and greater than about 170, the Roll data gets very unstable. essentially, the roll 17 | data flips over 180 degrees very quickly. To understand this property better, rotate the wii around the 18 | axis of the joystick. You see the sensor data stays constant (with noise). Because of this, it cant know 19 | the difference between arriving upside via 180 degree Roll, or 180 degree pitch. It just assumes its always 20 | 180 roll. 21 | 22 | 23 | * 24 | * This file is an adaptation of the code by these authors: 25 | * Tod E. Kurt, http://todbot.com/blog/ 26 | * 27 | * The Wii Nunchuck reading code is taken from Windmeadow Labs 28 | * http://www.windmeadow.com/node/42 29 | * 30 | * Conversion to Arduino 1.0 by Danjovic 31 | * http://hotbit.blogspot.com 32 | * 33 | * Included the Fix from Martin Peris by Leopold Klimesch 34 | * http://blog.martinperis.com/2011/04/arduino-wiichuck.html 35 | * 36 | * 37 | */ 38 | 39 | #ifndef WiiChuck_h 40 | #define WiiChuck_h 41 | 42 | #include "Arduino.h" 43 | #include 44 | #include 45 | 46 | 47 | // these may need to be adjusted for each nunchuck for calibration 48 | #define ZEROX 510 49 | #define ZEROY 490 50 | #define ZEROZ 460 51 | #define RADIUS 210 // probably pretty universal 52 | 53 | #define DEFAULT_ZERO_JOY_X 124 54 | #define DEFAULT_ZERO_JOY_Y 132 55 | 56 | //Set the power pins for the wiichuck, otherwise it will not be powered up 57 | #define pwrpin PORTC3 58 | #define gndpin PORTC2 59 | 60 | 61 | class WiiChuck { 62 | private: 63 | uint8_t cnt; 64 | uint8_t status[6]; // array to store wiichuck output 65 | uint8_t averageCounter; 66 | //int accelArray[3][AVERAGE_N]; // X,Y,Z 67 | int i; 68 | int total; 69 | uint8_t zeroJoyX; // these are about where mine are 70 | uint8_t zeroJoyY; // use calibrateJoy when the stick is at zero to correct 71 | int lastJoyX; 72 | int lastJoyY; 73 | int angles[3]; 74 | 75 | bool lastZ, lastC; 76 | 77 | 78 | public: 79 | 80 | uint8_t joyX; 81 | uint8_t joyY; 82 | bool buttonZ; 83 | bool buttonC; 84 | void begin() 85 | { 86 | //Set power pinds 87 | DDRC |= _BV(pwrpin) | _BV(gndpin); 88 | 89 | PORTC &=~ _BV(gndpin); 90 | 91 | PORTC |= _BV(pwrpin); 92 | 93 | delay(100); // wait for things to stabilize 94 | 95 | 96 | //send initialization handshake 97 | Wire.begin(); 98 | cnt = 0; 99 | averageCounter = 0; 100 | // instead of the common 0x40 -> 0x00 initialization, we 101 | // use 0xF0 -> 0x55 followed by 0xFB -> 0x00. 102 | // this lets us use 3rd party nunchucks (like cheap $4 ebay ones) 103 | // while still letting us use official oness. 104 | // only side effect is that we no longer need to decode bytes in _nunchuk_decode_byte 105 | // seehttp://forum.arduino.cc/index.php?topic=45924#msg333160 106 | // 107 | Wire.beginTransmission(0x52); // device address 108 | Wire.write(0xF0); 109 | Wire.write(0x55); 110 | Wire.endTransmission(); 111 | delay(1); 112 | Wire.beginTransmission(0x52); 113 | Wire.write(0xFB); 114 | Wire.write((uint8_t)0x00); 115 | 116 | Wire.endTransmission(); 117 | update(); 118 | for (i = 0; i<3;i++) { 119 | angles[i] = 0; 120 | } 121 | zeroJoyX = DEFAULT_ZERO_JOY_X; 122 | zeroJoyY = DEFAULT_ZERO_JOY_Y; 123 | } 124 | 125 | 126 | void calibrateJoy() { 127 | zeroJoyX = joyX; 128 | zeroJoyY = joyY; 129 | } 130 | 131 | void update() { 132 | 133 | Wire.requestFrom (0x52, 6); // request data from nunchuck 134 | while (Wire.available ()) { 135 | // receive byte as an integer 136 | status[cnt] = _nunchuk_decode_byte (Wire.read()); // 137 | cnt++; 138 | } 139 | if (cnt > 5) { 140 | lastZ = buttonZ; 141 | lastC = buttonC; 142 | lastJoyX = readJoyX(); 143 | lastJoyY = readJoyY(); 144 | //averageCounter ++; 145 | //if (averageCounter >= AVERAGE_N) 146 | // averageCounter = 0; 147 | 148 | cnt = 0; 149 | joyX = (status[0]); 150 | joyY = (status[1]); 151 | for (i = 0; i < 3; i++) 152 | //accelArray[i][averageCounter] = ((int)status[i+2] << 2) + ((status[5] & (B00000011 << ((i+1)*2) ) >> ((i+1)*2))); 153 | angles[i] = (status[i+2] << 2) + ((status[5] & (B00000011 << ((i+1)*2) ) >> ((i+1)*2))); 154 | 155 | //accelYArray[averageCounter] = ((int)status[3] << 2) + ((status[5] & B00110000) >> 4); 156 | //accelZArray[averageCounter] = ((int)status[4] << 2) + ((status[5] & B11000000) >> 6); 157 | 158 | buttonZ = !( status[5] & B00000001); 159 | buttonC = !((status[5] & B00000010) >> 1); 160 | _send_zero(); // send the request for next bytes 161 | 162 | } 163 | } 164 | 165 | 166 | // UNCOMMENT FOR DEBUGGING 167 | //byte * getStatus() { 168 | // return status; 169 | //} 170 | 171 | float readAccelX() { 172 | // total = 0; // accelArray[xyz][averageCounter] * FAST_WEIGHT; 173 | return (float)angles[0] - ZEROX; 174 | } 175 | float readAccelY() { 176 | // total = 0; // accelArray[xyz][averageCounter] * FAST_WEIGHT; 177 | return (float)angles[1] - ZEROY; 178 | } 179 | float readAccelZ() { 180 | // total = 0; // accelArray[xyz][averageCounter] * FAST_WEIGHT; 181 | return (float)angles[2] - ZEROZ; 182 | } 183 | 184 | bool zPressed() { 185 | return (buttonZ && ! lastZ); 186 | } 187 | bool cPressed() { 188 | return (buttonC && ! lastC); 189 | } 190 | 191 | // for using the joystick like a directional button 192 | bool rightJoy(int thresh=60) { 193 | return (readJoyX() > thresh and lastJoyX <= thresh); 194 | } 195 | 196 | // for using the joystick like a directional button 197 | bool leftJoy(int thresh=60) { 198 | return (readJoyX() < -thresh and lastJoyX >= -thresh); 199 | } 200 | 201 | 202 | int readJoyX() { 203 | return (int) joyX - zeroJoyX; 204 | } 205 | 206 | int readJoyY() { 207 | return (int)joyY - zeroJoyY; 208 | } 209 | 210 | 211 | // R, the radius, generally hovers around 210 (at least it does with mine) 212 | // int R() { 213 | // return sqrt(readAccelX() * readAccelX() +readAccelY() * readAccelY() + readAccelZ() * readAccelZ()); 214 | // } 215 | 216 | 217 | // returns roll degrees 218 | int readRoll() { 219 | return (int)(atan2(readAccelX(),readAccelZ())/ M_PI * 180.0); 220 | } 221 | 222 | // returns pitch in degrees 223 | int readPitch() { 224 | return (int) (acos(readAccelY()/RADIUS)/ M_PI * 180.0); // optionally swap 'RADIUS' for 'R()' 225 | } 226 | 227 | private: 228 | uint8_t _nunchuk_decode_byte (uint8_t x) 229 | { 230 | //decode is only necessary with certain initializations 231 | //x = (x ^ 0x17) + 0x17; 232 | return x; 233 | } 234 | 235 | void _send_zero() 236 | { 237 | Wire.beginTransmission (0x52); // transmit to device 0x52 238 | Wire.write ((uint8_t)0x00); // sends one byte 239 | Wire.endTransmission (); // stop transmitting 240 | } 241 | 242 | }; 243 | 244 | 245 | #endif 246 | -------------------------------------------------------------------------------- /user_projects/nunchuk/nunchuk_src/nunchuk_src.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Nunchuck as moving light controller LightHack 4 | * 5 | * https://github.com/ETCLabs/lighthack 6 | * 7 | * (c) 2018 ETC &James Bithell 8 | * 9 | ******************************************************************************/ 10 | 11 | /******************************************************************************* 12 | * Includes 13 | ******************************************************************************/ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #ifdef BOARD_HAS_USB_SERIAL 21 | #include 22 | SLIPEncodedUSBSerial SLIPSerial(thisBoardsSerialUSB); 23 | #else 24 | #include 25 | SLIPEncodedSerial SLIPSerial(Serial); 26 | #endif 27 | #include 28 | 29 | #include 30 | #include "WiiLib.h" 31 | 32 | WiiChuck chuck = WiiChuck(); //http://playground.arduino.cc/Main/WiiChuckClass 33 | 34 | 35 | /******************************************************************************* 36 | * Macros and Constants 37 | ******************************************************************************/ 38 | #define SUBSCRIBE ((int32_t)1) 39 | #define UNSUBSCRIBE ((int32_t)0) 40 | 41 | #define EDGE_DOWN ((int32_t)1) 42 | #define EDGE_UP ((int32_t)0) 43 | 44 | #define OSC_BUF_MAX_SIZE 512 45 | 46 | const String HANDSHAKE_QUERY = "ETCOSC?"; 47 | const String HANDSHAKE_REPLY = "OK"; 48 | 49 | //See displayScreen() below - limited to 10 chars (after 6 prefix chars) 50 | const String VERSION_STRING = "1.0.0.5"; 51 | 52 | // Change these values to alter how long we wait before sending an OSC ping 53 | // to see if Eos is still there, and then finally how long before we 54 | // disconnect and show the splash screen 55 | // Values are in milliseconds 56 | #define PING_AFTER_IDLE_INTERVAL 2500 57 | #define TIMEOUT_AFTER_IDLE_INTERVAL 5000 58 | 59 | /******************************************************************************* 60 | 61 | * Global Variables 62 | ******************************************************************************/ 63 | unsigned long zLastPressed, cLastPressed; 64 | unsigned long lastMessageRxTime = 0; 65 | bool timeoutPingSent = false; 66 | int xCorrectionFactor = 0; 67 | int yCorrectionFactor = 0; 68 | int currentX; 69 | int currentY; 70 | 71 | /******************************************************************************* 72 | * Given an unknown OSC message we check to see if it's a handshake message. 73 | * If it's a handshake we issue a subscribe, otherwise we begin route the OSC 74 | * message to the appropriate function. 75 | * 76 | * Parameters: 77 | * msg - The OSC message of unknown importance 78 | * 79 | * Return Value: void 80 | * 81 | ******************************************************************************/ 82 | void parseOSCMessage(String& msg) 83 | { 84 | // check to see if this is the handshake string 85 | if (msg.indexOf(HANDSHAKE_QUERY) != -1) 86 | { 87 | // handshake string found! 88 | SLIPSerial.beginPacket(); 89 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 90 | SLIPSerial.endPacket(); 91 | } 92 | } 93 | 94 | /******************************************************************************* 95 | * Sends a message to Eos informing them of a joystick movement. 96 | * 97 | ******************************************************************************/ 98 | void sendWheelMove(int x, int y) 99 | { 100 | String wheelMsgA = "/eos/wheel"; 101 | String wheelMsgB = "/eos/wheel"; 102 | 103 | if (chuck.buttonZ && chuck.buttonC) { 104 | wheelMsgA.concat("/fine"); 105 | wheelMsgB.concat("/fine"); 106 | x = x*3; //Calibration Factor for fine 107 | y = y*1.5; //Calibration Factor - Y has smaller range on most moving lights 108 | } else { 109 | wheelMsgA.concat("/coarse"); 110 | wheelMsgB.concat("/coarse"); 111 | x = x*0.3; //Calibration Factor for coarse 112 | y = y*0.15; //Calibration Factor - Y has smaller range on most moving lights 113 | } 114 | wheelMsgA.concat("/pan"); 115 | wheelMsgB.concat("/tilt"); 116 | 117 | OSCMessage wheelUpdateA(wheelMsgA.c_str()); 118 | wheelUpdateA.add(x); 119 | SLIPSerial.beginPacket(); 120 | wheelUpdateA.send(SLIPSerial); 121 | SLIPSerial.endPacket(); 122 | 123 | OSCMessage wheelUpdateB(wheelMsgB.c_str()); 124 | wheelUpdateB.add(y); 125 | SLIPSerial.beginPacket(); 126 | wheelUpdateB.send(SLIPSerial); 127 | SLIPSerial.endPacket(); 128 | } 129 | 130 | /******************************************************************************* 131 | * Sends a message to Eos informing them of a key press. 132 | * 133 | * Parameters: 134 | * down - whether a key has been pushed down (true) or released (false) 135 | * key - the key that has moved 136 | * 137 | * Return Value: void 138 | * 139 | ******************************************************************************/ 140 | void sendKeyPress(String key) 141 | { 142 | key = "/eos/key/" + key; 143 | OSCMessage keyMsg(key.c_str()); 144 | keyMsg.add(EDGE_DOWN); 145 | SLIPSerial.beginPacket(); 146 | keyMsg.send(SLIPSerial); 147 | SLIPSerial.endPacket(); 148 | 149 | OSCMessage keyMsgUp(key.c_str()); 150 | keyMsgUp.add(EDGE_UP); 151 | SLIPSerial.beginPacket(); 152 | keyMsgUp.send(SLIPSerial); 153 | SLIPSerial.endPacket(); 154 | 155 | } 156 | 157 | /******************************************************************************* 158 | * Checks the status of all the buttons relevant to Eos (i.e. Next & Last) 159 | * 160 | * Parameters: none 161 | * 162 | * Return Value: void 163 | * 164 | ******************************************************************************/ 165 | void checkButtons() 166 | { 167 | if (chuck.buttonZ && !chuck.buttonC && zLastPressed < (millis()-500)) { 168 | delay(1000); 169 | if (!chuck.buttonC) { //If after 300 seconds the C button isn't depressed it means this isn't a false trigger as they try and push both buttons together - so go ahead and send a keypress 170 | sendKeyPress("NEXT"); 171 | zLastPressed = millis(); 172 | } 173 | } 174 | if (chuck.buttonC && !chuck.buttonZ && cLastPressed < (millis()-500)) { 175 | delay(1000); 176 | if (!chuck.buttonZ) { //If after 500 seconds the Z button isn't depressed it means this isn't a false trigger as they try and push both buttons together - so go ahead and send a keypress 177 | sendKeyPress("LAST"); 178 | cLastPressed = millis(); 179 | } 180 | } 181 | if (chuck.buttonC && chuck.buttonZ) { 182 | zLastPressed = millis()+200; 183 | cLastPressed = millis()+200; //Make an even bigger delay after you release both keys to stop it jumping straight to a button 184 | } 185 | } 186 | 187 | /******************************************************************************* 188 | * Here we setup our various input devices. We also prepare 189 | * to communicate OSC with Eos by setting up SLIPSerial. Once we are done with 190 | * setup() we pass control over to loop() and never call setup() again. 191 | * 192 | * NOTE: This function is the entry function. This is where control over the 193 | * Arduino is passed to us (the end user). 194 | * 195 | ******************************************************************************/ 196 | void setup() 197 | { 198 | SLIPSerial.begin(115200); 199 | // This is a hack around an Arduino bug. It was taken from the OSC library 200 | //examples 201 | #ifdef BOARD_HAS_USB_SERIAL 202 | while (!SerialUSB); 203 | #else 204 | while (!Serial); 205 | #endif 206 | 207 | // This is necessary for reconnecting a device because it needs some time 208 | // for the serial port to open, but meanwhile the handshake message was 209 | // sent from Eos 210 | SLIPSerial.beginPacket(); 211 | SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length()); 212 | SLIPSerial.endPacket(); 213 | 214 | chuck.begin(); 215 | chuck.update(); 216 | zLastPressed = millis(); 217 | cLastPressed = millis(); 218 | chuck.calibrateJoy(); //Calibrate the 0 of the joystick 219 | delay(2000); //Wait for joystick to ensure fully loaded up 220 | chuck.update(); 221 | if (chuck.readJoyX() != 0 || chuck.readJoyY() != 0) { 222 | //Calibration has failed (seems to be happening on certain Nunchuks) - setup a correction factor by taking the current reading and subtracting it from future ones. 223 | xCorrectionFactor = chuck.readJoyX(); 224 | yCorrectionFactor = chuck.readJoyY(); 225 | } 226 | } 227 | 228 | /******************************************************************************* 229 | * Here we service, monitor, and otherwise control all our peripheral devices. 230 | * First, we retrieve the status of our encoders and buttons and update Eos. 231 | * Next, we check if there are any OSC messages for us. 232 | * Finally, we update our display (if an update is necessary) 233 | * 234 | * NOTE: This function is our main loop and thus this function will be called 235 | * repeatedly forever 236 | * 237 | ******************************************************************************/ 238 | 239 | void loop() 240 | { 241 | static String curMsg; 242 | int size; 243 | 244 | delay(20); //Try not to overcheck on the Chuck 245 | chuck.update(); //Request position of buttons & joystick 246 | checkButtons(); 247 | 248 | // check satus of chuck joystick 249 | currentX = chuck.readJoyX() - xCorrectionFactor; 250 | currentY = chuck.readJoyY() - yCorrectionFactor; 251 | 252 | if (currentX != 0 || currentY != 0) { 253 | sendWheelMove(currentX, currentY); 254 | } 255 | 256 | // Then we check to see if any OSC commands have come from Eos 257 | // and update the display accordingly. 258 | size = SLIPSerial.available(); 259 | if (size > 0) 260 | { 261 | // Fill the msg with all of the available bytes 262 | while (size--) 263 | curMsg += (char)(SLIPSerial.read()); 264 | } 265 | if (SLIPSerial.endofPacket()) 266 | { 267 | parseOSCMessage(curMsg); 268 | lastMessageRxTime = millis(); 269 | // We only care about the ping if we haven't heard recently 270 | // Clear flag when we get any traffic 271 | timeoutPingSent = false; 272 | curMsg = String(); 273 | } 274 | 275 | if(lastMessageRxTime > 0) 276 | { 277 | unsigned long diff = millis() - lastMessageRxTime; 278 | //We first check if it's been too long and we need to time out 279 | if(diff > TIMEOUT_AFTER_IDLE_INTERVAL) 280 | { 281 | lastMessageRxTime = 0; 282 | timeoutPingSent = false; 283 | } 284 | 285 | //It could be the console is sitting idle. Send a ping once to 286 | // double check that it's still there, but only once after 2.5s have passed 287 | if(!timeoutPingSent && diff > PING_AFTER_IDLE_INTERVAL) 288 | { 289 | OSCMessage ping("/eos/ping"); 290 | ping.add("arduinoHello"); // This way we know who is sending the ping 291 | SLIPSerial.beginPacket(); 292 | ping.send(SLIPSerial); 293 | SLIPSerial.endPacket(); 294 | timeoutPingSent = true; 295 | } 296 | } 297 | 298 | } 299 | --------------------------------------------------------------------------------