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 encoder
MicState = 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);
}     
   
    
}