UNPKG

@homebridge-plugins/homebridge-smarthq

Version:

The SmartHQ plugin allows you to interact with SmartHQ Devices in HomeKit and with Siri.

847 lines 61.2 kB
import { readFileSync } from 'node:fs'; import { argv } from 'node:process'; import { SmartHQIceMaker } from './devices/OpalIceMaker/index.js'; import axios from 'axios'; import pkg from 'lodash'; import ws from 'ws'; import { SmartHQAdvantium } from './devices/advantium.js'; import { SmartHQAirConditioner } from './devices/airConditioner.js'; import { SmartHQBeverageCenter } from './devices/beverageCenter.js'; import { SmartHQClothesDryer } from './devices/clothesDryer.js'; import { SmartHQClothesWasher } from './devices/clothesWasher.js'; import { SmartHQCoffeeMaker } from './devices/coffeeMaker.js'; import { SmartHQDishWasher } from './devices/dishwasher.js'; import { SmartHQHood } from './devices/hood.js'; import { SmartHQMicrowave } from './devices/microwave.js'; import { SmartHQOven } from './devices/oven.js'; import { SmartHQRefrigerator } from './devices/refrigerator.js'; import { SmartHQWaterFilter } from './devices/waterFilter.js'; import { SmartHQWaterHeater } from './devices/waterHeater.js'; import { SmartHQWaterSoftener } from './devices/waterSoftener.js'; import getAccessToken, { refreshAccessToken } from './getAccessToken.js'; import { API_URL, ERD_CODES, ERD_TYPES, KEEPALIVE_TIMEOUT, PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; const { find } = pkg; axios.defaults.baseURL = API_URL; /** * HomebridgePlatform * This class is the main constructor for your plugin, this is where you should * parse the user config and discover/register accessories with Homebridge. */ export class SmartHQPlatform { accessories; api; log; hap; config; Service; Characteristic; tokenSet; platformConfig; platformLogging; platformRefreshRate; platformPushRate; platformUpdateRate; debugMode; version; constructor(log, config, api) { this.accessories = []; this.api = api; this.hap = this.api.hap; this.log = log; // only load if configured if (!config) { return; } // Plugin options into our config variables. this.config = { platform: PLATFORM_NAME, name: config.name, credentials: config.credentials, devices: config.devices, options: config.options, deviceOptions: config.deviceOptions, }; // Plugin Configuration this.getPlatformLogSettings(); this.getPlatformRateSettings(); this.getPlatformConfigSettings(); this.getVersion(); // Finish initializing the platform this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.debugLog(`Finished initializing platform: ${config.name}`); // verify the config (async () => { try { await this.verifyConfig(); await this.debugLog('Config OK'); } catch (e) { await this.errorLog(`Verify Config, Error Message: ${e.message}, Submit Bugs Here: https://bit.ly/@homebridge-plugins/homebridge-smarthq-bug-report`); this.debugErrorLog(`Verify Config, Error: ${e}`); } })(); // When this event is fired it means Homebridge has restored all cached accessories from disk. // Dynamic Platform plugins should only register new accessories after this event was fired, // in order to ensure they weren't added to homebridge already. This event can also be used // to start discovery of new accessories. this.api.on('didFinishLaunching', async () => { log.debug('Executed didFinishLaunching callback'); // run the method to discover / register your devices as accessories try { await this.discoverDevices(); } catch (e) { await this.errorLog(`Failed to Discover Devices ${JSON.stringify(e.message ?? e)}`); } }); } /** * This function is invoked when homebridge restores cached accessories from disk at startup. * It should be used to setup event handlers for characteristics and update respective values. */ configureAccessory(accessory) { this.infoLog(`Loading accessory from cache: ${accessory.displayName}`); // add the restored accessory to the accessories cache so we can track if it has already been registered this.accessories.push(accessory); } /** * Verify the config passed to the plugin is valid */ async verifyConfig() { if (!this.config.credentials) { throw new Error('No Credentials Found'); } else { if (!this.config.credentials.username) { throw new Error('No Username Found'); } if (!this.config.credentials.password) { throw new Error('No Password Found'); } } } async startRefreshTokenLogic() { if (!this.tokenSet) { throw new Error('Token set is undefined'); } if (this.tokenSet.refresh_token) { try { this.tokenSet = await refreshAccessToken(this.tokenSet.refresh_token); } catch (e) { await this.debugErrorLog(`Failed to refresh Access Token, Error Message: ${e.message ?? e}`); // Handle invalid_grant error (expired/revoked refresh token) if (e.error === 'invalid_grant' || e.message?.includes('invalid_grant') || e.message?.includes('Invalid refresh token')) { await this.debugWarnLog('Refresh token is invalid or expired. Attempting to re-authenticate with username and password...'); // Try to get a new token using username/password const { username, password } = this.config.credentials ?? {}; if (username && password) { try { this.tokenSet = await getAccessToken(username, password); await this.successLog('Successfully re-authenticated with credentials'); // Set up axios with new token if (this.tokenSet.access_token) { axios.defaults.headers.common = { Authorization: `Bearer ${this.tokenSet.access_token}`, }; // Schedule next refresh if (this.tokenSet.expires_in) { setTimeout(this.startRefreshTokenLogic.bind(this), 1000 * (this.tokenSet.expires_in - 2000)); } return; // Successfully recovered } } catch (reAuthError) { await this.errorLog(`Failed to re-authenticate: ${reAuthError.message ?? reAuthError}`); await this.errorLog('Please verify your SmartHQ credentials are correct in the Homebridge config'); await this.errorLog('You may need to log in to the GE SmartHQ app to ensure your account is active'); } } else { await this.errorLog('No credentials available for re-authentication'); await this.errorLog('Please ensure username and password are set in your Homebridge config'); } } await this.errorLog('Submit Bugs Here: https://bit.ly/smarthq-bug-report'); throw e; // Re-throw to stop execution only if recovery failed } } else { throw new Error('Refresh token is undefined'); } if (!this.tokenSet.access_token) { throw new Error('Access token is undefined after refresh'); } axios.defaults.headers.common = { Authorization: `Bearer ${this.tokenSet.access_token}`, }; if (this.tokenSet.expires_in) { setTimeout(this.startRefreshTokenLogic.bind(this), 1000 * (this.tokenSet.expires_in - 2000)); } else { throw new Error('Token expiration time is undefined'); } } /** * This method is used to discover the your location and devices. * Accessories are registered by either their DeviceClass, DeviceModel, or DeviceID */ async discoverDevices() { try { const { username, password } = this.config.credentials ?? {}; if (!username || !password) { throw new Error('Username or password is undefined'); } try { this.tokenSet = await getAccessToken(username, password); } catch (e) { await this.errorLog(`discoverDevices, Failed to get Access Token, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/smarthq-bug-report`); return; // Stop execution if authentication fails } try { await this.startRefreshTokenLogic(); } catch (e) { await this.errorLog(`discoverDevices, Failed to start Refresh Token Logic, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/smarthq-bug-report`); return; // Stop execution if token refresh setup fails } try { const wssData = await axios.get('/websocket'); const connection = new ws(wssData.data.endpoint); connection.on('message', (data) => { const obj = JSON.parse(data.toString()); this.debugLog(`data: ${JSON.stringify(obj)}`); if (obj.kind === 'publish#erd') { const accessory = find(this.accessories, a => a.context.device.applianceId === obj.item.applianceId); if (!accessory) { this.infoLog('Device not found in my list. Maybe we should rerun this plugin?'); return; } if (ERD_CODES[obj.item.erd]) { this.debugLog(`ERD_CODES: ${ERD_CODES[obj.item.erd]}`); this.debugLog(`obj>item>value: ${obj.item.value}`); if (obj.item.erd === ERD_TYPES.UPPER_OVEN_LIGHT) { const service = accessory.getService('Upper Oven Light'); if (service) { service.updateCharacteristic(this.Characteristic.On, obj.item.value === '01'); } } } } }); connection.on('close', (_, reason) => { this.debugLog('Connection closed'); this.debugLog(`reason: ${reason.toString()}`); }); connection.on('open', () => { connection.send(JSON.stringify({ kind: 'websocket#subscribe', action: 'subscribe', resources: ['/appliance/*/erd/*'], })); setInterval(() => connection.send(JSON.stringify({ kind: 'websocket#ping', id: 'keepalive-ping', action: 'ping', })), KEEPALIVE_TIMEOUT); }); } catch (e) { await this.errorLog(`discoverDevices, Failed to get Websocket Data, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/smarthq-bug-report`); } try { const devices = await axios.get('/appliance'); const userId = devices.data.userId; for (const device of devices.data.items) { const [{ data: details }, { data: features }] = await Promise.all([ axios.get(`/appliance/${device.applianceId}`), axios.get(`/appliance/${device.applianceId}/feature`), ]); this.debugLog(`Device: ${JSON.stringify(device)}`); switch (device.type) { case 'Dishwasher': await this.createSmartHQDishWasher(userId, device, details, features); break; case 'Oven': await this.createSmartHQOven(userId, device, details, features); break; case 'Refrigerator': await this.createSmartHQRefrigerator(userId, device, details, features); break; case 'Opal Nugget Ice Maker': await this.createSmartHQIceMaker(userId, device, details, features); break; case 'Air Conditioner': case 'Portable AC': case 'Split Air Conditioner': case 'Through Wall AC': await this.createSmartHQAirConditioner(userId, device, details, features); break; case 'Hood': await this.createSmartHQHood(userId, device, details, features); break; case 'Clothes Washer': await this.createSmartHQClothesWasher(userId, device, details, features); break; case 'Clothes Dryer': await this.createSmartHQClothesDryer(userId, device, details, features); break; case 'Whole Home Water Filter': await this.createSmartHQWaterFilter(userId, device, details, features); break; case 'Whole Home Water Softener': await this.createSmartHQWaterSoftener(userId, device, details, features); break; case 'Whole Home Water Heater': await this.createSmartHQWaterHeater(userId, device, details, features); break; case 'Advantium': await this.createSmartHQAdvantium(userId, device, details, features); break; case 'Microwave': await this.createSmartHQMicrowave(userId, device, details, features); break; case 'Coffee Maker': case 'Espresso Maker': await this.createSmartHQCoffeeMaker(userId, device, details, features); break; case 'Beverage Center': await this.createSmartHQBeverageCenter(userId, device, details, features); break; default: await this.warnLog(`Device Type Not Supported: ${device.type}`); break; } } } catch (e) { await this.errorLog(`discoverDevices, Failed to get Devices Data, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/smarthq-bug-report`); } } catch (e) { await this.errorLog(`discoverDevices, No Device Config, Error Message: ${e.message ?? e}, Submit Bugs Here: https://bit.ly/smarthq-bug-report`); } } async createSmartHQDishWasher(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists if (!device.hide_device) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); // Restore accessory this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new SmartHQDishWasher(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); // the accessory does not yet exist, so we need to create it // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new SmartHQDishWasher(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQOven(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists if (!device.hide_device) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); // Restore accessory this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new SmartHQOven(this, existingAccessory, device); await this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); // the accessory does not yet exist, so we need to create it // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new SmartHQOven(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQIceMaker(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists if (!device.hide_device) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); // Restore accessory // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new SmartHQIceMaker(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); // the accessory does not yet exist, so we need to create it // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new SmartHQIceMaker(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQRefrigerator(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists if (!device.hide_device) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); // Restore accessory this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new SmartHQRefrigerator(this, existingAccessory, device); await this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); // the accessory does not yet exist, so we need to create it // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new SmartHQRefrigerator(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQAirConditioner(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { // the accessory already exists if (!device.hide_device) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); // Restore accessory this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` new SmartHQAirConditioner(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); // the accessory does not yet exist, so we need to create it // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` new SmartHQAirConditioner(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQHood(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQHood(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQHood(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQClothesWasher(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory new SmartHQClothesWasher(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQClothesWasher(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQClothesDryer(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); // create the accessory handler for the restored accessory new SmartHQClothesDryer(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQClothesDryer(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQWaterFilter(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQWaterFilter(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQWaterFilter(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQWaterSoftener(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQWaterSoftener(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQWaterSoftener(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQWaterHeater(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQWaterHeater(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQWaterHeater(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQAdvantium(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQAdvantium(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQAdvantium(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQMicrowave(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQMicrowave(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); } else { this.unregisterPlatformAccessories(existingAccessory); } } else if (!device.hide_device && !existingAccessory) { this.infoLog(`Adding new accessory: ${device.nickname}`); const accessory = new this.api.platformAccessory(device.nickname, uuid); accessory.context.device = device; accessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; accessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); accessory.context.device.firmware = device.firmware ?? await this.getVersion(); new SmartHQMicrowave(this, accessory, device); this.debugLog(`${device.nickname} uuid: ${device.applianceId}`); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.accessories.push(accessory); } else { this.debugErrorLog(`Unable to Register new device: ${JSON.stringify(device.nickname)}`); } } async createSmartHQCoffeeMaker(userId, device, details, features) { const uuid = this.api.hap.uuid.generate(device.applianceId); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { if (!device.hide_device) { existingAccessory.context.device = device; existingAccessory.context = { device: { brand: 'GE', ...details, ...features }, userId }; existingAccessory.displayName = await this.validateAndCleanDisplayName(device.nickname, 'nickname', device.nickname); existingAccessory.context.device.firmware = device.firmware ?? await this.getVersion(); this.api.updatePlatformAccessories([existingAccessory]); this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName}`); new SmartHQCoffeeMaker(this, existingAccessory, device); this.debugLog(`${device.nickname} uuid: ${device.appl