import board import displayio from adafruit_bitmap_font import bitmap_font from adafruit_button import Button import adafruit_touchscreen from digitalio import DigitalInOut import busio import neopixel from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager # Import Philips Hue Bridge from adafruit_hue import Bridge
The code first imports all of the libraries required to run the Smart Lighting Controller.
Note that the code imports a special adafruit_hue
library. To communicate with the Hue Bridge, we wrote a CircuitPython helper module called CircuitPython_Hue
. This module makes HTTP requests ( or, "talks") to the Hue Bridge's IP address in order to interact with the bulbs.
- For more information about how HTTP works, check out our All The Internet of Things guide on this topic here.
# Get wifi details and more from a secrets.py file try: from secrets import secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise # ESP32 SPI esp32_cs = DigitalInOut(board.ESP_CS) esp32_ready = DigitalInOut(board.ESP_BUSY) esp32_reset = DigitalInOut(board.ESP_RESET) spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
The next chunk of code grabs information from a secrets.py file including wifi configuration. Then, it sets up the ESP32's SPI connections for use with the PyPortal. The wifi
object is set up here too - it's used later in the code to communicate with the bridge.
# Attempt to load bridge username and IP address from secrets.py try: username = secrets['hue_username'] bridge_ip = secrets['bridge_ip'] my_bridge = Bridge(wifi, bridge_ip, username) except: # Perform first-time bridge setup my_bridge = Bridge(wifi) print('Finding bridge address...') ip = my_bridge.discover_bridge() print('Attempting to register username, press the link button on your Hue Bridge now!') username = my_bridge.register_username() print('ADD THESE VALUES TO SECRETS.PY: \ \n\t"bridge_ip":"{0}", \ \n\t"hue_username":"{1}"'.format(ip, username)) raise
The entire Hue system is built around the Hue Bridge. To communicate with the bridge, you'll need to register a unique username (stored on the bridge) for the PyPortal. you'll also need the Bridge's IP address to communicate with it. There's a bit of a HTTP request/reply "dance" associated with this process and it's tricky.
The CircuitPython Hue library automates the process of discovering and registering a username with the bridge - but, you'll still need to add these values to the secrets.py file.
- For more information about the bridge setup - read the Hue Bridge Setup page of this guide.
print('loading colors...') # color conversions (RGB to Philips Hue-compatible HSB) red = my_bridge.rgb_to_hsb([255, 0, 0]) white = my_bridge.rgb_to_hsb([255, 255, 255]) orange = my_bridge.rgb_to_hsb([255, 165, 0]) yellow = my_bridge.rgb_to_hsb([255, 255, 0]) green = my_bridge.rgb_to_hsb([0, 255, 0]) blue = my_bridge.rgb_to_hsb([0, 0, 255]) purple = my_bridge.rgb_to_hsb([128, 0, 128]) pink = my_bridge.rgb_to_hsb([255, 192, 203]) hue_hsb = {'red': red, 'white': white, 'orange': orange, 'yellow': yellow, 'green': green, 'blue': blue, 'purple': purple, 'pink': pink}
When you want to modify the light's state from within your code, you'd use the set_light
method in CircuitPython_Hue
. This method mirrors Philips' set light state API endpoint - but it has some non user-friendly features.
The Hue API uses HSB (Hue, Saturation, Brightness) instead of RGB (red, green, blue) values. It also does not take in brightness percentages, or hue degrees. Instead, it takes in scaled integer values which directly relate to to the brightness percentage or hue degrees.
To make this simpler - we added a rgb_to_hsb
method within the Hue helper module. Passing a list of RGB values into this method will return a scaled HSB list.
-
If you wish to use RGB colors with your PyPortal Hue Controller - use this method before calling
set_light
to convert and scale your colors.
Our code predefines a few colors (red, white, orange, yellow, green, blue, purple, pink) by converting them from RGB values to scaled HSB values. using rgb_to_hsb
.
# Make the display context button_group = displayio.Group() board.DISPLAY.show(button_group) # preload the font print('loading font...') font = bitmap_font.load_font("/fonts/Arial-12.bdf") glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: ' font.load_glyphs(glyphs) # button properties BUTTON_WIDTH = 60 BUTTON_HEIGHT = 60 buttons = []
Before the code can create buttons, it needs to create a displayio
group to hold them. Here - the code selects a font, preloads it with glyphs, and sets button properties for BUTTON_WIDTH
and BUTTON_HEIGHT
.
# button fill colors button_colors = {'red':0xFF0000, 'white':0xFFFFFF, 'orange':0xFF9900, 'yellow':0xFFFF00, 'green':0x00FF00, 'blue':0x0000FF, 'purple':0x9900FF, 'pink': 0xFF00FF} # list of color buttons and their properties color_btn = [ {'name':'red', 'pos':(15, 80), 'color':button_colors['red']}, {'name':'white', 'pos':(85, 80), 'color':button_colors['white']}, {'name':'orange', 'pos':(155, 80), 'color':button_colors['orange']}, {'name':'yellow', 'pos':(225, 80), 'color':button_colors['yellow']}, {'name':'pink', 'pos':(15, 155), 'color':button_colors['pink']}, {'name':'green', 'pos':(85, 155), 'color':button_colors['green']}, {'name':'blue', 'pos':(155, 155), 'color':button_colors['blue']}, {'name':'purple', 'pos':(225, 155), 'color':button_colors['purple']} ] # generate color buttons from color_btn list for i in color_btn: button = Button(x=i['pos'][0], y=i['pos'][1], width=BUTTON_WIDTH, height=BUTTON_HEIGHT, name=i['name'], fill_color=i['color'], style=Button.ROUNDRECT) buttons.append(button) # light property buttons and their properties prop_btn = [ {'name':'onoff', 'pos':(15, 15), 'label':'on/off'}, {'name':'up', 'pos':(75, 15), 'label':'+'}, {'name':'down', 'pos':(135, 15), 'label':'-'}, {'name':'room', 'pos':(195, 15), 'label':'room'}, {'name':'lamp', 'pos':(255, 15), 'label':'lamp'} ] # generate property buttons from prop_btn list for i in prop_btn: button = Button(name=i['name'], x=i['pos'][0], y=i['pos'][1], width=55, height=40, label=i['label'], label_font=font, style=Button.SHADOWROUNDRECT) buttons.append(button) # add buttons to the group for b in buttons: button_group.append(b.group)
The next chunk of code creates buttons for the light colors and properties.
First, a list of dictionary items is created corresponding to button properties. The color_btn
list contains information for the button such as the button's name, position on the display, and hex color value. The prop_btn
list contains the button's name, position and text label.
Then, buttons are generated from the list and appended to a button
list. When all the buttons have been added to button
list, they are appended to the displayio button_group
one-by-one.
# Hue Light/Group Identifiers hue_lights={'lamp': 1, 'livingroom': 2} hue_selector = hue_lights['lamp'] # Default to 25% brightness current_brightness = 25
The code sets up hue light identifiers in hue_lights
and automatically selects the lamp identifier in the list. This prevents sending the bridge a None type if a button is pressed and no light is selected. It also sets a brightness default of 25%.
- For more information about setting up light identifiers, read the Hue Lights Setup section of this guide.
-
To enumerate all the lights connected to the bridge, you can run the following line from your code:
my_lights = my_bridge.get_lights
and print the JSON response from the hue bridge:my_lights
.
while True: touch = ts.touch_point if touch: for i, button in enumerate(buttons): if button.contains(touch): button.selected = True
The main loop checks for if the screen was touched. If it was, it searches for which button in the button
list was touched.
Once the button was determined, the button's selected
property is set to True
- inverting the button's color until the action is fully performed (i.e: the light is changed, toggled, etc). By doing this, you are creating a visual status indicator.
if button.name == 'living': hue_selector = hue_lights['livingroom'] print('Switching to ', hue_selector) elif button.name == 'room': hue_selector = hue_lights['lamp'] print('Switching to ', hue_selector)
Then, the code checks the button's name against its function (what it does). If the button's name is lamp or room, hue_selector
is toggled to the value of the button.
Whenever the code in the main loop sends data to the bridge to modify a light, it uses hue_selector
to indicate the light being modified.
elif button.name == 'onoff': print('Toggling {0}...'.format(hue_selector)) my_bridge.toggle_light(int(hue_selector))
If the on/off button is tapped, the code will toggle the state of the selected light.
- The Hue Light API does not support toggling light state - we added a
toggle_light
method into theCircuitPython_Hue
library to get the current state of the light , toggle it, and callset_light
with the toggled state of the light..
elif button.name == 'up': current_brightness += 25 print('Setting {0} brightness to {1}'.format(hue_selector, current_brightness)) my_bridge.set_light(int(hue_selector), bri=current_brightness) elif button.name == 'down': current_brightness -= 25 print('Setting {0} brightness to {1}'.format(hue_selector, current_brightness)) my_bridge.set_light(int(hue_selector), bri=current_brightness)
When either of the brightness buttons are pressed, they'll modify the value of current_brightness
by +/-25% and send that value to the bridge using set_light
.
else: print('Setting {0} color to {1}'.format(hue_selector, button.name)) my_bridge.set_light(light_id=int(hue_selector), hue=int(hue_hsb[button.name][0]), sat=int(hue_hsb[button.name][1]), bri=int(hue_hsb[button.name][2])) button.selected = False
If any of the color buttons are pressed, the color's hue, saturation, and brightness values are individually passed into set_light
along with the light (hue_selector
).
Since the code is finished processing the action, the button's fill is set to its original color by de-selecting the button (button.selected = False
).
Going further!
This guide has only scratched the surface of the Philips Hue API! We've detailed out a good amount of the API actions within the Adafruit_CircuitPython_Hue library, like discovering new bulbs or setting a scene.
If you didn't see the API action you were looking for, feel free to add it to the library. You may find it useful to read through the library and the API reference in different tabs, and cross-referencing them (Docs are here, you'll need to create an account)
If you end up adding a feature to the library - pull requests on the GitHub repository are appreciated!
- If you are not sure how to create a pull request, Kattni has a fantastic guide about contributing to CircuitPython using GitHub.