1
0
Fork 0

Merge branch 'master' into master

master
Gordon Williams 2021-01-29 08:43:43 +00:00 committed by GitHub
commit af98eb9017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 2127 additions and 34 deletions

View File

@ -1007,7 +1007,7 @@
{ "id": "widpedom",
"name": "Pedometer widget",
"icon": "widget.png",
"version":"0.10",
"version":"0.11",
"description": "Daily pedometer widget",
"tags": "widget",
"type":"widget",
@ -1153,7 +1153,7 @@
"name": "Commandline-Clock",
"shortName":"CLI-Clock",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "Simple CLI-Styled Clock",
"tags": "clock,cli,command,bash,shell",
"type":"clock",
@ -1553,7 +1553,7 @@
"name": "BangleRun",
"shortName": "BangleRun",
"icon": "banglerun.png",
"version": "0.06",
"version": "0.07",
"interface": "interface.html",
"description": "An app for running sessions. Displays info and logs your run for later viewing.",
"tags": "run,running,fitness,outdoors",
@ -2021,7 +2021,7 @@
"name": "Vertical watch face",
"shortName":"Vertical Face",
"icon": "app.png",
"version":"0.07",
"version":"0.08",
"description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1",
"tags": "clock",
"type":"clock",
@ -2139,7 +2139,7 @@
{ "id": "multiclock",
"name": "Multi Clock",
"icon": "multiclock.png",
"version":"0.08",
"version":"0.11",
"description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3",
"readme": "README.md",
"tags": "clock",
@ -2152,6 +2152,9 @@
{"name":"digi.face.js","url":"digi.js"},
{"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}
]
},
@ -2582,7 +2585,7 @@
{ "id": "astral",
"name": "Astral Clock",
"icon": "app-icon.png",
"version":"0.01",
"version":"0.02",
"readme": "README.md",
"description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.",
"tags": "clock",
@ -2592,6 +2595,18 @@
{"name":"astral.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "alpinenav",
"name": "Alpine Nav",
"icon": "app-icon.png",
"version":"0.01",
"readme": "README.md",
"description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime",
"tags": "outdoors,gps",
"storage": [
{"name":"alpinenav.app.js","url":"app.js"},
{"name":"alpinenav.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "lifeclk",
"name": "Game of Life Clock",
"shortName":"Conway's Clock",
@ -2624,5 +2639,58 @@
"data": [
{"name":"speedalt.json"}
]
}
},
{ "id": "de-stress",
"name": "De-Stress",
"shortName":"De-Stress",
"icon": "app.png",
"version":"0.01",
"description": "Simple haptic heartbeat",
"storage": [
{"name":"de-stress.app.js","url":"app.js"},
{"name":"de-stress.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "gpsservice",
"name": "Low power GPS Service",
"shortName":"GPS Service",
"icon": "gpsservice.png",
"version":"0.03",
"description": "low power GPS widget",
"tags": "gps outdoors navigation",
"readme": "README.md",
"storage": [
{"name":"gpsservice.app.js","url":"app.js"},
{"name":"gpsservice.settings.js","url":"settings.js"},
{"name":"gpsservice.settings.json","url":"settings.json"},
{"name":"gpsservice.wid.js","url":"widget.js"},
{"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true}
]
},
{ "id": "mclockplus",
"name": "Morph Clock+",
"shortName":"Morph Clock+",
"icon": "mclockplus.png",
"version":"1.0",
"description": "Morphing Clock with more readable seconds and date and additional stopwatch",
"tags": "clock",
"type": "clock",
"readme": "README.md",
"storage": [
{"name":"mclockplus.app.js","url":"mclockplus.app.js"},
{"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true}
]
},
{ "id": "intervals",
"name": "Intervals App",
"shortName":"Intervals",
"icon": "intervals.png",
"version":"0.01",
"description": "Intervals for training. It is possible to configure work time and rest time and number of sets.",
"tags": "",
"storage": [
{"name":"intervals.app.js","url":"intervals.app.js"},
{"name":"intervals.img","url":"intervals-icon.js","evaluate":true}
]
}
]

30
apps/alpinenav/README.md Normal file
View File

@ -0,0 +1,30 @@
Alpine Navigator
================
App that performs GPS monitoring to track and display position relative to a given origin in realtime.
![screenshot](./sample.png)
Functions
---------
Note if you've not used GPS yet I suggest using one of the GPS apps to get your first fix and confirm as I've found that helps initially.
The GPS and magnetometer will be turned on and after a few moments, when the watch buzzes and the dot turns from red to pink, that means the GPS is fixed. all your movements now will be displayed with a line drawn back to show your position relative to the start. New waypoints will be added based on checking every 10 seconds for at least 5 meters of movement. The map will scale to your distance travelled so the route will always remain within the window, the accelerometer/pedometer is not used - this is a purely GPS and compass solution so can be used for driving/cycling etc. A log file will be recorded that tracks upto 1000 waypoints, this isn't a big file and you could remove the limit but I've kept it fairly conservative here as it's not intended as a main feature, there's already good GPS recorders for the Bangle. The following other items are displayed:
1. altitude at origin, this is displayed left of the centre.
2. current altitude, displayed centre right
3. distance from origin, bottom left (meters)
4. distance travelled, bottom right (meters)
5. compass heading, at the top
For the display, the route is kept at a set resolution, so there's no risk of running into memory problems if you run this for long periods or any length of time because the waypoints will be reduced when it reaches a set threshold so you may see the path smooth out slightly at intervals.
If you get strange values or dashes for the compass, it just needs calibration so you need to move the watch around briefly for this each time, ideally 360 degrees around itself, which involves taking the watch off. If you don't want to do that you can also just wave your hand around for a few seconds like you're at a rave or Dr Strange making a Sling Ring but often just moving your wrist a bit is enough.
The buttons do the following:
BTN1: this will display an 'X' in the bottom of the screen and lock all the buttons, this is to prevent you accidentally pressing either of the below. Remember to press this again to unlock it! soft and hard reset will both still work.
BTN2: this removes all waypoints aside from the origin and your current location; sometimes during smaller journeys and walks, the GPS can give sporadic differences in locations because of the error margins of GPS and this can add noise to your route.
BTN3: this will pause the GPS and magnetometer, useful for saving power for situations where you don't necessarily need to track parts of your route e.g. you're going indoors/shelter for some time. You'll know it's paused because the compass won't update it's reading and all the metrics will be blacked out on the screen.
Things to do next
-----------------
There's a GPS widget that's been developed to leverage low-power mode capability on the sensor, will look to incorporate that.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mUywkEIf4A/AHUBiAYWgcwDC0v+IYW///C6sC+c/kAYUj/xj/wDCgvBgfyVihhBAQQASh6TCMikvYoRkU/73CMicD+ZnFViJFBj5MBMiU/+IuBJoJkRCoUvfIPy/5kQVgM//7gBC4KCDFxSsDgTHCl8QWgaRKmBJBFIzmDSJXzYBECWobbJAAKNIMhYlBOoK/IMhZXCmYMLABAkCS4RkSXZoNJRBo/CgK6UBwTWBBIs/SJBAGl7UFegIXMaogHEehAAHj/yIYsfehAAGMQISFMRxbCiEDU4ZiQZY5iQZYpiSbQ8/cwzLOCiQA/AH4A1A"))

BIN
apps/alpinenav/app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

237
apps/alpinenav/app.js Normal file
View File

@ -0,0 +1,237 @@
var background_colour = "#000000";
var foregound_colour = "#ccff99";
var position_colour = "#ff3329";
var log_limit = 1000;
var max_array_size = 50;
var pause_tracker = false;
var temp;
var file;
var d;
var origin_lat;
var origin_lon;
var current_lat;
var current_lon;
var current_speed;
var distance_from_origin = 0;
var distane_travelled = 0;
var log_size;
var waypoints = [];
var start_alt = 0;
var current_alt = 0;
var button_lock = false;
var display_waypoints = [];
var waypoint = {
lat: "",
lon: ""
};
var compass_heading = "---";
function calcCrow(lat1, lon1, lat2, lon2) {
var R = 6371e3;
var dLat = toRad(lat2 - lat1);
var dLon = toRad(lon2 - lon1);
lat1 = toRad(lat1);
lat2 = toRad(lat2);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
function toRad(Value) {
return Value * Math.PI / 180;
}
function draw() {
if (pause_tracker)
g.setColor(background_colour);
else
g.setColor(foregound_colour);
g.setFont("6x8", 2);
g.setFontAlign(0, 1);
g.drawString(distance_from_origin.toFixed(0), 40, 220, true);
g.drawString(distane_travelled.toFixed(0), 200, 220, true);
g.setFont("6x8", 1);
g.drawString(start_alt.toFixed(0), 40, 120, true);
g.drawString(current_alt.toFixed(0), 200, 120, true);
if (button_lock) {
g.setFont("6x8", 2);
g.setFontAlign(0, 0);
g.drawString("X", 120, 220, true);
g.setFont("6x8", 1);
}
}
function cull_array() {
for (var i = 2; i <= waypoints.length; i += 2)
waypoints.splice(i, 1);
}
function process_and_display() {
g.setColor(background_colour);
g.fillRect(10, 65, 230, 230);
g.setColor(foregound_colour);
if (waypoints.length > max_array_size)
cull_array();
rescale();
if (display_waypoints.length > 0) {
for (let x = 0; x < display_waypoints.length - 1; x++) {
g.reset();
g.setColor(foregound_colour);
g.drawLine(display_waypoints[x].lon, display_waypoints[x].lat, display_waypoints[x + 1].lon, display_waypoints[x + 1].lat);
}
g.reset();
g.setColor(position_colour);
g.fillCircle(display_waypoints[display_waypoints.length - 1].lon, display_waypoints[display_waypoints.length - 1].lat, 3);
}
}
function process_GPS() {
if (waypoints.length > 0) {
//check distance
temp_distance = calcCrow(current_lat, current_lon, waypoints[waypoints.length - 1].lat, waypoints[waypoints.length - 1].lon);
if (temp_distance > 5) {
var temp = Object.create(waypoint);
temp.lat = current_lat;
temp.lon = current_lon;
waypoints.push(temp);
distane_travelled += temp_distance;
distance_from_origin = calcCrow(current_lat, current_lon, waypoints[0].lat, waypoints[0].lon);
process_and_display();
if (log_size < log_limit) {
var csv = [
d,
origin_lat - current_lat,
current_lon - origin_lon,
current_speed,
current_alt
];
file.write(csv.join(",") + "\n");
log_size += 1;
}
}
}
else {
g.setColor(position_colour);
g.fillCircle(120, 120, 3);
}
draw();
}
function rescale() {
var max_val = 0;
display_waypoints = [];
for (let x = 0; x < waypoints.length; x++) {
if (Math.abs(waypoints[x].lat) > max_val)
max_val = Math.abs(waypoints[x].lat);
if (Math.abs(waypoints[x].lon) > max_val)
max_val = Math.abs(waypoints[x].lon);
}
scaler = 60 / max_val;
for (let x = 0; x < waypoints.length; x++) {
temp = Object.create(waypoint);
temp.lat = 140 - Math.round(waypoints[x].lat * scaler);
temp.lon = 120 - Math.round(waypoints[x].lon * scaler);
display_waypoints.push(temp);
}
}
Bangle.setCompassPower(1);
Bangle.setGPSPower(1);
g.clear();
process_GPS();
var poll_GPS = setInterval(process_GPS, 9000);
setWatch(function () {
if (!button_lock) {
waypoints.splice(1);
process_GPS();
}
}, BTN2, { repeat: true, edge: "falling" });
setWatch(function () {
if (!button_lock) {
if (!pause_tracker) {
Bangle.setCompassPower(0);
Bangle.setGPSPower(0);
pause_tracker = true;
}
else {
Bangle.setCompassPower(1);
Bangle.setGPSPower(1);
pause_tracker = false;
}
}
}, BTN3, { repeat: true, edge: "falling" });
setWatch(function () {
if (button_lock) {
button_lock = false;
g.setFontAlign(0, 0);
g.drawString(" ", 120, 220, true);
}
else {
button_lock = true;
g.setFontAlign(0, 0);
g.drawString("X", 120, 220, true);
}
}, BTN1, { repeat: true, edge: "falling" });
Bangle.on('GPS', function (g) {
if (g.fix) {
if (waypoints.length == 0) {
file = require("Storage").open("alpine_log.csv", "w");
file.write("");
file = require("Storage").open("alpine_log.csv", "a");
Bangle.buzz();
position_colour = 0xF81F;
origin_lat = g.lat;
origin_lon = g.lon;
start_alt = g.alt;
current_speed = g.speed;
sat_count = g.satellites;
var csv = [
d,
origin_lat,
origin_lon,
current_speed,
current_alt
];
file.write(csv.join(",") + "\n");
var temp = Object.create(waypoint);
temp.lat = 0;
temp.lon = 0;
process_GPS();
waypoints.push(temp);
}
else {
current_lat = g.lat - origin_lat;
current_lon = origin_lon - g.lon;
current_speed = g.speed;
sat_count = g.satellites;
current_alt = g.alt;
gps_time = g.time;
}
}
});
Bangle.on('mag', function (m) {
if (isNaN(m.heading))
compass_heading = "---";
else
compass_heading = 360 - Math.round(m.heading);
current_colour = g.getColor();
g.reset();
g.setColor(background_colour);
g.fillRect(140, 30, 190, 55);
g.setColor(foregound_colour);
g.setFont("6x8", 2);
if(compass_heading<100)
compass_heading = " " + compass_heading.toString();
g.drawString(compass_heading, 150, 15, true);
});

BIN
apps/alpinenav/sample.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

2
apps/astral/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: Create astral clock app
0.02: Fixed Whirlpool galaxy RA/DA, larger compass display, fixed moonphase overlapping battery widget

View File

@ -32,14 +32,14 @@ if (test_file !== undefined) {
if (astral_settings === undefined) {
astral_settings = {
version: 1,
lat: 51.9492,
lon: 0.2834,
lat: 51.5074,
lon: 0.1278,
astral_default: true,
extras: [
{ name: "Andromeda", ra: "004244", de: "411609", type: 3 },
{ name: "Cigar", ra: "095552", de: "694047", type: 3 },
{ name: "Pinwheel", ra: "140313", de: "542057", type: 3 },
{ name: "Whirlpool", ra: "1329553", de: "471143", type: 3 },
{ name: "Whirlpool", ra: "132953", de: "471143", type: 3 },
{ name: "Orion", ra: "053517", de: "-052328", type: 2 },
{ name: "Hercules", ra: "160515", de: "174455", type: 1 },
{ name: "Beehive", ra: "084024", de: "195900", type: 1 },
@ -662,12 +662,12 @@ function draw_moon(phase) {
if (phase == 5) {
g.fillCircle(200, Yaxis, 30);
g.setColor("#000000");
g.fillRect(220, 20, 240, 90);
g.fillRect(220, 25, 240, 90);
}
else if (phase == 6) {
g.fillCircle(200, Yaxis, 30);
g.setColor("#000000");
g.fillRect(200, 20, 240, 90);
g.fillRect(200, 25, 240, 90);
}
else if (phase == 1) {
g.fillCircle(200, Yaxis, 30);
@ -679,12 +679,12 @@ function draw_moon(phase) {
else if (phase == 3) {
g.fillCircle(200, Yaxis, 30);
g.setColor("#000000");
g.fillRect(160, 20, 180, 90);
g.fillRect(160, 25, 180, 90);
}
else if (phase == 2) {
g.fillCircle(200, Yaxis, 30);
g.setColor("#000000");
g.fillRect(160, 20, 200, 90);
g.fillRect(160, 25, 200, 90);
}
else if (phase == 7) {
g.fillCircle(200, Yaxis, 30);
@ -823,19 +823,23 @@ setWatch(function () {
display_colour = default_colour;
else
display_colour = setupcomplete_colour;
draw_moon(current_moonphase);
}
}, BTN4, { repeat: true });
//events
Bangle.on('mag', function (m) {
g.setFont("6x8",2);
if (isNaN(m.heading))
compass_heading = "--";
compass_heading = "---";
else
compass_heading = 360 - Math.round(m.heading);
g.setColor("#000000");
g.fillRect(100, 10, 140, 20);
// g.setColor("#000000");
// g.fillRect(160, 10, 160, 20);
g.setColor(display_colour);
g.drawString(" " + (compass_heading) + " ", 130, 20, true /*clear background*/);
if(compass_heading<100)
compass_heading = " " + compass_heading;
g.drawString(compass_heading, 150, 20, true /*clear background*/);
});
Bangle.on('GPS', function (g) {

View File

@ -4,3 +4,4 @@
0.04: Use offscreen buffer for flickerless updates
0.05: Complete rewrite. New UI, GPS & HRM Kalman filters, activity logging
0.06: Reading HDOP directly from the GPS event (needs Espruino 2v07 or above)
0.07: Fixed GPS update, added guards against NaN values

View File

@ -1 +1 @@
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function e(e){var r;g.setFontVector(30),g.setFontAlign(0,-1,0),n((e.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),e=Math.floor(n/3600),r=Math.floor(n/60)%60,a=n%60;return(e?e+":":"")+("0"+r).substr(-2)+":"+("0"+a).substr(-2)}(e.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),r=n%60;return("0"+e).substr(-2)+"'"+("0"+r).substr(-2)+'"'}(e.speed),60,115),n(e.hr.toFixed(0),180,115),n(e.steps.toFixed(0),60,175),n(e.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(e.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(r=new Date).getHours()).substr(-2)+":"+("0"+r.getMinutes()).substr(-2),120,220),g.setColor(t[e.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(e.status,200,220)}function r(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),e(t),Bangle.drawWidgets()}var a;function s(t){t.status===a.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n")}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Running,e(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(a||(a={}));const o={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:a.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var i;i=o,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop}(i,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,r=Math.abs(e)+101-n.confidence,a=t.hrError/(t.hrError+r);t.hr+=e*a,t.hrError+=(r-t.hrError)*a}(t,n)),Bangle.setHRMPower(1)}(o),function(t){Bangle.on("step",()=>function(t){t.status===a.Running&&(t.steps+=1)}(t))}(o),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&r(t)}),r(t)}(o),setWatch(()=>s(o),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===a.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===a.Running?t.status=a.Paused:t.status=a.Stopped,e(t)}(o),BTN3,{repeat:!0,edge:"falling"})}();
!function(){"use strict";const t={STOP:63488,PAUSE:65504,RUN:2016};function n(t,n,r){g.setColor(0),g.fillRect(n-60,r,n+60,r+30),g.setColor(65535),g.drawString(t,n,r)}function r(r){var e;g.setFontVector(30),g.setFontAlign(0,-1,0),n((r.distance/1e3).toFixed(2),60,55),n(function(t){const n=Math.round(t),r=Math.floor(n/3600),e=Math.floor(n/60)%60,o=n%60;return(r?r+":":"")+("0"+e).substr(-2)+":"+("0"+o).substr(-2)}(r.duration),180,55),n(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),r=Math.floor(n/60),e=n%60;return("0"+r).substr(-2)+"'"+("0"+e).substr(-2)+'"'}(r.speed),60,115),n(r.hr.toFixed(0),180,115),n(r.steps.toFixed(0),60,175),n(r.cadence.toFixed(0),180,175),g.setFont("6x8",2),g.setColor(r.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(e=new Date).getHours()).substr(-2)+":"+("0"+e.getMinutes()).substr(-2),120,220),g.setColor(t[r.status]),g.fillRect(160,216,240,240),g.setColor(0),g.drawString(r.status,200,220)}function e(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),r(t),Bangle.drawWidgets()}var o;function a(t){t.status===o.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),r=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(r,"w"),t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n")}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Running,r(t)}!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(o||(o={}));const s={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,v:NaN,t:NaN,dt:NaN,pError:NaN,vError:NaN,hr:60,hrError:100,file:null,drawing:!1,status:o.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var i;i=s,Bangle.on("GPS",t=>function(t,n){t.lat=n.lat,t.lon=n.lon,t.alt=n.alt,t.vel=n.speed/3.6,t.fix=n.fix,t.dop=n.hdop,t.gpsValid=t.fix>0&&t.dop<=5,function(t){const n=Date.now(),r=(n-t.t)/1e3;if(t.t=n,t.dt+=r,t.status===o.Running&&(t.duration+=r),!t.gpsValid)return;const e=6371008.8+t.alt,a=e*Math.cos(t.lat)*Math.cos(t.lon),s=e*Math.cos(t.lat)*Math.sin(t.lon),i=e*Math.sin(t.lat),d=t.vel;if(!t.x)return t.x=a,t.y=s,t.z=i,t.v=d,t.pError=2.5*t.dop,void(t.vError=.05*t.dop);const u=a-t.x,g=s-t.y,l=i-t.z,c=d-t.v,p=Math.sqrt(u*u+g*g+l*l),f=Math.abs(c);t.pError+=t.v*t.dt,t.dt=0;const N=p+2.5*t.dop,h=f+.05*t.dop,S=t.pError/(t.pError+N)||0,x=t.vError/(t.vError+h)||0;t.x+=u*S,t.y+=g*S,t.z+=l*S,t.v+=c*x,t.pError+=(N-t.pError)*S,t.vError+=(h-t.vError)*x;const E=Math.sqrt(t.x*t.x+t.y*t.y+t.z*t.z);t.lat=180*Math.asin(t.z/E)/Math.PI||0,t.lon=180*Math.atan2(t.y,t.x)/Math.PI||0,t.alt=E-6371008.8,t.status===o.Running&&(t.distance+=p*S,t.speed=t.distance/t.duration||0,t.cadence=60*t.steps/t.duration||0)}(t),r(t),t.gpsValid&&t.status===o.Running&&function(t){t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(t)}(i,t)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const r=n.bpm-t.hr,e=Math.abs(r)+101-n.confidence,o=t.hrError/(t.hrError+e)||0;t.hr+=r*o,t.hrError+=(e-t.hrError)*o}(t,n)),Bangle.setHRMPower(1)}(s),function(t){Bangle.on("step",()=>function(t){t.status===o.Running&&(t.steps+=1)}(t))}(s),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&e(t)}),e(t)}(s),setWatch(()=>a(s),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(t){t.status===o.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(t),t.status===o.Running?t.status=o.Paused:t.status=o.Stopped,r(t)}(s),BTN3,{repeat:!0,edge:"falling"})}();

View File

@ -1,3 +1,5 @@
import { draw } from './display';
import { updateLog } from './log';
import { ActivityStatus, AppState } from './state';
declare var Bangle: any;
@ -34,6 +36,15 @@ function readGps(state: AppState, gps: GpsEvent): void {
state.vel = gps.speed / 3.6;
state.fix = gps.fix;
state.dop = gps.hdop;
state.gpsValid = state.fix > 0 && state.dop <= 5;
updateGps(state);
draw(state);
if (state.gpsValid && state.status === ActivityStatus.Running) {
updateLog(state);
}
}
function updateGps(state: AppState): void {
@ -80,8 +91,8 @@ function updateGps(state: AppState): void {
const pError = dpMag + state.dop * POS_ACCURACY;
const vError = dvMag + state.dop * VEL_ACCURACY;
const pGain = state.pError / (state.pError + pError);
const vGain = state.vError / (state.vError + vError);
const pGain = (state.pError / (state.pError + pError)) || 0;
const vGain = (state.vError / (state.vError + vError)) || 0;
state.x += dx * pGain;
state.y += dy * pGain;
@ -92,7 +103,7 @@ function updateGps(state: AppState): void {
const pMag = Math.sqrt(state.x * state.x + state.y * state.y + state.z * state.z);
state.lat = Math.asin(state.z / pMag) * 180 / Math.PI;
state.lat = (Math.asin(state.z / pMag) * 180 / Math.PI) || 0;
state.lon = (Math.atan2(state.y, state.x) * 180 / Math.PI) || 0;
state.alt = pMag - EARTH_RADIUS;

View File

@ -20,7 +20,7 @@ function updateHrm(state: AppState, hrm: HrmData) {
const dHr = hrm.bpm - state.hr;
const hrError = Math.abs(dHr) + 101 - hrm.confidence;
const hrGain = state.hrError / (state.hrError + hrError);
const hrGain = (state.hrError / (state.hrError + hrError)) || 0;
state.hr += dHr * hrGain;
state.hrError += (hrError - state.hrError) * hrGain;

View File

@ -1,2 +1,3 @@
0.07: Submitted to App Loader
0.08: Fixes issue where face would redraw on wake leading to all memory being used and watch crashing.
0.09: Add BTN1 status line with ID,Fw ver, mem %, battery %

14
apps/cliock/README.md Normal file
View File

@ -0,0 +1,14 @@
# Cli Clock
A retro VT100 command line style clock
## Screenshots
### Normall face
![](cli_clock.jpg)
### With BTN1 pressed
* Successive presses of BTN1 will show id, FW version, battery %, memory %
![](cli_clock_with_id.jpg)

View File

@ -4,6 +4,14 @@ var marginTop = 40;
var flag = false;
var WeekDays = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
const NONE_MODE = "none";
const ID_MODE = "id";
const VER_MODE = "ver";
const BATT_MODE = "batt";
const MEM_MODE = "mem";
let infoMode = NONE_MODE;
function drawAll(){
updateTime();
updateRest(new Date());
@ -13,6 +21,7 @@ function updateRest(now){
let date = locale.date(now,false);
writeLine(WeekDays[now.getDay()],1);
writeLine(date,2);
drawInfo(5);
}
function updateTime(){
if (!Bangle.isLCDOn()) return;
@ -32,13 +41,67 @@ function writeLineStart(line){
}
function writeLine(str,line){
g.setFont("6x8",fontsize);
g.setColor(0,1,0);
//g.setColor(0,1,0);
g.setColor(0,0x07E0,0);
g.setFontAlign(-1,-1);
g.clearRect(0,marginTop+line*30,((str.length+1)*20),marginTop+25+line*30);
writeLineStart(line);
g.drawString(str,25,marginTop+line*30);
}
function drawInfo(line) {
let val;
let str = "";
let col = 0x07E0; // green
switch(infoMode) {
case NONE_MODE:
col = 0x0000;
str = "";
break;
case ID_MODE:
val = NRF.getAddress().split(":");
str = "Id: " + val[4] + val[5];
break;
case VER_MODE:
str = "Fw: " + process.env.VERSION;
break;
case MEM_MODE:
val = process.memory();
str = "Memory: " + Math.round(val.usage*100/val.total) + "%";
break;
case BATT_MODE:
default:
str = "Battery: " + E.getBattery() + "%";
}
g.setColor(col);
g.fillRect(0, marginTop-3+line*30, 239, marginTop+25+line*30);
g.setColor(0,0,0);
g.setFontAlign(0, -1);
g.drawString(str, g.getWidth()/2, marginTop+line*30);
}
function changeInfoMode() {
switch(infoMode) {
case NONE_MODE:
infoMode = ID_MODE;
break;
case ID_MODE:
infoMode = VER_MODE;
break;
case VER_MODE:
infoMode = BATT_MODE;
break;
case BATT_MODE:
infoMode = MEM_MODE;
break;
case MEM_MODE:
default:
infoMode = NONE_MODE;
}
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -49,3 +112,4 @@ Bangle.on('lcdPower',function(on) {
});
var click = setInterval(updateTime, 1000);
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
setWatch(() => { changeInfoMode(); drawAll(); }, BTN1, {repeat: true});

BIN
apps/cliock/cli_clock.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4A/ADsBiIABBIoICiAWJim73dEDAkRogJBoIYIBoIACBwYgBBIY6GAAINBF4VLoIIC2gJDBARGFCYINBBwO0iEBpYWC3YbBJA0B2gNCBwNLiIuBEAe7pYXGIwYwDoIIIC4wlEGAQuFBAIXGjYOFE4IuFC4OxC5oAHC+6GGABCPIC6zXFIxLXHgNEMBm0ogXGPAJIMoh2GMAWxGBQuBLwxIOIxKRNFxIABiIwJFwMRC5MBGAIYGLoRGJGAZJGoIuLGBIuODAVLDAYWB2gWNSQQTBRYVLRhRJHDAIWCFx5JEIqKTEpe0RZhiJLiBJGIqYA/AH4A3A"))

29
apps/de-stress/app.js Normal file
View File

@ -0,0 +1,29 @@
//g.clear();
var img = {
width : 100, height : 100, bpp : 8,
transparent : 254,
buffer : require("heatshrink").decompress(atob("/wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4AglYAGE/44gHz4nnHKtPumez3C4WezFzp49ZLAgoCE4WeugnaHStPG4QAHzw9DE/Y6VHJQ9YMJwnEMk46CF4lzfglPug9WCAWYDQl0E4tzN4gLCMVC5Cp+YGoOezxBCubKHExxUEuiFCEoInECAhkjMQuYLArCFGwLLEHpjsGMIRpEfAsrMkwhBFAQ6BHJA9EKApDBHpAJBQYhPBE5iZBzAsDMb4gBEwg6MJhBkIYorgBPQiMMUAhkeHgkrug7ObI5qBHwhiGZYomOEojGeJQVzTx5kOMQ6JREAR3CDIJkcHobwEMiw+CAAYJEMSYWCAgRjcDgI4CYyiiDXopiFBooAWRwJkaHwjGVKwdzH4rADuhiXZAaICMbbtGHy2YBQ+YRDCiEMbidCULBkDLIwIIdqbmCp5jZPwIfDAYQAXXwNPzwACIQLQIACt0ZDIZBTwRFBHjWeHoSJCETlPc4ZjdAYYA7L4Jj/Mf5jCEYQDDAHhEEMbzH+McBfCMf4ADzxjep+YL/1PMb10MYQDCAHd0MYV0MbdzEYoA7UYdPMTBkCc4hj9leeAYRjbL4aIDAHN0UwRjeL4WYZHlPzBnCMbZkBQojtCAG6gEp5ibZARfCdwjG3ugDBzzGcMYTIEFAQA1ujGFMbjIFeAgA0HobGeZA9PAgYAyG4jGfZAyPBzBizf4LGjMggtCFAJpDAFw0FMURjCeAd0AgYAup90AgZjjMgWYFYZkwGImYMUhkDeYdPuZituZiDzximMYUrFwj6DAFF0TAg6CMcpkCSYpkqMQtPMVBkDuZktMQtzMVRkDLwZkoMQoFBMVZkJp5ijX4JizMhFPMkQjBMWpkHIAKjEADTrGMWZkDuY8DuZkdMQKKEMWpkDUIxFEACocGdoJi1MhCqBAwgATLYLkEMXJkDIY4GEAB+ep9PC4aDBMXRkJukruhiRCgxi+MglzJAtPMR7cGNIJi+MglPJYhSBzBhLzB0FzwWBMX5lGMghVGAAtzld0bwph/MhNzWYzKGNwR2Euhi/MhhTHA4ZrHA4Rh/Mp10YIlzA4JoCBQjE/ZTK8BA45i/MqtzX4jE/Mj0rYQjECBYZP/MrFPMoWep5h/ZT90ujE/MsZh/MsZB/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/ACg"))
};
function hr(){
Bangle.buzz(100,0.1).then(()=>{
g.clear();
return new Promise(resolve=>setTimeout(resolve,250)); // wait 250ms
}).then(()=>{
return Bangle.buzz(150);
}).then(()=>{
g.drawImage(img, 25, 40, {scale:2});
});
}
setInterval(hr, 2000);
g.flip();
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

BIN
apps/de-stress/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,3 @@
0.01: New App
0.02: Restore to SuperE mode on power off.
0.03: dont reset to SuperE mode on power, as it prevents its general use

138
apps/gpsservice/README.md Normal file
View File

@ -0,0 +1,138 @@
# GPS Service
A configurable, low power GPS widget that runs in the background.
## Goals
To develop a low power GPS widget that runs in the background and to
facilitate an OS grid reference display in a watch face.
* 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 following options for the GPS
Service. Go to Settings, select App/Widgets and then 'GPS Service'.
- GPS - On/Off. When this value is changed the GPS Service will be
powered on or off and the GPS Widget will be displayed.
- 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.
## Screenshots
### GPS Watch face
* The Age value is the number of seconds since the last position fix was received.
![](gps_face.jpg)
### Grid Reference Watch face
* The time shown is the timestamp of the last position fix.
* The age value is shown at the bottom of the screen.
![](osref_face.jpg)
## Interface for Apps
The code below demonstrates how you can setup and start the gpsservice from your own App.
```js
function test_gps_on() {
var settings = WIDGETS.gpsservice.gps_get_settings();
// change the settings to what you require
settings.gpsservice = true;
settings.update = 65;
settings.search = 5;
settings.power_mode = "PSMOO";
WIDGETS.gpsservice.gps_set_settings(settings);
WIDGETS.gpsservice.reload(); // will power on
}
```
In your app can retrieve the last fix as and when required.
```js
var fix = {
fix: 0,
alt: 0,
lat: 0,
lon: 0,
speed: 0,
time: 0,
satellites: 0
};
// only attempt to get gps fix if gpsservice is loaded
if (WIDGETS.gpsservice !== undefined) {
fix = WIDGETS.gpsservice.gps_get_fix();
gps_on = WIDGETS.gpsservice.gps_get_status();
}
if (fix.fix) {
var time = formatTime(fix.time);
var age = timeSince(time);
```
When done you can turn the gpsservice off using the code below.
```js
function test_gps_off() {
var settings = WIDGETS.gpsservice.gps_get_settings();
settings.gpsservice = false;
settings.power_mode = "SuperE";
WIDGETS.gpsservice.gps_set_settings(settings);
WIDGETS.gpsservice.reload(); // will power off
}
```
## To Do List
* add a logging option with options for interval between log points
* add graphics and icons to the watch faces to make them look nicer
## 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)

70
apps/gpsservice/app.js Normal file
View File

@ -0,0 +1,70 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
const SETTINGS_FILE = "gpsservice.settings.json";
let settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
function updateSettings() {
require("Storage").write(SETTINGS_FILE, settings);
}
function reloadWidget() {
if (WIDGETS.gpsservice)
WIDGETS.gpsservice.reload();
}
function showMainMenu() {
var power_options = ["SuperE","PSMOO"];
const mainmenu = {
'': { 'title': 'GPS Service' },
'< Exit': ()=>{load();},
'GPS': {
value: !!settings.gpsservice,
format: v =>v?'On':'Off',
onchange: v => {
settings.gpsservice = v;
updateSettings();
reloadWidget(); // only when we change On/Off status
},
},
'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.period =v;
updateSettings();
}
},
'Search (s)': {
value: settings.search,
min: 1,
max: 65,
step: 1,
onchange: v => {
settings.search = v;
updateSettings();
}
},
'< Back': ()=>{load();}
};
return E.showMenu(mainmenu);
}
showMainMenu();

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -0,0 +1,4 @@
(function(back) {
// just go right to our app
load("gpsservice.app.js");
})();

View File

@ -0,0 +1 @@
{"gpsservice":false, "power_mode":"SuperE", "update":120, "search":6}

259
apps/gpsservice/test-bed.js Normal file
View File

@ -0,0 +1,259 @@
/*
test bed for working out lowest power consumption, with workable GPS
Load into IDE and upload code to RAM when connected to watch
*/
Bangle.on('GPS-raw',function (d) {
if (d[0]=="$") return;
if (d.startsWith("\xB5\x62\x05\x01")) print("GPS ACK");
else if (d.startsWith("\xB5\x62\x05\x00")) print("GPS NACK");
// 181,98 sync chars
else print("GPS",E.toUint8Array(d).join(","));
});
function writeGPScmd(cmd) {
var d = [0xB5,0x62]; // sync chars
d = d.concat(cmd);
var a=0,b=0;
for (var i=2;i<d.length;i++) {
a += d[i];
b += a;
}
d.push(a&255,b&255);
console.log(d);
Serial1.write(d);
}
// quick hack
function wait(ms){
var start = new Date().getTime();
var end = start;
while(end < start + ms) {
end = new Date().getTime();
}
}
/*
* gps_disable_nmea_output
*
* disables all NMEA messages to be output from the GPS.
* even though the parser can cope with NMEA messages and ignores them, it
* may save power to disable them completely.
*
*
* DO NOT USE
* HB: This is a bad idea on a Bangle JS
* You wont get any fixes if you call this function
*
*
*/
function UBX_CFG_DISABLE_NMEA() {
writeGPScmd([0x06, 0x00, /* UBX-CFG-PRT */
20, 0x00,
0x01, 0x00, 0x00, 0x00, /* UART1, reserved, no TX ready */
0xe0, 0x08, 0x00, 0x00, /* UART mode (8N1) */
0x80, 0x25, 0x00, 0x00, /* UART baud rate (9600) */
0x01, 0x00, /* input protocols (uBx only) */
0x01, 0x00, /* output protocols (uBx only) */
0x00, 0x00, /* flags */
0x00, 0x00]); /* reserved */
}
/*
* gps_disable_nmea_output
*
* disables all NMEA messages to be output from the GPS.
* even though the parser can cope with NMEA messages and ignores them, it
* may save power to disable them completely.
*
* Can use this to switch NMEA back on if you call
* UBX_CFG_DISABLE_NMEA()
*
*/
function UBX_CFG_RESTORE_NMEA() {
writeGPScmd([0x06, 0x00, /* UBX-CFG-PRT */
20, 0x00,
0x01, 0x00, 0x00, 0x00, /* UART1, reserved, no TX ready */
0xe0, 0x08, 0x00, 0x00, /* UART mode (8N1) */
0x80, 0x25, 0x00, 0x00, /* UART baud rate (9600) */
0x03, 0x00, /* input protocols (uBx only) */
0x03, 0x00, /* output protocols (uBx only) */
0x00, 0x00, /* flags */
0x00, 0x00]); /* reserved */
}
function UBX_CFG_PMS() {
// UBX-CFG-PMS - enable power management - Super-E
writeGPScmd([0x06,0x86, // msg class + type
8,0,//length
0x00,0x03, 0,0, 0,0, 0,0]);
}
function UBX_CFG_INTERVAL(period, ontime) {
writeGPScmd([0x06,0x86, // msg class + type
8,0, //length
//v0, interval period ontime reserved
0x00, 0x02, period, 0, ontime, 0, 0, 0 ]);
// the values are little endian, least significant byte first
}
/*
* set update baud rate
*
* the setting is in milliseconds in 2 bytes, max 65 seconds
* we are passing in a value in seconds
* we set the most significant byte only
* 8 seconds ~ 8192ms 0x2000, 0x20 = 32 = 4*8
*
*/
function UBX_CFG_RATE(rate) {
rate = (rate * 4) % 256;
//console.log("rate=" + rate);
writeGPScmd([0x06,0x08, // class, id
0x06, 0, // length
0x00, rate, // b0: 8192ms 0x2000, 0x00FF (~65sec)
0x01, 0x00, // b2:
0x01, 0x00]); // b4: timeref GPS
}
/*
* 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
// code on github had 7 - all 3 set ?
}
/*
* 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]);
}
// convert an integer to an array of bytes
function int_2_bytes( x ){
var bytes = [];
var i = 4;
do {
bytes[--i] = x & (255);
x = x>>8;
} while (i);
return bytes;
}
/*
* Extended Power Management
* update and search are in seconds
*
* 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 */
// little endian, lsb first
//0x30, 0x75, 0x00, 0x00, /* update period, ms, 120s=00 01 D4 C0, 30s= 00 00 75 30 */
//0x88, 0x13, 0x00, 0x00, /* search period, ms, 120s, 20s = 00 00 4E 20, 5s = 13 88 */
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 */
}
function onGPS(fix) {
console.log(fix);
}
function setupGPS() {
Bangle.setGPSPower(1);
UBX_CFG_RESET();
wait(100);
//UBX_CFG_RESTORE_NMEA();
//wait(20);
UBX_CFG_PM2(120,5);
wait(20);
UBX_CFG_RXM();
wait(20);
UBX_CFG_SAVE();
wait(20);
Bangle.on('GPS',onGPS);
}
function test_gps_on() {
var settings = WIDGETS.gpsservice.gps_get_settings();
settings.gpsservice = true;
settings.update = 65;
settings.search = 5;
settings.power_mode = "PSMOO";
WIDGETS.gpsservice.gps_set_settings(settings);
WIDGETS.gpsservice.reload(); // will power on
}
function test_gps_off() {
var settings = WIDGETS.gpsservice.gps_get_settings();
settings.gpsservice = false;
settings.power_mode = "SuperE";
WIDGETS.gpsservice.gps_set_settings(settings);
WIDGETS.gpsservice.reload(); // will power off
}
// test_gps_on();
// test_gps_off();

283
apps/gpsservice/widget.js Normal file
View File

@ -0,0 +1,283 @@
(() => {
var settings = {};
var fixToggle = false; // toggles once for each reading
var have_fix = false;
var debug = false;
var last_fix = {
fix: 0,
alt: 0,
lat: 0,
lon: 0,
speed: 0,
time: 0,
satellites: 0
};
function gps_get_fix() { return last_fix; }
function gps_get_status() { return WIDGETS.gpsservice.width === 24 ? true : false;}
function gps_get_version() { return "0.03"; }
function log_debug(o) {
if (debug) console.log(o);
}
function gps_set_debug(v) {
debug = v;
}
// Called by the GPS widget settings to reload settings and decide what to do
function reload() {
settings = gps_get_settings();
log_debug(settings);
Bangle.removeListener('GPS',onGPS);
if (settings.gpsservice) {
gps_power_on();
} else {
gps_power_off();
}
}
// retrieve the settings from Storage, can be called by external apps
function gps_get_settings() {
var sets = require("Storage").readJSON("gpsservice.settings.json",1)||{};
sets.gpsservice = sets.gpsservice||false;
sets.update = sets.update||120;
sets.search = sets.search||5;
sets.power_mode = sets.power_mode||"SuperE";
return sets;
}
// pass in the required settings, can be called by external apps
function gps_set_settings(sets) {
settings.gpsservice = sets.gpsservice||false;
settings.update = sets.update||120;
settings.search = sets.search||5;
settings.power_mode = sets.power_mode||"SuperE";
require("Storage").write("gpsservice.settings.json", settings);
}
// issue: currently possible to call this without having set settings.gpsservice in settings file
function gps_power_on() {
have_fix = false;
fixToggle = false;
setupGPS();
WIDGETS.gpsservice.width = 24;
}
function gps_power_off() {
//setupSuperE(); // return to expected setup for other apps
Bangle.setGPSPower(0);
have_fix = false;
fixToggle = false;
last_fix.fix = 0;
WIDGETS.gpsservice.width = 0;
}
// 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();
}
Bangle.on('GPS',onGPS);
}
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<d.length;i++) {
a += d[i];
b += a;
}
d.push(a&255,b&255);
Serial1.write(d);
}
// UBX-CFG-PMS - enable power management - Super-E
function UBX_CFG_PMS() {
writeGPScmd([0x06,0x86, // msg class + type
8,0,//length
0x00,0x03, 0,0, 0,0, 0,0]);
}
// convert an integer to an array of bytes
function int_2_bytes( x ){
var bytes = [];
var i = 4;
do {
bytes[--i] = x & (255);
x = x>>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]);
}
// draw the widget
function draw() {
if (!settings.gpsservice) return;
g.reset();
g.drawImage(atob("GBgCAAAAAAAAAAQAAAAAAD8AAAAAAP/AAAAAAP/wAAAAAH/8C9AAAB/8L/QAAAfwv/wAAAHS//wAAAAL//gAAAAf/+AAAAAf/4AAAAL//gAAAAD/+DwAAAB/Uf8AAAAfA//AAAACAf/wAAAAAH/0AAAAAB/wAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),this.x,this.y);
if (gps_get_status() === true && have_fix) {
g.setColor("#00FF00");
g.drawImage(fixToggle ? atob("CgoCAAAAA0AAOAAD5AAPwAAAAAAAAAAAAAAAAA==") : atob("CgoCAAABw0AcOAHj5A8PwHwAAvgAB/wABUAAAA=="),this.x,this.y+14);
} else {
g.setColor("#0000FF");
if (fixToggle) g.setFont("6x8").drawString("?",this.x,this.y+14);
}
}
function onGPS(fix) {
fixToggle = !fixToggle;
WIDGETS.gpsservice.draw();
log_debug(fix);
last_fix.satellites = fix.satellites;
/*
* If we have a fix record it, we will get another soon. Apps
* will see the timestamp of the last fix and be able to work out
* if it is stale. This means an App will always have the last
* known fix, and we avoid saying no fix all the time.
*
*/
if (fix.fix) {
last_fix.fix = fix.fix;
last_fix.alt = fix.alt;
last_fix.lat = fix.lat;
last_fix.lon = fix.lon;
last_fix.speed = fix.speed;
last_fix.time = fix.time;
}
}
// redraw when the LCD turns on
Bangle.on('lcdPower', function(on) {
if (on) WIDGETS.gpsservice.draw();
});
// add the widget
WIDGETS.gpsservice = {
area:"tl",
width:24,
draw:draw,
gps_power_on:gps_power_on,
gps_power_off:gps_power_off,
gps_get_status:gps_get_status,
gps_get_fix:gps_get_fix,
gps_get_version:gps_get_version,
gps_get_settings:gps_get_settings,
gps_set_settings:gps_set_settings,
gps_set_debug:gps_set_debug,
reload:function() {
reload();
Bangle.drawWidgets(); // relayout all widgets
}};
// load settings, set correct widget width
reload();
})();

6
apps/intervals/README.md Normal file
View File

@ -0,0 +1,6 @@
# Intervals app
Just my first app to test this amazing smartwatch.
The idea is to create a simple app which allows to define a workout, being able to configure the work time, rest time and number of sets. Try it out, it is quite simple.
This is obviously version 0.01 but it should work.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwg96xAWVhGIwAuWGCoXrCIZfFDZkJyAKTAAOZBaojEzIADGBoOChITEAgIUCGBAhCCwIlFA4YwIhIjCDQYtDAoIMBQhgUCzJMDR5xYGFwSPMHwYRED4KPMCQcIxKtHWApoCEYczmYXCPgYAEGgZGCAgMDC4pEGA4gVBGwIWCF6GqhWgC4kqBYYAGGgegC4QWCC4kKI4oQBC4guEC4wJDC4gtBGoIXDmWqBoQMBBQUwIAQXJKopSDJooXGmQRBBwYLBFgR2EC4QAEC4RIDToRQDC56pESYhHDAAYFBA4YvMD4oOEL5bMGaJIQFC5ZiEAoxRCC5I1KJoIXVBYJ4FEYgAGPAwwHSYIAGJAwwHEA5+HB5AvMZI4XKBwu72ELAQIHCAoQCBchQMCAQIADDgIfEDBYRBAAI5DCxYUDAxYYMAAgWPDIoVSAFAA=="))

View File

@ -0,0 +1,167 @@
var counter;
var counterInterval;
var settings;
var work=false; //true if training false if resting
var prep=true; //true if we are in prep phase (at the beginning of the session
var setNum;
var paused;
function endPart() {
g.clear();
if (prep){
prep=false;
work = true;
counter = settings.workseg+settings.workmin*60;
startCounter();
}else if (work){
work = false;
counter = settings.restseg+settings.restmin*60;
startCounter();
}
else{
if (setNum>1)
{
setNum--;
work = true;
counter = settings.workseg+settings.workmin*60;
startCounter();
}
else
{
//End session
setWatch(showMenu, BTN2);
E.showMessage("Press BTN2 for\ngoing back\nto the menu","Well done!");
}
}
}
function printCounter(counter){
g.setFontAlign(0,0); // center font
g.setFont("Vector",80); // vector font, 80px
// draw the current counter value
let minutes = Math.floor(counter/60);
let segs = counter % 60;
let textMinutes = minutes.toString().padStart(2, "0");
let textSegs = segs.toString().padStart(2,"0");
g.clearRect(0,80,239,160);
g.setFont("Vector",30);
if (prep)
g.setColor(125,125,0);
else if (work)
g.setColor(255,0,0);
else
g.setColor(0,255,0);
g.drawString(setNum,120,50);
if (prep)
g.drawString("PREPARATION",120,190);
else if (work)
g.drawString("WORK",120,190);
else
g.drawString("REST",120,190);
g.setFont("Vector",10);
g.drawString("Touch to pause",120,215);
g.setFont("Vector",80);
g.drawString(textMinutes+":"+textSegs,120,120);
}
function signal_to_user(le){
if (settings.buzz)
Bangle.buzz(le);
else
Bangle.beep(le,4000);
}
function countDown() {
counter--;
if (counter<4 && counter>0)
signal_to_user(200);
if (counter==0)
signal_to_user(600);
if (counter<0) {
clearInterval(counterInterval);
counterInterval = undefined;
endPart();
}
else
{
printCounter(counter);
}
g.flip();
}
function startCounter(){
if (!counterInterval)
counterInterval = setInterval(countDown, 1000);
}
function pause()
{
if (!paused)
{
clearInterval(counterInterval);
counterInterval = undefined;
setWatch(resume,BTN4);
setWatch(resume,BTN5);
paused=true;
g.clear();
g.setFont("Vector",60);
g.drawString("PAUSED",120,120);
g.setFont("Vector",20);
g.drawString("Touch to continue",120,180);
}
}
function resume(){
if (paused)
{
g.clear();
counterInterval = setInterval(countDown, 1000);
setWatch(pause,BTN4);
setWatch(pause,BTN5);
paused=false;
}
}
function startSession() {
E.showMenu();
paused=false;
setWatch(pause,BTN4);
setWatch(pause,BTN5);
require('Storage').write('intervals.settings.json',settings);
setNum = settings.sets;
counter = 5;//prep time
prep=true;
work=false;
startCounter();
}
function showMenu()
{
settings = require('Storage').readJSON('intervals.settings.json',1)||
{ sets:1,workseg:10,workmin:0,restseg:5,restmin:0,buzz:true};
var mainmenu = {
"" : { "title" : "-- Main Menu --" },
"START" : function() { startSession(); },
"Sets" : { value : settings.sets,min:0,max:20,step:1,onchange : v => { settings.sets=v; } },
"Work minutes" : { value : settings.workmin,min:0,max:59,step:1,onchange : v => { settings.workmin=v; } },
"Work seconds" : { value : settings.workseg,min:0,max:59,step:5,onchange : v => { settings.workseg=v; } },
"Rest minutes" : { value : settings.restmin,min:0,max:59,step:1,onchange : v => { settings.restmin=v; } },
"Rest seconds" : { value : settings.restseg,min:0,max:59,step:5,onchange : v => { settings.restseg=v; } },
"Signal type" : { value : settings.buzz,format : v => v?"Buzz":"Beep",onchange : v => { settings.buzz=v; }}
};
E.showMenu(mainmenu);
}
showMenu();

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1 @@
1.0: Created app

21
apps/mclockplus/README.md Normal file
View File

@ -0,0 +1,21 @@
# Morphing Clock Plus
Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch.
![](Screenshot.JPG)
## Usage
In addition to the Morphing Clock, a simple stopwatch can be started in the lower part of the display.
BTN3 starts and stops the stopwatch.
BTN1 resets/clears the stopwatch.
## Requests
Please leave bug reports and requests by raising an issue [here](https://github.com/skauertz/BangleApps).
## Creator
Sebastian Kauertz https://github.com/skauertz

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A="))

View File

@ -0,0 +1,318 @@
// Morphing Clock +
// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch
// Icon by https://icons8.com
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
var locale = require("locale");
var CHARW = 28; // how tall are digits?
var CHARP = 2; // how chunky are digits?
var Y = 50; // start height
// Offscreen buffer
var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true});
var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer};
// The last time that we displayed
var lastTime = "-----";
// If animating, this is the interval's id
var animInterval;
var timeInterval;
// Variables for the stopwatch
var counter = -1; // Counts seconds
var oldDate = new Date(2020,0,1); // Initialize to a past date
var swInterval; // The interval's id
var B3 = 0; // Flag to track BTN3's current function
var w1; // watch id for BTN1
var w3; // watch id for BTN3
/* Get array of lines from digit d to d+1.
n is the amount (0..1)
maxFive is true is this digit only counts 0..5 */
const DIGITS = {
" ":n=>[],
"0":n=>[
[n,0,1,0],
[1,0,1,1],
[1,1,1,2],
[n,2,1,2],
[n,1,n,2],
[n,0,n,1]],
"1":n=>[
[1-n,0,1,0],
[1,0,1,1],
[1-n,1,1,1],
[1-n,1,1-n,2],
[1-n,2,1,2]],
"2":n=>[
[0,0,1,0],
[1,0,1,1],
[0,1,1,1],
[0,1+n,0,2],
[1,2-n,1,2],
[0,2,1,2]],
"3":n=>[
[0,0,1-n,0],
[0,0,0,n],
[1,0,1,1],
[0,1,1,1],
[1,1,1,2],
[n,2,1,2]],
"4":n=>[
[0,0,0,1],
[1,0,1-n,0],
[1,0,1,1-n],
[0,1,1,1],
[1,1,1,2],
[1-n,2,1,2]],
"5to0": n=>[ // 5 -> 0
[0,0,0,1],
[0,0,1,0],
[n,1,1,1],
[1,1,1,2],
[0,2,1,2],
[0,2,0,2],
[1,1-n,1,1],
[0,1,0,1+n]],
"5to6": n=>[ // 5 -> 6
[0,0,0,1],
[0,0,1,0],
[0,1,1,1],
[1,1,1,2],
[0,2,1,2],
[0,2-n,0,2]],
"6":n=>[
[0,0,0,1-n],
[0,0,1,0],
[n,1,1,1],
[1,1-n,1,1],
[1,1,1,2],
[n,2,1,2],
[0,1-n,0,2-2*n]],
"7":n=>[
[0,0,0,n],
[0,0,1,0],
[1,0,1,1],
[1-n,1,1,1],
[1,1,1,2],
[1-n,2,1,2],
[1-n,1,1-n,2]],
"8":n=>[
[0,0,0,1],
[0,0,1,0],
[1,0,1,1],
[0,1,1,1],
[1,1,1,2],
[0,2,1,2],
[0,1,0,2-n]],
"9":n=>[
[0,0,0,1],
[0,0,1,0],
[1,0,1,1],
[0,1,1-n,1],
[0,1,0,1+n],
[1,1,1,2],
[0,2,1,2]],
":":n=>[
[0.4,0.4,0.6,0.4],
[0.6,0.4,0.6,0.6],
[0.6,0.6,0.4,0.6],
[0.4,0.4,0.4,0.6],
[0.4,1.4,0.6,1.4],
[0.6,1.4,0.6,1.6],
[0.6,1.6,0.4,1.6],
[0.4,1.4,0.4,1.6]]
};
/* Draw a transition between lastText and thisText.
'n' is the amount - 0..1 */
function drawDigits(lastText,thisText,n) {
"ram"
const p = CHARP; // padding around digits
const s = CHARW; // character size
var x = 16; // x offset
g.reset();
for (var i=0;i<lastText.length;i++) {
var lastCh = lastText[i];
var thisCh = thisText[i];
if (thisCh==":") x-=4;
if (lastCh!=thisCh) {
var ch, chn = n;
if ((thisCh-1==lastCh ||
(thisCh==0 && lastCh==5) ||
(thisCh==0 && lastCh==9)))
ch = lastCh;
else {
ch = thisCh;
chn = 0;
}
buf.clear();
if (ch=="5") ch = (lastCh==5 && thisCh==0)?"5to0":"5to6";
var l = DIGITS[ch](chn);
l.forEach(c=>{
if (c[0]!=c[2]) // horiz
buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s);
else if (c[1]!=c[3]) // vert
buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s);
});
g.drawImage(bufimg,x,Y);
}
if (thisCh==":") x-=4;
x+=s+p+7;
}
}
function drawDate() {
var x = (CHARW + CHARP + 8)*5;
var y = Y + 2*CHARW + CHARP;
var d = new Date();
// meridian
g.reset();
g.setFont("6x8",2);
g.setFontAlign(-1,-1);
if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true);
// date
g.setFont("Vector16");
g.setFontAlign(0,-1);
// Only draw the date if it has changed:
if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) {
var date = locale.date(d,false);
g.clearRect(1,y+8,g.getWidth(),y+24);
g.drawString(date, g.getWidth()/2, y+8, true);
oldDate = d;
}
}
function drawSeconds() {
var x = (CHARW + CHARP + 8)*5;
var y = Y + 2*CHARW + CHARP;
var d = new Date();
// seconds
g.reset();
g.setFont("6x8",2);
g.setFontAlign(-1,-1);
g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true);
}
/* Show the current time, and animate if needed */
function showTime() {
if (animInterval) return; // in animation - quit
var d = new Date();
var hours = d.getHours();
if (is12Hour) hours = ((hours + 11) % 12) + 1;
var t = (" "+hours).substr(-2)+":"+
("0"+d.getMinutes()).substr(-2);
var l = lastTime;
// same - don't animate
if (t==l || l=="-----") {
drawDigits(l,t,0);
drawDate();
drawSeconds();
lastTime = t;
return;
}
var n = 0;
animInterval = setInterval(function() {
n += 1/10;
if (n>=1) {
n=1;
clearInterval(animInterval);
animInterval = undefined;
}
drawDigits(l,t,n);
drawSeconds();
}, 20);
lastTime = t;
}
function stopWatch() {
counter++;
var hrs = Math.floor(counter/3600);
var mins = Math.floor((counter-hrs*3600)/60);
var secs = counter - mins*60 - hrs*3600;
// When starting the stopwatch:
if (B3) {
// Set BTN3 to stop the stopwatch and bind itself to restart it:
w3=setWatch(() => {clearInterval(swInterval);
swInterval=undefined;
if (w3) {clearWatch(w3);w3=undefined;}
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();},
BTN3, {repeat:false,edge:"falling"});
B3 = 1;},
BTN3, {repeat:false,edge:"falling"});
B3 = 0; // BTN3 is bound to stop the stopwatch
}
// Bind BTN1 to call the reset function:
if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"});
// Draw elapsed time:
g.reset();
g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24");
g.clearRect(1,180,g.getWidth(),210);
if (hrs>0) {
g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true);
g.drawString( ":", g.getWidth()/2 - 48, 180, true);
}
g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true);
g.drawString( ":", g.getWidth()/2, 180, true);
g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true);
}
function resetStopWatch() {
// Stop the interval if necessary:
if (swInterval) {
clearInterval(swInterval);
swInterval=undefined;
}
// Clear the stopwatch:
g.clearRect(1,180,g.getWidth(),210);
// Reset the counter:
counter = -1;
// Set BTN3 to start the stopwatch again:
if (!B3) {
// In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually:
if (w3) {clearWatch(w3);w3=undefined;}
// Set BTN3 to start the watch again:
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
B3 = 1; // BTN3 is bound to start the stopwatch
}
// Reset watch on BTN1:
if (w1) {clearWatch(w1);w1=undefined;}
}
Bangle.on('lcdPower',function(on) {
if (animInterval) {
clearInterval(animInterval);
animInterval = undefined;
}
if (timeInterval) {
clearInterval(timeInterval);
timeInterval = undefined;
}
if (on) {
showTime();
timeInterval = setInterval(showTime, 1000);
} else {
lastTime = "-----";
}
});
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
// Update time once a second
timeInterval = setInterval(showTime, 1000);
showTime();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
// Start stopwatch when BTN3 is pressed
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
B3 = 1; // BTN3 is bound to start the stopwatch

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -6,7 +6,6 @@
0.06: Add txt clock
0.07: Add Time Date clock and fix font sizes
0.08: Add pinned clock face
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

View File

@ -13,6 +13,9 @@
{"name":"ana.face.js","url":"ana.min.js"},
{"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}
]
},

95
apps/multiclock/gps.js Normal file
View File

@ -0,0 +1,95 @@
(() => {
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;
})();

202
apps/multiclock/osref.js Normal file
View File

@ -0,0 +1,202 @@
(() => {
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;
})();

41
apps/multiclock/ped.js Normal file
View File

@ -0,0 +1,41 @@
(() => {
function getFace(){
function draw() {
let steps = "-";
let show_steps = false;
// only attempt to get steps if activepedom is loaded
if (WIDGETS.activepedom !== undefined) {
steps = WIDGETS.activepedom.getSteps();
} else if (WIDGETS.wpedom !== undefined) {
steps = WIDGETS.wpedom.getSteps();
}
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.setFont("Vector", 80);
g.setColor(1,1,1); // white
g.setFontAlign(0, -1);
g.drawString(time, g.getWidth()/2, 60);
g.setColor(0,255,0); // green
g.setFont("Vector", 60);
g.drawString(steps, g.getWidth()/2, 160);
}
function onSecond(){
var t = new Date();
if ((t.getSeconds() % 5) === 0) draw();
}
return {init:draw, tick:onSecond};
}
return getFace;
})();

View File

@ -2,3 +2,4 @@
0.05: Stop hours being displayed wrong if moving from 2 digits to 1 (fix #516)
0.06: Tweak sizing to allow widgets at top, and add widgets (fix #567)
0.07: Added leading zero to hours and minutes
0.08: Show step count by calling wpedom.getSteps() or activepedom.getSteps()

View File

@ -40,6 +40,7 @@ function drawTimeDate() {
//We will create custom "Widgets" for our face.
function drawSteps() {
var steps = "-";
//Reset to defaults.
g.reset();
// draw the date (2x size 7 segment)
@ -48,7 +49,12 @@ function drawSteps() {
g.setFontAlign(-1,0); // align right bottom
g.drawString("STEPS", 145, 40, true /*clear background*/);
g.setColor('#bdc3c7');
g.drawString("-", 145, 65, true /*clear background*/);
if (WIDGETS.activepedom !== undefined) {
steps = WIDGETS.activepedom.getSteps();
} else if (WIDGETS.wpedom !== undefined) {
steps = WIDGETS.wpedom.getSteps();
}
g.drawString(steps, 145, 65, true /*clear background*/);
}
function drawBPM(on) {
@ -114,6 +120,7 @@ Bangle.on('lcdPower',on=>{
//Screen on
drawBPM(HRMstate);
drawTimeDate();
drawSteps();
drawBattery();
} else {
//Screen off
@ -133,14 +140,14 @@ Bangle.on('touch', function(button) {
//HRM Controller.
setWatch(function(){
if(!HRMstate){
console.log("Toggled HRM");
//console.log("Toggled HRM");
//Turn on.
Bangle.buzz();
Bangle.setHRMPower(1);
currentHRM = "CALC";
HRMstate = true;
} else if(HRMstate){
console.log("Toggled HRM");
//console.log("Toggled HRM");
//Turn off.
Bangle.buzz();
Bangle.setHRMPower(0);
@ -153,7 +160,7 @@ setWatch(function(){
Bangle.on('HRM', function(hrm) {
if(hrm.confidence > 90){
/*Do more research to determine effect algorithm for heartrate average.*/
console.log(hrm.bpm);
//console.log(hrm.bpm);
currentHRM = hrm.bpm;
drawBPM(HRMstate);
}

View File

@ -7,3 +7,4 @@
0.08: Ensure redrawing works with variable size widget system
0.09: Add daily goal
0.10: Fix daily goal, don't store settings in separate file
0.11: added getSteps() method for apps to retrieve step count

View File

@ -112,7 +112,11 @@
});
// add your widget
WIDGETS["wpedom"]={area:"tl",width:26,draw:draw,reload:reload};
WIDGETS["wpedom"]={area:"tl",width:26,
draw:draw,
reload:reload,
getSteps:()=>stp_today
};
// Load data at startup
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
if (pedomData) {

2
core

@ -1 +1 @@
Subproject commit 8bfdeebf705ced95699dcbbccfa05a99e7d3f4a9
Subproject commit 1b1293a5eb9b8bb9e4f743c4599f0587f597d368

View File

@ -32,3 +32,6 @@
top: 36px;
left: -24px;
}
.btn-favourite {
color: red;
}