mirror of https://github.com/espruino/BangleApps
Merge pull request #640 from hughbarney/master
low power configurable gpsservice widget, gpsservice settings app and example client watch faces for osrefpull/641/head
commit
a035719f76
24
apps.json
24
apps.json
|
@ -2139,7 +2139,7 @@
|
|||
{ "id": "multiclock",
|
||||
"name": "Multi Clock",
|
||||
"icon": "multiclock.png",
|
||||
"version":"0.08",
|
||||
"version":"0.10",
|
||||
"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}
|
||||
]
|
||||
},
|
||||
|
@ -2616,6 +2619,21 @@
|
|||
{"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.01",
|
||||
"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}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# 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.
|
||||
|
||||

|
||||
|
||||
|
||||
## 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();
|
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA="))
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
|
@ -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,230 @@
|
|||
/*
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
(() => {
|
||||
var settings = {};
|
||||
var fixToggle = false; // toggles once for each reading
|
||||
var have_fix = 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.1"; }
|
||||
|
||||
// Called by the GPS widget settings to reload settings and decide what to do
|
||||
function reload() {
|
||||
settings = require("Storage").readJSON("gpsservice.settings.json",1)||{};
|
||||
settings.gpsservice = settings.gpsservice||false;
|
||||
settings.update = settings.update||120;
|
||||
settings.search = settings.search||5;
|
||||
settings.power_mode = settings.power_mode||"SuperE";
|
||||
//console.log(settings);
|
||||
|
||||
Bangle.removeListener('GPS',onGPS);
|
||||
|
||||
if (settings.gpsservice) {
|
||||
gps_power_on();
|
||||
} else {
|
||||
gps_power_off();
|
||||
}
|
||||
}
|
||||
|
||||
function gps_power_on() {
|
||||
have_fix = false;
|
||||
fixToggle = false;
|
||||
setupGPS();
|
||||
WIDGETS.gpsservice.width = 24;
|
||||
}
|
||||
|
||||
function gps_power_off() {
|
||||
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);
|
||||
//console.log(settings);
|
||||
|
||||
if (settings.power_mode === "PSMOO") {
|
||||
//console.log("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);
|
||||
} else {
|
||||
//console.log("setupGPS() Super-E");
|
||||
UBX_CFG_RESET();
|
||||
wait(100);
|
||||
|
||||
UBX_CFG_PMS();
|
||||
wait(20);
|
||||
|
||||
UBX_CFG_SAVE();
|
||||
wait(20);
|
||||
}
|
||||
Bangle.on('GPS',onGPS);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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,
|
||||
reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
}};
|
||||
|
||||
// load settings, set correct widget width
|
||||
reload();
|
||||
|
||||
})();
|
||||
|
|
@ -6,7 +6,5 @@
|
|||
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
|
||||
|
|
|
@ -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,43 @@
|
|||
(() => {
|
||||
|
||||
function getFace(){
|
||||
|
||||
function draw() {
|
||||
let steps = -1;
|
||||
let show_steps = false;
|
||||
|
||||
// only attempt to get steps if activepedom is loaded
|
||||
if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
show_steps = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (show_steps) {
|
||||
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;
|
||||
|
||||
})();
|
Loading…
Reference in New Issue