1
0
Fork 0

Merge branch 'nujw-master'

master
Gordon Williams 2021-10-25 09:20:00 +01:00
commit f83daabf74
15 changed files with 1159 additions and 3 deletions

View File

@ -3223,6 +3223,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 +3464,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",

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="))