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
JavaScript
"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;