diff --git a/arrival_times.py b/arrival_times.py index aee164b..1b5dfb1 100644 --- a/arrival_times.py +++ b/arrival_times.py @@ -1,6 +1,6 @@ import datetime -class ArrivalTime(): +class ArrivalTime: """ Represents the arrival times of buses at one of the configured stops """ def __init__(self, stop_id: str, route_id: str, destination: str, due_in_seconds: int, is_added: bool = False) -> None: @@ -14,7 +14,7 @@ class ArrivalTime(): def due_in_minutes(self) -> int: return int(self.due_in_seconds / 60) - def isDue(self) -> bool: + def is_due(self) -> bool: return self.due_in_minutes < 1 def due_in_str(self) -> str: diff --git a/config.py b/config.py index 77eb4d2..bfc7880 100644 --- a/config.py +++ b/config.py @@ -6,7 +6,7 @@ class Config: with open("config.yaml") as f: self.config = yaml.safe_load(f.read()) - # Pre-load some dictionaries to simplify lookups + # Preload some dictionaries to simplify lookups self.walk_time_by_stop = {} for s in self.config.get("stops", []): self.walk_time_by_stop[str(s["stop_id"])] = s["walk_time"] diff --git a/gtfs_client.py b/gtfs_client.py index 803c001..9c70b04 100644 --- a/gtfs_client.py +++ b/gtfs_client.py @@ -10,10 +10,9 @@ import refresh_feed import requests import sys import time -import threading import zipfile -class GTFSClient(): +class GTFSClient: def __init__(self, feed_url: str, gtfs_r_url: str, gtfs_r_api_key: str, stop_codes: list[str], routes_for_stops: dict[str, str], update_queue: queue.Queue, update_interval_seconds: int = 60): @@ -31,10 +30,10 @@ class GTFSClient(): except: last_mtime = 0 - refreshed, new_mtime = refresh_feed.update_local_file_from_url_v1(last_mtime, feed_name, feed_url) + _, new_mtime = refresh_feed.update_local_file_from_url_v1(last_mtime, feed_name, feed_url) # Load the feed - self.feed = self._read_feed(feed_name, dist_units='km', stop_codes = stop_codes) + self.feed = self._read_feed(feed_name, dist_units='km') gc.collect() self.stop_ids = self.__wanted_stop_ids() self.deltas = {} @@ -46,7 +45,7 @@ class GTFSClient(): if update_interval_seconds and update_queue: self._update_interval_seconds = update_interval_seconds - def _read_feed(self, path: gk.Path, dist_units: str, stop_codes: list[str]) -> gk.Feed: + def _read_feed(self, path: str, dist_units: str) -> gk.Feed: """ NOTE: This helper method was extracted from gtfs_kit.feed to modify it to only load the stop_times for the stops we are interested in, @@ -55,7 +54,7 @@ class GTFSClient(): This version also reads CSV data straight from the zip file to avoid wearing out the Pi's SD card. """ - FILES_TO_LOAD = [ + files_to_load = [ # List of feed files to load. stop_times.txt is loaded separately. 'trips.txt', 'routes.txt', @@ -65,8 +64,7 @@ class GTFSClient(): 'agency.txt' ] - path = gk.Path(path) - if not path.exists(): + if not os.path.exists(path): raise ValueError("Path {} does not exist".format(path)) print("Loading GTFS feed {}".format(path), file=sys.stderr) @@ -74,7 +72,7 @@ class GTFSClient(): feed_dict = {table: None for table in gk.cs.GTFS_REF["table"]} with zipfile.ZipFile(path) as z: - for filename in FILES_TO_LOAD: + for filename in files_to_load: table = filename.split(".")[0] # read the file with z.open(filename) as f: @@ -216,20 +214,22 @@ class GTFSClient(): next_buses.drop(index=ids_to_delete, inplace=True) return next_buses - def __time_to_seconds(self, s: str) -> int: + @staticmethod + def __time_to_seconds(s: str) -> int: sx = s.split(":") if len(sx) != 3: print("Malformed timestamp:", s) return 0 return int(sx[0]) * 3600 + int(sx[1]) * 60 + int (sx[2]) - def __due_in_seconds(self, time_str: str) -> int: + @staticmethod + def __due_in_seconds(time_str: str) -> int: """ Returns the number of seconds in the future that the time_str (format hh:mm:ss) is """ now = datetime.datetime.now().strftime("%H:%M:%S") - tnow = self.__time_to_seconds(now) - tstop = self.__time_to_seconds(time_str) + tnow = GTFSClient.__time_to_seconds(now) + tstop = GTFSClient.__time_to_seconds(time_str) if tstop > tnow: return tstop - tnow else: @@ -251,8 +251,7 @@ class GTFSClient(): return destination - def __poll_gtfsr_deltas(self) -> list[map, set]: - + def __poll_gtfsr_deltas(self) -> tuple[dict, list, list]: try: # Poll GTFS-R API if self.gtfs_r_api_key != "": @@ -260,7 +259,7 @@ class GTFSClient(): response = requests.get(url = self.gtfs_r_url, headers = headers, timeout=(2, 10)) if response.status_code != 200: print("GTFS-R sent non-OK response: {}\n{}".format(response.status_code, response.text)) - return ({}, [], []) + return {}, [], [] deltas_json = json.loads(response.text) else: @@ -277,7 +276,6 @@ class GTFSClient(): today = datetime.date.today().strftime("%Y%m%d") for e in deltas_json.get("entity", []): - is_deleted = e.get("is_deleted") or False try: trip_update = e.get("trip_update") trip = trip_update.get("trip") @@ -328,12 +326,12 @@ class GTFSClient(): print("Unsupported action:", trip_action) except Exception as x: print("Error parsing GTFS-R entry:", str(e)) - raise(x) + raise x return deltas, canceled_trips, added_stops except Exception as e: print("Polling for GTFS-R failed:", str(e)) - return ({}, [], []) + return {}, [], [] def get_next_n_buses(self, num_entries: int) -> pd.core.frame.DataFrame: @@ -362,7 +360,7 @@ class GTFSClient(): self.added_stops = added_stops arrivals = [] - # take more entries than we need in case there are cancelations + # take more entries than we need in case there are cancellations buses = self.get_next_n_buses(15) for index, bus in buses.iterrows(): @@ -374,7 +372,7 @@ class GTFSClient(): arrival = ArrivalTime(stop_id = bus["stop_code"], route_id = bus["route_short_name"], destination = bus["trip_headsign"], - due_in_seconds = self.__due_in_seconds(bus["arrival_time"]) + delta, + due_in_seconds = GTFSClient.__due_in_seconds(bus["arrival_time"]) + delta, is_added = False ) arrivals.append(arrival) diff --git a/main.py b/main.py index 97fc90a..b761d66 100755 --- a/main.py +++ b/main.py @@ -90,7 +90,7 @@ def update_screen(config: Config, updates: list[ArrivalTime]) -> None: line = line_num, route = update.route_id, destination = update.destination, - time_left = 'Due' if update.isDue() else update.due_in_str(), + time_left = 'Due' if update.is_due() else update.due_in_str(), time_color = lcd_color, text_color = COLOR_LCD_GREEN if update.is_added else COLOR_LCD_AMBER ) @@ -146,7 +146,9 @@ def main(): update_queue=update_queue, update_interval_seconds=config.update_interval_seconds) + # Schedule feed refresh, and force the first one schedule.every(config.update_interval_seconds).seconds.do(scheduler.refresh) + scheduler.refresh() # Main event loop running = True diff --git a/refresh_feed.py b/refresh_feed.py index 18239e4..385d80f 100644 --- a/refresh_feed.py +++ b/refresh_feed.py @@ -72,42 +72,4 @@ def update_local_file_from_url_v1(last_mtime, local_file, url): else: print('No need to refresh feed.', file=sys.stderr) - return updated, mtime - - -# v2: download remote file conditionally, with HTTP's If-Modified-Since header. -# This requires the remote server to support both sending the Last-Modified -# header and receiving the If-Modified-Since header. -# -def update_local_file_from_url_v2(last_mtime, local_file, url): - - # Get the remote file, but only if it has changed - r = requests.get(url, headers={ - 'If-Modified-Since': ts_to_httpdate(last_mtime) - }) - - updated, mtime = False, last_mtime - - if r.status_code == requests.codes.ok: - # File is updated and we just downloaded the content - updated = True - - # write new content to local file - write_file_with_time(local_file, r.content, mtime) - - # Update our notion of the file's last modification time - if 'Last-Modified' in r.headers: - mtime = httpdate_to_ts(r.headers['Last-Modified']) - else: - print('HEY! no Last-Modified header for {}'.format(url), - file=sys.stderr) - - elif r.status_code == requests.codes.not_modified: - # Successful call, but no updates to file - print('As of {}, server says {} is the same'.format(time.ctime(), url)) - else: - # http request failed - print('HEY! get for {} returned {}'.format(url, r.status_code), - file=sys.stderr) - - return updated, mtime + return updated, mtime \ No newline at end of file