Merge branch 'master' of github.com:ff2005/BangleApps into feature/nifty-clock-b

pull/863/head
Filipe Fradique 2021-10-26 01:27:59 +01:00
commit 4edc952b77
54 changed files with 1768 additions and 163 deletions

View File

@ -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"}
]
}
]

4
apps/aclock/README.md Normal file
View File

@ -0,0 +1,4 @@
# Analogue Clock
![](screenshot_analog.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

4
apps/boldclk/README.md Normal file
View File

@ -0,0 +1,4 @@
# Bold Clock
![](screenshot_bold.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -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

View File

@ -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) {

4
apps/clock2x3/README.md Normal file
View File

@ -0,0 +1,4 @@
# 2x3 Pixel Clock
![](screenshot_pixel.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

4
apps/ffcniftya/README.md Normal file
View File

@ -0,0 +1,4 @@
# Nifty-A Clock
![](screenshot_nifty.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

5
apps/flappy/README.md Normal file
View File

@ -0,0 +1,5 @@
# Flappy Bird
![](screenshot1_flappy.png)
![](screenshot2_flappy.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

4
apps/floralclk/README.md Normal file
View File

@ -0,0 +1,4 @@
# Floral Clock
![](screenshot_floral.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

1
apps/gpstouch/Changelog Normal file
View File

@ -0,0 +1 @@
0.01: First version

16
apps/gpstouch/README.md Normal file
View File

@ -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
![](screenshot1.png)
![](screenshot2.png)
![](screenshot3.png)
![](screenshot4.png)

128
apps/gpstouch/geotools.js Normal file
View File

@ -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);
}

View File

@ -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();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA="))

BIN
apps/gpstouch/gpstouch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -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);
}
});
}

4
apps/s7clk/README.md Normal file
View File

@ -0,0 +1,4 @@
# Simple 7 Segment Clock
![](screenshot_s7segment.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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
![](screenshot_score.png)
## Bangle.js 1
| Keybinding | Description |
|---------------------|------------------------------|

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,2 @@
0.01: Created app
0.10: Different colour schemes selectable in SloMo Clock settings.

View File

@ -0,0 +1,6 @@
# SloMo Clock
Simple 24h clock with large digits.
![](Screenshot.JPG)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2"))

118
apps/slomoclock/app.js Normal file
View File

@ -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();

View File

@ -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);
});

BIN
apps/slomoclock/watch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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); },

2
apps/speedalt2/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Initial import.
0.07: Add swipe to change screens.

134
apps/speedalt2/README.md Normal file
View File

@ -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.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY"))

725
apps/speedalt2/app.js Normal file
View File

@ -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);

BIN
apps/speedalt2/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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);
});

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="));
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="))

View File

@ -0,0 +1 @@
0.01: New App!

View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

4
apps/trex/README.md Normal file
View File

@ -0,0 +1,4 @@
# T-Rex
![](screenshot_trex.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

4
apps/waveclk/README.md Normal file
View File

@ -0,0 +1,4 @@
# Wave Clock
![](screenshot.png)

84
bin/thumbnailer.js Executable file
View File

@ -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

View File

@ -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";
}