Merge branch 'master' into master
82
apps.json
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
Alpine Navigator
|
||||
================
|
||||
App that performs GPS monitoring to track and display position relative to a given origin in realtime.
|
||||
|
||||

|
||||
|
||||
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.
|
|
@ -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"))
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -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);
|
||||
});
|
After Width: | Height: | Size: 706 KiB |
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"})}();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 %
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Cli Clock
|
||||
|
||||
A retro VT100 command line style clock
|
||||
|
||||
## Screenshots
|
||||
### Normall face
|
||||
|
||||

|
||||
|
||||
### With BTN1 pressed
|
||||
|
||||
* Successive presses of BTN1 will show id, FW version, battery %, memory %
|
||||
|
||||

|
|
@ -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});
|
||||
|
|
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 130 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AH4A/ADsBiIABBIoICiAWJim73dEDAkRogJBoIYIBoIACBwYgBBIY6GAAINBF4VLoIIC2gJDBARGFCYINBBwO0iEBpYWC3YbBJA0B2gNCBwNLiIuBEAe7pYXGIwYwDoIIIC4wlEGAQuFBAIXGjYOFE4IuFC4OxC5oAHC+6GGABCPIC6zXFIxLXHgNEMBm0ogXGPAJIMoh2GMAWxGBQuBLwxIOIxKRNFxIABiIwJFwMRC5MBGAIYGLoRGJGAZJGoIuLGBIuODAVLDAYWB2gWNSQQTBRYVLRhRJHDAIWCFx5JEIqKTEpe0RZhiJLiBJGIqYA/AH4A3A"))
|
|
@ -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" });
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -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
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
|
@ -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();
|
After Width: | Height: | Size: 129 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA="))
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 122 KiB |
|
@ -0,0 +1,4 @@
|
|||
(function(back) {
|
||||
// just go right to our app
|
||||
load("gpsservice.app.js");
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
{"gpsservice":false, "power_mode":"SuperE", "update":120, "search":6}
|
|
@ -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();
|
||||
|
|
@ -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();
|
||||
|
||||
})();
|
||||
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwg96xAWVhGIwAuWGCoXrCIZfFDZkJyAKTAAOZBaojEzIADGBoOChITEAgIUCGBAhCCwIlFA4YwIhIjCDQYtDAoIMBQhgUCzJMDR5xYGFwSPMHwYRED4KPMCQcIxKtHWApoCEYczmYXCPgYAEGgZGCAgMDC4pEGA4gVBGwIWCF6GqhWgC4kqBYYAGGgegC4QWCC4kKI4oQBC4guEC4wJDC4gtBGoIXDmWqBoQMBBQUwIAQXJKopSDJooXGmQRBBwYLBFgR2EC4QAEC4RIDToRQDC56pESYhHDAAYFBA4YvMD4oOEL5bMGaJIQFC5ZiEAoxRCC5I1KJoIXVBYJ4FEYgAGPAwwHSYIAGJAwwHEA5+HB5AvMZI4XKBwu72ELAQIHCAoQCBchQMCAQIADDgIfEDBYRBAAI5DCxYUDAxYYMAAgWPDIoVSAFAA=="))
|
|
@ -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();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1 @@
|
|||
1.0: Created app
|
|
@ -0,0 +1,21 @@
|
|||
# Morphing Clock Plus
|
||||
|
||||
Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch.
|
||||
|
||||

|
||||
|
||||
## 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
|
After Width: | Height: | Size: 41 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A="))
|
|
@ -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
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
||||
})();
|
|
@ -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;
|
||||
|
||||
})();
|
|
@ -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;
|
||||
|
||||
})();
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -32,3 +32,6 @@
|
|||
top: 36px;
|
||||
left: -24px;
|
||||
}
|
||||
.btn-favourite {
|
||||
color: red;
|
||||
}
|
||||
|
|