diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 9f0ba155..04336190 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -160,10 +160,29 @@ class Bridge extends Extension { } async permitJoin(message) { - const value = this.getValue(message); - await this.zigbee.permitJoin(value); + if (typeof message === 'object' && !message.hasOwnProperty('value')) { + throw new Error('Invalid payload'); + } + + let value; + let resolvedEntity; + if (typeof message === 'object') { + value = message.value; + if (message.device) { + resolvedEntity = this.zigbee.resolveEntity(message.device); + if (!resolvedEntity || resolvedEntity.type !== 'device') { + throw new Error(`Device '${message.device}' does not exist`); + } + } + } else { + value = message; + } + + await this.zigbee.permitJoin(value, resolvedEntity); await this.publishInfo(); - return utils.getResponse(message, {value: value}, null); + return utils.getResponse( + message, resolvedEntity ? {value: value, device: message.device} : {value: value}, null, + ); } configLastSeen(message) { diff --git a/lib/zigbee.js b/lib/zigbee.js index 6bd802f8..c6e42d3c 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -116,12 +116,16 @@ class Zigbee extends events.EventEmitter { logger.info('zigbee-herdsman stopped'); } - async permitJoin(permit) { + async permitJoin(permit, resolvedEntity) { permit ? - logger.info('Zigbee: allowing new devices to join.') : + logger.info(`Zigbee: allowing new devices to join${resolvedEntity ? ` via ${resolvedEntity.name}` : ''}.`) : logger.info('Zigbee: disabling joining new devices.'); - await this.herdsman.permitJoin(permit); + if (resolvedEntity && permit) { + await this.herdsman.permitJoin(permit, resolvedEntity.device); + } else { + await this.herdsman.permitJoin(permit); + } } async getPermitJoin() { diff --git a/test/bridge.test.js b/test/bridge.test.js index b500ba51..8811f251 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -224,6 +224,34 @@ describe('Bridge', () => { ); }); + it('Should allow permit join via device', async () => { + const device = zigbeeHerdsman.devices.bulb; + zigbeeHerdsman.permitJoin.mockClear(); + MQTT.publish.mockClear(); + 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(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/permit_join', + stringify({"data":{"value":true,"device":"bulb"},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + + // Device does not exist + zigbeeHerdsman.permitJoin.mockClear(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({value: true, device: 'bulb_not_existing_woeeee'})); + await flushPromises(); + expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(0); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/permit_join', + stringify({"data":{},"status":"error","error":"Device 'bulb_not_existing_woeeee' does not exist"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should put transaction in response when request is done with transaction', async () => { MQTT.publish.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false, "transaction": 22})); @@ -249,10 +277,10 @@ describe('Bridge', () => { it('Should put error in response when format is incorrect', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value_not_good": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/config/last_seen', stringify({"value_not_good": false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bridge/response/permit_join', + 'zigbee2mqtt/bridge/response/config/last_seen', stringify({"data":{},"status":"error","error": "No value given"}), {retain: false, qos: 0}, expect.any(Function) );