From e9c7e2ede72369ed0b2ebb7e9401484bbd76807b Mon Sep 17 00:00:00 2001 From: OmegaRogue Date: Wed, 13 Jan 2021 14:47:50 +0100 Subject: [PATCH 001/181] =?UTF-8?q?=F0=9F=94=80Merge=20branch=20master=20f?= =?UTF-8?q?rom=20upstream=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create HRV.js * Add files via upload * Create hrv-icon.js * Delete hrv-icon.js * Create HRV-icon.js * Update apps.json * Update apps.json * Update apps.json * Rename HRV.js to app.js * Rename HRV-icon.js to app-icon.js * Update apps.json * Update apps.json * Update apps.json * Update apps.json * add hardalarm * fix app description * fix same name for storage in apps.json * Add dtlaunch * Update app.js updated with declutter and additional options * Create ChangeLog * Update app.js * Create README.md * Update README.md * Update app.js * Update app.js * Update README.md * Update app.js updated so that now it takes readings every 3 minutes in continous mode and also the sample range has been extended to 30 seconds rather than just 20 * tweak * Create app.js * Add files via upload * Delete icon.png * Add files via upload * Update apps.json * Delete app-icon.png * Create app-icon.js * Update app-icon.js * Update app-icon.js * Update app-icon.js * Update app-icon.js * Add files via upload * Update app-icon.js * Update app.js * Create README.md * Update apps.json * Update README.md * Update apps.json * Update apps.json * Update app-icon.js * Update apps.json * Update app-icon.js * Update app-icon.js * Update apps.json * Update app-icon.js * Update apps.json * Update app-icon.js * Update apps.json * Update apps.json * Update README.md * Update app.js * Update README.md * Update app.js * Improve readibility to README.md * bump version * activepedom 0.05: Fix default step/distance display if it hasn't been set up first * Add files via upload * Update apps.json * Update apps.json * Update apps.json * Update app-icon.js * Update app-icon.js * Update app-icon.js * Delete app-icon.png * Add files via upload * Delete app-icon.png * Add files via upload * Update app-icon.js * Delete app-icon.png * Add files via upload * Update apps.json * Update README.md * Update apps.json * Update app-icon.js * Update app-icon.js * Update apps.json * Create hrmexp.js * Delete app.js * Update apps.json * Update apps.json * Update app-icon.js * Update app-icon.js * Update apps.json * Update app-icon.js * Update hrmexp.js * Update hrmexp.js * Update apps.json * Create README.md * Add files via upload * Create app.js * Create app-icon.js * Update apps.json * Update apps.json * Update apps.json * Update apps.json * Update apps.json * remove remove * Update README.md * Update README.md * Update README.md * Added Interface file to allow downloading (and deletion!) of BangleRun data files * added WIDGETS.activepedom.getSteps() * Update ChangeLog 0.06: Added WIDGETS.activepedom.getSteps() * bump versions * Create interface.html * Update apps.json * Update README.md * Add files via upload * Update and rename breather_settings.txt to settings.js * Update apps.json * Update app.js * Update app.js * Add files via upload * Update README.md * Delete readme_gif.gif * Update README.md * Update apps.json * Update README.md * Update README.md * Update README.md * Update README.md * Update app.js * Update app.js * Update apps.json * Rename settings.js to settings.json * feat(app): Add "Lazy Clock" app * update multiclock * update dtlaunch app * minor tweak for faster writes (best to end with a newline anyway) Co-authored-by: Gordon Williams Co-authored-by: Ben Jabituya <74158243+jabituyaben@users.noreply.github.com> Co-authored-by: jamespsteinberg@gmail.com Co-authored-by: jeffmer Co-authored-by: krichtof Co-authored-by: hughbarney Co-authored-by: Olly Cross Co-authored-by: Jeff Magee --- apps.json | 108 ++++++++++- apps/HRV/ChangeLog | 3 + apps/HRV/README.md | 18 ++ apps/HRV/app-icon.js | 1 + apps/HRV/app.js | 303 +++++++++++++++++++++++++++++++ apps/HRV/hrv.png | Bin 0 -> 749 bytes apps/activepedom/ChangeLog | 4 +- apps/activepedom/widget.js | 9 +- apps/banglerun/interface.html | 216 ++++++++++++++++++++++ apps/banglerun/src/gps.ts | 4 + apps/banglerun/src/log.ts | 5 +- apps/breath/README.md | 12 ++ apps/breath/app-icon.js | 1 + apps/breath/app-icon.png | Bin 0 -> 1336 bytes apps/breath/app.js | 222 ++++++++++++++++++++++ apps/breath/settings.json | 1 + apps/dtlaunch/ChangeLog | 4 + apps/dtlaunch/README.md | 16 ++ apps/dtlaunch/app-icon.js | 1 + apps/dtlaunch/app.js | 73 ++++++++ apps/dtlaunch/icon.png | Bin 0 -> 305 bytes apps/dtlaunch/screenshot.jpg | Bin 0 -> 49806 bytes apps/edisonsball/README.md | 5 + apps/edisonsball/app-icon.js | 1 + apps/edisonsball/app-icon.png | Bin 0 -> 821 bytes apps/edisonsball/app.js | 149 +++++++++++++++ apps/gpsrec/ChangeLog | 2 +- apps/hardalarm/ChangeLog | 1 + apps/hardalarm/app-icon.js | 1 + apps/hardalarm/app.js | 112 ++++++++++++ apps/hardalarm/app.png | Bin 0 -> 1909 bytes apps/hardalarm/boot.js | 25 +++ apps/hardalarm/hardalarm.js | 127 +++++++++++++ apps/hardalarm/widget.js | 11 ++ apps/hrrawexp/README.md | 13 ++ apps/hrrawexp/app-icon.js | 1 + apps/hrrawexp/app-icon.png | Bin 0 -> 826 bytes apps/hrrawexp/app.js | 97 ++++++++++ apps/hrrawexp/interface.html | 54 ++++++ apps/lazyclock/ChangeLog | 1 + apps/lazyclock/README.md | 26 +++ apps/lazyclock/lazyclock-app.js | 236 ++++++++++++++++++++++++ apps/lazyclock/lazyclock-icon.js | 1 + apps/lazyclock/lazyclock.png | Bin 0 -> 1229 bytes apps/multiclock/ChangeLog | 2 + apps/multiclock/ana.min.js | 2 - apps/multiclock/clock.js | 15 +- apps/smtswch/README.md | 1 + 48 files changed, 1866 insertions(+), 18 deletions(-) create mode 100644 apps/HRV/ChangeLog create mode 100644 apps/HRV/README.md create mode 100644 apps/HRV/app-icon.js create mode 100644 apps/HRV/app.js create mode 100644 apps/HRV/hrv.png create mode 100644 apps/banglerun/interface.html create mode 100644 apps/breath/README.md create mode 100644 apps/breath/app-icon.js create mode 100644 apps/breath/app-icon.png create mode 100644 apps/breath/app.js create mode 100644 apps/breath/settings.json create mode 100644 apps/dtlaunch/ChangeLog create mode 100644 apps/dtlaunch/README.md create mode 100644 apps/dtlaunch/app-icon.js create mode 100644 apps/dtlaunch/app.js create mode 100644 apps/dtlaunch/icon.png create mode 100644 apps/dtlaunch/screenshot.jpg create mode 100644 apps/edisonsball/README.md create mode 100644 apps/edisonsball/app-icon.js create mode 100644 apps/edisonsball/app-icon.png create mode 100644 apps/edisonsball/app.js create mode 100644 apps/hardalarm/ChangeLog create mode 100644 apps/hardalarm/app-icon.js create mode 100644 apps/hardalarm/app.js create mode 100644 apps/hardalarm/app.png create mode 100644 apps/hardalarm/boot.js create mode 100644 apps/hardalarm/hardalarm.js create mode 100644 apps/hardalarm/widget.js create mode 100644 apps/hrrawexp/README.md create mode 100644 apps/hrrawexp/app-icon.js create mode 100644 apps/hrrawexp/app-icon.png create mode 100644 apps/hrrawexp/app.js create mode 100644 apps/hrrawexp/interface.html create mode 100644 apps/lazyclock/ChangeLog create mode 100644 apps/lazyclock/README.md create mode 100644 apps/lazyclock/lazyclock-app.js create mode 100644 apps/lazyclock/lazyclock-icon.js create mode 100644 apps/lazyclock/lazyclock.png delete mode 100644 apps/multiclock/ana.min.js diff --git a/apps.json b/apps.json index 0dd96d3fe..68e637644 100644 --- a/apps.json +++ b/apps.json @@ -1341,7 +1341,7 @@ "name": "Active Pedometer", "shortName":"Active Pedometer", "icon": "app.png", - "version":"0.04", + "version":"0.06", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", "tags": "outdoors,widget", "readme": "README.md", @@ -1554,7 +1554,8 @@ "shortName": "BangleRun", "icon": "banglerun.png", "version": "0.05", - "description": "An app for running sessions.", + "interface": "interface.html", + "description": "An app for running sessions. Displays info and logs your run for later viewing.", "tags": "run,running,fitness,outdoors", "allow_emulator": false, "storage": [ @@ -2138,12 +2139,12 @@ { "id": "multiclock", "name": "Multi Clock", "icon": "multiclock.png", - "version":"0.07", + "version":"0.08", "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", "readme": "README.md", "tags": "clock", "type":"clock", - "allow_emulator":false, + "allow_emulator":true, "storage": [ {"name":"multiclock.app.js","url":"clock.js"}, {"name":"big.face.js","url":"big.js"}, @@ -2478,5 +2479,104 @@ {"name":"gmeter.app.js","url":"app.js"}, {"name":"gmeter.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "dtlaunch", + "name": "Desktop Launcher", + "icon": "icon.png", + "version":"0.03", + "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", + "readme": "README.md", + "tags": "tool,system,launcher", + "type":"launch", + "storage": [ + {"name":"dtlaunch.app.js","url":"app.js"}, + {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "HRV", + "name": "Heart Rate Variability monitor", + "shortName":"HRV monitor", + "icon": "hrv.png", + "version":"0.03", + "description": "Heart Rate Variability monitor, see Readme for more info", + "tags": "", + "readme": "README.md", + "storage": [ + {"name":"HRV.app.js","url":"app.js"}, + {"name":"HRV.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "hardalarm", + "name": "Hard Alarm", + "shortName":"HardAlarm", + "icon": "app.png", + "version":"0.01", + "description": "Make sure you wake up! Count to the right number to turn off the alarm", + "tags": "tool,alarm,widget", + "storage": [ + {"name":"hardalarm.app.js","url":"app.js"}, + {"name":"hardalarm.boot.js","url":"boot.js"}, + {"name":"hardalarm.js","url":"hardalarm.js"}, + {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, + {"name":"hardalarm.wid.js","url":"widget.js"} + ], + "data": [ + {"name":"hardalarm.json"} + ] +}, +{ "id": "edisonsball", + "name": "Edison's Ball", + "shortName":"Edison's Ball", + "icon": "app-icon.png", + "version":"0.01", + "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", + "tags": "", + "readme": "README.md", + "storage": [ + {"name":"edisonsball.app.js","url":"app.js"}, + {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "hrrawexp", + "name": "HRM Data Exporter", + "shortName":"HRM Data Exporter", + "icon": "app-icon.png", + "version":"0.01", + "description": "export raw hrm signal data to a csv file", + "tags": "", + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"hrrawexp.app.js","url":"app.js"}, + {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "breath", + "name": "Breathing App", + "shortName":"Breathing App", + "icon": "app-icon.png", + "version":"0.01", + "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", + "tags": "tools,health", + "readme": "README.md", + "storage": [ + {"name":"breath.app.js","url":"app.js"}, + {"name":"breath.settings.json","url":"settings.json"}, + {"name":"breath.img","url":"app-icon.js","evaluate":true} + ] +}, +{ "id": "lazyclock", + "name": "Lazy Clock", + "icon": "lazyclock.png", + "version":"0.01", + "readme": "README.md", + "description": "Tells the time, roughly", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, + {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} + ] } ] diff --git a/apps/HRV/ChangeLog b/apps/HRV/ChangeLog new file mode 100644 index 000000000..447062294 --- /dev/null +++ b/apps/HRV/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Added options to either run as a one-off reading, or a continuous mode to log data until the watch is reset +0.03: Add RMSSD recording diff --git a/apps/HRV/README.md b/apps/HRV/README.md new file mode 100644 index 000000000..1cfb351d3 --- /dev/null +++ b/apps/HRV/README.md @@ -0,0 +1,18 @@ +Monitor Heart Rate Variability using the Bangle.JS +=================================================== + +One-time mode: +------------- + +This will take a HRV measurement over a single approx 30 second period. It will also provide you with a HR reading based on the post-processing of the signal. + +HRV metrics displayed are currently RMSSD (Root Mean Square of the Successive Differences) and also SDNN (standard deviation of NN intervals). + +Continuous mode: +---------------- + +This will continually take measurements over 30 second periods every 3 and half minutes and log them to a CSV file on the Bangle until the watch is reset; this file can then be reviewed in Excel or other apps. The log file is reset each time you restart and select this mode to save on storage. The log file is just 1 line per each 3 minute cycle showing: timestamp, HR, SDNN, RMSSD, sample count, Temp (uncalibrated CPU temp), and movement based on the accelerometer. The additional metrics aside from the HRM data are useful in analysing sleep. + +Note that in both modes, if the watch seems unresponsive, it's processing data and if you continue to hold the reset button it will eventually restart. + +If your sample count is less than around 5 samples and/or the readings don’t look right, try repositioning the watch and try again - you can use the HR monitor app to confirm fitting. diff --git a/apps/HRV/app-icon.js b/apps/HRV/app-icon.js new file mode 100644 index 000000000..1125d437a --- /dev/null +++ b/apps/HRV/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAYAA8AAAA8AA8ABgB8AA8ADwD4AA8AH4HwAA8AP8PgAA8Af+fAAA8A//+AAA8B+P8AAA8D4H4AAA8HwDwAAA8PgBgAAA8PAAAAAA8GAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA/////+AA//////AAf/////AAP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) diff --git a/apps/HRV/app.js b/apps/HRV/app.js new file mode 100644 index 000000000..5919d1d79 --- /dev/null +++ b/apps/HRV/app.js @@ -0,0 +1,303 @@ +var option = null; + +//debugging or analysis files +var logfile = require("Storage").open("HRV_log.csv", "w"); + +logfile = require("Storage").open("HRV_log.csv", "a"); + +var csv = [ + "time", + "sample count", + "HR", + "SDNN", + "RMSSD", + "Temp", + "movement" + ]; +logfile.write(csv.join(",")+"\n"); + +var debugging = true; + +var first_signals = 0; // ignore the first several signals +var heartrate = []; +var BPM_array = []; +var raw_HR_array = new Float32Array(1536); +var alternate_array = new Float32Array(3072); +var pulse_array = []; +var pulsecount = 0; +var cutoff_threshold = 0.5; +var sample_frequency = 51.6; +var gap_threshold = 0.15; +var hr_min = 40; +var hr_max = 160; +var movement = 0; + +function storeMyData(data, file_type) { + log = raw_HR_array; + // shift elements backwards - note the 4, because a Float32 is 4 bytes + log.set(new Float32Array(log.buffer, 4 /*bytes*/)); + // add ad final element + log[log.length - 1] = data; +} + +function average(samples) { + var sum = 0; + for (var i = 0; i < samples.length; i++) { + sum += parseFloat(samples[i]); + } + var avg = sum / samples.length; + return avg; +} + +function StandardDeviation (array) { + const n = array.length; + const mean = array.reduce((a, b) => a + b) / n; + return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n); +} + +function turn_off() { + Bangle.setHRMPower(0); + + var accel = setInterval(function () { + movement = movement + Bangle.getAccel().diff; + }, 1000); + + g.clear(); + g.drawString("processing 1/5", 120, 120); + + rolling_average(raw_HR_array,5); + g.clear(); + g.drawString("processing 2/5", 120, 120); + + upscale(); + g.clear(); + g.drawString("processing 3/5", 120, 120); + + rolling_average(alternate_array,5); + g.clear(); + g.drawString("processing 4/5", 120, 120); + + apply_cutoff(); + find_peaks(); + + g.clear(); + g.drawString("processing 5/5", 120, 120); + + calculate_HRV(); +} + +function bernstein(A, B, C, D, E, t) { + s = 1 - t; + x = (A * Math.pow(s, 4)) + (B * 4 * Math.pow(s, 3) * t) + (C * 6 * s * s * t * t) + + (D * 4 * s * Math.pow(t, 3)) + (E * Math.pow(t, 4)); + return x; +} + +function upscale() { + var index = 0; + for (let i = raw_HR_array.length - 1; i > 5; i -= 5) { + p0 = raw_HR_array[i]; + p1 = raw_HR_array[i - 1]; + p2 = raw_HR_array[i - 2]; + p3 = raw_HR_array[i - 3]; + p4 = raw_HR_array[i - 4]; + for (let T = 0; T < 100; T += 10) { + x = T / 100; + D = bernstein(p0, p1, p2, p3, p4, x); + alternate_array[index] = D; + index++; + } + } +} + +function rolling_average(values, count) { + var temp_array = []; + + for (let i = 0; i < values.length; i++) { + temp_array = []; + for (let x = 0; x < count; x++) + temp_array.push(values[i + x]); + + values[i] = average(temp_array); + } +} + +function apply_cutoff() { + var x; + for (let i = 0; i < alternate_array.length; i++) { + x = alternate_array[i]; + if (x < cutoff_threshold) + x = cutoff_threshold; + alternate_array[i] = x; + } +} + +function find_peaks() { + var previous; + var previous_slope = 0; + var slope; + var gap_size = 0; + var temp_array = []; + + for (let i = 0; i < alternate_array.length; i++) { + if (previous == null) + previous = alternate_array[i]; + slope = alternate_array[i] - previous; + if (slope * previous_slope < 0) { + if (gap_size > 30) { + pulse_array.push(gap_size); + gap_size = 0; + } + } + else { + gap_size++; + } + previous_slope = slope; + previous = alternate_array[i]; + } +} + +function RMSSD(samples){ + var sum = 0; + var square = 0; + var data = []; + var value = 0; + + for (let i = 0; i < samples.length-1; i++) { + value = Math.abs(samples[i]-samples[i+1])*((1 / (sample_frequency * 2)) * 1000); + data.push(value); + } + + for (let i = 0; i < data.length; i++) { + square = data[i] * data[i]; + Math.round(square); + sum += square; + } + + var meansquare = sum/data.length; + var RMS = Math.sqrt(meansquare); + RMS = parseInt(RMS); + return RMS; +} + +function calculate_HRV() { + var gap_average = average(pulse_array); + var temp_array = []; + var gap_max = (1 + gap_threshold) * gap_average; + var gap_min = (1 - gap_threshold) * gap_average; + for (let i = 0; i < pulse_array.length; i++) { + if (pulse_array[i] > gap_min && pulse_array[i] < gap_max) + temp_array.push(pulse_array[i]); + } + gap_average = average(temp_array); + var calculatedHR = (sample_frequency*60)/(gap_average/2); + if(option == 0) + g.flip(); + g.clear(); + //var display_stdv = StandardDeviation(pulse_array).toFixed(1); + var SDNN = (StandardDeviation(temp_array) * (1 / (sample_frequency * 2) * 1000)).toFixed(0); + var RMS_SD = RMSSD(temp_array); + g.drawString("SDNN:" + SDNN + +"\nRMSSD:" + RMS_SD + + "\nHR:" + calculatedHR.toFixed(0) + +"\nSample Count:" + temp_array.length, 120, 120); + + if(option == 0){ + Bangle.buzz(500,1); + clearInterval(routine); + } + + else{ + var csv = [ + 0|getTime(), + temp_array.length, + calculatedHR.toFixed(0), + SDNN, + RMS_SD, + E.getTemperature(), + movement.toFixed(5) + ]; + logfile.write(csv.join(",")+"\n"); + + movement = 0; + // for (let i = 0; i < raw_HR_array.length; i++) { + // raw_HR_array[i] = null; + //} + } +} + +function btn1Pressed() { + if(option === null){ + clearInterval(accel); + g.clear(); + g.drawString("one-off assessment", 120, 120); + option = 0; + Bangle.setHRMPower(1); + } +} + +function btn3Pressed() { + if(option === null){ + logfile.write(""); //reset HRV log + clearInterval(accel); + g.clear(); + g.drawString("continuous mode", 120, 120); + option = 1; + Bangle.setHRMPower(1); + } +} + +var routine = setInterval(function () { + clearInterval(accel); + first_signals = 0; // ignore the first several signals + pulsecount = 0; + BPM_array = []; + heartrate = []; + pulse_array = []; + Bangle.setHRMPower(1); +}, 180000); + +var accel = setInterval(function () { + movement = movement + Bangle.getAccel().diff; +}, 1000); + +g.clear(); +g.setColor("#00ff7f"); +g.setFont("6x8", 2); +g.setFontAlign(-1,1); +g.drawString("continuous", 120, 210); +g.setFontAlign(-1,1); +g.drawString("one-time", 140, 50); + +g.setColor("#ffffff"); +g.setFontAlign(0, 0); // center font +g.drawString("check app README", 120, 120); +g.drawString("for more info", 120, 140); + +setWatch(btn1Pressed, BTN1, {repeat:true}); +setWatch(btn3Pressed, BTN3, {repeat:true}); + +Bangle.on('HRM', function (hrm) { + if(option == 0) + g.flip(); + if (first_signals < 3) { + g.clear(); + g.drawString("setting up...\nremain still " + first_signals * 20 + "%", 120, 120); + first_signals++; + } + else { + BPM_array = hrm.raw; + if(hrm.bpm > hr_min && hrm.bpm < hr_max) + heartrate.push(hrm.bpm); + if (pulsecount < 7) { + for (let i = 0; i < 256; i++) { + storeMyData(BPM_array[i], 0); + } + g.clear(); + g.drawString("logging: " + ((pulsecount/6)*100).toFixed(0) + "%", 120, 120); + } + if(pulsecount == 6) + turn_off(); + pulsecount++; + } +}); diff --git a/apps/HRV/hrv.png b/apps/HRV/hrv.png new file mode 100644 index 0000000000000000000000000000000000000000..7fdbce610555f4fb39ed1106c2b7e61e6a6c339b GIT binary patch literal 749 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf00v@9M??Vs0RI60 zpuMM)0007TNkl@9@iHZ0c8tFs1 z;DQ?>iW|7pbhW3aoa)na?{&Pl9H^}AP>2u0n)uJd0QT1c_1)o0_*??$d1EMi) zAQp=$^Z8tv&1On88inXf*sX&sHXrA*a&`_DG1@1nT321|bCl^L!<{-AD0b@p4Dna+-|qQpH8O@#h=e-lFeofUK61~NX5WByj(69SuU5bg^}%cOT1q1 zhyMx1Q=vgf$G|-I@_0OCygBSs#ad0l7_QZq15I2`(_-64`Xb{pf`0+?2VmJ)wESE|pgI8BRpVtc3`Nm;$FEj|d zVDRJpeji3|(Ag~%3I-o9dTk#J2AbmWIO_RJp+P8%!H?4dbm*++ayf(NKM4&&X$*cm z6bfnILZ{Pt)=WMm`A#ev*??$d1EP@)h(h*ex`pyAW;CyO~_P f5RGg=EQ<0DVjKyPk>G_d00000NkvXXu0mjf48luD literal 0 HcmV?d00001 diff --git a/apps/activepedom/ChangeLog b/apps/activepedom/ChangeLog index ca26a648a..9dc698827 100644 --- a/apps/activepedom/ChangeLog +++ b/apps/activepedom/ChangeLog @@ -1,4 +1,6 @@ 0.01: New Widget! 0.02: Distance calculation and display 0.03: Data logging and display -0.04: Steps are set to 0 in log on new day \ No newline at end of file +0.04: Steps are set to 0 in log on new day +0.05: Fix default step/distance display if it hasn't been set up first +0.06: Added WIDGETS.activepedom.getSteps() diff --git a/apps/activepedom/widget.js b/apps/activepedom/widget.js index ed91a4cfd..f67017014 100644 --- a/apps/activepedom/widget.js +++ b/apps/activepedom/widget.js @@ -68,6 +68,8 @@ 'stepSensitivity' : 80, 'stepGoal' : 10000, 'stepLength' : 75, + 'lineOne' : "Distance", + 'lineTwo' : "Steps", }; if (!settings) { loadSettings(); } return (key in settings) ? settings[key] : DEFAULTS[key]; @@ -160,7 +162,6 @@ if (active == 1) g.setColor(0x07E0); //green else g.setColor(0xFFFF); //white g.setFont("6x8", 2); - if (setting('lineOne') == 'Steps') { g.drawString(kFormatterSteps(stepsCounted),this.x+1,this.y); //first line, big number, steps } @@ -227,6 +228,6 @@ setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive) timerStoreData = setInterval(storeData, storeDataInterval); //store data regularly - //Add widget - WIDGETS["activepedom"]={area:"tl",width:width,draw:draw}; -})(); \ No newline at end of file + //Add widget, use: WIDGETS.activepedom.getSteps() inside another App to return todays step count + WIDGETS["activepedom"]={area:"tl",width:width,draw:draw, getSteps:()=>stepsCounted}; +})(); diff --git a/apps/banglerun/interface.html b/apps/banglerun/interface.html new file mode 100644 index 000000000..177904077 --- /dev/null +++ b/apps/banglerun/interface.html @@ -0,0 +1,216 @@ + + + + + +
+ + + + + diff --git a/apps/banglerun/src/gps.ts b/apps/banglerun/src/gps.ts index 3c0ee120d..bad6fd1c0 100644 --- a/apps/banglerun/src/gps.ts +++ b/apps/banglerun/src/gps.ts @@ -24,6 +24,10 @@ function parseNmea(state: AppState, nmea: string): void { const tokens = nmea.split(','); const sentence = tokens[0].substr(3, 3); + // FIXME: Bangle.js reports HDOP from GGA - can this be used instead + // of manually parsing all of the raw GPS data, which can cause FIFO_FULL + // errors? + switch (sentence) { case 'GGA': state.lat = parseCoordinate(tokens[2]) * (tokens[3] === 'N' ? 1 : -1); diff --git a/apps/banglerun/src/log.ts b/apps/banglerun/src/log.ts index fb0676121..2ab7b4191 100644 --- a/apps/banglerun/src/log.ts +++ b/apps/banglerun/src/log.ts @@ -17,11 +17,10 @@ function initLog(state: AppState): void { 'distance', 'heartrate', 'steps', - ].join(',')); + ].join(',') + '\n'); } function updateLog(state: AppState): void { - state.file.write('\n'); state.file.write([ Date.now().toFixed(0), state.lat.toFixed(6), @@ -31,7 +30,7 @@ function updateLog(state: AppState): void { state.distance.toFixed(2), state.hr.toFixed(0), state.steps.toFixed(0), - ].join(',')); + ].join(',') + '\n'); } export { initLog, updateLog }; diff --git a/apps/breath/README.md b/apps/breath/README.md new file mode 100644 index 000000000..9db00f734 --- /dev/null +++ b/apps/breath/README.md @@ -0,0 +1,12 @@ +Breathing App +============= +This app attempts to aid relaxation and train breath syncronicity by providing a visualisation of a circle that expands and contracts to guide breathing rate. The app also modulates the vibration motor so you don't neccessarily have to look at the screen. Your HR is displayed in the lower left and there are a few parameters you can change to tailor what works best for you. The menu is quie self-explanatory, the 'ex_in_ratio' just allows an option to make the exhale speed slightly faster if you prefer that - durations can be further altered by increasing the pause times. + +Resonance frequency breathing is a way of breathing (slow relaxed diaphragmatic breathing at around 3-7 breaths per minute) that has a regulating effect on the autonomic nervous system and other key body systems such as the circulatory system. This has many benefits, supported by numerous studies e.g.: + +Increases pulmonary function +Lowers blood pressure +Improves baroreflex gain +Improves heart rate variability +Increases the ability to handle stress +Clinical improvements in asthma diff --git a/apps/breath/app-icon.js b/apps/breath/app-icon.js new file mode 100644 index 000000000..c1373e414 --- /dev/null +++ b/apps/breath/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AFqoAFF94zoF5QxkF5gxiF/t5F92k5wvt5AvtvXOAAIvqvJeBF9i9BAAYvoFwovnvQuGF816XYYwiFw4tIF8qMHF86NJF8iOLGDqOMztV0YvOxIABF5qIB0l6RxFPp1VMBgtCGB4eDvTtH0dPL4gwHFQYwPXBjBTGBwvVYTIv/FyAviFpYuOF6bsOF8wqFFpxffACIuuF7llL99lGKKPeGKAvgGBzufF6XHAAPNFzIvUAAZqEFqC/SF44AkF/4A/AH4A/ADIA=")) \ No newline at end of file diff --git a/apps/breath/app-icon.png b/apps/breath/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..740eb698f32bcf5435923df1e82a68ff80010d4d GIT binary patch literal 1336 zcmV-81;_e{P)BKMW8~1mU4^e2C=O|P)kx;(iR)6 zv`zX#P*hBSYEY8K=!(9Oc&Pt0on0R6@2Pvv|NGAQcITWqCj};$;QtL7bu?6!cY`qngxdjJ0dN_NO8}oG z!bu3}lw$~|ylrj%)J^whVg+Cm`T&3<46*N=aJTtRofS#&G{p7*cT5t@oliUL_BF!x0Qb(4Gtt}k zn%%xe*cyNcSTxT8fVc2x)x~yt8)0hzPo9j)x6z0%(+>7}ld;DBgKYz-ZbJDI=Zyx& zJS;x@xLy86*b+bzKy9%bX-b40@n$>xV=?XkvINu=yOB3Tiq>7oM{1{!cZ^t!D?pke zu%=waf?V0Cgur@I-wO!`xaV#O8=i0@_s)10^k|}En8EO?i$(Tifx1$~Trpv8B4d~( zpCR)*m~sj6J0C*(i6Aar)gc71r7jKWYIK>m>01nIVYr+!yc?E8b_c-v z0xI?W9!TPxeh#2sTxim z0G8(Gz~zvzG=E04ZUsbd8EG}Q_AO_ z3Bcu4(t0?uwefrHk@`Z%vC%4Kdbcy!0>&Py>6rv>NdV3^TS+2(Va!KYw3<{0=mqg6v&A}#v6rGV(P|PpN?3Pew?#8gMS!l+c@Qyx-?n!S&kwOElAW%FT1fX0aW1jQX25xU zTO{Br1D`YN?>%3rd|_msf&knV+4AFG&ObG2;L_f*j6I`O%(Sy(a9WULHB7F)&H40? z8TaG|VCs%+YFkbF%Sm!AuhmR6b7l6O5+Na<}rWa9(?`xyp^#`wHY2?}n za2XR0u%_(aQ%U`1-IE_6q)(+X-^F-kYd;;FPBJwSungc=a#VGfcNvA^>5?6whe}#< ztO%YuyIgGocq_mwbm89-uRfUpG=SesM5qht z!;!yealBenyHN1*X^`ZCkkdet3X&C&6!TKg0B@w_tv{LZU){=5W_n;%#(;7tvm3+b z^}w2u`O~pDQj^;#hrpo_!l4_d01^(6q(aWfdliKH!B_|&6QC}T{st{?HU0Sbsww>B z!Tz)T>QG020nY@G0j9&k8F(q4*3=>n_MS2{aMk{5`GRbO*UO@(V0bo%LRWWJreBD! uNZDb|99}Q!0DiHexdr!u2_~4pPW}a#P==mUQ_`;h0000= 0 && angle < 90) { + if (angle == 2) { + clearInterval(); + g.setFontAlign(-1, -1); + g.drawString("<<", 220, 40); + status = 7; + timeout = setTimeout(function () { + interval = restart_interval(); + }, settings.exhale_pause * 1000); + } + direction = 0; + } + else { + if (angle == 90) + angle = -90; + if (angle == -90) { + clearInterval(); + g.setFontAlign(-1, -1); + g.drawString("<<", 220, 40); + status = 7; + timeout = setTimeout(function () { + interval = restart_interval(); + }, settings.inhale_pause * 1000); + } + direction = 1; + } + g.drawString(display_HR, 20, 200); + + g.flip(); + + if (settings.vibrate == "forward") + Bangle.buzz(50, Math.abs(origin)/1.5); + else if (settings.vibrate == "backward") + Bangle.buzz(50, (1.6 - (Math.abs(origin)))); +} + +function restart_interval() { + status = 6; + var calc = 5 - settings.period; + calc *= 15; + calc += 120; + if(direction == 1 && settings.ex_in_ratio == "5:6"){ + calc -= calc*0.2; + } + interval = setInterval(circle, calc); +} + +function update_menu() { + g.clear(); + g.setColor(settings.colour[0]); + g.setFontAlign(-1, -1); + g.drawString("+/-", 200, 200); + g.drawString("<>", 220, 40); + g.drawString("GO", 210, 120); + g.setFontAlign(-1, -1); + var cursor = 60; + + while (cursor < 180) { + var key = Object.keys(settings)[(cursor - 60) / 20]; + var value = settings[key]; + + if (status == ((cursor - 60) / 20)) { + g.setColor(colours.white[0]); + } + else + g.setColor(settings.colour[0]); + + var display_txt = selection[(cursor - 60) / 20] + ": " + value; + + if(((cursor - 60) / 20) == 3) + display_txt = selection[(cursor - 60) / 20] + ": " + value[1]; + + g.drawString(display_txt, 10, cursor); + cursor += 20; + } +} + +function btn1Pressed() { + if (status < 6) { + status += 1; + if (status == 6) + status = 0; + + update_menu(); + } + else if (status == 7) { + clearTimeout(timeout); + clearInterval(); + status = 0; + update_menu(); + } +} + +function btn2Pressed() { + if (status < 6) { + settings_file = require("Storage").open("breath.settings.json", "w"); + settings_file.write(JSON.stringify(settings)); + Bangle.setHRMPower(1); + g.setColor(settings.colour[0]); + restart_interval(); + } +} + +function btn3Pressed() { + if (status < 6) { + if (status == 0) { + settings.period += 1; + if (settings.period > 6) + settings.period = 1; + } + else if (status == 1) { + settings.exhale_pause += 1; + if (settings.exhale_pause > 4) + settings.exhale_pause = 1; + } + else if (status == 2) { + settings.inhale_pause += 1; + if (settings.inhale_pause > 4) + settings.inhale_pause = 1; + } + else if (status == 3) { + if (settings.colour[0] == colours.green[0]) { + settings.colour = colours.blue; + } + else if (settings.colour[0] == colours.blue[0]) + settings.colour = colours.red; + else if (settings.colour[0] == colours.red[0]) + settings.colour = colours.yellow; + else if (settings.colour[0] == colours.yellow[0]) + settings.colour = colours.green; + } + else if (status == 4) { + if (settings.vibrate == "forward") + settings.vibrate = "backward"; + else if (settings.vibrate == "backward") + settings.vibrate = "off"; + else if (settings.vibrate == "off") + settings.vibrate = "forward"; + } + else if(status == 5){ + if(settings.ex_in_ratio == "1:1") + settings.ex_in_ratio = "5:6"; + else + settings.ex_in_ratio = "1:1"; + } + update_menu(); + } +} + +update_menu(); + +setWatch(btn1Pressed, BTN1, { repeat: true }); +setWatch(btn2Pressed, BTN2, { repeat: true }); +setWatch(btn3Pressed, BTN3, { repeat: true }); + +Bangle.on('HRM', function (hrm) { + if (first_signal) + first_signal = false; + else{ + var signal = hrm.bpm; + if(signal > 50 && signal < 180) + display_HR = signal; + } +}); diff --git a/apps/breath/settings.json b/apps/breath/settings.json new file mode 100644 index 000000000..98e585456 --- /dev/null +++ b/apps/breath/settings.json @@ -0,0 +1 @@ +{"period":2,"exhale_pause":1,"inhale_pause":1,"colour":["#00ff7f","green"],"vibrate":"forward","ex_in_ratio":"5:6"} diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog new file mode 100644 index 000000000..3df4ab63b --- /dev/null +++ b/apps/dtlaunch/ChangeLog @@ -0,0 +1,4 @@ +0.01: Initial version +0.02: Multiple pages +0.03: cycle thru pages + diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md new file mode 100644 index 000000000..70f7ff931 --- /dev/null +++ b/apps/dtlaunch/README.md @@ -0,0 +1,16 @@ +# Desktop style App Launcher + +![](screenshot.jpg) + +In the picture above, the Settings app is selected. +## Controls + +**BTN1** - move backward through app icons on a page + +**BTN2** - run the selected app + +**BTN3** - move forward through app icons + +**Swipe Left** - move to next page of app icons + +**Swipe Right** - move to previous page of app icons \ No newline at end of file diff --git a/apps/dtlaunch/app-icon.js b/apps/dtlaunch/app-icon.js new file mode 100644 index 000000000..a49bb0af4 --- /dev/null +++ b/apps/dtlaunch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4ATxAAQC+2N7vd7AX/C/6/7a/4X/a/4X/C/4X/C/4Xfl3iC6vu9wXtI653WAH4A/ABg")) \ No newline at end of file diff --git a/apps/dtlaunch/app.js b/apps/dtlaunch/app.js new file mode 100644 index 000000000..329a96958 --- /dev/null +++ b/apps/dtlaunch/app.js @@ -0,0 +1,73 @@ +/* Desktop launcher +* +*/ + +var s = require("Storage"); +var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); +apps.sort((a,b)=>{ + var n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; +}); + +var Napps = apps.length; +var Npages = Math.ceil(Napps/6); +var maxPage = Npages-1; +var selected = 0; +var oldselected = -1; +var page = 0; + +function draw_icon(p,n,selected) { + var x = (n%3)*80; + var y = n>2?130:40; + (selected?g.setColor(0.3,0.3,0.3):g.setColor(0,0,0)).fillRect(x,y,x+79,y+89); + g.drawImage(s.read(apps[p*6+n].icon),x+10,y+10,{scale:1.25}); + g.setColor(-1).setFontAlign(0,-1,0).setFont("6x8",1); + var txt = apps[p*6+n].name.split(" "); + for (var i = 0; i < txt.length; i++) { + txt[i] = txt[i].trim(); + g.drawString(txt[i],x+40,y+70+i*8); + } +} + +function drawPage(p){ + g.setColor(0,0,0).fillRect(0,0,239,239); + g.setFont("6x8",2).setFontAlign(0,-1,0).setColor(1,1,1).drawString("Bangle ("+(p+1)+"/"+Npages+")",120,12); + for (var i=0;i<6;i++) { + if (!apps[p*6+i]) return i; + draw_icon(p,i,selected==i); + } +} + +Bangle.on("swipe",(dir)=>{ + selected = 0; + oldselected=-1; + if (dir<0){ + ++page; if (page>maxPage) page=0; + drawPage(page); + } else { + --page; if (page<0) page=maxPage; + drawPage(page); + } +}); + +function nextapp(d){ + oldselected = selected; + selected+=d; + selected = selected<0?5:selected>5?0:selected; + selected = (page*6+selected)>=Napps?0:selected; + draw_icon(page,selected,true); + if (oldselected>=0) draw_icon(page,oldselected,false); +} + +function doselect(){ + load(apps[page*6+selected].src); +} + +setWatch(nextapp.bind(null,-1), BTN1, {repeat:true,edge:"falling"}); +setWatch(doselect, BTN2, {repeat:true,edge:"falling"}); +setWatch(nextapp.bind(null,1), BTN3, {repeat:true,edge:"falling"}); + +drawPage(0); diff --git a/apps/dtlaunch/icon.png b/apps/dtlaunch/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..daa22371ba24a6a290c4c52e5d8b7a17c004fd1e GIT binary patch literal 305 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

wI~H+ww$yU7f*%jF`*o80omBC3LW0biJBxJ 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%0Ajz4<8%n#*Wf8#It6?Jd= z;@_q#&@R8!U4PeQ{$jlU0LDM^D(c_$#s2`MU2ow_(8me>^H0@&XC~xy;;WUaYe}3xHFuL z4|?LR_?dve%8`FzK7?egz{!k7-2?OE@^-bi5oln-hce(VGb{V;W;E_(=<#Je_eQAfNrT!81t9u$f$nt$@^Yve#sOnkzuh3UL zZHoXW%I#YkcieBrvZL{$YkUVrpau#C2Hu^io}WsPeewS9^r-zl zmFq2!oSy8KC06-Z0uKZqLtcmRUym5D>?~}69)o93rFrx{N7R2h^dE~K@;N{Kd0Btx zy6KPbpUCHz@mHh$4>w)`{A%5&3@W1ZKaE?p>MNCOX;a~6ML98Ow@(+y$qN-L!2}`3 zG19+Nyf1BUX=i0?YZ5~YHg@S0ai~;95ex-Uzyr`%<&EF`fUiI6i~j)V75b^+{{Z!N z{{Z#t2mXs%_|7u^4L?`;A4S7^%<2@M?DoOx+|^s2Rxo;eyj7+D0FZlsy;W{HNA(}l zzd4ciaoE(qB{XE&^5pZ5GtFsfNUNd9Q~-9bAbOh3)PLj+AJ_WUrkDQ!ASeF-b$^{_ xQ)1D7COsc)XJ{;P2LruW(_=$uq^Km5+N(eOf`8y=`qjNR{yh`wYdD0D|JisQC8z)Z literal 0 HcmV?d00001 diff --git a/apps/edisonsball/README.md b/apps/edisonsball/README.md new file mode 100644 index 000000000..b8e9ec106 --- /dev/null +++ b/apps/edisonsball/README.md @@ -0,0 +1,5 @@ +The application is based on a technique that Thomas Edison used to prevent falling asleep using a steel ball. Essentially the app starts with a display that shows the current HR value that the watch alarm is set to and this can be adjusted with buttons 1 and 3. This HR settng should be the approximate value you want the alarm to trigger and so you should ideally know both what your HR is currently and what your heartrate normally is during sleep. For your current HR according to the watch, you can simply use the HR monitor available in the Espruino app loader, and then from that you can choose a lower value as the target for the alarm and adjust as required. + +When you press the middle button on the side, the HR monitor starts, the alarm will trigger when your heart rate average drops to the limit you’ve set and has a certain level of steadiness that is determined by a assessing the variance over several readings - the sensitivity of this variance can be adjusted in a variable in the app's code under 'ADVANCED SETTINGS' if needed. The code also has a basic logging function which shows, in a CSV file, when you started the HR tracker and when the alarm was triggered. + +When the alarm triggers, the app resets and you can adjust the HR setting again to a lower value and/or restart. diff --git a/apps/edisonsball/app-icon.js b/apps/edisonsball/app-icon.js new file mode 100644 index 000000000..1125d437a --- /dev/null +++ b/apps/edisonsball/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAYAA8AAAA8AA8ABgB8AA8ADwD4AA8AH4HwAA8AP8PgAA8Af+fAAA8A//+AAA8B+P8AAA8D4H4AAA8HwDwAAA8PgBgAAA8PAAAAAA8GAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA/////+AA//////AAf/////AAP////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) diff --git a/apps/edisonsball/app-icon.png b/apps/edisonsball/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a6f170aec6fba37a927fb0cff74fd86258e1bbdb GIT binary patch literal 821 zcmV-51Iqk~P)?`<2o|D(2mzJ&AYh7U<11pJNg!wfF`Ask?0ElO?rvxH{(C2SKNuM9 zX20*7z1?{oG->jGlTp;l0tbOS&;@J+Rt4Wp0q=oVz$4%;@LkcnCe{N3zzk4yEHl7$ zU~>cV{$VN52TbE0JX+>}{y?j)v;j}c1R4b{1ABo^U}aEp!Ds!z>oV6p0b1)7ek(8* z2`~h7duZ$io<%f12HL68MmYQrpoe6GK?XPzXvj{a_Dd`y{MbwYI~8Aag7%_2CF&2ohggqQ0oH*5)JIJ2S! z4bci57d~H6TvlHc!k;kPL~XLbXW?@}Q+zq*Sj39o&=e22DfGvH71{K2C zqJ2bBJH%CCvVhss*2?KX%j#ZH(5%Q}U{ILN11D}WT>DqhOX<$Oz?`K<*#Nv0?Rx8$&Vg3U34-hBGYhP1NYW}42rtSi zl?C8bgEGdl12ZGb8QPXf;2yBOVd 0){ + //Bangle.beep(500,4000); + Bangle.buzz(500,1); + alarm_length--; + } + else{ + clearInterval(alarm); + if(trigger_count > 1) + Bangle.setHRMPower(0); + } +} + +function average(nums) { + return nums.reduce((a, b) => (a + b)) / nums.length; +} + +function getStandardDeviation (array) { + const n = array.length; + const mean = array.reduce((a, b) => a + b) / n; + return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n); +} + +function checkHR() { + var bpm = currentBPM, isCurrent = true; + if (bpm===undefined) { + bpm = lastBPM; + isCurrent = false; + } + if (bpm===undefined || bpm < lower_limit_BPM || bpm > upper_limit_BPM) + bpm = "--"; + if (bpm != "--"){ + HR_samples.push(bpm); + // Terminal.println(bpm); + } + + if(HR_samples.length == 5){ + g.clear(); + average_HR = average(HR_samples).toFixed(0); + stdev_HR = getStandardDeviation (HR_samples).toFixed(1); + + g.drawString("HR: " + average_HR, 120,100); + g.drawString("STDEV: " + stdev_HR, 120,160); + HR_samples = []; + if(average_HR < target_heartrate && stdev_HR < deviation_threshold){ + + Bangle.setHRMPower(0); + alarm_length = 4; + setInterval(alarm, 2000); + + trigger_count++; + var csv = [ + 0|getTime(), + launchtime, + average_HR, + stdev_HR + ]; + file.write(csv.join(",")+"\n"); + + heartrate_set = false; + update_target_HR(); + } + } +} + +update_target_HR(); + +setWatch(btn1Pressed, BTN1, {repeat:true}); +setWatch(btn2Pressed, BTN2, {repeat:true}); +setWatch(btn3Pressed, BTN3, {repeat:true}); + +Bangle.on('HRM',function(hrm) { + + if(trigger_count < 2){ + if (firstBPM) + firstBPM=false; // ignore the first one as it's usually rubbish + else { + currentBPM = hrm.bpm; + lastBPM = currentBPM; + } + checkHR(); + } +}); diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index 8c08a4ec5..b06264159 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -17,4 +17,4 @@ Ensure default time period is 10 0.14: Now use the openstmap lib for map plotting 0.15: Add plotTrack method to allow current track to be plotted on a map (#395) - Add gpsrec app to Settings menu +0.16: Add gpsrec app to Settings menu diff --git a/apps/hardalarm/ChangeLog b/apps/hardalarm/ChangeLog new file mode 100644 index 000000000..b8b4561b8 --- /dev/null +++ b/apps/hardalarm/ChangeLog @@ -0,0 +1 @@ +0.01: Add a number to match to turn off alarm diff --git a/apps/hardalarm/app-icon.js b/apps/hardalarm/app-icon.js new file mode 100644 index 000000000..6def7b58f --- /dev/null +++ b/apps/hardalarm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkE5gA/AF9cBZXFQYIOGBIUMC5PATgQJFqAIBgovMBwISDAYQ5HGBAAGFxQ/FgMzmcgJ5BIKgAXFIxYuBgMxCIMjmcQgECiBHLFwITBFYIvBiBLBmQwLqECCYMziICBmIeBD4IwKFwQAIGAJGJRQUhgIbDAocQJBHAgYlCOQIrDAoUwhhGIiZZBX4gFEAgIXICIMwX4gFFC45eDF5RgI4pZHAo0gC43AXoaPJC4J4GRwQAMSA4XfmKuBC6kQRYQXMO4YACXYYADO46nDIwcCiBIFU47XDBwcTkBQFa4/MH4sAkYxBBAoWGC4I/DmQ1BdwJPEC5CQEmAECGQKOKMA0gCYRgELxBICCYUyFQZgCJgIWI5lQYIxjCGYMFC5PFLAiKDFwRGJGATCFLoYuKGAYYGiAuMDAkiCoMiCx6SCAAq7IF48F4tQCoMFqAXP4AQF4B1MSAZXFMwIXPA41VC5wA/ADAA==")) diff --git a/apps/hardalarm/app.js b/apps/hardalarm/app.js new file mode 100644 index 000000000..61467b421 --- /dev/null +++ b/apps/hardalarm/app.js @@ -0,0 +1,112 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var alarms = require("Storage").readJSON("hardalarm.json",1)||[]; +/*alarms = [ + { on : true, + hr : 6.5, // hours + minutes/60 + msg : "Eat chocolate", + last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! + rp : true, // repeat + as : false, // auto snooze + } +];*/ + +function formatTime(t) { + var hrs = 0|t; + var mins = Math.round((t-hrs)*60); + return hrs+":"+("0"+mins).substr(-2); +} + +function getCurrentHr() { + var time = new Date(); + return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); +} + +function showMainMenu() { + const menu = { + '': { 'title': 'Alarms' }, + 'New Alarm': ()=>editAlarm(-1) + }; + alarms.forEach((alarm,idx)=>{ + txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr); + if (alarm.rp) txt += " (repeat)"; + menu[txt] = function() { + editAlarm(idx); + }; + }); + menu['< Back'] = ()=>{load();}; + return E.showMenu(menu); +} + +function editAlarm(alarmIndex) { + var newAlarm = alarmIndex<0; + var hrs = 12; + var mins = 0; + var en = true; + var repeat = true; + var as = false; + if (!newAlarm) { + var a = alarms[alarmIndex]; + hrs = 0|a.hr; + mins = Math.round((a.hr-hrs)*60); + en = a.on; + repeat = a.rp; + as = a.as; + } + const menu = { + '': { 'title': 'Alarms' }, + 'Hours': { + value: hrs, + onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Minutes': { + value: mins, + onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Enabled': { + value: en, + format: v=>v?"On":"Off", + onchange: v=>en=v + }, + 'Repeat': { + value: en, + format: v=>v?"Yes":"No", + onchange: v=>repeat=v + }, + 'Auto snooze': { + value: as, + format: v=>v?"Yes":"No", + onchange: v=>as=v + } + }; + function getAlarm() { + var hr = hrs+(mins/60); + var day = 0; + // If alarm is for tomorrow not today (eg, in the past), set day + if (hr < getCurrentHr()) + day = (new Date()).getDate(); + // Save alarm + return { + on : en, hr : hr, + last : day, rp : repeat, as: as + }; + } + menu["> Save"] = function() { + if (newAlarm) alarms.push(getAlarm()); + else alarms[alarmIndex] = getAlarm(); + require("Storage").write("hardalarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function() { + alarms.splice(alarmIndex,1); + require("Storage").write("hardalarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + } + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/hardalarm/app.png b/apps/hardalarm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ed830c5eb89e7257dbe8fd82cbed6525455f3096 GIT binary patch literal 1909 zcmV-*2a5QKP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NI-$_J4RA>e5S#3yGXBfWy$jw?{ znYGcqwTrF$Y^}OeKd*Ab(^PF?k7{YG2-t#>7bALSF=f2OI&>-?eMlmIh z;=Ze{|385LdIg8H0(a5to50`*^qvQ>184*O0)kR<5QU#1{3XzCDLs#83GgnMK&q($*e#Xu|YIV|bU)9By8ao_`>3-C)^xvznbf#}Xtzu5xIekdWx zHF^ma_W<`DET@834DuV0s4yLQRLhY#a4 zvcTcWX2!L6GXk9fZLS4{bGqX2Jb(VYOrAVh#*Q7UkeQh&H*VZ;>^#+bfIl7V_+bSe z01qwqDEE3{VWAv8d{`DNSP(I#yNrKFI^ zZof)&P-zKo+_+J;ZQCa4>FL5PGk*MdX>Dzl$B!Q?iS_H(E1tI9*48EjDL+47Dk>^u z-n@BIR8%C%$;r~v(jr`0UIuv-@<~Y{p98oAgP&PO%gV}R<;s;-E&RyK%aer*7pnbU zP*5O?7cY)!&(6-4J9q9Vy)9d|$hdLiBJCVVR#uiYG&D#nxU+$mBdz9wuS47o8}9(z zHk|oTD1_3;)1gC$q_VP7I5fT|Ui9I@*BPjWA-(7Aoo{MtQUl_7U>oZuGRae)cIb;f z{gCGh{0=Mc0x2mea`NO!nKETc#4bCtb?eqhT^IK4+ZT7R+2%+7)~#EzYSk)rV|@jk zncAq!({lyBu@qOYULDhUvJW0S=xBC0d`=L3k-l}b9?LWiU;wfVxDpZ)WbN9uv6`sy zZg-;L^685{d2z50CNd7-GBp1I=FFKR6DLlLv%{OiiH6IkFZ!f!_Q6EP0dNLLixw>k z7i_;9Hf*qQ!qt)BYoEFgj*h@I4&bJxHG4L1=6K>8mz>2`pr9YkLH)P0Ee z9_APm8ivHAjEszM!Qq#kvWF55ViA^KdF@m8A!5K*Fs?u(p0}b(o9;8MMj}}cFUA$n zx=)@ka}7oV0|T;u|9cTT|jSdZ%pjsrzcfaRdV`tIAdVs zxqbU~KWY&f`Xq%jiU>QdVBA7X14%tSJ>i1SU%m(U*49_=nKNhP?p=P%@K4&O?t`1A zaR5^-t*cjqC6EUW9FW1mLD{@{vx-oeADlRG!mSUEeC<>B!BGfI;{aZ>v>F>5-Sp%5 zjCY!wn`Q3Yxw2%*5?Qxyosv0q>Xh8S@ANq!&V*uUpSlmuQXT`yht?S2%9SfBmUlCv z`HvnwlJfF$6$WtjNqnPnF!%1=lbV_uEoyu+Z=g^5W*du`;$KCi$3Ywi;_Yp=7Z^F>FVl|S+i!T9M~?yl#+*?Ddle91-pPlJt^d405miMI0x|S0mJ1PGst;L z%-E(+pYGUsN+l*HsF%wd@Z+7B<99f|imuS(uF zJh0sJoc|y*uMH1r0959;qH5p=RzSRI_|jq~#bku>d?X~smSJqe@SwJ~R@&R!!%Fz& zZBq*P`34kGiYg3w0JPBw@IAqUlmsw`^-C{7@KeCgRcW%BY-;ps`_2+rhUfdhWq{l3 zCct1=A616Lj4BzJWdmOTY|j9OOG6$&4?NccoP8d=RDh|!bNY{lDEbBEAA#=yZlB?h z?`zcVq!tWDH3fiI$ZOYq!(zDoL1LFT)rY(SIk3u?&08kxE^T?>^WV)rm-Zm+VIATi vj-Hk5khjpY>QGR-qkaI%c!l$wG-~NzCgLD&G8fCx00000NkvXXu0mjfrc{7t literal 0 HcmV?d00001 diff --git a/apps/hardalarm/boot.js b/apps/hardalarm/boot.js new file mode 100644 index 000000000..e327317e3 --- /dev/null +++ b/apps/hardalarm/boot.js @@ -0,0 +1,25 @@ +// check for alarms +(function() { + var alarms = require('Storage').readJSON('hardalarm.json',1)||[]; + var time = new Date(); + var active = alarms.filter(a=>a.on); + if (active.length) { + active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24); + var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); + if (!require('Storage').read("hardalarm.js")) { + console.log("No alarm app!"); + require('Storage').write('hardalarm.json',"[]"); + } else { + var t = 3600000*(active[0].hr-hr); + if (active[0].last == time.getDate() || t < 0) t += 86400000; + if (t<1000) t=1000; + /* execute alarm at the correct time. We avoid execing immediately + since this code will get called AGAIN when hardalarm.js is loaded. alarm.js + will then clearInterval() to get rid of this call so it can proceed + normally. */ + setTimeout(function() { + load("hardalarm.js"); + },t); + } + } +})(); diff --git a/apps/hardalarm/hardalarm.js b/apps/hardalarm/hardalarm.js new file mode 100644 index 000000000..c3623a193 --- /dev/null +++ b/apps/hardalarm/hardalarm.js @@ -0,0 +1,127 @@ +// Chances are boot0.js got run already and scheduled *another* +// 'load(hardalarm.js)' - so let's remove it first! +clearInterval(); + +function formatTime(t) { + var hrs = 0|t; + var mins = Math.round((t-hrs)*60); + return hrs+":"+("0"+mins).substr(-2); +} + +function getCurrentHr() { + var time = new Date(); + return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); +} + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +function getRandomFromRange(lowerRangeMin, lowerRangeMax, higherRangeMin, higherRangeMax) { + var lowerRange = lowerRangeMax - lowerRangeMin; + var higherRange = higherRangeMax - higherRangeMin; + var fullRange = lowerRange + higherRange; + var randomNum = getRandomInt(fullRange); + if(randomNum <= (lowerRangeMax - lowerRangeMin)) { + return randomNum + lowerRangeMin; + } else { + return randomNum + (higherRangeMin - lowerRangeMax); + } +} + +function showNumberPicker(currentGuess, randomNum) { + if(currentGuess == randomNum) { + E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum); + } else { + E.showMessage("" + currentGuess, "Get to " + randomNum); + } +} + +function showPrompt(msg, buzzCount, alarm) { + E.showPrompt(msg,{ + title:"STAY AWAKE!", + buttons : {"Sleep":0,"Stop":1} // default is sleep so it'll come back in 10 mins + }).then(function(choice) { + buzzCount = 0; + if (choice==0) { + if(alarm.ohr===undefined) alarm.ohr = alarm.hr; + alarm.hr += 10/60; // 10 minutes + require("Storage").write("hardalarm.json",JSON.stringify(alarms)); + load(); + } else if(choice==1) { + alarm.last = (new Date()).getDate(); + if (alarm.ohr!==undefined) { + alarm.hr = alarm.ohr; + delete alarm.ohr; + } + if (!alarm.rp) alarm.on = false; + require("Storage").write("hardalarm.json",JSON.stringify(alarms)); + load(); + } + }); +} + +function showAlarm(alarm) { + var msg = formatTime(alarm.hr); + var buzzCount = 20; + if (alarm.msg) + msg += "\n"+alarm.msg; + var okClicked = false; + var currentGuess = 10; + var randomNum = getRandomFromRange(0, 7, 13, 20); + showNumberPicker(currentGuess, randomNum) + setWatch(o => { + if(!okClicked && currentGuess < 20) { + currentGuess = currentGuess + 1; + showNumberPicker(currentGuess, randomNum); + } + }, BTN1, {repeat: true, edge: 'rising'}); + + setWatch(o => { + if(currentGuess == randomNum) { + okClicked = true; + showPrompt(msg, buzzCount, alarm); + } + }, BTN2, {repeat: true, edge: 'rising'}); + + setWatch(o => { + if(!okClicked && currentGuess > 0) { + currentGuess = currentGuess - 1; + showNumberPicker(currentGuess, randomNum); + } + }, BTN3, {repeat: true, edge: 'rising'}); + + function buzz() { + Bangle.buzz(500).then(()=>{ + setTimeout(()=>{ + Bangle.buzz(500).then(function() { + setTimeout(()=>{ + Bangle.buzz(2000).then(function() { + if (buzzCount--) + setTimeout(buzz, 2000); + else if(alarm.as) { // auto-snooze + buzzCount = 20; + setTimeout(buzz, 600000); // 10 minutes + } + }); + },100); + }); + },100); + }); + } + buzz(); +} + +// Check for alarms +var day = (new Date()).getDate(); +var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early +var alarms = require("Storage").readJSON("hardalarm.json",1)||[]; +var active = alarms.filter(a=>a.on&&(a.hra.hr-b.hr); + showAlarm(active[0]); +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/hardalarm/widget.js b/apps/hardalarm/widget.js new file mode 100644 index 000000000..677266195 --- /dev/null +++ b/apps/hardalarm/widget.js @@ -0,0 +1,11 @@ +(() => { + var alarms = require('Storage').readJSON('hardalarm.json',1)||[]; + alarms = alarms.filter(alarm=>alarm.on); + if (!alarms.length) return; // no alarms, no widget! + delete alarms; + // add the widget + WIDGETS["alarm"]={area:"tl",width:24,draw:function() { + g.setColor(-1); + g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + }}; +})() diff --git a/apps/hrrawexp/README.md b/apps/hrrawexp/README.md new file mode 100644 index 000000000..abf2d3d7c --- /dev/null +++ b/apps/hrrawexp/README.md @@ -0,0 +1,13 @@ +Extract hrm raw signal data to CSV file +======================================= + +Simple app that will run the heart rate monitor for a defined period of time you set at the start. + +-The app creates a csv file (it's actually just 1 column) and you can download this via My Apps in the App Loader. + +-The max time value is 60 minutes. + +-The first item holds the data/time when the readings were taken and the file is reset each time the app is run. + +-The hrm sensor is sampled @50Hz and this app does not do any processing on it other than clip overly high/extreme values, the array is written as-is. There is an example Python script that can process this signal, smooth it and also extract a myriad of heart rate variability metrics using the hrvanalysis library: +https://github.com/jabituyaben/BangleJS-HRM-Signal-Processing diff --git a/apps/hrrawexp/app-icon.js b/apps/hrrawexp/app-icon.js new file mode 100644 index 000000000..01718675e --- /dev/null +++ b/apps/hrrawexp/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDCBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAP4AAD/8A//AAH//D//gAP//n//gAf/////AA/////+AA/////8MA/////4cB/////w+B/////B+B////+D+B////8H+B////4P+B////w/+B/+P/h/+B/+H/D/+A/+D+H/8A//h8P/8A//w4f/8Af/4A//4Af/8B//4AP/+D//wAP//H//wAH/////gAD/////AAB////+AAA////+AAAf///8AAAP///wAAAH///gAAAD///AAAAA//+AAAAAf/4AAAAAH/gAAAAAB+AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")) diff --git a/apps/hrrawexp/app-icon.png b/apps/hrrawexp/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b120cdb425f7696078594536d3867e97c9784354 GIT binary patch literal 826 zcmV-A1I7G_P)Wqu?F5J1kx$pjY z^WH4(7k;Zb=XbupJLlf}JMU6aQBhG*f{u*ideDy*cpi`AG5n7aoWw!=f_n{eb>cN_ zsO4K)%XJn1;y0YAR*@+Y{B0dbKS-F=q)P04mUF{mW;t# z@$n0BykM@Icq^~`8@OF?!6-)2SBudSdHzUTjD|dcv6AvpT&_jY`KQsHM;^Xvl6=`X zibXN6hw)A$`S}>11M-K^74upw+*db*9Wo>9=79WuJQQ#@Q(S9Wwl2J;Z9lsMO%lwF(TZcarR`KdpWIup`0Q+g%`{=Ok|w%JD!QH*@|x=*6|qQY7xHeu zPtpo_R`%XMiqB2&|DP7FbbQRWm=_zqqz8mPDq_BHqME{&i2{3s z7P*N0unecj^#zj;;;C4k4i7%#Uy%JoRxh?Uk|#7$!vz;OFWd+9SgG+%o)%-Kx7Vqx zF0s%!)+X}3sr)-;Agg>tw1@uYHR*=EH!p35sA@hWb$ zQGQbR{iqZhmEQtIV`o|UhA+Uyw$uc635S}t(Tktump>+aYnU_ip_zaJ>%`OKE@v-ph!=L!j^y6ejMMXv6K7Sbe9D~&mDgXcg07*qoM6N<$ Eg1f_xtN;K2 literal 0 HcmV?d00001 diff --git a/apps/hrrawexp/app.js b/apps/hrrawexp/app.js new file mode 100644 index 000000000..f78d3fbf6 --- /dev/null +++ b/apps/hrrawexp/app.js @@ -0,0 +1,97 @@ +var counter = 1; +var logging_started; +var interval; +var value; + +var file = require("Storage").open("hrm_log.csv", "w"); +file.write(""); + +file = require("Storage").open("hrm_log.csv", "a"); + +function update_timer() { + g.clear(); + g.setColor("#00ff7f"); + g.setFont("6x8", 4); + g.setFontAlign(0, 0); // center font + + g.drawString(counter, 120, 120); + g.setFont("6x8", 2); + g.setFontAlign(-1, -1); + g.drawString("-", 220, 200); + g.drawString("+", 220, 40); + g.drawString("GO", 210, 120); + + g.setColor("#ffffff"); + g.setFontAlign(0, 0); // center font + g.drawString("Timer (minutes)", 120, 90); + + g.setFont("6x8", 4); // bitmap font, 8x magnified + + if (!logging_started) + g.flip(); +} + +function btn1Pressed() { + if (!logging_started) { + if (counter < 60) + counter += 1; + else + counter = 1; + update_timer(); + } +} + +function btn3Pressed() { + if (!logging_started) { + if (counter > 1) + counter -= 1; + else + counter = 60; + update_timer(); + } +} + +function btn2Pressed() { + launchtime = 0 | getTime(); + file.write(launchtime + "," + "\n"); + logging_started = true; + counter = counter * 60; + interval = setInterval(countDown, 1000); + Bangle.setHRMPower(1); +} + +function fmtMSS(e) { + var m = Math.floor(e % 3600 / 60).toString().padStart(2, '0'), + s = Math.floor(e % 60).toString().padStart(2, '0'); + return m + ':' + s; +} + +function countDown() { + g.clear(); + counter--; + if (counter == 0) { + Bangle.setHRMPower(0); + clearInterval(interval); + g.drawString("Finished", g.getWidth() / 2, g.getHeight() / 2); + Bangle.buzz(500, 1); + } + else + g.drawString(fmtMSS(counter), g.getWidth() / 2, g.getHeight() / 2); +} + +update_timer(); + +setWatch(btn1Pressed, BTN1, { repeat: true }); +setWatch(btn2Pressed, BTN2, { repeat: true }); +setWatch(btn3Pressed, BTN3, { repeat: true }); + +Bangle.on('HRM', function (hrm) { + for (let i = 0; i < hrm.raw.length; i++) { + value = hrm.raw[i]; + if (value < -2) + value = -2; + if (value > 6) + value = 6; + file.write(value + "," + "\n"); + } +}); diff --git a/apps/hrrawexp/interface.html b/apps/hrrawexp/interface.html new file mode 100644 index 000000000..703341415 --- /dev/null +++ b/apps/hrrawexp/interface.html @@ -0,0 +1,54 @@ + + + + + +

+ + + + + + + diff --git a/apps/lazyclock/ChangeLog b/apps/lazyclock/ChangeLog new file mode 100644 index 000000000..1fc732a04 --- /dev/null +++ b/apps/lazyclock/ChangeLog @@ -0,0 +1 @@ +0.01: Launch app \ No newline at end of file diff --git a/apps/lazyclock/README.md b/apps/lazyclock/README.md new file mode 100644 index 000000000..5f10b1707 --- /dev/null +++ b/apps/lazyclock/README.md @@ -0,0 +1,26 @@ +# Lazy clock + +This clock gives you the time (roughly). + +* 11:05 becomes 'About eleven' +* 15:34 becomes 'Just gone half three' +* 04:12 becomes 'Around quarter past four' + +Phrases have a 10 min 'resolution': +* 10:00 - 10:09: past the hour, +* 10:10 - 10:19: about quarter past +* 10:20 - 10:29: nearly half past +* 10:30 - 10:39: just gone half past +* 10:40 - 10:49: about quarter to +* 10:50 - 10:59: almost the hour + +Various phrases and combinations for each time chunk are provided. + +## Usage + +* Press BTN-1 to see the actual time and date +* Press BTN-3 to cycle through lazy descriptions + +## Attributions + +Icon from https://icons8.com diff --git a/apps/lazyclock/lazyclock-app.js b/apps/lazyclock/lazyclock-app.js new file mode 100644 index 000000000..cd7edf329 --- /dev/null +++ b/apps/lazyclock/lazyclock-app.js @@ -0,0 +1,236 @@ +let secondInterval; +let showRealTime = false; + +const utils = { + random: function(items) { + return items[~~(items.length * Math.random())]; + }, + + oneIn: function(chance) { + return Math.floor(Math.random() * Math.floor(chance + 1)) === chance; + }, + + hours2Word: function(hours, minutes) { + const numbers = [ + 'twelve', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'eleven', + 'twelve', + ]; + + let adjustedHours = hours; + + if (minutes > 40) { + adjustedHours += 1; + } + + if (adjustedHours > 12) { + adjustedHours -= 12; + } + + return numbers[adjustedHours]; + }, + + print: function(str) { + let fontSize = 4; + const width = g.getWidth(); + const height = g.getHeight() - 48; + const lines = str.split(`\n`).length; + let totalHeight; + + do { + g.setFont("6x8", fontSize); + totalHeight = g.getFontHeight() * lines; + if (fontSize === 1 || (g.stringWidth(str) < width && totalHeight < height)) { + break; + } + fontSize--; + + } while (true); + + const x = width / 2; + + const y = (g.getHeight() / 2) - (g.getFontHeight() * ((lines - 1) / 2)); + g.drawString(str, x, y < 25 ? 24 : y); + } +}; + +const words = { + approx: ['\'Bout', 'About', 'Around', `Summat\nlike`, 'Near', 'Close to'], + approach: ['Nearly', `Coming\nup to`, 'Approaching', `A touch\nbefore`], + past: [`A shade\nafter`, `A whisker\nafter`, 'Just gone'], + quarter: ['Quarter', `Fifteen\nminutes`], + half: ['Half', 'Half past'], + exactly: ['exactly', 'on the dot', 'o\' clock'], + ish: ['-ish', `\n(ish)`] +}; + +function switchMode() { + showRealTime = !showRealTime; + refreshTime(); +} + +function drawRealTime(date) { + const pad = (number) => `0${number}`.substr(-2); + const hours = pad(date.getHours()); + const minutes = pad(date.getMinutes()); + + g.setFontAlign(0,1); + g.setFont("6x8", 8); + g.drawString(`${hours}:${minutes}`, g.getWidth() / 2, g.getHeight() / 2); + + g.setFont("6x8", 3); + g.setFontAlign(0, -1); + g.drawString(date.toISOString().split('T')[0], g.getWidth() / 2, g.getHeight() / 2); + +} + +function drawDumbTime(time) { + const hours = time.getHours(); + const minutes = time.getMinutes(); + + function formatTime(hours, minutes) { + const makeApprox = (str, template) => { + let _template = template || 'approx'; + if (utils.oneIn(2)) { + _template = 'approx'; + + if (utils.oneIn(words.approx.length)) { + const ish = utils.random(words.ish); + return `${str}${ish}`; + } + } + + const approx = `${utils.random(words[_template])} `; + + return `${approx}\n${str.toLowerCase()}`; + }; + + const formatters = { + 'onTheHour': (hoursAsWord) => { + const exactly = utils.random(words.exactly); + + return `${hoursAsWord}\n${exactly}`; + }, + 'nearTheHour': (hoursAsWord) => { + const template = (minutes < 10) ? 'past' : 'approach'; + + return makeApprox(hoursAsWord, template); + }, + 'nearQuarter': (hoursAsWord, minutes) => { + const direction = (minutes > 30) ? 'to' : 'past'; + const quarter = utils.random(words.quarter); + + const formatted = `${quarter} ${direction}\n${hoursAsWord}`; + + return (minutes === 15 || minutes === 45) ? formatted : makeApprox(formatted); + }, + 'nearHalf': (hoursAsWord, minutes) => { + const half = utils.random(words.half); + + const formatted = `${half}\n${hoursAsWord}`; + + const template = (minutes > 30) ? 'past' : 'approach'; + return (minutes === 30) ? formatted : makeApprox(formatted, template); + }, + }; + + function getFormatter(hours, minutes) { + if (minutes === 0) { + return formatters.onTheHour; + } else if (minutes > 50 || minutes < 10) { + return formatters.nearTheHour; + } else if (minutes > 40|| minutes < 20) { + return formatters.nearQuarter; + } else { + return formatters.nearHalf; + } + } + + const hoursAsWord = utils.hours2Word(hours, minutes); + + const formatter = getFormatter(hours, minutes); + + return formatter(hoursAsWord, minutes); + } + + utils.print(formatTime(hours, minutes)); +} + +function cancelTimeout() { + if (secondInterval) { + clearTimeout(secondInterval); + } + + secondInterval = undefined; +} + +function refreshTime() { + cancelTimeout(); + + g.clearRect(0, 24, g.getWidth(), g.getHeight()-24); + g.reset(); + g.setFontAlign(0,0); + + const time = new Date(); + + const method = showRealTime ? drawRealTime : drawDumbTime; + + method(time); + + const secondsTillRefresh = 60 - time.getSeconds(); + + secondInterval = setTimeout(refreshTime, secondsTillRefresh * 1000); +} + + +function startClock() { + const secondsToRefresh = refreshTime(); +} + +function addEvents() { + Bangle.on('lcdPower', (on) => { + cancelTimeout(); + if (on) { + startClock(); + } + }); + + setWatch(switchMode, BTN1, { + repeat: true, + edge: "falling" + }); + + setWatch(Bangle.showLauncher, BTN2, { + repeat: false, + edge: "falling" + }); + + + setWatch(refreshTime, BTN3, { + repeat: true, + edge: "falling" + }); +} + +function init() { + g.clear(); + + startClock(); + Bangle.loadWidgets(); + Bangle.drawWidgets(); + + addEvents(); +} + + +init(); diff --git a/apps/lazyclock/lazyclock-icon.js b/apps/lazyclock/lazyclock-icon.js new file mode 100644 index 000000000..0ba6eb144 --- /dev/null +++ b/apps/lazyclock/lazyclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A1iIXXDCwXYF1kBIzEQXlfdC9sNC6oWB6BFVFy5dWIqoXV93u8ArThwXB9wLHhGIAAeAFwwwJCwgABC54uFC5XoHQoXQnBTFCwwkFlwXClAjFFhI7CwYWB8YOFRpYMCkfjFwQPDLpYMHABQwFC6IwETQ4TIGAwXOCQIQDIyIRFGARyRGAQXQRIwXCDoTGGXJAKBeQwA/AH4A6A")) \ No newline at end of file diff --git a/apps/lazyclock/lazyclock.png b/apps/lazyclock/lazyclock.png new file mode 100644 index 0000000000000000000000000000000000000000..4991cd2db72bab73c382acf0217c280e529db8ac GIT binary patch literal 1229 zcmV;;1Ty=HP)%TK~zH71I{1+i!}{xl>; zF_;)55vwuA7*gnknqHV#QY9#VHW(upm_QQ^R83S2K{TirlAvHqC~c_-1(eb*?R2+u zJ}(MnyPe%G?rcrWK6i7zIq!MjIdf*t4CKg>BS(%5kntMm-iKBk1d0{vZ=amEtEs8U z^+VV4LnKX>0rLcQcJ@ZU^GkF*16-p;z?Z8J5b#SVU4Hs%?>s;CvKnCVgDaj=p$?dx ztueA1z=&=Zq&TkWir-^obq!c5uK>RT4scwM)!7^`2Y@!L`9T}j92&3jd|d;&;rV$e zOMxCgbyMNn8qkL7C5ZL;@R98COLr_Xo&nwX+C*ZJw@~vcU;{s6SeLoA^vGy0r1dWp zV43|OcLOfyCi;h7m%5J~pZtwUW;0-!cdnXmL#$_{rtz>pw$dlVTS!&{pzfoxZbz#R zer}k|0tiP@RMvm=pO_!EkOFP43Sm==@H9{gbRq1bH&`z#n+C?mVrr5G(B`TT0lozm zdz25;7rZ}{5artecEmln6XhO zf__GN<^h6=JgnmGOe`c1Fnrr2Wzg9FdSe#Izw#z9^G5qAEE%Ph;{4K7VgjYp)*bB> zl`o8?&F4D3Q;}ihgrlK#2^5wL$xxbav z9w|#Xe4SdZ(g;jQ!)!cJV86=HbBP7d8JNDMH{95}drI}>eMuER?Y?tlgD}@J_7>o= zWPQq}M%KviW^WW`aFE!=3ur6`a7x6k{By&C(@70n#d8MoZF%FpzaO9U%itW(PDh;C zW=CvQU!bViwF?TcLLp?r1Q4lAYJO{^M3lFa)nk8Myk;I?rmA~rYVFZ6*U6~I=E+Uq<8QDjdxlC8j8XoB(MBTELtTr6mxcC2Hh- zQC4}GFpfj?+*z;EX;)?W`G$(Nv;&lH>)Qi7nJ6A8yqQ2@VKSYJh<0~l2KtkgyKKu| z{zv_SlU~IUw@~@^%Zq>~;@gILE@joc4h>oRh84o1gjFaKB@7e;ML-w`0hd8qRrzF~Kk{LBP05(Q8P7LVw3XBzStiEb r&hXCou*9S~?aP!SM~)mh{`dR?AMT?pdR?;}00000NkvXXu0mjf!Xiaf literal 0 HcmV?d00001 diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index 7b553ae42..ac53da24b 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -5,6 +5,8 @@ 0.05: Add README 0.06: Add txt clock 0.07: Add Time Date clock and fix font sizes +0.08: Add pinned clock face + diff --git a/apps/multiclock/ana.min.js b/apps/multiclock/ana.min.js deleted file mode 100644 index f0c671e97..000000000 --- a/apps/multiclock/ana.min.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(){return function(){function e(a,d,b,c){a*=k;g.fillPoly([120+Math.sin(a)*d,134-Math.cos(a)*d,120+Math.sin(a+h)*c,134-Math.cos(a+h)*c,120+Math.sin(a)*b,134-Math.cos(a)*b,120+Math.sin(a-h)*c,134-Math.cos(a-h)*c])}function l(){g.setColor(0,0,0);e(360*f.getSeconds()/60,-5,90,3);0===f.getSeconds()&&(e(360*(d.getHours()+d.getMinutes()/60)/12,-16,60,7),e(360*d.getMinutes()/60,-16,86,7),d=new Date);g.setColor(1,1,1);e(360*(d.getHours()+d.getMinutes()/60)/12,-16,60,7);e(360*d.getMinutes()/60,-16, - 86,7);g.setColor(0,1,1);f=new Date;e(360*f.getSeconds()/60,-5,90,3);g.setColor(0,0,0);g.fillCircle(120,134,2)}var h=Math.PI/2,k=Math.PI/180,d,f;return{init:function(){f=d=new Date;g.setColor(1,1,1);for(var a=0;60>a;a++){var e=360*a/60,b=e*k,c=120+100*Math.sin(b);b=134-100*Math.cos(b);0==e%90?(g.setColor(0,1,1),g.fillRect(c-6,b-6,c+6,b+6)):0==e%30?(g.setColor(0,1,1),g.fillRect(c-4,b-4,c+4,b+4)):(g.setColor(1,1,1),g.fillRect(c-1,b-1,c+1,b+1))}l()},tick:l}}})(); \ No newline at end of file diff --git a/apps/multiclock/clock.js b/apps/multiclock/clock.js index ed2383b8d..50410f096 100644 --- a/apps/multiclock/clock.js +++ b/apps/multiclock/clock.js @@ -1,6 +1,8 @@ var FACES = []; -var iface = 0; -require("Storage").list(/\.face\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face)))); +var STOR = require("Storage"); +STOR.list(/\.face\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face)))); +var lastface = STOR.readJSON("multiclock.json")||{pinned:0}; +var iface = lastface.pinned; var face = FACES[iface](); var intervalRefSec; @@ -25,7 +27,14 @@ function setButtons(){ face = FACES[iface](); startdraw(); } - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + function finish(){ + if (lastface.pinned!=iface){ + lastface.pinned=iface; + STOR.write("multiclock.json",lastface); + } + Bangle.showLauncher(); + } + setWatch(finish, BTN2, {repeat:false,edge:"falling"}); setWatch(newFace.bind(null,1), BTN1, {repeat:true,edge:"rising"}); setWatch(newFace.bind(null,-1), BTN3, {repeat:true,edge:"rising"}); } diff --git a/apps/smtswch/README.md b/apps/smtswch/README.md index 3ac6658c9..fbefa3431 100644 --- a/apps/smtswch/README.md +++ b/apps/smtswch/README.md @@ -20,6 +20,7 @@ This app allows you to remotely control devices (or anything else you like!) wit First, you'll need a device that supports BLE. Install EspruinoHub following the directions at [https://github.com/espruino/EspruinoHub](https://github.com/espruino/EspruinoHub) + Install [Node-RED](https://nodered.org/docs/getting-started) ## Example Node-RED flow From 30b6763ee0bd8cc7a6d8b9e04e209f899a4f3c3f Mon Sep 17 00:00:00 2001 From: OmegaRogue Date: Wed, 13 Jan 2021 15:07:32 +0100 Subject: [PATCH 002/181] =?UTF-8?q?=E2=8F=AA=20(DANE)=20Revert=20Number=20?= =?UTF-8?q?display=20to=20button=20counter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert Number display to button counter as mentioned in #613 Signed-off-by: OmegaRogue --- apps/dane/app.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/dane/app.js b/apps/dane/app.js index 841ecdfeb..3663d92a1 100644 --- a/apps/dane/app.js +++ b/apps/dane/app.js @@ -67,7 +67,7 @@ function levelColor(l) { function drawBattery() { const l = E.getBattery(), c = levelColor(l); - count = l; + // count = l; const xl = 45 + l * (194 - 46) / 100; g.clearRect(46, 58 + 80 + yOffset + 37, 193, height - 5); g.setColor(c).fillRect(46, 58 + 80 + yOffset + 37, xl, height - 5); @@ -124,14 +124,14 @@ drawClock(); setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: "falling"}); -// setWatch(function () { -// count++; -// drawCounterText(); -// }, BTN1, {repeat: true, edge: "falling"}); -// setWatch(function () { -// count--; -// drawCounterText(); -// }, BTN3, {repeat: true, edge: "falling"}); +setWatch(function () { + count++; + drawCounterText(); +}, BTN1, {repeat: true, edge: "falling"}); +setWatch(function () { + count--; + drawCounterText(); +}, BTN3, {repeat: true, edge: "falling"}); // refesh every 100 milliseconds setInterval(updateClock, 500); From 7e87e6aa0b78c7571730eb70c41e6f85b1aae08b Mon Sep 17 00:00:00 2001 From: OmegaRogue Date: Wed, 13 Jan 2021 15:13:31 +0100 Subject: [PATCH 003/181] =?UTF-8?q?=F0=9F=94=96=20(DANE)=20Bump=20version?= =?UTF-8?q?=20to=200.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump version of DANE Watchface to 0.16 in preparation of release/dane/0.16 Signed-off-by: OmegaRogue --- apps.json | 2 +- apps/dane/ChangeLog | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 68e637644..12922a382 100644 --- a/apps.json +++ b/apps.json @@ -1492,7 +1492,7 @@ "name": "Digital Assistant, not EDITH", "shortName": "DANE", "icon": "app.png", - "version": "0.15", + "version": "0.16", "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/", "tags": "clock", "type": "clock", diff --git a/apps/dane/ChangeLog b/apps/dane/ChangeLog index 48bb5c4be..fa37446bb 100644 --- a/apps/dane/ChangeLog +++ b/apps/dane/ChangeLog @@ -10,4 +10,5 @@ 0.12: Move code to Arwes Module 0.13: Improve icon 0.14: Switch Icon back to 8bit web palette to fix it -0.15: Hotfix: Remove var declaration from app image \ No newline at end of file +0.15: Hotfix: Remove var declaration from app image +0.16: Revert: Change Counter back to button control \ No newline at end of file From 23f7fd5ecd5730ac7813c17dcd1cd4c109cf6cfb Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 14:40:51 +1300 Subject: [PATCH 004/181] Create waypoints.json --- apps/speedalt/waypoints.json | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 apps/speedalt/waypoints.json diff --git a/apps/speedalt/waypoints.json b/apps/speedalt/waypoints.json new file mode 100644 index 000000000..17cd2ca20 --- /dev/null +++ b/apps/speedalt/waypoints.json @@ -0,0 +1,61 @@ +[ + { + "name":"NONE" + }, + { + "name":"Omori", + "lat":-38.9058670, + "lon":175.7613350 + }, + { + "name":"DeltW", + "lat":-38.9438550, + "lon":175.7676930 + }, + { + "name":"DeltE", + "lat":-38.9395240, + "lon":175.7814420 + }, + { + "name":"BClub", + "lat":-38.9446020, + "lon":175.8475720 + }, + { + "name":"Hapua", + "lat":-38.8177750, + "lon":175.8088720 + }, + { + "name":"Nook", + "lat":-38.7848090, + "lon":175.7839440 + }, + { + "name":"ChyBy", + "lat":-38.7975050, + "lon":175.7551960 + }, + { + "name":"Waiha", + "lat":-38.7219630, + "lon":175.7481520 + }, + { + "name":"KwaKw", + "lat":-38.6632310, + "lon":175.8670320 + }, + { + "name":"Hatep", + "lat":-38.8547420, + "lon":176.0089124 + }, + { + "name":"Kinlc", + "lat":-38.6614442, + "lon":175.9161607 + } + +] From c030bed00c7a0ee6c21bbb8f96ac795e873832f4 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 16:31:31 +1300 Subject: [PATCH 005/181] Added distance units. --- apps/speedalt/settings.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 21171edee..01de13c27 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -43,6 +43,9 @@ 'Knots (spd)' : function() { setUnits(1.852,'knots'); }, 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Km (dist)' : function() { setUnits(1000,'km'); }, + 'Miles (dist)' : function() { setUnits(1609.344,'miles'); }, + 'Nm (dist)' : function() { setUnits(1852.001,'nm'); }, 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, 'Feet (alt)' : function() { setUnitsAlt(0.3048,'feet'); } }; From 19f7b6a06a408807e6688b4c6a6e28412f1a8e62 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 16:34:07 +1300 Subject: [PATCH 006/181] Max 6 char names --- apps/speedalt/waypoints.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/speedalt/waypoints.json b/apps/speedalt/waypoints.json index 17cd2ca20..d1af258b2 100644 --- a/apps/speedalt/waypoints.json +++ b/apps/speedalt/waypoints.json @@ -8,17 +8,17 @@ "lon":175.7613350 }, { - "name":"DeltW", + "name":"DeltaW", "lat":-38.9438550, "lon":175.7676930 }, { - "name":"DeltE", + "name":"DeltaE", "lat":-38.9395240, "lon":175.7814420 }, { - "name":"BClub", + "name":"BtClub", "lat":-38.9446020, "lon":175.8475720 }, @@ -33,7 +33,7 @@ "lon":175.7839440 }, { - "name":"ChyBy", + "name":"ChryBy", "lat":-38.7975050, "lon":175.7551960 }, @@ -43,17 +43,17 @@ "lon":175.7481520 }, { - "name":"KwaKw", + "name":"KwaKwa", "lat":-38.6632310, "lon":175.8670320 }, { - "name":"Hatep", + "name":"Hatepe", "lat":-38.8547420, "lon":176.0089124 }, { - "name":"Kinlc", + "name":"Kinloc", "lat":-38.6614442, "lon":175.9161607 } From 585119b271616e7bb556099dbec08720c68582d1 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 16:40:12 +1300 Subject: [PATCH 007/181] Add waypoints file and distance to selected waypoint display. --- apps/speedalt/app.js | 147 +++++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7ccb20935..23475b097 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 0.07 +Ver : 0.10 Mike Bennett mike[at]kereru.com */ @@ -11,22 +11,9 @@ var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts require("Font7x11Numeric7Seg").add(Graphics); -/* -var mainmenu = { - "" : { "title" : "-- Units --" }, - "default" : function() { setUnits(0,''); }, - "Kph (spd)" : function() { setUnits(1,'kph'); }, - "Knots (spd)" : function() { setUnits(1.852,'knots'); }, - "Mph (spd)" : function() { setUnits(1.60934,'mph'); }, - "m/s (spd)" : function() { setUnits(3.6,'m/s'); }, - "Meters (alt)" : function() { setUnitsAlt(1,'m'); }, - "Feet (alt)" : function() { setUnitsAlt(0.3048,'feet'); }, - "Exit" : function() { exitMenu(); }, // remove the menu and restore -}; -*/ - var lastFix = {fix:0,satellites:0}; -var showSpeed = 1; // 1 = Speed in primary display. 0 = alt in primary +var primaryDisp = 1; // 1 = Speed in primary display. 0 = alt/dist in primary +var altDisp = 1; // 1 = alt, 0 = dist to wp var showMax = 0; // 1 = display the max values. 0 = display the cur fix var maxPress = 0; // Time max button pressed. Used to calculate short or long press. var canDraw = 1; @@ -41,6 +28,36 @@ max.alt = 0; var emulator = 0; if (process.env.BOARD=="EMSCRIPTEN") emulator = 1; // 1 = running in emulator. Supplies test values; +var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; +var wpindex=0; +var wp = {}; // Waypoint to use for distance from cur position. + +function nextwp(inc){ + if (altDisp) return; + wpindex+=inc; + if (wpindex>=waypoints.length) wpindex=0; + if (wpindex<0) wpindex = waypoints.length-1; + wp = waypoints[wpindex]; + onGPS(lastFix); +} + +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(settings.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + function drawFix(speed,units,sats,alt,alt_units) { if (!canDraw) return; @@ -51,26 +68,29 @@ function drawFix(speed,units,sats,alt,alt_units) { // Primary Display val = speed.toString(); - if ( !showSpeed ) val = alt.toString(); + if ( !primaryDisp ) val = alt.toString(); // Primary Units u = settings.spd_unit; - if ( !showSpeed ) u = alt_units; + if ( !primaryDisp ) u = alt_units; drawPrimary(val,u); // Secondary Display val = alt.toString(); - if ( !showSpeed ) val = speed.toString(); + if ( !primaryDisp ) val = speed.toString(); // Secondary Units u = alt_units; - if ( !showSpeed ) u = settings.spd_unit; + if ( !primaryDisp ) u = settings.spd_unit; drawSecondary(val,u); // Time drawTime(); + + // Waypoint name + drawWP(); //Sats drawSats(sats); @@ -154,6 +174,7 @@ function drawSecondary(n,u) { } + function drawTime() { var x = 0; var y = 160; @@ -168,6 +189,22 @@ function drawTime() { buf.drawString(time,x,y); } +function drawWP() { + var nm = wp.name; + if ( nm == undefined ) nm = ''; + if ( nm == 'NONE' ) nm = ''; + if ( altDisp ) nm=''; + + + buf.setFontAlign(-12,1); //left, bottom + buf.setColor(2); +// buf.setFont("6x8", 1); + buf.setFontVector(20); + buf.drawString(nm.substring(0,6),77,160); + +} + + function drawSats(sats) { buf.setFontAlign(1,1); //right, bottom buf.setColor(3); @@ -205,22 +242,51 @@ function onGPS(fix) { else { speed = fix.speed; if ( emulator ) speed = '100'; - speed = Math.round(parseFloat(speed)/parseFloat(settings.spd),0); + speed = Math.round(parseFloat(speed)/parseFloat(settings.spd)); } // ==== Altitude ==== alt = fix.alt; if ( emulator ) alt = '360'; - alt = Math.round(parseFloat(alt)/parseFloat(settings.alt),0); + alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); + + // ==== Distance to waypoint ==== + if ( emulator ) { + lastFix.lat = -38.92; + lastFix.lon = 175.7613350; + } + + dist = distance(lastFix,wp); + if (isNaN(dist)) dist = 0; + // Record max values if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); - if ( showMax ) drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); - else drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit); + if ( showMax ) { + // Speed and alt maximums + drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); + } + else { + if ( altDisp ) { + // Show speed/altitude + drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit); + } + else { + // Show speed/distance + if ( dist <= 0 ) { + // No WP selected + drawFix(speed,settings.spd_unit,fix.satellites,'',''); + } + else { + drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit); + } + } + } - } else { + } + else { doBuzz(0); drawNoFix(fix.satellites); } @@ -263,28 +329,38 @@ function doBuzz2() { } function toggleDisplay() { - showSpeed = !showSpeed; - onGPS(lastFix); // Back to Speed display + primaryDisp = !primaryDisp; + onGPS(lastFix); // Update display +} + +function toggleAltDist() { + altDisp = !altDisp; + onGPS(lastFix); } function toggleMax() { // if ( inMenu ) return; showMax = !showMax; - onGPS(lastFix); // Back to Speed display + onGPS(lastFix); } function setButtons(){ - - // Show launcher when middle button pressed - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); // Switch between fix and max display on short press or reset max values on long press setWatch(maxPressed, BTN1,{repeat:true,edge:"rising"}); setWatch(maxReleased, BTN1,{repeat:true,edge:"falling"}); - // Touch screen to toggle display + // Show launcher when middle button pressed + setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + + // Select a waypoint for dist display + setWatch(nextwp.bind(this,1), BTN3, {repeat:true,edge:"falling"}); + + // Touch left screen to toggle display setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); - setWatch(toggleDisplay, BTN5, {repeat:true,edge:"falling"}); + + // Touch left screen to toggle between alt or dist + setWatch(toggleAltDist, BTN5, {repeat:true,edge:"falling"}); } @@ -341,6 +417,10 @@ settings.spd = settings.spd||0; // Multiplier for speed unit conversions. 0 = u settings.spd_unit = settings.spd_unit||''; // Displayed speed unit settings.alt = settings.alt||0.3048;// Multiplier for altitude unit conversions. settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units + +settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. +settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units + settings.colour = settings.colour||0; // Colour scheme. settings.buzz = settings.buzz||0; // Buzz when fix lost or gained. @@ -405,4 +485,3 @@ Bangle.on('GPS', onGPS); setButtons(); setInterval(updateClock, 30000); - From a5be6ac4174edc2dab320ed366fd2374ad3de453 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 16:42:44 +1300 Subject: [PATCH 008/181] Added distance to waypoint. --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d5e301251..b5a98ec85 100644 --- a/apps.json +++ b/apps.json @@ -2625,8 +2625,8 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"0.07", - "description": "GPS speed and altitude display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "version":"1.00", + "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", "type":"app", "allow_emulator":true, From 693ed73acfde67bd2177853cd8265b569390d769 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 16:46:12 +1300 Subject: [PATCH 009/181] New feature. Added waypoints file and distance to selected waypoint display. --- apps/speedalt/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 258b5051a..ac05ad706 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,3 +5,4 @@ 0.05 : Add setting to turn vibrate on/off. 0.06 : Tweaks to vibration settings. 0.07 : Switch to BTN1 for Max toggle and reset function. +1.00 : New feature. Added waypoints file and distance to selected waypoint display. From a68842d418abf0850e8efefd0d9102994ac60a6f Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 17:06:49 +1300 Subject: [PATCH 010/181] Update README.md --- apps/speedalt/README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index e1d00653e..d7a21bef5 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -1,12 +1,20 @@ -Displays the GPS speed and altitude. One 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. +Displays the GPS speed, altitude and distance to selected waypoint. One 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. -Display Tap : Swaps the displays. You can have either speed or altitude on the large primary display. +You can chose between two modes. One showing speed and altitude and one showing speed and distance to waypoint. -BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum values recorded. +The waypoints list is the same as that used with the [GPS Navigation] app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. + +Left Display Tap : Swaps the displays. You can have either speed or altitude/distance on the large primary display. + +Right Display Tap : Swaps the modes between Speed+Altitude or Speed+Distance Note: You cannot swap to Speed+Distance or select waypoints while displaying MAX values. + +BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. BTN1 : Long press > 2 secs resets the recorded maximum values. -App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. +BTN3 : (Only in Speed+Distance mode) Select waypoint. Last fix distance from selected waypoint is displayed. + +App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. ![](screen1.png) ![](screen2.png) @@ -15,4 +23,6 @@ App Settings : Select the desired display units. Speed can be as per the default 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! -( Many thanks to Gordon Williams. Awesome job. ) +Thanks: +Many thanks to Gordon Williams. Awesome job. +Also to @jeffmer, the developer of the 'GPS Navigation' app. From 4808a3aebeaece60e0a8e20f0bf4eff4e120bcaf Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 17:13:49 +1300 Subject: [PATCH 011/181] Update README.md --- apps/speedalt/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index d7a21bef5..6d8018a56 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -16,10 +16,16 @@ BTN3 : (Only in Speed+Distance mode) Select waypoint. Last fix distance from sel App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. -![](screen1.png) -![](screen2.png) -![](screen3.png) -![](screen4.png) +Speed and Altitude:
+![](screen1.png)

+Left tap swaps displays:
+![](screen2.png)

+Distance to waypoint DeltaW:
+![](screen5.png)

+MAX Values instead:
+![](screen3.png)

+Settings:
+![](screen4.png)

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! From 468b80504f50713ad50a85e338e162b5fbf9129b Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 17:14:21 +1300 Subject: [PATCH 012/181] Add files via upload --- apps/speedalt/screen5.png | Bin 0 -> 4201 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedalt/screen5.png diff --git a/apps/speedalt/screen5.png b/apps/speedalt/screen5.png new file mode 100644 index 0000000000000000000000000000000000000000..1d6d51f10be7f654a3d2942bbdf9ec5e26409213 GIT binary patch literal 4201 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 literal 0 HcmV?d00001 From 5907747904c89d564e8ddc1e6196d577c283f157 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 17:22:20 +1300 Subject: [PATCH 013/181] Update README.md --- apps/speedalt/README.md | 71 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 6d8018a56..cbff3cf5d 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -6,7 +6,7 @@ The waypoints list is the same as that used with the [GPS Navigation] app so the Left Display Tap : Swaps the displays. You can have either speed or altitude/distance on the large primary display. -Right Display Tap : Swaps the modes between Speed+Altitude or Speed+Distance Note: You cannot swap to Speed+Distance or select waypoints while displaying MAX values. +Right Display Tap : Swaps the modes between Speed+Altitude or Speed+Distance Note: You cannot swap to Speed+Distance while displaying MAX values. BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. @@ -32,3 +32,72 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft Thanks: Many thanks to Gordon Williams. Awesome job. Also to @jeffmer, the developer of the 'GPS Navigation' app. + +Waypoints: + +Create a file waypoints.json and write to storage on the Bangle.js using the IDE. + +Sample waypoints.json + +

+[
+  {
+  "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
+  }
+]
+
From 4a75840802ad108800349d1d845a610e5ab1922d Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 17:33:49 +1300 Subject: [PATCH 014/181] Update app.js --- apps/speedalt/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 23475b097..4432e2d85 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -34,6 +34,8 @@ var wp = {}; // Waypoint to use for distance from cur position. function nextwp(inc){ if (altDisp) return; + if ( showMax ) return; + wpindex+=inc; if (wpindex>=waypoints.length) wpindex=0; if (wpindex<0) wpindex = waypoints.length-1; @@ -194,6 +196,7 @@ function drawWP() { if ( nm == undefined ) nm = ''; if ( nm == 'NONE' ) nm = ''; if ( altDisp ) nm=''; + if ( showMax ) nm=''; buf.setFontAlign(-12,1); //left, bottom From 17cff7315f96f69945093d335a3720239eea2739 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 18:48:29 +1300 Subject: [PATCH 015/181] Update waypoints.json --- apps/speedalt/waypoints.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/speedalt/waypoints.json b/apps/speedalt/waypoints.json index d1af258b2..b117bc5c2 100644 --- a/apps/speedalt/waypoints.json +++ b/apps/speedalt/waypoints.json @@ -28,6 +28,11 @@ "lon":175.8088720 }, { + "name":"MotuTa", + "lat":-38.85454, + "lon":175.94199 + }, + { "name":"Nook", "lat":-38.7848090, "lon":175.7839440 From cb57c9004713a6ab5be41981fcf47bd670c06a23 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 18:59:33 +1300 Subject: [PATCH 016/181] Update app.js --- apps/speedalt/app.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 4432e2d85..f468e0395 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -357,14 +357,13 @@ function setButtons(){ setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); // Select a waypoint for dist display - setWatch(nextwp.bind(this,1), BTN3, {repeat:true,edge:"falling"}); + setWatch(nextwp.bind(this,1), BTN3, {repeat:true,edge:"rising"}); // Touch left screen to toggle display - setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); + setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); // Touch left screen to toggle between alt or dist - setWatch(toggleAltDist, BTN5, {repeat:true,edge:"falling"}); - + setWatch(toggleAltDist, BTN5, {repeat:true,edge:"rising"}); } @@ -420,10 +419,8 @@ settings.spd = settings.spd||0; // Multiplier for speed unit conversions. 0 = u settings.spd_unit = settings.spd_unit||''; // Displayed speed unit settings.alt = settings.alt||0.3048;// Multiplier for altitude unit conversions. settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units - settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units - settings.colour = settings.colour||0; // Colour scheme. settings.buzz = settings.buzz||0; // Buzz when fix lost or gained. From 2f2faea7b326a799a05f1c5cd3bfc89b66ac0fe5 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 19:01:57 +1300 Subject: [PATCH 017/181] Add distance units --- apps/speedalt/settings.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 01de13c27..3f4d6303f 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -19,6 +19,12 @@ writeSettings(); } + function setUnitsDist(d,u) { + settings.dist = d; + settings.dist_unit = u; + writeSettings(); + } + function setColour(c) { settings.colour = c; writeSettings(); @@ -43,9 +49,9 @@ 'Knots (spd)' : function() { setUnits(1.852,'knots'); }, 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, - 'Km (dist)' : function() { setUnits(1000,'km'); }, - 'Miles (dist)' : function() { setUnits(1609.344,'miles'); }, - 'Nm (dist)' : function() { setUnits(1852.001,'nm'); }, + 'Km (dist)' : function() { setUnitsDist(1000,'km'); }, + 'Miles (dist)' : function() { setUnitsDist(1609.344,'miles'); }, + 'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); }, 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, 'Feet (alt)' : function() { setUnitsAlt(0.3048,'feet'); } }; From 47a86312a2ae62ebf562011f595929ea12a73621 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 21:59:22 +1300 Subject: [PATCH 018/181] LEDs indicate Spd+Alt or Spd+Dist modes Use red/green leds to indicate current mode. Use BTN1 for all tasks. In Spd+Alt mode changes MAX settings. In Spd+Dist mode selects next waypoint. --- apps/speedalt/app.js | 65 ++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index f468e0395..609638191 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 0.10 +Ver : 0.11 Mike Bennett mike[at]kereru.com */ @@ -40,7 +40,6 @@ function nextwp(inc){ if (wpindex>=waypoints.length) wpindex=0; if (wpindex<0) wpindex = waypoints.length-1; wp = waypoints[wpindex]; - onGPS(lastFix); } function radians(a) { @@ -64,10 +63,14 @@ function drawFix(speed,units,sats,alt,alt_units) { if (!canDraw) return; buf.clear(); + + drawLED(0); // Use LED to indicate current mode var val = ''; var u=''; + // LED to distingush current mode + // Primary Display val = speed.toString(); if ( !primaryDisp ) val = alt.toString(); @@ -111,6 +114,8 @@ function drawNoFix(sats) { buf.clear(); + drawLED(1); + buf.setFontAlign(0,0); buf.setColor(3); @@ -175,7 +180,14 @@ function drawSecondary(n,u) { buf.drawString(u,s,135); } +function drawLED(rst) { + LED1.reset(); + LED2.reset(); + if ( rst ) return; + if ( altDisp ) LED2.set(); // green = Speed/Alt mode + else LED1.set(); // red = Speed/Dist mode +} function drawTime() { var x = 0; @@ -225,6 +237,9 @@ function onGPS(fix) { var m; if (fix.fix || emulator) { + + lastFix.fix=1; + doBuzz(1); //==== Speed ==== @@ -290,6 +305,7 @@ function onGPS(fix) { } else { + lastFix.fix=0; doBuzz(0); drawNoFix(fix.satellites); } @@ -320,8 +336,6 @@ function doBuzz(hasFix) { Bangle.buzz(); return; } - - } // Second buzz @@ -337,28 +351,21 @@ function toggleDisplay() { } function toggleAltDist() { + if ( showMax ) return; altDisp = !altDisp; onGPS(lastFix); } -function toggleMax() { -// if ( inMenu ) return; - showMax = !showMax; - onGPS(lastFix); -} - function setButtons(){ - // Switch between fix and max display on short press or reset max values on long press - setWatch(maxPressed, BTN1,{repeat:true,edge:"rising"}); - setWatch(maxReleased, BTN1,{repeat:true,edge:"falling"}); + // Spd+Alt : Switch between fix and max display on short press or reset max values on long press + // Spd+Dist : Select next waypoint + setWatch(btnPressed, BTN1,{repeat:true,edge:"rising"}); + setWatch(btnReleased, BTN1,{repeat:true,edge:"falling"}); // Show launcher when middle button pressed setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); - // Select a waypoint for dist display - setWatch(nextwp.bind(this,1), BTN3, {repeat:true,edge:"rising"}); - // Touch left screen to toggle display setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); @@ -367,19 +374,29 @@ function setButtons(){ } -function maxPressed() { +function btnPressed() { + if ( !lastFix.fix ) return; maxPress = getTime(); } -function maxReleased() { +function btnReleased() { + if ( !lastFix.fix ) return; var dur = getTime()-maxPress; - - if ( dur < 2 ) toggleMax(); // Short press toggle fix/max display - else { - max.spd = 0; // Long press resets max values. - max.alt = 0; - onGPS(lastFix); // redraw display + if ( altDisp ) { + // Spd+Alt mode - Switch between fix and MAX + if ( dur < 2 ) { + showMax = !showMax; // Short press toggle fix/max display + } + else { + max.spd = 0; // Long press resets max values. + max.alt = 0; + } } + else { + // Spd+Dist mode - Select next waypoint + nextwp(1); + } + onGPS(lastFix); } function updateClock() { From ad5169eb98eccc50f06674fcdbce953ab3dcdb8d Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:00:49 +1300 Subject: [PATCH 019/181] Update ChangeLog --- apps/speedalt/ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index ac05ad706..51d8cced3 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,4 +5,5 @@ 0.05 : Add setting to turn vibrate on/off. 0.06 : Tweaks to vibration settings. 0.07 : Switch to BTN1 for Max toggle and reset function. -1.00 : New feature. Added waypoints file and distance to selected waypoint display. +1.00 : New feature. Added waypoints file and distance to selected waypoint display +1.01 : Use LEDs to indicate mode. Green = Spd+Alt. Red = Spd+Dist. BTN1 for all functions. From 6cc5761b7623701d4dd1757c7d48ff340fa81ed0 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:01:48 +1300 Subject: [PATCH 020/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 609638191..58a3a7e63 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 0.11 +Ver : 1.01 Mike Bennett mike[at]kereru.com */ From 4da33b20c8bfe871282aa20e4a3b1a232dbcf043 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:02:36 +1300 Subject: [PATCH 021/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index b5a98ec85..6329f9e26 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.00", + "version":"1.01", "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", "type":"app", From b7b60dc3a61b4715a9dc70d9e586905000251404 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:24:08 +1300 Subject: [PATCH 022/181] Update README.md --- apps/speedalt/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index cbff3cf5d..c198a89b7 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -1,18 +1,18 @@ Displays the GPS speed, altitude and distance to selected waypoint. One 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. -You can chose between two modes. One showing speed and altitude and one showing speed and distance to waypoint. +You can chose between two modes. One showing speed and altitude (green LED) and one showing speed and distance to waypoint (red LED). The waypoints list is the same as that used with the [GPS Navigation] app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. Left Display Tap : Swaps the displays. You can have either speed or altitude/distance on the large primary display. -Right Display Tap : Swaps the modes between Speed+Altitude or Speed+Distance Note: You cannot swap to Speed+Distance while displaying MAX values. +Right Display Tap : Swaps the modes between Speed+Altitude (green LED) or Speed+Distance (red LED). -BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. +BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. -BTN1 : Long press > 2 secs resets the recorded maximum values. +BTN1 : [Speed+Altitude] Long press > 2 secs resets the recorded maximum values. -BTN3 : (Only in Speed+Distance mode) Select waypoint. Last fix distance from selected waypoint is displayed. +BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected waypoint is displayed. App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. From 731303ee71ccc327d59b512653d12911b657a966 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:24:58 +1300 Subject: [PATCH 023/181] Update app.js --- apps/speedalt/app.js | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 58a3a7e63..01e0373d5 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -34,8 +34,6 @@ var wp = {}; // Waypoint to use for distance from cur position. function nextwp(inc){ if (altDisp) return; - if ( showMax ) return; - wpindex+=inc; if (wpindex>=waypoints.length) wpindex=0; if (wpindex<0) wpindex = waypoints.length-1; @@ -208,7 +206,6 @@ function drawWP() { if ( nm == undefined ) nm = ''; if ( nm == 'NONE' ) nm = ''; if ( altDisp ) nm=''; - if ( showMax ) nm=''; buf.setFontAlign(-12,1); //left, bottom @@ -224,7 +221,7 @@ function drawSats(sats) { buf.setFontAlign(1,1); //right, bottom buf.setColor(3); buf.setFont("6x8", 2); - if ( showMax ) { + if ( showMax && altDisp ) { buf.setColor(2); buf.drawString("MAX",240,160); } @@ -282,27 +279,26 @@ function onGPS(fix) { if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); - if ( showMax ) { - // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); - } - else { - if ( altDisp ) { + if ( altDisp ) { + if ( showMax ) { + // Speed and alt maximums + drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); + } + else { // Show speed/altitude drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit); } + } + else { + // Show speed/distance + if ( dist <= 0 ) { + // No WP selected + drawFix(speed,settings.spd_unit,fix.satellites,'',''); + } else { - // Show speed/distance - if ( dist <= 0 ) { - // No WP selected - drawFix(speed,settings.spd_unit,fix.satellites,'',''); - } - else { - drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit); - } + drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit); } } - } else { lastFix.fix=0; @@ -351,7 +347,6 @@ function toggleDisplay() { } function toggleAltDist() { - if ( showMax ) return; altDisp = !altDisp; onGPS(lastFix); } From 1ad16ab585a091b024515bba47069e11be39da94 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:25:44 +1300 Subject: [PATCH 024/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 01e0373d5..a3ff5491b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.01 +Ver : 1.02 Mike Bennett mike[at]kereru.com */ From fde821022af55a56fc87ec92f22a6f637456e995 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 31 Jan 2021 22:26:23 +1300 Subject: [PATCH 025/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 6329f9e26..4e804d718 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.01", + "version":"1.02", "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", "type":"app", From 677ed52b0188a6ed7ba7d71e1939bf4e2f277122 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 19:40:37 +1300 Subject: [PATCH 026/181] Change screen for no fix. --- apps/speedalt/app.js | 213 ++++++++++++++++--------------------------- 1 file changed, 80 insertions(+), 133 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index a3ff5491b..5c13324f2 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.02 +Ver : 1.03 Mike Bennett mike[at]kereru.com */ @@ -17,9 +17,9 @@ var altDisp = 1; // 1 = alt, 0 = dist to wp var showMax = 0; // 1 = display the max values. 0 = display the cur fix var maxPress = 0; // Time max button pressed. Used to calculate short or long press. var canDraw = 1; -var lastBuzz = 0; // What sort of buzz was last performed. 0 = no fix, 1 = fix. -var timerBuzz2 = 0; // ID of timer for fix second buzz var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var ledID = 0; // ID of interval doing the LED flasher +var ledOn = 0; // LED state flag for flasher var max = {}; max.spd = 0; @@ -57,18 +57,17 @@ function distance(a,b){ return d; } -function drawFix(speed,units,sats,alt,alt_units) { +function drawFix(speed,units,sats,alt,alt_units,age,fix) { if (!canDraw) return; buf.clear(); - drawLED(0); // Use LED to indicate current mode + if ( fix ) drawLED(0); // Use LED to indicate current mode + else drawLEDFlash(0); // Flashing indicate no fix sodisplaying last known var val = ''; var u=''; - // LED to distingush current mode - // Primary Display val = speed.toString(); if ( !primaryDisp ) val = alt.toString(); @@ -96,40 +95,11 @@ function drawFix(speed,units,sats,alt,alt_units) { drawWP(); //Sats - drawSats(sats); + if ( fix ) drawSats('Sats:'+sats); + else drawSats('Age:'+age); g.reset(); g.drawImage(img,0,40); -// g.flip(); - - -} - - -function drawNoFix(sats) { - if (!canDraw) return; - var u; - - buf.clear(); - - drawLED(1); - - buf.setFontAlign(0,0); - buf.setColor(3); - - buf.setFontVector(25); - buf.drawString("Waiting for GPS",120,56); - - // Time - drawTime(); - - //Sats - drawSats(sats); - - g.reset(); - g.drawImage(img,0,40); -// g.flip(); - } @@ -179,14 +149,25 @@ function drawSecondary(n,u) { } function drawLED(rst) { + if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher LED1.reset(); LED2.reset(); if ( rst ) return; - if ( altDisp ) LED2.set(); // green = Speed/Alt mode else LED1.set(); // red = Speed/Dist mode } +function drawLEDFlash(rst) { + if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher + LED1.reset(); + LED2.reset(); + ledOn = 0; + if ( rst ) return; + if ( altDisp ) ledID = setInterval(function() {ledOn=!ledOn;digitalWrite(LED2,ledOn);},500); // green = Speed/Alt mode + else ledID = setInterval(function() {ledOn=!ledOn;digitalWrite(LED1,ledOn);},500); // red = Speed/Dist mode +} + + function drawTime() { var x = 0; var y = 160; @@ -207,7 +188,6 @@ function drawWP() { if ( nm == 'NONE' ) nm = ''; if ( altDisp ) nm=''; - buf.setFontAlign(-12,1); //left, bottom buf.setColor(2); // buf.setFont("6x8", 1); @@ -218,128 +198,98 @@ function drawWP() { function drawSats(sats) { - buf.setFontAlign(1,1); //right, bottom + + if ( showMax && altDisp ) { + buf.setFontVector(20); + buf.setFontAlign(0,1); //centre, bottom + buf.setColor(2); + buf.drawString("MAX",120,160); + } + buf.setColor(3); buf.setFont("6x8", 2); - if ( showMax && altDisp ) { - buf.setColor(2); - buf.drawString("MAX",240,160); - } - else buf.drawString("Sats:"+sats,240,160); + buf.setFontAlign(1,1); //right, bottom + buf.drawString(sats,240,160); } function onGPS(fix) { - lastFix = fix; + +//print ( fix); + + if ( emulator ) { + fix.fix = 1; + fix.speed = 125; + fix.alt = 390; + fix.lat = -38.92; + fix.lon = 175.7613350; + fix.course = 245; + fix.satellites = 12; + fix.time = new Date(); + } + + if (fix.fix) lastFix = fix; + var m; - if (fix.fix || emulator) { + speed = '---'; + alt = '---'; + dist = '---'; + age = '---'; + + if (lastFix.fix == 1 ) { - lastFix.fix=1; - - doBuzz(1); - - //==== Speed ==== - if ( settings.spd == 0 ) { - var strSpeed = require("locale").speed(fix.speed); - m = strSpeed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units - - if ( emulator ) { - speed = '125'; //testing only - settings.spd_unit = 'kph'; - } - else { + //==== Speed ==== + if ( settings.spd == 0 ) { + m = require("locale").speed(lastFix.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units speed = m[1]; settings.spd_unit = m[2]; } + else { + // Calculate for selected units + speed = lastFix.speed; + if ( emulator ) speed = '100'; + speed = Math.round(parseFloat(speed)/parseFloat(settings.spd)); + } + if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); + + // ==== Altitude ==== + alt = lastFix.alt; + alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); + if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); + + // ==== Distance to waypoint ==== + dist = distance(lastFix,wp); + if (isNaN(dist)) dist = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lastFix.time.getTime()/1000)); //Can be negative if GPS time and watch drift apart. + if ( age > 90 ) age = '>90'; } - // Calculate for selected units - else { - speed = fix.speed; - if ( emulator ) speed = '100'; - speed = Math.round(parseFloat(speed)/parseFloat(settings.spd)); - } - - // ==== Altitude ==== - alt = fix.alt; - if ( emulator ) alt = '360'; - alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); - - // ==== Distance to waypoint ==== - if ( emulator ) { - lastFix.lat = -38.92; - lastFix.lon = 175.7613350; - } - - dist = distance(lastFix,wp); - if (isNaN(dist)) dist = 0; - - - // Record max values - if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); - if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); - + if ( altDisp ) { if ( showMax ) { // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); + drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit,age,fix.fix); } else { // Show speed/altitude - drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit); + drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit,age,fix.fix); } } else { // Show speed/distance if ( dist <= 0 ) { // No WP selected - drawFix(speed,settings.spd_unit,fix.satellites,'',''); + drawFix(speed,settings.spd_unit,fix.satellites,'','',age,fix.fix); } else { - drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit); + drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit,age,fix.fix); } } - } - else { - lastFix.fix=0; - doBuzz(0); - drawNoFix(fix.satellites); - } } -// Vibrate watch when fix lost or gained. -function doBuzz(hasFix) { - - // nothing to do - if ( lastBuzz === hasFix || !settings.buzz ) { - return; - } - - // fix gained - double buzz - if ( !lastBuzz && hasFix ) { - if ( dbg ) print('Fix'); - lastBuzz = 1; - Bangle.buzz(); - timerBuzz2 = setInterval(doBuzz2, 600); // Trigger a second buzz - return; - } - - // fix lost - single buzz - if ( lastBuzz && !hasFix ) { - if ( dbg ) print('Fix lost'); - lastBuzz = 0; - Bangle.buzz(); - return; - } -} - -// Second buzz -function doBuzz2() { - if ( dbg ) print('Buzz2'); - clearInterval(timerBuzz2); - Bangle.buzz(); - } function toggleDisplay() { primaryDisp = !primaryDisp; @@ -370,12 +320,10 @@ function setButtons(){ } function btnPressed() { - if ( !lastFix.fix ) return; maxPress = getTime(); } function btnReleased() { - if ( !lastFix.fix ) return; var dur = getTime()-maxPress; if ( altDisp ) { // Spd+Alt mode - Switch between fix and MAX @@ -434,7 +382,6 @@ settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units settings.colour = settings.colour||0; // Colour scheme. -settings.buzz = settings.buzz||0; // Buzz when fix lost or gained. /* Colour Pallet Idx From fe019eb184ae0798462bb0302af011dee23ee07e Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 19:41:19 +1300 Subject: [PATCH 027/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4e804d718..bf1e983c7 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.02", + "version":"1.03", "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", "type":"app", From 1669f5f910ef9c984fbc237cc9bce3cf68245cfb Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 22:20:48 +1300 Subject: [PATCH 028/181] Update app.js --- apps/speedalt/app.js | 66 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 5c13324f2..9394c9c71 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -6,7 +6,7 @@ Mike Bennett mike[at]kereru.com const dbg = 0; -var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); +var buf = Graphics.createArrayBuffer(240,160,4,{msb:true}); // Load fonts require("Font7x11Numeric7Seg").add(Graphics); @@ -62,8 +62,8 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { buf.clear(); - if ( fix ) drawLED(0); // Use LED to indicate current mode - else drawLEDFlash(0); // Flashing indicate no fix sodisplaying last known + if ( fix ) drawLED(); // Use LED to indicate current mode + else drawLEDFlash(); // Flashing indicate no fix so displaying last known var val = ''; var u=''; @@ -148,25 +148,29 @@ function drawSecondary(n,u) { buf.drawString(u,s,135); } -function drawLED(rst) { +function drawLED() { if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher - LED1.reset(); - LED2.reset(); - if ( rst ) return; - if ( altDisp ) LED2.set(); // green = Speed/Alt mode - else LED1.set(); // red = Speed/Dist mode + drawLEDCircle(0); } -function drawLEDFlash(rst) { +function drawLEDFlash() { if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher - LED1.reset(); - LED2.reset(); ledOn = 0; - if ( rst ) return; - if ( altDisp ) ledID = setInterval(function() {ledOn=!ledOn;digitalWrite(LED2,ledOn);},500); // green = Speed/Alt mode - else ledID = setInterval(function() {ledOn=!ledOn;digitalWrite(LED1,ledOn);},500); // red = Speed/Dist mode + ledID = setInterval(function() {ledOn=!ledOn;drawLEDCircle(ledOn);},500); // Toggle the LED + } +function drawLEDCircle(rst) { + var x=225; + var y=120; + var r=10; + if ( rst ) buf.setColor(0); // background = off + else if ( altDisp ) buf.setColor(5); // blue = Speed/Alt mode + else buf.setColor(4); // red = Speed/Dist mode + buf.fillCircle(x,y,r); + g.reset(); + g.drawImage(img,0,40); +} function drawTime() { var x = 0; @@ -196,7 +200,6 @@ function drawWP() { } - function drawSats(sats) { if ( showMax && altDisp ) { @@ -213,9 +216,6 @@ function drawSats(sats) { } function onGPS(fix) { - -//print ( fix); - if ( emulator ) { fix.fix = 1; @@ -232,14 +232,13 @@ function onGPS(fix) { var m; - speed = '---'; + speed = '---'; alt = '---'; dist = '---'; age = '---'; if (lastFix.fix == 1 ) { - - //==== Speed ==== + // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lastFix.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units speed = m[1]; @@ -253,17 +252,17 @@ function onGPS(fix) { } if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); - // ==== Altitude ==== + // Altitude alt = lastFix.alt; alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); - // ==== Distance to waypoint ==== + // Distance to waypoint dist = distance(lastFix,wp); if (isNaN(dist)) dist = 0; // Age of last fix (secs) - age = Math.max(0,Math.round(getTime())-(lastFix.time.getTime()/1000)); //Can be negative if GPS time and watch drift apart. + age = Math.max(0,Math.round(getTime())-(lastFix.time.getTime()/1000)); if ( age > 90 ) age = '>90'; } @@ -303,7 +302,6 @@ function toggleAltDist() { function setButtons(){ - // Spd+Alt : Switch between fix and max display on short press or reset max values on long press // Spd+Dist : Select next waypoint setWatch(btnPressed, BTN1,{repeat:true,edge:"rising"}); setWatch(btnReleased, BTN1,{repeat:true,edge:"falling"}); @@ -351,7 +349,6 @@ function updateClock() { g.drawImage(img,0,40); // g.flip(); - // Something different to display in the emulator if ( emulator ) { max.spd++; max.alt++; @@ -370,7 +367,7 @@ function stopDraw() { canDraw=false; } -// ===== Main Prog ===== +// =Main Prog // Read settings. let settings = require('Storage').readJSON('speedalt.json',1)||{}; @@ -389,18 +386,21 @@ Colour Pallet Idx 1 : Speed/Alt 2 : Units 3 : Sats +4 : led1 (red) +5 : led2 (blue) */ var img = { width:buf.getWidth(), height:buf.getHeight(), - bpp:2, + bpp:4, buffer:buf.buffer, - palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB,0xF800,0x051F,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]) }; -if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF]); -if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800]); - +/* +if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]); +if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]); +*/ // Find speed unit if using locale speed if ( settings.spd == 0 ) { From 6e42bea69e8701c1da394ec1c7829f8a0a95b8ac Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 22:48:30 +1300 Subject: [PATCH 029/181] Update app.js --- apps/speedalt/app.js | 93 ++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 60 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 9394c9c71..e6b028767 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,25 +1,23 @@ /* Speed and Altitude [speedalt] -Ver : 1.03 +Ver : 1.04 Mike Bennett mike[at]kereru.com */ const dbg = 0; -var buf = Graphics.createArrayBuffer(240,160,4,{msb:true}); +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts require("Font7x11Numeric7Seg").add(Graphics); -var lastFix = {fix:0,satellites:0}; +var lf = {fix:0,satellites:0}; var primaryDisp = 1; // 1 = Speed in primary display. 0 = alt/dist in primary var altDisp = 1; // 1 = alt, 0 = dist to wp var showMax = 0; // 1 = display the max values. 0 = display the cur fix var maxPress = 0; // Time max button pressed. Used to calculate short or long press. var canDraw = 1; var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. -var ledID = 0; // ID of interval doing the LED flasher -var ledOn = 0; // LED state flag for flasher var max = {}; max.spd = 0; @@ -62,9 +60,6 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { buf.clear(); - if ( fix ) drawLED(); // Use LED to indicate current mode - else drawLEDFlash(); // Flashing indicate no fix so displaying last known - var val = ''; var u=''; @@ -148,30 +143,6 @@ function drawSecondary(n,u) { buf.drawString(u,s,135); } -function drawLED() { - if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher - drawLEDCircle(0); -} - -function drawLEDFlash() { - if ( ledID > 0 ) clearInterval(ledID); // Stop the flasher - ledOn = 0; - ledID = setInterval(function() {ledOn=!ledOn;drawLEDCircle(ledOn);},500); // Toggle the LED - -} - -function drawLEDCircle(rst) { - var x=225; - var y=120; - var r=10; - if ( rst ) buf.setColor(0); // background = off - else if ( altDisp ) buf.setColor(5); // blue = Speed/Alt mode - else buf.setColor(4); // red = Speed/Dist mode - buf.fillCircle(x,y,r); - g.reset(); - g.drawImage(img,0,40); -} - function drawTime() { var x = 0; var y = 160; @@ -202,23 +173,29 @@ function drawWP() { function drawSats(sats) { - if ( showMax && altDisp ) { - buf.setFontVector(20); - buf.setFontAlign(0,1); //centre, bottom - buf.setColor(2); - buf.drawString("MAX",120,160); - } - buf.setColor(3); buf.setFont("6x8", 2); buf.setFontAlign(1,1); //right, bottom buf.drawString(sats,240,160); + + buf.setFontVector(20); + buf.setColor(2); + + if ( altDisp ) buf.drawString("A",240,140); + else buf.drawString("D",240,140); + + if ( showMax && altDisp ) { + buf.setFontAlign(0,1); //centre, bottom + buf.drawString("MAX",120,164); + } + + } function onGPS(fix) { if ( emulator ) { - fix.fix = 1; + fix.fix = 0; fix.speed = 125; fix.alt = 390; fix.lat = -38.92; @@ -228,7 +205,7 @@ function onGPS(fix) { fix.time = new Date(); } - if (fix.fix) lastFix = fix; + if (fix.fix) lf = fix; var m; @@ -237,32 +214,32 @@ function onGPS(fix) { dist = '---'; age = '---'; - if (lastFix.fix == 1 ) { + if (lf.fix == 1 ) { // Speed if ( settings.spd == 0 ) { - m = require("locale").speed(lastFix.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units speed = m[1]; settings.spd_unit = m[2]; } else { // Calculate for selected units - speed = lastFix.speed; + speed = lf.speed; if ( emulator ) speed = '100'; speed = Math.round(parseFloat(speed)/parseFloat(settings.spd)); } if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); // Altitude - alt = lastFix.alt; + alt = lf.alt; alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); // Distance to waypoint - dist = distance(lastFix,wp); + dist = distance(lf,wp); if (isNaN(dist)) dist = 0; // Age of last fix (secs) - age = Math.max(0,Math.round(getTime())-(lastFix.time.getTime()/1000)); + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); if ( age > 90 ) age = '>90'; } @@ -292,12 +269,12 @@ function onGPS(fix) { function toggleDisplay() { primaryDisp = !primaryDisp; - onGPS(lastFix); // Update display + onGPS(lf); // Update display } function toggleAltDist() { altDisp = !altDisp; - onGPS(lastFix); + onGPS(lf); } function setButtons(){ @@ -337,7 +314,7 @@ function btnReleased() { // Spd+Dist mode - Select next waypoint nextwp(1); } - onGPS(lastFix); + onGPS(lf); } function updateClock() { @@ -360,7 +337,7 @@ function startDraw(){ canDraw=true; g.clear(); Bangle.drawWidgets(); - onGPS(lastFix); // draw app screen + onGPS(lf); // draw app screen } function stopDraw() { @@ -386,21 +363,17 @@ Colour Pallet Idx 1 : Speed/Alt 2 : Units 3 : Sats -4 : led1 (red) -5 : led2 (blue) */ var img = { width:buf.getWidth(), height:buf.getHeight(), - bpp:4, + bpp:2, buffer:buf.buffer, - palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB,0xF800,0x051F,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]) + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) }; -/* -if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]); -if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF]); -*/ +if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF]); +if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800]); // Find speed unit if using locale speed if ( settings.spd == 0 ) { @@ -439,7 +412,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); Bangle.setGPSPower(1); -onGPS(lastFix); +onGPS(lf); Bangle.on('GPS', onGPS); setButtons(); From 2905f110942819b3bb3d6f5740cd362fd35617cf Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 22:49:44 +1300 Subject: [PATCH 030/181] Update ChangeLog --- apps/speedalt/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 51d8cced3..5470ed1d4 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -6,4 +6,4 @@ 0.06 : Tweaks to vibration settings. 0.07 : Switch to BTN1 for Max toggle and reset function. 1.00 : New feature. Added waypoints file and distance to selected waypoint display -1.01 : Use LEDs to indicate mode. Green = Spd+Alt. Red = Spd+Dist. BTN1 for all functions. +1.04 : Misc tweaks. From d475f56660249b2b113240c7a20cff4cb0bd9ca9 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 1 Feb 2021 22:50:21 +1300 Subject: [PATCH 031/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index bf1e983c7..e8fbc1105 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.03", + "version":"1.04", "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", "type":"app", From b7e0be2b4ab10389df9f02ff25bf3d94ea48a148 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 08:16:28 +1300 Subject: [PATCH 032/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index e6b028767..e86bc1e38 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -289,8 +289,8 @@ function setButtons(){ // Touch left screen to toggle display setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); - // Touch left screen to toggle between alt or dist - setWatch(toggleAltDist, BTN5, {repeat:true,edge:"rising"}); + // Toggle between alt or dist + setWatch(toggleAltDist, BTN2, {repeat:true,edge:"rising"}); } From ae58842e729a16fd17a76b97bf5dcd549d00c717 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 08:22:32 +1300 Subject: [PATCH 033/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index e86bc1e38..7364531fd 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -290,7 +290,7 @@ function setButtons(){ setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); // Toggle between alt or dist - setWatch(toggleAltDist, BTN2, {repeat:true,edge:"rising"}); + setWatch(toggleAltDist, BTN2, {repeat:true,edge:"falling"}); } From 10c18757f4ca16cea991f958da9f51e4bbb96397 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 08:27:32 +1300 Subject: [PATCH 034/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7364531fd..97da2f2b4 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -290,7 +290,7 @@ function setButtons(){ setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); // Toggle between alt or dist - setWatch(toggleAltDist, BTN2, {repeat:true,edge:"falling"}); + setWatch(toggleAltDist, BTN3, {repeat:true,edge:"rising"}); } From 2f1f561d43f4012ffe313cdae9d18b63b750ffb8 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 08:32:36 +1300 Subject: [PATCH 035/181] Update app.js --- apps/speedalt/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 97da2f2b4..7f5c76de0 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -286,12 +286,12 @@ function setButtons(){ // Show launcher when middle button pressed setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); - // Touch left screen to toggle display - setWatch(toggleDisplay, BTN4, {repeat:true,edge:"rising"}); - // Toggle between alt or dist - setWatch(toggleAltDist, BTN3, {repeat:true,edge:"rising"}); + setWatch(toggleAltDist, BTN3, {repeat:true,edge:"falling"}); + // Touch left screen to toggle display + setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); + } function btnPressed() { From 23493e946b15d351a505b30b152532ab34f07107 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 08:42:53 +1300 Subject: [PATCH 036/181] Update README.md --- apps/speedalt/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index c198a89b7..41def8b11 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -1,12 +1,10 @@ Displays the GPS speed, altitude and distance to selected waypoint. One 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. -You can chose between two modes. One showing speed and altitude (green LED) and one showing speed and distance to waypoint (red LED). +You can chose between two modes. One showing speed and altitude (A) and one showing speed and distance to waypoint (D). The waypoints list is the same as that used with the [GPS Navigation] app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. -Left Display Tap : Swaps the displays. You can have either speed or altitude/distance on the large primary display. - -Right Display Tap : Swaps the modes between Speed+Altitude (green LED) or Speed+Distance (red LED). +Left Display Tap : Swaps the displays. You can have either speed or [A]ltitude/[D]istance on the large primary display. BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. @@ -14,6 +12,8 @@ BTN1 : [Speed+Altitude] Long press > 2 secs resets the recorded maximum values. BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected waypoint is displayed. +BTN3 : Swaps the modes between Speed+Altitude. + App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. Speed and Altitude:
From 098be8a59a0645a32a59aa2ebdef22dd47dcee0a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 10:40:27 +1300 Subject: [PATCH 037/181] Memory optimisation Stopped loading entire waypoint list into memory. --- apps/speedalt/app.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7f5c76de0..2b70b84c7 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,7 +1,8 @@ /* Speed and Altitude [speedalt] -Ver : 1.04 +Ver : 1.05 Mike Bennett mike[at]kereru.com +process.memory() */ const dbg = 0; @@ -26,16 +27,20 @@ max.alt = 0; var emulator = 0; if (process.env.BOARD=="EMSCRIPTEN") emulator = 1; // 1 = running in emulator. Supplies test values; -var waypoints = require("Storage").readJSON("waypoints.json")||[{name:"NONE"}]; -var wpindex=0; var wp = {}; // Waypoint to use for distance from cur position. -function nextwp(inc){ +function nxtWp(inc){ if (altDisp) return; - wpindex+=inc; - if (wpindex>=waypoints.length) wpindex=0; - if (wpindex<0) wpindex = waypoints.length-1; - wp = waypoints[wpindex]; + settings.wp+=inc; + loadWp(); +} + +function loadWp() { + var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + if (settings.wp>=w.length) settings.wp=0; + if (settings.wp<0) settings.wp = w.length-1; + require("Storage").write('speedalt.json',settings); + wp = w[settings.wp]; } function radians(a) { @@ -312,7 +317,7 @@ function btnReleased() { } else { // Spd+Dist mode - Select next waypoint - nextwp(1); + nxtWp(1); } onGPS(lf); } @@ -355,7 +360,10 @@ settings.alt = settings.alt||0.3048;// Multiplier for altitude unit conversions. settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units -settings.colour = settings.colour||0; // Colour scheme. +settings.colour = settings.colour||0; // Colour scheme. +settings.wp = settings.wp||0; // Last selected waypoint for dist + +loadWp(); /* Colour Pallet Idx From 2cda0b0a5445ef35e99a277722c28689efc98615 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 10:41:04 +1300 Subject: [PATCH 038/181] Update ChangeLog --- apps/speedalt/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 5470ed1d4..fa189dd31 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -7,3 +7,4 @@ 0.07 : Switch to BTN1 for Max toggle and reset function. 1.00 : New feature. Added waypoints file and distance to selected waypoint display 1.04 : Misc tweaks. +1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. From 61f4d023a3350a2e2d5f9dbc1790308a7777e8d2 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 10:41:46 +1300 Subject: [PATCH 039/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index e8fbc1105..5a46930c4 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.04", + "version":"1.05", "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", "type":"app", From d5e031b21f11b8b97571add61294988869384852 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 10:51:59 +1300 Subject: [PATCH 040/181] Update README.md --- apps/speedalt/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 41def8b11..f9db449f4 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -12,9 +12,11 @@ BTN1 : [Speed+Altitude] Long press > 2 secs resets the recorded maximum values. BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected waypoint is displayed. -BTN3 : Swaps the modes between Speed+Altitude. +BTN3 : Swaps the modes between Speed+[A]ltitude or Speed+[D]istance. -App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. +App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default, high contrast (all white on black) or night ( all red on black ). + +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 fix or not. Speed and Altitude:
![](screen1.png)

@@ -35,7 +37,7 @@ Also to @jeffmer, the developer of the 'GPS Navigation' app. Waypoints: -Create a file waypoints.json and write to storage on the Bangle.js using the IDE. +Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displyed in Speed+[D]istance mode. Sample waypoints.json From a7b79618e816e026e0cd3cabec758bbb54156626 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 11:09:40 +1300 Subject: [PATCH 041/181] Update app.js --- apps/speedalt/app.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 2b70b84c7..2dcb78a67 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -200,9 +200,9 @@ function drawSats(sats) { function onGPS(fix) { if ( emulator ) { - fix.fix = 0; - fix.speed = 125; - fix.alt = 390; + fix.fix = 1; + fix.speed = 15; + fix.alt = 354; fix.lat = -38.92; fix.lon = 175.7613350; fix.course = 245; @@ -229,10 +229,11 @@ function onGPS(fix) { else { // Calculate for selected units speed = lf.speed; - if ( emulator ) speed = '100'; - speed = Math.round(parseFloat(speed)/parseFloat(settings.spd)); + speed = parseFloat(speed)/parseFloat(settings.spd); } if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); + if ( speed < 10 ) speed = speed.toFixed(1); + else speed = Math.round(speed); // Altitude alt = lf.alt; From 04b35145f6972939a0f01c8915c52670c06fe609 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 11:20:20 +1300 Subject: [PATCH 042/181] Add files via upload --- apps/speedalt/screen1.png | Bin 4324 -> 3937 bytes apps/speedalt/screen2.png | Bin 4138 -> 4026 bytes apps/speedalt/screen3.png | Bin 4193 -> 4491 bytes apps/speedalt/screen5.png | Bin 4201 -> 4427 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/speedalt/screen1.png b/apps/speedalt/screen1.png index eac5e7e7aa7be41cd9bb0d3d478d3a312f936960..fa477875dbc1e1aa100a8b8ee05d203b935e6e2f 100644 GIT binary patch literal 3937 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 BE0zEN literal 4324 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 diff --git a/apps/speedalt/screen3.png b/apps/speedalt/screen3.png index 9e061958e7e1e60aac01b75f1687c33c70f378af..1c32720197b79278cfc9890a36d8bc3ee7936b3c 100644 GIT binary patch literal 4491 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 literal 4201 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 From 8eb99f2452c4d2d5009cf22fe5d37f3ad077e606 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 11:21:41 +1300 Subject: [PATCH 043/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 2dcb78a67..77e6ff258 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -201,7 +201,7 @@ function onGPS(fix) { if ( emulator ) { fix.fix = 1; - fix.speed = 15; + fix.speed = 10; fix.alt = 354; fix.lat = -38.92; fix.lon = 175.7613350; @@ -231,9 +231,9 @@ function onGPS(fix) { speed = lf.speed; speed = parseFloat(speed)/parseFloat(settings.spd); } - if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); if ( speed < 10 ) speed = speed.toFixed(1); else speed = Math.round(speed); + if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); // Altitude alt = lf.alt; From fe9133cc24c0da1ca92b91c05070f66baa65215b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 15:57:41 +1300 Subject: [PATCH 044/181] Update app.js --- apps/speedalt/app.js | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 77e6ff258..2b16ef9da 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.05 +Ver : 1.06 Mike Bennett mike[at]kereru.com process.memory() */ @@ -13,8 +13,6 @@ var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); require("Font7x11Numeric7Seg").add(Graphics); var lf = {fix:0,satellites:0}; -var primaryDisp = 1; // 1 = Speed in primary display. 0 = alt/dist in primary -var altDisp = 1; // 1 = alt, 0 = dist to wp var showMax = 0; // 1 = display the max values. 0 = display the cur fix var maxPress = 0; // Time max button pressed. Used to calculate short or long press. var canDraw = 1; @@ -30,7 +28,7 @@ if (process.env.BOARD=="EMSCRIPTEN") emulator = 1; // 1 = running in emulator. var wp = {}; // Waypoint to use for distance from cur position. function nxtWp(inc){ - if (altDisp) return; + if (settings.modeA) return; settings.wp+=inc; loadWp(); } @@ -39,7 +37,7 @@ function loadWp() { var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; if (settings.wp>=w.length) settings.wp=0; if (settings.wp<0) settings.wp = w.length-1; - require("Storage").write('speedalt.json',settings); + savSettings(); wp = w[settings.wp]; } @@ -70,21 +68,21 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { // Primary Display val = speed.toString(); - if ( !primaryDisp ) val = alt.toString(); + if ( !settings.primSpd ) val = alt.toString(); // Primary Units u = settings.spd_unit; - if ( !primaryDisp ) u = alt_units; + if ( !settings.primSpd ) u = alt_units; drawPrimary(val,u); // Secondary Display val = alt.toString(); - if ( !primaryDisp ) val = speed.toString(); + if ( !settings.primSpd ) val = speed.toString(); // Secondary Units u = alt_units; - if ( !primaryDisp ) u = settings.spd_unit; + if ( !settings.primSpd ) u = settings.spd_unit; drawSecondary(val,u); @@ -166,7 +164,7 @@ function drawWP() { var nm = wp.name; if ( nm == undefined ) nm = ''; if ( nm == 'NONE' ) nm = ''; - if ( altDisp ) nm=''; + if ( settings.modeA ) nm=''; buf.setFontAlign(-12,1); //left, bottom buf.setColor(2); @@ -186,10 +184,10 @@ function drawSats(sats) { buf.setFontVector(20); buf.setColor(2); - if ( altDisp ) buf.drawString("A",240,140); + if ( settings.modeA ) buf.drawString("A",240,140); else buf.drawString("D",240,140); - if ( showMax && altDisp ) { + if ( showMax && settings.modeA ) { buf.setFontAlign(0,1); //centre, bottom buf.drawString("MAX",120,164); } @@ -249,7 +247,7 @@ function onGPS(fix) { if ( age > 90 ) age = '>90'; } - if ( altDisp ) { + if ( settings.modeA ) { if ( showMax ) { // Speed and alt maximums drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit,age,fix.fix); @@ -274,12 +272,14 @@ function onGPS(fix) { function toggleDisplay() { - primaryDisp = !primaryDisp; + settings.primSpd = !settings.primSpd; + savSettings(); onGPS(lf); // Update display } function toggleAltDist() { - altDisp = !altDisp; + settings.modeA = !settings.modeA; + savSettings(); onGPS(lf); } @@ -306,7 +306,7 @@ function btnPressed() { function btnReleased() { var dur = getTime()-maxPress; - if ( altDisp ) { + if ( settings.modeA ) { // Spd+Alt mode - Switch between fix and MAX if ( dur < 2 ) { showMax = !showMax; // Short press toggle fix/max display @@ -350,6 +350,10 @@ function stopDraw() { canDraw=false; } +function savSettings() { + require("Storage").write('speedalt.json',settings); +} + // =Main Prog // Read settings. @@ -363,6 +367,9 @@ settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units settings.colour = settings.colour||0; // Colour scheme. settings.wp = settings.wp||0; // Last selected waypoint for dist +settings.modeA = settings.modeA||0; // 0 = [D], 1 = [A] +settings.primSpd = settings.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary + loadWp(); From a91adfcf2dac2a23bb8ab389219ed66324aa9982 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 15:59:12 +1300 Subject: [PATCH 045/181] Update ChangeLog --- apps/speedalt/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index fa189dd31..265b2e4e1 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -8,3 +8,4 @@ 1.00 : New feature. Added waypoints file and distance to selected waypoint display 1.04 : Misc tweaks. 1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. +1.06 : Save dispay settings and restore when app restarted. From fc7ec82ebf06b5006e9a923bcef830051367cb13 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 16:00:00 +1300 Subject: [PATCH 046/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 5a46930c4..1d50aa1e6 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.05", + "version":"1.06", "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", "type":"app", From 5b07163ecc51ad2b2c31e120ebaabdb9c2198d3d Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 2 Feb 2021 16:01:29 +1300 Subject: [PATCH 047/181] Update ChangeLog --- apps/speedalt/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 265b2e4e1..405f80f63 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -8,4 +8,4 @@ 1.00 : New feature. Added waypoints file and distance to selected waypoint display 1.04 : Misc tweaks. 1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. -1.06 : Save dispay settings and restore when app restarted. +1.06 : Save display settings and restore when app restarted. From 20a02090ec1699d9378c31ae165fe127927a5c04 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 17:03:44 +1300 Subject: [PATCH 048/181] Update app.js --- apps/speedalt/app.js | 75 ++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 2b16ef9da..21e472c11 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,12 +1,10 @@ /* Speed and Altitude [speedalt] -Ver : 1.06 +Ver : 1.07 Mike Bennett mike[at]kereru.com process.memory() */ -const dbg = 0; - var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -22,8 +20,7 @@ var max = {}; max.spd = 0; max.alt = 0; -var emulator = 0; -if (process.env.BOARD=="EMSCRIPTEN") emulator = 1; // 1 = running in emulator. Supplies test values; +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. @@ -63,28 +60,28 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { buf.clear(); - var val = ''; + var v = ''; var u=''; // Primary Display - val = speed.toString(); - if ( !settings.primSpd ) val = alt.toString(); + v = speed.toString(); + if ( !settings.primSpd ) v = alt.toString(); // Primary Units u = settings.spd_unit; if ( !settings.primSpd ) u = alt_units; - drawPrimary(val,u); + drawPrimary(v,u); // Secondary Display - val = alt.toString(); - if ( !settings.primSpd ) val = speed.toString(); + v = alt.toString(); + if ( !settings.primSpd ) v = speed.toString(); // Secondary Units u = alt_units; if ( !settings.primSpd ) u = settings.spd_unit; - drawSecondary(val,u); + drawSecondary(v,u); // Time drawTime(); @@ -106,15 +103,16 @@ function drawPrimary(n,u) { // Primary Display var s=40; // Font size - if ( n.length <= 7 ) s=48; - if ( n.length <= 6 ) s=55; - if ( n.length <= 5 ) s=68; - if ( n.length <= 4 ) s=85; - if ( n.length <= 3 ) s=110; + var l=n.length; + + if ( l <= 7 ) s=48; + if ( l <= 6 ) s=55; + if ( l <= 5 ) s=68; + if ( l <= 4 ) s=85; + if ( l <= 3 ) s=110; buf.setFontAlign(0,-1); //Centre buf.setColor(1); - buf.setFontVector(s); buf.drawString(n,110,0); @@ -128,11 +126,12 @@ function drawPrimary(n,u) { function drawSecondary(n,u) { var s=180; // units X position - if ( n.length <= 5 ) s=155; - if ( n.length <= 4 ) s=125; - if ( n.length <= 3 ) s=100; - if ( n.length <= 2 ) s=65; - if ( n.length <= 1 ) s=35; + var l=n.length; + if ( l <= 5 ) s=155; + if ( l <= 4 ) s=125; + if ( l <= 3 ) s=100; + if ( l <= 2 ) s=65; + if ( l <= 1 ) s=35; buf.setFontAlign(-1,1); //left, bottom buf.setColor(1); @@ -212,10 +211,10 @@ function onGPS(fix) { var m; - speed = '---'; - alt = '---'; - dist = '---'; - age = '---'; + var speed = '---'; + var alt = '---'; + var dist = '---'; + var age = '---'; if (lf.fix == 1 ) { // Speed @@ -324,19 +323,11 @@ function btnReleased() { } function updateClock() { - if ( dbg ) print('Updating clock'); if (!canDraw) return; - drawTime(); g.reset(); g.drawImage(img,0,40); -// g.flip(); - - if ( emulator ) { - max.spd++; - max.alt++; - } - + if ( emulator ) {max.spd++;max.alt++;} } function startDraw(){ @@ -391,24 +382,15 @@ var img = { if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF]); if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800]); -// Find speed unit if using locale speed -if ( settings.spd == 0 ) { - var strSpeed = require("locale").speed(1); - m = strSpeed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units - settings.spd_unit = m[2]; -} - var SCREENACCESS = { withApp:true, request:function(){ this.withApp=false; stopDraw(); - clearWatch(); }, release:function(){ this.withApp=true; startDraw(); - setButtons(); } }; @@ -423,13 +405,10 @@ Bangle.on('lcdPower',function(on) { // All set up. Lets go. g.clear(); -Bangle.setLCDBrightness(1); Bangle.loadWidgets(); Bangle.drawWidgets(); Bangle.setGPSPower(1); - onGPS(lf); Bangle.on('GPS', onGPS); - setButtons(); setInterval(updateClock, 30000); From 1944382768ecbdb8b13668ab5fd119dab4d417ad Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 17:05:59 +1300 Subject: [PATCH 049/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 1d50aa1e6..f22a88ee1 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.06", + "version":"1.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", "type":"app", From 50f6075d40b9c03430dd80711efa002376b8ae7e Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 17:06:41 +1300 Subject: [PATCH 050/181] Update ChangeLog --- apps/speedalt/ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 405f80f63..debc08453 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -8,4 +8,5 @@ 1.00 : New feature. Added waypoints file and distance to selected waypoint display 1.04 : Misc tweaks. 1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. -1.06 : Save display settings and restore when app restarted. +1.06 : Save display settings and restore when app restarted. +1.07 : Memory optimisation. From 65001996639749bd93d576bebe75f79df1c7866b Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 17:39:16 +1300 Subject: [PATCH 051/181] Update app.js --- apps/speedalt/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 21e472c11..41ea06253 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -387,10 +387,12 @@ var SCREENACCESS = { request:function(){ this.withApp=false; stopDraw(); + clearWatch(); }, release:function(){ this.withApp=true; startDraw(); + setButtons(); } }; @@ -412,3 +414,4 @@ onGPS(lf); Bangle.on('GPS', onGPS); setButtons(); setInterval(updateClock, 30000); + From fb3853ce38bd6a7854fd67a70bab2c950f47b20c Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 18:04:48 +1300 Subject: [PATCH 052/181] Update app.js --- apps/speedalt/app.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 41ea06253..cd47bf15b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -220,7 +220,7 @@ function onGPS(fix) { // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units - speed = m[1]; + speed = parseFloat(m[1]); settings.spd_unit = m[2]; } else { @@ -387,12 +387,10 @@ var SCREENACCESS = { request:function(){ this.withApp=false; stopDraw(); - clearWatch(); }, release:function(){ this.withApp=true; startDraw(); - setButtons(); } }; @@ -414,4 +412,3 @@ onGPS(lf); Bangle.on('GPS', onGPS); setButtons(); setInterval(updateClock, 30000); - From 6bece4a54cfe42d35e045e8933dae6d1c34d868a Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 18:58:03 +1300 Subject: [PATCH 053/181] Update app.js --- apps/speedalt/app.js | 55 ++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index cd47bf15b..efd653802 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.07 +Ver : 1.07a mem optimise Mike Bennett mike[at]kereru.com process.memory() */ @@ -211,35 +211,35 @@ function onGPS(fix) { var m; - var speed = '---'; - var alt = '---'; - var dist = '---'; + var sp = '---'; + var al = '---'; + var di = '---'; var age = '---'; if (lf.fix == 1 ) { // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units - speed = parseFloat(m[1]); + sp = parseFloat(m[1]); settings.spd_unit = m[2]; } else { // Calculate for selected units - speed = lf.speed; - speed = parseFloat(speed)/parseFloat(settings.spd); + sp = lf.speed; + sp = parseFloat(sp)/parseFloat(settings.spd); } - if ( speed < 10 ) speed = speed.toFixed(1); - else speed = Math.round(speed); - if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + if (parseFloat(sp) > parseFloat(max.spd) ) max.spd = parseFloat(sp); // Altitude - alt = lf.alt; - alt = Math.round(parseFloat(alt)/parseFloat(settings.alt)); - if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(settings.alt)); + if (parseFloat(al) > parseFloat(max.alt) ) max.alt = parseFloat(al); // Distance to waypoint - dist = distance(lf,wp); - if (isNaN(dist)) dist = 0; + 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)); @@ -253,17 +253,17 @@ function onGPS(fix) { } else { // Show speed/altitude - drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,fix.satellites,al,settings.alt_unit,age,fix.fix); } } else { // Show speed/distance - if ( dist <= 0 ) { + if ( di <= 0 ) { // No WP selected - drawFix(speed,settings.spd_unit,fix.satellites,'','',age,fix.fix); + drawFix(sp,settings.spd_unit,fix.satellites,'','',age,fix.fix); } else { - drawFix(speed,settings.spd_unit,fix.satellites,dist,settings.dist_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,fix.satellites,di,settings.dist_unit,age,fix.fix); } } @@ -384,23 +384,14 @@ if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF8 var SCREENACCESS = { withApp:true, - request:function(){ - this.withApp=false; - stopDraw(); - }, - release:function(){ - this.withApp=true; - startDraw(); - } + request:function(){this.withApp=false;stopDraw();}, + release:function(){this.withApp=true;startDraw();} }; Bangle.on('lcdPower',function(on) { if (!SCREENACCESS.withApp) return; - if (on) { - startDraw(); - } else { - stopDraw(); - } + if (on) startDraw(); + else stopDraw(); }); // All set up. Lets go. From 69c4519b941b495f26cf4645405e23ff008928d8 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 21:03:05 +1300 Subject: [PATCH 054/181] Update settings.js --- apps/speedalt/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 3f4d6303f..114664e2c 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -34,11 +34,11 @@ '': {'title': 'GPS Speed Alt'}, '< Back': back, 'Units' : function() { E.showMenu(unitsMenu); }, - 'Colours' : function() { E.showMenu(colMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }/*, 'Vibrate' : { value : settings.buzz, format : v => v?"On":"Off", - onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } + onchange : () => { settings.buzz = !settings.buzz; writeSettings(); }*/ }}; const unitsMenu = { From 80927cfaab767394d305578bad4fb55d31f30269 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:01:55 +1300 Subject: [PATCH 055/181] Update app.js --- apps/speedalt/app.js | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index efd653802..672bada49 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.07a mem optimise +Ver : 1.07b low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ @@ -208,7 +208,12 @@ function onGPS(fix) { } if (fix.fix) lf = fix; + doFix(); + +} +function doFix() { + var m; var sp = '---'; @@ -249,21 +254,21 @@ function onGPS(fix) { if ( settings.modeA ) { if ( showMax ) { // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit,age,fix.fix); + drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); } else { // Show speed/altitude - drawFix(sp,settings.spd_unit,fix.satellites,al,settings.alt_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); } } else { // Show speed/distance if ( di <= 0 ) { // No WP selected - drawFix(sp,settings.spd_unit,fix.satellites,'','',age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); } else { - drawFix(sp,settings.spd_unit,fix.satellites,di,settings.dist_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); } } @@ -332,6 +337,7 @@ function updateClock() { function startDraw(){ canDraw=true; + setLpMode(0); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -339,12 +345,28 @@ function startDraw(){ function stopDraw() { canDraw=false; + setLpMode(1); // on } function savSettings() { require("Storage").write('speedalt.json',settings); } +// Is low power GPS service available to use? +function isLP() { + if (WIDGETS.gpsservice == undefined) return(0); + return(1); +} + +function setLpMode(on) { + if ( !lp ) return; + var settings = WIDGETS.gpsservice.gps_get_settings(); + settings.gpsservice = on; + settings.power_mode = (on)?'PSMOO':'SuperE'; + WIDGETS.gpsservice.gps_set_settings(settings); + WIDGETS.gpsservice.reload(); +} + // =Main Prog // Read settings. @@ -398,6 +420,10 @@ Bangle.on('lcdPower',function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + +var lp = isLP(); // Low power GPS widget installed. + + Bangle.setGPSPower(1); onGPS(lf); Bangle.on('GPS', onGPS); From 2a120cb7694a906be83065d282cb2acd64f52482 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:07:22 +1300 Subject: [PATCH 056/181] Update app.js --- apps/speedalt/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 672bada49..9c083a27d 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -363,6 +363,9 @@ function setLpMode(on) { var settings = WIDGETS.gpsservice.gps_get_settings(); settings.gpsservice = on; settings.power_mode = (on)?'PSMOO':'SuperE'; + + print(settings.power_mode); + WIDGETS.gpsservice.gps_set_settings(settings); WIDGETS.gpsservice.reload(); } From 0698cc31a3bf28daccf4b36cda437123a62310d9 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:14:18 +1300 Subject: [PATCH 057/181] Update app.js --- apps/speedalt/app.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 9c083a27d..52dace3f3 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -425,9 +425,8 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); var lp = isLP(); // Low power GPS widget installed. - - Bangle.setGPSPower(1); +setLpMode(0); onGPS(lf); Bangle.on('GPS', onGPS); setButtons(); From d20fb4cd53927c222af6c37a8f4f2a9e592b9d03 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:20:49 +1300 Subject: [PATCH 058/181] Update app.js --- apps/speedalt/app.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 52dace3f3..af298d27c 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -354,19 +354,18 @@ function savSettings() { // Is low power GPS service available to use? function isLP() { - if (WIDGETS.gpsservice == undefined) return(0); - return(1); + retrun (WIDGETS.gpsservice==undefined)?0:1; } function setLpMode(on) { if ( !lp ) return; - var settings = WIDGETS.gpsservice.gps_get_settings(); - settings.gpsservice = on; - settings.power_mode = (on)?'PSMOO':'SuperE'; + var s = WIDGETS.gpsservice.gps_get_settings(); + s.gpsservice = on; + s.power_mode = (on)?'PSMOO':'SuperE'; - print(settings.power_mode); + print(s.power_mode+''+s.update); - WIDGETS.gpsservice.gps_set_settings(settings); + WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From cf08ec851de848c210a82ae07074a65590f7f6d8 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:24:30 +1300 Subject: [PATCH 059/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index af298d27c..5c87ac002 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -363,7 +363,7 @@ function setLpMode(on) { s.gpsservice = on; s.power_mode = (on)?'PSMOO':'SuperE'; - print(s.power_mode+''+s.update); + print(s.power_mode+' '+s.update); WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 2ab9a562841f40120c1d50026fd601e6d04a6188 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:26:30 +1300 Subject: [PATCH 060/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 5c87ac002..6c31bc44f 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -363,7 +363,7 @@ function setLpMode(on) { s.gpsservice = on; s.power_mode = (on)?'PSMOO':'SuperE'; - print(s.power_mode+' '+s.update); + print(s.power_mode+' ['+s.update+']'); WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 9f81246989d44d2b71a046461c475d537116e6f4 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:28:29 +1300 Subject: [PATCH 061/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 6c31bc44f..7df9c0ea5 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -354,7 +354,7 @@ function savSettings() { // Is low power GPS service available to use? function isLP() { - retrun (WIDGETS.gpsservice==undefined)?0:1; + return (WIDGETS.gpsservice==undefined)?0:1; } function setLpMode(on) { From 06651b2575c973e45de65f3a4099e5a1f994d469 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:42:05 +1300 Subject: [PATCH 062/181] Update app.js --- apps/speedalt/app.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7df9c0ea5..fde27bec5 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -362,9 +362,6 @@ function setLpMode(on) { var s = WIDGETS.gpsservice.gps_get_settings(); s.gpsservice = on; s.power_mode = (on)?'PSMOO':'SuperE'; - - print(s.power_mode+' ['+s.update+']'); - WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From acaa293abc06c4cecc06d2d27180076dde9a4ec6 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:45:37 +1300 Subject: [PATCH 063/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index fde27bec5..fe080d110 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -360,7 +360,7 @@ function isLP() { function setLpMode(on) { if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); - s.gpsservice = on; + s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 6fd412a25c4b1a5032d16e5deb50ced4bb90a4ff Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:47:18 +1300 Subject: [PATCH 064/181] Update app.js --- apps/speedalt/app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index fe080d110..c279a227e 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -362,6 +362,9 @@ function setLpMode(on) { var s = WIDGETS.gpsservice.gps_get_settings(); s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; + +print(s.power_mode+' ['+s.update+']'); + WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From 7d40130b20a6c7e11c2ddfa08a48e95e0f1d7d93 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:48:35 +1300 Subject: [PATCH 065/181] Update settings.js --- apps/speedalt/settings.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 114664e2c..5f7418dbb 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -38,8 +38,9 @@ 'Vibrate' : { value : settings.buzz, format : v => v?"On":"Off", - onchange : () => { settings.buzz = !settings.buzz; writeSettings(); }*/ - }}; + onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } + }*/ + }; const unitsMenu = { '': {'title': 'Units'}, From e8605338bd398cd71d56a410d1eba3ff5f79e8c8 Mon Sep 17 00:00:00 2001 From: nujw Date: Thu, 4 Feb 2021 22:57:01 +1300 Subject: [PATCH 066/181] Update app.js --- apps/speedalt/app.js | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index c279a227e..b80c923bd 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.07b low power gps widget +Ver : 1.07 Mike Bennett mike[at]kereru.com process.memory() */ @@ -208,12 +208,7 @@ function onGPS(fix) { } if (fix.fix) lf = fix; - doFix(); - -} -function doFix() { - var m; var sp = '---'; @@ -254,21 +249,21 @@ function doFix() { if ( settings.modeA ) { if ( showMax ) { // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); + drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit,age,fix.fix); } else { // Show speed/altitude - drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); + drawFix(sp,settings.spd_unit,fix.satellites,al,settings.alt_unit,age,fix.fix); } } else { // Show speed/distance if ( di <= 0 ) { // No WP selected - drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); + drawFix(sp,settings.spd_unit,fix.satellites,'','',age,fix.fix); } else { - drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); + drawFix(sp,settings.spd_unit,fix.satellites,di,settings.dist_unit,age,fix.fix); } } @@ -337,7 +332,6 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode(0); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -345,30 +339,12 @@ function startDraw(){ function stopDraw() { canDraw=false; - setLpMode(1); // on } function savSettings() { require("Storage").write('speedalt.json',settings); } -// Is low power GPS service available to use? -function isLP() { - return (WIDGETS.gpsservice==undefined)?0:1; -} - -function setLpMode(on) { - if ( !lp ) return; - var s = WIDGETS.gpsservice.gps_get_settings(); - s.gpsservice = true; - s.power_mode = (on)?'PSMOO':'SuperE'; - -print(s.power_mode+' ['+s.update+']'); - - WIDGETS.gpsservice.gps_set_settings(s); - WIDGETS.gpsservice.reload(); -} - // =Main Prog // Read settings. @@ -422,10 +398,7 @@ Bangle.on('lcdPower',function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); - -var lp = isLP(); // Low power GPS widget installed. Bangle.setGPSPower(1); -setLpMode(0); onGPS(lf); Bangle.on('GPS', onGPS); setButtons(); From 82ffaf63830aae6959f2fbab3c30658a06fd5900 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:02:35 +1300 Subject: [PATCH 067/181] Update ChangeLog --- apps/speedalt/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index debc08453..47f335b6f 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -10,3 +10,4 @@ 1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. 1.06 : Save display settings and restore when app restarted. 1.07 : Memory optimisation. +2.01 : Integrate with Low Power GPS service From ded3b115bd38f54addd6fa9e84a97426e50f4892 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:04:05 +1300 Subject: [PATCH 068/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f22a88ee1..ea91d4981 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"1.07", + "version":"2.01", "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", "type":"app", From d79336d212c0f7518c67b894f57383633d7502ff Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:06:09 +1300 Subject: [PATCH 069/181] Low Power widget branch --- apps/speedalt/app.js | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index b80c923bd..672bada49 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.07 +Ver : 1.07b low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ @@ -208,7 +208,12 @@ function onGPS(fix) { } if (fix.fix) lf = fix; + doFix(); + +} +function doFix() { + var m; var sp = '---'; @@ -249,21 +254,21 @@ function onGPS(fix) { if ( settings.modeA ) { if ( showMax ) { // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit,age,fix.fix); + drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); } else { // Show speed/altitude - drawFix(sp,settings.spd_unit,fix.satellites,al,settings.alt_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); } } else { // Show speed/distance if ( di <= 0 ) { // No WP selected - drawFix(sp,settings.spd_unit,fix.satellites,'','',age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); } else { - drawFix(sp,settings.spd_unit,fix.satellites,di,settings.dist_unit,age,fix.fix); + drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); } } @@ -332,6 +337,7 @@ function updateClock() { function startDraw(){ canDraw=true; + setLpMode(0); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -339,12 +345,28 @@ function startDraw(){ function stopDraw() { canDraw=false; + setLpMode(1); // on } function savSettings() { require("Storage").write('speedalt.json',settings); } +// Is low power GPS service available to use? +function isLP() { + if (WIDGETS.gpsservice == undefined) return(0); + return(1); +} + +function setLpMode(on) { + if ( !lp ) return; + var settings = WIDGETS.gpsservice.gps_get_settings(); + settings.gpsservice = on; + settings.power_mode = (on)?'PSMOO':'SuperE'; + WIDGETS.gpsservice.gps_set_settings(settings); + WIDGETS.gpsservice.reload(); +} + // =Main Prog // Read settings. @@ -398,6 +420,10 @@ Bangle.on('lcdPower',function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + +var lp = isLP(); // Low power GPS widget installed. + + Bangle.setGPSPower(1); onGPS(lf); Bangle.on('GPS', onGPS); From f290969ea1708e789f4912dcf580a2be8eec55da Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:06:36 +1300 Subject: [PATCH 070/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 672bada49..4fc027664 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,6 +1,6 @@ /* Speed and Altitude [speedalt] -Ver : 1.07b low power gps widget +Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ From 1afb2e28ddc1391a9f8272669988cc4a6384ef53 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:09:28 +1300 Subject: [PATCH 071/181] Set theme jekyll-theme-leap-day --- _config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index c74188174..b84971359 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-slate \ No newline at end of file +theme: jekyll-theme-leap-day \ No newline at end of file From e803faf71bc7c5693e7e889424f9401286abe801 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:47:59 +1300 Subject: [PATCH 073/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 4fc027664..a55a65659 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -361,7 +361,7 @@ function isLP() { function setLpMode(on) { if ( !lp ) return; var settings = WIDGETS.gpsservice.gps_get_settings(); - settings.gpsservice = on; + settings.gpsservice = true; settings.power_mode = (on)?'PSMOO':'SuperE'; WIDGETS.gpsservice.gps_set_settings(settings); WIDGETS.gpsservice.reload(); From 19186dfae4ac4f99f0f1a8ce8a40bac2075b3848 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 08:48:47 +1300 Subject: [PATCH 074/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ea91d4981..efae93f7c 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.01", + "version":"2.02", "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", "type":"app", From 961c5e47180bc7f55b99724967f69bbe3140c5c4 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:01:14 +1300 Subject: [PATCH 075/181] Update app.js --- apps/speedalt/app.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index a55a65659..6c14972d4 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -221,6 +221,8 @@ function doFix() { var di = '---'; var age = '---'; +print(lf.fix+' '+lf.alt); + if (lf.fix == 1 ) { // Speed if ( settings.spd == 0 ) { @@ -360,9 +362,12 @@ function isLP() { function setLpMode(on) { if ( !lp ) return; - var settings = WIDGETS.gpsservice.gps_get_settings(); - settings.gpsservice = true; - settings.power_mode = (on)?'PSMOO':'SuperE'; + var s = WIDGETS.gpsservice.gps_get_settings(); + s.gpsservice = true; + s.power_mode = (on)?'PSMOO':'SuperE'; + +print( s.power_mode ); + WIDGETS.gpsservice.gps_set_settings(settings); WIDGETS.gpsservice.reload(); } From cc47b51c1ce58029322d09b903d0be4c616c4ce3 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:01:55 +1300 Subject: [PATCH 076/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index efae93f7c..8b26252e1 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.02", + "version":"2.03", "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", "type":"app", From 7044a12c6389b78ceaa454d29d0f3f13867fbe14 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:14:47 +1300 Subject: [PATCH 077/181] Update app.js --- apps/speedalt/app.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 6c14972d4..d27d441c6 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -360,6 +360,10 @@ function isLP() { return(1); } +function lpGetFix() { + onGPS(WIDGETS.gpsservice.gps_get_fix()); +} + function setLpMode(on) { if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); @@ -431,6 +435,9 @@ var lp = isLP(); // Low power GPS widget installed. Bangle.setGPSPower(1); onGPS(lf); -Bangle.on('GPS', onGPS); + +if ( lp ) setInterval(lpGetFix, 1000); +else Bangle.on('GPS', onGPS); + setButtons(); setInterval(updateClock, 30000); From 2bcfc725363d391abc41929b3985c9fb4689cb35 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:15:33 +1300 Subject: [PATCH 078/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 8b26252e1..f5064fc77 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.03", + "version":"2.04", "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", "type":"app", From 0dc34edf3bca8f75fa03ebd15d8e63e2b444576b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:23:56 +1300 Subject: [PATCH 079/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index d27d441c6..03641a952 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -372,7 +372,7 @@ function setLpMode(on) { print( s.power_mode ); - WIDGETS.gpsservice.gps_set_settings(settings); + WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From d228eb3c2b19fdd90e4425562908d45677b9f72a Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:24:39 +1300 Subject: [PATCH 080/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f5064fc77..6406ebf7b 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.04", + "version":"2.05", "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", "type":"app", From 7fb03ff9003b87ce98cba89fba2ea0568cb5c79f Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:35:29 +1300 Subject: [PATCH 081/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 03641a952..ecf54dde3 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -370,7 +370,7 @@ function setLpMode(on) { s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; -print( s.power_mode ); +print( s ); WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 45e55e48ccfcf7a34155391cbacba12fc59f7812 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:36:02 +1300 Subject: [PATCH 082/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 6406ebf7b..e636cf4bb 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.05", + "version":"2.06", "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", "type":"app", From 1116b4400a18153e9036ea7c554e6344296464bb Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:44:48 +1300 Subject: [PATCH 083/181] Update app.js --- apps/speedalt/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index ecf54dde3..625df8447 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -347,7 +347,9 @@ function startDraw(){ function stopDraw() { canDraw=false; - setLpMode(1); // on +// setLpMode(1); // on +setLpMode(0); // off + } function savSettings() { From af83dafee4d956c1ac1ff668e799f92e37be57a4 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 09:45:43 +1300 Subject: [PATCH 084/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index e636cf4bb..fa3ab83c4 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.06", + "version":"2.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", "type":"app", From b3357b2efaa1c43a0b6cdcdb62cee29d6849baf5 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 10:19:49 +1300 Subject: [PATCH 085/181] Update app.js --- apps/speedalt/app.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 625df8447..3ebba0bb0 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -347,9 +347,7 @@ function startDraw(){ function stopDraw() { canDraw=false; -// setLpMode(1); // on -setLpMode(0); // off - + setLpMode(1); // on } function savSettings() { @@ -372,7 +370,7 @@ function setLpMode(on) { s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; -print( s ); +print('[a] '+s.power_mode); WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 569b08200c2cd0cf1b9edd473974cbf9418bd2e4 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 10:31:26 +1300 Subject: [PATCH 086/181] Update app.js --- apps/speedalt/app.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 3ebba0bb0..7a7cb7ec4 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -207,6 +207,10 @@ function onGPS(fix) { fix.time = new Date(); } + +print(fix.fix+' '+fix.alt); + + if (fix.fix) lf = fix; doFix(); @@ -220,10 +224,8 @@ function doFix() { var al = '---'; var di = '---'; var age = '---'; - -print(lf.fix+' '+lf.alt); - - if (lf.fix == 1 ) { + + if (lf.fix == 1 ) { // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units @@ -370,7 +372,7 @@ function setLpMode(on) { s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; -print('[a] '+s.power_mode); +print('[b] '+s.power_mode); WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From 0520d9a2074fb2840aedb9abfb7f0737435192b4 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 10:43:15 +1300 Subject: [PATCH 087/181] Update app.js --- apps/speedalt/app.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7a7cb7ec4..6398e377f 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -206,10 +206,6 @@ function onGPS(fix) { fix.satellites = 12; fix.time = new Date(); } - - -print(fix.fix+' '+fix.alt); - if (fix.fix) lf = fix; doFix(); @@ -371,9 +367,6 @@ function setLpMode(on) { var s = WIDGETS.gpsservice.gps_get_settings(); s.gpsservice = true; s.power_mode = (on)?'PSMOO':'SuperE'; - -print('[b] '+s.power_mode); - WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From 40346cfb6f094247affa6fd5faa67b76a5b1e7fc Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 10:47:54 +1300 Subject: [PATCH 088/181] Update app.js --- apps/speedalt/app.js | 99 +++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 6398e377f..530f064b0 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -208,12 +208,7 @@ function onGPS(fix) { } if (fix.fix) lf = fix; - doFix(); - -} -function doFix() { - var m; var sp = '---'; @@ -221,56 +216,56 @@ function doFix() { var di = '---'; var age = '---'; - if (lf.fix == 1 ) { - // Speed - if ( settings.spd == 0 ) { - m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units - sp = parseFloat(m[1]); - settings.spd_unit = m[2]; - } - else { - // Calculate for selected units - sp = lf.speed; - sp = parseFloat(sp)/parseFloat(settings.spd); - } - if ( sp < 10 ) sp = sp.toFixed(1); - else sp = Math.round(sp); - if (parseFloat(sp) > parseFloat(max.spd) ) max.spd = parseFloat(sp); - - // Altitude - al = lf.alt; - al = Math.round(parseFloat(al)/parseFloat(settings.alt)); - if (parseFloat(al) > parseFloat(max.alt) ) max.alt = parseFloat(al); - - // Distance to waypoint - di = distance(lf,wp); - if (isNaN(di)) di = 0; - - // Age of last fix (secs) - age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); - if ( age > 90 ) age = '>90'; - } - - if ( settings.modeA ) { - if ( showMax ) { - // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); - } - else { - // Show speed/altitude - drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); - } +// if (lf.fix == 1 ) { + // Speed + if ( settings.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + settings.spd_unit = m[2]; } else { - // Show speed/distance - if ( di <= 0 ) { - // No WP selected - drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); - } - else { - drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); - } + // Calculate for selected units + sp = lf.speed; + sp = parseFloat(sp)/parseFloat(settings.spd); } + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + if (parseFloat(sp) > parseFloat(max.spd) ) max.spd = parseFloat(sp); + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(settings.alt)); + if (parseFloat(al) > parseFloat(max.alt) ) max.alt = parseFloat(al); + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + if ( age > 90 ) age = '>90'; +// } + + if ( settings.modeA ) { + if ( showMax ) { + // Speed and alt maximums + drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); + } + else { + // Show speed/altitude + drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); + } + } + else { + // Show speed/distance + if ( di <= 0 ) { + // No WP selected + drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); + } + else { + drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); + } + } } From 2e34b4f030e9c364628cab72fcb186bb9d5af2c7 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 11:14:34 +1300 Subject: [PATCH 089/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 530f064b0..97e84548e 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -216,7 +216,7 @@ function onGPS(fix) { var di = '---'; var age = '---'; -// if (lf.fix == 1 ) { + if (lf.fix == 1 ) { // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units @@ -244,7 +244,7 @@ function onGPS(fix) { // Age of last fix (secs) age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); if ( age > 90 ) age = '>90'; -// } + } if ( settings.modeA ) { if ( showMax ) { From fae839f86cf9ddd666bec9d64943a31d9c2e7b5b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 11:23:12 +1300 Subject: [PATCH 090/181] Update app.js --- apps/speedalt/app.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 97e84548e..eac8d186c 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -90,9 +90,11 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { drawWP(); //Sats - if ( fix ) drawSats('Sats:'+sats); - else drawSats('Age:'+age); - +// if ( fix ) drawSats('Sats:'+sats); +// else drawSats('Age:'+age); + if ( age > 10 ) drawSats('Age:'+age); + else drawSats('Sats:'+sats); + g.reset(); g.drawImage(img,0,40); @@ -242,7 +244,9 @@ function onGPS(fix) { if (isNaN(di)) di = 0; // 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)); + var time = formatTime(lf.time); + age = timeSince(time); if ( age > 90 ) age = '>90'; } From 0f8af3e8eced769c3d816bec0638fb29fe6269ba Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 11:27:07 +1300 Subject: [PATCH 091/181] Update app.js --- apps/speedalt/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index eac8d186c..398ac5adf 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -244,9 +244,7 @@ function onGPS(fix) { if (isNaN(di)) di = 0; // Age of last fix (secs) - //age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); - var time = formatTime(lf.time); - age = timeSince(time); + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); if ( age > 90 ) age = '>90'; } From c284a9c999d356390a93a53de9392101c0b69154 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 11:55:25 +1300 Subject: [PATCH 092/181] Update app.js --- apps/speedalt/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 398ac5adf..23d2fa62f 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -209,8 +209,6 @@ function onGPS(fix) { fix.time = new Date(); } - if (fix.fix) lf = fix; - var m; var sp = '---'; @@ -218,7 +216,9 @@ function onGPS(fix) { var di = '---'; var age = '---'; - if (lf.fix == 1 ) { + if (fix.fix) { + lf = fix; + // Speed if ( settings.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units @@ -342,7 +342,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - setLpMode(1); // on + if (lf.fix) setLpMode(1); // on. Keep lp mode off until we have a first fix. } function savSettings() { From 0e2c518efd78a513f834d50be967bdf69f4de291 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 13:22:00 +1300 Subject: [PATCH 093/181] Update app.js --- apps/speedalt/app.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 23d2fa62f..3e579f7cb 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -15,6 +15,7 @@ var showMax = 0; // 1 = display the max values. 0 = display the cur fix var maxPress = 0; // Time max button pressed. Used to calculate short or long press. 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; @@ -334,6 +335,10 @@ function updateClock() { function startDraw(){ canDraw=true; + if (tmrLP) { + clearInterval(tmrLP); // Stop any scheduled drop to low power + tmrLP = false; + } setLpMode(0); // off g.clear(); Bangle.drawWidgets(); @@ -342,7 +347,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - if (lf.fix) setLpMode(1); // on. Keep lp mode off until we have a first fix. + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode(1);}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. } function savSettings() { From 40604b93a31b2aecd86b390367c33cc316a80e0b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 13:27:24 +1300 Subject: [PATCH 094/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 3e579f7cb..7f29dbd79 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ - +var v = '2'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts From c7aec562d96d197341b4101587256d56b330e90c Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 13:54:04 +1300 Subject: [PATCH 095/181] Update app.js --- apps/speedalt/app.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7f29dbd79..dc5aa9f83 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '2'; +var v = '3'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -335,10 +335,6 @@ function updateClock() { function startDraw(){ canDraw=true; - if (tmrLP) { - clearInterval(tmrLP); // Stop any scheduled drop to low power - tmrLP = false; - } setLpMode(0); // off g.clear(); Bangle.drawWidgets(); @@ -365,6 +361,7 @@ function lpGetFix() { } function setLpMode(on) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); s.gpsservice = true; From 92f9a8e1c341441fdc9ad62745a6dc5fd97a160b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 14:03:07 +1300 Subject: [PATCH 096/181] Update app.js --- apps/speedalt/app.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index dc5aa9f83..0fd924bc5 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '3'; +var v = '4'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -91,9 +91,10 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { drawWP(); //Sats -// if ( fix ) drawSats('Sats:'+sats); -// else drawSats('Age:'+age); - if ( age > 10 ) drawSats('Age:'+age); + if ( age > 10 ) { + if ( age > 90 ) age = '>90'; + drawSats('Age:'+age); + } else drawSats('Sats:'+sats); g.reset(); @@ -164,13 +165,10 @@ function drawTime() { function drawWP() { var nm = wp.name; - if ( nm == undefined ) nm = ''; - if ( nm == 'NONE' ) nm = ''; - if ( settings.modeA ) nm=''; + if ( nm == undefined || nm == 'NONE' || settings.modeA ) nm = ''; - buf.setFontAlign(-12,1); //left, bottom + buf.setFontAlign(-1,1); //left, bottom buf.setColor(2); -// buf.setFont("6x8", 1); buf.setFontVector(20); buf.drawString(nm.substring(0,6),77,160); @@ -246,7 +244,6 @@ function onGPS(fix) { // Age of last fix (secs) age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); - if ( age > 90 ) age = '>90'; } if ( settings.modeA ) { From 6f79080f8c7f840f8c0041f0cb9ad5098018bbff Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 14:31:24 +1300 Subject: [PATCH 097/181] Update app.js --- apps/speedalt/app.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 0fd924bc5..71bf28563 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '4'; +var v = '5'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -65,22 +65,18 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { var u=''; // Primary Display - v = speed.toString(); - if ( !settings.primSpd ) v = alt.toString(); + v = (settings.primSpd)?speed.toString():alt.toString(); // Primary Units - u = settings.spd_unit; - if ( !settings.primSpd ) u = alt_units; + u = (settings.primSpd)?settings.spd_unit:alt_units; drawPrimary(v,u); // Secondary Display - v = alt.toString(); - if ( !settings.primSpd ) v = speed.toString(); + v = (settings.primSpd)?alt.toString():speed.toString(); // Secondary Units - u = alt_units; - if ( !settings.primSpd ) u = settings.spd_unit; + u = (settings.primSpd)?alt_units:settings.spd_unit; drawSecondary(v,u); From acaf6d32b73ce3258fc73256744b137f48529f93 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 14:44:12 +1300 Subject: [PATCH 098/181] Update app.js --- apps/speedalt/app.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 71bf28563..493e9119f 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '5'; +var v = '6'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -328,7 +328,7 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode(0); // off + setLpMode('SuperE'); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -336,7 +336,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode(1);}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. } function savSettings() { @@ -353,14 +353,16 @@ function lpGetFix() { onGPS(WIDGETS.gpsservice.gps_get_fix()); } -function setLpMode(on) { +function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); - s.gpsservice = true; - s.power_mode = (on)?'PSMOO':'SuperE'; - WIDGETS.gpsservice.gps_set_settings(s); - WIDGETS.gpsservice.reload(); + if ( m <> s.power_mode ) { + s.gpsservice = true; + s.power_mode = (on)?'PSMOO':'SuperE'; + WIDGETS.gpsservice.gps_set_settings(s); + WIDGETS.gpsservice.reload(); + } } // =Main Prog From f4815c5ff8a5855245aed020a285d45d5d8b9d60 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 14:46:26 +1300 Subject: [PATCH 099/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 493e9119f..426d35f3a 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -359,7 +359,7 @@ function setLpMode(m) { var s = WIDGETS.gpsservice.gps_get_settings(); if ( m <> s.power_mode ) { s.gpsservice = true; - s.power_mode = (on)?'PSMOO':'SuperE'; + s.power_mode = m; WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } From 2c4f83d0504035c57e57463c410369a799b812d9 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 14:51:49 +1300 Subject: [PATCH 100/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 426d35f3a..f974d75a2 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '6'; +var v = '7'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -357,7 +357,7 @@ function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); - if ( m <> s.power_mode ) { + if ( m !== s.power_mode ) { s.gpsservice = true; s.power_mode = m; WIDGETS.gpsservice.gps_set_settings(s); From cae0222b94df682620ed42e9ce2ee739871afe55 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 15:20:17 +1300 Subject: [PATCH 101/181] Update app.js --- apps/speedalt/app.js | 45 ++++++++++++-------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index f974d75a2..7d2cf5795 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -4,7 +4,7 @@ Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com process.memory() */ -var v = '7'; +var v = '8'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -220,11 +220,8 @@ function onGPS(fix) { sp = parseFloat(m[1]); settings.spd_unit = m[2]; } - else { - // Calculate for selected units - sp = lf.speed; - sp = parseFloat(sp)/parseFloat(settings.spd); - } + else sp = parseFloat(lf.speed)/parseFloat(settings.spd); // Calculate for selected units + if ( sp < 10 ) sp = sp.toFixed(1); else sp = Math.round(sp); if (parseFloat(sp) > parseFloat(max.spd) ) max.spd = parseFloat(sp); @@ -243,24 +240,13 @@ function onGPS(fix) { } if ( settings.modeA ) { - if ( showMax ) { - // Speed and alt maximums - drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); - } - else { - // Show speed/altitude - drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); - } - } + if ( showMax ) drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); // Speed and alt maximums + else drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); // Show speed/altitude + } else { // Show speed/distance - if ( di <= 0 ) { - // No WP selected - drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); - } - else { - drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); - } + if ( di <= 0 ) drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); // No WP selected + else drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); } } @@ -303,18 +289,11 @@ function btnReleased() { var dur = getTime()-maxPress; if ( settings.modeA ) { // Spd+Alt mode - Switch between fix and MAX - if ( dur < 2 ) { - showMax = !showMax; // Short press toggle fix/max display - } - else { - max.spd = 0; // Long press resets max values. - max.alt = 0; - } - } - else { - // Spd+Dist mode - Select next waypoint - nxtWp(1); + if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display + else { max.spd = 0; max.alt = 0; } // Long press resets max values. } + else nxtWp(1); // Spd+Dist mode - Select next waypoint + onGPS(lf); } From 2d935f77f80f3169fd76f429d87128264df6f994 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 17:23:33 +1300 Subject: [PATCH 102/181] Update ChangeLog --- apps/speedalt/ChangeLog | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 47f335b6f..2c77ccafa 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,9 +5,4 @@ 0.05 : Add setting to turn vibrate on/off. 0.06 : Tweaks to vibration settings. 0.07 : Switch to BTN1 for Max toggle and reset function. -1.00 : New feature. Added waypoints file and distance to selected waypoint display -1.04 : Misc tweaks. -1.05 : Memory optimisation. Stopped loading entire waypoint list into memory. -1.06 : Save display settings and restore when app restarted. -1.07 : Memory optimisation. -2.01 : Integrate with Low Power GPS service +1.00 : New features. Added waypoints file and distance to selected waypoint display. Added integration with Low Power GPS service. Save display settings and restore when app restarted. From b019b7e6f822c5c8f7251376905dfdca182942f9 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 17:24:51 +1300 Subject: [PATCH 103/181] Update app.js --- apps/speedalt/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7d2cf5795..3de66926b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,10 +1,8 @@ /* Speed and Altitude [speedalt] -Ver : 2.01 low power gps widget Mike Bennett mike[at]kereru.com -process.memory() */ -var v = '8'; +var v = '1.00'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts From 3e5fcbfdf076fb5a5c999caa790062df6e496a66 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 17:45:03 +1300 Subject: [PATCH 104/181] Update README.md --- apps/speedalt/README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index f9db449f4..2c8d5917a 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -31,9 +31,13 @@ Settings:
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. -Also to @jeffmer, the developer of the 'GPS Navigation' app. +Low Power GPS Service : + +This app will work quite happily without this service but will use the Low power GPS Service if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significant longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. + +When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. This improves power saving while the display is off. This delay gives an opportunity to restore the display before the power mode is switched. + +There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will contribute to this. 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: @@ -103,3 +107,11 @@ Sample waypoints.json } ] + +Thanks: + +Many thanks to Gordon Williams. Awesome job. + +Special thanks also to @jeffmer, for the 'GPS Navigation' app and @hughbarney for the 'Low power GPS Service' work. + + From c618ebfdccc3e42d05db4ce842a93ed2b6bca629 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 17:56:37 +1300 Subject: [PATCH 105/181] Update README.md --- apps/speedalt/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 2c8d5917a..c5f8883a5 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -33,17 +33,19 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft Low Power GPS Service : -This app will work quite happily without this service but will use the Low power GPS Service if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significant longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. +This app will work quite happily without this service but will use the Low power GPS Service if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. -When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. This improves power saving while the display is off. This delay gives an opportunity to restore the display before the power mode is switched. +When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. This improves power saving while the display is off. This delay gives an opportunity to restore the display before the power mode is switched. -There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will contribute to this. 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. +There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. 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: -Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displyed in Speed+[D]istance mode. +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. -Sample waypoints.json +The GPS Navigation app in the App Loader has a really nice waypoints file editor. + +Sample waypoints.json (My sailing waypoints)

 [

From f610ce521cab3748b1df25569cff75b82fbf5184 Mon Sep 17 00:00:00 2001
From: nujw 
Date: Fri, 5 Feb 2021 18:08:33 +1300
Subject: [PATCH 106/181] Update README.md

---
 apps/speedalt/README.md | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md
index c5f8883a5..cdffa483d 100644
--- a/apps/speedalt/README.md
+++ b/apps/speedalt/README.md
@@ -1,10 +1,12 @@
-Displays the GPS speed, altitude and distance to selected waypoint. One 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. 
+# GPS Speed, Altimeter and Distance to Waypoint
 
-You can chose between two modes. One showing speed and altitude (A) and one showing speed and distance to waypoint (D). 
+You can switch between two display modes. One showing speed and altitude (A) and one showing speed and distance to waypoint (D). 
+
+Within each display mode 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. 
 
 The waypoints list is the same as that used with the [GPS Navigation] app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information.
 
-Left Display Tap : Swaps the displays. You can have either speed or [A]ltitude/[D]istance on the large primary display.
+Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display.
 
 BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded.
 
@@ -14,9 +16,9 @@ BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected wa
 
 BTN3 : Swaps the modes between Speed+[A]ltitude or Speed+[D]istance.
 
-App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance caqn be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default, high contrast (all white on black) or night ( all red on black ). 
+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 ). 
 
-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 fix or not.  
+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.  
 
 Speed and Altitude:
![](screen1.png)

@@ -31,7 +33,7 @@ Settings:
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! -Low Power GPS Service : +## Low Power GPS Service This app will work quite happily without this service but will use the Low power GPS Service if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. @@ -39,7 +41,7 @@ When using the Low Power GPS Service this app switches the GPS to SuperE (defaul There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. 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 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. @@ -110,7 +112,7 @@ Sample waypoints.json (My sailing waypoints) ]

-Thanks: +## Thanks Many thanks to Gordon Williams. Awesome job. From de21b9b57013346c7f045e9278bf6e8048c74dc5 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 18:09:32 +1300 Subject: [PATCH 107/181] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index fa3ab83c4..b5a98ec85 100644 --- a/apps.json +++ b/apps.json @@ -2625,7 +2625,7 @@ "name": "GPS Speedo and Altimeter", "shortName":"GPS Speed Alt", "icon": "app.png", - "version":"2.07", + "version":"1.00", "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", "type":"app", From fd17073693f5309a704acae47291309ebbce9416 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 18:22:25 +1300 Subject: [PATCH 108/181] Update README.md --- apps/speedalt/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index cdffa483d..13e840bfb 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -4,7 +4,7 @@ You can switch between two display modes. One showing speed and altitude (A) and Within each display mode 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. -The waypoints list is the same as that used with the [GPS Navigation] app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. +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. Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. @@ -35,9 +35,9 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft ## Low Power GPS Service -This app will work quite happily without this service but will use the Low power GPS Service if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. +This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. -When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. This improves power saving while the display is off. This delay gives an opportunity to restore the display before the power mode is switched. +When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. 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. There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. 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. @@ -45,7 +45,7 @@ There are a couple of things to consider when using the Low Power GPS Service. T 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 app in the App Loader has a really nice waypoints file editor. +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) @@ -116,6 +116,6 @@ Sample waypoints.json (My sailing waypoints) Many thanks to Gordon Williams. Awesome job. -Special thanks also to @jeffmer, for the 'GPS Navigation' app and @hughbarney for the 'Low power GPS Service' work. +Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) work. From 256cb4f6c32200e9991ad3b1cf238ced5c5f660b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 21:48:15 +1300 Subject: [PATCH 109/181] Update app.js --- apps/speedalt/app.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 3de66926b..09b8d69e0 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.00'; +var v = '1.01'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -10,6 +10,7 @@ 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 maxPress = 0; // Time max button pressed. Used to calculate short or long press. var canDraw = 1; var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. @@ -262,6 +263,19 @@ function toggleAltDist() { onGPS(lf); } +function togglePwrSav() { + pwrSav=!pwrSav; + if ( pwrSav ) { + var s = require('Storage').readJSON('setting.json',1)||{}; + var t = s.timeout||10; + Bangle.setLCDTimeout(t); + } + else { + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(1); + } +} + function setButtons(){ // Spd+Dist : Select next waypoint @@ -277,6 +291,9 @@ function setButtons(){ // Touch left screen to toggle display setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); + // Touch right screen to toggle pwrSav + setWatch(togglePwrSav, BTN5, {repeat:true,edge:"falling"}); + } function btnPressed() { From 91935f4ebe6c9b12a0f96ee66802e7828c0400b3 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 21:58:14 +1300 Subject: [PATCH 110/181] Update app.js --- apps/speedalt/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 09b8d69e0..eb81d417b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.01'; +var v = '1.02'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -266,6 +266,7 @@ function toggleAltDist() { function togglePwrSav() { pwrSav=!pwrSav; if ( pwrSav ) { + LED1.reset(); var s = require('Storage').readJSON('setting.json',1)||{}; var t = s.timeout||10; Bangle.setLCDTimeout(t); @@ -273,6 +274,7 @@ function togglePwrSav() { else { Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); + LED1.set(); } } From 10539c251629fa998ebc647106c433928e83af6d Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 22:07:31 +1300 Subject: [PATCH 111/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index eb81d417b..40712b061 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.02'; +var v = '1.00'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts From c8dfac215d044ab5585641c485256f7a7a6f8c08 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 5 Feb 2021 22:11:02 +1300 Subject: [PATCH 112/181] Update README.md --- apps/speedalt/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 13e840bfb..00062269d 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -6,7 +6,9 @@ Within each display mode one figure is displayed on the watch face using the lar 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. -Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. +BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. + +BTN5 : Right Display Tap : Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. From 24afa000bdb4efc841048562cebee6b8b3faddc4 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 09:17:33 +1300 Subject: [PATCH 113/181] Update app.js --- apps/speedalt/app.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 40712b061..24838ccf1 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.00'; +var v = '1.01'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -345,9 +345,11 @@ function isLP() { return(1); } +/* function lpGetFix() { onGPS(WIDGETS.gpsservice.gps_get_fix()); } +*/ function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power @@ -414,15 +416,15 @@ Bangle.on('lcdPower',function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); - var lp = isLP(); // Low power GPS widget installed. - - -Bangle.setGPSPower(1); onGPS(lf); - -if ( lp ) setInterval(lpGetFix, 1000); -else Bangle.on('GPS', onGPS); - +if ( lp ) { + setLpMode('SuperE'); + setInterval(onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); +} +else { + Bangle.setGPSPower(1); + Bangle.on('GPS', onGPS); +} setButtons(); setInterval(updateClock, 30000); From d6afa194a944f5093046cbd6bbf157e71ad8e5b6 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 09:26:41 +1300 Subject: [PATCH 114/181] Update app.js --- apps/speedalt/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 24838ccf1..630a69a84 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -416,15 +416,17 @@ Bangle.on('lcdPower',function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -var lp = isLP(); // Low power GPS widget installed. onGPS(lf); + +var lp = isLP(); // Low power GPS widget installed? if ( lp ) { setLpMode('SuperE'); - setInterval(onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); + setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); } else { Bangle.setGPSPower(1); Bangle.on('GPS', onGPS); } + setButtons(); setInterval(updateClock, 30000); From 7bd7f371066fd3a94c48dcbb5eb2a9d3c481e334 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 10:28:20 +1300 Subject: [PATCH 115/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 630a69a84..0cc435544 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.01'; +var v = '1.02'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -355,7 +355,7 @@ function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); - if ( m !== s.power_mode ) { + if ( m !== s.power_mode || !s.gpsservice ) { s.gpsservice = true; s.power_mode = m; WIDGETS.gpsservice.gps_set_settings(s); From 3b3c1da0ea400f14a637a32eaa06abe022ed87fb Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 10:31:39 +1300 Subject: [PATCH 116/181] Update app.js --- apps/speedalt/app.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 0cc435544..8041dc095 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.02'; +var v = '1.03'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -345,12 +345,6 @@ function isLP() { return(1); } -/* -function lpGetFix() { - onGPS(WIDGETS.gpsservice.gps_get_fix()); -} -*/ - function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; From 0bfe32eec2884ea0afbd8a45b688a47ad281dce5 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 10:57:38 +1300 Subject: [PATCH 117/181] Update app-icon.js --- apps/speedalt/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app-icon.js b/apps/speedalt/app-icon.js index 6c03df55b..f4f24a18b 100644 --- a/apps/speedalt/app-icon.js +++ b/apps/speedalt/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("m02xH+AH4AJlgAMGWQ4lEw1Wq2BAAgHBG8QhFq+C1nXABGswQ4GGjhkBGJIAHOQI2ZDIcyGaQ3EmQ3WNAiaKABusNyoUDNCxuGGySdDNDBuGUoY0e2etAAWzGzoOCGheJxNlAA2J1o2LGprTNGRAAFbZw0LqwZI1pkGAAgJDUxdWGxTULFAmJFJGzURjaET6Q0DGZKYIrqjRT5Q0EGZwpEUZZqPaYaRMGg6LINhBJKNK40JAAI1Iq6fJGjDrIUQoVCwRqJDhA0OXYQRGwRsEAgWsNTCeIDYTwG1g1GqygJNROsq0ymVWq7TI2ZRJUQg1JUARkMAAg0FDhY1GDAxOKQwgAEKI6IKKAQ1KeAQ1IJ4VXAog1gC4Q1ImQVBAwYFBmQ1K1o1fMoQ1ORIQ1Ua5ahD1uzrqnEULo1L1gVBAAusGrDxHC4NlEY4aDAAZQGDhiHCGpZOJNodWmUyqxpIRBY1GQw7wCURAAPKIWtXhI1EwSFJNhIAMNQSgHwQ1EGwVXKBJsWDJSgENgjyKGyg0CNQ/XNQo1DwIRGbIS+HABYWLwI1GGwVWKhZtQChigGNhg2TCRhqIGwcy1g2YCBmsmQ0INgajIYgY1PdRKfCGpCjMG4ShNBxSfKGwyjIADOsGho1DbRI0YagQ1MG0Y0RGwjbLACLTDGh42FqxuY1lWGig2FmRuWwKdDGiZuHG6WBNC42JHAWCVBWswQyEGjQ3IHAJyBAAgHBCAwzbG5QAMGb44SGUgAkA")) +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) From 870092b340939fb15e9035db5ca61b5421d9daee Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 10:58:22 +1300 Subject: [PATCH 118/181] Add files via upload --- apps/speedalt/app.png | Bin 3356 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/speedalt/app.png b/apps/speedalt/app.png index 41849d3072b2e9625782fb0ae0d61b931b312373..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 100644 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( 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 mqNYDt-$NL}5QZ>>e&BBr{h5vNHZoHH0000 Date: Sat, 6 Feb 2021 10:59:36 +1300 Subject: [PATCH 119/181] Update app-icon.js --- apps/speedalt/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app-icon.js b/apps/speedalt/app-icon.js index 6c03df55b..f4f24a18b 100644 --- a/apps/speedalt/app-icon.js +++ b/apps/speedalt/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("m02xH+AH4AJlgAMGWQ4lEw1Wq2BAAgHBG8QhFq+C1nXABGswQ4GGjhkBGJIAHOQI2ZDIcyGaQ3EmQ3WNAiaKABusNyoUDNCxuGGySdDNDBuGUoY0e2etAAWzGzoOCGheJxNlAA2J1o2LGprTNGRAAFbZw0LqwZI1pkGAAgJDUxdWGxTULFAmJFJGzURjaET6Q0DGZKYIrqjRT5Q0EGZwpEUZZqPaYaRMGg6LINhBJKNK40JAAI1Iq6fJGjDrIUQoVCwRqJDhA0OXYQRGwRsEAgWsNTCeIDYTwG1g1GqygJNROsq0ymVWq7TI2ZRJUQg1JUARkMAAg0FDhY1GDAxOKQwgAEKI6IKKAQ1KeAQ1IJ4VXAog1gC4Q1ImQVBAwYFBmQ1K1o1fMoQ1ORIQ1Ua5ahD1uzrqnEULo1L1gVBAAusGrDxHC4NlEY4aDAAZQGDhiHCGpZOJNodWmUyqxpIRBY1GQw7wCURAAPKIWtXhI1EwSFJNhIAMNQSgHwQ1EGwVXKBJsWDJSgENgjyKGyg0CNQ/XNQo1DwIRGbIS+HABYWLwI1GGwVWKhZtQChigGNhg2TCRhqIGwcy1g2YCBmsmQ0INgajIYgY1PdRKfCGpCjMG4ShNBxSfKGwyjIADOsGho1DbRI0YagQ1MG0Y0RGwjbLACLTDGh42FqxuY1lWGig2FmRuWwKdDGiZuHG6WBNC42JHAWCVBWswQyEGjQ3IHAJyBAAgHBCAwzbG5QAMGb44SGUgAkA")) +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) From 464317aecbe50674d02d102d3e1c058e48fc91e5 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 11:00:09 +1300 Subject: [PATCH 120/181] Add files via upload --- apps/speedalt/app.png | Bin 3356 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/speedalt/app.png b/apps/speedalt/app.png index 41849d3072b2e9625782fb0ae0d61b931b312373..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 100644 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( 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 mqNYDt-$NL}5QZ>>e&BBr{h5vNHZoHH0000 Date: Sat, 6 Feb 2021 11:00:47 +1300 Subject: [PATCH 121/181] Update app.js --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 8041dc095..c6421df98 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.03'; +var v = '1.04'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts From 3a63458834daffe3b7879ed23a00ddb0f46e34ed Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 11:07:50 +1300 Subject: [PATCH 122/181] Major functionality update to speedalt. Renamed to GPS Adventure Sports. Added distance to waypoint. Added Low Power GPS Service. Many minor tweaks. --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index b5a98ec85..c85bf25d4 100644 --- a/apps.json +++ b/apps.json @@ -2622,8 +2622,8 @@ ] }, { "id": "speedalt", - "name": "GPS Speedo and Altimeter", - "shortName":"GPS Speed Alt", + "name": "GPS Adventure Sports", + "shortName":"GPS Adv", "icon": "app.png", "version":"1.00", "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.", From 083ce365deafe7504b2fed40aa54d084e91252f6 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 11:17:07 +1300 Subject: [PATCH 123/181] Update README.md --- apps/speedalt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 00062269d..03c4a0f17 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -37,7 +37,7 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft ## Low Power GPS Service -This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. In this case the GPS Service must be On for this app to obtain a fix. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. +This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. 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. From 417ebad08cc547872759a33dd99d3cdf61ab6033 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 12:09:14 +1300 Subject: [PATCH 124/181] Update app.js --- apps/speedalt/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index c6421df98..6c9f4aa3e 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.04'; +var v = '1.05'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -391,8 +391,8 @@ var img = { palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) }; -if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF]); -if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800]); +if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); +if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); var SCREENACCESS = { withApp:true, From f5dcf7b0e66fe67286a35d39a2dcb4cdce66643f Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 17:10:17 +1300 Subject: [PATCH 125/181] Update app.js --- apps/speedalt/app.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 6c9f4aa3e..f9d69f64e 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.05'; +var v = '1.06'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -11,7 +11,7 @@ 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 maxPress = 0; // Time max button pressed. Used to calculate short or long press. +// var maxPress = 0; // Time max button pressed. Used to calculate short or long press. 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 @@ -281,9 +281,20 @@ function togglePwrSav() { function setButtons(){ // Spd+Dist : Select next waypoint - setWatch(btnPressed, BTN1,{repeat:true,edge:"rising"}); - setWatch(btnReleased, BTN1,{repeat:true,edge:"falling"}); - +// setWatch(btnPressed, BTN1,{repeat:true,edge:"rising"}); +// setWatch(btnReleased, BTN1,{repeat:true,edge:"falling"}); + setWatch(function(e) { + var dur = e.time - e.lastTime; + if ( settings.modeA ) { + // 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 nxtWp(1); // Spd+Dist mode - Select next waypoint + onGPS(lf); + }, BTN1, { edge:"falling",repeat:true}); + + // Show launcher when middle button pressed setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); @@ -298,10 +309,12 @@ function setButtons(){ } +/* function btnPressed() { maxPress = getTime(); } + function btnReleased() { var dur = getTime()-maxPress; if ( settings.modeA ) { @@ -313,6 +326,7 @@ function btnReleased() { onGPS(lf); } +*/ function updateClock() { if (!canDraw) return; From 7ae823f6391806c49864480bd1bc1bb46abcaae4 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 17:15:13 +1300 Subject: [PATCH 126/181] Update app.js --- apps/speedalt/app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index f9d69f64e..17d55e516 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.06'; +var v = '1.07'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -292,20 +292,20 @@ function setButtons(){ } else nxtWp(1); // Spd+Dist mode - Select next waypoint onGPS(lf); - }, BTN1, { edge:"falling",repeat:true}); + }, BTN1, { edge:"falling",repeat:true,debounce:50}); // Show launcher when middle button pressed setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); // Toggle between alt or dist - setWatch(toggleAltDist, BTN3, {repeat:true,edge:"falling"}); + setWatch(toggleAltDist, BTN3, {repeat:true,edge:"falling",debounce:50}); // Touch left screen to toggle display - setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); + setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling",debounce:50}); // Touch right screen to toggle pwrSav - setWatch(togglePwrSav, BTN5, {repeat:true,edge:"falling"}); + setWatch(togglePwrSav, BTN5, {repeat:true,edge:"falling",debounce:50}); } From 39accb63b393d8d91ae64e97f731c6d5053a1da6 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 17:23:17 +1300 Subject: [PATCH 127/181] Update app.js --- apps/speedalt/app.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 17d55e516..76091f20b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -11,7 +11,6 @@ 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 maxPress = 0; // Time max button pressed. Used to calculate short or long press. 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 @@ -281,8 +280,6 @@ function togglePwrSav() { function setButtons(){ // Spd+Dist : Select next waypoint -// setWatch(btnPressed, BTN1,{repeat:true,edge:"rising"}); -// setWatch(btnReleased, BTN1,{repeat:true,edge:"falling"}); setWatch(function(e) { var dur = e.time - e.lastTime; if ( settings.modeA ) { @@ -309,25 +306,6 @@ function setButtons(){ } -/* -function btnPressed() { - maxPress = getTime(); -} - - -function btnReleased() { - var dur = getTime()-maxPress; - if ( settings.modeA ) { - // 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 nxtWp(1); // Spd+Dist mode - Select next waypoint - - onGPS(lf); -} -*/ - function updateClock() { if (!canDraw) return; drawTime(); From 3f5552170d0b081ccdd47e0cf45c9197490a6111 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 20:31:31 +1300 Subject: [PATCH 128/181] Update app.js --- apps/speedalt/app.js | 63 ++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 76091f20b..1603d5548 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.07'; +var v = '1.08'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -249,34 +249,6 @@ function onGPS(fix) { } - -function toggleDisplay() { - settings.primSpd = !settings.primSpd; - savSettings(); - onGPS(lf); // Update display -} - -function toggleAltDist() { - settings.modeA = !settings.modeA; - savSettings(); - onGPS(lf); -} - -function togglePwrSav() { - 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(); - } -} - function setButtons(){ // Spd+Dist : Select next waypoint @@ -293,17 +265,38 @@ function setButtons(){ // Show launcher when middle button pressed - setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + // setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + // 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",debounce:50}); + // Toggle between alt or dist - setWatch(toggleAltDist, BTN3, {repeat:true,edge:"falling",debounce:50}); + setWatch(function(e){ + settings.modeA = !settings.modeA; + savSettings(); + onGPS(lf); + }, BTN3, {repeat:true,edge:"falling",debounce:50}); // Touch left screen to toggle display - setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling",debounce:50}); + setWatch(function(e){ + settings.primSpd = !settings.primSpd; + savSettings(); + onGPS(lf); // Update display + }, BTN4, {repeat:true,edge:"falling",debounce:50}); - // Touch right screen to toggle pwrSav - setWatch(togglePwrSav, BTN5, {repeat:true,edge:"falling",debounce:50}); - } function updateClock() { From 64c1adeaf6db3aaa71b9e5bff548cc0bc4637f6b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 20:33:57 +1300 Subject: [PATCH 129/181] Update README.md --- apps/speedalt/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 03c4a0f17..54d7f0f94 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -8,16 +8,18 @@ The waypoints list is the same as that used with the [GPS Navigation](https://ba BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. -BTN5 : Right Display Tap : Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. - BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. BTN1 : [Speed+Altitude] Long press > 2 secs resets the recorded maximum values. BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected waypoint is displayed. +BTN2 : Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. + BTN3 : Swaps the modes between Speed+[A]ltitude or Speed+[D]istance. +BTN3 : Long press exit and return to watch. + 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 ). 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. From 90ae9f0f8c2198599e67112c906e31c400ec5245 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 20:42:01 +1300 Subject: [PATCH 130/181] Update app.js --- apps/speedalt/app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 1603d5548..d25d95f64 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.08'; +var v = '1.09'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -261,7 +261,7 @@ function setButtons(){ } else nxtWp(1); // Spd+Dist mode - Select next waypoint onGPS(lf); - }, BTN1, { edge:"falling",repeat:true,debounce:50}); + }, BTN1, { edge:"falling",repeat:true}); // Show launcher when middle button pressed @@ -281,21 +281,21 @@ function setButtons(){ Bangle.setLCDPower(1); LED1.set(); } - }, BTN2, {repeat:true,edge:"falling",debounce:50}); + }, BTN2, {repeat:true,edge:"falling"}); // Toggle between alt or dist setWatch(function(e){ settings.modeA = !settings.modeA; savSettings(); onGPS(lf); - }, BTN3, {repeat:true,edge:"falling",debounce:50}); + }, BTN3, {repeat:true,edge:"falling"}); // Touch left screen to toggle display setWatch(function(e){ settings.primSpd = !settings.primSpd; savSettings(); onGPS(lf); // Update display - }, BTN4, {repeat:true,edge:"falling",debounce:50}); + }, BTN4, {repeat:true,edge:"falling"}); } From 48a6d29d608ab529e991b8a6f8334c9585e395b8 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 21:08:13 +1300 Subject: [PATCH 131/181] Update app.js --- apps/speedalt/app.js | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index d25d95f64..85ed5e75b 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.09'; +var v = '1.10'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -269,17 +269,21 @@ function setButtons(){ // 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(); + var dur = e.time - e.lastTime; + if ( dur < 2 ) { // Short press. + 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(); + } + else setLpMode('SuperE',false); // long press, power off LP GPS } }, BTN2, {repeat:true,edge:"falling"}); @@ -309,7 +313,7 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode('SuperE'); // off + setLpMode('SuperE',true); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -317,7 +321,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO',true);}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. } function savSettings() { @@ -330,12 +334,12 @@ function isLP() { return(1); } -function setLpMode(m) { +function setLpMode(m,p) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); if ( m !== s.power_mode || !s.gpsservice ) { - s.gpsservice = true; + s.gpsservice = p; s.power_mode = m; WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); @@ -399,7 +403,7 @@ onGPS(lf); var lp = isLP(); // Low power GPS widget installed? if ( lp ) { - setLpMode('SuperE'); + setLpMode('SuperE',true); setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); } else { From 0e2e17229a0eee4125a0a27a6531858be4708f4b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 21:24:13 +1300 Subject: [PATCH 132/181] Update README.md --- apps/speedalt/README.md | 50 +++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 54d7f0f94..f4a679574 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -6,23 +6,39 @@ Within each display mode one figure is displayed on the watch face using the lar 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. -BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. - -BTN1 : [Speed+Altitude] Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. - -BTN1 : [Speed+Altitude] Long press > 2 secs resets the recorded maximum values. - -BTN1 : [Speed+Distance] Select next waypoint. Last fix distance from selected waypoint is displayed. - -BTN2 : Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. +## Buttons and Controls BTN3 : Swaps the modes between Speed+[A]ltitude or Speed+[D]istance. +### [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 : Long press > 2 secs resets the recorded maximum values. + +### [D]istance mode + +BTN1 : Select next waypoint. Last fix distance from selected waypoint is displayed. + +### Both modes + +BTN2 : Short press < 2 secs Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. + +BTN2 : Long press > 2 secs turns off the low power GPS service and GPS. Exit and restart the app to turn back on. If you are using the low power gps service it will keep the GPS powered on after exiting the app. This is a convenient way to turn it off. + BTN3 : Long press exit and return to watch. -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 ). +BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. -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. +## 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 ). + +## 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. + +## Screens Speed and Altitude:
![](screen1.png)

@@ -35,15 +51,17 @@ MAX Values instead:
Settings:
![](screen4.png)

-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! - ## Low Power GPS Service This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. 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. -There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. 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. +There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. + +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. + +When exiting this app the Low Power GPS Service will keep the GPS powered on, using battery. It can be manually turned off using the gps service settings menu. As a convenience, long press BTN2 for 2 seconds will turn it off while using this GPS Adv app. ## Waypoints @@ -116,6 +134,10 @@ Sample waypoints.json (My sailing waypoints) ] +## 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. From 9ee180cf4121f46597a829307d372243b1a24222 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 21:29:54 +1300 Subject: [PATCH 133/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 85ed5e75b..bd123173a 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.10'; +var v = '1.11'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -283,8 +283,8 @@ function setButtons(){ Bangle.setLCDPower(1); LED1.set(); } - else setLpMode('SuperE',false); // long press, power off LP GPS } + else setLpMode('SuperE',false); // long press, power off LP GPS }, BTN2, {repeat:true,edge:"falling"}); // Toggle between alt or dist From bafc1b5b48b10c358be08b2781d8c71ebcca8575 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 21:45:54 +1300 Subject: [PATCH 134/181] Update app.js --- apps/speedalt/app.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index bd123173a..62464f34c 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.11'; +var v = '1.12'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -284,7 +284,7 @@ function setButtons(){ LED1.set(); } } - else setLpMode('SuperE',false); // long press, power off LP GPS + else gpsOff(); // long press, power off LP GPS }, BTN2, {repeat:true,edge:"falling"}); // Toggle between alt or dist @@ -313,7 +313,7 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode('SuperE',true); // off + setLpMode('SuperE'); // off g.clear(); Bangle.drawWidgets(); onGPS(lf); // draw app screen @@ -321,7 +321,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO',true);}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. } function savSettings() { @@ -334,18 +334,27 @@ function isLP() { return(1); } -function setLpMode(m,p) { +function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); if ( m !== s.power_mode || !s.gpsservice ) { - s.gpsservice = p; + s.gpsservice = true; s.power_mode = m; WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); } } +function gpsOff() { + if ( !lp ) return; + var s = WIDGETS.gpsservice.gps_get_settings(); + s.gpsservice = true; + s.power_mode = 'SuperE'; + WIDGETS.gpsservice.gps_set_settings(s); + WIDGETS.gpsservice.reload(); +} + // =Main Prog // Read settings. @@ -403,7 +412,7 @@ onGPS(lf); var lp = isLP(); // Low power GPS widget installed? if ( lp ) { - setLpMode('SuperE',true); + setLpMode('SuperE'); setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); } else { From e033a7bcab08d1a9ef2f33ed88586942ed7718c5 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 6 Feb 2021 21:52:57 +1300 Subject: [PATCH 135/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 62464f34c..b533b1fbe 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.12'; +var v = '1.13'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -349,7 +349,7 @@ function setLpMode(m) { function gpsOff() { if ( !lp ) return; var s = WIDGETS.gpsservice.gps_get_settings(); - s.gpsservice = true; + s.gpsservice = false; s.power_mode = 'SuperE'; WIDGETS.gpsservice.gps_set_settings(s); WIDGETS.gpsservice.reload(); From dfba0a3e2ea03aa8ef3fb21ff4f37c11ac6c4dcb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 9 Feb 2021 08:34:52 +0000 Subject: [PATCH 136/181] add readme --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 4fbc1a779..c2b6ca075 100644 --- a/apps.json +++ b/apps.json @@ -2758,6 +2758,7 @@ "name": "Test User Input", "shortName":"Test User Input", "icon": "app.png", + "readme": "README.md", "version":"0.02", "description": "Basic app to test the bangle.js input interface. It displays the result in text or a switch on/off image.", "tags": "input,interface,buttons,touch", From 68252a0c5ecca0ecd328050f341c1b5d54025670 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 12:52:44 +0000 Subject: [PATCH 137/181] added gpssetup app --- apps/gpssetup/ChangeLog | 1 + apps/gpssetup/README.md | 64 +++++++++ apps/gpssetup/app.js | 260 ++++++++++++++++++++++++++++++++++++ apps/gpssetup/settings.js | 4 + apps/gpssetup/settings.json | 1 + 5 files changed, 330 insertions(+) create mode 100644 apps/gpssetup/ChangeLog create mode 100644 apps/gpssetup/README.md create mode 100644 apps/gpssetup/app.js create mode 100644 apps/gpssetup/settings.js create mode 100644 apps/gpssetup/settings.json diff --git a/apps/gpssetup/ChangeLog b/apps/gpssetup/ChangeLog new file mode 100644 index 000000000..0099beb29 --- /dev/null +++ b/apps/gpssetup/ChangeLog @@ -0,0 +1 @@ +0.01: First version of GPS Setup app diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md new file mode 100644 index 000000000..4992a16b5 --- /dev/null +++ b/apps/gpssetup/README.md @@ -0,0 +1,64 @@ +# GPS Setup + +An App to enable the GPS to be configured into low power mode. + +## Goals + +To develop app that sets the GPS up to run with the lowest possible +power consumption. + + +* An app that turns on the GPS and constantly displays the screen + will use around 75mA, the battery will last between 3-4 hours. + +* Using the GPS in a Widget in Super-E Power Saving Mode (PSM) with + the screen off most of the time, will consume around 35mA and you + might get 10hrs before a recharge. + +* Using the GPS in Power Saving Mode On/Off (PSMOO) with suitable + settings can reduce the average consumption to around 15mA. A + simple test using a 120s update period, 6s search period was still + running with 45% battery 20 hours after it started. + + +## Settings + +The Settings App enables you set the options below for the GPS. +Either start the App from the launcher or go to Settings, select +App/Widgets and then 'GPS Setup'. + +When you exit the setup App the settings will be stored in the +gpssetup.settings.json file; the GPS will be switched on and the +necessary commands sent to the GPS to configure it. The GPS is then +powered off. The GPS configuration is stored in the GPS non-volatile +memory so that next time the GPS is powered on they are used. These +settings will remain and impact every app that uses the GPS. + + +- Power Mode: + + - SuperE - the factory default setup for the GPS. The recommended + power saving mode. + + - PSMOO - On/Off power saving mode. Configured by interval and + search time. Choose this mode if you are happy to get a GPS + position update less often (say every 1 or 2 minutes). The longer + the interval the more time the GPS will spend sleeping in low + power mode (7mA) between obtaining fixes (35mA). For walking in + open country an update once every 60 seconds is adequate to put + you within a 6 digit grid refernce sqaure. + +- update - the time between two position fix attempts. + +- search - the time between two acquisition attempts if the receiver + is unable to get a position fix. + +## References + +* [UBLOX M8 Receiver Data Sheet](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29.pdf) + +* [UBLOX Power Management App Note](https://www.u-blox.com/sites/default/files/products/documents/PowerManagement_AppNote_%28UBX-13005162%29.pdf) + +* Some useful code on Github and be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) +and [here](https://github.com/thasti/utrak/blob/master/gps.c) + diff --git a/apps/gpssetup/app.js b/apps/gpssetup/app.js new file mode 100644 index 000000000..18dc57ec5 --- /dev/null +++ b/apps/gpssetup/app.js @@ -0,0 +1,260 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +const SETTINGS_FILE = "gpssetup.settings.json"; +let settings = undefined; +let settings_changed = false; + +function updateSettings() { + require("Storage").write(SETTINGS_FILE, settings); + settings_changed = true; +} + +function loadSettings() { + log_debug("loadSettings()"); + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.update = settings.update||120; + settings.search = settings.search||5; + settings.power_mode = settings.power_mode||"SuperE"; + log_debug(settings); +} + +/*********** GPS Power and Setup Functions ******************/ + +function log_debug(o) { + //console.log(o); +} + +// quick hack +function wait(ms){ + var start = new Date().getTime(); + var end = start; + while(end < start + ms) { + end = new Date().getTime(); + } +} + +function setupGPS() { + Bangle.setGPSPower(1); + if (settings.power_mode === "PSMOO") { + setupPSMOO(); + } else { + setupSuperE(); + } + + log_debug("Powering GPS Off"); + Bangle.setGPSPower(0); +} + +function setupPSMOO() { + log_debug("setupGPS() PSMOO"); + UBX_CFG_RESET(); + wait(100); + + UBX_CFG_PM2(settings.update, settings.search); + wait(20); + + UBX_CFG_RXM(); + wait(20); + + UBX_CFG_SAVE(); + wait(20); +} + +function setupSuperE() { + log_debug("setupGPS() Super-E"); + UBX_CFG_RESET(); + wait(100); + + UBX_CFG_PMS(); + wait(20); + + UBX_CFG_SAVE(); + wait(20); +} + +function writeGPScmd(cmd) { + var d = [0xB5,0x62]; // sync chars + d = d.concat(cmd); + var a=0,b=0; + for (var i=2;i>8; + } while (i); + + return bytes; +} + + +/* + * Extended Power Management + * update and search are in milli seconds + * settings are loaded little endian, lsb first + * + * https://github.com/thasti/utrak/blob/master/gps.c + */ +function UBX_CFG_PM2(update,search) { + + var u = int_2_bytes(update*1000); + var s = int_2_bytes(search*1000); + + writeGPScmd([0x06, 0x3B, /* class id */ + 44, 0, /* length */ + 0x01, 0x00, 0x00, 0x00, /* v1, reserved 1..3 */ + 0x00, 0x10, 0x00, 0x00, /* on/off-mode, update ephemeris */ + u[3], u[2], u[1], u[0], /* update period, ms, 120s=00 01 D4 C0, 30s= 00 00 75 30 */ + s[3], s[2], s[1], s[0], /* search period, ms, 120s, 20s = 00 00 4E 20, 5s = 13 88 */ + 0x00, 0x00, 0x00, 0x00, /* grid offset */ + 0x00, 0x00, /* on-time after first fix */ + 0x01, 0x00, /* minimum acquisition time */ + 0x00, 0x00, 0x00, 0x00, /* reserved 4,5 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 6 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 7 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 8,9,10 */ + 0x00, 0x00, 0x00, 0x00]); /* reserved 11 */ +} + +// enable power saving mode, after configured with PM2 +function UBX_CFG_RXM() { + writeGPScmd([0x06, 0x11, /* UBX-CFG-RXM */ + 2, 0, /* length */ + 0x08, 0x01]); /* reserved, enable power save mode */ +} + + +/* + * Save configuration otherwise it will reset when the GPS wakes up + * + */ +function UBX_CFG_SAVE() { + writeGPScmd([0x06, 0x09, // class id + 0x0D, 0x00, // length + 0x00, 0x00, 0x00, 0x00, // clear mask + 0xFF, 0xFF, 0x00, 0x00, // save mask + 0x00, 0x00, 0x00, 0x00, // load mask + 0x07]); // b2=eeprom b1=flash b0=bat backed ram +} + +/* + * Reset to factory settings using clear mask in UBX_CFG_CFG + * https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control + */ +function UBX_CFG_RESET() { + writeGPScmd([0x06, 0x09, // class id + 0x0D, 0x00, + 0xFF, 0xFB, 0x00, 0x00, // clear mask + 0x00, 0x00, 0x00, 0x00, // save mask + 0xFF, 0xFF, 0x00, 0x00, // load mask + 0x17]); +} + + +/*********** GPS Setup Menu App *****************************/ + +function showMainMenu() { + var power_options = ["SuperE","PSMOO"]; + + const mainmenu = { + '': { 'title': 'GPS Setup' }, + '< Back': ()=>{exitSetup();}, + 'Power Mode': { + value: 0 | power_options.indexOf(settings.power_mode), + min: 0, max: 1, + format: v => power_options[v], + onchange: v => { + settings.power_mode = power_options[v]; + updateSettings(); + }, + }, + + 'Update (s)': { + value: settings.update, + min: 10, + max: 1800, + step: 10, + onchange: v => { + settings.update =v; + updateSettings(); + } + }, + 'Search (s)': { + value: settings.search, + min: 1, + max: 65, + step: 1, + onchange: v => { + settings.search = v; + updateSettings(); + } + } + }; + + return E.showMenu(mainmenu); +} + +/* +function exitSetup() { + log_debug("exitSetup()"); + if (settings_changed) { + log_debug(settings); + E.showMessage("Configuring GPS"); + setupGPS(); + } + load(); +} +*/ + +function exitSetup() { + log_debug("exitSetup()"); + if (settings_changed) { + log_debug(settings); + E.showMessage("Configuring GPS"); + setTimeout(function() { + setupGPS(); + setTimeout(function() { load() }, 750); + }, 500); + } else { + load(); + }; +} + +/* +function exitSetup() { + log_debug("exitSetup()"); + if (settings_changed) { + log_debug(settings); + g.clear(); + g.setFontAlign(0,0); + g.setColor(3); + g.setFontVector(25); + g.drawString("Configuring GPS",120,120); + setTimeout(function() { + setupGPS(); + setTimeout(function() { load() }, 500); + }, 500); + } else load(); +} +*/ + +loadSettings(); +showMainMenu(); + diff --git a/apps/gpssetup/settings.js b/apps/gpssetup/settings.js new file mode 100644 index 000000000..0e3c621d1 --- /dev/null +++ b/apps/gpssetup/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app + load("gpssetup.app.js"); +})(); diff --git a/apps/gpssetup/settings.json b/apps/gpssetup/settings.json new file mode 100644 index 000000000..631ececdc --- /dev/null +++ b/apps/gpssetup/settings.json @@ -0,0 +1 @@ +{"power_mode":"SuperE", "update":120, "search":6} From 4fed30f5a224d28ee0b0fef6194f86386fbbcbb8 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 13:06:19 +0000 Subject: [PATCH 138/181] added apps.json settings for gpssetup app --- apps/gpssetup/icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/gpssetup/icon.js diff --git a/apps/gpssetup/icon.js b/apps/gpssetup/icon.js new file mode 100644 index 000000000..114e6600e --- /dev/null +++ b/apps/gpssetup/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/ACmsAAIss1mBwIfZqwABFpwuYC4IADGBYuaDQwuDF5ouYLo4vJIYousCYQOIHJIuUCo4uIHJIuUf4wlGEIQaHFywvHGAguiF5GBEIgbHFzAvJDwwuRvV6F6xLJFxmkAAQuKqwtKPQ4RKFwgwICgobHVRJAJFQOr0owIE5AtNDIYRI0ulGBAuNapYABCRGkGBAuGAoIpNGBIIFGBF6FwuBFygAKGBAulGBgujGBWAF0jpBehIvlqwws1jnCGFOs1heBGFQuBFoYwoFxFWwAwjFw+BAAIMBGEIuMGEIuIeQIQFGDouJ1gSHGDYuSGDYuUGDSzBFYP+dAQuNGBN6F6RcBFyAwHwAvQGAYuSGAheCF54wCAAYuRGAQABwGAC6QuWGAV6FyYA/AH4A2A=")); From 8449fc00d8f49e05025a7ecde4f8911563d5aac8 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 13:07:58 +0000 Subject: [PATCH 139/181] added apps.json settings for gpssetup app --- apps.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4fbc1a779..166aa2dec 100644 --- a/apps.json +++ b/apps.json @@ -2765,6 +2765,19 @@ {"name":"testuserinput.app.js","url":"app.js"}, {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "gpssetup", + "name": "GPS Setup", + "shortName":"GPS configuration app", + "icon": "gpssetup.png", + "version":"0.02", + "description": "Configure the GPS power options and store them in the GPS nvram", + "tags": "gps, tools, outdoors", + "storage": [ + {"name":"gpssetup.settings.js","url":"settings.js"}, + {"name":"gpssetup.settings.json","url":"settings.json"}, + {"name":"gpssetup.app.js","url":"app.js"}, + {"name":"gpssetup.img","url":"icon.js","evaluate":true} + ] } - ] From c0d5d096710cf0465e598f791ed2ff0f85940805 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 18:47:12 +0000 Subject: [PATCH 140/181] corrected apps.json and added gpssetup.png --- apps.json | 3 ++- apps/gpssetup/gpssetup.png | Bin 0 -> 2663 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 apps/gpssetup/gpssetup.png diff --git a/apps.json b/apps.json index 166aa2dec..b322198cd 100644 --- a/apps.json +++ b/apps.json @@ -2770,9 +2770,10 @@ "name": "GPS Setup", "shortName":"GPS configuration app", "icon": "gpssetup.png", - "version":"0.02", + "version":"0.01", "description": "Configure the GPS power options and store them in the GPS nvram", "tags": "gps, tools, outdoors", + "readme": "README.md", "storage": [ {"name":"gpssetup.settings.js","url":"settings.js"}, {"name":"gpssetup.settings.json","url":"settings.json"}, diff --git a/apps/gpssetup/gpssetup.png b/apps/gpssetup/gpssetup.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5899983a41a383c3880d3fb62e1598d4c96e9f GIT binary patch literal 2663 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 Date: Tue, 9 Feb 2021 18:49:54 +0000 Subject: [PATCH 141/181] corrected apps.json description for gpssetup --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index b322198cd..fd8818460 100644 --- a/apps.json +++ b/apps.json @@ -2768,7 +2768,7 @@ }, { "id": "gpssetup", "name": "GPS Setup", - "shortName":"GPS configuration app", + "shortName":"GPS Setup", "icon": "gpssetup.png", "version":"0.01", "description": "Configure the GPS power options and store them in the GPS nvram", From a167a17d3e224c4f4ed99149eb43f32b2f886269 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 10 Feb 2021 08:10:06 +1300 Subject: [PATCH 142/181] Update app.js --- apps/speedalt/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index b533b1fbe..9b1886cd2 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.13'; +var v = '1.14'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -321,7 +321,7 @@ function startDraw(){ function stopDraw() { canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 30000); //Drop to low power in 30 secs. Keep lp mode off until we have a first fix. + 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() { From ba01cdb0bc28f6f53c9935683b75d8167c44c5b8 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 10 Feb 2021 08:34:14 +1300 Subject: [PATCH 143/181] Update README.md --- apps/speedalt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index f4a679574..538575ae9 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -55,7 +55,7 @@ Settings:
This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. -When using the Low Power GPS Service 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. 30 seconds after the display is blanked by the watch this app will switch the GPS to PMOO mode and will only attempt to get a fix every minute or two. 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. +When using the Low Power GPS Service 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 PMOO mode and will only attempt to get a fix every minute or two. 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. There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. From e6206733aec129fed5fe302f8ed2b930dccc9d31 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 21:19:43 +0000 Subject: [PATCH 144/181] updated gpssetup README file --- apps/gpssetup/README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md index 4992a16b5..7a47d049a 100644 --- a/apps/gpssetup/README.md +++ b/apps/gpssetup/README.md @@ -4,16 +4,17 @@ An App to enable the GPS to be configured into low power mode. ## Goals -To develop app that sets the GPS up to run with the lowest possible -power consumption. +To develop an app that configures the GPS to run with the lowest +possible power consumption. +Example power consumption of the GPS while powered on: * An app that turns on the GPS and constantly displays the screen will use around 75mA, the battery will last between 3-4 hours. -* Using the GPS in a Widget in Super-E Power Saving Mode (PSM) with - the screen off most of the time, will consume around 35mA and you - might get 10hrs before a recharge. +* Using the GPS with Super-E Power Saving Mode (PSM) with the screen + off most of the time, will consume around 35mA and you might get + 10hrs before a recharge. * Using the GPS in Power Saving Mode On/Off (PSMOO) with suitable settings can reduce the average consumption to around 15mA. A @@ -23,22 +24,23 @@ power consumption. ## Settings -The Settings App enables you set the options below for the GPS. -Either start the App from the launcher or go to Settings, select -App/Widgets and then 'GPS Setup'. +The Settings App enables you set the options below. Either start the +App from the launcher or go to Settings, select App/Widgets and then +'GPS Setup'. -When you exit the setup App the settings will be stored in the -gpssetup.settings.json file; the GPS will be switched on and the +When you exit the setup app, the settings will be stored in the +gpssetup.settings.json file, the GPS will be switched on and the necessary commands sent to the GPS to configure it. The GPS is then powered off. The GPS configuration is stored in the GPS non-volatile -memory so that next time the GPS is powered on they are used. These -settings will remain and impact every app that uses the GPS. +memory so that next time the GPS is powered, that configuration is +used. These settings will remain for all apps that use the GPS. - Power Mode: - SuperE - the factory default setup for the GPS. The recommended - power saving mode. + power saving mode. If you need frequent (every second) updates on + position, then this is the mode for you. - PSMOO - On/Off power saving mode. Configured by interval and search time. Choose this mode if you are happy to get a GPS From a264a7adc3e8733fa2764bedd0f71c860fd6fe14 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 23:14:19 +0000 Subject: [PATCH 145/181] working gpssetup app, with promise chain, debugging left on --- apps/gpssetup/app.js | 75 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/apps/gpssetup/app.js b/apps/gpssetup/app.js index 18dc57ec5..c8405eaa6 100644 --- a/apps/gpssetup/app.js +++ b/apps/gpssetup/app.js @@ -22,16 +22,8 @@ function loadSettings() { /*********** GPS Power and Setup Functions ******************/ function log_debug(o) { - //console.log(o); -} - -// quick hack -function wait(ms){ - var start = new Date().getTime(); - var end = start; - while(end < start + ms) { - end = new Date().getTime(); - } + let timestamp = new Date().getTime(); + console.log(timestamp + " : " + o); } function setupGPS() { @@ -41,9 +33,16 @@ function setupGPS() { } else { setupSuperE(); } - - log_debug("Powering GPS Off"); - Bangle.setGPSPower(0); +} + +/* +// quick hack +function wait(ms){ + var start = new Date().getTime(); + var end = start; + while(end < start + ms) { + end = new Date().getTime(); + } } function setupPSMOO() { @@ -72,6 +71,50 @@ function setupSuperE() { UBX_CFG_SAVE(); wait(20); } +*/ + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function setupSuperE() { + log_debug("setupGPS() Super-E"); + Promise.resolve().then(function() { + UBX_CFG_RESET(); + return delay(100); + }).then(function() { + UBX_CFG_PMS(); + return delay(20); + }).then(function() { + UBX_CFG_SAVE(); + return delay(20); + }).then(function() { + log_debug("Powering GPS Off"); + Bangle.setGPSPower(0); + return delay(20); + }); +} + +function setupPSMOO() { + log_debug("setupGPS() PSMOO"); + Promise.resolve().then(function() { + UBX_CFG_RESET(); + return delay(100); + }).then(function() { + UBX_CFG_PM2(settings.update, settings.search); + return delay(20); + }).then(function() { + UBX_CFG_RXM(); + return delay(20); + }).then(function() { + UBX_CFG_SAVE(); + return delay(20); + }).then(function() { + log_debug("Powering GPS Off"); + Bangle.setGPSPower(0); + return delay(20); + }); +} function writeGPScmd(cmd) { var d = [0xB5,0x62]; // sync chars @@ -87,6 +130,7 @@ function writeGPScmd(cmd) { // UBX-CFG-PMS - enable power management - Super-E function UBX_CFG_PMS() { + log_debug("UBX_CFG_PMS()"); writeGPScmd([0x06,0x86, // msg class + type 8,0,//length 0x00,0x03, 0,0, 0,0, 0,0]); @@ -104,7 +148,6 @@ function int_2_bytes( x ){ return bytes; } - /* * Extended Power Management * update and search are in milli seconds @@ -113,6 +156,7 @@ function int_2_bytes( x ){ * https://github.com/thasti/utrak/blob/master/gps.c */ function UBX_CFG_PM2(update,search) { + log_debug("UBX_CFG_PM2()"); var u = int_2_bytes(update*1000); var s = int_2_bytes(search*1000); @@ -135,6 +179,7 @@ function UBX_CFG_PM2(update,search) { // enable power saving mode, after configured with PM2 function UBX_CFG_RXM() { + log_debug("UBX_CFG_RXM()"); writeGPScmd([0x06, 0x11, /* UBX-CFG-RXM */ 2, 0, /* length */ 0x08, 0x01]); /* reserved, enable power save mode */ @@ -146,6 +191,7 @@ function UBX_CFG_RXM() { * */ function UBX_CFG_SAVE() { + log_debug("UBX_CFG_SAVE()"); writeGPScmd([0x06, 0x09, // class id 0x0D, 0x00, // length 0x00, 0x00, 0x00, 0x00, // clear mask @@ -159,6 +205,7 @@ function UBX_CFG_SAVE() { * https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control */ function UBX_CFG_RESET() { + log_debug("UBX_CFG_RESET()"); writeGPScmd([0x06, 0x09, // class id 0x0D, 0x00, 0xFF, 0xFB, 0x00, 0x00, // clear mask From 119cf8183f26ef32588c9e7ee6362a43adb44ded Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 23:29:15 +0000 Subject: [PATCH 146/181] working gpssetup app, removed old code, turned off debugging --- apps/gpssetup/app.js | 98 +++++++++++++------------------------------- 1 file changed, 28 insertions(+), 70 deletions(-) diff --git a/apps/gpssetup/app.js b/apps/gpssetup/app.js index c8405eaa6..f4cade2d6 100644 --- a/apps/gpssetup/app.js +++ b/apps/gpssetup/app.js @@ -1,3 +1,17 @@ +/* + * GPS Setup app, hughbarney AT googlemail DOT com + * With thanks to Gordon Williams for support and advice + * + * UBLOX power modes: + * SuperE - will provide updates every second and consume 35mA, 75mA with LCD on + * PSMOO - will sleep for update time and consume around 7mA during that period + * after acquiring satelite fixes the GPS will settle into a cycle of + * obtaining fix, sleeping for update seconds, wake up, get fix and sleep. + * + * See README file for more details + * + */ + Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -22,8 +36,8 @@ function loadSettings() { /*********** GPS Power and Setup Functions ******************/ function log_debug(o) { - let timestamp = new Date().getTime(); - console.log(timestamp + " : " + o); + //let timestamp = new Date().getTime(); + //console.log(timestamp + " : " + o); } function setupGPS() { @@ -35,44 +49,6 @@ function setupGPS() { } } -/* -// quick hack -function wait(ms){ - var start = new Date().getTime(); - var end = start; - while(end < start + ms) { - end = new Date().getTime(); - } -} - -function setupPSMOO() { - log_debug("setupGPS() PSMOO"); - UBX_CFG_RESET(); - wait(100); - - UBX_CFG_PM2(settings.update, settings.search); - wait(20); - - UBX_CFG_RXM(); - wait(20); - - UBX_CFG_SAVE(); - wait(20); -} - -function setupSuperE() { - log_debug("setupGPS() Super-E"); - UBX_CFG_RESET(); - wait(100); - - UBX_CFG_PMS(); - wait(20); - - UBX_CFG_SAVE(); - wait(20); -} -*/ - function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -90,6 +66,12 @@ function setupSuperE() { return delay(20); }).then(function() { log_debug("Powering GPS Off"); + /* + * must be part of the promise chain to ensure that + * setup does not return and powerOff before config functions + * have run + * + */ Bangle.setGPSPower(0); return delay(20); }); @@ -111,6 +93,12 @@ function setupPSMOO() { return delay(20); }).then(function() { log_debug("Powering GPS Off"); + /* + * must be part of the promise chain to ensure that + * setup does not return and powerOff before config functions + * have run + * + */ Bangle.setGPSPower(0); return delay(20); }); @@ -258,18 +246,6 @@ function showMainMenu() { return E.showMenu(mainmenu); } -/* -function exitSetup() { - log_debug("exitSetup()"); - if (settings_changed) { - log_debug(settings); - E.showMessage("Configuring GPS"); - setupGPS(); - } - load(); -} -*/ - function exitSetup() { log_debug("exitSetup()"); if (settings_changed) { @@ -284,24 +260,6 @@ function exitSetup() { }; } -/* -function exitSetup() { - log_debug("exitSetup()"); - if (settings_changed) { - log_debug(settings); - g.clear(); - g.setFontAlign(0,0); - g.setColor(3); - g.setFontVector(25); - g.drawString("Configuring GPS",120,120); - setTimeout(function() { - setupGPS(); - setTimeout(function() { load() }, 500); - }, 500); - } else load(); -} -*/ - loadSettings(); showMainMenu(); From 0946a676b603e53e3da241966a17e95ddd8e006b Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 9 Feb 2021 23:41:39 +0000 Subject: [PATCH 147/181] corrected typo in gpssetup README --- apps/gpssetup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md index 7a47d049a..a8f0ce5b7 100644 --- a/apps/gpssetup/README.md +++ b/apps/gpssetup/README.md @@ -61,6 +61,6 @@ used. These settings will remain for all apps that use the GPS. * [UBLOX Power Management App Note](https://www.u-blox.com/sites/default/files/products/documents/PowerManagement_AppNote_%28UBX-13005162%29.pdf) -* Some useful code on Github and be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) +* Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) and [here](https://github.com/thasti/utrak/blob/master/gps.c) From 0784361e438544c6370d8372b15921e94df11a55 Mon Sep 17 00:00:00 2001 From: nujw Date: Wed, 10 Feb 2021 17:32:11 +1300 Subject: [PATCH 148/181] Bumped inter ver --- apps/speedalt/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 9b1886cd2..f87290155 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -2,7 +2,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com */ -var v = '1.14'; +var v = '1.15'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts From f3373ca717bac30d0e46a8313a7074995ccee9ee Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 10 Feb 2021 08:24:49 +0000 Subject: [PATCH 149/181] remove un-needed image --- apps/testuserinput/testuserinput.img | 1 - 1 file changed, 1 deletion(-) delete mode 100644 apps/testuserinput/testuserinput.img diff --git a/apps/testuserinput/testuserinput.img b/apps/testuserinput/testuserinput.img deleted file mode 100644 index 173f74796..000000000 --- a/apps/testuserinput/testuserinput.img +++ /dev/null @@ -1 +0,0 @@ -00]]]]]]]]]]]]]]WWWWWW׳WWW]׬׬]WVV׬ά׳VVVV׬ά׬VVVV׬ͬ׬VVVV׬ͬ׬VVVVͬVVVVͬVVVV]ͬ]VVVVVVVVV22V͢ǣǣ \ No newline at end of file From 28c7cfc3fac5309851d6b13ae60f3dc3b1529bf6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 10 Feb 2021 08:27:18 +0000 Subject: [PATCH 150/181] tweaks to pass CI test --- apps/gpssetup/app.js | 2 +- apps/gpssetup/icon.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/gpssetup/app.js b/apps/gpssetup/app.js index f4cade2d6..6b69271a1 100644 --- a/apps/gpssetup/app.js +++ b/apps/gpssetup/app.js @@ -257,7 +257,7 @@ function exitSetup() { }, 500); } else { load(); - }; + } } loadSettings(); diff --git a/apps/gpssetup/icon.js b/apps/gpssetup/icon.js index 114e6600e..fbe544b8b 100644 --- a/apps/gpssetup/icon.js +++ b/apps/gpssetup/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/ACmsAAIss1mBwIfZqwABFpwuYC4IADGBYuaDQwuDF5ouYLo4vJIYousCYQOIHJIuUCo4uIHJIuUf4wlGEIQaHFywvHGAguiF5GBEIgbHFzAvJDwwuRvV6F6xLJFxmkAAQuKqwtKPQ4RKFwgwICgobHVRJAJFQOr0owIE5AtNDIYRI0ulGBAuNapYABCRGkGBAuGAoIpNGBIIFGBF6FwuBFygAKGBAulGBgujGBWAF0jpBehIvlqwws1jnCGFOs1heBGFQuBFoYwoFxFWwAwjFw+BAAIMBGEIuMGEIuIeQIQFGDouJ1gSHGDYuSGDYuUGDSzBFYP+dAQuNGBN6F6RcBFyAwHwAvQGAYuSGAheCF54wCAAYuRGAQABwGAC6QuWGAV6FyYA/AH4A2A=")); +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/ACmsAAIss1mBwIfZqwABFpwuYC4IADGBYuaDQwuDF5ouYLo4vJIYousCYQOIHJIuUCo4uIHJIuUf4wlGEIQaHFywvHGAguiF5GBEIgbHFzAvJDwwuRvV6F6xLJFxmkAAQuKqwtKPQ4RKFwgwICgobHVRJAJFQOr0owIE5AtNDIYRI0ulGBAuNapYABCRGkGBAuGAoIpNGBIIFGBF6FwuBFygAKGBAulGBgujGBWAF0jpBehIvlqwws1jnCGFOs1heBGFQuBFoYwoFxFWwAwjFw+BAAIMBGEIuMGEIuIeQIQFGDouJ1gSHGDYuSGDYuUGDSzBFYP+dAQuNGBN6F6RcBFyAwHwAvQGAYuSGAheCF54wCAAYuRGAQABwGAC6QuWGAV6FyYA/AH4A2A=")) From 0e2f33d656ed6687e54a80b3682ce4139b77a1df Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 11 Feb 2021 09:45:50 +0000 Subject: [PATCH 151/181] gbridge: 0.19: Support for call incoming/start/end --- apps.json | 6 +++--- apps/gbridge/ChangeLog | 1 + apps/gbridge/widget.js | 24 ++++++++++++++++++------ apps/notify/ChangeLog | 1 + apps/notify/notify.js | 8 +++++--- apps/notifyfs/ChangeLog | 1 + apps/notifyfs/notify.js | 8 +++++--- 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/apps.json b/apps.json index 40d3ea45c..de96afe4f 100644 --- a/apps.json +++ b/apps.json @@ -80,7 +80,7 @@ "name": "Notifications (default)", "shortName":"Notifications", "icon": "notify.png", - "version":"0.05", + "version":"0.06", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "tags": "widget", "type": "notify", @@ -93,7 +93,7 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.06", + "version":"0.07", "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", "tags": "widget", "type": "notify", @@ -139,7 +139,7 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.18", + "version":"0.19", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "readme": "README.md", diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index bec2d305a..da163e466 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -18,3 +18,4 @@ Nicer display of alarm clock notifications 0.17: Modified music notification for updated 'notify' library 0.18: Added reporting of step count and HRM (new Gadgetbridges can now log this) +0.19: Support for call incoming/start/end diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index d616e8816..d611e6686 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -15,7 +15,7 @@ var currentSteps = 0, lastSentSteps=0; var activityInterval; var hrmTimeout; - + function settings() { let settings = require('Storage').readJSON("gbridge.json", true) || {}; if (!("showIcon" in settings)) { @@ -129,11 +129,23 @@ } function handleCallEvent(event) { - if (event.cmd === "accept") { + var callIcon = require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")); + if (event.cmd === "incoming") { require("notify").show({ size: 55, title: event.name, id: "call", - body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}); + body: event.number, icon:callIcon}); Bangle.buzz(); + } else if (event.cmd === "start") { + require("notify").show({ + size: 55, title: event.name, id: "call", bgColor : "#008000", titleBgColor : "#00C000", + body: "In progress: "+event.number, icon:callIcon}); + } else if (event.cmd === "end") { + require("notify").show({ + size: 55, title: event.name, id: "call", bgColor : "#800000", titleBgColor : "#C00000", + body: "Ended: "+event.number, icon:callIcon}); + setTimeout(function() { + require("notify").hide({ id: "call" }); + }, 2000); } } @@ -148,7 +160,7 @@ setTimeout(_=>Bangle.beep(), 1000); },2000); } - + function handleActivityEvent(event) { var s = settings(); // handle setting activity interval @@ -251,7 +263,7 @@ function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } - + // Send a summary of activity to Gadgetbridge function sendActivity(hrm) { var steps = currentSteps - lastSentSteps; @@ -279,7 +291,7 @@ } }); handleActivityEvent({}); // kicks off activity reporting - + // Finally add widget WIDGETS["gbridgew"] = {area: "tl", width: 24, draw: draw, reload: reload}; reload(); diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index a1e8e4418..c823f9033 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -2,3 +2,4 @@ 0.02: Add notification ID option 0.03: Pass `area{x,y,w,h}` to render callback instead of just `y` 0.05: Adjust position of notification src text +0.06: Support background color diff --git a/apps/notify/notify.js b/apps/notify/notify.js index 6f5261de1..b18826d24 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify.js @@ -41,7 +41,9 @@ function fitWords(text,rows,width) { src : string // optional source name body : string // optional body text icon : string // optional icon (image string) - render function(y) // function callback to render + render : function(y) // function callback to render + bgColor : int/string // optional background color (default black) + titleBgColor : int/string // optional background color for title (default black) } */ /* @@ -83,13 +85,13 @@ exports.show = function(options) { b = y+h-1, r = x+w-1; // bottom,right g.setClipRect(x,y, r,b); // clear area - g.setColor(0).fillRect(x,y, r,b); + g.setColor(options.bgColor||0).fillRect(x,y, r,b); // bottom border g.setColor(0x39C7).fillRect(0,b-1, r,b); b -= 2;h -= 2; // title bar if (options.title || options.src) { - g.setColor(0x39C7).fillRect(x,y, r,y+20); + g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index 16bc0ebb3..d4ea69cc8 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -4,3 +4,4 @@ 0.04: Pass `area{x,y,w,h}` to render callback instead of just `y` 0.05: Fix `g` corruption issue if .hide gets called twice 0.06: Adjust position of notification src text and notifications without title +0.07: Support background color diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index 2c622f624..0b73ad2d2 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -37,7 +37,9 @@ function fitWords(text,rows,width) { src : string // optional source name body : string // optional body text icon : string // optional icon (image string) - render function(y) // function callback to render + render : function(y) // function callback to render + bgColor : int/string // optional background color (default black) + titleBgColor : int/string // optional background color for title (default black) } */ exports.show = function(options) { @@ -53,11 +55,11 @@ exports.show = function(options) { w = 240, h = size; // clear screen - g.clear(1); + g.setColor(options.bgColor||0).fillRect(0,0,g.getWidth(),g.getHeight()); // top bar if (options.title||options.src) { const title = options.title || options.src - g.setColor(0x39C7).fillRect(x, y, x+w-1, y+30); + g.setColor(options.titleBgColor||0x39C7).fillRect(x, y, x+w-1, y+30); g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3); g.drawString(title.trim().substring(0, 13), x+5, y+3); if (options.title && options.src) { From b5c7ded4b590e0b9ed37cca6834a3d632eb24f67 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 11 Feb 2021 09:57:34 +0000 Subject: [PATCH 152/181] gbridge 0.20: Reduce memory usage --- apps.json | 2 +- apps/gbridge/ChangeLog | 1 + apps/gbridge/widget.js | 96 +++++++++++++++++------------------------- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/apps.json b/apps.json index de96afe4f..5d095ef39 100644 --- a/apps.json +++ b/apps.json @@ -139,7 +139,7 @@ { "id": "gbridge", "name": "Gadgetbridge", "icon": "app.png", - "version":"0.19", + "version":"0.20", "description": "The default notification handler for Gadgetbridge notifications from Android", "tags": "tool,system,android,widget", "readme": "README.md", diff --git a/apps/gbridge/ChangeLog b/apps/gbridge/ChangeLog index da163e466..579d3580e 100644 --- a/apps/gbridge/ChangeLog +++ b/apps/gbridge/ChangeLog @@ -19,3 +19,4 @@ 0.17: Modified music notification for updated 'notify' library 0.18: Added reporting of step count and HRM (new Gadgetbridges can now log this) 0.19: Support for call incoming/start/end +0.20: Reduce memory usage diff --git a/apps/gbridge/widget.js b/apps/gbridge/widget.js index d611e6686..efc620e36 100644 --- a/apps/gbridge/widget.js +++ b/apps/gbridge/widget.js @@ -2,13 +2,11 @@ // Music handling const state = { music: "stop", - musicInfo: { artist: "", album: "", track: "" }, - scrollPos: 0 }; // activity reporting @@ -43,14 +41,6 @@ return event; } } - function handleNotificationEvent(event) { - if (event.t === "notify") { - require("notify").show(prettifyNotificationEvent(event)); - Bangle.buzz(); - } else { // notify- - require("notify").hide(event); - } - } function updateMusic(options){ if (state.music === "play") { @@ -117,49 +107,6 @@ require("notify").hide("music"); } } - function handleMusicStateUpdate(event) { - if (state.music !== event.state) { - state.music = event.state - updateMusic({on: true}); - } - } - function handleMusicInfoUpdate(event) { - state.musicInfo = event; - updateMusic({on: false}); - } - - function handleCallEvent(event) { - var callIcon = require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw=")); - if (event.cmd === "incoming") { - require("notify").show({ - size: 55, title: event.name, id: "call", - body: event.number, icon:callIcon}); - Bangle.buzz(); - } else if (event.cmd === "start") { - require("notify").show({ - size: 55, title: event.name, id: "call", bgColor : "#008000", titleBgColor : "#00C000", - body: "In progress: "+event.number, icon:callIcon}); - } else if (event.cmd === "end") { - require("notify").show({ - size: 55, title: event.name, id: "call", bgColor : "#800000", titleBgColor : "#C00000", - body: "Ended: "+event.number, icon:callIcon}); - setTimeout(function() { - require("notify").hide({ id: "call" }); - }, 2000); - } - } - - function handleFindEvent(event) { - if (state.find) { - clearInterval(state.find); - delete state.find; - } - if (event.n) - state.find = setInterval(_=>{ - Bangle.buzz(); - setTimeout(_=>Bangle.beep(), 1000); - },2000); - } function handleActivityEvent(event) { var s = settings(); @@ -205,19 +152,52 @@ switch (event.t) { case "notify": case "notify-": - handleNotificationEvent(event); + if (event.t === "notify") { + require("notify").show(prettifyNotificationEvent(event)); + Bangle.buzz(); + } else { // notify- + require("notify").hide(event); + } break; case "musicinfo": - handleMusicInfoUpdate(event); + state.musicInfo = event; + updateMusic({on: false}); break; case "musicstate": - handleMusicStateUpdate(event); + if (state.music !== event.state) { + state.music = event.state + updateMusic({on: true}); + } break; case "call": - handleCallEvent(event); + var note = { size: 55, title: event.name, id: "call", + body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))} + if (event.cmd === "incoming") { + require("notify").show(note); + Bangle.buzz(); + } else if (event.cmd === "start") { + require("notify").show(Object.assign(note, { + bgColor : "#008000", titleBgColor : "#00C000", + body: "In progress: "+event.number})); + } else if (event.cmd === "end") { + require("notify").show(Object.assign(note, { + bgColor : "#800000", titleBgColor : "#C00000", + body: "Ended: "+event.number})); + setTimeout(function() { + require("notify").hide({ id: "call" }); + }, 2000); + } break; case "find": - handleFindEvent(event); + if (state.find) { + clearInterval(state.find); + delete state.find; + } + if (event.n) + state.find = setInterval(_=>{ + Bangle.buzz(); + setTimeout(_=>Bangle.beep(), 1000); + },2000); break; case "act": handleActivityEvent(event); From b7940598845d38e856308221bbcecca5fd33d12f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 11 Feb 2021 11:24:44 +0000 Subject: [PATCH 153/181] 0.02: Created gppsetup module --- apps.json | 3 +- apps/gpssetup/ChangeLog | 1 + apps/gpssetup/README.md | 42 +++++++-- apps/gpssetup/app.js | 190 ++++---------------------------------- apps/gpssetup/gpssetup.js | 179 +++++++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 180 deletions(-) create mode 100644 apps/gpssetup/gpssetup.js diff --git a/apps.json b/apps.json index 5d095ef39..89428e841 100644 --- a/apps.json +++ b/apps.json @@ -2771,11 +2771,12 @@ "name": "GPS Setup", "shortName":"GPS Setup", "icon": "gpssetup.png", - "version":"0.01", + "version":"0.02", "description": "Configure the GPS power options and store them in the GPS nvram", "tags": "gps, tools, outdoors", "readme": "README.md", "storage": [ + {"name":"gpssetup","url":"gpssetup.js"}, {"name":"gpssetup.settings.js","url":"settings.js"}, {"name":"gpssetup.settings.json","url":"settings.json"}, {"name":"gpssetup.app.js","url":"app.js"}, diff --git a/apps/gpssetup/ChangeLog b/apps/gpssetup/ChangeLog index 0099beb29..e57d53d8e 100644 --- a/apps/gpssetup/ChangeLog +++ b/apps/gpssetup/ChangeLog @@ -1 +1,2 @@ 0.01: First version of GPS Setup app +0.02: Created gppsetup module diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md index a8f0ce5b7..4d3922f50 100644 --- a/apps/gpssetup/README.md +++ b/apps/gpssetup/README.md @@ -1,6 +1,6 @@ # GPS Setup -An App to enable the GPS to be configured into low power mode. +An App and module to enable the GPS to be configured into low power mode. ## Goals @@ -15,14 +15,14 @@ Example power consumption of the GPS while powered on: * Using the GPS with Super-E Power Saving Mode (PSM) with the screen off most of the time, will consume around 35mA and you might get 10hrs before a recharge. - + * Using the GPS in Power Saving Mode On/Off (PSMOO) with suitable settings can reduce the average consumption to around 15mA. A simple test using a 120s update period, 6s search period was still running with 45% battery 20 hours after it started. -## Settings +## Settings App The Settings App enables you set the options below. Either start the App from the launcher or go to Settings, select App/Widgets and then @@ -38,11 +38,11 @@ used. These settings will remain for all apps that use the GPS. - Power Mode: - - SuperE - the factory default setup for the GPS. The recommended + - **SuperE** - the factory default setup for the GPS. The recommended power saving mode. If you need frequent (every second) updates on position, then this is the mode for you. - - PSMOO - On/Off power saving mode. Configured by interval and + - **PSMOO** - On/Off power saving mode. Configured by interval and search time. Choose this mode if you are happy to get a GPS position update less often (say every 1 or 2 minutes). The longer the interval the more time the GPS will spend sleeping in low @@ -55,6 +55,37 @@ used. These settings will remain for all apps that use the GPS. - search - the time between two acquisition attempts if the receiver is unable to get a position fix. +## Module + +A module is provided that'll allow you to set GPS configuration from your own +app. To use it: + +``` +// This will set up the GPS to current saved defaults. It's not normally +// needed unless the watch's battery has run down +require("gpssetup").setPowerMode(); + +// This sets up the PSMOO mode. update/search are optional in seconds +require("gpssetup").setPowerMode({ + power_mode:"PSMOO", + update:optional (default 120), + search:optional (default 5)}) + +// This sets up SuperE +require("gpssetup").setPowerMode({power_mode:"SuperE"}) +``` + +`setPowerMode` returns a promise, which is completed when the GPS is set up. + +So you can for instance do the following to turn the GPS off once it +has been configured: + +``` +require("gpssetup").setPowerMode().then(function() { + Bangle.setGPSPower(0); +}); +``` + ## References * [UBLOX M8 Receiver Data Sheet](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29.pdf) @@ -63,4 +94,3 @@ used. These settings will remain for all apps that use the GPS. * Some useful code on Github can be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control) and [here](https://github.com/thasti/utrak/blob/master/gps.c) - diff --git a/apps/gpssetup/app.js b/apps/gpssetup/app.js index 6b69271a1..e0d188af5 100644 --- a/apps/gpssetup/app.js +++ b/apps/gpssetup/app.js @@ -5,16 +5,21 @@ * UBLOX power modes: * SuperE - will provide updates every second and consume 35mA, 75mA with LCD on * PSMOO - will sleep for update time and consume around 7mA during that period - * after acquiring satelite fixes the GPS will settle into a cycle of + * after acquiring satelite fixes the GPS will settle into a cycle of * obtaining fix, sleeping for update seconds, wake up, get fix and sleep. * * See README file for more details - * + * */ Bangle.loadWidgets(); Bangle.drawWidgets(); +function log_debug(o) { + //let timestamp = new Date().getTime(); + //console.log(timestamp + " : " + o); +} + const SETTINGS_FILE = "gpssetup.settings.json"; let settings = undefined; let settings_changed = false; @@ -35,174 +40,15 @@ function loadSettings() { /*********** GPS Power and Setup Functions ******************/ -function log_debug(o) { - //let timestamp = new Date().getTime(); - //console.log(timestamp + " : " + o); -} - function setupGPS() { Bangle.setGPSPower(1); - if (settings.power_mode === "PSMOO") { - setupPSMOO(); - } else { - setupSuperE(); - } + setTimeout(function() { + require("gpssetup").setPowerMode().then(function() { + Bangle.setGPSPower(0); + }); + }, 100); } -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -function setupSuperE() { - log_debug("setupGPS() Super-E"); - Promise.resolve().then(function() { - UBX_CFG_RESET(); - return delay(100); - }).then(function() { - UBX_CFG_PMS(); - return delay(20); - }).then(function() { - UBX_CFG_SAVE(); - return delay(20); - }).then(function() { - log_debug("Powering GPS Off"); - /* - * must be part of the promise chain to ensure that - * setup does not return and powerOff before config functions - * have run - * - */ - Bangle.setGPSPower(0); - return delay(20); - }); -} - -function setupPSMOO() { - log_debug("setupGPS() PSMOO"); - Promise.resolve().then(function() { - UBX_CFG_RESET(); - return delay(100); - }).then(function() { - UBX_CFG_PM2(settings.update, settings.search); - return delay(20); - }).then(function() { - UBX_CFG_RXM(); - return delay(20); - }).then(function() { - UBX_CFG_SAVE(); - return delay(20); - }).then(function() { - log_debug("Powering GPS Off"); - /* - * must be part of the promise chain to ensure that - * setup does not return and powerOff before config functions - * have run - * - */ - Bangle.setGPSPower(0); - return delay(20); - }); -} - -function writeGPScmd(cmd) { - var d = [0xB5,0x62]; // sync chars - d = d.concat(cmd); - var a=0,b=0; - for (var i=2;i>8; - } while (i); - - return bytes; -} - -/* - * Extended Power Management - * update and search are in milli seconds - * settings are loaded little endian, lsb first - * - * https://github.com/thasti/utrak/blob/master/gps.c - */ -function UBX_CFG_PM2(update,search) { - log_debug("UBX_CFG_PM2()"); - - var u = int_2_bytes(update*1000); - var s = int_2_bytes(search*1000); - - writeGPScmd([0x06, 0x3B, /* class id */ - 44, 0, /* length */ - 0x01, 0x00, 0x00, 0x00, /* v1, reserved 1..3 */ - 0x00, 0x10, 0x00, 0x00, /* on/off-mode, update ephemeris */ - u[3], u[2], u[1], u[0], /* update period, ms, 120s=00 01 D4 C0, 30s= 00 00 75 30 */ - s[3], s[2], s[1], s[0], /* search period, ms, 120s, 20s = 00 00 4E 20, 5s = 13 88 */ - 0x00, 0x00, 0x00, 0x00, /* grid offset */ - 0x00, 0x00, /* on-time after first fix */ - 0x01, 0x00, /* minimum acquisition time */ - 0x00, 0x00, 0x00, 0x00, /* reserved 4,5 */ - 0x00, 0x00, 0x00, 0x00, /* reserved 6 */ - 0x00, 0x00, 0x00, 0x00, /* reserved 7 */ - 0x00, 0x00, 0x00, 0x00, /* reserved 8,9,10 */ - 0x00, 0x00, 0x00, 0x00]); /* reserved 11 */ -} - -// enable power saving mode, after configured with PM2 -function UBX_CFG_RXM() { - log_debug("UBX_CFG_RXM()"); - writeGPScmd([0x06, 0x11, /* UBX-CFG-RXM */ - 2, 0, /* length */ - 0x08, 0x01]); /* reserved, enable power save mode */ -} - - -/* - * Save configuration otherwise it will reset when the GPS wakes up - * - */ -function UBX_CFG_SAVE() { - log_debug("UBX_CFG_SAVE()"); - writeGPScmd([0x06, 0x09, // class id - 0x0D, 0x00, // length - 0x00, 0x00, 0x00, 0x00, // clear mask - 0xFF, 0xFF, 0x00, 0x00, // save mask - 0x00, 0x00, 0x00, 0x00, // load mask - 0x07]); // b2=eeprom b1=flash b0=bat backed ram -} - -/* - * Reset to factory settings using clear mask in UBX_CFG_CFG - * https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control - */ -function UBX_CFG_RESET() { - log_debug("UBX_CFG_RESET()"); - writeGPScmd([0x06, 0x09, // class id - 0x0D, 0x00, - 0xFF, 0xFB, 0x00, 0x00, // clear mask - 0x00, 0x00, 0x00, 0x00, // save mask - 0xFF, 0xFF, 0x00, 0x00, // load mask - 0x17]); -} - - /*********** GPS Setup Menu App *****************************/ function showMainMenu() { @@ -217,18 +63,17 @@ function showMainMenu() { format: v => power_options[v], onchange: v => { settings.power_mode = power_options[v]; - updateSettings(); + updateSettings(); }, }, - 'Update (s)': { value: settings.update, min: 10, max: 1800, step: 10, onchange: v => { - settings.update =v; - updateSettings(); + settings.update = v; + updateSettings(); } }, 'Search (s)': { @@ -237,8 +82,8 @@ function showMainMenu() { max: 65, step: 1, onchange: v => { - settings.search = v; - updateSettings(); + settings.search = v; + updateSettings(); } } }; @@ -262,4 +107,3 @@ function exitSetup() { loadSettings(); showMainMenu(); - diff --git a/apps/gpssetup/gpssetup.js b/apps/gpssetup/gpssetup.js new file mode 100644 index 000000000..f8fed68ff --- /dev/null +++ b/apps/gpssetup/gpssetup.js @@ -0,0 +1,179 @@ +const SETTINGS_FILE = "gpssetup.settings.json"; + +function log_debug(o) { + //let timestamp = new Date().getTime(); + //console.log(timestamp + " : " + o); +} + +function writeGPScmd(cmd) { + var d = [0xB5,0x62]; // sync chars + d = d.concat(cmd); + var a=0,b=0; + for (var i=2;i>8; + } while (i); + return bytes; + } + + var u = int_2_bytes(update*1000); + var s = int_2_bytes(search*1000); + + writeGPScmd([0x06, 0x3B, /* class id */ + 44, 0, /* length */ + 0x01, 0x00, 0x00, 0x00, /* v1, reserved 1..3 */ + 0x00, 0x10, 0x00, 0x00, /* on/off-mode, update ephemeris */ + u[3], u[2], u[1], u[0], /* update period, ms, 120s=00 01 D4 C0, 30s= 00 00 75 30 */ + s[3], s[2], s[1], s[0], /* search period, ms, 120s, 20s = 00 00 4E 20, 5s = 13 88 */ + 0x00, 0x00, 0x00, 0x00, /* grid offset */ + 0x00, 0x00, /* on-time after first fix */ + 0x01, 0x00, /* minimum acquisition time */ + 0x00, 0x00, 0x00, 0x00, /* reserved 4,5 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 6 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 7 */ + 0x00, 0x00, 0x00, 0x00, /* reserved 8,9,10 */ + 0x00, 0x00, 0x00, 0x00]); /* reserved 11 */ +} + +// enable power saving mode, after configured with PM2 +function UBX_CFG_RXM() { + log_debug("UBX_CFG_RXM()"); + writeGPScmd([0x06, 0x11, /* UBX-CFG-RXM */ + 2, 0, /* length */ + 0x08, 0x01]); /* reserved, enable power save mode */ +} + +/* + * Save configuration otherwise it will reset when the GPS wakes up + */ +function UBX_CFG_SAVE() { + log_debug("UBX_CFG_SAVE()"); + writeGPScmd([0x06, 0x09, // class id + 0x0D, 0x00, // length + 0x00, 0x00, 0x00, 0x00, // clear mask + 0xFF, 0xFF, 0x00, 0x00, // save mask + 0x00, 0x00, 0x00, 0x00, // load mask + 0x07]); // b2=eeprom b1=flash b0=bat backed ram +} + +/* + * Reset to factory settings using clear mask in UBX_CFG_CFG + * https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control + */ +function UBX_CFG_RESET() { + log_debug("UBX_CFG_RESET()"); + writeGPScmd([0x06, 0x09, // class id + 0x0D, 0x00, + 0xFF, 0xFB, 0x00, 0x00, // clear mask + 0x00, 0x00, 0x00, 0x00, // save mask + 0xFF, 0xFF, 0x00, 0x00, // load mask + 0x17]); +} + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +function setupSuperE() { + log_debug("setupGPS() Super-E"); + return Promise.resolve().then(function() { + UBX_CFG_RESET(); + return delay(100); + }).then(function() { + UBX_CFG_PMS(); + return delay(20); + }).then(function() { + UBX_CFG_SAVE(); + return delay(20); + }).then(function() { + log_debug("Powering GPS Off"); + /* + * must be part of the promise chain to ensure that + * setup does not return and powerOff before config functions + * have run + */ + return delay(20); + }); +} + +function setupPSMOO(settings) { + log_debug("setupGPS() PSMOO"); + return Promise.resolve().then(function() { + UBX_CFG_RESET(); + return delay(100); + }).then(function() { + UBX_CFG_PM2(settings.update, settings.search); + return delay(20); + }).then(function() { + UBX_CFG_RXM(); + return delay(20); + }).then(function() { + UBX_CFG_SAVE(); + return delay(20); + }).then(function() { + log_debug("Powering GPS Off"); + /* + * must be part of the promise chain to ensure that + * setup does not return and powerOff before config functions + * have run + */ + return delay(20); + }); +} + +/** Set GPS power mode (assumes GPS on), returns a promise. +Either: + +require("gpssetup").setPowerMode() // <-- set up GPS to current saved defaults +require("gpssetup").setPowerMode({power_mode:"PSMOO", update:optional, search:optional}) // <-- PSMOO mode +require("gpssetup").setPowerMode({power_mode:"SuperE"}) // <-- Super E mode + +See the README for more information + */ +exports.setPowerMode = function(options) { + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + if (options) { + if (options.update) settings.update = options.update; + if (options.search) settings.search = options.search; + if (options.power_mode) settings.power_mode = options.power_mode; + } + settings.update = settings.update||120; + settings.search = settings.search||5; + settings.power_mode = settings.power_mode||"SuperE"; + if (options) require("Storage").write(SETTINGS_FILE, settings); + if (settings.power_mode === "PSMOO") { + return setupPSMOO(settings); + } else { + return setupSuperE(); + } +}; From 4a452f86b7f22e72d5eb08b1ce251e3f33a8a06b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 11 Feb 2021 11:29:01 +0000 Subject: [PATCH 154/181] docs --- apps/gpssetup/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/gpssetup/README.md b/apps/gpssetup/README.md index 4d3922f50..8e64c6a30 100644 --- a/apps/gpssetup/README.md +++ b/apps/gpssetup/README.md @@ -84,7 +84,20 @@ has been configured: require("gpssetup").setPowerMode().then(function() { Bangle.setGPSPower(0); }); -``` +``` + +**Note:** It's not guaranteed that the user will have installed the `gpssetup` +app/module. It might be worth checking for its existence by surrounding the +`require` call with `try...catch` block. + +``` +var gpssetup; +try { + gpssetup = require("gpssetup") +} catch(e) { + E.showMessage("gpssetup\nnot installed"); +} +``` ## References From c5c3910bf5ebd13f106e7bb5f79a52956ee14b89 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 11 Feb 2021 20:20:57 +0000 Subject: [PATCH 155/181] added walkers clock --- apps/walkersclock/README.md | 63 ++++ apps/walkersclock/app.js | 522 +++++++++++++++++++++++++++ apps/walkersclock/gps_alt.jpg | Bin 0 -> 48399 bytes apps/walkersclock/gps_osref.jpg | Bin 0 -> 44994 bytes apps/walkersclock/gps_speed.jpg | Bin 0 -> 44988 bytes apps/walkersclock/icon.js | 1 + apps/walkersclock/walkersclock48.png | Bin 0 -> 1861 bytes 7 files changed, 586 insertions(+) create mode 100644 apps/walkersclock/README.md create mode 100644 apps/walkersclock/app.js create mode 100644 apps/walkersclock/gps_alt.jpg create mode 100644 apps/walkersclock/gps_osref.jpg create mode 100644 apps/walkersclock/gps_speed.jpg create mode 100644 apps/walkersclock/icon.js create mode 100644 apps/walkersclock/walkersclock48.png diff --git a/apps/walkersclock/README.md b/apps/walkersclock/README.md new file mode 100644 index 000000000..0c10f79eb --- /dev/null +++ b/apps/walkersclock/README.md @@ -0,0 +1,63 @@ +# Walkers Clock + +A larg font watch, displays steps, can switch GPS on/off, displays grid reference + +The watch works well with GPSsetup, the Activepedom or Widpedom +wdigets. A tiny GPS power widget is waiting in the wings for when +the v2.09 firware is released. + +## Features + +- Displays the time in large font +- Uses BTN1 to select modeline display (eg battery status or switch between setting when in a function mode +- Uses BTN3 to select the function mode (eg turn on/off GPS, or change GPS display) + - two function menus at present + GPS Power = On/Off + GPS Display = Grid | Speed Alt + when the modeline in CYAN use button BTN1 to switch between options +- Display the current steps if one of the steps widgets is installed +- Ensures that BTN2 requires a 1.5 second press in order to switch to the launcher + this is so you dont accidently switch out of the GPS/watch display with you coat sleeve +- Displays the timestamp of the last GPS fix when the GPS is on +- Buzzes when the GPS aquires a positional satellite fix +- Displays the current OS map grid reference in a large font +- Displays the age of the current GPS position fix in seconds +- Works in tandem with the GPS setup app so that you can reduce the power consumption of the GPS + +## BTN1 cycles the information line + +- By default the modeline is switched off +- Click BTN1 once and display your steps (if one of the step widgets is installed) +- Click BTN1 again and it will show battery % charge +- Click BTN1 again and it will switch the modeline off + +## BTN2 Long press to start the launcher + +BTN2 is confiured to respond to a 1.5 second press in order to switch +to the launcher App. Simply press and hold until you hear a buzz and +release. This avoids accidently switching out of the watch app when +clothing catches it. + +## BTN3 cycles the function mode + +- Click BTN3 once and the GPS ON / OFF menu is displayed +- If the GPS is OFF then pressing BTN1 will turn it ON +- If the GPS is ON then Clicking BTN1 will turn it OFF + +When the GPS is ON a second function menu can be displayed by +pressing BTN3 again. This will show options to change the GPS display +on the second line of the watch. + +- Grid - will display the GPS position converted to an OS Grid Ref +- Speed - will display the GPS speed inforation supplied in the last GPS fix +- Alt - will display the altitude information + +![](gps_osref.jpg) +![](gps_speed.jpg) +![](gps_alt.jpg) + +## Future Enhancements +* Ability to turn on the Heart Rate monitor +* Maybe a simple stopwatch capability +* Fix the screen flicker + diff --git a/apps/walkersclock/app.js b/apps/walkersclock/app.js new file mode 100644 index 000000000..df009534a --- /dev/null +++ b/apps/walkersclock/app.js @@ -0,0 +1,522 @@ +/* + * Walkers clock, hugh barney AT googlemail DOT com + * + * A clock that has the following features + * - displays the time in large font + * - uses BTN1 to select modeline display (eg battery status or switch between setting when in a function mode + * - uses BTN3 to select the function mode (eg turn on/off GPS, or change GPS display) + * - two function menus at present + * GPS Power = On/Off + * GPS Display = Grid | Speed Alt + * when the modeline in CYAN use button BTN1 to switch between options + * - display the current steps if one of the steps widgets is installed + * - ensures that BTN2 requires a 1.5 second press in order to switch to the launcher + * this is so you dont accidently switch out of the GPS/watch display with you coat sleeve + * - displays the timestamp of the last GPS fix when the GPS is on + * - buzzes when the GPS aquires a positional satellite fix + * - displays the current OS map grid reference in a large font + * - displays the age of the current GPS position fix in seconds + * - works in tandem with the GPS setup app so that you can reduce the power consumption of the GPS + * + */ + +const INFO_NONE = "none"; +const INFO_BATT = "batt"; +const INFO_STEPS = "step"; + +const FN_MODE_OFF = "fn_mode_off"; +const FN_MODE_GPS = "fn_mode_gps"; +const FN_MODE_GDISP = "fn_mode_gdisp"; + +const GPS_OFF = "gps_off"; +const GPS_TIME = "gps_time"; +const GPS_SATS = "gps_sats"; +const GPS_RUNNING = "gps_running"; + +const GDISP_OS = "g_osref"; +const GDISP_SPEED = "g_speed"; +const GDISP_ALT = "g_alt"; + +const Y_TIME = 40; +const Y_ACTIVITY = 120; +const Y_MODELINE = 200; + +let gpsState = GPS_OFF; +let gpsPowerState = false; +let infoMode = INFO_NONE; +let functionMode = FN_MODE_OFF; +let gpsDisplay = GDISP_OS; + +let last_steps = undefined; +let firstPress = 0; + +let last_fix = { + fix: 0, + alt: 0, + lat: 0, + lon: 0, + speed: 0, + time: 0, + satellites: 0 +}; + +function drawTime() { + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4].substr(0,5); + + g.reset(); + g.clearRect(0,24,239,239); + g.setColor(1,1,1); // white + g.setFontAlign(0, -1); + + if (gpsState == GPS_SATS || gpsState == GPS_RUNNING) { + time = last_fix.time.toUTCString().split(" "); + time = time[4]; + g.setFont("Vector", 56); + } else { + g.setFont("Vector", 80); + } + + g.drawString(time, g.getWidth()/2, Y_TIME); +} + +function drawSteps() { + g.setColor(0,255,0); // green + g.setFont("Vector", 60); + g.drawString(getSteps(), g.getWidth()/2, Y_ACTIVITY); +} + +function drawActivity() { + if (!gpsPowerState) { + drawSteps(); + return; + } + + g.setFont("6x8", 3); + g.setColor(1,1,1); + g.setFontAlign(0, -1); + + if (gpsState == GPS_TIME) { + g.drawString("Waiting for", g.getWidth()/2, Y_ACTIVITY); + g.drawString("GPS", g.getWidth()/2, Y_ACTIVITY + 30); + return; + } + + if (gpsState == GPS_SATS) { + g.drawString("Satellites", g.getWidth()/2, Y_ACTIVITY); + g.drawString(last_fix.satellites, g.getWidth()/2, Y_ACTIVITY + 30); + return; + } + + if (gpsState == GPS_RUNNING) { + //console.log("Draw GPS Running"); + let time = formatTime(last_fix.time); + let age = timeSince(time); + let os = OsGridRef.latLongToOsGrid(last_fix); + let ref = to_map_ref(6, os.easting, os.northing); + let speed; + + if (age < 0) age = 0; + g.setFontVector(40); + g.setColor(0xFFC0); + + switch(gpsDisplay) { + case GDISP_OS: + g.drawString(ref, 120, Y_ACTIVITY, true); + break; + case GDISP_SPEED: + speed = last_fix.speed; + speed = speed.toFixed(1); + g.drawString(speed + "kph", 120, Y_ACTIVITY, true); + break; + case GDISP_ALT: + g.drawString(last_fix.alt + "m" , 120, Y_ACTIVITY, true); + break; + } + + g.setFont("6x8",2); + g.setColor(1,1,1); + g.drawString(age, 120, Y_ACTIVITY + 46); + } +} + +function onTick() { + if (!Bangle.isLCDOn()) + return; + + if (gpsPowerState) { + drawAll(); + return; + } + + if (last_steps != getSteps()) { + last_steps = getSteps(); + drawAll(); + return; + } + + var t = new Date(); + + if (t.getSeconds() === 0 && !gpsPowerState) { + drawAll(); + } +} + +function drawAll(){ + drawTime(); + drawActivity(); // steps, hrt or gps + drawInfo(); +} + +function drawInfo() { + let val; + let str = ""; + let col = 0x07E0; // green + + //console.log("drawInfo(), infoMode=" + infoMode + " funcMode=" + functionMode); + + switch(functionMode) { + case FN_MODE_OFF: + break; + case FN_MODE_GPS: + col = 0x07FF; // cyan + str = "GPS: " + (gpsPowerState ? "ON" : "OFF"); + drawModeLine(str,col); + return; + case FN_MODE_GDISP: + col = 0x07FF; // cyan + switch(gpsDisplay) { + case GDISP_OS: + str = "GPS: Grid"; + break; + case GDISP_SPEED: + str = "GPS: Speed"; + break; + case GDISP_ALT: + str = "GPS: Alt"; + break; + } + drawModeLine(str,col); + return; + } + + switch(infoMode) { + case INFO_NONE: + col = 0x0000; + str = ""; + break; + case INFO_STEPS: + str = "Steps: " + getSteps(); + break; + case INFO_BATT: + default: + str = "Battery: " + E.getBattery() + "%"; + } + + drawModeLine(str,col); +} + +function drawModeLine(str, col) { + g.setFont("6x8", 3); + g.setColor(col); + g.fillRect(0, Y_MODELINE - 3, 239, Y_MODELINE + 25); + g.setColor(0,0,0); + g.setFontAlign(0, -1); + g.drawString(str, g.getWidth()/2, Y_MODELINE); +} + +function changeInfoMode() { + switch(functionMode) { + case FN_MODE_OFF: + break; + case FN_MODE_GPS: + gpsPowerState = !gpsPowerState; + Bangle.buzz(); + Bangle.setGPSPower(gpsPowerState ? 1 : 0); + if (gpsPowerState) { + gpsState = GPS_TIME; // waiting first response so we can display time + Bangle.on('GPS', processFix); + } else { + Bangle.removeListener("GPS", processFix); + gpsState = GPS_OFF; + } + resetLastFix(); + + // poke the gps widget indicator to change + if (WIDGETS.gps !== undefined) { + WIDGETS.gps.draw(); + } + functionMode = FN_MODE_OFF; + infoMode = INFO_NONE; + //drawInfo(); + return; + + case FN_MODE_GDISP: + switch (gpsDisplay) { + case GDISP_OS: + gpsDisplay = GDISP_SPEED; + break; + case GDISP_SPEED: + gpsDisplay = GDISP_ALT; + break; + case GDISP_ALT: + default: + gpsDisplay = GDISP_OS; + break; + } + } + + switch(infoMode) { + case INFO_NONE: + if (stepsWidget() !== undefined) + infoMode = INFO_STEPS; + else + infoMode = INFO_BATT; + break; + case INFO_STEPS: + infoMode = INFO_BATT; + break; + case INFO_BATT: + default: + infoMode = INFO_NONE; + } + //drawInfo(); +} + +function changeFunctionMode() { + //console.log("changeFunctionMode()"); + + if (gpsState != GPS_RUNNING) { + switch(functionMode) { + case FN_MODE_OFF: + functionMode = FN_MODE_GPS; + break; + case FN_MODE_GPS: + default: + functionMode = FN_MODE_OFF; + break; + } + } else { + // if GPS is RUNNING then we want the GPS display options first + switch(functionMode) { + case FN_MODE_OFF: + functionMode = FN_MODE_GDISP; + break; + case FN_MODE_GDISP: + functionMode = FN_MODE_GPS; + break; + case FN_MODE_GPS: + default: + functionMode = FN_MODE_OFF; + break; + } + } + + infoMode = INFO_NONE; // function mode overrides info mode +} + +function resetLastFix() { + last_fix = { + fix: 0, + alt: 0, + lat: 0, + lon: 0, + speed: 0, + time: 0, + satellites: 0 + }; +} + +function processFix(fix) { + last_fix.time = fix.time; + + if (gpsState == GPS_TIME) + gpsState = GPS_SATS; + + if (fix.fix) { + if (!last_fix.fix) Bangle.buzz(); // buzz on first position + gpsState = GPS_RUNNING; + last_fix = fix; + } +} + +function getSteps() { + if (stepsWidget() !== undefined) + return stepsWidget().getSteps(); + return "-"; +} + +function stepsWidget() { + if (WIDGETS.activepedom !== undefined) { + return WIDGETS.activepedom; + } else if (WIDGETS.wpedom !== undefined) { + return WIDGETS.wpedom; + } + return undefined; +} + + +/************* GPS / OSREF Code **************************/ + +function formatTime(now) { + var fd = now.toUTCString().split(" "); + return fd[4]; +} + +function timeSince(t) { + var hms = t.split(":"); + var now = new Date(); + + var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds()); + var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]); + + return (sn - st); +} + +Number.prototype.toRad = function() { return this*Math.PI/180; }; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */ +/* - www.movable-type.co.uk/scripts/gridref.js */ +/* - www.movable-type.co.uk/scripts/latlon-gridref.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +function OsGridRef(easting, northing) { + this.easting = 0|easting; + this.northing = 0|northing; +} +OsGridRef.latLongToOsGrid = function(point) { + var lat = point.lat.toRad(); + var lon = point.lon.toRad(); + + var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes + var F0 = 0.9996012717; // NatGrid scale factor on central meridian + var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W + var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres + var e2 = 1 - (b*b)/(a*a); // eccentricity squared + var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; + + var cosLat = Math.cos(lat), sinLat = Math.sin(lat); + var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature + var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature + var eta2 = nu/rho-1; + + var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0); + var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0); + var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0)); + var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0)); + var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + var cos3lat = cosLat*cosLat*cosLat; + var cos5lat = cos3lat*cosLat*cosLat; + var tan2lat = Math.tan(lat)*Math.tan(lat); + var tan4lat = tan2lat*tan2lat; + + var I = M + N0; + var II = (nu/2)*sinLat*cosLat; + var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2); + var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat); + var IV = nu*cosLat; + var V = (nu/6)*cos3lat*(nu/rho-tan2lat); + var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2); + + var dLon = lon-lon0; + var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon; + + var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6; + var E = E0 + IV*dLon + V*dLon3 + VI*dLon5; + + return new OsGridRef(E, N); +}; + +/* + * converts northing, easting to standard OS grid reference. + * + * [digits=10] - precision (10 digits = metres) + * to_map_ref(8, 651409, 313177); => 'TG 5140 1317' + * to_map_ref(0, 651409, 313177); => '651409,313177' + * + */ +function to_map_ref(digits, easting, northing) { + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + + let e = easting; + let n = northing; + + // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 + if (digits == 0) { + const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 }; + const ePad = e.toLocaleString('en', format); + const nPad = n.toLocaleString('en', format); + return `${ePad},${nPad}`; + } + + // get the 100km-grid indices + const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000); + + // translate those into numeric equivalents of the grid letters + let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5); + let l2 = (19 - n100km) * 5 % 25 + e100km % 5; + + // compensate for skipped 'I' and build grid letter-pairs + if (l1 > 7) l1++; + if (l2 > 7) l2++; + const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0)); + + // strip 100km-grid indices from easting & northing, and reduce precision + e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2)); + n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2)); + + // pad eastings & northings with leading zeros + e = e.toString().padStart(digits/2, '0'); + n = n.toString().padStart(digits/2, '0'); + + return `${letterPair} ${e} ${n}`; +} + +// start a timer and buzz whenn held long enough +function firstPressed() { + firstPress = getTime(); + pressTimer = setInterval(longPressCheck, 1500); +} + +// if you release too soon there is no buzz as timer is cleared +function thenReleased() { + var dur = getTime() - firstPress; + if (pressTimer) { + clearInterval(pressTimer); + pressTimer = undefined; + } + if ( dur >= 1.5 ) Bangle.showLauncher(); +} + +// when you feel the buzzer you know you have done a long press +function longPressCheck() { + Bangle.buzz(); + if (pressTimer) { + clearInterval(pressTimer); + pressTimer = undefined; + } +} + +var pressTimer; + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +drawAll(); + +Bangle.on('lcdPower',function(on) { + functionMode = FN_MODE_OFF; + infoMode = INFO_NONE; + if (on) drawAll(); +}); + +var click = setInterval(onTick, 5000); + +setWatch(() => { changeInfoMode(); drawAll(); }, BTN1, {repeat: true}); +setWatch(() => { changeFunctionMode(); drawAll(); }, BTN3, {repeat: true}); + +// make BTN require a long press (1.5 seconds) to switch to launcher +setWatch(firstPressed, BTN2,{repeat:true,edge:"rising"}); +setWatch(thenReleased, BTN2,{repeat:true,edge:"falling"}); + diff --git a/apps/walkersclock/gps_alt.jpg b/apps/walkersclock/gps_alt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d407998b62ff4c399864b44d5fe501fe87810fbd GIT binary patch literal 48399 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 C3;CP? literal 0 HcmV?d00001 diff --git a/apps/walkersclock/gps_speed.jpg b/apps/walkersclock/gps_speed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c021b128e837cc19b54d4fe5b3c2b1edb82eb73 GIT binary patch literal 44988 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 literal 0 HcmV?d00001 diff --git a/apps/walkersclock/icon.js b/apps/walkersclock/icon.js new file mode 100644 index 000000000..7312a1308 --- /dev/null +++ b/apps/walkersclock/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgRC/AH4Ae/4AEBaPwAgcPBaIvvBZkL8ED6ALHl/4+XgBY89vnw+ALHngDB+gLK3AjKng7JBwJTJgEfO76/iAH4A/ADAA=")) diff --git a/apps/walkersclock/walkersclock48.png b/apps/walkersclock/walkersclock48.png new file mode 100644 index 0000000000000000000000000000000000000000..492af0c617dc848ce203e5bdf94a2b7973092d5b GIT binary patch literal 1861 zcmV-L2fFx)P)W4^0B9;0jN}EcJ`XQ>+iWU(GlUCTo*u=r8tq2fIAqGEqIbdUB z*Xy;7y=(8xoPO}G?>+bY&wpp{IcI0!0q!qUq8*l(@5-1Y^rTWG zYr5m~v19LaC7NR<5-OmyqVh+GdILxa519yW`|f1^yz>z3NPwlMyI->Ri zZeS9)3nZi1lAIb?a{bEX#zb+=gqA2rVyagGC8!fl)06YW7m70x`E7W5Pf1Dj(nN91 zM8X6B8vrzAzFA*?U}WFEeWp{AUJK88a2e|o$Tbbydtk?oZ>NW}V0Lsz>KXtdyF_t1 z*7!__!#&n?xz zPM@TAaA4M$D8B!GUCp2D2w2!FAgDaGK$`WYq}lOn8!l~rCfA_A?s{8UX!lDm#1Ycs zu)XBV-RSK`H}rY7Vk+AmtYsZ2t*BfrLc!cRuxdpC1XW!G7|x9-_E#wIxx{U_Y&0S4j4Fc=EHG85kMj!u1YJ(+o#4RR=_q;ilSm&n84b z1SJ(!Ta|FauG4tnaykj=I!f_cV_nU@c-O~lr%TJKH!AoQ@F>8}9VO^fgc#u><#xMB zOHCyy$rJU^G>wq1qiKqz>FMNTXR>MI=kWVS2n2%wd8^j0yLGLztvwzAmUp0}tn!;m zslNiQ$k?l|ygWUBw4sTMt?fKkyq5Cq+boGLwzhM$p^2qS)7bsPPwUE?Z~c+r#Kg>i ze%E-k=4W;VMBT(+tEkRXO8psdd6GQS<11I@;q`h+NlC%!oGRo_r<0VF6wAZonew5O zA}J|p9$%!@5cp|ndG#}P1w?HnoF=($Mg3uWe^`1Vm6; zUUe4OlHpC~>C&%|os}66&om6fpu4+=Kp@D)OC8)9@BxfDO;uE1e`p}q`niZHVDsje zT$#&#zX16zs5Ox_nol-J^DXV7;ugww5fq}WYTl15s#1BV8|fN-X93$h^@F*ibgWMt~TPv-f`*xh0pzmgg@R0*)CiwLbYCB~ zBpMo+DpNM zL(p|;_VcpI2_bMd9Jj~~`{CP3qve&q2el_2r;CN4T88>Ez7$UywY&oWzP_xVXXW~C zi|`yUtNbb?M^I@B85NXe6;+TXkOQKFx;bY$fbb99;oKYV`{aAdGNt8}-x1UwG{uh_ z>uL^I*>2lWydx&oNB>uFN~00000NkvXXu0mjf3E`z) literal 0 HcmV?d00001 From eafdb5c776671dcd2bc9b2a1aa390c6489d13bdc Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 11 Feb 2021 20:34:51 +0000 Subject: [PATCH 156/181] added walkers clock to apps.json --- apps.json | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 89428e841..b05265cd2 100644 --- a/apps.json +++ b/apps.json @@ -2139,7 +2139,7 @@ { "id": "multiclock", "name": "Multi Clock", "icon": "multiclock.png", - "version":"0.11", + "version":"0.12", "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", "readme": "README.md", "tags": "clock", @@ -2153,8 +2153,6 @@ {"name":"txt.face.js","url":"txt.js"}, {"name":"timdat.face.js","url":"timdat.js"}, {"name":"ped.face.js","url":"ped.js"}, - {"name":"gps.face.js","url":"gps.js"}, - {"name":"osref.face.js","url":"osref.js"}, {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} ] }, @@ -2782,5 +2780,18 @@ {"name":"gpssetup.app.js","url":"app.js"}, {"name":"gpssetup.img","url":"icon.js","evaluate":true} ] +}, +{ "id": "walkersclock", + "name": "Walkers Clock", + "shortName":"Walkers Clock", + "icon": "walkersclock48.png", + "version":"0.01", + "description": "A larg font watch, displays steps, can switch GPS on/off, displays grid reference", + "tags": "gps, tools, outdoors", + "readme": "README.md", + "storage": [ + {"name":"walkersclock.app.js","url":"app.js"}, + {"name":"walkersclock.img","url":"icon.js","evaluate":true} + ] } ] From 4fdba2ac9058aea229428f324b5fe4fca1b1c614 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 11 Feb 2021 20:36:52 +0000 Subject: [PATCH 157/181] removed osref.js and gps.js from multiclock, superceded by GPSsetup and Walkers Clock --- apps/multiclock/ChangeLog | 1 + apps/multiclock/apps_entry.json | 2 - apps/multiclock/gps.js | 95 --------------- apps/multiclock/osref.js | 202 -------------------------------- 4 files changed, 1 insertion(+), 299 deletions(-) delete mode 100644 apps/multiclock/gps.js delete mode 100644 apps/multiclock/osref.js diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index 3ed2ed6b4..c02e390b2 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -9,3 +9,4 @@ 0.09: Added Pedometer clock 0.10: Added GPS and Grid Ref clock faces 0.11: Updated Pedometer clock to retrieve steps from either wpedom or activepedom +0.12: Removed GPS and Grid Ref clock faces, superceded by GPS setup and Walkers Clock diff --git a/apps/multiclock/apps_entry.json b/apps/multiclock/apps_entry.json index 19b6bc43d..6383609c1 100644 --- a/apps/multiclock/apps_entry.json +++ b/apps/multiclock/apps_entry.json @@ -14,8 +14,6 @@ {"name":"digi.face.js","url":"digi.min.js"}, {"name":"txt.face.js","url":"txt.min.js"}, {"name":"ped.face.js","url":"ped.js"}, - {"name":"osref.face.js","url":"osref.js"}, - {"name":"gps.face.js","url":"pgs.js"}, {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} ] }, diff --git a/apps/multiclock/gps.js b/apps/multiclock/gps.js deleted file mode 100644 index 3e756fc5e..000000000 --- a/apps/multiclock/gps.js +++ /dev/null @@ -1,95 +0,0 @@ -(() => { - - function getFace(){ - - //var img = require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA=")); - var nofix = 0; - - function formatTime(now) { - var fd = now.toUTCString().split(" "); - return fd[4]; - } - - - function timeSince(t) { - var hms = t.split(":"); - var now = new Date(); - - var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds()); - var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]); - - return (sn - st); - } - - function draw() { - var gps_on = false; - - var fix = { - fix: 0, - alt: 0, - lat: 0, - lon: 0, - speed: 0, - time: 0, - satellites: 0 - }; - - var y_line = 26; - var y_start = 46; - var x_start = 10; - - // only attempt to get gps fix if gpsservuce is loaded - if (WIDGETS.gpsservice !== undefined) { - fix = WIDGETS.gpsservice.gps_get_fix(); - gps_on = WIDGETS.gpsservice.gps_get_status(); - } - - g.reset(); - g.clearRect(0,24,239,239); - - if (fix.fix) { - var time = formatTime(fix.time); - var age = timeSince(time); - - g.setFontAlign(-1, -1); - g.setFont("6x8"); - g.setFontVector(22); - g.drawString("Alt: " + fix.alt +" m", x_start, y_start, true); - g.drawString("Lat: "+ fix.lat, x_start, y_start + y_line, true); - g.drawString("Lon: " + fix.lon, x_start, y_start + 2*y_line, true); - g.drawString("Time: " + time, x_start, y_start + 3*y_line, true); - g.drawString("Age(s): " + age, x_start, y_start + 4*y_line, true); - g.drawString("Satellites: " + fix.satellites, x_start, y_start + 5*y_line, true); - - } else if (gps_on) { - - g.setFontAlign(0, 1); - g.setFont("6x8", 2); - g.drawString("GPS Watch", 120, 60); - g.drawString("Waiting for GPS", 120, 80); - nofix = (nofix+1) % 4; - g.drawString(".".repeat(nofix) + " ".repeat(4-nofix), 120, 120); - g.setFontAlign(0,0); - g.drawString(fix.satellites + " satellites", 120, 100); - - } else if (!gps_on) { - - g.setFontAlign(0, 0); - g.setFont("6x8", 3); - g.drawString("GPS Watch", 120, 80); - g.drawString("GPS is off",120, 160); - - } - } - - function onSecond(){ - var t = new Date(); - if ((t.getSeconds() % 5) === 0) draw(); - } - - return {init:draw, tick:onSecond}; - } - - return getFace; - -})(); diff --git a/apps/multiclock/osref.js b/apps/multiclock/osref.js deleted file mode 100644 index c98b73134..000000000 --- a/apps/multiclock/osref.js +++ /dev/null @@ -1,202 +0,0 @@ -(() => { - - function getFace(){ - var nofix = 0; - - function formatTime(now) { - var fd = now.toUTCString().split(" "); - return fd[4]; - } - - function timeSince(t) { - var hms = t.split(":"); - var now = new Date(); - - var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds()); - var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]); - - return (sn - st); - } - - - Number.prototype.toRad = function() { return this*Math.PI/180; }; - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - /* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */ - /* - www.movable-type.co.uk/scripts/gridref.js */ - /* - www.movable-type.co.uk/scripts/latlon-gridref.html */ - /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - function OsGridRef(easting, northing) { - this.easting = 0|easting; - this.northing = 0|northing; - } - OsGridRef.latLongToOsGrid = function(point) { - var lat = point.lat.toRad(); - var lon = point.lon.toRad(); - - var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes - var F0 = 0.9996012717; // NatGrid scale factor on central meridian - var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W - var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres - var e2 = 1 - (b*b)/(a*a); // eccentricity squared - var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; - - var cosLat = Math.cos(lat), sinLat = Math.sin(lat); - var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature - var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature - var eta2 = nu/rho-1; - - var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0); - var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0); - var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0)); - var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0)); - var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc - - var cos3lat = cosLat*cosLat*cosLat; - var cos5lat = cos3lat*cosLat*cosLat; - var tan2lat = Math.tan(lat)*Math.tan(lat); - var tan4lat = tan2lat*tan2lat; - - var I = M + N0; - var II = (nu/2)*sinLat*cosLat; - var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2); - var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat); - var IV = nu*cosLat; - var V = (nu/6)*cos3lat*(nu/rho-tan2lat); - var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2); - - var dLon = lon-lon0; - var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon; - - var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6; - var E = E0 + IV*dLon + V*dLon3 + VI*dLon5; - - return new OsGridRef(E, N); - }; - - - /* - * converts northing, easting to standard OS grid reference. - * - * [digits=10] - precision (10 digits = metres) - * to_map_ref(8, 651409, 313177); => 'TG 5140 1317' - * to_map_ref(0, 651409, 313177); => '651409,313177' - * - */ - function to_map_ref(digits, easting, northing) { - if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing - - let e = easting; - let n = northing; - - // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 - if (digits == 0) { - const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 }; - const ePad = e.toLocaleString('en', format); - const nPad = n.toLocaleString('en', format); - return `${ePad},${nPad}`; - } - - // get the 100km-grid indices - const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000); - - // translate those into numeric equivalents of the grid letters - let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5); - let l2 = (19 - n100km) * 5 % 25 + e100km % 5; - - // compensate for skipped 'I' and build grid letter-pairs - if (l1 > 7) l1++; - if (l2 > 7) l2++; - const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0)); - - // strip 100km-grid indices from easting & northing, and reduce precision - e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2)); - n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2)); - - // pad eastings & northings with leading zeros - e = e.toString().padStart(digits/2, '0'); - n = n.toString().padStart(digits/2, '0'); - - return `${letterPair} ${e} ${n}`; - } - - function draw() { - var gps_on = false; - - var fix = { - fix: 0, - alt: 0, - lat: 0, - lon: 0, - speed: 0, - time: 0, - satellites: 0 - }; - - var y_line = 26; - var y_start = 46; - var x_start = 10; - - // only attempt to get gps fix if gpsservuce is loaded - if (WIDGETS.gpsservice !== undefined) { - fix = WIDGETS.gpsservice.gps_get_fix(); - gps_on = WIDGETS.gpsservice.gps_get_status(); - } - - g.reset(); - g.clearRect(0,24,239,239); - - if (fix.fix) { - var time = formatTime(fix.time); - var age = timeSince(time); - var os = OsGridRef.latLongToOsGrid(fix); - var ref = to_map_ref(6, os.easting, os.northing); - age = age >=0 ? age : 0; // avoid -1 etc - - g.reset(); - g.clearRect(0,24,239,239); - g.setFontAlign(0,0); - g.setFont("Vector"); - g.setColor(1,1,1); - g.setFontVector(40); - g.drawString(time, 120, 80); - - g.setFontVector(40); - g.setColor(0xFFC0); - g.drawString(ref, 120,140, true); - - g.setFont("6x8",2); - g.setColor(1,1,1); - g.drawString(age, 120, 194); - - } else if (gps_on) { - - g.setFontAlign(0, 1); - g.setFont("6x8", 2); - g.drawString("Gridref Watch", 120, 60); - g.drawString("Waiting for GPS", 120, 80); - nofix = (nofix+1) % 4; - g.drawString(".".repeat(nofix) + " ".repeat(4-nofix), 120, 120); - g.setFontAlign(0,0); - g.drawString(fix.satellites + " satellites", 120, 100); - - } else if (!gps_on) { - - g.setFontAlign(0, 0); - g.setFont("6x8", 3); - g.drawString("Gridref Watch", 120, 80); - g.drawString("GPS is off", 120, 160); - - } - } - - function onSecond(){ - var t = new Date(); - if ((t.getSeconds() % 5) === 0) draw(); - } - - return {init:draw, tick:onSecond}; - } - - return getFace; - -})(); From cd2b95f3ba74f7f47e7966c0b59abbaaca70a90f Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 11 Feb 2021 21:06:29 +0000 Subject: [PATCH 158/181] fixed icon for walkerswatch --- apps/walkersclock/icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/walkersclock/icon.js b/apps/walkersclock/icon.js index 7312a1308..5a48c6cc9 100644 --- a/apps/walkersclock/icon.js +++ b/apps/walkersclock/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwgRC/AH4Ae/4AEBaPwAgcPBaIvvBZkL8ED6ALHl/4+XgBY89vnw+ALHngDB+gLK3AjKng7JBwJTJgEfO76/iAH4A/ADAA=")) +require("heatshrink").decompress(atob("mEwxH+AH4A/AEcsAAYuuGFIqEF9gFJF/62SACAvvGCgYEwIASqwxVF4mmACQvEGCJcCF65iDF/4vbqwvVd4YeBJgIECAAulq4KDmS/XAAVXJwgvHOAIKCOoQuTGQ1WvYvCvYAGBQdWLwYtUMQwAQGAQuYXgJPCmQEBAAwKDCQKNZlmsAAKECAoQAEX4QQESKh4EDwVXegIvPSKgbBDwMs64ABAoOBAoQAE1gvGTAQwQCINXF4olBFw4KCAAYvCDIQvZABgv/F/4vqCoYvpGAMyF7ICBF6QADEIeBABQuCF4QACFyAwFDwRlCABNWHoUyFyoxEwOIxGBPoIAJMYYvCFyrzEGAIALFwS7TYRYACmQAGSxAtcACaNWwGArizBAwIAININWrgTCF6+GAAYvCA4gACwAKGCQIv/F+qqBAAQvCA4gACXAIKFF67cBAAQvCAwYKFmQGFF6wAZF6YxaFyo1HIBYpcAH4A/AAoA==")); From 7dde9a4d23dd1e1b65e67b04b8ff108b135985ee Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 11 Feb 2021 21:24:59 +0000 Subject: [PATCH 159/181] added type=clock for walkers clock in apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index b05265cd2..d5dcc9b3e 100644 --- a/apps.json +++ b/apps.json @@ -2787,7 +2787,8 @@ "icon": "walkersclock48.png", "version":"0.01", "description": "A larg font watch, displays steps, can switch GPS on/off, displays grid reference", - "tags": "gps, tools, outdoors", + "type":"clock", + "tags": "clock, gps, tools, outdoors", "readme": "README.md", "storage": [ {"name":"walkersclock.app.js","url":"app.js"}, From c3a45287d07dd4994aad6eb7c9aed0324a286065 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 13:53:08 +1300 Subject: [PATCH 160/181] Use new GPS settings module --- apps/speedalt/app.js | 72 +++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index f87290155..1b2c6cd65 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -1,8 +1,9 @@ /* Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com +1.16 : Use new GPS settings module */ -var v = '1.15'; +var v = '1.16'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -269,22 +270,18 @@ function setButtons(){ // Power saving on/off setWatch(function(e){ - var dur = e.time - e.lastTime; - if ( dur < 2 ) { // Short press. - 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(); - } + 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(); } - else gpsOff(); // long press, power off LP GPS }, BTN2, {repeat:true,edge:"falling"}); // Toggle between alt or dist @@ -328,31 +325,10 @@ function savSettings() { require("Storage").write('speedalt.json',settings); } -// Is low power GPS service available to use? -function isLP() { - if (WIDGETS.gpsservice == undefined) return(0); - return(1); -} - function setLpMode(m) { if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power - if ( !lp ) return; - var s = WIDGETS.gpsservice.gps_get_settings(); - if ( m !== s.power_mode || !s.gpsservice ) { - s.gpsservice = true; - s.power_mode = m; - WIDGETS.gpsservice.gps_set_settings(s); - WIDGETS.gpsservice.reload(); - } -} - -function gpsOff() { - if ( !lp ) return; - var s = WIDGETS.gpsservice.gps_get_settings(); - s.gpsservice = false; - s.power_mode = 'SuperE'; - WIDGETS.gpsservice.gps_set_settings(s); - WIDGETS.gpsservice.reload(); + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}) } // =Main Prog @@ -404,21 +380,29 @@ Bangle.on('lcdPower',function(on) { else stopDraw(); }); +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); -var lp = isLP(); // Low power GPS widget installed? -if ( lp ) { - setLpMode('SuperE'); - setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); +if ( gpssetup ) { + gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); +// setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); } else { Bangle.setGPSPower(1); - Bangle.on('GPS', onGPS); } +Bangle.on('GPS', onGPS); + setButtons(); setInterval(updateClock, 30000); From 8fcebaa7f880eba4016c2114e13d3c88656ee9a7 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 14:09:11 +1300 Subject: [PATCH 161/181] Rename var settings to cfg --- apps/speedalt/app.js | 80 ++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 1b2c6cd65..ae840fd47 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com 1.16 : Use new GPS settings module */ -var v = '1.16'; +var v = '1.17'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -25,17 +25,17 @@ var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulato var wp = {}; // Waypoint to use for distance from cur position. function nxtWp(inc){ - if (settings.modeA) return; - settings.wp+=inc; + if (cfg.modeA) return; + cfg.wp+=inc; loadWp(); } function loadWp() { var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; - if (settings.wp>=w.length) settings.wp=0; - if (settings.wp<0) settings.wp = w.length-1; + if (cfg.wp>=w.length) cfg.wp=0; + if (cfg.wp<0) cfg.wp = w.length-1; savSettings(); - wp = w[settings.wp]; + wp = w[cfg.wp]; } function radians(a) { @@ -48,7 +48,7 @@ function distance(a,b){ // Distance in selected units var d = Math.sqrt(x*x + y*y) * 6371000; - d = (d/parseFloat(settings.dist)).toFixed(2); + d = (d/parseFloat(cfg.dist)).toFixed(2); if ( d >= 100 ) d = parseFloat(d).toFixed(1); if ( d >= 1000 ) d = parseFloat(d).toFixed(0); @@ -64,18 +64,18 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { var u=''; // Primary Display - v = (settings.primSpd)?speed.toString():alt.toString(); + v = (cfg.primSpd)?speed.toString():alt.toString(); // Primary Units - u = (settings.primSpd)?settings.spd_unit:alt_units; + u = (cfg.primSpd)?cfg.spd_unit:alt_units; drawPrimary(v,u); // Secondary Display - v = (settings.primSpd)?alt.toString():speed.toString(); + v = (cfg.primSpd)?alt.toString():speed.toString(); // Secondary Units - u = (settings.primSpd)?alt_units:settings.spd_unit; + u = (cfg.primSpd)?alt_units:cfg.spd_unit; drawSecondary(v,u); @@ -160,7 +160,7 @@ function drawTime() { function drawWP() { var nm = wp.name; - if ( nm == undefined || nm == 'NONE' || settings.modeA ) nm = ''; + if ( nm == undefined || nm == 'NONE' || cfg.modeA ) nm = ''; buf.setFontAlign(-1,1); //left, bottom buf.setColor(2); @@ -179,10 +179,10 @@ function drawSats(sats) { buf.setFontVector(20); buf.setColor(2); - if ( settings.modeA ) buf.drawString("A",240,140); + if ( cfg.modeA ) buf.drawString("A",240,140); else buf.drawString("D",240,140); - if ( showMax && settings.modeA ) { + if ( showMax && cfg.modeA ) { buf.setFontAlign(0,1); //centre, bottom buf.drawString("MAX",120,164); } @@ -214,12 +214,12 @@ function onGPS(fix) { lf = fix; // Speed - if ( settings.spd == 0 ) { + if ( cfg.spd == 0 ) { m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units sp = parseFloat(m[1]); - settings.spd_unit = m[2]; + cfg.spd_unit = m[2]; } - else sp = parseFloat(lf.speed)/parseFloat(settings.spd); // Calculate for selected units + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units if ( sp < 10 ) sp = sp.toFixed(1); else sp = Math.round(sp); @@ -227,7 +227,7 @@ function onGPS(fix) { // Altitude al = lf.alt; - al = Math.round(parseFloat(al)/parseFloat(settings.alt)); + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); if (parseFloat(al) > parseFloat(max.alt) ) max.alt = parseFloat(al); // Distance to waypoint @@ -238,14 +238,14 @@ function onGPS(fix) { age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); } - if ( settings.modeA ) { - if ( showMax ) drawFix(max.spd,settings.spd_unit,lf.satellites,max.alt,settings.alt_unit,age,lf.fix); // Speed and alt maximums - else drawFix(sp,settings.spd_unit,lf.satellites,al,settings.alt_unit,age,lf.fix); // Show speed/altitude + if ( cfg.modeA ) { + if ( showMax ) drawFix(max.spd,cfg.spd_unit,lf.satellites,max.alt,cfg.alt_unit,age,lf.fix); // Speed and alt maximums + else drawFix(sp,cfg.spd_unit,lf.satellites,al,cfg.alt_unit,age,lf.fix); // Show speed/altitude } else { // Show speed/distance - if ( di <= 0 ) drawFix(sp,settings.spd_unit,lf.satellites,'','',age,lf.fix); // No WP selected - else drawFix(sp,settings.spd_unit,lf.satellites,di,settings.dist_unit,age,lf.fix); + if ( di <= 0 ) drawFix(sp,cfg.spd_unit,lf.satellites,'','',age,lf.fix); // No WP selected + else drawFix(sp,cfg.spd_unit,lf.satellites,di,cfg.dist_unit,age,lf.fix); } } @@ -255,7 +255,7 @@ function setButtons(){ // Spd+Dist : Select next waypoint setWatch(function(e) { var dur = e.time - e.lastTime; - if ( settings.modeA ) { + if ( cfg.modeA ) { // 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. @@ -286,14 +286,14 @@ function setButtons(){ // Toggle between alt or dist setWatch(function(e){ - settings.modeA = !settings.modeA; + cfg.modeA = !cfg.modeA; savSettings(); onGPS(lf); }, BTN3, {repeat:true,edge:"falling"}); // Touch left screen to toggle display setWatch(function(e){ - settings.primSpd = !settings.primSpd; + cfg.primSpd = !cfg.primSpd; savSettings(); onGPS(lf); // Update display }, BTN4, {repeat:true,edge:"falling"}); @@ -322,7 +322,7 @@ function stopDraw() { } function savSettings() { - require("Storage").write('speedalt.json',settings); + require("Storage").write('speedalt.json',cfg); } function setLpMode(m) { @@ -334,18 +334,18 @@ function setLpMode(m) { // =Main Prog // Read settings. -let settings = require('Storage').readJSON('speedalt.json',1)||{}; +let cfg = require('Storage').readJSON('speedalt.json',1)||{}; -settings.spd = settings.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed -settings.spd_unit = settings.spd_unit||''; // Displayed speed unit -settings.alt = settings.alt||0.3048;// Multiplier for altitude unit conversions. -settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units -settings.dist = settings.dist||1000;// Multiplier for distnce unit conversions. -settings.dist_unit = settings.dist_unit||'km'; // Displayed altitude units -settings.colour = settings.colour||0; // Colour scheme. -settings.wp = settings.wp||0; // Last selected waypoint for dist -settings.modeA = settings.modeA||0; // 0 = [D], 1 = [A] -settings.primSpd = settings.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary +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 = [D], 1 = [A] +cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary loadWp(); @@ -365,8 +365,8 @@ var img = { palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) }; -if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); -if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); +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, From 8e4cc30b3f59a607342201171181babd2705ee64 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 14:41:37 +1300 Subject: [PATCH 162/181] Display only actual fixes. If not a fix, then last fix --- apps/speedalt/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index ae840fd47..1b25b1b7d 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com 1.16 : Use new GPS settings module */ -var v = '1.17'; +var v = '1.18'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -210,8 +210,8 @@ function onGPS(fix) { var di = '---'; var age = '---'; - if (fix.fix) { - lf = fix; + if (fix.fix) lf = fix; + if (lf.fix) { // Speed if ( cfg.spd == 0 ) { From 925fff2181cbbc487990685ba3cf05246d8933aa Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 14:59:15 +1300 Subject: [PATCH 163/181] Exit settings directly into app --- apps/speedalt/app.js | 2 +- apps/speedalt/settings.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 1b25b1b7d..7d599c15f 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com 1.16 : Use new GPS settings module */ -var v = '1.18'; +var v = '1.19'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 5f7418dbb..b04395fb9 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -45,6 +45,7 @@ const unitsMenu = { '': {'title': 'Units'}, '< Back': function() { E.showMenu(appMenu); }, + '< Load GPS Adv': ()=>{load('speedalt.app.js');}, 'default (spd)' : function() { setUnits(0,''); }, 'Kph (spd)' : function() { setUnits(1,'kph'); }, 'Knots (spd)' : function() { setUnits(1.852,'knots'); }, From 3585c468cf4e0d501d1ee92cf4423c6e0ed84b50 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 15:14:19 +1300 Subject: [PATCH 164/181] Tweak settings --- apps.json | 2 +- apps/speedalt/settings.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index c85bf25d4..1c14740b3 100644 --- a/apps.json +++ b/apps.json @@ -2623,7 +2623,7 @@ }, { "id": "speedalt", "name": "GPS Adventure Sports", - "shortName":"GPS Adv", + "shortName":"GPS Adv Sport", "icon": "app.png", "version":"1.00", "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.", diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index b04395fb9..aeec76c98 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -33,6 +33,7 @@ const appMenu = { '': {'title': 'GPS Speed Alt'}, '< Back': back, + '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }/*, 'Vibrate' : { @@ -45,7 +46,6 @@ const unitsMenu = { '': {'title': 'Units'}, '< Back': function() { E.showMenu(appMenu); }, - '< Load GPS Adv': ()=>{load('speedalt.app.js');}, 'default (spd)' : function() { setUnits(0,''); }, 'Kph (spd)' : function() { setUnits(1,'kph'); }, 'Knots (spd)' : function() { setUnits(1.852,'knots'); }, From c1b076f0c68221640bec1a4afdf1109b6bcbe708 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 16:04:11 +1300 Subject: [PATCH 165/181] Changed multiple function arguments into a single object arg. --- apps/speedalt/app.js | 65 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 7d599c15f..064551182 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt] Mike Bennett mike[at]kereru.com 1.16 : Use new GPS settings module */ -var v = '1.19'; +var v = '1.20'; var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); // Load fonts @@ -55,7 +55,9 @@ function distance(a,b){ return d; } -function drawFix(speed,units,sats,alt,alt_units,age,fix) { +//function drawFix(speed,sats,alt,alt_units,age,fix) { +function drawFix(dat) { + if (!canDraw) return; buf.clear(); @@ -64,18 +66,18 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { var u=''; // Primary Display - v = (cfg.primSpd)?speed.toString():alt.toString(); + v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString(); // Primary Units - u = (cfg.primSpd)?cfg.spd_unit:alt_units; + u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units; drawPrimary(v,u); // Secondary Display - v = (cfg.primSpd)?alt.toString():speed.toString(); + v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString(); // Secondary Units - u = (cfg.primSpd)?alt_units:cfg.spd_unit; + u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit; drawSecondary(v,u); @@ -86,11 +88,11 @@ function drawFix(speed,units,sats,alt,alt_units,age,fix) { drawWP(); //Sats - if ( age > 10 ) { - if ( age > 90 ) age = '>90'; - drawSats('Age:'+age); + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); } - else drawSats('Sats:'+sats); + else drawSats('Sats:'+dat.sats); g.reset(); g.drawImage(img,0,40); @@ -238,14 +240,48 @@ function onGPS(fix) { age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); } + + // {speed:,sats:,alt:,alt_units:,age:,fix:} if ( cfg.modeA ) { - if ( showMax ) drawFix(max.spd,cfg.spd_unit,lf.satellites,max.alt,cfg.alt_unit,age,lf.fix); // Speed and alt maximums - else drawFix(sp,cfg.spd_unit,lf.satellites,al,cfg.alt_unit,age,lf.fix); // Show speed/altitude + if ( showMax ) + drawFix({ + speed:max.spd, + sats:lf.satellites, + alt:max.alt, + alt_units:cfg.alt_unit, + age:age, + fix:lf.fix + }); // Speed and alt maximums + else + drawFix({ + speed:sp, + sats:lf.satellites, + alt:al, + alt_units:cfg.alt_unit, + age:age, + fix:lf.fix + }); // Show speed/altitude } else { // Show speed/distance - if ( di <= 0 ) drawFix(sp,cfg.spd_unit,lf.satellites,'','',age,lf.fix); // No WP selected - else drawFix(sp,cfg.spd_unit,lf.satellites,di,cfg.dist_unit,age,lf.fix); + if ( di <= 0 ) + drawFix({ + speed:sp, + sats:lf.satellites, + alt:'', + alt_units:'', + age:age, + fix:lf.fix + }); // No WP selected + else + drawFix({ + speed:sp, + sats:lf.satellites, + alt:di, + alt_units:cfg.dist_unit, + age:age, + fix:lf.fix + }); } } @@ -396,7 +432,6 @@ Bangle.setGPSPower(1); if ( gpssetup ) { gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); -// setInterval(()=>onGPS(WIDGETS.gpsservice.gps_get_fix()), 1000); } else { Bangle.setGPSPower(1); From bce5b9f6d493e7f2b8bdb827f69a3a2b57a49be4 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 16:25:33 +1300 Subject: [PATCH 166/181] Update README.md --- apps/speedalt/README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 538575ae9..4dd564339 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -22,9 +22,7 @@ BTN1 : Select next waypoint. Last fix distance from selected waypoint is display ### Both modes -BTN2 : Short press < 2 secs Disables/Restores power saving timeout. Locks the screen on to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Tap again to restore power saving timeouts. - -BTN2 : Long press > 2 secs turns off the low power GPS service and GPS. Exit and restart the app to turn back on. If you are using the low power gps service it will keep the GPS powered on after exiting the app. This is a convenient way to turn it off. +BTN2 : Disables/Restores power saving timeout. Locks the screen on 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 : Long press exit and return to watch. @@ -51,18 +49,18 @@ MAX Values instead:
Settings:
![](screen4.png)

-## Low Power GPS Service +## Power Saving -This app will work quite happily without this service but will use the [Low power GPS Service](https://banglejs.com/apps/#low%20power%20gps%20service) if it is installed. You may choose to use the Low Power GPS Service to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Service Readme to understand what this does. +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. -When using the Low Power GPS Service 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 PMOO mode and will only attempt to get a fix every minute or two. 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. +This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps setup) 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. -There are a couple of things to consider when using the Low Power GPS Service. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. +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. + +There are a couple of things to consider when using the GPS Setup App. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. 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. -When exiting this app the Low Power GPS Service will keep the GPS powered on, using battery. It can be manually turned off using the gps service settings menu. As a convenience, long press BTN2 for 2 seconds will turn it off while using this GPS Adv app. - ## 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. @@ -142,6 +140,6 @@ Developed for my use in sailing, cycling and motorcycling. If you find this soft 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 Service](https://banglejs.com/apps/#low%20power%20gps%20service) work. +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. From 2b08a4b0c3f1a98e05dc4520d820aade8ece7351 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 16:27:45 +1300 Subject: [PATCH 167/181] Update README.md --- apps/speedalt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 4dd564339..3eaefa283 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -53,7 +53,7 @@ Settings:
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 setup) 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. +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. From 6a18185ef80d24cadd97bc012c66d3970cf1babc Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 12 Feb 2021 16:29:39 +1300 Subject: [PATCH 168/181] Update README.md --- apps/speedalt/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md index 3eaefa283..50ffce70c 100644 --- a/apps/speedalt/README.md +++ b/apps/speedalt/README.md @@ -57,8 +57,6 @@ This app will work quite happily on its own but will use the [GPS Setup App](htt 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. -There are a couple of things to consider when using the GPS Setup App. This app plus the LP GPS service together use a considerable chunk of the Bangle.JS memory. A large waypoints file will also contribute to this. - 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 From c81091e8b846c9e2571652bf4e3b5cff336a692f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 12 Feb 2021 09:14:52 +0000 Subject: [PATCH 169/181] fix walkersclock icon --- apps/walkersclock/icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/walkersclock/icon.js b/apps/walkersclock/icon.js index 5a48c6cc9..cc1b8b8da 100644 --- a/apps/walkersclock/icon.js +++ b/apps/walkersclock/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AEcsAAYuuGFIqEF9gFJF/62SACAvvGCgYEwIASqwxVF4mmACQvEGCJcCF65iDF/4vbqwvVd4YeBJgIECAAulq4KDmS/XAAVXJwgvHOAIKCOoQuTGQ1WvYvCvYAGBQdWLwYtUMQwAQGAQuYXgJPCmQEBAAwKDCQKNZlmsAAKECAoQAEX4QQESKh4EDwVXegIvPSKgbBDwMs64ABAoOBAoQAE1gvGTAQwQCINXF4olBFw4KCAAYvCDIQvZABgv/F/4vqCoYvpGAMyF7ICBF6QADEIeBABQuCF4QACFyAwFDwRlCABNWHoUyFyoxEwOIxGBPoIAJMYYvCFyrzEGAIALFwS7TYRYACmQAGSxAtcACaNWwGArizBAwIAININWrgTCF6+GAAYvCA4gACwAKGCQIv/F+qqBAAQvCA4gACXAIKFF67cBAAQvCAwYKFmQGFF6wAZF6YxaFyo1HIBYpcAH4A/AAoA==")); +require("heatshrink").decompress(atob("mEwxH+AH4A/AEcsAAYuuGFIqEF9gFJF/62SACAvvGCgYEwIASqwxVF4mmACQvEGCJcCF65iDF/4vbqwvVd4YeBJgIECAAulq4KDmS/XAAVXJwgvHOAIKCOoQuTGQ1WvYvCvYAGBQdWLwYtUMQwAQGAQuYXgJPCmQEBAAwKDCQKNZlmsAAKECAoQAEX4QQESKh4EDwVXegIvPSKgbBDwMs64ABAoOBAoQAE1gvGTAQwQCINXF4olBFw4KCAAYvCDIQvZABgv/F/4vqCoYvpGAMyF7ICBF6QADEIeBABQuCF4QACFyAwFDwRlCABNWHoUyFyoxEwOIxGBPoIAJMYYvCFyrzEGAIALFwS7TYRYACmQAGSxAtcACaNWwGArizBAwIAININWrgTCF6+GAAYvCA4gACwAKGCQIv/F+qqBAAQvCA4gACXAIKFF67cBAAQvCAwYKFmQGFF6wAZF6YxaFyo1HIBYpcAH4A/AAoA==")) From a766807607fdcb0750b59ad7578ef8c2b4791b93 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 13 Feb 2021 08:50:57 +1300 Subject: [PATCH 170/181] Final check and tidy --- apps/speedalt/app.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js index 064551182..9e95737c4 100644 --- a/apps/speedalt/app.js +++ b/apps/speedalt/app.js @@ -55,7 +55,6 @@ function distance(a,b){ return d; } -//function drawFix(speed,sats,alt,alt_units,age,fix) { function drawFix(dat) { if (!canDraw) return; @@ -240,8 +239,6 @@ function onGPS(fix) { age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); } - - // {speed:,sats:,alt:,alt_units:,age:,fix:} if ( cfg.modeA ) { if ( showMax ) drawFix({ @@ -300,10 +297,6 @@ function setButtons(){ onGPS(lf); }, BTN1, { edge:"falling",repeat:true}); - - // Show launcher when middle button pressed - // setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); - // Power saving on/off setWatch(function(e){ pwrSav=!pwrSav; @@ -418,7 +411,7 @@ Bangle.on('lcdPower',function(on) { var gpssetup; try { - gpssetup = require("gpssetup") + gpssetup = require("gpssetup"); } catch(e) { gpssetup = false; } From 56500622ee7e1cd37af70223bbb7a3be17d1108d Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 13 Feb 2021 09:07:07 +1300 Subject: [PATCH 171/181] Delete waypoints.json --- apps/speedalt/waypoints.json | 66 ------------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 apps/speedalt/waypoints.json diff --git a/apps/speedalt/waypoints.json b/apps/speedalt/waypoints.json deleted file mode 100644 index b117bc5c2..000000000 --- a/apps/speedalt/waypoints.json +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "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":"MotuTa", - "lat":-38.85454, - "lon":175.94199 - }, - { - "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 - } - -] From 5a98eda2d090d6060b94091a0cb1afd15367154e Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 13 Feb 2021 09:17:55 +1300 Subject: [PATCH 172/181] Delete _config.yml --- _config.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 _config.yml diff --git a/_config.yml b/_config.yml deleted file mode 100644 index b84971359..000000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-leap-day \ No newline at end of file From 97b47087f482cbbcccbd20e0cfe8b102c649ac40 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 13 Feb 2021 09:24:00 +1300 Subject: [PATCH 173/181] Update ChangeLog --- apps/speedalt/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 2c77ccafa..1020a8eec 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -5,4 +5,4 @@ 0.05 : Add setting to turn vibrate on/off. 0.06 : Tweaks to vibration settings. 0.07 : Switch to BTN1 for Max toggle and reset function. -1.00 : New features. Added waypoints file and distance to selected waypoint display. Added integration with Low Power GPS service. Save display settings and restore when app restarted. +1.00 : 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. From bbc98d1d46f755b24273d0a10ca7890d03b16da4 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Fri, 12 Feb 2021 20:34:10 +0000 Subject: [PATCH 174/181] added gps and hrm power status widgets --- apps.json | 22 ++++++++++++++++++++++ apps/widgps/ChangeLog | 2 ++ apps/widgps/README.md | 10 ++++++++++ apps/widgps/widget.js | 28 ++++++++++++++++++++++++++++ apps/widgps/widget.png | Bin 0 -> 328 bytes apps/widhrt/ChangeLog | 2 ++ apps/widhrt/README.md | 8 ++++++++ apps/widhrt/widget.js | 28 ++++++++++++++++++++++++++++ apps/widhrt/widget.png | Bin 0 -> 848 bytes 9 files changed, 100 insertions(+) create mode 100644 apps/widgps/ChangeLog create mode 100644 apps/widgps/README.md create mode 100644 apps/widgps/widget.js create mode 100644 apps/widgps/widget.png create mode 100644 apps/widhrt/ChangeLog create mode 100644 apps/widhrt/README.md create mode 100644 apps/widhrt/widget.js create mode 100644 apps/widhrt/widget.png diff --git a/apps.json b/apps.json index d5dcc9b3e..9674a54f6 100644 --- a/apps.json +++ b/apps.json @@ -2794,5 +2794,27 @@ {"name":"walkersclock.app.js","url":"app.js"}, {"name":"walkersclock.img","url":"icon.js","evaluate":true} ] +}, +{ "id": "widgps", + "name": "GPS Widget", + "icon": "widget.png", + "version":"0.01", + "description": "Show the power on/off status of the GPS", + "tags": "widget,gps", + "type":"widget", + "storage": [ + {"name":"widgps.wid.js","url":"widget.js"} + ] +}, +{ "id": "widhrt", + "name": "HRM Widget", + "icon": "widget.png", + "version":"0.01", + "description": "Show the power on/off status of the Heart Rate Monitor", + "tags": "widget, hrm", + "type":"widget", + "storage": [ + {"name":"widhrt.wid.js","url":"widget.js"} + ] } ] diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog new file mode 100644 index 000000000..20a17d487 --- /dev/null +++ b/apps/widgps/ChangeLog @@ -0,0 +1,2 @@ +0.01: First version + diff --git a/apps/widgps/README.md b/apps/widgps/README.md new file mode 100644 index 000000000..372747486 --- /dev/null +++ b/apps/widgps/README.md @@ -0,0 +1,10 @@ +# GPS Power Status Widget + +A simple widget that shows the on/off status of the GPS. + +The GPS can quickly run the battery down if it is on all the time so +it is useful to know if it has been switched on or not. + +- Uses Bangle.isGPSOn() +- Shows in grey when the GPS is off +- Shows in amber when the GPS is on diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js new file mode 100644 index 000000000..1a8fb6f13 --- /dev/null +++ b/apps/widgps/widget.js @@ -0,0 +1,28 @@ +(function(){ + var img = E.toArrayBuffer(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA==")); + + function draw() { + g.reset(); + if (Bangle.isGPSOn()) { + g.setColor(1,0.8,0); // on = amber + } else { + g.setColor(0.3,0.3,0.3); // off = grey + } + g.drawImage(img, 10+this.x, 2+this.y); + } + + var timerInterval; + Bangle.on('lcdPower', function(on) { + if (on) { + WIDGETS.gps.draw(); + if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["gps"].draw(), 2000); + } else { + if (timerInterval) { + clearInterval(timerInterval); + timerInterval = undefined; + } + } + }); + + WIDGETS.gps={area:"tr",width:24,draw:draw}; +})(); diff --git a/apps/widgps/widget.png b/apps/widgps/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..3677154aa85ff94b29711aa39cc82fec235362a5 GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCwj^(N7l!{JxM1({$v~06o-U3d z8I5mmp5|?G5NUlF|L9oI8i&{mZT(zpI)!?o*B)ry$mE^4T(72~TT-${ruVtGhsT3S z)hBe=^k&S|ER~Bn-@s|WQ1w3lxgY172G)X%&kZ~g%~ z@qbwTk7`@B$2Ktm=JK)yCX$A|jf%4bFN;N7Dms#q_N`7`U43oL-u-(Y00V--)78&q Iol`;+0LDRz>i_@% literal 0 HcmV?d00001 diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog new file mode 100644 index 000000000..20a17d487 --- /dev/null +++ b/apps/widhrt/ChangeLog @@ -0,0 +1,2 @@ +0.01: First version + diff --git a/apps/widhrt/README.md b/apps/widhrt/README.md new file mode 100644 index 000000000..67058c810 --- /dev/null +++ b/apps/widhrt/README.md @@ -0,0 +1,8 @@ +# Heart Rate Power Monitor Widget + +A simple widget that shows the on/off status of the Heart Rate +Monitor. + +- Uses Bangle.isHRTOn(). +- Shows in grey when the HRT is off +- Shows in red when the HRT is on diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js new file mode 100644 index 000000000..41de2b5d3 --- /dev/null +++ b/apps/widhrt/widget.js @@ -0,0 +1,28 @@ +(function(){ + var img = E.toArrayBuffer(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA=")); + + function draw() { + g.reset(); + if (Bangle.isHRMOn()) { + g.setColor(1,0,0); // on = red + } else { + g.setColor(0.3,0.3,0.3); // off = grey + } + g.drawImage(img, 10+this.x, 2+this.y); + } + + var timerInterval; + Bangle.on('lcdPower', function(on) { + if (on) { + WIDGETS.widhrt.draw(); + if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000); + } else { + if (timerInterval) { + clearInterval(timerInterval); + timerInterval = undefined; + } + } + }); + + WIDGETS.widhrt={area:"tr",width:24,draw:draw}; +})(); diff --git a/apps/widhrt/widget.png b/apps/widhrt/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8f5b7301f96377cd75574cf84d6f39b5ed44f6 GIT binary patch literal 848 zcmV-W1F!svP)MV7RDVA+4flSDQkih@c16CL$p;N)5V|1ucy%?IG4;&fs^BqS-#nF1MJUE*l zGXw{LcAy@p!h1sXKqv6QxVKkmT{8OK14n^XN}~#B06KsnXVvL?6Tn+rV1%1=|uMsm~SW zn!oIz(7bj$C z^2zKB8o*Q0cG*Icrd70`*aYlgcFxTJ8**UEmUYU5@2Alws0eTW_k$jyuZw(93_KD( zUd9RDj{X+yOxm3VW=XO$uD~@B{G0qvrM18>k^icTNEPr#h-84egbapJhgWX(N1ew2(rN>CRjfD4b`&cQ*n~Hfg z?{mgTe4lt*T_x~<0-5(YX&876)L6vT;1y*u_FRrUM$(2}Yz+X5L+-Byo}rsRpJR`a z97Z3+5W#QkI`j|y8FH{yy0Q=b*f0Yu7yV0tx1xU0 aKja??4m||(3#qLD0000 Date: Fri, 12 Feb 2021 20:41:01 +0000 Subject: [PATCH 175/181] added not that firmware v2.08.267 or later is required --- apps/widgps/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widgps/README.md b/apps/widgps/README.md index 372747486..d4a27c130 100644 --- a/apps/widgps/README.md +++ b/apps/widgps/README.md @@ -5,6 +5,6 @@ A simple widget that shows the on/off status of the GPS. The GPS can quickly run the battery down if it is on all the time so it is useful to know if it has been switched on or not. -- Uses Bangle.isGPSOn() +- Uses Bangle.isGPSOn(), requires firmware v2.08.167 or later - Shows in grey when the GPS is off - Shows in amber when the GPS is on From 3a97e4a8a1ed2820e0ba6ed6658be1b278129e81 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Fri, 12 Feb 2021 20:53:07 +0000 Subject: [PATCH 176/181] added README files to apps.json for widgps and widhrm --- apps.json | 2 ++ apps/widhrt/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 9674a54f6..f4c736e77 100644 --- a/apps.json +++ b/apps.json @@ -2802,6 +2802,7 @@ "description": "Show the power on/off status of the GPS", "tags": "widget,gps", "type":"widget", + "readme": "README.md", "storage": [ {"name":"widgps.wid.js","url":"widget.js"} ] @@ -2813,6 +2814,7 @@ "description": "Show the power on/off status of the Heart Rate Monitor", "tags": "widget, hrm", "type":"widget", + "readme": "README.md", "storage": [ {"name":"widhrt.wid.js","url":"widget.js"} ] diff --git a/apps/widhrt/README.md b/apps/widhrt/README.md index 67058c810..db16d3d35 100644 --- a/apps/widhrt/README.md +++ b/apps/widhrt/README.md @@ -3,6 +3,6 @@ A simple widget that shows the on/off status of the Heart Rate Monitor. -- Uses Bangle.isHRTOn(). +- Uses Bangle.isHRTOn(). Requires firmware v2.08.167 or later. - Shows in grey when the HRT is off - Shows in red when the HRT is on From f3fe69ddfc2604d4f01b6b88e31b862736b8a73d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Fri, 12 Feb 2021 21:02:38 +0000 Subject: [PATCH 177/181] updated descriptions in apps.json for widgps and widhrm, require v2.08.167 or later --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index f4c736e77..2a407a1e0 100644 --- a/apps.json +++ b/apps.json @@ -2799,7 +2799,7 @@ "name": "GPS Widget", "icon": "widget.png", "version":"0.01", - "description": "Show the power on/off status of the GPS", + "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", "tags": "widget,gps", "type":"widget", "readme": "README.md", @@ -2811,7 +2811,7 @@ "name": "HRM Widget", "icon": "widget.png", "version":"0.01", - "description": "Show the power on/off status of the Heart Rate Monitor", + "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", "tags": "widget, hrm", "type":"widget", "readme": "README.md", From 1ab10da07b3df8c23fd84891feb60c771bc23a49 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 15 Feb 2021 08:27:40 +1300 Subject: [PATCH 178/181] Update ChangeLog --- apps/speedalt/ChangeLog | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog index 1020a8eec..e87c903bb 100644 --- a/apps/speedalt/ChangeLog +++ b/apps/speedalt/ChangeLog @@ -1,8 +1,8 @@ -0.01 : Initial import. -0.02 : Misc development. -0.03 : Enable screen off. -0.04 : Vibrate once on no fix, twice on fix. -0.05 : Add setting to turn vibrate on/off. -0.06 : Tweaks to vibration settings. -0.07 : Switch to BTN1 for Max toggle and reset function. -1.00 : 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.01: Initial import. +0.02: Misc development. +0.03: Enable screen off. +0.04: Vibrate once on no fix, twice on fix. +0.05: Add setting to turn vibrate on/off. +0.06: Tweaks to vibration settings. +0.07: Switch to BTN1 for Max toggle and reset function. +1.00: 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. From dec45dca82d453c223430c21f5bf143f1946eaac Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 16 Feb 2021 16:52:47 +1300 Subject: [PATCH 179/181] Add en_NZ Locale data for New Zealand --- apps/locale/locales.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 0861bc907..14d0f43c5 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -245,6 +245,24 @@ var locales = { day: "söndag,måndag,tisdag,onsdag,torsdag,fredag,lördag", trans: { yes: "ja", Yes: "Ja", no: "nej", No: "Nej", ok: "ok", on: "on", off: "off" } }, + "en_NZ": { + lang: "en_NZ", + decimal_point: ".", + thousands_sep: ",", + currency_symbol: "$", + int_curr_symbol: "NZD", + speed: "kmh", + distance: { 0: "m", 1: "km" }, + temperature: "°C", + ampm: { 0: "am", 1: "pm" }, + timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, + datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%y" }, // Sunday, 1 March 2020 // 1/3/20 + abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + month: "January,February,March,April,May,June,July,August,September,October,November,December", + abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", + day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", + // No translation for english... + }, "en_AU": { lang: "en_AU", decimal_point: ".", From 7f89aec5e3931f30c4f10d84b79264bc1c18e01a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 16 Feb 2021 16:55:28 +1300 Subject: [PATCH 180/181] Update locales.js --- apps/locale/locales.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/locale/locales.js b/apps/locale/locales.js index 14d0f43c5..bad920b44 100644 --- a/apps/locale/locales.js +++ b/apps/locale/locales.js @@ -251,7 +251,7 @@ var locales = { thousands_sep: ",", currency_symbol: "$", int_curr_symbol: "NZD", - speed: "kmh", + speed: "kph", distance: { 0: "m", 1: "km" }, temperature: "°C", ampm: { 0: "am", 1: "pm" }, From b5d84df06edca8b0e4160cd4ded49001530e98cc Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 16 Feb 2021 16:59:48 +1300 Subject: [PATCH 181/181] Added New Zealand en_NZ --- apps/locale/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/locale/ChangeLog b/apps/locale/ChangeLog index c6779bf27..3d64cf8d7 100644 --- a/apps/locale/ChangeLog +++ b/apps/locale/ChangeLog @@ -8,3 +8,4 @@ Ensure 'on' is always supplied for translations 0.07: Improve handling of non-ASCII characters (fix #469) 0.08: Added Mavigation units and en_NAV +0.09: Added New Zealand en_NZ

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 N=r(m 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