Ensure your secrets.py file should at the very least have values for the fields ssid, password, timezone, aio_username, and aio_key. Make sure to copy it over to CIRCUITPY as well.

Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.

Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory FunHouse_IOT_Hub/iot_hub/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.

Your CIRCUITPY drive should now look similar to the following image:

CIRCUITPY
Be sure you have the secrets.py file complete and loaded onto the CIRCUITPY drive also or the code will not run properly.
# SPDX-FileCopyrightText: 2021 Eva Herrada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import ssl
import displayio
import board
from digitalio import DigitalInOut, Direction, Pull
from adafruit_display_text.label import Label
import terminalio
import touchio
import socketpool
import wifi
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_io.adafruit_io import IO_MQTT
from adafruit_dash_display import Hub

# Set up navigation buttons
up = DigitalInOut(board.BUTTON_UP)
up.direction = Direction.INPUT
up.pull = Pull.DOWN

select = DigitalInOut(board.BUTTON_SELECT)
select.direction = Direction.INPUT
select.pull = Pull.DOWN

down = DigitalInOut(board.BUTTON_DOWN)
down.direction = Direction.INPUT
down.pull = Pull.DOWN

back = touchio.TouchIn(board.CAP7)
submit = touchio.TouchIn(board.CAP8)

# Check for secrets.py. Note: for this project, your secrets.py needs an adafruit io api key as
# well as the wifi information
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# Make the rgb group for setting rgb hex values for NeoPixels
rgb_group = displayio.Group()
R_label = Label(
    terminalio.FONT,
    text="   +\nR:\n   -",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(5, 120),
    scale=2,
)
G_label = Label(
    terminalio.FONT,
    text="   +\nG:\n   -",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(90, 120),
    scale=2,
)
B_label = Label(
    terminalio.FONT,
    text="   +\nB:\n   -",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(175, 120),
    scale=2,
)
rgb_group.append(R_label)
rgb_group.append(G_label)
rgb_group.append(B_label)
R = Label(
    terminalio.FONT,
    text="00",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(35, 120),
    scale=2,
)
G = Label(
    terminalio.FONT,
    text="00",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(120, 120),
    scale=2,
)
B = Label(
    terminalio.FONT,
    text="00",
    color=0xFFFFFF,
    anchor_point=(0, 0.5),
    anchored_position=(205, 120),
    scale=2,
)
rgb_group.append(R)
rgb_group.append(G)
rgb_group.append(B)

# Set up callbacks

# pylint: disable=unused-argument
def rgb(last):
    """ Function for when the rgb screen is active """
    display.root_group = displayio.CIRCUITPYTHON_TERMINAL
    rgb_group[3].text = "00"
    rgb_group[4].text = "00"
    rgb_group[5].text = "00"
    display.root_group = rgb_group
    time.sleep(0.2)
    index = 0
    colors = [00, 00, 00]

    while True:
        if select.value:
            index += 1
            if index == 3:
                index = 0
            time.sleep(0.3)
            continue

        if up.value:
            colors[index] += 1
            if colors[index] == 256:
                colors[index] = 0
            rgb_group[index + 3].text = hex(colors[index])[2:]
            time.sleep(0.01)
            continue

        if down.value:
            colors[index] -= 1
            if colors[index] == -1:
                colors[index] = 255
            rgb_group[index + 3].text = hex(colors[index])[2:]
            time.sleep(0.01)
            continue

        if submit.value:
            color = ["{:02x}".format(colors[i]) for i in range(len(colors))]
            color = "#" + "".join(color)
            iot.publish("neopixel", color)
            break

        if back.value:
            break
        time.sleep(0.1)

    display.root_group = displayio.CIRCUITPYTHON_TERMINAL
    time.sleep(0.1)

def rgb_set_color(message):
    """ Sets the color of the rgb label based on the value of the feed """
    return int(message[1:], 16)

def door_color(message):
    """ Sets the color of the door label based on the value of the feed """
    door = bool(int(message))
    if door:
        return int(0x00FF00)
    return int(0xFF0000)

def on_door(client, feed_id, message):
    """ Sets the door text based on the value of the feed """
    door = bool(int(message))
    if door:
        return "Door: Closed"
    return "Door: Open"

