matterbridge-zigbee2mqtt 2.4.2 → 2.4.4-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/entity.js CHANGED
@@ -1,37 +1,13 @@
1
- /**
2
- * This file contains the classes ZigbeeEntity, ZigbeeDevice and ZigbeeGroup.
3
- *
4
- * @file entity.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 3.1.0
8
- *
9
- * Copyright 2023, 2024, 2025 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
- import { MatterbridgeDevice, airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, ClusterId, WindowCovering, DoorLock, BridgedDeviceBasicInformation, ThermostatCluster, Thermostat, getClusterNameById, powerSource, bridgedNode, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, electricalSensor, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, onOffLight, dimmableLight, colorTemperatureLight, onOffOutlet, SwitchesTag, NumberTag, coverDevice, thermostatDevice, MatterbridgeEndpoint, dimmableOutlet, doorLockDevice, occupancySensor, lightSensor, contactSensor, temperatureSensor, humiditySensor, pressureSensor, genericSwitch, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, } from 'matterbridge';
1
+ import { airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, ClusterId, WindowCovering, DoorLock, BridgedDeviceBasicInformation, ThermostatCluster, Thermostat, getClusterNameById, powerSource, bridgedNode, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, electricalSensor, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, onOffLight, dimmableLight, colorTemperatureLight, onOffOutlet, SwitchesTag, NumberTag, coverDevice, thermostatDevice, MatterbridgeEndpoint, dimmableOutlet, doorLockDevice, occupancySensor, lightSensor, contactSensor, temperatureSensor, humiditySensor, pressureSensor, genericSwitch, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, } from 'matterbridge';
24
2
  import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er, YELLOW } from 'matterbridge/logger';
25
3
  import { deepCopy, deepEqual, isValidNumber } from 'matterbridge/utils';
4
+ import { PowerSourceBehavior } from 'matterbridge/matter';
26
5
  import * as color from 'matterbridge/utils';
27
6
  import EventEmitter from 'events';
28
7
  import { hostname } from 'os';
29
- /**
30
- * Represents a Zigbee entity: a group or a device.
31
- *
32
- * @class
33
- * @extends {EventEmitter}
34
- */
8
+ function require(type, options) {
9
+ return { clusterId: ClusterId(1), options };
10
+ }
35
11
  export class ZigbeeEntity extends EventEmitter {
36
12
  log;
37
13
  serial = '';
@@ -51,7 +27,6 @@ export class ZigbeeEntity extends EventEmitter {
51
27
  ignoreFeatures = [];
52
28
  transition = false;
53
29
  propertyMap = new Map();
54
- // We save the tag list and device types and cluster servers and clients to avoid multiple lookups
55
30
  mutableDevice = new Map();
56
31
  colorTimeout = undefined;
57
32
  thermostatTimeout = undefined;
@@ -59,12 +34,6 @@ export class ZigbeeEntity extends EventEmitter {
59
34
  hasEndpoints = false;
60
35
  isRouter = false;
61
36
  noUpdate = false;
62
- /**
63
- * Creates an instance of ZigbeeEntity.
64
- *
65
- * @param {ZigbeePlatform} platform - The Zigbee platform instance.
66
- * @param {BridgeDevice | BridgeGroup} entity - The bridge device or group instance received from zigbee2mqtt.
67
- */
68
37
  constructor(platform, entity) {
69
38
  super();
70
39
  this.platform = platform;
@@ -82,26 +51,22 @@ export class ZigbeeEntity extends EventEmitter {
82
51
  this.en = gn;
83
52
  this.ien = ign;
84
53
  }
85
- this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: platform.debugEnabled ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
54
+ this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4, logLevel: platform.debugEnabled ? "debug" : "info" });
86
55
  this.log.debug(`Created MatterEntity: ${this.entityName}`);
87
56
  this.platform.z2m.on('MESSAGE-' + this.entityName, (payload) => {
88
- // Check if the message is a duplicate that can be ingored cause only linkquality and last_seen have changed (action is always passed)
89
57
  const now = Date.now();
90
58
  if (now - this.lastSeen < 1000 * 60 && deepEqual(this.lastPayload, payload, ['linkquality', 'last_seen', ...this.ignoreFeatures]) && !Object.prototype.hasOwnProperty.call(this.lastPayload, 'action')) {
91
59
  this.log.debug(`Skipping not changed ${platform.z2mDevicesRegistered ? 'MQTT message' : 'State update'} for accessory ${this.entityName}`);
92
60
  return;
93
61
  }
94
62
  this.lastSeen = Date.now();
95
- // Check and deep copy the payload
96
63
  if (deepEqual(this.lastPayload, payload, this.ignoreFeatures))
97
64
  return;
98
65
  this.lastPayload = deepCopy(payload);
99
66
  if (Object.prototype.hasOwnProperty.call(this.lastPayload, 'action'))
100
67
  delete this.lastPayload.action;
101
- // Remove each key in ignoreFeatures from the payload copy
102
68
  for (const key of this.ignoreFeatures) {
103
69
  if (Object.prototype.hasOwnProperty.call(payload, key)) {
104
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
105
70
  delete payload[key];
106
71
  this.log.debug(`Removed key ${CYAN}${key}${db} from payload`);
107
72
  }
@@ -115,17 +80,13 @@ export class ZigbeeEntity extends EventEmitter {
115
80
  return;
116
81
  }
117
82
  this.log.info(`${db}${platform.z2mDevicesRegistered ? 'MQTT message' : 'State update'} for device ${this.ien}${this.entityName}${rs}${db} payload: ${debugStringify(payload)}`);
118
- // Parse the payload and update the accessory
119
83
  Object.entries(payload).forEach(([key, value]) => {
120
- // Skip null and undefined values
121
84
  if (value === undefined || value === null)
122
85
  return;
123
86
  if (this.bridgedDevice === undefined || this.noUpdate)
124
87
  return;
125
- // Modify voltage to battery_voltage
126
88
  if (key === 'voltage' && this.isDevice && this.device?.power_source === 'Battery')
127
89
  key = 'battery_voltage';
128
- // Lookup the property in the propertyMap and ZigbeeToMatter table
129
90
  const propertyMap = this.propertyMap.get(key);
130
91
  if (propertyMap) {
131
92
  this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` +
@@ -145,21 +106,16 @@ export class ZigbeeEntity extends EventEmitter {
145
106
  }
146
107
  else
147
108
  this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in propertyMap`);
148
- // Switch actions on the endpoints
149
109
  if (key === 'action' && value !== '') {
150
110
  const propertyMap = this.propertyMap.get(('action_' + value));
151
111
  if (propertyMap) {
152
- // this.log.debug(`Payload entry ${CYAN}${value}${db} => name: ${CYAN}${propertyMap.name}${db} endpoint: ${CYAN}${propertyMap.endpoint}${db} action: ${CYAN}${propertyMap.action}${db}`);
153
112
  const child = this.bridgedDevice.getChildEndpointByName(propertyMap.endpoint);
154
113
  if (child && child.number)
155
- this.bridgedDevice.triggerSwitchEvent(propertyMap.action, this.log, child);
114
+ child.triggerSwitchEvent(propertyMap.action, this.log);
156
115
  }
157
116
  else
158
117
  this.log.debug(`*Payload entry ${CYAN}${('action_' + value)}${db} not found in propertyMap`);
159
118
  }
160
- // WindowCovering
161
- // Zigbee2MQTT cover: 0 = open, 100 = closed
162
- // Matter WindowCovering: 0 = open 10000 = closed
163
119
  if (key === 'position' && this.isDevice && isValidNumber(value, 0, 100)) {
164
120
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', value * 100);
165
121
  }
@@ -180,26 +136,17 @@ export class ZigbeeEntity extends EventEmitter {
180
136
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position);
181
137
  }
182
138
  }
183
- // ColorControl colorTemperatureMired and colorMode
184
139
  if (key === 'color_temp' && 'color_mode' in payload && payload['color_mode'] === 'color_temp') {
185
140
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds);
186
141
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorTemperatureMireds', Math.max(147, Math.min(500, typeof value === 'number' ? value : 0)));
187
142
  }
188
- // ColorControl currentHue, currentSaturation and colorMode
189
143
  if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'hs') {
190
144
  const { hue, saturation } = value;
191
145
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
192
146
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentHue', Math.round((hue / 360) * 254));
193
147
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentSaturation', Math.round((saturation / 100) * 254));
194
148
  }
195
- // ColorControl currentX, currentY and colorMode
196
149
  if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'xy') {
197
- /* not supported by Apple Home so we convert xy to hue and saturation
198
- const { x, y } = value as { x: number; y: number };
199
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY);
200
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentX', Math.max(Math.min(Math.round(x * 65536), 65279), 0));
201
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentY', Math.max(Math.min(Math.round(y * 65536), 65279), 0));
202
- */
203
150
  const { x, y } = value;
204
151
  const hsl = color.xyToHsl(x, y);
205
152
  const rgb = color.xyColorToRgbColor(x, y);
@@ -226,13 +173,6 @@ export class ZigbeeEntity extends EventEmitter {
226
173
  }
227
174
  });
228
175
  }
