Thursday, July 14, 2016

Home Automation via Bluetooth Proximity Awareness


-----
Bluetooth devices are everywhere and so are Raspberry PI computers.  Here we show how to combine the two for some home automation.
-----
If you are not interested in duplicating the build maybe this summary of what the rig does will change your mind:  Basically this allows any discoverable Bluetooth device (cell phone, smart watch, automobile, tablet, PC, toothbrush, etc.) to control GPIO outputs on the Raspberry PI.  For our use, the Raspberry PI monitors if one of the Bluetooth devices is found in range after being absent for a predetermined time.  If 'yes', turn ON some lighting in house so there is no fumbling around in the dark.  Then, turn the lights back OFF after a predetermined time.  It is easy to modify the Python source for other creative applications.

In addition to controlling the lights, the Bluetooth device "IN/OUT" status and other statistics are logged to the terminal and a Google Drive Sheet via IFTTT.
-----
A while back we did a proof of concept.  In the video above the blue LED turns ON or OFF depending on if the Raspberry PI (with BT-LE dongle) senses a Bluetooth signal from the iPad.  iPad Bluetooth OFF then blue LED OFF.  iPad Bluetooth ON then blue LED on.  From this point it is a trivial task to switch a relay to control a brighter heavier load LED strip in place of the low current blue LED.
 -----
And here is a short video of the completed rig turning ON and OFF a LED strip light.  Note we are toggling GPIO13 (the relay control line) via WebIOPI.  It works the same with the recognized Bluetooth devices, but it would make for a long and boring video to show that.
-----
Materials:
 - Raspberry PI Model B
- WiFi USB dongle (already on board the Raspberry PI3 Model B)
- Bluetooth USB dongle (already on board the Raspberry PI3 Model B)
- Switching relay to control the light Only 1-channel needed, but more allows for expansion.  Spend the extra $1 for a unit like the one in the link with opto isolation built in to help protect your RasPI.
- LED light strip Switch low voltage for safety.  This light is what the relay turns on to light up the room.  Many examples in the link.
- Power supply for the RasPI, wire, maybe a USB hub, a few other very obvious things.
-----
Connect it up like this:

-----
When you get the rig together it will look something like this.  The RasPI pictured has a few extra 'things' attached to it to support our LAN status and "Internet Up" status monitoring projects (in addition to a few other chores).

-----
Now for setting up the software...  The program has comments all throughout the code it to try to make things straightforward.  It is optional, but if you want to log the status of your Bluetooth devices to your Google Drive you need to do a few things first.
- Create a Google account if you don't have one.
- Create an IFTTT account you don't have one.
       - Connect the Google Drive channel and the Maker Channel.
       - Follow the IFTTT directions on creating a Maker Channel trigger to update a Goggle Drive file.
- Establish the Bluetooth devices that will be tracked.  Thanks to prb3333 the utilities and procedures to do this are on their instructables.com site.  This project leverages their work and credits them in the source code below.
- Now, copy the source code below and paste it into your favorite Raspbery PI Python editor.  If you are logging to Goggle Drive with IFTTT adjust the Maker Channel 'secret code' to match the one assigned to your account.  If you decided not to log to Goggle Drive then comment out the IFTTT Maker Channel call statements in the code.
- Enter the Bluetooth addresses you want to track into the variables of the Python source. 
- Adjust the scan variables, number of tracked devices, ON time, etc. variables to meet your application needs.
-------
All the wires connected?  Python script in the RasPI?  Variables adjusted to meet your needs?  Good job!  Save and run the script to enjoy the automation.

The logs to Goggle Drive and the RasPI terminal will look something like this:

 -----
Here is the Python source for the Raspberry PI:

# Bluetooth Device Data Logger and Light Turner Oner
# Logs BT Activity to Google Drive Sheet via IFTTT.Com Maker Channel
#
# WhiskeyTangoHotel.Com
# Based on: http://www.instructables.com/id/Raspberry-Pi-Bluetooth-InOut-Board-or-Whos-Hom/
# JULY 2016

# /home/pi/RasPI/Programs-RasPI/BTLogger/Array_BT_AutoLight.py

#!/usr/bin/python

import bluetooth
import requests # needed to get https request to IFTTT web page
import time
import datetime # to allow timestamp math
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)

