Light Motion is a lighting project that I am working on using the Walabot sensor and LIFX smart bulbs. As I work on this project I am running on Windows with Python 2.7, but you can easily take the same scripts run them from a Raspberry Pi or Linux machine instead.
The goal of this project is to automatically turn lights on or off based on the number of people in the room. So for example, if I step into a room, the lights would automatically turn on so I can see and not have to fumble around looking for the light switch (similar to lights trigger by a motion sensor). Then when I exit the room the lights turn off, since there is no one else in the room (this part is a little quicker than lights that are hooked up to a motion sensor). For some this could potentially help cut down their electric bill!
Some Lifx SetupBefore we can jump into the project though we do have some setup we need to take care of in order to have this project function correctly. You'll need to have Lifx bulbs and an account.
Assuming that's all good, next you'll need to generate your own Access Token! This is pretty straightforward, all you have to do is go to https://cloud.lifx.com/settings and then click on "Generate New Token".
Lifx Cloud Settings - Generate Access Token
Next you'll want to give it a meaningful label and click "Generate":
Lifx Cloud Settings - Add label for Access Token
Then you should have your Access Token, but be careful and make sure you save that token somewhere safe because once you refresh or leave the page you will NOT be able to see your token again and you'll have to end up generating a new one.
Lifx Cloud Settings - Access Token
Now we're ready to move on to the actual project!
Setting up WalabotSome of this next section assumes that you have gone through some of the getting started content for Walabot, and if you have not go check out here. Make sure to download and run the installer for the SDK and possibly check out a tutorial or two here.
Since for this project I am on Windows, I went ahead and installed the SDK for Windows.
The installer should be very straightforward, the usually "next next next" should get you through it. The Windows installer also includes some sample code and a nice walk through on the SDK itself, showcasing some of the capabilities of the Walabot.
Setting up PythonSo next we'll want to setup Python if you don't already have it on your machine. For the sake of this project I will be using Python 2.7, I found that it was a little more friendly with the Walabot SDK and current sample code out there for it. If you need to download Python 2.7 then head on over here to grab the installer.
Before I go on with some additional setup with Python, I want to make sure we go through downloading my IDE of choice for this project (or just about anything Python) and that's PyCharm! There is a lovely community edition that is free and just awesome, I would recommend downloading that and installing it.
So once you have PyCharm and Python 2.7 installed, we're ready to get started on writing some code for the project.
Creating the ProjectFire up PyCharm! And click on "Create New Project". If you're curious to see the final project, feel free to jump to the end and check out the repo here.
When you first try to create a project you may see something like this.
Expand the "Project Interpreter" area, we need to make sure that we have the proper one setup for this project (remember we want 2.7 for this project). So select "Existing interpreter", and if there is none listed in the dropdown that's alright, I will walk you through adding one.
Click the gear to the right of that dropdown and click "Add local..." and you should now see a new pop up like this:
Make sure to select "System Interpreter" on the left and hopefully you will see Python 2.7 already in the dropdown. If not, then let's go ahead and add it. Click on "..." to the right and a new pop up should appear, now locate the python.exe for your install of Python 2.7. If you selected the defaults, then you should find it at C:\Python27\python.exe. Once its located and selected, the "OK" should be enabled, so go ahead and click that. You should be back to the previous pop up now and it should hopefully look like this now:
Now click "OK" once again and you should now be back on the original windows with your project info.
You should hopefully see the correct interpreter selected and now go on and click "Create".
Awesome! You've created your new Python project, the previous steps should hopefully need to be done once, going forward future project should use the same setup.
So once open, your project space may look a little empty.
This is totally fine, we're to start adding our code next. Go ahead and right-click on your project "lightmotion" and hover over "New" and select "Python file".
Name it "main" and click "OK". And you should now have your first Python file opened on in your project.
Walabot AppWith our newly create file opened, let's start writing some code and build the Walabot app!
A couple things to note, we will need to import a couple libraries, the Walabot SDK of course and the requests library, which we will use to make our REST calls to the LIFX api.
Open up command prompt or the terminal windows in PyCharm and run the following commands:
python -m pip install "C:/Program Files/Walabot/WalabotSDK/python/WalabotAPI-1.0.35.zip"
python -m pip install requests
Now we're ready! So in your main.py file go ahead and copy and paste this bit of code:
from __future__ import print_function, division
from datetime import datetime # used to the current time
from math import sin, cos, radians, sqrt # used to calculate MAX_Y_VALUE
import requests
import json
import WalabotAPI as wlbt
try:
input = raw_input
except NameError:
pass
R_MIN, R_MAX, R_RES = 10, 60, 2 # SetArenaR parameters
THETA_MIN, THETA_MAX, THETA_RES = -10, 10, 10 # SetArenaTheta parameters
PHI_MIN, PHI_MAX, PHI_RES = -10, 10, 2 # SetArenaPhi parametes,
THRESHOLD = 15 # SetThreshold parametes
MAX_Y_VALUE = R_MAX * cos(radians(THETA_MAX)) * sin(radians(PHI_MAX))
SENSITIVITY = 0.25 # amount of seconds to wait after a move has been detected
TENDENCY_LOWER_BOUND = 0.1 # tendency below that won't count as entrance/exit
IGNORED_LENGTH = 3 # len in cm to ignore targets in center of arena
LIFX_TOKEN = '<Replace-with-LIFX-token-generated>'
wlbt.Init("C:\Program Files\Walabot\WalabotSDK\\bin\Win32\WalabotAPIWindows.dll")
wlbt.SetSettingsFolder()
What we're doing here is importing all our libraries needed, WalabotAPI, json (for reading results from LIFX), and requests (for making REST calls to LIFX) and few other things. Then we are setting up some constant variables here that will be used later on in the app. For LIFX_TOKEN make sure you replace the value with the generated token you created earlier in this project. We will need this to communicate with your lights later on. And finally we initialize the WalabotAPI object, take not of the path to the dll. This should be the location by default, but its always good to double check.
Now we're going to define our first method, this will set our initial value for people in the room. Add the following code next:
def getNumOfPeopleInside():
""" Gets the current number of people in the room as input and returns it.
Validate that the number is valid.
Returns:
num Number of people in the room that got as input
"""
num = input('- Enter current number of people in the room: ')
if (not num.isdigit()) or (int(num) < 0):
print('- Invalid input, try again.')
return getNumOfPeopleInside()
return int(num)
This method is fairly straightforward, it asks for how many people are currently in the room, this just gives the app something to start with. Next we're going to define a method to verify the Walabot is connect.
def verifyWalabotIsConnected():
""" Check for Walabot connectivity. loop until detect a Walabot.
"""
while True:
try:
wlbt.ConnectAny()
except wlbt.WalabotError as err:
if err.code == 19: # 'WALABOT_INSTRUMENT_NOT_FOUND'
input("- Connect Walabot and press 'Enter'.")
else:
print('- Connection to Walabot established.')
return
Next we're going to define a method to setup a profile for the connected Walabot device. And we'll define another method to start and calibrate the Walabot device connected. Add this next bit of code:
def setWalabotSettings():
""" Configure Walabot's profile, arena (r, theta, phi), threshold and
the image filter.
"""
wlbt.SetProfile(wlbt.PROF_SENSOR)
wlbt.SetArenaR(R_MIN, R_MAX, R_RES)
wlbt.SetArenaTheta(THETA_MIN, THETA_MAX, THETA_RES)
wlbt.SetArenaPhi(PHI_MIN, PHI_MAX, PHI_RES)
wlbt.SetThreshold(THRESHOLD)
wlbt.SetDynamicImageFilter(wlbt.FILTER_TYPE_NONE)
print('- Walabot Configurated.')
def startAndCalibrateWalabot():
""" Start the Walabot and calibrate it.
"""
wlbt.Start()
wlbt.StartCalibration()
print('- Calibrating...')
while wlbt.GetStatus()[0] == wlbt.STATUS_CALIBRATING:
wlbt.Trigger()
print('- Calibration ended.\n- Ready!')
So far so good, now we're going to add a larger chunk of code. This is going to have a lot of the main logic for determining movement in the area and help figure out if people are coming in or out of the room.
def getDataList():
""" Detect and record a list of Walabot sensor targets. Stop recording
and return the data when enough triggers has occured (according to the
SENSITIVITY) with no detection of targets.
Returns:
dataList: A list of the yPosCm attribute of the detected
sensor targets
"""
while True:
wlbt.Trigger()
targets = wlbt.GetSensorTargets()
if targets:
targets = [max(targets, key=distance)]
numOfFalseTriggers = 0
triggersToStop = wlbt.GetAdvancedParameter('FrameRate')*SENSITIVITY
while numOfFalseTriggers < triggersToStop:
wlbt.Trigger()
newTargets = wlbt.GetSensorTargets()
if newTargets:
targets.append(max(newTargets, key=distance))
numOfFalseTriggers = 0
else:
numOfFalseTriggers += 1
yList = [
t.yPosCm for t in targets if abs(t.yPosCm) > IGNORED_LENGTH]
if yList:
return yList
def distance(t):
return sqrt(t.xPosCm**2 + t.yPosCm**2 + t.zPosCm**2)
def analizeAndAlert(dataList, numOfPeople):
""" Analize a given dataList and print to the screen one of two results
if occured: an entrance or an exit.
Arguments:
dataList A list of values
numOfPeople The current number of people in the room
returns:
numOfPeople The new number of people in the room
"""
currentTime = datetime.now().strftime('%H:%M:%S')
tendency = getTypeOfMovement(dataList)
if tendency > 0:
result = ': Someone has left!'.ljust(25)
numOfPeople -= 1
elif tendency < 0:
result = ': Someone has entered!'.ljust(25)
numOfPeople += 1
else: # do not count as a valid entrance / exit
result = ': Someone is at the door!'.ljust(25)
numToDisplay = ' Currently '+str(numOfPeople)+' people in the room.'
print(currentTime+result+numToDisplay)
return numOfPeople
def getTypeOfMovement(dataList):
""" Calculate and return the type of movement detected.
The movement only counts as a movement inside/outside if the tendency
if above TENDENCY_LOWER_BOUND and if the we have at least of item from
both sides of the door header.
Arguments:
dataList A list of values
Returns:
tendency if zero - not count as a valid entrance/exit
if positive - counts as exiting the room
if negative - counts as entering the room
"""
if dataList:
velocity = getVelocity(dataList)
tendency = (velocity * len(dataList)) / (2 * MAX_Y_VALUE)
side1 = any(x > 0 for x in dataList)
side2 = any(x < 0 for x in dataList)
bothSides = side1 and side2
aboveLowerBound = abs(tendency) > TENDENCY_LOWER_BOUND
if bothSides or aboveLowerBound:
return tendency
return 0
def getVelocity(data):
""" Calculate velocity of a given set of values using linear regression.
Arguments:
data An iterator contains values.
Returns:
velocity The estimates slope.
"""
sumY = sumXY = 0
for x, y in enumerate(data):
sumY, sumXY = sumY + y, sumXY + x*y
if sumXY == 0: # no values / one values only / all values are 0
return 0
sumX = x * (x+1) / 2 # Gauss's formula - sum of first x natural numbers
sumXX = x * (x+1) * (2*x+1) / 6 # sum of sequence of squares
return (sumXY - sumX*sumY/(x+1)) / (sumXX - sumX**2/(x+1))
There's a lot there and it does look pretty dense, there are comments to help you identify what each piece of code is doing and if you would like more info I would recommend checking out the PeopleCounter sample project which inspired this project.
Next we're going to define a method to stop and disconnect the Walabot device. Add this next block of code:
def stopAndDisconnectWalabot():
""" Stops Walabot and disconnect the device.
"""
wlbt.Stop()
wlbt.Disconnect()
Alright, almost to the main stretch here. Now we're going to add the primary method for this app. This method is going to grab our lights from LIFX and toggle them on or off, depending on the number of people in the room. Go ahead and add this next chunk:
def PeopleCounter():
""" Main function. init and configure the Walabot, get the current number
of people from the user, start the main loop of the app.
Walabot scan constantly and record sets of data (when peoples are
near the door header). For each data set, the app calculates the type
of movement recorded and acts accordingly.
"""
verifyWalabotIsConnected()
""" Import lights from LIFX """
lights = json.loads(getLifxLights())
numOfPeople = getNumOfPeopleInside()
setWalabotSettings()
startAndCalibrateWalabot()
try:
while True:
dataList = getDataList()
numOfPeople = analizeAndAlert(dataList, numOfPeople)
""" If num of people greater than zero; toggle on lights"""
""" else toggle off lights"""
if numOfPeople > 0:
toggleLifxLights(lights[1]['id'], "on")
else:
toggleLifxLights(lights[1]['id'], "off")
except KeyboardInterrupt:
pass
finally:
stopAndDisconnectWalabot()
Something to note here, you may notice that I specifically have a light selected. For the sake of the demo I manually chose which light I wanted to toggle on and off, you can change this to loop through all your lights or manually find out which light index you would like to toggle by checking out response from getLifxLights.
Now we're going to define a couple more methods, these will be the methods that will interact with the LIFX api, sending and receiving requests.
def getLifxLights():
url = 'https://api.lifx.com/v1/lights/'
headers = {
'Authorization': 'Bearer ' + LIFX_TOKEN,
'Content-Type': 'application/json'
}
response = requests.get(url, headers=headers)
return response.content
def toggleLifxLights(lightId, toggle):
url = 'https://api.lifx.com/v1/lights/id:'+lightId+'/state'
data = '{ "power": "'+toggle+'" }'
headers = {
'Authorization': 'Bearer ' + LIFX_TOKEN,
'Content-Type': 'application/json; charset=utf-8'
}
response = requests.put(url, data=data, headers=headers)
return response
Almost there! Finally add this last bit of code:
if __name__ == '__main__':
PeopleCounter()
This will kick off the whole loop for our app and constantly check for people in the room using the Walabot sensor and making calls to LIFX to toggle the lights. Hopefully by now you should have a main.py file that looks pretty close to this.
Running the projectWell that was a lot of code huh? Looks like we're ready to see some of that work payoff and run the app!
We just need to simply run the main.py script that we finished writing the previous section. If you are still using PyCharm, I will go through setting up a configuration so you can easily run and debug your code.
Over on the top right you should see a dropdown, which may be empty and that's cool.
Go ahead and click on the dropdown and click on "Edit Configurations...". This should open up a new pop up window. Make sure you have Python selected on the left.
Next make sure you have the correct path for "Script path", this should be pointing to your main.py file. And make sure Python 2.7 is still set as your interpreter. The remaining defaults should good. Now click on the green plus button on the top left and select "Python". You should hopefully have a new Python section appear and your new "Unnamed" configuration created.
Feel free to rename your configuration to something a little more useful, like "Debug" or "Run", just not "Unnamed".
Make sure your Walabot sensor is connected and go ahead and click that green run button next to the dropdown!
Wrap UpIf all goes well you should now have a new motion sensor app running checking for people coming in and out of your room and hopefully your lights are turning on and off depending on the number of people in the room.
I hope you enjoyed this project and learned a bit about Walabot sensor and some bits about Python and maybe PyCharm.
Here's a small demo of the app in action:
Thanks for checking out this project and sticking through to the end. If there are any parts that you are unsure about or need any help with, feel free to comment or leave me a message and I will be glad to help out any way I can!
Some Extra FunIncluded in the repo for this project is a small proof of concept Alexa skill, which includes python libraries needed for it to run. Here's a quick look at what's in it:
Basically you will want to zip up the above and upload that to your lambda function in AWS for your Alexa Skill, and then with some minor editing you can be up and running with your own version of the WalaLightMotion skill!
Currently I have a version going through approval process, but I encourage you to take this and go further with it!
Comments