From e5019b137deb9d59266f1b929e8200f29dad2f9e Mon Sep 17 00:00:00 2001 From: Nahuel Lofeudo Date: Sun, 30 Apr 2023 07:33:02 +0100 Subject: [PATCH] Create a separate config file, move settings there --- api-key.txt | 1 - config.py | 35 +++++++++++++++++++++++++++++++++++ config.yaml | 28 ++++++++++++++++++++++++++++ gtfs_client.py | 9 +++++---- main.py | 37 +++++++++++++++++++++---------------- 5 files changed, 89 insertions(+), 21 deletions(-) delete mode 100644 api-key.txt create mode 100644 config.py create mode 100644 config.yaml diff --git a/api-key.txt b/api-key.txt deleted file mode 100644 index dcbe830..0000000 --- a/api-key.txt +++ /dev/null @@ -1 +0,0 @@ -API KEY FROM https://developer.nationaltransport.ie/ GOES HERE \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..1893e65 --- /dev/null +++ b/config.py @@ -0,0 +1,35 @@ +import yaml + +class Config: + def __init__(self): + # Load the config file + with open("config.yaml") as f: + self.__config = yaml.safe_load(f.read()) + + # Pre-load some dictionaries to simplify lookups + self.__walk_time_by_stop = {} + for s in self.__config.get("stops", []): + self.__walk_time_by_stop[s["stop_id"]] = s["walk_time"] + + @property + def gtfs_feed_url(self) -> str: + return self.__config.get("gtfs-feed-url") + + @property + def gtfs_api_url(self) -> str: + return self.__config.get("gtfs-r-api-url") + + @property + def gtfs_api_key(self) -> str: + return self.__config.get("gtfs-r-api_key") + + @property + def update_interval_seconds(self) -> int: + return self.__config.get("update-interval-seconds") + + @property + def stop_codes(self) -> list[str]: + return [str(s["stop_id"]) for s in self.__config.get("stops")] + + def minutes_to_stop(self, stop_id) -> int: + return self.__walk_time_by_stop.get(stop_id, 0) diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..20011e6 --- /dev/null +++ b/config.yaml @@ -0,0 +1,28 @@ +# Global parameters + +# URLs and API keys for the different parts of the GTFS-R feed +# You should not change these unless a new version of the API is released +gtfs-feed-url: "https://www.transportforireland.ie/transitData/Data/GTFS_Realtime.zip" +gtfs-r-api-url: "https://api.nationaltransport.ie/gtfsr/v2/gtfsr?format=json" + +# You should change this one. Use the key you get from TFI when you register for GTFS-R access +gtfs-r-api_key: "API KEY GOES HERE" + +# How often to refresh the display. +# It must be strictly larger than 60 because the GTFS-R API will throttle us otherwise +update-interval-seconds: 62 + +stops: [ + { + # Route 15A + stop_id: 1114, + walk_time: 15 + }, + { + # route 54A + stop_id: 2410, + walk_time: 9 + } +] + + diff --git a/gtfs_client.py b/gtfs_client.py index 0678cfd..289d130 100644 --- a/gtfs_client.py +++ b/gtfs_client.py @@ -17,7 +17,8 @@ class GTFSClient(): GTFS_URL = "https://api.nationaltransport.ie/gtfsr/v2/gtfsr?format=json" API_KEY = open("api-key.txt").read().strip() - def __init__(self, feed_url: str, stop_codes: list[str], update_queue: queue.Queue, update_interval_seconds: int = 60): + def __init__(self, feed_url: str, gtfs_r_url: str, gtfs_r_api_key: str, + stop_codes: list[str], update_queue: queue.Queue, update_interval_seconds: int = 60): self.stop_codes = stop_codes feed_name = feed_url.split('/')[-1] @@ -86,7 +87,7 @@ class GTFSClient(): # Finally, load stop_times.txt # Obtain the list of IDs of the desired stops. This is similar to what __wanted_stop_ids() does, # but without a dependency on a fully formed feed object - wanted_stop_ids = feed_dict.get("stops")[feed_dict.get("stops")["stop_code"].isin(stop_codes)]["stop_id"] + wanted_stop_ids = feed_dict.get("stops")[feed_dict.get("stops")["stop_code"].isin(self.stop_codes)]["stop_id"] with z.open("stop_times.txt") as f: iter_csv = pd.read_csv(f, iterator=True, chunksize=1000, dtype=gk.cs.DTYPE, encoding="utf-8-sig") df = pd.concat([chunk[chunk["stop_id"].isin(wanted_stop_ids)] for chunk in iter_csv]) @@ -239,8 +240,8 @@ class GTFSClient(): deltas[trip_id] = deltas_for_trip elif trip_action == "ADDED": - # TODO: Add support for added trips - pass + route_id = e.get("trip_update").get("trip").get("route_id") + elif trip_action == "CANCELED": canceled_trips.add(trip_id) else: diff --git a/main.py b/main.py index a09c4ec..0d2efed 100755 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from config import Config from curses import COLOR_GREEN, COLOR_RED from datetime import datetime import os @@ -22,27 +23,17 @@ COLOR_LCD_RED: pygame.Color = pygame.Color(0xff, 0x3a, 0x4a) COLOR_BACKGROUND = pygame.Color(0, 0, 0) UPDATE_INTERVAL_SECONDS = 62 TEXT_SIZE = 160 # Size of the font in pixels -STOPS = ['2410', '1114'] - -# Define how long it takes to walk to a particular stop -MINUTES_TO_ROUTE = { - '15A': 15, - '54A': 9 -} # Offsets of each part within a line XOFFSET_ROUTE = 24 XOFFSET_DESTINATION = 300 XOFFSEET_TIME_LEFT = 1606 -INTER_LINE_SPACE = -15 # 1920x720 -> 0 +INTER_LINE_SPACE = -15 # Some global variables window : pygame.Surface = None font: pygame.font.Font = None update_queue = queue.Queue(maxsize=10) -#scheduler = DublinBusSoapClient(stops=STOPS, update_queue=update_queue, update_interval_seconds=UPDATE_INTERVAL_SECONDS) -scheduler = GTFSClient(feed_url='https://www.transportforireland.ie/transitData/Data/GTFS_Realtime.zip', - stop_codes=STOPS, update_queue=update_queue, update_interval_seconds=UPDATE_INTERVAL_SECONDS) def get_line_offset(line: int) -> int: """ Calculate the Y offset within the display for a given text line """ @@ -77,12 +68,12 @@ def write_line(line: int, text: str, text_color: Color = COLOR_LCD_AMBER): window.blit(text_img, dest=(XOFFSET_ROUTE, vertical_offset)) -def update_screen(updates: list[ArrivalTime]) -> None: +def update_screen(config: Config(), updates: list[ArrivalTime]) -> None: """ Repaint the screen with the new arrival times """ updates = updates[0:LINE_COUNT] # take the first X lines for line_num, update in enumerate(updates): # Find what color we need to use for the ETA - time_to_walk = update.due_in_minutes - (MINUTES_TO_ROUTE.get(update.route_id) or 0) + time_to_walk = update.due_in_minutes - (config.minutes_to_stop(update.stop_id) or 0) lcd_color = None if time_to_walk > 5: lcd_color = COLOR_LCD_GREEN @@ -122,16 +113,30 @@ def main(): global font global window + global update_queue - """ Main method. Initialise graphics context """ + config = Config() + + # Initialise graphics context pygame.init() window = init_screen() pygame.font.init() font = pygame.font.Font(TEXT_FONT, TEXT_SIZE) - # Paint black + # Init screen clear_screen() + write_line(0, "Dublin Bus display") + write_line(1, "Loading feeds...") pygame.display.flip() + + # Create scheduler; load time tables + scheduler = GTFSClient(feed_url=config.gtfs_feed_url, + gtfs_r_url=config.gtfs_api_url, + gtfs_r_api_key=config.gtfs_api_key, + stop_codes=config.stop_codes, + update_queue=update_queue, + update_interval_seconds=config.update_interval_seconds) + scheduler.start() # Main event loop @@ -152,7 +157,7 @@ def main(): if update_queue.qsize() > 0: clear_screen() updates = update_queue.get() - update_screen(updates) + update_screen(config, updates) pygame.display.flip() # Display update ends