Created
May 11, 2016 20:06
-
-
Save Lorentz83/fd1365b1214866dad84df0b62ac40b9a to your computer and use it in GitHub Desktop.
WLBus is a pebble app to check the bus arrival time of CityBus in Lafayette and West Lafayette, IN.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* WLBus is a pebble app to check the bus arrival time of | |
* CityBus in Lafayette and West Lafayette, IN. | |
* The data is downloaded from the website | |
* http://citybus.doublemap.com/map/ | |
* | |
* NOTES | |
* - this software is a BETA version, I developed it in few days. | |
* You can use it at your own risk. | |
* - the "home" and "work" stops are hardcoded here inside, search | |
* for TODO comments to see where to change them. | |
* - I do not live in Lafayette anymore. If you are in need I may | |
* help you understanding and patching the code, but I do not | |
* actively maintain it anymore. | |
* | |
* INSTALL | |
* Go to cloud pebble https://cloudpebble.net, create a new | |
* pebble.js project and copy and paste this code in app.js file. | |
* Check cloudpebble documentation for details about how to set up | |
* the project and enable the developer mode in your pebble app. | |
* | |
* LICENSE | |
* | |
* The function distance is released under LGPLv3 | |
* See https://www.geodatasource.com/developers/javascript for details. | |
* | |
* The rest of the code is released under GPLv3 or later. | |
* | |
* Copyright (c) 2016 Lorenzo Bossi | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
* | |
*/ | |
var UI = require('ui'); | |
var ajax = require('ajax'); | |
var Wakeup = require('wakeup'); | |
var Vibe = require('ui/vibe'); | |
var checkAll; | |
var alarmCard = new UI.Card({ | |
title: 'WLBus', | |
subtitle: 'Alarm', | |
icon: 'images/wlbus', | |
scrollable: false | |
}); | |
var alarm = new UI.Menu({ | |
sections: [{ | |
title: 'Set alarm', | |
items: [], | |
}] | |
}); | |
var main = new UI.Menu({ | |
sections: [{ | |
title: 'WLBus', | |
items: [], | |
onClick: function(e) { | |
var min = e.item.wait[0]; | |
if (min < 5 && e.item.wait.length > 1) { | |
min = e.item.wait[1]; | |
} | |
var wait = []; | |
if (min >= 2) { | |
wait.push({title: '2 minute', minutes: 2}); | |
} | |
for (var i = 5 ; i<min ; i+=5) { | |
wait.push({title: i + ' minute', minutes: i}); | |
} | |
alarm.items(0, wait); | |
alarm.show(); | |
}, | |
}, { | |
title: 'Settings', | |
items: [{title: 'Refresh'}], | |
onClick: function(e) { | |
checkAll(); | |
}, | |
}] | |
}); | |
var waitMsg = function(){ | |
var refresh = main.items(1)[0]; | |
refresh.subtitle = 'please wait...'; | |
main.item(1,0, refresh); | |
}; | |
var waitMsgDone = function(){ | |
var refresh = main.items(1)[0]; | |
refresh.subtitle = ''; | |
main.item(1,0, refresh); | |
}; | |
main.on('select', function(e){ | |
e.section.onClick(e); | |
}); | |
Wakeup.on('wakeup', function(e) { | |
console.log('Wakeup event! ' + JSON.stringify(e)); | |
alarmCard.body('Time to take the bus!'); | |
alarmCard.show(); | |
Vibe.vibrate('long'); | |
}); | |
alarm.on('select', function(menu){ | |
var minutes = menu.item.minutes; | |
Wakeup.cancel('all'); | |
Wakeup.schedule({ | |
time: Date.now() / 1000 + 60 * minutes, | |
data: menu.item.title | |
}, | |
function(alarmEvent) { | |
if (alarmEvent.failed) { | |
alarmCard.body('Fail to set the alarm ' + alarmEvent.error); | |
Vibe.vibrate('short'); | |
} else { | |
alarmCard.body('Alarm set ' + minutes + ' minutes from now'); | |
} | |
alarmCard.show(); | |
} | |
); | |
}); | |
main.bus = []; // {title: text, subtitle: 'eta'} | |
main.update = function(){ | |
main.items(0, main.bus); | |
main.selection(0, 0); | |
}; | |
var checkEta = function(stop, callback) { | |
var url = 'http://citybus.doublemap.com/map/v2/eta?stop=' + stop; | |
console.log('Check eta ' + url ); | |
ajax({ url: url, type: 'json' }, | |
function(data) { | |
try { | |
var etas = data.etas[stop].etas; | |
var wait = etas; | |
wait.sort(function(a, b) {return a.avg - b.avg;}); | |
callback(true, wait); | |
} catch (ex) { | |
console.log('error ', url, ex.message); | |
callback(false, ex.message); | |
} | |
}, | |
function() { | |
callback(false, 'connection'); | |
} | |
); | |
}; | |
var checkBus = function(text, stop, route) { | |
console.log('Check bus ' + text + ' stop: ' + stop + ' route: ' + route); | |
var bus = {title: text, subtitle: '?', wait: []}; | |
main.bus.push(bus); | |
checkEta(stop, function(ok, wait) { | |
if (ok) { | |
wait = wait.filter(function(r){return r.route == route;}).map(function(r){return r.avg;}); | |
if ( wait.length > 0 ) { | |
bus.subtitle = wait.slice(0, 2).join(', '); | |
} else { | |
bus.subtitle = 'no ETAs available'; | |
} | |
bus.wait = wait; | |
} else { | |
bus.subtitle = 'ERR ' + wait; | |
} | |
main.update(); | |
}); | |
}; | |
var checkBuses = function(text, stop, routeNames) { | |
console.log('checking buses on stop ' + stop); | |
checkEta(stop, function(ok, wait) { | |
console.log('next buses ' + ok + ' ' + JSON.stringify(wait)); | |
if (ok) { | |
wait = wait.filter(function(r){ | |
return r.route in routeNames; | |
}).map(function(r){ | |
return {bus: routeNames[r.route], avg: r.avg}; | |
}); | |
var buses = {}; | |
var anyBus = false; | |
wait.forEach(function(next) { | |
var bus; | |
if ( buses[next.bus] ) { | |
bus = buses[next.bus]; | |
anyBus = true; | |
} else { | |
var title = text + ' - ' + next.bus.short_name; | |
bus = {title: title, subtitle: '?', wait: []}; | |
buses[next.bus] = bus; | |
main.bus.push(bus); | |
} | |
if (bus.wait.length <= 2) { | |
bus.wait.push(next.avg); | |
bus.subtitle = bus.wait.join(', '); | |
} | |
}); | |
if ( !anyBus ) { | |
main.bus.push({title: text, subtitle: 'no ETAs avaliable', wait: []}); | |
} | |
console.log('nex buses processed ' + JSON.stringify(buses) + buses.length); | |
} else { | |
main.bus.push({title: text, subtitle: 'ERR ' + wait, wait: []}); | |
console.log('error ' + wait + ' checking stop ' + text); | |
} | |
main.update(); | |
}); | |
}; | |
var ajaxJson = function(url, fn) { | |
ajax({ url: url, type: 'json' }, | |
fn, | |
function() { | |
fn('error'); | |
} | |
); | |
}; | |
var Barrier = function(fn) { | |
this._counter = 0; | |
this._enabled = false; | |
this._callback = fn; | |
}; | |
Barrier.prototype._tryExec = function() { | |
if (this._enabled === true && this._counter === 0) { | |
console.log('barrier, executing callback'); | |
this._callback.call(null); | |
} else { | |
console.log('barrier, NOT executing callback, waiting ' + this._counter); | |
} | |
}; | |
Barrier.prototype.enable = function(){ | |
if (this._enabled === true) { | |
throw 'The barrier has been already enabled.'; | |
} | |
this._enabled = true; | |
this._tryExec(); | |
}; | |
Barrier.prototype.add = function(fn){ | |
if (this._enabled === true) { | |
throw 'The barrier has been already enabled.'; | |
} | |
this._counter ++; | |
var This = this; | |
return function() { | |
This._counter --; | |
try { | |
fn.apply(null, arguments); | |
} catch (ex) { | |
console.log('error in barrier callback', ex.message); | |
} | |
This._tryExec(); | |
}; | |
}; | |
// https://www.geodatasource.com/developers/javascript | |
// LGPLv3 | |
var distance = function(lat1, lon1, lat2, lon2, unit) { | |
var radlat1 = Math.PI * lat1/180; | |
var radlat2 = Math.PI * lat2/180; | |
var theta = lon1-lon2; | |
var radtheta = Math.PI * theta/180; | |
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); | |
dist = Math.acos(dist); | |
dist = dist * 180/Math.PI; | |
dist = dist * 60 * 1.1515; | |
if (unit=="K") { dist = dist * 1.609344; } | |
if (unit=="N") { dist = dist * 0.8684; } | |
return dist; | |
}; | |
checkAll = function() { | |
waitMsg(); | |
main.bus = []; | |
var homeOfficeRoute = false; | |
var stopOffice = false; | |
var stopHome = false; | |
var stops = false; | |
var currentPos = false; | |
var routeNames = []; | |
var barrier = new Barrier(function() { | |
console.log('Check bus route ' + homeOfficeRoute + ' stopOffice '+ stopOffice +' stopHome '+ stopHome); | |
checkBus('time to home: ', stopOffice, homeOfficeRoute); | |
checkBus('time to work: ', stopHome, homeOfficeRoute); | |
for (var n = 0 ; n < stops.length ; n++) { | |
var stop = stops[n]; | |
var idx = stop.name.lastIndexOf(' - '); | |
if (idx >= 0) { | |
stop.short_name = stop.name.substr(idx + 3); | |
} else { | |
stop.short_name = stop.name; | |
} | |
stop.distance = distance(currentPos.latitude, currentPos.longitude, stop.lat, stop.lon); | |
} | |
stops.sort(function(a, b) {return a.distance - b.distance;}); | |
console.log('by distance: ' + JSON.stringify(stops)); | |
checkBuses(stops[0].short_name, stops[0].id, routeNames); | |
checkBuses(stops[1].short_name, stops[1].id, routeNames); | |
waitMsgDone(); | |
}); | |
navigator.geolocation.getCurrentPosition( barrier.add(function(pos){ | |
console.log('gps', JSON.stringify(pos), pos.coords.latitude, pos.coords.longitude ); | |
currentPos = pos.coords; | |
}), function() {console.log('GPS error');}, {enableHighAccuracy: true, maximumAge: 1000*60*5}); | |
ajaxJson('http://citybus.doublemap.com/map/v2/routes', barrier.add(function(routes){ | |
console.log('Routes downloaded: ' + JSON.stringify(routes)); | |
for (var n = 0 ; n < routes.length ; n++) { | |
var route = routes[n]; | |
if (route.short_name == '5B'){ //TODO this is the line "office" <--> "home" | |
console.log('route id found ' + route.id); | |
homeOfficeRoute = route.id; | |
} | |
routeNames[route.id] = route; | |
} | |
})); | |
ajaxJson('http://citybus.doublemap.com/map/v2/stops', barrier.add(function(data){ | |
console.log('Stops downloaded: ' + JSON.stringify(data)); | |
stops = data; | |
for (var n = 0 ; n < stops.length ; n++) { | |
var stop = stops[n]; | |
if (stop.name == 'Beering Hall on University St (@Shelter) - BUS389'){ //TODO this is the stop "office" | |
console.log('stopOffice id found ' + stop.id); | |
stopOffice = stop.id; | |
} | |
if (stop.name == 'Carlisle Rd & Windsor Dr (NW Corner) - BUS623NW'){ //TODO this is the stop "home" | |
console.log('stopHome id found ' + stop.id); | |
stopHome = stop.id; | |
} | |
} | |
})); | |
barrier.enable(); | |
}; | |
checkAll(); | |
main.show(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment