Code PyPortal with CircuitPython

Obtain your LIFX Access Token

All requests to the LIFX bulbs require an access token. Obtaining one is simple!

Navigate to the LIFX account settings page, name your access token something unique, and click generate.

Then, copy this token to a file or somewhere safe - you will not be able to view it again when you navigate away from this page.

CircuitPython Library Installation

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle matching your version of CircuitPython. PyPortal requires at least CircuitPython version 4.0.0.

Before continuing make sure your board's lib folder has the following files and folders copied over.

  • adafruit_lifx
  • adafruit_bitmap_font
  • adafruit_bus_device
  • adafruit_button
  • adafruit_display_shapes
  • adafruit_display_text
  • adafruit_esp32spi
  • adafruit_touchscreen
  • neopixel

Secrets File Setup

If you have not yet set up a secrets.py file in your CIRCUITPY drive and connected to the internet using it, follow this guide and come back to this page when you've successfully connected to the Internet

Add the LIFX Access Token you generated to to the secrets.py file:

Download: file
secrets = {
    'ssid' : '_your_wifi_ssid_',
    'password : '_your_wifi_password_',
    'lifx_token' : '_your_really_long_lifx_token_'
}

Add CircuitPython Code and Project Assets

In the embedded code element below, click on the Download: Project Zip link, and save the .zip archive file to your computer.

Then, uncompress the .zip file, it will unpack to a folder named pyportal_lifx_controller.

Copy the contents of the pyportal_lifx_controller directory to your PyPortal's CIRCUITPY drive.

"""
PyPortal Smart Lighting Controller
-------------------------------------------------------------
https://learn.adafruit.com/pyportal-smart-lighting-controller

Brent Rubell for Adafruit Industries, 2019
"""
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

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

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

# 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

# Make the display context
button_group = displayio.Group(max_size=20)
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 = []

# button fill colors (from https://api.developer.lifx.com/docs/colors)
button_colors = {'red':0xFF0000, 'white':0xFFFFFF,
                 'orange':0xFF9900, 'yellow':0xFFFF00,
                 'green':0x00FF00, 'blue':0x0000FF,
                 'purple':0x9900FF, 'pink': 0xFF00FF}

print('loading buttons...')

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

while True:
    touch = ts.touch_point
    if touch:
        for i, button in enumerate(buttons):
            if button.contains(touch):
                button.selected = True
                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)
                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
            else:
                button.selected = False

This is what the final contents of the CIRCUITPY drive will look like:

Setup Code

Before you can use the code, you'll need to modify the code.py file to add a label specifying which light you're controlling.

LIFX uses an identifier called a selector to identify the lights associated with your account. With a selector, you can use the light's unique identifier, location (bedroom, workbench, living room) or lighting groups. For simplicity, this guide assumes that you will be using a label as a selector.

To find the bulb's label:

Navigate to the LIFX app and select the light you'd like to control.

Tap the gear icon to reveal the detailed settings of the light.

The Name of the light is the light's label. 

Next, modify the lifx_lights variable within code.py to include the label from the App.

For example, if you're adding a new LIFX bulb called workbench, change the code from

lifx_lights = ['label:lamp', 'label:Main Room']

to

lifx_lights = ['label:lamp', 'label:workbench']

Be sure to include label: before the name of your light.

For more information on using LIFX light selectors, check out their API documentation on this topic here.

Code Usage

From the Mu Editor, click the Serial button to open the REPL. You should see the REPL display the code's status as it loads the interface.

Select a light by tapping the name of the light you'd like to control. If none is selected, the code will default to the first light specified within the lifx_lights list.

Tap the on/off button to toggle the power of the light you selected. Keep in mind that LIFX lights save state - if your light was previously set to red and turned off, it'll turn on with the same color.

  • Buttons invert their color while they make a request to the LIFX API. Once the request is complete, the button changes back to its original color. 
  • If you're having issues with your light not responding: scroll down to the Diagnosing Errors section below.

Tapping a color button will set the light to the button's fill color with the brightness specified by the + or - buttons.

Next up, we'll take a look at the code you loaded onto your PyPortal

This guide was first published on Apr 11, 2019. It was last updated on Apr 11, 2019. This page (Code PyPortal with CircuitPython) was last updated on Sep 18, 2019.