Device_name = [1,2,3,4,5,6,7,8,9] # Friendly name of the BT device
Device_ID = [1,2,3,4,5,6,7,8,9]   # BT identifier for device as xx:xx:xx:xx:xx:xx
Last_In = [1,2,3,4,5,6,7,8,9] #Time of last check in.
Last_Out = [1,2,3,4,5,6,7,8,9] #Time of last check out
Device_Status = [1,2,3,4,5,6,7,8,9] # String info on if device is IN or OUT.
Device_Delta = [1,2,3,4,5,6,7,8,9]  # Tracks the time a device has had the same BT check status

# BT device library (Use discovery_BT.py to find devices in discover mode)
# Set up the devices.  Change "Number_of_devices" var below to match.
Device_name[1] = "My iPhone 6S"
Device_ID[1] = "xx:xx:xx:xx:xx:xx"
Device_name[2] = "My Ford Edge Sync"
Device_ID[2] = "xx:xx:xx:xx:xx:xx"

# Program Control variables
Number_of_devices = 2 # this sets the value for the arrays loops in the program
Scan_interval = 30 # time in secs between BT scans.  Too fast will log phantom quick drops; 30 secs is good.
Noise_Filter = 2  # Only log deltas over xx min. Don't be confused by IN/OUT aliasing (worse as # gets larger)
Power_On_Treshold = 30 # Activate relay if device was OUT over xx minutes
Desired_relay_on_time = 10 # Keep the relay activated for xx minutes
# ~~~~~~~~~~~~~~~

# to use Raspberry Pi board pin numbers
GPIO.setmode(GPIO.BOARD)
# set up GPIO output channel
# I/O 11 = Blinks to confirm prog running
#     13 = Driving the relay
# COLORS TO IO PINS (POS), GND TO NEG
GPIO.setup(11, GPIO.OUT)
GPIO.setup(13, GPIO.OUT)

# Initialize some program variables
changenum = 0 # tracking number of BT status changes.
Relay_On_Time = datetime.datetime.now() # datetime the relay was activated
Number_of_devices = Number_of_devices + 1 # I didn't want to start with array[0]

for jj in range (1, Number_of_devices):
    Device_Status[jj] = " " # Hold the string value "IN" or "OUT" 
    Last_In[jj] = datetime.datetime.now()
    Last_Out[jj] = datetime.datetime.now()
    Device_Delta[jj] = 0 # Time In or Out for each device in minutes.
   
# Setup IFFT keys
MAKER_SECRET_KEY = "1234abcdefg-l234abc12"  #this is your IFTTT secret key
   
print " "
print "Testing LED/Relay/Light..."
for jj in range (0, 5): # blinkie self test LED
    GPIO.output(11,GPIO.HIGH)
    GPIO.output(13, GPIO.HIGH)
    time.sleep(.5)

    GPIO.output(11,GPIO.LOW)
    GPIO.output(13, GPIO.LOW)
    time.sleep(.5)
print "End LED/Relay/Light test."
print " "

print "Preparing for 1st run..."
print "Writing file headers to Google Drive Sheet..."
print " "

# Write header lines for Google Drive file and do some screen prints
url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "*"
res = requests.get(url)
time.sleep(.250)
url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "* Run Conditions are:"
res = requests.get(url)
time.sleep(.250)
url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "* Devices: " + str(Number_of_devices-1) + "&value2=" + "Scan Interval: " + str(Scan_interval) + " secs." + "&value3=" + "Noise Filter: " + str(Noise_Filter) + " mins."
res = requests.get(url)
time.sleep(.250)
url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "* Activate ON thresold: " + str(Power_On_Treshold) + " mins" + "&value2=" + "ON for: " + str(Desired_relay_on_time) + " mins."
res = requests.get(url)
#print str(res) + " is web request result for BT device: "   #used only for debug

Status_bar2 = "       Scan Interval:" + str(Scan_interval) + " secs   |  Noise Filter: " + str(Noise_Filter) + " mins."
Status_bar3 = "Activate ON thresold: " + str(Power_On_Treshold) +  " mins  |        ON for: " + str(Desired_relay_on_time) + " mins."
print Status_bar2
print Status_bar3
print "Tracking the following devices:"
print "--------------------------------------------"

t0 = datetime.datetime.now()  # for timestamp math. t0 is the time the program run started
First_run = 1   # always log intitial status for 1st run
Log_Deactivated = 0
runtime = datetime.datetime.now() - t0   # track how long the program has been running.

while True:  # Main loop forever, and ever...
    # First check to see if the relay should be turned off   
    Should_relay_be_off = datetime.datetime.now() - Relay_On_Time
    Should_relay_be_off = round(Should_relay_be_off.seconds/60.0,1)  # in minutes
    if (Should_relay_be_off >= Desired_relay_on_time):
        GPIO.output(13,GPIO.LOW) # Turn OFF the relay
        if (Log_Deactivated == 1):
            Log_Deactivated = 0
            url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "* Relay DEACTIVATED."
            res = requests.get(url)
            print " "
            print "   Relay DEACTIVATED " + " at " + time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
       
    for check_device in range (1, Number_of_devices):
        # Check to see if BT device is in range
        result = bluetooth.lookup_name(Device_ID[check_device], timeout=5)
        if (result != None):           
            Device_Delta[check_device] = datetime.datetime.now() - Last_Out[check_device]
            Device_Delta[check_device] = round(Device_Delta[check_device].seconds/60.0, 1) # In minutes
            Last_Out[check_device] = datetime.datetime.now() 
            Device_Status[check_device] = "IN.  Hours OUT was: "
            #GPIO.output(11,GPIO.HIGH) #Red RGB on  [debug statement]
           
            # A device is coming back IN. Should the relay be activated?
            if (Device_Delta[check_device] >= Power_On_Treshold):  # then turn on relay
                Relay_On_Time = datetime.datetime.now()
                GPIO.output(13,GPIO.HIGH) # Activate relay
                url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + "* " + Device_name[check_device] + " has ACTIVATED relay."
                res = requests.get(url)
                print " "
                print Device_name[check_device] + " has ACTIVATED relay at " +     time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
                Log_Deactivated = 1 # Flag to log/print when the relay is DEACTIVATED   
        else:
            Device_Delta[check_device] = datetime.datetime.now() - Last_In[check_device]
            Device_Delta[check_device] = round(Device_Delta[check_device].seconds/60.0, 1) # In minutes
            Last_In[check_device] = datetime.datetime.now() 
            Device_Status[check_device] = "OUT   Hours IN was: "
            #GPIO.output(11,GPIO.LOW) #Red RGB off [debug statement]
           
        # Print/Log only BT connection changes that exceed Noise_Filter value
        if (Device_Delta[check_device] >= Noise_Filter):    # comparing values in minutes here       
            url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + Device_name[check_device] + " is " + Device_Status[check_device] + "&value2=" + str(round((Device_Delta[check_device]/60.0),4)) # Write Delta as Hrs
            res = requests.get(url)
            #print str(res) + " is web request result for BT device: "   #used only for debug
       
            changenum = changenum + 1
            runtime = datetime.datetime.now()- t0
            Status_bar1 = "Runtime:" + str(runtime) + " | Status changes:" + str(changenum)
            Status_bar2 = "       Scan Interval:" + str(Scan_interval) + " secs   |  Noise Filter: " + str(Noise_Filter) + " mins."
            Status_bar3 = "Activate ON thresold: " + str(Power_On_Treshold) +  " mins  |        ON for: " + str(Desired_relay_on_time) + " mins."
            # Print BT device status to CRT
            print " "
            print time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
            print Status_bar1
            print Status_bar2
            print Status_bar3
            print "-----------------------------------------------"
            print str(check_device) + ": " + Device_name[check_device] + " is " + Device_Status[check_device] + str(round((Device_Delta[check_device]/60.0),4)) # Write Delta as Hrs

        if (First_run ==1):  # log to Google Sheets/print the intitial status of the tracked devices
            url = url = "https://maker.ifttt.com/trigger/BT_Logger/with/key/" + MAKER_SECRET_KEY + "?value1=" + Device_name[check_device] + " is " + Device_Status[check_device] + "&value2=" + "1ST RUN STATUS."
            res = requests.get(url)
            #print str(res) + " is web request result for BT device: "   #used only for debug
            print Device_name[check_device] + " is " + Device_Status[check_device] + "1ST RUN STATUS."
           
    if (First_run ==1):
        print " "
        print "Tracking begins at: " +     time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
        print " "
   
    First_run = 0 # no longer 1st run
   
    # Inverts or 'blinks' the LEDS every .5 secs to confirm program is running
    # Delay until next scan of BT device status.
    for jj in range (0, Scan_interval):    
        GPIO.output(11,GPIO.HIGH) #FLASH Red RGB on
        time.sleep(0.5)     
               
        GPIO.output(11,GPIO.LOW)   #Red LED Off
        time.sleep(0.5)
       
    for jj in range (0, 10):  #Quickie flack of red LED to show new scan starting
        GPIO.output(11,GPIO.HIGH) #FLASH Red RGB on
        time.sleep(0.1)     
               
        GPIO.output(11,GPIO.LOW)   #Red LED Off
        time.sleep(0.1)
    -----