homebridge 2.0.0-alpha.4 → 2.0.0-alpha.41
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 +210 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +140 -0
- package/dist/api.js.map +1 -0
- package/dist/bridgeService.d.ts +122 -0
- package/dist/bridgeService.d.ts.map +1 -0
- package/dist/bridgeService.js +396 -0
- package/dist/bridgeService.js.map +1 -0
- package/dist/childBridgeFork.d.ts +38 -0
- package/dist/childBridgeFork.d.ts.map +1 -0
- package/dist/childBridgeFork.js +241 -2
- package/dist/childBridgeFork.js.map +1 -7
- package/dist/childBridgeService.d.ts +204 -0
- package/dist/childBridgeService.d.ts.map +1 -0
- package/dist/childBridgeService.js +452 -0
- package/dist/childBridgeService.js.map +1 -0
- package/dist/childMatterBridgeFork.d.ts +108 -0
- package/dist/childMatterBridgeFork.d.ts.map +1 -0
- package/dist/childMatterBridgeFork.js +330 -0
- package/dist/childMatterBridgeFork.js.map +1 -0
- package/dist/childMatterBridgeService.d.ts +166 -0
- package/dist/childMatterBridgeService.d.ts.map +1 -0
- package/dist/childMatterBridgeService.js +623 -0
- package/dist/childMatterBridgeService.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +90 -2
- package/dist/cli.js.map +1 -7
- package/dist/externalPortService.d.ts +33 -0
- package/dist/externalPortService.d.ts.map +1 -0
- package/dist/externalPortService.js +59 -0
- package/dist/externalPortService.js.map +1 -0
- package/dist/index.d.ts +90 -1099
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -7
- package/dist/ipcService.d.ts +46 -0
- package/dist/ipcService.d.ts.map +1 -0
- package/dist/ipcService.js +62 -0
- package/dist/ipcService.js.map +1 -0
- package/dist/logger.d.ts +78 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +138 -0
- package/dist/logger.js.map +1 -0
- package/dist/matter/index.d.ts +13 -0
- package/dist/matter/index.d.ts.map +1 -0
- package/dist/matter/index.js +12 -0
- package/dist/matter/index.js.map +1 -0
- package/dist/matter/matterBridge.d.ts +64 -0
- package/dist/matter/matterBridge.d.ts.map +1 -0
- package/dist/matter/matterBridge.js +154 -0
- package/dist/matter/matterBridge.js.map +1 -0
- package/dist/matter/matterConfigValidator.d.ts +27 -0
- package/dist/matter/matterConfigValidator.d.ts.map +1 -0
- package/dist/matter/matterConfigValidator.js +162 -0
- package/dist/matter/matterConfigValidator.js.map +1 -0
- package/dist/matter/matterDevice.d.ts +107 -0
- package/dist/matter/matterDevice.d.ts.map +1 -0
- package/dist/matter/matterDevice.js +913 -0
- package/dist/matter/matterDevice.js.map +1 -0
- package/dist/matter/matterDiagnostics.d.ts +121 -0
- package/dist/matter/matterDiagnostics.d.ts.map +1 -0
- package/dist/matter/matterDiagnostics.js +323 -0
- package/dist/matter/matterDiagnostics.js.map +1 -0
- package/dist/matter/matterErrorHandler.d.ts +113 -0
- package/dist/matter/matterErrorHandler.d.ts.map +1 -0
- package/dist/matter/matterErrorHandler.js +482 -0
- package/dist/matter/matterErrorHandler.js.map +1 -0
- package/dist/matter/matterNetworkMonitor.d.ts +65 -0
- package/dist/matter/matterNetworkMonitor.d.ts.map +1 -0
- package/dist/matter/matterNetworkMonitor.js +227 -0
- package/dist/matter/matterNetworkMonitor.js.map +1 -0
- package/dist/matter/matterServer.d.ts +110 -0
- package/dist/matter/matterServer.d.ts.map +1 -0
- package/dist/matter/matterServer.js +584 -0
- package/dist/matter/matterServer.js.map +1 -0
- package/dist/matter/matterSharedTypes.d.ts +167 -0
- package/dist/matter/matterSharedTypes.d.ts.map +1 -0
- package/dist/matter/matterSharedTypes.js +55 -0
- package/dist/matter/matterSharedTypes.js.map +1 -0
- package/dist/matter/matterTypes.d.ts +35 -0
- package/dist/matter/matterTypes.d.ts.map +1 -0
- package/dist/matter/matterTypes.js +278 -0
- package/dist/matter/matterTypes.js.map +1 -0
- package/dist/matter/portAllocator.d.ts +85 -0
- package/dist/matter/portAllocator.d.ts.map +1 -0
- package/dist/matter/portAllocator.js +296 -0
- package/dist/matter/portAllocator.js.map +1 -0
- package/dist/platformAccessory.d.ts +56 -0
- package/dist/platformAccessory.d.ts.map +1 -0
- package/dist/platformAccessory.js +105 -0
- package/dist/platformAccessory.js.map +1 -0
- package/dist/plugin.d.ts +30 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +182 -0
- package/dist/plugin.js.map +1 -0
- package/dist/pluginManager.d.ts +77 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +375 -0
- package/dist/pluginManager.js.map +1 -0
- package/dist/server.d.ts +67 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +799 -0
- package/dist/server.js.map +1 -0
- package/dist/storageService.d.ts +13 -0
- package/dist/storageService.d.ts.map +1 -0
- package/dist/storageService.js +41 -0
- package/dist/storageService.js.map +1 -0
- package/dist/user.d.ts +13 -0
- package/dist/user.d.ts.map +1 -0
- package/dist/user.js +29 -0
- package/dist/user.js.map +1 -0
- package/dist/util/mac.d.ts +5 -0
- package/dist/util/mac.d.ts.map +1 -0
- package/dist/util/mac.js +14 -0
- package/dist/util/mac.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/package.json +31 -34
- package/bin/homebridge +0 -19
package/dist/server.js
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import qrcode from 'qrcode-terminal';
|
|
5
|
+
import { HomebridgeAPI } from './api.js';
|
|
6
|
+
import { BridgeService } from './bridgeService.js';
|
|
7
|
+
import { ChildBridgeService } from './childBridgeService.js';
|
|
8
|
+
import { ChildMatterBridgeService } from './childMatterBridgeService.js';
|
|
9
|
+
import { ExternalPortService } from './externalPortService.js';
|
|
10
|
+
import { IpcService } from './ipcService.js';
|
|
11
|
+
import { Logger } from './logger.js';
|
|
12
|
+
import { MatterConfigValidator } from './matter/matterConfigValidator.js';
|
|
13
|
+
import { PluginManager } from './pluginManager.js';
|
|
14
|
+
import { User } from './user.js';
|
|
15
|
+
import { validMacAddress } from './util/mac.js';
|
|
16
|
+
const log = Logger.internal;
|
|
17
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
18
|
+
export var ServerStatus;
|
|
19
|
+
(function (ServerStatus) {
|
|
20
|
+
/**
|
|
21
|
+
* When the server is starting up
|
|
22
|
+
*/
|
|
23
|
+
ServerStatus["PENDING"] = "pending";
|
|
24
|
+
/**
|
|
25
|
+
* When the server is online and has published the main bridge
|
|
26
|
+
*/
|
|
27
|
+
ServerStatus["OK"] = "ok";
|
|
28
|
+
/**
|
|
29
|
+
* When the server is shutting down
|
|
30
|
+
*/
|
|
31
|
+
ServerStatus["DOWN"] = "down";
|
|
32
|
+
})(ServerStatus || (ServerStatus = {}));
|
|
33
|
+
export class Server {
|
|
34
|
+
options;
|
|
35
|
+
api;
|
|
36
|
+
pluginManager;
|
|
37
|
+
bridgeService;
|
|
38
|
+
ipcService;
|
|
39
|
+
externalPortService;
|
|
40
|
+
config;
|
|
41
|
+
// used to keep track of child bridges
|
|
42
|
+
childBridges = new Map();
|
|
43
|
+
childMatterBridges = new Map();
|
|
44
|
+
// Track platform configurations for routing accessories
|
|
45
|
+
platformConfigs = new Map();
|
|
46
|
+
// current server status
|
|
47
|
+
serverStatus = "pending" /* ServerStatus.PENDING */;
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
this.options = options;
|
|
50
|
+
this.config = Server.loadConfig();
|
|
51
|
+
// object we feed to Plugins and BridgeService
|
|
52
|
+
this.api = new HomebridgeAPI();
|
|
53
|
+
this.ipcService = new IpcService();
|
|
54
|
+
this.externalPortService = new ExternalPortService(this.config.ports);
|
|
55
|
+
// set status to pending
|
|
56
|
+
this.setServerStatus("pending" /* ServerStatus.PENDING */);
|
|
57
|
+
// create new plugin manager
|
|
58
|
+
const pluginManagerOptions = {
|
|
59
|
+
activePlugins: this.config.plugins,
|
|
60
|
+
disabledPlugins: this.config.disabledPlugins,
|
|
61
|
+
customPluginPath: options.customPluginPath,
|
|
62
|
+
strictPluginResolution: options.strictPluginResolution,
|
|
63
|
+
};
|
|
64
|
+
this.pluginManager = new PluginManager(this.api, pluginManagerOptions);
|
|
65
|
+
// create new bridge service
|
|
66
|
+
const bridgeConfig = {
|
|
67
|
+
cachedAccessoriesDir: User.cachedAccessoryPath(),
|
|
68
|
+
cachedAccessoriesItemName: 'cachedAccessories',
|
|
69
|
+
};
|
|
70
|
+
// shallow copy the homebridge options to the bridge options object
|
|
71
|
+
Object.assign(bridgeConfig, this.options);
|
|
72
|
+
this.bridgeService = new BridgeService(this.api, this.pluginManager, this.externalPortService, bridgeConfig, this.config.bridge, this.config);
|
|
73
|
+
// Matter is handled via _matter configuration, not API events
|
|
74
|
+
// Intercept platform accessory registration to route to Matter bridges if needed
|
|
75
|
+
this.api.on("registerPlatformAccessories" /* InternalAPIEvent.REGISTER_PLATFORM_ACCESSORIES */, this.handleRegisterPlatformAccessories.bind(this));
|
|
76
|
+
this.api.on("unregisterPlatformAccessories" /* InternalAPIEvent.UNREGISTER_PLATFORM_ACCESSORIES */, this.handleUnregisterPlatformAccessories.bind(this));
|
|
77
|
+
// Handle external accessories (cameras, etc.) for Matter
|
|
78
|
+
this.api.on("publishExternalAccessories" /* InternalAPIEvent.PUBLISH_EXTERNAL_ACCESSORIES */, this.handlePublishExternalAccessories.bind(this));
|
|
79
|
+
// watch bridge events to check when server is online
|
|
80
|
+
this.bridgeService.bridge.on("advertised" /* AccessoryEventTypes.ADVERTISED */, () => {
|
|
81
|
+
this.setServerStatus("ok" /* ServerStatus.OK */);
|
|
82
|
+
});
|
|
83
|
+
// watch for the paired event to update the server status
|
|
84
|
+
this.bridgeService.bridge.on("paired" /* AccessoryEventTypes.PAIRED */, () => {
|
|
85
|
+
this.setServerStatus(this.serverStatus);
|
|
86
|
+
});
|
|
87
|
+
// watch for the unpaired event to update the server status
|
|
88
|
+
this.bridgeService.bridge.on("unpaired" /* AccessoryEventTypes.UNPAIRED */, () => {
|
|
89
|
+
this.setServerStatus(this.serverStatus);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Set the current server status and update parent via IPC
|
|
94
|
+
* @param status
|
|
95
|
+
*/
|
|
96
|
+
setServerStatus(status) {
|
|
97
|
+
this.serverStatus = status;
|
|
98
|
+
// Collect child Matter bridge statuses
|
|
99
|
+
const childMatterBridgeStatuses = Array.from(this.childMatterBridges.values()).map(bridge => bridge.getMetadata());
|
|
100
|
+
// Get Matter bridges status (child bridges only)
|
|
101
|
+
const matterStatus = childMatterBridgeStatuses.length > 0
|
|
102
|
+
? {
|
|
103
|
+
children: childMatterBridgeStatuses,
|
|
104
|
+
}
|
|
105
|
+
: undefined;
|
|
106
|
+
this.ipcService.sendMessage("serverStatusUpdate" /* IpcOutgoingEvent.SERVER_STATUS_UPDATE */, {
|
|
107
|
+
type: 'hap', // Main bridge is HAP
|
|
108
|
+
status: this.serverStatus,
|
|
109
|
+
paired: this.bridgeService?.bridge?._accessoryInfo?.paired() ?? null,
|
|
110
|
+
setupUri: this.bridgeService?.bridge?.setupURI() ?? null,
|
|
111
|
+
name: this.bridgeService?.bridge?.displayName || this.config.bridge.name,
|
|
112
|
+
username: this.config.bridge.username,
|
|
113
|
+
pin: this.config.bridge.pin,
|
|
114
|
+
matterStatus,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async start() {
|
|
118
|
+
if (this.config.bridge.disableIpc !== true) {
|
|
119
|
+
this.initializeIpcEventHandlers();
|
|
120
|
+
}
|
|
121
|
+
// Track existing Matter bridges before loading new configuration
|
|
122
|
+
const existingMatterBridgeIds = new Set(this.childMatterBridges.keys());
|
|
123
|
+
// Validate child Matter configurations
|
|
124
|
+
const matterValidation = MatterConfigValidator.validateAllChildMatterConfigs(this.config.platforms, this.config.accessories);
|
|
125
|
+
if (!matterValidation.isValid) {
|
|
126
|
+
log.error('Child Matter configuration validation failed. Please fix the errors above.');
|
|
127
|
+
// Continue anyway but child Matter bridges may not work properly
|
|
128
|
+
}
|
|
129
|
+
const promises = [];
|
|
130
|
+
// load the cached accessories
|
|
131
|
+
await this.bridgeService.loadCachedPlatformAccessoriesFromDisk();
|
|
132
|
+
// initialize plugins
|
|
133
|
+
await this.pluginManager.initializeInstalledPlugins();
|
|
134
|
+
if (this.config.platforms.length > 0) {
|
|
135
|
+
promises.push(...this.loadPlatforms());
|
|
136
|
+
}
|
|
137
|
+
if (this.config.accessories.length > 0) {
|
|
138
|
+
this.loadAccessories();
|
|
139
|
+
}
|
|
140
|
+
// Track which Matter bridges are still in the configuration
|
|
141
|
+
const currentMatterBridgeIds = new Set();
|
|
142
|
+
// Check platforms for Matter bridges
|
|
143
|
+
this.config.platforms.forEach((platformConfig) => {
|
|
144
|
+
if (platformConfig._matter && platformConfig.platform) {
|
|
145
|
+
try {
|
|
146
|
+
const plugin = this.pluginManager.getPluginForPlatform(platformConfig.platform);
|
|
147
|
+
const matterIdentifier = `${plugin.getPluginIdentifier()}-${platformConfig.platform}`;
|
|
148
|
+
currentMatterBridgeIds.add(matterIdentifier);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Plugin not found, skip
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// Check accessories for Matter bridges
|
|
156
|
+
this.config.accessories.forEach((accessoryConfig) => {
|
|
157
|
+
if (accessoryConfig._matter && accessoryConfig.accessory) {
|
|
158
|
+
try {
|
|
159
|
+
const plugin = this.pluginManager.getPluginForAccessory(accessoryConfig.accessory);
|
|
160
|
+
const matterIdentifier = `${plugin.getPluginIdentifier()}-${accessoryConfig.accessory}`;
|
|
161
|
+
currentMatterBridgeIds.add(matterIdentifier);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Plugin not found, skip
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// Clean up Matter bridges that were removed from configuration
|
|
169
|
+
for (const bridgeId of existingMatterBridgeIds) {
|
|
170
|
+
if (!currentMatterBridgeIds.has(bridgeId) && this.childMatterBridges.has(bridgeId)) {
|
|
171
|
+
const removedBridge = this.childMatterBridges.get(bridgeId);
|
|
172
|
+
log.info(`Matter bridge "${bridgeId}" was removed from configuration, cleaning up...`);
|
|
173
|
+
// Stop and clean up the removed bridge (with permanent flag)
|
|
174
|
+
await removedBridge.teardown(true);
|
|
175
|
+
this.childMatterBridges.delete(bridgeId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// start child HAP bridges
|
|
179
|
+
for (const childBridge of this.childBridges.values()) {
|
|
180
|
+
childBridge.start();
|
|
181
|
+
}
|
|
182
|
+
// start child Matter bridges
|
|
183
|
+
for (const childMatterBridge of this.childMatterBridges.values()) {
|
|
184
|
+
promises.push(childMatterBridge.start());
|
|
185
|
+
}
|
|
186
|
+
// restore cached accessories
|
|
187
|
+
this.bridgeService.restoreCachedPlatformAccessories();
|
|
188
|
+
this.api.signalFinished();
|
|
189
|
+
// wait for all platforms to publish their accessories before we publish the bridge
|
|
190
|
+
await Promise.all(promises)
|
|
191
|
+
.then(() => this.publishBridge());
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get the status of all Matter bridges (child bridges only)
|
|
195
|
+
*/
|
|
196
|
+
getMatterBridgesStatus() {
|
|
197
|
+
return Array.from(this.childMatterBridges.values()).map(bridge => bridge.getMetadata());
|
|
198
|
+
}
|
|
199
|
+
teardown() {
|
|
200
|
+
this.bridgeService.teardown();
|
|
201
|
+
// Stop all child Matter bridges
|
|
202
|
+
for (const childMatterBridge of this.childMatterBridges.values()) {
|
|
203
|
+
childMatterBridge.teardown().catch((error) => {
|
|
204
|
+
log.error(`Failed to stop child Matter bridge ${childMatterBridge.getDisplayName()}:`, error);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
this.setServerStatus("down" /* ServerStatus.DOWN */);
|
|
208
|
+
}
|
|
209
|
+
publishBridge() {
|
|
210
|
+
this.bridgeService.publishBridge();
|
|
211
|
+
this.printSetupInfo(this.config.bridge.pin);
|
|
212
|
+
}
|
|
213
|
+
// Matter accessories are managed via _matter configuration, not API methods
|
|
214
|
+
handlePublishExternalAccessories(accessories) {
|
|
215
|
+
log.info(`Publishing ${accessories.length} external accessories`);
|
|
216
|
+
// External accessories are only published to child Matter bridges, not main bridge
|
|
217
|
+
// Group accessories by plugin
|
|
218
|
+
const accessoriesByPlugin = new Map();
|
|
219
|
+
accessories.forEach((accessory) => {
|
|
220
|
+
const pluginIdentifier = accessory._associatedPlugin;
|
|
221
|
+
if (!pluginIdentifier) {
|
|
222
|
+
log.warn(`External accessory "${accessory.displayName}" has no plugin identifier`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!accessoriesByPlugin.has(pluginIdentifier)) {
|
|
226
|
+
accessoriesByPlugin.set(pluginIdentifier, []);
|
|
227
|
+
}
|
|
228
|
+
accessoriesByPlugin.get(pluginIdentifier).push(accessory);
|
|
229
|
+
});
|
|
230
|
+
// Process each plugin's external accessories
|
|
231
|
+
accessoriesByPlugin.forEach((pluginAccessories, pluginIdentifier) => {
|
|
232
|
+
// Check if there's already an external Matter bridge for this plugin
|
|
233
|
+
const externalMatterBridgeId = `${pluginIdentifier}-external`;
|
|
234
|
+
let externalMatterBridge = this.childMatterBridges.get(externalMatterBridgeId);
|
|
235
|
+
if (!externalMatterBridge) {
|
|
236
|
+
// Check if this plugin has any Matter-enabled platform config
|
|
237
|
+
let hasMatterConfig = false;
|
|
238
|
+
let baseMatterConfig = {};
|
|
239
|
+
// Look for any platform with Matter config from this plugin
|
|
240
|
+
for (const [bridgeId, childBridge] of this.childMatterBridges) {
|
|
241
|
+
if (bridgeId.startsWith(`${pluginIdentifier}-`) && childBridge.type === "platform" /* PluginType.PLATFORM */) {
|
|
242
|
+
hasMatterConfig = true;
|
|
243
|
+
// Use existing Matter bridge configuration as template
|
|
244
|
+
baseMatterConfig = {
|
|
245
|
+
// Port will be auto-allocated
|
|
246
|
+
};
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// If plugin has Matter-enabled platforms, create external bridge automatically
|
|
251
|
+
if (hasMatterConfig) {
|
|
252
|
+
log.info(`Automatically creating external Matter bridge for ${pluginIdentifier}`);
|
|
253
|
+
const plugin = this.pluginManager.getPlugin(pluginIdentifier);
|
|
254
|
+
if (!plugin) {
|
|
255
|
+
log.warn(`Could not find plugin ${pluginIdentifier} to create external Matter bridge`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Create synthetic config for external bridge
|
|
259
|
+
const externalConfig = {
|
|
260
|
+
platform: '__external__',
|
|
261
|
+
name: `${pluginIdentifier} External`,
|
|
262
|
+
_matter: {
|
|
263
|
+
...baseMatterConfig,
|
|
264
|
+
name: `${pluginIdentifier} External Matter`,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
externalMatterBridge = new ChildMatterBridgeService("platform" /* PluginType.PLATFORM */, plugin, externalConfig._matter, externalConfig, this.api, this.externalPortService, this.options, this.ipcService, this.config);
|
|
268
|
+
this.childMatterBridges.set(externalMatterBridgeId, externalMatterBridge);
|
|
269
|
+
// Start the external bridge
|
|
270
|
+
externalMatterBridge.start().catch((error) => {
|
|
271
|
+
log.error(`Failed to start external Matter bridge for ${pluginIdentifier}:`, error);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Publish accessories to appropriate bridge
|
|
276
|
+
if (externalMatterBridge) {
|
|
277
|
+
pluginAccessories.forEach((accessory) => {
|
|
278
|
+
externalMatterBridge.addAccessory(accessory);
|
|
279
|
+
log.info(`External accessory "${accessory.displayName}" added to automatic external Matter bridge`);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Check if any child Matter bridge from this plugin can handle it
|
|
284
|
+
let publishedToChildBridge = false;
|
|
285
|
+
for (const [bridgeId, childBridge] of this.childMatterBridges) {
|
|
286
|
+
if (bridgeId.startsWith(`${pluginIdentifier}-`)) {
|
|
287
|
+
pluginAccessories.forEach((accessory) => {
|
|
288
|
+
childBridge.addAccessory(accessory);
|
|
289
|
+
log.info(`External accessory "${accessory.displayName}" added to plugin's Matter bridge`);
|
|
290
|
+
});
|
|
291
|
+
publishedToChildBridge = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// External accessories are only published to child Matter bridges, not main bridge
|
|
296
|
+
if (!publishedToChildBridge) {
|
|
297
|
+
pluginAccessories.forEach((accessory) => {
|
|
298
|
+
log.debug(`External accessory "${accessory.displayName}" not published to Matter - configure _matter property for Matter support`);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
handleRegisterPlatformAccessories(accessories) {
|
|
305
|
+
// Route to HAP bridge (default behavior)
|
|
306
|
+
this.bridgeService.handleRegisterPlatformAccessories(accessories);
|
|
307
|
+
// Check if we need to also route to Matter bridges
|
|
308
|
+
for (const accessory of accessories) {
|
|
309
|
+
const platformKey = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
310
|
+
const platformConfig = this.platformConfigs.get(platformKey);
|
|
311
|
+
if (platformConfig) {
|
|
312
|
+
// Check if platform has child Matter bridge
|
|
313
|
+
if (platformConfig._matter && typeof platformConfig._matter === 'object') {
|
|
314
|
+
const matterIdentifier = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
315
|
+
const childMatterBridge = this.childMatterBridges.get(matterIdentifier);
|
|
316
|
+
if (childMatterBridge) {
|
|
317
|
+
childMatterBridge.addAccessory(accessory).catch((error) => {
|
|
318
|
+
log.error(`Failed to add accessory "${accessory.displayName}" to child Matter bridge:`, error);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
handleUnregisterPlatformAccessories(accessories) {
|
|
326
|
+
// Route to HAP bridge (default behavior)
|
|
327
|
+
this.bridgeService.handleUnregisterPlatformAccessories(accessories);
|
|
328
|
+
// Check if we need to also unregister from Matter bridges
|
|
329
|
+
for (const accessory of accessories) {
|
|
330
|
+
const platformKey = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
331
|
+
const platformConfig = this.platformConfigs.get(platformKey);
|
|
332
|
+
if (platformConfig) {
|
|
333
|
+
// Check if platform has child Matter bridge
|
|
334
|
+
if (platformConfig._matter && typeof platformConfig._matter === 'object') {
|
|
335
|
+
const matterIdentifier = `${accessory._associatedPlugin}-${accessory._associatedPlatform}`;
|
|
336
|
+
const childMatterBridge = this.childMatterBridges.get(matterIdentifier);
|
|
337
|
+
if (childMatterBridge) {
|
|
338
|
+
childMatterBridge.removeAccessory(accessory).catch((error) => {
|
|
339
|
+
log.error(`Failed to remove accessory "${accessory.displayName}" from child Matter bridge:`, error);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
static loadConfig() {
|
|
347
|
+
// Look for the configuration file
|
|
348
|
+
const configPath = User.configPath();
|
|
349
|
+
const defaultBridge = {
|
|
350
|
+
name: 'Homebridge',
|
|
351
|
+
username: 'CC:22:3D:E3:CE:30',
|
|
352
|
+
pin: '031-45-154',
|
|
353
|
+
};
|
|
354
|
+
if (!existsSync(configPath)) {
|
|
355
|
+
log.warn('config.json (%s) not found.', configPath);
|
|
356
|
+
return {
|
|
357
|
+
bridge: defaultBridge,
|
|
358
|
+
accessories: [],
|
|
359
|
+
platforms: [],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
let config;
|
|
363
|
+
try {
|
|
364
|
+
config = JSON.parse(readFileSync(configPath, { encoding: 'utf8' }));
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
log.error('There was a problem reading your config.json file.');
|
|
368
|
+
log.error('Please try pasting your config.json file here to validate it: https://jsonlint.com');
|
|
369
|
+
log.error('');
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
if (config.ports !== undefined) {
|
|
373
|
+
if (config.ports.start && config.ports.end) {
|
|
374
|
+
if (config.ports.start > config.ports.end) {
|
|
375
|
+
log.error('Invalid port pool configuration. End should be greater than or equal to start.');
|
|
376
|
+
config.ports = undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
log.error('Invalid configuration for \'ports\'. Missing \'start\' and \'end\' properties! Ignoring it!');
|
|
381
|
+
config.ports = undefined;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const bridge = config.bridge || defaultBridge;
|
|
385
|
+
bridge.name = bridge.name || defaultBridge.name;
|
|
386
|
+
bridge.username = bridge.username || defaultBridge.username;
|
|
387
|
+
bridge.pin = bridge.pin || defaultBridge.pin;
|
|
388
|
+
config.bridge = bridge;
|
|
389
|
+
const username = config.bridge.username;
|
|
390
|
+
if (!validMacAddress(username)) {
|
|
391
|
+
throw new Error(`Not a valid username: ${username}. Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`);
|
|
392
|
+
}
|
|
393
|
+
config.accessories = config.accessories || [];
|
|
394
|
+
config.platforms = config.platforms || [];
|
|
395
|
+
if (!Array.isArray(config.accessories)) {
|
|
396
|
+
log.error('Value provided for accessories must be an array[]');
|
|
397
|
+
config.accessories = [];
|
|
398
|
+
}
|
|
399
|
+
if (!Array.isArray(config.platforms)) {
|
|
400
|
+
log.error('Value provided for platforms must be an array[]');
|
|
401
|
+
config.platforms = [];
|
|
402
|
+
}
|
|
403
|
+
log.info('Loaded config.json with %s accessories and %s platforms.', config.accessories.length, config.platforms.length);
|
|
404
|
+
if (config.bridge.advertiser) {
|
|
405
|
+
if (![
|
|
406
|
+
"bonjour-hap" /* MDNSAdvertiser.BONJOUR */,
|
|
407
|
+
"ciao" /* MDNSAdvertiser.CIAO */,
|
|
408
|
+
"avahi" /* MDNSAdvertiser.AVAHI */,
|
|
409
|
+
"resolved" /* MDNSAdvertiser.RESOLVED */,
|
|
410
|
+
].includes(config.bridge.advertiser)) {
|
|
411
|
+
config.bridge.advertiser = undefined;
|
|
412
|
+
log.error('Value provided in bridge.advertiser is not valid, reverting to platform default.');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
config.bridge.advertiser = undefined;
|
|
417
|
+
}
|
|
418
|
+
return config;
|
|
419
|
+
}
|
|
420
|
+
loadAccessories() {
|
|
421
|
+
log.info(`Loading ${this.config.accessories.length} accessories...`);
|
|
422
|
+
this.config.accessories.forEach((accessoryConfig, index) => {
|
|
423
|
+
if (!accessoryConfig.accessory) {
|
|
424
|
+
log.warn('Your config.json contains an illegal accessory configuration object at position %d. '
|
|
425
|
+
+ 'Missing property \'accessory\'. Skipping entry...', index + 1); // we rather count from 1 for the normal people?
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const accessoryIdentifier = accessoryConfig.accessory;
|
|
429
|
+
const displayName = accessoryConfig.name;
|
|
430
|
+
if (!displayName) {
|
|
431
|
+
log.warn('Could not load accessory %s at position %d as it is missing the required \'name\' property!', accessoryIdentifier, index + 1);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
let plugin;
|
|
435
|
+
let constructor;
|
|
436
|
+
try {
|
|
437
|
+
plugin = this.pluginManager.getPluginForAccessory(accessoryIdentifier);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
log.error(error.message);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// check the plugin is not disabled
|
|
444
|
+
if (plugin.disabled) {
|
|
445
|
+
log.warn(`Ignoring config for the accessory "${accessoryIdentifier}" in your config.json as the plugin "${plugin.getPluginIdentifier()}" has been disabled.`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
constructor = plugin.getAccessoryConstructor(accessoryIdentifier);
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
log.error(`Error loading the accessory "${accessoryIdentifier}" requested in your config.json at position ${index + 1} - this is likely an issue with the "${plugin.getPluginIdentifier()}" plugin.`);
|
|
453
|
+
log.error(error); // error message contains more information and full stack trace
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const logger = Logger.withPrefix(displayName);
|
|
457
|
+
logger('Initializing %s accessory...', accessoryIdentifier);
|
|
458
|
+
if (accessoryConfig._bridge) {
|
|
459
|
+
// ensure the username is always uppercase
|
|
460
|
+
accessoryConfig._bridge.username = accessoryConfig._bridge.username.toUpperCase();
|
|
461
|
+
try {
|
|
462
|
+
this.validateChildBridgeConfig("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, accessoryConfig._bridge);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
log.error(error.message);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
let childBridge;
|
|
469
|
+
if (this.childBridges.has(accessoryConfig._bridge.username)) {
|
|
470
|
+
childBridge = this.childBridges.get(accessoryConfig._bridge.username);
|
|
471
|
+
logger(`Adding to existing child bridge ${accessoryConfig._bridge.username}`);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
logger(`Initializing child bridge ${accessoryConfig._bridge.username}`);
|
|
475
|
+
childBridge = new ChildBridgeService("accessory" /* PluginType.ACCESSORY */, accessoryIdentifier, plugin, accessoryConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
|
|
476
|
+
this.childBridges.set(accessoryConfig._bridge.username, childBridge);
|
|
477
|
+
}
|
|
478
|
+
// add config to child bridge service
|
|
479
|
+
childBridge.addConfig(accessoryConfig);
|
|
480
|
+
// If _matter is not defined, we're done - this accessory only uses HAP child bridge
|
|
481
|
+
if (!accessoryConfig._matter) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Handle child Matter bridge for accessories
|
|
486
|
+
if (accessoryConfig._matter && typeof accessoryConfig._matter === 'object') {
|
|
487
|
+
const matterIdentifier = `${plugin.getPluginIdentifier()}-${accessoryIdentifier}`;
|
|
488
|
+
logger(`Initializing child Matter bridge for accessory ${accessoryIdentifier}`);
|
|
489
|
+
// Ensure Matter config has proper name
|
|
490
|
+
// Try to get a human-friendly name from the accessory
|
|
491
|
+
// Convert CamelCase accessory identifier to spaces
|
|
492
|
+
const formattedAccessoryId = accessoryIdentifier.replace(/([A-Z])/g, ' $1').trim();
|
|
493
|
+
// Get plugin name without 'homebridge-' prefix and replace dashes with spaces
|
|
494
|
+
const pluginDisplayName = plugin.getPluginIdentifier()
|
|
495
|
+
.replace('homebridge-', '')
|
|
496
|
+
.replace(/-/g, ' ')
|
|
497
|
+
.split(' ')
|
|
498
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
499
|
+
.join(' ');
|
|
500
|
+
const defaultName = accessoryConfig.name || formattedAccessoryId || pluginDisplayName;
|
|
501
|
+
const matterConfig = {
|
|
502
|
+
...accessoryConfig._matter,
|
|
503
|
+
name: accessoryConfig._matter.name || defaultName,
|
|
504
|
+
};
|
|
505
|
+
const childMatterBridge = new ChildMatterBridgeService("accessory" /* PluginType.ACCESSORY */, plugin, matterConfig, accessoryConfig, this.api, this.externalPortService, this.options, this.ipcService);
|
|
506
|
+
this.childMatterBridges.set(matterIdentifier, childMatterBridge);
|
|
507
|
+
// If _bridge is also defined, we're done - accessory uses both protocols
|
|
508
|
+
if (accessoryConfig._bridge) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// If both _bridge and _matter are defined, the accessory is handled by child bridges
|
|
513
|
+
if (accessoryConfig._bridge || accessoryConfig._matter) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const accessoryInstance = new constructor(logger, accessoryConfig, this.api);
|
|
517
|
+
// pass accessoryIdentifier for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
|
|
518
|
+
const accessory = this.bridgeService.createHAPAccessory(plugin, accessoryInstance, displayName, accessoryIdentifier, accessoryConfig.uuid_base);
|
|
519
|
+
if (accessory) {
|
|
520
|
+
try {
|
|
521
|
+
this.bridgeService.bridge.addBridgedAccessory(accessory);
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
logger.error(`Error loading the accessory "${accessoryIdentifier}" from "${plugin.getPluginIdentifier()}" requested in your config.json:`, error.message);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
logger.info('Accessory %s returned empty set of services; not adding it to the bridge.', accessoryIdentifier);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
loadPlatforms() {
|
|
533
|
+
log.info(`Loading ${this.config.platforms.length} platforms...`);
|
|
534
|
+
const promises = [];
|
|
535
|
+
this.config.platforms.forEach((platformConfig, index) => {
|
|
536
|
+
if (!platformConfig.platform) {
|
|
537
|
+
log.warn('Your config.json contains an illegal platform configuration object at position %d. '
|
|
538
|
+
+ 'Missing property \'platform\'. Skipping entry...', index + 1); // we rather count from 1 for the normal people?
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const platformIdentifier = platformConfig.platform;
|
|
542
|
+
const displayName = platformConfig.name || platformIdentifier;
|
|
543
|
+
let plugin;
|
|
544
|
+
let constructor;
|
|
545
|
+
// do not load homebridge-config-ui-x when running in service mode
|
|
546
|
+
if (platformIdentifier === 'config' && process.env.UIX_SERVICE_MODE === '1') {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
plugin = this.pluginManager.getPluginForPlatform(platformIdentifier);
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
log.error(error.message);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
// check the plugin is not disabled
|
|
557
|
+
if (plugin.disabled) {
|
|
558
|
+
log.warn(`Ignoring config for the platform "${platformIdentifier}" in your config.json as the plugin "${plugin.getPluginIdentifier()}" has been disabled.`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
constructor = plugin.getPlatformConstructor(platformIdentifier);
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
log.error(`Error loading the platform "${platformIdentifier}" requested in your config.json at position ${index + 1} - this is likely an issue with the "${plugin.getPluginIdentifier()}" plugin.`);
|
|
566
|
+
log.error(error); // error message contains more information and full stack trace
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const logger = Logger.withPrefix(displayName);
|
|
570
|
+
logger('Initializing %s platform...', platformIdentifier);
|
|
571
|
+
// Store platform config for later routing
|
|
572
|
+
const platformKey = `${plugin.getPluginIdentifier()}-${platformIdentifier}`;
|
|
573
|
+
this.platformConfigs.set(platformKey, platformConfig);
|
|
574
|
+
// Handle child HAP bridge
|
|
575
|
+
if (platformConfig._bridge) {
|
|
576
|
+
// ensure the username is always uppercase
|
|
577
|
+
platformConfig._bridge.username = platformConfig._bridge.username.toUpperCase();
|
|
578
|
+
try {
|
|
579
|
+
this.validateChildBridgeConfig("platform" /* PluginType.PLATFORM */, platformIdentifier, platformConfig._bridge);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
log.error(error.message);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
logger(`Initializing child bridge ${platformConfig._bridge.username}`);
|
|
586
|
+
const childBridge = new ChildBridgeService("platform" /* PluginType.PLATFORM */, platformIdentifier, plugin, platformConfig._bridge, this.config, this.options, this.api, this.ipcService, this.externalPortService);
|
|
587
|
+
this.childBridges.set(platformConfig._bridge.username, childBridge);
|
|
588
|
+
// add config to child bridge service
|
|
589
|
+
childBridge.addConfig(platformConfig);
|
|
590
|
+
// If _matter is not defined, we're done - this platform only uses HAP child bridge
|
|
591
|
+
if (!platformConfig._matter) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Handle child Matter bridge
|
|
596
|
+
if (platformConfig._matter && typeof platformConfig._matter === 'object') {
|
|
597
|
+
const matterIdentifier = `${plugin.getPluginIdentifier()}-${platformIdentifier}`;
|
|
598
|
+
logger(`Initializing child Matter bridge for ${platformIdentifier}`);
|
|
599
|
+
// Ensure Matter config has proper name
|
|
600
|
+
// Try to get a human-friendly name from the platform
|
|
601
|
+
// Convert CamelCase platform identifier to spaces (e.g., "Ring" stays "Ring", "MySmartHome" becomes "My Smart Home")
|
|
602
|
+
const formattedPlatformId = platformIdentifier.replace(/([A-Z])/g, ' $1').trim();
|
|
603
|
+
// Get plugin name without 'homebridge-' prefix and replace dashes with spaces
|
|
604
|
+
const pluginDisplayName = plugin.getPluginIdentifier()
|
|
605
|
+
.replace('homebridge-', '')
|
|
606
|
+
.replace(/-/g, ' ')
|
|
607
|
+
.split(' ')
|
|
608
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
609
|
+
.join(' ');
|
|
610
|
+
const defaultName = platformConfig.name || formattedPlatformId || pluginDisplayName;
|
|
611
|
+
const matterConfig = {
|
|
612
|
+
...platformConfig._matter,
|
|
613
|
+
name: platformConfig._matter.name || defaultName,
|
|
614
|
+
};
|
|
615
|
+
const childMatterBridge = new ChildMatterBridgeService("platform" /* PluginType.PLATFORM */, plugin, matterConfig, platformConfig, this.api, this.externalPortService, this.options, this.ipcService);
|
|
616
|
+
this.childMatterBridges.set(matterIdentifier, childMatterBridge);
|
|
617
|
+
// If _bridge is also defined, we're done - platform uses both protocols
|
|
618
|
+
if (platformConfig._bridge) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
// If both _bridge and _matter are defined, the platform is handled by child bridges
|
|
623
|
+
if (platformConfig._bridge || platformConfig._matter) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const platform = new constructor(logger, platformConfig, this.api);
|
|
627
|
+
if (HomebridgeAPI.isDynamicPlatformPlugin(platform)) {
|
|
628
|
+
plugin.assignDynamicPlatform(platformIdentifier, platform);
|
|
629
|
+
}
|
|
630
|
+
else if (HomebridgeAPI.isStaticPlatformPlugin(platform)) { // Plugin 1.0, load accessories
|
|
631
|
+
promises.push(this.bridgeService.loadPlatformAccessories(plugin, platform, platformIdentifier, logger));
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// otherwise it's a IndependentPlatformPlugin which doesn't expose any methods at all.
|
|
635
|
+
// We just call the constructor and let it be enabled.
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
return promises;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Validate an external bridge config
|
|
642
|
+
*/
|
|
643
|
+
validateChildBridgeConfig(type, identifier, bridgeConfig) {
|
|
644
|
+
if (!validMacAddress(bridgeConfig.username)) {
|
|
645
|
+
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
646
|
+
+ `not a valid username in _bridge.username: "${bridgeConfig.username}". Must be 6 pairs of colon-separated hexadecimal chars (A-F 0-9), like a MAC address.`);
|
|
647
|
+
}
|
|
648
|
+
if (this.childBridges.has(bridgeConfig.username)) {
|
|
649
|
+
const childBridge = this.childBridges.get(bridgeConfig.username);
|
|
650
|
+
if (type === "platform" /* PluginType.PLATFORM */) {
|
|
651
|
+
// only a single platform can exist on one child bridge
|
|
652
|
+
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
653
|
+
+ `Duplicate username found in _bridge.username: "${bridgeConfig.username}". Each platform child bridge must have it's own unique username.`);
|
|
654
|
+
}
|
|
655
|
+
else if (childBridge?.identifier !== identifier) {
|
|
656
|
+
// only accessories of the same type can be added to the same child bridge
|
|
657
|
+
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
658
|
+
+ `Duplicate username found in _bridge.username: "${bridgeConfig.username}". You can only group accessories of the same type in a child bridge.`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (bridgeConfig.username === this.config.bridge.username.toUpperCase()) {
|
|
662
|
+
throw new Error(`Error loading the ${type} "${identifier}" requested in your config.json - `
|
|
663
|
+
+ `Username found in _bridge.username: "${bridgeConfig.username}" is the same as the main bridge. Each child bridge platform/accessory must have it's own unique username.`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Takes care of the IPC Events sent to Homebridge
|
|
668
|
+
*/
|
|
669
|
+
initializeIpcEventHandlers() {
|
|
670
|
+
// start ipc service
|
|
671
|
+
this.ipcService.start();
|
|
672
|
+
// handle restart child bridge event
|
|
673
|
+
this.ipcService.on("restartChildBridge" /* IpcIncomingEvent.RESTART_CHILD_BRIDGE */, (username) => {
|
|
674
|
+
// noinspection SuspiciousTypeOfGuard
|
|
675
|
+
if (typeof username === 'string') {
|
|
676
|
+
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
677
|
+
childBridge?.restartChildBridge();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
// handle stop child bridge event
|
|
681
|
+
this.ipcService.on("stopChildBridge" /* IpcIncomingEvent.STOP_CHILD_BRIDGE */, (username) => {
|
|
682
|
+
// noinspection SuspiciousTypeOfGuard
|
|
683
|
+
if (typeof username === 'string') {
|
|
684
|
+
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
685
|
+
childBridge?.stopChildBridge();
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
// handle start child bridge event
|
|
689
|
+
this.ipcService.on("startChildBridge" /* IpcIncomingEvent.START_CHILD_BRIDGE */, (username) => {
|
|
690
|
+
// noinspection SuspiciousTypeOfGuard
|
|
691
|
+
if (typeof username === 'string') {
|
|
692
|
+
const childBridge = this.childBridges.get(username.toUpperCase());
|
|
693
|
+
childBridge?.startChildBridge();
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
this.ipcService.on("childBridgeMetadataRequest" /* IpcIncomingEvent.CHILD_BRIDGE_METADATA_REQUEST */, () => {
|
|
697
|
+
this.ipcService.sendMessage("childBridgeMetadataResponse" /* IpcOutgoingEvent.CHILD_BRIDGE_METADATA_RESPONSE */, Array.from(this.childBridges.values()).map(x => x.getMetadata()));
|
|
698
|
+
});
|
|
699
|
+
// Matter bridge IPC handlers
|
|
700
|
+
this.ipcService.on("restartMatterBridge" /* IpcIncomingEvent.RESTART_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
701
|
+
log.info(`Received restart request for Matter bridge: ${matterBridgeId}`);
|
|
702
|
+
if (typeof matterBridgeId === 'string') {
|
|
703
|
+
const matterBridge = this.childMatterBridges.get(matterBridgeId);
|
|
704
|
+
if (matterBridge) {
|
|
705
|
+
log.info(`Restarting Matter bridge: ${matterBridgeId}`);
|
|
706
|
+
matterBridge.restartChildBridge();
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
log.warn(`Matter bridge not found: ${matterBridgeId}`);
|
|
710
|
+
log.debug('Available Matter bridges:', Array.from(this.childMatterBridges.keys()));
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
this.ipcService.on("stopMatterBridge" /* IpcIncomingEvent.STOP_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
715
|
+
log.info(`Received stop request for Matter bridge: ${matterBridgeId}`);
|
|
716
|
+
if (typeof matterBridgeId === 'string') {
|
|
717
|
+
const matterBridge = this.childMatterBridges.get(matterBridgeId);
|
|
718
|
+
if (matterBridge) {
|
|
719
|
+
log.info(`Stopping Matter bridge: ${matterBridgeId}`);
|
|
720
|
+
matterBridge.stopChildBridge();
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
log.warn(`Matter bridge not found: ${matterBridgeId}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
this.ipcService.on("startMatterBridge" /* IpcIncomingEvent.START_MATTER_BRIDGE */, (matterBridgeId) => {
|
|
728
|
+
log.info(`Received start request for Matter bridge: ${matterBridgeId}`);
|
|
729
|
+
if (typeof matterBridgeId === 'string') {
|
|
730
|
+
const matterBridge = this.childMatterBridges.get(matterBridgeId);
|
|
731
|
+
if (matterBridge) {
|
|
732
|
+
log.info(`Starting Matter bridge: ${matterBridgeId}`);
|
|
733
|
+
matterBridge.startChildBridge();
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
log.warn(`Matter bridge not found: ${matterBridgeId}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
this.ipcService.on("matterBridgeMetadataRequest" /* IpcIncomingEvent.MATTER_BRIDGE_METADATA_REQUEST */, () => {
|
|
741
|
+
// Return all child Matter bridge metadata
|
|
742
|
+
this.ipcService.sendMessage("matterBridgeMetadataResponse" /* IpcOutgoingEvent.MATTER_BRIDGE_METADATA_RESPONSE */, Array.from(this.childMatterBridges.values()).map(x => x.getMetadata()));
|
|
743
|
+
});
|
|
744
|
+
this.ipcService.on("matterAccessoriesRequest" /* IpcIncomingEvent.MATTER_ACCESSORIES_REQUEST */, () => {
|
|
745
|
+
// Collect Matter accessories from all child bridges
|
|
746
|
+
const allMatterAccessories = {
|
|
747
|
+
children: {},
|
|
748
|
+
};
|
|
749
|
+
// Add child bridge accessories
|
|
750
|
+
for (const [bridgeId, childBridge] of this.childMatterBridges) {
|
|
751
|
+
allMatterAccessories.children[bridgeId] = childBridge.getMatterAccessories ? childBridge.getMatterAccessories() : [];
|
|
752
|
+
}
|
|
753
|
+
this.ipcService.sendMessage("matterAccessoriesResponse" /* IpcOutgoingEvent.MATTER_ACCESSORIES_RESPONSE */, allMatterAccessories);
|
|
754
|
+
});
|
|
755
|
+
// Handle new Matter IPC events
|
|
756
|
+
this.ipcService.on("toggleMatterDevice" /* IpcIncomingEvent.TOGGLE_MATTER_DEVICE */, (data) => {
|
|
757
|
+
if (data && data.uuid) {
|
|
758
|
+
// This would toggle Matter for a specific device
|
|
759
|
+
// Implementation depends on how devices are managed
|
|
760
|
+
log.debug(`Toggle Matter for device ${data.uuid}: ${data.enabled}`);
|
|
761
|
+
// Send status update
|
|
762
|
+
this.ipcService.sendMessage("matterDeviceStatusUpdate" /* IpcOutgoingEvent.MATTER_DEVICE_STATUS_UPDATE */, {
|
|
763
|
+
uuid: data.uuid,
|
|
764
|
+
matterEnabled: data.enabled,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
this.ipcService.on("matterCommissioningInfoRequest" /* IpcIncomingEvent.MATTER_COMMISSIONING_INFO_REQUEST */, (matterBridgeId) => {
|
|
769
|
+
let commissioningInfo = null;
|
|
770
|
+
// Get child bridge commissioning info
|
|
771
|
+
const childBridge = this.childMatterBridges.get(matterBridgeId);
|
|
772
|
+
if (childBridge && childBridge.getCommissioningInfo) {
|
|
773
|
+
commissioningInfo = childBridge.getCommissioningInfo();
|
|
774
|
+
}
|
|
775
|
+
this.ipcService.sendMessage("matterCommissioningInfoResponse" /* IpcOutgoingEvent.MATTER_COMMISSIONING_INFO_RESPONSE */, commissioningInfo || { commissioned: false });
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
printSetupInfo(pin) {
|
|
779
|
+
/* eslint-disable no-console */
|
|
780
|
+
console.log('Setup Payload:');
|
|
781
|
+
console.log(this.bridgeService.bridge.setupURI());
|
|
782
|
+
if (!this.options.hideQRCode) {
|
|
783
|
+
console.log('Scan this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
784
|
+
qrcode.setErrorLevel('M'); // HAP specifies level M or higher for ECC
|
|
785
|
+
qrcode.generate(this.bridgeService.bridge.setupURI());
|
|
786
|
+
console.log('Or enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
console.log('Enter this code with your HomeKit app on your iOS device to pair with Homebridge:');
|
|
790
|
+
}
|
|
791
|
+
console.log(chalk.black.bgWhite(' '));
|
|
792
|
+
console.log(chalk.black.bgWhite(' ┌────────────┐ '));
|
|
793
|
+
console.log(chalk.black.bgWhite(` │ ${pin} │ `));
|
|
794
|
+
console.log(chalk.black.bgWhite(' └────────────┘ '));
|
|
795
|
+
console.log(chalk.black.bgWhite(' '));
|
|
796
|
+
/* eslint-enable no-console */
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
//# sourceMappingURL=server.js.map
|