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