CircuitPython Microcontroller Software Development

PyPortal Event Countdown Clock Mods III

Some big updates to the software for the modified PyPortal Event Countdown Clock. The UI was changed and several features added. It also seems to be more stable as it does not lock-up randomly. Let’s get into the changes.

  • Header and Footer status bars display information
    • Header
      • Temperature
        • Touch to change between Fahrenheit and Celsius
      • Time
        • Touch to change between 24 hour and 12 hour formats
      • Network Strength
    • Footer
      • Brightness
        • Touching the left side of the footer, dims the display backlight
        • Touching the right side of the footer, increases the brightness of the display backlight
        • Touching the middle of the footer, returns to auto brightness of the display backlight
      • Event of the number of events
  • Change the event displayed by touching the left of the display to show previous event or the right side to advance to the next event.
  • If an event has passed more than a day ago, they will be removed from the list of events and no longer displayed
  • The events do not need to be in order in the JSON file. They are sorted when they are loaded from the SD Card with the soonest event being displayed first.
Demo of the PyPortal Event Countdown Clock Mods

Noticed that the temperature was several degrees too high. This was due to the ADT7410 being too close to devices generating heat. I removed the ADT7410 and placed it on a small board and added a PH2 JST connector so it may be plugged into the I2C socket. This resulted in much more usable temperature reading.

PyPortal Event Countdown with temperature sensor moved off the PyPortal
ADT7410 Temperature Sensor moved to a separate board.

Adafruit PyPortal Titano

I just purchased an Adafruit PyPortal Titano, Product ID: 4444, to test out the software and see about allowing the software to be used on different sized screens. The Titano, has a 480×320 screen verses 320×240 of the PyPortal, Product ID: 4116.

The PyPortal Titano that I received had CircuitPython 5 installed, so the first order of business was to install CircuitPython 8. I started by going to the PyPortal CircuitPython Guide on Learn.Adafruit.

  1. Download the latest version of CircuitPython for the PyPortal Titano board by going to
  2. Download the UF2 file for CircuitPython 8.0.5
  3. Double click the reset button on the back of the Titano board and make certain the RGB LED turns green. If is is not green, try again.
  4. Copy the file to the PORTALBOOT drive that shows on your PC
    NOTE: Do not be concerned that the preinstalled demo no longer works. The library files are different for different versions.
  5. Download the CircuitPython 8 library files from
    NOTE: This step is optional as the required libraries are in the GitHub project files
  6. Go to GitHub and download the project files from
  7. Delete the files on the PyPortal Titano
  8. Unzip the files and copy the files in the PyPortal folder to the PyPortal Titano
  9. The PyPortal Titano will restart and the images will change between “Connecting to the Internet” and “Failed to Connect”. The images will not take up the full screen due to the difference in resolution. (Until the code is modified.) The reason it is not connecting is we need add files to an SD Card and put that into the PyPortal Titano.
  10. Copy the files in the SD_Card folder, from GitHub, and paste them into the root of a micro SD Card.
  11. While the SD Card is still in you PC, double click on the index.html file to open the configuration utility.
  12. You may choose to load the config.json file on the card or start creating a new file from scratch by adding new events.
  13. Once you are satisfied with the events, click on the “Secrets” tab and enter your Wi-Fi settings, Timezone, and Adafruit IO Account information.
  14. Once done, click the “Save JSON File” button and save it to the SD Card as config.json.
  15. Remove the card from the PC and insert it into the PyPortal Titano.
  16. Click the reset button on the back of the PyPortal Titano.
  17. If everything was done correctly, you can anticipate that the events will be displayed on the screen
PyPortal Event Countdown running on the PyPortal Titano
The application is in the upper left corner of the screen with space to the right and bottom.

You may notice from the image of the PyPortal Titano screen, the temperature is not displayed as the PyPortal Titano does not have the ADT7410 Temperature Sensor.

The next step will be to see how to detect the screen size or make it a setting in the config.json file.

CircuitPython Software Development Web Application Development

PyPortal Event Countdown Clock Mods II

I have implemented some of the items on my to do list from last week’s post and tested out a few more ideas. I have also published version 0.1 to GitHub.

I want to provide a few details on version 0.1 of the code and what is being worked for version 1.0. The two releases are very different with 1.0 being the one that I recommend you use once it is available.

Version 0.1 does automatically change the backlight brightness but it is not too dramatic therefore not very useful in my opinion. I’m disappointed by the way that turned out but plan to address it in version 1.0 with automatic and manual setting of the backlight.

