diff --git a/config.schema.json b/config.schema.json index eeb235a..fc4b6d0 100644 --- a/config.schema.json +++ b/config.schema.json @@ -58,7 +58,7 @@ "type": "boolean" }, "disable-thumbnail-refresh": { - "title": "Disable thumbnail refresh", + "title": "Disable thumbnail auto-refresh", "default": false, "description": "By default, the thumbnails of the cameras will refresh when the camera is enabled, disabling the auto refresh keeps the default thumbnail untouched.", "type": "boolean" @@ -67,8 +67,8 @@ "title": "Camera Thumbnail Refresh (seconds)", "type": "integer", "minimum": 0, - "placeholder": "60", - "description": "Force Thumbnail refresh every N seconds. Disabling thumbnail refresh setting takes priority over the timeout." + "placeholder": "3600", + "description": "Force Thumbnail refresh every N seconds. If auto refresh is disabled, this value is ignored." }, "camera-status-polling-seconds": { "title": "Status Polling (seconds)", diff --git a/src/blink-api.js b/src/blink-api.js index 95ed78b..c171b71 100644 --- a/src/blink-api.js +++ b/src/blink-api.js @@ -326,6 +326,14 @@ class BlinkAPI { await sleep(500); return this._request(method, path, body, maxTTL, false, httpErrorAsError); } + else if (res.status === 409) { + if (httpErrorAsError) { + if (!/busy/.test(res?._body?.message)) { + const status = res.headers.get('status') || res.status + ' ' + res.statusText; + throw new Error(`${method} ${targetPath} (${status})`); + } + } + } else if (res.status >= 400) { const status = res.headers.get('status') || res.status + ' ' + res.statusText; log.error(`${method} ${targetPath} (${status})`); diff --git a/src/blink.js b/src/blink.js index 23aa743..c7a0e48 100644 --- a/src/blink.js +++ b/src/blink.js @@ -5,7 +5,7 @@ const fs = require('fs'); const {stringify} = require('./stringify'); // const stringify = JSON.stringify; -const THUMBNAIL_TTL = 1 * 60; // 1min +const THUMBNAIL_TTL = 60 * 60; // 1min const BATTERY_TTL = 60 * 60; // 60min const MOTION_POLL = 15; const STATUS_POLL = 30; @@ -96,7 +96,11 @@ class BlinkNetwork extends BlinkDevice { } get status() { - return this.syncModule?.status; + return this.data.status ?? this.syncModule?.status; + } + + get online() { + return ['online'].includes(this.status); } get armed() { @@ -153,6 +157,10 @@ class BlinkCamera extends BlinkDevice { return this.data.status && this.data.status !== 'done' ? this.data.status : this.network.status; } + get online() { + return ['online', 'done'].includes(this.data.status) && (this.isCameraMini || this.network.online); + } + get armed() { return this.network.armed; } @@ -178,11 +186,17 @@ class BlinkCamera extends BlinkDevice { } get thumbnailCreatedAt() { + // we store it on the .data object to it will be auto scrubbed on the next data poll if (this.data.thumbnail_created_at) return this.data.thumbnail_created_at; - const dateRegex = /(\d{4})_(\d\d)_(\d\d)__(\d\d)_(\d\d)(am|pm)?$/i; - const [, year, month, day, hour, minute] = dateRegex.exec(this.thumbnail) || []; - this.thumbnailCreatedAt = Date.parse(`${year}-${month}-${day} ${hour}:${minute} +000`) || Date.now(); + const dateRegex = /(\d{4})_(\d\d)_(\d\d)__(\d\d)_(\d\d)(?:am|pm)?$|[?&]ts=(\d+)(?:&|$)/i; + const [, year, month, day, hour, minute, epoch] = dateRegex.exec(this.thumbnail) || []; + if (epoch) { + this.thumbnailCreatedAt = Date.parse(new Date(Number(epoch.padEnd(13, '0'))).toISOString()); + } + else { + this.thumbnailCreatedAt = Date.parse(`${year}-${month}-${day} ${hour}:${minute} +000`) || Date.now(); + } return this.data.thumbnail_created_at; } @@ -288,7 +302,8 @@ class BlinkCamera extends BlinkDevice { if (this.cacheThumbnail.has(thumbnailUrl)) return this.cacheThumbnail.get(thumbnailUrl); - const data = await this.blink.getUrl(thumbnailUrl + '.jpg'); + // legacy thumbnails need a suffix of .jpg appended to the url + const data = await this.blink.getUrl(thumbnailUrl.replace(/\.jpg|$/, '.jpg')); this.cacheThumbnail.clear(); // avoid memory from getting large this.cacheThumbnail.set(thumbnailUrl, data); return data; @@ -371,14 +386,14 @@ class Blink { const start = Date.now(); // if there is an error, we are going to retry for 15s and fail - let cmd = await Promise.resolve(fn()).catch(e => log.error(e)) || {message: 'busy'}; + let cmd = await Promise.resolve(fn()).catch(() => undefined) || {message: 'busy'}; while (cmd.message && /busy/i.test(cmd.message)) { // TODO: should this be an error? log.info(`Sleeping ${busyWait}s: ${cmd.message}`); await sleep(busyWait * 1000); if (Date.now() - start > timeout * 1000) return; - cmd = await Promise.resolve(fn()).catch(e => log.error(e)) || {message: 'busy'}; + cmd = await Promise.resolve(fn()).catch(() => undefined) || {message: 'busy'}; } return await this._commandWaitAll(networkID, cmd, timeout); } @@ -660,9 +675,16 @@ class Blink { const eligible = force || (camera.armed && camera.enabled); if (eligible && Date.now() >= lastSnapshot) { + if (camera.lowBattery || !camera.online) { + log(`${camera.name} - ${!camera.online ? 'Offline' : 'Low Battery'}; Skipping snapshot`); + return false; + } + + // set the thumbnail to the future to avoid pile-ons + camera.thumbnailCreatedAt = Date.now(); const networkID = camera.networkID; const cameraID = camera.cameraID; - log(`Refreshing snapshot for ${camera.name}`); + log(`${camera.name} - Refreshing snapshot`); let updateCamera = this.blinkAPI.updateCameraThumbnail; if (camera.isCameraMini) updateCamera = this.blinkAPI.updateOwlThumbnail; @@ -696,7 +718,7 @@ class Blink { const lastMedia = await this.getCameraLastMotion(camera.networkID, camera.cameraID); const lastSnapshot = Date.parse(lastMedia.created_at) + (this.snapshotRate * 1000); if (force || (camera.armed && camera.enabled && Date.now() >= lastSnapshot)) { - log(`Refreshing clip for ${camera.name}`); + log(`${camera.name} - Refreshing clip`); const cmd = async () => await this.blinkAPI.updateCameraClip(camera.networkID, camera.cameraID); await this._command(camera.networkID, cmd); diff --git a/src/blink.test.js b/src/blink.test.js index ab1538d..7f43625 100644 --- a/src/blink.test.js +++ b/src/blink.test.js @@ -63,47 +63,6 @@ describe('Blink', () => { await blink.logout(); }); - test.concurrent('refreshData()', async () => { - const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); - blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); - await blink.refreshData(); - expect(blink.blinkAPI.getAccountHomescreen).toHaveBeenCalled(); - expect(blink.networks.size).toBe(3); - expect(blink.cameras.size).toBe(3); - }); - test.concurrent('_commandWait()', async () => { - const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); - blink.blinkAPI.getAccountHomescreen.mockResolvedValue(JSON.parse(JSON.stringify(SAMPLE.HOMESCREEN))); - await blink.refreshData(); - - blink.blinkAPI.getCommand.mockResolvedValue(SAMPLE.COMMAND_RUNNING); - blink.blinkAPI.deleteCommand.mockResolvedValue({}); - const {id: commandID, network_id: networkID} = SAMPLE.COMMAND_RUNNING.commands[0]; - await blink._commandWait(networkID, commandID, 0.0001); - - expect(blink.blinkAPI.getCommand).toBeCalledTimes(2); - expect(blink.blinkAPI.deleteCommand).toBeCalledTimes(1); - - expect(await blink._commandWait(networkID)).toBeUndefined(); - expect(await blink._commandWait(null, commandID)).toBeUndefined(); - }); - test.concurrent('_lock()', async () => { - const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); - - let calls = 0; - const cmd = async () => { - await sleep(1); - calls++; - }; - blink._lock('test', cmd); - await blink._lock('test', cmd); - await sleep(11); - - expect(calls).toBe(1); - - await blink._lock('test', cmd); - expect(calls).toBe(2); - }); describe('BlinkCamera', () => { test.concurrent('.data', async () => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); @@ -228,13 +187,15 @@ describe('Blink', () => { }); test.concurrent.each([ - [false, false, false, false, 0, 0], - [false, false, true, false, 0, 0], - [false, false, false, true, 0, 0], - [false, false, true, true, Date.now(), 0], - [false, true, true, true, 0, 1], - [true, true, true, true, 0, 1], - ])('BlinkCamera.refreshThumbnail()', async (mini, force, armed, enabled, thumbnailDate, expected) => { + [false, false, false, false, 'ok', 'online', 1, 0], + [false, false, true, false, 'ok', 'online', 1, 0], + [false, false, false, true, 'ok', 'online', 1, 0], + [false, false, true, true, 'ok', 'online', Date.now(), 0], + [false, true, true, true, 'ok', 'online', 1, 1], + [true, true, true, true, null, 'online', 1, 1], + [false, true, true, true, 'ok', 'offline', 1, 0], + [false, true, true, true, 'low', 'online', 1, 0], + ])('BlinkCamera.refreshThumbnail()', async (mini, force, armed, enabled, battery, status, thumbnailDate, calls) => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); blink.blinkAPI.updateCameraThumbnail.mockResolvedValue(SAMPLE.UPDATE_THUMBNAIL); @@ -246,14 +207,16 @@ describe('Blink', () => { const cameraDevice = blink.cameras.get(cameraData.id); cameraDevice.network.data.armed = armed; cameraDevice.data.enabled = enabled; + if (battery) cameraDevice.data.battery = battery; + if (status) cameraDevice.data.status = status; if (thumbnailDate) cameraDevice.thumbnailCreatedAt = thumbnailDate; await cameraDevice.refreshThumbnail(force); - expect(blink.blinkAPI.updateCameraThumbnail).toBeCalledTimes(!mini ? expected : 0); - expect(blink.blinkAPI.updateOwlThumbnail).toBeCalledTimes(mini ? expected : 0); - expect(blink.blinkAPI.getCommand).toBeCalledTimes(expected); - expect(blink.blinkAPI.getAccountHomescreen).toBeCalledTimes(expected + 1); + expect(blink.blinkAPI.updateCameraThumbnail).toBeCalledTimes(!mini ? calls : 0); + expect(blink.blinkAPI.updateOwlThumbnail).toBeCalledTimes(mini ? calls : 0); + expect(blink.blinkAPI.getCommand).toBeCalledTimes(calls); + expect(blink.blinkAPI.getAccountHomescreen).toBeCalledTimes(calls + 1); }); test.concurrent.each([ @@ -322,6 +285,11 @@ describe('Blink', () => { expect(blink.blinkAPI.getAccountHomescreen).toBeCalledTimes(expected + 1); }); + test.concurrent.each([ + [], + ])('.thumbnailCreatedAt', async (url, date) => { + + }); test.concurrent.each([ [false, false, true, false, 0, BlinkCamera.PRIVACY_BYTES], [false, true, true, false, 0, BlinkCamera.PRIVACY_BYTES], @@ -435,7 +403,7 @@ describe('Blink', () => { }); }); describe('Blink', () => { - test.concurrent('Blink.diagnosticDebug()', async () => { + test.concurrent('.diagnosticDebug()', async () => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.login.mockResolvedValue({}); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); @@ -466,6 +434,47 @@ describe('Blink', () => { await blink.refreshData(); await blink.diagnosticDebug(); }); + test.concurrent('refreshData()', async () => { + const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); + blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); + await blink.refreshData(); + expect(blink.blinkAPI.getAccountHomescreen).toHaveBeenCalled(); + expect(blink.networks.size).toBe(3); + expect(blink.cameras.size).toBe(3); + }); + test.concurrent('_commandWait()', async () => { + const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); + blink.blinkAPI.getAccountHomescreen.mockResolvedValue(JSON.parse(JSON.stringify(SAMPLE.HOMESCREEN))); + await blink.refreshData(); + + blink.blinkAPI.getCommand.mockResolvedValue(SAMPLE.COMMAND_RUNNING); + blink.blinkAPI.deleteCommand.mockResolvedValue({}); + const {id: commandID, network_id: networkID} = SAMPLE.COMMAND_RUNNING.commands[0]; + await blink._commandWait(networkID, commandID, 0.0001); + + expect(blink.blinkAPI.getCommand).toBeCalledTimes(2); + expect(blink.blinkAPI.deleteCommand).toBeCalledTimes(1); + + expect(await blink._commandWait(networkID)).toBeUndefined(); + expect(await blink._commandWait(null, commandID)).toBeUndefined(); + }); + test.concurrent('_lock()', async () => { + const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); + + let calls = 0; + const cmd = async () => { + await sleep(1); + calls++; + }; + blink._lock('test', cmd); + await blink._lock('test', cmd); + await sleep(11); + + expect(calls).toBe(1); + + await blink._lock('test', cmd); + expect(calls).toBe(2); + }); test.concurrent.each([ [true, true, 1], [true, false, 2], @@ -494,13 +503,16 @@ describe('Blink', () => { } expect(blink.blinkAPI.armNetwork).toBeCalledTimes(expectedAPI); expect(blink.blinkAPI.getCommand).toBeCalledTimes(timeout ? 0 : 1); + }); + test.concurrent('.refreshCameraThumbnail()', async () => { + }); test.concurrent.each([ [0, Date.now(), 0, true, 0], [Date.now(), 0, -1, true, 1], [Date.now(), 0, 0, true, 1], [Date.now()-1, 0, Date.now(), false, 1], - ])('Blink.getCameraLastThumbnail()', async (cameraAt, thumbnailAt, mediaAt, expectThumbnail, expectAPI) => { + ])('.getCameraLastThumbnail()', async (cameraAt, thumbnailAt, mediaAt, expectThumbnail, expectAPI) => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); await blink.refreshData(); @@ -528,7 +540,7 @@ describe('Blink', () => { test.concurrent.each([ true, false, - ])('Blink.getCameraLastVideo()', async hasClip => { + ])('.getCameraLastVideo()', async hasClip => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); await blink.refreshData(); @@ -555,7 +567,7 @@ describe('Blink', () => { [false, null, 0, false], [true, 7000001, 1, true], [false, 7000001, 1, true], - ])('Blink.deleteCameraMotion()', async (hasClip, motionId, expectDelete, success) => { + ])('.deleteCameraMotion()', async (hasClip, motionId, expectDelete, success) => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); await blink.refreshData(); @@ -579,7 +591,7 @@ describe('Blink', () => { [true, false, true, 2], [true, true, false, 2], [true, false, false, 4], - ])('Blink.getSavedMedia()', async (hasClip, useNetworkId, useCameraId, expectedCount) => { + ])('.getSavedMedia()', async (hasClip, useNetworkId, useCameraId, expectedCount) => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); await blink.refreshData(); @@ -597,7 +609,7 @@ describe('Blink', () => { expect(res).toHaveLength(expectedCount); expect(blink.blinkAPI.getMediaChange).toBeCalledTimes(1); }); - test.concurrent('Blink.stopCameraLiveView()', async () => { + test.concurrent('.stopCameraLiveView()', async () => { const blink = new Blink(DEFAULT_BLINK_CLIENT_UUID); blink.blinkAPI.getAccountHomescreen.mockResolvedValue(SAMPLE.HOMESCREEN); await blink.refreshData(); diff --git a/src/homebridge/blink-hap.js b/src/homebridge/blink-hap.js index d4d0227..210b0fc 100644 --- a/src/homebridge/blink-hap.js +++ b/src/homebridge/blink-hap.js @@ -150,6 +150,17 @@ class BlinkDeviceHAP extends BlinkDevice { super(data, blink); } + static formatBoolean(val) { + if (val === true || val === 1) return '✅'; + if (val === false || val === 0) return '❌'; + return val; + } + + static formatDegrees(val) { + if (Number.isFinite(val)) return `${val}°C`; + return val; + } + bindCharacteristic(service, characteristic, desc, getFunc, setFunc, format) { const getCallback = async callback => { try { @@ -166,7 +177,7 @@ class BlinkDeviceHAP extends BlinkDevice { if (format && disp !== null) { disp = format.call(this, disp); } - log(`${desc} for ${this.name} is: ${disp}`); + log(`${this.name} - ${desc}: ${disp}`); }; const setCallback = async (val, callback) => { @@ -286,9 +297,11 @@ class BlinkNetworkHAP extends BlinkNetwork { if (!this.blink?.config?.noAlarm) { const securitySystem = this.accessory.addService(Service.SecuritySystem); this.bindCharacteristic(securitySystem, Characteristic.SecuritySystemCurrentState, - `${this.name} Armed (Current)`, this.getSecuritySystemCurrentState); + `${this.name} Armed (Current)`, async () => await this.getSecuritySystemCurrentState(), + null, BlinkDeviceHAP.formatBoolean); this.bindCharacteristic(securitySystem, Characteristic.SecuritySystemTargetState, - `${this.name} Armed (Target)`, this.getSecuritySystemState, this.setSecuritySystemState); + `${this.name} Armed (Target)`, async () => await this.getSecuritySystemState(), + async val => await this.setSecuritySystemState(val), BlinkDeviceHAP.formatBoolean); const validValues = [ Characteristic.SecuritySystemTargetState.STAY_ARM, Characteristic.SecuritySystemTargetState.AWAY_ARM, @@ -301,7 +314,7 @@ class BlinkNetworkHAP extends BlinkNetwork { const service = this.accessory.addService(Service.Switch, `${this.name} Arm`, `armed.${this.serial}`); this.bindCharacteristic(service, Characteristic.On, - `${this.name} Arm`, () => this.armed, this.setManualArmed); + `${this.name} Arm`, () => this.armed, this.setManualArmed, BlinkDeviceHAP.formatBoolean); this.bindCharacteristic(service, Characteristic.Name, `${this.name} Arm`, () => `Manual Arm`); } @@ -343,7 +356,7 @@ class BlinkCameraHAP extends BlinkCamera { // this.bindCharacteristic(cameraMode, Characteristic.PeriodicSnapshotsActive, // 'PeriodicSnapshotsActive', () => this.privacyMode); this.bindCharacteristic(cameraMode, Characteristic.ManuallyDisabled, - 'ManuallyDisabled', () => !this.network.armed); + 'ManuallyDisabled', () => !this.network.armed, null, BlinkDeviceHAP.formatBoolean); // const cameraStream = this.accessory.getService(Service.CameraRTPStreamManagement); // this.bindCharacteristic(cameraStream, Characteristic.Active, @@ -358,7 +371,7 @@ class BlinkCameraHAP extends BlinkCamera { const motionService = this.accessory.getService(Service.MotionSensor); this.bindCharacteristic(motionService, Characteristic.MotionDetected, - 'Motion', async () => await this.getMotionDetected()); + 'Motion', async () => await this.getMotionDetected(), null, BlinkDeviceHAP.formatBoolean); // disabling sensor active as it isn't clear if this actually is needed, but causes a lot of load on homebridge @@ -377,7 +390,7 @@ class BlinkCameraHAP extends BlinkCamera { // this.bindCharacteristic(batteryService, Characteristic.ChargingState, // 'Battery State', () => Characteristic.ChargingState.NOT_CHARGEABLE); this.bindCharacteristic(batteryService, Characteristic.StatusLowBattery, - 'Battery LowBattery', () => this.getLowBattery()); + 'Battery LowBattery', () => this.getLowBattery(), null, BlinkDeviceHAP.formatBoolean); // no temperature sensor on the minis if (!this.blink?.config?.noTemperatureSensor) { @@ -386,7 +399,7 @@ class BlinkCameraHAP extends BlinkCamera { // allow negative values tempService.getCharacteristic(Characteristic.CurrentTemperature).setProps({minValue: -100}); this.bindCharacteristic(tempService, Characteristic.CurrentTemperature, - 'Temperature', () => this.temperature); + 'Temperature', () => this.temperature, null, BlinkDeviceHAP.formatDegrees); // this.bindCharacteristic(tempService, Characteristic.StatusActive, // 'Temperature Sensor Active', () => true); } @@ -397,14 +410,14 @@ class BlinkCameraHAP extends BlinkCamera { const enabledSwitch = this.accessory.addService(Service.Switch, `${this.name} Motion Enabled`, `enabled.${this.serial}`); this.bindCharacteristic(enabledSwitch, Characteristic.On, - 'Enabled', () => this.getEnabled(), async val => await this.setEnabled(val)); + 'Enabled', () => this.getEnabled(), async val => await this.setEnabled(val), BlinkDeviceHAP.formatBoolean); } if (!this.blink?.config?.noPrivacySwitch) { const privacyModeService = this.accessory.addService(Service.Switch, `${this.name} Privacy Mode`, `privacy.${this.serial}`); this.bindCharacteristic(privacyModeService, Characteristic.On, - 'Privacy Mode', () => this.privacyMode, val => this.privacyMode = val); + 'Privacy Mode', () => this.privacyMode, val => this.privacyMode = val, BlinkDeviceHAP.formatBoolean); } // TODO: use snapshot_period_minutes for poll diff --git a/src/homebridge/blink-hap.test.js b/src/homebridge/blink-hap.test.js index 9e5e4d3..3a55f20 100644 --- a/src/homebridge/blink-hap.test.js +++ b/src/homebridge/blink-hap.test.js @@ -141,6 +141,18 @@ describe('BlinkHAP', () => { blinkDevice2.createAccessory(homebridge, [blinkDevice.accessory]); expect(blinkDevice2.context.cacheKey).toBe('CACHED_VALUE'); }); + test.concurrent('.formatDegrees()', () => { + expect(BlinkDeviceHAP.formatDegrees(37.1)).toBe('37.1°C'); + expect(BlinkDeviceHAP.formatDegrees(-40.0)).toBe('-40°C'); + expect(BlinkDeviceHAP.formatDegrees('test')).toBe('test'); + }); + test.concurrent('.formatBoolean()', () => { + expect(BlinkDeviceHAP.formatBoolean(true)).toBe('✅'); + expect(BlinkDeviceHAP.formatBoolean(1)).toBe('✅'); + expect(BlinkDeviceHAP.formatBoolean(false)).toBe('❌'); + expect(BlinkDeviceHAP.formatBoolean(0)).toBe('❌'); + expect(BlinkDeviceHAP.formatBoolean('test')).toBe('test'); + }); }); describe('BlinkNetworkHAP', () => { test.concurrent.each([ diff --git a/src/homebridge/index.js b/src/homebridge/index.js index c68b049..a848e6b 100644 --- a/src/homebridge/index.js +++ b/src/homebridge/index.js @@ -14,7 +14,7 @@ class HomebridgeBlink { this.config = config || {}; this.log = logger; this.api = api; - setLogger(logger, this.config['enable-verbose-logging'], this.config['enable-debug-logging']); + setLogger(logger, ['verbose', 'debug'].includes(this.config['logging']), this.config['logging'] === 'debug'); this.accessoryLookup = []; this.cachedAccessories = [];