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 lifx library import adafruit_lifx
The code first imports all of the libraries required to run the Smart Lighting Controller.
Note that the code imports a special adafruit_lifx
library. To communicate with the LIFX API, we wrote a CircuitPython helper module called CircuitPython_LIFX
. This module makes HTTP requests ( or, "talks") to the LIFX API server 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.
# 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) # These pins are used as both analog and digital! XL, XR and YU must be analog # and digital capable. YD just need to be digital ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR, board.TOUCH_YD, board.TOUCH_YU, calibration=((5200, 59000), (5800, 57000)), size=(320, 240))
The next chunk of code grabs information from a secrets.py file including wifi configuration and your LIFX Access Token. 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 LIFX API.
# Set this to your LIFX personal access token in secrets.py # (to obtain a token, visit: https://cloud.lifx.com/settings) lifx_token = secrets['lifx_token'] # Initialize the LIFX API Helper lifx = adafruit_lifx.LIFX(wifi, lifx_token) # Set these to your LIFX light selector (https://api.developer.lifx.com/docs/selectors) lifx_lights = ['label:Lamp', 'label:Bedroom'] # set default light properties current_light = lifx_lights[0] light_brightness = 1.0
The lifx_token
entry within secrets is set to a new variable, lifx_token
, and passed into the LIFX helper along with the wifi object created earlier.
Then, the code sets up a list of two lights (identified by their selectors). Later in this code, these lights are linked to buttons so the code can identify which light is being toggled.
Also, a default light brightness of 100% is set, along with a current_light
variable used to track the light being addressed.
# Make the display context button_group = displayio.Group() board.DISPLAY.root_group = 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
.
# 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':'lamp', 'pos':(195, 15), 'label':'lamp'}, {'name':'room', 'pos':(245, 15), 'label':'room'} ] # 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=40, 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.
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 toggled - inverting the button's color until the action is performed. By doing this, you are creating a visual status indicator.
if button.name == 'lamp': current_light = lifx_lights[0] print('Switching to ', current_light) elif button.name == 'room': current_light = lifx_lights[1] print('Switching to ', current_light)
Then, the code checks the button's name against its function (what it does). If the button's name is lamp or room, current_light
is toggled to the value of the button.
Whenever the code in the main loop sends data to the LIFX API to perform an action on a light, it uses the current_light
variable to select the light.
elif button.name == 'onoff': print('Toggling {0}...'.format(current_light)) lifx.toggle_light(current_light) elif button.name == 'up': light_brightness += 0.25 print('Setting {0} brightness to {1}'.format(current_light, light_brightness)) lifx.set_brightness(current_light, light_brightness) elif button.name == 'down': light_brightness -= 0.25 print('Setting {0} brightness to {1}'.format(current_light, light_brightness)) lifx.set_brightness(current_light, light_brightness) else: print('Setting {0} color to {1}'.format(current_light, button.name)) lifx.set_color(current_light, 'on', button.name, light_brightness) button.selected = False
The rest of the code performs an action based on the button's name by using the adafruit_lifx
helper module.
When the on/off button is pressed - the code calls lifx.toggle_light
and passes in the current_light
.
- This button corresponds to the Toggle Power endpoint defined in the LIFX remote API.
When the brightness button (+ or -) is pressed, the code passes the current_light
and light_brightness
into lifx.set_brightness
. Also, the light_brightness
increments or decrements by 1/4 each time the button is pressed.
- This button corresponds to the Set State endpoint defined in the LIFX remote API.
If the button's name doesn't correspond to a predefined name, we set the color using the button's name as the color passed into lifx.set_color
.
- This button corresponds to the Set State endpoint defined in the LIFX remote API.
- You're not limited by the colors defined in the button_colors list - you can adjust/mix colors and send those values to the LIFX lights, check out the colors documentation on their website.
Finally, the code sets its selected property to false, returning it to its original color to indicate an action has been performed.
Going further!
This guide has only scratched the surface of the LIFX Light Remote HTTP API. There are lots more endpoints, options, and effects to play around with. If you are interested in seeing how this works (in a deeper level) - take a look at the adafruit_lifx.py file located in the helper library's repository. It might be helpful to keep the LIFX API website open in another tab, for reference.
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.