Version 0.1 also freezes randomly, however I think it is a memory issue. I plan to address this by calling the CircuitPython garbage collection and/or implementing a watchdog timer. Not certain at the moment, which direction I will take but suspect that I will implement both.

Version 0.1 will also load all events, even those in the past so it is not very useful. Version 1.0 will address this by not loading past events.

The next version, 1.0, will have a different screen layout. As stated above, I wanted a way to manually control the backlighting so I have added icons at the bottom of the screen with touch target areas for increasing, decreasing, and automatic setting of the screen backlight. In order to add this as an option, I have created a 20 pixel high footer on the screen for the icons and to display brightness information. I may also display when the time is being updated as the touch interface is non-responsive during an update as it is a blocking call.

A 20 pixel header will also be included to display the time, the current event of the number of events, and a network strength indicator. The header and footer take away 40 pixels from the screen height but I think it will be worth it.

Hopefully I will finish this new version shortly and will have it available on GitHub. As you may have guessed, I will be on vacation/holiday for two weeks in about two weeks so there will not be any posts while I am away, unless it is about the vacation and I have stable enough internet to make some posts.

CircuitPython Microcontroller

PyPortal Event Countdown Clock Mods

I recently purchased an Adafruit PyPortal and looked at using John Park’s project PyPortal Event Countdown Clock project. It was fairly easy to get it up an running but switching from CircuitPython 4 to 8, proved to cause a bit of a challenge, not with the code, but with the libraries. Some were moved from one file to multiple files in one folder but eventually, I got it all sorted out.

I plan to use the PyPortal on an upcoming cruise, which lead me to some ideas of how to change the code to make it a bit more useful and easier to change events. Below is a list of some of the ideas to be implemented.

  • (Implemented) Allow for multiple events to be tracked
  • (Implemented) Use touch to change which event is being displayed
  • (Implemented) Add title and subtitle to the display
  • Load the events and event images from an SD Card
    • Use a JSON file
    • Create a webpage to easily modify the JSON
  • If possible, add the WiFi information to the config file
  • Add a clock option and easily switch between countdown and clock
  • Dim or turn off the backlight if the room is dark
  • Ignore events that have passed (Date is not today but in the past)

The first step was to add multiple events and add touch capability to allow moving back and forth through the events. Adding multiple events was not difficult. A new class files was added for event objects and a list was created to hold them. Next was to add touch and split the screen in half with a 20 pixel dead zone in the middle so that touching the left side would take you to the previous event and touching the right side would move to the next event. It was also necessary to add a variable to track the last index so we would know when to load the selected event.

Touch Zones for event change
Touch zones on PyPortal 320×240 screen

import time

class event:
    def __init__(self, title, subtitle, year, month, day, hour, minute, imageCountDown, imageEventDay, forecolor=0xF0C810):
        self.title = title
        self.subtitle = subtitle
        self.forecolor = forecolor
        self.year = year
        self.month = month = day
        self.hour = hour
        self.minute = minute = time.struct_time((year, month, day,
                                      hour, minute, 0,  # we don't track seconds
                                      -1, -1, False))  # we dont know day of week/year or DST
        self.imageCountDown = imageCountDown
        self.imageEventDay = imageEventDay

# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries
# SPDX-License-Identifier: MIT

This example will figure out the current local time using the internet, and
then draw out a countdown clock until an event occurs!
Once the event is happening, a new graphic is shown
import time
import board
import busio
from adafruit_pyportal import PyPortal
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from analogio import AnalogIn
import adafruit_adt7410
import adafruit_touchscreen
from event import event

events = []

events.append(event("Day 1", "Barcelona, Spain",
              2023, 5, 29, 00, 00,
              "barcelona_background.bmp", "barcelona_event.bmp"))
events.append(event("Day 2", "Fun Day At Sea",
              2023, 5, 30, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 3", "Málaga, Spain",
              2023, 5, 31, 00, 00,
              "countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 4", "Gibraltar (UK)",
              2023, 6, 1, 00, 00,
              "countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 5", "Lisbon, Portugal",
              2023, 6, 2, 00, 00,
              "countdown_background.bmp", "countdown_event.bmp"))
events.append(event("Day 6", "Fun Day At Sea",
              2023, 6, 3, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 7", "Fun Day At Sea",
              2023, 6, 4, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 8", "Fun Day At Sea",
              2023, 6, 5, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 9", "Fun Day At Sea",
              2023, 6, 6, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 10", "Fun Day At Sea",
              2023, 6, 7, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 11", "Fun Day At Sea",
              2023, 6, 8, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 12", "Fun Day At Sea",
              2023, 6, 9, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 13", "Fun Day At Sea",
              2023, 6, 10, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 14", "Fun Day At Sea",
              2023, 6, 11, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 15", "Fun Day At Sea",
              2023, 6, 12, 00, 00,
              "funday_background.bmp", "funday_event.bmp"))
