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 f6a2789..8577cc7 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 @@ -1,5 +1,3 @@ -import KalmanFilter from 'kalmanjs'; - const mockNoble = { on: jest.fn() }; @@ -36,6 +34,7 @@ import { Sensor } from '../../entities/sensor.entity'; import c from 'config'; import { NewDistanceEvent } from './new-distance.event'; import { RoomPresenceDistanceSensor } from '../room-presence/room-presence-distance.sensor'; +import KalmanFilter from 'kalmanjs'; describe('BluetoothLowEnergyService', () => { let service: BluetoothLowEnergyService; @@ -57,6 +56,11 @@ describe('BluetoothLowEnergyService', () => { return key === 'bluetoothLowEnergy' ? mockConfig : c.get(key); }) }; + const loggerService = { + log: jest.fn(), + error: jest.fn(), + warn: jest.fn() + }; const iBeaconData = Buffer.from([ 76, @@ -106,6 +110,7 @@ describe('BluetoothLowEnergyService', () => { .overrideProvider(ConfigService) .useValue(configService) .compile(); + module.useLogger(loggerService); service = module.get(BluetoothLowEnergyService); }); @@ -119,6 +124,16 @@ describe('BluetoothLowEnergyService', () => { expect(mockNoble.on).toHaveBeenCalledWith('discover', expect.any(Function)); }); + it('should warn if no whitelist has been configured', () => { + mockConfig.whitelist = ['abcd']; + service.onModuleInit(); + expect(loggerService.warn).not.toHaveBeenCalled(); + + mockConfig.whitelist = []; + service.onModuleInit(); + expect(loggerService.warn).toHaveBeenCalled(); + }); + it('should setup cluster listeners on bootstrap', () => { service.onApplicationBootstrap(); expect(clusterService.on).toHaveBeenCalledWith( @@ -149,6 +164,7 @@ describe('BluetoothLowEnergyService', () => { const handleDistanceSpy = jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); mockConfig.onlyIBeacon = true; mockConfig.processIBeacon = true; @@ -180,6 +196,7 @@ describe('BluetoothLowEnergyService', () => { const handleDistanceSpy = jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); mockConfig.processIBeacon = false; service.handleDiscovery({ @@ -209,6 +226,7 @@ describe('BluetoothLowEnergyService', () => { const handleDistanceSpy = jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); service.handleDiscovery({ id: '123-123', @@ -231,10 +249,41 @@ describe('BluetoothLowEnergyService', () => { ); }); + it('should ignore devices that are not on the whitelist', () => { + const handleDistanceSpy = jest + .spyOn(service, 'handleNewDistance') + .mockImplementation(() => undefined); + mockConfig.whitelist = ['123-1-1']; + + service.handleDiscovery({ + id: '123-1-2', + rssi: -82, + advertisement: {} + } as Peripheral); + expect(handleDistanceSpy).not.toHaveBeenCalled(); + expect(clusterService.publish).not.toHaveBeenCalled(); + }); + + it('should not publish anything if the whitelist is empty', () => { + const handleDistanceSpy = jest + .spyOn(service, 'handleNewDistance') + .mockImplementation(() => undefined); + mockConfig.whitelist = []; + + service.handleDiscovery({ + id: '89:47:65', + rssi: -82, + advertisement: {} + } as Peripheral); + expect(handleDistanceSpy).not.toHaveBeenCalled(); + expect(clusterService.publish).not.toHaveBeenCalled(); + }); + it('should apply tag distance override if it exists', () => { const handleDistanceSpy = jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); mockConfig.tagOverrides = { abcd: { measuredPower: -80 @@ -282,6 +331,7 @@ describe('BluetoothLowEnergyService', () => { const handleDistanceSpy = jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); mockConfig.tagOverrides = { abcd: { name: 'better name' @@ -339,6 +389,7 @@ describe('BluetoothLowEnergyService', () => { .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); const filterSpy = jest.spyOn(service, 'filterRssi').mockReturnValue(-50); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); service.handleDiscovery({ id: '12:ab:cd:12:cd', @@ -363,6 +414,7 @@ describe('BluetoothLowEnergyService', () => { jest .spyOn(service, 'handleNewDistance') .mockImplementation(() => undefined); + jest.spyOn(service, 'isOnWhitelist').mockReturnValue(true); service.handleDiscovery({ id: 'id1', 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 1b409d6..77107a6 100644 --- a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts +++ b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts @@ -1,5 +1,6 @@ import { Injectable, + Logger, OnApplicationBootstrap, OnModuleInit } from '@nestjs/common'; @@ -24,6 +25,7 @@ export const NEW_DISTANCE_CHANNEL = 'bluetooth-low-energy.new-distance'; export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) implements OnModuleInit, OnApplicationBootstrap { private readonly config: BluetoothLowEnergyConfig; + private readonly logger: Logger; constructor( private readonly entitiesService: EntitiesService, @@ -33,6 +35,7 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) ) { super(); this.config = this.configService.get('bluetoothLowEnergy'); + this.logger = new Logger(BluetoothLowEnergyService.name); } /** @@ -41,6 +44,10 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) onModuleInit(): void { noble.on('stateChange', BluetoothLowEnergyService.handleStateChange); noble.on('discover', this.handleDiscovery.bind(this)); + + if (!this.isWhitelistEnabled()) { + this.logger.warn('The whitelist is empty, no sensors will be created!'); + } } /** @@ -78,6 +85,10 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) ); this.handleNewDistance(event); this.clusterService.publish(NEW_DISTANCE_CHANNEL, event); + } else { + this.logger.debug( + `Ignoring tag with id ${tag.id} and signal strength ${tag.rssi}` + ); } } @@ -114,6 +125,15 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) ); // expected ibeacon data length } + /** + * Determines whether a whitelist has been configured or not. + * + * @returns Whitelist status + */ + isWhitelistEnabled(): boolean { + return this.config.whitelist?.length > 0; + } + /** * Checks if an id is on the whitelist of this component. * Always returns true if the whitelist is empty. @@ -124,7 +144,7 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) isOnWhitelist(id: string): boolean { const whitelist = this.config.whitelist; if (whitelist === undefined || whitelist.length === 0) { - return true; + return false; } return this.config.whitelistRegex