matterbridge-zigbee2mqtt 2.3.0-dev.4 → 2.3.0
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/CHANGELOG.md +1 -1
- package/dist/entity.d.ts +492 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/entity.js +251 -10
- package/dist/entity.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/payloadTypes.d.ts +25 -0
- package/dist/payloadTypes.d.ts.map +1 -0
- package/dist/payloadTypes.js +23 -0
- package/dist/payloadTypes.js.map +1 -0
- package/dist/platform.d.ts +88 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +48 -1
- package/dist/platform.js.map +1 -0
- package/dist/zigbee2mqtt.d.ts +182 -0
- package/dist/zigbee2mqtt.d.ts.map +1 -0
- package/dist/zigbee2mqtt.js +272 -22
- package/dist/zigbee2mqtt.js.map +1 -0
- package/dist/zigbee2mqttTypes.d.ts +350 -0
- package/dist/zigbee2mqttTypes.d.ts.map +1 -0
- package/dist/zigbee2mqttTypes.js +23 -0
- package/dist/zigbee2mqttTypes.js.map +1 -0
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -2
- package/tsconfig.production.json +0 -13
package/dist/entity.js
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the classes ZigbeeEntity, ZigbeeDevice and ZigbeeGroup.
|
|
3
|
+
*
|
|
4
|
+
* @file entity.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @date 2023-12-29
|
|
7
|
+
* @version 3.1.0
|
|
8
|
+
*
|
|
9
|
+
* Copyright 2023, 2024, 2025 Luca Liguori.
|
|
10
|
+
*
|
|
11
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
12
|
+
* you may not use this file except in compliance with the License.
|
|
13
|
+
* You may obtain a copy of the License at
|
|
14
|
+
*
|
|
15
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
*
|
|
17
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
+
* See the License for the specific language governing permissions and
|
|
21
|
+
* limitations under the License. *
|
|
22
|
+
*/
|
|
1
23
|
import { MatterbridgeDevice, airQualitySensor, colorTemperatureSwitch, dimmableSwitch, onOffSwitch, OnOff, LevelControl, ColorControl, ColorControlCluster, TemperatureMeasurement, BooleanState, RelativeHumidityMeasurement, PressureMeasurement, OccupancySensing, IlluminanceMeasurement, PowerSource, ClusterId, WindowCovering, DoorLock, BridgedDeviceBasicInformation, ThermostatCluster, Thermostat, getClusterNameById, powerSource, bridgedNode, AirQuality, TotalVolatileOrganicCompoundsConcentrationMeasurement, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, FormaldehydeConcentrationMeasurement, Pm1ConcentrationMeasurement, Pm25ConcentrationMeasurement, Pm10ConcentrationMeasurement, electricalSensor, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, onOffLight, dimmableLight, colorTemperatureLight, onOffOutlet, SwitchesTag, NumberTag, coverDevice, thermostatDevice, MatterbridgeEndpoint, dimmableOutlet, doorLockDevice, occupancySensor, lightSensor, contactSensor, temperatureSensor, humiditySensor, pressureSensor, genericSwitch, OnOffCluster, LevelControlCluster, WindowCoveringCluster, DoorLockCluster, } from 'matterbridge';
|
|
2
24
|
import { AnsiLogger, gn, dn, ign, idn, rs, db, debugStringify, hk, zb, or, nf, CYAN, er, YELLOW } from 'matterbridge/logger';
|
|
3
25
|
import { deepCopy, deepEqual, isValidNumber } from 'matterbridge/utils';
|
|
4
26
|
import * as color from 'matterbridge/utils';
|
|
5
27
|
import EventEmitter from 'events';
|
|
6
28
|
import { hostname } from 'os';
|
|
29
|
+
/**
|
|
30
|
+
* Represents a Zigbee entity: a group or a device.
|
|
31
|
+
*
|
|
32
|
+
* @class
|
|
33
|
+
* @extends {EventEmitter}
|
|
34
|
+
*/
|
|
7
35
|
export class ZigbeeEntity extends EventEmitter {
|
|
8
36
|
log;
|
|
9
37
|
serial = '';
|
|
@@ -23,6 +51,7 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
23
51
|
ignoreFeatures = [];
|
|
24
52
|
transition = false;
|
|
25
53
|
propertyMap = new Map();
|
|
54
|
+
// We save the tag list and device types and cluster servers and clients to avoid multiple lookups
|
|
26
55
|
mutableDevice = new Map();
|
|
27
56
|
colorTimeout = undefined;
|
|
28
57
|
thermostatTimeout = undefined;
|
|
@@ -30,6 +59,12 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
30
59
|
hasEndpoints = false;
|
|
31
60
|
isRouter = false;
|
|
32
61
|
noUpdate = false;
|
|
62
|
+
/**
|
|
63
|
+
* Creates an instance of ZigbeeEntity.
|
|
64
|
+
*
|
|
65
|
+
* @param {ZigbeePlatform} platform - The Zigbee platform instance.
|
|
66
|
+
* @param {BridgeDevice | BridgeGroup} entity - The bridge device or group instance received from zigbee2mqtt.
|
|
67
|
+
*/
|
|
33
68
|
constructor(platform, entity) {
|
|
34
69
|
super();
|
|
35
70
|
this.platform = platform;
|
|
@@ -47,22 +82,26 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
47
82
|
this.en = gn;
|
|
48
83
|
this.ien = ign;
|
|
49
84
|
}
|
|
50
|
-
this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4
|
|
85
|
+
this.log = new AnsiLogger({ logName: this.entityName, logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: platform.debugEnabled ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */ });
|
|
51
86
|
this.log.debug(`Created MatterEntity: ${this.entityName}`);
|
|
52
87
|
this.platform.z2m.on('MESSAGE-' + this.entityName, (payload) => {
|
|
88
|
+
// Check if the message is a duplicate that can be ingored cause only linkquality and last_seen have changed (action is always passed)
|
|
53
89
|
const now = Date.now();
|
|
54
90
|
if (now - this.lastSeen < 1000 * 60 && deepEqual(this.lastPayload, payload, ['linkquality', 'last_seen', ...this.ignoreFeatures]) && !Object.prototype.hasOwnProperty.call(this.lastPayload, 'action')) {
|
|
55
91
|
this.log.debug(`Skipping not changed ${platform.z2mDevicesRegistered ? 'MQTT message' : 'State update'} for accessory ${this.entityName}`);
|
|
56
92
|
return;
|
|
57
93
|
}
|
|
58
94
|
this.lastSeen = Date.now();
|
|
95
|
+
// Check and deep copy the payload
|
|
59
96
|
if (deepEqual(this.lastPayload, payload, this.ignoreFeatures))
|
|
60
97
|
return;
|
|
61
98
|
this.lastPayload = deepCopy(payload);
|
|
62
99
|
if (Object.prototype.hasOwnProperty.call(this.lastPayload, 'action'))
|
|
63
100
|
delete this.lastPayload.action;
|
|
101
|
+
// Remove each key in ignoreFeatures from the payload copy
|
|
64
102
|
for (const key of this.ignoreFeatures) {
|
|
65
103
|
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
66
105
|
delete payload[key];
|
|
67
106
|
this.log.debug(`Removed key ${CYAN}${key}${db} from payload`);
|
|
68
107
|
}
|
|
@@ -76,19 +115,24 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
76
115
|
return;
|
|
77
116
|
}
|
|
78
117
|
this.log.info(`${db}${platform.z2mDevicesRegistered ? 'MQTT message' : 'State update'} for device ${this.ien}${this.entityName}${rs}${db} payload: ${debugStringify(payload)}`);
|
|
118
|
+
// Parse the payload and update the accessory
|
|
79
119
|
Object.entries(payload).forEach(([key, value]) => {
|
|
120
|
+
// Skip null and undefined values
|
|
80
121
|
if (value === undefined || value === null)
|
|
81
122
|
return;
|
|
82
123
|
if (this.bridgedDevice === undefined || this.noUpdate)
|
|
83
124
|
return;
|
|
125
|
+
// Modify voltage to battery_voltage
|
|
84
126
|
if (key === 'voltage' && this.isDevice && this.device?.power_source === 'Battery')
|
|
85
127
|
key = 'battery_voltage';
|
|
128
|
+
// Modify illuminance and illuminance_lux
|
|
86
129
|
if (key === 'illuminance' && this.isDevice && this.device && this.device.definition && ['RTCGQ14LM'].includes(this.device.definition.model)) {
|
|
87
130
|
key = 'illuminance_lux';
|
|
88
131
|
}
|
|
89
132
|
if (key === 'illuminance' && typeof value === 'number' && this.isDevice && this.device && this.device.definition && ['ZG-204ZL', 'ZG-205Z/A'].includes(this.device.definition.model)) {
|
|
90
133
|
value = value * 10;
|
|
91
134
|
}
|
|
135
|
+
// Lookup the property in the propertyMap and ZigbeeToMatter table
|
|
92
136
|
const propertyMap = this.propertyMap.get(key);
|
|
93
137
|
if (propertyMap) {
|
|
94
138
|
this.log.debug(`Payload entry ${CYAN}${key}${db} => name: ${CYAN}${propertyMap.name}${db} type: ${CYAN}${propertyMap.type === '' ? 'generic' : propertyMap.type}${db} ` +
|
|
@@ -108,9 +152,11 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
108
152
|
}
|
|
109
153
|
else
|
|
110
154
|
this.log.debug(`*Payload entry ${CYAN}${key}${db} not found in propertyMap`);
|
|
155
|
+
// Switch actions on the endpoints
|
|
111
156
|
if (key === 'action' && value !== '') {
|
|
112
157
|
const propertyMap = this.propertyMap.get(('action_' + value));
|
|
113
158
|
if (propertyMap) {
|
|
159
|
+
// this.log.debug(`Payload entry ${CYAN}${value}${db} => name: ${CYAN}${propertyMap.name}${db} endpoint: ${CYAN}${propertyMap.endpoint}${db} action: ${CYAN}${propertyMap.action}${db}`);
|
|
114
160
|
const child = this.bridgedDevice.getChildEndpointByName(propertyMap.endpoint);
|
|
115
161
|
if (child && child.number)
|
|
116
162
|
this.bridgedDevice.triggerSwitchEvent(propertyMap.action, this.log, child);
|
|
@@ -118,6 +164,9 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
118
164
|
else
|
|
119
165
|
this.log.debug(`*Payload entry ${CYAN}${('action_' + value)}${db} not found in propertyMap`);
|
|
120
166
|
}
|
|
167
|
+
// WindowCovering
|
|
168
|
+
// Zigbee2MQTT cover: 0 = open, 100 = closed
|
|
169
|
+
// Matter WindowCovering: 0 = open 10000 = closed
|
|
121
170
|
if (key === 'position' && this.isDevice && isValidNumber(value, 0, 100)) {
|
|
122
171
|
this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'currentPositionLiftPercent100ths', value * 100);
|
|
123
172
|
}
|
|
@@ -138,10 +187,12 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
138
187
|
this.updateAttributeIfChanged(this.bridgedDevice, undefined, WindowCovering.Cluster.id, 'targetPositionLiftPercent100ths', position);
|
|
139
188
|
}
|
|
140
189
|
}
|
|
190
|
+
// ColorControl colorTemperatureMired and colorMode
|
|
141
191
|
if (key === 'color_temp' && 'color_mode' in payload && payload['color_mode'] === 'color_temp') {
|
|
142
192
|
this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorTemperatureMireds', Math.max(147, Math.min(500, typeof value === 'number' ? value : 0)));
|
|
143
193
|
this.updateAttributeIfChanged(this.bridgedDevice, undefined, ColorControl.Cluster.id, 'colorMode', ColorControl.ColorMode.ColorTemperatureMireds);
|
|
144
194
|
}
|
|
195
|
+
// ColorControl currentHue, currentSaturation and colorMode
|
|
145
196
|
if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'xy') {
|
|
146
197
|
const { x, y } = value;
|
|
147
198
|
const hsl = color.xyToHsl(x, y);
|
|
@@ -169,6 +220,13 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
169
220
|
}
|
|
170
221
|
});
|
|
171
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
|
+
*/
|
|
172
230
|
destroy() {
|
|
173
231
|
if (this.colorTimeout)
|
|
174
232
|
clearTimeout(this.colorTimeout);
|
|
@@ -177,6 +235,22 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
177
235
|
clearTimeout(this.thermostatTimeout);
|
|
178
236
|
this.thermostatTimeout = undefined;
|
|
179
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
|
+
*/
|
|
180
254
|
async createMutableDevice(definition, options, debug) {
|
|
181
255
|
if (this.platform.matterbridge.edge === true) {
|
|
182
256
|
this.bridgedDevice = (await MatterbridgeEndpoint.loadInstance(definition, options, debug));
|
|
@@ -189,6 +263,7 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
189
263
|
getBridgedDeviceBasicInformation() {
|
|
190
264
|
if (!this.bridgedDevice)
|
|
191
265
|
throw new Error('No bridged device');
|
|
266
|
+
// Add BridgedDeviceBasicInformation cluster and device type
|
|
192
267
|
const softwareVersion = parseInt(this.platform.z2mBridgeInfo?.version || '1');
|
|
193
268
|
const softwareVersionString = `${this.platform.z2mBridgeInfo?.version} (commit ${this.platform.z2mBridgeInfo?.commit})`;
|
|
194
269
|
const hardwareVersion = parseInt(this.platform.matterbridge.matterbridgeVersion || '1');
|
|
@@ -206,6 +281,7 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
206
281
|
addBridgedDeviceBasicInformation() {
|
|
207
282
|
if (!this.bridgedDevice)
|
|
208
283
|
throw new Error('No bridged device');
|
|
284
|
+
// Add BridgedDeviceBasicInformation cluster and device type
|
|
209
285
|
this.bridgedDevice.addDeviceType(bridgedNode);
|
|
210
286
|
this.bridgedDevice.addClusterServer(this.getBridgedDeviceBasicInformation());
|
|
211
287
|
return this.bridgedDevice;
|
|
@@ -226,13 +302,26 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
226
302
|
addPowerSource() {
|
|
227
303
|
if (!this.bridgedDevice)
|
|
228
304
|
throw new Error('No bridged device');
|
|
305
|
+
// Add PowerSource device type and cluster
|
|
229
306
|
this.bridgedDevice.addDeviceType(powerSource);
|
|
230
307
|
this.bridgedDevice.addClusterServer(this.getPowerSource());
|
|
231
308
|
return this.bridgedDevice;
|
|
232
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
|
+
*/
|
|
233
321
|
verifyMutableDevice(endpoint) {
|
|
234
322
|
if (!endpoint)
|
|
235
323
|
return false;
|
|
324
|
+
// Verify that all required server clusters are present in the main endpoint and in the child endpoints
|
|
236
325
|
for (const deviceType of endpoint.getDeviceTypes()) {
|
|
237
326
|
for (const clusterId of deviceType.requiredServerClusters) {
|
|
238
327
|
if (!endpoint.getClusterServerById(clusterId)) {
|
|
@@ -241,6 +330,7 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
241
330
|
}
|
|
242
331
|
}
|
|
243
332
|
}
|
|
333
|
+
// Verify that all required server clusters are present in the child endpoints
|
|
244
334
|
for (const childEndpoint of endpoint.getChildEndpoints()) {
|
|
245
335
|
for (const deviceType of childEndpoint.getDeviceTypes()) {
|
|
246
336
|
for (const clusterId of deviceType.requiredServerClusters) {
|
|
@@ -253,6 +343,17 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
253
343
|
}
|
|
254
344
|
return true;
|
|
255
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
|
+
*/
|
|
256
357
|
async configure() {
|
|
257
358
|
if (this.bridgedDevice?.getClusterServerById(WindowCovering.Cluster.id)) {
|
|
258
359
|
this.log.info(`Configuring ${this.bridgedDevice?.deviceName} WindowCovering cluster`);
|
|
@@ -269,6 +370,22 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
269
370
|
}
|
|
270
371
|
}
|
|
271
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
|
+
*/
|
|
272
389
|
updateAttributeIfChanged(deviceEndpoint, childEndpointName, clusterId, attributeName, value, lookup) {
|
|
273
390
|
if (childEndpointName && childEndpointName !== '') {
|
|
274
391
|
deviceEndpoint = this.bridgedDevice?.getChildEndpointByName(childEndpointName) ?? deviceEndpoint;
|
|
@@ -307,6 +424,18 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
307
424
|
this.log.error(`Error setting attribute ${hk}${getClusterNameById(ClusterId(clusterId))}${er}.${hk}${attributeName}${er} to ${value}: ${error}`);
|
|
308
425
|
}
|
|
309
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
|
+
*/
|
|
310
439
|
publishCommand(command, entityName, payload) {
|
|
311
440
|
this.log.debug(`executeCommand ${command} called for ${this.ien}${entityName}${rs}${db} payload: ${debugStringify(payload)}`);
|
|
312
441
|
if (entityName.startsWith('bridge/request')) {
|
|
@@ -316,7 +445,16 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
316
445
|
this.platform.publish(entityName, 'set', JSON.stringify(payload));
|
|
317
446
|
}
|
|
318
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 });
|
|
319
456
|
logPropertyMap() {
|
|
457
|
+
// Log properties
|
|
320
458
|
this.propertyMap.forEach((value, key) => {
|
|
321
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} ` +
|
|
322
460
|
`category ${CYAN}${value.category}${db} description ${CYAN}${value.description}${db} label ${CYAN}${value.label}${db} unit ${CYAN}${value.unit}${db} ` +
|
|
@@ -324,10 +462,33 @@ export class ZigbeeEntity extends EventEmitter {
|
|
|
324
462
|
});
|
|
325
463
|
}
|
|
326
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Represents a Zigbee group entity.
|
|
467
|
+
*
|
|
468
|
+
* @class
|
|
469
|
+
* @extends {ZigbeeEntity}
|
|
470
|
+
*/
|
|
327
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
|
+
*/
|
|
328
478
|
constructor(platform, group) {
|
|
329
479
|
super(platform, group);
|
|
330
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
|
+
*/
|
|
331
492
|
static async create(platform, group) {
|
|
332
493
|
const zigbeeGroup = new ZigbeeGroup(platform, group);
|
|
333
494
|
if (zigbeeGroup.platform.postfixHostname) {
|
|
@@ -347,19 +508,23 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
347
508
|
let isCover = false;
|
|
348
509
|
let isThermostat = false;
|
|
349
510
|
if (group.members.length === 0) {
|
|
511
|
+
// Create a virtual device for the empty group to use in automations
|
|
350
512
|
zigbeeGroup.log.debug(`Group: ${gn}${group.friendly_name}${rs}${db} is a ${CYAN}virtual${db} group`);
|
|
351
|
-
zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
|
|
513
|
+
zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([onOffSwitch], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
352
514
|
isSwitch = true;
|
|
353
515
|
zigbeeGroup.propertyMap.set('state', { name: 'state', type: 'switch', endpoint: '' });
|
|
354
516
|
}
|
|
355
517
|
else {
|
|
518
|
+
// Create a switch or light or outlet device for the group
|
|
356
519
|
group.members.forEach((member) => {
|
|
520
|
+
// const device = zigbeeGroup.platform.z2m.getDevice(member.ieee_address);
|
|
357
521
|
const device = zigbeeGroup.platform.z2mBridgeDevices?.find((device) => device.ieee_address === member.ieee_address);
|
|
358
522
|
if (!device)
|
|
359
523
|
return;
|
|
360
524
|
zigbeeGroup.log.debug(`Group ${gn}${group.friendly_name}${db}: member device ${dn}${device.friendly_name}${db}`);
|
|
361
525
|
device.definition?.exposes.forEach((expose) => {
|
|
362
526
|
if (expose.features) {
|
|
527
|
+
// Specific features with type
|
|
363
528
|
expose.features?.forEach((feature) => {
|
|
364
529
|
if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock') {
|
|
365
530
|
expose.type = 'child_lock';
|
|
@@ -389,6 +554,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
389
554
|
});
|
|
390
555
|
}
|
|
391
556
|
else {
|
|
557
|
+
// Generic features without type
|
|
392
558
|
zigbeeGroup.log.debug(`- generic type ${CYAN}${expose.type}${db} expose name ${CYAN}${expose.name}${db} property ${CYAN}${expose.property}${db}`);
|
|
393
559
|
}
|
|
394
560
|
});
|
|
@@ -434,14 +600,17 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
434
600
|
}
|
|
435
601
|
if (!deviceType)
|
|
436
602
|
return zigbeeGroup;
|
|
437
|
-
zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug");
|
|
603
|
+
zigbeeGroup.bridgedDevice = await zigbeeGroup.createMutableDevice([deviceType], { uniqueStorageKey: group.friendly_name }, zigbeeGroup.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
438
604
|
}
|
|
439
605
|
zigbeeGroup.addBridgedDeviceBasicInformation();
|
|
440
606
|
zigbeeGroup.addPowerSource();
|
|
441
607
|
zigbeeGroup.bridgedDevice.addRequiredClusterServers(zigbeeGroup.bridgedDevice);
|
|
608
|
+
// Verify the device
|
|
442
609
|
if (!zigbeeGroup.bridgedDevice || !zigbeeGroup.verifyMutableDevice(zigbeeGroup.bridgedDevice))
|
|
443
610
|
return zigbeeGroup;
|
|
611
|
+
// Log properties
|
|
444
612
|
zigbeeGroup.logPropertyMap();
|
|
613
|
+
// Add command handlers
|
|
445
614
|
if (isSwitch || isLight) {
|
|
446
615
|
if (isSwitch)
|
|
447
616
|
await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'switch');
|
|
@@ -449,6 +618,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
449
618
|
await zigbeeGroup.bridgedDevice.addFixedLabel('type', 'light');
|
|
450
619
|
zigbeeGroup.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
|
|
451
620
|
zigbeeGroup.log.warn(`Command identify called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
|
|
621
|
+
// logEndpoint(zigbeeGroup.bridgedDevice!);
|
|
452
622
|
});
|
|
453
623
|
zigbeeGroup.bridgedDevice.addCommandHandler('on', async () => {
|
|
454
624
|
zigbeeGroup.log.debug(`Command on called for ${zigbeeGroup.ien}${group.friendly_name}${rs}${db}`);
|
|
@@ -540,6 +710,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
540
710
|
zigbeeGroup.bridgedDevice.subscribeAttribute(ThermostatCluster.id, 'systemMode', (newValue, oldValue) => {
|
|
541
711
|
zigbeeGroup.bridgedDevice?.log.info(`Thermostat systemMode changed from ${oldValue} to ${newValue}`);
|
|
542
712
|
if (oldValue !== newValue) {
|
|
713
|
+
// Thermostat.SystemMode.Heat && newValue === Thermostat.SystemMode.Off
|
|
543
714
|
zigbeeGroup.bridgedDevice?.log.info(`Setting thermostat systemMode to ${newValue}`);
|
|
544
715
|
if (newValue === Thermostat.SystemMode.Off) {
|
|
545
716
|
zigbeeGroup.publishCommand('SystemMode', group.friendly_name, { system_mode: 'off' });
|
|
@@ -580,6 +751,7 @@ export class ZigbeeGroup extends ZigbeeEntity {
|
|
|
580
751
|
return zigbeeGroup;
|
|
581
752
|
}
|
|
582
753
|
}
|
|
754
|
+
// prettier-ignore
|
|
583
755
|
export const z2ms = [
|
|
584
756
|
{ type: 'switch', name: 'state', property: 'state', deviceType: onOffSwitch, cluster: OnOff.Cluster.id, attribute: 'onOff', converter: (value) => { return value === 'ON' ? true : false; } },
|
|
585
757
|
{ type: 'switch', name: 'brightness', property: 'brightness', deviceType: dimmableSwitch, cluster: LevelControl.Cluster.id, attribute: 'currentLevel', converter: (value) => { return Math.max(0, Math.min(254, value)); } },
|
|
@@ -639,19 +811,43 @@ export const z2ms = [
|
|
|
639
811
|
{ type: '', name: 'voltage', property: 'voltage', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'voltage', converter: (value) => { return value * 1000; } },
|
|
640
812
|
{ type: '', name: 'current', property: 'current', deviceType: electricalSensor, cluster: ElectricalPowerMeasurement.Cluster.id, attribute: 'activeCurrent', converter: (value) => { return value * 1000; } },
|
|
641
813
|
];
|
|
814
|
+
/**
|
|
815
|
+
* Represents a Zigbee device entity.
|
|
816
|
+
*
|
|
817
|
+
* @class
|
|
818
|
+
* @extends {ZigbeeEntity}
|
|
819
|
+
*/
|
|
642
820
|
export class ZigbeeDevice extends ZigbeeEntity {
|
|
821
|
+
/**
|
|
822
|
+
* Represents a Zigbee device entity.
|
|
823
|
+
*
|
|
824
|
+
* @class
|
|
825
|
+
* @extends {ZigbeeEntity}
|
|
826
|
+
*/
|
|
643
827
|
constructor(platform, device) {
|
|
644
828
|
super(platform, device);
|
|
645
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
|
+
*/
|
|
646
841
|
static async create(platform, device) {
|
|
647
842
|
const zigbeeDevice = new ZigbeeDevice(platform, device);
|
|
648
843
|
zigbeeDevice.serial = `${device.ieee_address}`;
|
|
649
844
|
if (zigbeeDevice.platform.postfixHostname) {
|
|
650
845
|
zigbeeDevice.serial = `${zigbeeDevice.serial}_${hostname}`.slice(0, 32);
|
|
651
846
|
}
|
|
847
|
+
// Set Coordinator and dedicated routers
|
|
652
848
|
if (device.friendly_name === 'Coordinator' || (device.model_id === 'ti.router' && device.manufacturer === 'TexasInstruments') || (device.model_id.startsWith('SLZB-') && device.manufacturer === 'SMLIGHT')) {
|
|
653
849
|
zigbeeDevice.isRouter = true;
|
|
654
|
-
zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
|
|
850
|
+
zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([doorLockDevice], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
655
851
|
zigbeeDevice.addBridgedDeviceBasicInformation();
|
|
656
852
|
zigbeeDevice.addPowerSource();
|
|
657
853
|
zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
|
|
@@ -669,6 +865,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
669
865
|
});
|
|
670
866
|
return zigbeeDevice;
|
|
671
867
|
}
|
|
868
|
+
// Get types and properties
|
|
672
869
|
const types = [];
|
|
673
870
|
const endpoints = [];
|
|
674
871
|
const names = [];
|
|
@@ -682,6 +879,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
682
879
|
const values = [];
|
|
683
880
|
device.definition?.exposes.forEach((expose) => {
|
|
684
881
|
if (expose.features) {
|
|
882
|
+
// Specific features with type
|
|
685
883
|
expose.features?.forEach((feature) => {
|
|
686
884
|
if (expose.type === 'lock' && feature.name === 'state' && feature.property === 'child_lock')
|
|
687
885
|
feature.name = 'child_lock';
|
|
@@ -699,10 +897,17 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
699
897
|
});
|
|
700
898
|
}
|
|
701
899
|
else {
|
|
900
|
+
// Generic features without type
|
|
901
|
+
// Change voltage to battery_voltage for battery powered devices
|
|
702
902
|
if (device.power_source === 'Battery' && expose.name === 'voltage')
|
|
703
903
|
expose.name = 'battery_voltage';
|
|
704
904
|
if (device.power_source === 'Battery' && expose.property === 'voltage')
|
|
705
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"
|
|
706
911
|
if (expose.description === 'Raw measured illuminance') {
|
|
707
912
|
expose.name = 'illuminance';
|
|
708
913
|
expose.property = 'illuminance';
|
|
@@ -763,6 +968,16 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
763
968
|
zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.featureBlackList];
|
|
764
969
|
if (platform.deviceFeatureBlackList[device.friendly_name])
|
|
765
970
|
zigbeeDevice.ignoreFeatures = [...zigbeeDevice.ignoreFeatures, ...platform.deviceFeatureBlackList[device.friendly_name]];
|
|
971
|
+
/*
|
|
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)}`);
|
|
980
|
+
*/
|
|
766
981
|
for (const [index, name] of names.entries()) {
|
|
767
982
|
if (platform.featureBlackList.includes(name)) {
|
|
768
983
|
zigbeeDevice.log.debug(`Device ${zigbeeDevice.en}${device.friendly_name}${db} feature ${name} is globally blacklisted`);
|
|
@@ -791,6 +1006,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
791
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}`);
|
|
792
1007
|
zigbeeDevice.propertyMap.set(property, { name, type, endpoint, category, description, label, unit, value_min, value_max, values: value });
|
|
793
1008
|
if (endpoint === '') {
|
|
1009
|
+
/* prettier-ignore */
|
|
794
1010
|
if (!zigbeeDevice.mutableDevice.has(endpoint)) {
|
|
795
1011
|
zigbeeDevice.mutableDevice.set(endpoint, { tagList: [], deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
|
|
796
1012
|
}
|
|
@@ -814,6 +1030,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
814
1030
|
if (endpoint === 'l6')
|
|
815
1031
|
tagList.push({ mfgCode: null, namespaceId: NumberTag.Six.namespaceId, tag: NumberTag.Six.tag, label: 'endpoint ' + endpoint });
|
|
816
1032
|
tagList.push({ mfgCode: null, namespaceId: SwitchesTag.Custom.namespaceId, tag: SwitchesTag.Custom.tag, label: 'endpoint ' + endpoint });
|
|
1033
|
+
/* prettier-ignore */
|
|
817
1034
|
if (!zigbeeDevice.mutableDevice.has(endpoint)) {
|
|
818
1035
|
zigbeeDevice.mutableDevice.set(endpoint, { tagList, deviceTypes: [z2m.deviceType], clusterServersIds: [...z2m.deviceType.requiredServerClusters, ClusterId(z2m.cluster)], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
|
|
819
1036
|
}
|
|
@@ -827,13 +1044,16 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
827
1044
|
}
|
|
828
1045
|
}
|
|
829
1046
|
else {
|
|
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`);
|
|
830
1048
|
}
|
|
1049
|
+
// Map actions to switches
|
|
831
1050
|
if (name === 'action' && zigbeeDevice.actions.length) {
|
|
832
1051
|
zigbeeDevice.log.info(`Device ${zigbeeDevice.ien}${device.friendly_name}${rs}${nf} has actions mapped to these switches on sub endpoints:`);
|
|
833
1052
|
zigbeeDevice.log.info(' controller events <=> zigbee2mqtt actions');
|
|
834
1053
|
if (!zigbeeDevice.bridgedDevice)
|
|
835
|
-
zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug");
|
|
1054
|
+
zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice([bridgedNode], { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
836
1055
|
zigbeeDevice.hasEndpoints = true;
|
|
1056
|
+
// Mapping actions
|
|
837
1057
|
const switchMap = ['Single Press', 'Double Press', 'Long Press '];
|
|
838
1058
|
const triggerMap = ['Single', 'Double', 'Long'];
|
|
839
1059
|
let count = 1;
|
|
@@ -866,16 +1086,19 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
866
1086
|
zigbeeDevice.composedType = 'button';
|
|
867
1087
|
}
|
|
868
1088
|
}
|
|
1089
|
+
// Add battery properties
|
|
869
1090
|
if (device.power_source === 'Battery') {
|
|
870
1091
|
zigbeeDevice.propertyMap.set('battery', { name: 'battery', type: '', endpoint: '' });
|
|
871
1092
|
zigbeeDevice.propertyMap.set('battery_low', { name: 'battery_low', type: '', endpoint: '' });
|
|
872
1093
|
zigbeeDevice.propertyMap.set('battery_voltage', { name: 'battery_voltage', type: '', endpoint: '' });
|
|
873
1094
|
}
|
|
1095
|
+
// Handle when the device has only child endpoints
|
|
874
1096
|
if (!zigbeeDevice.mutableDevice.has(''))
|
|
875
1097
|
zigbeeDevice.mutableDevice.set('', { tagList: [], deviceTypes: [bridgedNode, powerSource], clusterServersIds: [], clusterServersObjs: [], clusterClientsIds: [], clusterClientsObjs: [] });
|
|
876
1098
|
const mainEndpoint = zigbeeDevice.mutableDevice.get('');
|
|
877
1099
|
if (!mainEndpoint)
|
|
878
1100
|
return zigbeeDevice;
|
|
1101
|
+
// Remove duplicates and superset device Types on all endpoints
|
|
879
1102
|
for (const device of zigbeeDevice.mutableDevice.values()) {
|
|
880
1103
|
const deviceTypesMap = new Map();
|
|
881
1104
|
device.deviceTypes.forEach((deviceType) => {
|
|
@@ -891,15 +1114,20 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
891
1114
|
deviceTypesMap.delete(onOffLight.code);
|
|
892
1115
|
if (deviceTypesMap.has(dimmableLight.code) && deviceTypesMap.has(colorTemperatureLight.code))
|
|
893
1116
|
deviceTypesMap.delete(dimmableLight.code);
|
|
894
|
-
device.deviceTypes = Array.from(deviceTypesMap.values());
|
|
1117
|
+
device.deviceTypes = Array.from(deviceTypesMap.values()); /* .sort((a, b) => b.code - a.code);*/
|
|
895
1118
|
}
|
|
896
|
-
|
|
1119
|
+
// Create the mutable device for the main endpoint
|
|
1120
|
+
zigbeeDevice.bridgedDevice = await zigbeeDevice.createMutableDevice(mainEndpoint.deviceTypes, { uniqueStorageKey: device.friendly_name }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
1121
|
+
// Configure BridgedDeviceBasicInformation cluster
|
|
897
1122
|
mainEndpoint.clusterServersObjs.push(zigbeeDevice.getBridgedDeviceBasicInformation());
|
|
1123
|
+
// Configure PowerSource cluster
|
|
898
1124
|
mainEndpoint.clusterServersObjs.push(zigbeeDevice.getPowerSource());
|
|
1125
|
+
// Configure ColorControlCluster
|
|
899
1126
|
if (mainEndpoint.clusterServersIds.includes(ColorControl.Cluster.id)) {
|
|
900
1127
|
zigbeeDevice.log.debug(`Configuring device ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} ColorControlCluster cluster with HS: ${names.includes('color_hs')} XY: ${names.includes('color_xy')} CT: ${names.includes('color_temp')}`);
|
|
901
1128
|
zigbeeDevice.bridgedDevice.configureColorControlCluster(names.includes('color_hs') || names.includes('color_xy'), false, names.includes('color_temp'));
|
|
902
1129
|
}
|
|
1130
|
+
// Configure ThermostatCluster: Auto or Heating only or Cooling only. Set also min and max if available
|
|
903
1131
|
if (mainEndpoint.clusterServersIds.includes(Thermostat.Cluster.id)) {
|
|
904
1132
|
const heat = zigbeeDevice.propertyMap.get('occupied_heating_setpoint') || zigbeeDevice.propertyMap.get('current_heating_setpoint');
|
|
905
1133
|
const cool = zigbeeDevice.propertyMap.get('occupied_cooling_setpoint') || zigbeeDevice.propertyMap.get('current_cooling_setpoint');
|
|
@@ -910,18 +1138,20 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
910
1138
|
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} ` +
|
|
911
1139
|
`minHeating ${CYAN}${minHeating}${db} maxHeating ${CYAN}${maxHeating}${db} minCooling ${CYAN}${minCooling}${db} maxCooling ${CYAN}${maxCooling}${db}`);
|
|
912
1140
|
if (heat && !cool) {
|
|
913
|
-
zigbeeDevice.propertyMap.delete('running_state');
|
|
1141
|
+
zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only heating is supported cause it's not supported by the cluster without AutoMode
|
|
914
1142
|
mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultHeatingThermostatClusterServer(undefined, undefined, minHeating, maxHeating));
|
|
915
1143
|
}
|
|
916
1144
|
else if (!heat && cool) {
|
|
917
|
-
zigbeeDevice.propertyMap.delete('running_state');
|
|
1145
|
+
zigbeeDevice.propertyMap.delete('running_state'); // Remove running_state if only cooling is supported cause it's not supported by the cluster without AutoMode
|
|
918
1146
|
mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultCoolingThermostatClusterServer(undefined, undefined, minCooling, maxCooling));
|
|
919
1147
|
}
|
|
920
1148
|
else if (heat && cool) {
|
|
921
1149
|
mainEndpoint.clusterServersObjs.push(zigbeeDevice.bridgedDevice.getDefaultThermostatClusterServer(undefined, undefined, undefined, undefined, minHeating, maxHeating, minCooling, maxCooling));
|
|
922
1150
|
}
|
|
923
1151
|
}
|
|
1152
|
+
// Filter out duplicate clusters and clusters objects
|
|
924
1153
|
for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
|
|
1154
|
+
// Filter out duplicate server clusters and server clusters objects. Remove the cluster server id when a cluster server object is present.
|
|
925
1155
|
const deviceClusterServersMap = new Map();
|
|
926
1156
|
device.clusterServersIds.forEach((clusterServer) => {
|
|
927
1157
|
deviceClusterServersMap.set(clusterServer, clusterServer);
|
|
@@ -933,6 +1163,7 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
933
1163
|
});
|
|
934
1164
|
device.clusterServersIds = Array.from(deviceClusterServersMap.values());
|
|
935
1165
|
device.clusterServersObjs = Array.from(deviceClusterServersObjMap.values());
|
|
1166
|
+
// Filter out duplicate client clusters and client clusters objects. Remove the cluster client id when a cluster client object is present.
|
|
936
1167
|
const deviceClusterClientsMap = new Map();
|
|
937
1168
|
device.clusterClientsIds.forEach((clusterClient) => {
|
|
938
1169
|
deviceClusterClientsMap.set(clusterClient, clusterClient);
|
|
@@ -947,27 +1178,36 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
947
1178
|
zigbeeDevice.log.debug(`Device ${zigbeeDevice.ien}${zigbeeDevice.device?.friendly_name}${rs}${db} endpoint: ${ign}${endpoint === '' ? 'main' : endpoint}${rs}${db} => ` +
|
|
948
1179
|
`${nf}tagList: ${debugStringify(device.tagList)} deviceTypes: ${debugStringify(device.deviceTypes)} clusterServersIds: ${debugStringify(device.clusterServersIds)}`);
|
|
949
1180
|
}
|
|
1181
|
+
// Add the cluster objects to the main endpoint
|
|
950
1182
|
mainEndpoint.clusterServersObjs.forEach((clusterServerObj) => {
|
|
951
1183
|
zigbeeDevice.bridgedDevice?.addClusterServer(clusterServerObj);
|
|
952
1184
|
});
|
|
1185
|
+
// Add the cluster ids to the main endpoint
|
|
953
1186
|
zigbeeDevice.bridgedDevice.addClusterServerFromList(zigbeeDevice.bridgedDevice, mainEndpoint.clusterServersIds);
|
|
954
1187
|
zigbeeDevice.bridgedDevice.addRequiredClusterServers(zigbeeDevice.bridgedDevice);
|
|
1188
|
+
// Add the Fixed Label cluster to the main endpoint
|
|
955
1189
|
if (zigbeeDevice.composedType !== '')
|
|
956
1190
|
await zigbeeDevice.bridgedDevice.addFixedLabel('composed', zigbeeDevice.composedType);
|
|
1191
|
+
// Create the child endpoints
|
|
957
1192
|
for (const [endpoint, device] of zigbeeDevice.mutableDevice) {
|
|
958
1193
|
if (endpoint === '')
|
|
959
1194
|
continue;
|
|
960
|
-
const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug");
|
|
1195
|
+
const child = zigbeeDevice.bridgedDevice?.addChildDeviceTypeWithClusterServer(endpoint, device.deviceTypes, device.clusterServersIds, { tagList: device.tagList }, zigbeeDevice.log.logLevel === "debug" /* LogLevel.DEBUG */);
|
|
961
1196
|
device.clusterServersObjs.forEach((clusterServerObj) => {
|
|
962
1197
|
child.addClusterServer(clusterServerObj);
|
|
963
1198
|
});
|
|
964
1199
|
}
|
|
1200
|
+
// Verify the device
|
|
965
1201
|
if (!zigbeeDevice.verifyMutableDevice(zigbeeDevice.bridgedDevice))
|
|
966
1202
|
return zigbeeDevice;
|
|
1203
|
+
// Clear the mutable device from memory
|
|
967
1204
|
zigbeeDevice.mutableDevice.clear();
|
|
1205
|
+
// Log properties
|
|
968
1206
|
zigbeeDevice.logPropertyMap();
|
|
1207
|
+
// Add command handlers
|
|
969
1208
|
zigbeeDevice.bridgedDevice.addCommandHandler('identify', async (data) => {
|
|
970
1209
|
zigbeeDevice.log.debug(`Command identify called for ${zigbeeDevice.ien}${device.friendly_name}${rs}${db} endpoint: ${data.endpoint.number} request identifyTime:${data.request.identifyTime} `);
|
|
1210
|
+
// logEndpoint(zigbeeDevice.bridgedDevice!);
|
|
971
1211
|
});
|
|
972
1212
|
if (zigbeeDevice.bridgedDevice.getClusterServerById(OnOffCluster.id) || zigbeeDevice.hasEndpoints) {
|
|
973
1213
|
for (const child of zigbeeDevice.bridgedDevice.getChildEndpoints()) {
|
|
@@ -1229,3 +1469,4 @@ export class ZigbeeDevice extends ZigbeeEntity {
|
|
|
1229
1469
|
return zigbeeDevice;
|
|
1230
1470
|
}
|
|
1231
1471
|
}
|
|
1472
|
+
//# sourceMappingURL=entity.js.map
|