matterbridge-zigbee2mqtt 2.3.1-dev.2 → 2.3.2

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,9 +1,37 @@
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
+ */
1
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';
2
24
  import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er, YELLOW } from 'matterbridge/logger';
3
25
  import { deepCopy, deepEqual, isValidNumber } from 'matterbridge/utils';
4
26
  import * as color from 'matterbridge/utils';
5
27
  import EventEmitter from 'events';
6
28
  import { hostname } from 'os';
29
+ /**
30
+ * Represents a Zigbee entity: a group or a device.
31
+ *
32
+ * @class
33
+ * @extends {EventEmitter}
34
+ */
7
35
  export class ZigbeeEntity extends EventEmitter {
8
36
  log;
9
37
  serial = '';
@@ -23,6 +51,7 @@ export class ZigbeeEntity extends EventEmitter {
23
51
  ignoreFeatures = [];
24
52
  transition = false;
25
53
  propertyMap = new Map();
54
+ // We save the tag list and device types and cluster servers and clients to avoid multiple lookups
26
55
  mutableDevice = new Map();
27
56
  colorTimeout = undefined;
28
57
  thermostatTimeout = undefined;
@@ -30,6 +59,12 @@ export class ZigbeeEntity extends EventEmitter {
30
59
  hasEndpoints = false;
31
60
  isRouter = false;
32
61
  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
+ */
33
68
  constructor(platform, entity) {
34
69
  super();
35
70
  this.platform = platform;
@@ -47,22 +82,26 @@ export class ZigbeeEntity extends EventEmitter {
47
82
  this.en = gn;
48
83
  this.ien = ign;
49
84
  }
50
- this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4, logLevel: platform.debugEnabled ? "debug" : "info" });
85
+ this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: platform.debugEnabled ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
51
86
  this.log.debug(`Created MatterEntity: ${this.entityName}`);
52
87
  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)
53
89
  const now = Date.now();
54
90
  if (now - this.lastSeen < 1000 * 60 && deepEqual(this.lastPayload, payload, ['linkquality', 'last_seen', ...this.ignoreFeatures]) && !Object.prototype.hasOwnProperty.call(this.lastPayload, 'action')) {
55
91
  this.log.debug(`Skipping not changed ${platform.z2mDevicesRegistered ? 'MQTT message' : 'State update'} for accessory ${this.entityName}`);
56
92
  return;
57
93
  }
58
94
  this.lastSeen = Date.now();
95
+ // Check and deep copy the payload
59
96
  if (deepEqual(this.lastPayload, payload, this.ignoreFeatures))
60
97
  return;
61
98
  this.lastPayload = deepCopy(payload);
62
99
  if (Object.prototype.hasOwnProperty.call(this.lastPayload, 'action'))
63
100
  delete this.lastPayload.action;
101
+ // Remove each key in ignoreFeatures from the payload copy
64
102
  for (const key of this.ignoreFeatures) {
65
103
  if (Object.prototype.hasOwnProperty.call(payload, key)) {
104
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
66
105
  delete payload[key];
67
106
  this.log.debug(`Removed key ${CYAN}${key}${db} from payload`);
68
107
  }
@@ -76,19 +115,24 @@ export class ZigbeeEntity extends EventEmitter {
76
115
  return;
77
116
  }
78
117
  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
79
119
  Object.entries(payload).forEach(([key, value]) => {
120
+ // Skip null and undefined values
80
121
  if (value === undefined || value === null)
81
122
  return;
82
123
  if (this.bridgedDevice === undefined || this.noUpdate)
83
124
  return;
125
+ // Modify voltage to battery_voltage
84
126
  if (key === 'voltage' && this.isDevice && this.device?.power_source === 'Battery')
85
127
  key = 'battery_voltage';
128
+ // Modify illuminance and illuminance_lux
86
129
  if (key === 'illuminance' && this.isDevice && this.device && this.device.definition && ['RTCGQ14LM'].includes(this.device.definition.model)) {
87
130
  key = 'illuminance_lux';
88
131
  }
89
132
  if (key === 'illuminance' && typeof value === 'number' && this.isDevice && this.device && this.device.definition && ['ZG-204ZL', 'ZG-205Z/A'].includes(this.device.definition.model)) {
90
133
  value = value * 10;
91
134
  }
135
+ // Lookup the property in the propertyMap and ZigbeeToMatter table
92
136
  const propertyMap = this.propertyMap.get(key);
93
137
  if (propertyMap) {
94
138
  this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` +
@@ -108,9 +152,11 @@ export class ZigbeeEntity extends EventEmitter {
108
152
  }
109
153
  else
110
154
  this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in propertyMap`);
155
+ // Switch actions on the endpoints
111
156
  if (key === 'action' && value !== '') {
112
157
  const propertyMap = this.propertyMap.get(('action_' + value));
113
158
  if (propertyMap) {
159
+ // this.log.debug(`Payload entry ${CYAN}${value}${db} => name: ${CYAN}${propertyMap.name}${db} endpoint: ${CYAN}${propertyMap.endpoint}${db} action: ${CYAN}${propertyMap.action}${db}`);
114
160
  const child = this.bridgedDevice.getChildEndpointByName(propertyMap.endpoint);
115
161
  if (child && child.number)
116
162
  this.bridgedDevice.triggerSwitchEvent(propertyMap.action, this.log, child);
@@ -118,6 +164,9 @@ export class ZigbeeEntity extends EventEmitter {
118
164
  else
119
165
  this.log.debug(`*Payload entry ${CYAN}${('action_' + value)}${db} not found in propertyMap`);
120
166
  }
167
+ // WindowCovering
168
+ // Zigbee2MQTT cover: 0 = open, 100 = closed
169
+ // Matter WindowCovering: 0 = open 10000 = closed
121
170
  if (key === 'position' && this.isDevice && isValidNumber(value, 0, 100)) {
122
171
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', value * 100);
123
172
  }
@@ -138,19 +187,34 @@ export class ZigbeeEntity extends EventEmitter {
138
187
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position);
139
188
  }
140
189
  }
190
+ // ColorControl colorTemperatureMired and colorMode
141
191
  if (key === 'color_temp' && 'color_mode' in payload && payload['color_mode'] === 'color_temp') {
142
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorTemperatureMireds', Math.max(147, Math.min(500, typeof value === 'number' ? value : 0)));
143
192
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds);
193
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorTemperatureMireds', Math.max(147, Math.min(500, typeof value === 'number' ? value : 0)));
194
+ }
195
+ // ColorControl currentHue, currentSaturation and colorMode
196
+ if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'hs') {
197
+ const { hue, saturation } = value;
198
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
199
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentHue', Math.round((hue / 360) * 254));
200
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentSaturation', Math.round((saturation / 100) * 254));
144
201
  }
202
+ // ColorControl currentX, currentY and colorMode
145
203
  if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'xy') {
204
+ /* not supported by Apple Home so we convert xy to hue and saturation
205
+ const { x, y } = value as { x: number; y: number };
206
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentXAndCurrentY);
207
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentX', Math.max(Math.min(Math.round(x * 65536), 65279), 0));
208
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentY', Math.max(Math.min(Math.round(y * 65536), 65279), 0));
209
+ */
146
210
  const { x, y } = value;
147
211
  const hsl = color.xyToHsl(x, y);
148
212
  const rgb = color.xyColorToRgbColor(x, y);
149
213
  this.log.debug(`ColorControl xyToHsl ${CYAN}${x}${db} ${CYAN}${y}${db} => h ${CYAN}${hsl.h}${db} s ${CYAN}${hsl.s}${db} l ${CYAN}${hsl.l}${db}`);
150
214
  this.log.debug(`ColorControl xyToRgb ${CYAN}${x}${db} ${CYAN}${y}${db} => r ${CYAN}${rgb.r}${db} g ${CYAN}${rgb.g}${db} b ${CYAN}${rgb.b}${db}`);
215
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
151
216
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentHue', Math.round((hsl.h / 360) * 254));
152
217
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'currentSaturation', Math.round((hsl.s / 100) * 254));
153
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
154
218
  }
