programming [ python ]

Control a Phillips Hue Bulb with the Flick of a Wand!

kano promo photo

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:

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 qhuewe’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.

wand motions

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 bulbs state to the wands current spell. Well 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) 

[https://youtu.be/DWk1ZUb1avU](https://youtu.be/DWk1ZUb1avU)

https://youtu.be/DWk1ZUb1avU

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 :)