diff --git a/apps/bootgattbat/ChangeLog b/apps/bootgattbat/ChangeLog index df07f6ad0..3df6422e5 100644 --- a/apps/bootgattbat/ChangeLog +++ b/apps/bootgattbat/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial release. 0.02: Handle the case where other apps have set bleAdvert to an array +0.03: Use the bleAdvert module diff --git a/apps/bootgattbat/boot.js b/apps/bootgattbat/boot.js index 34d9f8d93..f9b5969e2 100644 --- a/apps/bootgattbat/boot.js +++ b/apps/bootgattbat/boot.js @@ -1,26 +1,8 @@ (() => { function advertiseBattery() { - if(Array.isArray(Bangle.bleAdvert)){ - // ensure we're in the cycle - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0x180F]){ - ad[0x180F] = [E.getBattery()]; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] }); - }else{ - // simple object - Bangle.bleAdvert[0x180F] = [E.getBattery()]; - } - - NRF.setAdvertising(Bangle.bleAdvert); + require("ble_advert").set(0x180F, [E.getBattery()]); } - if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; setInterval(advertiseBattery, 60 * 1000); advertiseBattery(); })(); diff --git a/apps/bootgattbat/metadata.json b/apps/bootgattbat/metadata.json index f67b4507d..b53708c2e 100644 --- a/apps/bootgattbat/metadata.json +++ b/apps/bootgattbat/metadata.json @@ -2,7 +2,7 @@ "id": "bootgattbat", "name": "BLE GATT Battery Service", "shortName": "BLE Battery Service", - "version": "0.02", + "version": "0.03", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", diff --git a/apps/bootgatthrm/ChangeLog b/apps/bootgatthrm/ChangeLog index 2512ed877..205f036b5 100644 --- a/apps/bootgatthrm/ChangeLog +++ b/apps/bootgatthrm/ChangeLog @@ -1,3 +1,4 @@ 0.01: Initial release. 0.02: Added compatibility to OpenTracks and added HRM Location 0.03: Allow setting to keep BLE connected +0.04: Use the bleAdvert module diff --git a/apps/bootgatthrm/boot.js b/apps/bootgatthrm/boot.js index 0fceb86d9..efba1f453 100644 --- a/apps/bootgatthrm/boot.js +++ b/apps/bootgatthrm/boot.js @@ -4,18 +4,13 @@ * This function prepares BLE heart rate Advertisement. */ - NRF.setAdvertising( - { - 0x180d: undefined - }, - { - // We need custom Advertisement settings for Apps like OpenTracks - connectable: true, - discoverable: true, - scannable: true, - whenConnected: true, - } - ); + require("ble_advert").set(0x180d, undefined, { + // We need custom Advertisement settings for Apps like OpenTracks + connectable: true, + discoverable: true, + scannable: true, + whenConnected: true, + }); NRF.setServices({ 0x180D: { // heart_rate @@ -28,7 +23,6 @@ } } }); - } const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected; diff --git a/apps/bootgatthrm/metadata.json b/apps/bootgatthrm/metadata.json index dc831ab7d..d32b51601 100644 --- a/apps/bootgatthrm/metadata.json +++ b/apps/bootgatthrm/metadata.json @@ -2,7 +2,7 @@ "id": "bootgatthrm", "name": "BLE GATT HRM Service", "shortName": "BLE HRM Service", - "version": "0.03", + "version": "0.04", "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", diff --git a/apps/btadv/ChangeLog b/apps/btadv/ChangeLog index 07e67157c..245f4fca6 100644 --- a/apps/btadv/ChangeLog +++ b/apps/btadv/ChangeLog @@ -1,2 +1,3 @@ 0.01: New app! 0.02: Advertise accelerometer data and sensor location +0.03: Use the bleAdvert module diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index 713172ca1..b56546ba1 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -667,6 +667,8 @@ const getBleAdvert = (map: (s: BleServ) => T, all = false) => { // done via advertise in setServices() //const updateBleAdvert = () => { +// require("ble_advert").set(...) +// // let bleAdvert: ReturnType>; // // if (!(bleAdvert = (Bangle as any).bleAdvert)) { diff --git a/apps/btadv/metadata.json b/apps/btadv/metadata.json index efe024a2f..060c2b498 100644 --- a/apps/btadv/metadata.json +++ b/apps/btadv/metadata.json @@ -2,7 +2,7 @@ "id": "btadv", "name": "btadv", "shortName": "btadv", - "version": "0.02", + "version": "0.03", "description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth", "icon": "icon.png", "tags": "health,tool,sensors,bluetooth", diff --git a/apps/bthome/ChangeLog b/apps/bthome/ChangeLog index ca91e0f34..82c93ee1f 100644 --- a/apps/bthome/ChangeLog +++ b/apps/bthome/ChangeLog @@ -2,4 +2,5 @@ 0.02: Fix double-button press if you press the next button within 30s (#3243) 0.03: Cope with identical duplicate buttons (fix #3260) Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising) -0.04: Fix duplicate button on edit->save \ No newline at end of file +0.04: Fix duplicate button on edit->save +0.05: Use the bleAdvert module diff --git a/apps/bthome/boot.js b/apps/bthome/boot.js index 7c9e37f77..00e08df90 100644 --- a/apps/bthome/boot.js +++ b/apps/bthome/boot.js @@ -1,5 +1,3 @@ -// Ensure we have the bleAdvert global (to play well with other stuff) -if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; Bangle.btHomeData = []; { require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up @@ -39,20 +37,6 @@ Bangle.btHome = function(extras, options) { if (bat) bat.v = E.getBattery(); var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2]; // Add to the list of available advertising - if(Array.isArray(Bangle.bleAdvert)){ - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0xFCD2]){ - ad[0xFCD2] = advert; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0xFCD2: advert }); - } else { - Bangle.bleAdvert[0xFCD2] = advert; - } var advOptions = {}; var updateTimeout = 10*60*1000; // update every 10 minutes if (options.event) { // if it's an event... @@ -60,7 +44,7 @@ Bangle.btHome = function(extras, options) { advOptions.whenConnected = true; updateTimeout = 30000; // slow down in 30 seconds } - NRF.setAdvertising(Bangle.bleAdvert, advOptions); + require("ble_advert").set(0xFCD2, advert, advOptions); if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout); Bangle.btHomeTimeout = setTimeout(function() { delete Bangle.btHomeTimeout; diff --git a/apps/bthome/metadata.json b/apps/bthome/metadata.json index 1ccc19316..0156a5d32 100644 --- a/apps/bthome/metadata.json +++ b/apps/bthome/metadata.json @@ -1,7 +1,7 @@ { "id": "bthome", "name": "BTHome", "shortName":"BTHome", - "version":"0.04", + "version":"0.05", "description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth", "icon": "icon.png", "type": "app", diff --git a/apps/openhaystack/ChangeLog b/apps/openhaystack/ChangeLog index 067366a77..67d7bbabf 100644 --- a/apps/openhaystack/ChangeLog +++ b/apps/openhaystack/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Keep advertising when connected +0.03: Use ble_advert module to work with other BLE advert apps diff --git a/apps/openhaystack/custom.html b/apps/openhaystack/custom.html index 1b57884bc..774b5e6cb 100644 --- a/apps/openhaystack/custom.html +++ b/apps/openhaystack/custom.html @@ -40,7 +40,7 @@ const key = E.toUint8Array(atob(${JSON.stringify(keyValue)})); // public key const mac = [ key[0] | 0b11000000, key[1], key[2], key[3], key[4], key[5] ].map(x => x.toString(16).padStart(2, '0')).join(':'); // mac address const adv = [ 0x1e, 0xff, 0x4c, 0x00, 0x12, 0x19, 0x00, key[6], key[7], key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15], key[16], key[17], key[18], key[19], key[20], key[21], key[22], key[23], key[24], key[25], key[26], key[27], key[0] >> 6, 0x00 ]; // advertising packet NRF.setAddress(mac); -NRF.setAdvertising([adv,{}],{whenConnected: true, interval: 1000}); // advertise AirTag *and* normal device name (to remain connectable) +require("ble_advert").push(adv, {whenConnected: true, interval: 1000}); // advertise AirTag *and* normal device name (to remain connectable) } `; // send finished app diff --git a/apps/openhaystack/metadata.json b/apps/openhaystack/metadata.json index 28d3423f9..712e7ebaf 100644 --- a/apps/openhaystack/metadata.json +++ b/apps/openhaystack/metadata.json @@ -1,7 +1,7 @@ { "id": "openhaystack", "name": "OpenHaystack (AirTag)", "icon": "icon.png", - "version":"0.02", + "version":"0.03", "description": "Copy a base64 key from https://github.com/seemoo-lab/openhaystack and make your Bangle.js trackable as if it's an AirTag", "tags": "openhaystack,bluetooth,ble,tracking,airtag", "type": "bootloader", diff --git a/modules/ble_advert.js b/modules/ble_advert.js new file mode 100644 index 000000000..0a037cfd8 --- /dev/null +++ b/modules/ble_advert.js @@ -0,0 +1,69 @@ +var advertise = function (options) { + var clone = function (obj) { + if (Array.isArray(obj)) { + return obj.map(clone); + } + else if (typeof obj === "object") { + var r = {}; + for (var k in obj) { + r[k] = clone(obj[k]); + } + return r; + } + return obj; + }; + NRF.setAdvertising(clone(Bangle.bleAdvert), options); +}; +var manyAdv = function (bleAdvert) { + return Array.isArray(bleAdvert) && typeof bleAdvert[0] === "object"; +}; +exports.set = function (id, advert, options) { + var _a, _b, _c; + var bangle = Bangle; + if (manyAdv(bangle.bleAdvert)) { + var found = false; + var obj = void 0; + for (var _i = 0, _d = bangle.bleAdvert; _i < _d.length; _i++) { + var ad = _d[_i]; + if (Array.isArray(ad)) + continue; + obj = ad; + if (ad[id]) { + ad[id] = advert; + found = true; + break; + } + } + if (!found) { + if (obj) + obj[id] = advert; + else + bangle.bleAdvert.push((_a = {}, _a[id] = advert, _a)); + } + } + else if (bangle.bleAdvert) { + if (Array.isArray(bangle.bleAdvert)) { + bangle.bleAdvert = [bangle.bleAdvert, (_b = {}, _b[id] = advert, _b)]; + } + else { + bangle.bleAdvert[id] = advert; + } + } + else { + bangle.bleAdvert = (_c = {}, _c[id] = advert, _c); + } + advertise(options); +}; +exports.push = function (adv, options) { + var bangle = Bangle; + if (manyAdv(bangle.bleAdvert)) { + bangle.bleAdvert.push(adv); + } + else if (bangle.bleAdvert) { + bangle.bleAdvert = [bangle.bleAdvert, adv]; + } + else { + bangle.bleAdvert = [adv, {}]; + } + advertise(options); +}; diff --git a/modules/ble_advert.ts b/modules/ble_advert.ts new file mode 100644 index 000000000..c0b852f1d --- /dev/null +++ b/modules/ble_advert.ts @@ -0,0 +1,136 @@ +declare let exports: any; +//declare let BLE_DEBUG: undefined | true; + +type BleAdvertObj = { [key: string | number]: number[] }; +type BleAdvert = BleAdvertObj | number[]; +type BangleWithAdvert = (typeof Bangle) & { bleAdvert?: BleAdvert | BleAdvert[]; }; +type SetAdvertisingOptions = typeof NRF.setAdvertising extends (data: any, options: infer Opts) => any ? Opts : never; + +const advertise = (options: SetAdvertisingOptions) => { + const clone = (obj: any): any => { + // just for our use-case + if(Array.isArray(obj)){ + return obj.map(clone); + }else if(typeof obj === "object"){ + const r = {}; + for(const k in obj){ + // @ts-expect-error implicitly + r[k] = clone(obj[k]); + } + return r; + } + return obj; + }; + + // clone the object, to avoid firmware behaving like so: + // bleAdvert = [Uint8Array, { [0x180f]: ... }] + // ^ ^ + // | | + // | +- added by this call + // +- modified from a previous setAdvertising() + // + // The firmware (jswrap_ble_setAdvertising) will convert arrays within + // the advertising array to Uint8Array, but if this has already been done, + // we get iterator errors. So we clone the object to avoid these mutations + // taking effect for later calls. + // + // This also allows us to identify previous adverts correctly by id. + NRF.setAdvertising(clone((Bangle as BangleWithAdvert).bleAdvert), options); +}; + +const manyAdv = (bleAdvert: BleAdvert | BleAdvert[] | undefined): bleAdvert is BleAdvert[] => { + return Array.isArray(bleAdvert) && typeof bleAdvert[0] === "object"; +}; + +exports.set = (id: string | number, advert: number[], options?: SetAdvertisingOptions) => { + const bangle = Bangle as BangleWithAdvert; + + if(manyAdv(bangle.bleAdvert)){ + let found = false; + let obj; + for(let ad of bangle.bleAdvert){ + if(Array.isArray(ad)) continue; + obj = ad; + if(ad[id]){ + ad[id] = advert; + found = true; + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`bleAdvert is array, found existing entry for ${id}, replaced`) + break; + } + } + if(!found){ + if(obj) + obj[id] = advert; + else + bangle.bleAdvert.push({ [id]: advert }); + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`bleAdvert is array, no entry for ${id}, created`) + } + }else if(bangle.bleAdvert){ + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`bleAdvert is object, ${id} entry ${id in bangle.bleAdvert ? "replaced" : "created"}`); + if(Array.isArray(bangle.bleAdvert)){ + bangle.bleAdvert = [bangle.bleAdvert, { [id]: advert }]; + }else{ + bangle.bleAdvert[id] = advert; + } + }else{ + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`bleAdvert not present, created`); + bangle.bleAdvert = { [id]: advert }; + } + + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`NRF.setAdvertising({ ${Object.keys(bangle.bleAdvert).join(", ")} }, ${JSON.stringify(options)})`); + + advertise(options); +}; + +exports.push = (adv: number[], options?: SetAdvertisingOptions) => { + const bangle = Bangle as BangleWithAdvert; + + if(manyAdv(bangle.bleAdvert)){ + bangle.bleAdvert.push(adv); + }else if(bangle.bleAdvert){ + bangle.bleAdvert = [bangle.bleAdvert, adv]; + }else{ + // keep a second entry for normal/original advertising as well as this extra one + bangle.bleAdvert = [adv, {}]; + } + + advertise(options); +}; + +/* +exports.remove = (id: string | number, options?: SetAdvertisingOptions) => { + const bangle = Bangle as BangleWithAdvert; + + // if(typeof BLE_DEBUG !== "undefined") + // console.log(`ble_advert.remove(${id}, ${JSON.stringify(options)})`); + + if(manyAdv(bangle.bleAdvert)){ + let i = 0; + for(const ad of bangle.bleAdvert){ + if(Array.isArray(ad)) continue; + if(ad[id]){ + delete ad[id]; + let empty = true; + // eslint-disable-next-line no-unused-vars + for(const _ in ad){ + empty = false; + break; + } + if(empty) bangle.bleAdvert.splice(i, 1); + break; + } + i++; + } + }else if(bangle.bleAdvert){ + if(!Array.isArray(bangle.bleAdvert)) + delete bangle.bleAdvert[id]; + } + + advertise(options); +}; +*/ diff --git a/tsconfig.json b/tsconfig.json index da6aba2e9..5d46f672c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -61,7 +61,7 @@ "*/*/*.ts", "apps/*/*.js", // with checkJs: true "*/*/*.d.ts", - "modules/*.d.ts", + "modules/*.ts", "typescript/types/*.d.ts" ] }