homebridge 2.0.0-beta.30 → 2.0.0-beta.31

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 (88) hide show
  1. package/dist/api.d.ts +267 -3
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +92 -0
  4. package/dist/api.js.map +1 -1
  5. package/dist/bridgeService.d.ts +4 -2
  6. package/dist/bridgeService.d.ts.map +1 -1
  7. package/dist/bridgeService.js +1 -3
  8. package/dist/bridgeService.js.map +1 -1
  9. package/dist/childBridgeFork.d.ts +29 -2
  10. package/dist/childBridgeFork.d.ts.map +1 -1
  11. package/dist/childBridgeFork.js +294 -4
  12. package/dist/childBridgeFork.js.map +1 -1
  13. package/dist/childBridgeService.d.ts +19 -0
  14. package/dist/childBridgeService.d.ts.map +1 -1
  15. package/dist/childBridgeService.js +38 -3
  16. package/dist/childBridgeService.js.map +1 -1
  17. package/dist/externalPortService.d.ts +27 -6
  18. package/dist/externalPortService.d.ts.map +1 -1
  19. package/dist/externalPortService.js +73 -7
  20. package/dist/externalPortService.js.map +1 -1
  21. package/dist/index.d.ts +47 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +15 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/ipcService.d.ts +20 -0
  26. package/dist/ipcService.d.ts.map +1 -1
  27. package/dist/ipcService.js.map +1 -1
  28. package/dist/logger.d.ts +6 -0
  29. package/dist/logger.d.ts.map +1 -1
  30. package/dist/logger.js +8 -0
  31. package/dist/logger.js.map +1 -1
  32. package/dist/matter/index.d.ts +123 -0
  33. package/dist/matter/index.d.ts.map +1 -0
  34. package/dist/matter/index.js +19 -0
  35. package/dist/matter/index.js.map +1 -0
  36. package/dist/matter/matterAccessoryCache.d.ts +96 -0
  37. package/dist/matter/matterAccessoryCache.d.ts.map +1 -0
  38. package/dist/matter/matterAccessoryCache.js +192 -0
  39. package/dist/matter/matterAccessoryCache.js.map +1 -0
  40. package/dist/matter/matterBehaviors.d.ts +194 -0
  41. package/dist/matter/matterBehaviors.d.ts.map +1 -0
  42. package/dist/matter/matterBehaviors.js +665 -0
  43. package/dist/matter/matterBehaviors.js.map +1 -0
  44. package/dist/matter/matterConfigValidator.d.ts +81 -0
  45. package/dist/matter/matterConfigValidator.d.ts.map +1 -0
  46. package/dist/matter/matterConfigValidator.js +240 -0
  47. package/dist/matter/matterConfigValidator.js.map +1 -0
  48. package/dist/matter/matterErrorHandler.d.ts +106 -0
  49. package/dist/matter/matterErrorHandler.d.ts.map +1 -0
  50. package/dist/matter/matterErrorHandler.js +495 -0
  51. package/dist/matter/matterErrorHandler.js.map +1 -0
  52. package/dist/matter/matterLogFormatter.d.ts +19 -0
  53. package/dist/matter/matterLogFormatter.d.ts.map +1 -0
  54. package/dist/matter/matterLogFormatter.js +144 -0
  55. package/dist/matter/matterLogFormatter.js.map +1 -0
  56. package/dist/matter/matterNetworkMonitor.d.ts +68 -0
  57. package/dist/matter/matterNetworkMonitor.d.ts.map +1 -0
  58. package/dist/matter/matterNetworkMonitor.js +249 -0
  59. package/dist/matter/matterNetworkMonitor.js.map +1 -0
  60. package/dist/matter/matterServer.d.ts +656 -0
  61. package/dist/matter/matterServer.d.ts.map +1 -0
  62. package/dist/matter/matterServer.js +1690 -0
  63. package/dist/matter/matterServer.js.map +1 -0
  64. package/dist/matter/matterServerHelpers.d.ts +81 -0
  65. package/dist/matter/matterServerHelpers.d.ts.map +1 -0
  66. package/dist/matter/matterServerHelpers.js +323 -0
  67. package/dist/matter/matterServerHelpers.js.map +1 -0
  68. package/dist/matter/matterSharedTypes.d.ts +170 -0
  69. package/dist/matter/matterSharedTypes.d.ts.map +1 -0
  70. package/dist/matter/matterSharedTypes.js +52 -0
  71. package/dist/matter/matterSharedTypes.js.map +1 -0
  72. package/dist/matter/matterStorage.d.ts +128 -0
  73. package/dist/matter/matterStorage.d.ts.map +1 -0
  74. package/dist/matter/matterStorage.js +415 -0
  75. package/dist/matter/matterStorage.js.map +1 -0
  76. package/dist/matter/matterTypes.d.ts +745 -0
  77. package/dist/matter/matterTypes.d.ts.map +1 -0
  78. package/dist/matter/matterTypes.js +174 -0
  79. package/dist/matter/matterTypes.js.map +1 -0
  80. package/dist/server.d.ts +23 -1
  81. package/dist/server.d.ts.map +1 -1
  82. package/dist/server.js +385 -7
  83. package/dist/server.js.map +1 -1
  84. package/dist/user.d.ts +1 -0
  85. package/dist/user.d.ts.map +1 -1
  86. package/dist/user.js +3 -0
  87. package/dist/user.js.map +1 -1
  88. package/package.json +16 -15
