Introduction: Arduino OSC Interface
This code is designed to connect an Arduino to the ETC EOS control system. Connection is via USB and messages are sent using Open Show Control (OSC) protocol.
Testing was done with a temperature probe and a sound sensor trigger, and was used to control Hue, Saturation and Intensity of a single channel on the lighting console. I've found that the format for the OSC message being sent is VERY particular and is the most frustrating part of the project. Credit must be given to the ETCLabs and ETC Lighthack repositories on Github that were instrumental in getting me the building blocks to get this code up and running. Scale would be the next step- multiple sensors controlling multiple lights.
Step 1: Code
*******************************************************************************
* Includes ******************************************************************************/ #include <OSCBoards.h> #include <OSCBundle.h> #include <OSCData.h> #include <OSCMessage.h> #include <OSCMatch.h> #include <OSCTiming.h>
#include <OneWire.h> #include <DallasTemperature.h>
#ifdef BOARD_HAS_USB_SERIAL #include <SLIPEncodedUSBSerial.h> SLIPEncodedUSBSerial SLIPSerial(thisBoardsSerialUSB); #else #include <SLIPEncodedSerial.h> SLIPEncodedSerial SLIPSerial(Serial); #endif #include <string.h>
/******************************************************************************* * Macros and Constants ******************************************************************************/
#define OSC_BUF_MAX_SIZE 512
const String HANDSHAKE_QUERY = "ETCOSC?"; const String HANDSHAKE_REPLY = "OK";
//See displayScreen() below - limited to 10 chars (after 6 prefix chars) const String VERSION_STRING = "1.0.0.5";
#define ONE_WIRE_BUS 2 //Data wire is plugged into pin 2 on Arduino
OneWire oneWire(ONE_WIRE_BUS); //Setup a oneWire instance to communicate with any OneWire device
DallasTemperature sensors(&oneWire); /******************************************************************************* * Local Types ******************************************************************************
******************************************************************************* * Global Variables ******************************************************************************/ int CurrentTemp = 0; //variable for temp int SatValue = 0; //variable for Saturation int Hue = 0; //variable for Hue int Brightness = 30; //starting brightness value int moreFuego = 10; //amount intensity up change int Dimness = 2; //amount auto down intensity change int Chan = 11; //channel affected. Effected?
const int MIC = 3; //input from button/mic sensor
unsigned long elapseLimit = 3000; //amount of time before dimness unsigned long PrevTime = 0; //restart point for counter
bool MicState = false; bool ledState = false; //state variable for the LED
bool updateDisplay = false; bool connectedToEos = false; unsigned long lastMessageRxTime = 0; bool timeoutPingSent = false;
/******************************************************************************* * Local Functions ******************************************************************************
*******************************************************************************
* Issues all our subscribes to Eos. When subscribed, Eos will keep us updated
* with the latest values for a given parameter.
* Kept here for reference, not necessary if all information is designed to travel
* in only one direction.
*
* Parameters: none
*
* Return Value: void
*
******************************************************************************/
void issueSubscribes()
{
// Add a filter so we don't get spammed with unwanted OSC messages from Eos
OSCMessage filter("/eos/filter/add");
filter.add("/eos/out/param/*");
filter.add("/eos/out/ping");
SLIPSerial.beginPacket();
filter.send(SLIPSerial);
SLIPSerial.endPacket();}
/*******************************************************************************
* Given an unknown OSC message we check to see if it's a handshake message.
* If it's a handshake we issue a subscribe, otherwise we begin route the OSC
* message to the appropriate function.
*
* Parameters:
* msg - The OSC message of unknown importance
*
* Return Value: void
*
******************************************************************************/
void parseOSCMessage(String& msg)
{
// check to see if this is the handshake string
if (msg.indexOf(HANDSHAKE_QUERY) != -1)
{
// handshake string found!
SLIPSerial.beginPacket();
SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length());
SLIPSerial.endPacket(); // Let Eos know we want updates on some things
issueSubscribes(); // Make our splash screen go away
connectedToEos = true;
updateDisplay = true;
}
else
{
// prepare the message for routing by filling an OSCMessage object with our message string
OSCMessage oscmsg;
oscmsg.fill((uint8_t*)msg.c_str(), (int)msg.length());
}
}/*******************************************************************************
* Here we setup our encoder, lcd, and various input devices. We also prepare
* to communicate OSC with Eos by setting up SLIPSerial. Once we are done with
* setup() we pass control over to loop() and never call setup() again.
*
* NOTE: This function is the entry function. This is where control over the
* Arduino is passed to us (the end user).
*
* Parameters: none
*
* Return Value: void
*
******************************************************************************/
void setup()
{
SLIPSerial.begin(115200);
// This is a hack around an Arduino bug. It was taken from the OSC library
//examples
#ifdef BOARD_HAS_USB_SERIAL
while (!SerialUSB);
#else
while (!Serial);
#endif
pinMode(MIC, INPUT); //input from sound sensor to pin 3//pinMode(13, OUTPUT); //flash LED if sound triggers for debug //digitalWrite(13, ledState); //sound sensor has onboard LED, so not really necessary
sensors.begin();
// This is necessary for reconnecting a device because it needs some time
// for the serial port to open, but meanwhile the handshake message was
// sent from Eos
SLIPSerial.beginPacket();
SLIPSerial.write((const uint8_t*)HANDSHAKE_REPLY.c_str(), (size_t)HANDSHAKE_REPLY.length());
SLIPSerial.endPacket();
// Let Eos know we want updates on some things
issueSubscribes();delay(1000);
OSCMessage on("/eos/chan/11/at/"); //set initial starting intensity on channel 11
on.add(Brightness);
SLIPSerial.beginPacket(); //send the thing
on.send(SLIPSerial);
SLIPSerial.endPacket();
delay(1000);
}/******************************************************************************* * Here we service, monitor, and otherwise control all our peripheral devices. * First, we retrieve the status of our encoders and buttons and update Eos. * Then we apply any transformation functions and output the data to Eos.
*
* NOTE: This function is our main loop and thus this function will be called
* repeatedly forever
*
* Parameters: none
*
* Return Value: void
*
******************************************************************************/
void loop()
{
unsigned long currentTime = millis(); //checks current time
boolean timeOut = false;
static String curMsg;
int size;
// get the updated state of each encoderMicState = digitalRead(MIC); //check for sensor triggered
if (MicState == HIGH) {
Brightness = Brightness+moreFuego; //change intensity output by current brightness + step
Brightness = constrain(Brightness, 0, 100); //keeps numbers in bounds
}sensors.requestTemperatures(); //queries temperature probe
CurrentTemp = sensors.getTempCByIndex(0); //converts to degrees Celcius.
//Change C to F for the other system.
CurrentTemp = constrain(CurrentTemp, 0, 40);
//constrains to between 0 and 40, ensures we stay within bounds and don't
//exceed workable values when converting to Hue and Saturation
if (CurrentTemp >= 24) //we are in the hot zone
{
Hue = 360;
SatValue = (CurrentTemp-24)*5; //scale our temp into Saturation
}
else //we are in the cold zone
{
Hue = 240;
SatValue = (24-CurrentTemp)*4; //scale our temp into Saturation
}
OSCMessage stuff("/eos/cs/color/hs/"); //prep EOS to receive colour
stuff.add(Hue); //add Hue variable
stuff.add(SatValue); //add Saturation variable
SLIPSerial.beginPacket(); //send the thing
stuff.send(SLIPSerial);
SLIPSerial.endPacket();delay(10);
if(MicState == HIGH) { //if intensity trigger was triggered
OSCMessage intensity("/eos/at/"); //prep EOS for intensity value
intensity.add(Brightness); //add brightness variable to message
SLIPSerial.beginPacket(); //send the thing
intensity.send(SLIPSerial);
SLIPSerial.endPacket(); PrevTime = currentTime; //a message was sent, so reset our timer
} else {
delay(1);
}
//check to see if long enough to lower intensity
if(currentTime-PrevTime>elapseLimit){ //compares time values, if greater than limit, run intensity down
Brightness = Brightness-Dimness; //subtracts our down step from current intensity level
if (Brightness <= 30) { //sets our minimum intensity level, also keeps number from going negative.
Brightness = 30;
}
OSCMessage downboy("/eos/at"); //our intensity down section
downboy.add(Brightness); //adds intensity value to message
SLIPSerial.beginPacket(); //send the thing
downboy.send(SLIPSerial);
SLIPSerial.endPacket(); PrevTime = currentTime; //a message was sent, so we want to reset our timer
} else {
delay(1);
}
}




