matterbridge-zigbee2mqtt 2.2.2 → 2.3.0-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
@@ -20,12 +20,18 @@
20
20
  * See the License for the specific language governing permissions and
21
21
  * limitations under the License. *
22
22
  */
23
- import { DeviceTypes, MatterbridgeDevice, airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, Identify, 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, } from 'matterbridge';
24
- import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er } from 'matterbridge/logger';
25
- import { deepCopy, deepEqual } from 'matterbridge/utils';
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';
24
+ import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er, YELLOW } from 'matterbridge/logger';
25
+ import { deepCopy, deepEqual, isValidNumber } from 'matterbridge/utils';
26
26
  import * as color from 'matterbridge/utils';
27
27
  import EventEmitter from 'events';
28
28
  import { hostname } from 'os';
29
+ /**
30
+ * Represents a Zigbee entity: a group or a device.
31
+ *
32
+ * @class
33
+ * @extends {EventEmitter}
34
+ */
29
35
  export class ZigbeeEntity extends EventEmitter {
30
36
  log;
31
37
  serial = '';
@@ -38,7 +44,6 @@ export class ZigbeeEntity extends EventEmitter {
38
44
  actions = [];
39
45
  en = '';
40
46
  ien = '';
41
- // public bridgedDevice: BridgedBaseDevice | undefined;
42
47
  bridgedDevice;
43
48
  eidn = `${or}`;
44
49
  lastPayload = {};
@@ -46,95 +51,20 @@ export class ZigbeeEntity extends EventEmitter {
46
51
  ignoreFeatures = [];
47
52
  transition = false;
48
53
  propertyMap = new Map();
54
+ // We save the tag list and device types and cluster servers and clients to avoid multiple lookups
55
+ mutableDevice = new Map();
49
56
  colorTimeout = undefined;
50
57
  thermostatTimeout = undefined;
58
+ composedType = '';
51
59
  hasEndpoints = false;
52
60
  isRouter = false;
53
61
  noUpdate = false;
54
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
55
- createDevice(definition, includeServerList = [], options, debug) {
56
- this.bridgedDevice = new MatterbridgeDevice(definition, undefined, this.log.logLevel === "debug" /* LogLevel.DEBUG */);
57
- this.bridgedDevice.addClusterServerFromList(this.bridgedDevice, includeServerList);
58
- // Add bridgedNode device type and BridgedDeviceBasicInformation cluster
59
- this.bridgedDevice.addDeviceType(bridgedNode);
60
- if (this.isDevice && this.device && this.device.friendly_name === 'Coordinator') {
61
- this.bridgedDevice.createDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Coordinator');
62
- }
63
- else if (this.isDevice && this.device) {
64
- 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);
65
- }
66
- else if (this.isGroup && this.group) {
67
- this.bridgedDevice.createDefaultBridgedDeviceBasicInformationClusterServer(this.group.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Group');
68
- }
69
- // Add powerSource device type and PowerSource cluster
70
- this.bridgedDevice.addDeviceType(powerSource);
71
- if (this.isDevice) {
72
- if (this.device?.power_source === 'Battery')
73
- this.bridgedDevice.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok);
74
- else
75
- this.bridgedDevice.createDefaultPowerSourceWiredClusterServer();
76
- }
77
- else if (this.isGroup) {
78
- this.bridgedDevice.createDefaultPowerSourceWiredClusterServer();
79
- }
80
- return this.bridgedDevice;
81
- }
82
- configure() {
83
- if (this.bridgedDevice?.getClusterServerById(WindowCovering.Cluster.id)) {
84
- this.log.info(`Configuring ${this.bridgedDevice?.deviceName} WindowCovering cluster`);
85
- this.bridgedDevice?.setWindowCoveringTargetAsCurrentAndStopped();
86
- }
87
- if (this.bridgedDevice?.getClusterServerById(DoorLock.Cluster.id)) {
88
- this.log.info(`Configuring ${this.bridgedDevice?.deviceName} DoorLock cluster`);
89
- // const state = this.bridgedDevice?.getClusterServerById(DoorLock.Cluster.id)?.getLockStateAttribute();
90
- const state = this.bridgedDevice?.getAttribute(DoorLock.Cluster.id, 'lockState', this.log);
91
- if (this.bridgedDevice.number) {
92
- if (state === DoorLock.LockState.Locked)
93
- this.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Lock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
94
- if (state === DoorLock.LockState.Unlocked)
95
- this.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Unlock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
96
- // if (state === DoorLock.LockState.Locked) this.bridgedDevice?.getClusterServer(DoorLockCluster)?.triggerLockOperationEvent({ lockOperationType: DoorLock.LockOperationType.Lock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null });
97
- // if (state === DoorLock.LockState.Unlocked) this.bridgedDevice?.getClusterServer(DoorLockCluster)?.triggerLockOperationEvent({ lockOperationType: DoorLock.LockOperationType.Unlock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null });
98
- }
99
- }
100
- }
101
62
  /**
102
- * Retrieves the value of the specified attribute from the given endpoint and cluster.
63
+ * Creates an instance of ZigbeeEntity.
103
64
  *
104
- * @param {ClusterId} clusterId - The ID of the cluster to retrieve the attribute from.
105
- * @param {string} event - The name of the event to trigger.
106
- * @param {Record<string, any>} payload - The payload of the event to trigger.
107
- * @param {AnsiLogger} [log] - Optional logger for error and info messages.
108
- * @param {Endpoint} [endpoint] - Optional the child endpoint to retrieve the attribute from.
65
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
66
+ * @param {BridgeDevice | BridgeGroup} entity - The bridge device or group instance received from zigbee2mqtt.
109
67
  */
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- triggerEvent(clusterId, event, payload, log, endpoint) {
112
- if (!endpoint)
113
- endpoint = this.bridgedDevice;
114
- if (!endpoint.number)
115
- return;
116
- const clusterServer = endpoint.getClusterServerById(clusterId);
117
- if (!clusterServer) {
118
- log?.error(`triggerEvent error: Cluster ${clusterId} not found on endpoint ${endpoint.name}:${endpoint.number}`);
119
- return;
120
- }
121
- const capitalizedEventName = event.charAt(0).toUpperCase() + event.slice(1);
122
- if (!clusterServer.isEventSupportedByName(event) && !clusterServer.isEventSupportedByName(capitalizedEventName)) {
123
- if (log)
124
- log.error(`triggerEvent error: Attribute ${event} not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
125
- return;
126
- }
127
- // Find the getter method
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
- if (!clusterServer[`trigger${capitalizedEventName}Event`]) {
130
- log?.error(`triggerEvent error: Trigger trigger${capitalizedEventName}Event not found on Cluster ${clusterServer.name} on endpoint ${endpoint.name}:${endpoint.number}`);
131
- return;
132
- }
133
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
134
- const trigger = clusterServer[`trigger${capitalizedEventName}Event`];
135
- trigger(payload);
136
- log?.info(`${db}Trigger event ${hk}${clusterServer.name}.${capitalizedEventName}${db} on endpoint ${or}${endpoint.name}:${endpoint.number}${db}`);
137
- }
138
68
  constructor(platform, entity) {
139
69
  super();
140
70
  this.platform = platform;
@@ -196,37 +126,32 @@ export class ZigbeeEntity extends EventEmitter {
196
126
  if (key === 'voltage' && this.isDevice && this.device?.power_source === 'Battery')
197
127
  key = 'battery_voltage';
198
128
  // Modify illuminance and illuminance_lux
199
- if (key === 'illuminance' && this.isDevice && this.device?.definition?.model === 'ZG-204ZL') {
129
+ if (key === 'illuminance' && this.isDevice && this.device && this.device.definition && ['RTCGQ14LM'].includes(this.device.definition.model)) {
200
130
  key = 'illuminance_lux';
201
- value = Math.pow(10, typeof value === 'number' ? value / 10000 : 0);
202
131
  }
203
- if (key === 'illuminance' && this.isDevice && this.device?.definition?.model === 'RTCGQ14LM') {
204
- key = 'illuminance_lux';
205
- }
206
- if (key === 'illuminance' && this.isDevice && this.device?.definition?.model === 'USM-300ZB') {
207
- key = 'illuminance_lux';
208
- }
209
- if (key === 'illuminance' && !('illuminance_lux' in payload)) {
210
- key = 'illuminance_lux';
132
+ if (key === 'illuminance' && typeof value === 'number' && this.isDevice && this.device && this.device.definition && ['ZG-204ZL', 'ZG-205Z/A'].includes(this.device.definition.model)) {
133
+ value = value * 10;
211
134
  }
212
135
  // Lookup the property in the propertyMap and ZigbeeToMatter table
213
136
  const propertyMap = this.propertyMap.get(key);
214
- if (propertyMap)
215
- this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` + `endpoint: ${CYAN}${propertyMap.endpoint === '' ? 'main' : propertyMap.endpoint}${db}`);
216
- else
217
- this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in propertyMap`);
218
- let z2m;
219
- z2m = z2ms.find((z2m) => z2m.type === propertyMap?.type && z2m.property === propertyMap?.name);
220
- if (!z2m)
221
- z2m = z2ms.find((z2m) => z2m.property === propertyMap?.name);
222
- if (z2m) {
223
- if (z2m.converter || z2m.valueLookup) {
224
- this.updateAttributeIfChanged(this.bridgedDevice, propertyMap === undefined || propertyMap.endpoint === '' ? undefined : propertyMap.endpoint, z2m.cluster, z2m.attribute, z2m.converter ? z2m.converter(value) : value, z2m.valueLookup);
225
- return;
137
+ if (propertyMap) {
138
+ this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` +
139
+ `endpoint: ${CYAN}${propertyMap.endpoint === '' ? 'main' : propertyMap.endpoint}${db}`);
140
+ let z2m;
141
+ z2m = z2ms.find((z2m) => z2m.type === propertyMap?.type && z2m.property === propertyMap?.name);
142
+ if (!z2m)
143
+ z2m = z2ms.find((z2m) => z2m.property === propertyMap?.name);
144
+ if (z2m) {
145
+ if (z2m.converter || z2m.valueLookup) {
146
+ this.updateAttributeIfChanged(this.bridgedDevice, propertyMap === undefined || propertyMap.endpoint === '' ? undefined : propertyMap.endpoint, z2m.cluster, z2m.attribute, z2m.converter ? z2m.converter(value) : value, z2m.valueLookup);
147
+ return;
148
+ }
226
149
  }
150
+ else
151
+ this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in zigbeeToMatter converter`);
227
152
  }
228
153
  else
229
- this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in zigbeeToMatter converter table`);
154
+ this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in propertyMap`);
230
155
  // Switch actions on the endpoints
231
156
  if (key === 'action' && value !== '') {
232
157
  const propertyMap = this.propertyMap.get(('action_' + value));
@@ -240,25 +165,24 @@ export class ZigbeeEntity extends EventEmitter {
240
165
  this.log.debug(`*Payload entry ${CYAN}${('action_' + value)}${db} not found in propertyMap`);
241
166
  }
242
167
  // WindowCovering
243
- if (key === 'state' && this.isGroup && this.bridgedDevice.hasClusterServer(WindowCovering.Complete)) {
244
- if (value === 'OPEN') {
245
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', 0);
246
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', 0);
168
+ // Zigbee2MQTT cover: 0 = open, 100 = closed
169
+ // Matter WindowCovering: 0 = open 10000 = closed
170
+ if (key === 'position' && this.isDevice && isValidNumber(value, 0, 100)) {
171
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', value * 100);
172
+ }
173
+ if (key === 'moving' && this.isDevice) {
174
+ if (value === 'UP') {
175
+ const status = WindowCovering.MovementStatus.Opening;
176
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'operationalStatus', { global: status, lift: status, tilt: status });
247
177
  }
248
- if (value === 'CLOSE') {
249
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', 10000);
250
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', 10000);
178
+ else if (value === 'DOWN') {
179
+ const status = WindowCovering.MovementStatus.Closing;
180
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'operationalStatus', { global: status, lift: status, tilt: status });
251
181
  }
252
- }
253
- if (key === 'position') {
254
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', typeof value === 'number' ? 10000 - value * 100 : 0);
255
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', typeof value === 'number' ? 10000 - value * 100 : 0);
256
- }
257
- if (key === 'moving') {
258
- const status = value === 'UP' ? WindowCovering.MovementStatus.Opening : value === 'DOWN' ? WindowCovering.MovementStatus.Closing : WindowCovering.MovementStatus.Stopped;
259
- this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'operationalStatus', { global: status, lift: status, tilt: status });
260
- if (value === 'STOP') {
261
- const position = this.bridgedDevice.getClusterServerById(WindowCovering.Cluster.id)?.getCurrentPositionLiftPercent100thsAttribute();
182
+ else if (value === 'STOP') {
183
+ const status = WindowCovering.MovementStatus.Stopped;
184
+ this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'operationalStatus', { global: status, lift: status, tilt: status });
185
+ const position = this.bridgedDevice.getAttribute(WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', this.log);
262
186
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', position);
263
187
  this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position);
264
188
  }
@@ -283,25 +207,26 @@ export class ZigbeeEntity extends EventEmitter {
283
207
  });
284
208
  this.platform.z2m.on('ONLINE-' + this.entityName, () => {
285
209
  this.log.info(`ONLINE message for device ${this.ien}${this.entityName}${rs}`);
286
- if (this.bridgedDevice?.number !== undefined) {
287
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.setReachableAttribute(true);
288
- if (this.bridgedDevice.number)
289
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: true });
290
- this.log.info(`${db}Set accessory attribute ${hk}BridgedDeviceBasicInformation.reachable: true`);
291
- this.log.info(`${db}Trigger accessory event ${hk}ReachableChangedEvent: true`);
210
+ if (this.bridgedDevice?.maybeNumber !== undefined) {
211
+ this.bridgedDevice?.setAttribute(BridgedDeviceBasicInformation.Cluster.id, 'reachable', true, this.log);
212
+ this.bridgedDevice?.triggerEvent(BridgedDeviceBasicInformation.Cluster.id, 'reachableChanged', { reachableNewValue: true }, this.log);
292
213
  }
293
214
  });
294
215
  this.platform.z2m.on('OFFLINE-' + this.entityName, () => {
295
216
  this.log.warn(`OFFLINE message for device ${this.ien}${this.entityName}${rs}`);
296
- if (this.bridgedDevice?.number !== undefined) {
297
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.setReachableAttribute(false);
298
- if (this.bridgedDevice.number)
299
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.triggerReachableChangedEvent({ reachableNewValue: false });
300
- this.log.info(`${db}Set accessory attribute ${hk}BridgedDeviceBasicInformation.reachable: false`);
301
- this.log.info(`${db}Trigger accessory event ${hk}ReachableChangedEvent: false`);
217
+ if (this.bridgedDevice?.maybeNumber !== undefined) {
218
+ this.bridgedDevice?.setAttribute(BridgedDeviceBasicInformation.Cluster.id, 'reachable', false, this.log);
219
+ this.bridgedDevice?.triggerEvent(BridgedDeviceBasicInformation.Cluster.id, 'reachableChanged', { reachableNewValue: false }, this.log);
302
220
  }
303
221
  });
304
222
  }
223
+ /**
224
+ * Destroys the ZigbeeEntity instance by clearing any active timeouts.
225
+ *
226
+ * @remarks
227
+ * This method is used to clean up the ZigbeeEntity instance by clearing any active timeouts for color and thermostat operations.
228
+ * It ensures that no further actions are taken on these timeouts after the entity is destroyed.
229
+ */
305
230
  destroy() {
306
231
  if (this.colorTimeout)
307
232
  clearTimeout(this.colorTimeout);
@@ -310,17 +235,168 @@ export class ZigbeeEntity extends EventEmitter {
310
235
  clearTimeout(this.thermostatTimeout);
311
236
  this.thermostatTimeout = undefined;
312
237
  }
238
+ /**
239
+ * Creates a mutable device with the specified definition and includes the specified server list.
240
+ *
241
+ * @param {DeviceTypeDefinition | AtLeastOne<DeviceTypeDefinition>} definition - The device type definition.
242
+ * @param {ClusterId[]} [includeServerList=[]] - The list of server clusters to include.
243
+ * @param {EndpointOptions} [options] - Optional endpoint options.
244
+ * @param {boolean} [debug] - Optional debug flag.
245
+ * @returns {MatterbridgeDevice} The created mutable device.
246
+ *
247
+ * @remarks
248
+ * This method creates a mutable device based on the provided definition. It adds the specified server clusters
249
+ * to the device and configures the device with basic information and power source clusters. If the device is a
250
+ * coordinator, it sets up the basic information cluster with coordinator-specific details. If the device is a
251
+ * group, it sets up the basic information cluster with group-specific details. The method also configures the
252
+ * power source cluster based on the device's power source.
253
+ */
254
+ async createMutableDevice(definition, options, debug) {
255
+ if (this.platform.matterbridge.edge === true) {
256
+ this.bridgedDevice = (await MatterbridgeEndpoint.loadInstance(definition, options, debug));
257
+ }
258
+ else {
259
+ this.bridgedDevice = await MatterbridgeDevice.loadInstance(definition, undefined, debug);
260
+ }
261
+ return this.bridgedDevice;
262
+ }
263
+ getBridgedDeviceBasicInformation() {
264
+ if (!this.bridgedDevice)
265
+ throw new Error('No bridged device');
266
+ // Add BridgedDeviceBasicInformation cluster and device type
267
+ const softwareVersion = parseInt(this.platform.z2mBridgeInfo?.version || '1');
268
+ const softwareVersionString = `${this.platform.z2mBridgeInfo?.version} (commit ${this.platform.z2mBridgeInfo?.commit})`;
269
+ const hardwareVersion = parseInt(this.platform.matterbridge.matterbridgeVersion || '1');
270
+ const hardwareVersionString = this.platform.matterbridge.matterbridgeVersion || 'unknown';
271
+ if (this.isDevice && this.device && this.device.friendly_name === 'Coordinator') {
272
+ return this.bridgedDevice.getDefaultBridgedDeviceBasicInformationClusterServer(this.device.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Coordinator', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
273
+ }
274
+ else if (this.isDevice && this.device) {
275
+ 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);
276
+ }
277
+ if (!this.group)
278
+ throw new Error('No group found');
279
+ return this.bridgedDevice.getDefaultBridgedDeviceBasicInformationClusterServer(this.group.friendly_name, this.serial, 0xfff1, 'zigbee2MQTT', 'Group', softwareVersion, softwareVersionString, hardwareVersion, hardwareVersionString);
280
+ }
281
+ addBridgedDeviceBasicInformation() {
282
+ if (!this.bridgedDevice)
283
+ throw new Error('No bridged device');
284
+ // Add BridgedDeviceBasicInformation cluster and device type
285
+ this.bridgedDevice.addDeviceType(bridgedNode);
286
+ this.bridgedDevice.addClusterServer(this.getBridgedDeviceBasicInformation());
287
+ return this.bridgedDevice;
288
+ }
289
+ getPowerSource() {
290
+ if (!this.bridgedDevice)
291
+ throw new Error('No bridged device');
292
+ if (this.isDevice) {
293
+ if (this.device?.power_source === 'Battery') {
294
+ return this.bridgedDevice.getDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok);
295
+ }
296
+ else {
297
+ return this.bridgedDevice?.getDefaultPowerSourceWiredClusterServer();
298
+ }
299
+ }
300
+ return this.bridgedDevice?.getDefaultPowerSourceWiredClusterServer();
301
+ }
302
+ addPowerSource() {
303
+ if (!this.bridgedDevice)
304
+ throw new Error('No bridged device');
305
+ // Add PowerSource device type and cluster
306
+ this.bridgedDevice.addDeviceType(powerSource);
307
+ this.bridgedDevice.addClusterServer(this.getPowerSource());
308
+ return this.bridgedDevice;
309
+ }
310
+ /**
311
+ * Verifies that all required server clusters are present on the main endpoint and child endpoints.
312
+ *
313
+ * @param {MatterbridgeDevice} endpoint - The device endpoint to verify.
314
+ * @returns {boolean} True if all required server clusters are present, false otherwise.
315
+ *
316
+ * @remarks
317
+ * This method checks if all required server clusters are present on the main endpoint and its child endpoints.
318
+ * It logs an error message if any required server cluster is missing and returns false. If all required server
319
+ * clusters are present, it returns true.
320
+ */
321
+ verifyMutableDevice(endpoint) {
322
+ if (!endpoint)
323
+ return false;
324
+ // Verify that all required server clusters are present in the main endpoint and in the child endpoints
325
+ for (const deviceType of endpoint.getDeviceTypes()) {
326
+ for (const clusterId of deviceType.requiredServerClusters) {
327
+ if (!endpoint.getClusterServerById(clusterId)) {
328
+ endpoint.addClusterServerFromList(endpoint, [clusterId]);
329
+ 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`);
330
+ }
331
+ }
332
+ }
333
+ // Verify that all required server clusters are present in the child endpoints
334
+ for (const childEndpoint of endpoint.getChildEndpoints()) {
335
+ for (const deviceType of childEndpoint.getDeviceTypes()) {
336
+ for (const clusterId of deviceType.requiredServerClusters) {
337
+ if (!childEndpoint.getClusterServerById(clusterId)) {
338
+ endpoint.addClusterServerFromList(childEndpoint, [clusterId]);
339
+ 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`);
340
+ }
341
+ }
342
+ }
343
+ }
344
+ return true;
345
+ }
346
+ /**
347
+ * Configures the device by setting up the WindowCovering and DoorLock clusters if they are present.
348
+ *
349
+ * @returns {Promise<void>} A promise that resolves when the configuration is complete.
350
+ *
351
+ * @remarks
352
+ * This method configures the device by checking for the presence of the WindowCovering and DoorLock clusters.
353
+ * If the WindowCovering cluster is present, it sets the target as the current position and stops any ongoing
354
+ * movement. If the DoorLock cluster is present, it retrieves the lock state and triggers the appropriate lock
355
+ * operation event based on the current state.
356
+ */
357
+ async configure() {
358
+ if (this.bridgedDevice?.getClusterServerById(WindowCovering.Cluster.id)) {
359
+ this.log.info(`Configuring ${this.bridgedDevice?.deviceName} WindowCovering cluster`);
360
+ await this.bridgedDevice?.setWindowCoveringTargetAsCurrentAndStopped();
361
+ }
362
+ if (this.bridgedDevice?.getClusterServerById(DoorLock.Cluster.id)) {
363
+ this.log.info(`Configuring ${this.bridgedDevice?.deviceName} DoorLock cluster`);
364
+ const state = this.bridgedDevice?.getAttribute(DoorLock.Cluster.id, 'lockState', this.log);
365
+ if (this.bridgedDevice.maybeNumber) {
366
+ if (state === DoorLock.LockState.Locked)
367
+ this.bridgedDevice?.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Lock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
368
+ if (state === DoorLock.LockState.Unlocked)
369
+ this.bridgedDevice?.triggerEvent(DoorLock.Cluster.id, 'lockOperation', { lockOperationType: DoorLock.LockOperationType.Unlock, operationSource: DoorLock.OperationSource.Manual, userIndex: null, fabricIndex: null, sourceNode: null }, this.log);
370
+ }
371
+ }
372
+ }
373
+ /**
374
+ * Updates the attribute of a cluster on a device endpoint if the value has changed.
375
+ *
376
+ * @param {Endpoint} deviceEndpoint - The device endpoint to update.
377
+ * @param {string | undefined} childEndpointName - The name of the child endpoint, if any.
378
+ * @param {number} clusterId - The ID of the cluster to update.
379
+ * @param {string} attributeName - The name of the attribute to update.
380
+ * @param {PayloadValue} value - The new value of the attribute.
381
+ * @param {string[]} [lookup] - Optional lookup array for converting string values to indices.
382
+ *
383
+ * @remarks
384
+ * This method checks if the specified attribute of a cluster on a device endpoint has changed. If the attribute
385
+ * has changed, it updates the attribute with the new value. If a lookup array is provided, it converts string
386
+ * values to their corresponding indices in the lookup array. The method logs the update process and handles any
387
+ * errors that occur during the update.
388
+ */
313
389
  updateAttributeIfChanged(deviceEndpoint, childEndpointName, clusterId, attributeName, value, lookup) {
314
390
  if (childEndpointName && childEndpointName !== '') {
315
391
  deviceEndpoint = this.bridgedDevice?.getChildEndpointByName(childEndpointName) ?? deviceEndpoint;
316
392
  }
317
393
  const cluster = deviceEndpoint.getClusterServerById(ClusterId(clusterId));
318
394
  if (cluster === undefined) {
319
- 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?`);
395
+ 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?`);
320
396
  return;
321
397
  }
322
398
  if (!cluster.isAttributeSupportedByName(attributeName)) {
323
- 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`);
399
+ 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`);
324
400
  return;
325
401
  }
326
402
  if (lookup !== undefined) {
@@ -328,26 +404,38 @@ export class ZigbeeEntity extends EventEmitter {
328
404
  value = lookup.indexOf(value);
329
405
  }
330
406
  else {
331
- this.log.debug(`***Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
407
+ this.log.debug(`Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
332
408
  `attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} value ${zb}${typeof value === 'object' ? debugStringify(value) : value}${db} not found in lookup ${debugStringify(lookup)}`);
333
409
  return;
334
410
  }
335
411
  }
336
- const localValue = cluster.attributes[attributeName].getLocal();
412
+ const localValue = this.bridgedDevice?.getAttribute(ClusterId(clusterId), attributeName, undefined, deviceEndpoint);
337
413
  if (typeof value === 'object' ? deepEqual(value, localValue) : value === localValue) {
338
- this.log.debug(`*Skip update endpoint ${this.eidn}${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
339
- `attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} already ${zb}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
414
+ this.log.debug(`Skip update endpoint ${deviceEndpoint.name}:${deviceEndpoint.number}${childEndpointName ? ' (' + childEndpointName + ')' : ''} ` +
415
+ `attribute ${getClusterNameById(ClusterId(clusterId))}.${attributeName} already ${typeof value === 'object' ? debugStringify(value) : value}`);
340
416
  return;
341
417
  }
342
418
  this.log.info(`${db}Update endpoint ${this.eidn}${deviceEndpoint.name}:${deviceEndpoint.number}${db}${childEndpointName ? ' (' + zb + childEndpointName + db + ')' : ''} ` +
343
- `attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${db}.${hk}${attributeName}${db} from ${zb}${typeof localValue === 'object' ? debugStringify(localValue) : localValue}${db} to ${zb}${typeof value === 'object' ? debugStringify(value) : value}${db}`);
419
+ `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}`);
344
420
  try {
345
- cluster.attributes[attributeName].setLocal(value);
421
+ this.bridgedDevice?.setAttribute(ClusterId(clusterId), attributeName, value, undefined, deviceEndpoint);
346
422
  }
347
423
  catch (error) {
348
424
  this.log.error(`Error setting attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${er}.${hk}${attributeName}${er} to ${value}: ${error}`);
349
425
  }
350
426
  }
427
+ /**
428
+ * Publishes a command to the specified entity with the given payload.
429
+ *
430
+ * @param {string} command - The command to execute.
431
+ * @param {string} entityName - The name of the entity to publish the command to.
432
+ * @param {Payload} payload - The payload of the command.
433
+ *
434
+ * @remarks
435
+ * This method logs the execution of the command and publishes the command to the specified entity.
436
+ * If the entity name starts with 'bridge/request', it publishes the payload without a 'set' suffix.
437
+ * Otherwise, it publishes the payload with a 'set' suffix.
438
+ */
351
439
  publishCommand(command, entityName, payload) {
352
440
  this.log.debug(`executeCommand ${command} called for ${this.ien}${entityName}${rs}${db} payload: ${debugStringify(payload)}`);
353
441
  if (entityName.startsWith('bridge/request')) {
@@ -357,15 +445,57 @@ export class ZigbeeEntity extends EventEmitter {
357
445
  this.platform.publish(entityName, 'set', JSON.stringify(payload));
358
446
  }
359
447
  }
448
+ /**
449
+ * Logs the property map of the Zigbee entity.
450
+ *
451
+ * @remarks
452
+ * This method iterates over the property map of the Zigbee entity and logs each property's details,
453
+ * including its name, type, values, minimum and maximum values, unit, and endpoint.
454
+ */
455
+ // zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
456
+ logPropertyMap() {
457
+ // Log properties
458
+ this.propertyMap.forEach((value, key) => {
459
+ 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} ` +
460
+ `category ${CYAN}${value.category}${db} description ${CYAN}${value.description}${db} label ${CYAN}${value.label}${db} unit ${CYAN}${value.unit}${db} ` +
461
+ `values ${CYAN}${value.values}${db} value_min ${CYAN}${value.value_min}${db} value_max ${CYAN}${value.value_max}${db}`);
462
+ });
463
+ }
360
464
  }
465
+ /**
466
+ * Represents a Zigbee group entity.
467
+ *
468
+ * @class
469
+ * @extends {ZigbeeEntity}
470
+ */
361
471
  export class ZigbeeGroup extends ZigbeeEntity {
472
+ /**
473
+ * Creates an instance of ZigbeeGroup.
474
+ *
475
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
476
+ * @param {BridgeGroup} group - The bridge group instance.
477
+ */
362
478
  constructor(platform, group) {
363
479
  super(platform, group);
364
- if (this.platform.postfixHostname) {
365
- this.serial = `group-${group.id}_${hostname}`.slice(0, 32);
480
+ }
481
+ /**
482
+ * Creates a new ZigbeeGroup instance.
483
+ *
484
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
485
+ * @param {BridgeGroup} group - The bridge group instance.
486
+ * @returns {Promise<ZigbeeGroup>} A promise that resolves to the created ZigbeeGroup instance.
487
+ *
488
+ * @remarks
489
+ * This method initializes a new ZigbeeGroup instance, sets up its properties, and configures the device
490
+ * based on the group members. It also adds command handlers for the group.
491
+ */
492
+ static async create(platform, group) {
493
+ const zigbeeGroup = new ZigbeeGroup(platform, group);
494
+ if (zigbeeGroup.platform.postfixHostname) {
495
+ zigbeeGroup.serial = `group-${group.id}_${hostname}`.slice(0, 32);
366
496
  }
367
497
  else {
368
- this.serial = `group-${group.id}`.slice(0, 32);
498
+ zigbeeGroup.serial = `group-${group.id}`.slice(0, 32);
369
499
  }
370
500
  let useState = false;
371
501
  let useBrightness = false;
@@ -379,19 +509,19 @@ export class ZigbeeGroup extends ZigbeeEntity {
379
509
  let isThermostat = false;
380
510
  if (group.members.length === 0) {
381
511
  // Create a virtual device for the empty group to use in automations
382
- this.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} is a ${CYAN}virtual${db} group`);
383
- this.bridgedDevice = this.createDevice([onOffSwitch], [...onOffSwitch.requiredServerClusters]);
512
+ zigbeeGroup.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} is a ${CYAN}virtual${db} group`);
513
+ zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
384
514
  isSwitch = true;
385
- this.propertyMap.set('state', { name: 'state', type: 'switch', endpoint: '' });
515
+ zigbeeGroup.propertyMap.set('state', { name: 'state', type: 'switch', endpoint: '' });
386
516
  }
387
517
  else {
388
518
  // Create a switch or light or outlet device for the group
389
519
  group.members.forEach((member) => {
390
- // const device = this.platform.z2m.getDevice(member.ieee_address);
391
- const device = this.platform.z2mBridgeDevices?.find((device) => device.ieee_address === member.ieee_address);
520
+ // const device = zigbeeGroup.platform.z2m.getDevice(member.ieee_address);
521
+ const device = zigbeeGroup.platform.z2mBridgeDevices?.find((device) => device.ieee_address === member.ieee_address);
392
522
  if (!device)
393
523
  return;
394
- this.log.debug(`Group ${gn}${group.friendly_name}${db}: member device ${dn}${device.friendly_name}${db}`);
524
+ zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${db}: member device ${dn}${device.friendly_name}${db}`);
395
525
  device.definition?.exposes.forEach((expose) => {
396
526
  if (expose.features) {
397
527
  // Specific features with type
@@ -400,7 +530,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
400
530
  expose.type = 'child_lock';
401
531
  feature.name = 'child_lock';
402
532
  }
403
- this.log.debug(`- specific type ${CYAN}${expose.type}${db}${feature.endpoint ? ' endpoint ' + CYAN + feature.endpoint + db : ''}${db} feature name ${CYAN}${feature.name}${db} property ${CYAN}${feature.property}${db} min ${CYAN}${feature.value_min}${db} max ${CYAN}${feature.value_max}${db}`);
533
+ zigbeeGroup.log.debug(`- specific type ${CYAN}${expose.type}${db}${feature.endpoint ? ' endpoint ' + CYAN + feature.endpoint + db : ''}${db} feature name ${CYAN}${feature.name}${db} property ${CYAN}${feature.property}${db} min ${CYAN}${feature.value_min}${db} max ${CYAN}${feature.value_max}${db}`);
404
534
  if (expose.type === 'switch' || expose.type === 'light') {
405
535
  if (expose.type === 'switch')
406
536
  isSwitch = true;
@@ -425,12 +555,12 @@ export class ZigbeeGroup extends ZigbeeEntity {
425
555
  }
426
556
  else {
427
557
  // Generic features without type
428
- this.log.debug(`- generic type ${CYAN}${expose.type}${db} expose name ${CYAN}${expose.name}${db} property ${CYAN}${expose.property}${db}`);
558
+ zigbeeGroup.log.debug(`- generic type ${CYAN}${expose.type}${db} expose name ${CYAN}${expose.name}${db} property ${CYAN}${expose.property}${db}`);
429
559
  }
430
560
  });
431
561
  });
432
- this.log.debug(`Group ${gn}${group.friendly_name}${rs}${db} switch: ${CYAN}${isSwitch}${db} light: ${CYAN}${isLight}${db} cover: ${CYAN}${isCover}${db} thermostat: ${CYAN}${isThermostat}${db}`);
433
- this.log.debug(`Group ${gn}${group.friendly_name}${rs}${db} state: ${CYAN}${useState}${db} brightness: ${CYAN}${useBrightness}${db} color: ${CYAN}${useColor}${db} color_temp: ${CYAN}${useColorTemperature}${db} min: ${CYAN}${minColorTemperature}${db} max: ${CYAN}${maxColorTemperature}${db}`);
562
+ zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${rs}${db} switch: ${CYAN}${isSwitch}${db} light: ${CYAN}${isLight}${db} cover: ${CYAN}${isCover}${db} thermostat: ${CYAN}${isThermostat}${db}`);
563
+ zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${rs}${db} state: ${CYAN}${useState}${db} brightness: ${CYAN}${useBrightness}${db} color: ${CYAN}${useColor}${db} color_temp: ${CYAN}${useColorTemperature}${db} min: ${CYAN}${minColorTemperature}${db} max: ${CYAN}${maxColorTemperature}${db}`);
434
564
  let deviceType;
435
565
  if (useState) {
436
566
  deviceType = onOffLight;
@@ -440,192 +570,185 @@ export class ZigbeeGroup extends ZigbeeEntity {
440
570
  deviceType = onOffLight;
441
571
  else if (platform.outletList.includes(group.friendly_name))
442
572
  deviceType = onOffOutlet;
443
- this.propertyMap.set('state', { name: 'state', type: isLight ? 'light' : 'switch', endpoint: '' });
573
+ zigbeeGroup.propertyMap.set('state', { name: 'state', type: isLight ? 'light' : 'switch', endpoint: '' });
444
574
  }
445
575
  if (useBrightness) {
446
576
  deviceType = dimmableLight;
447
- this.propertyMap.set('brightness', { name: 'brightness', type: 'light', endpoint: '' });
577
+ zigbeeGroup.propertyMap.set('brightness', { name: 'brightness', type: 'light', endpoint: '' });
448
578
  }
449
579
  if (useColorTemperature) {
450
580
  deviceType = colorTemperatureLight;
451
- this.propertyMap.set('color_temp', { name: 'color_temp', type: 'light', endpoint: '' });
581
+ zigbeeGroup.propertyMap.set('color_temp', { name: 'color_temp', type: 'light', endpoint: '' });
452
582
  }
453
583
  if (useColor) {
454
584
  deviceType = colorTemperatureLight;
455
- this.propertyMap.set('color', { name: 'color', type: 'light', endpoint: '' });
585
+ zigbeeGroup.propertyMap.set('color', { name: 'color', type: 'light', endpoint: '' });
456
586
  }
457
587
  if (isCover) {
458
- deviceType = DeviceTypes.WINDOW_COVERING;
459
- this.propertyMap.set('state', { name: 'state', type: 'cover', endpoint: '' });
460
- this.propertyMap.set('position', { name: 'position', type: 'cover', endpoint: '' });
461
- this.propertyMap.set('moving', { name: 'moving', type: 'cover', endpoint: '' });
588
+ deviceType = coverDevice;
589
+ zigbeeGroup.propertyMap.set('state', { name: 'state', type: 'cover', endpoint: '' });
590
+ zigbeeGroup.propertyMap.set('position', { name: 'position', type: 'cover', endpoint: '' });
591
+ zigbeeGroup.propertyMap.set('moving', { name: 'moving', type: 'cover', endpoint: '' });
462
592
  }
463
593
  if (isThermostat) {
464
- deviceType = DeviceTypes.THERMOSTAT;
465
- this.propertyMap.set('local_temperature', { name: 'local_temperature', type: 'climate', endpoint: '' });
466
- this.propertyMap.set('current_heating_setpoint', { name: 'current_heating_setpoint', type: 'climate', endpoint: '' });
467
- this.propertyMap.set('current_cooling_setpoint', { name: 'current_cooling_setpoint', type: 'climate', endpoint: '' });
468
- this.propertyMap.set('running_state', { name: 'running_state', type: 'climate', endpoint: '' });
469
- this.propertyMap.set('system_mode', { name: 'system_mode', type: 'climate', endpoint: '' });
594
+ deviceType = thermostatDevice;
595
+ zigbeeGroup.propertyMap.set('local_temperature', { name: 'local_temperature', type: 'climate', endpoint: '' });
596
+ zigbeeGroup.propertyMap.set('current_heating_setpoint', { name: 'current_heating_setpoint', type: 'climate', endpoint: '' });
597
+ zigbeeGroup.propertyMap.set('current_cooling_setpoint', { name: 'current_cooling_setpoint', type: 'climate', endpoint: '' });
598
+ zigbeeGroup.propertyMap.set('running_state', { name: 'running_state', type: 'climate', endpoint: '' });
599
+ zigbeeGroup.propertyMap.set('system_mode', { name: 'system_mode', type: 'climate', endpoint: '' });
470
600
  }
471
601
  if (!deviceType)
472
- return;
473
- this.bridgedDevice = this.createDevice([deviceType], [...deviceType.requiredServerClusters]);
602
+ return zigbeeGroup;
603
+ zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
474
604
  }
475
- if (!this.bridgedDevice)
476
- return;
477
- // Properties
478
- this.propertyMap.forEach((value, key) => {
479
- this.log.debug(`Property ${CYAN}${key}${db} name ${CYAN}${value.name}${db} type ${CYAN}${value.type}${db} endpoint ${CYAN}${value.endpoint === '' ? 'main' : value.endpoint}${db}`);
480
- });
481
- // Command handlers
605
+ zigbeeGroup.addBridgedDeviceBasicInformation();
606
+ zigbeeGroup.addPowerSource();
607
+ zigbeeGroup.bridgedDevice.addRequiredClusterServers(zigbeeGroup.bridgedDevice);
608
+ // Verify the device
609
+ if (!zigbeeGroup.bridgedDevice || !zigbeeGroup.verifyMutableDevice(zigbeeGroup.bridgedDevice))
610
+ return zigbeeGroup;
611
+ // Log properties
612
+ zigbeeGroup.logPropertyMap();
613
+ // Add command handlers
482
614
  if (isSwitch || isLight) {
483
- this.bridgedDevice.addFixedLabel('type', 'switch');
484
- this.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
485
- this.log.warn(`Command identify called for ${this.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
486
- // logEndpoint(this.bridgedDevice!);
615
+ if (isSwitch)
616
+ await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'switch');
617
+ if (isLight)
618
+ await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'light');
619
+ zigbeeGroup.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
620
+ zigbeeGroup.log.warn(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
621
+ // logEndpoint(zigbeeGroup.bridgedDevice!);
487
622
  });
488
- this.bridgedDevice.addCommandHandler('on', async ({ attributes: { onOff } }) => {
489
- this.log.debug(`Command on called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
490
- this.publishCommand('on', group.friendly_name, { state: 'ON' });
623
+ zigbeeGroup.bridgedDevice.addCommandHandler('on', async () => {
624
+ zigbeeGroup.log.debug(`Command on called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
625
+ zigbeeGroup.publishCommand('on', group.friendly_name, { state: 'ON' });
491
626
  });
492
- this.bridgedDevice.addCommandHandler('off', async ({ attributes: { onOff } }) => {
493
- this.log.debug(`Command off called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
494
- this.publishCommand('off', group.friendly_name, { state: 'OFF' });
627
+ zigbeeGroup.bridgedDevice.addCommandHandler('off', async () => {
628
+ zigbeeGroup.log.debug(`Command off called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
629
+ zigbeeGroup.publishCommand('off', group.friendly_name, { state: 'OFF' });
495
630
  });
496
- this.bridgedDevice.addCommandHandler('toggle', async ({ attributes: { onOff } }) => {
497
- this.log.debug(`Command toggle called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
498
- this.publishCommand('toggle', group.friendly_name, { state: 'TOGGLE' });
631
+ zigbeeGroup.bridgedDevice.addCommandHandler('toggle', async () => {
632
+ zigbeeGroup.log.debug(`Command toggle called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
633
+ zigbeeGroup.publishCommand('toggle', group.friendly_name, { state: 'TOGGLE' });
499
634
  });
500
635
  }
501
636
  if (isLight) {
502
- this.bridgedDevice.addFixedLabel('type', 'light');
503
- if (this.bridgedDevice.hasClusterServer(LevelControl.Complete)) {
504
- this.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level }, attributes: { currentLevel } }) => {
505
- this.log.debug(`Command moveToLevel called for ${this.ien}${group.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`);
506
- this.publishCommand('moveToLevel', group.friendly_name, { brightness: level });
637
+ if (useBrightness) {
638
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level } }) => {
639
+ zigbeeGroup.log.debug(`Command moveToLevel called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${level}`);
640
+ zigbeeGroup.publishCommand('moveToLevel', group.friendly_name, { brightness: level });
507
641
  });
508
- this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level }, attributes: { currentLevel } }) => {
509
- this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${group.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`);
510
- this.publishCommand('moveToLevelWithOnOff', group.friendly_name, { brightness: level });
642
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level } }) => {
643
+ zigbeeGroup.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${level}`);
644
+ zigbeeGroup.publishCommand('moveToLevelWithOnOff', group.friendly_name, { brightness: level });
511
645
  });
