From d8855b8e6f1abc63daf6771a9ec07279ebc168d9 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 7 May 2025 21:15:05 +0200 Subject: [PATCH] fix: Fix bind/unbind by coordinator ieeeAddr not working (#27333) Co-authored-by: bo0tzz --- lib/extension/bind.ts | 7 +++++-- lib/types/api.ts | 8 ++++---- lib/zigbee.ts | 6 ++++-- test/extensions/bind.test.ts | 19 +++++++++++++++++++ test/extensions/onEvent.test.ts | 2 +- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/lib/extension/bind.ts b/lib/extension/bind.ts index c577704cf..9d608f32a 100755 --- a/lib/extension/bind.ts +++ b/lib/extension/bind.ts @@ -198,7 +198,7 @@ interface ParsedMQTTMessage { type: "bind" | "unbind"; sourceKey?: string; sourceEndpointKey?: string | number; - targetKey?: string; + targetKey?: string | number; targetEndpointKey?: string | number; clusters?: string[]; skipDisableReporting: boolean; @@ -242,7 +242,10 @@ export default class Bind extends Extension { return [message, {type, skipDisableReporting}, `Source device '${message.from}' does not exist`]; } - const resolvedTarget = message.to === DEFAULT_BIND_GROUP.name ? DEFAULT_BIND_GROUP : this.zigbee.resolveEntity(message.to); + const resolvedTarget = + message.to === DEFAULT_BIND_GROUP.name || message.to === DEFAULT_BIND_GROUP.ID + ? DEFAULT_BIND_GROUP + : this.zigbee.resolveEntity(message.to); if (!resolvedTarget) { return [message, {type, skipDisableReporting}, `Target device or group '${message.to}' does not exist`]; diff --git a/lib/types/api.ts b/lib/types/api.ts index 352f0d6ff..a40e0db67 100644 --- a/lib/types/api.ts +++ b/lib/types/api.ts @@ -474,7 +474,7 @@ export interface Zigbee2MQTTAPI { "bridge/request/device/bind": { from: string; from_endpoint: string | number | "default"; - to: string; + to: string | number; to_endpoint?: string | number; clusters?: string[]; skip_disable_reporting?: boolean; @@ -483,7 +483,7 @@ export interface Zigbee2MQTTAPI { "bridge/response/device/bind": { from: string; from_endpoint: string | number; - to: string; + to: string | number; to_endpoint: string | number | undefined; clusters: string[]; failed: string[]; @@ -492,7 +492,7 @@ export interface Zigbee2MQTTAPI { "bridge/request/device/unbind": { from: string; from_endpoint: string | number | "default"; - to: string; + to: string | number; to_endpoint?: string | number; clusters?: string[]; skip_disable_reporting?: boolean; @@ -501,7 +501,7 @@ export interface Zigbee2MQTTAPI { "bridge/response/device/unbind": { from: string; from_endpoint: string | number; - to: string; + to: string | number; to_endpoint: string | number | undefined; clusters: string[]; failed: string[]; diff --git a/lib/zigbee.ts b/lib/zigbee.ts index 63bd45a87..e23a34120 100644 --- a/lib/zigbee.ts +++ b/lib/zigbee.ts @@ -23,6 +23,7 @@ export default class Zigbee { private eventBus: EventBus; private groupLookup = new Map(); private deviceLookup = new Map(); + private coordinatorIeeeAddr!: string; constructor(eventBus: EventBus) { this.eventBus = eventBus; @@ -73,6 +74,7 @@ export default class Zigbee { throw error; } + this.coordinatorIeeeAddr = this.herdsman.getDevicesByType("Coordinator")[0].ieeeAddr; await this.resolveDevicesDefinitions(); this.herdsman.on("adapterDisconnected", () => this.eventBus.emitAdapterDisconnected()); @@ -280,8 +282,8 @@ export default class Zigbee { return this.resolveDevice(key.ieeeAddr); } - if (typeof key === "string" && key.toLowerCase() === "coordinator") { - return this.resolveDevice(this.herdsman.getDevicesByType("Coordinator")[0].ieeeAddr); + if (typeof key === "string" && (key.toLowerCase() === "coordinator" || key === this.coordinatorIeeeAddr)) { + return this.resolveDevice(this.coordinatorIeeeAddr); } const settingsDevice = settings.getDevice(key.toString()); diff --git a/test/extensions/bind.test.ts b/test/extensions/bind.test.ts index 796c58e9d..6f526a4c2 100644 --- a/test/extensions/bind.test.ts +++ b/test/extensions/bind.test.ts @@ -281,6 +281,25 @@ describe("Extension: Bind", () => { ); }); + it("Should allow to bind to coordinator by ieeeAddr", async () => { + const device = devices.remote; + const target = devices.coordinator.getEndpoint(1)!; + const endpoint = device.getEndpoint(1)!; + mockClear(device); + mockMQTTEvents.message( + "zigbee2mqtt/bridge/request/device/bind", + stringify({from: "remote", to: devices.coordinator.ieeeAddr, clusters: ["genOnOff"]}), + ); + await flushPromises(); + expect(endpoint.bind).toHaveBeenCalledTimes(1); + expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); + expect(mockMQTTPublishAsync).toHaveBeenCalledWith( + "zigbee2mqtt/bridge/response/device/bind", + stringify({data: {from: "remote", from_endpoint: "default", to: "0x00124b00120144ae", clusters: ["genOnOff"], failed: []}, status: "ok"}), + {}, + ); + }); + it("Should log error when there is nothing to bind", async () => { const device = devices.bulb_color; const endpoint = device.getEndpoint(1)!; diff --git a/test/extensions/onEvent.test.ts b/test/extensions/onEvent.test.ts index ddcd79cc5..0d7972ae7 100644 --- a/test/extensions/onEvent.test.ts +++ b/test/extensions/onEvent.test.ts @@ -18,7 +18,7 @@ import * as settings from "../../lib/util/settings"; const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug]; -returnDevices.push(devices.bulb.ieeeAddr, devices.LIVOLO.ieeeAddr); +returnDevices.push(devices.bulb.ieeeAddr, devices.LIVOLO.ieeeAddr, devices.coordinator.ieeeAddr); describe("Extension: OnEvent", () => { let controller: Controller;