From f8cc9f213cf38c24f11141f17f701b587a5e9688 Mon Sep 17 00:00:00 2001 From: Kenneth Allen Date: Wed, 14 May 2025 22:35:55 +1000 Subject: [PATCH] Implement PTV data fetching --- __main__.py | 2 +- cache.py | 3 +- ptv.py | 65 +++++++++++++++++++++++++++-------------- requirements.txt | 8 +++++ test_after_palette.png | Bin 10735 -> 0 bytes 5 files changed, 53 insertions(+), 25 deletions(-) delete mode 100644 test_after_palette.png diff --git a/__main__.py b/__main__.py index 66ffcfa..5ab97fd 100644 --- a/__main__.py +++ b/__main__.py @@ -55,7 +55,7 @@ pal_image.putpalette((0x00,0x00,0x00, 0xff,0xff,0xff, 0xff,0xff,0x00, 0xff,0x00, image_4color = image.quantize(palette=pal_image, dither=Image.NONE) image_4color.save('test_after_palette.png') -show_on_screen = True +show_on_screen = False if show_on_screen: import epd7in3g as epd try: diff --git a/cache.py b/cache.py index 94f9f96..953389b 100644 --- a/cache.py +++ b/cache.py @@ -7,14 +7,13 @@ class Cache: self.db = db def get(self, key, timeout, f): - now = datetime.datetime.now() + now = datetime.datetime.now(datetime.timezone.utc) earliest_acceptable = now - timeout match self.db.execute( 'SELECT value FROM cache WHERE key = ? AND timestamp >= ?', (key, earliest_acceptable.isoformat()), ).fetchall(): case [(value,)]: - self.db.commit() return value value = f() diff --git a/ptv.py b/ptv.py index 24507d1..dd07c16 100644 --- a/ptv.py +++ b/ptv.py @@ -17,14 +17,7 @@ def sign(request): def fetch(path): res = httpx.get(sign(path)) - try: - res.raise_for_status() - except: - try: - print(res.json()) - except: - pass - raise + res.raise_for_status() return res.json() # Lilydale route 9 (type 0) (dir 1 in, 8 out) (stop 1229) @@ -46,22 +39,50 @@ 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')] , [2415, 2460]), - Route('766' , 15800, 2, [(13, 'N'), (207, 'S')] , [17861] ), - Route('612' , 13024, 2, [(13, 'N'), (158, 'S')] , [17861] ), + 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): - deps = fetch(f'/v3/departures/route_type/{route_type_id}/stop/{stop_id}')['departures'] - for dep in deps: - for key in ['estimated_departure_utc', 'scheduled_departure_utc']: - if dep[key]: - dep[key] = datetime.fromisoformat(dep[key]) - deps.sort(key=lambda dep: dep['estimated_departure_utc'] or dep['scheduled_departure_utc']) - return deps + 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 departure_time(dep): + return datetime.fromisoformat(dep['estimated_departure_utc'] or dep['scheduled_departure_utc']) def get_departure_data(): - by_route_type = defaultdict(list) - for route_type, stop in set((r.route_type_id, s) for r in local_routes for s in r.stops): - by_route_type[route_type] += fetch_departures(stop, route_type) - return by_route_type + bus = fetch_departures(17861, 2) + train = fetch_departures(1229, 0) + tram_109w = fetch_departures(2460, 1) + tram_70w = fetch_departures(2161, 1) + + next_express_dep = None + train.sort(key=departure_time) + for dep in train: + if dep['direction_id'] == 1: + match fetch_run(dep['run_ref']): + 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 { + '612 S': earliest(dep for dep in bus if dep['route_id'] == 13024 and dep['direction_id'] == 139), + '612 N': earliest(dep for dep in bus if dep['route_id'] == 13024 and dep['direction_id'] == 13), + '766 N': earliest(dep for dep in bus if dep['route_id'] == 15800 and dep['direction_id'] == 13), + + 'Union W': earliest(dep for dep in train if dep['direction_id'] == 1 and dep is not next_express_dep), + 'Union W Express': departure_time(next_express_dep) if next_express_dep else None, + 'Union E': earliest(dep for dep in train if dep['direction_id'] in {2, 8}), + + '109 W': earliest(tram_109w), + '70 W': earliest(tram_70w), + } diff --git a/requirements.txt b/requirements.txt index e94c687..6027c6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,13 @@ +anyio==4.9.0 +certifi==2025.4.26 colorzero==2.0 gpiozero==2.0.1 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==3.10 lgpio==0.2.2.0 pillow==11.2.1 +sniffio==1.3.1 spidev==3.6 +typing_extensions==4.13.2 diff --git a/test_after_palette.png b/test_after_palette.png deleted file mode 100644 index 5495f5d7e5a0a196f46c08539412109860137dc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10735 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