UNPKG

homebridge-savanthost

Version:
386 lines 17.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SavantHostHomebridgePlatform = void 0; const ssh2_1 = require("ssh2"); const platformAccessory_1 = require("./platformAccessory"); const settings_1 = require("./settings"); const auth_1 = require("./auth"); class SavantHostHomebridgePlatform { log; config; api; Service; Characteristic; accessories = new Map(); discoveredCacheUUIDs = []; // 修改类型定义 CustomServices = {}; CustomCharacteristics = {}; scenes = new Map(); pollTimer = null; hubConfig = null; isActivated = false; constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.Service = api.hap.Service; this.Characteristic = api.hap.Characteristic; // 初始化为空对象,确保不会出现 undefined this.CustomServices = {}; this.CustomCharacteristics = {}; // 使用异步 IIFE 来处理动态导入 (async () => { try { const { EveHomeKitTypes } = await Promise.resolve().then(() => __importStar(require('homebridge-lib/lib/EveHomeKitTypes.js'))); if (EveHomeKitTypes) { const eve = new EveHomeKitTypes(this.api); this.CustomServices = eve.Services; this.CustomCharacteristics = eve.Characteristics; this.log.debug('成功加载 EveHomeKitTypes'); } else { this.log.warn('EveHomeKitTypes 模块不可用'); } } catch (error) { this.log.error('加载 EveHomeKitTypes 失败:', error); } })(); this.log.debug('初始化平台:', this.config.name); this.api.on('didFinishLaunching', async () => { this.log.debug('执行 didFinishLaunching 回调'); // 检查插件激活状态 await this.checkActivation(); }); } // 检查插件激活状态 async checkActivation() { try { // 检查是否已激活 this.isActivated = await (0, auth_1.isPluginActivated)(this.log); if (this.isActivated) { this.log.info('插件已激活,开始运行...'); this.startPolling(); return; } // 获取配置中的授权码 const authCode = this.config.authCode; if (!authCode) { // 获取地址码并提示用户 const addressCode = await (0, auth_1.getAddressCode)(this.log); this.log.warn('插件未激活!请联系开发者获取授权码'); this.log.warn(`您的设备地址码: ${addressCode}`); this.log.warn('请在插件配置中填入授权码后重启Homebridge'); return; } // 尝试激活插件 const activationResult = await (0, auth_1.activatePlugin)(authCode, this.log); if (activationResult) { this.isActivated = true; this.log.info('插件已成功激活,开始运行...'); this.startPolling(); } else { const addressCode = await (0, auth_1.getAddressCode)(this.log); this.log.error('授权码无效,插件无法启动'); this.log.warn(`您的设备地址码: ${addressCode}`); this.log.warn('请确认授权码正确或联系开发者获取新的授权码'); } } catch (error) { this.log.error('检查授权状态出错:', error); } } async createSSHConnection() { return new Promise((resolve, reject) => { const client = new ssh2_1.Client(); client .on('ready', () => { this.log.info('SSH 连接已建立'); resolve(client); }) .on('error', (err) => { this.log.error('SSH 连接错误:', err); reject(err); }) .connect({ host: this.hubConfig.ip, port: this.hubConfig.port, username: this.hubConfig.username, password: this.hubConfig.password, algorithms: { serverHostKey: ['ssh-rsa', 'ecdsa-sha2-nistp256', 'ssh-ed25519'], }, hostVerifier: () => true, readyTimeout: 30000, // 连接超时时间30秒 debug: (message) => { this.log.debug('SSH Debug:', message); }, }); }); } async closeSSHConnection(client) { return new Promise((resolve) => { client.on('close', () => { this.log.debug('SSH 连接已关闭'); resolve(); }); client.end(); }); } startPolling() { // 如果未激活,不启动 if (!this.isActivated) { this.log.warn('插件未激活,无法启动轮询'); return; } if (!this.config.hubs?.[0]) { this.log.error('未找到有效的主机配置'); return; } this.hubConfig = this.config.hubs[0]; // 确保设置了轮询间隔,如果未设置则使用默认值 if (!this.hubConfig.statePollingInterval) { this.hubConfig.statePollingInterval = this.config.statePollingInterval || 300; } // 验证轮询间隔 if (this.hubConfig.statePollingInterval < 60 || this.hubConfig.statePollingInterval > 3600) { this.log.warn(`轮询间隔 ${this.hubConfig.statePollingInterval} 超出范围,将使用默认值 300 秒`); this.hubConfig.statePollingInterval = 300; } this.log.info(`使用轮询间隔: ${this.hubConfig.statePollingInterval} 秒`); // 立即执行第一次查询 this.log.debug('执行首次场景查询'); this.fetchScenes(); // 设置定时轮询 this.pollTimer = setInterval(() => { this.log.debug(`执行定时场景查询 (间隔: ${this.hubConfig.statePollingInterval} 秒)`); this.fetchScenes(); }, this.hubConfig.statePollingInterval * 1000); // 确保定时器不会阻止进程退出 this.pollTimer.unref(); } getScliPath() { return this.hubConfig.hostType === 'ProHost' ? '/Users/rpm/Applications/RacePointMedia/sclibridge' : '/usr/local/bin/sclibridge'; } async executeCommand(command) { let client = null; try { this.log.debug('建立新的 SSH 连接'); client = await this.createSSHConnection(); const scliPath = this.getScliPath(); const fullCommand = `${scliPath} ${command}`; this.log.debug('准备执行命令:', fullCommand); const result = await new Promise((resolve, reject) => { // 根据主机类型设置不同的环境变量和路径 const setupCommands = this.hubConfig.hostType === 'ProHost' ? [ 'export PATH="/Users/rpm/Applications/RacePointMedia:$PATH"', 'cd /Users/rpm/Applications/RacePointMedia', ] : [ 'export PATH="/usr/local/bin:$PATH"', 'export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"', 'cd /usr/local/bin', ]; // 组合所有命令 const wrappedCommand = [...setupCommands, fullCommand].join(' && '); this.log.debug('完整命令:', wrappedCommand); client.exec(wrappedCommand, (err, stream) => { if (err) { this.log.error('执行命令失败:', err); reject(err); return; } let output = ''; let errorOutput = ''; stream.on('data', (data) => { const str = data.toString(); this.log.debug('收到命令输出:', str); output += str; }); stream.stderr.on('data', (data) => { const str = data.toString(); this.log.debug('收到错误输出:', str); errorOutput += str; }); stream.on('close', (code) => { this.log.debug('命令执行完成,退出码:', code); this.log.debug('清理后的输出:', output.trim()); this.log.debug('错误输出:', errorOutput || '无错误输出'); if (code !== 0) { this.log.error(`命令执行失败,退出码: ${code}`); this.log.error(`错误输出: ${errorOutput || '无错误输出'}`); resolve({ stdout: '', stderr: errorOutput }); return; } resolve({ stdout: output.trim(), stderr: errorOutput }); }); }); }); return result; } finally { if (client) { this.log.debug('命令执行完成,关闭 SSH 连接'); await this.closeSSHConnection(client); } } } async fetchScenes() { try { this.log.debug('尝试获取场景列表'); const command = 'getSceneNames'; this.log.debug('执行场景查询命令:', `${this.getScliPath()} ${command}`); const { stdout } = await this.executeCommand(command); this.log.debug('收到原始场景数据:', stdout); const scenes = this.parseScenes(stdout); this.log.info(`成功解析 ${scenes.length} 个场景`); scenes.forEach(scene => { this.log.debug(`场景信息: 名称=${scene.sceneName}, ID=${scene.sceneId}, 用户=${scene.sceneUser}`); }); this.updateAccessories(scenes); return scenes; } catch (error) { this.log.error('执行场景查询命令时出错:', error); return []; } } parseScenes(output) { if (!output) { this.log.warn('没有收到场景数据'); return []; } const lines = output.split('\n').filter(line => line.trim()); this.log.debug('场景数据行数:', lines.length); return lines.map(line => { this.log.debug('处理场景数据行:', line); const [sceneName, sceneId, sceneUser] = line.split(',').map(item => item.trim()); if (!sceneName || !sceneId || !sceneUser) { this.log.warn('无效的场景数据行:', line); return null; } return { sceneName, sceneId, sceneUser }; }).filter((scene) => scene !== null); } updateAccessories(scenes) { this.log.debug('开始更新配件列表'); this.log.debug('当前场景数量:', scenes.length); // 重置已发现的配件列表 this.discoveredCacheUUIDs = []; this.scenes.clear(); // 清空现有场景列表 // 创建一个 Map 来跟踪场景 ID 和对应的用户列表 const sceneIdUsers = new Map(); // 首先收集所有相同 ID 的场景的用户 for (const scene of scenes) { const users = sceneIdUsers.get(scene.sceneId) || new Set(); users.add(scene.sceneUser); sceneIdUsers.set(scene.sceneId, users); } for (const scene of scenes) { this.log.debug('处理场景:', scene.sceneName); const uuid = this.api.hap.uuid.generate(scene.sceneId); this.log.debug('场景UUID:', uuid); // 获取此场景 ID 的所有用户 const users = sceneIdUsers.get(scene.sceneId); if (users && users.size > 1) { this.log.info(`场景 "${scene.sceneName}" (ID: ${scene.sceneId}) 有多个用户: ${Array.from(users).join(', ')}`); } // 添加到发现列表 this.discoveredCacheUUIDs.push(uuid); this.scenes.set(scene.sceneName, scene); const existingAccessory = this.accessories.get(uuid); if (existingAccessory) { this.log.debug('更新现有配件:', scene.sceneName); // 检查是否需要更新用户 if (existingAccessory.context.scene.sceneUser !== scene.sceneUser) { this.log.info(`更新场景 "${scene.sceneName}" 的用户从 "${existingAccessory.context.scene.sceneUser}""${scene.sceneUser}"`); } existingAccessory.context.scene = scene; this.api.updatePlatformAccessories([existingAccessory]); new platformAccessory_1.SavantHostPlatformAccessory(this, existingAccessory); } else { this.log.debug('创建新配件:', scene.sceneName); const accessory = new this.api.platformAccessory(scene.sceneName, uuid); accessory.context.scene = scene; new platformAccessory_1.SavantHostPlatformAccessory(this, accessory); this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.accessories.set(uuid, accessory); } } // 移除不存在的配件 const accessoriesToRemove = []; for (const [uuid, accessory] of this.accessories) { if (!this.discoveredCacheUUIDs.includes(uuid)) { this.log.info('从缓存中移除配件:', accessory.displayName); accessoriesToRemove.push(accessory); this.accessories.delete(uuid); } } if (accessoriesToRemove.length > 0) { this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, accessoriesToRemove); } this.log.debug('配件更新完成'); this.log.debug('当前配件数量:', this.accessories.size); this.log.debug('当前场景数量:', this.scenes.size); } /** * This function is invoked when homebridge restores cached accessories from disk at startup. * It should be used to set up event handlers for characteristics and update respective values. */ configureAccessory(accessory) { this.log.info('从缓存加载配件:', accessory.displayName); this.accessories.set(accessory.UUID, accessory); } async activateScene(sceneName, sceneId, sceneUser) { try { const command = `activateScene '${sceneName}' '${sceneId}' '${sceneUser}'`; this.log.debug('执行场景激活命令:', `${this.getScliPath()} ${command}`); const { stdout } = await this.executeCommand(command); this.log.debug('场景激活结果:', stdout); } catch (error) { this.log.error('执行场景激活命令时出错:', error); } } } exports.SavantHostHomebridgePlatform = SavantHostHomebridgePlatform; //# sourceMappingURL=platform.js.map