UNPKG

homebridge

Version:
288 lines 12 kB
/** * Matter.js Server Implementation for Homebridge Plugin API * * This is a thin facade that delegates to focused submodules under ./server/. * All public method signatures are preserved for external callers. */ import { EventEmitter } from 'node:events'; import { Logger as MatterLogger, LogLevel as MatterLogLevel } from '@matter/main'; import { Logger } from '../logger.js'; import { BehaviorRegistry, RegistryManager } from './behaviors/index.js'; import { createHomebridgeLogFormatter } from './logFormatter.js'; import { AccessoryManager, AccessoryQuery, CommissioningManager, FabricManager, ServerLifecycle, StateManager, validateAndSanitizeConfig, } from './server/index.js'; import { clusters, deviceTypes, } from './types.js'; const log = Logger.withPrefix('Matter/Server'); const NON_HEX_RE = /[^A-F0-9]/gi; const HEX_PAIR_RE = /.{1,2}/g; /** * Matter Server for Homebridge Plugin API * Allows plugins to register Matter accessories explicitly */ export class MatterServer extends EventEmitter { // --- Internal state --- config; serverNode = null; aggregator = null; accessories = new Map(); behaviorRegistry; registryManager; isRunning = false; shutdownHandler = null; cleanupHandlers = []; accessoryCache = null; monitoringEnabled = false; // Public properties for bridge identification username; bridgeName; // --- Sub-modules --- commissioningManager; fabricManager; serverLifecycle; stateManager; accessoryManager; accessoryQuery; constructor(config) { super(); // Store the validated config this.config = validateAndSanitizeConfig(config); // Initialize bridge identification properties const cleanId = this.config.uniqueId.replace(NON_HEX_RE, ''); this.username = cleanId.match(HEX_PAIR_RE)?.slice(0, 6).join(':').toUpperCase() || this.config.uniqueId; this.bridgeName = this.config.serialNumber ? `Matter Bridge ${this.config.serialNumber}` : 'Matter Bridge'; // Configure Matter.js library logging if (this.config.debugModeEnabled) { log.info('Matter debug mode enabled - verbose logging active'); MatterLogger.level = MatterLogLevel.DEBUG; } else { MatterLogger.level = MatterLogLevel.NOTICE; } MatterLogger.format = createHomebridgeLogFormatter(); MatterLogger.destinations.default.write = (text) => { if (text.trim() !== '') { console.log(text); // eslint-disable-line no-console } }; // Initialize sub-modules this.commissioningManager = new CommissioningManager(); this.fabricManager = new FabricManager(() => this.serverNode, () => this.serverLifecycle.matterStoragePath); this.serverLifecycle = new ServerLifecycle(); this.behaviorRegistry = new BehaviorRegistry(this.accessories, this); this.registryManager = new RegistryManager(); this.stateManager = new StateManager(this.accessories, this, () => this.monitoringEnabled); this.accessoryManager = new AccessoryManager(); this.accessoryQuery = new AccessoryQuery(this.accessories, () => this.accessoryCache); } // ============================================================================ // Lifecycle methods // ============================================================================ async start() { return this.serverLifecycle.start(this.getLifecycleDeps()); } async runServer() { return this.serverLifecycle.runServer(this.getLifecycleDeps()); } async stop() { return this.serverLifecycle.stop(this.getLifecycleDeps(), this.accessories); } // ============================================================================ // Accessory registration (Plugin API - matches HAP pattern) // ============================================================================ async registerPlatformAccessories(pluginIdentifier, platformName, accessories) { for (const accessory of accessories) { await this.accessoryManager.registerAccessory(pluginIdentifier, platformName, accessory, this.getAccessoryManagerDeps()); } } async unregisterAccessory(uuid) { return this.accessoryManager.unregisterAccessory(uuid, this.getAccessoryManagerDeps()); } async unregisterPlatformAccessories(_pluginIdentifier, _platformName, accessories) { for (const accessory of accessories) { await this.accessoryManager.unregisterAccessory(accessory.UUID, this.getAccessoryManagerDeps()); } } async updatePlatformAccessories(accessories) { if (!this.accessoryCache) { log.warn('Cannot update Matter platform accessories - cache not initialized'); return; } for (const accessory of accessories) { const internal = accessory; if (!this.accessories.has(accessory.UUID)) { log.warn(`Cannot update Matter accessory ${accessory.UUID} - not registered in current session`); continue; } if (!this.accessoryCache.hasCached(accessory.UUID)) { log.warn(`Cannot update Matter accessory ${accessory.UUID} - not found in cache`); continue; } this.accessories.set(accessory.UUID, internal); log.debug(`Updated Matter accessory ${accessory.UUID} (${accessory.displayName})`); } this.accessoryCache.requestSave(this.accessories); } // ============================================================================ // State management (Plugin API) // ============================================================================ async updateAccessoryState(uuid, cluster, attributes, partId) { return this.stateManager.updateAccessoryState(uuid, cluster, attributes, partId); } getAccessoryState(uuid, cluster, partId) { return this.stateManager.getAccessoryState(uuid, cluster, partId); } async triggerCommand(uuid, cluster, command, args, partId) { return this.stateManager.triggerCommand(uuid, cluster, command, args, partId); } // ============================================================================ // Accessory queries // ============================================================================ getAccessories() { return this.accessoryQuery.getAccessories(); } getAccessory(uuid) { return this.accessoryQuery.getAccessory(uuid); } getAllCachedAccessories() { return this.accessoryQuery.getAllCachedAccessories(); } getCachedAccessory(uuid) { return this.accessoryQuery.getCachedAccessory(uuid); } collectAccessories(bridgeUsername, bridgeType, bridgeName) { return this.accessoryQuery.collectAccessories(bridgeUsername, bridgeType, bridgeName); } getAccessoryInfo(uuid) { return this.accessoryQuery.getAccessoryInfo(uuid); } // ============================================================================ // Fabric management // ============================================================================ getFabricInfo() { return this.fabricManager.getFabricInfo(); } isCommissioned() { return this.fabricManager.isCommissioned(); } getCommissionedFabricCount() { return this.fabricManager.getCommissionedFabricCount(); } getCommissioningSnapshot() { return this.fabricManager.getCommissioningSnapshot(); } async removeFabric(fabricIndex) { return this.fabricManager.removeFabric(fabricIndex); } hasFabric(fabricIndex) { return this.fabricManager.hasFabric(fabricIndex); } // ============================================================================ // Commissioning info // ============================================================================ getCommissioningInfo() { return { ...this.commissioningManager.commissioningInfo, serialNumber: this.config.serialNumber || this.config.uniqueId, passcode: this.commissioningManager.passcode, discriminator: this.commissioningManager.discriminator, commissioned: this.isCommissioned(), }; } // ============================================================================ // Server info & monitoring // ============================================================================ getServerInfo() { return { running: this.isRunning, port: this.config.port || 5540, deviceCount: this.accessories.size, commissioned: this.isCommissioned(), fabricCount: this.getCommissionedFabricCount(), serialNumber: this.config.serialNumber || this.config.uniqueId, }; } getStorageStats() { // Storage is now managed natively by matter.js return null; } isServerRunning() { return this.isRunning; } getDeviceTypes() { return deviceTypes; } getClusters() { return clusters; } enableStateMonitoring() { this.monitoringEnabled = true; log.debug('Matter state monitoring enabled'); } disableStateMonitoring() { this.monitoringEnabled = false; log.debug('Matter state monitoring disabled'); } isMonitoringEnabled() { return this.monitoringEnabled; } notifyStateChange(uuid, cluster, state, partId) { this.stateManager.notifyStateChange(uuid, cluster, state, partId); } // ============================================================================ // Internal dependency builders for sub-modules // ============================================================================ getCommissioningDeps() { return { config: this.config, serverNode: this.serverNode, matterStoragePath: this.serverLifecycle.matterStoragePath, serialNumber: this.config.serialNumber || this.config.uniqueId, emitter: this, fabricManager: this.fabricManager, }; } getLifecycleDeps() { return { config: this.config, commissioningManager: this.commissioningManager, fabricManager: this.fabricManager, getCommissioningDeps: () => this.getCommissioningDeps(), getAccessoryCache: () => this.accessoryCache, setAccessoryCache: (cache) => { this.accessoryCache = cache; }, setServerNode: (node) => { this.serverNode = node; }, getServerNode: () => this.serverNode, setAggregator: (agg) => { this.aggregator = agg; }, getAggregator: () => this.aggregator, setIsRunning: (running) => { this.isRunning = running; }, getIsRunning: () => this.isRunning, cleanupHandlers: this.cleanupHandlers, shutdownHandler: this.shutdownHandler, setShutdownHandler: (handler) => { this.shutdownHandler = handler; }, onStop: () => this.stop(), }; } getAccessoryManagerDeps() { return { config: this.config, accessories: this.accessories, behaviorRegistry: this.behaviorRegistry, registryManager: this.registryManager, accessoryCache: this.accessoryCache, getServerNode: () => this.serverNode, getAggregator: () => this.aggregator, getIsRunning: () => this.isRunning, getMonitoringEnabled: () => this.monitoringEnabled, isCommissioned: () => this.isCommissioned(), }; } } //# sourceMappingURL=server.js.map