diff --git a/lib/extension/bridgeConfig.js b/lib/extension/bridgeConfig.js index ae0dde3f..64749988 100644 --- a/lib/extension/bridgeConfig.js +++ b/lib/extension/bridgeConfig.js @@ -22,6 +22,7 @@ class BridgeConfig extends BaseExtension { this.groups = this.groups.bind(this); this.rename = this.rename.bind(this); this.remove = this.remove.bind(this); + this.forceRemove = this.forceRemove.bind(this); this.ban = this.ban.bind(this); this.deviceOptions = this.deviceOptions.bind(this); this.addGroup = this.addGroup.bind(this); @@ -40,6 +41,7 @@ class BridgeConfig extends BaseExtension { 'devices/get': this.devices, 'rename': this.rename, 'remove': this.remove, + 'force_remove': this.forceRemove, 'ban': this.ban, 'device_options': this.deviceOptions, 'add_group': this.addGroup, @@ -214,16 +216,25 @@ class BridgeConfig extends BaseExtension { logger.info(`Removed group '${name}'`); } + async forceRemove(topic, message) { + await this.removeForceRemoveOrBan('force_remove', message); + } + async remove(topic, message) { - await this.removeOrBan(false, message); + await this.removeForceRemoveOrBan('remove', message); } async ban(topic, message) { - await this.removeOrBan(true, message); + await this.removeForceRemoveOrBan('ban', message); } - async removeOrBan(ban, message) { + async removeForceRemoveOrBan(action, message) { const entity = this.zigbee.resolveEntity(message.trim()); + const lookup = { + ban: ['banned', 'Banning', 'ban'], + force_remove: ['force_removed', 'Force removing', 'force remove'], + remove: ['removed', 'Removing', 'remove'], + }; const cleanup = () => { // Remove from configuration.yaml @@ -232,20 +243,24 @@ class BridgeConfig extends BaseExtension { // Remove from state this.state.remove(entity.settings.ID); - logger.info(`Successfully ${ban ? 'banned' : 'removed'} ${entity.settings.friendlyName}`); - this.mqtt.log(ban ? 'device_banned' : 'device_removed', message); + logger.info(`Successfully ${lookup[action][0]} ${entity.settings.friendlyName}`); + this.mqtt.log(`device_${lookup[action][0]}`, message); }; - // Remove from zigbee network. try { - logger.info(`Removing '${entity.settings.friendlyName}'`); - await entity.device.removeFromNetwork(); + logger.info(`${lookup[action][1]} '${entity.settings.friendlyName}'`); + if (action === 'force_remove') { + await entity.device.removeFromDatabase(); + } else { + await entity.device.removeFromNetwork(); + } + cleanup(); } catch (error) { - logger.error(`Failed to ${ban ? 'ban' : 'remove'} ${entity.settings.friendlyName} (${error})`); + logger.error(`Failed to ${lookup[action][2]} ${entity.settings.friendlyName} (${error})`); } - if (ban) { + if (action === 'ban') { settings.banDevice(entity.settings.ID); } } diff --git a/test/bridgeConfig.test.js b/test/bridgeConfig.test.js index c281b86b..b6ff9cfa 100644 --- a/test/bridgeConfig.test.js +++ b/test/bridgeConfig.test.js @@ -247,6 +247,28 @@ describe('Bridge config', () => { expect(settings.get().ban.length).toBe(0); }); + it('Should allow to force remove device', async () => { + controller.state.state = {'0x000b57fffec6a5b3': {brightness: 100}}; + const device = zigbeeHerdsman.devices.bulb_color; + device.removeFromDatabase.mockClear(); + expect(settings.get().ban.length).toBe(0); + await flushPromises(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/config/force_remove', 'bulb_color'); + await flushPromises(); + expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); + expect(controller.state[device.ieeeAddr]).toBeUndefined(); + expect(settings.getDevice('bulb_color')).toBeNull(); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/log', + JSON.stringify({type: 'device_force_removed', message: 'bulb_color'}), + {qos: 0, retain: false}, + expect.any(Function) + ); + expect(controller.state.state).toStrictEqual({}); + expect(settings.get().ban.length).toBe(0); + }); + it('Should allow to ban device', async () => { const device = zigbeeHerdsman.devices.bulb_color; device.removeFromNetwork.mockClear(); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index ea6bbce9..4795ecdb 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -77,6 +77,7 @@ class Device { this.meta = {}; this.ping = jest.fn(); this.removeFromNetwork = jest.fn(); + this.removeFromDatabase = jest.fn(); this.save = jest.fn(); this.manufacturerName = manufacturerName; this.lastSeen = 1000;