events.append(event("Day 16", "New York, New York",
              2023, 6, 13, 00, 00,
              "nyc_background.bmp", "nyc_event.bmp"))

# determine the current working directory
# needed so we know where to find files
cwd = ("/"+__file__).rsplit('/', 1)[0]
# Initialize the pyportal object and let us know what data to fetch and where
# to display it
pyportal = PyPortal(status_neopixel=board.NEOPIXEL,

big_font = bitmap_font.load_font(cwd+"/fonts/Helvetica-Bold-36.bdf")
big_font.load_glyphs(b'0123456789')  # pre-load glyphs for fast printing
title_font = bitmap_font.load_font(cwd+"/fonts/Arial-36.bdf")
subtitle_font = bitmap_font.load_font(cwd+"/fonts/Arial-20.bdf")

text_color = 0xFFFFFF
days_position = (big_font, 8, 207)
hours_position = (big_font, 110, 207)
minutes_position = (big_font, 220, 207)
title_position = (title_font, 8, 140)
subtitle_position = (subtitle_font, 8, 175)

text_areas = []
for pos in (days_position, hours_position, minutes_position, title_position, subtitle_position):
    textarea = Label(pos[0])
    textarea.x = pos[1]
    textarea.y = pos[2]
    textarea.color = text_color

refresh_time = None

# Touchscreen setup
# ------Rotate 270:
screen_width = 240
screen_height = 320
ts = adafruit_touchscreen.Touchscreen(board.TOUCH_XL, board.TOUCH_XR,
                                      board.TOUCH_YD, board.TOUCH_YU,
                                          (5200, 59000), (5800, 57000)),
                                      size=(screen_width, screen_height))

# ------------- Inputs and Outputs Setup ------------- #
light_sensor = AnalogIn(board.LIGHT)
    # attempt to init. the temperature sensor
    i2c_bus = busio.I2C(board.SCL, board.SDA)
    adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48)
    adt.high_resolution = True
except ValueError:
    # Did not find ADT7410. Probably running on Titano or Pynt
    adt = None

event_index = 0
last_event_index = -1

while True:
    touch = ts.touch_point
    light = light_sensor.value

    if touch:
        # print(f"Touched x={ts.touch_point[0]}, y={ts.touch_point[1]}")
        if touch[0] < 150:
            event_index = event_index - 1
            if event_index < 0:
                event_index = len(events) - 1
        elif touch[0] > 170:
            event_index = event_index + 1
            if event_index > len(events) - 1:
                event_index = 0

    if event_index != last_event_index:
        pyportal.set_background(cwd + "/" + events[event_index].imageCountDown)
        text_areas[3].color = events[event_index].forecolor
        text_areas[3].text = events[event_index].title
        text_areas[4].color = events[event_index].forecolor
        text_areas[4].text = events[event_index].subtitle
        last_event_index = event_index

    # only query the online time once per hour (and on first run)
    if (not refresh_time) or (time.monotonic() - refresh_time) > 3600:
            print("Getting time from internet!")
            refresh_time = time.monotonic()
        except RuntimeError as e:
            print("Some error occured, retrying! -", e)

    now = time.localtime()
    print("Current time:", now)
    remaining = time.mktime(events[event_index].date) - time.mktime(now)
    print("Time remaining (s):", remaining)
    if remaining < 0:
        # oh, its event time!
        pyportal.set_background(cwd + "/" + events[event_index].imageEventDay)
        while True:  # that's all folks
    secs_remaining = remaining % 60
    remaining //= 60
    mins_remaining = remaining % 60
    remaining //= 60
    hours_remaining = remaining % 24
    remaining //= 24
    days_remaining = remaining
    print("%d days, %d hours, %d minutes and %s seconds" %
          (days_remaining, hours_remaining, mins_remaining, secs_remaining))
    text_areas[0].text = '{:>2}'.format(days_remaining)  # set days textarea
    text_areas[1].text = '{:>2}'.format(hours_remaining)  # set hours textarea
    text_areas[2].text = '{:>2}'.format(mins_remaining)  # set minutes textarea

    # update every 10 seconds
    # time.sleep(10)

Short Demo of implementing multiple events