Merge branch 'espruino:master' into dev_0.5
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
## GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude...
|
||||||
|
|
||||||
|
...all taken from internal sources.
|
||||||
|
|
||||||
|
#### To speed-up GPS reception it is strongly recommended to upload AGPS data with ["Assisted GPS Update"](https://banglejs.com/apps/?id=assistedgps)
|
||||||
|
|
||||||
|
#### If "CALIB!" is shown on the display or the compass heading differs too much from GPS heading, compass calibration should be done with the ["Navigation Compass" App](https://banglejs.com/apps/?id=magnav)
|
||||||
|
|
||||||
|
**Credits:**<br>
|
||||||
|
Bike Speedometer App by <i>github.com/HilmarSt</i><br>
|
||||||
|
Big parts of the software are based on <i>github.com/espruino/BangleApps/tree/master/apps/speedalt</i><br>
|
||||||
|
Compass and Compass Calibration based on <i>github.com/espruino/BangleApps/tree/master/apps/magnav</i>
|
After Width: | Height: | Size: 4.1 KiB |
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+64A/AC+sF1uBgAwsq1W1krGEmswIFDlcAFoMrqyGjlcrGAQDB1guBBQJghKYZZCMYhqBlYugFAesgAuFYgQIHAE2sYMZDfwIABbgIuowMAqwABb4wAjFVQAEqyMrF4cAlYABqwypR4RgBwIyplYnF1hnBGIo8BAAQvhGIj6C1hpBgChBGCqGBqwdCRQQnCB4gJBGAgtWc4WBPoi9JH4ILBGYQATPoRHJRYoACwLFBLi4tGLIyLEA5QuPCoYpEMhBBBGDIuFgArIYQIUHA4b+GABLUBAwoQIXorDGI5RNGCB9WRQ0AJwwHGDxChOH4oDCRI4/GXpAaB1gyLEwlWKgTrBT46ALCogQKZoryFCwzgGBgz/NZpaQHHBCdEF5hKBBxWBUwoGBgEAEoIyHHYesBg7aBJQ7SBBAIvEIIJCBD4IFBgBIGEAcAUA8rGAIWHS4QvDCAJAHG4JfRCgKCFeAovCdRIiBDYq/NABi0Cfo5IEBgjUGACZ6BqwcGwLxBFYRsEHIKBIJwLkBNoIHDF468GYgIBBXY4EDE4IHDYwSwCN4IGBCIp5CJYtWgBZBHAgFEMoRjEE4QDCLYJUEUoaCBPYoQCgA4FGozxFLYwfEQgqrGexIYFBoxbDS4YHCIAYVEEAZcCYwwvGfoQHEcwQHHIg9WIAS9BIoYYESoowIABQuBUgg1DVwwACEpIwBChDLFDQ5JLlZnHJAajBQwgLEO4LDBHKAhBFxQxFCIIACAwadLHgJJBAAUrQJxYFAAbKPCwRGCCqAAm"))
|
|
@ -0,0 +1,546 @@
|
||||||
|
// Bike Speedometer by https://github.com/HilmarSt
|
||||||
|
// Big parts of this software are based on https://github.com/espruino/BangleApps/tree/master/apps/speedalt
|
||||||
|
// Compass and Compass Calibration based on https://github.com/espruino/BangleApps/tree/master/apps/magnav
|
||||||
|
|
||||||
|
const BANGLEJS2 = 1;
|
||||||
|
const screenH = g.getHeight();
|
||||||
|
const screenYstart = 24; // 0..23 for widgets
|
||||||
|
const screenY_Half = screenH / 2 + screenYstart;
|
||||||
|
const screenW = g.getWidth();
|
||||||
|
const screenW_Half = screenW / 2;
|
||||||
|
const fontFactorB2 = 2/3;
|
||||||
|
const colfg=g.theme.fg, colbg=g.theme.bg;
|
||||||
|
const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
|
||||||
|
|
||||||
|
var altiGPS=0, altiBaro=0;
|
||||||
|
var hdngGPS=0, hdngCompass=0, calibrateCompass=false;
|
||||||
|
|
||||||
|
/*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;
|
||||||
|
|
||||||
|
}());
|
||||||
|
|
||||||
|
|
||||||
|
//==================================== MAIN ====================================
|
||||||
|
|
||||||
|
var lf = {fix:0,satellites:0};
|
||||||
|
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
||||||
|
var canDraw = 1;
|
||||||
|
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
|
||||||
|
var sec; // actual seconds for testing purposes
|
||||||
|
|
||||||
|
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" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values;
|
||||||
|
|
||||||
|
var wp = {}; // Waypoint to use for distance from cur position.
|
||||||
|
var SATinView = 0;
|
||||||
|
|
||||||
|
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 drawFix(dat) {
|
||||||
|
|
||||||
|
if (!canDraw) return;
|
||||||
|
|
||||||
|
g.clearRect(0,screenYstart,screenW,screenH);
|
||||||
|
|
||||||
|
var v = '';
|
||||||
|
var u='';
|
||||||
|
|
||||||
|
// Primary Display
|
||||||
|
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
|
||||||
|
|
||||||
|
// Primary Units
|
||||||
|
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
|
||||||
|
|
||||||
|
drawPrimary(v,u);
|
||||||
|
|
||||||
|
// Secondary Display
|
||||||
|
v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString();
|
||||||
|
|
||||||
|
// Secondary Units
|
||||||
|
u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit;
|
||||||
|
|
||||||
|
drawSecondary(v,u);
|
||||||
|
|
||||||
|
// Time
|
||||||
|
drawTime();
|
||||||
|
|
||||||
|
//Sats
|
||||||
|
if ( dat.age > 10 ) {
|
||||||
|
if ( dat.age > 90 ) dat.age = '>90';
|
||||||
|
drawSats('Age:'+dat.age);
|
||||||
|
}
|
||||||
|
else if (!BANGLEJS2) {
|
||||||
|
drawSats('Sats:'+dat.sats);
|
||||||
|
} else {
|
||||||
|
if (lf.fix) {
|
||||||
|
drawSats('Sats:'+dat.sats);
|
||||||
|
} else {
|
||||||
|
drawSats('View:' + SATinView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawClock() {
|
||||||
|
if (!canDraw) return;
|
||||||
|
g.clearRect(0,screenYstart,screenW,screenH);
|
||||||
|
drawTime();
|
||||||
|
g.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawPrimary(n,u) {
|
||||||
|
//if(emulator)console.log("\n1: " + n +" "+ u);
|
||||||
|
var s=40; // Font size
|
||||||
|
var l=n.length;
|
||||||
|
|
||||||
|
if ( l <= 7 ) s=48;
|
||||||
|
if ( l <= 6 ) s=55;
|
||||||
|
if ( l <= 5 ) s=66;
|
||||||
|
if ( l <= 4 ) s=85;
|
||||||
|
if ( l <= 3 ) s=110;
|
||||||
|
|
||||||
|
// X -1=left (default), 0=center, 1=right
|
||||||
|
// Y -1=top (default), 0=center, 1=bottom
|
||||||
|
g.setFontAlign(0,-1); // center, top
|
||||||
|
if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
|
||||||
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
|
g.setFontVector(s);
|
||||||
|
g.drawString(n, screenW_Half - 10, screenYstart);
|
||||||
|
|
||||||
|
// Primary Units
|
||||||
|
s = 35; // Font size
|
||||||
|
g.setFontAlign(1,-1,3); // right, top, rotate
|
||||||
|
g.setColor(col1);
|
||||||
|
if (BANGLEJS2) s = 20;
|
||||||
|
g.setFontVector(s);
|
||||||
|
g.drawString(u, screenW - 20, screenYstart + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawSecondary(n,u) {
|
||||||
|
//if(emulator)console.log("2: " + n +" "+ u);
|
||||||
|
|
||||||
|
if (calibrateCompass) hdngCompass = "CALIB!";
|
||||||
|
else hdngCompass +="°";
|
||||||
|
|
||||||
|
g.setFontAlign(0,1);
|
||||||
|
g.setColor(col1);
|
||||||
|
|
||||||
|
g.setFontVector(12).drawString("Altitude GPS / Barometer", screenW_Half - 5, screenY_Half - 10);
|
||||||
|
g.setFontVector(20);
|
||||||
|
g.drawString(n+" "+u+" / "+altiBaro+" "+u, screenW_Half, screenY_Half + 11);
|
||||||
|
|
||||||
|
g.setFontVector(12).drawString("Heading GPS / Compass", screenW_Half - 10, screenY_Half + 26);
|
||||||
|
g.setFontVector(20);
|
||||||
|
g.drawString(hdngGPS+"° / "+hdngCompass, screenW_Half, screenY_Half + 47);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawTime() {
|
||||||
|
var x = 0, y = screenH;
|
||||||
|
g.setFontAlign(-1,1); // left, bottom
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
|
||||||
|
g.setColor(colbg);
|
||||||
|
g.drawString(time,x+1,y); // clear old time
|
||||||
|
|
||||||
|
time = require("locale").time(new Date(),1);
|
||||||
|
|
||||||
|
g.setColor(colfg); // draw new time
|
||||||
|
g.drawString(time,x+2,y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function drawSats(sats) {
|
||||||
|
|
||||||
|
g.setColor(col1);
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.setFontAlign(1,1); //right, bottom
|
||||||
|
g.drawString(sats,screenW,screenH);
|
||||||
|
|
||||||
|
g.setFontVector(18);
|
||||||
|
g.setColor(col1);
|
||||||
|
|
||||||
|
if ( cfg.modeA == 1 ) {
|
||||||
|
if ( showMax ) {
|
||||||
|
g.setFontAlign(0,1); //centre, bottom
|
||||||
|
g.drawString('MAX',120,164);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGPS(fix) {
|
||||||
|
|
||||||
|
if ( emulator ) {
|
||||||
|
fix.fix = 1;
|
||||||
|
fix.speed = Math.random()*30; // calmed by Kalman filter if cfg.spdFilt
|
||||||
|
fix.alt = Math.random()*200 -20; // calmed by Kalman filter if cfg.altFilt
|
||||||
|
fix.lat = 50.59; // google.de/maps/@50.59,8.53,17z
|
||||||
|
fix.lon = 8.53;
|
||||||
|
fix.course = 365;
|
||||||
|
fix.satellites = sec;
|
||||||
|
fix.time = new Date();
|
||||||
|
fix.smoothed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var m;
|
||||||
|
|
||||||
|
var sp = '---';
|
||||||
|
var al = '---';
|
||||||
|
var di = '---';
|
||||||
|
var age = '---';
|
||||||
|
|
||||||
|
if (fix.fix) lf = fix;
|
||||||
|
|
||||||
|
hdngGPS = lf.course;
|
||||||
|
if (isNaN(hdngGPS)) hdngGPS = "---";
|
||||||
|
else if (0 == hdngGPS) hdngGPS = "0?";
|
||||||
|
else hdngGPS = hdngGPS.toFixed(0);
|
||||||
|
|
||||||
|
if (emulator) hdngCompass = hdngGPS;
|
||||||
|
if (emulator) altiBaro = lf.alt.toFixed(0);
|
||||||
|
|
||||||
|
if (lf.fix) {
|
||||||
|
|
||||||
|
if (BANGLEJS2 && !emulator) Bangle.removeListener('GPS-raw', onGPSraw);
|
||||||
|
|
||||||
|
// 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 = parseFloat(sp);
|
||||||
|
|
||||||
|
// Altitude
|
||||||
|
al = lf.alt;
|
||||||
|
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
|
||||||
|
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( cfg.modeA == 1 ) {
|
||||||
|
if ( showMax )
|
||||||
|
drawFix({
|
||||||
|
speed:max.spd,
|
||||||
|
sats:lf.satellites,
|
||||||
|
alt:max.alt,
|
||||||
|
alt_units:cfg.alt_unit,
|
||||||
|
age:age,
|
||||||
|
fix:lf.fix
|
||||||
|
}); // Speed and alt maximums
|
||||||
|
else
|
||||||
|
drawFix({
|
||||||
|
speed:sp,
|
||||||
|
sats:lf.satellites,
|
||||||
|
alt:al,
|
||||||
|
alt_units:cfg.alt_unit,
|
||||||
|
age:age,
|
||||||
|
fix:lf.fix
|
||||||
|
}); // Show speed/altitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtons(){
|
||||||
|
setWatch(_=>load(), BTN1);
|
||||||
|
|
||||||
|
onGPS(lf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateClock() {
|
||||||
|
if (!canDraw) return;
|
||||||
|
drawTime();
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
if ( emulator ) {
|
||||||
|
max.spd++; max.alt++;
|
||||||
|
d=new Date(); sec=d.getSeconds();
|
||||||
|
onGPS(lf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//###
|
||||||
|
let cfg = {};
|
||||||
|
cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
||||||
|
cfg.spd_unit = 'km/h'; // Displayed speed unit
|
||||||
|
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
|
||||||
|
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
|
||||||
|
cfg.dist = 1000; // Multiplier for distnce unit conversions.
|
||||||
|
cfg.dist_unit = 'km'; // Displayed distnce units
|
||||||
|
cfg.modeA = 1;
|
||||||
|
cfg.primSpd = 1; // 1 = Spd in primary, 0 = Spd in secondary
|
||||||
|
|
||||||
|
cfg.spdFilt = false;
|
||||||
|
cfg.altFilt = false;
|
||||||
|
|
||||||
|
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
|
||||||
|
if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
|
||||||
|
|
||||||
|
function onGPSraw(nmea) {
|
||||||
|
var nofGP = 0, nofBD = 0, nofGL = 0;
|
||||||
|
if (nmea.slice(3,6) == "GSV") {
|
||||||
|
// console.log(nmea.slice(1,3) + " " + nmea.slice(11,13));
|
||||||
|
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
|
||||||
|
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
|
||||||
|
if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
|
||||||
|
SATinView = nofGP + nofBD + nofGL;
|
||||||
|
} }
|
||||||
|
if(BANGLEJS2) Bangle.on('GPS-raw', onGPSraw);
|
||||||
|
|
||||||
|
function onPressure(dat) { altiBaro = dat.altitude.toFixed(0); }
|
||||||
|
|
||||||
|
Bangle.setBarometerPower(1); // needs some time...
|
||||||
|
g.clearRect(0,screenYstart,screenW,screenH);
|
||||||
|
onGPS(lf);
|
||||||
|
Bangle.setGPSPower(1);
|
||||||
|
Bangle.on('GPS', onGPS);
|
||||||
|
Bangle.on('pressure', onPressure);
|
||||||
|
|
||||||
|
Bangle.setCompassPower(1);
|
||||||
|
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||||
|
if (!CALIBDATA) calibrateCompass = true;
|
||||||
|
function Compass_tiltfixread(O,S){
|
||||||
|
"ram";
|
||||||
|
//console.log(O.x+" "+O.y+" "+O.z);
|
||||||
|
var m = Bangle.getCompass();
|
||||||
|
var g = Bangle.getAccel();
|
||||||
|
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
|
||||||
|
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
|
||||||
|
if (d<0) d+=360;
|
||||||
|
var phi = Math.atan(-g.x/-g.z);
|
||||||
|
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
|
||||||
|
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
|
||||||
|
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
|
||||||
|
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
|
||||||
|
var yh = m.dz*sinphi - m.dx*cosphi;
|
||||||
|
var psi = Math.atan2(yh,xh)*180/Math.PI;
|
||||||
|
if (psi<0) psi+=360;
|
||||||
|
return psi;
|
||||||
|
}
|
||||||
|
var Compass_heading = 0;
|
||||||
|
function Compass_newHeading(m,h){
|
||||||
|
var s = Math.abs(m - h);
|
||||||
|
var delta = (m>h)?1:-1;
|
||||||
|
if (s>=180){s=360-s; delta = -delta;}
|
||||||
|
if (s<2) return h;
|
||||||
|
var hd = h + delta*(1 + Math.round(s/5));
|
||||||
|
if (hd<0) hd+=360;
|
||||||
|
if (hd>360)hd-= 360;
|
||||||
|
return hd;
|
||||||
|
}
|
||||||
|
function Compass_reading() {
|
||||||
|
"ram";
|
||||||
|
var d = Compass_tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||||
|
Compass_heading = Compass_newHeading(d,Compass_heading);
|
||||||
|
hdngCompass = Compass_heading.toFixed(0);
|
||||||
|
}
|
||||||
|
if (!calibrateCompass) setInterval(Compass_reading,200);
|
||||||
|
|
||||||
|
setButtons();
|
||||||
|
if (emulator) setInterval(updateClock, 2000);
|
||||||
|
else setInterval(updateClock, 10000);
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
After Width: | Height: | Size: 751 B |
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"id": "bikespeedo",
|
||||||
|
"name": "Bike Speedometer (beta)",
|
||||||
|
"shortName": "Bike Speedomet.",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"Screenshot.png"}],
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tool,cycling,bicycle,outdoors,sport",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"bikespeedo.app.js","url":"app.js"},
|
||||||
|
{"name":"bikespeedo.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
|
@ -47,3 +47,4 @@
|
||||||
0.41: Add Keyboard and Mouse Bluetooth HID option
|
0.41: Add Keyboard and Mouse Bluetooth HID option
|
||||||
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
|
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
|
||||||
0.43: Fix Gadgetbridge handling with Programmable:off
|
0.43: Fix Gadgetbridge handling with Programmable:off
|
||||||
|
0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM)
|
||||||
|
|
|
@ -4,7 +4,7 @@ of the time. */
|
||||||
E.showMessage("Updating boot0...");
|
E.showMessage("Updating boot0...");
|
||||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||||
var boot = "";
|
var boot = "", bootPost = "";
|
||||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
|
@ -15,6 +15,7 @@ if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't chang
|
||||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||||
boot += `E.setFlags({pretokenise:1});\n`;
|
boot += `E.setFlags({pretokenise:1});\n`;
|
||||||
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
|
||||||
|
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
|
||||||
if (s.ble!==false) {
|
if (s.ble!==false) {
|
||||||
if (s.HID) { // Human interface device
|
if (s.HID) { // Human interface device
|
||||||
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
|
||||||
|
@ -195,8 +196,8 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
|
||||||
|
|
||||||
// Append *.boot.js files
|
// Append *.boot.js files
|
||||||
// These could change bleServices/bleServiceOptions if needed
|
// These could change bleServices/bleServiceOptions if needed
|
||||||
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||||
var aPriority = a.match(getPriority);
|
var aPriority = a.match(getPriority);
|
||||||
var bPriority = b.match(getPriority);
|
var bPriority = b.match(getPriority);
|
||||||
if (aPriority && bPriority){
|
if (aPriority && bPriority){
|
||||||
|
@ -207,17 +208,39 @@ require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a==b ? 0 : (a>b ? 1 : -1);
|
return a==b ? 0 : (a>b ? 1 : -1);
|
||||||
}).forEach(bootFile=>{
|
});
|
||||||
|
// precalculate file size
|
||||||
|
var fileSize = boot.length + bootPost.length;
|
||||||
|
bootFiles.forEach(bootFile=>{
|
||||||
|
// match the size of data we're adding below in bootFiles.forEach
|
||||||
|
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+1;
|
||||||
|
});
|
||||||
|
// write file in chunks (so as not to use up all RAM)
|
||||||
|
require('Storage').write('.boot0',boot,0,fileSize);
|
||||||
|
var fileOffset = boot.length;
|
||||||
|
bootFiles.forEach(bootFile=>{
|
||||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||||
// which would cause an error!
|
// which would cause an error!
|
||||||
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
// we write:
|
||||||
|
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
||||||
|
// but we need to do this without ever loading everything into RAM as some
|
||||||
|
// boot files seem to be getting pretty big now.
|
||||||
|
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
|
||||||
|
fileOffset+=2+bootFile.length+1;
|
||||||
|
var bf = require('Storage').read(bootFile);
|
||||||
|
require('Storage').write('.boot0',bf,fileOffset);
|
||||||
|
fileOffset+=bf.length;
|
||||||
|
require('Storage').write('.boot0',"\n",fileOffset);
|
||||||
|
fileOffset+=1;
|
||||||
});
|
});
|
||||||
// update ble
|
require('Storage').write('.boot0',bootPost,fileOffset);
|
||||||
boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`;
|
|
||||||
// write file
|
|
||||||
require('Storage').write('.boot0',boot);
|
|
||||||
delete boot;
|
delete boot;
|
||||||
|
delete bootPost;
|
||||||
|
delete bootFiles;
|
||||||
|
delete fileSize;
|
||||||
|
delete fileOffset;
|
||||||
E.showMessage("Reloading...");
|
E.showMessage("Reloading...");
|
||||||
eval(require('Storage').read('.boot0'));
|
eval(require('Storage').read('.boot0'));
|
||||||
// .bootcde should be run automatically after if required, since
|
// .bootcde should be run automatically after if required, since
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.43",
|
"version": "0.44",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Bordle
|
||||||
|
|
||||||
|
The Bangle version of a popular word guessing game. The goal is to guess a 5 letter word in 6 tries or less. After each guess, the letters in the guess are
|
||||||
|
marked in colors: yellow for a letter that appears in the to-be-guessed word, but in a different location and green for a letter in the correct position.
|
||||||
|
|
||||||
|
Only words contained in the internal dictionary are allowed as valid guesses. At app launch, a target word is picked from the dictionary at random.
|
||||||
|
|
||||||
|
On startup, a grid of 6 lines with 5 (empty) letter boxes is displayed. Swiping left or right at any time switches between grid view and keyboard view.
|
||||||
|
The keyboad was inspired by the 'Scribble' app (it is a simplified version using the layout library). The letter group "Z ..." contains the delete key and
|
||||||
|
the enter key. Hitting enter after the 5th letter will add the guess to the grid view and color mark it.
|
||||||
|
|
||||||
|
The (English language) dictionary was derived from the the Unix ispell word list by filtering out plurals and past particples (and some hand editing) from all 5 letter words.
|
||||||
|
It is contained in the file 'wordlencr.txt' which contains one long string (no newline characters) of all the words concatenated. It would not be too difficult to swap it
|
||||||
|
out for a different language version. The keyboard currently only supports the 26 characters of the latin alphabet (no accents or umlauts).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AA/TADwoIFkYyOF0owIF04wGUSqvVBZQtZGJYJIFzomKF0onIF07EKF0owLF9wNEnwACE6oZILxovbMBov/F/4v/C54uWF/4vKBQQLLF/4YPFwYMLF7AZGF5Y5KF5xJIFwoMJD44vaBhwvcLQpgHF8gGRF6xYNBpQvTXBoNOF65QJBIgvjBywvUV5YOOF64OIB54v/cQwAKB5ov/F84wKADYuIF+AwkFIwwnE45hmExCSlEpTEiERr3KADw+PF0ownUSoseA=="))
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,159 @@
|
||||||
|
var Layout = require("Layout");
|
||||||
|
|
||||||
|
var gameState = 0;
|
||||||
|
var keyState = 0;
|
||||||
|
var keyStateIdx = 0;
|
||||||
|
|
||||||
|
function buttonPushed(b) {
|
||||||
|
if (keyState==0) {
|
||||||
|
keyState++;
|
||||||
|
keyStateIdx = b;
|
||||||
|
if (b<6) {
|
||||||
|
for (i=1; i<=5; ++i) {
|
||||||
|
var c = String.fromCharCode(i+64+(b-1)*5);
|
||||||
|
layout["bt"+i.toString()].label = c;
|
||||||
|
layout["bt"+i.toString()].bgCol = wordle.keyColors[c]||g.theme.bg;
|
||||||
|
}
|
||||||
|
layout.bt6.label = "<";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
layout.bt1.label = "Z";
|
||||||
|
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
|
||||||
|
layout.bt2.label = "<del>";
|
||||||
|
layout.bt4.label = "<ent>";
|
||||||
|
layout.bt3.label = layout.bt5.label = " ";
|
||||||
|
layout.bt6.label = "<";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // actual button pushed
|
||||||
|
inp = layout.input.label;
|
||||||
|
if (b!=6) {
|
||||||
|
if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64);
|
||||||
|
else if (layout.input.label.length>0 && b==2) inp = inp.slice(0,-1);
|
||||||
|
layout.input.label = inp;
|
||||||
|
}
|
||||||
|
layout = getKeyLayout(inp);
|
||||||
|
keyState = 0;
|
||||||
|
if (inp.length==5 && keyStateIdx==6 && b==4) {
|
||||||
|
rc = wordle.addGuess(inp);
|
||||||
|
layout.input.label = "";
|
||||||
|
layout.update();
|
||||||
|
gameState = 0;
|
||||||
|
if (rc>0) return;
|
||||||
|
g.clear();
|
||||||
|
wordle.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layout.update();
|
||||||
|
g.clear();
|
||||||
|
layout.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKeyLayout(text) {
|
||||||
|
return new Layout( {
|
||||||
|
type: "v", c: [
|
||||||
|
{type:"txt", font:"6x8:2", id:"input", label:text, pad: 3},
|
||||||
|
{type: "h", c: [
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt1", label:"ABCDE", cb: l=>buttonPushed(1), pad:4, filly:1, fillx:1 },
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt2", label:"FGHIJ", cb: l=>buttonPushed(2), pad:4, filly:1, fillx:1 },
|
||||||
|
]},
|
||||||
|
{type: "h", c: [
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt3", label:"KLMNO", cb: l=>buttonPushed(3), pad:4, filly:1, fillx:1 },
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt4", label:"PQRST", cb: l=>buttonPushed(4), pad:4, filly:1, fillx:1 },
|
||||||
|
]},
|
||||||
|
{type: "h", c: [
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt5", label:"UVWXY", cb: l=>buttonPushed(5), pad:4, filly:1, fillx:1 },
|
||||||
|
{type:"btn", font:"6x8:2", id:"bt6", label:"Z ...", cb: l=>buttonPushed(6), pad:4, filly:1, fillx:1 },
|
||||||
|
]}
|
||||||
|
]});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wordle {
|
||||||
|
constructor(word) {
|
||||||
|
this.word = word;
|
||||||
|
this.guesses = [];
|
||||||
|
this.guessColors = [];
|
||||||
|
this.keyColors = [];
|
||||||
|
this.nGuesses = -1;
|
||||||
|
if (word == "rnd") {
|
||||||
|
this.words = require("Storage").read("wordlencr.txt");
|
||||||
|
i = Math.floor(Math.floor(this.words.length/5)*Math.random())*5;
|
||||||
|
this.word = this.words.slice(i, i+5).toUpperCase();
|
||||||
|
}
|
||||||
|
console.log(this.word);
|
||||||
|
}
|
||||||
|
render(clear) {
|
||||||
|
h = g.getHeight();
|
||||||
|
bh = Math.floor(h/6);
|
||||||
|
bbh = Math.floor(0.85*bh);
|
||||||
|
w = g.getWidth();
|
||||||
|
bw = Math.floor(w/5);
|
||||||
|
bbw = Math.floor(0.85*bw);
|
||||||
|
if (clear) g.clear();
|
||||||
|
g.setFont("Vector", Math.floor(bbh*0.95)).setFontAlign(0,0);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
for (i=0; i<6; ++i) {
|
||||||
|
for (j=0; j<5; ++j) {
|
||||||
|
if (i<=this.nGuesses) {
|
||||||
|
g.setColor(this.guessColors[i][j]).fillRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
|
||||||
|
g.setColor(g.theme.fg).drawString(this.guesses[i][j], 2+j*bw+bw/2, 2+i*bh+bh/2);
|
||||||
|
}
|
||||||
|
g.setColor(g.theme.fg).drawRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addGuess(w) {
|
||||||
|
if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
|
||||||
|
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
|
||||||
|
layout = getKeyLayout("");
|
||||||
|
wordle.render(true);
|
||||||
|
});
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
this.guesses.push(w);
|
||||||
|
this.nGuesses++;
|
||||||
|
this.guessColors.push([]);
|
||||||
|
correct = 0;
|
||||||
|
var sol = this.word;
|
||||||
|
for (i=0; i<w.length; ++i) {
|
||||||
|
c = w[i];
|
||||||
|
col = g.theme.bg;
|
||||||
|
if (sol[i]==c) {
|
||||||
|
sol = sol.substr(0,i) + '?' + sol.substr(i+1);
|
||||||
|
col = "#0f0";
|
||||||
|
++correct;
|
||||||
|
}
|
||||||
|
else if (sol.includes(c)) col = "#ff0";
|
||||||
|
if (col!=g.theme.bg) this.keyColors[c] = this.keyColors[c] || col;
|
||||||
|
else this.keyColors[c] = "#00f";
|
||||||
|
this.guessColors[this.nGuesses].push(col);
|
||||||
|
}
|
||||||
|
if (correct==5) {
|
||||||
|
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){load();});
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (this.nGuesses==5) {
|
||||||
|
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){load();});
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wordle = new Wordle("rnd");
|
||||||
|
layout = getKeyLayout("");
|
||||||
|
wordle.render(true);
|
||||||
|
|
||||||
|
Bangle.on('swipe', function (dir) {
|
||||||
|
if (dir==1 || dir==-1) {
|
||||||
|
g.clear();
|
||||||
|
if (gameState==0) {
|
||||||
|
layout.render();
|
||||||
|
gameState = 1;
|
||||||
|
}
|
||||||
|
else if (gameState==1) {
|
||||||
|
wordle.render();
|
||||||
|
gameState = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ "id": "bordle",
|
||||||
|
"name": "Bordle",
|
||||||
|
"shortName":"Bordle",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Bangle version of a popular word search game",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"tags": "game, text",
|
||||||
|
"storage": [
|
||||||
|
{"name":"bordle.app.js","url":"bordle.app.js"},
|
||||||
|
{"name":"wordlencr.txt","url":"wordlencr.txt"},
|
||||||
|
{"name":"bordle.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
|
@ -2,3 +2,6 @@
|
||||||
0.02: added emulator capability and display of widgets
|
0.02: added emulator capability and display of widgets
|
||||||
0.03: bug of advancing time fixed; doztime now correct within ca. 1 second
|
0.03: bug of advancing time fixed; doztime now correct within ca. 1 second
|
||||||
0.04: changed time colour from slightly off white to pure white
|
0.04: changed time colour from slightly off white to pure white
|
||||||
|
0.05: extraneous comments and code removed
|
||||||
|
display improved
|
||||||
|
now supports Adjust Clock widget, if installed
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
// Positioning values for graphics buffers
|
// Positioning values for graphics buffers
|
||||||
const g_height = 80; // total graphics height
|
const g_height = 80; // total graphics height
|
||||||
const g_x_off = 0; // position from left was 16, then 8 here
|
const g_x_off = 0; // position from left
|
||||||
const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240
|
const g_y_off = (180 - g_height)/2; // vertical center for graphics region
|
||||||
const g_width = 240 - 2 * g_x_off; // total graphics width
|
const g_width = 240 - 2 * g_x_off; // total graphics width
|
||||||
const g_height_d = 28; // height of date region was 32
|
const g_height_d = 28; // height of date region
|
||||||
const g_y_off_d = 0; // y position of date region within graphics region
|
const g_y_off_d = 0; // y position of date region within graphics region
|
||||||
const spacing = 0; // space between date and time in graphics region
|
const spacing = 6; // space between date and time in graphics region
|
||||||
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
|
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
|
||||||
const g_height_t = 44; // height of time region was 48
|
const g_height_t = 44; // height of time region
|
||||||
|
|
||||||
// Other vars
|
// Other vars
|
||||||
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
|
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
|
||||||
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
|
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
|
||||||
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
|
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
|
||||||
const timeColour = "#ffffff";
|
const timeColour = "#ffffff";
|
||||||
const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#8383ff","#ff00ff","#ff0080"]; //blue was 0000ff
|
const dateColours = ["#ff0000","#ff8000","#ffff00","#00ff00","#0080ff","#ff00ff","#ffffff"];
|
||||||
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20
|
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line
|
||||||
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20
|
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line
|
||||||
const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30
|
const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line
|
||||||
const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
|
const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
|
||||||
const baseYear = 11584;
|
const baseYear = 11584;
|
||||||
const baseDate = Date(2020,11,21); // month values run from 0 to 11
|
const baseDate = Date(2020,11,21); // month values run from 0 to 11
|
||||||
|
@ -42,28 +42,25 @@ var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
|
||||||
g.clear(); // start with blank screen
|
g.clear(); // start with blank screen
|
||||||
g.flip = function()
|
g.flip = function()
|
||||||
{
|
{
|
||||||
g.setBgColor(0,0,0);
|
g.setBgColor(0,0,0);
|
||||||
g.setColor(dateColour);
|
g.setColor(dateColour);
|
||||||
g.drawImage(
|
g.drawImage(
|
||||||
{
|
{
|
||||||
width:g_width,
|
width:g_width,
|
||||||
height:g_height_d,
|
height:g_height_d,
|
||||||
buffer:g_d.buffer
|
buffer:g_d.buffer
|
||||||
}, g_x_off, g_y_off + g_y_off_d);
|
}, g_x_off, g_y_off + g_y_off_d);
|
||||||
g.setColor(timeColour2);
|
g.setColor(timeColour2);
|
||||||
g.drawImage(
|
g.drawImage(
|
||||||
{
|
{
|
||||||
width:g_width,
|
width:g_width,
|
||||||
height:g_height_t,
|
height:g_height_t,
|
||||||
buffer:g_t.buffer
|
buffer:g_t.buffer
|
||||||
}, g_x_off, g_y_off + g_y_off_t);
|
}, g_x_off, g_y_off + g_y_off_t);
|
||||||
};
|
};
|
||||||
|
|
||||||
setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1
|
setWatch(function(){ modeTime(); }, BTN, {repeat:true} );
|
||||||
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2
|
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" });
|
||||||
//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
|
|
||||||
//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
|
|
||||||
//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
|
|
||||||
|
|
||||||
Bangle.on('touch', function(button, xy) { //from Gordon Williams
|
Bangle.on('touch', function(button, xy) { //from Gordon Williams
|
||||||
if (button==1) toggleTimeDigits();
|
if (button==1) toggleTimeDigits();
|
||||||
|
@ -71,10 +68,10 @@ Bangle.on('touch', function(button, xy) { //from Gordon Williams
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildSequence(targ){
|
function buildSequence(targ){
|
||||||
for(let i=0;i<targ.length;++i){
|
for(let i=0;i<targ.length;++i){
|
||||||
sequence.push(new Date(accum.getTime()));
|
sequence.push(new Date(accum.getTime()));
|
||||||
accum.setDate(accum.getDate()+targ[i]);
|
accum.setDate(accum.getDate()+targ[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buildSequence(B2);
|
buildSequence(B2);
|
||||||
buildSequence(B2);
|
buildSequence(B2);
|
||||||
|
@ -91,154 +88,137 @@ buildSequence(B1);
|
||||||
buildSequence(B2);
|
buildSequence(B2);
|
||||||
|
|
||||||
function getDate(dt){
|
function getDate(dt){
|
||||||
let index = sequence.findIndex(n => n > dt)-1;
|
let index = sequence.findIndex(n => n > dt)-1;
|
||||||
let year = baseYear+parseInt(index/12);
|
let year = baseYear+parseInt(index/12);
|
||||||
let month = index % 12;
|
let month = index % 12;
|
||||||
let day = parseInt((dt-sequence[index])/86400000);
|
let day = parseInt((dt-sequence[index])/86400000);
|
||||||
let colour = dateColours[day % 6];
|
let colour = dateColours[day % 6];
|
||||||
if(day==30){ colour=dateColours[6]; }
|
if(day==30){ colour=dateColours[6]; }
|
||||||
return({"year":year,"month":month,"day":day,"colour":colour});
|
return({"year":year,"month":month,"day":day,"colour":colour});
|
||||||
}
|
}
|
||||||
function toggleTimeDigits(){
|
function toggleTimeDigits(){
|
||||||
addTimeDigit = !addTimeDigit;
|
addTimeDigit = !addTimeDigit;
|
||||||
modeTime();
|
modeTime();
|
||||||
}
|
}
|
||||||
function toggleDateFormat(){
|
function toggleDateFormat(){
|
||||||
dateFormat = !dateFormat;
|
dateFormat = !dateFormat;
|
||||||
modeTime();
|
modeTime();
|
||||||
}
|
}
|
||||||
function formatDate(res,dateFormat){
|
function formatDate(res,dateFormat){
|
||||||
let yyyy = res.year.toString(12);
|
let yyyy = res.year.toString(12);
|
||||||
calenDef = calen10;
|
calenDef = calen10;
|
||||||
if(!dateFormat){ //ordinal format
|
if(!dateFormat){ //ordinal format
|
||||||
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
|
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
|
||||||
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
|
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
|
||||||
if(res.day==30){
|
if(res.day==30){
|
||||||
calenDef = calen7;
|
calenDef = calen7;
|
||||||
let m = ((res.month+1).toString(12)).substr(-2);
|
let m = ((res.month+1).toString(12)).substr(-2);
|
||||||
return(yyyy+"-"+"S"+m); // ordinal format
|
return(yyyy+"-"+"S"+m); // ordinal format
|
||||||
}
|
}
|
||||||
return(yyyy+"-"+mm+"-"+dd);
|
return(yyyy+"-"+mm+"-"+dd);
|
||||||
}
|
}
|
||||||
let m = res.month.toString(12); // cardinal format
|
let m = res.month.toString(12); // cardinal format
|
||||||
let w = parseInt(res.day/6);
|
let w = parseInt(res.day/6);
|
||||||
let d = res.day%6;
|
let d = res.day%6;
|
||||||
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
|
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
|
||||||
return(yyyy+"-"+m+"-"+w+"-"+d);
|
return(yyyy+"-"+m+"-"+w+"-"+d);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeDozTime(text,def){
|
function writeDozTime(text,def){
|
||||||
let pts = def.pts;
|
let pts = def.pts;
|
||||||
let x=def.pt0[0];
|
let x=def.pt0[0];
|
||||||
let y=def.pt0[1];
|
let y=def.pt0[1];
|
||||||
g_t.clear();
|
g_t.clear();
|
||||||
g_t.setFont("Vector",def.size);
|
g_t.setFont("Vector",def.size);
|
||||||
for(let i in text){
|
for(let i in text){
|
||||||
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new
|
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); }
|
||||||
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new
|
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); }
|
||||||
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
|
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
|
||||||
x = x+def.step[0];
|
x = x+def.step[0];
|
||||||
y = y+def.step[1];
|
y = y+def.step[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function writeDozDate(text,def,colour){
|
function writeDozDate(text,def,colour){
|
||||||
|
|
||||||
dateColour = colour;
|
dateColour = colour;
|
||||||
let pts = def.pts;
|
let pts = def.pts;
|
||||||
let x=def.pt0[0];
|
let x=def.pt0[0];
|
||||||
let y=def.pt0[1];
|
let y=def.pt0[1];
|
||||||
g_d.clear();
|
g_d.clear();
|
||||||
g_d.setFont("Vector",def.size);
|
g_d.setFont("Vector",def.size);
|
||||||
for(let i in text){
|
for(let i in text){
|
||||||
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new
|
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); }
|
||||||
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new
|
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); }
|
||||||
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
|
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
|
||||||
x = x+def.step[0];
|
x = x+def.step[0];
|
||||||
y = y+def.step[1];
|
y = y+def.step[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
//for malaire's Adjust Clock widget, if used
|
||||||
|
function adjustedNow() {
|
||||||
|
return WIDGETS.adjust ? new Date(WIDGETS.adjust.now()) : new Date();
|
||||||
|
}
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
// Functions for time mode
|
// Functions for time mode
|
||||||
function drawTime()
|
function drawTime()
|
||||||
{
|
{
|
||||||
let dt = new Date();
|
let dt = adjustedNow();
|
||||||
let date = "";
|
let date = "";
|
||||||
let timeDef;
|
let timeDef;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
dt.setDate(dt.getDate());
|
dt.setDate(dt.getDate());
|
||||||
if(addTimeDigit){
|
if(addTimeDigit){
|
||||||
x =
|
x =
|
||||||
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
|
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
|
||||||
let msg = "00000"+Math.floor(x).toString(12);
|
let msg = "00000"+Math.floor(x).toString(12);
|
||||||
let time = msg.substr(-5,3)+"."+msg.substr(-2);
|
let time = msg.substr(-5,3)+"."+msg.substr(-2);
|
||||||
let wait = 347*(1-(x%1));
|
let wait = 347*(1-(x%1));
|
||||||
timeDef = time6;
|
timeDef = time6;
|
||||||
} else {
|
} else {
|
||||||
x =
|
x =
|
||||||
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
|
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
|
||||||
let msg = "0000"+Math.floor(x).toString(12);
|
let msg = "0000"+Math.floor(x).toString(12);
|
||||||
let time = msg.substr(-4,3)+"."+msg.substr(-1);
|
let time = msg.substr(-4,3)+"."+msg.substr(-1);
|
||||||
let wait = 4167*(1-(x%1));
|
let wait = 4167*(1-(x%1));
|
||||||
timeDef = time5;
|
timeDef = time5;
|
||||||
}
|
}
|
||||||
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
|
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
|
||||||
date = formatDate(res,dateFormat);
|
date = formatDate(res,dateFormat);
|
||||||
if(dt<timeActiveUntil)
|
if(dt<timeActiveUntil)
|
||||||
{
|
{
|
||||||
// Write to background buffers, then display on screen
|
// Write to background buffers, then display on screen
|
||||||
writeDozDate(date,calenDef,res.colour);
|
writeDozDate(date,calenDef,res.colour);
|
||||||
writeDozTime(time,timeDef);
|
writeDozTime(time,timeDef);
|
||||||
g.flip();
|
g.flip();
|
||||||
// Ready next interval
|
// Ready next interval
|
||||||
drawtime_timeout = setTimeout(drawTime,wait);
|
drawtime_timeout = setTimeout(drawTime,wait);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Clear screen
|
// Clear screen
|
||||||
g_d.clear();
|
g_d.clear();
|
||||||
g_t.clear();
|
g_t.clear();
|
||||||
g.flip();
|
g.flip();
|
||||||
|
|
||||||
}
|
}
|
||||||
lastX = x;
|
lastX = x;
|
||||||
}
|
}
|
||||||
function modeTime()
|
function modeTime()
|
||||||
{
|
{
|
||||||
timeActiveUntil = new Date();
|
timeActiveUntil = adjustedNow();
|
||||||
timeActiveUntil.setDate(timeActiveUntil.getDate());
|
timeActiveUntil.setDate(timeActiveUntil.getDate());
|
||||||
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+86400);
|
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+604800);
|
||||||
if (typeof drawtime_timeout !== 'undefined')
|
if (typeof drawtime_timeout !== 'undefined')
|
||||||
{
|
{
|
||||||
clearTimeout(drawtime_timeout);
|
clearTimeout(drawtime_timeout);
|
||||||
}
|
}
|
||||||
drawTime();
|
drawTime();
|
||||||
}
|
}
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
|
|
||||||
// Functions for weather mode - TODO
|
|
||||||
// function drawWeather() {}
|
|
||||||
// function modeWeather() {}
|
|
||||||
|
|
||||||
// Start time on twist
|
// Start time on twist
|
||||||
Bangle.on('twist',function() {
|
Bangle.on('twist',function() {
|
||||||
modeTime();
|
modeTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Time fix with GPS
|
|
||||||
function fixTime() {
|
|
||||||
Bangle.on("GPS",function cb(g) {
|
|
||||||
Bangle.setGPSPower(0,"time");
|
|
||||||
Bangle.removeListener("GPS",cb);
|
|
||||||
if (!g.time || (g.time.getFullYear()<2000) ||
|
|
||||||
(g.time.getFullYear()>2200)) {
|
|
||||||
} else {
|
|
||||||
// We have a GPS time. Set time
|
|
||||||
setTime(g.time.getTime()/1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Bangle.setGPSPower(1,"time");
|
|
||||||
setTimeout(fixTime, 10*60*1000); // every 10 minutes
|
|
||||||
}
|
|
||||||
// Start time fixing with GPS on next 10 minute interval
|
|
||||||
setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000);
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "doztime",
|
"id": "doztime",
|
||||||
"name": "Dozenal Time",
|
"name": "Dozenal Time",
|
||||||
"shortName": "Dozenal Time",
|
"shortName": "Dozenal Time",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "A dozenal Holocene calendar and dozenal diurnal clock",
|
"description": "A dozenal Holocene calendar and dozenal diurnal clock",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -8,3 +8,4 @@
|
||||||
0.07: Fix crash if an odd number of active boxes are configured (fix #1473)
|
0.07: Fix crash if an odd number of active boxes are configured (fix #1473)
|
||||||
0.08: Added support for notifications from exstats. Support all stats from exstats
|
0.08: Added support for notifications from exstats. Support all stats from exstats
|
||||||
0.09: Fix broken start/stop if recording not enabled (fix #1561)
|
0.09: Fix broken start/stop if recording not enabled (fix #1561)
|
||||||
|
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "run",
|
{ "id": "run",
|
||||||
"name": "Run",
|
"name": "Run",
|
||||||
"version":"0.09",
|
"version":"0.10",
|
||||||
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "run,running,fitness,outdoors,gps",
|
"tags": "run,running,fitness,outdoors,gps",
|
||||||
|
|
|
@ -42,6 +42,11 @@
|
||||||
value: Math.max(statsIDs.indexOf(settings[boxID]),0),
|
value: Math.max(statsIDs.indexOf(settings[boxID]),0),
|
||||||
format: v => statsList[v].name,
|
format: v => statsList[v].name,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
|
for (var i=1;i<=6;i++)
|
||||||
|
if (settings["B"+i]==statsIDs[v]) {
|
||||||
|
settings["B"+i]="";
|
||||||
|
boxMenu["Box "+i].value=0;
|
||||||
|
}
|
||||||
settings[boxID] = statsIDs[v];
|
settings[boxID] = statsIDs[v];
|
||||||
saveSettings();
|
saveSettings();
|
||||||
},
|
},
|
||||||
|
@ -60,7 +65,7 @@
|
||||||
'': { 'title': 'Run' },
|
'': { 'title': 'Run' },
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
};
|
};
|
||||||
if (WIDGETS["recorder"])
|
if (global.WIDGETS&&WIDGETS["recorder"])
|
||||||
menu[/*LANG*/"Record Run"] = {
|
menu[/*LANG*/"Record Run"] = {
|
||||||
value : !!settings.record,
|
value : !!settings.record,
|
||||||
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
|
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
|
||||||
|
@ -87,7 +92,7 @@
|
||||||
notificationsMenu[/*LANG*/"Dist Pattern"] = {
|
notificationsMenu[/*LANG*/"Dist Pattern"] = {
|
||||||
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
|
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
|
||||||
min: 0, max: vibPatterns.length,
|
min: 0, max: vibPatterns.length,
|
||||||
format: v => vibPatterns[v]||"Off",
|
format: v => vibPatterns[v]||/*LANG*/"Off",
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.notify.dist.notifications = vibTimes[v];
|
settings.notify.dist.notifications = vibTimes[v];
|
||||||
sampleBuzz(vibTimes[v]);
|
sampleBuzz(vibTimes[v]);
|
||||||
|
@ -97,7 +102,7 @@
|
||||||
notificationsMenu[/*LANG*/"Step Pattern"] = {
|
notificationsMenu[/*LANG*/"Step Pattern"] = {
|
||||||
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
|
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
|
||||||
min: 0, max: vibPatterns.length,
|
min: 0, max: vibPatterns.length,
|
||||||
format: v => vibPatterns[v]||"Off",
|
format: v => vibPatterns[v]||/*LANG*/"Off",
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.notify.step.notifications = vibTimes[v];
|
settings.notify.step.notifications = vibTimes[v];
|
||||||
sampleBuzz(vibTimes[v]);
|
sampleBuzz(vibTimes[v]);
|
||||||
|
@ -107,7 +112,7 @@
|
||||||
notificationsMenu[/*LANG*/"Time Pattern"] = {
|
notificationsMenu[/*LANG*/"Time Pattern"] = {
|
||||||
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
|
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
|
||||||
min: 0, max: vibPatterns.length,
|
min: 0, max: vibPatterns.length,
|
||||||
format: v => vibPatterns[v]||"Off",
|
format: v => vibPatterns[v]||/*LANG*/"Off",
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.notify.time.notifications = vibTimes[v];
|
settings.notify.time.notifications = vibTimes[v];
|
||||||
sampleBuzz(vibTimes[v]);
|
sampleBuzz(vibTimes[v]);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: Initial Release
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Seiko 5actus
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This is built on the knowledge of what I gained through designing the rolex watch face and improves on it by getting more done right at the start.
|
||||||
|
|
||||||
|
This watch is modeled after one I personally own and love, I have spent quite a bit of time designing this in a pixel art editor to try and make it as clean as possible and am quite happy with how it came out.
|
||||||
|
|
||||||
|
This watch face works in both the light and dark themes but I personally think it looks a lot cleaner in the dark them.
|
||||||
|
|
||||||
|
This watch whilst technically designed in a way that would work with the BangleJs has been only listed to work with the BangleJs2, if someones wants to test it on a first gen and let me know if it works then i'll allow it to be installed on both devices but I assume with how the images have been designed it would look strange on a first gen watch.
|
||||||
|
|
||||||
|
Special thanks to:
|
||||||
|
* rozek (for his updated widget draw code for utilization with background images)
|
||||||
|
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
|
||||||
|
* The community (for helping drive such a wonderful project)
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkEBpMB+fziAjRCQQXBHyoXEgIRLgMwC5EAj8gC5MC+QXJn4XKBgJHJMhkfJAYXEh/xC5cDBofzJocvIxKiCHpBGNExMCIxi2KeRIJFgMiiYBCkQ1Jh67EAAMSCgICBiQjFn8xDYX/AgQANn4qEgf/JIcDkcxiUSiMRY4cv+ZaFj6bDgZGBkMRDIIXD/7CHn5TDFYIADFIcRSxgAvXQwAQgRyDACcje4wAESQ4RDmMQgSGBj8zAAnyTgauH/65Cj7EBkMicAPyBYIABCgcRkYWCmYvCewMhmUiiMyF4gUBDoXzn7/Dj4RBF4IXB+QrDj40DmJgBiEyBYMDL4qcEgUikYqDCgSGKAAUSn8hbh8BgICBl/yFggwEbhMC/4sHAAcTIhIsBGYLcJGAQOHgLAEGBM/Jg0vRgsQZAMQBAQUBif/AwLJDfoQ1DkDOBkIOCgQDBFAKCDaIRPGSwqGBgM/dwUDeQiZEEAowCgXxb5jGGgQ+GaAjaHeIbmISYToHRYTPKYQIODE4cfEA4AEPghtEgQJCI5Mv+AXHJAiIIBggXFj/xDBMCcAYXGga7LcYIXIUpY1GC4SiJVRAXCiAWJA"))
|
|
@ -0,0 +1,181 @@
|
||||||
|
var imgBg = {
|
||||||
|
width : 176, height : 176, bpp : 2,
|
||||||
|
transparent : 1,
|
||||||
|
buffer : require("heatshrink").decompress(atob("qoASv/8//1C6YASrorD6opjuorHBAIriQYwriV9FXFZldFjrSEFY6FjQcorPSYqtZFZaxaVoiDkZRArLv4rV/orT/5rGABtf/4rSq//+79UFaptIK5puHFZQUBFda5IABZuBCw4rKTAPVq4rUNw4rK/4rBroqRQAIrVQSd1N4QrQNZIAOFdbzBQ4IrOCQIIDWKQYFFZhqBHwaeBACBwBFazZQThQrJ/7CHABaSEFaTaWOIYrONJArTToorIdpDdRDQLJOCBDhRIxBoPABdXAwwrRVKQ+GZR7aZDYRHNM4IraOZyTQZbQrKaIwAKr7LOHRNdFaJzOr56RuppZACBgRAEtW1Wl1WqzWm1Nqy2qC5hyTrWq1IrFzWqyoXLv7mFQRlqqtp0tVy2V0uptNVA4JWK/72Fr4yFFY9VLINWyqJBFZtfFYwGGQZIrS////peF/6xM1KDDQQIrMKwJQFA4SEKQYLbGAoIrKv5PGGY4rGEYIAHyrZKQQoIDQhoARKwR6GBJIAXJpKECMIoAXUpaELBYQAICZR4CPYo3Kq4IBr7RITIzZFq7mOGwXXBYIjB//3/9drt/+5AGZ4RKHuoNEFZVdupQBuorBupjCJIyNIrqELr4mBr99vorEK476PBxAYC79//orCr4QBu5XIXAwiIcwxXCKYJlBFYSvIQRI7HTiLjBFYzZHAAzdBDA4AKDY6CMQgYQOABT8CEQjdJFTAAKuo8MADokkAH4A/AH4A/ABn1FldXFlfVTX4A/AH4A/AH4APr//ABVVrorcv4rL6tXFbgqL//1Qbv/1QAE1AED14re/wrK1Yr/Ff4rEcY3VFf4r/Ff4r/EaoAH/4rK14r/FZYALFb1/FZbTWAA9fFZYNBFjoA/AH4A/AH4A/vots+pu/AH4A/AH4A/ADdX6ousr4GFrohgABP/FbN/+o7O/6NZv5HOB4IrZ///LBlXB5wrO/qCNB5pzOOhgNBK7RICDhQNCX5P96td/91u4FBvpJLAoQPDrplEQRNdFYPVu93qvVO5JYJurZDSJV1FYP9FYP16oXBfJRKIbJpQB7vVv/3AwJvCbpTZVVIP9/9d6/9AALdTbJgAVEJDZMACoiCLAjZNAAqSKbpjZNSoo7PQg4zCIx9/FaBQBJ4rZRHqJQBFYzZRLCN/HooYRCQIRQYQ1dDCFVQR4A/ADFXCSK5RDJ40Iq6nPW6LcIq//fh39SrNf/6EN/47OFZp0NFbd/K5wPPI5obNM5F1FaYPNdYLcGfpA3IDQIrXABZ6FDSLcZTxAIBW4zcBFa4ZBFaLsNFZYZGFZBpIACCdBFZ7BFq7dSZJArOfQwAHHQYYBFaA+JABwhBIggrObirIJFZLuBbioXJFZI/JWJorBEJIJJS4KFRrqbKFZLvDupYSeZIrJbiwrBNwIrnTQYrReBIrNCpArKBRQAKIJQrLNhCvNJidXQSZYCPCiCTIQS6KEI4pVAAddFaF1FbAZHfioAVFYyUJWbRXHLrqxFFYhVeLI4rq6orCMAoAhFa4"))
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set hour hand image */
|
||||||
|
|
||||||
|
var imgHour = {
|
||||||
|
width : 16, height : 176, bpp : 2,
|
||||||
|
transparent : 0,
|
||||||
|
buffer : require("heatshrink").decompress(atob("AH4A/AH4A/AEk//gDp///gEDAYPAh4DB+E/AYP8AaYbDEYYrDLdgD/Af4DXh/wAYIA/AGwA="))
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set minute hand image */
|
||||||
|
|
||||||
|
var imgMin = {
|
||||||
|
width : 8, height : 176, bpp : 2,
|
||||||
|
transparent : 0,
|
||||||
|
buffer : require("heatshrink").decompress(atob("AH4A/AB8P+AB/AP4B/AIcA4DPHA="))
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set second hand image */
|
||||||
|
|
||||||
|
var imgSec = {
|
||||||
|
width : 6, height : 176, bpp : 2,
|
||||||
|
transparent : 1,
|
||||||
|
buffer : require("heatshrink").decompress(atob("qoA/ADFf6v9AU1c6vNFlICWvtXAXlVA="))
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Sets the font for the date at an appropriate size of 14 */
|
||||||
|
|
||||||
|
Graphics.prototype.setFontWorkSans = function(scale) {
|
||||||
|
// Actual height 12 (12 - 1)
|
||||||
|
this.setFontCustom(atob("AAAAAAADwAAAPAAAAAAAAAvAAC/0AH/gAL/QAD9AAAIAAAAACQAAL/9AC/r9APAA8A8ADwDwAfAH//wAH/8AAAAAADwAAAtAAAH//8Af//wAAAAAAAAAABwAUAfgHwDwA/APAP8A8DzwD/9PAD/Q8AAABQAAA0AHwDwA9AHwDw4PAPDw8A///gA/f8AAAAAAAAQAAAvAAAP8AALzwAD8PAAv//wB///AAAPAAAAUAAAAAAC/88AP/y8A8NDwDywPAPD18A8L/AAAGgAAAAAAC/8AA//9ALTx8A8ODwDw4PALz/8ALD/AAAAAAPAAAA8AAADwB/APC/8A9/gAD/AAAPgAAAAAAAAAAAAB8fwAf//wDw8PAPDw8A8PDwB///AC8vwAAAAAAAAAAD/DgAv/PQDwsPAPC08A8PDwB//8AB//QAAAAAAAAAAA8DwADwPAABAE"), 46, atob("BAYJBggICQgJCAkJBA=="), 14+(scale<<8)+(2<<16));
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Sets the font for the day at an appropriate size of 12 */
|
||||||
|
|
||||||
|
Graphics.prototype.setFontWorkSansSmall = function(scale) {
|
||||||
|
// Actual height 12 (11 - 0)
|
||||||
|
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAABAP/zwGqjQAAAAP0AAEAAAP4AAGAAAAEoAAs/gL//QL88AAt/gL/+QK88AAYAAAUFAD+PAHPDgv//8fr70Hj7QCx+AAAAAC8AAL/AAPHBgL/PQB68AAPgAC9/APT7wEDjwAC/QAAAAAAuAC7/gL/DwPPywL9/gCwvAAD7wAABgAAAAP0AAEAAAACkAC//wP0C8sAAPYAAGPQA+D//0Af9ABQAADzAAB/AAv8AAB/AADyAABAAAACAAADgAADgAC//AAr5AADgAADQAAABAAAD9AAD0AAAAACwAACwAACwAACwAAAgAAABAAADwAADQAAAkAAv0Af9AL+AAvAAAAKgAD//ALgLgPADwPADwD//QA/9AAAAAAwAADwAAL//gL//gAAAAAgBQD4DwPAPwPA/wLnzwD/TwAUBQAAFADwPQLADwPDDwPvjwD+/AAQYAAAoAAD8AAv8AD08AP//gL//gAA8AAAAAL/PAPrDwPPDwPPDwPH/AAAoAAKgAC/+AHzrgPPDwPPDwH3/gBS+AKAAAPAAAPAbgPL/gP/QAPwAALAAAAAYAD+/ALvjwPLDwPLDwH//gBk+AAYAAD/PALTzwPDzwPDjwD//AAv8AAQBAA8DwA4DgAAAAAEBAAPD9AOD4AAAAABQAADwAAP4AANsAA8OAA4PABwHAAIUAAM8AAM8AAM8AAM8AAM8AAMoABgCAA4LAA8OAAdsAAP8AAHwAADgABgAADwAAPCzgPDzwLvBAD9AAAQAAABoAAv/wC0A8DD/OLPX3OMDjONHDHP/7Dfi5B4HQAP9AAACgAC/gB/8AP48AP48AB/8AAC/gAACgAAAAL//gP//wPDDwPDDwLv3gD+/AAAYAAGQAB/+AH0fQPADwPADwPADwDwPQBgNAAAAAL//gP//wPADwPADwHQDgD//AA/8AAAAAAAAAL//gP//wPDDwPDDwPDDwPADwAAAAAAAAL//gP//gPDAAPDAAPDAAPAAAAGQAB/+AD0fQPADwPCjwPDzwHz/QBy/gAAAAAAAAL//gL//gADAAADAAADAAL//gL//gAAAAAAAAL//gL//gAAAAAAeAAAvgAADwAADwL//gP//AAAAAAAAAL//gL//gAPAAA/0ADx/APAPwIABgAAAAL//gL//wAADwAADwAADwAACgAAAAL//gP6qQC/gAAH/QAAvwAv9AL9AAP//wGqqQAAAAL//gL6qQC+AAAP0AAB/AL//wL//gAAAAAGQAB/+AH0fQPADwPADwPADwD6vQB/9AAGQAAAAAL//gP//gPDwAPDwAH7gAD/AAAAAAAGQAB/+AH0fQPADwPAD9PAD/D6vbB/9PAGQAAAAAL//gP//gPDgAPD0ALr/AD/LwAUAgAUFAD+PALvDwPHDwPDjwLT7gDx/AAAAALAAAPAAAPAAAP//wPqqQPAAAPAAAAAAAP/+AGqvgAADwAADwAADwL//AL/4AAAAAKAAAL+AAAv9AAAvwAB/gAv8AL9AAKAAAKQAAL/QAAf/gAAPwAv/QL+AAL/gAAL/gAAvwC/+AP9AAEAAAEABgPgLwD9+AAfwAA/8AL0fgPADwOAAAL0AAB/AAAH/wA/qQL4AAPAAAFACgPAPwPA/wPHzwPvDwP4DwLQDwAAAAAAAAv///uqqvsAAPdAAAf4AAB/0AAC/wAAC4cAAKsAAPv///Kqqp"), 32, atob("BAMFCAgLCAMEBAcHAwYDBQgFBwcHBwcHBwcEBAcHBwcLCAgICQgHCQkEBwgHCgkJCAkICAcJCAwHBwgEBQQ="), 12+(scale<<8)+(2<<16));
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Set variables to get screen width, height and center points */
|
||||||
|
|
||||||
|
let W = g.getWidth();
|
||||||
|
let H = g.getHeight();
|
||||||
|
let cx = W/2;
|
||||||
|
let cy = H/2;
|
||||||
|
let Timeout;
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
|
/* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */
|
||||||
|
|
||||||
|
Bangle.drawWidgets = function () {
|
||||||
|
var w = g.getWidth(), h = g.getHeight();
|
||||||
|
|
||||||
|
var pos = {
|
||||||
|
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
|
||||||
|
tr:{x:w-1, y:0, r:1, c:0},
|
||||||
|
bl:{x:0, y:h-24, r:0, c:0},
|
||||||
|
br:{x:w-1, y:h-24, r:1, c:0}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (global.WIDGETS) {
|
||||||
|
for (var wd of WIDGETS) {
|
||||||
|
var p = pos[wd.area];
|
||||||
|
if (!p) continue;
|
||||||
|
|
||||||
|
wd.x = p.x - p.r*wd.width;
|
||||||
|
wd.y = p.y;
|
||||||
|
|
||||||
|
p.x += wd.width*(1-2*p.r);
|
||||||
|
p.c++;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.reset(); // also loads the current theme
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var wd of WIDGETS) {
|
||||||
|
g.setClipRect(wd.x,wd.y, wd.x+wd.width-1,23);
|
||||||
|
wd.draw(wd);
|
||||||
|
}
|
||||||
|
} catch (e) { print(e); }
|
||||||
|
|
||||||
|
g.reset(); // clears the clipping rectangle!
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Draws the clock hands and date */
|
||||||
|
|
||||||
|
function drawHands() {
|
||||||
|
let d = new Date();
|
||||||
|
|
||||||
|
let hour = d.getHours() % 12;
|
||||||
|
let min = d.getMinutes();
|
||||||
|
let sec = d.getSeconds();
|
||||||
|
|
||||||
|
let twoPi = 2*Math.PI;
|
||||||
|
let Pi = Math.PI;
|
||||||
|
|
||||||
|
let hourAngle = (hour+(min/60))/12 * twoPi - Pi;
|
||||||
|
let minAngle = (min/60) * twoPi - Pi;
|
||||||
|
let secAngle = (sec/60) * twoPi - Pi;
|
||||||
|
|
||||||
|
g.setFontWorkSans();
|
||||||
|
g.setColor(g.theme.bg);
|
||||||
|
g.setFontAlign(0,0,0);
|
||||||
|
g.drawString(d.getDate(),162,90);
|
||||||
|
g.setFontWorkSansSmall();
|
||||||
|
let weekDay = d.toString().split(" ");
|
||||||
|
if (weekDay[0] == "Sat"){g.setColor(0,0,1);}
|
||||||
|
else if (weekDay[0] == "Sun"){g.setColor(1,0,0);}
|
||||||
|
else {g.setColor(g.theme.bg);}
|
||||||
|
g.drawString(weekDay[0].toUpperCase(), 137, 90);
|
||||||
|
|
||||||
|
handLayers = [
|
||||||
|
{x:cx,
|
||||||
|
y:cy,
|
||||||
|
image:imgHour,
|
||||||
|
rotate:hourAngle,
|
||||||
|
center:true
|
||||||
|
},
|
||||||
|
{x:cx,
|
||||||
|
y:cy,
|
||||||
|
image:imgMin,
|
||||||
|
rotate:minAngle,
|
||||||
|
center:true
|
||||||
|
},
|
||||||
|
{x:cx,
|
||||||
|
y:cy,
|
||||||
|
image:imgSec,
|
||||||
|
rotate:secAngle,
|
||||||
|
center:true
|
||||||
|
}];
|
||||||
|
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
g.drawImages(handLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBackground() {
|
||||||
|
g.clear(1);
|
||||||
|
g.setBgColor(g.theme.bg);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
bgLayer = [
|
||||||
|
{x:cx,
|
||||||
|
y:cy,
|
||||||
|
image:imgBg,
|
||||||
|
center:true
|
||||||
|
}];
|
||||||
|
g.drawImages(bgLayer);
|
||||||
|
g.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Refresh the display every second */
|
||||||
|
|
||||||
|
function displayRefresh() {
|
||||||
|
g.clear(true);
|
||||||
|
drawBackground();
|
||||||
|
drawHands();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
let Pause = 1000 - (Date.now() % 1000);
|
||||||
|
Timeout = setTimeout(displayRefresh,Pause);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('lcdPower', (on) => {
|
||||||
|
if (on) {
|
||||||
|
if (Timeout != null) { clearTimeout(Timeout); Timeout = undefined;}
|
||||||
|
displayRefresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
// load widgets after 'setUI' so they're aware there is a clock active
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
displayRefresh();
|
|
@ -0,0 +1,17 @@
|
||||||
|
{ "id": "seiko-5actus",
|
||||||
|
"name": "Seiko 5actus",
|
||||||
|
"shortName":"5actus",
|
||||||
|
"icon": "seiko-5actus.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A watch designed after then Seiko 5actus from the 1970's",
|
||||||
|
"tags": "clock",
|
||||||
|
"type": "clock",
|
||||||
|
"supports":["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"seiko-5actus.app.js","url":"app.js"},
|
||||||
|
{"name":"seiko-5actus.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1 @@
|
||||||
|
0.01: New widget!
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Close Button
|
||||||
|
|
||||||
|
Adds a  button to close the current app and go back to the clock.
|
||||||
|
(Widget is not visible on the clock screen)
|
||||||
|
|
||||||
|

|
||||||
|

|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"id": "widclose",
|
||||||
|
"name": "Close Button",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "A button to close the current app",
|
||||||
|
"readme": "README.md",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "widget",
|
||||||
|
"tags": "widget,tools",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"screenshots": [{"url":"screenshot_light.png"},{"url":"screenshot_dark.png"}],
|
||||||
|
"storage": [
|
||||||
|
{"name":"widclose.wid.js","url":"widget.js"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 261 B |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
if (!Bangle.CLOCK) WIDGETS.close = {
|
||||||
|
area: "tr", width: 24, sortorder: 10, // we want the right-most spot please
|
||||||
|
draw: function() {
|
||||||
|
Bangle.removeListener("touch", this.touch);
|
||||||
|
Bangle.on("touch", this.touch);
|
||||||
|
g.reset().setColor("#f00").drawImage(atob( // hardcoded red to match setUI back button
|
||||||
|
// b/w version of preview.png, 24x24
|
||||||
|
"GBgBABgAAf+AB//gD//wH//4P//8P//8fn5+fjx+fxj+f4H+/8P//8P/f4H+fxj+fjx+fn5+P//8P//8H//4D//wB//gAf+AABgA"
|
||||||
|
), this.x, this.y);
|
||||||
|
}, touch: function(_, c) {
|
||||||
|
const w = WIDGETS.close;
|
||||||
|
if (w && c.x>=w.x && c.x<=w.x+24 && c.y>=w.y && c.y<=w.y+24) load();
|
||||||
|
}
|
||||||
|
};
|
|
@ -22,10 +22,14 @@ function bangleDownload() {
|
||||||
var promise = Promise.resolve();
|
var promise = Promise.resolve();
|
||||||
// Normal files
|
// Normal files
|
||||||
normalFiles.forEach((filename,n) => {
|
normalFiles.forEach((filename,n) => {
|
||||||
|
if (filename==".firmware") {
|
||||||
|
console.log("Ignoring .firmware file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
promise = promise.then(() => {
|
promise = promise.then(() => {
|
||||||
Progress.hide({sticky: true});
|
Progress.hide({sticky: true});
|
||||||
var percent = n/fileCount;
|
var percent = n/fileCount;
|
||||||
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0});
|
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
|
||||||
return Comms.readFile(filename).then(data => zip.file(filename,data));
|
return Comms.readFile(filename).then(data => zip.file(filename,data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -36,7 +40,7 @@ function bangleDownload() {
|
||||||
promise = promise.then(() => {
|
promise = promise.then(() => {
|
||||||
Progress.hide({sticky: true});
|
Progress.hide({sticky: true});
|
||||||
var percent = (normalFiles.length+n)/fileCount;
|
var percent = (normalFiles.length+n)/fileCount;
|
||||||
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0});
|
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
|
||||||
return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data));
|
return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
2
core
|
@ -1 +1 @@
|
||||||
Subproject commit 6e94cf77a2b355389dcd5efa55e2249a9b31983c
|
Subproject commit 27c7db6035832837ca3909ea52939f60803df72f
|
|
@ -59,6 +59,7 @@ options is an object containing:
|
||||||
* `label` - the text on the button
|
* `label` - the text on the button
|
||||||
* `cb` - a callback function
|
* `cb` - a callback function
|
||||||
* `cbl` - a callback function for long presses
|
* `cbl` - a callback function for long presses
|
||||||
|
* `back` - a callback function, passed as `back` into Bangle.setUI
|
||||||
|
|
||||||
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
|
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
|
||||||
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
|
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
|
||||||
|
@ -89,7 +90,7 @@ function Layout(layout, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
this.lazy = options.lazy || false;
|
this.lazy = options.lazy || false;
|
||||||
|
|
||||||
var btnList;
|
var btnList, uiSet;
|
||||||
Bangle.setUI(); // remove all existing input handlers
|
Bangle.setUI(); // remove all existing input handlers
|
||||||
if (process.env.HWVERSION!=2) {
|
if (process.env.HWVERSION!=2) {
|
||||||
// no touchscreen, find any buttons in 'layout'
|
// no touchscreen, find any buttons in 'layout'
|
||||||
|
@ -104,7 +105,7 @@ function Layout(layout, options) {
|
||||||
this.physBtns = 0;
|
this.physBtns = 0;
|
||||||
this.buttons = btnList;
|
this.buttons = btnList;
|
||||||
this.selectedButton = -1;
|
this.selectedButton = -1;
|
||||||
Bangle.setUI("updown", dir=>{
|
Bangle.setUI({mode:"updown", back:options.back}, dir=>{
|
||||||
var s = this.selectedButton, l=this.buttons.length;
|
var s = this.selectedButton, l=this.buttons.length;
|
||||||
if (dir===undefined && this.buttons[s])
|
if (dir===undefined && this.buttons[s])
|
||||||
return this.buttons[s].cb();
|
return this.buttons[s].cb();
|
||||||
|
@ -119,8 +120,10 @@ function Layout(layout, options) {
|
||||||
}
|
}
|
||||||
this.selectedButton = s;
|
this.selectedButton = s;
|
||||||
});
|
});
|
||||||
|
uiSet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back});
|
||||||
|
|
||||||
if (options.btns) {
|
if (options.btns) {
|
||||||
var buttons = options.btns;
|
var buttons = options.btns;
|
||||||
|
|