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