Merge branch 'master' of github.com:espruino/BangleApps
|
@ -1,11 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Make overriding the HRM event optional
|
0.02: Write available data on reset or kill
|
||||||
Emit BTHRM event for external sensor
|
|
||||||
Add recorder app plugin
|
|
||||||
0.03: Prevent readings from internal sensor mixing into BT values
|
|
||||||
Mark events with src property
|
|
||||||
Show actual source of event in app
|
|
||||||
0.04: Allow reading additional data if available: HRM battery and position
|
|
||||||
Better caching of scanned BT device properties
|
|
||||||
New setting for not starting the BTHRM together with HRM
|
|
||||||
Save some RAM by not definining functions if disabled in settings
|
|
||||||
|
|
|
@ -11,34 +11,30 @@ var currentSlot = 0;
|
||||||
var hrvSlots = [10,20,30,60,120,300];
|
var hrvSlots = [10,20,30,60,120,300];
|
||||||
var hrvValues = {};
|
var hrvValues = {};
|
||||||
var rrRmsProgress;
|
var rrRmsProgress;
|
||||||
var saved = false;
|
|
||||||
|
|
||||||
var rrNumberOfValues = 0;
|
var rrNumberOfValues = 0;
|
||||||
var rrSquared = 0;
|
var rrSquared = 0;
|
||||||
var rrLastValue
|
var rrLastValue;
|
||||||
var rrMax;
|
var rrMax;
|
||||||
var rrMin;
|
var rrMin;
|
||||||
|
|
||||||
function calcHrv(rr){
|
function calcHrv(rr){
|
||||||
//Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/
|
//Calculate HRV with RMSSD method: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5624990/
|
||||||
for (currentRr of rr){
|
for (var currentRr of rr){
|
||||||
if (!rrMax) rrMax = currentRr;
|
if (!rrMax) rrMax = currentRr;
|
||||||
if (!rrMin) rrMin = currentRr;
|
if (!rrMin) rrMin = currentRr;
|
||||||
rrMax = Math.max(rrMax, currentRr);
|
rrMax = Math.max(rrMax, currentRr);
|
||||||
rrMin = Math.min(rrMin, currentRr);
|
rrMin = Math.min(rrMin, currentRr);
|
||||||
//print("Calc for: " + currentRr);
|
|
||||||
rrNumberOfValues++;
|
rrNumberOfValues++;
|
||||||
if (!rrLastValue){
|
if (!rrLastValue){
|
||||||
rrLastValue = currentRr;
|
rrLastValue = currentRr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr);
|
rrSquared += (rrLastValue - currentRr)*(rrLastValue - currentRr);
|
||||||
|
|
||||||
//print("rr²: " + rrSquared);
|
|
||||||
rrLastValue = currentRr;
|
rrLastValue = currentRr;
|
||||||
}
|
}
|
||||||
var rms = Math.sqrt(rrSquared / rrNumberOfValues);
|
var rms = Math.sqrt(rrSquared / rrNumberOfValues);
|
||||||
//print("rms: " + rms);
|
|
||||||
return rms;
|
return rms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,17 +52,36 @@ function draw(y, hrv) {
|
||||||
if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms";
|
if (hrvValues[hrvSlots[i]]) str += hrvValues[hrvSlots[i]].toFixed(1) + "ms";
|
||||||
g.setFontVector(16).drawString(str,px,y+44+(i*17));
|
g.setFontVector(16).drawString(str,px,y+44+(i*17));
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setRotation(3);
|
g.setRotation(3);
|
||||||
g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10);
|
g.setFontVector(12).drawString("Reset",g.getHeight()/2, g.getWidth()-10);
|
||||||
g.setRotation(0);
|
g.setRotation(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function write(){
|
||||||
|
if (!hrvValues[hrvSlots[0]]){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = require('Storage').open("bthrv.csv", "a");
|
||||||
|
var data = new Date(startingTime).toISOString();
|
||||||
|
for (var i = 0; i < hrvSlots.length; i++ ){
|
||||||
|
data += ",";
|
||||||
|
if (hrvValues[hrvSlots[i]]){
|
||||||
|
data += hrvValues[hrvSlots[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data += "," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
||||||
|
data += "\n";
|
||||||
|
file.write(data);
|
||||||
|
Bangle.buzz(500);
|
||||||
|
}
|
||||||
|
|
||||||
function onBtHrm(e) {
|
function onBtHrm(e) {
|
||||||
if (e.rr && !startingTime) Bangle.buzz(500);
|
if (e.rr && !startingTime) Bangle.buzz(500);
|
||||||
if (e.rr && !startingTime) startingTime=Date.now();
|
if (e.rr && !startingTime) startingTime=Date.now();
|
||||||
//print("Event:" + e.rr);
|
|
||||||
|
|
||||||
var hrv = calcHrv(e.rr);
|
var hrv = calcHrv(e.rr);
|
||||||
if (hrv){
|
if (hrv){
|
||||||
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
|
if (currentSlot <= hrvSlots.length && (Date.now() - startingTime) > (hrvSlots[currentSlot] * 1000) && !hrvValues[hrvSlots[currentSlot]]){
|
||||||
|
@ -74,35 +89,25 @@ function onBtHrm(e) {
|
||||||
currentSlot++;
|
currentSlot++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!saved && currentSlot == hrvSlots.length){
|
|
||||||
var file = require('Storage').open("bthrv.csv", "a");
|
|
||||||
var data = new Date(startingTime).toISOString();
|
|
||||||
for (var c of hrvSlots){
|
|
||||||
data+=","+hrvValues[c];
|
|
||||||
}
|
|
||||||
data+="," + rrMax + "," + rrMin + ","+rrNumberOfValues;
|
|
||||||
data+="\n";
|
|
||||||
file.write(data);
|
|
||||||
saved = true;
|
|
||||||
Bangle.buzz(500);
|
|
||||||
}
|
|
||||||
if (hrv){
|
if (hrv){
|
||||||
if (!ui){
|
if (!ui){
|
||||||
Bangle.setUI("leftright", ()=>{
|
Bangle.setUI("leftright", ()=>{
|
||||||
resetHrv();
|
resetHrv();
|
||||||
clear(30);
|
clear(30);
|
||||||
});
|
});
|
||||||
ui = true;
|
ui = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(30, hrv);
|
draw(30, hrv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetHrv(){
|
function resetHrv(){
|
||||||
|
write();
|
||||||
hrvValues={};
|
hrvValues={};
|
||||||
startingTime=undefined;
|
startingTime=undefined;
|
||||||
currentSlot=0;
|
currentSlot=0;
|
||||||
saved=false;
|
|
||||||
rrNumberOfValues = 0;
|
rrNumberOfValues = 0;
|
||||||
rrSquared = 0;
|
rrSquared = 0;
|
||||||
rrLastValue = undefined;
|
rrLastValue = undefined;
|
||||||
|
@ -117,7 +122,6 @@ g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
|
||||||
if (Bangle.setBTHRMPower){
|
if (Bangle.setBTHRMPower){
|
||||||
Bangle.on('BTHRM', onBtHrm);
|
Bangle.on('BTHRM', onBtHrm);
|
||||||
Bangle.setBTHRMPower(1,'bthrv');
|
Bangle.setBTHRMPower(1,'bthrv');
|
||||||
|
@ -133,6 +137,11 @@ if (Bangle.setBTHRMPower){
|
||||||
file.write(data);
|
file.write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
E.on('kill', ()=>{
|
||||||
|
write();
|
||||||
|
Bangle.setBTHRMPower(0,'bthrv');
|
||||||
|
});
|
||||||
|
|
||||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||||
} else {
|
} else {
|
||||||
|
@ -140,4 +149,3 @@ if (Bangle.setBTHRMPower){
|
||||||
g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16);
|
g.drawString("Missing BT HRM",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrv'));
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "bthrv",
|
"id": "bthrv",
|
||||||
"name": "Bluetooth Heart Rate variance calculator",
|
"name": "Bluetooth Heart Rate variance calculator",
|
||||||
"shortName": "BT HRV",
|
"shortName": "BT HRV",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Calculates HRV from a a BT HRM with interval data",
|
"description": "Calculates HRV from a a BT HRM with interval data",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Fix crash on start
|
0.02: Fix crash on start #1423
|
||||||
0.03: Added power saving mode, move all read/write log actions into lib/module, fix #1445
|
0.03: Added power saving mode, move all read/write log actions into lib/module
|
||||||
|
0.04: Fix #1445, display loading info, add icons to display service states
|
||||||
|
|
|
@ -8,16 +8,16 @@ also provides a power saving mode using the built in movement calculation. The i
|
||||||
#### Operating Principle
|
#### Operating Principle
|
||||||
* __ESS calculation__
|
* __ESS calculation__
|
||||||
The accelerometer polls values with 12.5Hz. On each poll the magnitude value is saved. When 13 values are collected, every 1.04 seconds, the standard deviation over this values is calculated.
|
The accelerometer polls values with 12.5Hz. On each poll the magnitude value is saved. When 13 values are collected, every 1.04 seconds, the standard deviation over this values is calculated.
|
||||||
Is the calculated standard deviation lower than the "no movement" threshold (__NoMoThresh__) a "no movement" counter is incremented. Each time the "no movement" threshold is reached the "no movement" counter will be reset. The first time no movement is detected the actual timestamp is cached (in _sleeplog.firstnomodate_) for logging.
|
Is the calculated standard deviation lower than the "no movement" threshold (__NoMo Thresh__) a "no movement" counter is incremented. Each time the "no movement" threshold is reached the "no movement" counter will be reset. The first time no movement is detected the actual timestamp is cached (in _sleeplog.firstnomodate_) for logging.
|
||||||
When the "no movement" counter reaches the sleep threshold the watch is considered as resting. (The sleep threshold is calculated from the __MinDuration__ setting, Example: _sleep threshold = MinDuration * 60 / calculation interval => 10min * 60s/min / 1.04s ~= 576,9 rounded up to 577_)
|
When the "no movement" counter reaches the sleep threshold the watch is considered as resting. (The sleep threshold is calculated from the __Min Duration__ setting, Example: _sleep threshold = Min Duration * 60 / calculation interval => 10min * 60s/min / 1.04s ~= 576,9 rounded up to 577_)
|
||||||
* __Power Saving Mode__
|
* __Power Saving Mode__
|
||||||
On power saving mode the movement value of bangle's build in health event is checked against the maximal movement threshold (__MaxMove__). The event is only triggered every 10 minutes which decreases the battery impact but also reduces accurracy.
|
On power saving mode the movement value of bangle's build in health event is checked against the maximal movement threshold (__Max Move__). The event is only triggered every 10 minutes which decreases the battery impact but also reduces accurracy.
|
||||||
* ___Sleeping___ __or__ ___Not Worn___
|
* ___Sleeping___ __or__ ___Not Worn___
|
||||||
To check if a resting watch indicates a sleeping status, the internal temperature must be greater than the temperature threshold (__TempThresh__). Otherwise the watch is considered as not worn.
|
To check if a resting watch indicates a sleeping status, the internal temperature must be greater than the temperature threshold (__Temp Thresh__). Otherwise the watch is considered as not worn.
|
||||||
* __True Sleep__
|
* __True Sleep__
|
||||||
The true sleep value is a simple addition of all registert sleeping periods.
|
The true sleep value is a simple addition of all registert sleeping periods.
|
||||||
* __Consecutive Sleep__
|
* __Consecutive Sleep__
|
||||||
In addition the consecutive sleep value tries to predict the complete time you were asleep, even the light sleeping phases with registered movements. All periods after a sleeping period will be summarized til the first following non sleeping period that is longer then the maximal awake duration (__MaxAwake__). If this sum is lower than the minimal consecutive sleep duration (__MinConsec__) it is not considered, otherwise it will be added to the consecutive sleep value.
|
In addition the consecutive sleep value tries to predict the complete time you were asleep, even the light sleeping phases with registered movements. All periods after a sleeping period will be summarized til the first following non sleeping period that is longer then the maximal awake duration (__Max Awake__). If this sum is lower than the minimal consecutive sleep duration (__Min Consec__) it is not considered, otherwise it will be added to the consecutive sleep value.
|
||||||
* __Logging__
|
* __Logging__
|
||||||
To minimize the log size only a changed state is logged. The logged timestamp is matching the beginning of its measurement period.
|
To minimize the log size only a changed state is logged. The logged timestamp is matching the beginning of its measurement period.
|
||||||
When not on power saving mode a movement is detected nearly instantaneous and the detection of a no movement period is delayed by the minimal no movement duration. To match the beginning of the measurement period a cached timestamp (_sleeplog.firstnomodate_) is logged.
|
When not on power saving mode a movement is detected nearly instantaneous and the detection of a no movement period is delayed by the minimal no movement duration. To match the beginning of the measurement period a cached timestamp (_sleeplog.firstnomodate_) is logged.
|
||||||
|
@ -34,41 +34,44 @@ also provides a power saving mode using the built in movement calculation. The i
|
||||||
---
|
---
|
||||||
### Settings
|
### Settings
|
||||||
---
|
---
|
||||||
* __BreakTod__ | break at time of day
|
* __Break Tod__ | break at time of day
|
||||||
_0_ / _1_ / _..._ / __10__ / _..._ / _12_
|
_0_ / _1_ / _..._ / __10__ / _..._ / _12_
|
||||||
Change time of day on wich the lower graph starts and the upper graph ends.
|
Change time of day on wich the lower graph starts and the upper graph ends.
|
||||||
* __MaxAwake__ | maximal awake duration
|
* __Max Awake__ | maximal awake duration
|
||||||
_15min_ / _20min_ / _..._ / __60min__ / _..._ / _120min_
|
_15min_ / _20min_ / _..._ / __60min__ / _..._ / _120min_
|
||||||
Adjust the maximal awake duration upon the exceeding of which aborts the consecutive sleep period.
|
Adjust the maximal awake duration upon the exceeding of which aborts the consecutive sleep period.
|
||||||
* __MinConsec__ | minimal consecutive sleep duration
|
* __Min Consec__ | minimal consecutive sleep duration
|
||||||
_15min_ / _20min_ / _..._ / __30min__ / _..._ / _120min_
|
_15min_ / _20min_ / _..._ / __30min__ / _..._ / _120min_
|
||||||
Adjust the minimal consecutive sleep duration that will be considered for the consecutive sleep value.
|
Adjust the minimal consecutive sleep duration that will be considered for the consecutive sleep value.
|
||||||
* __TempThresh__ | temperature threshold
|
* __Temp Thresh__ | temperature threshold
|
||||||
_20°C_ / _20.5°C_ / _..._ / __25°C__ / _..._ / _40°C_
|
_20°C_ / _20.5°C_ / _..._ / __25°C__ / _..._ / _40°C_
|
||||||
The internal temperature must be greater than this threshold to log _sleeping_, otherwise it is _not worn_.
|
The internal temperature must be greater than this threshold to log _sleeping_, otherwise it is _not worn_.
|
||||||
* __PowerSaving__
|
* __Power Saving__
|
||||||
_on_ / __off__
|
_on_ / __off__
|
||||||
En-/Disable power saving mode. _Saves battery, but might decrease accurracy._
|
En-/Disable power saving mode. _Saves battery, but might decrease accurracy._
|
||||||
* __MaxMove__ | maximal movement threshold
|
In app icon showing that power saving mode is enabled: 
|
||||||
|
* __Max Move__ | maximal movement threshold
|
||||||
(only available when on power saving mode)
|
(only available when on power saving mode)
|
||||||
_50_ / _51_ / _..._ / __100__ / _..._ / _200_
|
_50_ / _51_ / _..._ / __100__ / _..._ / _200_
|
||||||
On power saving mode the watch is considered resting if this threshold is lower or equal to the movement value of bangle's health event.
|
On power saving mode the watch is considered resting if this threshold is lower or equal to the movement value of bangle's health event.
|
||||||
* __NoMoThresh__ | no movement threshold
|
* __NoMo Thresh__ | no movement threshold
|
||||||
(only available when not on power saving mode)
|
(only available when not on power saving mode)
|
||||||
_0.006_ / _0.007_ / _..._ / __0.012__ / _..._ / _0.020_
|
_0.006_ / _0.007_ / _..._ / __0.012__ / _..._ / _0.020_
|
||||||
The standard deviation over the measured values needs to be lower then this threshold to count as not moving.
|
The standard deviation over the measured values needs to be lower then this threshold to count as not moving.
|
||||||
The defaut threshold value worked best for my watch. A threshold value below 0.008 may get triggert by noise.
|
The defaut threshold value worked best for my watch. A threshold value below 0.008 may get triggert by noise.
|
||||||
* __MinDuration__ | minimal no movement duration
|
* __Min Duration__ | minimal no movement duration
|
||||||
(only available when not on power saving mode)
|
(only available when not on power saving mode)
|
||||||
_5min_ / _6min_ / _..._ / __10min__ / _..._ / _15min_
|
_5min_ / _6min_ / _..._ / __10min__ / _..._ / _15min_
|
||||||
If no movement is detected for this duration, the watch is considered as resting.
|
If no movement is detected for this duration, the watch is considered as resting.
|
||||||
* __Enabled__
|
* __Enabled__
|
||||||
__on__ / _off_
|
__on__ / _off_
|
||||||
En-/Disable the service (all background activities). _Saves the most battery, but might make this app useless._
|
En-/Disable the service (all background activities). _Saves the most battery, but might make this app useless._
|
||||||
|
In app icon showing that the service is disabled: 
|
||||||
* __Logfile__
|
* __Logfile__
|
||||||
__default__ / _off_
|
__default__ / _off_
|
||||||
En-/Disable logging by setting the logfile to _sleeplog.log_ / _undefined_.
|
En-/Disable logging by setting the logfile to _sleeplog.log_ / _undefined_.
|
||||||
If the logfile has been customized it is displayed with _custom_.
|
If the logfile has been customized it is displayed with _custom_.
|
||||||
|
In app icon showing that logging is disabled: 
|
||||||
|
|
||||||
---
|
---
|
||||||
### Global Object and Module Functions
|
### Global Object and Module Functions
|
||||||
|
|
|
@ -64,7 +64,7 @@ function drawLog(topY, viewUntil) {
|
||||||
for (var x = 0; x < hours; x++) {
|
for (var x = 0; x < hours; x++) {
|
||||||
g.fillRect(x * stepwidth, y + 2, x * stepwidth, y + 4);
|
g.fillRect(x * stepwidth, y + 2, x * stepwidth, y + 4);
|
||||||
g.setFontAlign(-1, -1).setFont("6x8")
|
g.setFontAlign(-1, -1).setFont("6x8")
|
||||||
.drawString((startHour + x) % 24, x * stepwidth, y + 6);
|
.drawString((startHour + x) % 24, x * stepwidth + 1, y + 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// define variables for sleep calculation
|
// define variables for sleep calculation
|
||||||
|
@ -127,34 +127,24 @@ function drawLog(topY, viewUntil) {
|
||||||
return output.map(value => value /= 6E4);
|
return output.map(value => value /= 6E4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// define draw night to function
|
// define function to draw the analysis
|
||||||
function drawNightTo(prevDays) {
|
function drawAnalysis(toDate) {
|
||||||
// calculate 10am of this or a previous day
|
//var t0 = Date.now();
|
||||||
var date = Date();
|
|
||||||
date = Date(date.getFullYear(), date.getMonth(), date.getDate() - prevDays, breaktod);
|
|
||||||
|
|
||||||
// get width
|
// get width
|
||||||
var width = g.getWidth();
|
var width = g.getWidth();
|
||||||
|
|
||||||
// clear app area
|
|
||||||
g.clearRect(0, 24, width, width);
|
|
||||||
|
|
||||||
// define variable for sleep calculation
|
// define variable for sleep calculation
|
||||||
var outputs = [0, 0]; // [estimated, true]
|
var outputs = [0, 0]; // [estimated, true]
|
||||||
// draw log graphs and read outputs
|
|
||||||
drawLog(110, date).forEach(
|
|
||||||
(value, index) => outputs[index] += value);
|
|
||||||
drawLog(145, Date(date.valueOf() - 432E5)).forEach(
|
|
||||||
(value, index) => outputs[index] += value);
|
|
||||||
|
|
||||||
// reduce date by 1s to ensure correct headline
|
// clear analysis area
|
||||||
date = Date(date.valueOf() - 1E3);
|
g.clearRect(0, 71, width, width);
|
||||||
// draw headline, on red bg if service or loggging disabled or green bg if powersaving enabled
|
|
||||||
g.setColor(global.sleeplog && sleeplog.enabled && sleeplog.logfile ? sleeplog.powersaving ? 2016 : g.theme.bg : 63488);
|
// draw log graphs and read outputs
|
||||||
g.fillRect(0, 30, width, 66).reset();
|
drawLog(110, toDate).forEach(
|
||||||
g.setFont("12x20").setFontAlign(0, -1);
|
(value, index) => outputs[index] += value);
|
||||||
g.drawString("Night to " + require('locale').dow(date, 1) + "\n" +
|
drawLog(144, Date(toDate.valueOf() - 432E5)).forEach(
|
||||||
require('locale').date(date, 1), width / 2, 30);
|
(value, index) => outputs[index] += value);
|
||||||
|
|
||||||
// draw outputs
|
// draw outputs
|
||||||
g.reset(); // area: 0, 70, width, 105
|
g.reset(); // area: 0, 70, width, 105
|
||||||
|
@ -166,8 +156,57 @@ function drawNightTo(prevDays) {
|
||||||
Math.floor(outputs[0] % 60) + "min", width - 10, 70);
|
Math.floor(outputs[0] % 60) + "min", width - 10, 70);
|
||||||
g.drawString(Math.floor(outputs[1] / 60) + "h " +
|
g.drawString(Math.floor(outputs[1] / 60) + "h " +
|
||||||
Math.floor(outputs[1] % 60) + "min", width - 10, 90);
|
Math.floor(outputs[1] % 60) + "min", width - 10, 90);
|
||||||
|
|
||||||
|
//print("analysis processing seconds:", Math.round(Date.now() - t0) / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define draw night to function
|
||||||
|
function drawNightTo(prevDays) {
|
||||||
|
// calculate 10am of this or a previous day
|
||||||
|
var toDate = Date();
|
||||||
|
toDate = Date(toDate.getFullYear(), toDate.getMonth(), toDate.getDate() - prevDays, breaktod);
|
||||||
|
|
||||||
|
// get width
|
||||||
|
var width = g.getWidth();
|
||||||
|
var center = width / 2;
|
||||||
|
|
||||||
|
// reduce date by 1s to ensure correct headline
|
||||||
|
toDate = Date(toDate.valueOf() - 1E3);
|
||||||
|
|
||||||
|
// clear heading area
|
||||||
|
g.clearRect(0, 24, width, 70);
|
||||||
|
|
||||||
|
// display service states: service, loggging and powersaving
|
||||||
|
if (!sleeplog.enabled) {
|
||||||
|
// draw disabled service icon
|
||||||
|
g.setColor(1, 0, 0)
|
||||||
|
.drawImage(atob("FBSBAAH4AH/gH/+D//w/n8f5/nud7znP85z/f+/3/v8/z/P895+efGPj4Hw//8H/+Af+AB+A"), 2, 36);
|
||||||
|
} else if (!sleeplog.logfile) {
|
||||||
|
// draw disabled log icon
|
||||||
|
g.reset().drawImage(atob("EA6BAM//z/8AAAAAz//P/wAAAADP/8//AAAAAM//z/8="), 4, 40)
|
||||||
|
.setColor(1, 0, 0).fillPoly([2, 38, 4, 36, 22, 54, 20, 56]);
|
||||||
|
}
|
||||||
|
// draw power saving icon
|
||||||
|
if (sleeplog.powersaving) g.setColor(0, 1, 0)
|
||||||
|
.drawImage(atob("FBSBAAAAcAD/AH/wP/4P/+H//h//4//+fv/nj/7x/88//Of/jH/4j/8I/+Af+AH+AD8AA4AA"), width - 22, 36);
|
||||||
|
|
||||||
|
// draw headline
|
||||||
|
g.reset().setFont("12x20").setFontAlign(0, -1);
|
||||||
|
g.drawString("Night to " + require('locale').dow(toDate, 1) + "\n" +
|
||||||
|
require('locale').date(toDate, 1), center, 30);
|
||||||
|
|
||||||
|
// show loading info
|
||||||
|
var info = "calculating data ...\nplease be patient :)";
|
||||||
|
var y0 = center + 30;
|
||||||
|
var bounds = [center - 80, y0 - 20, center + 80, y0 + 20];
|
||||||
|
g.clearRect.apply(g, bounds).drawRect.apply(g, bounds);
|
||||||
|
g.setFont("6x8").setFontAlign(0, 0);
|
||||||
|
g.drawString(info, center, y0);
|
||||||
|
|
||||||
|
// calculate and draw analysis after timeout for faster feedback
|
||||||
|
if (ATID) ATID = clearTimeout(ATID);
|
||||||
|
ATID = setTimeout(drawAnalysis, 100, toDate);
|
||||||
|
}
|
||||||
|
|
||||||
// define function to draw and setup UI
|
// define function to draw and setup UI
|
||||||
function startApp() {
|
function startApp() {
|
||||||
|
@ -182,8 +221,9 @@ function startApp() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// define day to display
|
// define day to display and analysis timeout id
|
||||||
var prevDays = 0;
|
var prevDays = 0;
|
||||||
|
var ATID;
|
||||||
|
|
||||||
// setup app
|
// setup app
|
||||||
g.clear();
|
g.clear();
|
||||||
|
|
|
@ -28,36 +28,43 @@ if (sleeplog.enabled) {
|
||||||
resting: undefined,
|
resting: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
|
|
||||||
// define stop function (logging will restart if enabled and boot file is executed)
|
// define function to handle stopping the service, it will be restarted on reload if enabled
|
||||||
stop: function() {
|
stopHandler: function() {
|
||||||
// remove all listeners
|
// remove all listeners
|
||||||
Bangle.removeListener('accel', sleeplog.accel);
|
Bangle.removeListener('accel', sleeplog.accel);
|
||||||
Bangle.removeListener('health', sleeplog.health);
|
Bangle.removeListener('health', sleeplog.health);
|
||||||
E.removeListener('kill', () => sleeplog.stop());
|
|
||||||
// exit on missing global object
|
|
||||||
if (!global.sleeplog) return;
|
|
||||||
// write log with undefined sleeping status
|
// write log with undefined sleeping status
|
||||||
require("sleeplog").writeLog(0, [Math.floor(Date.now()), 0]);
|
require("sleeplog").writeLog(0, [Math.floor(Date.now()), 0]);
|
||||||
// reset always used cached values
|
// reset cached values if sleeplog is defined
|
||||||
sleeplog.resting = undefined;
|
if (global.sleeplog) {
|
||||||
sleeplog.status = undefined;
|
sleeplog.resting = undefined;
|
||||||
sleeplog.ess_values = [];
|
sleeplog.status = undefined;
|
||||||
sleeplog.nomocount = 0;
|
// reset cached ESS calculation values
|
||||||
sleeplog.firstnomodate = undefined;
|
if (!sleeplog.powersaving) {
|
||||||
|
sleeplog.ess_values = [];
|
||||||
|
sleeplog.nomocount = 0;
|
||||||
|
sleeplog.firstnomodate = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// define restart function (also use for initial starting)
|
// define function to remove the kill listener and stop the service
|
||||||
|
// https://github.com/espruino/BangleApps/issues/1445
|
||||||
|
stop: function() {
|
||||||
|
E.removeListener('kill', sleeplog.stopHandler);
|
||||||
|
sleeplog.stopHandler();
|
||||||
|
},
|
||||||
|
|
||||||
|
// define function to initialy start or restart the service
|
||||||
start: function() {
|
start: function() {
|
||||||
// exit on missing global object
|
// add kill listener
|
||||||
if (!global.sleeplog) return;
|
E.on('kill', sleeplog.stopHandler);
|
||||||
// add health listener if defined and
|
// add health listener if defined and
|
||||||
if (sleeplog.health) Bangle.on('health', sleeplog.health);
|
if (sleeplog.health) Bangle.on('health', sleeplog.health);
|
||||||
// add acceleration listener if defined and set status to unknown
|
// add acceleration listener if defined and set status to unknown
|
||||||
if (sleeplog.accel) Bangle.on('accel', sleeplog.accel);
|
if (sleeplog.accel) Bangle.on('accel', sleeplog.accel);
|
||||||
// add kill listener
|
|
||||||
E.on('kill', () => sleeplog.stop());
|
|
||||||
// read log since 5min ago and restore status to last known state or unknown
|
// read log since 5min ago and restore status to last known state or unknown
|
||||||
sleeplog.status = (require("sleeplog").readLog(0, Date.now() - 3E5)[1] || [0, 0])[1]
|
sleeplog.status = (require("sleeplog").readLog(0, Date.now() - 3E5)[1] || [0, 0])[1];
|
||||||
// update resting according to status
|
// update resting according to status
|
||||||
sleeplog.resting = sleeplog.status % 2;
|
sleeplog.resting = sleeplog.status % 2;
|
||||||
// write restored status to log
|
// write restored status to log
|
||||||
|
|
After Width: | Height: | Size: 9.5 KiB |
|
@ -5,8 +5,8 @@ exports = {
|
||||||
if (typeof global.sleeplog !== "object") return;
|
if (typeof global.sleeplog !== "object") return;
|
||||||
|
|
||||||
// set default logfile
|
// set default logfile
|
||||||
logfile = (typeof logfile === "string" && logfile.endsWith(".log")) ? logfile :
|
if ((typeof logfile !== "string" || !logfile.endsWith(".log")) &&
|
||||||
logfile === false ? undefined : "sleeplog.log";
|
logfile !== false) logfile = "sleeplog.log";
|
||||||
|
|
||||||
// stop if enabled
|
// stop if enabled
|
||||||
if (global.sleeplog.enabled) global.sleeplog.stop();
|
if (global.sleeplog.enabled) global.sleeplog.stop();
|
||||||
|
@ -40,8 +40,9 @@ exports = {
|
||||||
// - string // additional information
|
// - string // additional information
|
||||||
readLog: function(logfile, since, until) {
|
readLog: function(logfile, since, until) {
|
||||||
// check/set logfile
|
// check/set logfile
|
||||||
logfile = typeof logfile === "string" && logfile.endsWith(".log") ? logfile :
|
if (typeof logfile !== "string" || !logfile.endsWith(".log")) {
|
||||||
(global.sleeplog || {}).logfile || "sleeplog.log";
|
logfile = (global.sleeplog || {}).logfile || "sleeplog.log";
|
||||||
|
}
|
||||||
|
|
||||||
// check if since is in the future
|
// check if since is in the future
|
||||||
if (since > Date()) return [];
|
if (since > Date()) return [];
|
||||||
|
@ -73,8 +74,10 @@ exports = {
|
||||||
// replace log with input if at least one entry like above is inside another array
|
// replace log with input if at least one entry like above is inside another array
|
||||||
writeLog: function(logfile, input) {
|
writeLog: function(logfile, input) {
|
||||||
// check/set logfile
|
// check/set logfile
|
||||||
logfile = typeof logfile === "string" && logfile.endsWith(".log") ? logfile :
|
if (typeof logfile !== "string" || !logfile.endsWith(".log")) {
|
||||||
(global.sleeplog || {}).logfile || "sleeplog.log";
|
if (!global.sleeplog || sleeplog.logfile === false) return;
|
||||||
|
logfile = sleeplog.logfile || "sleeplog.log";
|
||||||
|
}
|
||||||
|
|
||||||
// check if input is an array
|
// check if input is an array
|
||||||
if (typeof input !== "object" || typeof input.length !== "number") return;
|
if (typeof input !== "object" || typeof input.length !== "number") return;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id":"sleeplog",
|
"id":"sleeplog",
|
||||||
"name":"Sleep Log",
|
"name":"Sleep Log",
|
||||||
"shortName": "SleepLog",
|
"shortName": "SleepLog",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Log and view your sleeping habits. This app derived from SleepPhaseAlarm and uses also the principe of Estimation of Stationary Sleep-segments (ESS). It also provides a power saving mode using the built in movement calculation.",
|
"description": "Log and view your sleeping habits. This app derived from SleepPhaseAlarm and uses also the principe of Estimation of Stationary Sleep-segments (ESS). It also provides a power saving mode using the built in movement calculation.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 26 KiB |
|
@ -29,11 +29,6 @@
|
||||||
storage.writeJSON(filename, settings);
|
storage.writeJSON(filename, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// define circulate function
|
|
||||||
function circulate(min, max, value) {
|
|
||||||
return value > max ? min : value < min ? max : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// define function to change values that need a restart of the service
|
// define function to change values that need a restart of the service
|
||||||
function changeRestart() {
|
function changeRestart() {
|
||||||
require("sleeplog").setEnabled(settings.enabled, settings.logfile, settings.powersaving);
|
require("sleeplog").setEnabled(settings.enabled, settings.logfile, settings.powersaving);
|
||||||
|
@ -49,77 +44,79 @@
|
||||||
title: "Sleep Log",
|
title: "Sleep Log",
|
||||||
selected: selected
|
selected: selected
|
||||||
},
|
},
|
||||||
"< Exit": () => load(),
|
"Exit": () => load(),
|
||||||
"< Back": () => back(),
|
"< Back": () => back(),
|
||||||
"BreakTod": {
|
"Break Tod": {
|
||||||
value: settings.breaktod,
|
value: settings.breaktod,
|
||||||
step: 1,
|
step: 1,
|
||||||
onchange: function(v) {
|
min: 0,
|
||||||
this.value = v = circulate(0, 23, v);
|
max: 23,
|
||||||
writeSetting("breaktod", v);
|
wrap: true,
|
||||||
}
|
onchange: v => writeSetting("breaktod", v),
|
||||||
},
|
},
|
||||||
"MaxAwake": {
|
"Max Awake": {
|
||||||
value: settings.maxawake / 6E4,
|
value: settings.maxawake / 6E4,
|
||||||
step: 5,
|
step: 5,
|
||||||
|
min: 15,
|
||||||
|
max: 120,
|
||||||
|
wrap: true,
|
||||||
format: v => v + "min",
|
format: v => v + "min",
|
||||||
onchange: function(v) {
|
onchange: v => writeSetting("maxawake", v * 6E4),
|
||||||
this.value = v = circulate(15, 120, v);
|
|
||||||
writeSetting("maxawake", v * 6E4);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"MinConsec": {
|
"Min Consec": {
|
||||||
value: settings.minconsec / 6E4,
|
value: settings.minconsec / 6E4,
|
||||||
step: 5,
|
step: 5,
|
||||||
|
min: 15,
|
||||||
|
max: 120,
|
||||||
|
wrap: true,
|
||||||
format: v => v + "min",
|
format: v => v + "min",
|
||||||
onchange: function(v) {
|
onchange: v => writeSetting("minconsec", v * 6E4),
|
||||||
this.value = v = circulate(15, 120, v);
|
|
||||||
writeSetting("minconsec", v * 6E4);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"TempThresh": {
|
"Temp Thresh": {
|
||||||
value: settings.tempthresh,
|
value: settings.tempthresh,
|
||||||
step: 0.5,
|
step: 0.5,
|
||||||
|
min: 20,
|
||||||
|
max: 40,
|
||||||
|
wrap: true,
|
||||||
format: v => v + "°C",
|
format: v => v + "°C",
|
||||||
onchange: function(v) {
|
onchange: v => writeSetting("tempthresh", v),
|
||||||
this.value = v = circulate(20, 40, v);
|
|
||||||
writeSetting("tempthresh", v);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"PowerSaving": {
|
"Power Saving": {
|
||||||
value: settings.powersaving,
|
value: settings.powersaving,
|
||||||
format: v => v ? "on" : "off",
|
format: v => v ? "on" : "off",
|
||||||
onchange: function(v) {
|
onchange: function(v) {
|
||||||
settings.powersaving = v;
|
settings.powersaving = v;
|
||||||
changeRestart();
|
changeRestart();
|
||||||
showMain(7);
|
// redraw menu with changed entries subsequent to onchange
|
||||||
|
// https://github.com/espruino/Espruino/issues/2149
|
||||||
|
setTimeout(showMain, 1, 6);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MaxMove": {
|
"Max Move": {
|
||||||
value: settings.maxmove,
|
value: settings.maxmove,
|
||||||
step: 1,
|
step: 1,
|
||||||
onchange: function(v) {
|
min: 50,
|
||||||
this.value = v = circulate(50, 200, v);
|
max: 200,
|
||||||
writeSetting("maxmove", v);
|
wrap: true,
|
||||||
}
|
onchange: v => writeSetting("maxmove", v),
|
||||||
},
|
},
|
||||||
"NoMoThresh": {
|
"NoMo Thresh": {
|
||||||
value: settings.nomothresh,
|
value: settings.nomothresh,
|
||||||
step: 0.001,
|
step: 0.001,
|
||||||
|
min: 0.006,
|
||||||
|
max: 0.02,
|
||||||
|
wrap: true,
|
||||||
format: v => ("" + v).padEnd(5, "0"),
|
format: v => ("" + v).padEnd(5, "0"),
|
||||||
onchange: function(v) {
|
onchange: v => writeSetting("nomothresh", v),
|
||||||
this.value = v = circulate(0.006, 0.02, v);
|
|
||||||
writeSetting("nomothresh", v);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"MinDuration": {
|
"Min Duration": {
|
||||||
value: Math.floor(settings.sleepthresh * stFactor),
|
value: Math.floor(settings.sleepthresh * stFactor),
|
||||||
step: 1,
|
step: 1,
|
||||||
|
min: 5,
|
||||||
|
max: 15,
|
||||||
|
wrap: true,
|
||||||
format: v => v + "min",
|
format: v => v + "min",
|
||||||
onchange: function(v) {
|
onchange: v => writeSetting("sleepthresh", Math.ceil(v / stFactor)),
|
||||||
this.value = v = circulate(5, 15, v);
|
|
||||||
writeSetting("sleepthresh", Math.ceil(v / stFactor));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Enabled": {
|
"Enabled": {
|
||||||
value: settings.enabled,
|
value: settings.enabled,
|
||||||
|
@ -130,7 +127,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Logfile ": {
|
"Logfile ": {
|
||||||
value: settings.logfile === "sleeplog.log" ? true : settings.logfile.endsWith(".log") ? "custom" : false,
|
value: settings.logfile === "sleeplog.log" ? true : (settings.logfile || "").endsWith(".log") ? "custom" : false,
|
||||||
format: v => v === true ? "default" : v ? "custom" : "off",
|
format: v => v === true ? "default" : v ? "custom" : "off",
|
||||||
onchange: function(v) {
|
onchange: function(v) {
|
||||||
if (v !== "custom") {
|
if (v !== "custom") {
|
||||||
|
@ -141,11 +138,8 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// check power saving mode to delete unused entries
|
// check power saving mode to delete unused entries
|
||||||
(settings.powersaving ? ["NoMoThresh", "MinDuration"] : ["MaxMove"]).forEach(property => delete mainMenu[property]);
|
(settings.powersaving ? ["NoMo Thresh", "Min Duration"] : ["Max Move"]).forEach(property => delete mainMenu[property]);
|
||||||
var menu = E.showMenu(mainMenu);
|
var menu = E.showMenu(mainMenu);
|
||||||
// workaround to display changed entries correct
|
|
||||||
// https://github.com/espruino/Espruino/issues/2149
|
|
||||||
if (selected) setTimeout(m => m.draw(), 1, menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw main menu
|
// draw main menu
|
||||||
|
|
|
@ -8,3 +8,4 @@
|
||||||
0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted.
|
0.08: New features. Added waypoints file and distance to selected waypoint display. Added integration with GPS Setup module to switch GPS to low power mode when screen off. Save display settings and restore when app restarted.
|
||||||
0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight.
|
0.09: Add third screen mode with large clock and waypoint selection display to ease visibility in bright daylight.
|
||||||
0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings.
|
0.10: Add Kalman filter to smooth the speed and altitude values. Can be disabled in settings.
|
||||||
|
0.11: Now also runs on Bangle.js 2 with basic functionality
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# GPS Speed, Altimeter and Distance to Waypoint
|
# GPS Speed, Altimeter and Distance to Waypoint
|
||||||
|
|
||||||
You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint.
|
You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint.
|
||||||
|
|
||||||
|
*Note for **Bangle.js 2:** Currently only the BTN3 functionality is working with the Bangle.js 2 button.*
|
||||||
|
|
||||||
Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed.
|
Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed.
|
||||||
|
|
||||||
|
@ -10,6 +12,8 @@ The waypoints list is the same as that used with the [GPS Navigation](https://ba
|
||||||
|
|
||||||
BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint
|
BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint
|
||||||
|
|
||||||
|
***Bangle.js 2:** Currently only this button function is working*
|
||||||
|
|
||||||
### [A]ltitude mode
|
### [A]ltitude mode
|
||||||
|
|
||||||
BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded.
|
BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded.
|
||||||
|
|
|
@ -5,7 +5,16 @@ Mike Bennett mike[at]kereru.com
|
||||||
1.01 : Third mode large clock display
|
1.01 : Third mode large clock display
|
||||||
1.02 : add smoothing with kalman filter
|
1.02 : add smoothing with kalman filter
|
||||||
*/
|
*/
|
||||||
var v = '1.02g';
|
//var v = '1.02g';
|
||||||
|
|
||||||
|
const BANGLEJS2 = process.env.HWVERSION==2;
|
||||||
|
const screenH = g.getHeight();
|
||||||
|
const screenH_Half = screenH / 2;
|
||||||
|
const screenH_Third = screenH / 3;
|
||||||
|
const screenH_TwoThirds = screenH * 2 / 3;
|
||||||
|
const screenW = g.getWidth();
|
||||||
|
const screenW_Half = screenW / 2;
|
||||||
|
const fontFactorB2 = 2/3;
|
||||||
|
|
||||||
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
||||||
var KalmanFilter = (function () {
|
var KalmanFilter = (function () {
|
||||||
|
@ -171,10 +180,11 @@ var KalmanFilter = (function () {
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
|
||||||
var buf = Graphics.createArrayBuffer(240,160,2,{msb:true});
|
|
||||||
|
|
||||||
// Load fonts
|
var buf = Graphics.createArrayBuffer(screenW,screenH_TwoThirds,2,{msb:true});
|
||||||
require("Font7x11Numeric7Seg").add(Graphics);
|
|
||||||
|
if (!BANGLEJS2)
|
||||||
|
require("Font7x11Numeric7Seg").add(Graphics); // Load fonts
|
||||||
|
|
||||||
var lf = {fix:0,satellites:0};
|
var lf = {fix:0,satellites:0};
|
||||||
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
||||||
|
@ -188,9 +198,10 @@ max.spd = 0;
|
||||||
max.alt = 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.
|
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 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 wp = {}; // Waypoint to use for distance from cur position.
|
||||||
|
var SATinView = 0;
|
||||||
|
|
||||||
function nxtWp(inc){
|
function nxtWp(inc){
|
||||||
cfg.wp+=inc;
|
cfg.wp+=inc;
|
||||||
|
@ -212,7 +223,7 @@ function radians(a) {
|
||||||
function distance(a,b){
|
function distance(a,b){
|
||||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||||
var y = radians(b.lat-a.lat);
|
var y = radians(b.lat-a.lat);
|
||||||
|
|
||||||
// Distance in selected units
|
// Distance in selected units
|
||||||
var d = Math.sqrt(x*x + y*y) * 6371000;
|
var d = Math.sqrt(x*x + y*y) * 6371000;
|
||||||
d = (d/parseFloat(cfg.dist)).toFixed(2);
|
d = (d/parseFloat(cfg.dist)).toFixed(2);
|
||||||
|
@ -228,41 +239,53 @@ function drawFix(dat) {
|
||||||
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
var v = '';
|
var v = '';
|
||||||
var u='';
|
var u='';
|
||||||
|
|
||||||
// Primary Display
|
// Primary Display
|
||||||
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
|
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
|
||||||
|
|
||||||
// Primary Units
|
// Primary Units
|
||||||
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
|
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
|
||||||
|
|
||||||
drawPrimary(v,u);
|
drawPrimary(v,u);
|
||||||
|
|
||||||
// Secondary Display
|
// Secondary Display
|
||||||
v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString();
|
v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString();
|
||||||
|
|
||||||
// Secondary Units
|
// Secondary Units
|
||||||
u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit;
|
u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit;
|
||||||
|
|
||||||
drawSecondary(v,u);
|
drawSecondary(v,u);
|
||||||
|
|
||||||
// Time
|
// Time
|
||||||
drawTime();
|
drawTime();
|
||||||
|
|
||||||
// Waypoint name
|
// Waypoint name
|
||||||
drawWP();
|
drawWP();
|
||||||
|
|
||||||
//Sats
|
//Sats
|
||||||
if ( dat.age > 10 ) {
|
if ( dat.age > 10 ) {
|
||||||
if ( dat.age > 90 ) dat.age = '>90';
|
if ( dat.age > 90 ) dat.age = '>90';
|
||||||
drawSats('Age:'+dat.age);
|
drawSats('Age:'+dat.age);
|
||||||
}
|
}
|
||||||
else drawSats('Sats:'+dat.sats);
|
else drawSats('Sats:'+dat.sats);
|
||||||
|
|
||||||
|
/* else if (!BANGLEJS2) {
|
||||||
|
drawSats('Sats:'+dat.sats);
|
||||||
|
} else {
|
||||||
|
if (lf.fix) {
|
||||||
|
if(emulator)console.log("fix "+lf.fix);
|
||||||
|
drawSats('Sats:'+dat.sats);
|
||||||
|
} else {
|
||||||
|
if(emulator)console.log("inView: "+SATinView);
|
||||||
|
drawSats('View:' + SATinView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
g.reset();
|
g.reset();
|
||||||
g.drawImage(img,0,40);
|
g.drawImage(img,0,40);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawClock() {
|
function drawClock() {
|
||||||
|
@ -275,125 +298,143 @@ function drawClock() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawPrimary(n,u) {
|
function drawPrimary(n,u) {
|
||||||
|
if(emulator)console.log("drawPrimary: " + n +" "+ u);
|
||||||
// Primary Display
|
// Primary Display
|
||||||
|
|
||||||
var s=40; // Font size
|
var s=40; // Font size
|
||||||
var l=n.length;
|
var l=n.length;
|
||||||
|
|
||||||
if ( l <= 7 ) s=48;
|
if ( l <= 7 ) s=48;
|
||||||
if ( l <= 6 ) s=55;
|
if ( l <= 6 ) s=55;
|
||||||
if ( l <= 5 ) s=66;
|
if ( l <= 5 ) s=66;
|
||||||
if ( l <= 4 ) s=85;
|
if ( l <= 4 ) s=85;
|
||||||
if ( l <= 3 ) s=110;
|
if ( l <= 3 ) s=110;
|
||||||
|
|
||||||
buf.setFontAlign(0,-1); //Centre
|
buf.setFontAlign(0,-1); //Centre
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
buf.setFontVector(s);
|
buf.setFontVector(s);
|
||||||
buf.drawString(n,110,0);
|
buf.drawString(n,screenW_Half-10,0);
|
||||||
|
|
||||||
|
// Primary Units
|
||||||
|
s = 35; // Font size
|
||||||
// Primary Units
|
|
||||||
buf.setFontAlign(1,-1,3); //right
|
buf.setFontAlign(1,-1,3); //right
|
||||||
buf.setColor(2);
|
buf.setColor(2);
|
||||||
buf.setFontVector(35);
|
if (BANGLEJS2) s = 20;
|
||||||
buf.drawString(u,210,0);
|
buf.setFontVector(s);
|
||||||
|
buf.drawString(u,screenW-30,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSecondary(n,u) {
|
function drawSecondary(n,u) {
|
||||||
|
if(emulator)console.log("drawSecondary: " + n +" "+ u);
|
||||||
var s=180; // units X position
|
var xu = 180; // units X position
|
||||||
var l=n.length;
|
var l=n.length;
|
||||||
if ( l <= 5 ) s=155;
|
if ( l <= 5 ) xu = 155;
|
||||||
if ( l <= 4 ) s=125;
|
if ( l <= 4 ) xu = 125;
|
||||||
if ( l <= 3 ) s=100;
|
if ( l <= 3 ) xu = 100;
|
||||||
if ( l <= 2 ) s=65;
|
if ( l <= 2 ) xu = 65;
|
||||||
if ( l <= 1 ) s=35;
|
if ( l <= 1 ) xu = 35;
|
||||||
|
|
||||||
buf.setFontAlign(-1,1); //left, bottom
|
buf.setFontAlign(-1,1); //left, bottom
|
||||||
buf.setColor(1);
|
buf.setColor(1);
|
||||||
buf.setFontVector(45);
|
var s = 45; // Font size
|
||||||
buf.drawString(n,5,140);
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
|
buf.setFontVector(s);
|
||||||
|
buf.drawString(n,5,screenH_TwoThirds-20);
|
||||||
|
|
||||||
// Secondary Units
|
// Secondary Units
|
||||||
buf.setFontAlign(-1,1); //left, bottom
|
buf.setFontAlign(-1,1); //left, bottom
|
||||||
buf.setColor(2);
|
|
||||||
buf.setFontVector(30);
|
buf.setColor(2);
|
||||||
buf.drawString(u,s,135);
|
s = 30; // Font size
|
||||||
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
|
buf.setFontVector(s);
|
||||||
|
buf.drawString(u,xu - (BANGLEJS2*20),screenH_TwoThirds-25);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTime() {
|
function drawTime() {
|
||||||
var x, y;
|
var x, y;
|
||||||
|
|
||||||
if ( cfg.modeA == 2 ) {
|
if ( cfg.modeA == 2 ) {
|
||||||
x=120;
|
x = screenW_Half;
|
||||||
y=0;
|
y = 0;
|
||||||
buf.setFontAlign(0,-1);
|
buf.setFontAlign(0,-1);
|
||||||
buf.setFontVector(80);
|
buf.setFontVector(screenH_Third);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
x = 0;
|
x = 0;
|
||||||
y = 160;
|
y = screenH_TwoThirds;
|
||||||
buf.setFontAlign(-1,1);
|
buf.setFontAlign(-1,1);
|
||||||
|
if (!BANGLEJS2)
|
||||||
buf.setFont("7x11Numeric7Seg", 2);
|
buf.setFont("7x11Numeric7Seg", 2);
|
||||||
|
else
|
||||||
|
buf.setFont("6x8", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buf.setColor(0);
|
buf.setColor(0);
|
||||||
buf.drawString(time,x,y);
|
buf.drawString(time,x,y);
|
||||||
time = require("locale").time(new Date(),1);
|
time = require("locale").time(new Date(),1);
|
||||||
buf.setColor(3);
|
buf.setColor(3);
|
||||||
buf.drawString(time,x,y);
|
buf.drawString(time,x,y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawWP() {
|
function drawWP() { // from waypoints.json - see README.md
|
||||||
var nm = wp.name;
|
var nm = wp.name;
|
||||||
if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = '';
|
if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = '';
|
||||||
buf.setColor(2);
|
if (emulator) nm="waypoint";
|
||||||
|
buf.setColor(2);
|
||||||
|
var s = 20; // Font size
|
||||||
|
|
||||||
if ( cfg.modeA == 0 ) { // dist mode
|
if ( cfg.modeA == 0 ) { // dist mode
|
||||||
|
if(emulator)console.log("drawWP() 0: "+nm);
|
||||||
buf.setFontAlign(-1,1); //left, bottom
|
buf.setFontAlign(-1,1); //left, bottom
|
||||||
buf.setFontVector(20);
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
buf.drawString(nm.substring(0,6),72,160);
|
buf.setFontVector(s);
|
||||||
|
buf.drawString(nm.substring(0,6),72,screenH_TwoThirds-(BANGLEJS2 * 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cfg.modeA == 2 ) { // clock/large mode
|
if ( cfg.modeA == 2 ) { // clock/large mode
|
||||||
|
if(emulator)console.log("drawWP() 2: "+nm);
|
||||||
|
s = 55; // Font size
|
||||||
buf.setFontAlign(0,1); //left, bottom
|
buf.setFontAlign(0,1); //left, bottom
|
||||||
buf.setFontVector(55);
|
if (BANGLEJS2) s *= fontFactorB2;
|
||||||
buf.drawString(nm.substring(0,6),120,160);
|
buf.setFontVector(s);
|
||||||
|
buf.drawString(nm.substring(0,6),screenW_Half,screenH_TwoThirds-(BANGLEJS2 * 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSats(sats) {
|
function drawSats(sats) {
|
||||||
|
|
||||||
buf.setColor(3);
|
buf.setColor(3);
|
||||||
buf.setFont("6x8", 2);
|
buf.setFont("6x8", 2);
|
||||||
buf.setFontAlign(1,1); //right, bottom
|
buf.setFontAlign(1,1); //right, bottom
|
||||||
buf.drawString(sats,240,160);
|
buf.drawString(sats,screenW,screenH_TwoThirds);
|
||||||
|
|
||||||
|
s = 30; // Font size
|
||||||
|
if (BANGLEJS2) s = 18;
|
||||||
|
buf.setFontVector(s);
|
||||||
|
buf.setColor(2);
|
||||||
|
|
||||||
buf.setFontVector(30);
|
|
||||||
buf.setColor(2);
|
|
||||||
|
|
||||||
if ( cfg.modeA == 1 ) {
|
if ( cfg.modeA == 1 ) {
|
||||||
buf.drawString('A',240,140);
|
buf.drawString('A',screenW,140-(BANGLEJS2 * 40));
|
||||||
if ( showMax ) {
|
if ( showMax ) {
|
||||||
buf.setFontAlign(0,1); //centre, bottom
|
buf.setFontAlign(0,1); //centre, bottom
|
||||||
buf.drawString('MAX',120,164);
|
buf.drawString('MAX',120,164);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( cfg.modeA == 0 ) buf.drawString('D',240,140);
|
if ( cfg.modeA == 0 ) buf.drawString('D',screenW,140-(BANGLEJS2 * 40));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGPS(fix) {
|
function onGPS(fix) {
|
||||||
|
|
||||||
if ( emulator ) {
|
if ( emulator ) {
|
||||||
fix.fix = 1;
|
fix.fix = 1;
|
||||||
fix.speed = 10 + (Math.random()*5);
|
fix.speed = 10 + (Math.random()*5);
|
||||||
fix.alt = 354 + (Math.random()*50);
|
fix.alt = 354 + (Math.random()*50);
|
||||||
fix.lat = -38.92;
|
fix.lat = -38.92;
|
||||||
fix.lon = 175.7613350;
|
fix.lon = 175.7613350;
|
||||||
fix.course = 245;
|
fix.course = 245;
|
||||||
fix.satellites = 12;
|
fix.satellites = 12;
|
||||||
fix.time = new Date();
|
fix.time = new Date();
|
||||||
|
@ -402,7 +443,7 @@ function onGPS(fix) {
|
||||||
|
|
||||||
var m;
|
var m;
|
||||||
|
|
||||||
var sp = '---';
|
var sp = '---';
|
||||||
var al = '---';
|
var al = '---';
|
||||||
var di = '---';
|
var di = '---';
|
||||||
var age = '---';
|
var age = '---';
|
||||||
|
@ -411,6 +452,8 @@ function onGPS(fix) {
|
||||||
|
|
||||||
if (lf.fix) {
|
if (lf.fix) {
|
||||||
|
|
||||||
|
// if (BANGLEJS2 && !emulator) Bangle.removeListener('GPS-raw', onGPSraw);
|
||||||
|
|
||||||
// Smooth data
|
// Smooth data
|
||||||
if ( lf.smoothed !== 1 ) {
|
if ( lf.smoothed !== 1 ) {
|
||||||
if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed);
|
if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed);
|
||||||
|
@ -418,8 +461,8 @@ function onGPS(fix) {
|
||||||
lf.smoothed = 1;
|
lf.smoothed = 1;
|
||||||
if ( max.n <= 15 ) max.n++;
|
if ( max.n <= 15 ) max.n++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Speed
|
// Speed
|
||||||
if ( cfg.spd == 0 ) {
|
if ( cfg.spd == 0 ) {
|
||||||
m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
|
m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
|
||||||
|
@ -427,7 +470,7 @@ function onGPS(fix) {
|
||||||
cfg.spd_unit = m[2];
|
cfg.spd_unit = m[2];
|
||||||
}
|
}
|
||||||
else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units
|
else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units
|
||||||
|
|
||||||
if ( sp < 10 ) sp = sp.toFixed(1);
|
if ( sp < 10 ) sp = sp.toFixed(1);
|
||||||
else sp = Math.round(sp);
|
else sp = Math.round(sp);
|
||||||
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
|
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
|
||||||
|
@ -444,9 +487,9 @@ function onGPS(fix) {
|
||||||
// Age of last fix (secs)
|
// Age of last fix (secs)
|
||||||
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cfg.modeA == 1 ) {
|
if ( cfg.modeA == 1 ) {
|
||||||
if ( showMax )
|
if ( showMax )
|
||||||
drawFix({
|
drawFix({
|
||||||
speed:max.spd,
|
speed:max.spd,
|
||||||
sats:lf.satellites,
|
sats:lf.satellites,
|
||||||
|
@ -455,7 +498,7 @@ function onGPS(fix) {
|
||||||
age:age,
|
age:age,
|
||||||
fix:lf.fix
|
fix:lf.fix
|
||||||
}); // Speed and alt maximums
|
}); // Speed and alt maximums
|
||||||
else
|
else
|
||||||
drawFix({
|
drawFix({
|
||||||
speed:sp,
|
speed:sp,
|
||||||
sats:lf.satellites,
|
sats:lf.satellites,
|
||||||
|
@ -467,7 +510,7 @@ function onGPS(fix) {
|
||||||
}
|
}
|
||||||
if ( cfg.modeA == 0 ) {
|
if ( cfg.modeA == 0 ) {
|
||||||
// Show speed/distance
|
// Show speed/distance
|
||||||
if ( di <= 0 )
|
if ( di <= 0 )
|
||||||
drawFix({
|
drawFix({
|
||||||
speed:sp,
|
speed:sp,
|
||||||
sats:lf.satellites,
|
sats:lf.satellites,
|
||||||
|
@ -476,7 +519,7 @@ function onGPS(fix) {
|
||||||
age:age,
|
age:age,
|
||||||
fix:lf.fix
|
fix:lf.fix
|
||||||
}); // No WP selected
|
}); // No WP selected
|
||||||
else
|
else
|
||||||
drawFix({
|
drawFix({
|
||||||
speed:sp,
|
speed:sp,
|
||||||
sats:lf.satellites,
|
sats:lf.satellites,
|
||||||
|
@ -494,7 +537,7 @@ function onGPS(fix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setButtons(){
|
function setButtons(){
|
||||||
|
if (!BANGLEJS2) { // Buttons for Bangle.js
|
||||||
// Spd+Dist : Select next waypoint
|
// Spd+Dist : Select next waypoint
|
||||||
setWatch(function(e) {
|
setWatch(function(e) {
|
||||||
var dur = e.time - e.lastTime;
|
var dur = e.time - e.lastTime;
|
||||||
|
@ -506,10 +549,10 @@ function setButtons(){
|
||||||
else nxtWp(1); // Spd+Dist or Clock mode - Select next waypoint
|
else nxtWp(1); // Spd+Dist or Clock mode - Select next waypoint
|
||||||
onGPS(lf);
|
onGPS(lf);
|
||||||
}, BTN1, { edge:"falling",repeat:true});
|
}, BTN1, { edge:"falling",repeat:true});
|
||||||
|
|
||||||
// Power saving on/off
|
// Power saving on/off
|
||||||
setWatch(function(e){
|
setWatch(function(e){
|
||||||
pwrSav=!pwrSav;
|
pwrSav=!pwrSav;
|
||||||
if ( pwrSav ) {
|
if ( pwrSav ) {
|
||||||
LED1.reset();
|
LED1.reset();
|
||||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||||
|
@ -522,15 +565,15 @@ function setButtons(){
|
||||||
LED1.set();
|
LED1.set();
|
||||||
}
|
}
|
||||||
}, BTN2, {repeat:true,edge:"falling"});
|
}, BTN2, {repeat:true,edge:"falling"});
|
||||||
|
|
||||||
// Toggle between alt or dist
|
// Toggle between alt or dist
|
||||||
setWatch(function(e){
|
setWatch(function(e){
|
||||||
cfg.modeA = cfg.modeA+1;
|
cfg.modeA = cfg.modeA+1;
|
||||||
if ( cfg.modeA > 2 ) cfg.modeA = 0;
|
if ( cfg.modeA > 2 ) cfg.modeA = 0;
|
||||||
savSettings();
|
savSettings();
|
||||||
onGPS(lf);
|
onGPS(lf);
|
||||||
}, BTN3, {repeat:true,edge:"falling"});
|
}, BTN3, {repeat:true,edge:"falling"});
|
||||||
|
|
||||||
// Touch left screen to toggle display
|
// Touch left screen to toggle display
|
||||||
setWatch(function(e){
|
setWatch(function(e){
|
||||||
cfg.primSpd = !cfg.primSpd;
|
cfg.primSpd = !cfg.primSpd;
|
||||||
|
@ -538,11 +581,42 @@ function setButtons(){
|
||||||
onGPS(lf); // Update display
|
onGPS(lf); // Update display
|
||||||
}, BTN4, {repeat:true,edge:"falling"});
|
}, BTN4, {repeat:true,edge:"falling"});
|
||||||
|
|
||||||
|
} else { // Buttons for Bangle.js 2
|
||||||
|
setWatch(function(e){ // Bangle.js BTN3
|
||||||
|
cfg.modeA = cfg.modeA+1;
|
||||||
|
if ( cfg.modeA > 2 ) cfg.modeA = 0;
|
||||||
|
if(emulator)console.log("cfg.modeA="+cfg.modeA);
|
||||||
|
savSettings();
|
||||||
|
onGPS(lf);
|
||||||
|
}, BTN1, {repeat:true,edge:"falling"});
|
||||||
|
|
||||||
|
/* Bangle.on('tap', function(data) { // data - {dir, double, x, y, z}
|
||||||
|
cfg.primSpd = !cfg.primSpd;
|
||||||
|
if(emulator)console.log("!cfg.primSpd");
|
||||||
|
}); */
|
||||||
|
|
||||||
|
/* Bangle.on('swipe', function(dir) {
|
||||||
|
if (dir < 0) { // left: Bangle.js BTN3
|
||||||
|
cfg.modeA = cfg.modeA+1;
|
||||||
|
if ( cfg.modeA > 2 ) cfg.modeA = 0;
|
||||||
|
if(emulator)console.log("cfg.modeA="+cfg.modeA);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // right: Bangle.js BTN4
|
||||||
|
cfg.primSpd = !cfg.primSpd;
|
||||||
|
if(emulator)console.log("!cfg.primSpd");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
savSettings();
|
||||||
|
onGPS(lf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateClock() {
|
function updateClock() {
|
||||||
if (!canDraw) return;
|
if (!canDraw) return;
|
||||||
drawTime();
|
drawTime();
|
||||||
g.reset();
|
g.reset();
|
||||||
g.drawImage(img,0,40);
|
g.drawImage(img,0,40);
|
||||||
if ( emulator ) {max.spd++;max.alt++;}
|
if ( emulator ) {max.spd++;max.alt++;}
|
||||||
|
@ -573,21 +647,21 @@ function setLpMode(m) {
|
||||||
|
|
||||||
// =Main Prog
|
// =Main Prog
|
||||||
|
|
||||||
// Read settings.
|
// Read settings.
|
||||||
let cfg = require('Storage').readJSON('speedalt.json',1)||{};
|
let cfg = require('Storage').readJSON('speedalt.json',1)||{};
|
||||||
|
|
||||||
cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed
|
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.spd_unit = cfg.spd_unit||'km/h'; // Displayed speed unit
|
||||||
cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions.
|
cfg.alt = cfg.alt||1;// Multiplier for altitude unit conversions. (feet:'0.3048')
|
||||||
cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units
|
cfg.alt_unit = cfg.alt_unit||'meter'; // Displayed altitude units ('feet')
|
||||||
cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions.
|
cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions.
|
||||||
cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units
|
cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units
|
||||||
cfg.colour = cfg.colour||0; // Colour scheme.
|
cfg.colour = cfg.colour||0; // Colour scheme.
|
||||||
cfg.wp = cfg.wp||0; // Last selected waypoint for dist
|
cfg.wp = cfg.wp||0; // Last selected waypoint for dist
|
||||||
cfg.modeA = cfg.modeA||0; // 0 = [D]ist, 1 = [A]ltitude, 2 = [C]lock
|
cfg.modeA = cfg.modeA||1; // 0 = [D]ist, 1 = [A]ltitude, 2 = [C]lock
|
||||||
cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary
|
cfg.primSpd = cfg.primSpd||1; // 1 = Spd in primary, 0 = Spd in secondary
|
||||||
|
|
||||||
cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
|
cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt;
|
||||||
cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
|
cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt;
|
||||||
|
|
||||||
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
|
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
|
||||||
|
@ -602,29 +676,43 @@ Colour Pallet Idx
|
||||||
2 : Units
|
2 : Units
|
||||||
3 : Sats
|
3 : Sats
|
||||||
*/
|
*/
|
||||||
|
const background = 0; // g.theme.bg = 0xFFFF = gelb!?
|
||||||
var img = {
|
var img = {
|
||||||
width:buf.getWidth(),
|
width:buf.getWidth(),
|
||||||
height:buf.getHeight(),
|
height:buf.getHeight(),
|
||||||
bpp:2,
|
bpp:2,
|
||||||
buffer:buf.buffer,
|
buffer:buf.buffer,
|
||||||
palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB])
|
palette:new Uint16Array([background,0x4FE0,0xEFE0,0x07DB]) // "Default"
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]);
|
if ( cfg.colour == 1 ) img.palette = new Uint16Array([background,0xFFFF,0xFFF6,0xDFFF]); // "Hi contrast"
|
||||||
if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]);
|
if ( cfg.colour == 2 ) img.palette = new Uint16Array([background,0xFF800,0xFAE0,0xF813]); // "Night"
|
||||||
|
|
||||||
var SCREENACCESS = {
|
var SCREENACCESS = {
|
||||||
withApp:true,
|
withApp:true,
|
||||||
request:function(){this.withApp=false;stopDraw();},
|
request:function(){this.withApp=false;stopDraw();},
|
||||||
release:function(){this.withApp=true;startDraw();}
|
release:function(){this.withApp=true;startDraw();}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bangle.on('lcdPower',function(on) {
|
Bangle.on('lcdPower',function(on) {
|
||||||
if (!SCREENACCESS.withApp) return;
|
if (!SCREENACCESS.withApp) return;
|
||||||
if (on) startDraw();
|
if (on) startDraw();
|
||||||
else stopDraw();
|
else stopDraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
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);
|
||||||
|
*/
|
||||||
|
|
||||||
var gpssetup;
|
var gpssetup;
|
||||||
try {
|
try {
|
||||||
gpssetup = require("gpssetup");
|
gpssetup = require("gpssetup");
|
||||||
|
@ -634,8 +722,6 @@ try {
|
||||||
|
|
||||||
// All set up. Lets go.
|
// All set up. Lets go.
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
onGPS(lf);
|
onGPS(lf);
|
||||||
Bangle.setGPSPower(1);
|
Bangle.setGPSPower(1);
|
||||||
|
|
||||||
|
@ -650,3 +736,5 @@ Bangle.on('GPS', onGPS);
|
||||||
|
|
||||||
setButtons();
|
setButtons();
|
||||||
setInterval(updateClock, 10000);
|
setInterval(updateClock, 10000);
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
"id": "speedalt",
|
"id": "speedalt",
|
||||||
"name": "GPS Adventure Sports",
|
"name": "GPS Adventure Sports",
|
||||||
"shortName": "GPS Adv Sport",
|
"shortName": "GPS Adv Sport",
|
||||||
"version": "0.10",
|
"version": "0.11",
|
||||||
"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.",
|
"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.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,outdoors",
|
"tags": "tool,outdoors",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
|
|
|
@ -49,14 +49,14 @@
|
||||||
'': {'title': 'Units'},
|
'': {'title': 'Units'},
|
||||||
'< Back': function() { E.showMenu(appMenu); },
|
'< Back': function() { E.showMenu(appMenu); },
|
||||||
'default (spd)' : function() { setUnits(0,''); },
|
'default (spd)' : function() { setUnits(0,''); },
|
||||||
'Kph (spd)' : function() { setUnits(1,'kph'); },
|
'km/h (spd)' : function() { setUnits(1,'km/h'); },
|
||||||
'Knots (spd)' : function() { setUnits(1.852,'kts'); },
|
'Knots (spd)' : function() { setUnits(1.852,'kts'); },
|
||||||
'Mph (spd)' : function() { setUnits(1.60934,'mph'); },
|
'Mph (spd)' : function() { setUnits(1.60934,'mph'); },
|
||||||
'm/s (spd)' : function() { setUnits(3.6,'m/s'); },
|
'm/s (spd)' : function() { setUnits(3.6,'m/s'); },
|
||||||
'Km (dist)' : function() { setUnitsDist(1000,'km'); },
|
'Km (dist)' : function() { setUnitsDist(1000,'km'); },
|
||||||
'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); },
|
'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); },
|
||||||
'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); },
|
'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); },
|
||||||
'Meters (alt)' : function() { setUnitsAlt(1,'m'); },
|
'Meters (alt)' : function() { setUnitsAlt(1,'meter'); },
|
||||||
'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); }
|
'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|