def pub_lamp(lamp):
    if isinstance(lamp, str):
        lamp = eval(lamp)  # pylint: disable=eval-used
    iot.publish("lamp", str(not lamp))
    # funhouse.set_text(f"Lamp: {not lamp}", 0)
    time.sleep(0.3)

display = board.DISPLAY

# Set your Adafruit IO Username and Key in secrets.py
# (visit io.adafruit.com if you need to create an account,
# or if you need your Adafruit IO key.)
aio_username = secrets["aio_username"]
aio_key = secrets["aio_key"]

print("Connecting to %s" % secrets["ssid"])
wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected to %s!" % secrets["ssid"])

# Create a socket pool
pool = socketpool.SocketPool(wifi.radio)

# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=secrets["aio_username"],
    password=secrets["aio_key"],
    socket_pool=pool,
    ssl_context=ssl.create_default_context(),
)

# Initialize an Adafruit IO MQTT Client
io = IO_MQTT(mqtt_client)

iot = Hub(display=display, io=io, nav=(up, select, down, back, submit))

iot.add_device(
    feed_key="lamp",
    default_text="Lamp: ",
    formatted_text="Lamp: {}",
    pub_method=pub_lamp,
)
iot.add_device(
    feed_key="temperature",
    default_text="Temperature: ",
    formatted_text="Temperature: {:.1f} C",
)
iot.add_device(
    feed_key="humidity", default_text="Humidity: ", formatted_text="Humidity: {:.2f}%"
)
iot.add_device(
    feed_key="neopixel",
    default_text="LED: ",
    formatted_text="LED: {}",
    color_callback=rgb_set_color,
    pub_method=rgb,
)
iot.add_device(
    feed_key="battery",
    default_text="Battery: ",
    formatted_text="Battery: {}%",
)
iot.add_device(
    feed_key="door",
    default_text="Door: ",
    formatted_text="Door: {}",
    color_callback=door_color,
    callback=on_door,
    )

iot.get()

while True:
    iot.loop()
    time.sleep(0.01)

Customizing the Devices

Lines 204-236 in code.py are where I add all the devices. It's good to note that while I do call them devices, they're just Adafruit IO feeds with a few functions that make it easier to handle the data how we want to.

If you'd like to add your own device, it's good to know what the parameters are:

  • feed_key - The key of the Adafruit IO feed you want to use. This can't be left blank.
  • default_text - The text to set to the display before a value has been gotten. You can leave this blank and it should be fine.
  • formatted_text - The text in a way that can be formatted using curly braces (ex. "Lamp: {}". If you want a specific number of decimal points to be displayed put :.1f in the curly braces, replacing 1 with the precision you want. If you leave this blank it won't display any text unless you have a custom callback function that returns the formatted text you want.
  • color_callback - The function you want to be called whenever the device gets a new value to set the color of the text for that device. You can see two examples of this on line 150 and 154 of code.py. You can leave this blank.
  • callback - The callback you want to run after the device gets a new message. This can do a number of things like turn on the FunHouse's built in dotstars or play a sound. There's a function on line 161 that shows how you could use a callback to set the text in a slightly more complicated way. If left blank, this defaults to a built-in callback that just uses formatted_text to set the text.
  • pub_method - The function you want to call when the select button is pressed. Only 2 devices in code.py have one of these. If you look at line 168 you can see an example of a simple one that just toggles the lamp, but if you look at line 102 you can see a much more complicated one that displays a whole new screen and ui.

Code Behavior

The code should show 6 lines with the various feeds. To navigate use the three buttons left of the display. The upper button moves the cursor up, the middle button will let you publish a new value to the feed if that is enabled (in my example it's only enabled for the lamp and neopixel feed).

If you wanted to turn the lamp on, you'd press the middle button and it will turn on pretty quickly.

If you want to change the color of the NeoPixels, navigate down to it with the third button then select it with the middle button. From there it will take you to a screen where you can edit the hex values for the red, green, and blue components of the NeoPixels. Use the up and down button to increase or decrease them and use the select button to switch between the colors. When you're done, you can submit it with the Capacitive Touch 8 button on the upper right of the screen. If you decide that you don't want to submit it, press the Capacitive Touch 7 button on the upper left of the screen and it will take you back to the home screen without submitting the value you set.

This guide was first published on May 26, 2021. It was last updated on Mar 28, 2024.

This page (Code the FunHouse IOT Hub) was last updated on Mar 27, 2024.

Text editor powered by tinymce.