Project tutorial

Building an IR Bridge with the SmartThings ThingShield © Apache-2.0

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

  • 6,974 views
  • 5 comments
  • 26 respects

Components and supplies

About this project

Introduction

A simple TV remote is capable of a lot more that channel changing when connected to the SmartThings cloud. With a small part count and an immediate payoff, the project is a good way to explore the platform.

Background on Arduino and the ThingShield

The SmartThings ThingShield makes it easy to build SmartThings prototypes using Arduino. The shield connects directly to the UNO linking to the SmartThings hub via the Zigbee protocol. A switch on the shield allows one to choose between using pins 0,1 or 2,3 to communicate with the Arduino. The ThingShield already reserves the use of pin 6.

For more information on getting started with Arudino: http://arduino.cc/en/Guide/Introduction

Step 1: You will need


Step 2: Download the IR Library

The project depends on a marvelous Arduino library developed by Ken Shirriff. https://github.com/shirriff/Arduino-IRremote

Install the library Arduino library in the usual manner.

http://arduino.cc/en/Guide/Libraries

Once installed, we need to modify IRRemoteInt.h to use the timer on pin 9. In this example, we use pin 3 for communication with the ThingShield.


Step 3: Arduino Sketch

Next, upload the sketch to your Arduino board (see code section).


Step 4: The Build

The build is simple and straightforward. First connect the ThingShield on top of the Arduino. Although only the Arduino is shown in the diagram below, pin placements are the same with the shield attached.

For the IR Receiver, connect the corresponding pins to ground and 5V. Then attach the data pin to Arduino pin 11. Connect the IR emitting LED to pin 9 and ground. The 100 ohm resistor is optional in this design as the LED can likely handle the max current from the Arduino as it blinks rapidly to send signals. Left on continuously, the same current would likely burn out the LED.


Step 5: Creating a Custom SmartThings Device Type

In the SmartThings IDE, we next create a new device type for our ThingShield.

Go to the “My Device Types” section, click “New SmartDevice” on the right.

Creating a new SmartDevice requires two steps. First, give the new device type a name at the top “Ir Bridge.” Note we will reference the name of our SmartDevice in our SmartApp code later.

We need to define the Attributes (variables) and Commands (functions) of the device. For this tutorial, we will create the attributes and commands for two programmable buttons and a record button. Add each of the attributes and commands shown. Select “Create” to continue onto coding the device.

Next, copy the device definition into the code window in the IDE (see code section). Click the “Save” button at the top, and then click “Publish” to make the device available to you.


Step 6: Updating the ThingShield to Your New Device Type

Make sure to add your ThingShield to your SmartThings hub if it is not already setup. To pair the device, attach the ThingShield to your Arudino and give it power. Hold the “Switch” button on the shield for 6 seconds. With the SmartThings smartphone app, select the Add button. Press the physical “switch” button one more time and you should see the hub identify the ThingShield.

Back, in the IDE, navigate to your Arduino ThingShield by clicking on “devices” on the home screen. Select your device from the list and click the “Edit” button at the bottom of the page. From the “Type” dropdown menu, choose the new SmartDevice type you created. It’s helpful to give devices meaningful label names when you need to select them later. Hit update to set the device to your new device type.

Note, when publishing updates to your custom device types in the future, you will want to come back to confirm that your physical devices remained associated with the correct device type after a published update.


Step 7: Writing the SmartThings App

We have one more piece of code in the project – the SmartApp itself. Navigate to “My SmartApps” and start a “New SmartApp” by clicking the button on the right. Give it a name, a description and a category (“My apps”). Click “Create” to continue coding the app.

Copy the smartApp code (see code section). Select “Save” and then click “Publish” to make the SmartApp available on your Smartthings iOS or Android (coming soon) app.

Note that we reference the SmartDevice type by name as “IrBridge” without the original space we originally used in the name “Ir Bridge”:

input "irDevice", "device.IrBridge"

The line allows us to show only devices of the type “Ir Bridge” as options to select in the SmartApp. The camelCasing used is a very specific: When referencing device types in smart apps, spaces are removed from the name. The first character and characters originally following a space are capitalized. All others are lowercase, regardless of the original capitalization.


Step 8: Enable the SmartApp

Within the SmartThings smartphone app, associate the new SmartApp with our new IR device and select the switches you wish to control. Then click “install.”

To program each of the buttons in the app, go to the tile details by clicking the gear in the corner of the tile. Select the big record button – it will turn red to show you are in record mode. Then click the tile (Play/Pause or B) you wish to control – it will turn yellow. Aim your remote at the ThingShield and press the button you want to learn. The ThingShield will now send that code to the SmartThings cloud and associate it with the button you chose in the SmartApp. The newly programmed button will turn green and the record button will go back to white.

The next time you push that button on your remote control, you will toggle the switch you associated with the button in the SmartApp.

Code

Arduino sketchC/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 definitionGroovy
/**
 *  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 codeGroovy
/**
 *  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()
    }
}

Comments

Similar projects you might like

Distance Measurement Vehicle via Websocket

Project tutorial by Matthew Lee

  • 2,676 views
  • 1 comment
  • 21 respects

Barbot: Cocktail Mixing Robot

Project tutorial by sidlauskas

  • 4,093 views
  • 2 comments
  • 29 respects

RING PONG

Project showcase by aerodynamics

  • 1,332 views
  • 0 comments
  • 6 respects

An Urban Plant Watering Solution

Project tutorial by James Yu

  • 3,846 views
  • 7 comments
  • 17 respects

Terminal Chat Client!

Project tutorial by Adam Cellon

  • 1,334 views
  • 3 comments
  • 9 respects
Add projectSign up / Login