From d53b63d67ed09348da21453df4aa21c1031872b5 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Sun, 3 Jan 2021 11:08:33 +0100 Subject: [PATCH] Allow joining for a specific amount of time. https://github.com/nurikk/z2m-frontend/issues/202 --- lib/extension/bridge.js | 17 ++++++++++++----- lib/zigbee.js | 7 ++++--- test/bridge.test.js | 30 ++++++++++++++++++++++++------ test/controller.test.js | 4 ++-- test/legacy/bridgeLegacy.test.js | 4 ++-- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 70b5e9037..bb7c3a0c1 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -43,6 +43,7 @@ class Bridge extends Extension { this.eventBus.on(`groupMembersChanged`, () => this.publishGroups()); this.eventBus.on(`devicesChanged`, () => this.publishDevices()); + this.zigbee.on('permitJoinChanged', (data) => this.permitJoinChanged(data)); await this.publishInfo(); await this.publishDevices(); await this.publishGroups(); @@ -61,6 +62,10 @@ class Bridge extends Extension { logger.add(new EventTransport({level: logger.getLevel()})); } + permitJoinChanged(data) { + this.publishInfo(); + } + async onMQTTMessage(topic, message) { const match = topic.match(requestRegex); if (match && this.requestLookup[match[1].toLowerCase()]) { @@ -165,9 +170,11 @@ class Bridge extends Extension { } let value; + let time; let resolvedEntity; if (typeof message === 'object') { value = message.value; + time = message.time; if (message.device) { resolvedEntity = this.zigbee.resolveEntity(message.device); if (!resolvedEntity || resolvedEntity.type !== 'device') { @@ -178,11 +185,11 @@ class Bridge extends Extension { value = message; } - await this.zigbee.permitJoin(value, resolvedEntity); - await this.publishInfo(); - return utils.getResponse( - message, resolvedEntity ? {value: value, device: message.device} : {value: value}, null, - ); + await this.zigbee.permitJoin(value, resolvedEntity, time); + const response = {value}; + if (resolvedEntity) response.device = message.device; + if (time) response.time = message.time; + return utils.getResponse(message, response, null); } configLastSeen(message) { diff --git a/lib/zigbee.js b/lib/zigbee.js index bbc699665..ab892460e 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -68,6 +68,7 @@ class Zigbee extends events.EventEmitter { this.herdsman.on('deviceJoined', (data) => this.emit('event', 'deviceJoined', data)); this.herdsman.on('deviceLeave', (data) => this.emit('event', 'deviceLeave', data)); this.herdsman.on('message', (data) => this.emit('event', 'message', data)); + this.herdsman.on('permitJoinChanged', (data) => this.emit('permitJoinChanged', data)); logger.info('zigbee-herdsman started'); logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`); @@ -118,15 +119,15 @@ class Zigbee extends events.EventEmitter { logger.info('zigbee-herdsman stopped'); } - async permitJoin(permit, resolvedEntity) { + async permitJoin(permit, resolvedEntity, time=undefined) { permit ? logger.info(`Zigbee: allowing new devices to join${resolvedEntity ? ` via ${resolvedEntity.name}` : ''}.`) : logger.info('Zigbee: disabling joining new devices.'); if (resolvedEntity && permit) { - await this.herdsman.permitJoin(permit, resolvedEntity.device); + await this.herdsman.permitJoin(permit, resolvedEntity.device, time); } else { - await this.herdsman.permitJoin(permit); + await this.herdsman.permitJoin(permit, undefined, time); } } diff --git a/test/bridge.test.js b/test/bridge.test.js index d464ce1f3..88170d647 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -201,8 +201,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', 'true'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":true},"status":"ok"}), @@ -214,8 +213,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false})); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":false},"status":"ok"}), @@ -235,6 +233,27 @@ describe('Bridge', () => { ); }); + it('Should allow permit join for certain time', async () => { + zigbeeHerdsman.permitJoin.mockClear(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false, "time": 10})); + await flushPromises(); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, 10); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/permit_join', + stringify({"data":{"value":false,"time": 10},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should republish bridge info when permit join changes', async () => { + MQTT.publish.mockClear(); + await zigbeeHerdsman.events.permitJoinChanged({permitted: false, time: 10}); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + }); + it('Should allow permit join via device', async () => { const device = zigbeeHerdsman.devices.bulb; zigbeeHerdsman.permitJoin.mockClear(); @@ -242,8 +261,7 @@ describe('Bridge', () => { MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({value: true, device: 'bulb'})); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, device); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, device, undefined); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({"data":{"value":true,"device":"bulb"},"status":"ok"}), diff --git a/test/controller.test.js b/test/controller.test.js index fb0ad5544..dec20b768 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -36,7 +36,7 @@ describe('Controller', () => { expect(zigbeeHerdsman.setLED).toHaveBeenCalledTimes(0); expect(zigbeeHerdsman.setTransmitPower).toHaveBeenCalledTimes(0); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); expect(logger.info).toHaveBeenCalledWith(`Currently ${Object.values(zigbeeHerdsman.devices).length - 1} devices are joined:`) expect(logger.info).toHaveBeenCalledWith('bulb (0x000b57fffec6a5b2): LED1545G12 - IKEA TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (Router)'); expect(logger.info).toHaveBeenCalledWith('remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch (EndDevice)'); @@ -202,7 +202,7 @@ describe('Controller', () => { settings.set(['permit_join'], false); await controller.start(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); }); it('Start controller with disable_led', async () => { diff --git a/test/legacy/bridgeLegacy.test.js b/test/legacy/bridgeLegacy.test.js index 8823eb2ab..f94c5cba2 100644 --- a/test/legacy/bridgeLegacy.test.js +++ b/test/legacy/bridgeLegacy.test.js @@ -123,12 +123,12 @@ describe('Bridge legacy', () => { MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'true'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(true, undefined, undefined); zigbeeHerdsman.permitJoin.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/config/permit_join', 'false'); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); - expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false, undefined, undefined); }); it('Should allow to reset', async () => {