From d8ceb3c1672c339f748ae4c7b5b54fb489834701 Mon Sep 17 00:00:00 2001 From: "dominic.griesel" Date: Tue, 7 Aug 2018 16:38:03 +0200 Subject: [PATCH 1/2] Started working on presence detection --- src/lib/iobroker-objects.ts | 6 ++-- src/lib/presence.ts | 20 +++++++++++++ src/main.ts | 57 +++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 30 deletions(-) create mode 100644 src/lib/presence.ts diff --git a/src/lib/iobroker-objects.ts b/src/lib/iobroker-objects.ts index 5389cdf9..21ccb045 100644 --- a/src/lib/iobroker-objects.ts +++ b/src/lib/iobroker-objects.ts @@ -2,7 +2,7 @@ import { ChannelObjectDefinition, DeviceObjectDefinition, StateObjectDefinition import { Global as _ } from "./global"; /** Extends a device object in the ioBroker objects DB */ -export async function extendDevice( +export async function extendDeviceObject( deviceId: string, peripheral: BLE.Peripheral, object: DeviceObjectDefinition, @@ -42,7 +42,7 @@ export async function extendDevice( } -export async function extendChannel( +export async function extendChannelObject( channelId: string, object: ChannelObjectDefinition, ) { @@ -74,7 +74,7 @@ export async function extendChannel( } } -export async function extendState( +export async function extendStateObject( stateId: string, object: StateObjectDefinition, ) { diff --git a/src/lib/presence.ts b/src/lib/presence.ts new file mode 100644 index 00000000..527d887c --- /dev/null +++ b/src/lib/presence.ts @@ -0,0 +1,20 @@ +export class PresenceInfo { + /** A list of recent timestamps when this device has been detected */ + public recentTimestamps: number[] = []; + /** The duration in milliseconds this presence info will be kept for */ + public expiryDuration: number = 10000; + + /** Tries to expire this presence info. In the process, recentTimestamps is cleaned from old entries */ + public expire(): boolean { + const now = Date.now(); + this.recentTimestamps = this.recentTimestamps.filter(ts => (now - ts) > this.expiryDuration); + return this.recentTimestamps.length === 0; + } + + /** Computes the average frequency this device is updated with */ + public computeUpdateFrequency() { + if (this.recentTimestamps.length < 2) return null; + return (this.recentTimestamps[this.recentTimestamps.length - 1] - this.recentTimestamps[0]) + / (this.recentTimestamps.length - 1); + } +} diff --git a/src/main.ts b/src/main.ts index 40461703..87086513 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { exec } from "child_process"; import { ExtendedAdapter, Global as _ } from "./lib/global"; -import { extendChannel, extendDevice, extendState } from "./lib/iobroker-objects"; +import { extendChannelObject, extendDeviceObject, extendStateObject } from "./lib/iobroker-objects"; import utils from "./lib/utils"; // Load all registered plugins @@ -226,29 +226,7 @@ async function onDiscover(peripheral: BLE.Peripheral) { return; } - // Always ensure the rssi state exists and gets a value - await extendState(`${deviceId}.rssi`, { - id: "rssi", - common: { - role: "value.rssi", - name: "signal strength (RSSI)", - desc: "Signal strength of the device", - type: "number", - read: true, - write: false, - }, - native: {}, - }); - // update RSSI information - const rssiState = await adapter.$getState(`${deviceId}.rssi`); - if ( - rssiState == null || - (rssiState.val !== peripheral.rssi && // only save changes - rssiState.lc + rssiUpdateInterval < Date.now()) // and dont update too frequently - ) { - _.log(`updating rssi state for ${deviceId}`, "debug"); - await adapter.$setState(`${deviceId}.rssi`, peripheral.rssi, true); - } + await updateMetadata(deviceId, peripheral); // Now update device-specific objects and states const context = plugin.createContext(peripheral); @@ -259,19 +237,19 @@ async function onDiscover(peripheral: BLE.Peripheral) { if (objects == null) return; // Ensure the device object exists - await extendDevice(deviceId, peripheral, objects.device); + await extendDeviceObject(deviceId, peripheral, objects.device); // Ensure the channel objects exist (optional) if (objects.channels != null && objects.channels.length > 0) { await Promise.all( objects.channels.map( - c => extendChannel(deviceId + "." + c.id, c), + c => extendChannelObject(deviceId + "." + c.id, c), ), ); } // Ensure the state objects exist. These might change in every advertisement frame await Promise.all( objects.states.map( - s => extendState(deviceId + "." + s.id, s), + s => extendStateObject(deviceId + "." + s.id, s), ), ); @@ -294,6 +272,31 @@ async function onDiscover(peripheral: BLE.Peripheral) { } +async function updateMetadata(deviceId: string, peripheral: BLE.Peripheral) { + // Always ensure the rssi state exists and gets a value + await extendStateObject(`${deviceId}.rssi`, { + id: "rssi", + common: { + role: "value.rssi", + name: "signal strength (RSSI)", + desc: "Signal strength of the device", + type: "number", + read: true, + write: false, + }, + native: {}, + }); + // update RSSI information + const rssiState = await adapter.$getState(`${deviceId}.rssi`); + if (rssiState == null || + (rssiState.val !== peripheral.rssi && // only save changes + rssiState.lc + rssiUpdateInterval < Date.now()) // and dont update too frequently + ) { + _.log(`updating rssi state for ${deviceId}`, "debug"); + await adapter.$setState(`${deviceId}.rssi`, peripheral.rssi, true); + } +} + let isScanning = false; function startScanning() { if (isScanning) return; From 73906c64f2f041ab9b1d324af84f32d68e874364 Mon Sep 17 00:00:00 2001 From: "dominic.griesel" Date: Thu, 9 Aug 2018 17:16:09 +0200 Subject: [PATCH 2/2] Prepare presence states and counting --- src/main.ts | 74 +++++++++++++++++++++++++++++++++++++++++++++++---- tsconfig.json | 3 ++- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/main.ts b/src/main.ts index 87086513..599dcbc3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { exec } from "child_process"; import { ExtendedAdapter, Global as _ } from "./lib/global"; import { extendChannelObject, extendDeviceObject, extendStateObject } from "./lib/iobroker-objects"; +import { PresenceInfo } from "./lib/presence"; import utils from "./lib/utils"; // Load all registered plugins @@ -16,6 +17,10 @@ let services: string[] = []; /** How frequent the RSSI of devices should be updated */ let rssiUpdateInterval: number = 0; +/** A map of all currently active devices */ +const presenceMap = new Map(); +let expirePresenceMapTimer: NodeJS.Timer; + // noble-Treiber-Instanz let noble; @@ -89,17 +94,19 @@ let adapter: ExtendedAdapter = utils.adapter({ }); if (noble.state === "poweredOn") startScanning(); adapter.setState("info.driverState", noble.state, true); + + // regularly expire the presence map + expirePresenceMapTimer = setInterval(expirePresenceMap, 10000); }, // is called when adapter shuts down - callback has to be called under any circumstances! unload: (callback) => { try { + if (expirePresenceMapTimer) clearInterval(expirePresenceMapTimer); stopScanning(); noble.removeAllListeners("stateChange"); - callback(); - } catch (e) { - callback(); - } + } catch (e) { /* noop */ } + callback(); }, // is called if a subscribed object changes @@ -210,7 +217,7 @@ async function onDiscover(peripheral: BLE.Peripheral) { return; } - const deviceId = peripheral.address; + const deviceId = getDeviceID(peripheral); // find out which plugin is handling this let plugin: Plugin; @@ -272,6 +279,13 @@ async function onDiscover(peripheral: BLE.Peripheral) { } +/** + * Returns the device ID used for a peripheral + */ +function getDeviceID(peripheral: BLE.Peripheral) { + return peripheral.address; +} + async function updateMetadata(deviceId: string, peripheral: BLE.Peripheral) { // Always ensure the rssi state exists and gets a value await extendStateObject(`${deviceId}.rssi`, { @@ -295,6 +309,56 @@ async function updateMetadata(deviceId: string, peripheral: BLE.Peripheral) { _.log(`updating rssi state for ${deviceId}`, "debug"); await adapter.$setState(`${deviceId}.rssi`, peripheral.rssi, true); } + + // Ensure the channel for meta information exists + await extendChannelObject(`${deviceId}.meta`, { + id: "meta", + common: { + name: "meta information", + desc: "Meta information about this device", + role: "meta", + }, + native: {}, + }); + // TODO: where to put the isKnownDevice flag? On the channel object? + + // Ensure the following state objects exist and have a value + // Presence counter + await extendStateObject(`${deviceId}.meta.presenceCounter`, { + id: "presenceCounter", + common: { + name: "presence counter", + desc: "Internally, this is used to determine whether a device is present or not", + role: "meta", + type: "number", + read: true, + write: false, + def: 0, + }, + native: {}, + }); + // TODO: Update presence counter value... When to increase it? When to decrease it? + await extendStateObject(`${deviceId}.meta.isPresent`, { + id: "isPresent", + common: { + name: "is present", + desc: "Whether this device is present or not", + role: "meta", + type: "boolean", + read: true, + write: false, + def: false, + }, + native: {}, + }); + // TODO: Update isPresent state (dependent on presence counter value) +} + +/** Checks all entries of the presence map and removes them if necessary */ +function expirePresenceMap() { + for (const [deviceID, p] of presenceMap.entries()) { + if (p.expire()) presenceMap.delete(deviceID); + } } let isScanning = false; diff --git a/tsconfig.json b/tsconfig.json index 8a0e9edc..238dc2bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "stripInternal": true, "target": "es5", "lib": [ "es6" ], - "strictPropertyInitialization": true + "strictPropertyInitialization": true, + "downlevelIteration": true //"watch": true }, "include": [