$all_routes = ShuttleSchedule::get_route_list();

while ($daemon->sleep(10)) {
  db::ping(); // keep the database connection running

  $time = time();
  $too_old = $time - 7200; // arbitrary 2 hour timeout threshold for now

  // only keep track of shuttles that are running
  $routes = array_filter($all_routes, array('ShuttleSchedule', 'is_running'));

  if (count($routes)) {
    foreach ($routes as $route) {
      // force NextBusReader to cache
      NextBusReader::get_predictions($route);
      NextBusReader::get_coordinates($route);
    }

    $sql = "SELECT device_id, device_type, route_id, stop_id, start_time FROM ShuttleSubscription WHERE ("
      . implode(' OR ', array_map('wrap_route_criterion', $routes))
      .     ") AND start_time <= $time AND start_time > $too_old";

    if (!$result = db::$connection->query($sql)) {
      d_error("sql failed: {$db->errno} {$db->error} in $sql");
    } else {
      while ($row = $result->fetch_assoc()) {
	$route_id = $row['route_id'];

	// skip rows whose start times are more than 1.5 loops ago
	if ($time - $row['start_time'] > 1.5 * ShuttleSchedule::get_interval($route_id))
	  continue;
	 foreach ($stopTimes as $index => $stopTimeInfo) {
	   $stopInfo = $stops[$stopTimeInfo['id']];
	   foreach ($stopInfo as $property => $value) {
	     if ($property == 'title')
	       continue;
	     $stopTimes[$index][$property] = $value;
	   }
	 }
       }
     }

     $data['stops'] = $stopTimes;

     if ($gpsActive) {
       $data['gpsActive'] = TRUE;
       $data['vehicleLocations'] = NextBusReader::get_coordinates($route);
     }

     $data['now'] = $time;

   } else {
     $data = Array('error' => "no route parameter");
   }

   break;
 case 'subscribe': case 'unsubscribe':
   require_once $APIROOT . '/push/apns_lib.php';

   $data = Array('error' => "could not perform $command");
   if ($sub = APNSSubscriber::create()) {
     $route = $_REQUEST['route'];