Skip to content

Instantly share code, notes, and snippets.

@Lorentz83
Created May 11, 2016 20:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lorentz83/fd1365b1214866dad84df0b62ac40b9a to your computer and use it in GitHub Desktop.
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.
/**
* 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