Skip to content

Part 2. The Code

Rachel edited this page Aug 24, 2016 · 2 revisions

Time for the fun part.

You can find the presence.py script here: https://github.com/initialstate/pi-sensor-free-presence-detector/blob/master/presence.py

I'm going to go over it briefly so you know what's happening!

First we import important packages:

import subprocess
from time import sleep
from threading import Thread
from ISStreamer.Streamer import Streamer

"subprocess" allows us to make calls like we would in the command line from the script. "time" is so that we can wait between actions. "threading" is necessary because we are looking for more than one device. Threads are basically separated pieces of code that can run at the exact same time as other threads. "Streamer" is the Initial State streamer that let's us send data to a web-based dashboard.

Next we do some initializing:

# Edit these for how many people/devices you want to track
occupant = ["Rachel","Jamie","Broc","Adam","Jeff","Raymond","David","Kaylee"]

# MAC addresses for our phones
address = ["xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx","xx:xx:xx:xx:xx:xx"]

# Sleep once right when this script is called to give the Pi enough time
# to connect to the network
sleep(60)

# Initialize the Initial State streamer
# Be sure to add your unique access key
streamer = Streamer(bucket_name=":office:Who's at the Office?", bucket_key="office_presence", access_key="Your_Access_Key")

# Some arrays to help minimize streaming and account for devices
# disappearing from the network when asleep
firstRun = [1] * len(occupant)
presentSent = [0] * len(occupant)
notPresentSent = [0] * len(occupant)
counter = [0] * len(occupant)

In this use case, I am tracking who is currently at the office, so I have an array with our names. The address array contains the corresponding MAC addresses for our phones. If you want to add more devices, simply add more values to both arrays.

We sleep for a minute before trying to initialize the Initial State streamer because we'll want to run this script when the Pi boots up and need to wait for an internet connection. Then we can create a stream with visible bucket name ":office:Who's at the Office?", hidden bucket key "office_presence", and unique access key.

Be sure to replace YOUR_ACCESS_KEY with your Initial State access key!

Because we a) don't want to stream a ton of messages constantly if a device's presence hasn't changed and b) want to wait a reasonable amount of time before declaring a device absent since some phone's disappear when their screen goes black, we create some arrays to handle when something should be streamed.

Now for the meat of our code:

# Function that checks for device presence
def whosHere(i):

	# 30 second pause to allow main thread to finish arp-scan and populate output
	sleep(30)

	# Loop through checking for devices and counting if they're not present
	while True:

		# Exits thread if Keyboard Interrupt occurs
		if stop == True:
			print "Exiting Thread"
			exit()
		else:
			pass

		# If a listed device address is present print and stream
		if address[i] in output:
	        print(occupant[i] + "'s device is connected to your network")
	        if presentSent[i] == 0:
	        	# Stream that device is present
	        	streamer.log(occupant[i],":office:")
	        	streamer.flush()
	        	print(occupant[i] + " present streamed")
	        	# Reset counters so another stream isn't sent if the device
	        	# is still present
	        	firstRun[i] = 0
	        	presentSent[i] = 1
	        	notPresentSent[i] = 0
	        	counter[i] = 0
	        	sleep(900)
	        else:
	        	# If a stream's already been sent, just wait for 15 minutes
	        	counter[i] = 0
	        	sleep(900)

The function "whosHere()" is where we check for the device and steam if it's present or not. "output" is the result of the main thread calling arp-scan like before and looing for the device address. If it's there, the following if statement is called. We check to see if "device present" has been streamed and either wait 15 minutes if it has or stream it!

	    # If a listed device address is not present, print and stream
	    else:
	    	print(occupant[i] + "'s device is not present")
	    	# Only consider a device offline if it's counter has reached 30
	    	# This is the same as 15 minutes passing
	    	if counter[i] == 30 or firstRun[i] == 1:
	    		firstRun[i] = 0
		        if notPresentSent[i] == 0:
		        	# Stream that device is not present
		        	streamer.log(occupant[i],":no_entry_sign::office:")
		        	streamer.flush()
		        	print(occupant[i] + " not present streamed")
		        	# Reset counters so another stream isn't sent if the device
	        		# is still present
		        	notPresentSent[i] = 1
		        	presentSent[i] = 0
		        	counter[i] = 0
		        else:
		        	# If a stream's already been sent, wait 30 seconds
		        	counter[i] = 0
		        	sleep(30)
		    # Count how many 30 second intervals have happened since the device 
		    # disappeared from the network
		    else:
		    	counter[i] = counter[i] + 1
		    	print(occupant[i] + "'s counter at " + str(counter[i]))
		    	sleep(30)

If the device's address isn't present, the else statement is called. Like above, we check to see if "device not present" has been streamed, but only if the device hasn't shown up on the network in a 15 minute period. This way your device will hopefully be picked up even if it's been sleeping for a little bit.

# Main thread

try:

	# Initialize a variable to trigger threads to exit when True
	global stop
	stop = False

	# Start the thread(s)
	# It will start as many threads as there are values in the occupant array
	for i in range(len(occupant)):
		t = Thread(target=whosHere, args=(i,))
		t.start()

	while True:
		# Make output global so the threads can see it
		global output
		# Assign list of devices on the network to "output"
		output = subprocess.check_output("sudo arp-scan -l", shell=True)
		# Wait 30 seconds between scans
		sleep(30)

except KeyboardInterrupt:
	# On a keyboard interrupt signal threads to exit
	stop = True
	exit()

Because we have multiple devices, we create a thread for each one here. Some logic is added to signal threads to exit on a Keyboard Interrupt. Arp-scan is called on line 109 and refreshed every 30 seconds.

Now run the script with:

sudo python presence.py

Watch what prints to the terminal to make sure your devices are being detected and that streaming is working. Head over to Initial State to checkout your streams!

<< Part 2: Initial State - Part 2: Your Personal Dashboard >>