From 24c84f9ba6a42d0d5aeda4743243d80f3fd7cb48 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 16 Nov 2020 09:18:49 +0000 Subject: [PATCH] add GPS Time Server --- apps.json | 12 +++++++ apps/gpstimeserver/ChangeLog | 1 + apps/gpstimeserver/README.md | 59 ++++++++++++++++++++++++++++++++++ apps/gpstimeserver/widget.js | 53 ++++++++++++++++++++++++++++++ apps/gpstimeserver/widget.png | Bin 0 -> 2011 bytes 5 files changed, 125 insertions(+) create mode 100644 apps/gpstimeserver/ChangeLog create mode 100644 apps/gpstimeserver/README.md create mode 100644 apps/gpstimeserver/widget.js create mode 100644 apps/gpstimeserver/widget.png 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 0000000000000000000000000000000000000000..793b025510045a65d9bce13c9a79654f6999f224 GIT binary patch literal 2011 zcmV<12PF83P)1yxzB@n9HU?uGW73%5fZTPS zp{OcVQ&pr@RaB8SQ3@qZP;#g=Zls1nN^wjK7^I{{q@p|mH8D2E3Fj9+-+OoZ;e3Z* zhmX{D+N$$ucV>3>H~;y+_P_97*2JX0v956qPy;LiatC_5fF=+g+*JquGl1(FzY6RJ z94G~*0;L8h1V|aD=|`}ZRj#)_JK(y;-vAp?idl{{HkVFeS$-B}HY)&EgCYLn?c3=@EpCr<*fWXTc$GBn9ctzI^tZ;eSDcH?$^ zYw&V%B3U=Qdm|8sJ-(ovwbPslSb(OcCYqX>68ApCWPMRC4GYS_Gy$HXuJO@H1AeXZ ziVdZBp`wI&88%c3&zK4-v+V4uD2eO1XL5k+8rMXW$*lA=HWlZk1UneIwj`foyA>dV zx_8%1hJhBV@iLd9248;Qexinv81#5Ncp8oaQ04KkdiCnWn67EO^Zo~< z>ss7f8KQo>!BW^VC)pJSTUGDNxnH8nK|AvkvI7~8jRXX(qJ`bAoCI$Z4=|IipF7+Ic1*!ujj)1mj@5;0bcL{t7DOVox z_OX6??ufx~IE>rvX5G4V6c!dzRaHepLj&{Y&ri%b+SwOZiBqGh#E~niBoGlV1Eqiy z#2f@$5XL{AIPCx9hPQ*W?;@{y9Ra_(<|RW*u6e;=kR3aAP+MC|eSJNerX`}@^DQ?A zVzKay8k&zbN9sUzCe^2WPD7I!Y-F z!$1mn^?DauF0_FVKHRR%5fW}_4$cO??zG+6L=-$@@%#VwGrY#C|drvkACPzdq6zM%1azBB6=KrNAE{TlZ}wWEh-#X^8QyCC15l8Z@0sJ$Bx2X=4ztFjEhZULqNg($T(Gfh5++x2Z= zH&U?e;&tvhah`qcUEByoNY^#eb&d8=gnjK@l%Kr7)(dTMLwjjB>`)0eM=k;705^Ol z$6Jk*OiL*fqyOgdH-CF#wn9xEHu`bjk#9}|;17*oL9rjmKoN^zazI}3>llp>#2VaH zhm!hXrIe_@8ZC`?7G_AMXDH&vry@bjKsRQf2gBcu>F-7bdNBMws9-NjnN?OxxfzNK zKN)d**Q;CyflS_Ld?3h>}|{eH~AA%svznKp|cM}y2 zC0{{mNV}7wDW+_0*M^_p{t@$6yAA*cK5aA_95{M`g7{5drh|LR=W^-2KO-zQq}_?M z=b$;9NQV<)&p~tKAZ*zPA*qZSaPTdzcSKY;B0o`>ulI>tKbWwu=A+G#3&4Ft^;2=O zHAH*AmeOLr_g*8~-GaSh#kpYa;xE4O4G|u1U^xix?1BUu>hcTu@^D#y|PDOc<~U$CFKRSOiN; zH1s<#o55aCCPKF&(;RVy`}u~0{)H0)yu8eMR=@|zCg#}%OJ?XK6Lef1q&;VdQA>%Z z=CH~dcVGZhs8{YZd8$LOs7M=;69Ab0IIwg0P?^mP?wk-{3zmHYb*`jsdZxrxqGORs zB<#o;>57DUV$&i2f^i3~E)zWp^~13qDo7VRRH|beq|~=n!a3@D48_iI2L`aTSat({ zAM*>-CRjdGBiANU0Y(UxjE4b^0IseUQK1=M1H5BBY_|v=F4dWtEiwGvv9w?ml}JEK zwQ1ae0Ujx}HJ@k+zo69E1shGmg6SHwE2|Ki6=Ba#4lKm-2`gw`sXJheq7(nhn=bA#`(EU9xT#$ zc&1Kah8!skLF>N*o3t)bkZpPT0c$2_TYPx?BJ4cY#TSZgJa&INx%RQ8oo^RZm1y*Z zG_Lzhx`PUo3R*Sw%($z>#<%-dV2VQk;i%%`4L=<{v82nQ2`Z-9c;sHYzoJ05rC2$N zDwM|}EZ?gx6CL9Ze8~Sk@TI|hoxTWHI)d~DZhxGX<=f6}ug?C75G({$iNc8>XJE~b zlu|8Ha$1X8-mJb?w2gZ{JP0_cj&=K^w04HLc`Jf01(&xKPWoS{yQ-}@9Xd{jj;R#A teuK-OLGAEURWGFwIMa3h-~JcdzW|Ck!|Z)wy}JMa002ovPDHLkV1lHfqQ?LL literal 0 HcmV?d00001