From 188e76c68cf61c58a0f796b7b4343281f1251eff Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sun, 1 Nov 2020 22:34:37 +0100 Subject: [PATCH] feat(bluetooth-low-energy): share discovered apps with cluster --- .../bluetooth-low-energy.service.spec.ts | 37 +++++++++++++++++ .../bluetooth-low-energy.service.ts | 40 ++++++++++++++++--- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.spec.ts b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.spec.ts index 55b9950..c68ba53 100644 --- a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.spec.ts +++ b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.spec.ts @@ -19,6 +19,7 @@ import { Peripheral } from '@abandonware/noble'; import { ConfigService } from '../../config/config.service'; import { Test, TestingModule } from '@nestjs/testing'; import { + APP_DISCOVERY_CHANNEL, BluetoothLowEnergyService, NEW_DISTANCE_CHANNEL, } from './bluetooth-low-energy.service'; @@ -152,6 +153,14 @@ describe('BluetoothLowEnergyService', () => { expect.any(Function) ); expect(clusterService.subscribe).toHaveBeenCalledWith(NEW_DISTANCE_CHANNEL); + + expect(clusterService.on).toHaveBeenCalledWith( + APP_DISCOVERY_CHANNEL, + expect.any(Function) + ); + expect(clusterService.subscribe).toHaveBeenCalledWith( + APP_DISCOVERY_CHANNEL + ); }); it('should detect iBeacons based on their manufacturer data', () => { @@ -951,6 +960,34 @@ describe('BluetoothLowEnergyService', () => { expect(discoverSpy).toHaveBeenCalledTimes(1); }); + it('should publish discovered companion app IDs to the cluster', async () => { + jest + .spyOn(service, 'handleNewDistance') + .mockImplementation(() => undefined); + jest.spyOn(service, 'isWhitelistEnabled').mockReturnValue(true); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); + jest.spyOn(service, 'discoverCompanionAppId').mockResolvedValue('app-id'); + + await service.handleDiscovery({ + id: 'abcd1234', + rssi: -50, + connectable: true, + advertisement: { + localName: 'Test Beacon', + txPowerLevel: -72, + manufacturerData: APPLE_MANUFACTURER_DATA, + }, + } as Peripheral); + + expect(clusterService.publish).toHaveBeenCalledWith( + APP_DISCOVERY_CHANNEL, + { + tagId: 'abcd1234', + appId: 'app-id', + } + ); + }); + it('should temporarily blacklist devices that time out from discovery attempts', async () => { jest.useFakeTimers('modern'); jest diff --git a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts index 1ce9424..780188a 100644 --- a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts +++ b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts @@ -26,8 +26,11 @@ import { BluetoothLowEnergyPresenceSensor } from './bluetooth-low-energy-presenc import { BluetoothService } from '../bluetooth/bluetooth.service'; export const NEW_DISTANCE_CHANNEL = 'bluetooth-low-energy.new-distance'; +export const APP_DISCOVERY_CHANNEL = 'bluetooth-low-energy.app-discovery'; const APPLE_ADVERTISEMENT_ID = Buffer.from([0x4c, 0x00, 0x10]); +type AppDiscoveryEvent = { tagId: string; appId: string }; + @Injectable() // parameters determined experimentally export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) @@ -74,6 +77,12 @@ export class BluetoothLowEnergyService this.handleNewDistance.bind(this) ); this.clusterService.subscribe(NEW_DISTANCE_CHANNEL); + + this.clusterService.on( + APP_DISCOVERY_CHANNEL, + this.handleAppDiscovery.bind(this) + ); + this.clusterService.subscribe(APP_DISCOVERY_CHANNEL); } /** @@ -410,13 +419,19 @@ export class BluetoothLowEnergyService try { this.logger.log(`Attempting app discovery for tag ${tag.id}`); const appId = await this.discoverCompanionAppId(tag); - this.companionAppTags.set(tag.id, appId); if (appId) { this.logger.log( `Discovered companion app with ID ${appId} for tag ${tag.id}` ); } + + const event = { + tagId: tag.id, + appId: appId, + } as AppDiscoveryEvent; + this.clusterService.publish(APP_DISCOVERY_CHANNEL, event); + this.handleAppDiscovery(event); } catch (e) { if (e.message === 'timed out') { this.logger.debug( @@ -436,13 +451,28 @@ export class BluetoothLowEnergyService this.companionAppTags.delete(tag.id); } } + } - const appId = this.companionAppTags.get(tag.id); - if (appId != null) { - tag.id = appId; - } + const appId = this.companionAppTags.get(tag.id); + if (appId != null) { + tag.id = appId; } return tag; } + + /** + * Adds discovered app information to the local cache. + * Does not override already existing values to null. + * + * @param event - Discovered information + */ + protected handleAppDiscovery(event: AppDiscoveryEvent): void { + const oldId = this.companionAppTags.get(event.tagId); + + if (!(oldId != null && event.appId == null)) { + this.companionAppTags.set(event.tagId, event.appId); + this.companionAppBlacklist.delete(event.tagId); + } + } }