diff --git a/apps.json b/apps.json index 50b669541..9c09d6050 100644 --- a/apps.json +++ b/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} + ] +} ] diff --git a/apps/gpsservice/README.md b/apps/gpsservice/README.md new file mode 100644 index 000000000..5234e0cad --- /dev/null +++ b/apps/gpsservice/README.md @@ -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) + diff --git a/apps/gpsservice/app.js b/apps/gpsservice/app.js new file mode 100644 index 000000000..14bc48938 --- /dev/null +++ b/apps/gpsservice/app.js @@ -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(); diff --git a/apps/gpsservice/gps_face.jpg b/apps/gpsservice/gps_face.jpg new file mode 100644 index 000000000..839d86895 Binary files /dev/null and b/apps/gpsservice/gps_face.jpg differ diff --git a/apps/gpsservice/gpsservice-icon.js b/apps/gpsservice/gpsservice-icon.js new file mode 100644 index 000000000..b3f2dd3d4 --- /dev/null +++ b/apps/gpsservice/gpsservice-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t3uJeVuMQCqcBLisAi4XLxAABFxAXKgc4DBAuBRhQXEDAq7MmYXEwBHEXZYXFGAOqAAKDMmczC4mIC62CC50PC4JIBkQABiIvRmURAAUSjQXSFwMoxGKC6CRFwUSVYgXLPIgXXwMYegoXLJAYXCGBnzGA0hPQIwMgYwGC6gwCC4ZIMC4gYBC604C4ZISmcRVgapQAAMhC6GIJIwXCMBcIxGDDBAuLC4IwGAARGMAAQWGmAXPJQoWMC4pwCCpoXJAB4XXAH4A/ABQA=")) diff --git a/apps/gpsservice/gpsservice.png b/apps/gpsservice/gpsservice.png new file mode 100644 index 000000000..970e85139 Binary files /dev/null and b/apps/gpsservice/gpsservice.png differ diff --git a/apps/gpsservice/osref_face.jpg b/apps/gpsservice/osref_face.jpg new file mode 100644 index 000000000..ff1cece7c Binary files /dev/null and b/apps/gpsservice/osref_face.jpg differ diff --git a/apps/gpsservice/settings.js b/apps/gpsservice/settings.js new file mode 100644 index 000000000..471c23163 --- /dev/null +++ b/apps/gpsservice/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app + load("gpsservice.app.js"); +})(); diff --git a/apps/gpsservice/settings.json b/apps/gpsservice/settings.json new file mode 100644 index 000000000..3d687f9c2 --- /dev/null +++ b/apps/gpsservice/settings.json @@ -0,0 +1 @@ +{"gpsservice":false, "power_mode":"SuperE", "update":120, "search":6} diff --git a/apps/gpsservice/test-bed.js b/apps/gpsservice/test-bed.js new file mode 100644 index 000000000..1872e8c11 --- /dev/null +++ b/apps/gpsservice/test-bed.js @@ -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>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(); + +})(); + diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index ac53da24b..ff26b5716 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -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 diff --git a/apps/multiclock/apps_entry.json b/apps/multiclock/apps_entry.json index 91ed12d6c..19b6bc43d 100644 --- a/apps/multiclock/apps_entry.json +++ b/apps/multiclock/apps_entry.json @@ -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} ] }, diff --git a/apps/multiclock/gps.js b/apps/multiclock/gps.js new file mode 100644 index 000000000..3e756fc5e --- /dev/null +++ b/apps/multiclock/gps.js @@ -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; + +})(); diff --git a/apps/multiclock/osref.js b/apps/multiclock/osref.js new file mode 100644 index 000000000..c98b73134 --- /dev/null +++ b/apps/multiclock/osref.js @@ -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; + +})(); diff --git a/apps/multiclock/ped.js b/apps/multiclock/ped.js new file mode 100644 index 000000000..31d3719df --- /dev/null +++ b/apps/multiclock/ped.js @@ -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; + +})();