Using CircuitPython is recommended for beginner to intermediate coders. It's more compact, easier to understand, and easy to change and debug.

Add CircuitPython Libraries

This page assumes your PyPortal is set up with the appropriate CircuitPython libraries and has been connected to the internet. 

Add CircuitPython Code

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 Adafruit_IO_Scheduled_Trigger.

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

# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import busio
from digitalio import DigitalInOut
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import neopixel
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_io.adafruit_io import IO_MQTT

### WiFi ###

# Get wifi details and more from a file
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in, please add them there!")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(
    board.NEOPIXEL, 1, brightness=0.2
)  # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
# Uncomment below for an externally defined RGB LED
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

# Set up a pin for controlling the relay
power_pin = DigitalInOut(board.D3)

# Define callback functions which will be called when certain events happen.
# pylint: disable=unused-argument
def connected(client):
    # Connected function will be called when the client is connected to Adafruit IO.
    # This is a good place to subscribe to feed changes.  The client parameter
    # passed to this function is the Adafruit IO MQTT client so you can make
    # calls against it easily.
    print("Connected to Adafruit IO!")

def subscribe(client, userdata, topic, granted_qos):
    # This method is called when the client subscribes to a new feed.
    print("Listening for changes on relay feed...")

def unsubscribe(client, userdata, topic, pid):
    # This method is called when the client unsubscribes from a feed.
    print("Unsubscribed from {0} with PID {1}".format(topic, pid))

# pylint: disable=unused-argument
def disconnected(client):
    # Disconnected function will be called when the client disconnects.
    print("Disconnected from Adafruit IO!")

# pylint: disable=unused-argument
def on_message(client, feed_id, payload):
    # Message function will be called when a subscribed feed has a new value.
    # The feed_id parameter identifies the feed, and the payload parameter has
    # the new value.
    print("Feed {0} received new value: {1}".format(feed_id, payload))

def on_relay_msg(client, topic, message):
    # Method called whenever user/feeds/relay has a new value
    if message == "morning":
        print("Morning - turning outlet ON")
        power_pin.value = True
    elif message == "night":
        print("Night - turning outlet OFF")
        power_pin.value = False
        print("Unexpected value received on relay feed.")

# Connect to WiFi
print("Connecting to WiFi...")

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Initialize a new MQTT Client object
mqtt_client = MQTT.MQTT(

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

# Connect the callback methods defined above to Adafruit IO
io.on_connect = connected
io.on_disconnect = disconnected
io.on_subscribe = subscribe
io.on_unsubscribe = unsubscribe
io.on_message = on_message

# Connect to Adafruit IO
print("Connecting to Adafruit IO...")

# Set up a message handler for the relay feed
io.add_feed_callback("relay", on_relay_msg)

# Subscribe to all messages on the relay feed

# Get the most recent value on the relay feed

# Start a blocking loop to check for new messages
while True:
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data, retrying\n", e)

Secrets File Setup

Open the file on your CircuitPython device using Mu or your favorite text editor. You're going to edit this file to enter your WiFi credentials along with your keys. 

  • Change ssid to the name of your WiFi network
  • Change password to your WiFi network's password 
  • Change aio_username to your Adafruit IO Username
  • Change aio_key to your Adafruit IO Key.
secrets = {
    'ssid' : 'home ssid',
    'password' : 'my password',
    'timezone' : "America/New_York", #
    'aio_username' : 'MY_ADAFRUIT_IO_USERNAME',
    'aio_key' : 'MY_ADAFRUIT_IO_KEY',

Code Usage

The PyPortal should boot up. The code connects the PyPortal to your WiFi network. Then, it connects to Adafruit IO and subscribes to the feed you set up earlier.

This code continuously checks the relay feed for new values. 

Every weekday at 8AM, the scheduled action publishes the value "morning" to your relay feed.

When the feed updates with a new value, the on_battery_msg() function executes and checks if the value matches the text "morning". 

If the feed's value matches the text "morning", the PyPortal's D3 pin is written high. This turns on the light connected to the outlet.

Every evening at 11PM,  the scheduled trigger publishes the value "night" to your relay feed. The on_battery_msg() will read the new value, check if it matches the text "night", and turns off the outlet.


My appliance is not turning on or off.

First, check your wiring against the assembly and wiring page.

Next, make sure your Adafruit IO feed is named relay and your triggers are set up to send the value morning and night every weekday.


Then, navigate to your Feeds page and click the relay feed. If it's been a day since you set up the triggers, the values morning or night should be in the feed's value list. If they're not, check the action set up.

This guide was first published on Sep 29, 2020. It was last updated on Jun 17, 2024.

This page (Code with CircuitPython) was last updated on Jun 17, 2024.

Text editor powered by tinymce.