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 000000000..b3294dae0 Binary files /dev/null and b/apps/openlocatebeacon/openlocatebeacon.png differ