diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 2f6f644ad5..fd3f20d66d 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/identify': this.touchlinkIdentify.bind(this), 'touchlink/scan': this.touchlinkScan.bind(this), 'health_check': this.healthCheck.bind(this), }; @@ -218,6 +219,17 @@ class Bridge extends Extension { return utils.getResponse(message, {value}, null); } + async touchlinkIdentify(message) { + if (typeof message !== 'object' || !message.hasOwnProperty('ieee_address') || + !message.hasOwnProperty('channel')) { + throw new Error('Invalid payload'); + } + + logger.info(`Start Touchlink identify of '${message.ieee_address}' on channel ${message.channel}`); + await this.zigbee.touchlinkIdentify(message.ieee_address, message.channel); + return utils.getResponse(message, {ieee_address: message.ieee_address, channel: message.channel}, null); + } + async touchlinkFactoryReset(message) { let result = false; const payload = {}; diff --git a/lib/zigbee.js b/lib/zigbee.js index bb29fff6f1..04aec19ea9 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -295,6 +295,10 @@ class Zigbee extends events.EventEmitter { return this.herdsman.touchlinkFactoryReset(ieeeAddr, channel); } + async touchlinkIdentify(ieeeAddr, channel) { + await this.herdsman.touchlinkIdentify(ieeeAddr, channel); + } + async touchlinkScan() { return this.herdsman.touchlinkScan(); } diff --git a/test/bridge.test.js b/test/bridge.test.js index 651513ad52..70a5b33fda 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -711,6 +711,33 @@ describe('Bridge', () => { ); }); + it('Should allow to touchlink identify specific device', async () => { + MQTT.publish.mockClear(); + zigbeeHerdsman.touchlinkIdentify.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/identify', stringify({ieee_address: '0x1239', channel: 12})); + await flushPromises(); + expect(zigbeeHerdsman.touchlinkIdentify).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.touchlinkIdentify).toHaveBeenCalledWith('0x1239', 12); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/touchlink/identify', + stringify({"data":{"ieee_address":'0x1239',"channel":12},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Touchlink identify fails when payload is invalid', async () => { + MQTT.publish.mockClear(); + zigbeeHerdsman.touchlinkIdentify.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/touchlink/identify', stringify({ieee_address: '0x1239'})); + await flushPromises(); + expect(zigbeeHerdsman.touchlinkIdentify).toHaveBeenCalledTimes(0); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/touchlink/identify', + stringify({"data":{},"status":"error","error":"Invalid payload"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should allow to touchlink factory reset (fails)', async () => { MQTT.publish.mockClear(); zigbeeHerdsman.touchlinkFactoryResetFirst.mockClear(); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index af9c5b6995..13bb75fd25 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -180,6 +180,7 @@ const mock = { touchlinkFactoryReset: jest.fn(), touchlinkFactoryResetFirst: jest.fn(), touchlinkScan: jest.fn(), + touchlinkIdentify: jest.fn(), start: jest.fn(), permitJoin: jest.fn(), getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}),