Merge pull request #640 from hughbarney/master

low power configurable gpsservice widget, gpsservice settings app and example client watch faces for osref
pull/641/head
Gordon Williams 2021-01-22 08:07:05 +00:00 committed by GitHub
commit a035719f76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1000 additions and 7 deletions

View File

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

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

@ -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.
![](gps_face.jpg)
### Grid Reference Watch face
* The time shown is the timestamp of the last position fix.
* The age value is shown at the bottom of the screen.
![](osref_face.jpg)
## To Do List
* add a logging option with options for interval between log points
* add graphics and icons to the watch faces to make them look nicer
## References
* [UBLOX M8 Receiver Data Sheet](https://www.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_%28UBX-13003221%29.pdf)
* [UBLOX Power Management App Note](https://www.u-blox.com/sites/default/files/products/documents/PowerManagement_AppNote_%28UBX-13005162%29.pdf)
* Some useful code on Github and be found [here](https://portal.u-blox.com/s/question/0D52p0000925T00CAE/ublox-max-m8q-getting-stuck-when-sleeping-with-extint-pin-control)
and [here](https://github.com/thasti/utrak/blob/master/gps.c)

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

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

View File

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

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

@ -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);
}

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

@ -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();
})();

View File

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

View File

@ -13,6 +13,9 @@
{"name":"ana.face.js","url":"ana.min.js"},
{"name":"digi.face.js","url":"digi.min.js"},
{"name":"txt.face.js","url":"txt.min.js"},
{"name":"ped.face.js","url":"ped.js"},
{"name":"osref.face.js","url":"osref.js"},
{"name":"gps.face.js","url":"pgs.js"},
{"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true}
]
},

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

@ -0,0 +1,95 @@
(() => {
function getFace(){
//var img = require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA="));
var nofix = 0;
function formatTime(now) {
var fd = now.toUTCString().split(" ");
return fd[4];
}
function timeSince(t) {
var hms = t.split(":");
var now = new Date();
var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds());
var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]);
return (sn - st);
}
function draw() {
var gps_on = false;
var fix = {
fix: 0,
alt: 0,
lat: 0,
lon: 0,
speed: 0,
time: 0,
satellites: 0
};
var y_line = 26;
var y_start = 46;
var x_start = 10;
// only attempt to get gps fix if gpsservuce is loaded
if (WIDGETS.gpsservice !== undefined) {
fix = WIDGETS.gpsservice.gps_get_fix();
gps_on = WIDGETS.gpsservice.gps_get_status();
}
g.reset();
g.clearRect(0,24,239,239);
if (fix.fix) {
var time = formatTime(fix.time);
var age = timeSince(time);
g.setFontAlign(-1, -1);
g.setFont("6x8");
g.setFontVector(22);
g.drawString("Alt: " + fix.alt +" m", x_start, y_start, true);
g.drawString("Lat: "+ fix.lat, x_start, y_start + y_line, true);
g.drawString("Lon: " + fix.lon, x_start, y_start + 2*y_line, true);
g.drawString("Time: " + time, x_start, y_start + 3*y_line, true);
g.drawString("Age(s): " + age, x_start, y_start + 4*y_line, true);
g.drawString("Satellites: " + fix.satellites, x_start, y_start + 5*y_line, true);
} else if (gps_on) {
g.setFontAlign(0, 1);
g.setFont("6x8", 2);
g.drawString("GPS Watch", 120, 60);
g.drawString("Waiting for GPS", 120, 80);
nofix = (nofix+1) % 4;
g.drawString(".".repeat(nofix) + " ".repeat(4-nofix), 120, 120);
g.setFontAlign(0,0);
g.drawString(fix.satellites + " satellites", 120, 100);
} else if (!gps_on) {
g.setFontAlign(0, 0);
g.setFont("6x8", 3);
g.drawString("GPS Watch", 120, 80);
g.drawString("GPS is off",120, 160);
}
}
function onSecond(){
var t = new Date();
if ((t.getSeconds() % 5) === 0) draw();
}
return {init:draw, tick:onSecond};
}
return getFace;
})();

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

