Update CircuitPython
First make sure you are running the latest version of Adafruit CircuitPython for your board.
See the guide page Install CircuitPython in the Adafruit Learning System guide Adafruit PyPortal.
Once you have your CircuitPython libraries installed, you can grab the rest of the project files. Use the Download Project Bundle button in the code window below to download the code, fonts, background bitmap and a starter settings.toml file.
Copy the contents of the zip file to your PyPortal CIRCUITPY drive.
# SPDX-FileCopyrightText: 2019 Dan Cogliano for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from os import getenv
import time
import gc
import board
import busio
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import adafruit_requests
import digitalio
import analogio
from adafruit_pyportal import PyPortal
from adafruit_display_shapes.circle import Circle
from adafruit_display_shapes.roundrect import RoundRect
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import adafruit_touchscreen
from adafruit_minimqtt import MQTT
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
ssid = getenv("CIRCUITPY_WIFI_SSID")
password = getenv("CIRCUITPY_WIFI_PASSWORD")
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
aio_key = getenv("ADAFRUIT_AIO_KEY")
if None in [ssid, password, aio_username, aio_key]:
raise RuntimeError(
"WiFi and Adafruit IO settings are kept in settings.toml, "
"please add them there. The settings file must contain "
"'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', "
"'ADAFRUIT_AIO_USERNAME' and 'ADAFRUIT_AIO_KEY' at a minimum."
)
DISPLAY_COLOR = 0x006600
SWITCH_COLOR = 0x008800
SWITCH_FILL_COLOR = 0xffffff
# Switch location
SWITCHX = 260
SWITCHY = 4
FEED_NAME = "pyportal-switch"
months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
def get_local_timestamp(location=None):
# pylint: disable=line-too-long
"""Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API.
:param str location: Your city and country, e.g. ``"New York, US"``.
"""
api_url = None
location = getenv('timezone', location)
if location:
print("Getting time for timezone", location)
api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location)
else: # we'll try to figure it out from the IP address
print("Getting time from IP address")
api_url = TIME_SERVICE % (aio_username, aio_key)
api_url += TIME_SERVICE_TIMESTAMP
try:
print("api_url:",api_url)
response = requests.get(api_url)
times = response.text.split(' ')
seconds = int(times[0])
tzoffset = times[1]
tzhours = int(tzoffset[0:3])
tzminutes = int(tzoffset[3:5])
tzseconds = tzhours * 60 * 60
if tzseconds < 0:
tzseconds -= tzminutes * 60
else:
tzseconds += tzminutes * 60
print(seconds + tzseconds, tzoffset, tzhours, tzminutes)
except KeyError:
raise KeyError("Was unable to lookup the time, try setting timezone in your settings.toml according to http://worldtimeapi.org/timezones") # pylint: disable=line-too-long
# now clean up
response.close()
response = None
gc.collect()
return int(seconds + tzseconds)
def create_text_areas(configs):
"""Given a list of area specifications, create and return text areas."""
text_areas = []
for cfg in configs:
textarea = label.Label(cfg['font'], text=' '*cfg['size'])
textarea.x = cfg['x']
textarea.y = cfg['y']
textarea.color = cfg['color']
text_areas.append(textarea)
return text_areas
class Switch(object):
def __init__(self, pin, my_pyportal):
self.switch = digitalio.DigitalInOut(pin)
self.switch.direction = digitalio.Direction.OUTPUT
rect = RoundRect(SWITCHX, SWITCHY, 31, 60, 16, outline=SWITCH_COLOR,
fill=SWITCH_FILL_COLOR, stroke=3)
my_pyportal.root_group.append(rect)
self.circle_on = Circle(SWITCHX + 15, SWITCHY + 16, 10, fill=SWITCH_FILL_COLOR)
my_pyportal.root_group.append(self.circle_on)
self.circle_off = Circle(SWITCHX + 15, SWITCHY + 42, 10, fill=DISPLAY_COLOR)
my_pyportal.root_group.append(self.circle_off)
# turn switch on or off
def enable(self, enable):
print("turning switch to ", enable)
self.switch.value = enable
if enable:
self.circle_off.fill = SWITCH_FILL_COLOR
self.circle_on.fill = DISPLAY_COLOR
else:
self.circle_on.fill = SWITCH_FILL_COLOR
self.circle_off.fill = DISPLAY_COLOR
def toggle(self):
if self.switch.value:
self.enable(False)
else:
self.enable(True)
def status(self):
return self.switch.value
# you'll need to pass in an io username and key
TIME_SERVICE = "http://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s"
# See https://apidock.com/ruby/DateTime/strftime for full options
TIME_SERVICE_TIMESTAMP = '&fmt=%25s+%25z'
class Clock(object):
def __init__(self, my_pyportal):
self.low_light = False
self.update_time = None
self.snapshot_time = None
self.pyportal = my_pyportal
self.current_time = 0
self.light = analogio.AnalogIn(board.LIGHT)
text_area_configs = [dict(x=0, y=105, size=10, color=DISPLAY_COLOR, font=time_font),
dict(x=260, y=153, size=3, color=DISPLAY_COLOR, font=ampm_font),
dict(x=110, y=40, size=20, color=DISPLAY_COLOR, font=date_font)]
self.text_areas = create_text_areas(text_area_configs)
self.text_areas[2].text = "starting..."
for ta in self.text_areas:
self.pyportal.root_group.append(ta)
def adjust_backlight(self, force=False):
"""Check light level. Adjust the backlight and background image if it's dark."""
if force or (self.light.value >= 1500 and self.low_light):
self.pyportal.set_backlight(1.00)
self.low_light = False
elif self.light.value <= 1000 and not self.low_light:
self.pyportal.set_backlight(0.1)
self.low_light = True
def tick(self, now):
self.adjust_backlight()
if (not self.update_time) or ((now - self.update_time) >= 300):
# Update the time
print("update the time")
self.update_time = int(now)
self.snapshot_time = get_local_timestamp(getenv("timezone"))
self.current_time = time.localtime(self.snapshot_time)
else:
self.current_time = time.localtime(int(now) - self.update_time + self.snapshot_time)
hour = self.current_time.tm_hour
if hour > 12:
hour = hour % 12
if hour == 0:
hour = 12
time_string = '%2d:%02d' % (hour,self.current_time.tm_min)
self.text_areas[0].text = time_string
ampm_string = "AM"
if self.current_time.tm_hour >= 12:
ampm_string = "PM"
self.text_areas[1].text = ampm_string
self.text_areas[2].text = (months[int(self.current_time.tm_mon - 1)] +
" " + str(self.current_time.tm_mday))
try:
board.DISPLAY.refresh(target_frames_per_second=60)
except AttributeError:
board.DISPLAY.refresh_soon()
board.DISPLAY.wait_for_frame()
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
# This function will be called when the client is connected
# successfully to the broker.
onoff_feed = f"{aio_username}/feeds/{FEED_NAME}"
print(f"Connected to Adafruit IO! Listening for topic changes on {onoff_feed}")
# Subscribe to all changes on the onoff_feed.
client.subscribe(onoff_feed)
def disconnected(client, userdata, rc):
# This method is called when the client is disconnected
print('Disconnected from Adafruit IO!')
def message(client, topic, message):
# This method is called when a topic the client is subscribed to
# has a new message.
print('New message on topic {0}: {1}'.format(topic, message))
if message in ("ON","TRUE","1"):
switch.enable(True)
else:
switch.enable(False)
############################################
esp32_cs = digitalio.DigitalInOut(board.ESP_CS)
esp32_ready = digitalio.DigitalInOut(board.ESP_BUSY)
esp32_reset = digitalio.DigitalInOut(board.ESP_RESET)
WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
board.TOUCH_YD, board.TOUCH_YU,
calibration=(
(5200, 59000),
(5800, 57000)
),
size=(WIDTH, HEIGHT))
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset, debug=False)
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", [hex(i) for i in esp.MAC_address])
pyportal = PyPortal(esp=esp,
external_spi=spi,
default_bg="/background.bmp")
ampm_font = bitmap_font.load_font("/fonts/RobotoMono-18.bdf")
ampm_font.load_glyphs(b'ampAMP')
date_font = bitmap_font.load_font("/fonts/RobotoMono-18.bdf")
date_font.load_glyphs(b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
time_font = bitmap_font.load_font("/fonts/RobotoMono-72.bdf")
time_font.load_glyphs(b'0123456789:')
clock = Clock(pyportal)
for ap in esp.scan_networks():
print("\t%s\t\tRSSI: %d" % (str(ap['ssid'], 'utf-8'), ap['rssi']))
print("Connecting to AP...")
while not esp.is_connected:
try:
esp.connect_AP(ssid, password)
except RuntimeError as e:
print("could not connect to AP, retrying: ",e)
continue
print("Connected to", str(esp.ssid, 'utf-8'), "\tRSSI:", esp.rssi)
print("My IP address is", esp.pretty_ip(esp.ip_address))
wifi = adafruit_esp32spi_wifimanager.WiFiManager(
esp, ssid, password, debug=True
)
# Set up a MiniMQTT Client
mqtt_client = MQTT(broker='io.adafruit.com',
username=aio_username,
password=aio_key,
network_manager=wifi,
socket_pool=pool,
ssl_context=ssl_context)
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
mqtt_client.on_message = message
mqtt_client.connect()
switch = Switch(board.D4, pyportal)
second_timer = time.monotonic()
while True:
#time.sleep(1)
p = ts.touch_point
if p:
#if p[0] >= 140 and p[0] <= 170 and p[1] >= 160 and p[1] <= 220:
# touch anywhere on the screen
print("touch!")
clock.adjust_backlight(True)
switch.toggle()
time.sleep(1)
# poll once per second
if time.monotonic() - second_timer >= 1.0:
second_timer = time.monotonic()
# Poll the message queue
try:
mqtt_client.loop()
except RuntimeError:
print("reconnecting wifi")
mqtt_client.reconnect_wifi()
# Update the PyPortal display
clock.tick(time.monotonic())
When all the files are loaded onto the PyPortal CIRCUITPY drive, the following files should be on there:
Library Files
Before continuing, please make sure these files and folders are in your board's lib folder.
- adafruit_bitmap_font
- adafruit_bus_device
- adafruit_display_shapes
- adafruit_display_text
- adafruit_esp32spi
- adafruit_io
- adafruit_minimqtt
- adafruit_pyportal
- adafruit_connection_manager.mpy
- adafruit_fakerequests.mpy
- adafruit_minimqr.mpy
- adafruit_pixelbuf.mpy
- adafruit_request.mpy
- adafruit_logging.mpy
- adafruit_minimqtt.mpy
- adafruit_pyportal.mpy
- adafruit_requests.mpy
- adafruit_ticks.mpy
- adafruit_touchscreen.mpy
- neopixel.mpy
- simpleio.mpy
Settings File Setup
Next is to get the PyPortal connected to the internet. To do this, we need to create a settings file. If you have not yet set up a settings.toml file in your CIRCUITPY drive and connected to the internet using it, follow this guide and come back when you've successfully connected to the internet.
Using Mu or any text editor, you should add your Adafruit IO Username and Adafruit IO Key to the settings.toml file. This project connects to Adafruit IO to retrieve the current date and time as well as integrating voice commands using Amazon Alexa and Google Assistant. Adafruit IO is free to use, but you'll need to log in with your Adafruit account to use it. If you don't already have an Adafruit login, create one here.
If you haven't used Adafruit IO before, check out this guide for more info.
Once you have logged into your account, there are two pieces of information you'll need to place in your settings.toml file: Adafruit IO username, and Adafruit IO key. Head to io.adafruit.com and simply click the View AIO Key link on the left hand side of the Adafruit IO page to get this information.
Then, update the aoi_username and aio_key values in the settings.toml file. Your settings.toml file should look like this:
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT # This file is where you keep private settings, passwords, and tokens! # If you put them in the code you risk committing that info or sharing it CIRCUITPY_WIFI_SSID="your-wifi-ssid" CIRCUITPY_WIFI_PASSWORD="your-wifi-password" ADAFRUIT_AIO_USERNAME="my_username" ADAFRUIT_AIO_KEY="my_key" timezone="" # leave blank or use timezone from # http://worldtimeapi.org/timezones
Fonts
Two fonts are used to display the date and time on the PyPortal. You will need to create a fonts folder on your CircuitPython CIRCUITPY drive and Ensure above you downloaded the font files from the project's GitHub repository
You will also need a background image for the display. You can use the one included in the project. This background is in the public domain and can be found at here, as well as other similar backgrounds.
The background.bmp file is placed in the main (root) directory of the CIRCUITPY drive.
In the next pages, we will connect to Adafruit IO for remote controlling the appliance.
Page last edited April 15, 2025
Text editor powered by tinymce.