Juan Pablo Risso
Published © Apache-2.0

Building an IR Bridge with the SmartThings ThingShield

Tutorial on how to building an IR Bridge with the SmartThings ThingShield

IntermediateFull instructions provided8,906
Building an IR Bridge with the SmartThings ThingShield

Things used in this project

Hardware components

Arduino UNO & Genuino UNO
Arduino UNO & Genuino UNO
×1
SmartThings Shield for Arduino
SmartThings Shield for Arduino
×1
IR Emitting LED
×1
IR Receiver Diode
×1
100 Ohm Resistor (optional)
×1

Story

Read more

Code

Arduino sketch

C/C++
//*****************************************************************************
/// @file
/// @brief
///   Arduino SmartThings IR Shield
//*****************************************************************************
#include <SoftwareSerial.h> //TODO need to set due to some weird wire language linker, should we absorb this whole library into smartthings
#include <SmartThings.h>
#include <IRremote.h>

//*****************************************************************************
// Pin Definitions    | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                    V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
#define PIN_LED         13
#define PIN_RECV        11
#define PIN_THING_RX    3
#define PIN_THING_TX    2

//*****************************************************************************
// Global Variables   | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                    V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
SmartThingsCallout_t messageCallout;    // call out function forward decalaration
SmartThings smartthing(PIN_THING_RX, PIN_THING_TX, messageCallout);  // constructor

bool isDebugEnabled;    // enable or disable debug in this example
int stateLED;           // state to track last set value of LED

IRrecv irrecv(PIN_RECV);
IRsend irsend;
decode_results results;

//*****************************************************************************
// API Functions    | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                  V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************
void setup()
{
  // setup default state of global variables
  isDebugEnabled = true;
  stateLED = 0;                 // matches state of hardware pin set below

  // setup hardware pins 
  pinMode(PIN_LED, OUTPUT);     // define PIN_LED as an output
  digitalWrite(PIN_LED, LOW);   // set value to LOW (off) to match stateLED=0

  if (isDebugEnabled)
  { // setup debug serial port
    Serial.begin(9600);         // setup serial with a baud rate of 9600
    Serial.println("setup..");  // print out 'setup..' on start
  }

  irrecv.enableIRIn(); // Start the receiver
}

//*****************************************************************************
void loop()
{
  // run smartthing logic
  smartthing.run();  

  if (irrecv.decode(&results)) 
  {
    blue();
    irrecv.resume(); // Receive the next value
    Serial.println(results.value, HEX);
    //dump(&results);

    //EXAMPLE: smartthing.send("HEX,XXXCODE");    
    String irCmd;

    if (results.decode_type == NEC) {
      irCmd = String(results.value, HEX) + "," + "NEC" + String(results.bits, DEC) + ":" + String(results.value, HEX);
    } 
    else if (results.decode_type == SONY) {
      irCmd = String(results.value, HEX) + "," + "SNY" + String(results.bits, DEC) + ":" + String(results.value, HEX);
    } 
    else if (results.decode_type == RC5) {
      irCmd = String(results.value, HEX) + "," + "RC5" + String(results.bits, DEC) + ":" + String(results.value, HEX);
    } 
    else if (results.decode_type == RC6) {
      irCmd = String(results.value, HEX) + "," + "RC6" + String(results.bits, DEC) + ":" + String(results.value, HEX);
    }
    else
    {
      irCmd = String(results.value, HEX) + "," + "RAW" + String(results.bits, DEC) + ":";
    }
    Serial.println(irCmd);
    smartthing.send(irCmd);
    irCmd = "";
  }

}

