diff --git a/lib/extension/availability.ts b/lib/extension/availability.ts index 66f5eadbb..e684dd3e5 100644 --- a/lib/extension/availability.ts +++ b/lib/extension/availability.ts @@ -5,6 +5,12 @@ import * as settings from '../util/settings'; import debounce from 'debounce'; import bind from 'bind-decorator'; +const retrieveOnReconnect = [ + {keys: ['state']}, + {keys: ['brightness'], condition: (state: KeyValue): boolean => state.state === 'ON'}, + {keys: ['color', 'color_temp'], condition: (state: KeyValue): boolean => state.state === 'ON'}, +]; + export default class Availability extends Extension { private timers: {[s: string]: NodeJS.Timeout} = {}; private availabilityCache: {[s: string]: boolean} = {}; @@ -187,17 +193,18 @@ export default class Availability extends Extension { * device can send multiple times after each other. */ if (device.definition && !device.zh.interviewing && !this.retrieveStateDebouncers[device.ieeeAddr]) { - this.retrieveStateDebouncers[device.ieeeAddr] = debounce(() => { + this.retrieveStateDebouncers[device.ieeeAddr] = debounce(async () => { logger.debug(`Retrieving state of '${device.name}' after reconnect`); // Color and color temperature converters do both, only needs to be called once. - const keySet = [['state'], ['brightness'], ['color', 'color_temp']]; - for (const keys of keySet) { - const converter = device.definition.toZigbee.find((c) => c.key.find((k) => keys.includes(k))); - converter?.convertGet?.(device.endpoint(), keys[0], + for (const item of retrieveOnReconnect) { + if (item.condition && this.state.get(device) && !item.condition(this.state.get(device))) continue; + const converter = device.definition.toZigbee.find((c) => c.key.find((k) => item.keys.includes(k))); + await converter?.convertGet?.(device.endpoint(), item.keys[0], {message: this.state.get(device) || {}, mapped: device.definition}) .catch((e) => { logger.error(`Failed to read state of '${device.name}' after reconnect (${e.message})`); }); + await utils.sleep(500); } }, utils.seconds(2)); } diff --git a/test/availability.test.js b/test/availability.test.js index d36a0013e..132d360a6 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -241,6 +241,9 @@ describe('Availability', () => { it('Should retrieve device state when it reconnects', async () => { MQTT.publish.mockClear(); + const device = controller.zigbee.resolveEntity(devices.bulb_color.ieeeAddr); + controller.state.set(device, {state: 'OFF'}) + const endpoint = devices.bulb_color.getEndpoint(1); endpoint.read.mockClear(); @@ -253,18 +256,15 @@ describe('Availability', () => { expect(endpoint.read).toHaveBeenCalledTimes(0); await advancedTime(utils.seconds(2)); - expect(endpoint.read).toHaveBeenCalledTimes(3); + expect(endpoint.read).toHaveBeenCalledTimes(1); expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); - expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']); - expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', - ['colorMode', 'currentX', 'currentY', 'enhancedCurrentHue', 'currentSaturation', 'colorTemperature']); endpoint.read.mockClear(); await zigbeeHerdsman.events.deviceAnnounce({device: devices.bulb_color}); await flushPromises(); endpoint.read.mockImplementationOnce(() => {throw new Error('')}); await advancedTime(utils.seconds(3)); - expect(endpoint.read).toHaveBeenCalledTimes(3); + expect(endpoint.read).toHaveBeenCalledTimes(1); }); it('Should republish availability when device is renamed', async () => {