diff --git a/apps.json b/apps.json index 681c77c9b..ad5200593 100644 --- a/apps.json +++ b/apps.json @@ -2369,5 +2369,17 @@ {"name":"isoclock.app.js","url":"isoclock.js"}, {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} ] +}, +{ "id": "gpstimeserver", + "name": "GPS Time Server", + "icon": "widget.png", + "version":"0.01", + "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", + "tags": "widget", + "type": "widget", + "readme": "README.md", + "storage": [ + {"name":"gpstimeserver.wid.js","url":"widget.js"} + ] } ] diff --git a/apps/gpstimeserver/ChangeLog b/apps/gpstimeserver/ChangeLog new file mode 100644 index 000000000..4c21f3ace --- /dev/null +++ b/apps/gpstimeserver/ChangeLog @@ -0,0 +1 @@ +0.01: New Widget! diff --git a/apps/gpstimeserver/README.md b/apps/gpstimeserver/README.md new file mode 100644 index 000000000..c6d89d56f --- /dev/null +++ b/apps/gpstimeserver/README.md @@ -0,0 +1,59 @@ +# GPS Time Server + +A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server, UUID 0x1805. + +Other Espruino Bluetooth devices can then find it and use it to synchronise time. + +**Note:** Because GPS is kept on, you'll need to keep your Bangle.js on charge for this to be useful. + +## Usage + +Just install this widget, and from then on any app which loads widgets will +display the icon ![](widget.png) in the top left, and Bangle.js will be +broadcasting the current time to any device that connects. + +## Technical + +This implements the [Bluetooth Time Service](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=292957) listed [here](https://www.bluetooth.com/specifications/gatt/). + +The Bluetooth docs are verbose and hard to read, so here's a rundown of how it works. + +* The Bangle advertises service `0x1805` +* You connect to it, and request service `0x1805` and characteristic `0x2A2B` +* A 10 byte array is returned: + +``` +[ + year_lowbyte, + year_highbyte, + month, + day_of_month, + hours, + minutes, + seconds, + day_of_week, // 1=monday...7=sunday + subseconds, // 0..255 + update_reason // 0=unknown currently +] +``` + +``` +//NRF.requestDevice({ filters: [{ services: ['1805'] }] }).then(print) + +var gatt; +NRF.connect("c7:4b:2e:c6:f5:45 random").then(function(g) { + gatt = g; + return gatt.getPrimaryService("1805"); +}).then(function(service) { + return service.getCharacteristic("2A2B"); +}).then(function(c) { + return c.readValue(); +}).then(function(d) { + console.log("Got:", JSON.stringify(d.buffer)); + var year = d.getUint16(0,1); + // ... + gatt.disconnect(); +}).catch(function(e) { + console.log("Something's broken.",e); +}); +``` diff --git a/apps/gpstimeserver/widget.js b/apps/gpstimeserver/widget.js new file mode 100644 index 000000000..5d1dd4c34 --- /dev/null +++ b/apps/gpstimeserver/widget.js @@ -0,0 +1,53 @@ +(() => { + +function getBLECurrentTimeData(d) { + var updateReason = 0; // unknown update reason + return [ + d.getFullYear()&0xFF, + d.getFullYear()>>8, + d.getMonth()+1, + d.getDate(), + d.getHours(), + d.getMinutes(), + d.getSeconds(), + d.getDay() ? d.getDay() : 7/*sunday*/, + Math.floor(d.getMilliseconds()*255/1000), + updateReason + ]; +} + +NRF.setServices({ + 0x1805 : { + 0x2A2B : { + value : getBLECurrentTimeData(new Date()), + readable : true, + notify : true + } + } +}, { advertise: [ '1805' ] }); + +Bangle.on('GPS', function(fix) { + if (fix.time !== undefined) { + NRF.updateServices({ + 0x1805 : { + 0x2A2B : { + value : getBLECurrentTimeData(fix.time), + notify : true + } + } + }); + } +}); +Bangle.setGPSPower(1); + + + function draw() { + g.reset(); + g.drawImage(require("heatshrink").decompress(atob("i0XxH+CR0HhEHEyEOi1AAAMWhAUNisW6/XwICBi0PHpgUC69WAYUWIpcVxAVGsgsLi2sCAOsg4EDiwVPlZYCCoUzss6IwxBE68rDYJBBldlAAVeNpIADNoNdxIWDssrCYMJgKZDF4SZCxGtCollmcJAALFDnTFE1utxNdrtXq9WqwVDeJAVB1tdrwABFgM6maOKwQWCIQgbBmQVJmQVCCwlXF4LoKCoaHDCoSgFAAldCwYtCqxbCLRQVECwNWr4VBr4VJmYWFrpcDCpM6neJC4pdCChEsss7C4+IFRI4DC4LBKCpBQLAAgA=")), this.x, this.y); + } + WIDGETS["gpstimeserver"]={ + area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right) + width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout + draw:draw // called to draw the widget + }; +})() diff --git a/apps/gpstimeserver/widget.png b/apps/gpstimeserver/widget.png new file mode 100644 index 000000000..793b02551 Binary files /dev/null and b/apps/gpstimeserver/widget.png differ