//*****************************************************************************
void messageCallout(String message)
{

  smartthing.shieldSetLED(0, 0, 0);
  // if debug is enabled print out the received message
  if (isDebugEnabled)
  {
    Serial.print("Rx: '");
    Serial.print(message);
    Serial.println("' ");
  }

  String type = message.substring(0,3);
  int startCode = message.indexOf(':');
  String lenStr = message.substring(3,startCode);
  String codeStr = message.substring(startCode + 1);
  unsigned long code;

  //turn the hex string to a long unsigned
  if(type != "RAW")
    code = stringToNum(codeStr,16); //will not work for RAW

  int len =  stringToNum(lenStr,10);

  //For each type - NEC,SON,PAN,JVC,RC5,RC6,etc...the first 3  
  if(type == "NEC")
  {
    Serial.println("NEC-SEND");
    Serial.println(len);
    Serial.println(code,HEX);

    irsend.sendNEC(code,len);
    irrecv.enableIRIn();
  }
  else if(type == "SNY")
  {
    irsend.sendSony(code,len);
    irrecv.enableIRIn();
  }
  else if(type == "RC5")
  {
    irsend.sendRC5(code,len);
    irrecv.enableIRIn();
  }
  else if(type == "RC6")
  {
    irsend.sendRC6(code,len);
    irrecv.enableIRIn();
  } 
}

// Dumps out the decode_results structure.
// Call this after IRrecv::decode()
// void * to work around compiler issue
//void dump(void *v) {
//  decode_results *results = (decode_results *)v
void dump(decode_results *results) {
  int count = results->rawlen;
  if (results->decode_type == UNKNOWN) {
    Serial.print("Unknown encoding: ");
  } 
  else if (results->decode_type == NEC) {
    Serial.print("Decoded NEC: ");
  } 
  else if (results->decode_type == SONY) {
    Serial.print("Decoded SONY: ");
  } 
  else if (results->decode_type == RC5) {
    Serial.print("Decoded RC5: ");
  } 
  else if (results->decode_type == RC6) {
    Serial.print("Decoded RC6: ");
  }
  else if (results->decode_type == PANASONIC) {	
    Serial.print("Decoded PANASONIC - Address: ");
    Serial.print(results->panasonicAddress,HEX);
    Serial.print(" Value: ");
  }
  else if (results->decode_type == JVC) {
     Serial.print("Decoded JVC: ");
  }
  Serial.print(results->value, HEX);
  Serial.print(" (");
  Serial.print(results->bits, DEC);
  Serial.println(" bits)");
  Serial.print("Raw (");
  Serial.print(count, DEC);
  Serial.print("): ");

  for (int i = 0; i < count; i++) {     if ((i % 2) == 1) {       Serial.print(results->rawbuf[i]*USECPERTICK, DEC);
    } 
    else {
      Serial.print(-(int)results->rawbuf[i]*USECPERTICK, DEC);
    }
    Serial.print(" ");
  }
  Serial.println("");
}

//*****************************************************************************
// Local Functions  | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
//                  V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
//*****************************************************************************

unsigned long stringToNum(String s, int base) //10 for decimal, 16 for hex
{
  unsigned long i = 0;
  unsigned long value = 0;
  unsigned long place = s.length();
  char c;
  unsigned long sign = 1;

  for(i; i < s.length(); i++) 
  {     
    place--;     
    c = s[i];     
    if(c == '-') 
    {       
      sign = -1;     
    } else if (c >= '0' && c <= '9')  //0 to 9     
    {       
      value += ( c - '0') *  exponent(base,place);     
    } else if (c >= 'A' && ((c - 'A' + 10) < base))  //65     
    {       
      value += (( c - 'A') + 10) *  exponent(base,place);     
    }     
      else if (c >= 'a' && (c - 'a' +  10) < base)  //97
    {
      value += (( c - 'a') + 10) *  exponent(base,place);
    }     
  }
  value *= sign;
  return value;  
}

unsigned long exponent(int num, int power)
{
  unsigned long total = num;
  unsigned long i = 1;
  for(power;  i < power; i++)
  {
    total *=  num;
  }
  return (power == 0) ? 1 : total;

}

void green()
{
  smartthing.shieldSetLED(0, 1, 0);
}

