UNPKG

@shadman-a/homebridge-my-ac

Version:

A Homebridge plugin for controlling/monitoring LG ThinQ devices via LG ThinQ platform.

223 lines 10.5 kB
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; import { Helper } from './helper.js'; import { ThinQ } from './lib/ThinQ.js'; import { EventEmitter } from 'events'; import { PlatformType } from './lib/constants.js'; import { ManualProcessNeeded, NotConnectedError } from './errors/index.js'; import Characteristics from './characteristics/index.js'; /** * LGThinQHomebridgePlatform * This class serves as the main entry point for the Homebridge plugin. It handles * configuration parsing, device discovery, and accessory registration. */ export class LGThinQHomebridgePlatform { log; config; api; Service; Characteristic; customCharacteristics; // Tracks restored cached accessories accessories = []; ThinQ; events; intervalTime; // Enable ThinQ1 support enable_thinq1 = false; // This is only required when using Custom Services and Characteristics not support by HomeKit CustomServices; CustomCharacteristics; 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.customCharacteristics = Characteristics(this.api.hap.Characteristic); this.events = new EventEmitter(); this.enable_thinq1 = config.thinq1; this.config.devices = this.config.devices || []; // Set the refresh interval for polling device data this.intervalTime = (config.refresh_interval || 5) * 1000; this.ThinQ = new ThinQ(this, config, log); // Validate required configuration parameters if (!config.country || !config.language || !((config.username && config.password) || config.refresh_token)) { this.log.error('Missing required config parameter.'); return; } const didFinishLaunching = () => { // Discover and register devices after the platform is ready this.ThinQ.isReady().then(() => { this.log.info('Successfully connected to the ThinQ API.'); const discoverDevices = () => { this.discoverDevices().then(async () => { await this.startMonitor(); }).catch(err => { if (err instanceof NotConnectedError) { // Retry device discovery after 30 seconds if not connected setTimeout(() => { discoverDevices(); }, 30000); } else { this.log.error(err.message); this.log.debug(err); } }); }; discoverDevices(); }).catch(err => { if (err.message === 'Internal Server Error' || err.code?.indexOf('ECONN') === 0 || err instanceof NotConnectedError) { this.log.error('LG ThinQ internal server error, try again later.'); } else { this.log.error('ThinQ API is not ready. Please check configuration and try again.'); } this.log.error(err.message); this.log.debug(err); }); }; this.api.on('didFinishLaunching', async () => { log.debug('Executed didFinishLaunching callback'); didFinishLaunching(); }); } /** * Invoked when Homebridge restores cached accessories from disk at startup. * This method sets up event handlers for characteristics and updates respective values. * * @param accessory - The cached accessory to configure. */ configureAccessory(accessory) { this.log.info('Loading accessory from Homebridge cache:', accessory.displayName); if (!accessory.context.device) { this.log.error('Accessory does not have a device context. Cannot restore accessory:', accessory.displayName); return; } // Add the restored accessory to the accessories cache this.accessories.push(accessory); } /** * Discovers devices from the ThinQ API and registers them as Homebridge accessories. */ async discoverDevices() { const accessoriesToRemoveUUID = this.accessories.map(accessory => accessory.UUID); const devices = await this.ThinQ.devices(); if (!devices.length) { this.log.warn('No ThinQ devices in your account.'); } for (const device of devices) { this.log.debug('Device [' + device.name + ']: ', device.toString()); this.log.debug(JSON.stringify(device.data)); // Skip ThinQ1 devices if support is disabled if (!this.enable_thinq1 && device.platform === PlatformType.ThinQ1) { this.log.debug('Thinq1 device is skipped: ', device.toString()); continue; } // Skip devices not explicitly enabled in the configuration if (this.config.devices.length && !this.config.devices.find((enabled) => enabled.id === device.id)) { this.log.info('Device skipped: ', device.id); continue; } this.log.info('[' + device.name + '] Setting up device!'); const setupSuccess = await this.ThinQ.setup(device); if (!setupSuccess) { this.log.warn('[' + device.name + '] Failed to setup device!'); continue; } const accessoryType = Helper.make(device); if (accessoryType === null) { this.log.info('Device not supported: ' + device.platform + ': ' + device.toString()); this.ThinQ.unregister(device).then(() => { this.log.debug(device.id, '- unregistered!'); }); continue; } let lgThinQDevice; const existingAccessory = this.accessories.find(accessory => accessory.UUID === device.id); if (existingAccessory) { // Remove the UUID from the removal list if the accessory already exists accessoriesToRemoveUUID.splice(accessoriesToRemoveUUID.indexOf(device.id), 1); this.log.info('Restoring existing accessory:', device.toString()); existingAccessory.context.device = device; lgThinQDevice = new accessoryType(this, existingAccessory, this.log); } else { this.log.info('Adding new accessory:', device.toString()); const category = Helper.category(device); // Create a new accessory const accessory = new this.api.platformAccessory(device.name, device.id, category); accessory.context.device = device; lgThinQDevice = new accessoryType(this, accessory, this.log); // Register the accessory with Homebridge this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } // Bind the update event for the device this.events.on(device.id, lgThinQDevice.update.bind(lgThinQDevice)); // Perform the first-time update lgThinQDevice.updateAccessoryCharacteristic(device); } // Remove accessories that are no longer present in the ThinQ API const accessoriesToRemove = this.accessories.filter(accessory => accessoriesToRemoveUUID.includes(accessory.UUID)); if (accessoriesToRemove.length) { accessoriesToRemove.map(accessory => { this.log.info('Removing accessory:', accessory.displayName); this.accessories.splice(this.accessories.indexOf(accessory), 1); }); this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessoriesToRemove); } } /** * Starts monitoring devices for updates using MQTT or polling. */ async startMonitor() { // Filter ThinQ2 devices const thinq2devices = this.accessories.filter(accessory => accessory.context.device.platform === PlatformType.ThinQ2); if (thinq2devices.length) { // Start polling ThinQ2 devices at the configured interval setInterval(() => { this.ThinQ.devices().then((devices) => { devices.filter(device => device.platform === PlatformType.ThinQ2).forEach(device => { this.events.emit(device.id, device.snapshot); }); }); }, this.intervalTime); this.log.info('Start MQTT listener for ThinQ2 devices'); await this.ThinQ.registerMQTTListener((data) => { if ('data' in data && 'deviceId' in data) { this.events.emit(data.deviceId, data.data?.state?.reported); } }); } // Stop here if there are no ThinQ1 devices if (this.accessories.length <= thinq2devices.length) { return; } // Start polling ThinQ1 devices this.log.info('Start polling device data every ' + this.config.refresh_interval + ' seconds.'); const ThinQ = this.ThinQ; const interval = setInterval(async () => { try { for (const accessory of this.accessories) { const device = accessory.context.device; if (device.platform === PlatformType.ThinQ1 && this.enable_thinq1) { const deviceWithSnapshot = await ThinQ.pollMonitor(device); if (deviceWithSnapshot.snapshot.raw !== null) { this.events.emit(device.id, deviceWithSnapshot.snapshot); } } } } catch (err) { if (err instanceof ManualProcessNeeded) { this.log.info('Stop polling device data.'); this.log.warn(err.message); clearInterval(interval); return; // Stop the plugin here } } }, this.intervalTime); } } //# sourceMappingURL=platform.js.map