matterbridge-zigbee2mqtt 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/entity.ts DELETED
@@ -1,578 +0,0 @@
1
- /**
2
- * This file contains the classes ZigbeeEntity, ZigbeeDevice and ZigbeeGroup.
3
- *
4
- * @file entity.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 2.0.0
8
- *
9
- * Copyright 2023, 2024 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
-
24
- // matter.js imports
25
- import {
26
- DeviceTypes,
27
- DeviceTypeDefinition,
28
- logEndpoint,
29
- AirQuality,
30
- MatterbridgeDevice,
31
- airQualitySensor,
32
- colorTemperatureSwitch,
33
- dimmableSwitch,
34
- onOffSwitch,
35
- BridgedDeviceBasicInformation,
36
- Identify,
37
- Groups,
38
- Scenes,
39
- OnOff,
40
- LevelControl,
41
- ColorControl,
42
- ColorControlCluster,
43
- Switch,
44
- TemperatureMeasurement,
45
- BooleanState,
46
- RelativeHumidityMeasurement,
47
- PressureMeasurement,
48
- OccupancySensing,
49
- IlluminanceMeasurement,
50
- PowerSource,
51
- ClusterId,
52
- TvocMeasurement,
53
- WindowCovering,
54
- WindowCoveringCluster,
55
- OnOffCluster,
56
- LevelControlCluster,
57
- } from 'matterbridge';
58
-
59
- import { AnsiLogger, TimestampFormat, gn, dn, ign, idn, rs, db, nf, wr, stringify, debugStringify } from 'node-ansi-logger';
60
- import { ZigbeePlatform } from './platform.js';
61
- import { BridgeDevice, BridgeGroup } from './zigbee2mqttTypes.js';
62
- import { Payload } from './payloadTypes.js';
63
- import * as color from './colorUtils.js';
64
- import EventEmitter from 'events';
65
- import { hostname } from 'os';
66
-
67
- export class ZigbeeEntity extends EventEmitter {
68
- protected log: AnsiLogger;
69
- protected platform: ZigbeePlatform;
70
- public device: BridgeDevice | undefined;
71
- public group: BridgeGroup | undefined;
72
- protected accessoryName: string = '';
73
- public isDevice: boolean = false;
74
- public isGroup: boolean = false;
75
- protected en = '';
76
- protected ien = '';
77
- public bridgedDevice: BridgedBaseDevice | undefined;
78
-
79
- constructor(platform: ZigbeePlatform, entity: BridgeDevice | BridgeGroup) {
80
- super();
81
- this.platform = platform;
82
- if ((entity as BridgeDevice).ieee_address !== undefined) {
83
- this.device = entity as BridgeDevice;
84
- this.accessoryName = entity.friendly_name;
85
- this.isDevice = true;
86
- this.en = dn;
87
- this.ien = idn;
88
- }
89
- if ((entity as BridgeGroup).id !== undefined) {
90
- this.group = entity as BridgeGroup;
91
- this.accessoryName = entity.friendly_name;
92
- this.isGroup = true;
93
- this.en = gn;
94
- this.ien = ign;
95
- }
96
- this.log = new AnsiLogger({ logName: this.accessoryName, logTimestampFormat: TimestampFormat.TIME_MILLIS, logDebug: platform.debugEnabled });
97
- this.log.debug(`Created MatterEntity: ${this.accessoryName}`);
98
-
99
- this.platform.z2m.on('MESSAGE-' + this.accessoryName, (payload: object) => {
100
- if (this.bridgedDevice === undefined) return;
101
- const debugEnabled = this.platform.debugEnabled;
102
- this.log.setLogDebug(true);
103
- this.log.debug(`MQTT message for accessory ${this.ien}${this.accessoryName}${rs}${db} payload: ${debugStringify(payload)}`);
104
- this.log.setLogDebug(debugEnabled);
105
- Object.entries(payload).forEach(([key, value]) => {
106
- if (this.bridgedDevice === undefined) return;
107
- if (key === 'position') {
108
- this.bridgedDevice.getClusterServerById(WindowCovering.Cluster.id)?.setCurrentPositionLiftPercent100thsAttribute(10000 - value * 100);
109
- }
110
- if (key === 'moving') {
111
- const status = value === 'UP' ? WindowCovering.MovementStatus.Opening : value === 'DOWN' ? WindowCovering.MovementStatus.Closing : WindowCovering.MovementStatus.Stopped;
112
- this.bridgedDevice.getClusterServerById(WindowCovering.Cluster.id)?.setOperationalStatusAttribute({ global: status, lift: status, tilt: status });
113
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} operationalStatus: ${status}`);
114
- if (value === 'STOP') {
115
- const position = this.bridgedDevice.getClusterServerById(WindowCovering.Cluster.id)?.getCurrentPositionLiftPercent100thsAttribute();
116
- this.bridgedDevice.getClusterServerById(WindowCovering.Cluster.id)?.setCurrentPositionLiftPercent100thsAttribute(position);
117
- }
118
- }
119
- if (key === 'state') {
120
- this.bridgedDevice.getClusterServerById(OnOff.Cluster.id)?.setOnOffAttribute(value === 'ON' ? true : false);
121
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} onOffAttribute: ${value === 'ON' ? true : false}`);
122
- }
123
- if (key === 'brightness') {
124
- this.bridgedDevice.getClusterServerById(LevelControl.Cluster.id)?.setCurrentLevelAttribute(value);
125
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} currentLevelAttribute: ${value}`);
126
- }
127
- if (key === 'color_temp' && 'color_mode' in payload && payload['color_mode'] === 'color_temp') {
128
- this.bridgedDevice.getClusterServerById(ColorControl.Cluster.id)?.setColorTemperatureMiredsAttribute(value);
129
- this.bridgedDevice.getClusterServerById(ColorControl.Cluster.id)?.setColorModeAttribute(ColorControl.ColorMode.ColorTemperatureMireds);
130
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} colorTemperatureMireds: ${value}`);
131
- }
132
- if (key === 'color' && 'color_mode' in payload && payload['color_mode'] === 'xy') {
133
- const hsl = color.xyToHsl(value.x, value.y);
134
- this.bridgedDevice.getClusterServerById(ColorControl.Cluster.id)?.setCurrentHueAttribute((hsl.h / 360) * 254);
135
- this.bridgedDevice.getClusterServerById(ColorControl.Cluster.id)?.setCurrentSaturationAttribute((hsl.s / 100) * 254);
136
- this.bridgedDevice.getClusterServerById(ColorControl.Cluster.id)?.setColorModeAttribute(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
137
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} colorXY: X:${value.x} Y:${value.y}`);
138
- }
139
- if (key === 'battery') {
140
- this.bridgedDevice.getClusterServerById(PowerSource.Cluster.id)?.setBatPercentRemainingAttribute(Math.round(value * 2));
141
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} batPercentRemaining: ${Math.round(value * 2)}`);
142
- }
143
- if (key === 'temperature') {
144
- this.bridgedDevice.getClusterServerById(TemperatureMeasurement.Cluster.id)?.setMeasuredValueAttribute(Math.round(value * 100));
145
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} measuredValue: ${Math.round(value * 100)}`);
146
- }
147
- if (key === 'humidity') {
148
- this.bridgedDevice.getClusterServerById(RelativeHumidityMeasurement.Cluster.id)?.setMeasuredValueAttribute(Math.round(value * 100));
149
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} measuredValue: ${Math.round(value * 100)}`);
150
- }
151
- if (key === 'pressure') {
152
- this.bridgedDevice.getClusterServerById(PressureMeasurement.Cluster.id)?.setMeasuredValueAttribute(Math.round(value));
153
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} measuredValue: ${Math.round(value)}`);
154
- }
155
- if (key === 'contact') {
156
- this.bridgedDevice.getClusterServerById(BooleanState.Cluster.id)?.setStateValueAttribute(value);
157
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} stateValue: ${Math.round(value)}`);
158
- }
159
- if (key === 'water_leak') {
160
- this.bridgedDevice.getClusterServerById(BooleanState.Cluster.id)?.setStateValueAttribute(value);
161
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} stateValue: ${Math.round(value)}`);
162
- }
163
- if (key === 'carbon_monoxide') {
164
- this.bridgedDevice.getClusterServerById(BooleanState.Cluster.id)?.setStateValueAttribute(value);
165
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} stateValue: ${Math.round(value)}`);
166
- }
167
- if (key === 'air_quality') {
168
- // excellent, good, moderate, poor, unhealthy, out_of_range unknown
169
- const airQuality =
170
- value === 'unhealthy'
171
- ? AirQuality.AirQualityType.VeryPoor
172
- : value === 'poor'
173
- ? AirQuality.AirQualityType.Poor
174
- : value === 'moderate'
175
- ? AirQuality.AirQualityType.Moderate
176
- : value === 'good'
177
- ? AirQuality.AirQualityType.Fair
178
- : value === 'excellent'
179
- ? AirQuality.AirQualityType.Good
180
- : AirQuality.AirQualityType.Unknown;
181
- this.bridgedDevice.getClusterServerById(AirQuality.Cluster.id)?.setAirQualityAttribute(airQuality);
182
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} airQuality: ${airQuality}`);
183
- }
184
- if (key === 'voc') {
185
- this.bridgedDevice.getClusterServerById(TvocMeasurement.Cluster.id)?.setMeasuredValueAttribute(value);
186
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} measuredValue: ${value}`);
187
- }
188
- if (key === 'occupancy') {
189
- this.bridgedDevice.getClusterServerById(OccupancySensing.Cluster.id)?.setOccupancyAttribute({ occupied: value as boolean });
190
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} occupancy: ${value}`);
191
- }
192
- if (key === 'illuminance_lux' || (key === 'illuminance' && !('illuminance_lux' in payload))) {
193
- this.bridgedDevice.getClusterServerById(IlluminanceMeasurement.Cluster.id)?.setMeasuredValueAttribute(Math.round(Math.max(Math.min(10000 * Math.log10(value) + 1, 0xfffe), 0)));
194
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} measuredValue: ${Math.round(Math.max(Math.min(10000 * Math.log10(value) + 1, 0xfffe), 0))}`);
195
- }
196
- });
197
- });
198
-
199
- this.platform.z2m.on('ONLINE-' + this.accessoryName, () => {
200
- this.log.info(`ONLINE message for accessory ${this.ien}${this.accessoryName}${rs}`);
201
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.setReachableAttribute(true);
202
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} reachable: true`);
203
- });
204
-
205
- this.platform.z2m.on('OFFLINE-' + this.accessoryName, () => {
206
- this.log.warn(`OFFLINE message for accessory ${this.ien}${this.accessoryName}${wr}`);
207
- this.bridgedDevice?.getClusterServerById(BridgedDeviceBasicInformation.Cluster.id)?.setReachableAttribute(false);
208
- this.log.debug(`Setting accessory ${this.ien}${this.accessoryName}${rs}${db} reachable: false`);
209
- });
210
- }
211
-
212
- protected publishCommand(command: string, entityName: string, payload: Payload) {
213
- this.log.debug(`executeCommand ${command} called for ${this.ien}${entityName}${rs}${db} payload: ${stringify(payload, true)}`);
214
- const topic = this.platform.z2m.mqttTopic + '/' + entityName + '/set';
215
- this.platform.z2m.publish(topic, JSON.stringify(payload));
216
- this.log.info(`MQTT publish topic: ${topic} payload: ${stringify(payload, true)} for ${this.en}${entityName}`);
217
- }
218
- }
219
-
220
- export class ZigbeeGroup extends ZigbeeEntity {
221
- constructor(platform: ZigbeePlatform, group: BridgeGroup) {
222
- super(platform, group);
223
-
224
- // TODO Add the group scanning for real groups. This cover only automations
225
- this.bridgedDevice = new BridgedBaseDevice(this, onOffSwitch, [Identify.Cluster.id, Groups.Cluster.id, Scenes.Cluster.id, OnOff.Cluster.id]);
226
-
227
- // Command handlers
228
- this.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
229
- this.log.warn(`Command identify called for ${this.ien}${group.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
230
- logEndpoint(this.bridgedDevice!);
231
- });
232
- this.bridgedDevice.addCommandHandler('on', async ({ attributes: { onOff } }) => {
233
- this.log.debug(`Command on called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
234
- this.publishCommand('on', group.friendly_name, { state: 'ON' });
235
- });
236
- this.bridgedDevice.addCommandHandler('off', async ({ attributes: { onOff } }) => {
237
- this.log.debug(`Command off called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
238
- this.publishCommand('off', group.friendly_name, { state: 'OFF' });
239
- });
240
- this.bridgedDevice.addCommandHandler('toggle', async ({ attributes: { onOff } }) => {
241
- this.log.debug(`Command toggle called for ${this.ien}${group.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
242
- this.publishCommand('toggle', group.friendly_name, { state: 'TOGGLE' });
243
- });
244
- }
245
- }
246
-
247
- interface ZigbeeToMatter {
248
- //[key: string]: string;
249
- type: string;
250
- name: string;
251
- property: string;
252
- deviceType: DeviceTypeDefinition;
253
- cluster: number;
254
- attribute: number;
255
- }
256
-
257
- /* eslint-disable */
258
- // prettier-ignore
259
- const z2ms: ZigbeeToMatter[] = [
260
- { type: 'switch', name: 'state', property: 'state', deviceType: onOffSwitch, cluster: OnOff.Cluster.id, attribute: OnOff.Cluster.attributes.onOff.id },
261
- { type: 'switch', name: 'brightness', property: 'brightness', deviceType: dimmableSwitch, cluster: LevelControl.Cluster.id, attribute: LevelControl.Cluster.attributes.currentLevel.id },
262
- { type: 'switch', name: 'color_xy', property: 'color_xy', deviceType: colorTemperatureSwitch, cluster: ColorControl.Cluster.id, attribute: ColorControl.Cluster.attributes.colorMode.id },
263
- { type: 'outlet', name: 'state', property: 'state', deviceType: DeviceTypes.ON_OFF_LIGHT, cluster: OnOff.Cluster.id, attribute: OnOff.Cluster.attributes.onOff.id },
264
- { type: 'outlet', name: 'brightness', property: 'brightness', deviceType: DeviceTypes.DIMMABLE_PLUGIN_UNIT, cluster: LevelControl.Cluster.id, attribute: LevelControl.Cluster.attributes.currentLevel.id },
265
- { type: 'light', name: 'state', property: 'state', deviceType: DeviceTypes.ON_OFF_LIGHT, cluster: OnOff.Cluster.id, attribute: OnOff.Cluster.attributes.onOff.id },
266
- { type: 'light', name: 'brightness', property: 'brightness', deviceType: DeviceTypes.DIMMABLE_LIGHT, cluster: LevelControl.Cluster.id, attribute: LevelControl.Cluster.attributes.currentLevel.id },
267
- { type: 'light', name: 'color_xy', property: 'color_xy', deviceType: DeviceTypes.COLOR_TEMPERATURE_LIGHT, cluster: ColorControl.Cluster.id, attribute: ColorControl.Cluster.attributes.colorMode.id },
268
- { type: 'cover', name: 'state', property: 'state', deviceType: DeviceTypes.WINDOW_COVERING, cluster: WindowCovering.Cluster.id, attribute: WindowCovering.Complete.attributes.currentPositionLiftPercent100ths.id },
269
- { type: '', name: 'occupancy', property: 'occupancy', deviceType: DeviceTypes.OCCUPANCY_SENSOR, cluster: OccupancySensing.Cluster.id, attribute: OccupancySensing.Cluster.attributes.occupancy.id },
270
- { type: '', name: 'illuminance', property: 'illuminance', deviceType: DeviceTypes.LIGHT_SENSOR, cluster: IlluminanceMeasurement.Cluster.id, attribute: IlluminanceMeasurement.Cluster.attributes.measuredValue.id },
271
- { type: '', name: 'contact', property: 'contact', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: BooleanState.Cluster.attributes.stateValue.id },
272
- { type: '', name: 'water_leak', property: 'water_leak', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: BooleanState.Cluster.attributes.stateValue.id },
273
- { type: '', name: 'vibration', property: 'vibration', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: BooleanState.Cluster.attributes.stateValue.id },
274
- { type: '', name: 'smoke', property: 'smoke', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: BooleanState.Cluster.attributes.stateValue.id },
275
- { type: '', name: 'carbon_monoxide', property: 'carbon_monoxide', deviceType: DeviceTypes.CONTACT_SENSOR, cluster: BooleanState.Cluster.id, attribute: BooleanState.Cluster.attributes.stateValue.id },
276
- { type: '', name: 'temperature', property: 'temperature', deviceType: DeviceTypes.TEMPERATURE_SENSOR, cluster: TemperatureMeasurement.Cluster.id, attribute: TemperatureMeasurement.Cluster.attributes.measuredValue.id },
277
- { type: '', name: 'humidity', property: 'humidity', deviceType: DeviceTypes.HUMIDITY_SENSOR, cluster: RelativeHumidityMeasurement.Cluster.id, attribute: RelativeHumidityMeasurement.Cluster.attributes.measuredValue.id },
278
- { type: '', name: 'pressure', property: 'pressure', deviceType: DeviceTypes.PRESSURE_SENSOR, cluster: PressureMeasurement.Cluster.id, attribute: PressureMeasurement.Cluster.attributes.measuredValue.id },
279
- { type: '', name: 'air_quality', property: 'air_quality', deviceType: airQualitySensor, cluster: AirQuality.Cluster.id, attribute: AirQuality.Cluster.attributes.airQuality.id },
280
- { type: '', name: 'voc', property: 'voc', deviceType: airQualitySensor, cluster: TvocMeasurement.Cluster.id, attribute: TvocMeasurement.Cluster.attributes.measuredValue.id },
281
- { type: '', name: 'action', property: 'action', deviceType: DeviceTypes.GENERIC_SWITCH, cluster: Switch.Cluster.id, attribute: Switch.Cluster.attributes.currentPosition.id },
282
- ];
283
- /* eslint-enable */
284
-
285
- export class ZigbeeDevice extends ZigbeeEntity {
286
- constructor(platform: ZigbeePlatform, device: BridgeDevice) {
287
- super(platform, device);
288
- if (device.friendly_name === 'Coordinator') return;
289
-
290
- // Get types and properties
291
- const types: string[] = [];
292
- const endpoints: string[] = [];
293
- const names: string[] = [];
294
- const properties: string[] = [];
295
- const forceLight = ['Aqara switch T1'];
296
- const forceOutlet = ['Aqara switch no neutral'];
297
- device.definition?.exposes.forEach((expose) => {
298
- if (expose.features) {
299
- //Specific features with type
300
- types.push(expose.type);
301
- if (expose.endpoint) endpoints.push(expose.endpoint);
302
- expose.features?.forEach((feature) => {
303
- names.push(feature.name);
304
- properties.push(feature.property);
305
- });
306
- } else {
307
- //Generic features without type
308
- if (expose.name) names.push(expose.name);
309
- properties.push(expose.property);
310
- }
311
- });
312
- device.definition?.options.forEach((option) => {
313
- if (option.name) names.push(option.name);
314
- properties.push(option.property);
315
- });
316
- if (forceLight.includes(device.friendly_name)) {
317
- types.forEach((type, index) => {
318
- types[index] = type === 'switch' ? 'light' : type;
319
- this.log.info(`Changed ${device.friendly_name} to light`);
320
- });
321
- }
322
- if (forceOutlet.includes(device.friendly_name)) {
323
- types.forEach((type, index) => {
324
- types[index] = type === 'switch' ? 'outlet' : type;
325
- this.log.info(`Changed ${device.friendly_name} to outlet`);
326
- });
327
- }
328
- if (types.length === 0) types.push('');
329
- this.log.debug(`**Device ${this.ien}${device.friendly_name}${rs}${nf} endpoints: ${endpoints.length} \ntypes: ${types.join(' ')} \nproperties: ${properties.join(' ')} \nnames: ${names.join(' ')}`);
330
-
331
- [...types].forEach((type) => {
332
- [...names].forEach((name) => {
333
- const z2m = z2ms.find((z2m) => z2m.type === type && z2m.property === name);
334
- if (z2m) {
335
- this.log.debug(`***Device ${this.ien}${device.friendly_name}${rs}${nf} type: ${type} property: ${name} => deviceType: ${z2m.deviceType.name} cluster: ${z2m.cluster} attribute: ${z2m.attribute}`);
336
- const requiredServerClusters: ClusterId[] = [];
337
- if (z2m.deviceType.requiredServerClusters.includes(Groups.Cluster.id)) requiredServerClusters.push(Groups.Cluster.id);
338
- if (z2m.deviceType.requiredServerClusters.includes(Scenes.Cluster.id)) requiredServerClusters.push(Scenes.Cluster.id);
339
- if (!this.bridgedDevice) this.bridgedDevice = new BridgedBaseDevice(this, z2m.deviceType, [Identify.Cluster.id, ...requiredServerClusters, ClusterId(z2m.cluster)]);
340
- else this.bridgedDevice.addDeviceTypeAndClusterServer(z2m.deviceType, [ClusterId(z2m.cluster)]);
341
- names.splice(names.indexOf(name), 1);
342
- }
343
- });
344
- types.splice(types.indexOf(type), 1);
345
- });
346
- this.log.debug(`****Device ${this.ien}${device.friendly_name}${rs}${nf} endpoints: ${endpoints.length} \ntypes: ${types.join(' ')} \nproperties: ${properties.join(' ')} \nnames: ${names.join(' ')}`);
347
-
348
- if (!this.bridgedDevice) return;
349
-
350
- // Command handlers
351
- this.bridgedDevice.addCommandHandler('identify', async ({ request: { identifyTime } }) => {
352
- this.log.debug(`Command identify called for ${this.ien}${device.friendly_name}${rs}${db} identifyTime:${identifyTime}`);
353
- logEndpoint(this.bridgedDevice!);
354
- });
355
- if (this.bridgedDevice.hasClusterServer(OnOffCluster)) {
356
- this.bridgedDevice.addCommandHandler('on', async ({ attributes: { onOff } }) => {
357
- this.log.debug(`Command on called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
358
- this.publishCommand('on', device.friendly_name, { state: 'ON' });
359
- });
360
- this.bridgedDevice.addCommandHandler('off', async ({ attributes: { onOff } }) => {
361
- this.log.debug(`Command off called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
362
- this.publishCommand('off', device.friendly_name, { state: 'OFF' });
363
- });
364
- this.bridgedDevice.addCommandHandler('toggle', async ({ attributes: { onOff } }) => {
365
- this.log.debug(`Command toggle called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${onOff.getLocal()}`);
366
- this.publishCommand('toggle', device.friendly_name, { state: 'TOGGLE' });
367
- });
368
- }
369
- if (this.bridgedDevice.hasClusterServer(LevelControlCluster)) {
370
- this.bridgedDevice.addCommandHandler('moveToLevel', async ({ request: { level }, attributes: { currentLevel } }) => {
371
- this.log.debug(`Command moveToLevel called for ${this.ien}${device.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`);
372
- this.publishCommand('moveToLevel', device.friendly_name, { brightness: level });
373
- });
374
- this.bridgedDevice.addCommandHandler('moveToLevelWithOnOff', async ({ request: { level }, attributes: { currentLevel } }) => {
375
- this.log.debug(`Command moveToLevelWithOnOff called for ${this.ien}${device.friendly_name}${rs}${db} request: ${level} attributes: ${currentLevel}`);
376
- this.publishCommand('moveToLevelWithOnOff', device.friendly_name, { brightness: level });
377
- });
378
- }
379
- if (this.bridgedDevice.hasClusterServer(ColorControlCluster) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('colorTemperatureMireds')) {
380
- this.bridgedDevice.addCommandHandler('moveToColorTemperature', async ({ request: request, attributes: attributes }) => {
381
- this.log.debug(`Command moveToColorTemperature called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.colorTemperatureMireds} attributes: ${attributes.colorTemperatureMireds?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
382
- this.log.warn(`Command moveToColorTemperature called for ${this.ien}${device.friendly_name}${rs}${db} colorMode`, attributes.colorMode.getLocal());
383
- attributes.colorMode.setLocal(ColorControl.ColorMode.ColorTemperatureMireds);
384
- this.publishCommand('moveToColorTemperature', device.friendly_name, { color_temp: request.colorTemperatureMireds });
385
- });
386
- }
387
- if (this.bridgedDevice.hasClusterServer(ColorControlCluster) && this.bridgedDevice.getClusterServer(ColorControlCluster)?.isAttributeSupportedByName('currentHue')) {
388
- let lastRequestedHue = 0;
389
- let lastRequestedSaturation = 0;
390
- let lastRequestTimeout: NodeJS.Timeout;
391
- this.bridgedDevice.addCommandHandler('moveToHue', async ({ request: request, attributes: attributes }) => {
392
- this.log.debug(`Command moveToHue called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.hue} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
393
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
394
- lastRequestedHue = request.hue;
395
- lastRequestTimeout = setTimeout(() => {
396
- clearTimeout(lastRequestTimeout);
397
- const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (lastRequestedSaturation / 254) * 100, 50);
398
- this.publishCommand('moveToHue', device.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
399
- }, 500);
400
- });
401
- this.bridgedDevice.addCommandHandler('moveToSaturation', async ({ request: request, attributes: attributes }) => {
402
- this.log.debug(`Command moveToSaturation called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`);
403
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
404
- lastRequestedSaturation = request.saturation;
405
- lastRequestTimeout = setTimeout(() => {
406
- clearTimeout(lastRequestTimeout);
407
- const rgb = color.hslColorToRgbColor((lastRequestedHue / 254) * 360, (request.saturation / 254) * 100, 50);
408
- this.publishCommand('moveToHue', device.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
409
- }, 500);
410
- });
411
- this.bridgedDevice.addCommandHandler('moveToHueAndSaturation', async ({ request: request, attributes: attributes }) => {
412
- this.log.debug(
413
- `Command moveToHueAndSaturation called for ${this.ien}${device.friendly_name}${rs}${db} request: ${request.hue}-${request.saturation} attributes: hue ${attributes.currentHue?.getLocal()} saturation ${attributes.currentSaturation?.getLocal()} colorMode ${attributes.colorMode.getLocal()}`,
414
- );
415
- attributes.colorMode.setLocal(ColorControl.ColorMode.CurrentHueAndCurrentSaturation);
416
- const rgb = color.hslColorToRgbColor((request.hue / 254) * 360, (request.saturation / 254) * 100, 50);
417
- this.publishCommand('moveToHueAndSaturation', device.friendly_name, { color: { r: rgb.r, g: rgb.g, b: rgb.b } });
418
- });
419
- }
420
- if (this.bridgedDevice.hasClusterServer(WindowCoveringCluster)) {
421
- this.bridgedDevice.addCommandHandler('upOrOpen', async (data) => {
422
- this.log.info(`Command upOrOpen called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
423
- data.attributes.currentPositionLiftPercent100ths?.setLocal(0);
424
- data.attributes.targetPositionLiftPercent100ths?.setLocal(0);
425
- this.publishCommand('upOrOpen', device.friendly_name, { state: 'OPEN' });
426
- });
427
- this.bridgedDevice.addCommandHandler('downOrClose', async (data) => {
428
- this.log.info(`Command downOrClose called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.currentPositionLiftPercent100ths?.getLocal()}`);
429
- data.attributes.currentPositionLiftPercent100ths?.setLocal(10000);
430
- data.attributes.targetPositionLiftPercent100ths?.setLocal(10000);
431
- this.publishCommand('downOrClose', device.friendly_name, { state: 'CLOSE' });
432
- });
433
- this.bridgedDevice.addCommandHandler('stopMotion', async (data) => {
434
- this.log.info(`Command stopMotion called for ${this.ien}${device.friendly_name}${rs}${db} attribute: ${data.attributes.operationalStatus?.getLocal()}`);
435
- const liftPercent100thsValue = data.attributes.currentPositionLiftPercent100ths?.getLocal();
436
- if (liftPercent100thsValue) {
437
- data.attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
438
- data.attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
439
- }
440
- data.attributes.operationalStatus?.setLocal({ global: WindowCovering.MovementStatus.Stopped, lift: WindowCovering.MovementStatus.Stopped, tilt: WindowCovering.MovementStatus.Stopped });
441
- this.publishCommand('stopMotion', device.friendly_name, { state: 'STOP' });
442
- });
443
- this.bridgedDevice.addCommandHandler('goToLiftPercentage', async ({ request: { liftPercent100thsValue }, attributes }) => {
444
- this.log.info(`Command goToLiftPercentage called for ${this.ien}${device.friendly_name}${rs}${db} liftPercent100thsValue: ${liftPercent100thsValue}`);
445
- this.log.info(`Command goToLiftPercentage current: ${attributes.currentPositionLiftPercent100ths?.getLocal()} target: ${attributes.targetPositionLiftPercent100ths?.getLocal()}`);
446
- //attributes.currentPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
447
- attributes.targetPositionLiftPercent100ths?.setLocal(liftPercent100thsValue);
448
- this.publishCommand('goToLiftPercentage', device.friendly_name, { position: 100 - liftPercent100thsValue / 100 });
449
- });
450
- }
451
- }
452
- }
453
-
454
- export class BridgedBaseDevice extends MatterbridgeDevice {
455
- //public deviceName: string;
456
- public hasEndpoints = false;
457
-
458
- constructor(entity: ZigbeeEntity, definition: DeviceTypeDefinition, includeServerList: ClusterId[] = [], includeClientList?: ClusterId[]) {
459
- super(definition);
460
-
461
- // Add all other server clusters in the includelist
462
- this.addDeviceClusterServer(includeServerList);
463
-
464
- // Add BridgedDeviceBasicInformationCluster
465
- if (entity.isDevice && entity.device) {
466
- this.addBridgedDeviceBasicInformationCluster(entity.device.friendly_name, entity.device.definition.vendor, entity.device.definition.model, entity.device.ieee_address);
467
- } else if (entity.isGroup && entity.group) {
468
- this.addBridgedDeviceBasicInformationCluster(entity.group.friendly_name, 'zigbee2MQTT', 'Group', `group-${entity.group.id}`);
469
- }
470
-
471
- // Add PowerSource cluster
472
- if (entity.isDevice) {
473
- if (entity.device?.power_source === 'Battery') this.createDefaultPowerSourceReplaceableBatteryClusterServer(100, PowerSource.BatChargeLevel.Ok);
474
- else this.createDefaultPowerSourceWiredClusterServer();
475
- } else if (entity.isGroup) {
476
- this.createDefaultPowerSourceWiredClusterServer();
477
- }
478
-
479
- // Add all other client clusters in the includelist
480
- this.addDeviceClusterClient(includeClientList);
481
- }
482
-
483
- /**
484
- * Adds BridgedDeviceBasicInformationCluster
485
- *
486
- * @protected
487
- * @param deviceName Name of the device
488
- * @param deviceSerial Serial of the device
489
- */
490
- protected addBridgedDeviceBasicInformationCluster(deviceName: string, vendorName: string, productName: string, deviceSerial: string) {
491
- this.createDefaultBridgedDeviceBasicInformationClusterServer(deviceName.slice(0, 32), (deviceSerial + '_' + hostname).slice(0, 32), 0xfff1, vendorName, productName);
492
- }
493
-
494
- /**
495
- * Adds mandatory clusters to the device
496
- *
497
- * @protected
498
- * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters
499
- * @param includeServerList List of clusters to include
500
- */
501
- // attributeInitialValues?: { [key: ClusterId]: AttributeInitialValues<any> }
502
- protected addDeviceClusterServer(includeServerList: ClusterId[] = []) {
503
- if (includeServerList.includes(Identify.Cluster.id) && !this.hasClusterServer(Identify.Cluster)) {
504
- this.createDefaultIdentifyClusterServer();
505
- }
506
- if (includeServerList.includes(Groups.Cluster.id) && !this.hasClusterServer(Groups.Cluster)) {
507
- this.createDefaultGroupsClusterServer();
508
- }
509
- if (includeServerList.includes(Scenes.Cluster.id) && !this.hasClusterServer(Scenes.Cluster)) {
510
- this.createDefaultScenesClusterServer();
511
- }
512
- if (includeServerList.includes(OnOff.Cluster.id)) {
513
- this.createDefaultOnOffClusterServer();
514
- }
515
- if (includeServerList.includes(LevelControl.Cluster.id)) {
516
- this.createDefaultLevelControlClusterServer();
517
- }
518
- if (includeServerList.includes(ColorControl.Cluster.id)) {
519
- this.createDefaultColorControlClusterServer();
520
- }
521
- if (includeServerList.includes(WindowCovering.Cluster.id)) {
522
- this.createDefaultWindowCoveringClusterServer();
523
- }
524
- if (includeServerList.includes(Switch.Cluster.id)) {
525
- this.createDefaultSwitchClusterServer();
526
- }
527
- if (includeServerList.includes(TemperatureMeasurement.Cluster.id)) {
528
- this.createDefaultTemperatureMeasurementClusterServer();
529
- }
530
- if (includeServerList.includes(RelativeHumidityMeasurement.Cluster.id)) {
531
- this.createDefaultRelativeHumidityMeasurementClusterServer();
532
- }
533
- if (includeServerList.includes(PressureMeasurement.Cluster.id)) {
534
- this.createDefaultPressureMeasurementClusterServer();
535
- }
536
- if (includeServerList.includes(BooleanState.Cluster.id)) {
537
- this.createDefaultBooleanStateClusterServer(true);
538
- }
539
- if (includeServerList.includes(OccupancySensing.Cluster.id)) {
540
- this.createDefaultOccupancySensingClusterServer(false);
541
- }
542
- if (includeServerList.includes(IlluminanceMeasurement.Cluster.id)) {
543
- this.createDefaultIlluminanceMeasurementClusterServer();
544
- }
545
- if (includeServerList.includes(AirQuality.Cluster.id)) {
546
- this.createDefaultAirQualityClusterServer();
547
- }
548
- if (includeServerList.includes(TvocMeasurement.Cluster.id)) {
549
- this.createDefaultTvocMeasurementClusterServer();
550
- }
551
- }
552
-
553
- /**
554
- * Adds mandatory client clusters to the device
555
- *
556
- * @protected
557
- * @param attributeInitialValues Optional object with initial attribute values for automatically added clusters
558
- * @param includeClientList List of clusters to include
559
- */
560
- // attributeInitialValues?: { [key: ClusterId]: AttributeInitialValues<any> },
561
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
562
- protected addDeviceClusterClient(includeClientList: ClusterId[] = []) {
563
- /* Not implemented */
564
- }
565
-
566
- public addDeviceTypeAndClusterServer(deviceType: DeviceTypeDefinition, serverList: ClusterId[]) {
567
- this.addDeviceType(deviceType);
568
- this.addDeviceClusterServer(serverList);
569
- }
570
-
571
- configure() {
572
- if (this.getClusterServerById(WindowCovering.Cluster.id)) {
573
- // eslint-disable-next-line no-console
574
- console.log(`Configuring ${this.deviceName}`);
575
- this.setWindowCoveringTargetAsCurrentAndStopped();
576
- }
577
- }
578
- }
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- /**
2
- * This file contains the entry point of Matterbridge.
3
- *
4
- * @file index.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 2.0.0
8
- *
9
- * Copyright 2023, 2024 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
-
24
- import { Matterbridge, PlatformConfig } from 'matterbridge';
25
- import { AnsiLogger } from 'node-ansi-logger';
26
- import { ZigbeePlatform } from './platform.js';
27
-
28
- /**
29
- * This is the standard interface for Matterbridge plugins.
30
- * Each plugin should export a default function that follows this signature.
31
- * Each plugin should return the platform.
32
- *
33
- * Initializes the Zigbee2mqtt plugin.
34
- *
35
- * @param {Matterbridge} matterbridge - The Matterbridge instance.
36
- * @param {AnsiLogger} log - The logger instance.
37
- * @param {PlatformConfig} config - The platform configuration.
38
- * @returns {ZigbeePlatform} The initialized Zigbee platform.
39
- */
40
- export default function initializePlugin(matterbridge: Matterbridge, log: AnsiLogger, config: PlatformConfig): ZigbeePlatform {
41
- return new ZigbeePlatform(matterbridge, log, config);
42
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * This file contains the types for Payload.
3
- *
4
- * @file payloadTypes.ts
5
- * @author Luca Liguori
6
- * @date 2023-12-29
7
- * @version 1.0.0
8
- *
9
- * Copyright 2023, 2024 Luca Liguori.
10
- *
11
- * Licensed under the Apache License, Version 2.0 (the "License");
12
- * you may not use this file except in compliance with the License.
13
- * You may obtain a copy of the License at
14
- *
15
- * http://www.apache.org/licenses/LICENSE-2.0
16
- *
17
- * Unless required by applicable law or agreed to in writing, software
18
- * distributed under the License is distributed on an "AS IS" BASIS,
19
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
- * See the License for the specific language governing permissions and
21
- * limitations under the License. *
22
- */
23
-
24
- export type PayloadValue = string | number | boolean | bigint | object | undefined | null;
25
-
26
- export type Payload = {
27
- [key: string]: PayloadValue; // This allows any string as a key, and the value can be PayloadValue.
28
- };