diff --git a/lib/extension/availability.js b/lib/extension/availability.js index af83d5a59..681523af3 100644 --- a/lib/extension/availability.js +++ b/lib/extension/availability.js @@ -24,6 +24,8 @@ class Availability extends Extension { this.timers = {}; this.state = {}; + this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.resolvedEntity), this.constructor.name); + this.blocklist = settings.get().advanced.availability_blocklist .concat(settings.get().advanced.availability_blacklist) .map((e) => settings.getEntity(e).ID); @@ -33,6 +35,12 @@ class Availability extends Extension { .map((e) => settings.getEntity(e).ID); } + onDeviceRemoved(resolvedEntity) { + this.mqtt.publish(`${resolvedEntity.name}/availability`, null, {retain: true, qos: 0}); + delete this.state[resolvedEntity.device.ieeeAddr]; + clearTimeout(this.timers[resolvedEntity.device.ieeeAddr]); + } + inPasslistOrNotInBlocklist(device) { const ieeeAddr = device.ieeeAddr; const deviceSettings = settings.getDevice(ieeeAddr); @@ -114,10 +122,7 @@ class Availability extends Extension { } setTimerPingable(device) { - if (this.timers[device.ieeeAddr]) { - clearTimeout(this.timers[device.ieeeAddr]); - } - + clearTimeout(this.timers[device.ieeeAddr]); this.timers[device.ieeeAddr] = setTimeout(async () => { await this.handleIntervalPingable(device); }, utils.secondsToMilliseconds(this.availability_timeout)); diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 12c8e8169..970032393 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -377,7 +377,7 @@ class Bridge extends Extension { // Fire event if (entity.type === 'device') { - this.eventBus.emit('deviceRemoved', {device: entity.device}); + this.eventBus.emit('deviceRemoved', {resolvedEntity: entity}); } // Remove from configuration.yaml diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 098053b25..b3ca81338 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -2056,7 +2056,7 @@ class HomeAssistant extends Extension { this.discoveryTopic = settings.get().advanced.homeassistant_discovery_topic; this.statusTopic = settings.get().advanced.homeassistant_status_topic; - this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.device), this.constructor.name); + this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.resolvedEntity), this.constructor.name); this.eventBus.on('publishEntityState', (data) => this.onPublishEntityState(data), this.constructor.name); this.eventBus.on('deviceRenamed', (data) => this.onDeviceRenamed(data.device, data.homeAssisantRename), this.constructor.name, @@ -2069,12 +2069,11 @@ class HomeAssistant extends Extension { } } - onDeviceRemoved(device) { - logger.debug(`Clearing Home Assistant discovery topic for '${device.ieeeAddr}'`); - delete this.discovered[device.ieeeAddr]; - const resolvedEntity = this.zigbee.resolveEntity(device); + onDeviceRemoved(resolvedEntity) { + logger.debug(`Clearing Home Assistant discovery topic for '${resolvedEntity.name}'`); + delete this.discovered[resolvedEntity.device.ieeeAddr]; for (const config of this.getConfigs(resolvedEntity)) { - const topic = this.getDiscoveryTopic(config, device); + const topic = this.getDiscoveryTopic(config, resolvedEntity.device); this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); } } diff --git a/lib/extension/legacy/bridgeLegacy.js b/lib/extension/legacy/bridgeLegacy.js index 5d4b92f30..895e6aecd 100644 --- a/lib/extension/legacy/bridgeLegacy.js +++ b/lib/extension/legacy/bridgeLegacy.js @@ -316,7 +316,7 @@ class BridgeLegacy extends Extension { const cleanup = () => { // Fire event - this.eventBus.emit('deviceRemoved', {device: entity.device}); + this.eventBus.emit('deviceRemoved', {resolvedEntity: entity}); // Remove from configuration.yaml settings.removeDevice(entity.settings.ID); diff --git a/test/availability.test.js b/test/availability.test.js index 4b96a412b..981b90a4f 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -27,7 +27,8 @@ describe('Availability', () => { settings._reRead(); data.writeEmptyState(); jest.useFakeTimers(); - settings.set(['advanced', 'availability_timeout'], 10) + settings.set(['advanced', 'availability_timeout'], 10); + settings.set(['experimental', 'new_api'], true); controller = new Controller(); mocksClear.forEach((m) => m.mockClear()); await controller.start(); @@ -397,4 +398,15 @@ describe('Availability', () => { expect.any(Function) ); }); + + it('Should clear retained availability topic when device is remove', async () => { + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', 'bulb_color'); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bulb_color/availability', + null, + {retain: true, qos: 0}, expect.any(Function) + ); + }); });