Sometimes I feel, kids do this just to irritate us. Or may be, we always have a prioritized list of problems in our subconscious mind. When we solve the top one, the next one pops up and gets highlighted. It is indeed never ending.
Few extra hours of running TV will hardly make any difference in monthly bill. But it bothers me a lot. Conservation of energy is really important to me. Especially when I know, I am not using renewable energy.
In my last project (Kid's Eye Safe Smart TV - Part 1), I was successful in addressing my topmost concern. Reinforcement learning worked very well for them. But nowadays, I am noticing that TV continues to run for hours without any viewers. No one bothers to switch off the TV before going outdoor or moving to another room to play. May be that's why they are called Kids.
Let them be kids. Let's do something to trade off with this situation.
This is enhancement of my last project (Kid's Eye Safe Smart TV - Part 1). I have added one more sensor called PIR motion sensor (HC-SR501).
Dual Technology Occupancy Sensor (Ultrasonic sensor & Motion sensor) yields a very responsive and reliable solution to detect occupancy. PIR sensor detects the change of infrared radiations emitted by the subject in motion in its field of view. Perfect for my project to detect audience in front of TV.
Circuit built using ESP32 will read Ultrasonic Distance Sensor (HC-SR04) and PIR Motion Sensor (HC-SR501). Based on that reading, Micropython logic will decide when to pause or resume or switch off the TV by calling smart TV API (e.g. in my case Roku TV API to toggle Play/Pause
In this PoC, if viewer goes very close (within preconfigured threshold - 1 meter) to the TV or no viewer (no motion) detected for certain period of time (say 15 seconds), TV will be paused (switch off may be the preferable option in real life) automatically.
Tools,Technologies and Components used in this article
- Thonny, Python IDE for beginners
- MicroPython Firmware on the ESP32
- Roku TV External Control Protocol (ECP)
- MicroPython Libraries:
- Hardware Components:
- ESP-WROOM-32 Dev Kit
- HC-SR04 Ultrasonic Sensor
- HC-SR501 PIR Motion Sensor
- Oled display 128x64 - Optional
- 3.3v & 5v Breadboard Power Supply
- Breadboard Jumper Wires
- 1kΩ & 470Ω Resistors
- Less Smart TV (Roku)
Build the Circuit
Connect all the components as shown in the diagram below.
Schematic Diagram:Note: In case if you are wondering, why I have used "H" pin (3.3v) instead of VCC (+5v), please refer the following nice hack - Cheap Pyroelectric Infrared PIR Motion Sensor on 3.3v.
- Connect ESP32 development board to your computer.
- Open Thonny Python IDE.
- Select Run --> Select interpreter...
- Choose MicroPython (ESP32) as device and corresponding port.
- Upload following python libraries
- Upload the below "main.py" (pre-alpha version). For my Roku TV, Rest endpoint to simulate "Play/Pause" remote button is
import wifimgr from hcsr04 import HCSR04 from machine import Pin,I2C import ssd1306,time import urequests def init(): i2c = I2C(scl=Pin(19), sda=Pin(18)) oled = ssd1306.SSD1306_I2C(128, 64, i2c, 0x3c) hcsr04 = HCSR04(trigger_pin=32, echo_pin=35, echo_timeout_us=1000000) hcsr501Pin = Pin(26, Pin.IN) hcsr501Pin.irq(trigger=Pin.IRQ_FALLING, handler=interruptCallbackHandler) return oled, hcsr04 def interruptCallbackHandler(p): global motionDetected motionDetected = True global lastTimeMotionDetected lastTimeMotionDetected = time.time() print("$$$$Motion detected at %s" % lastTimeMotionDetected) def log(msg, x, y): oled.text(msg, x, y) oled.show() def clearDisplay(): oled.fill(0) def toggleTvPlayPause(): try: response = urequests.post("http://192.168.0.20:8060/keypress/play") print("API Response %s" % response.status_code) return response.status_code == 200 except: print("Error !!!") log("Error !!!", 0, 30) return False def anyoneWatching(): global motionDetected if motionDetected: # If no motion detected in next 15 secs if ((time.time() - lastTimeMotionDetected) > 15): motionDetected = False print("Not Watching at %s" % time.time()) return False else: print("Watching at %s" % time.time()) return True else: print(">>>Not Watching at %s" % time.time()) return False def play(): global isPaused if isPaused and toggleTvPlayPause(): isPaused = False def pause(): global isPaused if not isPaused and toggleTvPlayPause(): isPaused = True # Initialize OLED display, Distance & Motion sensor oled, distanceSensor = init() # Connect to WIfi log("Connecting...", 0, 0) wlan = wifimgr.get_connection() if wlan is None: log("No wifi !!!", 0, 20) print("Unable to connect to Wifi") else: log("Connected :-)", 0, 20) wifimgr.deactivate_ap() print("Deactivated AP mode.") time.sleep_ms(1000) prevDistance = -1 isPaused = False motionDetected = True lastTimeMotionDetected = time.time() while True: currDistance = int(distanceSensor.distance_cm()) time.sleep(1) if currDistance != prevDistance and motionDetected: print("Distance: %s cm" % currDistance) clearDisplay() log("Dist: %s cm" % currDistance,0, 0) prevDistance = currDistance if anyoneWatching(): if currDistance < 100: pause() else: play() else: pause() print("TV -> %s" % ("Pause" if isPaused else "Play")) log("TV -> %s" % ("Pause" if isPaused else "Play"), 0, 30) print("Motion -> %s" % ("Y" if motionDetected else "N")) log("Motion -> %s" % ("Y" if motionDetected else "N"), 0, 40) print("***************************")
Use the following method to configure interrupt for a Pin
Pin.irq(handler=None, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING), \*, priority=1, wake=None, hard=False)
I have added lots of print statements to capture the flow in console log for better understanding of demo video. Try to minimize the use of
interruptCallbackHandler (External Interrupt Handler), DO NOT use anything extra. It has to be simple and short as much as possible. Please refer Tips and recommended practices for writing interrupt handlers.
Now, I am feeling responsible towards mother planet The Earth.
Circuit In Action
If you have pet, then you can include ESP32-CAM to detect the type of audience (pet or human) and use that input to drive the logic accordingly.
All codes used in this post are available on Github: srccodes/smart-tv-occupancy-sensor.