void blue()
{
  smartthing.shieldSetLED(0, 0, 1);
}
void red()
{
  smartthing.shieldSetLED(1, 0, 0);
}

void off()
{
  delay(100);
  smartthing.shieldSetLED(0, 0, 0);
}

Device definition

Groovy
/**
 *  Smart Ir
 *
 *  Author: danny@smartthings.com
 *  Date: 2013-03-06
 */

metadata 
{
	// Simulator metadata
	simulator {}

	// UI tile definitions
	tiles 
	{
		standardTile("recStatus", "device.recStatus", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) 
		{			
			state "off", label: 'record', action: "record", backgroundColor: "#ffffff"
            		state "on", label: 'record', action: "record", backgroundColor: "#ff0000"
		}

        	//this is a secondary tile //set, unset, prog - green, white, yellow
		standardTile("buttonA", "device.buttonAStatus", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true)
		{

			state "unset", label: '', action: "buttonA", icon: "st.custom.buttons.play-pause", backgroundColor: "#cccccc" //gray
			state "prog", label: '', action: "buttonA", icon: "st.custom.buttons.play-pause", backgroundColor: "#FDE910" //yellow
			state "set", label: '', action: "buttonA", icon: "st.custom.buttons.play-pause", backgroundColor: "#79b821" //green
		}

		//set, unset, prog - green, white, yellow
		standardTile("buttonB", "device.buttonBStatus", width: 1, height: 1, canChangeIcon: true, canChangeBackground: true) 
		{

			state "unset", label: '', action: "buttonB", icon: "st.custom.buttons.b", backgroundColor: "#cccccc" //gray
			state "prog", label: '', action: "buttonB", icon: "st.custom.buttons.b", backgroundColor: "#FDE910" //yellow
			state "set", label: '', action: "buttonB", icon: "st.custom.buttons.b", backgroundColor: "#79b821" //green
		}

		//possible main tiles
		main (["buttonA","buttonB"])
        	//this is an array for multiple tiles
		details (["recStatus","buttonA","buttonB"])
	}
}

// Parse incoming device messages to generate events
def parse(String description) 
{

	log.trace "parse:"

	def value = zigbee.parse(description)?.text
	def codeParts = value.split(/,/)

    	log.trace "code: ${codeParts[0]}"

	//[0] is the hex, [1] is the resend
	//of [1], first three chars are the type, followed by code

	if(device.currentValue("recStatus") == "on") //in record mode
	{
		log.trace "record enabled"
		if(device.currentValue("lastButton") != "")
	{

	log.trace "last button is active"

        def buttonStatus = "${device.currentValue("lastButton")}Status";
        def buttonHex = "${device.currentValue("lastButton")}Hex";
        def buttonCode = "${device.currentValue("lastButton")}Code";

        def result = [
        	createEvent(name:buttonStatus, value:"set", isStateChange:true) //turn the button green
	  		,createEvent(name:buttonHex, value:codeParts[0], isStateChange:true) //store the code
	    	,createEvent(name:buttonCode, value:codeParts[1], isStateChange:true) //store the code
        	,createEvent(name:"recStatus", value:"off", isStateChange:true)        
        	,createEvent(name:"lastButton", value:"", isStateChange:true) //reset the last button

        ]
	  	return result
	  }
      else{
        log.trace "no button selected"

      }
	}
	else
	{
	//if not
	  //check to see it matches any buttons
	  if(codeParts[0] == device.currentValue("buttonAHex"))
	  {
	  	//send an event related to buttonA
		def result = createEvent(name: "button", value: "A",isStateChange:true)
		log.debug "Parse returned ${result?.descriptionText}"
		return result

	  }
	  else if(codeParts[0] == device.currentValue("buttonBHex"))
	  {
	  	//send an event related to buttonB
		def result = createEvent(name: "button", value: "B", isStateChange:true )
		log.debug "Parse returned ${result?.descriptionText}"
		return result
	  }
	}
    def result = createEvent(name: null, value: "")
	return result
}