@ -0,0 +1,202 @@
(() => {
function getFace(){
var nofix = 0;
function formatTime(now) {
var fd = now.toUTCString().split(" ");
return fd[4];
}
function timeSince(t) {
var hms = t.split(":");
var now = new Date();
var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds());
var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]);
return (sn - st);
}
Number.prototype.toRad = function() { return this*Math.PI/180; };
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */
/* - www.movable-type.co.uk/scripts/gridref.js */
/* - www.movable-type.co.uk/scripts/latlon-gridref.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
function OsGridRef(easting, northing) {
this.easting = 0|easting;
this.northing = 0|northing;
}
OsGridRef.latLongToOsGrid = function(point) {
var lat = point.lat.toRad();
var lon = point.lon.toRad();
var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
var eta2 = nu/rho-1;
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
var cos3lat = cosLat*cosLat*cosLat;
var cos5lat = cos3lat*cosLat*cosLat;
var tan2lat = Math.tan(lat)*Math.tan(lat);
var tan4lat = tan2lat*tan2lat;
var I = M + N0;
var II = (nu/2)*sinLat*cosLat;
var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
var IV = nu*cosLat;
var V = (nu/6)*cos3lat*(nu/rho-tan2lat);
var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
var dLon = lon-lon0;
var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon;
var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6;
var E = E0 + IV*dLon + V*dLon3 + VI*dLon5;
return new OsGridRef(E, N);
};
/*
* converts northing, easting to standard OS grid reference.
*
* [digits=10] - precision (10 digits = metres)
* to_map_ref(8, 651409, 313177); => 'TG 5140 1317'
* to_map_ref(0, 651409, 313177); => '651409,313177'
*
*/
function to_map_ref(digits, easting, northing) {
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
let e = easting;
let n = northing;
// use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7
if (digits == 0) {
const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 };
const ePad = e.toLocaleString('en', format);
const nPad = n.toLocaleString('en', format);
return `${ePad},${nPad}`;
}
// get the 100km-grid indices
const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000);
// translate those into numeric equivalents of the grid letters
let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5);
let l2 = (19 - n100km) * 5 % 25 + e100km % 5;
// compensate for skipped 'I' and build grid letter-pairs
if (l1 > 7) l1++;
if (l2 > 7) l2++;
const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0));
// strip 100km-grid indices from easting & northing, and reduce precision
e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2));
n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2));
// pad eastings & northings with leading zeros
e = e.toString().padStart(digits/2, '0');
n = n.toString().padStart(digits/2, '0');
return `${letterPair} ${e} ${n}`;
}
function draw() {
var gps_on = false;
var fix = {
fix: 0,
alt: 0,
lat: 0,
lon: 0,
speed: 0,
time: 0,
satellites: 0
};
var y_line = 26;
var y_start = 46;
var x_start = 10;
// only attempt to get gps fix if gpsservuce is loaded
if (WIDGETS.gpsservice !== undefined) {
fix = WIDGETS.gpsservice.gps_get_fix();
gps_on = WIDGETS.gpsservice.gps_get_status();
}
g.reset();
g.clearRect(0,24,239,239);
if (fix.fix) {
var time = formatTime(fix.time);
var age = timeSince(time);
var os = OsGridRef.latLongToOsGrid(fix);
var ref = to_map_ref(6, os.easting, os.northing);
age = age >=0 ? age : 0; // avoid -1 etc
g.reset();
g.clearRect(0,24,239,239);
g.setFontAlign(0,0);
g.setFont("Vector");
g.setColor(1,1,1);
g.setFontVector(40);
g.drawString(time, 120, 80);
g.setFontVector(40);
g.setColor(0xFFC0);
g.drawString(ref, 120,140, true);
g.setFont("6x8",2);
g.setColor(1,1,1);
g.drawString(age, 120, 194);
} else if (gps_on) {
g.setFontAlign(0, 1);
g.setFont("6x8", 2);
g.drawString("Gridref Watch", 120, 60);
g.drawString("Waiting for GPS", 120, 80);
nofix = (nofix+1) % 4;
g.drawString(".".repeat(nofix) + " ".repeat(4-nofix), 120, 120);
g.setFontAlign(0,0);
g.drawString(fix.satellites + " satellites", 120, 100);
} else if (!gps_on) {
g.setFontAlign(0, 0);
g.setFont("6x8", 3);
g.drawString("Gridref Watch", 120, 80);
g.drawString("GPS is off", 120, 160);
}
}
function onSecond(){
var t = new Date();
if ((t.getSeconds() % 5) === 0) draw();
}
return {init:draw, tick:onSecond};
}
return getFace;
})();

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

@ -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;
})();