diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index cbebaeb2d..2f6f644ad 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -30,6 +30,7 @@ class Bridge extends Extension { 'config/elapsed': this.configElapsed.bind(this), 'config/log_level': this.configLogLevel.bind(this), 'touchlink/factory_reset': this.touchlinkFactoryReset.bind(this), + 'touchlink/scan': this.touchlinkScan.bind(this), 'health_check': this.healthCheck.bind(this), }; } @@ -218,17 +219,38 @@ class Bridge extends Extension { } async touchlinkFactoryReset(message) { - logger.info('Start touchlink factory reset'); - const result = await this.zigbee.touchlinkFactoryReset(); + let result = false; + const payload = {}; + if (typeof message === 'object' && message.hasOwnProperty('ieee_address') && + message.hasOwnProperty('channel')) { + logger.info(`Start Touchlink factory reset of '${message.ieee_address}' on channel ${message.channel}`); + result = await this.zigbee.touchlinkFactoryReset(message.ieee_address, message.channel); + payload.ieee_address = message.ieee_address; + payload.channel = message.channel; + } else { + logger.info('Start Touchlink factory reset of first found device'); + result = await this.zigbee.touchlinkFactoryResetFirst(); + } + if (result) { logger.info('Successfully factory reset device through Touchlink'); - return utils.getResponse(message, {}, null); + return utils.getResponse(message, payload, null); } else { logger.error('Failed to factory reset device through Touchlink'); throw new Error('Failed to factory reset device through Touchlink'); } } + async touchlinkScan(message) { + logger.info('Start Touchlink scan'); + const result = await this.zigbee.touchlinkScan(); + const found = result.map((r) => { + return {ieee_address: r.ieeeAddr, channel: r.channel}; + }); + logger.info('Finished Touchlink scan'); + return utils.getResponse(message, {found}, null); + } + /** * Utils */ diff --git a/lib/zigbee.js b/lib/zigbee.js index fd5b75165..bb29fff6f 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -287,8 +287,16 @@ class Zigbee extends events.EventEmitter { } } - async touchlinkFactoryReset() { - return this.herdsman.touchlinkFactoryReset(); + async touchlinkFactoryResetFirst() { + return this.herdsman.touchlinkFactoryResetFirst(); + } + + async touchlinkFactoryReset(ieeeAddr, channel) { + return this.herdsman.touchlinkFactoryReset(ieeeAddr, channel); + } + + async touchlinkScan() { + return this.herdsman.touchlinkScan(); } } diff --git a/test/bridge.test.js b/test/bridge.test.js index 376318faf..651513ad5 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -684,11 +684,11 @@ describe('Bridge', () => { it('Should allow to touchlink factory reset (succeeds)', async () => { MQTT.publish.mockClear(); - zigbeeHerdsman.touchlinkFactoryReset.mockClear(); - zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(true); + zigbeeHerdsman.touchlinkFactoryResetFirst.mockClear(); + zigbeeHerdsman.touchlinkFactoryResetFirst.mockReturnValueOnce(true); MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); await flushPromises(); - expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', stringify({"data":{},"status":"ok"}), @@ -696,17 +696,46 @@ describe('Bridge', () => { ); }); - it('Should allow to touchlink factory reset (fails)', async () => { + it('Should allow to touchlink factory reset specific device', async () => { MQTT.publish.mockClear(); zigbeeHerdsman.touchlinkFactoryReset.mockClear(); - zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(false); - MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); + zigbeeHerdsman.touchlinkFactoryReset.mockReturnValueOnce(true); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', stringify({ieee_address: '0x1239', channel: 12})); await flushPromises(); expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledWith('0x1239', 12); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/touchlink/factory_reset', + stringify({"data":{"ieee_address":'0x1239',"channel":12},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should allow to touchlink factory reset (fails)', async () => { + MQTT.publish.mockClear(); + zigbeeHerdsman.touchlinkFactoryResetFirst.mockClear(); + zigbeeHerdsman.touchlinkFactoryResetFirst.mockReturnValueOnce(false); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); + await flushPromises(); + expect(zigbeeHerdsman.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', stringify({"data":{},"status":"error","error":"Failed to factory reset device through Touchlink"}), {retain: false, qos: 0}, expect.any(Function) ); }); + + it('Should allow to touchlink scan', async () => { + MQTT.publish.mockClear(); + zigbeeHerdsman.touchlinkScan.mockClear(); + zigbeeHerdsman.touchlinkScan.mockReturnValueOnce([{ieeeAddr: '0x123', channel: 12}, {ieeeAddr: '0x124', channel: 24}]); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/scan', ''); + await flushPromises(); + expect(zigbeeHerdsman.touchlinkScan).toHaveBeenCalledTimes(1); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/touchlink/scan', + stringify({"data":{"found":[{ieee_address: '0x123', channel: 12}, {ieee_address: '0x124', channel: 24}]},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); }); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index 8bddc37bd..af9c5b699 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -178,6 +178,8 @@ const groups = { const mock = { setTransmitPower: jest.fn(), touchlinkFactoryReset: jest.fn(), + touchlinkFactoryResetFirst: jest.fn(), + touchlinkScan: jest.fn(), start: jest.fn(), permitJoin: jest.fn(), getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}),