def record()
{
	//enter record mode
    log.debug "RecordMode changing from ${device.currentValue("recStatus")}"

	clearLast() //clear last button

	//toggle attribute to on/off
	if(device.currentValue("recStatus") == "on")
	{
		sendEvent(name:"recStatus", value:"off", isStateChange:true)
		//tile color changes to white
	}
	else
	{
		sendEvent(name:"recStatus", value:"on", isStateChange:true)
		//tile color changes to red
	}
}

def buttonA()
{
	log.debug "ButtonA pressed"
	if(device.currentValue("recStatus") == "on") //if in record mode, set button to be programmed
	{
		clearLast()
        log.debug "Put buttonA in programming mode"

		//set the lastTile attribute to tileA
		//turn it yellow
		sendEvent(name:"buttonAStatus", value:"prog", isStateChange:true)
		sendEvent(name:"lastButton", value:"buttonA", isStateChange:true)
	}
	else if(device.currentValue("buttonAStatus") == "set") //if it's set, send the stored code
	{
		log.debug "Send buttonA Code"
		//send the remote code to the blaster
		zigbee.smartShield(text: "${device.currentValue("buttonACode")}").format()
	}
	else
	{
		log.debug "button is currently ${device.currentValue("buttonAStatus")}"
	}
}

def buttonB()
{
	clearLast()
	log.debug "ButtonB pressed"
	if(device.currentValue("recStatus") == "on") //if in record mode, set button to be programmed
	{
		log.debug "Put button in programming mode"
		//set the lastTile attribute to tileA
		//turn it yellow
		sendEvent(name:"buttonBStatus", value:"prog", isStateChange:true)
		sendEvent(name:"lastButton", value:"buttonB", isStateChange:true)

	}
	else if(device.currentValue("buttonBStatus") == "set") //if it's set, send the stored code
	{
		log.debug "Send buttonB Code"
		//send the remote code to the blaster
		zigbee.smartShield(text: "${device.currentValue("buttonBCode")}").format()
	}
	else if(device.currentValue("buttonBStatus") == "unset")
	{
		log.debug "button is currently unset"
	}
}

def clearLast()
{
	if(device.currentValue("lastButton") != "")
	{
		sendEvent(name:"${device.currentValue("lastButton")}", value:"unset", isStateChange:true)
		sendEvent(name:"lastButton", value:"", isStateChange:true)
	}
}

smartApp code

Groovy
/**
 *  IR Receiver
 *
 *  Author: danny@smartthings.com
 *  Date: 2013-03-31
 */
preferences {
	section("Pick an IR device...") {
    	input "irDevice", "device.IrBridge"
    }
	section("Button A turns on or off..."){
		input "switch1", "capability.switch", title: "This light", required: false
	}
	section("Button B turns on or off..."){
		input "switch2", "capability.switch", title: "This light", required: false
	}    
}

def installed() {
	log.debug "Installed with settings: ${settings}"
	subscribe(irDevice, "button.B", handleB)
        subscribe(irDevice, "button.A",handleA)
}

def updated() {
	log.debug "Updated with settings: ${settings}"
	unsubscribe()
        subscribe(irDevice, "button.B", handleB)
        subscribe(irDevice, "button.A",handleA)
}

def handleA(evt) {
	log.debug "received button A"
    if (switch1.currentValue("switch") == "on") {
        switch1.off()
    }
    else {
        switch1.on()
    }
}

def handleB(evt) {
	log.debug "received button B"
    if (switch2.currentValue("switch") == "on") {
        switch2.off()
    }
    else {
        switch2.on()
    }
}

Credits

Juan Pablo Risso

Juan Pablo Risso

4 projects • 30 followers
I am a geek, technology Innovator, Software Engineer and Maker. I work at Samsung / SmartThings propelling the Internet of Things.

Comments