512
646
  }
513
- if (this.bridgedDevice.hasClusterServer(ColorControl.Complete) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) {
514
- this.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request: request, attributes: attributes }) => {
515
- this.log.debug(`Command moveToColorTemperature called for ${this.ien}${group.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds} attributes: ${attributes.colorTemperatureMireds?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
516
- this.log.debug(`Command moveToColorTemperature called for ${this.ien}${group.friendly_name}${rs}${db} colorMode`, attributes.colorMode.getLocal());
517
- attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds);
518
- this.publishCommand('moveToColorTemperature', group.friendly_name, { color_temp: request.colorTemperatureMireds });
647
+ if (useColorTemperature) {
648
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request: request }) => {
649
+ zigbeeGroup.log.debug(`Command moveToColorTemperature called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds}`);
650
+ await zigbeeGroup.bridgedDevice?.setAttribute(ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds);
651
+ zigbeeGroup.publishCommand('moveToColorTemperature', group.friendly_name, { color_temp: request.colorTemperatureMireds });
519
652
  });
520
653
  }
521
- if (this.bridgedDevice.hasClusterServer(ColorControl.Complete) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentHue')) {
654
+ if (useColor) {
522
655
  let lastRequestedHue = 0;
523
656
  let lastRequestedSaturation = 0;
524
- this.bridgedDevice.addCommandHandler('moveToHue', async ({ request: request, attributes: attributes }) => {
525
- this.log.debug(`Command moveToHue called for ${this.ien}${group.friendly_name}${rs}${db} request: ${request.hue} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
526
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
657
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToHue', async ({ request: request }) => {
658
+ zigbeeGroup.log.debug(`Command moveToHue called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${request.hue}`);
659
+ await zigbeeGroup.bridgedDevice?.setAttribute(ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
527
660
  lastRequestedHue = request.hue;
528
- this.colorTimeout = setTimeout(() => {
529
- clearTimeout(this.colorTimeout);
661
+ zigbeeGroup.colorTimeout = setTimeout(() => {
662
+ clearTimeout(zigbeeGroup.colorTimeout);
530
663
  const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (lastRequestedSaturation / 254) * 100, 50);
531
- this.publishCommand('moveToHue', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
664
+ zigbeeGroup.publishCommand('moveToHue', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
532
665
  }, 500);
533
666
  });
534
- this.bridgedDevice.addCommandHandler('moveToSaturation', async ({ request: request, attributes: attributes }) => {
535
- this.log.debug(`Command moveToSaturation called for ${this.ien}${group.friendly_name}${rs}${db} request: ${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
536
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
667
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToSaturation', async ({ request: request }) => {
668
+ zigbeeGroup.log.debug(`Command moveToSaturation called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${request.saturation}`);
669
+ await zigbeeGroup.bridgedDevice?.setAttribute(ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
537
670
  lastRequestedSaturation = request.saturation;
538
- this.colorTimeout = setTimeout(() => {
539
- clearTimeout(this.colorTimeout);
671
+ zigbeeGroup.colorTimeout = setTimeout(() => {
672
+ clearTimeout(zigbeeGroup.colorTimeout);
540
673
  const rgb = color.hslColorToRgbColor((lastRequestedHue / 254) * 360, (request.saturation / 254) * 100, 50);
541
- this.publishCommand('moveToSaturation', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
674
+ zigbeeGroup.publishCommand('moveToSaturation', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
542
675
  }, 500);
543
676
  });
544
- this.bridgedDevice.addCommandHandler('moveToHueAndSaturation', async ({ request: request, attributes: attributes }) => {
545
- this.log.debug(`Command moveToHueAndSaturation called for ${this.ien}${group.friendly_name}${rs}${db} request: ${request.hue}-${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
546
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
677
+ zigbeeGroup.bridgedDevice.addCommandHandler('moveToHueAndSaturation', async ({ request: request }) => {
678
+ zigbeeGroup.log.debug(`Command moveToHueAndSaturation called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} request: ${request.hue}-${request.saturation}`);
679
+ await zigbeeGroup.bridgedDevice?.setAttribute(ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
547
680
  const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50);
548
- this.publishCommand('moveToHueAndSaturation', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
681
+ zigbeeGroup.publishCommand('moveToHueAndSaturation', group.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
549
682
  });
550
683
  }
551
684
  }
552
685
  if (isCover) {
553
- this.bridgedDevice.addFixedLabel('type', 'cover');
554
- this.bridgedDevice.addCommandHandler('upOrOpen', async (data) => {
555
- this.log.debug(`Command upOrOpen called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
556
- data.attributes.currentPositionLiftPercent100ths?.setLocal(0);
557
- data.attributes.targetPositionLiftPercent100ths?.setLocal(0);
558
- this.publishCommand('upOrOpen', group.friendly_name, { state: 'OPEN' });
686
+ await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'cover');
687
+ zigbeeGroup.bridgedDevice.addCommandHandler('upOrOpen', async () => {
688
+ zigbeeGroup.log.debug(`Command upOrOpen called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
689
+ await zigbeeGroup.bridgedDevice?.setWindowCoveringCurrentTargetStatus(0, 0, WindowCovering.MovementStatus.Stopped);
690
+ zigbeeGroup.publishCommand('upOrOpen', group.friendly_name, { state: 'OPEN' });
559
691
  });
560
- this.bridgedDevice.addCommandHandler('downOrClose', async (data) => {
561
- this.log.debug(`Command downOrClose called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
562
- data.attributes.currentPositionLiftPercent100ths?.setLocal(10000);
563
- data.attributes.targetPositionLiftPercent100ths?.setLocal(10000);
564
- this.publishCommand('downOrClose', group.friendly_name, { state: 'CLOSE' });
692
+ zigbeeGroup.bridgedDevice.addCommandHandler('downOrClose', async () => {
693
+ zigbeeGroup.log.debug(`Command downOrClose called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
694
+ await zigbeeGroup.bridgedDevice?.setWindowCoveringCurrentTargetStatus(10000, 10000, WindowCovering.MovementStatus.Stopped);
695
+ zigbeeGroup.publishCommand('downOrClose', group.friendly_name, { state: 'CLOSE' });
565
696
  });
566
- this.bridgedDevice.addCommandHandler('stopMotion', async (data) => {
567
- this.log.debug(`Command stopMotion called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${data.attributes.operationalStatus?.getLocal()}`);
568
- const liftPercent100thsValue = data.attributes.currentPositionLiftPercent100ths?.getLocal();
569
- if (liftPercent100thsValue) {
570
- data.attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
571
- data.attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
572
- }
573
- data.attributes.operationalStatus?.setLocal({ global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped });
574
- this.publishCommand('stopMotion', group.friendly_name, { state: 'STOP' });
697
+ zigbeeGroup.bridgedDevice.addCommandHandler('stopMotion', async () => {
698
+ zigbeeGroup.log.debug(`Command stopMotion called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
699
+ await zigbeeGroup.bridgedDevice?.setWindowCoveringTargetAsCurrentAndStopped();
700
+ zigbeeGroup.publishCommand('stopMotion', group.friendly_name, { state: 'STOP' });
575
701
  });
576
- this.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue }, attributes }) => {
577
- this.log.debug(`Command goToLiftPercentage called for ${this.ien}${group.friendly_name}${rs}${db} liftPercent100thsValue: ${liftPercent100thsValue}`);
578
- this.log.debug(`Command goToLiftPercentage current: ${attributes.currentPositionLiftPercent100ths?.getLocal()} target: ${attributes.targetPositionLiftPercent100ths?.getLocal()}`);
579
- attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
580
- attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
581
- this.publishCommand('goToLiftPercentage', group.friendly_name, { position: 100 - liftPercent100thsValue / 100 });
702
+ zigbeeGroup.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
703
+ zigbeeGroup.log.debug(`Command goToLiftPercentage called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} liftPercent100thsValue: ${liftPercent100thsValue}`);
704
+ await zigbeeGroup.bridgedDevice?.setWindowCoveringCurrentTargetStatus(liftPercent100thsValue, liftPercent100thsValue, WindowCovering.MovementStatus.Stopped);
705
+ zigbeeGroup.publishCommand('goToLiftPercentage', group.friendly_name, { position: 100 - liftPercent100thsValue / 100 });
582
706
  });
583
707
  }
584
708
  if (isThermostat) {
585
- this.bridgedDevice.addFixedLabel('type', 'climate');
586
- this.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (newValue, oldValue) => {
587
- this.bridgedDevice?.log.info(`Thermostat systemMode changed from ${oldValue} to ${newValue}`);
709
+ await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'climate');
710
+ zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (newValue, oldValue) => {
711
+ zigbeeGroup.bridgedDevice?.log.info(`Thermostat systemMode changed from ${oldValue} to ${newValue}`);
588
712
  if (oldValue !== newValue) {
589
713
  // Thermostat.SystemMode.Heat && newValue === Thermostat.SystemMode.Off
590
- this.bridgedDevice?.log.info(`Setting thermostat systemMode to ${newValue}`);
714
+ zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat systemMode to ${newValue}`);
591
715
  if (newValue === Thermostat.SystemMode.Off) {
592
- this.publishCommand('SystemMode', group.friendly_name, { system_mode: 'off' });
716
+ zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'off' });
593
717
  }
594
718
  else if (newValue === Thermostat.SystemMode.Heat) {
595
- this.publishCommand('SystemMode', group.friendly_name, { system_mode: 'heat' });
719
+ zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'heat' });
596
720
  }
597
721
  else if (newValue === Thermostat.SystemMode.Cool) {
598
- this.publishCommand('SystemMode', group.friendly_name, { system_mode: 'cool' });
722
+ zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'cool' });
599
723
  }
600
- this.noUpdate = true;
601
- this.thermostatTimeout = setTimeout(() => {
602
- this.noUpdate = false;
724
+ zigbeeGroup.noUpdate = true;
725
+ zigbeeGroup.thermostatTimeout = setTimeout(() => {
726
+ zigbeeGroup.noUpdate = false;
603
727
  }, 2 * 1000);
604
728
  }
605
- }, this.bridgedDevice.log, this.bridgedDevice);
606
- this.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint',
607
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
608
- (newValue, oldValue) => {
609
- this.bridgedDevice?.log.info(`Thermostat occupiedHeatingSetpoint changed from ${oldValue / 100} to ${newValue / 100}`);
610
- this.bridgedDevice?.log.info(`Setting thermostat occupiedHeatingSetpoint to ${newValue / 100}`);
611
- this.publishCommand('OccupiedHeatingSetpoint', group.friendly_name, { current_heating_setpoint: Math.round(newValue / 100) });
612
- this.noUpdate = true;
613
- this.thermostatTimeout = setTimeout(() => {
614
- this.noUpdate = false;
729
+ }, zigbeeGroup.log);
730
+ zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', (newValue, oldValue) => {
731
+ zigbeeGroup.bridgedDevice?.log.info(`Thermostat occupiedHeatingSetpoint changed from ${oldValue / 100} to ${newValue / 100}`);
732
+ zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat occupiedHeatingSetpoint to ${newValue / 100}`);
733
+ zigbeeGroup.publishCommand('CurrentHeatingSetpoint', group.friendly_name, { current_heating_setpoint: Math.round(newValue / 100) });
734
+ zigbeeGroup.publishCommand('OccupiedHeatingSetpoint', group.friendly_name, { occupied_heating_setpoint: Math.round(newValue / 100) });
735
+ zigbeeGroup.noUpdate = true;
736
+ zigbeeGroup.thermostatTimeout = setTimeout(() => {
737
+ zigbeeGroup.noUpdate = false;
615
738
  }, 2 * 1000);
616
- }, this.bridgedDevice.log, this.bridgedDevice);
617
- this.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint',
618
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
619
- (newValue, oldValue) => {
620
- this.bridgedDevice?.log.info(`Thermostat occupiedCoolingSetpoint changed from ${oldValue / 100} to ${newValue / 100}`);
621
- this.bridgedDevice?.log.info(`Setting thermostat occupiedCoolingSetpoint to ${newValue / 100}`);
622
- this.publishCommand('OccupiedCoolingSetpoint', group.friendly_name, { current_cooling_setpoint: Math.round(newValue / 100) });
623
- this.noUpdate = true;
624
- this.thermostatTimeout = setTimeout(() => {
625
- this.noUpdate = false;
739
+ }, zigbeeGroup.log);
740
+ zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', (newValue, oldValue) => {
741
+ zigbeeGroup.bridgedDevice?.log.info(`Thermostat occupiedCoolingSetpoint changed from ${oldValue / 100} to ${newValue / 100}`);
742
+ zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat occupiedCoolingSetpoint to ${newValue / 100}`);
743
+ zigbeeGroup.publishCommand('CurrentCoolingSetpoint', group.friendly_name, { current_cooling_setpoint: Math.round(newValue / 100) });
744
+ zigbeeGroup.publishCommand('OccupiedCoolingSetpoint', group.friendly_name, { occupied_cooling_setpoint: Math.round(newValue / 100) });
745
+ zigbeeGroup.noUpdate = true;
746
+ zigbeeGroup.thermostatTimeout = setTimeout(() => {
747
+ zigbeeGroup.noUpdate = false;
626
748
  }, 2 * 1000);
627
- }, this.bridgedDevice.log, this.bridgedDevice);
749
+ }, zigbeeGroup.log);
628
750
  }
751
+ return zigbeeGroup;
629
752
  }
