diff --git a/lib/extension/receive.js b/lib/extension/receive.js index 41d742935..4655e7ea0 100755 --- a/lib/extension/receive.js +++ b/lib/extension/receive.js @@ -79,25 +79,6 @@ class Receive extends Extension { return false; } - /** - * Don't handle re-transmitted Xiaomi messages. - * https://github.com/Koenkk/zigbee2mqtt/issues/1238 - * - * Some Xiaomi router devices re-transmit messages from Xiaomi end devices. - * The source address of these message is set to the one of the Xiaomi router. - * Therefore it looks like if the message came from the Xiaomi router, while in - * fact it came from the end device. - * Handling these message would result in false state updates. - * The group ID attribute of these message defines the source address of the end device. - * As the same message is also received directly from the end device, it makes no sense - * to handle these messages. - */ - const hasGroupID = data.hasOwnProperty('groupID') && !!data.groupID; - if (utils.isXiaomiDevice(data.device) && data.device.type === 'Router' && hasGroupID) { - logger.debug('Skipping re-transmitted Xiaomi message'); - return false; - } - if (!resolvedEntity.definition) { if (data.device.interviewing) { logger.debug(`Skipping message, definition is undefined and still interviewing`); @@ -113,6 +94,24 @@ class Receive extends Extension { } onZigbeeEvent(type, data, resolvedEntity) { + /** + * Handling of re-transmitted Xiaomi messages. + * https://github.com/Koenkk/zigbee2mqtt/issues/1238 + * https://github.com/Koenkk/zigbee2mqtt/issues/3592 + * + * Some Xiaomi router devices re-transmit messages from Xiaomi end devices. + * The network address of these message is set to the one of the Xiaomi router. + * Therefore it looks like if the message came from the Xiaomi router, while in + * fact it came from the end device. + * Handling these message would result in false state updates. + * The group ID attribute of these message defines the network address of the end device. + */ + if (type === 'message' && utils.isXiaomiDevice(data.device) && data.device.type === 'Router' && data.groupID) { + logger.debug('Handling re-transmitted Xiaomi message'); + data.device = this.zigbee.getDeviceByNetworkAddress(data.groupID); + resolvedEntity = this.zigbee.resolveEntity(data.device); + } + if (!this.shouldProcess(type, data, resolvedEntity)) { return; } diff --git a/lib/zigbee.js b/lib/zigbee.js index 8e29b7062..fd5b75165 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -140,6 +140,10 @@ class Zigbee extends events.EventEmitter { return this.herdsman.getDeviceByIeeeAddr(ieeeAddr); } + getDeviceByNetworkAddress(networkAddress) { + return this.herdsman.getDeviceByNetworkAddress(networkAddress); + } + getDevicesByType(type) { return this.herdsman.getDevicesByType(type); } diff --git a/test/receive.test.js b/test/receive.test.js index 7b0f3c4bf..f3ddad708 100755 --- a/test/receive.test.js +++ b/test/receive.test.js @@ -364,13 +364,18 @@ describe('Receive', () => { expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false}); }); - it('Should not handle messages forwarded Xiaomi messages', async () => { + it('Should handle forwarded Xiaomi messages', async () => { const device = zigbeeHerdsman.devices.ZNCZ02LM; - const data = {onOff: 1}; - const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10, groupID: 599}; + const payload = {data: {measuredValue: -85}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10, groupID: 6539}; await zigbeeHerdsman.events.message(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(0); + expect(MQTT.publish).toHaveBeenCalledTimes(1); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/weather_sensor', + stringify({temperature: -0.85, linkquality: 10}), + {"qos": 1, "retain": false}, + expect.any(Function), + ) }); it('Should handle messages from Xiaomi router devices', async () => { diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index c27cb87d7..4bde032e6 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -180,6 +180,9 @@ const mock = { getDeviceByIeeeAddr: jest.fn().mockImplementation((ieeeAddr) => { return Object.values(devices).filter((d) => returnDevices.length === 0 || returnDevices.includes(d.ieeeAddr)).find((d) => d.ieeeAddr === ieeeAddr); }), + getDeviceByNetworkAddress: jest.fn().mockImplementation((networkAddress) => { + return Object.values(devices).filter((d) => returnDevices.length === 0 || returnDevices.includes(d.networkAddress)).find((d) => d.networkAddress === networkAddress); + }), getGroups: jest.fn().mockImplementation((query) => { return Object.values(groups); }),