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