diff --git a/lib/extension/bridge.ts b/lib/extension/bridge.ts index 10f5fea0..2cc0087a 100644 --- a/lib/extension/bridge.ts +++ b/lib/extension/bridge.ts @@ -44,6 +44,7 @@ export default class Bridge extends Extension { 'backup': this.backup, 'touchlink/factory_reset': this.touchlinkFactoryReset, 'touchlink/identify': this.touchlinkIdentify, + 'install_code/add': this.installCodeAdd, 'touchlink/scan': this.touchlinkScan, 'health_check': this.healthCheck, 'options': this.bridgeOptions, @@ -248,6 +249,17 @@ export default class Bridge extends Extension { return utils.getResponse(message, {zip: base64Zip}, null); } + @bind async installCodeAdd(message: KeyValue | string): Promise { + if (typeof message === 'object' && !message.hasOwnProperty('value')) { + throw new Error('Invalid payload'); + } + + const value = typeof message === 'object' ? message.value : message; + await this.zigbee.addInstallCode(value); + logger.info('Successfully added new install code'); + return utils.getResponse(message, {value}, null); + } + @bind async permitJoin(message: KeyValue | string): Promise { if (typeof message === 'object' && !message.hasOwnProperty('value')) { throw new Error('Invalid payload'); diff --git a/lib/zigbee.ts b/lib/zigbee.ts index b7d3ccc9..a97576b9 100644 --- a/lib/zigbee.ts +++ b/lib/zigbee.ts @@ -310,6 +310,10 @@ export default class Zigbee { return this.herdsman.touchlinkFactoryReset(ieeeAddr, channel); } + async addInstallCode(installCode: string): Promise { + await this.herdsman.addInstallCode(installCode); + } + async touchlinkIdentify(ieeeAddr: string, channel: number): Promise { await this.herdsman.touchlinkIdentify(ieeeAddr, channel); } diff --git a/test/bridge.test.js b/test/bridge.test.js index 733debdc..ff6453d2 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -913,6 +913,47 @@ describe('Bridge', () => { ); }); + it('Add install code', async () => { + MQTT.publish.mockClear(); + + // By object + zigbeeHerdsman.addInstallCode.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/install_code/add', stringify({value: 'my-code'})); + await flushPromises(); + expect(zigbeeHerdsman.addInstallCode).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.addInstallCode).toHaveBeenCalledWith('my-code'); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/install_code/add', + stringify({"data":{"value":'my-code'},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + + // By string + zigbeeHerdsman.addInstallCode.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/install_code/add', 'my-string-code'); + await flushPromises(); + expect(zigbeeHerdsman.addInstallCode).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.addInstallCode).toHaveBeenCalledWith('my-string-code'); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/install_code/add', + stringify({"data":{"value":'my-code'},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Add install code error', async () => { + MQTT.publish.mockClear(); + zigbeeHerdsman.addInstallCode.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/install_code/add', stringify({wrong: 'my-code'})); + await flushPromises(); + expect(zigbeeHerdsman.addInstallCode).toHaveBeenCalledTimes(0); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/install_code/add', + stringify({"data":{},"status":"error","error":"Invalid payload"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + it('Should allow to touchlink identify specific device', async () => { MQTT.publish.mockClear(); zigbeeHerdsman.touchlinkIdentify.mockClear(); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index 2cb7dee7..4d65f88c 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -214,6 +214,7 @@ const mock = { backup: jest.fn(), isStopping: jest.fn(), permitJoin: jest.fn(), + addInstallCode: jest.fn(), getCoordinatorVersion: jest.fn().mockReturnValue({type: 'z-Stack', meta: {version: 1, revision: 20190425}}), getNetworkParameters: jest.fn().mockReturnValue({panID: 0x162a, extendedPanID: [0, 11, 22], channel: 15}), on: (type, handler) => {