feat: Allow to preserve device config when removing device (#32765)

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
Miguel Cabral
2026-08-18 21:13:38 +02:00
committed by GitHub
co-authored by Koen Kanters
parent be1cbb5f3b
commit 95d072a788
3 changed files with 39 additions and 9 deletions
+13 -3
View File
@@ -677,6 +677,7 @@ export default class Bridge extends Extension {
const friendlyName = entity.name;
let block = false;
let force = false;
let keepConfig = false;
let clearCache = false;
let blockForceLog = "";
@@ -684,8 +685,9 @@ export default class Bridge extends Extension {
const payload = message as Zigbee2MQTTAPI["bridge/request/device/remove"];
block = !!payload.block;
force = !!payload.force;
keepConfig = !!payload.keep_config;
clearCache = !!payload.clear_cache;
blockForceLog = ` (block: ${block}, force: ${force}, clear cache: ${clearCache})`;
blockForceLog = ` (block: ${block}, force: ${force}, keep config: ${keepConfig}, clear cache: ${clearCache})`;
} else if (entityType === "group" && messageIsObject) {
const payload = message as Zigbee2MQTTAPI["bridge/request/group/remove"];
force = !!payload.force;
@@ -710,7 +712,9 @@ export default class Bridge extends Extension {
this.zigbee.removeDeviceFromLookup(entity.ID);
}
settings.removeDevice(entity.ID as string);
if (!keepConfig) {
settings.removeDevice(entity.ID as string);
}
} else {
if (force) {
entity.zh.removeFromDatabase();
@@ -739,7 +743,13 @@ export default class Bridge extends Extension {
// Refresh Cluster definition
await this.publishDefinitions();
const responseData: Zigbee2MQTTAPI["bridge/response/device/remove"] = {id: ID, block, force, clear_cache: clearCache};
const responseData: Zigbee2MQTTAPI["bridge/response/device/remove"] = {
id: ID,
block,
force,
keep_config: keepConfig,
clear_cache: clearCache,
};
return utils.getResponse(message, responseData);
}
+2
View File
@@ -622,6 +622,7 @@ export interface Zigbee2MQTTAPI {
id: string;
block?: boolean;
force?: boolean;
keep_config?: boolean;
clear_cache?: boolean;
};
@@ -629,6 +630,7 @@ export interface Zigbee2MQTTAPI {
id: string;
block: boolean;
force: boolean;
keep_config: boolean;
clear_cache: boolean;
};
+24 -6
View File
@@ -2988,7 +2988,7 @@ describe("Extension: Bridge", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: false, force: false, clear_cache: false}, status: "ok"}),
stringify({data: {id: "bulb", block: false, force: false, keep_config: false, clear_cache: false}, status: "ok"}),
{},
);
expect(settings.get().blocklist).toStrictEqual([]);
@@ -3009,7 +3009,7 @@ describe("Extension: Bridge", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: false, force: false, clear_cache: false}, status: "ok"}),
stringify({data: {id: "bulb", block: false, force: false, keep_config: false, clear_cache: false}, status: "ok"}),
{},
);
});
@@ -3027,7 +3027,7 @@ describe("Extension: Bridge", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: false, force: true, clear_cache: false}, status: "ok"}),
stringify({data: {id: "bulb", block: false, force: true, keep_config: false, clear_cache: false}, status: "ok"}),
{},
);
});
@@ -3044,12 +3044,30 @@ describe("Extension: Bridge", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: true, force: true, clear_cache: false}, status: "ok"}),
stringify({data: {id: "bulb", block: true, force: true, keep_config: false, clear_cache: false}, status: "ok"}),
{},
);
expect(settings.get().blocklist).toStrictEqual(["0x000b57fffec6a5b2"]);
});
it("Should allow to keep configuration when removing device", async () => {
const device = devices.bulb;
const removeSpy = vi.spyOn(controller.zigbee, "removeDeviceFromLookup");
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/remove", stringify({id: "bulb", keep_config: true}));
await flushPromises();
expect(device.removeFromDatabase).not.toHaveBeenCalled();
expect(device.removeFromNetwork).toHaveBeenCalledTimes(1);
expect(settings.getDevice("bulb")).toBeDefined();
expect(removeSpy).not.toHaveBeenCalled();
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: false, force: false, keep_config: true, clear_cache: false}, status: "ok"}),
{},
);
});
it("Should allow to clear cache when removing device", async () => {
const device = devices.bulb;
const removeSpy = vi.spyOn(controller.zigbee, "removeDeviceFromLookup");
@@ -3063,7 +3081,7 @@ describe("Extension: Bridge", () => {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/devices", expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/remove",
stringify({data: {id: "bulb", block: false, force: false, clear_cache: true}, status: "ok"}),
stringify({data: {id: "bulb", block: false, force: false, keep_config: false, clear_cache: true}, status: "ok"}),
{},
);
});
@@ -3135,7 +3153,7 @@ describe("Extension: Bridge", () => {
stringify({
data: {},
status: "error",
error: "Failed to remove device 'bulb' (block: false, force: false, clear cache: false) (Error: device timeout)",
error: "Failed to remove device 'bulb' (block: false, force: false, keep config: false, clear cache: false) (Error: device timeout)",
}),
{},
);