/ MagTag_NextBus / nextbus.py
nextbus.py
1 # SPDX-FileCopyrightText: 2020 Phillip Burgess for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 """ 6 NextBus class -- handles NextBus server queries and infers 7 arrival times based on last-queried predictions and elapsed time. 8 """ 9 10 # pylint: disable=bare-except, too-many-instance-attributes, too-many-arguments 11 12 import re 13 import time 14 15 class NextBus(): 16 """ Class to handle NextBus prediction times for one route & stop. 17 """ 18 19 def __init__(self, network, agency, route, stop, data=None, 20 max_predictions=3, minimum_time=300): 21 """ Constructor expects a Requests-capable Network object, 22 strings for transit agency, route, and stop ID, plus optional 23 per-stop data as defined by an application (e.g. text 24 description, but could be an object or tuple of data, or None, 25 or whatever's needed by the app), limits for the maximum number 26 of future arrivals to predict (limited to server response, 27 typically 5) and minimum time below which arrivals are not shown 28 (to discourage unsafe bus-chasing). 29 """ 30 self.network = network 31 self.agency = agency 32 self.route = route 33 self.stop = stop 34 self.data = data 35 self.max_predictions = max_predictions 36 self.minimum_time = minimum_time 37 self.predictions = [] 38 self.last_query_time = -1000 39 40 def fetch(self): 41 """ Contact NextBus server and request predictions for one 42 agency/route/stop. 43 """ 44 try: 45 url = ('http://webservices.nextbus.com/service/publicXMLFeed?' + 46 'command=predictions&a=' + self.agency + 47 '&r=' + self.route + '&s=' + self.stop) 48 response = self.network.requests.get(url) 49 if response.status_code == 200: 50 string = response.text 51 self.last_query_time = time.monotonic() 52 self.predictions = [] 53 while len(self.predictions) < self.max_predictions: 54 # CircuitPython version of re library doesn't have findall. 55 # Search for first instance of seconds="N" string and then 56 # increment the string position based on match length. 57 match = re.search('seconds=\"[0-9]*', string) 58 if match: 59 seconds = int(match.group(0)[9:]) # Remove 'seconds="' 60 if seconds >= self.minimum_time: 61 self.predictions.append(seconds) 62 string = string[match.end():] 63 else: 64 break 65 self.predictions.sort() 66 except: 67 # If server query fails, we can keep extrapolating from the 68 # last set of predictions and try query again on next pass. 69 pass 70 71 def predict(self): 72 """ Extrapolate predictions based on last values queried from 73 NextBus server and time elapsed since last query. Predictions 74 are returned as a list of integer seconds values. 75 """ 76 times = [] 77 for predict in self.predictions: 78 seconds = predict - (time.monotonic() - self.last_query_time) 79 if seconds >= self.minimum_time: 80 times.append(seconds) 81 return times