Merge pull request #3438 from bobrippling/fix/ble-advert-array

Add `ble_advert` module
master
Rob Pilling 2024-05-30 08:22:22 +01:00 committed by GitHub
commit 456cf7f121
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 229 additions and 57 deletions

View File

@ -1,2 +1,3 @@
0.01: Initial release. 0.01: Initial release.
0.02: Handle the case where other apps have set bleAdvert to an array 0.02: Handle the case where other apps have set bleAdvert to an array
0.03: Use the bleAdvert module

View File

@ -1,26 +1,8 @@
(() => { (() => {
function advertiseBattery() { function advertiseBattery() {
if(Array.isArray(Bangle.bleAdvert)){ require("ble_advert").set(0x180F, [E.getBattery()]);
// 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);
} }
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
setInterval(advertiseBattery, 60 * 1000); setInterval(advertiseBattery, 60 * 1000);
advertiseBattery(); advertiseBattery();
})(); })();

View File

@ -2,7 +2,7 @@
"id": "bootgattbat", "id": "bootgattbat",
"name": "BLE GATT Battery Service", "name": "BLE GATT Battery Service",
"shortName": "BLE 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", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
"icon": "bluetooth.png", "icon": "bluetooth.png",
"type": "bootloader", "type": "bootloader",

View File

@ -1,3 +1,4 @@
0.01: Initial release. 0.01: Initial release.
0.02: Added compatibility to OpenTracks and added HRM Location 0.02: Added compatibility to OpenTracks and added HRM Location
0.03: Allow setting to keep BLE connected 0.03: Allow setting to keep BLE connected
0.04: Use the bleAdvert module

View File

@ -4,18 +4,13 @@
* This function prepares BLE heart rate Advertisement. * This function prepares BLE heart rate Advertisement.
*/ */
NRF.setAdvertising( require("ble_advert").set(0x180d, undefined, {
{ // We need custom Advertisement settings for Apps like OpenTracks
0x180d: undefined connectable: true,
}, discoverable: true,
{ scannable: true,
// We need custom Advertisement settings for Apps like OpenTracks whenConnected: true,
connectable: true, });
discoverable: true,
scannable: true,
whenConnected: true,
}
);
NRF.setServices({ NRF.setServices({
0x180D: { // heart_rate 0x180D: { // heart_rate
@ -28,7 +23,6 @@
} }
} }
}); });
} }
const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected; const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected;

View File

@ -2,7 +2,7 @@
"id": "bootgatthrm", "id": "bootgatthrm",
"name": "BLE GATT HRM Service", "name": "BLE GATT HRM Service",
"shortName": "BLE 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", "description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
"icon": "bluetooth.png", "icon": "bluetooth.png",
"type": "bootloader", "type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: New app! 0.01: New app!
0.02: Advertise accelerometer data and sensor location 0.02: Advertise accelerometer data and sensor location
0.03: Use the bleAdvert module

View File

@ -667,6 +667,8 @@ const getBleAdvert = <T>(map: (s: BleServ) => T, all = false) => {
// done via advertise in setServices() // done via advertise in setServices()
//const updateBleAdvert = () => { //const updateBleAdvert = () => {
// require("ble_advert").set(...)
//
// let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>; // let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>;
// //
// if (!(bleAdvert = (Bangle as any).bleAdvert)) { // if (!(bleAdvert = (Bangle as any).bleAdvert)) {

View File

@ -2,7 +2,7 @@
"id": "btadv", "id": "btadv",
"name": "btadv", "name": "btadv",
"shortName": "btadv", "shortName": "btadv",
"version": "0.02", "version": "0.03",
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth", "description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
"icon": "icon.png", "icon": "icon.png",
"tags": "health,tool,sensors,bluetooth", "tags": "health,tool,sensors,bluetooth",

View File

@ -2,4 +2,5 @@
0.02: Fix double-button press if you press the next button within 30s (#3243) 0.02: Fix double-button press if you press the next button within 30s (#3243)
0.03: Cope with identical duplicate buttons (fix #3260) 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) Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
0.04: Fix duplicate button on edit->save 0.04: Fix duplicate button on edit->save
0.05: Use the bleAdvert module

View File

@ -1,5 +1,3 @@
// Ensure we have the bleAdvert global (to play well with other stuff)
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
Bangle.btHomeData = []; Bangle.btHomeData = [];
{ {
require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up 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(); if (bat) bat.v = E.getBattery();
var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2]; var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2];
// Add to the list of available advertising // 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 advOptions = {};
var updateTimeout = 10*60*1000; // update every 10 minutes var updateTimeout = 10*60*1000; // update every 10 minutes
if (options.event) { // if it's an event... if (options.event) { // if it's an event...
@ -60,7 +44,7 @@ Bangle.btHome = function(extras, options) {
advOptions.whenConnected = true; advOptions.whenConnected = true;
updateTimeout = 30000; // slow down in 30 seconds 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); if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout);
Bangle.btHomeTimeout = setTimeout(function() { Bangle.btHomeTimeout = setTimeout(function() {
delete Bangle.btHomeTimeout; delete Bangle.btHomeTimeout;

View File

@ -1,7 +1,7 @@
{ "id": "bthome", { "id": "bthome",
"name": "BTHome", "name": "BTHome",
"shortName":"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", "description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
"icon": "icon.png", "icon": "icon.png",
"type": "app", "type": "app",

View File

@ -1,2 +1,3 @@
0.01: New App! 0.01: New App!
0.02: Keep advertising when connected 0.02: Keep advertising when connected
0.03: Use ble_advert module to work with other BLE advert apps

View File

@ -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 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 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.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 // send finished app

View File

@ -1,7 +1,7 @@
{ "id": "openhaystack", { "id": "openhaystack",
"name": "OpenHaystack (AirTag)", "name": "OpenHaystack (AirTag)",
"icon": "icon.png", "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", "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", "tags": "openhaystack,bluetooth,ble,tracking,airtag",
"type": "bootloader", "type": "bootloader",

69
modules/ble_advert.js Normal file
View File

@ -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);
};

136
modules/ble_advert.ts Normal file
View File

@ -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);
};
*/

View File

@ -61,7 +61,7 @@
"*/*/*.ts", "*/*/*.ts",
"apps/*/*.js", // with checkJs: true "apps/*/*.js", // with checkJs: true
"*/*/*.d.ts", "*/*/*.d.ts",
"modules/*.d.ts", "modules/*.ts",
"typescript/types/*.d.ts" "typescript/types/*.d.ts"
] ]
} }