/ 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