import time import json from secrets import secrets import board from adafruit_pyportal import PyPortal from adafruit_bitmap_font import bitmap_font from adafruit_display_text.label import Label from digitalio import DigitalInOut, Direction, Pull import analogio import displayio import adafruit_logging as logging
Notably, the secrets.py file contains a dictionary containing hidden information (like api keys, wifi network credentials, etc.) and some general configuration information like location strings for the time and weather services. It's not in the repo, so copy the template below into a text editor and replace the placeholders with your information.
secrets = { 'ssid' : 'your wifi ssid', 'password' : 'your wifi password', 'timezone' : None, 'openweather_token': 'get it from openweather.org', 'celcius': True, 'timezone': 'your timezone string', 'city_id': 'your openweather city id' }
You can find a list of cities and their IDs in http://bulk.openweathermap.org/sample/city.list.json.gz
Save the file to the main (root) directory of your PyPortal CIRCUITPY flash drive.
Configuration and Variables
The data source and location for the PyPortal data fetch support needs to be set up to get the weather information:
# Set up where we'll be fetching data from DATA_SOURCE = 'http://api.openweathermap.org/data/2.5/weather?id='+secrets['city_id'] DATA_SOURCE += '&appid='+secrets['openweather_token'] # You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22' DATA_LOCATION = []
With that set up, we can create the PyPortal instance as well as the light sensor and snooze button input:
pyportal = PyPortal(url=DATA_SOURCE, json_path=DATA_LOCATION, status_neopixel=board.NEOPIXEL) light = analogio.AnalogIn(board.LIGHT) snooze_button = DigitalInOut(board.D3) snooze_button.direction = Direction.INPUT snooze_button.pull = Pull.UP
There is a variety of global variables having to do with managing the alarm, including snoozing:
alarm_background = 'red_alert.bmp' alarm_file = 'alarm.wav' alarm_enabled = True alarm_armed = True alarm_interval = 10.0 alarm_hour = 7 alarm_minute = 00 snooze_time = None snooze_interval = 600.0
Finally there are variables relating to weather display, update timing, and tracking the light level (to avoid repeatedly updating the display).
icon_file = None icon_sprite = None celcius = secrets['celcius'] refresh_time = None update_time = None weather_refresh = None current_time = None low_light = False
To provide some visual variation as well as fit more information on the screen, three fonts are used in this project:
- a large font for displaying the current time,
- a medium font for displaying the alarm time, and
- a small font for displaying the temperature.
The time is the main piece of information so we want a big, bold, highly visible font for that. Having the alarm time visible is nice to have, and it also provides a clear indication of whether the alarm is turned on. The weather and temperature aren't crucial, so a smaller font suffices, as well as being sized in keeping with the weather graphic.
#################### # Load the fonts time_font = bitmap_font.load_font('/fonts/Anton-Regular-104.bdf') time_font.load_glyphs(b'0123456789:') # pre-load glyphs for fast printing alarm_font = bitmap_font.load_font('/fonts/Helvetica-Bold-36.bdf') alarm_font.load_glyphs(b'0123456789:') temperature_font = bitmap_font.load_font('/fonts/Arial-16.bdf') temperature_font.load_glyphs(b'-0123456789CF')
Logging
The logger module (see this guide for information on it) is used for debug and error output. It's initial included in the repository, but will be in the bundle eventually. It needs to be initialized and the logging level set. If you want to see debug information, set this to logging.DEBUG
instead.
logger = logging.getLogger('alarm_clock') logger.setLevel(logging.ERROR) # change as desired
Support Functions
In addition to the state classes, there are a handful of support functions.
create_text_areas
created a list of TextArea
instances based on a list of dictionaries that define the position, font, color, and text length of each area:
def create_text_areas(configs): """Given a list of area specifications, create and return test areas.""" text_areas = [] for cfg in configs: textarea = 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
We have a clear_splash
function that removes display elements from the PyPortal, leaving only the background. Since the background is set up first by the PyPortal code, this can simply pop off elements that have been added since then.
def clear_splash(): for _ in range(len(pyportal.splash) - 1): pyportal.splash.pop()
Finally we have a function that takes a touch (which is a tuple of x, y, and pressure values) and a button definition (a dictionary with left, right, top, and bottom keys) and returns whether the touch is in the area covered by the button.
def touch_in_button(t, b): in_horizontal = b['left'] <= t[0] <= b['right'] in_vertical = b['top'] <= t[1] <= b['bottom'] return in_horizontal and in_vertical