155
219
  });
156
220
  });
@@ -169,6 +233,13 @@ export class ZigbeeEntity extends EventEmitter {
169
233
  }
170
234
  });
171
235
  }
236
+ /**
237
+ * Destroys the ZigbeeEntity instance by clearing any active timeouts.
238
+ *
239
+ * @remarks
240
+ * This method is used to clean up the ZigbeeEntity instance by clearing any active timeouts for color and thermostat operations.
241
+ * It ensures that no further actions are taken on these timeouts after the entity is destroyed.
242
+ */
172
243
  destroy() {
173
244
  if (this.colorTimeout)
174
245
  clearTimeout(this.colorTimeout);
@@ -177,6 +248,22 @@ export class ZigbeeEntity extends EventEmitter {
177
248
  clearTimeout(this.thermostatTimeout);
178
249
  this.thermostatTimeout = undefined;
179
250
  }
251
+ /**
252
+ * Creates a mutable device with the specified definition and includes the specified server list.
253
+ *
254
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The device type definition.
255
+ * @param {ClusterId[]} [includeServerList=[]] - The list of server clusters to include.
256
+ * @param {EndpointOptions} [options] - Optional endpoint options.
257
+ * @param {boolean} [debug] - Optional debug flag.
258
+ * @returns {MatterbridgeDevice} The created mutable device.
259
+ *
260
+ * @remarks
261
+ * This method creates a mutable device based on the provided definition. It adds the specified server clusters
262
+ * to the device and configures the device with basic information and power source clusters. If the device is a
263
+ * coordinator, it sets up the basic information cluster with coordinator-specific details. If the device is a
264
+ * group, it sets up the basic information cluster with group-specific details. The method also configures the
265
+ * power source cluster based on the device's power source.
266
+ */
180
267
  async createMutableDevice(definition, options, debug) {
181
268
  if (this.platform.matterbridge.edge === true) {
182
269
  this.bridgedDevice = (await MatterbridgeEndpoint.loadInstance(definition, options, debug));
@@ -189,6 +276,7 @@ export class ZigbeeEntity extends EventEmitter {
189
276
  getBridgedDeviceBasicInformation() {
190
277
  if (!this.bridgedDevice)
191
278
  throw new Error('No bridged device');
279
+ // Add BridgedDeviceBasicInformation cluster and device type
192
280
  const softwareVersion = parseInt(this.platform.z2mBridgeInfo?.version || '1');
193
281
  const softwareVersionString = `${this.platform.z2mBridgeInfo?.version} (commit ${this.platform.z2mBridgeInfo?.commit})`;
194
282
  const hardwareVersion = parseInt(this.platform.matterbridge.matterbridgeVersion || '1');
@@ -206,6 +294,7 @@ export class ZigbeeEntity extends EventEmitter {
206
294
  addBridgedDeviceBasicInformation() {
207
295
  if (!this.bridgedDevice)
208
296
  throw new Error('No bridged device');
297
+ // Add BridgedDeviceBasicInformation cluster and device type
209
298
  this.bridgedDevice.addDeviceType(bridgedNode);
210
299
  this.bridgedDevice.addClusterServer(this.getBridgedDeviceBasicInformation());
211
300
  return this.bridgedDevice;
@@ -226,13 +315,26 @@ export class ZigbeeEntity extends EventEmitter {
226
315
  addPowerSource() {
227
316
  if (!this.bridgedDevice)
228
317
  throw new Error('No bridged device');
318
+ // Add PowerSource device type and cluster
229
319
  this.bridgedDevice.addDeviceType(powerSource);
230
320
  this.bridgedDevice.addClusterServer(this.getPowerSource());
231
321
  return this.bridgedDevice;
232
322
  }
323
+ /**
324
+ * Verifies that all required server clusters are present on the main endpoint and child endpoints.
325
+ *
326
+ * @param {MatterbridgeDevice} endpoint - The device endpoint to verify.
327
+ * @returns {boolean} True if all required server clusters are present, false otherwise.
328
+ *
329
+ * @remarks
330
+ * This method checks if all required server clusters are present on the main endpoint and its child endpoints.
331
+ * It logs an error message if any required server cluster is missing and returns false. If all required server
332
+ * clusters are present, it returns true.
333
+ */
233
334
  verifyMutableDevice(endpoint) {
234
335
  if (!endpoint)
235
336
  return false;
337
+ // Verify that all required server clusters are present in the main endpoint and in the child endpoints
236
338
  for (const deviceType of endpoint.getDeviceTypes()) {
237
339
  for (const clusterId of deviceType.requiredServerClusters) {
238
340
  if (!endpoint.getClusterServerById(clusterId)) {
@@ -241,6 +343,7 @@ export class ZigbeeEntity extends EventEmitter {
241
343
  }
242
344
  }
243
345
  }
346
+ // Verify that all required server clusters are present in the child endpoints
244
347
  for (const childEndpoint of endpoint.getChildEndpoints()) {
245
348
  for (const deviceType of childEndpoint.getDeviceTypes()) {
246
349
  for (const clusterId of deviceType.requiredServerClusters) {
@@ -253,6 +356,17 @@ export class ZigbeeEntity extends EventEmitter {
253
356
  }
254
357
  return true;
255
358
  }
359
+ /**
360
+ * Configures the device by setting up the WindowCovering and DoorLock clusters if they are present.
361
+ *
362
+ * @returns {Promise<void>} A promise that resolves when the configuration is complete.
363
+ *
364
+ * @remarks
365
+ * This method configures the device by checking for the presence of the WindowCovering and DoorLock clusters.
366
+ * If the WindowCovering cluster is present, it sets the target as the current position and stops any ongoing
367
+ * movement. If the DoorLock cluster is present, it retrieves the lock state and triggers the appropriate lock
368
+ * operation event based on the current state.
369
+ */
256
370
  async configure() {
257
371
  if (this.bridgedDevice?.getClusterServerById(WindowCovering.Cluster.id)) {
258
372
  this.log.info(`Configuring ${this.bridgedDevice?.deviceName} WindowCovering cluster`);
@@ -269,6 +383,22 @@ export class ZigbeeEntity extends EventEmitter {
269
383
  }
270
384
  }
271
385
  }
386
+ /**
387
+ * Updates the attribute of a cluster on a device endpoint if the value has changed.
388
+ *
389
+ * @param {Endpoint} deviceEndpoint - The device endpoint to update.
390
+ * @param {string | undefined} childEndpointName - The name of the child endpoint, if any.
391
+ * @param {number} clusterId - The ID of the cluster to update.
392
+ * @param {string} attributeName - The name of the attribute to update.
393
+ * @param {PayloadValue} value - The new value of the attribute.
394
+ * @param {string[]} [lookup] - Optional lookup array for converting string values to indices.
395
+ *
396
+ * @remarks
397
+ * This method checks if the specified attribute of a cluster on a device endpoint has changed. If the attribute
398
+ * has changed, it updates the attribute with the new value. If a lookup array is provided, it converts string
399
+ * values to their corresponding indices in the lookup array. The method logs the update process and handles any
400
+ * errors that occur during the update.
401
+ */
272
402
  updateAttributeIfChanged(deviceEndpoint, childEndpointName, clusterId, attributeName, value, lookup) {
273
403
  if (childEndpointName && childEndpointName !== '') {
274
404
  deviceEndpoint = this.bridgedDevice?.getChildEndpointByName(childEndpointName) ?? deviceEndpoint;
@@ -307,6 +437,18 @@ export class ZigbeeEntity extends EventEmitter {
307
437
  this.log.error(`Error setting attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${er}.${hk}${attributeName}${er} to ${value}: ${error}`);
308
438
  }
309
439
  }
440
+ /**
441
+ * Publishes a command to the specified entity with the given payload.
442
+ *
443
+ * @param {string} command - The command to execute.
444
+ * @param {string} entityName - The name of the entity to publish the command to.
445
+ * @param {Payload} payload - The payload of the command.
446
+ *
447
+ * @remarks
448
+ * This method logs the execution of the command and publishes the command to the specified entity.
449
+ * If the entity name starts with 'bridge/request', it publishes the payload without a 'set' suffix.
450
+ * Otherwise, it publishes the payload with a 'set' suffix.
451
+ */
310
452
  publishCommand(command, entityName, payload) {
311
453
  this.log.debug(`executeCommand ${command} called for ${this.ien}${entityName}${rs}${db} payload: ${debugStringify(payload)}`);
312
454
  if (entityName.startsWith('bridge/request')) {
@@ -316,7 +458,16 @@ export class ZigbeeEntity extends EventEmitter {
316
458
  this.platform.publish(entityName, 'set', JSON.stringify(payload));
317
459
  }
318
460
  }
461
+ /**
462
+ * Logs the property map of the Zigbee entity.
463
+ *
464
+ * @remarks
465
+ * This method iterates over the property map of the Zigbee entity and logs each property's details,
466
+ * including its name, type, values, minimum and maximum values, unit, and endpoint.
467
+ */
468
+ // zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
319
469
  logPropertyMap() {
470
+ // Log properties
320
471
  this.propertyMap.forEach((value, key) => {
321
472
  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} ` +
322
473
  `category ${CYAN}${value.category}${db} description ${CYAN}${value.description}${db} label ${CYAN}${value.label}${db} unit ${CYAN}${value.unit}${db} ` +
@@ -324,10 +475,33 @@ export class ZigbeeEntity extends EventEmitter {
324
475
  });
325
476
  }
326
477
  }
478
+ /**
479
+ * Represents a Zigbee group entity.
480
+ *
481
+ * @class
482
+ * @extends {ZigbeeEntity}
483
+ */
327
484
  export class ZigbeeGroup extends ZigbeeEntity {
485
+ /**
486
+ * Creates an instance of ZigbeeGroup.
487
+ *
488
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
489
+ * @param {BridgeGroup} group - The bridge group instance.
490
+ */
328
491
  constructor(platform, group) {
329
492
  super(platform, group);
330
493
  }
494
+ /**
495
+ * Creates a new ZigbeeGroup instance.
496
+ *
497
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
498
+ * @param {BridgeGroup} group - The bridge group instance.
499
+ * @returns {Promise<ZigbeeGroup>} A promise that resolves to the created ZigbeeGroup instance.
500
+ *
501
+ * @remarks
502
+ * This method initializes a new ZigbeeGroup instance, sets up its properties, and configures the device
503
+ * based on the group members. It also adds command handlers for the group.
504
+ */
331
505
  static async create(platform, group) {
332
506
  const zigbeeGroup = new ZigbeeGroup(platform, group);
333
507
  if (zigbeeGroup.platform.postfixHostname) {
@@ -347,19 +521,23 @@ export class ZigbeeGroup extends ZigbeeEntity {
347
521
  let isCover = false;
348
522
  let isThermostat = false;
349
523
  if (group.members.length === 0) {
524
+ // Create a virtual device for the empty group to use in automations
350
525
  zigbeeGroup.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} is a ${CYAN}virtual${db} group`);
351
- zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
526
+ zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
352
527
  isSwitch = true;
353
528
  zigbeeGroup.propertyMap.set('state', { name: 'state', type: 'switch', endpoint: '' });
354
529
  }
355
530
  else {
531
+ // Create a switch or light or outlet device for the group
356
532
  group.members.forEach((member) => {
533
+ // const device = zigbeeGroup.platform.z2m.getDevice(member.ieee_address);
357
534
  const device = zigbeeGroup.platform.z2mBridgeDevices?.find((device) => device.ieee_address === member.ieee_address);
358
535
  if (!device)
359
536
  return;
360
537
  zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${db}: member device ${dn}${device.friendly_name}${db}`);
361
538
  device.definition?.exposes.forEach((expose) => {
362
539
  if (expose.features) {
540
+ // Specific features with type
363
541
  expose.features?.forEach((feature) => {
364
542
  if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock') {
365
543
  expose.type = 'child_lock';
@@ -389,6 +567,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
389
567
  });
390
568
  }
391
569
  else {
570
+ // Generic features without type
392
571
  zigbeeGroup.log.debug(`- generic type ${CYAN}${expose.type}${db} expose name ${CYAN}${expose.name}${db} property ${CYAN}${expose.property}${db}`);
393
572
  }
394
573
  });
@@ -434,14 +613,17 @@ export class ZigbeeGroup extends ZigbeeEntity {
434
613
  }
435
614
  if (!deviceType)
436
615
  return zigbeeGroup;
437
- zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
616
+ zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
438
617
  }
439
618
  zigbeeGroup.addBridgedDeviceBasicInformation();
440
619
  zigbeeGroup.addPowerSource();
441
620
  zigbeeGroup.bridgedDevice.addRequiredClusterServers(zigbeeGroup.bridgedDevice);
621
+ // Verify the device
442
622
  if (!zigbeeGroup.bridgedDevice || !zigbeeGroup.verifyMutableDevice(zigbeeGroup.bridgedDevice))
443
623
  return zigbeeGroup;
624
+ // Log properties
444
625
  zigbeeGroup.logPropertyMap();
626
+ // Add command handlers
445
627
  if (isSwitch || isLight) {
446
628
  if (isSwitch && !isLight)
447
629
  await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'switch');
@@ -449,6 +631,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
449
631
  await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'light');
450
632
  zigbeeGroup.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
451
633
  zigbeeGroup.log.warn(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
634
+ // logEndpoint(zigbeeGroup.bridgedDevice!);
452
635
  });
453
636
  zigbeeGroup.bridgedDevice.addCommandHandler('on', async () => {
454
637
  zigbeeGroup.log.debug(`Command on called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
@@ -540,6 +723,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
540
723
  zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (newValue, oldValue) => {
541
724
  zigbeeGroup.bridgedDevice?.log.info(`Thermostat systemMode changed from ${oldValue} to ${newValue}`);
542
725
  if (oldValue !== newValue) {
726
+ // Thermostat.SystemMode.Heat && newValue === Thermostat.SystemMode.Off
543
727
  zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat systemMode to ${newValue}`);
544
728
  if (newValue === Thermostat.SystemMode.Off) {
545
729
  zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'off' });
@@ -580,6 +764,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
580
764
  return zigbeeGroup;
581
765
  }
582
766
  }
767
+ // prettier-ignore
583
768
  export const z2ms = [
584
769
  { type: 'switch', name: 'state', property: 'state', deviceType: onOffSwitch, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
585
770
  { type: 'switch', name: 'brightness', property: 'brightness', deviceType: dimmableSwitch, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(1, Math.min(254, value)); } },
@@ -639,19 +824,43 @@ export const z2ms = [
639
824
  { type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return value * 1000; } },
640
825
  { type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return value * 1000; } },
641
826
  ];
827
+ /**
828
+ * Represents a Zigbee device entity.
829
+ *
830
+ * @class
831
+ * @extends {ZigbeeEntity}
832
+ */
642
833
  export class ZigbeeDevice extends ZigbeeEntity {
834
+ /**
835
+ * Represents a Zigbee device entity.
836
+ *
837
+ * @class
838
+ * @extends {ZigbeeEntity}
839
+ */
643
840
  constructor(platform, device) {
644
841
  super(platform, device);
645
842
  }
843
+ /**
844
+ * Creates a new ZigbeeDevice instance.
845
+ *
846
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
847
+ * @param {BridgeDevice} device - The bridge device instance.
848
+ * @returns {Promise<ZigbeeDevice>} A promise that resolves to the created ZigbeeDevice instance.
849
+ *
850
+ * @remarks
851
+ * This method initializes a new ZigbeeDevice instance, sets up its properties, and configures the device
852
+ * based on the device definition and options. It also adds command handlers for the device.
853
+ */
646
854
  static async create(platform, device) {
647
855
  const zigbeeDevice = new ZigbeeDevice(platform, device);
648
856
  zigbeeDevice.serial = `${device.ieee_address}`;
649
857
  if (zigbeeDevice.platform.postfixHostname) {
650
858
  zigbeeDevice.serial = `${zigbeeDevice.serial}_${hostname}`.slice(0, 32);
651
859
  }
860
+ // Set Coordinator and dedicated routers
652
861
  if (device.friendly_name === 'Coordinator' || (device.model_id === 'ti.router' && device.manufacturer === 'TexasInstruments') || (device.model_id.startsWith('SLZB-') && device.manufacturer === 'SMLIGHT')) {
653
862
  zigbeeDevice.isRouter = true;
654
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
863
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
655
864
  zigbeeDevice.addBridgedDeviceBasicInformation();
656
865
  zigbeeDevice.addPowerSource();
657
866
  zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
@@ -669,6 +878,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
669
878
  });
670
879
  return zigbeeDevice;
671
880
  }
881
+ // Get types and properties
672
882
  const types = [];
673
883
  const endpoints = [];
674
884
  const names = [];
@@ -682,6 +892,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
682
892
  const values = [];
683
893
  device.definition?.exposes.forEach((expose) => {
684
894
  if (expose.features) {
895
+ // Specific features with type
685
896
  expose.features?.forEach((feature) => {
686
897
  if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock')
687
898
  feature.name = 'child_lock';
@@ -699,10 +910,17 @@ export class ZigbeeDevice extends ZigbeeEntity {
699
910
  });
700
911
  }
701
912
  else {
913
+ // Generic features without type
914
+ // Change voltage to battery_voltage for battery powered devices
702
915
  if (device.power_source === 'Battery' && expose.name === 'voltage')
703
916
  expose.name = 'battery_voltage';
704
917
  if (device.power_source === 'Battery' && expose.property === 'voltage')
705
918
  expose.property = 'battery_voltage';
919
+ // Fix illuminance and illuminance_lux for light sensors:
920
+ // illuminance is raw value (use like it is)
921
+ // illuminance_lux is in lux (convert with log10)
922
+ // illuminance has description "Raw measured illuminance"
923
+ // illuminance_lux has description "Measured illuminance in lux"
706
924
  if (expose.description === 'Raw measured illuminance') {
707
925
  expose.name = 'illuminance';
708
926
  expose.property = 'illuminance';
@@ -763,6 +981,16 @@ export class ZigbeeDevice extends ZigbeeEntity {
763
981
  zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.featureBlackList];
764
982
  if (platform.deviceFeatureBlackList[device.friendly_name])
765
983
  zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]];
984
+ /*
985
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`);
986
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`);
987
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`);
988
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`);
989
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - categories[${categories.length}]: ${debugStringify(categories)}`);
990
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - descriptions[${descriptions.length}]: ${debugStringify(descriptions)}`);
991
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - labels[${labels.length}]: ${debugStringify(labels)}`);
992
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - units[${units.length}]: ${debugStringify(units)}`);
993
+ */
766
994
  for (const [index, name] of names.entries()) {
767
995
  if (platform.featureBlackList.includes(name)) {
768
996
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} feature ${name} is globally blacklisted`);
@@ -791,6 +1019,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
791
1019
  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}`);
792
1020
  zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
793
1021
  if (endpoint === '') {
1022
+ /* prettier-ignore */
794
1023
  if (!zigbeeDevice.mutableDevice.has(endpoint)) {
795
1024
  zigbeeDevice.mutableDevice.set(endpoint, { tagList: [], deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
796
1025
  }
@@ -814,6 +1043,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
814
1043
  if (endpoint === 'l6')
815
1044
  tagList.push({ mfgCode: null, namespaceId: NumberTag.Six.namespaceId, tag: NumberTag.Six.tag, label: 'endpoint ' + endpoint });
816
1045
  tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'endpoint ' + endpoint });
1046
+ /* prettier-ignore */
817
1047
  if (!zigbeeDevice.mutableDevice.has(endpoint)) {
818
1048
  zigbeeDevice.mutableDevice.set(endpoint, { tagList, deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
819
1049
  }
@@ -827,13 +1057,16 @@ export class ZigbeeDevice extends ZigbeeEntity {
827
1057
  }
828
1058
  }
829
1059
  else {
1060
+ // 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`);
830
1061
  }
1062
+ // Map actions to switches
831
1063
  if (name === 'action' && zigbeeDevice.actions.length) {
832
1064
  zigbeeDevice.log.info(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`);
833
1065
  zigbeeDevice.log.info(' controller events <=> zigbee2mqtt actions');
834
1066
  if (!zigbeeDevice.bridgedDevice)
835
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
1067
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
836
1068
  zigbeeDevice.hasEndpoints = true;
1069
+ // Mapping actions
837
1070
  const switchMap = ['Single Press', 'Double Press', 'Long Press '];
838
1071
  const triggerMap = ['Single', 'Double', 'Long'];
839
1072
  let count = 1;
@@ -866,16 +1099,19 @@ export class ZigbeeDevice extends ZigbeeEntity {
866
1099
  zigbeeDevice.composedType = 'button';
867
1100
  }
868
1101
  }
1102
+ // Add battery properties
869
1103
  if (device.power_source === 'Battery') {
870
1104
  zigbeeDevice.propertyMap.set('battery', { name: 'battery', type: '', endpoint: '' });
871
1105
  zigbeeDevice.propertyMap.set('battery_low', { name: 'battery_low', type: '', endpoint: '' });
872
1106
  zigbeeDevice.propertyMap.set('battery_voltage', { name: 'battery_voltage', type: '', endpoint: '' });
873
1107
  }
1108
+ // Handle when the device has only child endpoints
874
1109
  if (!zigbeeDevice.mutableDevice.has(''))
875
1110
  zigbeeDevice.mutableDevice.set('', { tagList: [], deviceTypes: [bridgedNode, powerSource], clusterServersIds: [], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
876
1111
  const mainEndpoint = zigbeeDevice.mutableDevice.get('');
877
1112
  if (!mainEndpoint)
878
1113
  return zigbeeDevice;
1114
+ // Remove duplicates and superset device Types on all endpoints
879
1115
  for (const device of zigbeeDevice.mutableDevice.values()) {
880
1116
  const deviceTypesMap = new Map();
881
1117
  device.deviceTypes.forEach((deviceType) => {
@@ -891,17 +1127,22 @@ export class ZigbeeDevice extends ZigbeeEntity {
891
1127
  deviceTypesMap.delete(onOffLight.code);
892
1128
  if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code))
893
1129
  deviceTypesMap.delete(dimmableLight.code);
894
- device.deviceTypes = Array.from(deviceTypesMap.values());
1130
+ device.deviceTypes = Array.from(deviceTypesMap.values()); /* .sort((a, b) => b.code - a.code);*/
895
1131
  }
896
- zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice(mainEndpoint.deviceTypes, { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
1132
+ // Create the mutable device for the main endpoint
1133
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice(mainEndpoint.deviceTypes, { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1134
+ // Configure BridgedDeviceBasicInformation cluster
897
1135
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.getBridgedDeviceBasicInformation());
1136
+ // Configure PowerSource cluster
898
1137
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.getPowerSource());
1138
+ // Configure ColorControlCluster
899
1139
  if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
900
1140
  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')}`);
901
1141
  if (!names.includes('color_hs') && !names.includes('color_xy')) {
902
1142
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getCtColorControlClusterServer());
903
1143
  }
904
1144
  }
1145
+ // Configure ThermostatCluster: Auto or Heating only or Cooling only. Set also min and max if available
905
1146
  if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
906
1147
  const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('current_heating_setpoint');
907
1148
  const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('current_cooling_setpoint');
@@ -912,18 +1153,20 @@ export class ZigbeeDevice extends ZigbeeEntity {
912
1153
  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} ` +
913
1154
  `minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
914
1155
  if (heat && !cool) {
915
- zigbeeDevice.propertyMap.delete('running_state');
1156
+ zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only heating is supported cause it's not supported by the cluster without AutoMode
916
1157
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating));
917
1158
  }
918
1159
  else if (!heat && cool) {
919
- zigbeeDevice.propertyMap.delete('running_state');
1160
+ zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only cooling is supported cause it's not supported by the cluster without AutoMode
920
1161
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling));
921
1162
  }
922
1163
  else if (heat && cool) {
923
1164
  mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling));
924
1165
  }
925
1166
  }
1167
+ // Filter out duplicate clusters and clusters objects
926
1168
  for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
1169
+ // Filter out duplicate server clusters and server clusters objects. Remove the cluster server id when a cluster server object is present.
927
1170
  const deviceClusterServersMap = new Map();
928
1171
  device.clusterServersIds.forEach((clusterServer) => {
929
1172
  deviceClusterServersMap.set(clusterServer, clusterServer);
@@ -935,6 +1178,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
935
1178
  });
936
1179
  device.clusterServersIds = Array.from(deviceClusterServersMap.values());
937
1180
  device.clusterServersObjs = Array.from(deviceClusterServersObjMap.values());
1181
+ // Filter out duplicate client clusters and client clusters objects. Remove the cluster client id when a cluster client object is present.
938
1182
  const deviceClusterClientsMap = new Map();
939
1183
  device.clusterClientsIds.forEach((clusterClient) => {
940
1184
  deviceClusterClientsMap.set(clusterClient, clusterClient);
@@ -949,27 +1193,36 @@ export class ZigbeeDevice extends ZigbeeEntity {
949
1193
  zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
950
1194
  `${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
951
1195
  }
1196
+ // Add the cluster objects to the main endpoint
952
1197
  mainEndpoint.clusterServersObjs.forEach((clusterServerObj) => {
953
1198
  zigbeeDevice.bridgedDevice?.addClusterServer(clusterServerObj);
954
1199
  });
1200
+ // Add the cluster ids to the main endpoint
955
1201
  zigbeeDevice.bridgedDevice.addClusterServerFromList(zigbeeDevice.bridgedDevice, mainEndpoint.clusterServersIds);
956
1202
  zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
1203
+ // Add the Fixed Label cluster to the main endpoint
957
1204
  if (zigbeeDevice.composedType !== '')
958
1205
  await zigbeeDevice.bridgedDevice.addFixedLabel('composed', zigbeeDevice.composedType);
1206
+ // Create the child endpoints
959
1207
  for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
960
1208
  if (endpoint === '')
961
1209
  continue;
962
- const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug");
1210
+ const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
963
1211
  device.clusterServersObjs.forEach((clusterServerObj) => {
964
1212
  child.addClusterServer(clusterServerObj);
965
1213
  });
966
1214
  }
1215
+ // Verify the device
967
1216
  if (!zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice))
968
1217
  return zigbeeDevice;
1218
+ // Clear the mutable device from memory
969
1219
  zigbeeDevice.mutableDevice.clear();
1220
+ // Log properties
970
1221
  zigbeeDevice.logPropertyMap();
1222
+ // Add command handlers
971
1223
  zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
972
1224
  zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} `);
1225
+ // logEndpoint(zigbeeDevice.bridgedDevice!);
973
1226
  });
974
1227
  if (zigbeeDevice.bridgedDevice.getClusterServerById(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
975
1228
  for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
@@ -1241,3 +1494,4 @@ export class ZigbeeDevice extends ZigbeeEntity {
1241
1494
  return zigbeeDevice;
1242
1495
  }
1243
1496
  }
1497
+ //# sourceMappingURL=entity.js.map