diff --git a/lib/extension/receive.ts b/lib/extension/receive.ts index f897d1874..b8ea56b13 100755 --- a/lib/extension/receive.ts +++ b/lib/extension/receive.ts @@ -180,8 +180,11 @@ export default class Receive extends Extension { if (!utils.objectIsEmpty(payload)) { await publish(payload); - } else { - await utils.publishLastSeen({device: data.device, reason: "messageEmitted"}, settings.get(), true, this.publishEntityState); + } else if (settings.get().advanced.last_seen && settings.get().advanced.last_seen !== "disable") { + // A message was received that produced no payload (e.g. a frame the converter has no data + // for). Publish through the regular publish() path so the per-device debounce/throttle + // still applies, instead of publishing the full cached state immediately via publishLastSeen. + await publish({}); } } } diff --git a/test/extensions/receive.test.ts b/test/extensions/receive.test.ts index 6051e535a..e44d44cbd 100644 --- a/test/extensions/receive.test.ts +++ b/test/extensions/receive.test.ts @@ -190,6 +190,30 @@ describe("Extension: Receive", () => { expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual("zigbee2mqtt/bridge/health"); }); + it("Should not bypass the debounce when a message produces no payload", async () => { + const device = devices.WSDCGQ11LM; + settings.set(["devices", device.ieeeAddr, "debounce"], 0.1); + settings.set(["advanced", "last_seen"], "ISO_8601"); + // Attribute report without measuredValue: the lumi_temperature converter returns nothing. + const payload = { + data: {}, + cluster: "msTemperatureMeasurement", + device, + endpoint: device.getEndpoint(1), + type: "attributeReport", + linkquality: 10, + }; + await mockZHEvents.message(payload); + await flushPromises(); + // The empty payload must not be published immediately (bypassing the debounce). + vi.advanceTimersByTime(50); + expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0); + vi.runOnlyPendingTimers(); + await flushPromises(); + expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual("zigbee2mqtt/weather_sensor"); + }); + it("Should debounce and retain messages when set via device_options", async () => { const device = devices.WSDCGQ11LM; settings.set(["device_options", "debounce"], 0.1);