@@ -0,0 +1,665 @@
1
+ /**
2
+ * Custom Matter Behavior Classes for Homebridge
3
+ *
4
+ * These custom behaviors extend the base Matter.js behaviors and override
5
+ * command methods to allow plugins to inject custom handlers.
6
+ *
7
+ * Note: Only clusters with user-triggered commands need custom behaviors.
8
+ * Read-only clusters (like sensors) don't need custom behaviors since
9
+ * they only report state, they don't receive commands.
10
+ */
11
+ import { ColorControlServer, DoorLockServer, FanControlServer, IdentifyServer, LevelControlServer, OnOffServer, RvcCleanModeServer, RvcOperationalStateServer, RvcRunModeServer, ServiceAreaServer, ThermostatServer, WindowCoveringBaseServer, } from '@matter/main/behaviors';
12
+ import { RvcOperationalState } from '@matter/main/clusters';
13
+ import { Logger } from '../logger.js';
14
+ import { clusterNames } from './matterTypes.js';
15
+ const log = Logger.withPrefix('Matter/Behaviours');
16
+ /**
17
+ * Command names for each cluster
18
+ * Provides type safety and autocomplete for command names
19
+ */
20
+ const commandNames = {
21
+ OnOff: {
22
+ on: 'on',
23
+ off: 'off',
24
+ toggle: 'toggle',
25
+ },
26
+ LevelControl: {
27
+ moveToLevel: 'moveToLevel',
28
+ moveToLevelWithOnOff: 'moveToLevelWithOnOff',
29
+ move: 'move',
30
+ step: 'step',
31
+ stop: 'stop',
32
+ },
33
+ WindowCovering: {
34
+ upOrOpen: 'upOrOpen',
35
+ downOrClose: 'downOrClose',
36
+ stopMotion: 'stopMotion',
37
+ goToLiftPercentage: 'goToLiftPercentage',
38
+ goToTiltPercentage: 'goToTiltPercentage',
39
+ },
40
+ FanControl: {
41
+ step: 'step',
42
+ fanModeChange: 'fanModeChange',
43
+ percentSettingChange: 'percentSettingChange',
44
+ },
45
+ DoorLock: {
46
+ lockDoor: 'lockDoor',
47
+ unlockDoor: 'unlockDoor',
48
+ },
49
+ Thermostat: {
50
+ setpointRaiseLower: 'setpointRaiseLower',
51
+ systemModeChange: 'systemModeChange',
52
+ occupiedHeatingSetpointChange: 'occupiedHeatingSetpointChange',
53
+ occupiedCoolingSetpointChange: 'occupiedCoolingSetpointChange',
54
+ },
55
+ Identify: {
56
+ identify: 'identify',
57
+ },
58
+ ColorControl: {
59
+ moveToColorTemperatureLogic: 'moveToColorTemperatureLogic',
60
+ moveToHueAndSaturationLogic: 'moveToHueAndSaturationLogic',
61
+ moveToColorLogic: 'moveToColorLogic',
62
+ moveToHueLogic: 'moveToHueLogic',
63
+ moveToSaturationLogic: 'moveToSaturationLogic',
64
+ stopAllColorMovement: 'stopAllColorMovement',
65
+ },
66
+ RvcOperationalState: {
67
+ pause: 'pause',
68
+ resume: 'resume',
69
+ goHome: 'goHome',
70
+ },
71
+ RvcRunMode: {
72
+ changeToMode: 'changeToMode',
73
+ },
74
+ RvcCleanMode: {
75
+ changeToMode: 'changeToMode',
76
+ },
77
+ ServiceArea: {
78
+ selectAreas: 'selectAreas',
79
+ skipArea: 'skipArea',
80
+ },
81
+ };
82
+ /**
83
+ * Store for custom handlers
84
+ * Maps endpoint ID -> cluster name -> command name -> handler
85
+ */
86
+ const handlerStore = new Map();
87
+ /**
88
+ * Store for accessory references
89
+ * This allows handlers to update cached clusters after state changes
90
+ */
91
+ let accessoriesMap = null;
92
+ /**
93
+ * Mapping of endpoint IDs to their parent UUID and part ID (for composed devices)
94
+ * Format: Map<endpointId, { parentUuid: string, partId: string }>
95
+ */
96
+ const partEndpointMap = new Map();
97
+ /**
98
+ * Set the accessories map reference for cache syncing
99
+ */
100
+ export function setAccessoriesMap(map) {
101
+ accessoriesMap = map;
102
+ }
103
+ /**
104
+ * Register a part endpoint mapping
105
+ * This associates a part endpoint ID with its parent accessory UUID and part ID
106
+ *
107
+ * @param endpointId - The endpoint ID (e.g., '{parentUuid}-part-{partId}')
108
+ * @param parentUuid - The parent accessory UUID
109
+ * @param partId - The part identifier within the parent
110
+ */
111
+ export function registerPartEndpoint(endpointId, parentUuid, partId) {
112
+ partEndpointMap.set(endpointId, { parentUuid, partId });
113
+ log.debug(`Registered part endpoint mapping: ${endpointId} -> parent=${parentUuid}, partId=${partId}`);
114
+ }
115
+ /**
116
+ * Sync endpoint state back to cached clusters
117
+ * Ensures state changes from handlers persist across restarts
118
+ *
119
+ * @param endpointId - Unique endpoint identifier (accessory UUID)
120
+ * @param clusterName - Name of the Matter cluster
121
+ * @param attributes - Cluster attributes to sync
122
+ */
123
+ function syncEndpointStateToCache(endpointId, clusterName, attributes) {
124
+ if (!accessoriesMap) {
125
+ return;
126
+ }
127
+ const accessory = accessoriesMap.get(endpointId);
128
+ if (!accessory || !accessory.clusters) {
129
+ return;
130
+ }
131
+ // Update the cached clusters with the new state
132
+ if (!accessory.clusters[clusterName]) {
133
+ accessory.clusters[clusterName] = {};
134
+ }
135
+ accessory.clusters[clusterName] = {
136
+ ...accessory.clusters[clusterName],
137
+ ...attributes,
138
+ };
139
+ log.debug(`Synced ${clusterName} state to cache for ${endpointId}:`, attributes);
140
+ }
141
+ /**
142
+ * Register a handler for a specific endpoint/cluster/command
143
+ *
144
+ * @param endpointId - Unique endpoint identifier (typically the accessory UUID)
145
+ * @param clusterName - Name of the Matter cluster (e.g., 'onOff', 'levelControl')
146
+ * @param commandName - Name of the command method (e.g., 'on', 'off', 'moveToLevel')
147
+ * @param handler - Callback function to execute when the command is received
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * registerHandler('my-light-uuid', 'onOff', 'on', async () => {
152
+ * console.log('Light turned on!')
153
+ * })
154
+ * ```
155
+ */
156
+ export function registerHandler(endpointId, clusterName, commandName, handler) {
157
+ if (!handlerStore.has(endpointId)) {
158
+ handlerStore.set(endpointId, new Map());
159
+ }
160
+ const endpointHandlers = handlerStore.get(endpointId);
161
+ if (!endpointHandlers.has(clusterName)) {
162
+ endpointHandlers.set(clusterName, new Map());
163
+ }
164
+ const clusterHandlers = endpointHandlers.get(clusterName);
165
+ clusterHandlers.set(commandName, handler);
166
+ log.debug(`Registered handler for ${endpointId}.${clusterName}.${commandName}`);
167
+ }
168
+ /**
169
+ * Get a handler for a specific endpoint/cluster/command
170
+ */
171
+ function getHandler(endpointId, clusterName, commandName) {
172
+ return handlerStore.get(endpointId)?.get(clusterName)?.get(commandName);
173
+ }
174
+ /**
175
+ * Optional methods that are called internally by Matter.js
176
+ * These are typically handled by their combined counterparts (e.g., moveToHueAndSaturationLogic)
177
+ */
178
+ const OPTIONAL_METHODS = new Set([
179
+ commandNames.ColorControl.moveToHueLogic,
180
+ commandNames.ColorControl.moveToSaturationLogic,
181
+ commandNames.ColorControl.stopAllColorMovement,
182
+ ]);
183
+ /**
184
+ * Execute a handler with consistent error handling and logging
185
+ *
186
+ * @param endpointId - Unique endpoint identifier (accessory UUID or part endpoint ID)
187
+ * @param clusterName - Name of the Matter cluster
188
+ * @param commandName - Name of the command method
189
+ * @param request - Optional request data passed to the handler
190
+ */
191
+ function executeHandler(endpointId, clusterName, commandName, request) {
192
+ const handler = getHandler(endpointId, clusterName, commandName);
193
+ log.debug(`${clusterName}.${commandName} called for endpoint ${endpointId}`);
194
+ if (handler) {
195
+ // Build context for the handler
196
+ // Check if this endpoint is a part of a composed device
197
+ const partInfo = partEndpointMap.get(endpointId);
198
+ const context = partInfo
199
+ ? { uuid: partInfo.parentUuid, partId: partInfo.partId }
200
+ : { uuid: endpointId, partId: undefined };
201
+ handler(request, context);
202
+ log.debug(` ✓ Plugin handler for ${endpointId}.${clusterName}.${commandName} executed successfully`);
203
+ }
204
+ else if (!OPTIONAL_METHODS.has(commandName)) {
205
+ // warn about missing handlers, except for optional methods
206
+ log.warn(` ⚠ No handler registered for ${endpointId}.${clusterName}.${commandName}`);
207
+ }
208
+ }
209
+ /**
210
+ * Custom OnOff Server that calls plugin handlers
211
+ */
212
+ export class HomebridgeOnOffServer extends OnOffServer {
213
+ on() {
214
+ const endpointId = this.endpoint.id;
215
+ executeHandler(endpointId, clusterNames.OnOff, commandNames.OnOff.on);
216
+ const result = super.on();
217
+ syncEndpointStateToCache(endpointId, clusterNames.OnOff, { onOff: true });
218
+ return result;
219
+ }
220
+ off() {
221
+ const endpointId = this.endpoint.id;
222
+ executeHandler(endpointId, clusterNames.OnOff, commandNames.OnOff.off);
223
+ const result = super.off();
224
+ syncEndpointStateToCache(endpointId, clusterNames.OnOff, { onOff: false });
225
+ return result;
226
+ }
227
+ toggle() {
228
+ const endpointId = this.endpoint.id;
229
+ executeHandler(endpointId, clusterNames.OnOff, commandNames.OnOff.toggle);
230
+ const result = super.toggle();
231
+ // Read the new state from the endpoint and sync to cache
232
+ const newState = this.state.onOff;
233
+ syncEndpointStateToCache(endpointId, clusterNames.OnOff, { onOff: newState });
234
+ return result;
235
+ }
236
+ }
237
+ /**
238
+ * Custom LevelControl Server that calls plugin handlers
239
+ */
240
+ export class HomebridgeLevelControlServer extends LevelControlServer {
241
+ moveToLevel(request) {
242
+ const endpointId = this.endpoint.id;
243
+ executeHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.moveToLevel, request);
244
+ const result = super.moveToLevel(request);
245
+ syncEndpointStateToCache(endpointId, clusterNames.LevelControl, { currentLevel: request.level });
246
+ return result;
247
+ }
248
+ moveToLevelWithOnOff(request) {
249
+ const endpointId = this.endpoint.id;
250
+ // Try specific handler first, fall back to moveToLevel handler
251
+ const handler = getHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.moveToLevelWithOnOff)
252
+ || getHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.moveToLevel);
253
+ log.debug(`LevelControl.moveToLevelWithOnOff called for endpoint ${endpointId} with level ${request.level}`);
254
+ if (handler) {
255
+ handler(request);
256
+ log.debug(` ✓ Plugin handler for ${endpointId}.LevelControl.moveToLevelWithOnOff executed successfully`);
257
+ }
258
+ else {
259
+ log.warn(` ⚠ No handler registered for ${endpointId}.levelControl.moveToLevelWithOnOff or moveToLevel`);
260
+ }
261
+ const result = super.moveToLevelWithOnOff(request);
262
+ syncEndpointStateToCache(endpointId, clusterNames.LevelControl, { currentLevel: request.level });
263
+ return result;
264
+ }
265
+ move(request) {
266
+ const endpointId = this.endpoint.id;
267
+ executeHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.move, request);
268
+ return super.move(request);
269
+ }
270
+ step(request) {
271
+ const endpointId = this.endpoint.id;
272
+ executeHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.step, request);
273
+ return super.step(request);
274
+ }
275
+ stop(request) {
276
+ const endpointId = this.endpoint.id;
277
+ executeHandler(endpointId, clusterNames.LevelControl, commandNames.LevelControl.stop, request);
278
+ return super.stop(request);
279
+ }
280
+ }
281
+ /**
282
+ * WindowCovering state property names
283
+ * These correspond to the Matter.js WindowCovering cluster attribute names
284
+ */
285
+ const WindowCoveringStateProps = {
286
+ targetPositionLiftPercent100ths: 'targetPositionLiftPercent100ths',
287
+ currentPositionLiftPercent100ths: 'currentPositionLiftPercent100ths',
288
+ targetPositionTiltPercent100ths: 'targetPositionTiltPercent100ths',
289
+ currentPositionTiltPercent100ths: 'currentPositionTiltPercent100ths',
290
+ };
291
+ /**
292
+ * Custom WindowCovering Server that calls plugin handlers
293
+ */
294
+ export class HomebridgeWindowCoveringServer extends WindowCoveringBaseServer {
295
+ /**
296
+ * Sync window covering position state to cache
297
+ * @param endpointId - The endpoint ID
298
+ * @param targetProperty - Target position property name (e.g., 'targetPositionLiftPercent100ths')
299
+ * @param currentProperty - Current position property name (e.g., 'currentPositionLiftPercent100ths')
300
+ */
301
+ syncPositionStateToCache(endpointId, targetProperty, currentProperty) {
302
+ const currentState = this.state;
303
+ const stateUpdate = {};
304
+ if (currentState[targetProperty] !== undefined) {
305
+ stateUpdate[targetProperty] = currentState[targetProperty];
306
+ }
307
+ if (currentState[currentProperty] !== undefined) {
308
+ stateUpdate[currentProperty] = currentState[currentProperty];
309
+ }
310
+ syncEndpointStateToCache(endpointId, clusterNames.WindowCovering, stateUpdate);
311
+ }
312
+ upOrOpen() {
313
+ const endpointId = this.endpoint.id;
314
+ executeHandler(endpointId, clusterNames.WindowCovering, commandNames.WindowCovering.upOrOpen);
315
+ const result = super.upOrOpen();
316
+ // Sync state to cache - window covering opening
317
+ this.syncPositionStateToCache(endpointId, WindowCoveringStateProps.targetPositionLiftPercent100ths, WindowCoveringStateProps.currentPositionLiftPercent100ths);
318
+ return result;
319
+ }
320
+ downOrClose() {
321
+ const endpointId = this.endpoint.id;
322
+ executeHandler(endpointId, clusterNames.WindowCovering, commandNames.WindowCovering.downOrClose);
323
+ const result = super.downOrClose();
324
+ // Sync state to cache - window covering closing
325
+ this.syncPositionStateToCache(endpointId, WindowCoveringStateProps.targetPositionLiftPercent100ths, WindowCoveringStateProps.currentPositionLiftPercent100ths);
326
+ return result;
327
+ }
328
+ stopMotion() {
329
+ const endpointId = this.endpoint.id;
330
+ executeHandler(endpointId, clusterNames.WindowCovering, commandNames.WindowCovering.stopMotion);
331
+ const result = super.stopMotion();
332
+ // Sync state to cache - window covering stopped
333
+ this.syncPositionStateToCache(endpointId, WindowCoveringStateProps.targetPositionLiftPercent100ths, WindowCoveringStateProps.currentPositionLiftPercent100ths);
334
+ return result;
335
+ }
336
+ goToLiftPercentage(request) {
337
+ const endpointId = this.endpoint.id;
338
+ executeHandler(endpointId, clusterNames.WindowCovering, commandNames.WindowCovering.goToLiftPercentage, request);
339
+ const result = super.goToLiftPercentage(request);
340
+ // Sync state to cache - window covering moving to target position
341
+ this.syncPositionStateToCache(endpointId, WindowCoveringStateProps.targetPositionLiftPercent100ths, WindowCoveringStateProps.currentPositionLiftPercent100ths);
342
+ return result;
343
+ }
344
+ goToTiltPercentage(request) {
345
+ const endpointId = this.endpoint.id;
346
+ executeHandler(endpointId, clusterNames.WindowCovering, commandNames.WindowCovering.goToTiltPercentage, request);
347
+ const result = super.goToTiltPercentage(request);
348
+ // Sync state to cache - window covering tilting to target angle
349
+ this.syncPositionStateToCache(endpointId, WindowCoveringStateProps.targetPositionTiltPercent100ths, WindowCoveringStateProps.currentPositionTiltPercent100ths);
350
+ return result;
351
+ }
352
+ }
353
+ /**
354
+ * Custom FanControl Server that calls plugin handlers
355
+ */
356
+ export class HomebridgeFanControlServer extends FanControlServer {
357
+ initialize() {
358
+ super.initialize();
359
+ // React to fanMode attribute changes (on/off)
360
+ this.reactTo(this.events.fanMode$Changed, this.#handleFanModeChange);
361
+ // React to percentSetting attribute changes (speed)
362
+ this.reactTo(this.events.percentSetting$Changed, this.#handlePercentSettingChange);
363
+ }
364
+ #handleFanModeChange(value, oldValue) {
365
+ const endpointId = this.endpoint.id;
366
+ executeHandler(endpointId, clusterNames.FanControl, commandNames.FanControl.fanModeChange, { fanMode: value, oldFanMode: oldValue });
367
+ syncEndpointStateToCache(endpointId, clusterNames.FanControl, { fanMode: value });
368
+ }
369
+ #handlePercentSettingChange(value, oldValue) {
370
+ const endpointId = this.endpoint.id;
371
+ executeHandler(endpointId, clusterNames.FanControl, commandNames.FanControl.percentSettingChange, {
372
+ percentSetting: value,
373
+ oldPercentSetting: oldValue,
374
+ });
375
+ syncEndpointStateToCache(endpointId, clusterNames.FanControl, {
376
+ percentSetting: value,
377
+ percentCurrent: value,
378
+ });
379
+ }
380
+ }
381
+ /**
382
+ * Custom DoorLock Server that calls plugin handlers
383
+ */
384
+ export class HomebridgeDoorLockServer extends DoorLockServer {
385
+ lockDoor() {
386
+ const endpointId = this.endpoint.id;
387
+ executeHandler(endpointId, clusterNames.DoorLock, commandNames.DoorLock.lockDoor);
388
+ const result = super.lockDoor();
389
+ // Sync lock state to cache
390
+ const currentState = this.state;
391
+ if (currentState.lockState !== undefined) {
392
+ syncEndpointStateToCache(endpointId, clusterNames.DoorLock, { lockState: currentState.lockState });
393
+ }
394
+ return result;
395
+ }
396
+ unlockDoor() {
397
+ const endpointId = this.endpoint.id;
398
+ executeHandler(endpointId, clusterNames.DoorLock, commandNames.DoorLock.unlockDoor);
399
+ const result = super.unlockDoor();
400
+ // Sync lock state to cache
401
+ const currentState = this.state;
402
+ if (currentState.lockState !== undefined) {
403
+ syncEndpointStateToCache(endpointId, clusterNames.DoorLock, { lockState: currentState.lockState });
404
+ }
405
+ return result;
406
+ }
407
+ }
408
+ /**
409
+ * Custom Thermostat Server that calls plugin handlers
410
+ */
411
+ export class HomebridgeThermostatServer extends ThermostatServer {
412
+ initialize() {
413
+ super.initialize();
414
+ // React to systemMode attribute changes (off, heat, cool, auto, etc.)
415
+ this.reactTo(this.events.systemMode$Changed, this.#handleSystemModeChange);
416
+ // React to occupiedHeatingSetpoint attribute changes (target heating temperature)
417
+ const events = this.events;
418
+ if (events.occupiedHeatingSetpoint$Changing) {
419
+ this.reactTo(events.occupiedHeatingSetpoint$Changing, this.#handleOccupiedHeatingSetpointChanging);
420
+ }
421
+ // React to occupiedCoolingSetpoint attribute changes (target cooling temperature)
422
+ if (events.occupiedCoolingSetpoint$Changing) {
423
+ this.reactTo(events.occupiedCoolingSetpoint$Changing, this.#handleOccupiedCoolingSetpointChanging);
424
+ }
425
+ }
426
+ #handleSystemModeChange(value, oldValue) {
427
+ const endpointId = this.endpoint.id;
428
+ executeHandler(endpointId, clusterNames.Thermostat, commandNames.Thermostat.systemModeChange, {
429
+ systemMode: value,
430
+ oldSystemMode: oldValue,
431
+ });
432
+ syncEndpointStateToCache(endpointId, clusterNames.Thermostat, { systemMode: value });
433
+ }
434
+ #handleOccupiedHeatingSetpointChanging(value) {
435
+ const endpointId = this.endpoint.id;
436
+ const oldValue = this.state.occupiedHeatingSetpoint;
437
+ executeHandler(endpointId, clusterNames.Thermostat, commandNames.Thermostat.occupiedHeatingSetpointChange, {
438
+ occupiedHeatingSetpoint: value,
439
+ oldOccupiedHeatingSetpoint: oldValue,
440
+ });
441
+ syncEndpointStateToCache(endpointId, clusterNames.Thermostat, { occupiedHeatingSetpoint: value });
442
+ }
443
+ #handleOccupiedCoolingSetpointChanging(value) {
444
+ const endpointId = this.endpoint.id;
445
+ const oldValue = this.state.occupiedCoolingSetpoint;
446
+ executeHandler(endpointId, clusterNames.Thermostat, commandNames.Thermostat.occupiedCoolingSetpointChange, {
447
+ occupiedCoolingSetpoint: value,
448
+ oldOccupiedCoolingSetpoint: oldValue,
449
+ });
450
+ syncEndpointStateToCache(endpointId, clusterNames.Thermostat, { occupiedCoolingSetpoint: value });
451
+ }
452
+ setpointRaiseLower(request) {
453
+ const endpointId = this.endpoint.id;
454
+ executeHandler(endpointId, clusterNames.Thermostat, commandNames.Thermostat.setpointRaiseLower, request);
455
+ const result = super.setpointRaiseLower(request);
456
+ // Sync thermostat setpoints to cache
457
+ const currentState = this.state;
458
+ const stateUpdate = {};
459
+ if (currentState.occupiedCoolingSetpoint !== undefined) {
460
+ stateUpdate.occupiedCoolingSetpoint = currentState.occupiedCoolingSetpoint;
461
+ }
462
+ if (currentState.occupiedHeatingSetpoint !== undefined) {
463
+ stateUpdate.occupiedHeatingSetpoint = currentState.occupiedHeatingSetpoint;
464
+ }
465
+ syncEndpointStateToCache(endpointId, clusterNames.Thermostat, stateUpdate);
466
+ return result;
467
+ }
468
+ }
469
+ /**
470
+ * Custom Identify Server that calls plugin handlers
471
+ */
472
+ export class HomebridgeIdentifyServer extends IdentifyServer {
473
+ identify(request) {
474
+ const endpointId = this.endpoint.id;
475
+ executeHandler(endpointId, clusterNames.Identify, commandNames.Identify.identify, request);
476
+ return super.identify(request);
477
+ }
478
+ }
479
+ /**
480
+ * Custom ColorControl Server that calls plugin handlers
481
+ *
482
+ * ColorControl handles color changes for lights (hue, saturation, XY color, color temperature).
483
+ * Plugin developers can override these *Logic methods to handle color changes in their hardware.
484
+ *
485
+ * Features (Xy, ColorTemperature, HueSaturation) are added by the device type, not this behavior.
486
+ * This ensures each device only gets the features it needs.
487
+ */
488
+ export class HomebridgeColorControlServer extends ColorControlServer {
489
+ /**
490
+ * Called when color temperature is changed
491
+ * @param colorTemperatureMireds - Target color temperature in mireds (micro reciprocal degrees)
492
+ * @param transitionTime - Transition time in seconds (0 = as fast as possible)
493
+ */
494
+ moveToColorTemperatureLogic(colorTemperatureMireds, transitionTime) {
495
+ const endpointId = this.endpoint.id;
496
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.moveToColorTemperatureLogic, { colorTemperatureMireds, transitionTime });
497
+ const result = super.moveToColorTemperatureLogic(colorTemperatureMireds, transitionTime);
498
+ // Sync color temperature to cache
499
+ const currentState = this.state;
500
+ if (currentState.colorTemperatureMireds !== undefined) {
501
+ syncEndpointStateToCache(endpointId, clusterNames.ColorControl, {
502
+ colorTemperatureMireds: currentState.colorTemperatureMireds,
503
+ });
504
+ }
505
+ return result;
506
+ }
507
+ /**
508
+ * Called when hue and saturation are changed together
509
+ * @param hue - Target hue value (0-254 for normal hue, 0-65535 for enhanced hue)
510
+ * @param saturation - Target saturation value (0-254)
511
+ * @param transitionTime - Transition time in seconds (0 = as fast as possible)
512
+ */
513
+ moveToHueAndSaturationLogic(hue, saturation, transitionTime) {
514
+ const endpointId = this.endpoint.id;
515
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.moveToHueAndSaturationLogic, { hue, saturation, transitionTime });
516
+ const result = super.moveToHueAndSaturationLogic(hue, saturation, transitionTime);
517
+ // Sync hue and saturation to cache
518
+ const currentState = this.state;
519
+ const stateUpdate = {};
520
+ if (currentState.currentHue !== undefined) {
521
+ stateUpdate.currentHue = currentState.currentHue;
522
+ }
523
+ if (currentState.currentSaturation !== undefined) {
524
+ stateUpdate.currentSaturation = currentState.currentSaturation;
525
+ }
526
+ syncEndpointStateToCache(endpointId, clusterNames.ColorControl, stateUpdate);
527
+ return result;
528
+ }
529
+ /**
530
+ * Called when XY color coordinates are changed
531
+ * @param targetX - Target X value (0-65535 representing 0.0-1.0 in CIE color space)
532
+ * @param targetY - Target Y value (0-65535 representing 0.0-1.0 in CIE color space)
533
+ * @param transitionTime - Transition time in seconds (0 = as fast as possible)
534
+ */
535
+ moveToColorLogic(targetX, targetY, transitionTime) {
536
+ const endpointId = this.endpoint.id;
537
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.moveToColorLogic, { targetX, targetY, transitionTime });
538
+ const result = super.moveToColorLogic(targetX, targetY, transitionTime);
539
+ // Sync XY color to cache
540
+ const currentState = this.state;
541
+ const stateUpdate = {};
542
+ if (currentState.currentX !== undefined) {
543
+ stateUpdate.currentX = currentState.currentX;
544
+ }
545
+ if (currentState.currentY !== undefined) {
546
+ stateUpdate.currentY = currentState.currentY;
547
+ }
548
+ syncEndpointStateToCache(endpointId, clusterNames.ColorControl, stateUpdate);
549
+ return result;
550
+ }
551
+ /**
552
+ * Called when hue is changed individually
553
+ * @param targetHue - Target hue value
554
+ * @param direction - Direction to move (shortest, longest, up, down)
555
+ * @param transitionTime - Transition time in seconds
556
+ * @param isEnhancedHue - Whether this is enhanced hue (16-bit) or normal hue (8-bit)
557
+ */
558
+ moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue = false) {
559
+ const endpointId = this.endpoint.id;
560
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.moveToHueLogic, { targetHue, direction, transitionTime, isEnhancedHue });
561
+ const result = super.moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue);
562
+ // Sync hue to cache
563
+ const currentState = this.state;
564
+ const stateUpdate = {};
565
+ if (isEnhancedHue && currentState.enhancedCurrentHue !== undefined) {
566
+ stateUpdate.enhancedCurrentHue = currentState.enhancedCurrentHue;
567
+ }
568
+ else if (currentState.currentHue !== undefined) {
569
+ stateUpdate.currentHue = currentState.currentHue;
570
+ }
571
+ syncEndpointStateToCache(endpointId, clusterNames.ColorControl, stateUpdate);
572
+ return result;
573
+ }
574
+ /**
575
+ * Called when saturation is changed individually
576
+ * @param targetSaturation - Target saturation value (0-254)
577
+ * @param transitionTime - Transition time in seconds
578
+ */
579
+ moveToSaturationLogic(targetSaturation, transitionTime) {
580
+ const endpointId = this.endpoint.id;
581
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.moveToSaturationLogic, { targetSaturation, transitionTime });
582
+ const result = super.moveToSaturationLogic(targetSaturation, transitionTime);
583
+ // Sync saturation to cache
584
+ const currentState = this.state;
585
+ if (currentState.currentSaturation !== undefined) {
586
+ syncEndpointStateToCache(endpointId, clusterNames.ColorControl, {
587
+ currentSaturation: currentState.currentSaturation,
588
+ });
589
+ }
590
+ return result;
591
+ }
592
+ /**
593
+ * Called when all color movement should be stopped
594
+ */
595
+ stopAllColorMovement() {
596
+ const endpointId = this.endpoint.id;
597
+ executeHandler(endpointId, clusterNames.ColorControl, commandNames.ColorControl.stopAllColorMovement);
598
+ return super.stopAllColorMovement();
599
+ }
600
+ }
601
+ /**
602
+ * Custom RvcOperationalState Server that calls plugin handlers
603
+ * Handles robotic vacuum cleaner operational state commands
604
+ */
605
+ export class HomebridgeRvcOperationalStateServer extends RvcOperationalStateServer {
606
+ pause() {
607
+ const endpointId = this.endpoint.id;
608
+ executeHandler(endpointId, clusterNames.RvcOperationalState, commandNames.RvcOperationalState.pause);
609
+ return super.pause();
610
+ }
611
+ resume() {
612
+ const endpointId = this.endpoint.id;
613
+ executeHandler(endpointId, clusterNames.RvcOperationalState, commandNames.RvcOperationalState.resume);
614
+ return super.resume();
615
+ }
616
+ goHome() {
617
+ const endpointId = this.endpoint.id;
618
+ executeHandler(endpointId, clusterNames.RvcOperationalState, commandNames.RvcOperationalState.goHome);
619
+ // Return success response instead of calling unimplemented base method
620
+ return {
621
+ commandResponseState: {
622
+ errorStateId: RvcOperationalState.ErrorState.NoError,
623
+ },
624
+ };
625
+ }
626
+ }
627
+ /**
628
+ * Custom RvcRunMode Server that calls plugin handlers
629
+ * Handles robotic vacuum cleaner run mode changes
630
+ */
631
+ export class HomebridgeRvcRunModeServer extends RvcRunModeServer {
632
+ changeToMode(request) {
633
+ const endpointId = this.endpoint.id;
634
+ executeHandler(endpointId, clusterNames.RvcRunMode, commandNames.RvcRunMode.changeToMode, request);
635
+ return super.changeToMode(request);
636
+ }
637
+ }
638
+ /**
639
+ * Custom RvcCleanMode Server that calls plugin handlers
640
+ * Handles robotic vacuum cleaner cleaning mode changes
641
+ */
642
+ export class HomebridgeRvcCleanModeServer extends RvcCleanModeServer {
643
+ changeToMode(request) {
644
+ const endpointId = this.endpoint.id;
645
+ executeHandler(endpointId, clusterNames.RvcCleanMode, commandNames.RvcCleanMode.changeToMode, request);
646
+ return super.changeToMode(request);
647
+ }
648
+ }
649
+ /**
650
+ * Custom ServiceArea Server that calls plugin handlers
651
+ * Handles service area selection for robotic vacuum cleaners
652
+ */
653
+ export class HomebridgeServiceAreaServer extends ServiceAreaServer {
654
+ selectAreas(request) {
655
+ const endpointId = this.endpoint.id;
656
+ executeHandler(endpointId, clusterNames.ServiceArea, commandNames.ServiceArea.selectAreas, request);
657
+ return super.selectAreas(request);
658
+ }
659
+ skipArea(request) {
660
+ const endpointId = this.endpoint.id;
661
+ executeHandler(endpointId, clusterNames.ServiceArea, commandNames.ServiceArea.skipArea, request);
662
+ return super.skipArea(request);
663
+ }
664
+ }
665
+ //# sourceMappingURL=matterBehaviors.js.map