From f1453c5b8101de21970a9efbc011d331f09f3350 Mon Sep 17 00:00:00 2001 From: jeffyactive Date: Mon, 19 Aug 2024 13:51:28 -0400 Subject: [PATCH] Added OpenLocate Beacon app --- apps/openlocatebeacon/ChangeLog | 1 + apps/openlocatebeacon/README.md | 28 ++++ apps/openlocatebeacon/metadata.json | 19 +++ .../openlocatebeacon/openlocatebeacon-icon.js | 1 + apps/openlocatebeacon/openlocatebeacon.js | 123 ++++++++++++++++++ apps/openlocatebeacon/openlocatebeacon.png | Bin 0 -> 1977 bytes 6 files changed, 172 insertions(+) create mode 100644 apps/openlocatebeacon/ChangeLog create mode 100644 apps/openlocatebeacon/README.md create mode 100644 apps/openlocatebeacon/metadata.json create mode 100644 apps/openlocatebeacon/openlocatebeacon-icon.js create mode 100644 apps/openlocatebeacon/openlocatebeacon.js create mode 100644 apps/openlocatebeacon/openlocatebeacon.png diff --git a/apps/openlocatebeacon/ChangeLog b/apps/openlocatebeacon/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/openlocatebeacon/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/openlocatebeacon/README.md b/apps/openlocatebeacon/README.md new file mode 100644 index 000000000..ef4d20a21 --- /dev/null +++ b/apps/openlocatebeacon/README.md @@ -0,0 +1,28 @@ +# OpenLocate Beacon + +Collect geolocation sensor data from the Bangle.js 2's GPS and barometer, display the live readings on-screen, and broadcast in Bluetooth Low Energy (BLE) OpenLocate Beacon packets (LCI over BLE) to any listening devices in range. + + +## Usage + +The advertising packets will be recognised by [Pareto Anywhere](https://www.reelyactive.com/pareto/anywhere/) open source middleware and any other program which observes the standard packet types. See our [Bangle.js Development Guide](https://reelyactive.github.io/diy/banglejs-dev/) for details. + + +## Features + +Advertises packets with the OpenLocate Beacon geolocation element when a GPS fix is available, and packets with the name "Bangle.js" otherwise. + + +## Controls + +None. + + +## Requests + +[Contact reelyActive](https://www.reelyactive.com/contact/) for support/updates. + + +## Creator + +Developed by [jeffyactive](https://github.com/jeffyactive) of [reelyActive](https://www.reelyactive.com) diff --git a/apps/openlocatebeacon/metadata.json b/apps/openlocatebeacon/metadata.json new file mode 100644 index 000000000..87e38f985 --- /dev/null +++ b/apps/openlocatebeacon/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "openlocatebeacon", + "name": "OpenLocate Beacon", + "shortName": "OpenLocale Beacon", + "version": "0.01", + "description": "Advertise GPS geolocation date using the OpenLocate Beacon packet specification.", + "icon": "openlocatebeacon.png", + "screenshots": [], + "type": "app", + "tags": "tool,sensors,bluetooth", + "supports" : [ "BANGLEJS2" ], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + { "name": "openlocatebeacon.app.js", "url": "openlocatebeacon.js" }, + { "name": "openlocatebeacon.img", "url": "openlocatebeacon-icon.js", + "evaluate": true } + ] +} \ No newline at end of file diff --git a/apps/openlocatebeacon/openlocatebeacon-icon.js b/apps/openlocatebeacon/openlocatebeacon-icon.js new file mode 100644 index 000000000..7e838aaa0 --- /dev/null +++ b/apps/openlocatebeacon/openlocatebeacon-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhHXAB+sr1dnMzmcPh4DBnNdr2sDyAsOropCABczroyaxE5FhoAFnOIFqutFqgxE1ouSrwdHnWIxGz2YPBAYIHBnQTHrwuQLgwsBURwyGnIWN2bmFnR4S1oxFmZyCFx0zLY2r0md4AACzuk1ZjGDoowKCAk6JwuBFggAFzuBOApiEmYuIBwjSE2ddvOcFxIABzl5rpWErxQJN4QuIPIN5FpYADvKlFGAivGHZAuSGBCDEFwg6EOoZnBFyQwCK4mzQg4IECIYuBqzqKehVWDwxWFLwc5M4czLxPB5HP5/I4JgJmYfDnJgFEwJeFG4MyLw4tB6wPB6wxBMA8yRAhgDHAOtY44FBq2cFw3Qa4nX6AwGziQBEIwAB1o1DHoyOG4PPFwoAB56SGSAKBGA4SVDBgc6AwKOG45eGMAXHSAwbBnSQGnIvD1psFF43IXgQAF6yQGF4SQDXQczdwezF5pfJF5uzF4bED1gACF5KPSKoMzEY0PgAAJuQdFMAIvH5wQGuQkKABVOzhgG57BE63PLw2cpwvVkgvGGAPI5/Q6HP5AuGF4MkF6pgIAAPB4/H4ILHLxsrMBbBHABlyLxcrF5ZgKABJeOqyRMMCVyEBlWwLyNywuPyzsNwPXeS6NTAAPX67AMSKCNNXwIvBSBqRBGBlyRpqOCAAIRNSJiNPRwQABqwwPF5IuPqwvD1gUOSJKNPgGsF4bBPgEqSI2clQYOXoYADlbCUXiErFwyRQgCREuQVPRoqRTkmWFwOWXh6NHGCaRBRqAuLGCIAQFxowgFx70ClYtZlbqJMUZcRAA1WFqdWFq5jESp0rLbAyJq0rGggFBqwsSA==")) \ No newline at end of file diff --git a/apps/openlocatebeacon/openlocatebeacon.js b/apps/openlocatebeacon/openlocatebeacon.js new file mode 100644 index 000000000..fe2420173 --- /dev/null +++ b/apps/openlocatebeacon/openlocatebeacon.js @@ -0,0 +1,123 @@ +/** + * Copyright reelyActive 2024 + * We believe in an open Internet of Things + */ + + +// Non-user-configurable constants +const APP_ID = 'openlocatebeacon'; +const ADVERTISING_OPTIONS = { showName: false, interval: 5000 }; + + +// Global variables +let bar, gps; +let sequenceNumber = 0; + + +// Menus +let mainMenu = { + "": { "title": "OpenLocateBeacon" }, + "Lat": { value: null }, + "Lon": { value: null }, + "Altitude": { value: null }, + "Satellites": { value: null }, + "HDOP": { value: null } +}; + + +// Encode the OpenLocate geo location element advertising packet +function encodeGeoLocationElement() { + let lci = new Uint8Array(16); + let seqFrag = ((sequenceNumber++ & 0x0f) << 4) + 0x01; + let rfc6225lat = toRfc6225Coordinate(gps.lat); + let rfc6225lon = toRfc6225Coordinate(gps.lon); + let rfc6225alt = toRfc6225Coordinate(bar.altitude); + lci[0] = rfc6225lat.integer >> 7; + lci[1] = ((rfc6225lat.integer & 0xff) << 1) + (rfc6225lat.fraction >> 24); + lci[2] = (rfc6225lat.fraction >> 16) & 0xff; + lci[3] = (rfc6225lat.fraction >> 8) & 0xff; + lci[4] = rfc6225lat.fraction & 0xff; + lci[5] = rfc6225lon.integer >> 7; + lci[6] = ((rfc6225lon.integer & 0xff) << 1) + (rfc6225lon.fraction >> 24); + lci[7] = (rfc6225lon.fraction >> 16) & 0xff; + lci[8] = (rfc6225lon.fraction >> 8) & 0xff; + lci[9] = rfc6225lon.fraction & 0xff; + lci[10] = Number.isFinite(bar.altitude) ? 0x10 : 0x00; + lci[11] = (rfc6225alt.integer >> 16) & 0xff; + lci[12] = (rfc6225alt.integer >> 8) & 0xff; + lci[13] = rfc6225alt.integer & 0xff; + lci[14] = rfc6225alt.fraction & 0xff; + lci[15] = 0x41; + + return [ + 0x02, 0x01, 0x06, // Flags + 0x16, 0x16, 0x94, 0xfd, 0x09, seqFrag, 0x30, lci[0], lci[1], lci[2], + lci[3], lci[4], lci[5], lci[6], lci[7], lci[8], lci[9], lci[10], lci[11], + lci[12], lci[13], lci[14], lci[15] + ]; +} + + +// Convert a latitude or longitude coordinate to RFC6225 +function toRfc6225Coordinate(coordinate) { + let integer = Math.floor(coordinate); + let fraction = Math.round((coordinate - integer) * 0x1ffffff); + + if(integer < 0) { + integer += 0x1ff + 1; + } + + return { integer: integer, fraction: fraction }; +} + + +// Convert altitude to RFC6225 +function toRfc6225Altitude(altitude) { + if(!Number.isFinite(altitude)) { + return { integer: 0, fraction: 0 }; + } + + let integer = Math.floor(altitude); + let fraction = Math.round((altitude - integer) * 0xff); + + if(integer < 0) { + integer += 0x3fffff + 1; + } + + return { integer: integer, fraction: fraction }; +} + + +// Update barometer +Bangle.on('pressure', (newBar) => { + bar = newBar; + + mainMenu.Altitude.value = bar.altitude.toFixed(1) + 'm'; + E.showMenu(mainMenu); +}); + + +// Update GPS +Bangle.on('GPS', (newGps) => { + gps = newGps; + + mainMenu.Lat.value = gps.lat.toFixed(4); + mainMenu.Lon.value = gps.lon.toFixed(4); + mainMenu.Satellites.value = gps.satellites; + mainMenu.HDOP.value = (gps.hdop * 5).toFixed(1) + 'm'; + E.showMenu(gpsMenu); + + if(Number.isFinite(gps.lat) && Number.isFinite(gps.lon)) { + NRF.setAdvertising(encodeGeoLocationElement(), ADVERTISING_OPTIONS); + } + else { + NRF.setAdvertising({}, { name:"Bangle.js" }); + } +}); + + +// On start: enable sensors and display main menu +g.clear(); +Bangle.setGPSPower(true, APP_ID); +Bangle.setBarometerPower(true, APP_ID); +E.showMenu(mainMenu); \ No newline at end of file diff --git a/apps/openlocatebeacon/openlocatebeacon.png b/apps/openlocatebeacon/openlocatebeacon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3294dae03285aa269545fe5b267ea6f357cc32a GIT binary patch literal 1977 zcmV;q2S)gbP)f7_p*YaLP86U;HN2;H3qFl1<^M+g?mBSd_c_)VV)srv@-O;2Os(Yhp5Y z3`xmX?6uUx$$8NkMaL>@sFrJJ3S>H2F?xmdG5og*{~#YCRM#0i1gfZQd@A*SMX|3@ z@8?61dN{&mpu3&s#Qb9a#La5mdy}?AeR1%;z2)1>FI8Q=@kdij`<=TeT;LXim;wPb zbuR=H@{U5B`gOs1pr)JcV^$Wb<(|pdZz`*4ZttKC&Hx(7lQQvBCa=mrn5{G>>XxZ0A<#YqL-E291 zu<*!7G3Sy-G#mWQdLI-naC>y+E2j?>+-#+05P_7v@_>a~U`J>Uftp&|$*#r&zuMep z&-vwY{U_>VR?Nr=NZRvVCtu^DVN1Lz44h zYR_>96qT{Zo*ld8#2veT-^Ioj{W{ZHFKJB`0=B^y-!W0Ogfvo=`zoxN8ia-#u z_AXP_?3T0KKG2%O752QC+++58VaJ(EPrs99GDcq4^^}jqeSrq@Hsk0C3)0Xor63D& zd#{Wmwj;_KsP0Bv;M=xcI{vf09d|n=3EOz+{PZoxasTLgxOp(@0vRn^jbqUc5h)09 zSV+5oq|FGROj!fP<{XyO{T7>WqR8geN6rS$->}N~{55@mS7~G>G@>0Mkdm^n=)1@y zO=CA6QpQCfY`>lfwgwu9UjaSgwBvB9OA*uvsV0OH%|; zP5}|<-J%jX5&Gdy`A5Fqc6&f_!Ts@HZZeE-(REilaYS4?gl~-DyO)c~c~#sx%Z>fG z*)R@L5WI2Adal9soP%ZqG*G5U%=h^9fUpU$g~@(zbnO59c#^Q`HVtYZ*?6a*m)afJ>$n92YM!&dAT zk3p&EoMNdg7kGnVVwD#?2`^E+j5kKqZk!IuWgjN%tdB$)zJAUL)_1X7A7#3dJ|GQe}I3fZH}ZJ#@M zVv=`%*ONT{)?+jcDt9iguasnp`YN6qOIvgdQi>+TCAZ|F?LRXtC65I!u^k5T_`&-{ z4wfc`sipabmwD|kQk0_hSN>HrZPBsP#4srfK1Qp&M2I=qX-ld+ZdI2l&U~qy-{ni^o2^v_PptKb=*)U@E;3hV|*whq=X?aqRDT z^tbYa0KRLf*y?IvaMvP~OLIT-=7ucOQr~$`O`TnE4Fp-y<`1!-4a8Z;b234mh z+93id_Yh$`E}4%HTlggVbB-#IQgPk+vPXFOL{ZCzs?TVp(7rHCNV$gy6A}_ahgBGU zANwYX-FltcXe=~-8zn?MLWGHmilW2LFlQL6oQ-b@YUZ)fQuQ*69Zm(ELWGHrkEg@V z5ah>Jk7Z4-Q9qB>WZ7In9{#9Sh%hFTi4MC!_ycU;G}i9T!hwtyG-`wh)6mf1yjX(^p zd@*KbX3|O52n}LIlRfV^5VlWYBYV?!H$V()pr)p#UcGwJdDoaZgq6;4w{Vy$n;~f0 zfdGhM4Fn`6CQ^!PbnnKJN3r_3G>0rqrHm3Z?LYt}i8WAdZLPn*KV`Ybpa7OL$@#)z zFn5w5Xa}qjz#$NjmX=0|Zt+|&E1u5oIxZY;k4S}@WK-{u+Uj3i61G4Isl5iP_TmIu-?K>@iAo&*)xC_KB zX8{fgug*+YfC!}Qceqn{2&AB{t}Y=V!K?S3J34BK3(+;1TQ>RYFXL