From 5f9019a95fb460f1dc4a31d09e2424ff56049c14 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:12:16 +0200 Subject: [PATCH] fix: Publish endpoint names in `bridge/devices` (#28057) --- lib/extension/bridge.ts | 1 + lib/model/device.ts | 40 ++++++++++++++-------------------- lib/types/api.ts | 1 + test/extensions/bridge.test.ts | 18 +++++++++++++-- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/extension/bridge.ts b/lib/extension/bridge.ts index e58d1b65c..01bc18be5 100644 --- a/lib/extension/bridge.ts +++ b/lib/extension/bridge.ts @@ -730,6 +730,7 @@ export default class Bridge extends Extension { for (const endpoint of device.zh.endpoints) { const data: (typeof endpoints)[keyof typeof endpoints] = { + name: device.endpointName(endpoint), scenes: utils.getScenes(endpoint), bindings: [], configured_reportings: [], diff --git a/lib/model/device.ts b/lib/model/device.ts index dc2e4c3ac..413215531 100644 --- a/lib/model/device.ts +++ b/lib/model/device.ts @@ -76,43 +76,33 @@ export default class Device { } endpoint(key?: string | number): zh.Endpoint | undefined { - let endpoint: zh.Endpoint | undefined; - if (!key) { key = "default"; + } else if (!Number.isNaN(Number(key))) { + return this.zh.getEndpoint(Number(key)); } - if (!Number.isNaN(Number(key))) { - endpoint = this.zh.getEndpoint(Number(key)); - } else if (this.definition?.endpoint) { - const ID = this.definition?.endpoint?.(this.zh)[key]; + if (this.definition?.endpoint) { + const ID = this.definition.endpoint(this.zh)[key]; if (ID) { - endpoint = this.zh.getEndpoint(ID); - } else if (key === "default") { - endpoint = this.zh.endpoints[0]; - } else { - return undefined; + return this.zh.getEndpoint(ID); } - } else { - if (key !== "default") { - return undefined; - } - - endpoint = this.zh.endpoints[0]; } - return endpoint; + return key === "default" ? this.zh.endpoints[0] : undefined; } endpointName(endpoint: zh.Endpoint): string | undefined { let epName: string | undefined; if (this.definition?.endpoint) { - const mapping = this.definition?.endpoint(this.zh); - for (const [name, id] of Object.entries(mapping)) { - if (id === endpoint.ID) { + const mapping = this.definition.endpoint(this.zh); + + for (const name in mapping) { + if (mapping[name] === endpoint.ID) { epName = name; + break; } } } @@ -124,9 +114,11 @@ export default class Device { getEndpointNames(): string[] { const names: string[] = []; - for (const name in this.definition?.endpoint?.(this.zh) ?? {}) { - if (name !== "default") { - names.push(name); + if (this.definition?.endpoint) { + for (const name in this.definition.endpoint(this.zh)) { + if (name !== "default") { + names.push(name); + } } } diff --git a/lib/types/api.ts b/lib/types/api.ts index 020348551..8ff35509f 100644 --- a/lib/types/api.ts +++ b/lib/types/api.ts @@ -197,6 +197,7 @@ export interface Zigbee2MQTTScene { } export interface Zigbee2MQTTDeviceEndpoint { + name?: string; bindings: Zigbee2MQTTDeviceEndpointBinding[]; configured_reportings: Zigbee2MQTTDeviceEndpointConfiguredReporting[]; clusters: {input: string[]; output: string[]}; diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 2dab0abf1..413efcf35 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -350,7 +350,7 @@ describe("Extension: Bridge", () => { it("Should publish devices on startup", async () => { await resetExtension(); - // console.log(mockMQTTPublishAsync.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); + // console.log(mockMQTTPublishAsync.mock.calls.find((c) => c[0] === "zigbee2mqtt/bridge/devices")[1]); expect(mockMQTTPublishAsync).toHaveBeenCalledWith( "zigbee2mqtt/bridge/devices", stringify([ @@ -923,6 +923,7 @@ describe("Extension: Bridge", () => { disabled: false, endpoints: { "1": { + name: "ep1", bindings: [ {cluster: "genLevelCtrl", target: {endpoint: 1, ieee_address: "0x000b57fffec6a5b3", type: "endpoint"}}, {cluster: "genOnOff", target: {endpoint: 1, ieee_address: "0x000b57fffec6a5b3", type: "endpoint"}}, @@ -934,7 +935,13 @@ describe("Extension: Bridge", () => { configured_reportings: [], scenes: [], }, - "2": {bindings: [], clusters: {input: ["genBasic"], output: ["genOta", "genOnOff"]}, configured_reportings: [], scenes: []}, + "2": { + name: "ep2", + bindings: [], + clusters: {input: ["genBasic"], output: ["genOta", "genOnOff"]}, + configured_reportings: [], + scenes: [], + }, }, friendly_name: "remote", ieee_address: "0x0017880104e45517", @@ -1887,42 +1894,49 @@ describe("Extension: Bridge", () => { disabled: false, endpoints: { "10": { + name: "l5", bindings: [], clusters: {input: ["genBasic", "genScenes", "genOnOff", "genLevelCtrl"], output: []}, configured_reportings: [], scenes: [], }, "11": { + name: "l6", bindings: [], clusters: {input: ["genBasic", "genScenes", "closuresWindowCovering"], output: []}, configured_reportings: [], scenes: [], }, "12": { + name: "l7", bindings: [], clusters: {input: ["genBasic", "genScenes", "closuresWindowCovering"], output: []}, configured_reportings: [], scenes: [], }, "5": { + name: "l1", bindings: [], clusters: {input: ["genBasic", "genScenes", "genOnOff", "genLevelCtrl", "lightingColorCtrl"], output: []}, configured_reportings: [], scenes: [], }, "7": { + name: "l2", bindings: [], clusters: {input: ["genBasic", "genScenes", "genOnOff", "genLevelCtrl"], output: []}, configured_reportings: [], scenes: [], }, "8": { + name: "l3", bindings: [], clusters: {input: ["genBasic", "genScenes", "genOnOff", "genLevelCtrl"], output: []}, configured_reportings: [], scenes: [], }, "9": { + name: "l4", bindings: [], clusters: {input: ["genBasic", "genScenes", "genOnOff", "genLevelCtrl"], output: []}, configured_reportings: [],