fix(ignore): Changes for new ZHC definition.version (#30402)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: burmistrzak <61958704+burmistrzak@users.noreply.github.com>
Co-authored-by: rhysfred <rhysfred@users.noreply.github.com>
Co-authored-by: rhys <rhys@frontleftspeaker.com>
Co-authored-by: Patrick ZAJDA <patrick@zajda.fr>
Co-authored-by: Ignacio Hernandez-Ros <ignacio@hernandez-ros.com>
Co-authored-by: Rohan Kapoor <rohan@rohankapoor.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
Co-authored-by: Stephan Garland <stephan.garland@affirm.com>
Co-authored-by: Andrei LAZAROV <andrei_lazarov@yahoo.com>
This commit is contained in:
Koen Kanters
2026-02-05 21:29:58 +01:00
co-authored by dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> burmistrzak rhysfred rhys Patrick ZAJDA Ignacio Hernandez-Ros Rohan Kapoor Nerivec Stephan Garland Andrei LAZAROV
parent 6cce05a514
commit b71461e03c
4 changed files with 66 additions and 11 deletions
+13 -6
View File
@@ -1,6 +1,5 @@
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import * as zhc from "zigbee-herdsman-converters";
import Device from "../model/device";
import type {Zigbee2MQTTAPI} from "../types/api";
import logger from "../util/logger";
@@ -94,17 +93,25 @@ export default class Configure extends Extension {
return;
}
const definitionVersion = device.definition.version;
if (!force) {
if (device.options.disabled || !device.interviewed) {
return;
}
if (device.zh.meta?.configured !== undefined) {
// Only configure end devices when it is active, otherwise it will likely fails as they are sleeping.
if (device.zh.type === "EndDevice" && event !== "zigbee_event") {
return;
}
// Only configure end devices when it is active, otherwise it will likely fails as they are sleeping.
if (device.zh.type === "EndDevice" && event !== "zigbee_event") {
const shouldReconfigure =
// Should always reconfigure when not configured before
device.zh.meta?.configured === undefined ||
// Or should reconfigure when definition.version is not '0.0.0' and differs from last `meta.configured`.
// In older Z2M versions the stored `meta.configured` was the hash of the configure function.
// Since we don't want to reconfigure all devices, we don't re-configure when the definition has the default version of '0.0.0'.
(definitionVersion !== "0.0.0" && device.zh.meta?.configured !== definitionVersion);
if (!shouldReconfigure) {
return;
}
}
@@ -122,8 +129,8 @@ export default class Configure extends Extension {
logger.info(`Configuring '${device.name}'`);
try {
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), device.definition);
logger.info(`Successfully configured '${device.name}'`);
device.zh.meta.configured = zhc.getConfigureKey(device.definition);
logger.info(`Successfully configured '${device.name}' (definition v${definitionVersion})`);
device.zh.meta.configured = definitionVersion;
device.zh.save();
this.eventBus.emitDevicesChanged();
} catch (error) {
+1 -1
View File
@@ -358,7 +358,7 @@ describe("Extension: Bind", () => {
{attribute: "currentX", minimumReportInterval: 5, maximumReportInterval: 0xffff, reportableChange: 1},
{attribute: "currentY", minimumReportInterval: 5, maximumReportInterval: 0xffff, reportableChange: 1},
]);
expect(devices.bulb_color.meta.configured).toBe(332242049);
expect(devices.bulb_color.meta.configured).toBe("0.0.0");
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/unbind",
stringify({
+51 -3
View File
@@ -4,13 +4,15 @@ import * as data from "../mocks/data";
import {mockLogger} from "../mocks/logger";
import {events as mockMQTTEvents, mockMQTTPublishAsync} from "../mocks/mqtt";
import {flushPromises} from "../mocks/utils";
import {type Device, devices, type Endpoint, events as mockZHEvents} from "../mocks/zigbeeHerdsman";
import {devices, type Endpoint, events as mockZHEvents, type Device as ZhDevice} from "../mocks/zigbeeHerdsman";
import stringify from "json-stable-stringify-without-jsonify";
import {InterviewState} from "zigbee-herdsman/dist/controller/model/device";
import {Controller} from "../../lib/controller";
import Device from "../../lib/model/device";
import Configure from "../../lib/extension/configure";
import * as settings from "../../lib/util/settings";
import assert from "node:assert";
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
@@ -23,7 +25,7 @@ describe("Extension: Configure", () => {
await controller.addExtension(new Configure(...controller.extensionArgs));
};
const mockClear = (device: Device): void => {
const mockClear = (device: ZhDevice): void => {
for (const endpoint of device.endpoints) {
endpoint.read.mockClear();
endpoint.write.mockClear();
@@ -42,7 +44,7 @@ describe("Extension: Configure", () => {
const endpoint2 = device.getEndpoint(2)!;
expect(endpoint2.write).toHaveBeenCalledTimes(1);
expect(endpoint2.write).toHaveBeenCalledWith("genBasic", {49: {type: 25, value: 11}}, {disableDefaultResponse: true, manufacturerCode: 4107});
expect(device.meta.configured).toBe(332242049);
expect(device.meta.configured).toBe("0.0.0");
};
const expectBulbConfigured = (): void => {
@@ -147,6 +149,52 @@ describe("Extension: Configure", () => {
expectBulbConfigured();
});
it("Should re-configure when the version of the definition changes to a different value than the default (0.0.0)", async () => {
// Device is initially configured (definition has uses default version of 0.0.0)
const device = devices.bulb;
expectBulbConfigured();
// Nothing happens when receiving a Zigbee message
mockClear(device);
await mockZHEvents.lastSeenChanged({device});
await flushPromises();
expectBulbNotConfigured();
// Simulate that the definition version changes
const resolvedEntity = controller.zigbee.resolveEntity(device);
assert(resolvedEntity instanceof Device && resolvedEntity.definition);
assert(resolvedEntity.definition.version === "0.0.0");
try {
// Change the version to a different value
resolvedEntity.definition.version = "0.0.1";
// Now it should re-configure upon receiving a Zigbee message
mockClear(device);
await mockZHEvents.lastSeenChanged({device});
await flushPromises();
expectBulbConfigured();
} finally {
resolvedEntity.definition.version = "0.0.0";
}
});
it("Should NOT re-configure when version of the definition is 0.0.0", async () => {
// This test the migration from the old configureKey (hash of the configure function) to the new definition version system.
// See commment in `configure.ts` -> `shouldReconfigure` for more details.
// Device is initially configured (definition has uses default version of 0.0.0)
const device = devices.bulb;
expectBulbConfigured();
device.meta.configured = 1321;
// Nothing happens when receiving a Zigbee message
mockClear(device);
await mockZHEvents.lastSeenChanged({device});
await flushPromises();
expectBulbNotConfigured();
});
it("Should allow to configure via MQTT", async () => {
mockClear(devices.remote);
expectRemoteNotConfigured();
+1 -1
View File
@@ -143,7 +143,7 @@ describe("Extension: OTAUpdate", () => {
expect(infoCalls[7]).toStrictEqual(`Interviewing 'bulb'`);
expect(infoCalls[8]).toStrictEqual(`Configuring 'bulb'`);
expect(infoCalls[10]).toStrictEqual(`Successfully interviewed 'bulb'`);
expect(infoCalls[11]).toStrictEqual(`Successfully configured 'bulb'`);
expect(infoCalls[11]).toStrictEqual(`Successfully configured 'bulb' (definition v0.0.0)`);
expect(devices.bulb.save).toHaveBeenCalledTimes(1);
expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith("genBasic", ["dateCode", "swBuildId"], {sendPolicy: "immediate"});