homebridge
Version:
HomeKit support for the impatient
288 lines • 12 kB
JavaScript
/**
* 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