Merge branch 'master' of github.com:ff2005/BangleApps into feature/nifty-clock-b
83
apps.json
|
@ -18,7 +18,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.32",
|
||||
"version": "0.33",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
@ -458,6 +458,7 @@
|
|||
"icon": "clock-analog.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -473,6 +474,7 @@
|
|||
"icon": "clock2x3.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -503,6 +505,7 @@
|
|||
"description": "T-Rex game in the style of Chrome's offline game",
|
||||
"icon": "trex.png",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -1093,6 +1096,7 @@
|
|||
"icon": "icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -1312,6 +1316,7 @@
|
|||
"description": "A Flappy Bird game clone",
|
||||
"icon": "app.png",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -1386,6 +1391,7 @@
|
|||
"icon": "bold_clock.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -3223,6 +3229,46 @@
|
|||
],
|
||||
"data": [{"name":"speedalt.json"}]
|
||||
},
|
||||
{ "id": "speedalt2",
|
||||
"name": "GPS Adventure Sports II",
|
||||
"shortName":"GPS Adv Sport II",
|
||||
"icon": "app.png",
|
||||
"version":"0.07",
|
||||
"description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.",
|
||||
"tags": "tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"type":"app",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"speedalt2.app.js","url":"app.js"},
|
||||
{"name":"speedalt2.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"speedalt2.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"speedalt2.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "slomoclock",
|
||||
"name": "SloMo Clock",
|
||||
"shortName":"SloMo Clock",
|
||||
"icon": "watch.png",
|
||||
"version":"0.10",
|
||||
"description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"slomoclock.app.js","url":"app.js"},
|
||||
{"name":"slomoclock.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"slomoclock.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"slomoclock.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "de-stress",
|
||||
"name": "De-Stress",
|
||||
|
@ -3424,7 +3470,7 @@
|
|||
{
|
||||
"id": "widcom",
|
||||
"name": "Compass Widget",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
@ -3504,7 +3550,7 @@
|
|||
"icon": "simplest.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"simplest.app.js","url":"app.js"},
|
||||
|
@ -3941,6 +3987,7 @@
|
|||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -3956,6 +4003,7 @@
|
|||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -4002,6 +4050,7 @@
|
|||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
@ -4054,5 +4103,33 @@
|
|||
{"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"vernierrespirate.json"}]
|
||||
},
|
||||
{
|
||||
"id": "gpstouch",
|
||||
"name": "GPS Touch",
|
||||
"version": "0.01",
|
||||
"description": "A touch based GPS watch, shows OS map reference",
|
||||
"icon": "gpstouch.png",
|
||||
"tags": "tools,app",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"geotools","url":"geotools.js"},
|
||||
{"name":"gpstouch.app.js","url":"gpstouch.app.js"},
|
||||
{"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "swiperclocklaunch",
|
||||
"name": "Swiper Clock Launch",
|
||||
"version": "0.01",
|
||||
"description": "Navigate between clock and launcher with Swipe action",
|
||||
"icon": "swiperclocklaunch.png",
|
||||
"type": "boot",
|
||||
"tags": "system",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"swiperclocklaunch.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Analogue Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,4 @@
|
|||
# Bold Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -34,3 +34,4 @@
|
|||
0.32: Fix single quote error in g.wrapString polyfill
|
||||
improve g.stringMetrics polyfill
|
||||
Fix issue where re-running bootupdate could disable existing polyfills
|
||||
0.33: Add E.showScroller polyfill
|
||||
|
|
|
@ -133,6 +133,11 @@ else if (mode=="updown") {
|
|||
throw new Error("Unknown UI mode");
|
||||
};\n`;
|
||||
}
|
||||
delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);b<c&&(c=b);g.setColor(g.theme.fg);for(var d=0;d<l;d++){var m=d+c;if(0>m||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fgG).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(),
|
||||
k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`;
|
||||
}
|
||||
delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill
|
||||
boot += `Graphics.prototype.imageMetrics=function(src) {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# 2x3 Pixel Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,4 @@
|
|||
# Nifty-A Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,5 @@
|
|||
# Flappy Bird
|
||||
|
||||

|
||||

|
||||
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,4 @@
|
|||
# Floral Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: First version
|
|
@ -0,0 +1,16 @@
|
|||
# GPS Touch
|
||||
|
||||
- A touch controlled GPS watch for Bangle JS 2
|
||||
- Key feature is the conversion of Lat/Lon into Ordinance Servey Grid Reference
|
||||
- Swipe left and right to change the display
|
||||
- Select GPS and switch the GPS On or Off by touching twice in the top half of the display
|
||||
- Select LOGGER and switch the GPS Recorder On or Off by touching twice in the top half of the display
|
||||
- Displays the GPS time in the bottom half of the screen when the GPS is powered on, otherwise 00:00:00
|
||||
- Select display of Course, Speed, Altitude, Longitude, Latitude, Ordinance Servey Grid Reference
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
*
|
||||
* A module of Geo functions for use with gps fixes
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
* let os = geo.gpsToOSGrid(fix);
|
||||
* let ref = geo.gpsToOSMapRef(fix);
|
||||
*
|
||||
*/
|
||||
|
||||
Number.prototype.toRad = function() { return this*Math.PI/180; };
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
|
||||
/* - www.movable-type.co.uk/scripts/gridref.js */
|
||||
/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
function OsGridRef(easting, northing) {
|
||||
this.easting = 0|easting;
|
||||
this.northing = 0|northing;
|
||||
}
|
||||
OsGridRef.latLongToOsGrid = function(point) {
|
||||
var lat = point.lat.toRad();
|
||||
var lon = point.lon.toRad();
|
||||
|
||||
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
|
||||
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
|
||||
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W
|
||||
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
|
||||
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
|
||||
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
|
||||
|
||||
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
|
||||
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
|
||||
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
|
||||
var eta2 = nu/rho-1;
|
||||
|
||||
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
|
||||
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
|
||||
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
|
||||
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
|
||||
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
|
||||
|
||||
var cos3lat = cosLat*cosLat*cosLat;
|
||||
var cos5lat = cos3lat*cosLat*cosLat;
|
||||
var tan2lat = Math.tan(lat)*Math.tan(lat);
|
||||
var tan4lat = tan2lat*tan2lat;
|
||||
|
||||
var I = M + N0;
|
||||
var II = (nu/2)*sinLat*cosLat;
|
||||
var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
|
||||
var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
|
||||
var IV = nu*cosLat;
|
||||
var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
|
||||
var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
|
||||
|
||||
var dLon = lon-lon0;
|
||||
var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
|
||||
|
||||
var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
|
||||
var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
|
||||
|
||||
return new OsGridRef(E, N);
|
||||
};
|
||||
|
||||
/*
|
||||
* converts northing, easting to standard OS grid reference.
|
||||
*
|
||||
* [digits=10] - precision (10 digits = metres)
|
||||
* to_map_ref(8, 651409, 313177); => 'TG 5140 1317'
|
||||
* to_map_ref(0, 651409, 313177); => '651409,313177'
|
||||
*
|
||||
*/
|
||||
function to_map_ref(digits, easting, northing) {
|
||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
|
||||
|
||||
let e = easting;
|
||||
let n = northing;
|
||||
|
||||
// use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7
|
||||
if (digits == 0) {
|
||||
const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 };
|
||||
const ePad = e.toLocaleString('en', format);
|
||||
const nPad = n.toLocaleString('en', format);
|
||||
return `${ePad},${nPad}`;
|
||||
}
|
||||
|
||||
// get the 100km-grid indices
|
||||
const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000);
|
||||
|
||||
// translate those into numeric equivalents of the grid letters
|
||||
let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5);
|
||||
let l2 = (19 - n100km) * 5 % 25 + e100km % 5;
|
||||
|
||||
// compensate for skipped 'I' and build grid letter-pairs
|
||||
if (l1 > 7) l1++;
|
||||
if (l2 > 7) l2++;
|
||||
const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
|
||||
|
||||
// strip 100km-grid indices from easting & northing, and reduce precision
|
||||
e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
|
||||
|
||||
// pad eastings & northings with leading zeros
|
||||
e = e.toString().padStart(digits/2, '0');
|
||||
n = n.toString().padStart(digits/2, '0');
|
||||
|
||||
return `${letterPair} ${e} ${n}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Module exports section, example code below
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
* let os = geo.gpsToOSGrid(fix);
|
||||
* let ref = geo.gpsToOSMapRef(fix);
|
||||
*/
|
||||
|
||||
// get easting and northings
|
||||
exports.gpsToOSGrid = function(gps_fix) {
|
||||
return OsGridRef.latLongToOsGrid(gps_fix);
|
||||
}
|
||||
|
||||
// string with an OS Map grid reference
|
||||
exports.gpsToOSMapRef = function(gps_fix) {
|
||||
let os = OsGridRef.latLongToOsGrid(last_fix);
|
||||
return to_map_ref(6, os.easting, os.northing);
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
let geo = require("geotools");
|
||||
let last_fix;
|
||||
let listennerCount = 0;
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
function resetLastFix() {
|
||||
last_fix = {
|
||||
fix: 0,
|
||||
alt: 0,
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
speed: 0,
|
||||
time: 0,
|
||||
course: 0,
|
||||
satellites: 0
|
||||
};
|
||||
}
|
||||
|
||||
function processFix(fix) {
|
||||
last_fix.time = fix.time;
|
||||
log_debug(fix);
|
||||
|
||||
if (fix.fix) {
|
||||
if (!last_fix.fix) {
|
||||
// we dont need to suppress this in quiet mode as it is user initiated
|
||||
Bangle.buzz(1500); // buzz on first position
|
||||
}
|
||||
last_fix = fix;
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var time = da[4].substr(0,5);
|
||||
var hh = da[4].substr(0,2);
|
||||
var mm = da[4].substr(3,2);
|
||||
|
||||
g.reset();
|
||||
drawTop(d,hh,mm);
|
||||
drawInfo();
|
||||
}
|
||||
|
||||
function drawTop(d,hh,mm) {
|
||||
g.setFont("Vector", w/3);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(0, 24, w, ((h-24)/2) + 24);
|
||||
g.setColor(g.theme.fg);
|
||||
|
||||
g.setFontAlign(1,0); // right aligned
|
||||
g.drawString(hh, (w/2) - 6, ((h-24)/4) + 24);
|
||||
g.setFontAlign(-1,0); // left aligned
|
||||
g.drawString(mm, (w/2) + 6, ((h-24)/4) + 24);
|
||||
|
||||
// for the colon
|
||||
g.setFontAlign(0,0); // centre aligned
|
||||
if (d.getSeconds()&1) g.drawString(":", w/2, ((h-24)/4) + 24);
|
||||
}
|
||||
|
||||
function drawInfo() {
|
||||
if (infoData[infoMode] && infoData[infoMode].calc) {
|
||||
g.setFont("Vector", w/7);
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
if (infoData[infoMode].get_color)
|
||||
g.setColor(infoData[infoMode].get_color());
|
||||
else
|
||||
g.setColor("#0ff");
|
||||
g.fillRect(0, ((h-24)/2) + 24 + 1, w, h);
|
||||
|
||||
if (infoData[infoMode].is_control)
|
||||
g.setColor("#fff");
|
||||
else
|
||||
g.setColor("#000");
|
||||
|
||||
g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24);
|
||||
}
|
||||
}
|
||||
|
||||
const infoData = {
|
||||
ID_LAT: {
|
||||
calc: () => 'Lat: ' + last_fix.lat.toFixed(4),
|
||||
},
|
||||
ID_LON: {
|
||||
calc: () => 'Lon: ' + last_fix.lon.toFixed(4),
|
||||
},
|
||||
ID_SPEED: {
|
||||
calc: () => 'Speed: ' + last_fix.speed.toFixed(1),
|
||||
},
|
||||
ID_ALT: {
|
||||
calc: () => 'Alt: ' + last_fix.alt.toFixed(0),
|
||||
},
|
||||
ID_COURSE: {
|
||||
calc: () => 'Course: '+ last_fix.course.toFixed(0),
|
||||
},
|
||||
ID_SATS: {
|
||||
calc: () => 'Satelites: ' + last_fix.satellites,
|
||||
},
|
||||
ID_TIME: {
|
||||
calc: () => formatTime(last_fix.time),
|
||||
},
|
||||
OS_REF: {
|
||||
calc: () => !last_fix.fix ? "OO 000 000" : geo.gpsToOSMapRef(last_fix),
|
||||
},
|
||||
GPS_POWER: {
|
||||
calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off',
|
||||
action: () => toggleGPS(),
|
||||
get_color: () => Bangle.isGPSOn() ? '#f00' : '#00f',
|
||||
is_control: true,
|
||||
},
|
||||
GPS_LOGGER: {
|
||||
calc: () => 'Logger ' + loggerStatus(),
|
||||
action: () => toggleLogger(),
|
||||
get_color: () => loggerStatus() == "ON" ? '#f00' : '#00f',
|
||||
is_control: true,
|
||||
},
|
||||
};
|
||||
|
||||
function toggleGPS() {
|
||||
if (loggerStatus() == "ON")
|
||||
return;
|
||||
|
||||
Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch');
|
||||
// add or remove listenner
|
||||
if (Bangle.isGPSOn()) {
|
||||
if (listennerCount == 0) {
|
||||
Bangle.on('GPS', processFix);
|
||||
listennerCount++;
|
||||
log_debug("listennerCount=" + listennerCount);
|
||||
}
|
||||
} else {
|
||||
if (listennerCount > 0) {
|
||||
Bangle.removeListener("GPS", processFix);
|
||||
listennerCount--;
|
||||
log_debug("listennerCount=" + listennerCount);
|
||||
}
|
||||
}
|
||||
resetLastFix();
|
||||
}
|
||||
|
||||
function loggerStatus() {
|
||||
var settings = require("Storage").readJSON("gpsrec.json",1)||{};
|
||||
if (settings == {}) return "Install";
|
||||
return settings.recording ? "ON" : "OFF";
|
||||
}
|
||||
|
||||
function toggleLogger() {
|
||||
var settings = require("Storage").readJSON("gpsrec.json",1)||{};
|
||||
if (settings == {}) return;
|
||||
|
||||
settings.recording = !settings.recording;
|
||||
require("Storage").write("gpsrec.json", settings);
|
||||
|
||||
if (WIDGETS["gpsrec"])
|
||||
WIDGETS["gpsrec"].reload();
|
||||
|
||||
if (settings.recording && listennerCount == 0) {
|
||||
Bangle.on('GPS', processFix);
|
||||
listennerCount++;
|
||||
log_debug("listennerCount=" + listennerCount);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(now) {
|
||||
try {
|
||||
var fd = now.toUTCString().split(" ");
|
||||
return fd[4];
|
||||
} catch (e) {
|
||||
return "00:00:00";
|
||||
}
|
||||
}
|
||||
|
||||
const infoList = Object.keys(infoData).sort();
|
||||
let infoMode = infoList[0];
|
||||
|
||||
function nextInfo() {
|
||||
let idx = infoList.indexOf(infoMode);
|
||||
if (idx > -1) {
|
||||
if (idx === infoList.length - 1) infoMode = infoList[0];
|
||||
else infoMode = infoList[idx + 1];
|
||||
}
|
||||
}
|
||||
|
||||
function prevInfo() {
|
||||
let idx = infoList.indexOf(infoMode);
|
||||
if (idx > -1) {
|
||||
if (idx === 0) infoMode = infoList[infoList.length - 1];
|
||||
else infoMode = infoList[idx - 1];
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on('swipe', dir => {
|
||||
if (dir == 1) prevInfo() else nextInfo();
|
||||
draw();
|
||||
});
|
||||
|
||||
let prevTouch = 0;
|
||||
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
let dur = 1000*(getTime() - prevTouch);
|
||||
prevTouch = getTime();
|
||||
|
||||
if (dur <= 1000 && xy.y < h/2 && infoData[infoMode].is_control) {
|
||||
Bangle.buzz();
|
||||
if (infoData[infoMode] && infoData[infoMode].action) {
|
||||
infoData[infoMode].action();
|
||||
draw();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower', on => {
|
||||
if (secondInterval)
|
||||
clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on)
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
});
|
||||
|
||||
resetLastFix();
|
||||
|
||||
// add listenner if already powered on, plus tag app
|
||||
if (Bangle.isGPSOn() || loggerStatus() == "ON") {
|
||||
Bangle.setGPSPower(1, 'gpstouch');
|
||||
if (listennerCount == 0) {
|
||||
Bangle.on('GPS', processFix);
|
||||
listennerCount++;
|
||||
log_debug("listennerCount=" + listennerCount);
|
||||
}
|
||||
}
|
||||
|
||||
g.clear();
|
||||
var secondInterval = setInterval(draw, 1000);
|
||||
draw();
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA="))
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -21,9 +21,6 @@ GB({"t":"notify-","id":1})
|
|||
GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"})
|
||||
GB({"t":"notify~","id":1,"title":"High St"})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"})
|
||||
GB({"t":"notify~","id":1,"title":"0 yd - High St"})
|
||||
GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"})
|
||||
|
||||
*/
|
||||
|
||||
|
@ -97,140 +94,6 @@ function showMessage(msgid) {
|
|||
});
|
||||
}
|
||||
|
||||
// Show a single menu item for the message
|
||||
function showMessageMenuItem(y, idx) {
|
||||
var msg = MESSAGES[idx];
|
||||
var W = g.getWidth(), H=48;
|
||||
if (msg.new) g.setBgColor("#4F4");
|
||||
else g.setBgColor("#CFC");
|
||||
g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg);
|
||||
var m = msg.title+"\n"+msg.body;
|
||||
if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2);
|
||||
if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2);
|
||||
if (msg.body) {
|
||||
g.setFontAlign(-1,-1).setFont("6x8");
|
||||
var l = g.wrapString(msg.body, W-14);
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
g.drawString(l.join("\n"), 12,y+20);
|
||||
}
|
||||
}
|
||||
//test
|
||||
//g.clear(1); showMessageMenuItem(MESSAGES[0],24)
|
||||
|
||||
if (process.env.HWVERSION==1) { // Bangle.js 1
|
||||
showBigMenu = function(options) {
|
||||
/* options = {
|
||||
h = height
|
||||
items = # of items
|
||||
draw = function(y, idx)
|
||||
onSelect = function(idx)
|
||||
}*/
|
||||
var selected = 0;
|
||||
var menuScroll = 0;
|
||||
var menuShowing = false;
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var m = w/2;
|
||||
var n = Math.floor((h-48)/options.h);
|
||||
|
||||
function drawMenu() {
|
||||
g.reset();
|
||||
if (selected>=n+menuScroll) menuScroll = 1+selected-n;
|
||||
if (selected<menuScroll) menuScroll = selected;
|
||||
// draw
|
||||
g.setColor(g.theme.fg);
|
||||
for (var i=0;i<n;i++) {
|
||||
var idx = i+menuScroll;
|
||||
if (idx<0 || idx>=options.items) break;
|
||||
var y = 24+i*options.h;
|
||||
options.draw(y, idx);
|
||||
// border for selected
|
||||
if (i+menuScroll==selected) {
|
||||
g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2);
|
||||
}
|
||||
}
|
||||
// arrows
|
||||
g.setColor(menuScroll ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,6,m-14,20,m+14,20]);
|
||||
g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg);
|
||||
g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]);
|
||||
}
|
||||
g.clearRect(0,24,w-1,h-1);
|
||||
drawMenu();
|
||||
Bangle.setUI("updown",dir=>{
|
||||
if (dir) {
|
||||
selected += dir;
|
||||
if (selected<0) selected = options.items-1;
|
||||
if (selected>=options.items) selected = 0;
|
||||
drawMenu();
|
||||
} else {
|
||||
options.onSelect(selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else { // Bangle.js 2
|
||||
showBigMenu = function(options) {
|
||||
/* options = {
|
||||
h = height
|
||||
items = # of items
|
||||
draw = function(y, idx)
|
||||
onSelect = function(idx)
|
||||
}*/
|
||||
var menuScroll = 0;
|
||||
var menuShowing = false;
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
var n = Math.ceil((h-24)/options.h);
|
||||
var menuScrollMax = options.h*options.items - (h-24);
|
||||
|
||||
function drawItem(i) {
|
||||
var y = 24+i*options.h-menuScroll;
|
||||
if (i<0 || i>=options.items || y<-options.h || y>=h) return;
|
||||
options.draw(y, i);
|
||||
}
|
||||
|
||||
function drawMenu() {
|
||||
g.reset().clearRect(0,24,w-1,h-1);
|
||||
g.setClipRect(0,24,w-1,h-1);
|
||||
for (var i=0;i<n;i++) drawItem(i);
|
||||
g.setClipRect(0,0,w-1,h-1);
|
||||
}
|
||||
drawMenu();
|
||||
g.flip(); // force an update now to make this snappier
|
||||
|
||||
Bangle.dragHandler = e=>{
|
||||
var dy = e.dy;
|
||||
if (menuScroll - dy < 0)
|
||||
dy = menuScroll;
|
||||
if (menuScroll - dy > menuScrollMax)
|
||||
dy = menuScroll - menuScrollMax;
|
||||
if (!dy) return;
|
||||
g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1);
|
||||
g.scroll(0,dy);
|
||||
menuScroll -= dy;
|
||||
if (e.dy < 0) {
|
||||
drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1);
|
||||
if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2);
|
||||
} else {
|
||||
drawItem(Math.floor((menuScroll+24)/options.h));
|
||||
if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1);
|
||||
}
|
||||
g.setClipRect(0,0,w-1,h-1);
|
||||
};
|
||||
Bangle.on('drag',Bangle.dragHandler);
|
||||
Bangle.touchHandler = (_,e)=>{
|
||||
if (e.y<20) return;
|
||||
var i = Math.floor((e.y+menuScroll-24) / options.h);
|
||||
if (i>=0 && i<options.items)
|
||||
options.onSelect(i);
|
||||
};
|
||||
Bangle.on("touch", Bangle.touchHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function checkMessages(forceShowMenu) {
|
||||
// If no messages, just show 'no messages' and return
|
||||
if (!MESSAGES.length)
|
||||
|
@ -240,31 +103,39 @@ function checkMessages(forceShowMenu) {
|
|||
buttons : {"Ok":1}
|
||||
}).then(() => { load() });
|
||||
// we have >0 messages
|
||||
// TODO: IF A NEW MESSAGE, SHOW IT
|
||||
// If we have a new message, show it
|
||||
if (!forceShowMenu) {
|
||||
var newMessages = MESSAGES.filter(m=>m.new);
|
||||
if (newMessages.length)
|
||||
return showMessage(newMessages[0].id);
|
||||
}
|
||||
// Otherwise show a menu
|
||||
var m = {
|
||||
"":{title:"Messages"},
|
||||
"< Back": ()=>load()
|
||||
};
|
||||
/*g.setFont("6x8");
|
||||
MESSAGES.forEach(msg=>{
|
||||
// "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"
|
||||
var title = g.wrapString(msg.title, g.getWidth())[0];
|
||||
m[title] = function() {
|
||||
showMessage(msg.id);
|
||||
}
|
||||
});
|
||||
E.showMenu(m);*/
|
||||
showBigMenu({
|
||||
E.showScroller({
|
||||
h : 48,
|
||||
items : MESSAGES.length,
|
||||
draw : showMessageMenuItem,
|
||||
onSelect : idx => showMessage(MESSAGES[idx].id)
|
||||
c : MESSAGES.length,
|
||||
draw : function(idx, r) {"ram"
|
||||
var msg = MESSAGES[idx-1];
|
||||
if (msg && msg.new) g.setBgColor("#4F4");
|
||||
else g.setBgColor((idx&1) ? "#CFC" : "#9F9");
|
||||
g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg);
|
||||
if (idx==0) msg = {title:"< Back"};
|
||||
var m = msg.title+"\n"+msg.body;
|
||||
if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2);
|
||||
if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2);
|
||||
if (msg.body) {
|
||||
g.setFontAlign(-1,-1).setFont("6x8");
|
||||
var l = g.wrapString(msg.body, r.w-14);
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
g.drawString(l.join("\n"), r.x+12,r.y+20);
|
||||
}
|
||||
},
|
||||
select : idx => {
|
||||
if (idx==0) load();
|
||||
else showMessage(MESSAGES[idx-1].id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Simple 7 Segment Clock
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -4,6 +4,9 @@ This app will allow you to keep scores for most kinds of sports.
|
|||
To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`).
|
||||
In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again.
|
||||
|
||||
## Screenshot
|
||||

|
||||
|
||||
## Bangle.js 1
|
||||
| Keybinding | Description |
|
||||
|---------------------|------------------------------|
|
||||
|
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,2 @@
|
|||
0.01: Created app
|
||||
0.10: Different colour schemes selectable in SloMo Clock settings.
|
|
@ -0,0 +1,6 @@
|
|||
# SloMo Clock
|
||||
|
||||
Simple 24h clock with large digits.
|
||||
|
||||

|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2"))
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Simple watch [slomoclock]
|
||||
Mike Bennett mike[at]kereru.com
|
||||
0.01 : Initial
|
||||
0.03 : Use Layout library
|
||||
*/
|
||||
|
||||
var v='0.10';
|
||||
|
||||
// Colours
|
||||
const col = [];
|
||||
col[2] = 0xF800;
|
||||
col[3] = 0xFAE0;
|
||||
col[4] = 0xF7E0;
|
||||
col[5] = 0x4FE0;
|
||||
col[6] = 0x019F;
|
||||
col[7] = 0x681F;
|
||||
col[8] = 0xFFFF;
|
||||
|
||||
const colH = [];
|
||||
colH[0]= 0x001F;
|
||||
colH[1]= 0x023F;
|
||||
colH[2]= 0x039F;
|
||||
colH[3]= 0x051F;
|
||||
colH[4]= 0x067F;
|
||||
colH[5]= 0x07FD;
|
||||
colH[6]= 0x07F6;
|
||||
colH[7]= 0x07EF;
|
||||
colH[8]= 0x07E8;
|
||||
colH[9]= 0x07E3;
|
||||
colH[10]= 0x07E0;
|
||||
colH[11]= 0x5FE0;
|
||||
colH[12]= 0x97E0;
|
||||
colH[13]= 0xCFE0;
|
||||
colH[14]= 0xFFE0;
|
||||
colH[15]= 0xFE60;
|
||||
colH[16]= 0xFC60;
|
||||
colH[17]= 0xFAA0;
|
||||
colH[18]= 0xF920;
|
||||
colH[19]= 0xF803;
|
||||
colH[20]= 0xF80E;
|
||||
colH[21]= 0x981F;
|
||||
colH[22]= 0x681F;
|
||||
colH[23]= 0x301F;
|
||||
|
||||
// Colour incremented with every 10 sec timer event
|
||||
var colNum = 0;
|
||||
var lastMin = -1;
|
||||
|
||||
var Layout = require("Layout");
|
||||
var layout = new Layout( {
|
||||
type:"h", c: [
|
||||
{type:"v", c: [
|
||||
{type:"txt", font:"40%", label:"", id:"hour", valign:1},
|
||||
{type:"txt", font:"40%", label:"", id:"min", valign:-1},
|
||||
]},
|
||||
{type:"v", c: [
|
||||
{type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1},
|
||||
{type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1},
|
||||
]}
|
||||
]
|
||||
}, {lazy:true});
|
||||
|
||||
// update the screen
|
||||
function draw() {
|
||||
var date = new Date();
|
||||
|
||||
// Update time
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var hh = parseFloat(timeStr.substring(0,2));
|
||||
var mm = parseFloat(timeStr.substring(3,5));
|
||||
|
||||
// Surprise colours
|
||||
if ( lastMin != mm ) colNum = Math.floor(Math.random() * 24);
|
||||
lastMin = mm;
|
||||
|
||||
layout.hour.label = timeStr.substring(0,2);
|
||||
layout.min.label = timeStr.substring(3,5);
|
||||
|
||||
// Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs.
|
||||
layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] : col[cfg.colour];
|
||||
layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] :col[cfg.colour];
|
||||
|
||||
// Update date
|
||||
layout.day.label = date.getDate();
|
||||
layout.mon.label = require("locale").month(date,1);
|
||||
|
||||
layout.render();
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (secondInterval) clearInterval(secondInterval);
|
||||
secondInterval = undefined;
|
||||
if (on) {
|
||||
secondInterval = setInterval(draw, 10000);
|
||||
draw(); // draw immediately
|
||||
}
|
||||
});
|
||||
|
||||
var secondInterval = setInterval(draw, 10000);
|
||||
|
||||
// Configuration
|
||||
let cfg = require('Storage').readJSON('slomoclock.json',1)||{};
|
||||
cfg.colour = cfg.colour||0; // Colours
|
||||
|
||||
// update time and draw
|
||||
g.clear();
|
||||
draw();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
|
@ -0,0 +1,38 @@
|
|||
(function(back) {
|
||||
|
||||
let settings = require('Storage').readJSON('slomoclock.json',1)||{};
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').write('slomoclock.json',settings);
|
||||
}
|
||||
|
||||
function setColour(c) {
|
||||
settings.colour = c;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
const appMenu = {
|
||||
'': {'title': 'SloMo Clock'},
|
||||
'< Back': back,
|
||||
'Colours' : function() { E.showMenu(colMenu); }
|
||||
//,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }
|
||||
//,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); }
|
||||
};
|
||||
|
||||
const colMenu = {
|
||||
'': {'title': 'Colours'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
'Mysterion' : function() { setColour(0); },
|
||||
'Surprise' : function() { setColour(1); },
|
||||
'Red' : function() { setColour(2); },
|
||||
'Orange' : function() { setColour(3); },
|
||||
'Yellow' : function() { setColour(4); },
|
||||
'Green' : function() { setColour(5); },
|
||||
'Blue' : function() { setColour(6); },
|
||||
'Violet' : function() { setColour(7); },
|
||||
'White' : function() { setColour(8); }
|
||||
};
|
||||
|
||||
E.showMenu(appMenu);
|
||||
|
||||
});
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -32,7 +32,7 @@
|
|||
|
||||
|
||||
const appMenu = {
|
||||
'': {'title': 'GPS Speed Alt'},
|
||||
'': {'title': 'GPS Adv Sprt'},
|
||||
'< Back': back,
|
||||
'< Load GPS Adv Sport': ()=>{load('speedalt.app.js');},
|
||||
'Units' : function() { E.showMenu(unitsMenu); },
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial import.
|
||||
0.07: Add swipe to change screens.
|
|
@ -0,0 +1,134 @@
|
|||
# GPS Speed, Altimeter and Distance to Waypoint
|
||||
|
||||
What is the difference between **GPS Adventure Sports** and **GPS Adventure Sports II** ?
|
||||
|
||||
**GPS Adventure Sports** has 3 screens, each of which display different sets of information.
|
||||
|
||||
**GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time.
|
||||
|
||||
In all other respect they perform the same functions.
|
||||
|
||||
The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information.
|
||||
|
||||
## Buttons and Controls
|
||||
|
||||
**BTN1** ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values.
|
||||
|
||||
**BTN1** ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed.
|
||||
|
||||
**BTN2** : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts.
|
||||
|
||||
**BTN3** : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time
|
||||
|
||||
**BTN3** : Long press exit and return to watch.
|
||||
|
||||
**Touch Screen** If the 'Touch' setting is ON then :
|
||||
|
||||
Swipe Left/Right cycles between the five screens.
|
||||
|
||||
Touch functions as BTN1 short press.
|
||||
|
||||
|
||||
## App Settings
|
||||
|
||||
Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ).
|
||||
|
||||
## Kalman Filter
|
||||
|
||||
This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new values. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found.
|
||||
|
||||
## Loss of fix
|
||||
|
||||
When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not.
|
||||
|
||||
## Power Saving
|
||||
|
||||
The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery.
|
||||
|
||||
This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps%20setup) if it is installed. You may choose to use the GPS Setup App to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Setup App Readme to understand what this does.
|
||||
|
||||
When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched.
|
||||
|
||||
The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode.
|
||||
|
||||
## Waypoints
|
||||
|
||||
Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode.
|
||||
|
||||
The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.)
|
||||
|
||||
Sample waypoints.json (My sailing waypoints)
|
||||
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
"name":"NONE"
|
||||
},
|
||||
{
|
||||
"name":"Omori",
|
||||
"lat":-38.9058670,
|
||||
"lon":175.7613350
|
||||
},
|
||||
{
|
||||
"name":"DeltaW",
|
||||
"lat":-38.9438550,
|
||||
"lon":175.7676930
|
||||
},
|
||||
{
|
||||
"name":"DeltaE",
|
||||
"lat":-38.9395240,
|
||||
"lon":175.7814420
|
||||
},
|
||||
{
|
||||
"name":"BtClub",
|
||||
"lat":-38.9446020,
|
||||
"lon":175.8475720
|
||||
},
|
||||
{
|
||||
"name":"Hapua",
|
||||
"lat":-38.8177750,
|
||||
"lon":175.8088720
|
||||
},
|
||||
{
|
||||
"name":"Nook",
|
||||
"lat":-38.7848090,
|
||||
"lon":175.7839440
|
||||
},
|
||||
{
|
||||
"name":"ChryBy",
|
||||
"lat":-38.7975050,
|
||||
"lon":175.7551960
|
||||
},
|
||||
{
|
||||
"name":"Waiha",
|
||||
"lat":-38.7219630,
|
||||
"lon":175.7481520
|
||||
},
|
||||
{
|
||||
"name":"KwaKwa",
|
||||
"lat":-38.6632310,
|
||||
"lon":175.8670320
|
||||
},
|
||||
{
|
||||
"name":"Hatepe",
|
||||
"lat":-38.8547420,
|
||||
"lon":176.0089124
|
||||
},
|
||||
{
|
||||
"name":"Kinloc",
|
||||
"lat":-38.6614442,
|
||||
"lon":175.9161607
|
||||
}
|
||||
]
|
||||
</pre>
|
||||
|
||||
## Comments and Feedback
|
||||
|
||||
Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy!
|
||||
|
||||
## Thanks
|
||||
|
||||
Many thanks to Gordon Williams. Awesome job.
|
||||
|
||||
Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY"))
|
|
@ -0,0 +1,725 @@
|
|||
/*
|
||||
Speed and Altitude [speedalt2]
|
||||
Mike Bennett mike[at]kereru.com
|
||||
0.01 : Initial
|
||||
0.06 : Add Posn screen
|
||||
0.07 : Add swipe to change screens same as BTN3
|
||||
*/
|
||||
var v = '1.05';
|
||||
|
||||
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
||||
var KalmanFilter = (function () {
|
||||
'use strict';
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
}
|
||||
|
||||
function _defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var descriptor = props[i];
|
||||
descriptor.enumerable = descriptor.enumerable || false;
|
||||
descriptor.configurable = true;
|
||||
if ("value" in descriptor) descriptor.writable = true;
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) {
|
||||
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) _defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* KalmanFilter
|
||||
* @class
|
||||
* @author Wouter Bulten
|
||||
* @see {@link http://github.com/wouterbulten/kalmanjs}
|
||||
* @version Version: 1.0.0-beta
|
||||
* @copyright Copyright 2015-2018 Wouter Bulten
|
||||
* @license MIT License
|
||||
* @preserve
|
||||
*/
|
||||
var KalmanFilter =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
/**
|
||||
* Create 1-dimensional kalman filter
|
||||
* @param {Number} options.R Process noise
|
||||
* @param {Number} options.Q Measurement noise
|
||||
* @param {Number} options.A State vector
|
||||
* @param {Number} options.B Control vector
|
||||
* @param {Number} options.C Measurement vector
|
||||
* @return {KalmanFilter}
|
||||
*/
|
||||
function KalmanFilter() {
|
||||
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||
_ref$R = _ref.R,
|
||||
R = _ref$R === void 0 ? 1 : _ref$R,
|
||||
_ref$Q = _ref.Q,
|
||||
Q = _ref$Q === void 0 ? 1 : _ref$Q,
|
||||
_ref$A = _ref.A,
|
||||
A = _ref$A === void 0 ? 1 : _ref$A,
|
||||
_ref$B = _ref.B,
|
||||
B = _ref$B === void 0 ? 0 : _ref$B,
|
||||
_ref$C = _ref.C,
|
||||
C = _ref$C === void 0 ? 1 : _ref$C;
|
||||
|
||||
_classCallCheck(this, KalmanFilter);
|
||||
|
||||
this.R = R; // noise power desirable
|
||||
|
||||
this.Q = Q; // noise power estimated
|
||||
|
||||
this.A = A;
|
||||
this.C = C;
|
||||
this.B = B;
|
||||
this.cov = NaN;
|
||||
this.x = NaN; // estimated signal without noise
|
||||
}
|
||||
/**
|
||||
* Filter a new value
|
||||
* @param {Number} z Measurement
|
||||
* @param {Number} u Control
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
|
||||
_createClass(KalmanFilter, [{
|
||||
key: "filter",
|
||||
value: function filter(z) {
|
||||
var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
||||
|
||||
if (isNaN(this.x)) {
|
||||
this.x = 1 / this.C * z;
|
||||
this.cov = 1 / this.C * this.Q * (1 / this.C);
|
||||
} else {
|
||||
// Compute prediction
|
||||
var predX = this.predict(u);
|
||||
var predCov = this.uncertainty(); // Kalman gain
|
||||
|
||||
var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction
|
||||
|
||||
this.x = predX + K * (z - this.C * predX);
|
||||
this.cov = predCov - K * this.C * predCov;
|
||||
}
|
||||
|
||||
return this.x;
|
||||
}
|
||||
/**
|
||||
* Predict next value
|
||||
* @param {Number} [u] Control
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "predict",
|
||||
value: function predict() {
|
||||
var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
||||
return this.A * this.x + this.B * u;
|
||||
}
|
||||
/**
|
||||
* Return uncertainty of filter
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "uncertainty",
|
||||
value: function uncertainty() {
|
||||
return this.A * this.cov * this.A + this.R;
|
||||
}
|
||||
/**
|
||||
* Return the last filtered measurement
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "lastMeasurement",
|
||||
value: function lastMeasurement() {
|
||||
return this.x;
|
||||
}
|
||||
/**
|
||||
* Set measurement noise Q
|
||||
* @param {Number} noise
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "setMeasurementNoise",
|
||||
value: function setMeasurementNoise(noise) {
|
||||
this.Q = noise;
|
||||
}
|
||||
/**
|
||||
* Set the process noise R
|
||||
* @param {Number} noise
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: "setProcessNoise",
|
||||
value: function setProcessNoise(noise) {
|
||||
this.R = noise;
|
||||
}
|
||||
}]);
|
||||
|
||||
return KalmanFilter;
|
||||
}();
|
||||
|
||||
return KalmanFilter;
|
||||
|
||||
}());
|
||||
|
||||
|
||||
var buf = Graphics.createArrayBuffer(240,160,2,{msb:true});
|
||||
|
||||
// Load fonts
|
||||
//require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
var lf = {fix:0,satellites:0};
|
||||
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
||||
var pwrSav = 1; // 1 = default power saving with watch screen off and GPS to PMOO mode. 0 = screen kept on.
|
||||
var canDraw = 1;
|
||||
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
|
||||
var tmrLP; // Timer for delay in switching to low power after screen turns off
|
||||
|
||||
var max = {};
|
||||
max.spd = 0;
|
||||
max.alt = 0;
|
||||
max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data.
|
||||
|
||||
var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values;
|
||||
|
||||
var wp = {}; // Waypoint to use for distance from cur position.
|
||||
|
||||
function nxtWp(inc){
|
||||
cfg.wp+=inc;
|
||||
loadWp();
|
||||
}
|
||||
|
||||
function loadWp() {
|
||||
var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}];
|
||||
if (cfg.wp>=w.length) cfg.wp=0;
|
||||
if (cfg.wp<0) cfg.wp = w.length-1;
|
||||
savSettings();
|
||||
wp = w[cfg.wp];
|
||||
}
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function distance(a,b){
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
|
||||
// Distance in selected units
|
||||
var d = Math.sqrt(x*x + y*y) * 6371000;
|
||||
d = (d/parseFloat(cfg.dist)).toFixed(2);
|
||||
if ( d >= 100 ) d = parseFloat(d).toFixed(1);
|
||||
if ( d >= 1000 ) d = parseFloat(d).toFixed(0);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
function drawScrn(dat) {
|
||||
|
||||
if (!canDraw) return;
|
||||
|
||||
buf.clear();
|
||||
|
||||
var n;
|
||||
n = dat.val.toString();
|
||||
|
||||
var s=50; // Font size
|
||||
var l=n.length;
|
||||
|
||||
if ( l <= 7 ) s=55;
|
||||
if ( l <= 6 ) s=60;
|
||||
if ( l <= 5 ) s=80;
|
||||
if ( l <= 4 ) s=100;
|
||||
if ( l <= 3 ) s=120;
|
||||
|
||||
buf.setFontAlign(0,0); //Centre
|
||||
buf.setColor(1);
|
||||
buf.setFontVector(s);
|
||||
buf.drawString(n,126,52);
|
||||
|
||||
|
||||
// Primary Units
|
||||
buf.setFontAlign(-1,1); //left, bottom
|
||||
buf.setColor(2);
|
||||
buf.setFontVector(35);
|
||||
buf.drawString(dat.unit,5,164);
|
||||
|
||||
if ( dat.max ) drawMax(); // MAX display indicator
|
||||
if ( dat.wp ) drawWP(); // Waypoint name
|
||||
|
||||
//Sats
|
||||
if ( dat.sat ) {
|
||||
if ( dat.age > 10 ) {
|
||||
if ( dat.age > 90 ) dat.age = '>90';
|
||||
drawSats('Age:'+dat.age);
|
||||
}
|
||||
else drawSats('Sats:'+dat.sats);
|
||||
}
|
||||
|
||||
g.reset();
|
||||
g.drawImage(img,0,40);
|
||||
|
||||
if ( pwrSav ) LED1.reset();
|
||||
else LED1.set();
|
||||
|
||||
}
|
||||
|
||||
function drawPosn(dat) {
|
||||
if (!canDraw) return;
|
||||
buf.clear();
|
||||
|
||||
var x, y;
|
||||
x=210;
|
||||
y=0;
|
||||
buf.setFontAlign(1,-1);
|
||||
buf.setFontVector(60);
|
||||
buf.setColor(1);
|
||||
|
||||
buf.drawString(dat.lat,x,y);
|
||||
buf.drawString(dat.lon,x,y+70);
|
||||
|
||||
x = 240;
|
||||
buf.setColor(2);
|
||||
buf.setFontVector(40);
|
||||
buf.drawString(dat.ns,x,y);
|
||||
buf.drawString(dat.ew,x,y+70);
|
||||
|
||||
|
||||
//Sats
|
||||
if ( dat.sat ) {
|
||||
if ( dat.age > 10 ) {
|
||||
if ( dat.age > 90 ) dat.age = '>90';
|
||||
drawSats('Age:'+dat.age);
|
||||
}
|
||||
else drawSats('Sats:'+dat.sats);
|
||||
}
|
||||
|
||||
g.reset();
|
||||
g.drawImage(img,0,40);
|
||||
|
||||
if ( pwrSav ) LED1.reset();
|
||||
else LED1.set();
|
||||
|
||||
}
|
||||
|
||||
function drawClock() {
|
||||
if (!canDraw) return;
|
||||
|
||||
buf.clear();
|
||||
var x, y;
|
||||
x=185;
|
||||
y=0;
|
||||
buf.setFontAlign(1,-1);
|
||||
buf.setFontVector(94);
|
||||
time = require("locale").time(new Date(),1);
|
||||
|
||||
buf.setColor(1);
|
||||
|
||||
buf.drawString(time.substring(0,2),x,y);
|
||||
buf.drawString(time.substring(3,5),x,y+80);
|
||||
|
||||
g.reset();
|
||||
g.drawImage(img,0,40);
|
||||
|
||||
if ( pwrSav ) LED1.reset();
|
||||
else LED1.set();
|
||||
}
|
||||
|
||||
function drawWP() {
|
||||
var nm = wp.name;
|
||||
if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = '';
|
||||
buf.setColor(2);
|
||||
|
||||
buf.setFontAlign(0,1); //left, bottom
|
||||
buf.setFontVector(48);
|
||||
buf.drawString(nm.substring(0,8),120,140);
|
||||
|
||||
}
|
||||
|
||||
function drawSats(sats) {
|
||||
buf.setColor(3);
|
||||
buf.setFont("6x8", 2);
|
||||
buf.setFontAlign(1,1); //right, bottom
|
||||
buf.drawString(sats,240,160);
|
||||
}
|
||||
|
||||
function drawMax() {
|
||||
buf.setFontVector(30);
|
||||
buf.setColor(2);
|
||||
buf.setFontAlign(0,1); //centre, bottom
|
||||
buf.drawString('MAX',120,164);
|
||||
}
|
||||
|
||||
function onGPS(fix) {
|
||||
|
||||
if ( emulator ) {
|
||||
fix.fix = 1;
|
||||
fix.speed = 10 + (Math.random()*5);
|
||||
fix.alt = 354 + (Math.random()*50);
|
||||
fix.lat = -38.92;
|
||||
fix.lon = 175.7613350;
|
||||
fix.course = 245;
|
||||
fix.satellites = 12;
|
||||
fix.time = new Date();
|
||||
fix.smoothed = 0;
|
||||
}
|
||||
|
||||
var m;
|
||||
|
||||
var sp = '---';
|
||||
var al = '---';
|
||||
var di = '---';
|
||||
var age = '---';
|
||||
var lat = '---.--';
|
||||
var ns = '';
|
||||
var ew = '';
|
||||
var lon = '---.--';
|
||||
|
||||
|
||||
if (fix.fix) lf = fix;
|
||||
|
||||
if (lf.fix) {
|
||||
|
||||
// Smooth data
|
||||
if ( lf.smoothed !== 1 ) {
|
||||
if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed);
|
||||
if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt);
|
||||
lf.smoothed = 1;
|
||||
if ( max.n <= 15 ) max.n++;
|
||||
}
|
||||
|
||||
|
||||
// Speed
|
||||
if ( cfg.spd == 0 ) {
|
||||
m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
|
||||
sp = parseFloat(m[1]);
|
||||
cfg.spd_unit = m[2];
|
||||
}
|
||||
else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units
|
||||
|
||||
if ( sp < 10 ) sp = sp.toFixed(1);
|
||||
else sp = Math.round(sp);
|
||||
|
||||
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp;
|
||||
|
||||
// Altitude
|
||||
al = lf.alt;
|
||||
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
|
||||
|
||||
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al;
|
||||
|
||||
// Distance to waypoint
|
||||
di = distance(lf,wp);
|
||||
if (isNaN(di)) di = 0;
|
||||
|
||||
// Age of last fix (secs)
|
||||
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
||||
|
||||
// Lat / Lon
|
||||
ns = 'N';
|
||||
if ( lf.lat < 0 ) ns = 'S';
|
||||
lat = Math.abs(lf.lat.toFixed(2));
|
||||
|
||||
ew = 'E';
|
||||
if ( lf.lon < 0 ) ew = 'W';
|
||||
lon = Math.abs(lf.lon.toFixed(2));
|
||||
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 0 ) {
|
||||
// Speed
|
||||
if ( showMax )
|
||||
drawScrn({
|
||||
val:max.spd,
|
||||
unit:cfg.spd_unit,
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
max:true,
|
||||
wp:false,
|
||||
sat:true
|
||||
}); // Speed maximums
|
||||
else
|
||||
drawScrn({
|
||||
val:sp,
|
||||
unit:cfg.spd_unit,
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
max:false,
|
||||
wp:false,
|
||||
sat:true
|
||||
});
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 1 ) {
|
||||
// Alt
|
||||
if ( showMax )
|
||||
drawScrn({
|
||||
val:max.alt,
|
||||
unit:cfg.alt_unit,
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
max:true,
|
||||
wp:false,
|
||||
sat:true
|
||||
}); // Alt maximums
|
||||
else
|
||||
drawScrn({
|
||||
val:al,
|
||||
unit:cfg.alt_unit,
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
max:false,
|
||||
wp:false,
|
||||
sat:true
|
||||
});
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 2 ) {
|
||||
// Dist
|
||||
drawScrn({
|
||||
val:di,
|
||||
unit:cfg.dist_unit,
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
max:false,
|
||||
wp:true,
|
||||
sat:true
|
||||
});
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 3 ) {
|
||||
// Position
|
||||
drawPosn({
|
||||
sats:lf.satellites,
|
||||
age:age,
|
||||
lat:lat,
|
||||
lon:lon,
|
||||
ns:ns,
|
||||
ew:ew,
|
||||
sat:true
|
||||
});
|
||||
}
|
||||
|
||||
if ( cfg.modeA == 4 ) {
|
||||
// Large clock
|
||||
drawClock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function prevScrn() {
|
||||
cfg.modeA = cfg.modeA-1;
|
||||
if ( cfg.modeA < 0 ) cfg.modeA = 4;
|
||||
savSettings();
|
||||
onGPS(lf);
|
||||
}
|
||||
|
||||
function nextScrn() {
|
||||
cfg.modeA = cfg.modeA+1;
|
||||
if ( cfg.modeA > 4 ) cfg.modeA = 0;
|
||||
savSettings();
|
||||
onGPS(lf);
|
||||
}
|
||||
|
||||
// Next function on a screen
|
||||
function nextFunc(dur) {
|
||||
if ( cfg.modeA == 0 || cfg.modeA == 1 ) {
|
||||
// Spd+Alt mode - Switch between fix and MAX
|
||||
if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display
|
||||
else { max.spd = 0; max.alt = 0; } // Long press resets max values.
|
||||
}
|
||||
else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint
|
||||
onGPS(lf);
|
||||
}
|
||||
|
||||
|
||||
function updateClock() {
|
||||
if (!canDraw) return;
|
||||
if ( cfg.modeA != 4 ) return;
|
||||
drawClock();
|
||||
if ( emulator ) {max.spd++;max.alt++;}
|
||||
}
|
||||
|
||||
function startDraw(){
|
||||
canDraw=true;
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
setLpMode('SuperE'); // off
|
||||
onGPS(lf); // draw app screen
|
||||
}
|
||||
|
||||
function stopDraw() {
|
||||
canDraw=false;
|
||||
if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix.
|
||||
}
|
||||
|
||||
function savSettings() {
|
||||
require("Storage").write('speedalt2.json',cfg);
|
||||
}
|
||||
|
||||
function setLpMode(m) {
|
||||
if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power
|
||||
if ( !gpssetup ) return;
|
||||
gpssetup.setPowerMode({power_mode:m});
|
||||
}
|
||||
|
||||
// == Events
|
||||
|
||||
function setButtons(){
|
||||
|
||||
// BTN1 - Max speed/alt or next waypoint
|
||||
setWatch(function(e) {
|
||||
var dur = e.time - e.lastTime;
|
||||
nextFunc(dur);
|
||||
}, BTN1, { edge:"falling",repeat:true});
|
||||
|
||||
// Power saving on/off
|
||||
setWatch(function(e){
|
||||
pwrSav=!pwrSav;
|
||||
if ( pwrSav ) {
|
||||
LED1.reset();
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var t = s.timeout||10;
|
||||
Bangle.setLCDTimeout(t);
|
||||
}
|
||||
else {
|
||||
Bangle.setLCDTimeout(0);
|
||||
// Bangle.setLCDPower(1);
|
||||
LED1.set();
|
||||
}
|
||||
}, BTN2, {repeat:true,edge:"falling"});
|
||||
|
||||
// BTN3 - next screen
|
||||
setWatch(function(e){
|
||||
nextScrn();
|
||||
}, BTN3, {repeat:true,edge:"falling"});
|
||||
|
||||
/*
|
||||
// Touch screen same as BTN1 short
|
||||
setWatch(function(e){
|
||||
nextFunc(1); // Same as BTN1 short
|
||||
}, BTN4, {repeat:true,edge:"falling"});
|
||||
setWatch(function(e){
|
||||
nextFunc(1); // Same as BTN1 short
|
||||
}, BTN5, {repeat:true,edge:"falling"});
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (!SCREENACCESS.withApp) return;
|
||||
if (on) startDraw();
|
||||
else stopDraw();
|
||||
});
|
||||
|
||||
Bangle.on('swipe',function(dir) {
|
||||
if ( ! cfg.touch ) return;
|
||||
if(dir == 1) prevScrn();
|
||||
else nextScrn();
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(button){
|
||||
if ( ! cfg.touch ) return;
|
||||
nextFunc(0); // Same function as short BTN1
|
||||
/*
|
||||
switch(button){
|
||||
case 1: // BTN4
|
||||
console.log('BTN4');
|
||||
prevScrn();
|
||||
break;
|
||||
case 2: // BTN5
|
||||
console.log('BTN5');
|
||||
nextScrn();
|
||||
break;
|
||||
case 3:
|
||||
console.log('MDL');
|
||||
nextFunc(0); // Centre - same function as short BTN1
|
||||
break;
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
|
||||
|
||||
// == Main Prog
|
||||
|
||||
// Read settings.
|
||||
let cfg = require('Storage').readJSON('speedalt2.json',1)||{};
|
||||
|
||||
cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
||||
cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit
|
||||
cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions.
|
||||
cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units
|
||||
cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions.
|
||||
cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units
|
||||
cfg.colour = cfg.colour||0; // Colour scheme.
|
||||
cfg.wp = cfg.wp||0; // Last selected waypoint for dist
|
||||
cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Position 4=Clock
|
||||
cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary
|
||||
|
||||
cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
|
||||
cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
|
||||
cfg.touch = cfg.touch==undefined?true:cfg.touch;
|
||||
|
||||
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
|
||||
if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
|
||||
|
||||
loadWp();
|
||||
|
||||
/*
|
||||
Colour Pallet Idx
|
||||
0 : Background (black)
|
||||
1 : Speed/Alt
|
||||
2 : Units
|
||||
3 : Sats
|
||||
*/
|
||||
var img = {
|
||||
width:buf.getWidth(),
|
||||
height:buf.getHeight(),
|
||||
bpp:2,
|
||||
buffer:buf.buffer,
|
||||
palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB])
|
||||
};
|
||||
|
||||
if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]);
|
||||
if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]);
|
||||
|
||||
var SCREENACCESS = {
|
||||
withApp:true,
|
||||
request:function(){this.withApp=false;stopDraw();},
|
||||
release:function(){this.withApp=true;startDraw();}
|
||||
};
|
||||
|
||||
var gpssetup;
|
||||
try {
|
||||
gpssetup = require("gpssetup");
|
||||
} catch(e) {
|
||||
gpssetup = false;
|
||||
}
|
||||
|
||||
// All set up. Lets go.
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
onGPS(lf);
|
||||
Bangle.setGPSPower(1);
|
||||
|
||||
if ( gpssetup ) {
|
||||
gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); });
|
||||
}
|
||||
else {
|
||||
Bangle.setGPSPower(1);
|
||||
}
|
||||
|
||||
Bangle.on('GPS', onGPS);
|
||||
|
||||
setButtons();
|
||||
setInterval(updateClock, 10000);
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,89 @@
|
|||
(function(back) {
|
||||
|
||||
let settings = require('Storage').readJSON('speedalt2.json',1)||{};
|
||||
//settings.buzz = settings.buzz||1;
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').write('speedalt2.json',settings);
|
||||
}
|
||||
|
||||
function setUnits(m,u) {
|
||||
settings.spd = m;
|
||||
settings.spd_unit = u;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function setUnitsAlt(m,u) {
|
||||
settings.alt = m;
|
||||
settings.alt_unit = u;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function setUnitsDist(d,u) {
|
||||
settings.dist = d;
|
||||
settings.dist_unit = u;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
function setColour(c) {
|
||||
settings.colour = c;
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
|
||||
const appMenu = {
|
||||
'': {'title': 'GPS Adv Sprt II'},
|
||||
'< Back': back,
|
||||
'< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');},
|
||||
'Units' : function() { E.showMenu(unitsMenu); },
|
||||
'Colours' : function() { E.showMenu(colMenu); },
|
||||
'Kalman Filter' : function() { E.showMenu(kalMenu); },
|
||||
'Touch' : {
|
||||
value : settings.touch,
|
||||
format : v => v?"On":"Off",
|
||||
onchange : () => { settings.touch = !settings.touch; writeSettings(); }
|
||||
}
|
||||
};
|
||||
|
||||
const unitsMenu = {
|
||||
'': {'title': 'Units'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
'default (spd)' : function() { setUnits(0,''); },
|
||||
'Kph (spd)' : function() { setUnits(1,'kph'); },
|
||||
'Knots (spd)' : function() { setUnits(1.852,'kts'); },
|
||||
'Mph (spd)' : function() { setUnits(1.60934,'mph'); },
|
||||
'm/s (spd)' : function() { setUnits(3.6,'m/s'); },
|
||||
'Km (dist)' : function() { setUnitsDist(1000,'km'); },
|
||||
'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); },
|
||||
'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); },
|
||||
'Meters (alt)' : function() { setUnitsAlt(1,'m'); },
|
||||
'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); }
|
||||
};
|
||||
|
||||
const colMenu = {
|
||||
'': {'title': 'Colours'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
'Default' : function() { setColour(0); },
|
||||
'Hi Contrast' : function() { setColour(1); },
|
||||
'Night' : function() { setColour(2); }
|
||||
};
|
||||
|
||||
const kalMenu = {
|
||||
'': {'title': 'Kalman Filter'},
|
||||
'< Back': function() { E.showMenu(appMenu); },
|
||||
'Speed' : {
|
||||
value : settings.spdFilt,
|
||||
format : v => v?"On":"Off",
|
||||
onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); }
|
||||
},
|
||||
'Altitude' : {
|
||||
value : settings.altFilt,
|
||||
format : v => v?"On":"Off",
|
||||
onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
E.showMenu(appMenu);
|
||||
|
||||
});
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="));
|
||||
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,17 @@
|
|||
// clock -> launcher
|
||||
(function() {
|
||||
var sui = Bangle.setUI;
|
||||
Bangle.setUI = function(mode, cb) {
|
||||
sui(mode,cb);
|
||||
if (!mode.startsWith("clock")) return;
|
||||
Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); };
|
||||
Bangle.on("swipe", Bangle.swipeHandler);
|
||||
};
|
||||
})();
|
||||
// launcher -> clock
|
||||
setTimeout(function() {
|
||||
if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") {
|
||||
Bangle.swipeHandler = dir => { if (dir>0) load(); };
|
||||
Bangle.on("swipe", Bangle.swipeHandler);
|
||||
}
|
||||
}, 10);
|
After Width: | Height: | Size: 889 B |
|
@ -0,0 +1,4 @@
|
|||
# T-Rex
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 668 B |
|
@ -0,0 +1,4 @@
|
|||
# Wave Clock
|
||||
|
||||

|
||||
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/node
|
||||
|
||||
var EMULATOR = "banglejs1";
|
||||
|
||||
var appId;
|
||||
|
||||
if (process.argv.length!=3) {
|
||||
console.log("USAGE:");
|
||||
console.log(" bin/thumbnailer.jd APP_ID");
|
||||
process.exit(1);
|
||||
}
|
||||
appId = process.argv[2];
|
||||
imageFn = "out.png";
|
||||
|
||||
if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) {
|
||||
console.log("You need to:");
|
||||
console.log(" git clone https://github.com/espruino/EspruinoWebIDE");
|
||||
console.log("At the same level as this project");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emulator_"+EMULATOR+".js").toString());
|
||||
eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emu_"+EMULATOR+".js").toString());
|
||||
eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/common.js").toString());
|
||||
|
||||
var SETTINGS = {
|
||||
pretokenise : true
|
||||
};
|
||||
var Const = {
|
||||
};
|
||||
module = undefined;
|
||||
eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString());
|
||||
eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString());
|
||||
eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString());
|
||||
var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json"));
|
||||
var app = apps.find(a=>a.id==appId);
|
||||
if (!app) ERROR(`App ${JSON.stringify(appId)} not found`);
|
||||
if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`);
|
||||
|
||||
|
||||
jsRXCallback = function() {};
|
||||
jsUpdateGfx = function() {};
|
||||
|
||||
// wait until loaded...
|
||||
setTimeout(function() {
|
||||
console.log("Loaded...");
|
||||
jsInit();
|
||||
jsIdle(true); // not automatic
|
||||
|
||||
AppInfo.getFiles(app, {
|
||||
fileGetter:function(url) {
|
||||
console.log(__dirname+"/"+url);
|
||||
return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary"));
|
||||
}, settings : SETTINGS}).then(files => {
|
||||
//console.log(files);
|
||||
var command = "Bangle.factoryReset()\n";
|
||||
command += files.map(f=>f.cmd).join("\n")+"\n";
|
||||
command += `load("${appId}.app.js")\n`;
|
||||
//console.log(command);
|
||||
console.log("Uploading...");
|
||||
jsTransmitString(command);
|
||||
console.log("Done.");
|
||||
jsIdle();
|
||||
jsIdle();
|
||||
jsIdle();
|
||||
jsStopIdle();
|
||||
|
||||
var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4);
|
||||
jsGetGfxContents(rgba);
|
||||
|
||||
var Jimp = require("jimp");
|
||||
let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) {
|
||||
if (err) throw err;
|
||||
let buffer = image.bitmap.data;
|
||||
buffer.set(rgba);
|
||||
image.write(imageFn, (err) => {
|
||||
if (err) throw err;
|
||||
console.log("Image written as "+imageFn);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a
|
||||
Subproject commit 8bbdf699210ab4d265a29a2bb0fd823cb5bca78a
|
|
@ -39,8 +39,8 @@
|
|||
.btn.btn-favourite:hover { color: red; }
|
||||
|
||||
.icon.icon-emulator { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-emulator::before {
|
||||
content: "\01F5B5";
|
||||
.icon.icon-emulator {
|
||||
content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E");
|
||||
}
|
||||
.icon.icon-favourite { text-indent: 0px; } /*override spectre*/
|
||||
.icon.icon-favourite::before {
|
||||
|
@ -53,3 +53,4 @@
|
|||
.icon.icon-interface::before {
|
||||
content: "\01F5AB";
|
||||
}
|
||||
|
||||
|
|