homebridge-nest-accfactory
Version:
Homebridge support for Nest/Google devices including HomeKit Secure Video (HKSV) support for doorbells and cameras
106 lines (89 loc) • 3.6 kB
JavaScript
// Device Module Loader
// Part of homebridge-nest-accfactory
//
// Loads and registers device support modules used by the platform.
// Discovers HomeKitDevice-based device classes, collects any additional
// exported helpers (such as processRawData), and builds a device registry
// for use by the main system module.
//
// Responsibilities:
// - Discover device support modules from the device directory
// - Load HomeKitDevice subclasses dynamically
// - Validate required module metadata (TYPE and VERSION)
// - Register optional helper exports alongside each device class
// - Provide HomeKit accessory category mapping for supported device types
//
// Features:
// - Dynamic module loading from the plugin devices directory
// - Automatic registration of HomeKitDevice subclasses
// - Support for additional exported helpers such as processRawData()
// - Version logging for loaded device modules
// - HomeKit category resolution via getDeviceHKCategory()
//
// Notes:
// - Device modules must export a class extending HomeKitDevice
// - Device classes must provide static TYPE and VERSION properties
// - Additional named exports are attached to the module entry when present
// - Used by the main system module to create and manage device instances
//
// Code version 2026.05.06
// Mark Hulskamp
;
// Define nodejs module requirements
import path from 'node:path';
import fs from 'node:fs/promises';
import url from 'node:url';
// Import our modules
import HomeKitDevice from './HomeKitDevice.js';
// Define constants
import { __dirname, DEVICE_CATEGORY_MAP } from './consts.js';
async function loadDeviceModules(log, pluginDir = '') {
let baseDir = path.join(__dirname, pluginDir);
let deviceMap = new Map();
let files = (await fs.readdir(baseDir)).sort();
log?.debug?.('Base module - %s', HomeKitDevice.VERSION);
for (const file of files) {
if (file.endsWith('.js') === false) {
continue;
}
try {
let modulePath = url.pathToFileURL(path.join(baseDir, file)).href;
let module = await import(modulePath);
// Check all exports for valid HomeKitDevice subclasses
for (const exported of Object.values(module)) {
if (typeof exported !== 'function') {
continue;
}
// Only process if it's a HomeKitDevice subclass
if (HomeKitDevice.prototype.isPrototypeOf(exported.prototype) === false) {
continue;
}
// Validate TYPE and VERSION
if (typeof exported.TYPE !== 'string' || exported.TYPE === '' || typeof exported.VERSION !== 'string' || exported.VERSION === '') {
log?.warn?.('Skipping device module %s (missing TYPE or VERSION)', file);
continue;
}
// Register this class if not already registered
if (deviceMap.has(exported.TYPE) === false) {
let entry = { class: exported };
// Add additional named functions (like processRawData)
for (const [key, value] of Object.entries(module)) {
if (typeof value === 'function' && value !== exported) {
entry[key] = value;
}
}
deviceMap.set(exported.TYPE, entry);
log?.debug?.('%s module - %s', exported.TYPE, exported.VERSION);
}
}
} catch (error) {
log?.warn?.('Failed to load device support module "%s": %s', file, error.message);
}
}
return deviceMap;
}
function getDeviceHKCategory(type) {
return DEVICE_CATEGORY_MAP[type] ?? 1; // Categories.OTHER
}
// Define exports
export { loadDeviceModules, getDeviceHKCategory };