229
- /**
230
- * Destroys the ZigbeeEntity instance by clearing any active timeouts.
231
- *
232
- * @remarks
233
- * This method is used to clean up the ZigbeeEntity instance by clearing any active timeouts for color and thermostat operations.
234
- * It ensures that no further actions are taken on these timeouts after the entity is destroyed.
235
- */
236
176
  destroy() {
237
177
  if (this.colorTimeout)
238
178
  clearTimeout(this.colorTimeout);
@@ -241,107 +181,58 @@ export class ZigbeeEntity extends EventEmitter {
241
181
  clearTimeout(this.thermostatTimeout);
242
182
  this.thermostatTimeout = undefined;
243
183
  }
244
- /**
245
- * Creates a mutable device with the specified definition and includes the specified server list.
246
- *
247
- * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The device type definition.
248
- * @param {ClusterId[]} [includeServerList=[]] - The list of server clusters to include.
249
- * @param {EndpointOptions} [options] - Optional endpoint options.
250
- * @param {boolean} [debug] - Optional debug flag.
251
- * @returns {MatterbridgeDevice} The created mutable device.
252
- *
253
- * @remarks
254
- * This method creates a mutable device based on the provided definition. It adds the specified server clusters
255
- * to the device and configures the device with basic information and power source clusters. If the device is a
256
- * coordinator, it sets up the basic information cluster with coordinator-specific details. If the device is a
257
- * group, it sets up the basic information cluster with group-specific details. The method also configures the
258
- * power source cluster based on the device's power source.
259
- */
260
- async createMutableDevice(definition, options, debug) {
261
- if (this.platform.matterbridge.edge === true) {
262
- this.bridgedDevice = (await MatterbridgeEndpoint.loadInstance(definition, options, debug));
263
- }
264
- else {
265
- this.bridgedDevice = await MatterbridgeDevice.loadInstance(definition, undefined, debug);
266
- }
267
- return this.bridgedDevice;
268
- }
269
- getBridgedDeviceBasicInformation() {
184
+ addBridgedDeviceBasicInformation() {
270
185
  if (!this.bridgedDevice)
271
186
  throw new Error('No bridged device');
272
- // Add BridgedDeviceBasicInformation cluster and device type
273
187
  const softwareVersion = parseInt(this.platform.z2mBridgeInfo?.version || '1');
274
188
  const softwareVersionString = `${this.platform.z2mBridgeInfo?.version} (commit ${this.platform.z2mBridgeInfo?.commit})`;
275
189
  const hardwareVersion = parseInt(this.platform.matterbridge.matterbridgeVersion || '1');
276
190
  const hardwareVersionString = this.platform.matterbridge.matterbridgeVersion || 'unknown';
277
191
  if (this.isDevice && this.device && this.device.friendly_name === 'Coordinator') {
278
- return this.bridgedDevice.getDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Coordinator', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
192
+ this.bridgedDevice.createDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Coordinator', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
193
+ return this.bridgedDevice;
279
194
  }
280
195
  else if (this.isDevice && this.device) {
281
- return this.bridgedDevice.getDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, this.device.definition ? this.device.definition.vendor : this.device.manufacturer, this.device.definition ? this.device.definition.model : this.device.model_id, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
196
+ this.bridgedDevice.createDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, this.device.definition ? this.device.definition.vendor : this.device.manufacturer, this.device.definition ? this.device.definition.model : this.device.model_id, softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
197
+ return this.bridgedDevice;
282
198
  }
283
199
  if (!this.group)
284
200
  throw new Error('No group found');
285
- return this.bridgedDevice.getDefaultBridgedDeviceBasicInformationClusterServer(this.group.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Group', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
286
- }
287
- addBridgedDeviceBasicInformation() {
288
- if (!this.bridgedDevice)
289
- throw new Error('No bridged device');
290
- // Add BridgedDeviceBasicInformation cluster and device type
291
- this.bridgedDevice.addDeviceType(bridgedNode);
292
- this.bridgedDevice.addClusterServer(this.getBridgedDeviceBasicInformation());
201
+ this.bridgedDevice.createDefaultBridgedDeviceBasicInformationClusterServer(this.group.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Group', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
293
202
  return this.bridgedDevice;
294
203
  }
295
- getPowerSource() {
204
+ addPowerSource() {
296
205
  if (!this.bridgedDevice)
297
206
  throw new Error('No bridged device');
298
207
  if (this.isDevice) {
299
208
  if (this.device?.power_source === 'Battery') {
300
- return this.bridgedDevice.getDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok);
209
+ this.bridgedDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok);
301
210
  }
302
211
  else {
303
- return this.bridgedDevice?.getDefaultPowerSourceWiredClusterServer();
212
+ this.bridgedDevice.createDefaultPowerSourceWiredClusterServer();
304
213
  }
305
214
  }
306
- return this.bridgedDevice?.getDefaultPowerSourceWiredClusterServer();
307
- }
308
- addPowerSource() {
309
- if (!this.bridgedDevice)
310
- throw new Error('No bridged device');
311
- // Add PowerSource device type and cluster
312
- this.bridgedDevice.addDeviceType(powerSource);
313
- this.bridgedDevice.addClusterServer(this.getPowerSource());
215
+ if (this.isGroup) {
216
+ this.bridgedDevice.createDefaultPowerSourceWiredClusterServer();
217
+ }
314
218
  return this.bridgedDevice;
315
219
  }
316
- /**
317
- * Verifies that all required server clusters are present on the main endpoint and child endpoints.
318
- *
319
- * @param {MatterbridgeDevice} endpoint - The device endpoint to verify.
320
- * @returns {boolean} True if all required server clusters are present, false otherwise.
321
- *
322
- * @remarks
323
- * This method checks if all required server clusters are present on the main endpoint and its child endpoints.
324
- * It logs an error message if any required server cluster is missing and returns false. If all required server
325
- * clusters are present, it returns true.
326
- */
327
220
  verifyMutableDevice(endpoint) {
328
221
  if (!endpoint)
329
222
  return false;
330
- // Verify that all required server clusters are present in the main endpoint and in the child endpoints
331
223
  for (const deviceType of endpoint.getDeviceTypes()) {
332
224
  for (const clusterId of deviceType.requiredServerClusters) {
333
- if (!endpoint.getClusterServerById(clusterId)) {
334
- endpoint.addClusterServerFromList(endpoint, [clusterId]);
225
+ if (!endpoint.hasClusterServer(clusterId)) {
226
+ endpoint.addClusterServers([clusterId]);
335
227
  this.log.warn(`Endpoint with device type ${deviceType.name} (0x${deviceType.code.toString(16)}) requires cluster server ${getClusterNameById(clusterId)} (0x${clusterId.toString(16)}) but it is not present on endpoint`);
336
228
  }
337
229
  }
338
230
  }
339
- // Verify that all required server clusters are present in the child endpoints
340
231
  for (const childEndpoint of endpoint.getChildEndpoints()) {
341
232
  for (const deviceType of childEndpoint.getDeviceTypes()) {
342
233
  for (const clusterId of deviceType.requiredServerClusters) {
343
- if (!childEndpoint.getClusterServerById(clusterId)) {
344
- endpoint.addClusterServerFromList(childEndpoint, [clusterId]);
234
+ if (!childEndpoint.hasClusterServer(clusterId)) {
235
+ childEndpoint.addClusterServers([clusterId]);
345
236
  this.log.warn(`Child endpoint with device type ${deviceType.name} (0x${deviceType.code.toString(16)}) requires cluster server ${getClusterNameById(clusterId)} (0x${clusterId.toString(16)}) but it is not present on child endpoint`);
346
237
  }
347
238
  }
@@ -349,23 +240,12 @@ export class ZigbeeEntity extends EventEmitter {
349
240
  }
350
241
  return true;
351
242
  }
352
- /**
353
- * Configures the device by setting up the WindowCovering and DoorLock clusters if they are present.
354
- *
355
- * @returns {Promise<void>} A promise that resolves when the configuration is complete.
356
- *
357
- * @remarks
358
- * This method configures the device by checking for the presence of the WindowCovering and DoorLock clusters.
359
- * If the WindowCovering cluster is present, it sets the target as the current position and stops any ongoing
360
- * movement. If the DoorLock cluster is present, it retrieves the lock state and triggers the appropriate lock
361
- * operation event based on the current state.
362
- */
363
243
  async configure() {
364
- if (this.bridgedDevice?.getClusterServerById(WindowCovering.Cluster.id)) {
244
+ if (this.bridgedDevice?.hasClusterServer(WindowCovering.Cluster.id)) {
365
245
  this.log.info(`Configuring ${this.bridgedDevice?.deviceName} WindowCovering cluster`);
366
246
  await this.bridgedDevice?.setWindowCoveringTargetAsCurrentAndStopped();
367
247
  }
368
- if (this.bridgedDevice?.getClusterServerById(DoorLock.Cluster.id)) {
248
+ if (this.bridgedDevice?.hasClusterServer(DoorLock.Cluster.id)) {
369
249
  this.log.info(`Configuring ${this.bridgedDevice?.deviceName} DoorLock cluster`);
370
250
  const state = this.bridgedDevice?.getAttribute(DoorLock.Cluster.id, 'lockState', this.log);
371
251
  if (this.bridgedDevice.maybeNumber) {
@@ -376,32 +256,15 @@ export class ZigbeeEntity extends EventEmitter {
376
256
  }
377
257
  }
378
258
  }
379
- /**
380
- * Updates the attribute of a cluster on a device endpoint if the value has changed.
381
- *
382
- * @param {Endpoint} deviceEndpoint - The device endpoint to update.
383
- * @param {string | undefined} childEndpointName - The name of the child endpoint, if any.
384
- * @param {number} clusterId - The ID of the cluster to update.
385
- * @param {string} attributeName - The name of the attribute to update.
386
- * @param {PayloadValue} value - The new value of the attribute.
387
- * @param {string[]} [lookup] - Optional lookup array for converting string values to indices.
388
- *
389
- * @remarks
390
- * This method checks if the specified attribute of a cluster on a device endpoint has changed. If the attribute
391
- * has changed, it updates the attribute with the new value. If a lookup array is provided, it converts string
392
- * values to their corresponding indices in the lookup array. The method logs the update process and handles any
393
- * errors that occur during the update.
394
- */
395
259
  updateAttributeIfChanged(deviceEndpoint, childEndpointName, clusterId, attributeName, value, lookup) {
396
260
  if (childEndpointName && childEndpointName !== '') {
397
261
  deviceEndpoint = this.bridgedDevice?.getChildEndpointByName(childEndpointName) ?? deviceEndpoint;
398
262
  }
399
- const cluster = deviceEndpoint.getClusterServerById(ClusterId(clusterId));
400
- if (cluster === undefined) {
263
+ if (!deviceEndpoint.hasClusterServer(ClusterId(clusterId))) {
401
264
  this.log.debug(`Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} cluster ${hk}${clusterId}${db}-${hk}${getClusterNameById(ClusterId(clusterId))}${db} not found: is z2m converter exposing all features?`);
402
265
  return;
403
266
  }
404
- if (!cluster.isAttributeSupportedByName(attributeName)) {
267
+ if (!deviceEndpoint.hasAttributeServer(ClusterId(clusterId), attributeName)) {
405
268
  this.log.debug(`Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} error attribute ${hk}${clusterId}${db}-${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} not found`);
406
269
  return;
407
270
  }
@@ -415,7 +278,7 @@ export class ZigbeeEntity extends EventEmitter {
415
278
  return;
416
279
  }
417
280
  }
418
- const localValue = this.bridgedDevice?.getAttribute(ClusterId(clusterId), attributeName, undefined, deviceEndpoint);
281
+ const localValue = deviceEndpoint.getAttribute(ClusterId(clusterId), attributeName, undefined);
419
282
  if (typeof value === 'object' ? deepEqual(value, localValue) : value === localValue) {
420
283
  this.log.debug(`Skip update endpoint ${deviceEndpoint.name}:${deviceEndpoint.number}${childEndpointName ? ' (' + childEndpointName + ')' : ''} ` +
421
284
  `attribute ${getClusterNameById(ClusterId(clusterId))}.${attributeName} already ${typeof value === 'object' ? debugStringify(value) : value}`);
@@ -424,24 +287,12 @@ export class ZigbeeEntity extends EventEmitter {
424
287
  this.log.info(`${db}Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
425
288
  `attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} from ${YELLOW}${typeof localValue === 'object' ? debugStringify(localValue) : localValue}${db} to ${YELLOW}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
426
289
  try {
427
- this.bridgedDevice?.setAttribute(ClusterId(clusterId), attributeName, value, undefined, deviceEndpoint);
290
+ deviceEndpoint.setAttribute(ClusterId(clusterId), attributeName, value, undefined);
428
291
  }
429
292
  catch (error) {
430
293
  this.log.error(`Error setting attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${er}.${hk}${attributeName}${er} to ${value}: ${error}`);
431
294
  }
432
295
  }
433
- /**
434
- * Publishes a command to the specified entity with the given payload.
435
- *
436
- * @param {string} command - The command to execute.
437
- * @param {string} entityName - The name of the entity to publish the command to.
438
- * @param {Payload} payload - The payload of the command.
439
- *
440
- * @remarks
441
- * This method logs the execution of the command and publishes the command to the specified entity.
442
- * If the entity name starts with 'bridge/request', it publishes the payload without a 'set' suffix.
443
- * Otherwise, it publishes the payload with a 'set' suffix.
444
- */
445
296
  publishCommand(command, entityName, payload) {
446
297
  this.log.debug(`executeCommand ${command} called for ${this.ien}${entityName}${rs}${db} payload: ${debugStringify(payload)}`);
447
298
  if (entityName.startsWith('bridge/request')) {
@@ -451,16 +302,7 @@ export class ZigbeeEntity extends EventEmitter {
451
302
  this.platform.publish(entityName, 'set', JSON.stringify(payload));
452
303
  }
453
304
  }
454
- /**
455
- * Logs the property map of the Zigbee entity.
456
- *
457
- * @remarks
458
- * This method iterates over the property map of the Zigbee entity and logs each property's details,
459
- * including its name, type, values, minimum and maximum values, unit, and endpoint.
460
- */
461
- // zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
462
305
  logPropertyMap() {
463
- // Log properties
464
306
  this.propertyMap.forEach((value, key) => {
465
307
  this.log.debug(`Property ${CYAN}${key}${db} name ${CYAN}${value.name}${db} type ${CYAN}${value.type === '' ? 'generic' : value.type}${db} endpoint ${CYAN}${value.endpoint === '' ? 'main' : value.endpoint}${db} ` +
466
308
  `category ${CYAN}${value.category}${db} description ${CYAN}${value.description}${db} label ${CYAN}${value.label}${db} unit ${CYAN}${value.unit}${db} ` +
@@ -468,33 +310,10 @@ export class ZigbeeEntity extends EventEmitter {
468
310
  });
469
311
  }
470
312
  }
471
- /**
472
- * Represents a Zigbee group entity.
473
- *
474
- * @class
475
- * @extends {ZigbeeEntity}
476
- */
477
313
  export class ZigbeeGroup extends ZigbeeEntity {
478
- /**
479
- * Creates an instance of ZigbeeGroup.
480
- *
481
- * @param {ZigbeePlatform} platform - The Zigbee platform instance.
482
- * @param {BridgeGroup} group - The bridge group instance.
483
- */
484
314
  constructor(platform, group) {
485
315
  super(platform, group);
486
316
  }
487
- /**
488
- * Creates a new ZigbeeGroup instance.
489
- *
490
- * @param {ZigbeePlatform} platform - The Zigbee platform instance.
491
- * @param {BridgeGroup} group - The bridge group instance.
492
- * @returns {Promise<ZigbeeGroup>} A promise that resolves to the created ZigbeeGroup instance.
493
- *
494
- * @remarks
495
- * This method initializes a new ZigbeeGroup instance, sets up its properties, and configures the device
496
- * based on the group members. It also adds command handlers for the group.
497
- */
498
317
  static async create(platform, group) {
499
318
  const zigbeeGroup = new ZigbeeGroup(platform, group);
500
319
  if (zigbeeGroup.platform.postfixHostname) {
@@ -503,7 +322,6 @@ export class ZigbeeGroup extends ZigbeeEntity {
503
322
  else {
504
323
  zigbeeGroup.serial = `group-${group.id}`.slice(0, 32);
505
324
  }
506
- // Set the device entity select
507
325
  zigbeeGroup.log.warn(`***Group ${zigbeeGroup.en}${group.friendly_name}${db} adds select device ${group.id} (${group.friendly_name})`);
508
326
  if (!platform.selectDevice.get(`group-${group.id}`)) {
509
327
  platform.selectDevice.set(`group-${group.id}`, { serial: `group-${group.id}`, name: group.friendly_name, icon: 'wifi', entities: [] });
@@ -519,23 +337,19 @@ export class ZigbeeGroup extends ZigbeeEntity {
519
337
  let isCover = false;
520
338
  let isThermostat = false;
521
339
  if (group.members.length === 0) {
522
- // Create a virtual device for the empty group to use in automations
523
340
  zigbeeGroup.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} is a ${CYAN}virtual${db} group`);
524
- zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
341
+ zigbeeGroup.bridgedDevice = new MatterbridgeEndpoint([onOffSwitch, bridgedNode, powerSource], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
525
342
  isSwitch = true;
526
343
  zigbeeGroup.propertyMap.set('state', { name: 'state', type: 'switch', endpoint: '' });
527
344
  }
528
345
  else {
529
- // Create a switch or light or outlet device for the group
530
346
  group.members.forEach((member) => {
531
- // const device = zigbeeGroup.platform.z2m.getDevice(member.ieee_address);
532
347
  const device = zigbeeGroup.platform.z2mBridgeDevices?.find((device) => device.ieee_address === member.ieee_address);
533
348
  if (!device)
534
349
  return;
535
350
  zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${db}: member device ${dn}${device.friendly_name}${db}`);
536
351
  device.definition?.exposes.forEach((expose) => {
537
352
  if (expose.features) {
538
- // Specific features with type
539
353
  expose.features?.forEach((feature) => {
540
354
  if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock') {
541
355
  expose.type = 'child_lock';
@@ -565,7 +379,6 @@ export class ZigbeeGroup extends ZigbeeEntity {
565
379
  });
566
380
  }
567
381
  else {
568
- // Generic features without type
569
382
  zigbeeGroup.log.debug(`- generic type ${CYAN}${expose.type}${db} expose name ${CYAN}${expose.name}${db} property ${CYAN}${expose.property}${db}`);
570
383
  }
571
384
  });
@@ -611,17 +424,14 @@ export class ZigbeeGroup extends ZigbeeEntity {
611
424
  }
612
425
  if (!deviceType)
613
426
  return zigbeeGroup;
614
- zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
427
+ zigbeeGroup.bridgedDevice = new MatterbridgeEndpoint([deviceType, bridgedNode, powerSource], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
615
428
  }
616
429
  zigbeeGroup.addBridgedDeviceBasicInformation();
617
430
  zigbeeGroup.addPowerSource();
618
- zigbeeGroup.bridgedDevice.addRequiredClusterServers(zigbeeGroup.bridgedDevice);
619
- // Verify the device
431
+ zigbeeGroup.bridgedDevice.addRequiredClusterServers();
620
432
  if (!zigbeeGroup.bridgedDevice || !zigbeeGroup.verifyMutableDevice(zigbeeGroup.bridgedDevice))
621
433
  return zigbeeGroup;
622
- // Log properties
623
434
  zigbeeGroup.logPropertyMap();
624
- // Add command handlers
625
435
  if (isSwitch || isLight) {
626
436
  if (isSwitch && !isLight)
627
437
  await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'switch');
@@ -629,7 +439,6 @@ export class ZigbeeGroup extends ZigbeeEntity {
629
439
  await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'light');
630
440
  zigbeeGroup.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
631
441
  zigbeeGroup.log.warn(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
632
- // logEndpoint(zigbeeGroup.bridgedDevice!);
633
442
  });
634
443
  zigbeeGroup.bridgedDevice.addCommandHandler('on', async () => {
635
444
  zigbeeGroup.log.debug(`Command on called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
@@ -721,7 +530,6 @@ export class ZigbeeGroup extends ZigbeeEntity {
721
530
  zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (newValue, oldValue) => {
722
531
  zigbeeGroup.bridgedDevice?.log.info(`Thermostat systemMode changed from ${oldValue} to ${newValue}`);
723
532
  if (oldValue !== newValue) {
724
- // Thermostat.SystemMode.Heat && newValue === Thermostat.SystemMode.Off
725
533
  zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat systemMode to ${newValue}`);
726
534
  if (newValue === Thermostat.SystemMode.Off) {
727
535
  zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'off' });
@@ -762,7 +570,6 @@ export class ZigbeeGroup extends ZigbeeEntity {
762
570
  return zigbeeGroup;
763
571
  }
764
572
  }
765
- // prettier-ignore
766
573
  export const z2ms = [
767
574
  { type: 'switch', name: 'state', property: 'state', deviceType: onOffSwitch, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
768
575
  { type: 'switch', name: 'brightness', property: 'brightness', deviceType: dimmableSwitch, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(1, Math.min(254, value)); } },
@@ -821,46 +628,22 @@ export const z2ms = [
821
628
  { type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return value * 1000; } },
822
629
  { type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return value * 1000; } },
823
630
  ];
824
- /**
825
- * Represents a Zigbee device entity.
826
- *
827
- * @class
828
- * @extends {ZigbeeEntity}
829
- */
830
631
  export class ZigbeeDevice extends ZigbeeEntity {
831
- /**
832
- * Represents a Zigbee device entity.
833
- *
834
- * @class
835
- * @extends {ZigbeeEntity}
836
- */
837
632
  constructor(platform, device) {
838
633
  super(platform, device);
839
634
  }
840
- /**
841
- * Creates a new ZigbeeDevice instance.
842
- *
843
- * @param {ZigbeePlatform} platform - The Zigbee platform instance.
844
- * @param {BridgeDevice} device - The bridge device instance.
845
- * @returns {Promise<ZigbeeDevice>} A promise that resolves to the created ZigbeeDevice instance.
846
- *
847
- * @remarks
848
- * This method initializes a new ZigbeeDevice instance, sets up its properties, and configures the device
849
- * based on the device definition and options. It also adds command handlers for the device.
850
- */
851
635
  static async create(platform, device) {
852
636
  const zigbeeDevice = new ZigbeeDevice(platform, device);
853
637
  zigbeeDevice.serial = `${device.ieee_address}`;
854
638
  if (zigbeeDevice.platform.postfixHostname) {
855
639
  zigbeeDevice.serial = `${zigbeeDevice.serial}_${hostname}`.slice(0, 32);
856
640
  }
857
- // Set Coordinator and dedicated routers
858
641
  if (device.friendly_name === 'Coordinator' || (device.model_id === 'ti.router' && device.manufacturer === 'TexasInstruments') || (device.model_id.startsWith('SLZB-') && device.manufacturer === 'SMLIGHT')) {
859
642
  zigbeeDevice.isRouter = true;
860
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
643
+ zigbeeDevice.bridgedDevice = new MatterbridgeEndpoint([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
861
644
  zigbeeDevice.addBridgedDeviceBasicInformation();
862
645
  zigbeeDevice.addPowerSource();
863
- zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
646
+ zigbeeDevice.bridgedDevice.addRequiredClusterServers();
864
647
  await zigbeeDevice.bridgedDevice.addFixedLabel('type', 'lock');
865
648
  zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice);
866
649
  zigbeeDevice.bridgedDevice.addCommandHandler('lockDoor', async () => {
@@ -875,7 +658,6 @@ export class ZigbeeDevice extends ZigbeeEntity {
875
658
  });
876
659
  return zigbeeDevice;
877
660
  }
878
- // Get types and properties
879
661
  const types = [];
880
662
  const endpoints = [];
881
663
  const names = [];
@@ -889,7 +671,6 @@ export class ZigbeeDevice extends ZigbeeEntity {
889
671
  const values = [];
890
672
  device.definition?.exposes.forEach((expose) => {
891
673
  if (expose.features) {
892
- // Specific features with type
893
674
  expose.features?.forEach((feature) => {
894
675
  if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock')
895
676
  feature.name = 'child_lock';
@@ -907,8 +688,6 @@ export class ZigbeeDevice extends ZigbeeEntity {
907
688
  });
908
689
  }
909
690
  else {
910
- // Generic features without type
911
- // Change voltage to battery_voltage for battery powered devices
912
691
  if (device.power_source === 'Battery' && expose.name === 'voltage')
913
692
  expose.name = 'battery_voltage';
914
693
  if (device.power_source === 'Battery' && expose.property === 'voltage')
@@ -957,7 +736,6 @@ export class ZigbeeDevice extends ZigbeeEntity {
957
736
  types[index] = type === 'switch' || type === 'light' ? 'outlet' : type;
958
737
  });
959
738
  }
960
- // Set the device entity select
961
739
  platform.selectEntity.set('last_seen', { name: 'last_seen', description: 'Last seen', icon: 'hub' });
962
740
  for (const [index, property] of properties.entries()) {
963
741
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} adds select device ${device.ieee_address} (${device.friendly_name})`);
@@ -969,21 +747,10 @@ export class ZigbeeDevice extends ZigbeeEntity {
969
747
  platform.selectEntity.set(property, { name: property, description: descriptions[index], icon: 'hub' });
970
748
  platform.selectDevice.get(device.ieee_address)?.entities?.push({ name: property, description: descriptions[index], icon: 'hub' });
971
749
  }
972
- // Set the global and devic based feature blacklist
973
750
  if (platform.featureBlackList)
974
751
  zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.featureBlackList];
975
752
  if (platform.deviceFeatureBlackList[device.friendly_name])
976
753
  zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]];
977
- /*
978
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`);
979
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`);
980
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`);
981
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`);
982
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - categories[${categories.length}]: ${debugStringify(categories)}`);
983
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - descriptions[${descriptions.length}]: ${debugStringify(descriptions)}`);
984
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - labels[${labels.length}]: ${debugStringify(labels)}`);
985
- zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - units[${units.length}]: ${debugStringify(units)}`);
986
- */
987
754
  for (const [index, name] of names.entries()) {
988
755
  if (platform.featureBlackList.includes(name) || platform.featureBlackList.includes(properties[index])) {
989
756
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} feature ${name} property ${properties[index]} is globally blacklisted`);
@@ -1012,9 +779,8 @@ export class ZigbeeDevice extends ZigbeeEntity {
1012
779
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${zb}${endpoint}${db} type: ${zb}${type}${db} property: ${zb}${name}${db} => deviceType: ${z2m.deviceType?.name} cluster: ${z2m.cluster} attribute: ${z2m.attribute}`);
1013
780
  zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
1014
781
  if (endpoint === '') {
1015
- /* prettier-ignore */
1016
782
  if (!zigbeeDevice.mutableDevice.has(endpoint)) {
1017
- zigbeeDevice.mutableDevice.set(endpoint, { tagList: [], deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
783
+ zigbeeDevice.mutableDevice.set(endpoint, { tagList: [], deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersOptions: [], clusterClientsIds: [], clusterClientsOptions: [] });
1018
784
  }
1019
785
  else {
1020
786
  zigbeeDevice.mutableDevice.get(endpoint)?.deviceTypes.push(z2m.deviceType);
@@ -1036,9 +802,8 @@ export class ZigbeeDevice extends ZigbeeEntity {
1036
802
  if (endpoint === 'l6')
1037
803
  tagList.push({ mfgCode: null, namespaceId: NumberTag.Six.namespaceId, tag: NumberTag.Six.tag, label: 'endpoint ' + endpoint });
1038
804
  tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'endpoint ' + endpoint });
1039
- /* prettier-ignore */
1040
805
  if (!zigbeeDevice.mutableDevice.has(endpoint)) {
1041
- zigbeeDevice.mutableDevice.set(endpoint, { tagList, deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
806
+ zigbeeDevice.mutableDevice.set(endpoint, { tagList, deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersOptions: [], clusterClientsIds: [], clusterClientsOptions: [] });
1042
807
  }
1043
808
  else {
1044
809
  zigbeeDevice.mutableDevice.get(endpoint)?.deviceTypes.push(z2m.deviceType);
@@ -1050,16 +815,13 @@ export class ZigbeeDevice extends ZigbeeEntity {
1050
815
  }
1051
816
  }
1052
817
  else {
1053
- // zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${zb}${endpoint}${db} type: ${zb}${type}${db} property: ${zb}${name}${db} => no mapping found`);
1054
818
  }
1055
- // Map actions to switches
1056
819
  if (name === 'action' && zigbeeDevice.actions.length) {
1057
820
  zigbeeDevice.log.info(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`);
1058
821
  zigbeeDevice.log.info(' controller events <=> zigbee2mqtt actions');
1059
822
  if (!zigbeeDevice.bridgedDevice)
1060
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
823
+ zigbeeDevice.bridgedDevice = new MatterbridgeEndpoint([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
1061
824
  zigbeeDevice.hasEndpoints = true;
1062
- // Mapping actions
1063
825
  const switchMap = ['Single Press', 'Double Press', 'Long Press '];
1064
826
  const triggerMap = ['Single', 'Double', 'Long'];
1065
827
  let count = 1;
@@ -1072,7 +834,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1072
834
  }
1073
835
  const tagList = [];
1074
836
  tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'switch_' + count });
1075
- zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
837
+ zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersOptions: [], clusterClientsIds: [], clusterClientsOptions: [] });
1076
838
  }
1077
839
  else {
1078
840
  for (let i = 0; i < zigbeeDevice.actions.length; i += 3) {
@@ -1084,7 +846,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1084
846
  }
1085
847
  const tagList = [];
1086
848
  tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'switch_' + count });
1087
- zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
849
+ zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersOptions: [], clusterClientsIds: [], clusterClientsOptions: [] });
1088
850
  count++;
1089
851
  }
1090
852
  }
@@ -1092,19 +854,16 @@ export class ZigbeeDevice extends ZigbeeEntity {
1092
854
  zigbeeDevice.composedType = 'button';
1093
855
  }
1094
856
  }
1095
- // Add battery properties
1096
857
  if (device.power_source === 'Battery') {
1097
858
  zigbeeDevice.propertyMap.set('battery', { name: 'battery', type: '', endpoint: '' });
1098
859
  zigbeeDevice.propertyMap.set('battery_low', { name: 'battery_low', type: '', endpoint: '' });
1099
860
  zigbeeDevice.propertyMap.set('battery_voltage', { name: 'battery_voltage', type: '', endpoint: '' });
1100
861
  }
1101
- // Handle when the device has only child endpoints
1102
862
  if (!zigbeeDevice.mutableDevice.has(''))
1103
- zigbeeDevice.mutableDevice.set('', { tagList: [], deviceTypes: [bridgedNode, powerSource], clusterServersIds: [], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
863
+ zigbeeDevice.mutableDevice.set('', { tagList: [], deviceTypes: [bridgedNode, powerSource], clusterServersIds: [], clusterServersOptions: [], clusterClientsIds: [], clusterClientsOptions: [] });
1104
864
  const mainEndpoint = zigbeeDevice.mutableDevice.get('');
1105
865
  if (!mainEndpoint)
1106
866
  return zigbeeDevice;
1107
- // Remove duplicates and superset device Types on all endpoints
1108
867
  for (const device of zigbeeDevice.mutableDevice.values()) {
1109
868
  const deviceTypesMap = new Map();
1110
869
  device.deviceTypes.forEach((deviceType) => {
@@ -1120,22 +879,28 @@ export class ZigbeeDevice extends ZigbeeEntity {
1120
879
  deviceTypesMap.delete(onOffLight.code);
1121
880
  if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code))
1122
881
  deviceTypesMap.delete(dimmableLight.code);
1123
- device.deviceTypes = Array.from(deviceTypesMap.values()); /* .sort((a, b) => b.code - a.code);*/
882
+ deviceTypesMap.delete(powerSource.code);
883
+ device.deviceTypes = Array.from(deviceTypesMap.values());
1124
884
  }
1125
- // Create the mutable device for the main endpoint
1126
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice(mainEndpoint.deviceTypes, { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1127
- // Configure BridgedDeviceBasicInformation cluster
1128
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.getBridgedDeviceBasicInformation());
1129
- // Configure PowerSource cluster
1130
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.getPowerSource());
1131
- // Configure ColorControlCluster
885
+ zigbeeDevice.bridgedDevice = new MatterbridgeEndpoint([...mainEndpoint.deviceTypes, bridgedNode, powerSource], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
886
+ zigbeeDevice.addBridgedDeviceBasicInformation();
887
+ zigbeeDevice.addPowerSource();
888
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(PowerSource.Cluster.id), 1);
889
+ const options = require(PowerSourceBehavior.with(PowerSource.Feature.Wired), {
890
+ wiredCurrentType: PowerSource.WiredCurrentType.Ac,
891
+ description: 'AC Power',
892
+ status: PowerSource.PowerSourceStatus.Active,
893
+ order: 0,
894
+ endpointList: [],
895
+ });
896
+ mainEndpoint.clusterServersOptions.push(options);
1132
897
  if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
1133
898
  zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
1134
899
  if (!names.includes('color_hs') && !names.includes('color_xy')) {
1135
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getCtColorControlClusterServer());
900
+ zigbeeDevice.bridgedDevice.createCtColorControlClusterServer();
901
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(ColorControl.Cluster.id), 1);
1136
902
  }
1137
903
  }
1138
- // Configure ThermostatCluster: Auto or Heating only or Cooling only. Set also min and max if available
1139
904
  if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
1140
905
  const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('current_heating_setpoint');
1141
906
  const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('current_cooling_setpoint');
@@ -1146,80 +911,65 @@ export class ZigbeeDevice extends ZigbeeEntity {
1146
911
  zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} Thermostat cluster with heating ${CYAN}${heat ? 'supported' : 'not supported'}${db} cooling ${CYAN}${cool ? 'supported' : 'not supported'}${db} ` +
1147
912
  `minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
1148
913
  if (heat && !cool) {
1149
- zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only heating is supported cause it's not supported by the cluster without AutoMode
1150
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating));
914
+ zigbeeDevice.propertyMap.delete('running_state');
915
+ zigbeeDevice.bridgedDevice.createDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating);
916
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
1151
917
  }
1152
918
  else if (!heat && cool) {
1153
- zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only cooling is supported cause it's not supported by the cluster without AutoMode
1154
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling));
919
+ zigbeeDevice.propertyMap.delete('running_state');
920
+ zigbeeDevice.bridgedDevice.createDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling);
921
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
1155
922
  }
1156
923
  else if (heat && cool) {
1157
- mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling));
924
+ zigbeeDevice.bridgedDevice.createDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling);
925
+ mainEndpoint.clusterServersIds.splice(mainEndpoint.clusterServersIds.indexOf(Thermostat.Cluster.id), 1);
1158
926
  }
1159
927
  }
1160
- // Filter out duplicate clusters and clusters objects
1161
928
  for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
1162
- // Filter out duplicate server clusters and server clusters objects. Remove the cluster server id when a cluster server object is present.
1163
- const deviceClusterServersMap = new Map();
929
+ const deviceClusterServersIdMap = new Map();
1164
930
  device.clusterServersIds.forEach((clusterServer) => {
1165
- deviceClusterServersMap.set(clusterServer, clusterServer);
931
+ deviceClusterServersIdMap.set(clusterServer, clusterServer);
1166
932
  });
1167
933
  const deviceClusterServersObjMap = new Map();
1168
- device.clusterServersObjs.forEach((clusterServerObj) => {
1169
- deviceClusterServersMap.delete(clusterServerObj.id);
1170
- deviceClusterServersObjMap.set(clusterServerObj.id, clusterServerObj);
934
+ device.clusterServersOptions.forEach((clusterServer) => {
935
+ deviceClusterServersIdMap.delete(clusterServer.clusterId);
936
+ deviceClusterServersObjMap.set(clusterServer.clusterId, clusterServer);
1171
937
  });
1172
- device.clusterServersIds = Array.from(deviceClusterServersMap.values());
1173
- device.clusterServersObjs = Array.from(deviceClusterServersObjMap.values());
1174
- // Filter out duplicate client clusters and client clusters objects. Remove the cluster client id when a cluster client object is present.
1175
- const deviceClusterClientsMap = new Map();
938
+ device.clusterServersIds = Array.from(deviceClusterServersIdMap.values());
939
+ device.clusterServersOptions = Array.from(deviceClusterServersObjMap.values());
940
+ const deviceClusterClientsIdMap = new Map();
1176
941
  device.clusterClientsIds.forEach((clusterClient) => {
1177
- deviceClusterClientsMap.set(clusterClient, clusterClient);
942
+ deviceClusterClientsIdMap.set(clusterClient, clusterClient);
1178
943
  });
1179
944
  const deviceClusterClientsObjMap = new Map();
1180
- device.clusterClientsObjs.forEach((clusterClientObj) => {
1181
- deviceClusterClientsMap.delete(clusterClientObj.id);
1182
- deviceClusterClientsObjMap.set(clusterClientObj.id, clusterClientObj);
945
+ device.clusterClientsOptions.forEach((clusterClient) => {
946
+ deviceClusterClientsIdMap.delete(clusterClient.clusterId);
947
+ deviceClusterClientsObjMap.set(clusterClient.clusterId, clusterClient);
1183
948
  });
1184
- device.clusterClientsIds = Array.from(deviceClusterClientsMap.values());
1185
- device.clusterClientsObjs = Array.from(deviceClusterClientsObjMap.values());
949
+ device.clusterClientsIds = Array.from(deviceClusterClientsIdMap.values());
950
+ device.clusterClientsOptions = Array.from(deviceClusterClientsObjMap.values());
1186
951
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
1187
952
  `${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
1188
953
  }
1189
- // Add the cluster objects to the main endpoint
1190
- mainEndpoint.clusterServersObjs.forEach((clusterServerObj) => {
1191
- zigbeeDevice.bridgedDevice?.addClusterServer(clusterServerObj);
1192
- });
1193
- // Add the cluster ids to the main endpoint
1194
- zigbeeDevice.bridgedDevice.addClusterServerFromList(zigbeeDevice.bridgedDevice, mainEndpoint.clusterServersIds);
1195
- zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
1196
- // Add the Fixed Label cluster to the main endpoint
954
+ zigbeeDevice.bridgedDevice.addClusterServers(mainEndpoint.clusterServersIds);
955
+ zigbeeDevice.bridgedDevice.addRequiredClusterServers();
1197
956
  if (zigbeeDevice.composedType !== '')
1198
957
  await zigbeeDevice.bridgedDevice.addFixedLabel('composed', zigbeeDevice.composedType);
1199
- // Create the child endpoints
1200
958
  for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
1201
959
  if (endpoint === '')
1202
960
  continue;
1203
- const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1204
- device.clusterServersObjs.forEach((clusterServerObj) => {
1205
- child.addClusterServer(clusterServerObj);
1206
- });
961
+ zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug");
1207
962
  }
1208
- // Verify the device
1209
963
  if (!zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice))
1210
964
  return zigbeeDevice;
1211
- // Clear the mutable device from memory
1212
965
  zigbeeDevice.mutableDevice.clear();
1213
- // Log properties
1214
966
  zigbeeDevice.logPropertyMap();
1215
- // Add command handlers
1216
967
  zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
1217
968
  zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} `);
1218
- // logEndpoint(zigbeeDevice.bridgedDevice!);
1219
969
  });
1220
- if (zigbeeDevice.bridgedDevice.getClusterServerById(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
970
+ if (zigbeeDevice.bridgedDevice.hasClusterServer(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
1221
971
  for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
1222
- if (zigbeeDevice.platform.matterbridge.edge && child.hasClusterServer(OnOffCluster)) {
972
+ if (child.hasClusterServer(OnOffCluster)) {
1223
973
  child.addCommandHandler('on', async (data) => {
1224
974
  zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1225
975
  const payload = {};
@@ -1271,9 +1021,9 @@ export class ZigbeeDevice extends ZigbeeEntity {
1271
1021
  zigbeeDevice.publishCommand('toggle', device.friendly_name, payload);
1272
1022
  });
1273
1023
  }
1274
- if (zigbeeDevice.bridgedDevice.getClusterServerById(LevelControlCluster.id) || zigbeeDevice.hasEndpoints) {
1024
+ if (zigbeeDevice.bridgedDevice.hasClusterServer(LevelControlCluster.id) || zigbeeDevice.hasEndpoints) {
1275
1025
  for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
1276
- if (zigbeeDevice.platform.matterbridge.edge && child.hasClusterServer(LevelControlCluster)) {
1026
+ if (child.hasClusterServer(LevelControlCluster)) {
1277
1027
  child.addCommandHandler('moveToLevel', async (data) => {
1278
1028
  zigbeeDevice.log.debug(`Command moveToLevel called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1279
1029
  const payload = {};
@@ -1317,7 +1067,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1317
1067
  zigbeeDevice.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload);
1318
1068
  });
1319
1069
  }
1320
- if (zigbeeDevice.bridgedDevice.getClusterServerById(ColorControlCluster.id) && zigbeeDevice.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) {
1070
+ if (zigbeeDevice.bridgedDevice.hasAttributeServer(ColorControlCluster.id, 'colorTemperatureMireds')) {
1321
1071
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request }) => {
1322
1072
  zigbeeDevice.log.debug(`Command moveToColorTemperature called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds}`);
1323
1073
  await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, zigbeeDevice.log);
@@ -1327,7 +1077,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1327
1077
  zigbeeDevice.publishCommand('moveToColorTemperature', device.friendly_name, payload);
1328
1078
  });
1329
1079
  }
1330
- if (zigbeeDevice.bridgedDevice.getClusterServerById(ColorControlCluster.id) && zigbeeDevice.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentX')) {
1080
+ if (zigbeeDevice.bridgedDevice.hasAttributeServer(ColorControlCluster.id, 'currentX')) {
1331
1081
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToColor', async ({ request }) => {
1332
1082
  zigbeeDevice.log.debug(`Command moveToColor called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: X: ${request.colorX} Y: ${request.colorY}`);
1333
1083
  await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY, zigbeeDevice.log);
@@ -1337,7 +1087,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1337
1087
  zigbeeDevice.publishCommand('moveToColor', device.friendly_name, payload);
1338
1088
  });
1339
1089
  }
1340
- if (zigbeeDevice.bridgedDevice.getClusterServerById(ColorControlCluster.id) && zigbeeDevice.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentHue')) {
1090
+ if (zigbeeDevice.bridgedDevice.hasAttributeServer(ColorControlCluster.id, 'currentHue')) {
1341
1091
  let lastRequestedHue = 0;
1342
1092
  let lastRequestedSaturation = 0;
1343
1093
  zigbeeDevice.bridgedDevice.addCommandHandler('moveToHue', async ({ request }) => {
@@ -1376,7 +1126,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1376
1126
  zigbeeDevice.publishCommand('moveToHueAndSaturation', device.friendly_name, payload);
1377
1127
  });
1378
1128
  }
1379
- if (zigbeeDevice.bridgedDevice.getClusterServerById(WindowCoveringCluster.id)) {
1129
+ if (zigbeeDevice.bridgedDevice.hasClusterServer(WindowCoveringCluster.id)) {
1380
1130
  zigbeeDevice.bridgedDevice.addCommandHandler('upOrOpen', async () => {
1381
1131
  zigbeeDevice.log.debug(`Command upOrOpen called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1382
1132
  if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
@@ -1407,7 +1157,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1407
1157
  zigbeeDevice.publishCommand('goToLiftPercentage', device.friendly_name, { position: liftPercent100thsValue / 100 });
1408
1158
  });
1409
1159
  }
1410
- if (zigbeeDevice.bridgedDevice.getClusterServerById(DoorLockCluster.id)) {
1160
+ if (zigbeeDevice.bridgedDevice.hasClusterServer(DoorLockCluster.id)) {
1411
1161
  zigbeeDevice.bridgedDevice.addCommandHandler('lockDoor', async ({ request: request }) => {
1412
1162
  zigbeeDevice.log.debug(`Command lockDoor called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`, request);
1413
1163
  await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Locked, zigbeeDevice.log);
@@ -1419,7 +1169,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
1419
1169
  zigbeeDevice.publishCommand('unlockDoor', device.friendly_name, { state: 'UNLOCK' });
1420
1170
  });
1421
1171
  }
1422
- if (zigbeeDevice.bridgedDevice.getClusterServerById(ThermostatCluster.id)) {
1172
+ if (zigbeeDevice.bridgedDevice.hasClusterServer(ThermostatCluster.id)) {
1423
1173
  zigbeeDevice.bridgedDevice.addCommandHandler('setpointRaiseLower', async ({ request: request }) => {
1424
1174
  zigbeeDevice.log.debug(`Command setpointRaiseLower called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request:`, request);
1425
1175
  if (request.mode === Thermostat.SetpointRaiseLowerMode.Heat || request.mode === Thermostat.SetpointRaiseLowerMode.Both) {
@@ -1447,44 +1197,40 @@ export class ZigbeeDevice extends ZigbeeEntity {
1447
1197
  }
1448
1198
  }
1449
1199
  });
1450
- const thermostat = zigbeeDevice.bridgedDevice.getClusterServerById(ThermostatCluster.id);
1451
- if (thermostat) {
1452
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
1453
- zigbeeDevice.log.debug(`Subscribe systemMode called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1454
- const system_mode = value === Thermostat.SystemMode.Off ? 'off' : value === Thermostat.SystemMode.Heat ? 'heat' : 'cool';
1455
- zigbeeDevice.publishCommand('SystemMode', device.friendly_name, { system_mode });
1200
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
1201
+ zigbeeDevice.log.debug(`Subscribe systemMode called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1202
+ const system_mode = value === Thermostat.SystemMode.Off ? 'off' : value === Thermostat.SystemMode.Heat ? 'heat' : 'cool';
1203
+ zigbeeDevice.publishCommand('SystemMode', device.friendly_name, { system_mode });
1204
+ zigbeeDevice.noUpdate = true;
1205
+ zigbeeDevice.thermostatTimeout = setTimeout(() => {
1206
+ zigbeeDevice.noUpdate = false;
1207
+ }, 5 * 1000);
1208
+ }, zigbeeDevice.log);
1209
+ if (zigbeeDevice.bridgedDevice.hasAttributeServer(ThermostatCluster.id, 'occupiedHeatingSetpoint'))
1210
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
1211
+ zigbeeDevice.log.debug(`Subscribe occupiedHeatingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1212
+ if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1213
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
1214
+ else if (zigbeeDevice.propertyMap.has('occupied_heating_setpoint'))
1215
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: Math.round(value / 100) });
1216
+ zigbeeDevice.noUpdate = true;
1217
+ zigbeeDevice.thermostatTimeout = setTimeout(() => {
1218
+ zigbeeDevice.noUpdate = false;
1219
+ }, 5 * 1000);
1220
+ }, zigbeeDevice.log);
1221
+ if (zigbeeDevice.bridgedDevice.hasAttributeServer(ThermostatCluster.id, 'occupiedCoolingSetpoint'))
1222
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
1223
+ zigbeeDevice.log.debug(`Subscribe occupiedCoolingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1224
+ if (zigbeeDevice.propertyMap.has('current_cooling_setpoint'))
1225
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: Math.round(value / 100) });
1226
+ else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint'))
1227
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: Math.round(value / 100) });
1456
1228
  zigbeeDevice.noUpdate = true;
1457
1229
  zigbeeDevice.thermostatTimeout = setTimeout(() => {
1458
1230
  zigbeeDevice.noUpdate = false;
1459
1231
  }, 5 * 1000);
1460
1232
  }, zigbeeDevice.log);
1461
- if (thermostat.attributes.occupiedHeatingSetpoint)
1462
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
1463
- zigbeeDevice.log.debug(`Subscribe occupiedHeatingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1464
- if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1465
- zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
1466
- else if (zigbeeDevice.propertyMap.has('occupied_heating_setpoint'))
1467
- zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: Math.round(value / 100) });
1468
- zigbeeDevice.noUpdate = true;
1469
- zigbeeDevice.thermostatTimeout = setTimeout(() => {
1470
- zigbeeDevice.noUpdate = false;
1471
- }, 5 * 1000);
1472
- }, zigbeeDevice.log);
1473
- if (thermostat.attributes.occupiedCoolingSetpoint)
1474
- zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
1475
- zigbeeDevice.log.debug(`Subscribe occupiedCoolingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1476
- if (zigbeeDevice.propertyMap.has('current_cooling_setpoint'))
1477
- zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: Math.round(value / 100) });
1478
- else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint'))
1479
- zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: Math.round(value / 100) });
1480
- zigbeeDevice.noUpdate = true;
1481
- zigbeeDevice.thermostatTimeout = setTimeout(() => {
1482
- zigbeeDevice.noUpdate = false;
1483
- }, 5 * 1000);
1484
- }, zigbeeDevice.log);
1485
- }
1486
1233
  }
1487
1234
  return zigbeeDevice;
1488
1235
  }
1489
1236
  }
1490
- //# sourceMappingURL=entity.js.map