diff --git a/__main__.py b/__main__.py index 5ab97fd..bc933d9 100644 --- a/__main__.py +++ b/__main__.py @@ -1,15 +1,26 @@ -from datetime import datetime +from datetime import datetime, timedelta, timezone from PIL import Image, ImageDraw, ImageFont +import cache +import ptv + +# Data +now = datetime.now(timezone.utc) +with cache.open() as c: + ptv_data = { + k: (v - now) // timedelta(minutes=1) if v else '-' + for k, v in ptv.get_departure_data(now, c).items() + } + BLACK = 0x000000 # 00 BGR WHITE = 0xffffff # 01 YELLOW = 0x00ffff # 10 RED = 0x0000ff # 11 -fontmm = ImageFont.truetype('NETWORKSANS-2019-REGULAR.TTF', 20) -font24 = ImageFont.truetype('NETWORKSANS-2019-REGULAR.TTF', 24) -font42 = ImageFont.truetype('NETWORKSANS-2019-REGULAR.TTF', 42) -font64 = ImageFont.truetype('NETWORKSANS-2019-REGULAR.TTF', 64) -fonttime = ImageFont.truetype('NETWORKSANS-2019-REGULAR.TTF', 92) +fontmm = ImageFont.truetype('assets/NETWORKSANS-2019-REGULAR.TTF', 20) +font24 = ImageFont.truetype('assets/NETWORKSANS-2019-REGULAR.TTF', 24) +font42 = ImageFont.truetype('assets/NETWORKSANS-2019-REGULAR.TTF', 42) +font64 = ImageFont.truetype('assets/NETWORKSANS-2019-REGULAR.TTF', 64) +fonttime = ImageFont.truetype('assets/NETWORKSANS-2019-REGULAR.TTF', 92) image = Image.new('RGB', (800, 480), WHITE) draw = ImageDraw.Draw(image) @@ -19,7 +30,6 @@ with Image.open('assets/ui test 2.png').convert('RGBA') as bg: image.paste(bg, (0, 0)) # Time -now = datetime.now() draw.text((-3, 480-3), f'{now:%H:%M}', font=fonttime, anchor="ls", fill=WHITE) # Date @@ -46,6 +56,16 @@ with Image.open(rainimgpath) as rainimg: image.paste(rainimg, mask=rainimg) draw.text((40, 55), f'mm', font=fontmm, anchor="mt") +# PTV +draw.text((375, 120), f'{ptv_data["612 S"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((500, 120), f'{ptv_data["612 N"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((700, 120), f'{ptv_data["766 N"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((375, 270), f'{ptv_data["Union W Express"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((500, 270), f'{ptv_data["Union W"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((700, 270), f'{ptv_data["Union E"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((500, 420), f'{ptv_data["109 W"]}', font=font64, anchor="ms", fill=BLACK) +draw.text((700, 420), f'{ptv_data["70 W"]}', font=font64, anchor="ms", fill=BLACK) + image.save('test_before_palette.png') # Dither into eink palette diff --git a/NETWORKSANS-2019-REGULAR.TTF b/assets/NETWORKSANS-2019-REGULAR.TTF similarity index 100% rename from NETWORKSANS-2019-REGULAR.TTF rename to assets/NETWORKSANS-2019-REGULAR.TTF diff --git a/cache.py b/cache.py index 953389b..2a8150d 100644 --- a/cache.py +++ b/cache.py @@ -6,12 +6,12 @@ class Cache: def __init__(self, db): self.db = db - def get(self, key, timeout, f): + def get(self, key, ttl, f): now = datetime.datetime.now(datetime.timezone.utc) - earliest_acceptable = now - timeout + earliest_acceptable = now - ttl match self.db.execute( 'SELECT value FROM cache WHERE key = ? AND timestamp >= ?', - (key, earliest_acceptable.isoformat()), + (key, earliest_acceptable), ).fetchall(): case [(value,)]: return value @@ -20,7 +20,7 @@ class Cache: self.db.execute( 'INSERT INTO cache (key, timestamp, value) VALUES (?, ?, ?) ' + 'ON CONFLICT(key) DO UPDATE SET timestamp = excluded.timestamp, value = excluded.value', - (key, now.isoformat(), value), + (key, now, value), ) return value diff --git a/ptv.py b/ptv.py index dd07c16..37d88f2 100644 --- a/ptv.py +++ b/ptv.py @@ -1,24 +1,26 @@ -from collections import defaultdict -from datetime import datetime +from datetime import datetime, timedelta from hashlib import sha1 import hmac import httpx +import json import os dev_id = os.environ['PTV_USER_ID'] api_key = os.environ['PTV_API_KEY'] def sign(request): - request += '&' if ('?' in request) else '?' + request += '&' if '?' in request else '?' request += 'devid=' request += dev_id hashed = hmac.new(api_key.encode(), request.encode(), sha1) return f'https://timetableapi.ptv.vic.gov.au{request}&signature={hashed.hexdigest().upper()}' -def fetch(path): - res = httpx.get(sign(path)) - res.raise_for_status() - return res.json() +def fetch(path, cache, ttl=timedelta(minutes=5)): + def do(): + res = httpx.get(sign(path)) + res.raise_for_status() + return res.text + return json.loads(cache.get(path, ttl, do)) # Lilydale route 9 (type 0) (dir 1 in, 8 out) (stop 1229) # Belgrave route 2 (type 0) (dir 1 in, 2 out) (stop 1229) @@ -27,52 +29,34 @@ def fetch(path): # 766 route 15800 (type 2) (dir 13 N, 207 S) (stop 17861) # 612 route 13024 (type 2) (dir 13 N, 158 S) (stop 17861) -class Route: - def __init__(self, name, route_id, route_type_id, directions, stops): - self.name = name - self.route_id = route_id - self.route_type_id = route_type_id - self.directions = directions - self.stops = stops +def fetch_departures(stop_id, route_type_id, cache): + # TODO: Expand runs + return fetch(f'/v3/departures/route_type/{route_type_id}/stop/{stop_id}', cache)['departures'] -local_routes = [ - Route('Lilydale', 9 , 0, [(1, 'in'), (8, 'out')] , [1229] ), - Route('Belgrave', 2 , 0, [(1, 'in'), (2, 'out')] , [1229] ), - Route('109' , 722 , 1, [(2, 'E'), (3, 'W')] , [2415, 2460]), - Route('70' , 940 , 1, [(28, 'E'), (29, 'W')] , [2162, 2161]), - Route('766' , 15800, 2, [(13, 'N'), (188, 'S')] , [17861] ), - Route('612' , 13024, 2, [(13, 'N'), (139, 'S')] , [17861] ), -] -# exp 951624 -# non-exp 951826 - -def fetch_departures(stop_id, route_type_id): - return fetch(f'/v3/departures/route_type/{route_type_id}/stop/{stop_id}')['departures'] - -def fetch_run(run_ref): - return fetch(f'/v3/runs/{run_ref}')['runs'] +def fetch_run(run_ref, cache): + return fetch(f'/v3/runs/{run_ref}', cache, timedelta(hours=8))['runs'] def departure_time(dep): return datetime.fromisoformat(dep['estimated_departure_utc'] or dep['scheduled_departure_utc']) -def get_departure_data(): - bus = fetch_departures(17861, 2) - train = fetch_departures(1229, 0) - tram_109w = fetch_departures(2460, 1) - tram_70w = fetch_departures(2161, 1) +def get_departure_data(now, cache): + bus = fetch_departures(17861, 2, cache) + train = fetch_departures(1229, 0, cache) + tram_109w = fetch_departures(2460, 1, cache) + tram_70w = fetch_departures(2161, 1, cache) next_express_dep = None train.sort(key=departure_time) for dep in train: - if dep['direction_id'] == 1: - match fetch_run(dep['run_ref']): + if dep['direction_id'] == 1 and departure_time(dep) > now: + match fetch_run(dep['run_ref'], cache): case [run]: if run['express_stop_count'] > 1: # Ignore East Richmond next_express_dep = dep break def earliest(deps): - return min((departure_time(dep) for dep in deps), default=None) + return min((departure_time(dep) for dep in deps if departure_time(dep) > now), default=None) return { '612 S': earliest(dep for dep in bus if dep['route_id'] == 13024 and dep['direction_id'] == 139),