UNPKG

homebridge-lg-thinq-ceiling-fan

Version:

A Homebridge plugin for controlling LG ceiling fans via the LG ThinQ platform with working speed control

261 lines (260 loc) 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LGCeilingFanPlatform = void 0; const settings_1 = require("./settings"); const lg_api_1 = require("./lg-api"); const ceiling_fan_accessory_1 = require("./ceiling-fan-accessory"); class LGCeilingFanPlatform { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.accessories = []; this.fanAccessories = new Map(); this.isAuthenticated = false; this.log.debug('Finished initializing platform:', this.config.name); if (!this.isValidConfig(this.config)) { this.log.error('Invalid configuration. Please check your config.json file.'); return; } this.lgApi = new lg_api_1.LGApi(this.config.country, this.config.language); this.api.on('didFinishLaunching', () => { this.log.debug('Executed didFinishLaunching callback'); this.discoverDevices(); this.startAuthenticationHealthCheck(); }); } configureAccessory(accessory) { this.log.info('Loading accessory from cache:', accessory.displayName); this.accessories.push(accessory); } isValidConfig(config) { if (!config.auth_mode) { this.log.error('auth_mode is required in configuration'); return false; } if (config.auth_mode === 'token' && !config.refresh_token) { this.log.error('refresh_token is required when using token authentication'); return false; } if (config.auth_mode === 'account' && (!config.username || !config.password)) { this.log.error('username and password are required when using account authentication'); return false; } if (!config.country) { this.log.error('country is required in configuration'); return false; } if (!config.language) { this.log.error('language is required in configuration'); return false; } return true; } async authenticate() { try { this.log.info('Authenticating with LG ThinQ API...'); if (this.config.auth_mode === 'token') { await this.lgApi.authenticateWithToken(this.config.refresh_token); } else { await this.lgApi.authenticateWithCredentials(this.config.username, this.config.password); } this.isAuthenticated = true; this.log.info('Successfully authenticated with LG ThinQ API'); return true; } catch (error) { this.log.error('Failed to authenticate with LG ThinQ API:', error); this.isAuthenticated = false; return false; } } async authenticateWithAutoRefresh() { try { this.log.info('Authenticating with LG ThinQ API...'); if (this.config.auth_mode === 'token') { if (this.config.auto_refresh !== false && this.config.save_credentials && this.config.username && this.config.password) { this.log.debug('Auto-refresh enabled with saved credentials'); await this.lgApi.autoRefreshWithCredentials(this.config.username, this.config.password); } else { await this.lgApi.authenticateWithToken(this.config.refresh_token); } } else { await this.lgApi.authenticateWithCredentials(this.config.username, this.config.password); } this.isAuthenticated = true; this.log.info('Successfully authenticated with LG ThinQ API'); const authData = this.lgApi.getAuthData(); if (authData && authData.refreshToken !== this.config.refresh_token) { this.log.debug('Refresh token updated'); } return true; } catch (error) { this.log.error('Failed to authenticate with LG ThinQ API:', error); this.isAuthenticated = false; return false; } } async executeWithAutoRefresh(apiCall) { if (!this.config.auto_refresh || !this.config.save_credentials || !this.config.username || !this.config.password) { return await apiCall(); } return await this.lgApi.executeWithRetry(apiCall, this.config.username, this.config.password); } async discoverDevices() { try { if (!await this.authenticateWithAutoRefresh()) { this.log.error('Authentication failed. Cannot discover devices.'); return; } const devices = await this.executeWithAutoRefresh(() => this.lgApi.getDevices()); this.log.info(`Found ${devices.length} devices from LG ThinQ API`); const configuredDevices = this.getConfiguredDevices(devices); this.log.info(`Configuring ${configuredDevices.length} ceiling fans`); for (const deviceConfig of configuredDevices) { await this.createOrUpdateAccessory(deviceConfig); } this.removeUnusedAccessories(configuredDevices); } catch (error) { this.log.error('Failed to discover devices:', error); } } getConfiguredDevices(apiDevices) { const configuredDevices = []; if (this.config.devices && this.config.devices.length > 0) { for (const deviceConfig of this.config.devices) { const apiDevice = apiDevices.find(d => d.deviceId === deviceConfig.id); if (apiDevice) { configuredDevices.push({ id: deviceConfig.id, name: deviceConfig.name || apiDevice.alias, model: deviceConfig.model || apiDevice.applianceType, max_speed: 4, }); } else { this.log.warn(`Device ${deviceConfig.id} not found in LG ThinQ account`); } } } else { for (const apiDevice of apiDevices) { if (this.isCeilingFan(apiDevice)) { configuredDevices.push({ id: apiDevice.deviceId, name: apiDevice.alias, model: apiDevice.applianceType, max_speed: 4, }); } } } return configuredDevices; } isCeilingFan(device) { const deviceType = device.applianceType?.toLowerCase() || ''; const deviceCode = device.deviceCode?.toLowerCase() || ''; const alias = device.alias?.toLowerCase() || ''; const fanKeywords = ['fan', 'ceiling', 'air_circulator', 'ventilator', 'vantilatör', 'tavan', 'havalandırma']; return fanKeywords.some(keyword => deviceType.includes(keyword) || deviceCode.includes(keyword) || alias.includes(keyword)); } async createOrUpdateAccessory(deviceConfig) { try { const uuid = this.api.hap.uuid.generate(deviceConfig.id); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); existingAccessory.context.device = deviceConfig; const fanAccessory = new ceiling_fan_accessory_1.LGCeilingFanAccessory(this, existingAccessory, deviceConfig, this.lgApi); this.fanAccessories.set(deviceConfig.id, fanAccessory); fanAccessory.startStatusUpdates(); this.api.updatePlatformAccessories([existingAccessory]); } else { this.log.info(`Adding new LG ceiling fan: ${deviceConfig.name}`); const accessory = new this.api.platformAccessory(deviceConfig.name || `LG Ceiling Fan ${deviceConfig.id}`, uuid); accessory.context.device = deviceConfig; const fanAccessory = new ceiling_fan_accessory_1.LGCeilingFanAccessory(this, accessory, deviceConfig, this.lgApi); this.fanAccessories.set(deviceConfig.id, fanAccessory); fanAccessory.startStatusUpdates(); this.accessories.push(accessory); this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); } } catch (error) { this.log.error(`Failed to create accessory for device ${deviceConfig.id}:`, error); } } removeUnusedAccessories(configuredDevices) { const configuredIds = new Set(configuredDevices.map(d => d.id)); const accessoriesToRemove = []; for (const accessory of this.accessories) { const deviceId = accessory.context.device?.id; if (deviceId && !configuredIds.has(deviceId)) { this.log.info('Removing unused accessory:', accessory.displayName); this.fanAccessories.delete(deviceId); accessoriesToRemove.push(accessory); } } if (accessoriesToRemove.length > 0) { this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, accessoriesToRemove); } } get platformConfig() { return this.config; } getLgApi() { return this.lgApi; } async executeApiWithAutoRefresh(apiCall) { return await this.executeWithAutoRefresh(apiCall); } startAuthenticationHealthCheck() { if (!this.config.auto_refresh || !this.config.save_credentials) { this.log.info('Authentication health monitoring disabled (auto_refresh or save_credentials not enabled)'); return; } const healthCheckInterval = 15 * 60 * 1000; setInterval(async () => { try { console.log('[Platform DEBUG] Running authentication health check'); if (!this.lgApi.isAuthenticated()) { console.log('[Platform DEBUG] Authentication appears invalid, attempting refresh'); this.log.warn('Authentication token appears expired, attempting auto-refresh'); if (this.config.username && this.config.password) { try { await this.lgApi.autoRefreshWithCredentials(this.config.username, this.config.password); this.log.info('Authentication auto-refresh successful'); console.log('[Platform DEBUG] Authentication health check: refresh successful'); } catch (error) { this.log.error(`Authentication auto-refresh failed: ${error.message}`); console.error('[Platform ERROR] Authentication health check failed:', error.message); } } else { this.log.error('Authentication invalid but no credentials available for auto-refresh'); } } else { console.log('[Platform DEBUG] Authentication health check: token appears valid'); } } catch (error) { console.error('[Platform ERROR] Authentication health check error:', error.message); this.log.error(`Authentication health check error: ${error.message}`); } }, healthCheckInterval); this.log.info(`Started authentication health monitoring (checking every ${healthCheckInterval / 60000} minutes)`); } } exports.LGCeilingFanPlatform = LGCeilingFanPlatform;