homebridge 2.0.0-alpha.41 → 2.0.0-alpha.42

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.
Files changed (87) hide show
  1. package/dist/api.d.ts +47 -20
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +43 -11
  4. package/dist/api.js.map +1 -1
  5. package/dist/bridgeService.d.ts +5 -10
  6. package/dist/bridgeService.d.ts.map +1 -1
  7. package/dist/bridgeService.js +3 -0
  8. package/dist/bridgeService.js.map +1 -1
  9. package/dist/childBridgeFork.d.ts +19 -0
  10. package/dist/childBridgeFork.d.ts.map +1 -1
  11. package/dist/childBridgeFork.js +198 -4
  12. package/dist/childBridgeFork.js.map +1 -1
  13. package/dist/childBridgeService.d.ts +27 -1
  14. package/dist/childBridgeService.d.ts.map +1 -1
  15. package/dist/childBridgeService.js +43 -1
  16. package/dist/childBridgeService.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +3 -1
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.d.ts +5 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/ipcService.d.ts +3 -1
  25. package/dist/ipcService.d.ts.map +1 -1
  26. package/dist/ipcService.js +2 -0
  27. package/dist/ipcService.js.map +1 -1
  28. package/dist/matter/index.d.ts +4 -6
  29. package/dist/matter/index.d.ts.map +1 -1
  30. package/dist/matter/index.js +4 -5
  31. package/dist/matter/index.js.map +1 -1
  32. package/dist/matter/matterConfigValidator.d.ts +2 -3
  33. package/dist/matter/matterConfigValidator.d.ts.map +1 -1
  34. package/dist/matter/matterConfigValidator.js +47 -38
  35. package/dist/matter/matterConfigValidator.js.map +1 -1
  36. package/dist/matter/matterErrorHandler.d.ts +6 -25
  37. package/dist/matter/matterErrorHandler.d.ts.map +1 -1
  38. package/dist/matter/matterErrorHandler.js +89 -99
  39. package/dist/matter/matterErrorHandler.js.map +1 -1
  40. package/dist/matter/matterServer.d.ts +125 -39
  41. package/dist/matter/matterServer.d.ts.map +1 -1
  42. package/dist/matter/matterServer.js +463 -223
  43. package/dist/matter/matterServer.js.map +1 -1
  44. package/dist/matter/matterSharedTypes.d.ts +1 -21
  45. package/dist/matter/matterSharedTypes.d.ts.map +1 -1
  46. package/dist/matter/matterSharedTypes.js +0 -4
  47. package/dist/matter/matterSharedTypes.js.map +1 -1
  48. package/dist/matter/matterStorage.d.ts +112 -0
  49. package/dist/matter/matterStorage.d.ts.map +1 -0
  50. package/dist/matter/matterStorage.js +355 -0
  51. package/dist/matter/matterStorage.js.map +1 -0
  52. package/dist/matter/matterTypes.d.ts +148 -20
  53. package/dist/matter/matterTypes.d.ts.map +1 -1
  54. package/dist/matter/matterTypes.js +91 -263
  55. package/dist/matter/matterTypes.js.map +1 -1
  56. package/dist/plugin.d.ts.map +1 -1
  57. package/dist/plugin.js +4 -2
  58. package/dist/plugin.js.map +1 -1
  59. package/dist/server.d.ts +12 -4
  60. package/dist/server.d.ts.map +1 -1
  61. package/dist/server.js +310 -332
  62. package/dist/server.js.map +1 -1
  63. package/dist/user.d.ts +1 -0
  64. package/dist/user.d.ts.map +1 -1
  65. package/dist/user.js +3 -0
  66. package/dist/user.js.map +1 -1
  67. package/package.json +4 -5
  68. package/dist/childMatterBridgeFork.d.ts +0 -108
  69. package/dist/childMatterBridgeFork.d.ts.map +0 -1
  70. package/dist/childMatterBridgeFork.js +0 -330
  71. package/dist/childMatterBridgeFork.js.map +0 -1
  72. package/dist/childMatterBridgeService.d.ts +0 -166
  73. package/dist/childMatterBridgeService.d.ts.map +0 -1
  74. package/dist/childMatterBridgeService.js +0 -623
  75. package/dist/childMatterBridgeService.js.map +0 -1
  76. package/dist/matter/matterBridge.d.ts +0 -64
  77. package/dist/matter/matterBridge.d.ts.map +0 -1
  78. package/dist/matter/matterBridge.js +0 -154
  79. package/dist/matter/matterBridge.js.map +0 -1
  80. package/dist/matter/matterDevice.d.ts +0 -107
  81. package/dist/matter/matterDevice.d.ts.map +0 -1
  82. package/dist/matter/matterDevice.js +0 -913
  83. package/dist/matter/matterDevice.js.map +0 -1
  84. package/dist/matter/portAllocator.d.ts +0 -85
  85. package/dist/matter/portAllocator.d.ts.map +0 -1
  86. package/dist/matter/portAllocator.js +0 -296
  87. package/dist/matter/portAllocator.js.map +0 -1
