From ec48507519877d9bc222fcca9f2157e833d3fd06 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Sun, 31 May 2020 12:02:30 +0200 Subject: [PATCH] feat(bluetooth-low-energy): add RSSI and measuredPower to entity Tuning BLE values is tricky if the measured RSSI and the measuredPower used for calculation are not visible anywhere. Putting them into the attributes would cause a lot of data to be constantly updated in the other integrations, so now they are just available in the API as an additional field of the entity. Closes #140, #177 and #203 --- .../bluetooth-low-energy-presence.sensor.ts | 29 ++++++++++++ .../bluetooth-low-energy.service.spec.ts | 44 ++++++++++++++++--- .../bluetooth-low-energy.service.ts | 19 +++++--- .../new-distance.event.ts | 6 +++ 4 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 src/integrations/bluetooth-low-energy/bluetooth-low-energy-presence.sensor.ts diff --git a/src/integrations/bluetooth-low-energy/bluetooth-low-energy-presence.sensor.ts b/src/integrations/bluetooth-low-energy/bluetooth-low-energy-presence.sensor.ts new file mode 100644 index 0000000..7c60823 --- /dev/null +++ b/src/integrations/bluetooth-low-energy/bluetooth-low-energy-presence.sensor.ts @@ -0,0 +1,29 @@ +import { RoomPresenceDistanceSensor } from '../room-presence/room-presence-distance.sensor'; + +class BluetoothLowEnergyMeasurement { + rssi: number; + measuredPower: number; + + constructor(rssi: number, measuredPower: number) { + this.rssi = rssi; + this.measuredPower = measuredPower; + } +} + +export class BluetoothLowEnergyPresenceSensor extends RoomPresenceDistanceSensor { + measuredValues: { [instance: string]: BluetoothLowEnergyMeasurement } = {}; + + handleNewMeasurement( + instanceName: string, + rssi: number, + measuredPower: number, + distance: number, + outOfRange: boolean + ): void { + this.measuredValues[instanceName] = new BluetoothLowEnergyMeasurement( + rssi, + measuredPower + ); + this.handleNewDistance(instanceName, distance, outOfRange); + } +} 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 6fb960b..164c0c7 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 @@ -33,7 +33,7 @@ import { BluetoothLowEnergyConfig } from './bluetooth-low-energy.config'; import { Sensor } from '../../entities/sensor'; import c from 'config'; import { NewDistanceEvent } from './new-distance.event'; -import { RoomPresenceDistanceSensor } from '../room-presence/room-presence-distance.sensor'; +import { BluetoothLowEnergyPresenceSensor } from './bluetooth-low-energy-presence.sensor'; import KalmanFilter from 'kalmanjs'; import { DeviceTracker } from '../../entities/device-tracker'; import * as util from 'util'; @@ -188,6 +188,8 @@ describe('BluetoothLowEnergyService', () => { 'test-instance', '2f234454cf6d4a0fadf2f4911ba9ffa6-1-2', 'Test Beacon', + -50, + -52, 0.7 ); expect(handleDistanceSpy).toHaveBeenCalledWith(expectedEvent); @@ -219,6 +221,8 @@ describe('BluetoothLowEnergyService', () => { 'test-instance', 'abcd1234', 'Test Beacon', + -59, + -59, 1 ); expect(handleDistanceSpy).toHaveBeenCalledWith(expectedEvent); @@ -247,6 +251,8 @@ describe('BluetoothLowEnergyService', () => { 'test-instance', '123-123', 'Test BLE Device', + -81, + -59, 10.5 ); expect(handleDistanceSpy).toHaveBeenCalledWith(expectedEvent); @@ -359,6 +365,8 @@ describe('BluetoothLowEnergyService', () => { 'test-instance', 'abcd', 'Test BLE Device', + -81, + -80, 1.1 ); expect(handleDistanceSpy).toHaveBeenCalledWith(expectedEvent); @@ -377,6 +385,7 @@ describe('BluetoothLowEnergyService', () => { expectedEvent.tagId = 'defg'; expectedEvent.distance = 10.5; + expectedEvent.measuredPower = -59; expect(handleDistanceSpy).toHaveBeenCalledWith(expectedEvent); expect(clusterService.publish).toHaveBeenCalledWith( NEW_DISTANCE_CHANNEL, @@ -658,27 +667,27 @@ describe('BluetoothLowEnergyService', () => { }); it('should pass distance information to existing room presence sensors', () => { - const sensor = new RoomPresenceDistanceSensor('test', 'Test', 0); + const sensor = new BluetoothLowEnergyPresenceSensor('test', 'Test', 0); entitiesService.has.mockReturnValue(true); entitiesService.get.mockReturnValue(sensor); const sensorHandleSpy = jest.spyOn(sensor, 'handleNewDistance'); service.handleNewDistance( - new NewDistanceEvent('test-instance', 'test', 'Test', 2) + new NewDistanceEvent('test-instance', 'test', 'Test', -80, -50, 2) ); expect(sensorHandleSpy).toHaveBeenCalledWith('test-instance', 2, false); }); it('should add new room presence sensor if no matching ones exist yet', () => { - const sensor = new RoomPresenceDistanceSensor('test', 'Test', 0); + const sensor = new BluetoothLowEnergyPresenceSensor('test', 'Test', 0); entitiesService.has.mockReturnValue(false); entitiesService.add.mockReturnValue(sensor); mockConfig.timeout = 20; const sensorHandleSpy = jest.spyOn(sensor, 'handleNewDistance'); service.handleNewDistance( - new NewDistanceEvent('test-instance', 'new', 'New Tag', 1.3) + new NewDistanceEvent('test-instance', 'new', 'New Tag', -80, -50, 1.3) ); expect(entitiesService.add).toHaveBeenCalledWith( @@ -699,6 +708,31 @@ describe('BluetoothLowEnergyService', () => { expect(sensorHandleSpy).toHaveBeenCalledWith('test-instance', 1.3, false); }); + it('should update the sensor RSSI and measuredPower information', () => { + const sensor = new BluetoothLowEnergyPresenceSensor('test', 'Test', 0); + entitiesService.has.mockReturnValue(true); + entitiesService.get.mockReturnValue(sensor); + + service.handleNewDistance( + new NewDistanceEvent('test-instance', 'test', 'Test', -80, -50, 2) + ); + + expect(sensor.measuredValues['test-instance'].rssi).toBe(-80); + expect(sensor.measuredValues['test-instance'].measuredPower).toBe(-50); + + service.handleNewDistance( + new NewDistanceEvent('test-instance-2', 'test', 'Test', -40, -45, 2) + ); + service.handleNewDistance( + new NewDistanceEvent('test-instance', 'test', 'Test', -70, -50, 2) + ); + + expect(sensor.measuredValues['test-instance-2'].rssi).toBe(-40); + expect(sensor.measuredValues['test-instance-2'].measuredPower).toBe(-45); + expect(sensor.measuredValues['test-instance'].rssi).toBe(-70); + expect(sensor.measuredValues['test-instance'].measuredPower).toBe(-50); + }); + it('should log the id of new peripherals that are found', () => { mockConfig.processIBeacon = true; jest.spyOn(service, 'isOnWhitelist').mockReturnValue(false); 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 444cfc6..ef25d6c 100644 --- a/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts +++ b/src/integrations/bluetooth-low-energy/bluetooth-low-energy.service.ts @@ -22,6 +22,7 @@ import { DISTRIBUTED_DEVICE_ID } from '../home-assistant/home-assistant.const'; import * as _ from 'lodash'; import { DeviceTracker } from '../../entities/device-tracker'; import { RoomPresenceDeviceTrackerProxyHandler } from '../room-presence/room-presence-device-tracker.proxy'; +import { BluetoothLowEnergyPresenceSensor } from './bluetooth-low-energy-presence.sensor'; export const NEW_DISTANCE_CHANNEL = 'bluetooth-low-energy.new-distance'; @@ -105,6 +106,8 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) globalSettings.instanceName, tag.id, tag.name, + tag.rssi, + tag.measuredPower, tag.distance, tag.distance > this.config.maxDistance ); @@ -127,9 +130,11 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) */ handleNewDistance(event: NewDistanceEvent): void { const sensorId = makeId(`ble ${event.tagId}`); - let sensor: RoomPresenceDistanceSensor; + let sensor: BluetoothLowEnergyPresenceSensor; if (this.entitiesService.has(sensorId)) { - sensor = this.entitiesService.get(sensorId) as RoomPresenceDistanceSensor; + sensor = this.entitiesService.get( + sensorId + ) as BluetoothLowEnergyPresenceSensor; } else { sensor = this.createRoomPresenceSensor( sensorId, @@ -138,8 +143,10 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) ); } - sensor.handleNewDistance( + sensor.handleNewMeasurement( event.instanceName, + event.rssi, + event.measuredPower, event.distance, event.outOfRange ); @@ -236,7 +243,7 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) sensorId: string, deviceId: string, deviceName: string - ): RoomPresenceDistanceSensor { + ): BluetoothLowEnergyPresenceSensor { const deviceTracker = this.createDeviceTracker( makeId(`${sensorId}-tracker`), deviceName @@ -256,7 +263,7 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) }, }, ]; - const rawSensor = new RoomPresenceDistanceSensor( + const rawSensor = new BluetoothLowEnergyPresenceSensor( sensorId, sensorName, this.config.timeout @@ -268,7 +275,7 @@ export class BluetoothLowEnergyService extends KalmanFilterable(Object, 0.8, 15) const sensor = this.entitiesService.add( proxiedSensor, customizations - ) as RoomPresenceDistanceSensor; + ) as BluetoothLowEnergyPresenceSensor; const interval = setInterval( sensor.updateState.bind(sensor), diff --git a/src/integrations/bluetooth-low-energy/new-distance.event.ts b/src/integrations/bluetooth-low-energy/new-distance.event.ts index 59db44a..a98aaaf 100644 --- a/src/integrations/bluetooth-low-energy/new-distance.event.ts +++ b/src/integrations/bluetooth-low-energy/new-distance.event.ts @@ -3,12 +3,16 @@ export class NewDistanceEvent { instanceName: string, tagId: string, tagName: string, + rssi: number, + measuredPower: number, distance: number, outOfRange = false ) { this.instanceName = instanceName; this.tagId = tagId; this.tagName = tagName; + this.rssi = rssi; + this.measuredPower = measuredPower; this.distance = distance; this.outOfRange = outOfRange; } @@ -16,6 +20,8 @@ export class NewDistanceEvent { instanceName: string; tagId: string; tagName: string; + rssi: number; + measuredPower: number; distance: number; outOfRange: boolean; }