630
753
  }
631
754
  // prettier-ignore
@@ -635,39 +758,41 @@ export const z2ms = [
635
758
  { type: 'switch', name: 'color_hs', property: 'color_hs', deviceType: colorTemperatureSwitch, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
636
759
  { type: 'switch', name: 'color_xy', property: 'color_xy', deviceType: colorTemperatureSwitch, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
637
760
  { type: 'switch', name: 'color_temp', property: 'color_temp', deviceType: colorTemperatureSwitch, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
638
- { type: 'outlet', name: 'state', property: 'state', deviceType: DeviceTypes.ON_OFF_PLUGIN_UNIT, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
639
- { type: 'outlet', name: 'brightness', property: 'brightness', deviceType: DeviceTypes.DIMMABLE_PLUGIN_UNIT, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(0, Math.min(254, value)); } },
640
- { type: 'light', name: 'state', property: 'state', deviceType: DeviceTypes.ON_OFF_LIGHT, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
641
- { type: 'light', name: 'brightness', property: 'brightness', deviceType: DeviceTypes.DIMMABLE_LIGHT, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(0, Math.min(254, value)); } },
642
- { type: 'light', name: 'color_hs', property: 'color_hs', deviceType: DeviceTypes.COLOR_TEMPERATURE_LIGHT, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
643
- { type: 'light', name: 'color_xy', property: 'color_xy', deviceType: DeviceTypes.COLOR_TEMPERATURE_LIGHT, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
644
- { type: 'light', name: 'color_temp', property: 'color_temp', deviceType: DeviceTypes.COLOR_TEMPERATURE_LIGHT, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
645
- { type: 'cover', name: 'state', property: 'state', deviceType: DeviceTypes.WINDOW_COVERING, cluster: WindowCovering.Cluster.id, attribute: 'currentPositionLiftPercent100ths' },
646
- { type: 'lock', name: 'state', property: 'state', deviceType: DeviceTypes.DOOR_LOCK, cluster: DoorLock.Cluster.id, attribute: 'lockState', converter: (value) => { return value === 'LOCK' ? DoorLock.LockState.Locked : DoorLock.LockState.Unlocked; } },
647
- { type: 'climate', name: 'local_temperature', property: 'local_temperature', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'localTemperature', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
648
- { type: 'climate', name: 'current_heating_setpoint', property: 'current_heating_setpoint', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
649
- { type: 'climate', name: 'current_cooling_setpoint', property: 'current_cooling_setpoint', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
650
- { type: 'climate', name: 'occupied_heating_setpoint', property: 'occupied_heating_setpoint', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
651
- { type: 'climate', name: 'occupied_cooling_setpoint', property: 'occupied_cooling_setpoint', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
652
- { type: 'climate', name: 'running_state', property: 'running_state', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'thermostatRunningMode', valueLookup: ['idle', '', '', 'cool', 'heat'] },
653
- { type: 'climate', name: 'system_mode', property: 'system_mode', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'systemMode', valueLookup: ['off', 'auto', '', 'cool', 'heat'] },
654
- { type: '', name: 'min_temperature_limit', property: 'min_temperature_limit', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'minHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
655
- { type: '', name: 'max_temperature_limit', property: 'max_temperature_limit', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'maxHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
656
- { type: '', name: 'min_heat_setpoint_limit', property: 'min_heat_setpoint_limit', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'minHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
657
- { type: '', name: 'max_heat_setpoint_limit', property: 'max_heat_setpoint_limit', deviceType: DeviceTypes.THERMOSTAT, cluster: Thermostat.Cluster.id, attribute: 'maxHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
658
- { type: '', name: 'presence', property: 'presence', deviceType: DeviceTypes.OCCUPANCY_SENSOR, cluster: OccupancySensing.Cluster.id, attribute: 'occupancy', converter: (value) => { return { occupied: value }; } },
659
- { type: '', name: 'occupancy', property: 'occupancy', deviceType: DeviceTypes.OCCUPANCY_SENSOR, cluster: OccupancySensing.Cluster.id, attribute: 'occupancy', converter: (value) => { return { occupied: value }; } },
660
- { type: '', name: 'illuminance', property: 'illuminance', deviceType: DeviceTypes.LIGHT_SENSOR, cluster: IlluminanceMeasurement.Cluster.id, attribute: 'measuredValue' },
661
- { type: '', name: 'illuminance_lux', property: 'illuminance_lux', deviceType: DeviceTypes.LIGHT_SENSOR, cluster: IlluminanceMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(Math.max(Math.min(10000 * Math.log10(value), 0xfffe), 0)); } },
662
- { type: '', name: 'contact', property: 'contact', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return value; } },
663
- { type: '', name: 'water_leak', property: 'water_leak', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
664
- { type: '', name: 'vibration', property: 'vibration', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
665
- { type: '', name: 'smoke', property: 'smoke', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
666
- { type: '', name: 'carbon_monoxide', property: 'carbon_monoxide', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
667
- { type: '', name: 'temperature', property: 'temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
668
- { type: '', name: 'humidity', property: 'humidity', deviceType: DeviceTypes.HUMIDITY_SENSOR, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
669
- { type: '', name: 'soil_moisture', property: 'soil_moisture', deviceType: DeviceTypes.HUMIDITY_SENSOR, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
670
- { type: '', name: 'pressure', property: 'pressure', deviceType: DeviceTypes.PRESSURE_SENSOR, cluster: PressureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return value; } },
761
+ { type: 'outlet', name: 'state', property: 'state', deviceType: onOffOutlet, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
762
+ { type: 'outlet', name: 'brightness', property: 'brightness', deviceType: dimmableOutlet, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(0, Math.min(254, value)); } },
763
+ { type: 'light', name: 'state', property: 'state', deviceType: onOffLight, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
764
+ { type: 'light', name: 'brightness', property: 'brightness', deviceType: dimmableLight, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(0, Math.min(254, value)); } },
765
+ { type: 'light', name: 'color_hs', property: 'color_hs', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
766
+ { type: 'light', name: 'color_xy', property: 'color_xy', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
767
+ { type: 'light', name: 'color_temp', property: 'color_temp', deviceType: colorTemperatureLight, cluster: ColorControl.Cluster.id, attribute: 'colorMode' },
768
+ { type: 'cover', name: 'state', property: 'state', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'targetPositionLiftPercent100ths' },
769
+ { type: 'cover', name: 'moving', property: 'moving', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'operationalStatus' },
770
+ { type: 'cover', name: 'position', property: 'position', deviceType: coverDevice, cluster: WindowCovering.Cluster.id, attribute: 'currentPositionLiftPercent100ths' },
771
+ { type: 'lock', name: 'state', property: 'state', deviceType: doorLockDevice, cluster: DoorLock.Cluster.id, attribute: 'lockState', converter: (value) => { return value === 'LOCK' ? DoorLock.LockState.Locked : DoorLock.LockState.Unlocked; } },
772
+ { type: 'climate', name: 'local_temperature', property: 'local_temperature', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'localTemperature', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
773
+ { type: 'climate', name: 'current_heating_setpoint', property: 'current_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
774
+ { type: 'climate', name: 'current_cooling_setpoint', property: 'current_cooling_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
775
+ { type: 'climate', name: 'occupied_heating_setpoint', property: 'occupied_heating_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedHeatingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
776
+ { type: 'climate', name: 'occupied_cooling_setpoint', property: 'occupied_cooling_setpoint', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'occupiedCoolingSetpoint', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
777
+ { type: 'climate', name: 'running_state', property: 'running_state', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'thermostatRunningMode', valueLookup: ['idle', '', '', 'cool', 'heat'] },
778
+ { type: 'climate', name: 'system_mode', property: 'system_mode', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'systemMode', valueLookup: ['off', 'auto', '', 'cool', 'heat'] },
779
+ { type: '', name: 'min_temperature_limit', property: 'min_temperature_limit', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'minHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
780
+ { type: '', name: 'max_temperature_limit', property: 'max_temperature_limit', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'maxHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
781
+ { type: '', name: 'min_heat_setpoint_limit', property: 'min_heat_setpoint_limit', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'minHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
782
+ { type: '', name: 'max_heat_setpoint_limit', property: 'max_heat_setpoint_limit', deviceType: thermostatDevice, cluster: Thermostat.Cluster.id, attribute: 'maxHeatSetpointLimit', converter: (value) => { return Math.max(-5000, Math.min(5000, value * 100)); } },
783
+ { type: '', name: 'presence', property: 'presence', deviceType: occupancySensor, cluster: OccupancySensing.Cluster.id, attribute: 'occupancy', converter: (value) => { return { occupied: value }; } },
784
+ { type: '', name: 'occupancy', property: 'occupancy', deviceType: occupancySensor, cluster: OccupancySensing.Cluster.id, attribute: 'occupancy', converter: (value) => { return { occupied: value }; } },
785
+ { type: '', name: 'illuminance', property: 'illuminance', deviceType: lightSensor, cluster: IlluminanceMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(Math.max(Math.min(value, 0xfffe), 0)); } },
786
+ { type: '', name: 'illuminance_lux', property: 'illuminance_lux', deviceType: lightSensor, cluster: IlluminanceMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(Math.max(Math.min(10000 * Math.log10(value), 0xfffe), 0)); } },
787
+ { type: '', name: 'contact', property: 'contact', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return value; } },
788
+ { type: '', name: 'water_leak', property: 'water_leak', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
789
+ { type: '', name: 'vibration', property: 'vibration', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
790
+ { type: '', name: 'smoke', property: 'smoke', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
791
+ { type: '', name: 'carbon_monoxide', property: 'carbon_monoxide', deviceType: contactSensor, cluster: BooleanState.Cluster.id, attribute: 'stateValue', converter: (value) => { return !value; } },
792
+ { type: '', name: 'temperature', property: 'temperature', deviceType: temperatureSensor, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
793
+ { type: '', name: 'humidity', property: 'humidity', deviceType: humiditySensor, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
794
+ { type: '', name: 'soil_moisture', property: 'soil_moisture', deviceType: humiditySensor, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
795
+ { type: '', name: 'pressure', property: 'pressure', deviceType: pressureSensor, cluster: PressureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return value; } },
671
796
  { type: '', name: 'air_quality', property: 'air_quality', deviceType: airQualitySensor, cluster: AirQuality.Cluster.id, attribute: 'airQuality', valueLookup: ['unknown', 'excellent', 'good', 'moderate', 'poor', 'unhealthy', 'out_of_range'] },
672
797
  { type: '', name: 'voc', property: 'voc', deviceType: airQualitySensor, cluster: TotalVolatileOrganicCompoundsConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.min(65535, value); } },
673
798
  { type: '', name: 'co', property: 'co', deviceType: airQualitySensor, cluster: CarbonMonoxideConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
@@ -676,8 +801,8 @@ export const z2ms = [
676
801
  { type: '', name: 'pm1', property: 'pm1', deviceType: airQualitySensor, cluster: Pm1ConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
677
802
  { type: '', name: 'pm25', property: 'pm25', deviceType: airQualitySensor, cluster: Pm25ConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
678
803
  { type: '', name: 'pm10', property: 'pm10', deviceType: airQualitySensor, cluster: Pm10ConcentrationMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value); } },
679
- { type: '', name: 'cpu_temperature', property: 'temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
680
- { type: '', name: 'device_temperature', property: 'device_temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
804
+ { type: '', name: 'cpu_temperature', property: 'temperature', deviceType: temperatureSensor, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
805
+ { type: '', name: 'device_temperature', property: 'device_temperature', deviceType: temperatureSensor, cluster: TemperatureMeasurement.Cluster.id, attribute: 'measuredValue', converter: (value) => { return Math.round(value * 100); } },
681
806
  { type: '', name: '', property: 'battery', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batPercentRemaining', converter: (value) => { return Math.round(value * 2); } },
682
807
  { type: '', name: '', property: 'battery_low', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batChargeLevel', converter: (value) => { return value === true ? PowerSource.BatChargeLevel.Critical : PowerSource.BatChargeLevel.Ok; } },
683
808
  { type: '', name: '', property: 'battery_voltage', deviceType: powerSource, cluster: PowerSource.Cluster.id, attribute: 'batVoltage', converter: (value) => { return value; } },
@@ -686,23 +811,68 @@ export const z2ms = [
686
811
  { type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return value * 1000; } },
687
812
  { type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return value * 1000; } },
688
813
  ];
814
+ /**
815
+ * Represents a Zigbee device entity.
816
+ *
817
+ * @class
818
+ * @extends {ZigbeeEntity}
819
+ */
689
820
  export class ZigbeeDevice extends ZigbeeEntity {
821
+ /**
822
+ * Represents a Zigbee device entity.
823
+ *
824
+ * @class
825
+ * @extends {ZigbeeEntity}
826
+ */
690
827
  constructor(platform, device) {
691
828
  super(platform, device);
692
- this.serial = `${device.ieee_address}`;
693
- if (this.platform.postfixHostname) {
694
- this.serial = `${this.serial}_${hostname}`.slice(0, 32);
829
+ }
830
+ /**
831
+ * Creates a new ZigbeeDevice instance.
832
+ *
833
+ * @param {ZigbeePlatform} platform - The Zigbee platform instance.
834
+ * @param {BridgeDevice} device - The bridge device instance.
835
+ * @returns {Promise<ZigbeeDevice>} A promise that resolves to the created ZigbeeDevice instance.
836
+ *
837
+ * @remarks
838
+ * This method initializes a new ZigbeeDevice instance, sets up its properties, and configures the device
839
+ * based on the device definition and options. It also adds command handlers for the device.
840
+ */
841
+ static async create(platform, device) {
842
+ const zigbeeDevice = new ZigbeeDevice(platform, device);
843
+ zigbeeDevice.serial = `${device.ieee_address}`;
844
+ if (zigbeeDevice.platform.postfixHostname) {
845
+ zigbeeDevice.serial = `${zigbeeDevice.serial}_${hostname}`.slice(0, 32);
695
846
  }
847
+ // Set Coordinator and dedicated routers
696
848
  if (device.friendly_name === 'Coordinator' || (device.model_id === 'ti.router' && device.manufacturer === 'TexasInstruments') || (device.model_id.startsWith('SLZB-') && device.manufacturer === 'SMLIGHT')) {
697
- this.bridgedDevice = this.createDevice([DeviceTypes.DOOR_LOCK], [Identify.Cluster.id, DoorLock.Cluster.id]);
698
- this.bridgedDevice.addFixedLabel('type', 'lock');
699
- this.isRouter = true;
849
+ zigbeeDevice.isRouter = true;
850
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
851
+ zigbeeDevice.addBridgedDeviceBasicInformation();
852
+ zigbeeDevice.addPowerSource();
853
+ zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
854
+ await zigbeeDevice.bridgedDevice.addFixedLabel('type', 'lock');
855
+ zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice);
856
+ zigbeeDevice.bridgedDevice.addCommandHandler('lockDoor', async () => {
857
+ zigbeeDevice.log.debug(`Command permit_join: false called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
858
+ await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Locked, zigbeeDevice.log);
859
+ zigbeeDevice.publishCommand('permit_join: false', 'bridge/request/permit_join', { value: false });
860
+ });
861
+ zigbeeDevice.bridgedDevice.addCommandHandler('unlockDoor', async () => {
862
+ zigbeeDevice.log.debug(`Command permit_join: true called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
863
+ await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Unlocked, zigbeeDevice.log);
864
+ zigbeeDevice.publishCommand('permit_join: true', 'bridge/request/permit_join', { value: true });
865
+ });
866
+ return zigbeeDevice;
700
867
  }
701
868
  // Get types and properties
702
869
  const types = [];
703
870
  const endpoints = [];
704
871
  const names = [];
705
872
  const properties = [];
873
+ const categories = [];
874
+ const descriptions = [];
875
+ const labels = [];
706
876
  const units = [];
707
877
  const value_mins = [];
708
878
  const value_maxs = [];
@@ -717,6 +887,9 @@ export class ZigbeeDevice extends ZigbeeEntity {
717
887
  endpoints.push(expose.endpoint || '');
718
888
  names.push(feature.name);
719
889
  properties.push(feature.property);
890
+ categories.push(feature.category ?? '');
891
+ descriptions.push(feature.description ?? '');
892
+ labels.push(feature.label ?? '');
720
893
  units.push(feature.unit ?? '');
721
894
  value_mins.push(feature.value_min ?? NaN);
722
895
  value_maxs.push(feature.value_max ?? NaN);
@@ -725,32 +898,56 @@ export class ZigbeeDevice extends ZigbeeEntity {
725
898
  }
726
899
  else {
727
900
  // Generic features without type
728
- types.push('');
729
- endpoints.push(expose.endpoint || '');
901
+ // Change voltage to battery_voltage for battery powered devices
730
902
  if (device.power_source === 'Battery' && expose.name === 'voltage')
731
903
  expose.name = 'battery_voltage';
732
904
  if (device.power_source === 'Battery' && expose.property === 'voltage')
733
905
  expose.property = 'battery_voltage';
906
+ // Fix illuminance and illuminance_lux for light sensors:
907
+ // illuminance is raw value (use like it is)
908
+ // illuminance_lux is in lux (convert with log10)
909
+ // illuminance has description "Raw measured illuminance"
910
+ // illuminance_lux has description "Measured illuminance in lux"
911
+ if (expose.description === 'Raw measured illuminance') {
912
+ expose.name = 'illuminance';
913
+ expose.property = 'illuminance';
914
+ expose.label = 'Illuminance';
915
+ expose.unit = '';
916
+ }
917
+ if (expose.description === 'Measured illuminance in lux') {
918
+ expose.name = 'illuminance_lux';
919
+ expose.property = 'illuminance_lux';
920
+ expose.label = 'Illuminance (lux)';
921
+ expose.unit = 'lx';
922
+ }
923
+ types.push('');
924
+ endpoints.push(expose.endpoint || '');
734
925
  names.push(expose.name || '');
735
926
  properties.push(expose.property);
927
+ categories.push(expose.category ?? '');
928
+ descriptions.push(expose.description ?? '');
929
+ labels.push(expose.label ?? '');
736
930
  units.push(expose.unit ?? '');
737
931
  value_mins.push(expose.value_min ?? NaN);
738
932
  value_maxs.push(expose.value_max ?? NaN);
739
933
  values.push(expose.values ? expose.values.join('|') : '');
740
934
  if (expose.name === 'action' && expose.values) {
741
- this.actions.push(...expose.values);
935
+ zigbeeDevice.actions.push(...expose.values);
742
936
  }
743
937
  }
744
938
  });
745
939
  device.definition?.options.forEach((option) => {
746
940
  types.push('');
941
+ endpoints.push(option.endpoint || '');
747
942
  names.push(option.name || '');
748
943
  properties.push(option.property);
944
+ categories.push(option.category ?? '');
945
+ descriptions.push(option.description ?? '');
946
+ labels.push(option.label ?? '');
749
947
  units.push(option.unit ?? '');
750
948
  value_mins.push(option.value_min ?? NaN);
751
949
  value_maxs.push(option.value_max ?? NaN);
752
950
  values.push(option.values ? option.values.join('|') : '');
753
- endpoints.push(option.endpoint || '');
754
951
  });
755
952
  if (platform.switchList.includes(device.friendly_name)) {
756
953
  types.forEach((type, index) => {
@@ -768,455 +965,503 @@ export class ZigbeeDevice extends ZigbeeEntity {
768
965
  });
769
966
  }
770
967
  if (platform.featureBlackList)
771
- this.ignoreFeatures = [...this.ignoreFeatures, ...platform.featureBlackList];
968
+ zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.featureBlackList];
772
969
  if (platform.deviceFeatureBlackList[device.friendly_name])
773
- this.ignoreFeatures = [...this.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]];
970
+ zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]];
774
971
  /*
775
- this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`);
776
- this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`);
777
- this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`);
778
- this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`);
779
- this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} - units[${units.length}]: ${debugStringify(units)}`);
972
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - types[${types.length}]: ${debugStringify(types)}`);
973
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - endpoints[${endpoints.length}]: ${debugStringify(endpoints)}`);
974
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - names[${names.length}]: ${debugStringify(names)}`);
975
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - properties[${properties.length}]: ${debugStringify(properties)}`);
976
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - categories[${categories.length}]: ${debugStringify(categories)}`);
977
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - descriptions[${descriptions.length}]: ${debugStringify(descriptions)}`);
978
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - labels[${labels.length}]: ${debugStringify(labels)}`);
979
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} - units[${units.length}]: ${debugStringify(units)}`);
780
980
  */
781
- names.forEach((name, index) => {
981
+ for (const [index, name] of names.entries()) {
782
982
  if (platform.featureBlackList.includes(name)) {
783
- this.log.debug(`Device ${this.en}${device.friendly_name}${db} feature ${name} is globally blacklisted`);
784
- return;
983
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} feature ${name} is globally blacklisted`);
984
+ continue;
785
985
  }
786
986
  if (platform.deviceFeatureBlackList[device.friendly_name]?.includes(name)) {
787
- this.log.debug(`Device ${this.en}${device.friendly_name}${db} feature ${name} is blacklisted`);
788
- return;
987
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} feature ${name} is blacklisted`);
988
+ continue;
789
989
  }
790
990
  if (name === 'transition') {
791
- this.log.debug(`*Device ${this.ien}${device.friendly_name}${rs}${db} transition is supported`);
792
- this.transition = true;
991
+ zigbeeDevice.log.debug(`*Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} transition is supported`);
992
+ zigbeeDevice.transition = true;
793
993
  }
794
994
  const type = types[index];
795
995
  const endpoint = endpoints[index];
796
996
  const property = properties[index];
797
997
  const unit = units[index];
998
+ const category = categories[index];
999
+ const description = descriptions[index];
1000
+ const label = labels[index];
798
1001
  const value_min = value_mins[index];
799
1002
  const value_max = value_maxs[index];
800
1003
  const value = values[index];
801
1004
  const z2m = z2ms.find((z2m) => z2m.type === type && z2m.name === name);
802
1005
  if (z2m) {
803
- this.log.debug(`Device ${this.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}`);
804
- this.propertyMap.set(property, { name, type, endpoint, value_min, value_max, values: value, unit });
1006
+ 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}`);
1007
+ zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
805
1008
  if (endpoint === '') {
806
- if (!this.bridgedDevice)
807
- this.bridgedDevice = this.createDevice([z2m.deviceType], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]);
808
- else
809
- this.bridgedDevice.addDeviceTypeWithClusterServer([z2m.deviceType], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)]);
1009
+ /* prettier-ignore */
1010
+ if (!zigbeeDevice.mutableDevice.has(endpoint)) {
1011
+ zigbeeDevice.mutableDevice.set(endpoint, { tagList: [], deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
1012
+ }
1013
+ else {
1014
+ zigbeeDevice.mutableDevice.get(endpoint)?.deviceTypes.push(z2m.deviceType);
1015
+ zigbeeDevice.mutableDevice.get(endpoint)?.clusterServersIds.push(...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster));
1016
+ }
810
1017
  }
811
1018
  else {
812
- if (!this.bridgedDevice)
813
- this.bridgedDevice = this.createDevice([bridgedNode]);
814
- const child = this.bridgedDevice.addChildDeviceTypeWithClusterServer(endpoint, [z2m.deviceType], [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], undefined, this.log.logLevel === "debug" /* LogLevel.DEBUG */);
1019
+ const tagList = [];
815
1020
  if (endpoint === 'l1')
816
- this.bridgedDevice.addTagList(child, null, NumberTag.One.namespaceId, NumberTag.One.tag, 'endpoint ' + endpoint);
1021
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.One.namespaceId, tag: NumberTag.One.tag, label: 'endpoint ' + endpoint });
817
1022
  if (endpoint === 'l2')
818
- this.bridgedDevice.addTagList(child, null, NumberTag.Two.namespaceId, NumberTag.Two.tag, 'endpoint ' + endpoint);
1023
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.Two.namespaceId, tag: NumberTag.Two.tag, label: 'endpoint ' + endpoint });
819
1024
  if (endpoint === 'l3')
820
- this.bridgedDevice.addTagList(child, null, NumberTag.Three.namespaceId, NumberTag.Three.tag, 'endpoint ' + endpoint);
1025
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.Three.namespaceId, tag: NumberTag.Three.tag, label: 'endpoint ' + endpoint });
821
1026
  if (endpoint === 'l4')
822
- this.bridgedDevice.addTagList(child, null, NumberTag.Four.namespaceId, NumberTag.Four.tag, 'endpoint ' + endpoint);
1027
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.Four.namespaceId, tag: NumberTag.Four.tag, label: 'endpoint ' + endpoint });
823
1028
  if (endpoint === 'l5')
824
- this.bridgedDevice.addTagList(child, null, NumberTag.Five.namespaceId, NumberTag.Five.tag, 'endpoint ' + endpoint);
1029
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.Five.namespaceId, tag: NumberTag.Five.tag, label: 'endpoint ' + endpoint });
825
1030
  if (endpoint === 'l6')
826
- this.bridgedDevice.addTagList(child, null, NumberTag.Six.namespaceId, NumberTag.Six.tag, 'endpoint ' + endpoint);
827
- /*
828
- if (endpoint === 'l1') this.addTagList(child, null, NumberTag.One.namespaceId, NumberTag.One.tag, 'endpoint ' + endpoint);
829
- if (endpoint === 'l1') this.addTagList(child, null, SwitchesTag.Custom.namespaceId, SwitchesTag.Custom.tag, 'endpoint ' + endpoint);
830
- if (endpoint === 'l2') this.addTagList(child, null, NumberTag.Two.namespaceId, NumberTag.Two.tag, 'endpoint ' + endpoint);
831
- if (endpoint === 'l2') this.addTagList(child, null, SwitchesTag.Custom.namespaceId, SwitchesTag.Custom.tag, 'endpoint ' + endpoint);
832
- */
833
- this.bridgedDevice.addFixedLabel('composed', type);
834
- this.hasEndpoints = true;
1031
+ tagList.push({ mfgCode: null, namespaceId: NumberTag.Six.namespaceId, tag: NumberTag.Six.tag, label: 'endpoint ' + endpoint });
1032
+ tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'endpoint ' + endpoint });
1033
+ /* prettier-ignore */
1034
+ if (!zigbeeDevice.mutableDevice.has(endpoint)) {
1035
+ zigbeeDevice.mutableDevice.set(endpoint, { tagList, deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
1036
+ }
1037
+ else {
1038
+ zigbeeDevice.mutableDevice.get(endpoint)?.deviceTypes.push(z2m.deviceType);
1039
+ zigbeeDevice.mutableDevice.get(endpoint)?.clusterServersIds.push(...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster));
1040
+ }
1041
+ if (zigbeeDevice.composedType === '')
1042
+ zigbeeDevice.composedType = type;
1043
+ zigbeeDevice.hasEndpoints = true;
835
1044
  }
836
1045
  }
837
1046
  else {
838
- // this.log.debug(`Device ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${zb}${endpoint}${db} type: ${zb}${type}${db} property: ${zb}${name}${db} => no mapping found`);
1047
+ // 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`);
839
1048
  }
840
1049
  // Map actions to switches
841
- if (name === 'action' && this.actions.length) {
842
- this.log.info(`Device ${this.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`);
843
- this.log.info(' controller events <=> zigbee2mqtt actions');
844
- if (!this.bridgedDevice)
845
- this.bridgedDevice = this.createDevice([bridgedNode]);
846
- this.hasEndpoints = true;
1050
+ if (name === 'action' && zigbeeDevice.actions.length) {
1051
+ zigbeeDevice.log.info(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`);
1052
+ zigbeeDevice.log.info(' controller events <=> zigbee2mqtt actions');
1053
+ if (!zigbeeDevice.bridgedDevice)
1054
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1055
+ zigbeeDevice.hasEndpoints = true;
847
1056
  // Mapping actions
848
1057
  const switchMap = ['Single Press', 'Double Press', 'Long Press '];
849
1058
  const triggerMap = ['Single', 'Double', 'Long'];
850
1059
  let count = 1;
851
- if (this.actions.length <= 3) {
1060
+ if (zigbeeDevice.actions.length <= 3) {
852
1061
  const actionsMap = [];
853
- for (let a = 0; a < this.actions.length; a++) {
854
- actionsMap.push(this.actions[a]);
855
- this.propertyMap.set('action_' + actionsMap[a], { name, type: '', endpoint: 'switch_' + count, action: triggerMap[a] });
856
- this.log.info(`-- Button ${count}: ${hk}${switchMap[a]}${nf} <=> ${zb}${actionsMap[a]}${nf}`);
1062
+ for (let a = 0; a < zigbeeDevice.actions.length; a++) {
1063
+ actionsMap.push(zigbeeDevice.actions[a]);
1064
+ zigbeeDevice.propertyMap.set('action_' + actionsMap[a], { name, type: '', endpoint: 'switch_' + count, action: triggerMap[a] });
1065
+ zigbeeDevice.log.info(`-- Button ${count}: ${hk}${switchMap[a]}${nf} <=> ${zb}${actionsMap[a]}${nf}`);
857
1066
  }
858
- const button = this.bridgedDevice.addChildDeviceTypeWithClusterServer('switch_' + count, [DeviceTypes.GENERIC_SWITCH], [...DeviceTypes.GENERIC_SWITCH.requiredServerClusters], undefined, this.log.logLevel === "debug" /* LogLevel.DEBUG */);
859
- this.bridgedDevice.addTagList(button, null, SwitchesTag.Custom.namespaceId, SwitchesTag.Custom.tag, 'switch_' + count);
1067
+ const tagList = [];
1068
+ tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'switch_' + count });
1069
+ zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
860
1070
  }
861
1071
  else {
862
- for (let i = 0; i < this.actions.length; i += 3) {
1072
+ for (let i = 0; i < zigbeeDevice.actions.length; i += 3) {
863
1073
  const actionsMap = [];
864
- for (let a = i; a < i + 3 && a < this.actions.length; a++) {
865
- actionsMap.push(this.actions[a]);
866
- this.propertyMap.set('action_' + actionsMap[a - i], { name, type: '', endpoint: 'switch_' + count, action: triggerMap[a - i] });
867
- this.log.info(`-- Button ${count}: ${hk}${switchMap[a - i]}${nf} <=> ${zb}${actionsMap[a - i]}${nf}`);
1074
+ for (let a = i; a < i + 3 && a < zigbeeDevice.actions.length; a++) {
1075
+ actionsMap.push(zigbeeDevice.actions[a]);
1076
+ zigbeeDevice.propertyMap.set('action_' + actionsMap[a - i], { name, type: '', endpoint: 'switch_' + count, action: triggerMap[a - i] });
1077
+ zigbeeDevice.log.info(`-- Button ${count}: ${hk}${switchMap[a - i]}${nf} <=> ${zb}${actionsMap[a - i]}${nf}`);
868
1078
  }
869
- const button = this.bridgedDevice.addChildDeviceTypeWithClusterServer('switch_' + count, [DeviceTypes.GENERIC_SWITCH], [...DeviceTypes.GENERIC_SWITCH.requiredServerClusters], undefined, this.log.logLevel === "debug" /* LogLevel.DEBUG */);
870
- this.bridgedDevice.addTagList(button, null, SwitchesTag.Custom.namespaceId, SwitchesTag.Custom.tag, 'switch_' + count);
1079
+ const tagList = [];
1080
+ tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'switch_' + count });
1081
+ zigbeeDevice.mutableDevice.set('switch_' + count, { tagList, deviceTypes: [genericSwitch], clusterServersIds: [...genericSwitch.requiredServerClusters], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
871
1082
  count++;
872
1083
  }
873
1084
  }
874
- this.bridgedDevice.addFixedLabel('composed', 'button');
1085
+ if (zigbeeDevice.composedType === '')
1086
+ zigbeeDevice.composedType = 'button';
875
1087
  }
876
- });
1088
+ }
877
1089
  // Add battery properties
878
1090
  if (device.power_source === 'Battery') {
879
- this.propertyMap.set('battery', { name: 'battery', type: '', endpoint: '' });
880
- this.propertyMap.set('battery_low', { name: 'battery_low', type: '', endpoint: '' });
881
- this.propertyMap.set('battery_voltage', { name: 'battery_voltage', type: '', endpoint: '' });
1091
+ zigbeeDevice.propertyMap.set('battery', { name: 'battery', type: '', endpoint: '' });
1092
+ zigbeeDevice.propertyMap.set('battery_low', { name: 'battery_low', type: '', endpoint: '' });
1093
+ zigbeeDevice.propertyMap.set('battery_voltage', { name: 'battery_voltage', type: '', endpoint: '' });
882
1094
  }
883
- // Add illuminance_lux
884
- if (this.propertyMap.has('illuminance') && !this.propertyMap.has('illuminance_lux')) {
885
- this.propertyMap.set('illuminance_lux', { name: 'illuminance_lux', type: '', endpoint: '' });
886
- }
887
- // Remove superset device Types
888
- if (this.bridgedDevice) {
889
- const deviceTypes = this.bridgedDevice.getDeviceTypes();
1095
+ if (!zigbeeDevice.mutableDevice.has(''))
1096
+ zigbeeDevice.mutableDevice.set('', { tagList: [], deviceTypes: [bridgedNode, powerSource], clusterServersIds: [], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
1097
+ const mainEndpoint = zigbeeDevice.mutableDevice.get('');
1098
+ if (!mainEndpoint)
1099
+ return zigbeeDevice;
1100
+ // Remove superset device Types and order them
1101
+ for (const device of zigbeeDevice.mutableDevice.values()) {
890
1102
  const deviceTypesMap = new Map();
891
- deviceTypes.forEach((deviceType) => {
1103
+ device.deviceTypes.forEach((deviceType) => {
892
1104
  deviceTypesMap.set(deviceType.code, deviceType);
893
1105
  });
894
- if (deviceTypesMap.has(DeviceTypes.ON_OFF_LIGHT.code) && deviceTypesMap.has(DeviceTypes.DIMMABLE_LIGHT.code)) {
895
- deviceTypesMap.delete(DeviceTypes.ON_OFF_LIGHT.code);
896
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} removing ON_OFF_LIGHT`);
897
- }
898
- if (deviceTypesMap.has(DeviceTypes.DIMMABLE_LIGHT.code) && deviceTypesMap.has(DeviceTypes.COLOR_TEMPERATURE_LIGHT.code)) {
899
- deviceTypesMap.delete(DeviceTypes.DIMMABLE_LIGHT.code);
900
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} removing DIMMABLE_LIGHT`);
901
- }
902
- this.bridgedDevice.setDeviceTypes(Array.from(deviceTypesMap.values()).sort((a, b) => b.code - a.code));
903
- const childEndpoints = this.bridgedDevice.getChildEndpoints();
904
- childEndpoints.forEach((childEndpoint) => {
905
- const deviceTypes = childEndpoint.getDeviceTypes();
906
- const deviceTypesMap = new Map();
907
- deviceTypes.forEach((deviceType) => {
908
- deviceTypesMap.set(deviceType.code, deviceType);
909
- });
910
- if (deviceTypesMap.has(DeviceTypes.ON_OFF_LIGHT.code) && deviceTypesMap.has(DeviceTypes.DIMMABLE_LIGHT.code)) {
911
- deviceTypesMap.delete(DeviceTypes.ON_OFF_LIGHT.code);
912
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} removing ON_OFF_LIGHT`);
913
- }
914
- if (deviceTypesMap.has(DeviceTypes.DIMMABLE_LIGHT.code) && deviceTypesMap.has(DeviceTypes.COLOR_TEMPERATURE_LIGHT.code)) {
915
- deviceTypesMap.delete(DeviceTypes.DIMMABLE_LIGHT.code);
916
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} removing DIMMABLE_LIGHT`);
917
- }
918
- childEndpoint.setDeviceTypes(Array.from(deviceTypesMap.values()).sort((a, b) => b.code - a.code));
919
- });
1106
+ if (deviceTypesMap.has(onOffSwitch.code) && deviceTypesMap.has(dimmableSwitch.code))
1107
+ deviceTypesMap.delete(onOffSwitch.code);
1108
+ if (deviceTypesMap.has(dimmableSwitch.code) && deviceTypesMap.has(colorTemperatureSwitch.code))
1109
+ deviceTypesMap.delete(dimmableSwitch.code);
1110
+ if (deviceTypesMap.has(onOffOutlet.code) && deviceTypesMap.has(dimmableOutlet.code))
1111
+ deviceTypesMap.delete(onOffOutlet.code);
1112
+ if (deviceTypesMap.has(onOffLight.code) && deviceTypesMap.has(dimmableLight.code))
1113
+ deviceTypesMap.delete(onOffLight.code);
1114
+ if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code))
1115
+ deviceTypesMap.delete(dimmableLight.code);
1116
+ device.deviceTypes = Array.from(deviceTypesMap.values()); /* .sort((a, b) => b.code - a.code);*/
920
1117
  }
1118
+ // Create the mutable device for the main endpoint
1119
+ zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice(mainEndpoint.deviceTypes, { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1120
+ // Configure BridgedDeviceBasicInformation cluster
1121
+ mainEndpoint.clusterServersObjs.push(zigbeeDevice.getBridgedDeviceBasicInformation());
1122
+ // Configure PowerSource cluster
1123
+ mainEndpoint.clusterServersObjs.push(zigbeeDevice.getPowerSource());
921
1124
  // Configure ColorControlCluster
922
- if (this.bridgedDevice && this.bridgedDevice.hasClusterServer(ColorControl.Complete)) {
923
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
924
- this.bridgedDevice.configureColorControlCluster(names.includes('color_hs') || names.includes('color_xy'), false, names.includes('color_temp'));
1125
+ if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
1126
+ 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')}`);
1127
+ zigbeeDevice.bridgedDevice.configureColorControlCluster(names.includes('color_hs') || names.includes('color_xy'), false, names.includes('color_temp'));
925
1128
  }
926
1129
  // Configure ThermostatCluster
927
- if (this.bridgedDevice && this.bridgedDevice.hasClusterServer(Thermostat.Complete)) {
928
- const heat = this.propertyMap.get('occupied_heating_setpoint') || this.propertyMap.get('current_heating_setpoint');
929
- const cool = this.propertyMap.get('occupied_cooling_setpoint') || this.propertyMap.get('current_cooling_setpoint');
1130
+ if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
1131
+ const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('current_heating_setpoint');
1132
+ const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('current_cooling_setpoint');
930
1133
  const minHeating = heat && heat.value_min !== undefined && !isNaN(heat.value_min) ? heat.value_min : 0;
931
1134
  const maxHeating = heat && heat.value_max !== undefined && !isNaN(heat.value_max) ? heat.value_max : 50;
932
1135
  const minCooling = cool && cool.value_min !== undefined && !isNaN(cool.value_min) ? cool.value_min : 0;
933
1136
  const maxCooling = cool && cool.value_max !== undefined && !isNaN(cool.value_max) ? cool.value_max : 50;
934
- this.log.debug(`Configuring device ${this.ien}${device.friendly_name}${rs}${db} Thermostat cluster with heating ${CYAN}${heat ? 'supported' : 'not supported'}${db} cooling ${CYAN}${cool ? 'supported' : 'not supported'}${db} ` +
1137
+ 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} ` +
935
1138
  `minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
936
1139
  if (heat && !cool) {
937
- this.propertyMap.delete('running_state'); // Remove running_state if only heating is supported cause it's not supported by the cluster without AutoMode
938
- this.bridgedDevice.addClusterServer(this.bridgedDevice.getDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating));
1140
+ zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only heating is supported cause it's not supported by the cluster without AutoMode
1141
+ mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating));
939
1142
  }
940
1143
  else if (!heat && cool) {
941
- this.propertyMap.delete('running_state'); // Remove running_state if only cooling is supported cause it's not supported by the cluster without AutoMode
942
- this.bridgedDevice.addClusterServer(this.bridgedDevice.getDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling));
1144
+ zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only cooling is supported cause it's not supported by the cluster without AutoMode
1145
+ mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling));
943
1146
  }
944
1147
  else if (heat && cool) {
945
- this.bridgedDevice.addClusterServer(this.bridgedDevice.getDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling));
1148
+ mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling));
946
1149
  }
947
1150
  }
948
- /* Verify that all required server clusters are present in the main endpoint and in the child endpoints */
949
- if (this.bridgedDevice) {
950
- this.bridgedDevice.getDeviceTypes().forEach((deviceType) => {
951
- deviceType.requiredServerClusters.forEach((clusterId) => {
952
- if (!this.bridgedDevice)
953
- return;
954
- if (!this.bridgedDevice.getClusterServerById(clusterId)) {
955
- this.log.error(`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`);
956
- this.bridgedDevice = undefined;
957
- }
958
- });
1151
+ // Filter out duplicate server clusters and server clusters objects
1152
+ for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
1153
+ // Filter out duplicate server clusters and server clusters objects
1154
+ const deviceClusterServersMap = new Map();
1155
+ device.clusterServersIds.forEach((clusterServer) => {
1156
+ deviceClusterServersMap.set(clusterServer, clusterServer);
1157
+ });
1158
+ const deviceClusterServersObjMap = new Map();
1159
+ device.clusterServersObjs.forEach((clusterServerObj) => {
1160
+ deviceClusterServersMap.delete(clusterServerObj.id);
1161
+ deviceClusterServersObjMap.set(clusterServerObj.id, clusterServerObj);
959
1162
  });
1163
+ device.clusterServersIds = Array.from(deviceClusterServersMap.values());
1164
+ device.clusterServersObjs = Array.from(deviceClusterServersObjMap.values());
1165
+ // Filter out duplicate client clusters and client clusters objects
1166
+ const deviceClusterClientsMap = new Map();
1167
+ device.clusterClientsIds.forEach((clusterClient) => {
1168
+ deviceClusterClientsMap.set(clusterClient, clusterClient);
1169
+ });
1170
+ const deviceClusterClientsObjMap = new Map();
1171
+ device.clusterClientsObjs.forEach((clusterClientObj) => {
1172
+ deviceClusterClientsMap.delete(clusterClientObj.id);
1173
+ deviceClusterClientsObjMap.set(clusterClientObj.id, clusterClientObj);
1174
+ });
1175
+ device.clusterClientsIds = Array.from(deviceClusterClientsMap.values());
1176
+ device.clusterClientsObjs = Array.from(deviceClusterClientsObjMap.values());
1177
+ zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
1178
+ `${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
960
1179
  }
961
- if (this.bridgedDevice) {
962
- this.bridgedDevice.getChildEndpoints().forEach((childEndpoint) => {
963
- childEndpoint.getDeviceTypes().forEach((deviceType) => {
964
- deviceType.requiredServerClusters.forEach((clusterId) => {
965
- if (!this.bridgedDevice)
966
- return;
967
- if (!childEndpoint.getClusterServerById(clusterId)) {
968
- this.log.error(`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`);
969
- this.bridgedDevice = undefined;
970
- }
971
- });
972
- });
1180
+ // Add the clusters to the main endpoint
1181
+ mainEndpoint.clusterServersObjs.forEach((clusterServerObj) => {
1182
+ zigbeeDevice.bridgedDevice?.addClusterServer(clusterServerObj);
1183
+ });
1184
+ zigbeeDevice.bridgedDevice.addClusterServerFromList(zigbeeDevice.bridgedDevice, mainEndpoint.clusterServersIds);
1185
+ zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
1186
+ if (zigbeeDevice.composedType !== '')
1187
+ await zigbeeDevice.bridgedDevice.addFixedLabel('composed', zigbeeDevice.composedType);
1188
+ // Create the child endpoints
1189
+ for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
1190
+ if (endpoint === '')
1191
+ continue;
1192
+ const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
1193
+ device.clusterServersObjs.forEach((clusterServerObj) => {
1194
+ child.addClusterServer(clusterServerObj);
973
1195
  });
974
1196
  }
975
- if (!this.bridgedDevice)
976
- return;
1197
+ // Verify the device
1198
+ if (!zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice))
1199
+ return zigbeeDevice;
977
1200
  // Log properties
978
- this.propertyMap.forEach((value, key) => {
979
- this.log.debug(`Property ${CYAN}${key}${db} name ${CYAN}${value.name}${db} type ${CYAN}${value.type === '' ? 'generic' : value.type}${db} values ${CYAN}${value.values}${db} ` +
980
- `value_min ${CYAN}${value.value_min}${db} value_max ${CYAN}${value.value_max}${db} unit ${CYAN}${value.unit}${db} endpoint ${CYAN}${value.endpoint === '' ? 'main' : value.endpoint}${db}`);
981
- });
982
- // Command handlers
983
- this.bridgedDevice.addCommandHandler('identify', async (data) => {
984
- this.log.debug(`Command identify called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} identifyTime:${data.attributes.identifyTime.getLocal()} identifyType:${data.attributes.identifyType.getLocal()} `);
985
- // logEndpoint(this.bridgedDevice!);
1201
+ zigbeeDevice.logPropertyMap();
1202
+ // Add command handlers
1203
+ zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
1204
+ zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} `);
1205
+ // logEndpoint(zigbeeDevice.bridgedDevice!);
986
1206
  });
987
- if (this.bridgedDevice.hasClusterServer(OnOff.Complete) || this.hasEndpoints) {
988
- this.bridgedDevice.addCommandHandler('on', async (data) => {
989
- if (!data.endpoint.number)
990
- return;
991
- this.log.debug(`Command on called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`);
1207
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
1208
+ for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
1209
+ if (zigbeeDevice.platform.matterbridge.edge && child.hasClusterServer(OnOffCluster)) {
1210
+ child.addCommandHandler('on', async (data) => {
1211
+ zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1212
+ const payload = {};
1213
+ payload['state_' + data.endpoint.uniqueStorageKey] = 'ON';
1214
+ zigbeeDevice.publishCommand('on', device.friendly_name, payload);
1215
+ });
1216
+ child.addCommandHandler('off', async (data) => {
1217
+ zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1218
+ const payload = {};
1219
+ payload['state_' + data.endpoint.uniqueStorageKey] = 'OFF';
1220
+ zigbeeDevice.publishCommand('off', device.friendly_name, payload);
1221
+ });
1222
+ child.addCommandHandler('toggle', async (data) => {
1223
+ zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1224
+ const payload = {};
1225
+ payload['state_' + data.endpoint.uniqueStorageKey] = 'TOGGLE';
1226
+ zigbeeDevice.publishCommand('toggle', device.friendly_name, payload);
1227
+ });
1228
+ }
1229
+ }
1230
+ zigbeeDevice.bridgedDevice.addCommandHandler('on', async (data) => {
1231
+ zigbeeDevice.log.debug(`Command on called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
992
1232
  const payload = {};
993
- const label = data.endpoint.uniqueStorageKey;
1233
+ const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
994
1234
  if (label === undefined)
995
1235
  payload['state'] = 'ON';
996
1236
  else
997
1237
  payload['state_' + label] = 'ON';
998
- this.publishCommand('on', device.friendly_name, payload);
1238
+ zigbeeDevice.publishCommand('on', device.friendly_name, payload);
999
1239
  });
1000
- this.bridgedDevice.addCommandHandler('off', async (data) => {
1001
- if (!data.endpoint.number)
1002
- return;
1003
- this.log.debug(`Command off called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`);
1240
+ zigbeeDevice.bridgedDevice.addCommandHandler('off', async (data) => {
1241
+ zigbeeDevice.log.debug(`Command off called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1004
1242
  const payload = {};
1005
- const label = data.endpoint.uniqueStorageKey;
1243
+ const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1006
1244
  if (label === undefined)
1007
1245
  payload['state'] = 'OFF';
1008
1246
  else
1009
1247
  payload['state_' + label] = 'OFF';
1010
- this.publishCommand('off', device.friendly_name, payload);
1248
+ zigbeeDevice.publishCommand('off', device.friendly_name, payload);
1011
1249
  });
1012
- this.bridgedDevice.addCommandHandler('toggle', async (data) => {
1013
- if (!data.endpoint.number)
1014
- return;
1015
- this.log.debug(`Command toggle called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} onOff: ${data.attributes.onOff.getLocal()}`);
1250
+ zigbeeDevice.bridgedDevice.addCommandHandler('toggle', async (data) => {
1251
+ zigbeeDevice.log.debug(`Command toggle called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number}`);
1016
1252
  const payload = {};
1017
- const label = data.endpoint.uniqueStorageKey;
1253
+ const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1018
1254
  if (label === undefined)
1019
1255
  payload['state'] = 'TOGGLE';
1020
1256
  else
1021
1257
  payload['state_' + label] = 'TOGGLE';
1022
- this.publishCommand('toggle', device.friendly_name, payload);
1258
+ zigbeeDevice.publishCommand('toggle', device.friendly_name, payload);
1023
1259
  });
1024
1260
  }
1025
- if (this.bridgedDevice.hasClusterServer(LevelControl.Complete) || this.hasEndpoints) {
1026
- this.bridgedDevice.addCommandHandler('moveToLevel', async (data) => {
1027
- if (!data.endpoint.number)
1028
- return;
1029
- this.log.debug(`Command moveToLevel called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime} attributes: ${data.attributes.currentLevel.getLocal()}`);
1261
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(LevelControlCluster.id) || zigbeeDevice.hasEndpoints) {
1262
+ for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
1263
+ if (zigbeeDevice.platform.matterbridge.edge && child.hasClusterServer(LevelControlCluster)) {
1264
+ child.addCommandHandler('moveToLevel', async (data) => {
1265
+ 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}`);
1266
+ const payload = {};
1267
+ payload['brightness_' + data.endpoint.uniqueStorageKey] = data.request.level;
1268
+ if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1269
+ payload['transition'] = Math.round(data.request.transitionTime / 10);
1270
+ zigbeeDevice.publishCommand('moveToLevel', device.friendly_name, payload);
1271
+ });
1272
+ child.addCommandHandler('moveToLevelWithOnOff', async (data) => {
1273
+ zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1274
+ const payload = {};
1275
+ payload['brightness_' + data.endpoint.uniqueStorageKey] = data.request.level;
1276
+ if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1277
+ payload['transition'] = Math.round(data.request.transitionTime / 10);
1278
+ zigbeeDevice.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload);
1279
+ });
1280
+ }
1281
+ }
1282
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToLevel', async (data) => {
1283
+ 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}`);
1030
1284
  const payload = {};
1031
- const label = data.endpoint.uniqueStorageKey;
1285
+ const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1032
1286
  if (label === undefined)
1033
1287
  payload['brightness'] = data.request.level;
1034
1288
  else
1035
1289
  payload['brightness_' + label] = data.request.level;
1036
- if (this.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1290
+ if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1037
1291
  payload['transition'] = Math.round(data.request.transitionTime / 10);
1038
- this.publishCommand('moveToLevel', device.friendly_name, payload);
1292
+ zigbeeDevice.publishCommand('moveToLevel', device.friendly_name, payload);
1039
1293
  });
1040
- this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async (data) => {
1041
- if (!data.endpoint.number)
1042
- return;
1043
- this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime} attributes: ${data.attributes.currentLevel.getLocal()}`);
1294
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async (data) => {
1295
+ zigbeeDevice.log.debug(`Command moveToLevelWithOnOff called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request: ${data.request.level} transition: ${data.request.transitionTime}`);
1044
1296
  const payload = {};
1045
- const label = data.endpoint.uniqueStorageKey;
1297
+ const label = zigbeeDevice.platform.matterbridge.edge ? undefined : data.endpoint.uniqueStorageKey;
1046
1298
  if (label === undefined)
1047
1299
  payload['brightness'] = data.request.level;
1048
1300
  else
1049
1301
  payload['brightness_' + label] = data.request.level;
1050
- if (this.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1302
+ if (zigbeeDevice.transition && data.request.transitionTime && data.request.transitionTime / 10 >= 1)
1051
1303
  payload['transition'] = Math.round(data.request.transitionTime / 10);
1052
- this.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload);
1304
+ zigbeeDevice.publishCommand('moveToLevelWithOnOff', device.friendly_name, payload);
1053
1305
  });
1054
1306
  }
1055
- if (this.bridgedDevice.hasClusterServer(ColorControl.Complete) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) {
1056
- this.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request: request, attributes: attributes }) => {
1057
- this.log.debug(`Command moveToColorTemperature called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds} attributes: ${attributes.colorTemperatureMireds?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
1058
- attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds);
1307
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(ColorControlCluster.id) && zigbeeDevice.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) {
1308
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request: request }) => {
1309
+ zigbeeDevice.log.debug(`Command moveToColorTemperature called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds}`);
1310
+ await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds, zigbeeDevice.log);
1059
1311
  const payload = { color_temp: request.colorTemperatureMireds };
1060
- if (this.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1312
+ if (zigbeeDevice.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1061
1313
  payload['transition'] = Math.round(request.transitionTime / 10);
1062
- this.publishCommand('moveToColorTemperature', device.friendly_name, payload);
1314
+ zigbeeDevice.publishCommand('moveToColorTemperature', device.friendly_name, payload);
1063
1315
  });
1064
1316
  }
1065
- if (this.bridgedDevice.hasClusterServer(ColorControl.Complete) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentHue')) {
1317
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(ColorControlCluster.id) && zigbeeDevice.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentHue')) {
1066
1318
  let lastRequestedHue = 0;
1067
1319
  let lastRequestedSaturation = 0;
1068
- this.bridgedDevice.addCommandHandler('moveToHue', async ({ request: request, attributes: attributes }) => {
1069
- this.log.debug(`Command moveToHue called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.hue} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
1070
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
1320
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToHue', async ({ request: request }) => {
1321
+ zigbeeDevice.log.debug(`Command moveToHue called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.hue}`);
1322
+ await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, zigbeeDevice.log);
1071
1323
  lastRequestedHue = request.hue;
1072
- this.colorTimeout = setTimeout(() => {
1073
- clearTimeout(this.colorTimeout);
1324
+ zigbeeDevice.colorTimeout = setTimeout(() => {
1325
+ clearTimeout(zigbeeDevice.colorTimeout);
1074
1326
  const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (lastRequestedSaturation / 254) * 100, 50);
1075
1327
  const payload = { color: { r: rgb.r, g: rgb.g, b: rgb.b } };
1076
- if (this.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1328
+ if (zigbeeDevice.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1077
1329
  payload['transition'] = Math.round(request.transitionTime / 10);
1078
- this.publishCommand('moveToHue', device.friendly_name, payload);
1330
+ zigbeeDevice.publishCommand('moveToHue', device.friendly_name, payload);
1079
1331
  }, 500);
1080
1332
  });
1081
- this.bridgedDevice.addCommandHandler('moveToSaturation', async ({ request: request, attributes: attributes }) => {
1082
- this.log.debug(`Command moveToSaturation called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
1083
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
1333
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToSaturation', async ({ request: request }) => {
1334
+ zigbeeDevice.log.debug(`Command moveToSaturation called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.saturation}`);
1335
+ await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, zigbeeDevice.log);
1084
1336
  lastRequestedSaturation = request.saturation;
1085
- this.colorTimeout = setTimeout(() => {
1086
- clearTimeout(this.colorTimeout);
1337
+ zigbeeDevice.colorTimeout = setTimeout(() => {
1338
+ clearTimeout(zigbeeDevice.colorTimeout);
1087
1339
  const rgb = color.hslColorToRgbColor((lastRequestedHue / 254) * 360, (request.saturation / 254) * 100, 50);
1088
1340
  const payload = { color: { r: rgb.r, g: rgb.g, b: rgb.b } };
1089
- if (this.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1341
+ if (zigbeeDevice.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1090
1342
  payload['transition'] = Math.round(request.transitionTime / 10);
1091
- this.publishCommand('moveToSaturation', device.friendly_name, payload);
1343
+ zigbeeDevice.publishCommand('moveToSaturation', device.friendly_name, payload);
1092
1344
  }, 500);
1093
1345
  });
1094
- this.bridgedDevice.addCommandHandler('moveToHueAndSaturation', async ({ request: request, attributes: attributes }) => {
1095
- this.log.debug(`Command moveToHueAndSaturation called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.hue}-${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
1096
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
1346
+ zigbeeDevice.bridgedDevice.addCommandHandler('moveToHueAndSaturation', async ({ request: request }) => {
1347
+ zigbeeDevice.log.debug(`Command moveToHueAndSaturation called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request: ${request.hue}-${request.saturation}`);
1348
+ await zigbeeDevice.bridgedDevice?.setAttribute(ColorControlCluster.id, 'colorMode', ColorControl.ColorMode.CurrentHueAndCurrentSaturation, zigbeeDevice.log);
1097
1349
  const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50);
1098
1350
  const payload = { color: { r: rgb.r, g: rgb.g, b: rgb.b } };
1099
- if (this.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1351
+ if (zigbeeDevice.transition && request.transitionTime && request.transitionTime / 10 >= 1)
1100
1352
  payload['transition'] = Math.round(request.transitionTime / 10);
1101
- this.publishCommand('moveToHueAndSaturation', device.friendly_name, payload);
1353
+ zigbeeDevice.publishCommand('moveToHueAndSaturation', device.friendly_name, payload);
1102
1354
  });
1103
1355
  }
1104
- if (this.bridgedDevice.hasClusterServer(WindowCovering.Complete)) {
1105
- this.bridgedDevice.addCommandHandler('upOrOpen', async (data) => {
1106
- this.log.debug(`Command upOrOpen called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
1107
- data.attributes.currentPositionLiftPercent100ths?.setLocal(0);
1108
- data.attributes.targetPositionLiftPercent100ths?.setLocal(0);
1109
- this.publishCommand('upOrOpen', device.friendly_name, { state: 'OPEN' });
1356
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(WindowCoveringCluster.id)) {
1357
+ zigbeeDevice.bridgedDevice.addCommandHandler('upOrOpen', async () => {
1358
+ zigbeeDevice.log.debug(`Command upOrOpen called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1359
+ if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1360
+ zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 0, zigbeeDevice.log);
1361
+ else
1362
+ await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(0);
1363
+ zigbeeDevice.publishCommand('upOrOpen', device.friendly_name, { state: 'OPEN' });
1110
1364
  });
1111
- this.bridgedDevice.addCommandHandler('downOrClose', async (data) => {
1112
- this.log.debug(`Command downOrClose called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
1113
- data.attributes.currentPositionLiftPercent100ths?.setLocal(10000);
1114
- data.attributes.targetPositionLiftPercent100ths?.setLocal(10000);
1115
- this.publishCommand('downOrClose', device.friendly_name, { state: 'CLOSE' });
1365
+ zigbeeDevice.bridgedDevice.addCommandHandler('downOrClose', async () => {
1366
+ zigbeeDevice.log.debug(`Command downOrClose called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1367
+ if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1368
+ zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', 10000, zigbeeDevice.log);
1369
+ else
1370
+ await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(10000);
1371
+ zigbeeDevice.publishCommand('downOrClose', device.friendly_name, { state: 'CLOSE' });
1116
1372
  });
1117
- this.bridgedDevice.addCommandHandler('stopMotion', async (data) => {
1118
- this.log.debug(`Command stopMotion called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.operationalStatus?.getLocal()}`);
1119
- const liftPercent100thsValue = data.attributes.currentPositionLiftPercent100ths?.getLocal();
1120
- if (liftPercent100thsValue) {
1121
- data.attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
1122
- data.attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
1123
- }
1124
- data.attributes.operationalStatus?.setLocal({ global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped });
1125
- this.publishCommand('stopMotion', device.friendly_name, { state: 'STOP' });
1373
+ zigbeeDevice.bridgedDevice.addCommandHandler('stopMotion', async () => {
1374
+ zigbeeDevice.log.debug(`Command stopMotion called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`);
1375
+ await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAsCurrentAndStopped();
1376
+ zigbeeDevice.publishCommand('stopMotion', device.friendly_name, { state: 'STOP' });
1126
1377
  });
1127
- this.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue }, attributes }) => {
1128
- this.log.debug(`Command goToLiftPercentage called for ${this.ien}${device.friendly_name}${rs}${db} liftPercent100thsValue: ${liftPercent100thsValue}`);
1129
- this.log.debug(`Command goToLiftPercentage current: ${attributes.currentPositionLiftPercent100ths?.getLocal()} target: ${attributes.targetPositionLiftPercent100ths?.getLocal()}`);
1130
- // attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
1131
- attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
1132
- this.publishCommand('goToLiftPercentage', device.friendly_name, { position: 100 - liftPercent100thsValue / 100 });
1378
+ zigbeeDevice.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue } }) => {
1379
+ zigbeeDevice.log.debug(`Command goToLiftPercentage called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request liftPercent100thsValue: ${liftPercent100thsValue}`);
1380
+ if (zigbeeDevice.isDevice && zigbeeDevice.propertyMap.has('position'))
1381
+ zigbeeDevice.bridgedDevice?.setAttribute(WindowCoveringCluster.id, 'targetPositionLiftPercent100ths', liftPercent100thsValue, zigbeeDevice.log);
1382
+ else
1383
+ await zigbeeDevice.bridgedDevice?.setWindowCoveringTargetAndCurrentPosition(liftPercent100thsValue);
1384
+ zigbeeDevice.publishCommand('goToLiftPercentage', device.friendly_name, { position: liftPercent100thsValue / 100 });
1133
1385
  });
1134
1386
  }
1135
- if (this.bridgedDevice.hasClusterServer(DoorLock.Complete)) {
1136
- this.bridgedDevice.addCommandHandler('lockDoor', async ({ request: request, attributes: attributes }) => {
1137
- this.log.debug(`Command lockDoor called for ${this.ien}${device.friendly_name}${rs}${db}`, request);
1138
- attributes.lockState?.setLocal(DoorLock.LockState.Locked);
1139
- if (!this.isRouter)
1140
- this.publishCommand('lockDoor', device.friendly_name, { state: 'LOCK' });
1141
- else
1142
- this.publishCommand('permit_join: false', 'bridge/request/permit_join', { value: false });
1387
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(DoorLockCluster.id)) {
1388
+ zigbeeDevice.bridgedDevice.addCommandHandler('lockDoor', async ({ request: request }) => {
1389
+ zigbeeDevice.log.debug(`Command lockDoor called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`, request);
1390
+ await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Locked, zigbeeDevice.log);
1391
+ zigbeeDevice.publishCommand('lockDoor', device.friendly_name, { state: 'LOCK' });
1143
1392
  });
1144
- this.bridgedDevice.addCommandHandler('unlockDoor', async ({ request: request, attributes: attributes }) => {
1145
- this.log.debug(`Command unlockDoor called for ${this.ien}${device.friendly_name}${rs}${db}`, request);
1146
- attributes.lockState?.setLocal(DoorLock.LockState.Unlocked);
1147
- if (!this.isRouter)
1148
- this.publishCommand('unlockDoor', device.friendly_name, { state: 'UNLOCK' });
1149
- else
1150
- this.publishCommand('permit_join: true', 'bridge/request/permit_join', { value: true });
1393
+ zigbeeDevice.bridgedDevice.addCommandHandler('unlockDoor', async ({ request: request }) => {
1394
+ zigbeeDevice.log.debug(`Command unlockDoor called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db}`, request);
1395
+ await zigbeeDevice.bridgedDevice?.setAttribute(DoorLockCluster.id, 'lockState', DoorLock.LockState.Unlocked, zigbeeDevice.log);
1396
+ zigbeeDevice.publishCommand('unlockDoor', device.friendly_name, { state: 'UNLOCK' });
1151
1397
  });
1152
1398
  }
1153
- if (this.bridgedDevice.hasClusterServer(Thermostat.Complete)) {
1154
- this.bridgedDevice.addCommandHandler('setpointRaiseLower', async ({ request: request, attributes: attributes }) => {
1155
- this.log.debug(`Command setpointRaiseLower called for ${this.ien}${device.friendly_name}${rs}${db}`, request);
1156
- if (request.mode === Thermostat.SetpointRaiseLowerMode.Heat && attributes.occupiedHeatingSetpoint) {
1157
- const setpoint = Math.round(attributes.occupiedHeatingSetpoint.getLocal() / 100 + request.amount / 10);
1158
- if (this.propertyMap.has('current_heating_setpoint')) {
1159
- this.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1160
- this.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_heating_setpoint: setpoint }));
1399
+ if (zigbeeDevice.bridgedDevice.getClusterServerById(ThermostatCluster.id)) {
1400
+ zigbeeDevice.bridgedDevice.addCommandHandler('setpointRaiseLower', async ({ request: request }) => {
1401
+ zigbeeDevice.log.debug(`Command setpointRaiseLower called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} request:`, request);
1402
+ if (request.mode === Thermostat.SetpointRaiseLowerMode.Heat || request.mode === Thermostat.SetpointRaiseLowerMode.Both) {
1403
+ const t = zigbeeDevice.bridgedDevice?.getAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', zigbeeDevice.log);
1404
+ const setpoint = Math.round(t / 100 + request.amount / 10);
1405
+ if (zigbeeDevice.propertyMap.has('current_heating_setpoint')) {
1406
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: setpoint });
1407
+ zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_heating_setpoint: setpoint }));
1161
1408
  }
1162
- else if (this.propertyMap.has('occupied_heating_setpoint')) {
1163
- this.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: setpoint });
1164
- this.log.debug('Command setpointRaiseLower sent:', debugStringify({ occupied_heating_setpoint: setpoint }));
1409
+ else if (zigbeeDevice.propertyMap.has('occupied_heating_setpoint')) {
1410
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: setpoint });
1411
+ zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ occupied_heating_setpoint: setpoint }));
1165
1412
  }
1166
1413
  }
1167
- if (request.mode === Thermostat.SetpointRaiseLowerMode.Cool && attributes.occupiedCoolingSetpoint) {
1168
- const setpoint = Math.round(attributes.occupiedCoolingSetpoint.getLocal() / 100 + request.amount / 10);
1169
- if (this.propertyMap.has('current_cooling_setpoint')) {
1170
- this.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: setpoint });
1171
- this.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_cooling_setpoint: setpoint }));
1414
+ if (request.mode === Thermostat.SetpointRaiseLowerMode.Cool || request.mode === Thermostat.SetpointRaiseLowerMode.Both) {
1415
+ const t = zigbeeDevice.bridgedDevice?.getAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', zigbeeDevice.log);
1416
+ const setpoint = Math.round(t / 100 + request.amount / 10);
1417
+ if (zigbeeDevice.propertyMap.has('current_cooling_setpoint')) {
1418
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: setpoint });
1419
+ zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ current_cooling_setpoint: setpoint }));
1172
1420
  }
1173
- else if (this.propertyMap.has('occupied_cooling_setpoint')) {
1174
- this.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: setpoint });
1175
- this.log.debug('Command setpointRaiseLower sent:', debugStringify({ occupied_cooling_setpoint: setpoint }));
1421
+ else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint')) {
1422
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: setpoint });
1423
+ zigbeeDevice.log.debug('Command setpointRaiseLower sent:', debugStringify({ occupied_cooling_setpoint: setpoint }));
1176
1424
  }
1177
1425
  }
1178
1426
  });
1179
- const thermostat = this.bridgedDevice.getClusterServer(ThermostatCluster.with(Thermostat.Feature.Heating, Thermostat.Feature.Cooling, Thermostat.Feature.AutoMode));
1427
+ const thermostat = zigbeeDevice.bridgedDevice.getClusterServerById(ThermostatCluster.id);
1180
1428
  if (thermostat) {
1181
- thermostat.subscribeSystemModeAttribute(async (value) => {
1182
- this.log.debug(`Subscribe systemMode called for ${this.ien}${device.friendly_name}${rs}${db} with:`, value);
1429
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', async (value) => {
1430
+ zigbeeDevice.log.debug(`Subscribe systemMode called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1183
1431
  const system_mode = value === Thermostat.SystemMode.Off ? 'off' : value === Thermostat.SystemMode.Heat ? 'heat' : 'cool';
1184
- this.publishCommand('SystemMode', device.friendly_name, { system_mode });
1185
- this.noUpdate = true;
1186
- this.thermostatTimeout = setTimeout(() => {
1187
- this.noUpdate = false;
1188
- }, 10 * 1000);
1189
- });
1190
- if (thermostat.subscribeOccupiedHeatingSetpointAttribute)
1191
- thermostat.subscribeOccupiedHeatingSetpointAttribute(async (value) => {
1192
- this.log.debug(`Subscribe occupiedHeatingSetpoint called for ${this.ien}${device.friendly_name}${rs}${db} with:`, value);
1193
- if (this.propertyMap.has('current_heating_setpoint'))
1194
- this.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
1195
- else if (this.propertyMap.has('occupied_heating_setpoint'))
1196
- this.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: Math.round(value / 100) });
1197
- if (this.bridgedDevice)
1198
- this.noUpdate = true;
1199
- this.thermostatTimeout = setTimeout(() => {
1200
- if (this.bridgedDevice)
1201
- this.noUpdate = false;
1202
- }, 10 * 1000);
1203
- });
1204
- if (thermostat.subscribeOccupiedCoolingSetpointAttribute)
1205
- thermostat.subscribeOccupiedCoolingSetpointAttribute(async (value) => {
1206
- this.log.debug(`Subscribe occupiedCoolingSetpoint called for ${this.ien}${device.friendly_name}${rs}${db} with:`, value);
1207
- if (this.propertyMap.has('current_cooling_setpoint'))
1208
- this.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: Math.round(value / 100) });
1209
- else if (this.propertyMap.has('occupied_cooling_setpoint'))
1210
- this.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: Math.round(value / 100) });
1211
- if (this.bridgedDevice)
1212
- this.noUpdate = true;
1213
- this.thermostatTimeout = setTimeout(() => {
1214
- if (this.bridgedDevice)
1215
- this.noUpdate = false;
1216
- }, 10 * 1000);
1217
- });
1432
+ zigbeeDevice.publishCommand('SystemMode', device.friendly_name, { system_mode });
1433
+ zigbeeDevice.noUpdate = true;
1434
+ zigbeeDevice.thermostatTimeout = setTimeout(() => {
1435
+ zigbeeDevice.noUpdate = false;
1436
+ }, 5 * 1000);
1437
+ }, zigbeeDevice.log);
1438
+ if (thermostat.attributes.occupiedHeatingSetpoint)
1439
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedHeatingSetpoint', async (value) => {
1440
+ zigbeeDevice.log.debug(`Subscribe occupiedHeatingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1441
+ if (zigbeeDevice.propertyMap.has('current_heating_setpoint'))
1442
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { current_heating_setpoint: Math.round(value / 100) });
1443
+ else if (zigbeeDevice.propertyMap.has('occupied_heating_setpoint'))
1444
+ zigbeeDevice.publishCommand('OccupiedHeatingSetpoint', device.friendly_name, { occupied_heating_setpoint: Math.round(value / 100) });
1445
+ zigbeeDevice.noUpdate = true;
1446
+ zigbeeDevice.thermostatTimeout = setTimeout(() => {
1447
+ zigbeeDevice.noUpdate = false;
1448
+ }, 5 * 1000);
1449
+ }, zigbeeDevice.log);
1450
+ if (thermostat.attributes.occupiedCoolingSetpoint)
1451
+ zigbeeDevice.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'occupiedCoolingSetpoint', async (value) => {
1452
+ zigbeeDevice.log.debug(`Subscribe occupiedCoolingSetpoint called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} with:`, value);
1453
+ if (zigbeeDevice.propertyMap.has('current_cooling_setpoint'))
1454
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { current_cooling_setpoint: Math.round(value / 100) });
1455
+ else if (zigbeeDevice.propertyMap.has('occupied_cooling_setpoint'))
1456
+ zigbeeDevice.publishCommand('OccupiedCoolingSetpoint', device.friendly_name, { occupied_cooling_setpoint: Math.round(value / 100) });
1457
+ zigbeeDevice.noUpdate = true;
1458
+ zigbeeDevice.thermostatTimeout = setTimeout(() => {
1459
+ zigbeeDevice.noUpdate = false;
1460
+ }, 5 * 1000);
1461
+ }, zigbeeDevice.log);
1218
1462
  }
1219
1463
  }
1464
+ return zigbeeDevice;
1220
1465
  }
1221
1466
  }
1222
1467
  //# sourceMappingURL=entity.js.map