Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions data/configuration.example.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Indicates the configuration version (used by configuration migrations)
version: 4
version: 5

# Home Assistant integration (MQTT discovery)
homeassistant:
Expand All @@ -15,7 +15,7 @@ mqtt:
# MQTT base topic for zigbee2mqtt MQTT messages
base_topic: zigbee2mqtt
# MQTT server URL
server: 'mqtt://localhost'
server: "mqtt://localhost"
# MQTT server authentication, uncomment if required:
# user: my_user
# password: my_password
Expand Down
28 changes: 11 additions & 17 deletions lib/extension/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "node:fs";
import path from "node:path";
import bind from "bind-decorator";
import stringify from "json-stable-stringify-without-jsonify";
import JSZip from "jszip";
Expand Down Expand Up @@ -319,14 +320,18 @@ export default class Bridge extends Extension {
@bind async backup(message: string | KeyValue): Promise<Zigbee2MQTTResponse<"bridge/response/backup">> {
await this.zigbee.backup();
const dataPath = data.getPath();
const files = utils
.getAllFiles(dataPath)
.map((f) => [f, f.substring(dataPath.length + 1)])
.filter((f) => !f[1].startsWith("log"));
const files = utils.getAllFiles(dataPath);
const zip = new JSZip();
const logDir = `log${path.sep}`;
const otaDir = `ota${path.sep}`;

for (const f of files) {
zip.file(f[1], fs.readFileSync(f[0]));
const name = f.slice(dataPath.length + 1);

// XXX: `log` could technically be something else depending on `log_directory` setting
if (!name.startsWith(logDir) && !name.startsWith(otaDir)) {
zip.file(name, fs.readFileSync(f));
}
}

const base64Zip = await zip.generateAsync({type: "base64"});
Expand Down Expand Up @@ -568,19 +573,8 @@ export default class Bridge extends Extension {
}

const device = this.getEntity("device", message.id);
logger.info(`Interviewing '${device.name}'`);

try {
await device.zh.interview(true);
logger.info(`Successfully interviewed '${device.name}'`);
} catch (error) {
throw new Error(`interview of '${device.name}' (${device.ieeeAddr}) failed: ${error}`, {cause: error});
}

// A re-interview can for example result in a different modelId, therefore reconsider the definition.
await device.resolveDefinition(true);
this.eventBus.emitDevicesChanged();
this.eventBus.emitExposesChanged({device});
await device.reInterview(this.eventBus);

return utils.getResponse(message, {id: message.id});
}
Expand Down
1 change: 1 addition & 0 deletions lib/extension/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default class Configure extends Extension {

await this.configure(data.device, "zigbee_event");
});
// TODO: this is triggering for any `data.status`, but should only for `successful`? (relies on `device.definition?` early-return?)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that even when a device interview fails, it's worth configuring it as it might be due to a non crucial error and then the configure makes the device at least partially working.

this.eventBus.onDeviceInterview(this, (data) => this.configure(data.device, "zigbee_event"));
this.eventBus.onLastSeenChanged(this, (data) => this.configure(data.device, "zigbee_event"));
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
Expand Down
1 change: 1 addition & 0 deletions lib/extension/homeassistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export class HomeAssistant extends Extension {
this.eventBus.onGroupMembersChanged(this, this.onGroupMembersChanged);
this.eventBus.onDeviceAnnounce(this, this.onZigbeeEvent);
this.eventBus.onDeviceJoined(this, this.onZigbeeEvent);
// TODO: this is triggering for any `data.status`?
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to do this always, similar to https://github.com/Koenkk/zigbee2mqtt/pull/30566/changes#r2722613265 the error might be not critical and maybe all info needed to identify the device has been received, thus it can discovered.

this.eventBus.onDeviceInterview(this, this.onZigbeeEvent);
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
this.eventBus.onScenesChanged(this, this.onScenesChanged);
Expand Down
2 changes: 2 additions & 0 deletions lib/extension/onEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export default class OnEvent extends Extension {
}
});
this.eventBus.onDeviceInterview(this, async (data) => {
// TODO: this is triggering for any `data.status`, any use outside `successful`?
// ZHC definition would skip from `device.definition?` but OnEvent from ZHC index wouldn't => triple triggering?
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interview failed doesn't (always) mean there is no definition, so I think this is fine.

Copy link
Copy Markdown
Collaborator Author

@Nerivec Nerivec Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I was thinking for this and all above is the fact status is: "started" | "successful" | "failed". So it always triggers twice?
We can take a closer look in a separate PR though, just though it was worth a TODO comment when I passed by these 😁

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends a bit wether we also want to trigger the onEvent on started, I guess not so only on failed or successful makes sense.

Copy link
Copy Markdown
Collaborator Author

@Nerivec Nerivec Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it's worth a look in a follow-up PR, same for the other 2 in HA and configure extensions, should de-dupe calls a bit.

await this.callOnEvent(data.device, {type: "deviceInterview", data: {...this.#getOnEventBaseData(data.device), status: data.status}});
});
this.eventBus.onDeviceAnnounce(this, async (data) => {
Expand Down
Loading