Introduction: Home Entertainment Auto Power-down (HEAP Controller)
I have 6 year old twins that seem to never remember to switch the TV off before bouncing off to do something else. Since I was looking for a project to get me started on Arduino, I decided this was a great opportunity! I set off gathering some requirements to get me started:
1. It needs to work on the basis of movement detection.
2. It needs to be able to switch off at least the TV and Audio systems when no movement is detected within a set period of time.
3. Controlling the TV and the Audio systems should be done via Infra red blasters.
4. It should require no power supply other than 5 volt from a USB port on the TV.
5. There should be some kind of visual and audible warning before it shuts down the TV & audio systems.
6. There should be an override, either a switch or a time window with a visual indication.
7. It should be housed in a small black box (Wife: as small as possible!)
Looking at the requirements, I was convinced that a small Arduino UNO (arduino.cc) in combination with a small PIR detector and a couple of IR blasters wold suffice as basis. Some experimenting and a small proof of concept proved it.
1. It needs to work on the basis of movement detection.
2. It needs to be able to switch off at least the TV and Audio systems when no movement is detected within a set period of time.
3. Controlling the TV and the Audio systems should be done via Infra red blasters.
4. It should require no power supply other than 5 volt from a USB port on the TV.
5. There should be some kind of visual and audible warning before it shuts down the TV & audio systems.
6. There should be an override, either a switch or a time window with a visual indication.
7. It should be housed in a small black box (Wife: as small as possible!)
Looking at the requirements, I was convinced that a small Arduino UNO (arduino.cc) in combination with a small PIR detector and a couple of IR blasters wold suffice as basis. Some experimenting and a small proof of concept proved it.
Step 1: Parts List
This is the parts list I ended up with:
1 x Arduino UNO
1 x PIR Motion Sensor for Arduino
2 x Infrared blasters (transmitter LED's)
2 x 3.5mm female stereo audio headphone jacks (to suite IR blasters)
1 x red 5mm diffused LED
1 x green 5mm diffused LED
1 x yellow 5mm diffused LED
1 x miniature speaker
1 x Realtime Clock module (RTC)
1 x Arduino IR receiver module
The Infrared blasters were salvaged from some old PC TV tuner cards. They were complete with IR LED, sticker, cable and 3.5mm plug. The LED's and miniature speaker were salvaged from some old pc spares.
I needed the Infrared receiver module only for reading the IR codes from my TV and Audio remotes. I used the Arduino UNO board in combination with the IR receiver module and a small bit of code to read out the IR codes to the Arduino serial monitor. More on this in step 2.
1 x Arduino UNO
1 x PIR Motion Sensor for Arduino
2 x Infrared blasters (transmitter LED's)
2 x 3.5mm female stereo audio headphone jacks (to suite IR blasters)
1 x red 5mm diffused LED
1 x green 5mm diffused LED
1 x yellow 5mm diffused LED
1 x miniature speaker
1 x Realtime Clock module (RTC)
1 x Arduino IR receiver module
The Infrared blasters were salvaged from some old PC TV tuner cards. They were complete with IR LED, sticker, cable and 3.5mm plug. The LED's and miniature speaker were salvaged from some old pc spares.
I needed the Infrared receiver module only for reading the IR codes from my TV and Audio remotes. I used the Arduino UNO board in combination with the IR receiver module and a small bit of code to read out the IR codes to the Arduino serial monitor. More on this in step 2.
Step 2: Reading the TV & Audio System IR Codes
Since my design revolves around powering down the TV and Audio systems using infrared I needed to know which codes exactly to transmit to the various peripherals. I decided to use the Arduino UNO and a small IR receiver module to simply print out the codes in the Arduino program serial interface.
Using the IRremote Arduino library (http://github.com/shirriff/Arduino-IRremote) in combination with the IR receiver module temporarily wired to the Arduino UNO board I was able to dump the power down codes for both the television and audio systems. I suggest you look at the IRrecord example which formed the basis of my recording code.
Using the IRremote Arduino library (http://github.com/shirriff/Arduino-IRremote) in combination with the IR receiver module temporarily wired to the Arduino UNO board I was able to dump the power down codes for both the television and audio systems. I suggest you look at the IRrecord example which formed the basis of my recording code.
Step 3: Mounting Everything
Step 4: Wiring It All Up
Step 5: Arduino Code
For the IR communications I used the IRemote library library for Arduino. To download from github (http://github.com/shirriff/Arduino-IRremote), click on the "Downloads" link in the upper right, click "Download as zip", and get a zip file. Unzip it and rename the directory shirriff-Arduino-IRremote-nnn to IRremote
For the DS1302 RTC I used the following library for Arduino: https://github.com/msparks/arduino-ds1302
To produce the beeper sound output I use the standard SPI.h Arduino library. For details see http://arduino.cc/en/Reference/SPI
#include <stdio.h>
#include <string.h>
#include <DS1302.h>
#include <SPI.h>
#include <IRremote.h>
/* Set the appropriate digital I/O pin connections for the RTC module */
uint8_t CE_PIN = 4;
uint8_t IO_PIN = 10;
uint8_t SCLK_PIN = 8;
#define RTC5vPin 5 // this pin will be used to provide +5v to the RTC
#define RTCgndPin 6 // this pin will be used to provide GND to the RTC
#define PIEZO_PIN 11
/* Create buffers */
char buf[50];
char day[10];
IRsend irsend;
/* Create a RTC DS1302 object */
DS1302 rtc(CE_PIN, IO_PIN, SCLK_PIN);
// SAMSUNG TV Power ON/OFF codes (Change this to match your remote codes)
unsigned int S_pwr[68]={ 4400,4550,500,1750,500,1750,500,1750,450,650,500,600,500,600,500,650,500,600,500,1750,450,1750,500,1750,500,600,500,600,500,650,500,600,500,600,500,650,500,1750,450,650,500,600,500,600,500,650,500,600,500,600,500,1750,500,600,500,1750,500,1750,500,1750,450,1750,500,1750,500,1750,450};
//LG Surround Sound (Change this to match your remote codes)
unsigned int LG_pwr[68]={ 4350,4550,500,600,500,600,500,1750,450,1750,500,600,500,1750,450,600,500,650,450,650,500,600,500,1750,450,1750,450,650,500,1700,500,600,500,650,450,650,450,1800,450,1750,450,1750,450,1750,500,600,500,600,500,600,500,1750,500,600,500,600,500,600,500,650,450,1750,500,1750,450,1750,450};
int calibrationTime = 30; //the time we give the sensor to calibrate (10-60 secs according to the datasheet)
long unsigned int lowIn; //the time when the sensor outputs a low impulse
long unsigned int pause = 3000; //the amount of milliseconds the sensor has to be low before we assume all motion has stopped
boolean override = false;
boolean lockLow = true;
boolean takeLowTime;
int pirPin = 3; // The digital pin connected to the PIR sensor's output
int ledPin = 13; // Onboard LED is wired to an external RED LED
int maxSecs = 300; // Max time for no movement before we switch the TV off
// Change this to suit your own needs (default 5 minutes)
int overrideStartHour = 19; // Which hour to start the override window
int overrideEndHour = 23; // Which hour to end the override window
int currentSecs = 0; // Global counter
//GREEN and YELLO LED pins
int greenLedPin = 12;
int yellowLedPin = 2;
int yellowState = 0;
void setup(){
Serial.begin(9600);
pinMode(RTC5vPin, OUTPUT); // sets Pin for HIGH output
digitalWrite(RTC5vPin, HIGH); // turn pin on (5v) to power RTC
pinMode(RTCgndPin, OUTPUT); // sets Pin for output
digitalWrite(RTCgndPin, LOW); // turn pin on LOW (GND) to power RTC
pinMode(11, OUTPUT); // sets Pin 11 for output
digitalWrite(11, LOW); // turn pin 11 on LOW (GND) to power RTC
pinMode(pirPin, INPUT); // initialize the PIR state detection pin as input
pinMode(ledPin, OUTPUT); // initialize the onboard LED pin as input
digitalWrite(pirPin, LOW); // start off with no movement
pinMode(greenLedPin,OUTPUT);
digitalWrite(greenLedPin, LOW);
pinMode(yellowLedPin,OUTPUT);
digitalWrite(yellowLedPin, LOW);
yellowState = 0;
/*
Initialize a new chip by turning off write protection and clearing the
clock halt flag. These methods needn't always be called. See the DS1302
datasheet for details.
Un-comment the code below to set up a new RTC chip. Once run you need to
comment the section of code again.
*/
//rtc.write_protect(false);
//rtc.halt(false);
//Time t(2013, 7, 7, 14, 45, 0, 0); // Create a new time object to set the date and time
//rtc.time(t); // Set the time and date on the chip
//rtc.write_protect(true);
//give the PIR sensor some time to calibrate
Serial.print("calibrating sensor ");
for(int i = 0; i < calibrationTime; i++){
Serial.print(".");
delay(1000);
}
Serial.println(" done");
Serial.println("SENSOR ACTIVE");
delay(50);
// reset the global counter
currentSecs = maxSecs;
StartupBeep();
}
void StartupBeep(){
tone(PIEZO_PIN,4978,2000);
delay(100);
noTone(PIEZO_PIN);
}
////////////////////////////
//LOOP
void loop(){
/* Get the current time and date from the chip */
Time t = rtc.time();
/* Name the day of the week */
memset(day, 0, sizeof(day)); /* clear day buffer */
/* Format the time and date and insert into the temporary buffer */
snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
day,
t.yr, t.mon, t.date,
t.hr, t.min, t.sec);
/* Print the formatted string to serial so we can see the time */
Serial.print(buf);
Serial.print(" - ");
Serial.print(" (hr: ");
Serial.print(t.hr);
Serial.print(") ");
if (t.hr >= overrideStartHour && t.hr <= overrideEndHour) {
override = true;
Serial.println("DISABLED");
} else {
Serial.println("ENABLED");
override = false;
}
if (override)
{
digitalWrite(greenLedPin, HIGH);
}
else
{
digitalWrite(greenLedPin, LOW);
}
if (!override) {
currentSecs = (currentSecs - 1);
Serial.println(currentSecs);
if (currentSecs <= 30) {
if (yellowState == 1) {
digitalWrite(yellowLedPin, HIGH);
yellowState = 0;
noTone(PIEZO_PIN);
}
else
{
digitalWrite(yellowLedPin, LOW);
yellowState = 1;
if (currentSecs >= 25) {
Serial.println("Beeping!");
tone(PIEZO_PIN,4978,1000);
}
}
}
if (currentSecs <= 0) {
//max time for no movement reached, switch off TV!
Serial.println("Swithcing off TV!");
toggleSamsungTV();
}
}
detectmotion();
delay(1000);
}
void check_time() {
/* Get the current time and date from the chip */
Time t = rtc.time();
/* Name the day of the week */
memset(day, 0, sizeof(day)); /* clear day buffer */
/* Format the time and date and insert into the temporary buffer */
snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
day,
t.yr, t.mon, t.date,
t.hr, t.min, t.sec);
/* Print the formatted string to serial so we can see the time */
Serial.print(buf);
Serial.print(" - ");
if (t.hr > overrideStartHour && t.hr < overrideEndHour) {
override = true;
Serial.println("DISABLED");
} else {
Serial.println("ENABLED");
override = false;
}
}
void detectmotion() {
if(digitalRead(pirPin) == HIGH){
digitalWrite(ledPin, HIGH); //the led visualizes the sensors output pin state
currentSecs = maxSecs;
digitalWrite(yellowLedPin, LOW);
yellowState = 0;
if(lockLow){
//makes sure we wait for a transition to LOW before any further output is made:
lockLow = false;
Serial.println("---");
Serial.print("motion detected at ");
Serial.print(millis()/1000);
Serial.println(" sec");
delay(50);
}
takeLowTime = true;
}
if(digitalRead(pirPin) == LOW){
digitalWrite(ledPin, LOW); //the led visualizes the sensors output pin state
if(takeLowTime){
lowIn = millis(); //save the time of the transition from high to LOW
takeLowTime = false; //make sure this is only done at the start of a LOW phase
}
//if the sensor is low for more than the given pause,
//we assume that no more motion is going to happen
if(!lockLow && millis() - lowIn > pause){
//makes sure this block of code is only executed again after
//a new motion sequence has been detected
lockLow = true;
Serial.print("motion ended at "); //output
Serial.print((millis() - pause)/1000);
Serial.println(" sec");
delay(50);
}
}
}
void toggleSamsungTV() {
// switch the TV off with the IR blaster
// repeat the transmission 3 times to be sure!
for (int i = 0; i < 3; i++) {
irsend.sendRaw(S_pwr,68,38);
Serial.println("Sent SAMSUNG POWER ON/OFF");
irsend.sendRaw(LG_pwr,68,38);
Serial.println("Sent LG POWER ON/OFF");
delay(40);
}
// reset the global counter
currentSecs = maxSecs;
}
For the DS1302 RTC I used the following library for Arduino: https://github.com/msparks/arduino-ds1302
To produce the beeper sound output I use the standard SPI.h Arduino library. For details see http://arduino.cc/en/Reference/SPI
#include <stdio.h>
#include <string.h>
#include <DS1302.h>
#include <SPI.h>
#include <IRremote.h>
/* Set the appropriate digital I/O pin connections for the RTC module */
uint8_t CE_PIN = 4;
uint8_t IO_PIN = 10;
uint8_t SCLK_PIN = 8;
#define RTC5vPin 5 // this pin will be used to provide +5v to the RTC
#define RTCgndPin 6 // this pin will be used to provide GND to the RTC
#define PIEZO_PIN 11
/* Create buffers */
char buf[50];
char day[10];
IRsend irsend;
/* Create a RTC DS1302 object */
DS1302 rtc(CE_PIN, IO_PIN, SCLK_PIN);
// SAMSUNG TV Power ON/OFF codes (Change this to match your remote codes)
unsigned int S_pwr[68]={ 4400,4550,500,1750,500,1750,500,1750,450,650,500,600,500,600,500,650,500,600,500,1750,450,1750,500,1750,500,600,500,600,500,650,500,600,500,600,500,650,500,1750,450,650,500,600,500,600,500,650,500,600,500,600,500,1750,500,600,500,1750,500,1750,500,1750,450,1750,500,1750,500,1750,450};
//LG Surround Sound (Change this to match your remote codes)
unsigned int LG_pwr[68]={ 4350,4550,500,600,500,600,500,1750,450,1750,500,600,500,1750,450,600,500,650,450,650,500,600,500,1750,450,1750,450,650,500,1700,500,600,500,650,450,650,450,1800,450,1750,450,1750,450,1750,500,600,500,600,500,600,500,1750,500,600,500,600,500,600,500,650,450,1750,500,1750,450,1750,450};
int calibrationTime = 30; //the time we give the sensor to calibrate (10-60 secs according to the datasheet)
long unsigned int lowIn; //the time when the sensor outputs a low impulse
long unsigned int pause = 3000; //the amount of milliseconds the sensor has to be low before we assume all motion has stopped
boolean override = false;
boolean lockLow = true;
boolean takeLowTime;
int pirPin = 3; // The digital pin connected to the PIR sensor's output
int ledPin = 13; // Onboard LED is wired to an external RED LED
int maxSecs = 300; // Max time for no movement before we switch the TV off
// Change this to suit your own needs (default 5 minutes)
int overrideStartHour = 19; // Which hour to start the override window
int overrideEndHour = 23; // Which hour to end the override window
int currentSecs = 0; // Global counter
//GREEN and YELLO LED pins
int greenLedPin = 12;
int yellowLedPin = 2;
int yellowState = 0;
void setup(){
Serial.begin(9600);
pinMode(RTC5vPin, OUTPUT); // sets Pin for HIGH output
digitalWrite(RTC5vPin, HIGH); // turn pin on (5v) to power RTC
pinMode(RTCgndPin, OUTPUT); // sets Pin for output
digitalWrite(RTCgndPin, LOW); // turn pin on LOW (GND) to power RTC
pinMode(11, OUTPUT); // sets Pin 11 for output
digitalWrite(11, LOW); // turn pin 11 on LOW (GND) to power RTC
pinMode(pirPin, INPUT); // initialize the PIR state detection pin as input
pinMode(ledPin, OUTPUT); // initialize the onboard LED pin as input
digitalWrite(pirPin, LOW); // start off with no movement
pinMode(greenLedPin,OUTPUT);
digitalWrite(greenLedPin, LOW);
pinMode(yellowLedPin,OUTPUT);
digitalWrite(yellowLedPin, LOW);
yellowState = 0;
/*
Initialize a new chip by turning off write protection and clearing the
clock halt flag. These methods needn't always be called. See the DS1302
datasheet for details.
Un-comment the code below to set up a new RTC chip. Once run you need to
comment the section of code again.
*/
//rtc.write_protect(false);
//rtc.halt(false);
//Time t(2013, 7, 7, 14, 45, 0, 0); // Create a new time object to set the date and time
//rtc.time(t); // Set the time and date on the chip
//rtc.write_protect(true);
//give the PIR sensor some time to calibrate
Serial.print("calibrating sensor ");
for(int i = 0; i < calibrationTime; i++){
Serial.print(".");
delay(1000);
}
Serial.println(" done");
Serial.println("SENSOR ACTIVE");
delay(50);
// reset the global counter
currentSecs = maxSecs;
StartupBeep();
}
void StartupBeep(){
tone(PIEZO_PIN,4978,2000);
delay(100);
noTone(PIEZO_PIN);
}
////////////////////////////
//LOOP
void loop(){
/* Get the current time and date from the chip */
Time t = rtc.time();
/* Name the day of the week */
memset(day, 0, sizeof(day)); /* clear day buffer */
/* Format the time and date and insert into the temporary buffer */
snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
day,
t.yr, t.mon, t.date,
t.hr, t.min, t.sec);
/* Print the formatted string to serial so we can see the time */
Serial.print(buf);
Serial.print(" - ");
Serial.print(" (hr: ");
Serial.print(t.hr);
Serial.print(") ");
if (t.hr >= overrideStartHour && t.hr <= overrideEndHour) {
override = true;
Serial.println("DISABLED");
} else {
Serial.println("ENABLED");
override = false;
}
if (override)
{
digitalWrite(greenLedPin, HIGH);
}
else
{
digitalWrite(greenLedPin, LOW);
}
if (!override) {
currentSecs = (currentSecs - 1);
Serial.println(currentSecs);
if (currentSecs <= 30) {
if (yellowState == 1) {
digitalWrite(yellowLedPin, HIGH);
yellowState = 0;
noTone(PIEZO_PIN);
}
else
{
digitalWrite(yellowLedPin, LOW);
yellowState = 1;
if (currentSecs >= 25) {
Serial.println("Beeping!");
tone(PIEZO_PIN,4978,1000);
}
}
}
if (currentSecs <= 0) {
//max time for no movement reached, switch off TV!
Serial.println("Swithcing off TV!");
toggleSamsungTV();
}
}
detectmotion();
delay(1000);
}
void check_time() {
/* Get the current time and date from the chip */
Time t = rtc.time();
/* Name the day of the week */
memset(day, 0, sizeof(day)); /* clear day buffer */
/* Format the time and date and insert into the temporary buffer */
snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
day,
t.yr, t.mon, t.date,
t.hr, t.min, t.sec);
/* Print the formatted string to serial so we can see the time */
Serial.print(buf);
Serial.print(" - ");
if (t.hr > overrideStartHour && t.hr < overrideEndHour) {
override = true;
Serial.println("DISABLED");
} else {
Serial.println("ENABLED");
override = false;
}
}
void detectmotion() {
if(digitalRead(pirPin) == HIGH){
digitalWrite(ledPin, HIGH); //the led visualizes the sensors output pin state
currentSecs = maxSecs;
digitalWrite(yellowLedPin, LOW);
yellowState = 0;
if(lockLow){
//makes sure we wait for a transition to LOW before any further output is made:
lockLow = false;
Serial.println("---");
Serial.print("motion detected at ");
Serial.print(millis()/1000);
Serial.println(" sec");
delay(50);
}
takeLowTime = true;
}
if(digitalRead(pirPin) == LOW){
digitalWrite(ledPin, LOW); //the led visualizes the sensors output pin state
if(takeLowTime){
lowIn = millis(); //save the time of the transition from high to LOW
takeLowTime = false; //make sure this is only done at the start of a LOW phase
}
//if the sensor is low for more than the given pause,
//we assume that no more motion is going to happen
if(!lockLow && millis() - lowIn > pause){
//makes sure this block of code is only executed again after
//a new motion sequence has been detected
lockLow = true;
Serial.print("motion ended at "); //output
Serial.print((millis() - pause)/1000);
Serial.println(" sec");
delay(50);
}
}
}
void toggleSamsungTV() {
// switch the TV off with the IR blaster
// repeat the transmission 3 times to be sure!
for (int i = 0; i < 3; i++) {
irsend.sendRaw(S_pwr,68,38);
Serial.println("Sent SAMSUNG POWER ON/OFF");
irsend.sendRaw(LG_pwr,68,38);
Serial.println("Sent LG POWER ON/OFF");
delay(40);
}
// reset the global counter
currentSecs = maxSecs;
}
Step 6: Using the Completed Controller
Once the controller is completely wired up, it should boot up once you connect the Arduino USB port to a USB port on your television set. After approximately 40 seconds and a short audible beep the PIR sensor will start monitoring the room for any movement and flash the RED LED accordingly. The PIR I installed has a reach of about 20 feet which is well within the limits of my living room.
LED status information:
RED LED flashes - Movement detected
GREEN LED on permanently - Override window is active, otherwise it is OFF
YELLOW LED flashing - Automatic power-down in 30 seconds unless movement is detected!
When the countdown timer reaches between the 30 and 20 second mark an audible beep will be heard 3 times to warn of a pending power-down.
LED status information:
RED LED flashes - Movement detected
GREEN LED on permanently - Override window is active, otherwise it is OFF
YELLOW LED flashing - Automatic power-down in 30 seconds unless movement is detected!
When the countdown timer reaches between the 30 and 20 second mark an audible beep will be heard 3 times to warn of a pending power-down.
Step 7: What's Next?
Obviously after using the controller with remarkable success for a while, I started thinking of ways to improve it even further. Currently setting the override window and timeout period requires a change in the Arduino sketch. I'd like to make this adjustable on the unit itself. This could be achieved by using a small LCD and some very small push buttons. Watch this space.....