Cleaned up a bunch of typos, removed unused code and made it tidier.
This commit is contained in:
parent
d9811d3f91
commit
03d22f47d7
|
|
@ -1,6 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
class ArrivalTime():
|
class ArrivalTime:
|
||||||
""" Represents the arrival times of buses at one of the configured stops """
|
""" 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:
|
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:
|
def due_in_minutes(self) -> int:
|
||||||
return int(self.due_in_seconds / 60)
|
return int(self.due_in_seconds / 60)
|
||||||
|
|
||||||
def isDue(self) -> bool:
|
def is_due(self) -> bool:
|
||||||
return self.due_in_minutes < 1
|
return self.due_in_minutes < 1
|
||||||
|
|
||||||
def due_in_str(self) -> str:
|
def due_in_str(self) -> str:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ class Config:
|
||||||
with open("config.yaml") as f:
|
with open("config.yaml") as f:
|
||||||
self.config = yaml.safe_load(f.read())
|
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 = {}
|
self.walk_time_by_stop = {}
|
||||||
for s in self.config.get("stops", []):
|
for s in self.config.get("stops", []):
|
||||||
self.walk_time_by_stop[str(s["stop_id"])] = s["walk_time"]
|
self.walk_time_by_stop[str(s["stop_id"])] = s["walk_time"]
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ import refresh_feed
|
||||||
import requests
|
import requests
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import threading
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
class GTFSClient():
|
class GTFSClient:
|
||||||
def __init__(self, feed_url: str, gtfs_r_url: str, gtfs_r_api_key: str,
|
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],
|
stop_codes: list[str], routes_for_stops: dict[str, str],
|
||||||
update_queue: queue.Queue, update_interval_seconds: int = 60):
|
update_queue: queue.Queue, update_interval_seconds: int = 60):
|
||||||
|
|
@ -31,10 +30,10 @@ class GTFSClient():
|
||||||
except:
|
except:
|
||||||
last_mtime = 0
|
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
|
# 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()
|
gc.collect()
|
||||||
self.stop_ids = self.__wanted_stop_ids()
|
self.stop_ids = self.__wanted_stop_ids()
|
||||||
self.deltas = {}
|
self.deltas = {}
|
||||||
|
|
@ -46,7 +45,7 @@ class GTFSClient():
|
||||||
if update_interval_seconds and update_queue:
|
if update_interval_seconds and update_queue:
|
||||||
self._update_interval_seconds = update_interval_seconds
|
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
|
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,
|
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
|
This version also reads CSV data straight from the zip file to avoid
|
||||||
wearing out the Pi's SD card.
|
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.
|
# List of feed files to load. stop_times.txt is loaded separately.
|
||||||
'trips.txt',
|
'trips.txt',
|
||||||
'routes.txt',
|
'routes.txt',
|
||||||
|
|
@ -65,8 +64,7 @@ class GTFSClient():
|
||||||
'agency.txt'
|
'agency.txt'
|
||||||
]
|
]
|
||||||
|
|
||||||
path = gk.Path(path)
|
if not os.path.exists(path):
|
||||||
if not path.exists():
|
|
||||||
raise ValueError("Path {} does not exist".format(path))
|
raise ValueError("Path {} does not exist".format(path))
|
||||||
|
|
||||||
print("Loading GTFS feed {}".format(path), file=sys.stderr)
|
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"]}
|
feed_dict = {table: None for table in gk.cs.GTFS_REF["table"]}
|
||||||
with zipfile.ZipFile(path) as z:
|
with zipfile.ZipFile(path) as z:
|
||||||
for filename in FILES_TO_LOAD:
|
for filename in files_to_load:
|
||||||
table = filename.split(".")[0]
|
table = filename.split(".")[0]
|
||||||
# read the file
|
# read the file
|
||||||
with z.open(filename) as f:
|
with z.open(filename) as f:
|
||||||
|
|
@ -216,20 +214,22 @@ class GTFSClient():
|
||||||
next_buses.drop(index=ids_to_delete, inplace=True)
|
next_buses.drop(index=ids_to_delete, inplace=True)
|
||||||
return next_buses
|
return next_buses
|
||||||
|
|
||||||
def __time_to_seconds(self, s: str) -> int:
|
@staticmethod
|
||||||
|
def __time_to_seconds(s: str) -> int:
|
||||||
sx = s.split(":")
|
sx = s.split(":")
|
||||||
if len(sx) != 3:
|
if len(sx) != 3:
|
||||||
print("Malformed timestamp:", s)
|
print("Malformed timestamp:", s)
|
||||||
return 0
|
return 0
|
||||||
return int(sx[0]) * 3600 + int(sx[1]) * 60 + int (sx[2])
|
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
|
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")
|
now = datetime.datetime.now().strftime("%H:%M:%S")
|
||||||
tnow = self.__time_to_seconds(now)
|
tnow = GTFSClient.__time_to_seconds(now)
|
||||||
tstop = self.__time_to_seconds(time_str)
|
tstop = GTFSClient.__time_to_seconds(time_str)
|
||||||
if tstop > tnow:
|
if tstop > tnow:
|
||||||
return tstop - tnow
|
return tstop - tnow
|
||||||
else:
|
else:
|
||||||
|
|
@ -251,8 +251,7 @@ class GTFSClient():
|
||||||
return destination
|
return destination
|
||||||
|
|
||||||
|
|
||||||
def __poll_gtfsr_deltas(self) -> list[map, set]:
|
def __poll_gtfsr_deltas(self) -> tuple[dict, list, list]:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Poll GTFS-R API
|
# Poll GTFS-R API
|
||||||
if self.gtfs_r_api_key != "":
|
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))
|
response = requests.get(url = self.gtfs_r_url, headers = headers, timeout=(2, 10))
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
print("GTFS-R sent non-OK response: {}\n{}".format(response.status_code, response.text))
|
print("GTFS-R sent non-OK response: {}\n{}".format(response.status_code, response.text))
|
||||||
return ({}, [], [])
|
return {}, [], []
|
||||||
|
|
||||||
deltas_json = json.loads(response.text)
|
deltas_json = json.loads(response.text)
|
||||||
else:
|
else:
|
||||||
|
|
@ -277,7 +276,6 @@ class GTFSClient():
|
||||||
today = datetime.date.today().strftime("%Y%m%d")
|
today = datetime.date.today().strftime("%Y%m%d")
|
||||||
|
|
||||||
for e in deltas_json.get("entity", []):
|
for e in deltas_json.get("entity", []):
|
||||||
is_deleted = e.get("is_deleted") or False
|
|
||||||
try:
|
try:
|
||||||
trip_update = e.get("trip_update")
|
trip_update = e.get("trip_update")
|
||||||
trip = trip_update.get("trip")
|
trip = trip_update.get("trip")
|
||||||
|
|
@ -328,12 +326,12 @@ class GTFSClient():
|
||||||
print("Unsupported action:", trip_action)
|
print("Unsupported action:", trip_action)
|
||||||
except Exception as x:
|
except Exception as x:
|
||||||
print("Error parsing GTFS-R entry:", str(e))
|
print("Error parsing GTFS-R entry:", str(e))
|
||||||
raise(x)
|
raise x
|
||||||
|
|
||||||
return deltas, canceled_trips, added_stops
|
return deltas, canceled_trips, added_stops
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Polling for GTFS-R failed:", str(e))
|
print("Polling for GTFS-R failed:", str(e))
|
||||||
return ({}, [], [])
|
return {}, [], []
|
||||||
|
|
||||||
|
|
||||||
def get_next_n_buses(self, num_entries: int) -> pd.core.frame.DataFrame:
|
def get_next_n_buses(self, num_entries: int) -> pd.core.frame.DataFrame:
|
||||||
|
|
@ -362,7 +360,7 @@ class GTFSClient():
|
||||||
self.added_stops = added_stops
|
self.added_stops = added_stops
|
||||||
|
|
||||||
arrivals = []
|
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)
|
buses = self.get_next_n_buses(15)
|
||||||
|
|
||||||
for index, bus in buses.iterrows():
|
for index, bus in buses.iterrows():
|
||||||
|
|
@ -374,7 +372,7 @@ class GTFSClient():
|
||||||
arrival = ArrivalTime(stop_id = bus["stop_code"],
|
arrival = ArrivalTime(stop_id = bus["stop_code"],
|
||||||
route_id = bus["route_short_name"],
|
route_id = bus["route_short_name"],
|
||||||
destination = bus["trip_headsign"],
|
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
|
is_added = False
|
||||||
)
|
)
|
||||||
arrivals.append(arrival)
|
arrivals.append(arrival)
|
||||||
|
|
|
||||||
4
main.py
4
main.py
|
|
@ -90,7 +90,7 @@ def update_screen(config: Config, updates: list[ArrivalTime]) -> None:
|
||||||
line = line_num,
|
line = line_num,
|
||||||
route = update.route_id,
|
route = update.route_id,
|
||||||
destination = update.destination,
|
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,
|
time_color = lcd_color,
|
||||||
text_color = COLOR_LCD_GREEN if update.is_added else COLOR_LCD_AMBER
|
text_color = COLOR_LCD_GREEN if update.is_added else COLOR_LCD_AMBER
|
||||||
)
|
)
|
||||||
|
|
@ -146,7 +146,9 @@ def main():
|
||||||
update_queue=update_queue,
|
update_queue=update_queue,
|
||||||
update_interval_seconds=config.update_interval_seconds)
|
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)
|
schedule.every(config.update_interval_seconds).seconds.do(scheduler.refresh)
|
||||||
|
scheduler.refresh()
|
||||||
|
|
||||||
# Main event loop
|
# Main event loop
|
||||||
running = True
|
running = True
|
||||||
|
|
|
||||||
|
|
@ -72,42 +72,4 @@ def update_local_file_from_url_v1(last_mtime, local_file, url):
|
||||||
else:
|
else:
|
||||||
print('No need to refresh feed.', file=sys.stderr)
|
print('No need to refresh feed.', file=sys.stderr)
|
||||||
|
|
||||||
return updated, mtime
|
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
|
|
||||||
Loading…
Reference in New Issue