Control a Phillips Hue Bulb with the Flick of a Wand!
17 min read
Welcome to Hogwarts!
In this guide, I’ll be teaching you how you can use the Harry Potter Kano Coding Kit to control a Phillips Hue light bulb using their awesome Hue API and my own kano wand Python 3 module. The tutorial is written for people newer to programming, so if you’re an advanced user you can skip to the end. You’ll just need a few things to get this demo running:
- Harry Potter Kano Coding Kit
- Phillips Hue bridge and bulb
- A Linux based OS
- Python 3
The Linux based OS is the strangest requirement, so let me explain. When I wrote my wand module I went looking around for an existing library to make interacting with the wand easier. The best I found was bluepy and unfortunately, it is only available on Linux. Since I use elementary OS on my primary computer this wasn’t an issue for me. I apologize about the limitation, I would love to port the module to a multiplatform python ble module if there is one!
I’ll start with the general design we’ll be using. I designed the kano wand module to be very flexible, allowing users to use subclass their own wand or to add dynamically callback functions as they need to. In this tutorial, we’ll use a subclass and extend the Wand
class with several functions like post_connect
and on_button
. We’ll also need a class to manage the Bridge
object and react to different spells from the wand. This class will flicker all our bulbs and reset them to their initial state when disconnecting. While we’re connected to a wand we’ll tell the manager to flicker based on the current wand’s spell. Before the major code segments, I will link to a gist with the step’s code so you can see the code at that point to make it easier to follow.
Alright, let’s get the project set up. First, let’s create a folder called “hue” for the project files. Next, we’ll want to get a copy of the module. You can either download and unpacking an archive of the files into the project folder or by cloning the repo into your hue folder with the following command:
git clone https://github.com/GammaGames/kano_wand.git
Next, we’ll need a place to write our code, so make a file called “hue.py” inside the folder. Finally, you’ll need to install a handful of requirements for the project. Some of these may need to be installed using sudo
, since bluepy requires user elevation to scan for low energy Bluetooth peripherals. You can install the required modules with the following in the command line:
pip3 install bluepy numpy qhue moosegesture
Bluepy and numpy are both requirements of the wand module, we can use MooseGesture to add some basic gesture recognition to our wand, and we’ll be using qhue to control the lights.
To start out open up our hue.py
file into your favorite code editor. We have to import all the required modules at the top of the file with the following gist:
from kano_wand.kano_wand import Shop, Wand, PATTERN
from qhue import Bridge
import moosegesture as mg
import time
import random
import math
The first line imports the Shop
and Wand
classes and the PATTERN
enum from the kano_wand module. Since we only need the Bridge
class from qhue
we’ll only import that. We also import moosegesture
as mg
to make it easier to type. We’ll also be importing time
, random
, and math
for various utilities in the script.
Let’s make a basic wand object that prints out the wand’s name and a shop that will scan for wands. We can do that with the following code gist:
class GestureWand(Wand):
def post_connect(self):
print(self.name)
shop = Shop(wand_class=GestureWand)
wands = []
while len(wands) == 0:
print("Scanning...")
wands = shop.scan(connect=True)
for wand in wands:
wand.disconnect()
First, we create our custom wand class. It uses post_connect
to print out the wand’s name after connecting. Then we create a Shop
and pass our custom class in using the wand_class
keyword argument. While our wand array is empty we’ll scan scan, automatically connecting to the wands. After we find some wands we will break out of the while
loop and disconnect them. The last two lines aren’t really necessary since the wands will automatically disconnect when the program ends, but it is better to be precise when writing code to try and prevent unexpected bugs.
To run the above code, we must run it using sudo
. For example, you can use the following command in your terminal (it will ask for your password):
sudo python3 hue.py
Next, let’s add some gestures. We want to surround our call to scan for wands with a try except
that will cleanly disconnect our wands when we stop the program with Ctrl+C (the easiest way to stop the script from now on). Make the following adjustment:
shop = Shop(wand_class=GestureWand)
wands = []
try:
while len(wands) == 0:
print("Scanning...")
wands = shop.scan(connect=True)
except KeyboardInterrupt as e:
for wand in wands:
wand.disconnect()
After connecting to our wand we want to subscribe to position and button notifications to store our position data (multiplying the y
value by -1
to correct the up/down movements) while holding the button and print out our gesture when we release it. I’ll be replacing the older wand with the following gist:
class GestureWand(Wand):
def post_connect(self):
self.pressed = False
self.positions = []
self.subscribe_button()
self.subscribe_position()
def on_position(self, x, y, pitch, roll):
if self.pressed:
self.positions.append(tuple([x, -1 * y]))
def on_button(self, pressed):
self.pressed = pressed
if not pressed:
gesture = mg.getGesture(self.positions)
self.positions = []
print(gesture)
For the final step in getting wand gestures into a usable form, we’ll add a dictionary that has all our supported spells from the wand motion guided included with the wand kit.
From the above, we can see that Stupefy can be described as moving the wand left and down, right, and left and down again and Wingardium Leviosa can be described as moving down and right, right, up and right, and finally down. I’ve found that the more precise you are when defining your gestures, the easier it is to cast the spell. We’ll use these lists of directions as keys in a dictionary, each gesture’s value being the spell’s name. My finished dictionary looks like this:
self.gestures = {
("DL", "R", "DL"): "stupefy",
("DR", "R", "UR", "D"): "wingardium_leviosa",
("UL", "UR"): "reducio",
("DR", "U", "UR", "DR", "UR"): "flipendo",
("R", "D"): "expelliarmus",
("UR", "U", "D", "UL", "L", "DL"): "incendio",
("UR", "U", "DR"): "lumos",
("U", "D", "DR", "R", "L"): "locomotor",
("DR", "DL"): "engorgio",
("UR", "R", "DR"): "aguamenti",
("UR", "R", "DR", "UR", "R", "DR"): "avis",
("D", "R", "U"): "reducto"
}
When we release the button, we’ll use MooseGesture’s findClosestMatchingGesture
function with the resulting position array to get a list of gestures that resemble our motion. If we get a match, we’ll store the resulting gesture’s value from the gestures dictionary and vibrate the wand gist:
def on_button(self, pressed):
self.pressed = pressed
if pressed:
self.spell = None
else:
gesture = mg.getGesture(self.positions)
self.positions = []
closest = mg.findClosestMatchingGesture(gesture, self.gestures, maxDifference=1)
if closest != None:
self.spell = self.gestures[closest[0]]
self.vibrate(PATTERN.SHORT)
print("{}: {}".format(gesture, self.spell))
That’s all there is to the wand! Now we’ll work on the light manager.
First you’ll have to get your bridge’s IP address and a username to use the API. You can do that using Phillip’s Get Started guide. We’ll make a manager object that knows the bridge IP and username, has an instance of a Bridge
object, and has an array of light IDs to control. We’ll use the following code (in this example, the bridge prints out the state of the light with the ID of 1
) gist:
class LightManager():
def __init__(self):
self.bridge_ip = "192.168.1.22"
self.username = "dBHN8d6Qkw6EJMqzEI2oI0zXJGiOdvyE2lRzFha8"
self.bridge = Bridge(self.bridge_ip, self.username)
self.light_ids = ["1"]
for id in self.light_ids:
light = self.bridge.lights[id]
print(light()["state"])
manager = LightManager()
Now we want to store the initial states of our lights so that we can reset them when closing our program, and add a flicker
method that will make the bulb flicker slightly for added magical effect.
We’ll store our initial states in a dictionary using the light’s ID as a key, and on reset we’ll pass the key’s value into the light’s state
function. After backing up the initial state, we can set the bulbs state to the default by passing **self.default
to the state
function, converting the dictionary into keyword arguments.
For a default state for the bulbs we can use a dictionary set to the value {"state": True, "bri": 144, "hue": 7676, "sat": 199}
. bri
is the brightness, hue
is the color and sat
is the saturation of the bulb. The default is a dim orange color, perfect for a torchlight effect. As for the flicker, we’ll use the default state with a random variation of brightness. We also have to pass in a transition time to the bulb so it flickers more quickly. The updated manager now looks like this:
class LightManager():
def __init__(self):
self.bridge_ip = "192.168.1.22"
self.username = "dBHN8d6Qkw6EJMqzEI2oI0zXJGiOdvyE2lRzFha8"
self.bridge = Bridge(self.bridge_ip, self.username)
self.light_ids = ["1"]
self.light_states = {}
self.default = {"on": True, "bri": 144, "hue": 7676, "sat": 199}
for id in self.light_ids:
light = self.bridge.lights[id]
state = self.default.copy()
s = light()['state']
for key in state:
state[key] = s[key]
self.light_states[id] = state
light.state(**self.default)
def flicker(self, transition):
for id in self.light_ids:
light = self.bridge.lights[id]
c = self.default.copy()
c["bri"] = c["bri"] + random.randint(0, 53)
light.state(transitiontime=transition, **c)
def reset(self):
for id in self.light_ids:
light = self.bridge.lights[id]
light.state(**self.light_states[id])
While the wand is connected, we’ll call the flicker
function and sleep while the bulb transitions. Philips recommends to limit commands to “Roughly 10 commands per second” for each light, so we’ll sleep for 100 to 200ms for every flicker. We’ll add the following after we scan for wandsgist:
wand = wands[0]
while wand.connected:
sleep = random.uniform(0.1, 0.2)
transition = math.ceil(sleep * 10)
manager.flicker(transition)
time.sleep(sleep)
manager.reset()
Now, all we have left is to set the bulb’s state to the wand’s current spell. We’ll remove`self.default` and use a dictionary with the current spell as the key (None being the default state). It looks like this:
self.color_values = {
None: {"bri": 144, "hue": 7676, "sat": 199},
"stupefy": {"hue": 0, "bri": 200, "sat": 150},
"wingardium_leviosa": {"hue": 37810, "bri": 100, "sat": 40},
"reducio": {"hue": 51900, "bri": 200, "sat": 200},
"flipendo": {"hue": 37445, "bri": 150, "sat": 140},
"expelliarmus": {"hue": 1547, "bri": 200, "sat": 200},
"incendio": {"hue": 7063, "bri": 200, "sat": 250},
"lumos": {"hue": 0, "bri": 204, "sat": 0},
"locomotor": {"hue": 12324, "bri": 100, "sat": 140},
"engorgio": {"hue": 32275, "bri": 125, "sat": 120},
"aguamenti": {"hue": 32275, "bri": 180, "sat": 200},
"avis": {"hue": 37445, "bri": 150, "sat": 130},
"reducto": {"hue": 37445, "bri": 180, "sat": 200}
}
We’ll get the current state by passing in the current spell when we flicker and we’ll store it inself.current
. We will set the light’s state using that value, and if “Lumos” has just been cast we’ll toggle the light. Our flicker
function now looks like this:
def flicker(self, spell, transition):
for id in self.light_ids:
light = self.bridge.lights[id]
on = light()['state']['on']
self.current = self.color_values[spell]
if spell == "lumos":
light.state(transitiontime=transition, on=not on, **self.current)
elif on:
c = self.current.copy()
c["bri"] = c["bri"] + random.randint(0, 53)
light.state(transitiontime=transition, **c)
We’ll only need a small modification to make the flicker
function use the spell, just pass in the wand’s current spell and set it to None to prevent flickering lights when you cast “Lumos” gist:
manager.flicker(wand.spell, transition)
if wand.spell == "lumos":
wand.spell = None
time.sleep(sleep)
Voila, we’re done! In roughly 100 lines of code, we have a wand that can recognize swishes and flicks and change a light bulb’s colors. The wand does require strict movements, but magic does require discipline to master! You can find the finished script in my kano-wand-demos repo.
Thank you for reading! If you have any issues feel free to submit an issue on the github page for the project and if you havey any feedback don’t hesitate to reach out to me on Twitter :)