Categories
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

event.py

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
        self.day = day
        self.hour = hour
        self.minute = minute
        self.date = 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

code.py

# 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,
                    default_bg=cwd+"/coming_soon.bmp")

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
    pyportal.splash.append(textarea)
    text_areas.append(textarea)

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,
                                      calibration=(
                                          (5200, 59000), (5800, 57000)),
                                      size=(screen_width, screen_height))

# ------------- Inputs and Outputs Setup ------------- #
light_sensor = AnalogIn(board.LIGHT)
try:
    # 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:
        try:
            print("Getting time from internet!")
            pyportal.get_local_time(False)
            refresh_time = time.monotonic()
        except RuntimeError as e:
            print("Some error occured, retrying! -", e)
            continue

    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
            pass
    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

By richteel

Software and hardware developer who likes learning new things with a passion of sharing knowledge with others.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: