diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index d6b50a241..7a2ad92d2 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -251,6 +251,9 @@ class Bridge extends Extension { ban = !!message.ban; force = !!message.force; banForceLog = ` (ban: ${ban}, force: ${force})`; + } else if (entityType === 'group' && typeof message === 'object') { + force = !!message.force; + banForceLog = ` (force: ${force})`; } try { @@ -266,7 +269,11 @@ class Bridge extends Extension { await entity.device.removeFromNetwork(); } } else { - await entity.group.removeFromDatabase(); + if (force) { + await entity.group.removeFromDatabase(); + } else { + await entity.group.removeFromNetwork(); + } } // Fire event @@ -291,7 +298,7 @@ class Bridge extends Extension { return utils.getResponse(message, {ID, ban: ban, force: force}, null); } else { this.publishGroups(); - return utils.getResponse(message, {ID}, null); + return utils.getResponse(message, {ID, force: force}, null); } } catch (error) { throw new Error( diff --git a/lib/extension/legacy/bridgeLegacy.js b/lib/extension/legacy/bridgeLegacy.js index 866e38bd6..b74b9f422 100644 --- a/lib/extension/legacy/bridgeLegacy.js +++ b/lib/extension/legacy/bridgeLegacy.js @@ -52,6 +52,7 @@ class BridgeLegacy extends Extension { 'device_options': this.deviceOptions, 'add_group': this.addGroup, 'remove_group': this.removeGroup, + 'force_remove_group': this.removeGroup, 'whitelist': this.whitelist, 'touchlink/factory_reset': this.touchlinkFactoryReset, }; @@ -272,8 +273,14 @@ class BridgeLegacy extends Extension { const name = message; const entity = this.zigbee.resolveEntity(message); assert(entity && entity.type === 'group', `Group '${message}' does not exist`); + + if (topic.includes('force')) { + entity.group.removeFromDatabase(); + } else { + entity.group.removeFromNetwork(); + } settings.removeGroup(message); - entity.group.removeFromDatabase(); + this.mqtt.publish('bridge/log', JSON.stringify({type: `group_removed`, message})); logger.info(`Removed group '${name}'`); } diff --git a/package.json b/package.json index b06fdaa2f..782cba7f3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "semver": "*", "winston": "*", "winston-syslog": "*", - "zigbee-herdsman": "0.12.99", + "zigbee-herdsman": "0.12.100", "zigbee-herdsman-converters": "12.0.126" }, "devDependencies": { diff --git a/test/bridge.test.js b/test/bridge.test.js index af33d4be1..1626b6852 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -292,12 +292,27 @@ describe('Bridge', () => { MQTT.publish.mockClear(); MQTT.events.message('zigbee2mqtt/bridge/request/group/remove', 'group_1'); await flushPromises(); + expect(group.removeFromNetwork).toHaveBeenCalledTimes(1); + expect(settings.getGroup('group_1')).toBeNull(); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/group/remove', + JSON.stringify({"data":{"ID": "group_1", "force": false},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should allow to force remove group', async () => { + const group = zigbeeHerdsman.groups.group_1; + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/group/remove', JSON.stringify({ID: "group_1", force: true})); + await flushPromises(); expect(group.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getGroup('group_1')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/remove', - JSON.stringify({"data":{"ID": "group_1"},"status":"ok"}), + JSON.stringify({"data":{"ID": "group_1", "force": true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/legacy/bridgeLegacy.test.js b/test/legacy/bridgeLegacy.test.js index cfdb96132..cfbcbfe03 100644 --- a/test/legacy/bridgeLegacy.test.js +++ b/test/legacy/bridgeLegacy.test.js @@ -318,6 +318,20 @@ describe('Bridge legacy', () => { MQTT.events.message('zigbee2mqtt/bridge/config/remove_group', 'group_1'); await flushPromises(); expect(settings.getGroup('to_be_removed')).toStrictEqual(null); + expect(group.removeFromNetwork).toHaveBeenCalledTimes(1); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/log', + JSON.stringify({type: 'group_removed', message: 'group_1'}), + {qos: 0, retain: false}, + expect.any(Function) + ); + }); + + it('Should allow to force remove groups', async () => { + const group = zigbeeHerdsman.groups.group_1; + MQTT.events.message('zigbee2mqtt/bridge/config/force_remove_group', 'group_1'); + await flushPromises(); + expect(settings.getGroup('to_be_removed')).toStrictEqual(null); expect(group.removeFromDatabase).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index 065b6dc18..dbdb89c06 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -8,6 +8,7 @@ class Group { this.meta = {}; this.members = members; this.removeFromDatabase = jest.fn(); + this.removeFromNetwork = jest.fn(); this.hasMember = (endpoint) => this.members.includes(endpoint); } }