Merge remote-tracking branch 'upstream/master'

merged in upstream changes
pull/643/head
hughbarney 2021-01-26 20:12:56 +00:00
commit 63131d3133
18 changed files with 672 additions and 19 deletions

View File

@ -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",
@ -2585,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",
@ -2595,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",
@ -2635,5 +2647,19 @@
{"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}
]
}
]

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

@ -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