Search This Blog

Saturday, October 2, 2010

Arduino IR remote TV sleep timer setter

Now that this project is finally up and running in field trials, I'm finally going to write it up.

The purpose of this project is automatically turn on the sleep timer of the television.

Story: My wife is an insomniac and stays up late every night watching TV.  She eventually falls asleep and leaves the TV on.  She NEVER remembers to set the sleep timer, plus the TiVo remote doesn't allow setting the TV sleep timer, and who knows where the original TV remote is most of the time.   We have DirecTV, and about 3 or 4 in the morning they do a download.  The TV goes black and quiet for a while, then comes back to life when the download is done.  This wakes me up every night and i have to get out of bed and turn off the TV because my wife as fallen asleep and lost the remote in the bed somewhere.   Arrrgh!

How it works:  Based on an Arduino board, it has an IR receiver ripped from an old broken TV, and an IR diode from a remote from a TV we don't have anymore.  The device sits on the TV stand, directly in front of the TV. When it detects IR traffic from the remote, it keeps resetting a timer.  If the timer expires, it assumes that the user has fallen asleep and is no longer controlling the TV.  The device then sends a remote control to the TV to set the sleep timer.   If the user doesn't notice (they are likely asleep), the sleep timer counts down and shuts off the TV.  You might ask why don't I just send a power command?  Problem is I can't really be sure the TV is on or off.  What if she actually turned the TV off before falling asleep.  It would be a disaster to have the device turn ON the TV.  Using the sleep timer is the perfect fail safe.  If the TV is off, the sleep command does nothing .  If the TV is on, it will count down and turn off.  Just takes a little longer than turning it off directly.

We have a SONY Bravia TV and use a DirecTV TiVo box primarily.  This works for those devices.  You will have to modify for your devices.

Arduino TV IR sleeper

The project is constructed of:
Connections:
The IR receivers have three pins.  Signal, Gnd, 5V.  I attached it to pin 9
int RECV_PIN = 9;                //IR detector module attached here
int led_green = 11; //led to indicate TV on & IR activity flickers green.  Connect the same way as the IR diode, but use a 2.2K resistor in series instead
int led_yellow = 13;          //led to indicated TV user is drowsy.  Same as led_green.

IR wiring
thanks to http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html for this drawing

I used the prototype shield, which is probably overkill but made a nice compact unit.  I bent the receiver and yellow/green LEDs so they pointed towards the user.  The IR tx diode is mounted on the back edge and points towards the TV.  I put on a reset switch because it is always nice to have a wave to recover.

This project proved way more difficult than i expected.  The device is both a reciever and a transmitter, and needed to detect IR transmission as an interrupt because I was running a timer from last activity.  Combining these three functions was tricky.  There was an IR library on Arduino playground http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1176098434 ,  that was based on delay(),.  The receiver worked just fine, but the transmitter failed to control the TV.  I experimented for hours and finally discovered that the timing of the bits in the Arduino were off.  I had to set the times much shorter than the spec, because the Arduino was timer and interrupts were slowing things down.  In the process of debugging and googling help, the http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html library bubbled up through google and i downloaded it.  This is much more solid and uses the PWM as the timing reference for transmit, so the TV liked it.  Luckily it supports the same devices that I had in my home, so I used it almost unmodified.   I had further troubles with the sending routine stopping the receiving routine, which the author admits to in his blog comments.  All you have to do is restart the receiver after you have transmitted.  I had further troubles with ambient IR triggering the receiver, and had to add a code filter to ignore anything that doesn't match a valid IR code from either the TiVo (NEC codes) or Sony.

So here is the code I ended up with.  you need to download the IR library and put it in your libraries folder.
This code has debugging to the serial monitor included, which helps you understand what is happening.

Code follows....

/*
// code to detect IR remote activity from a user
// When it ceases for a certain length of time
// Sleep timer code on the TV is sent to automatically turn off the TV
//
// Works for Sony TV's.  Could be modified for other TV's.  Detects TiVo remote activity (NEC)
// Ignores some other remotes on purpose to reduce stray noise
//
// Copyright 2010 Tom Wilson
*
 // note this code uses the sleep function, because the power code is a toggle.  I don't know
 // in advance if the TV is really on or off.  Using the sleep, we won't ever accidentally
 // turn the TV on when it was already off

//this version removes the LCD and uses the ken shirriff IR library for both rx & tx.
// Requires the tx diode to be attached to PIN3!!  Was on pin 10 in previous hardware.
// pin 3 can't be used with LCD display version anymore.  But it looks like
// Ken's code relies on a specific PWM output and timer, and it is too tricky to change.
 *
 * IR library and basic IR code from here:
 * IRremote: IRrecvDemo - demonstrates receiving IR codes with IRrecv
 * An IR detector/demodulator must be connected to the input RECV_PIN.
 * Version 0.1 July, 2009
 * Copyright 2009 Ken Shirriff
 * http://arcfn.com
  */