@@ -1,913 +0,0 @@
1
- /**
2
- * Matter Device Implementation
3
- *
4
- * Handles creation of individual Matter devices from HAP accessories
5
- * with proper Matter.js integration and type safety
6
- */
7
- import { Endpoint } from '@matter/main';
8
- import { BridgedDeviceBasicInformationServer } from '@matter/main/behaviors';
9
- import { ContactSensorDevice, DimmableLightDevice, DimmablePlugInUnitDevice, GenericSwitchDevice, HumiditySensorDevice, LightSensorDevice, OccupancySensorDevice, OnOffLightDevice, OnOffLightSwitchDevice, OnOffPlugInUnitDevice, OnOffSensorDevice, PressureSensorDevice, PumpDevice, RoomAirConditionerDevice, SmokeCoAlarmDevice, TemperatureSensorDevice, ThermostatDevice, WaterLeakDetectorDevice, WindowCoveringDevice, } from '@matter/main/devices';
10
- import { Logger } from '../logger.js';
11
- const log = Logger.withPrefix('Matter');
12
- /**
13
- * Type-safe wrapper for Matter endpoint with proper state handling
14
- */
15
- class TypedMatterEndpoint {
16
- endpoint;
17
- stateProxy;
18
- constructor(endpoint, stateProxy = {}) {
19
- this.endpoint = endpoint;
20
- this.stateProxy = stateProxy;
21
- }
22
- /**
23
- * Get typed state for a cluster
24
- */
25
- getClusterState(cluster) {
26
- return this.stateProxy[cluster];
27
- }
28
- /**
29
- * Update cluster state with type safety
30
- */
31
- async updateClusterState(cluster, updates) {
32
- if (!this.stateProxy[cluster]) {
33
- this.stateProxy[cluster] = {};
34
- }
35
- Object.assign(this.stateProxy[cluster], updates);
36
- // Trigger Matter.js state update
37
- // This would normally interact with the endpoint's behaviors
38
- // For now, we'll log the update
39
- log.debug(`Updated ${String(cluster)} state:`, updates);
40
- }
41
- /**
42
- * Get the raw endpoint
43
- */
44
- getRawEndpoint() {
45
- return this.endpoint;
46
- }
47
- }
48
- /**
49
- * Represents a single Matter device created from a HAP accessory
50
- */
51
- export class MatterDevice {
52
- accessory;
53
- endpoint = null;
54
- typedEndpoint = null;
55
- deviceType = null;
56
- primaryService = null;
57
- characteristicSubscriptions = new Map();
58
- cleanupCallbacks = [];
59
- isDestroyed = false;
60
- constructor(accessory) {
61
- this.accessory = accessory;
62
- // Identify primary service
63
- this.identifyPrimaryService();
64
- }
65
- /**
66
- * Get the HAP accessory
67
- */
68
- getAccessory() {
69
- return this.accessory;
70
- }
71
- /**
72
- * Identify the primary service from the accessory
73
- */
74
- identifyPrimaryService() {
75
- const services = this.accessory.services || [];
76
- // Find the primary service (not AccessoryInformation)
77
- for (const service of services) {
78
- const serviceName = service.constructor.name;
79
- if (serviceName !== 'AccessoryInformation' && serviceName !== 'ProtocolInformation') {
80
- this.primaryService = service;
81
- break;
82
- }
83
- }
84
- if (!this.primaryService && services.length > 0) {
85
- // Fallback to first service if no primary found
86
- this.primaryService = services[0];
87
- }
88
- }
89
- /**
90
- * Determine the Matter device type based on HAP service
91
- */
92
- getMatterDeviceType() {
93
- if (!this.primaryService) {
94
- return null;
95
- }
96
- const serviceName = this.primaryService.constructor.name;
97
- log.debug(`Mapping HAP service '${serviceName}' for accessory '${this.accessory.displayName}'`);
98
- const hasCharacteristic = (name) => this.primaryService.characteristics.some(c => c.constructor.name === name);
99
- // Enhanced HAP to Matter device type mapping
100
- let deviceTypeName;
101
- let endpointType;
102
- switch (serviceName) {
103
- case 'Lightbulb':
104
- // Simplify to avoid color control initialization issues
105
- if (hasCharacteristic('Brightness')) {
106
- deviceTypeName = 'DimmableLight';
107
- endpointType = DimmableLightDevice.with(BridgedDeviceBasicInformationServer);
108
- }
109
- else {
110
- deviceTypeName = 'OnOffLight';
111
- endpointType = OnOffLightDevice.with(BridgedDeviceBasicInformationServer);
112
- }
113
- break;
114
- case 'Outlet':
115
- if (hasCharacteristic('Brightness')) {
116
- deviceTypeName = 'DimmablePlugInUnit';
117
- endpointType = DimmablePlugInUnitDevice.with(BridgedDeviceBasicInformationServer);
118
- }
119
- else {
120
- deviceTypeName = 'OnOffPlugInUnit';
121
- endpointType = OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer);
122
- }
123
- break;
124
- case 'Switch':
125
- if (hasCharacteristic('ProgrammableSwitchEvent')) {
126
- deviceTypeName = 'GenericSwitch';
127
- endpointType = GenericSwitchDevice.with(BridgedDeviceBasicInformationServer);
128
- }
129
- else if (hasCharacteristic('Brightness')) {
130
- // Note: DimmerSwitch not available in Matter.js v0.15, use light switch
131
- deviceTypeName = 'OnOffLightSwitch';
132
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
133
- }
134
- else {
135
- deviceTypeName = 'OnOffLightSwitch';
136
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
137
- }
138
- break;
139
- case 'TemperatureSensor':
140
- deviceTypeName = 'TemperatureSensor';
141
- endpointType = TemperatureSensorDevice.with(BridgedDeviceBasicInformationServer);
142
- break;
143
- case 'HumiditySensor':
144
- deviceTypeName = 'HumiditySensor';
145
- endpointType = HumiditySensorDevice.with(BridgedDeviceBasicInformationServer);
146
- break;
147
- case 'LightSensor':
148
- deviceTypeName = 'LightSensor';
149
- endpointType = LightSensorDevice.with(BridgedDeviceBasicInformationServer);
150
- break;
151
- case 'OccupancySensor':
152
- deviceTypeName = 'OccupancySensor';
153
- endpointType = OccupancySensorDevice.with(BridgedDeviceBasicInformationServer);
154
- break;
155
- case 'ContactSensor':
156
- deviceTypeName = 'ContactSensor';
157
- endpointType = ContactSensorDevice.with(BridgedDeviceBasicInformationServer);
158
- break;
159
- case 'LockManagement':
160
- case 'Lock':
161
- case 'LockMgmt':
162
- case 'LockMechanism':
163
- // DoorLock has issues with actuatorEnabled, use simpler switch
164
- deviceTypeName = 'OnOffLightSwitch';
165
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
166
- break;
167
- case 'WindowCovering':
168
- case 'Window':
169
- deviceTypeName = 'WindowCovering';
170
- endpointType = WindowCoveringDevice.with(BridgedDeviceBasicInformationServer);
171
- break;
172
- case 'Thermostat':
173
- deviceTypeName = 'Thermostat';
174
- endpointType = ThermostatDevice.with(BridgedDeviceBasicInformationServer);
175
- break;
176
- case 'Fan':
177
- case 'Fanv2':
178
- // Fan has issues with fanModeSequence and percentCurrent, use switch
179
- deviceTypeName = 'OnOffLightSwitch';
180
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
181
- break;
182
- case 'GarageDoorOpener':
183
- case 'Door':
184
- // DoorLock has issues with actuatorEnabled, use simpler switch
185
- deviceTypeName = 'OnOffLightSwitch';
186
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
187
- break;
188
- case 'HeaterCooler':
189
- deviceTypeName = 'RoomAirConditioner';
190
- endpointType = RoomAirConditionerDevice.with(BridgedDeviceBasicInformationServer);
191
- break;
192
- case 'HumidifierDehumidifier':
193
- // Fan has issues with fanModeSequence and percentCurrent, use switch
194
- deviceTypeName = 'OnOffLightSwitch';
195
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
196
- break;
197
- case 'Valve':
198
- case 'Faucet':
199
- // WaterValve has issues with open/close commands, use switch
200
- deviceTypeName = 'OnOffLightSwitch';
201
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
202
- break;
203
- case 'IrrigationSystem':
204
- deviceTypeName = 'Pump';
205
- endpointType = PumpDevice.with(BridgedDeviceBasicInformationServer);
206
- break;
207
- case 'LeakSensor':
208
- deviceTypeName = 'WaterLeakDetector';
209
- endpointType = WaterLeakDetectorDevice.with(BridgedDeviceBasicInformationServer);
210
- break;
211
- case 'MotionSensor':
212
- deviceTypeName = 'OccupancySensor';
213
- endpointType = OccupancySensorDevice.with(BridgedDeviceBasicInformationServer);
214
- break;
215
- case 'SmokeSensor':
216
- case 'CarbonMonoxideSensor':
217
- case 'CarbonDioxideSensor':
218
- deviceTypeName = 'SmokeCoAlarm';
219
- endpointType = SmokeCoAlarmDevice.with(BridgedDeviceBasicInformationServer);
220
- break;
221
- case 'PressureSensor':
222
- deviceTypeName = 'PressureSensor';
223
- endpointType = PressureSensorDevice.with(BridgedDeviceBasicInformationServer);
224
- break;
225
- case 'SecuritySystem':
226
- deviceTypeName = 'OnOffSensor';
227
- endpointType = OnOffSensorDevice.with(BridgedDeviceBasicInformationServer);
228
- break;
229
- case 'StatelessProgrammableSwitch':
230
- deviceTypeName = 'GenericSwitch';
231
- endpointType = GenericSwitchDevice.with(BridgedDeviceBasicInformationServer);
232
- break;
233
- case 'Doorbell':
234
- deviceTypeName = 'GenericSwitch';
235
- endpointType = GenericSwitchDevice.with(BridgedDeviceBasicInformationServer);
236
- break;
237
- case 'Battery':
238
- case 'BatteryService':
239
- deviceTypeName = 'OnOffSensor';
240
- endpointType = OnOffSensorDevice.with(BridgedDeviceBasicInformationServer);
241
- break;
242
- case 'Slat':
243
- deviceTypeName = 'WindowCovering';
244
- endpointType = WindowCoveringDevice.with(BridgedDeviceBasicInformationServer);
245
- break;
246
- // Television/Media accessories
247
- case 'Television':
248
- case 'TelevisionSpeaker':
249
- case 'InputSource':
250
- // Use basic switch for TV control
251
- deviceTypeName = 'OnOffLightSwitch';
252
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
253
- break;
254
- // Speaker accessories
255
- case 'Speaker':
256
- case 'AirPlaySpeaker':
257
- // Use basic switch for speaker control
258
- deviceTypeName = 'OnOffLightSwitch';
259
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
260
- break;
261
- // Camera accessories
262
- case 'Camera':
263
- case 'CameraRTPStreamManagement':
264
- case 'CameraRecordingManagement':
265
- case 'CameraOperatingMode':
266
- case 'CameraEventRecordingManagement':
267
- // Cameras aren't directly supported in Matter, use sensor
268
- deviceTypeName = 'OccupancySensor';
269
- endpointType = OccupancySensorDevice.with(BridgedDeviceBasicInformationServer);
270
- break;
271
- // Audio/Video accessories
272
- case 'Microphone':
273
- case 'AudioStreamManagement':
274
- // Use basic switch for audio control
275
- deviceTypeName = 'OnOffLightSwitch';
276
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
277
- break;
278
- // Service/Management types
279
- case 'AccessoryInformation':
280
- case 'AccessoryRuntimeInformation':
281
- case 'ServiceLabel':
282
- case 'ThreadTransport':
283
- case 'WiFiTransport':
284
- case 'PowerManagement':
285
- case 'TransferTransportManagement':
286
- case 'TimeInformation':
287
- case 'FirmwareUpdate':
288
- case 'DiagnosticsSnapshot':
289
- // Skip service/management types - return null
290
- log.debug(`Skipping service type: ${serviceName}`);
291
- return null;
292
- // Siri/Assistant
293
- case 'Siri':
294
- case 'SiriEndpoint':
295
- case 'Assistant':
296
- case 'TargetControlManagement':
297
- case 'TargetControl':
298
- // Use basic switch for assistant control
299
- deviceTypeName = 'OnOffLightSwitch';
300
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
301
- break;
302
- // Additional sensors
303
- case 'AirQualitySensor':
304
- // Use temperature sensor as closest match
305
- deviceTypeName = 'TemperatureSensor';
306
- endpointType = TemperatureSensorDevice.with(BridgedDeviceBasicInformationServer);
307
- break;
308
- case 'FilterMaintenance':
309
- case 'AirPurifier':
310
- // Map to switch for air purifier control
311
- deviceTypeName = 'OnOffLightSwitch';
312
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
313
- break;
314
- // Additional common types
315
- case 'WiFiRouter':
316
- case 'WiFiSatellite':
317
- // Network devices - use basic switch
318
- deviceTypeName = 'OnOffLightSwitch';
319
- endpointType = OnOffLightSwitchDevice.with(BridgedDeviceBasicInformationServer);
320
- break;
321
- case 'PowerStrip':
322
- // Multiple outlets - use outlet
323
- deviceTypeName = 'OnOffPlugInUnit';
324
- endpointType = OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer);
325
- break;
326
- case 'ChargerService':
327
- // EV Charger or device charger
328
- deviceTypeName = 'OnOffPlugInUnit';
329
- endpointType = OnOffPlugInUnitDevice.with(BridgedDeviceBasicInformationServer);
330
- break;
331
- default:
332
- // Log all available services for debugging
333
- if (this.accessory.services && this.accessory.services.length > 0) {
334
- const serviceTypes = this.accessory.services.map(s => s.constructor.name).join(', ');
335
- log.warn(`Unknown HAP service type: ${serviceName} for accessory: ${this.accessory.displayName}. Available services: ${serviceTypes}`);
336
- }
337
- else {
338
- log.warn(`Unknown HAP service type: ${serviceName} for accessory: ${this.accessory.displayName}`);
339
- }
340
- // Default to basic on/off sensor
341
- deviceTypeName = 'OnOffSensor';
342
- endpointType = OnOffSensorDevice.with(BridgedDeviceBasicInformationServer);
343
- }
344
- this.deviceType = deviceTypeName;
345
- return { deviceType: deviceTypeName, endpointType };
346
- }
347
- /**
348
- * Create Matter endpoint from HAP accessory
349
- */
350
- async createEndpoint() {
351
- try {
352
- // Identify primary service
353
- this.primaryService = this.getPrimaryService();
354
- if (!this.primaryService) {
355
- log.warn(`No primary service found for: ${this.accessory.displayName}`);
356
- return null;
357
- }
358
- // Determine device type based on service
359
- const deviceMapping = this.getMatterDeviceType();
360
- if (!deviceMapping) {
361
- // Always log the service type for unsupported devices
362
- const serviceName = this.primaryService.constructor.name;
363
- log.warn(`Unsupported device type for: ${this.accessory.displayName} (Service: ${serviceName})`);
364
- return null;
365
- }
366
- const { deviceType, endpointType } = deviceMapping;
367
- this.deviceType = deviceType;
368
- log.info(`Creating Matter endpoint: ${deviceType} for ${this.accessory.displayName}`);
369
- // Create endpoint with proper configuration
370
- this.endpoint = await this.createMatterEndpoint(endpointType);
371
- if (!this.endpoint) {
372
- log.error(`Failed to create Matter endpoint for: ${this.accessory.displayName}`);
373
- return null;
374
- }
375
- // Create typed endpoint wrapper for type-safe state management
376
- this.typedEndpoint = new TypedMatterEndpoint(this.endpoint);
377
- // Initialize with current HAP values
378
- await this.initializeFromHAP();
379
- log.debug(`✅ Created Matter endpoint: ${this.deviceType} for ${this.accessory.displayName}`);
380
- return this.endpoint;
381
- }
382
- catch (error) {
383
- log.error(`Failed to create endpoint for ${this.accessory.displayName}:`, error);
384
- return null;
385
- }
386
- }
387
- /**
388
- * Parse version string to number (major version only)
389
- */
390
- parseVersion(versionString) {
391
- try {
392
- const parts = versionString.split('.');
393
- const major = Number.parseInt(parts[0], 10);
394
- return Number.isNaN(major) ? 1 : Math.min(major, 65535); // Limit to UINT16
395
- }
396
- catch {
397
- return 1;
398
- }
399
- }
400
- /**
401
- * Get AccessoryInformation values from HAP accessory
402
- */
403
- getAccessoryInformation() {
404
- // Try to get AccessoryInformation service from the accessory
405
- const infoService = this.accessory.services?.find(service => service.constructor.name === 'AccessoryInformation');
406
- // Default values matching Homebridge HAP bridge defaults
407
- let manufacturer = 'homebridge.io';
408
- let model = 'homebridge';
409
- let serialNumber = this.accessory.UUID.substring(0, 32);
410
- let firmwareRevision = '2.0.0';
411
- let hardwareRevision = '1.0.0';
412
- if (infoService) {
413
- // Get Manufacturer characteristic
414
- const manufacturerChar = infoService.characteristics?.find(c => c.constructor.name === 'Manufacturer');
415
- if (manufacturerChar?.value !== null && manufacturerChar?.value !== undefined) {
416
- manufacturer = String(manufacturerChar.value) || manufacturer;
417
- }
418
- // Get Model characteristic
419
- const modelChar = infoService.characteristics?.find(c => c.constructor.name === 'Model');
420
- if (modelChar?.value !== null && modelChar?.value !== undefined) {
421
- model = String(modelChar.value) || model;
422
- }
423
- // Get SerialNumber characteristic
424
- const serialChar = infoService.characteristics?.find(c => c.constructor.name === 'SerialNumber');
425
- if (serialChar?.value !== null && serialChar?.value !== undefined) {
426
- const serialValue = String(serialChar.value);
427
- serialNumber = serialValue ? serialValue.substring(0, 32) : serialNumber;
428
- }
429
- // Get FirmwareRevision characteristic
430
- const firmwareChar = infoService.characteristics?.find(c => c.constructor.name === 'FirmwareRevision');
431
- if (firmwareChar?.value !== null && firmwareChar?.value !== undefined) {
432
- firmwareRevision = String(firmwareChar.value) || firmwareRevision;
433
- }
434
- // Get HardwareRevision characteristic
435
- const hardwareChar = infoService.characteristics?.find(c => c.constructor.name === 'HardwareRevision');
436
- if (hardwareChar?.value !== null && hardwareChar?.value !== undefined) {
437
- hardwareRevision = String(hardwareChar.value) || hardwareRevision;
438
- }
439
- }
440
- return {
441
- manufacturer,
442
- model,
443
- serialNumber,
444
- firmwareRevision,
445
- hardwareRevision,
446
- };
447
- }
448
- /**
449
- * Create the specific Matter endpoint with proper type
450
- */
451
- async createMatterEndpoint(endpointType) {
452
- // Generate unique identifiers
453
- const uuidClean = this.accessory.UUID.replace(/-/g, '');
454
- const uniqueId = uuidClean.padEnd(32, '0').substring(0, 32);
455
- const deviceName = this.accessory.displayName;
456
- // Get AccessoryInformation from HAP accessory
457
- const accessoryInfo = this.getAccessoryInformation();
458
- log.debug(`AccessoryInformation for ${deviceName}:`, {
459
- manufacturer: accessoryInfo.manufacturer,
460
- model: accessoryInfo.model,
461
- serialNumber: accessoryInfo.serialNumber,
462
- firmware: accessoryInfo.firmwareRevision,
463
- hardware: accessoryInfo.hardwareRevision,
464
- });
465
- // Create bridged device configuration using HAP AccessoryInformation values
466
- // IMPORTANT: Both nodeLabel and productLabel must be set to the device name for it to appear correctly in Home app
467
- const bridgedDeviceConfig = {
468
- nodeLabel: deviceName.slice(0, 32), // Maximum 32 characters for nodeLabel
469
- productLabel: deviceName.slice(0, 64), // Maximum 64 characters for productLabel
470
- productName: accessoryInfo.model.slice(0, 32), // Model from HAP AccessoryInformation
471
- serialNumber: accessoryInfo.serialNumber.slice(0, 32), // SerialNumber from HAP
472
- uniqueId,
473
- vendorName: accessoryInfo.manufacturer.slice(0, 32), // Manufacturer from HAP
474
- vendorId: 0xFFF1, // Homebridge vendor ID
475
- productId: 0x8001,
476
- reachable: true,
477
- softwareVersion: this.parseVersion(accessoryInfo.firmwareRevision),
478
- softwareVersionString: accessoryInfo.firmwareRevision.slice(0, 64),
479
- hardwareVersion: this.parseVersion(accessoryInfo.hardwareRevision),
480
- hardwareVersionString: accessoryInfo.hardwareRevision.slice(0, 64),
481
- };
482
- try {
483
- // Create endpoint configuration with initial state based on device type
484
- const endpointConfig = {
485
- id: `endpoint-${this.accessory.UUID}`,
486
- bridgedDeviceBasicInformation: bridgedDeviceConfig,
487
- };
488
- // Set initial state based on device type
489
- // This maps HAP characteristics to Matter cluster attributes
490
- await this.setInitialEndpointState(endpointConfig);
491
- // Create the endpoint with BridgedDeviceBasicInformation
492
- // The endpoint needs the bridged device information for proper naming
493
- const endpoint = new Endpoint(endpointType, endpointConfig);
494
- return endpoint;
495
- }
496
- catch (error) {
497
- log.error(`Failed to create Matter device: ${error}`);
498
- return null;
499
- }
500
- }
501
- /**
502
- * Set initial endpoint state based on HAP values
503
- */
504
- async setInitialEndpointState(config) {
505
- // Map common characteristics
506
- switch (this.deviceType) {
507
- case 'OnOffLight':
508
- case 'OnOffPlugInUnit':
509
- case 'OnOffLightSwitch': {
510
- config.onOff = {
511
- onOff: this.getHAPValue('On') || false,
512
- };
513
- break;
514
- }
515
- case 'DimmableLight':
516
- case 'DimmablePlugInUnit': {
517
- config.onOff = {
518
- onOff: this.getHAPValue('On') || false,
519
- };
520
- config.levelControl = {
521
- currentLevel: Math.round((this.getHAPValue('Brightness') || 0) * 2.54),
522
- minLevel: 1,
523
- maxLevel: 254,
524
- };
525
- break;
526
- }
527
- case 'ColorTemperatureLight': {
528
- config.onOff = {
529
- onOff: this.getHAPValue('On') || false,
530
- };
531
- config.levelControl = {
532
- currentLevel: Math.round((this.getHAPValue('Brightness') || 0) * 2.54),
533
- minLevel: 1,
534
- maxLevel: 254,
535
- };
536
- const colorTemp = this.getHAPValue('ColorTemperature') || 370;
537
- const mireds = Math.round(1000000 / colorTemp);
538
- config.colorControl = {
539
- colorMode: 2,
540
- colorTemperatureMireds: Math.max(153, Math.min(500, mireds)),
541
- colorTempPhysicalMinMireds: 153,
542
- colorTempPhysicalMaxMireds: 500,
543
- };
544
- break;
545
- }
546
- case 'ExtendedColorLight': {
547
- config.onOff = {
548
- onOff: this.getHAPValue('On') || false,
549
- };
550
- config.levelControl = {
551
- currentLevel: Math.round((this.getHAPValue('Brightness') || 0) * 2.54),
552
- minLevel: 1,
553
- maxLevel: 254,
554
- };
555
- config.colorControl = {
556
- colorMode: 0,
557
- currentHue: Math.round((this.getHAPValue('Hue') || 0) * 254 / 360),
558
- currentSaturation: Math.round((this.getHAPValue('Saturation') || 0) * 2.54),
559
- colorTemperatureMireds: 370,
560
- colorTempPhysicalMinMireds: 153,
561
- colorTempPhysicalMaxMireds: 500,
562
- };
563
- break;
564
- }
565
- case 'TemperatureSensor': {
566
- config.temperatureMeasurement = {
567
- measuredValue: Math.round((this.getHAPValue('CurrentTemperature') || 20) * 100),
568
- minMeasuredValue: -27315,
569
- maxMeasuredValue: 32767,
570
- };
571
- break;
572
- }
573
- case 'HumiditySensor': {
574
- config.relativeHumidityMeasurement = {
575
- measuredValue: Math.round((this.getHAPValue('CurrentRelativeHumidity') || 50) * 100),
576
- minMeasuredValue: 0,
577
- maxMeasuredValue: 10000,
578
- };
579
- break;
580
- }
581
- case 'LightSensor': {
582
- const lux = this.getHAPValue('CurrentAmbientLightLevel') || 100;
583
- config.illuminanceMeasurement = {
584
- measuredValue: Math.round(Math.log10(Math.max(1, lux)) * 10000),
585
- minMeasuredValue: 0,
586
- maxMeasuredValue: 65534,
587
- };
588
- break;
589
- }
590
- case 'OccupancySensor': {
591
- config.occupancySensing = {
592
- occupancy: {
593
- occupied: Boolean(this.getHAPValue('OccupancyDetected') || this.getHAPValue('MotionDetected')),
594
- },
595
- };
596
- break;
597
- }
598
- case 'ContactSensor': {
599
- config.booleanState = {
600
- stateValue: (this.getHAPValue('ContactSensorState') || 0) === 0,
601
- };
602
- break;
603
- }
604
- case 'DoorLock': {
605
- const lockState = this.getHAPValue('LockCurrentState') || 0;
606
- config.doorLock = {
607
- lockState: lockState === 1 ? 1 : 2,
608
- lockType: 0,
609
- };
610
- break;
611
- }
612
- case 'WindowCovering': {
613
- const position = this.getHAPValue('CurrentPosition') || 0;
614
- config.windowCovering = {
615
- currentPositionLiftPercent100ths: position * 100,
616
- targetPositionLiftPercent100ths: (this.getHAPValue('TargetPosition') || position) * 100,
617
- };
618
- break;
619
- }
620
- case 'Thermostat': {
621
- config.thermostat = {
622
- localTemperature: Math.round((this.getHAPValue('CurrentTemperature') || 20) * 100),
623
- occupiedCoolingSetpoint: Math.round((this.getHAPValue('CoolingThresholdTemperature') || 26) * 100),
624
- occupiedHeatingSetpoint: Math.round((this.getHAPValue('HeatingThresholdTemperature') || 20) * 100),
625
- systemMode: this.getHAPValue('CurrentHeatingCoolingState') || 0,
626
- };
627
- break;
628
- }
629
- case 'Fan': {
630
- const speed = this.getHAPValue('RotationSpeed') || 0;
631
- config.fanControl = {
632
- percentSetting: Math.round(speed),
633
- fanMode: speed > 0 ? 3 : 0,
634
- };
635
- break;
636
- }
637
- case 'WaterLeakDetector': {
638
- config.booleanState = {
639
- stateValue: Boolean(this.getHAPValue('LeakDetected')),
640
- };
641
- break;
642
- }
643
- case 'SmokeCoAlarm': {
644
- config.smokeCoAlarm = {
645
- smokeState: this.getHAPValue('SmokeDetected') ? 1 : 0,
646
- coState: this.getHAPValue('CarbonMonoxideDetected') ? 1 : 0,
647
- };
648
- break;
649
- }
650
- case 'GenericSwitch': {
651
- // Generic switch for programmable switches
652
- config.switch = {
653
- currentPosition: 0,
654
- numberOfPositions: 2,
655
- };
656
- break;
657
- }
658
- default: {
659
- // Default to basic on/off if we have the characteristic
660
- if (this.getHAPValue('On') !== undefined) {
661
- config.onOff = {
662
- onOff: this.getHAPValue('On') || false,
663
- };
664
- }
665
- break;
666
- }
667
- }
668
- }
669
- /**
670
- * Initialize Matter endpoint from HAP values
671
- */
672
- async initializeFromHAP() {
673
- if (!this.endpoint || !this.primaryService || !this.typedEndpoint) {
674
- return;
675
- }
676
- // Initial values are already set during endpoint creation
677
- // This method is for future use when we need to refresh values
678
- log.debug(`Initialized Matter device with HAP values for: ${this.accessory.displayName}`);
679
- }
680
- /**
681
- * Start bidirectional sync between HAP and Matter
682
- */
683
- startSync() {
684
- if (!this.primaryService || !this.endpoint || this.isDestroyed) {
685
- return;
686
- }
687
- log.debug(`Starting sync for: ${this.accessory.displayName}`);
688
- // Limit number of subscriptions to prevent memory issues
689
- const MAX_SUBSCRIPTIONS = 50; // Prevent memory leaks from unbounded subscriptions
690
- let subscriptionCount = 0;
691
- // Subscribe to HAP characteristic changes
692
- for (const characteristic of this.primaryService.characteristics) {
693
- const charType = characteristic.constructor.name;
694
- // Skip non-data characteristics
695
- if (charType === 'Name' || charType === 'ServiceLabelIndex') {
696
- continue;
697
- }
698
- // Check subscription limit
699
- if (subscriptionCount >= MAX_SUBSCRIPTIONS) {
700
- log.warn(`Subscription limit reached (${MAX_SUBSCRIPTIONS}) for ${this.accessory.displayName}, skipping additional characteristics`);
701
- break;
702
- }
703
- // Prevent duplicate subscriptions
704
- if (this.characteristicSubscriptions.has(charType)) {
705
- log.debug(`Already subscribed to ${charType} for ${this.accessory.displayName}`);
706
- continue;
707
- }
708
- const handler = this.createHAPChangeHandler(charType);
709
- if (handler) {
710
- characteristic.on('change', handler);
711
- this.characteristicSubscriptions.set(charType, handler);
712
- this.cleanupCallbacks.push(() => {
713
- characteristic.off('change', handler);
714
- });
715
- subscriptionCount++;
716
- }
717
- }
718
- log.debug(`✅ Sync started for: ${this.accessory.displayName}`);
719
- }
720
- /**
721
- * Create HAP change handler for a characteristic
722
- */
723
- createHAPChangeHandler(characteristicType) {
724
- return (change) => {
725
- if (this.isDestroyed || !this.typedEndpoint) {
726
- return;
727
- }
728
- const { newValue } = change;
729
- log.debug(`HAP change for ${this.accessory.displayName}: ${characteristicType} = ${newValue}`);
730
- // Map HAP changes to Matter updates
731
- // This would be expanded to handle all characteristic types
732
- this.updateMatterFromHAP(characteristicType, newValue).catch((error) => {
733
- log.error(`Failed to update Matter from HAP for ${this.accessory.displayName}:`, error);
734
- });
735
- };
736
- }
737
- /**
738
- * Update Matter state from HAP change
739
- */
740
- async updateMatterFromHAP(characteristicType, value) {
741
- if (!this.typedEndpoint) {
742
- return;
743
- }
744
- // Map HAP characteristics to Matter cluster updates
745
- switch (characteristicType) {
746
- case 'On':
747
- await this.typedEndpoint.updateClusterState('onOff', { onOff: Boolean(value) });
748
- break;
749
- case 'Brightness':
750
- await this.typedEndpoint.updateClusterState('levelControl', {
751
- currentLevel: Math.round(value * 2.54),
752
- });
753
- break;
754
- case 'Hue':
755
- await this.typedEndpoint.updateClusterState('colorControl', {
756
- currentHue: Math.round(value * 254 / 360),
757
- });
758
- break;
759
- case 'Saturation':
760
- await this.typedEndpoint.updateClusterState('colorControl', {
761
- currentSaturation: Math.round(value * 2.54),
762
- });
763
- break;
764
- case 'ColorTemperature': {
765
- const mireds = Math.round(1000000 / value);
766
- await this.typedEndpoint.updateClusterState('colorControl', {
767
- colorTemperatureMireds: Math.max(153, Math.min(500, mireds)),
768
- });
769
- break;
770
- }
771
- case 'CurrentTemperature':
772
- await this.typedEndpoint.updateClusterState('temperatureMeasurement', {
773
- measuredValue: Math.round(value * 100),
774
- });
775
- break;
776
- case 'CurrentRelativeHumidity':
777
- await this.typedEndpoint.updateClusterState('relativeHumidityMeasurement', {
778
- measuredValue: Math.round(value * 100),
779
- });
780
- break;
781
- case 'OccupancyDetected':
782
- case 'MotionDetected':
783
- await this.typedEndpoint.updateClusterState('occupancySensing', {
784
- occupancy: { occupied: Boolean(value) },
785
- });
786
- break;
787
- case 'ContactSensorState':
788
- await this.typedEndpoint.updateClusterState('booleanState', {
789
- stateValue: value === 0,
790
- });
791
- break;
792
- case 'LockCurrentState':
793
- await this.typedEndpoint.updateClusterState('doorLock', {
794
- lockState: value === 1 ? 1 : 2,
795
- });
796
- break;
797
- // Add more characteristic mappings as needed
798
- }
799
- }
800
- /**
801
- * Stop synchronization
802
- */
803
- stopSync() {
804
- // Remove all HAP characteristic subscriptions
805
- if (this.primaryService) {
806
- for (const [charType, handler] of this.characteristicSubscriptions) {
807
- const characteristic = this.primaryService.characteristics.find(c => c.constructor.name === charType);
808
- if (characteristic && typeof handler === 'function') {
809
- characteristic.off('change', handler);
810
- }
811
- }
812
- }
813
- this.characteristicSubscriptions.clear();
814
- // Run all cleanup callbacks
815
- for (const cleanup of this.cleanupCallbacks) {
816
- try {
817
- const result = cleanup();
818
- if (result instanceof Promise) {
819
- result.catch(error => log.debug('Cleanup error:', error));
820
- }
821
- }
822
- catch (error) {
823
- log.debug('Cleanup error:', error);
824
- }
825
- }
826
- this.cleanupCallbacks.length = 0; // Clear array contents
827
- log.debug(`Sync stopped for: ${this.accessory.displayName}`);
828
- }
829
- /**
830
- * Get the primary service from accessory
831
- */
832
- getPrimaryService() {
833
- const utilityServices = [
834
- 'AccessoryInformation',
835
- 'BridgingState',
836
- 'HAPProtocolInformation',
837
- 'Pairing',
838
- 'BridgeConfiguration',
839
- ];
840
- for (const service of this.accessory.services) {
841
- // Try multiple ways to get the service type
842
- const serviceType = service.constructor.name
843
- || service.displayName
844
- || service.UUID;
845
- // Also check the service's UUID/subtype
846
- const serviceName = service.displayName || serviceType;
847
- if (!utilityServices.includes(serviceType)) {
848
- log.debug(`Found primary service for ${this.accessory.displayName}: ${serviceName} (type: ${serviceType})`);
849
- return service;
850
- }
851
- }
852
- return null;
853
- }
854
- /**
855
- * Get HAP characteristic value
856
- */
857
- getHAPValue(characteristicType) {
858
- const characteristic = this.getHAPCharacteristic(characteristicType);
859
- return characteristic?.value;
860
- }
861
- /**
862
- * Get HAP characteristic by type
863
- */
864
- getHAPCharacteristic(characteristicType) {
865
- if (!this.primaryService) {
866
- return null;
867
- }
868
- return this.primaryService.characteristics.find(c => c.constructor.name === characteristicType) || null;
869
- }
870
- /**
871
- * Convert Kelvin to Mireds
872
- */
873
- kelvinToMireds(kelvin) {
874
- return Math.round(1000000 / kelvin);
875
- }
876
- /**
877
- * Convert Mireds to Kelvin
878
- */
879
- miredsToKelvin(mireds) {
880
- return Math.round(1000000 / mireds);
881
- }
882
- /**
883
- * Get the device type
884
- */
885
- getDeviceType() {
886
- return this.deviceType;
887
- }
888
- /**
889
- * Get the Matter endpoint
890
- */
891
- async getEndpoint() {
892
- return this.endpoint;
893
- }
894
- /**
895
- * Clean up the device
896
- */
897
- async destroy() {
898
- if (this.isDestroyed) {
899
- return;
900
- }
901
- this.isDestroyed = true;
902
- this.stopSync();
903
- // Clear all references
904
- this.endpoint = null;
905
- this.typedEndpoint = null;
906
- this.primaryService = null;
907
- this.deviceType = null;
908
- this.characteristicSubscriptions.clear();
909
- this.cleanupCallbacks.length = 0; // Clear array contents
910
- log.debug(`Destroyed Matter device for: ${this.accessory.displayName}`);
911
- }
912
- }
913
- //# sourceMappingURL=matterDevice.js.map