homebridge 2.0.0-alpha.6 → 2.0.0-alpha.61
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/README.md +1 -1
- package/bin/homebridge.js +22 -0
- package/dist/api.d.ts +235 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +91 -0
- package/dist/api.js.map +1 -1
- package/dist/bridgeService.d.ts +11 -3
- package/dist/bridgeService.d.ts.map +1 -1
- package/dist/bridgeService.js +9 -5
- package/dist/bridgeService.js.map +1 -1
- package/dist/childBridgeFork.d.ts +30 -3
- package/dist/childBridgeFork.d.ts.map +1 -1
- package/dist/childBridgeFork.js +278 -5
- package/dist/childBridgeFork.js.map +1 -1
- package/dist/childBridgeService.d.ts +22 -0
- package/dist/childBridgeService.d.ts.map +1 -1
- package/dist/childBridgeService.js +85 -26
- package/dist/childBridgeService.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/externalPortService.d.ts +27 -6
- package/dist/externalPortService.d.ts.map +1 -1
- package/dist/externalPortService.js +73 -7
- package/dist/externalPortService.js.map +1 -1
- package/dist/index.d.ts +48 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/ipcService.d.ts +20 -0
- package/dist/ipcService.d.ts.map +1 -1
- package/dist/ipcService.js.map +1 -1
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +8 -0
- package/dist/logger.js.map +1 -1
- package/dist/matter/index.d.ts +59 -0
- package/dist/matter/index.d.ts.map +1 -0
- package/dist/matter/index.js +19 -0
- package/dist/matter/index.js.map +1 -0
- package/dist/matter/matterAccessoryCache.d.ts +77 -0
- package/dist/matter/matterAccessoryCache.d.ts.map +1 -0
- package/dist/matter/matterAccessoryCache.js +176 -0
- package/dist/matter/matterAccessoryCache.js.map +1 -0
- package/dist/matter/matterBehaviors.d.ts +158 -0
- package/dist/matter/matterBehaviors.d.ts.map +1 -0
- package/dist/matter/matterBehaviors.js +740 -0
- package/dist/matter/matterBehaviors.js.map +1 -0
- package/dist/matter/matterConfigValidator.d.ts +81 -0
- package/dist/matter/matterConfigValidator.d.ts.map +1 -0
- package/dist/matter/matterConfigValidator.js +240 -0
- package/dist/matter/matterConfigValidator.js.map +1 -0
- package/dist/matter/matterErrorHandler.d.ts +94 -0
- package/dist/matter/matterErrorHandler.d.ts.map +1 -0
- package/dist/matter/matterErrorHandler.js +485 -0
- package/dist/matter/matterErrorHandler.js.map +1 -0
- package/dist/matter/matterLogFormatter.d.ts +19 -0
- package/dist/matter/matterLogFormatter.d.ts.map +1 -0
- package/dist/matter/matterLogFormatter.js +126 -0
- package/dist/matter/matterLogFormatter.js.map +1 -0
- package/dist/matter/matterNetworkMonitor.d.ts +68 -0
- package/dist/matter/matterNetworkMonitor.d.ts.map +1 -0
- package/dist/matter/matterNetworkMonitor.js +249 -0
- package/dist/matter/matterNetworkMonitor.js.map +1 -0
- package/dist/matter/matterServer.d.ts +643 -0
- package/dist/matter/matterServer.d.ts.map +1 -0
- package/dist/matter/matterServer.js +1623 -0
- package/dist/matter/matterServer.js.map +1 -0
- package/dist/matter/matterSharedTypes.d.ts +165 -0
- package/dist/matter/matterSharedTypes.d.ts.map +1 -0
- package/dist/matter/matterSharedTypes.js +51 -0
- package/dist/matter/matterSharedTypes.js.map +1 -0
- package/dist/matter/matterStorage.d.ts +126 -0
- package/dist/matter/matterStorage.d.ts.map +1 -0
- package/dist/matter/matterStorage.js +419 -0
- package/dist/matter/matterStorage.js.map +1 -0
- package/dist/matter/matterTypes.d.ts +612 -0
- package/dist/matter/matterTypes.d.ts.map +1 -0
- package/dist/matter/matterTypes.js +150 -0
- package/dist/matter/matterTypes.js.map +1 -0
- package/dist/platformAccessory.d.ts +1 -0
- package/dist/platformAccessory.d.ts.map +1 -1
- package/dist/platformAccessory.js +8 -1
- package/dist/platformAccessory.js.map +1 -1
- package/dist/plugin.d.ts +0 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +8 -11
- package/dist/plugin.js.map +1 -1
- package/dist/pluginManager.d.ts.map +1 -1
- package/dist/pluginManager.js +22 -21
- package/dist/pluginManager.js.map +1 -1
- package/dist/server.d.ts +23 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +374 -10
- package/dist/server.js.map +1 -1
- package/dist/storageService.js +8 -8
- package/dist/storageService.js.map +1 -1
- package/dist/user.d.ts +1 -0
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +10 -7
- package/dist/user.js.map +1 -1
- package/dist/util/mac.d.ts.map +1 -1
- package/dist/util/mac.js +2 -2
- package/dist/util/mac.js.map +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/package.json +25 -26
- package/bin/homebridge +0 -19
|
@@ -0,0 +1,740 @@
|
|
|
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, IdentifyServer, LevelControlServer, OnOffServer, RvcCleanModeServer, RvcOperationalStateServer, RvcRunModeServer, ServiceAreaServer, ThermostatServer, WindowCoveringServer, } from '@matter/main/behaviors';
|
|
12
|
+
import { Logger } from '../logger.js';
|
|
13
|
+
const log = Logger.withPrefix('Matter/Behaviours');
|
|
14
|
+
/**
|
|
15
|
+
* Store for custom handlers
|
|
16
|
+
* Maps endpoint ID -> cluster name -> command name -> handler
|
|
17
|
+
*/
|
|
18
|
+
const handlerStore = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Store for accessory references
|
|
21
|
+
* This allows handlers to update cached clusters after state changes
|
|
22
|
+
*/
|
|
23
|
+
let accessoriesMap = null;
|
|
24
|
+
/**
|
|
25
|
+
* Set the accessories map reference for cache syncing
|
|
26
|
+
*/
|
|
27
|
+
export function setAccessoriesMap(map) {
|
|
28
|
+
accessoriesMap = map;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sync endpoint state back to cached clusters
|
|
32
|
+
* This ensures state changes from handlers persist across restarts
|
|
33
|
+
*/
|
|
34
|
+
function syncEndpointStateToCache(endpointId, clusterName, attributes) {
|
|
35
|
+
if (!accessoriesMap) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const accessory = accessoriesMap.get(endpointId);
|
|
39
|
+
if (!accessory) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Update the cached clusters with the new state
|
|
43
|
+
if (!accessory.clusters[clusterName]) {
|
|
44
|
+
accessory.clusters[clusterName] = {};
|
|
45
|
+
}
|
|
46
|
+
accessory.clusters[clusterName] = {
|
|
47
|
+
...accessory.clusters[clusterName],
|
|
48
|
+
...attributes,
|
|
49
|
+
};
|
|
50
|
+
log.debug(`Synced ${clusterName} state to cache for ${endpointId}:`, attributes);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a handler for a specific endpoint/cluster/command
|
|
54
|
+
*
|
|
55
|
+
* @param endpointId - Unique endpoint identifier (typically the accessory UUID)
|
|
56
|
+
* @param clusterName - Name of the Matter cluster (e.g., 'onOff', 'levelControl')
|
|
57
|
+
* @param commandName - Name of the command method (e.g., 'on', 'off', 'moveToLevel')
|
|
58
|
+
* @param handler - Callback function to execute when the command is received
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* registerHandler('my-light-uuid', 'onOff', 'on', async () => {
|
|
63
|
+
* console.log('Light turned on!')
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function registerHandler(endpointId, clusterName, commandName, handler) {
|
|
68
|
+
if (!handlerStore.has(endpointId)) {
|
|
69
|
+
handlerStore.set(endpointId, new Map());
|
|
70
|
+
}
|
|
71
|
+
const endpointHandlers = handlerStore.get(endpointId);
|
|
72
|
+
if (!endpointHandlers.has(clusterName)) {
|
|
73
|
+
endpointHandlers.set(clusterName, new Map());
|
|
74
|
+
}
|
|
75
|
+
const clusterHandlers = endpointHandlers.get(clusterName);
|
|
76
|
+
clusterHandlers.set(commandName, handler);
|
|
77
|
+
log.info(`Registered handler for ${endpointId}.${clusterName}.${commandName}`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a handler for a specific endpoint/cluster/command
|
|
81
|
+
*/
|
|
82
|
+
function getHandler(endpointId, clusterName, commandName) {
|
|
83
|
+
return handlerStore.get(endpointId)?.get(clusterName)?.get(commandName);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Custom OnOff Server that calls plugin handlers
|
|
87
|
+
*/
|
|
88
|
+
export class HomebridgeOnOffServer extends OnOffServer {
|
|
89
|
+
on() {
|
|
90
|
+
const endpointId = this.endpoint.id;
|
|
91
|
+
const handler = getHandler(endpointId, 'onOff', 'on');
|
|
92
|
+
log.info(`OnOff.on called for endpoint ${endpointId}`);
|
|
93
|
+
if (handler) {
|
|
94
|
+
try {
|
|
95
|
+
handler();
|
|
96
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
log.warn(` ⚠ No handler registered for ${endpointId}.onOff.on`);
|
|
104
|
+
}
|
|
105
|
+
const result = super.on();
|
|
106
|
+
// Sync the new state to cache for persistence
|
|
107
|
+
syncEndpointStateToCache(endpointId, 'onOff', { onOff: true });
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
off() {
|
|
111
|
+
const endpointId = this.endpoint.id;
|
|
112
|
+
const handler = getHandler(endpointId, 'onOff', 'off');
|
|
113
|
+
log.info(`OnOff.off called for endpoint ${endpointId}`);
|
|
114
|
+
if (handler) {
|
|
115
|
+
try {
|
|
116
|
+
handler();
|
|
117
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
log.warn(` ⚠ No handler registered for ${endpointId}.onOff.off`);
|
|
125
|
+
}
|
|
126
|
+
const result = super.off();
|
|
127
|
+
// Sync the new state to cache for persistence
|
|
128
|
+
syncEndpointStateToCache(endpointId, 'onOff', { onOff: false });
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
toggle() {
|
|
132
|
+
const endpointId = this.endpoint.id;
|
|
133
|
+
const handler = getHandler(endpointId, 'onOff', 'toggle');
|
|
134
|
+
log.info(`OnOff.toggle called for endpoint ${endpointId}`);
|
|
135
|
+
if (handler) {
|
|
136
|
+
try {
|
|
137
|
+
handler();
|
|
138
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const result = super.toggle();
|
|
145
|
+
// Read the new state from the endpoint and sync to cache
|
|
146
|
+
const newState = this.state.onOff;
|
|
147
|
+
syncEndpointStateToCache(endpointId, 'onOff', { onOff: newState });
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Custom LevelControl Server that calls plugin handlers
|
|
153
|
+
*/
|
|
154
|
+
export class HomebridgeLevelControlServer extends LevelControlServer {
|
|
155
|
+
moveToLevel(request) {
|
|
156
|
+
const endpointId = this.endpoint.id;
|
|
157
|
+
const handler = getHandler(endpointId, 'levelControl', 'moveToLevel');
|
|
158
|
+
log.info(`LevelControl.moveToLevel called for endpoint ${endpointId} with level ${request.level}`);
|
|
159
|
+
if (handler) {
|
|
160
|
+
try {
|
|
161
|
+
handler(request);
|
|
162
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
log.warn(` ⚠ No handler registered for ${endpointId}.levelControl.moveToLevel`);
|
|
170
|
+
}
|
|
171
|
+
const result = super.moveToLevel(request);
|
|
172
|
+
// Sync the new level to cache for persistence
|
|
173
|
+
syncEndpointStateToCache(endpointId, 'levelControl', { currentLevel: request.level });
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
moveToLevelWithOnOff(request) {
|
|
177
|
+
const endpointId = this.endpoint.id;
|
|
178
|
+
// Try to get a specific handler for moveToLevelWithOnOff first
|
|
179
|
+
let handler = getHandler(endpointId, 'levelControl', 'moveToLevelWithOnOff');
|
|
180
|
+
// Fall back to moveToLevel handler if no specific handler is registered
|
|
181
|
+
if (!handler) {
|
|
182
|
+
handler = getHandler(endpointId, 'levelControl', 'moveToLevel');
|
|
183
|
+
}
|
|
184
|
+
log.info(`LevelControl.moveToLevelWithOnOff called for endpoint ${endpointId} with level ${request.level}`);
|
|
185
|
+
if (handler) {
|
|
186
|
+
try {
|
|
187
|
+
handler(request);
|
|
188
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
log.warn(` ⚠ No handler registered for ${endpointId}.levelControl.moveToLevelWithOnOff or moveToLevel`);
|
|
196
|
+
}
|
|
197
|
+
const result = super.moveToLevelWithOnOff(request);
|
|
198
|
+
// Sync the new level to cache for persistence
|
|
199
|
+
syncEndpointStateToCache(endpointId, 'levelControl', { currentLevel: request.level });
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
move(request) {
|
|
203
|
+
const endpointId = this.endpoint.id;
|
|
204
|
+
const handler = getHandler(endpointId, 'levelControl', 'move');
|
|
205
|
+
log.info(`LevelControl.move called for endpoint ${endpointId}`);
|
|
206
|
+
if (handler) {
|
|
207
|
+
try {
|
|
208
|
+
handler(request);
|
|
209
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return super.move(request);
|
|
216
|
+
}
|
|
217
|
+
step(request) {
|
|
218
|
+
const endpointId = this.endpoint.id;
|
|
219
|
+
const handler = getHandler(endpointId, 'levelControl', 'step');
|
|
220
|
+
log.info(`LevelControl.step called for endpoint ${endpointId}`);
|
|
221
|
+
if (handler) {
|
|
222
|
+
try {
|
|
223
|
+
handler(request);
|
|
224
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return super.step(request);
|
|
231
|
+
}
|
|
232
|
+
stop(request) {
|
|
233
|
+
const endpointId = this.endpoint.id;
|
|
234
|
+
const handler = getHandler(endpointId, 'levelControl', 'stop');
|
|
235
|
+
log.info(`LevelControl.stop called for endpoint ${endpointId}`);
|
|
236
|
+
if (handler) {
|
|
237
|
+
try {
|
|
238
|
+
handler(request);
|
|
239
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return super.stop(request);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Custom WindowCovering Server that calls plugin handlers
|
|
250
|
+
*/
|
|
251
|
+
export class HomebridgeWindowCoveringServer extends WindowCoveringServer {
|
|
252
|
+
upOrOpen() {
|
|
253
|
+
const endpointId = this.endpoint.id;
|
|
254
|
+
const handler = getHandler(endpointId, 'windowCovering', 'upOrOpen');
|
|
255
|
+
log.info(`WindowCovering.upOrOpen called for endpoint ${endpointId}`);
|
|
256
|
+
if (handler) {
|
|
257
|
+
try {
|
|
258
|
+
handler();
|
|
259
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const result = super.upOrOpen();
|
|
266
|
+
// Sync state to cache - window covering opening
|
|
267
|
+
const currentState = this.state;
|
|
268
|
+
const stateUpdate = {};
|
|
269
|
+
if (currentState.targetPositionLiftPercent100ths !== undefined) {
|
|
270
|
+
stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths;
|
|
271
|
+
}
|
|
272
|
+
if (currentState.currentPositionLiftPercent100ths !== undefined) {
|
|
273
|
+
stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths;
|
|
274
|
+
}
|
|
275
|
+
syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
downOrClose() {
|
|
279
|
+
const endpointId = this.endpoint.id;
|
|
280
|
+
const handler = getHandler(endpointId, 'windowCovering', 'downOrClose');
|
|
281
|
+
log.info(`WindowCovering.downOrClose called for endpoint ${endpointId}`);
|
|
282
|
+
if (handler) {
|
|
283
|
+
try {
|
|
284
|
+
handler();
|
|
285
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const result = super.downOrClose();
|
|
292
|
+
// Sync state to cache - window covering closing
|
|
293
|
+
const currentState = this.state;
|
|
294
|
+
const stateUpdate = {};
|
|
295
|
+
if (currentState.targetPositionLiftPercent100ths !== undefined) {
|
|
296
|
+
stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths;
|
|
297
|
+
}
|
|
298
|
+
if (currentState.currentPositionLiftPercent100ths !== undefined) {
|
|
299
|
+
stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths;
|
|
300
|
+
}
|
|
301
|
+
syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate);
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
stopMotion() {
|
|
305
|
+
const endpointId = this.endpoint.id;
|
|
306
|
+
const handler = getHandler(endpointId, 'windowCovering', 'stopMotion');
|
|
307
|
+
log.info(`WindowCovering.stopMotion called for endpoint ${endpointId}`);
|
|
308
|
+
if (handler) {
|
|
309
|
+
try {
|
|
310
|
+
handler();
|
|
311
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const result = super.stopMotion();
|
|
318
|
+
// Sync state to cache - window covering stopped
|
|
319
|
+
const currentState = this.state;
|
|
320
|
+
const stateUpdate = {};
|
|
321
|
+
if (currentState.targetPositionLiftPercent100ths !== undefined) {
|
|
322
|
+
stateUpdate.targetPositionLiftPercent100ths = currentState.targetPositionLiftPercent100ths;
|
|
323
|
+
}
|
|
324
|
+
if (currentState.currentPositionLiftPercent100ths !== undefined) {
|
|
325
|
+
stateUpdate.currentPositionLiftPercent100ths = currentState.currentPositionLiftPercent100ths;
|
|
326
|
+
}
|
|
327
|
+
syncEndpointStateToCache(endpointId, 'windowCovering', stateUpdate);
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Custom DoorLock Server that calls plugin handlers
|
|
333
|
+
*/
|
|
334
|
+
export class HomebridgeDoorLockServer extends DoorLockServer {
|
|
335
|
+
lockDoor() {
|
|
336
|
+
const endpointId = this.endpoint.id;
|
|
337
|
+
const handler = getHandler(endpointId, 'doorLock', 'lockDoor');
|
|
338
|
+
log.info(`DoorLock.lockDoor called for endpoint ${endpointId}`);
|
|
339
|
+
if (handler) {
|
|
340
|
+
try {
|
|
341
|
+
handler();
|
|
342
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const result = super.lockDoor();
|
|
349
|
+
// Sync lock state to cache
|
|
350
|
+
const currentState = this.state;
|
|
351
|
+
if (currentState.lockState !== undefined) {
|
|
352
|
+
syncEndpointStateToCache(endpointId, 'doorLock', { lockState: currentState.lockState });
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
unlockDoor() {
|
|
357
|
+
const endpointId = this.endpoint.id;
|
|
358
|
+
const handler = getHandler(endpointId, 'doorLock', 'unlockDoor');
|
|
359
|
+
log.info(`DoorLock.unlockDoor called for endpoint ${endpointId}`);
|
|
360
|
+
if (handler) {
|
|
361
|
+
try {
|
|
362
|
+
handler();
|
|
363
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const result = super.unlockDoor();
|
|
370
|
+
// Sync lock state to cache
|
|
371
|
+
const currentState = this.state;
|
|
372
|
+
if (currentState.lockState !== undefined) {
|
|
373
|
+
syncEndpointStateToCache(endpointId, 'doorLock', { lockState: currentState.lockState });
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Custom Thermostat Server that calls plugin handlers
|
|
380
|
+
*/
|
|
381
|
+
export class HomebridgeThermostatServer extends ThermostatServer {
|
|
382
|
+
setpointRaiseLower(request) {
|
|
383
|
+
const endpointId = this.endpoint.id;
|
|
384
|
+
const handler = getHandler(endpointId, 'thermostat', 'setpointRaiseLower');
|
|
385
|
+
log.info(`Thermostat.setpointRaiseLower called for endpoint ${endpointId}`);
|
|
386
|
+
if (handler) {
|
|
387
|
+
try {
|
|
388
|
+
handler(request);
|
|
389
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const result = super.setpointRaiseLower(request);
|
|
396
|
+
// Sync thermostat setpoints to cache
|
|
397
|
+
const currentState = this.state;
|
|
398
|
+
const stateUpdate = {};
|
|
399
|
+
if (currentState.occupiedCoolingSetpoint !== undefined) {
|
|
400
|
+
stateUpdate.occupiedCoolingSetpoint = currentState.occupiedCoolingSetpoint;
|
|
401
|
+
}
|
|
402
|
+
if (currentState.occupiedHeatingSetpoint !== undefined) {
|
|
403
|
+
stateUpdate.occupiedHeatingSetpoint = currentState.occupiedHeatingSetpoint;
|
|
404
|
+
}
|
|
405
|
+
syncEndpointStateToCache(endpointId, 'thermostat', stateUpdate);
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Custom Identify Server that calls plugin handlers
|
|
411
|
+
*/
|
|
412
|
+
export class HomebridgeIdentifyServer extends IdentifyServer {
|
|
413
|
+
identify(request) {
|
|
414
|
+
const endpointId = this.endpoint.id;
|
|
415
|
+
const handler = getHandler(endpointId, 'identify', 'identify');
|
|
416
|
+
log.info(`Identify.identify called for endpoint ${endpointId}`);
|
|
417
|
+
if (handler) {
|
|
418
|
+
try {
|
|
419
|
+
handler(request);
|
|
420
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return super.identify(request);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Custom ColorControl Server that calls plugin handlers
|
|
431
|
+
*
|
|
432
|
+
* ColorControl handles color changes for lights (hue, saturation, XY color, color temperature).
|
|
433
|
+
* Plugin developers can override these *Logic methods to handle color changes in their hardware.
|
|
434
|
+
*
|
|
435
|
+
* Features (Xy, ColorTemperature, HueSaturation) are added by the device type, not this behavior.
|
|
436
|
+
* This ensures each device only gets the features it needs.
|
|
437
|
+
*/
|
|
438
|
+
export class HomebridgeColorControlServer extends ColorControlServer {
|
|
439
|
+
/**
|
|
440
|
+
* Called when color temperature is changed
|
|
441
|
+
* @param targetMireds - Target color temperature in mireds (micro reciprocal degrees)
|
|
442
|
+
* @param transitionTime - Transition time in seconds (0 = as fast as possible)
|
|
443
|
+
*/
|
|
444
|
+
moveToColorTemperatureLogic(targetMireds, transitionTime) {
|
|
445
|
+
const endpointId = this.endpoint.id;
|
|
446
|
+
const handler = getHandler(endpointId, 'colorControl', 'moveToColorTemperatureLogic');
|
|
447
|
+
log.info(`ColorControl.moveToColorTemperatureLogic called for endpoint ${endpointId} with mireds=${targetMireds}, transitionTime=${transitionTime}`);
|
|
448
|
+
if (handler) {
|
|
449
|
+
try {
|
|
450
|
+
handler({ targetMireds, transitionTime });
|
|
451
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const result = super.moveToColorTemperatureLogic(targetMireds, transitionTime);
|
|
458
|
+
// Sync color temperature to cache
|
|
459
|
+
const currentState = this.state;
|
|
460
|
+
if (currentState.colorTemperatureMireds !== undefined) {
|
|
461
|
+
syncEndpointStateToCache(endpointId, 'colorControl', {
|
|
462
|
+
colorTemperatureMireds: currentState.colorTemperatureMireds,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Called when hue and saturation are changed together
|
|
469
|
+
* @param targetHue - Target hue value (0-254 for normal hue, 0-65535 for enhanced hue)
|
|
470
|
+
* @param targetSaturation - Target saturation value (0-254)
|
|
471
|
+
* @param transitionTime - Transition time in seconds (0 = as fast as possible)
|
|
472
|
+
*/
|
|
473
|
+
moveToHueAndSaturationLogic(targetHue, targetSaturation, transitionTime) {
|
|
474
|
+
const endpointId = this.endpoint.id;
|
|
475
|
+
const handler = getHandler(endpointId, 'colorControl', 'moveToHueAndSaturationLogic');
|
|
476
|
+
log.info(`ColorControl.moveToHueAndSaturationLogic called for endpoint ${endpointId} with hue=${targetHue}, saturation=${targetSaturation}, transitionTime=${transitionTime}`);
|
|
477
|
+
if (handler) {
|
|
478
|
+
try {
|
|
479
|
+
handler({ targetHue, targetSaturation, transitionTime });
|
|
480
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const result = super.moveToHueAndSaturationLogic(targetHue, targetSaturation, transitionTime);
|
|
487
|
+
// Sync hue and saturation to cache
|
|
488
|
+
const currentState = this.state;
|
|
489
|
+
const stateUpdate = {};
|
|
490
|
+
if (currentState.currentHue !== undefined) {
|
|
491
|
+
stateUpdate.currentHue = currentState.currentHue;
|
|
492
|
+
}
|
|
493
|
+
if (currentState.currentSaturation !== undefined) {
|
|
494
|
+
stateUpdate.currentSaturation = currentState.currentSaturation;
|
|
495
|
+
}
|
|
496
|
+
syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate);
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Called when XY color coordinates are changed
|
|
501
|
+
* @param targetX - Target X value (0-65535 representing 0.0-1.0 in CIE color space)
|
|
502
|
+
* @param targetY - Target Y value (0-65535 representing 0.0-1.0 in CIE color space)
|
|
503
|
+
* @param transitionTime - Transition time in seconds (0 = as fast as possible)
|
|
504
|
+
*/
|
|
505
|
+
moveToColorLogic(targetX, targetY, transitionTime) {
|
|
506
|
+
const endpointId = this.endpoint.id;
|
|
507
|
+
const handler = getHandler(endpointId, 'colorControl', 'moveToColorLogic');
|
|
508
|
+
log.info(`ColorControl.moveToColorLogic called for endpoint ${endpointId} with x=${targetX}, y=${targetY}, transitionTime=${transitionTime}`);
|
|
509
|
+
if (handler) {
|
|
510
|
+
try {
|
|
511
|
+
handler({ targetX, targetY, transitionTime });
|
|
512
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const result = super.moveToColorLogic(targetX, targetY, transitionTime);
|
|
519
|
+
// Sync XY color to cache
|
|
520
|
+
const currentState = this.state;
|
|
521
|
+
const stateUpdate = {};
|
|
522
|
+
if (currentState.currentX !== undefined) {
|
|
523
|
+
stateUpdate.currentX = currentState.currentX;
|
|
524
|
+
}
|
|
525
|
+
if (currentState.currentY !== undefined) {
|
|
526
|
+
stateUpdate.currentY = currentState.currentY;
|
|
527
|
+
}
|
|
528
|
+
syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate);
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Called when hue is changed individually
|
|
533
|
+
* @param targetHue - Target hue value
|
|
534
|
+
* @param direction - Direction to move (shortest, longest, up, down)
|
|
535
|
+
* @param transitionTime - Transition time in seconds
|
|
536
|
+
* @param isEnhancedHue - Whether this is enhanced hue (16-bit) or normal hue (8-bit)
|
|
537
|
+
*/
|
|
538
|
+
moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue = false) {
|
|
539
|
+
const endpointId = this.endpoint.id;
|
|
540
|
+
const handler = getHandler(endpointId, 'colorControl', 'moveToHueLogic');
|
|
541
|
+
log.info(`ColorControl.moveToHueLogic called for endpoint ${endpointId} with hue=${targetHue}, direction=${direction}, transitionTime=${transitionTime}, enhanced=${isEnhancedHue}`);
|
|
542
|
+
if (handler) {
|
|
543
|
+
try {
|
|
544
|
+
handler({ targetHue, direction, transitionTime, isEnhancedHue });
|
|
545
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const result = super.moveToHueLogic(targetHue, direction, transitionTime, isEnhancedHue);
|
|
552
|
+
// Sync hue to cache
|
|
553
|
+
const currentState = this.state;
|
|
554
|
+
const stateUpdate = {};
|
|
555
|
+
if (isEnhancedHue && currentState.enhancedCurrentHue !== undefined) {
|
|
556
|
+
stateUpdate.enhancedCurrentHue = currentState.enhancedCurrentHue;
|
|
557
|
+
}
|
|
558
|
+
else if (currentState.currentHue !== undefined) {
|
|
559
|
+
stateUpdate.currentHue = currentState.currentHue;
|
|
560
|
+
}
|
|
561
|
+
syncEndpointStateToCache(endpointId, 'colorControl', stateUpdate);
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Called when saturation is changed individually
|
|
566
|
+
* @param targetSaturation - Target saturation value (0-254)
|
|
567
|
+
* @param transitionTime - Transition time in seconds
|
|
568
|
+
*/
|
|
569
|
+
moveToSaturationLogic(targetSaturation, transitionTime) {
|
|
570
|
+
const endpointId = this.endpoint.id;
|
|
571
|
+
const handler = getHandler(endpointId, 'colorControl', 'moveToSaturationLogic');
|
|
572
|
+
log.info(`ColorControl.moveToSaturationLogic called for endpoint ${endpointId} with saturation=${targetSaturation}, transitionTime=${transitionTime}`);
|
|
573
|
+
if (handler) {
|
|
574
|
+
try {
|
|
575
|
+
handler({ targetSaturation, transitionTime });
|
|
576
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
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, '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
|
+
const handler = getHandler(endpointId, 'colorControl', 'stopAllColorMovement');
|
|
598
|
+
log.info(`ColorControl.stopAllColorMovement called for endpoint ${endpointId}`);
|
|
599
|
+
if (handler) {
|
|
600
|
+
try {
|
|
601
|
+
handler();
|
|
602
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return super.stopAllColorMovement();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Custom RvcOperationalState Server that calls plugin handlers
|
|
613
|
+
* Handles robotic vacuum cleaner operational state commands
|
|
614
|
+
*/
|
|
615
|
+
export class HomebridgeRvcOperationalStateServer extends RvcOperationalStateServer {
|
|
616
|
+
pause() {
|
|
617
|
+
const endpointId = this.endpoint.id;
|
|
618
|
+
const handler = getHandler(endpointId, 'rvcOperationalState', 'pause');
|
|
619
|
+
log.info(`RvcOperationalState.pause called for endpoint ${endpointId}`);
|
|
620
|
+
if (handler) {
|
|
621
|
+
try {
|
|
622
|
+
handler();
|
|
623
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return super.pause();
|
|
630
|
+
}
|
|
631
|
+
resume() {
|
|
632
|
+
const endpointId = this.endpoint.id;
|
|
633
|
+
const handler = getHandler(endpointId, 'rvcOperationalState', 'resume');
|
|
634
|
+
log.info(`RvcOperationalState.resume called for endpoint ${endpointId}`);
|
|
635
|
+
if (handler) {
|
|
636
|
+
try {
|
|
637
|
+
handler();
|
|
638
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return super.resume();
|
|
645
|
+
}
|
|
646
|
+
goHome() {
|
|
647
|
+
const endpointId = this.endpoint.id;
|
|
648
|
+
const handler = getHandler(endpointId, 'rvcOperationalState', 'goHome');
|
|
649
|
+
log.info(`RvcOperationalState.goHome called for endpoint ${endpointId}`);
|
|
650
|
+
if (handler) {
|
|
651
|
+
try {
|
|
652
|
+
handler();
|
|
653
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return super.goHome();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Custom RvcRunMode Server that calls plugin handlers
|
|
664
|
+
* Handles robotic vacuum cleaner run mode changes
|
|
665
|
+
*/
|
|
666
|
+
export class HomebridgeRvcRunModeServer extends RvcRunModeServer {
|
|
667
|
+
changeToMode(request) {
|
|
668
|
+
const endpointId = this.endpoint.id;
|
|
669
|
+
const handler = getHandler(endpointId, 'rvcRunMode', 'changeToMode');
|
|
670
|
+
log.info(`RvcRunMode.changeToMode called for endpoint ${endpointId} with mode ${request.newMode}`);
|
|
671
|
+
if (handler) {
|
|
672
|
+
try {
|
|
673
|
+
handler(request);
|
|
674
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return super.changeToMode(request);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Custom RvcCleanMode Server that calls plugin handlers
|
|
685
|
+
* Handles robotic vacuum cleaner cleaning mode changes
|
|
686
|
+
*/
|
|
687
|
+
export class HomebridgeRvcCleanModeServer extends RvcCleanModeServer {
|
|
688
|
+
changeToMode(request) {
|
|
689
|
+
const endpointId = this.endpoint.id;
|
|
690
|
+
const handler = getHandler(endpointId, 'rvcCleanMode', 'changeToMode');
|
|
691
|
+
log.info(`RvcCleanMode.changeToMode called for endpoint ${endpointId} with mode ${request.newMode}`);
|
|
692
|
+
if (handler) {
|
|
693
|
+
try {
|
|
694
|
+
handler(request);
|
|
695
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return super.changeToMode(request);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Custom ServiceArea Server that calls plugin handlers
|
|
706
|
+
* Handles service area selection for robotic vacuum cleaners
|
|
707
|
+
*/
|
|
708
|
+
export class HomebridgeServiceAreaServer extends ServiceAreaServer {
|
|
709
|
+
selectAreas(request) {
|
|
710
|
+
const endpointId = this.endpoint.id;
|
|
711
|
+
const handler = getHandler(endpointId, 'serviceArea', 'selectAreas');
|
|
712
|
+
log.info(`ServiceArea.selectAreas called for endpoint ${endpointId}`);
|
|
713
|
+
if (handler) {
|
|
714
|
+
try {
|
|
715
|
+
handler(request);
|
|
716
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return super.selectAreas(request);
|
|
723
|
+
}
|
|
724
|
+
skipArea(request) {
|
|
725
|
+
const endpointId = this.endpoint.id;
|
|
726
|
+
const handler = getHandler(endpointId, 'serviceArea', 'skipArea');
|
|
727
|
+
log.info(`ServiceArea.skipArea called for endpoint ${endpointId}`);
|
|
728
|
+
if (handler) {
|
|
729
|
+
try {
|
|
730
|
+
handler(request);
|
|
731
|
+
log.info(' ✓ Plugin handler executed successfully');
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
log.error(' ✗ Error in plugin handler:', error);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return super.skipArea(request);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
//# sourceMappingURL=matterBehaviors.js.map
|