#include <IRremote.h>

//state definitions
#define TV_OFF 0     //no IR activity, TV is probably off
#define TV_ON  1     //IR activity, somebody is watching
#define DROWSY 2     //Was IR activity but it stopped.  User may be asleep

//time constants in ms
#define AWAKE_TIME  15000
#define DROWSY_TIME 15000

//IR transmit LED is hardwired to pin 3 for PWM and timer reasons, with a 200ohm resistor.
int RECV_PIN = 9;                //IR detector module attached here
int led_green = 11; //led to indicate TV on & IR activity flickers green
int led_yellow = 13;             //led to indicate drowsy state
int STATE = TV_OFF;              //variable to hold the mode
long lastIRMillis = 0;           //Time of last IR event

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

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
  pinMode(led_green, OUTPUT); //TV ON and IR activity LED
  pinMode(led_yellow, OUTPUT);          //This shows drowsy mode and IR sending
  digitalWrite(led_green, HIGH); //not activity yet
  digitalWrite(led_yellow, HIGH);       //not drowsy yet
  delay(2000);
  STATE = TV_OFF;
  Serial.println("Hello!  TV IR Sleeper is Ready");
  Serial.println("TV off");
  delay(500);
  digitalWrite(led_green, LOW);            //not activity yet
  digitalWrite(led_yellow, LOW);            //not drowsy yet

}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.println("");
    Serial.println(results.value, HEX);
    dump(&results);
    // always go to TV_ON state if IR activity was detected
    // this first if statement helps filter noise and false triggers from ambient IR
    if( results.value != 0) {
      STATE = TV_ON;
      Serial.println("TV on");
      lastIRMillis = millis();
      digitalWrite(led_yellow, LOW);
      digitalWrite(led_green, LOW);  //blink the green LED to show IR reception
      delay(200);
      digitalWrite(led_green, HIGH);
    }//if
    irrecv.resume(); // Receive the next value
  } else {
    delay(100);
    switch (STATE){
      case TV_OFF:
           break;
      case TV_ON:
          if (millis() - lastIRMillis > AWAKE_TIME) {
            //send a sleep timer set command and go to drowsy state
            STATE = DROWSY;
            Serial.println("Getting drowsy");
            digitalWrite(led_yellow, HIGH);
            digitalWrite(led_green, LOW);
            lastIRMillis = millis();
            sendIRSleep();
            irrecv.enableIRIn(); // Restart the receiver.  Code will hang if you don't do this after a send
          }//if    
          break;
      case DROWSY:
          if (millis() - lastIRMillis > DROWSY_TIME) {
              //user didn't wake up since sleep was sent, tv is off by now
              STATE = TV_OFF;
              Serial.println("TV off");
              lastIRMillis = millis();
              digitalWrite(led_yellow, LOW);
              digitalWrite(led_green, LOW);
              //toss out a mute for good luck in case the sleep didn't work
              for (int i = 0; i < 5; i++) {
                irsend.sendSony(0x290, 12); // Sony TV mute code
                delay(100);
              }
              irrecv.enableIRIn(); // Restart the receiver.  Code will hang if you don't do this after a send
          }//if
          break;
    }//switch      
  }//else results  
}//main


//send a sleep timer set command
// this is a double sleep burst with a pause between to set sleep mode and then set the timer
// repeat this code a lot to be sure the TV gets it
// code 2704 - sony off   101010010000  0xa90
// code 1744 - sony sleep 011011010000  0x6d0
// code 656 - sony mute   001010010000  0x290
void sendIRSleep() {
   //flicker the yellow LED to show transmission
   digitalWrite(led_yellow, HIGH);
   for (int i = 0; i < 20; i++) {
      irsend.sendSony(0x6d0, 12); // Sony TV sleep code (activates sleep timer)
      delay(100);
   }
   delay(1000);
   digitalWrite(led_yellow, LOW);
   for (int i = 0; i < 20; i++) {
      irsend.sendSony(0x6d0, 12); // Sony TV sleep code (sets the time)
      delay(100);
   }
   delay(200);
   digitalWrite(led_yellow, HIGH);
}

// THis additional function is for debugging
// 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.println("Could not decode message");
  }
  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: ");
    }
    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("");
}

No comments:

Post a Comment