UNPKG

@homebridge-plugins/homebridge-ewelink

Version:

Homebridge plugin to integrate eWeLink devices into HomeKit.

1,278 lines (1,229 loc) 86.7 kB
import { createServer } from 'node:http' import { createRequire } from 'node:module' import { join } from 'node:path' import process from 'node:process' import storage from 'node-persist' import PQueue from 'p-queue' import apiClient from './connection/api.js' import httpClient from './connection/http.js' import lanClient from './connection/lan.js' import wsClient from './connection/ws.js' import deviceTypes from './device/index.js' import eveService from './fakegato/fakegato-history.js' import platformConsts from './utils/constants.js' import platformChars from './utils/custom-chars.js' import eveChars from './utils/eve-chars.js' import { parseDeviceId, parseError } from './utils/functions.js' import platformLang from './utils/lang.js' const require = createRequire(import.meta.url) const plugin = require('../package.json') const devicesInHB = new Map() const queue = new PQueue({ interval: 250, intervalCap: 1, timeout: 10000, }) export default class { constructor(log, config, api) { if (!log || !api) { return } // Begin plugin initialisation try { this.api = api this.log = log this.isBeta = process.argv.includes('-D') this.apiClient = false this.apiServer = false this.httpClient = false this.lanClient = false this.lanDevices = false this.wsClient = false // Configuration objects for accessories this.deviceConf = {} this.rfSubdevices = {} this.hideChannels = [] this.hideMasters = [] this.ignoredDevices = [] this.ipOverride = {} this.obstructSwitches = {} // Retrieve the user's chosen language file const lang = platformConsts.allowed.language.includes(config.language) ? config.language : platformConsts.defaultValues.language this.lang = platformLang[lang] // Make sure user is running Homebridge v1.5 or above if (!api?.versionGreaterOrEqual('1.5.0')) { throw new Error(this.lang.hbVersionFail) } // Check the user has configured the plugin if (!config) { throw new Error(this.lang.pluginNotConf) } // Log some environment info for debugging this.log( '%s v%s | System %s | Node %s | HB v%s | HAPNodeJS v%s...', this.lang.initialising, plugin.version, process.platform, process.version, api.serverVersion, api.hap.HAPLibraryVersion(), ) // Apply the user's configuration this.config = platformConsts.defaultConfig this.applyUserConfig(config) // Set up the Homebridge events this.api.on('didFinishLaunching', () => this.pluginSetup()) this.api.on('shutdown', () => this.pluginShutdown()) } catch (err) { // Catch any errors during initialisation const eText = parseError(err, [this.lang.hbVersionFail, this.lang.pluginNotConf]) log.warn('***** %s. *****', this.lang.disabling) log.warn('***** %s. *****', eText) } } applyUserConfig(config) { // These shorthand functions save line space during config parsing const logDefault = (k, def) => { this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgDef, def) } const logDuplicate = (k) => { this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgDup) } const logIgnore = (k) => { this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgIgn) } const logIgnoreItem = (k) => { this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgIgnItem) } const logIncrease = (k, min) => { this.log.warn('%s [%s] %s %s.', this.lang.cfgItem, k, this.lang.cfgLow, min) } const logQuotes = (k) => { this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgQts) } const logRemove = (k) => { this.log.warn('%s [%s] %s.', this.lang.cfgItem, k, this.lang.cfgRmv) } // Begin applying the user's config Object.entries(config).forEach((entry) => { const [key, val] = entry switch (key) { case 'apiPort': { if (typeof val === 'string') { logQuotes(key) } const intVal = Number.parseInt(val, 10) if (Number.isNaN(intVal)) { logDefault(key, platformConsts.defaultValues[key]) this.config.apiPort = platformConsts.defaultValues[key] } else if (intVal < platformConsts.minValues[key]) { logIncrease(key, platformConsts.minValues[key]) this.config.apiPort = platformConsts.minValues[key] } else { this.config.apiPort = intVal } break } case 'appId': case 'appSecret': case 'ignoredHomes': case 'username': if (typeof val !== 'string') { logIgnore(key) } else { this.config[key] = val.replace(/\s+/g, '') } break case 'bridgeSensors': if (Array.isArray(val) && val.length > 0) { val.forEach((x) => { if (!x.fullDeviceId) { logIgnoreItem(key) return } const id = parseDeviceId(x.fullDeviceId) if (Object.keys(this.rfSubdevices).includes(id)) { logDuplicate(`${key}.${id}`) return } const entries = Object.entries(x) if (entries.length === 1) { logRemove(`${key}.${id}`) return } this.rfSubdevices[id] = {} entries.forEach((subEntry) => { const [k, v] = subEntry switch (k) { case 'curtainType': case 'deviceType': case 'type': { const index = k === 'type' ? 'sensorType' : k const inSet = platformConsts.allowed[index].includes(v) if (typeof v !== 'string' || !inSet) { logIgnore(`${key}.${id}.${k}`) } else { this.rfSubdevices[id][k] = inSet ? v : platformConsts.defaultValues[k] } break } case 'fullDeviceId': case 'label': break case 'operationTime': case 'operationTimeDown': case 'sensorTimeDifference': case 'sensorTimeLength': { if (typeof v === 'string') { logQuotes(`${key}.${id}.${k}`) } const intVal = Number.parseInt(v, 10) if (Number.isNaN(intVal)) { logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k]) this.rfSubdevices[id][k] = platformConsts.defaultValues[k] } else if (intVal < platformConsts.minValues[k]) { logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k]) this.rfSubdevices[id][k] = platformConsts.minValues[k] } else { this.rfSubdevices[id][k] = intVal } break } case 'sensorWebHook': if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { this.rfSubdevices[id][k] = v } break default: logRemove(`${key}.${id}.${k}`) break } }) }) } else { logIgnore(key) } break case 'countryCode': if (typeof val !== 'string' || val === '') { logIgnore(key) } else { this.config.countryCode = `+${val.replace(/\D/g, '')}` } break case 'disableDeviceLogging': case 'disableNoResponse': if (typeof val === 'string') { logQuotes(key) } this.config[key] = val === 'false' ? false : !!val break case 'fanDevices': case 'lightDevices': case 'multiDevices': case 'rfDevices': case 'sensorDevices': case 'singleDevices': case 'thDevices': if (Array.isArray(val) && val.length > 0) { val.forEach((x) => { if (!x.deviceId) { logIgnoreItem(key) return } const id = parseDeviceId(x.deviceId) if (Object.keys(this.deviceConf).includes(id)) { logDuplicate(`${key}.${id}`) return } const entries = Object.entries(x) if (entries.length === 1) { logRemove(`${key}.${id}`) return } this.deviceConf[id] = {} entries.forEach((subEntry) => { const [k, v] = subEntry switch (k) { case 'adaptiveLightingShift': case 'brightnessStep': case 'inUsePowerThreshold': case 'lowBattThreshold': case 'minTarget': case 'maxTarget': case 'operationTime': case 'operationTimeDown': case 'sensorTimeDifference': { if (typeof v === 'string') { logQuotes(`${key}.${id}.${k}`) } const intVal = Number.parseInt(v, 10) if (Number.isNaN(intVal)) { logDefault(`${key}.${id}.${k}`, platformConsts.defaultValues[k]) this.deviceConf[id][k] = platformConsts.defaultValues[k] } else if (intVal < platformConsts.minValues[k]) { logIncrease(`${key}.${id}.${k}`, platformConsts.minValues[k]) this.deviceConf[id][k] = platformConsts.minValues[k] } else { this.deviceConf[id][k] = intVal } break } case 'deviceId': case 'label': break case 'deviceModel': if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else if (!platformConsts.allowed.models[key].includes(v)) { logIgnore(`${key}.${id}.${k}`) } else if (v === 'gddc5' && key === 'singleDevices') { this.deviceConf[id].showAs = 'garage_eachen' } break case 'disableTimer': case 'hideLight': case 'hideLongDouble': case 'hideSensor': case 'hideSwitch': case 'humidityOffsetFactor': case 'isInched': case 'offlineAsOff': case 'offsetFactor': case 'resetOnStartup': case 'scaleBattery': case 'showHeatCool': if (typeof v === 'string') { logQuotes(`${key}.${id}.${k}`) } this.deviceConf[id][k] = v === 'false' ? false : !!v break case 'hideChannels': { if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { const channels = v.split(',') channels.forEach((channel) => { this.hideChannels.push(`${id}SW${channel.replace(/\D+/g, '')}`) this.deviceConf[id][k] = v }) } break } case 'humidityOffset': case 'offset': case 'targetTempThreshold': { if (typeof v === 'string') { logQuotes(`${key}.${id}.${k}`) } const numVal = Number(v) if (Number.isNaN(numVal)) { logIgnore(`${key}.${id}.${k}`) } else { this.deviceConf[id][k] = numVal } break } case 'ignoreDevice': if (typeof v === 'string') { logQuotes(`${key}.${id}.${k}`) } if (!!v && v !== 'false') { this.ignoredDevices.push(id) } break case 'inchChannels': case 'temperatureSource': if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { this.deviceConf[id][k] = v } break case 'ipAddress': { if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { this.ipOverride[id] = v } break } case 'obstructId': if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { const parsed = parseDeviceId(v) this.deviceConf[id][k] = parsed this.obstructSwitches[parsed] = id } break case 'sensorId': if (typeof v !== 'string' || v === '') { logIgnore(`${key}.${id}.${k}`) } else { this.deviceConf[id].sensorId = parseDeviceId(v) } break case 'sensorType': case 'showAs': case 'showAsEachen': case 'showAsMotor': { const inSet = platformConsts.allowed[k].includes(v) if (typeof v !== 'string' || !inSet) { logIgnore(`${key}.${id}.${k}`) } else { this.deviceConf[id][k] = inSet ? v : platformConsts.defaultValues[k] } break } default: logRemove(`${key}.${id}.${k}`) } }) }) } else { logIgnore(key) } break case 'httpHost': { const inSet = platformConsts.allowed.httpHosts.includes(val) if (typeof val !== 'string' || !inSet) { logIgnore(key) } const defaultAutoHost = val === 'auto' ? platformConsts.defaultValues[key] : val this.config.httpHost = inSet ? defaultAutoHost : platformConsts.defaultValues[key] break } case 'language': case 'mode': { const inSet = platformConsts.allowed[key].includes(val) if (typeof val !== 'string' || !inSet) { logIgnore(key) } this.config[key] = inSet ? val : platformConsts.defaultValues[key] break } case 'name': case 'platform': break case 'password': if (typeof val !== 'string') { logIgnore(key) } else { this.config.password = val } break default: logRemove(key) break } }) } async pluginSetup() { // Plugin has finished initialising so now onto setup try { // Log that the plugin initialisation has been successful this.log('%s.', this.lang.initialised) // Sort out some logging functions if (this.isBeta) { this.log.debug = this.log this.log.debugWarn = this.log.warn } else { this.log.debug = () => {} this.log.debugWarn = () => {} } // Check the eWeLink credentials are configured (except lan mode) if (this.config.mode !== 'lan' && (!this.config.username || !this.config.password)) { devicesInHB.forEach(accessory => this.removeAccessory(accessory)) throw new Error(this.lang.missingCreds) } // Require any libraries that the accessory instances use this.cusChar = new platformChars(this.api) this.eveChar = new eveChars(this.api) this.eveService = eveService(this.api) // Persist files are used to store device info that could be used for LAN only mode try { this.storageLAN = storage.create({ dir: join(this.api.user.persistPath(), '/../homebridge-ewelink'), forgiveParseErrors: true, }) await this.storageLAN.init() this.storageClientLAN = true } catch (err) { this.log.debugWarn(`${this.lang.storageSetupErr} ${parseError(err)}`) } // Persist files are used to store device info that can be used by my other plugins try { this.storageData = storage.create({ dir: join(this.api.user.persistPath(), '/../bwp91_cache'), forgiveParseErrors: true, }) await this.storageData.init() this.storageClientData = true this.temperatureCache = new Map() } catch (err) { this.log.debugWarn(`${this.lang.storageSetupErr} ${parseError(err)}`) } // Manually disable no response mode if mode is set to lan if (this.config.mode === 'lan') { this.config.disableNoResponse = true } const deviceList = [] const groupList = [] // Username and password are optional if (this.config.username && this.config.password) { // Set up the HTTP client, get the user HTTP host, and login this.httpClient = new httpClient(this) const authData = await this.httpClient.login() this.config.password = authData.password // Get a home and device list via HTTP request await this.httpClient.getHomes() const { httpDeviceList, httpGroupList } = await this.httpClient.getDevices() httpDeviceList.forEach(device => deviceList.push(device)) httpGroupList.forEach(group => groupList.push(group)) // Set up the WS client, get the user WS host and login if (this.config.mode !== 'lan') { this.wsClient = new wsClient(this, authData) await this.wsClient.login() // Refresh the WS connection every 60 minutes this.wsRefresh = setInterval(async () => { try { this.log.debug(this.lang.wsRef) await this.wsClient.login() } catch (err) { this.log.warn('%s %s.', this.lang.wsRefFail, parseError(err)) } }, 3600000) } // Clear the storage folder and start again when we have access to http devices if (this.storageClientLAN) { try { await this.storageLAN.clear() } catch (err) { this.log.debugWarn(`${this.lang.storageClearErr} ${parseError(err)}`) } } } else { // Warn that HTTP and WS are disabled this.log.warn('%s %s.', this.lang.httpDisabled, this.lang.missingCreds) // Get the persisted device data if we are in lan only mode if (this.config.mode === 'lan' && this.storageClientLAN) { try { this.log('Obtaining device list from storage.') const persistDeviceList = await this.storageLAN.values() persistDeviceList.forEach(device => deviceList.push(device)) } catch (err) { this.log.debugWarn(`${this.lang.storageReadErr} ${parseError(err)}`) } } } // Set up the LAN client, scan for device and start monitoring if (this.config.mode !== 'wan') { this.lanClient = new lanClient(this) this.lanDevices = await this.lanClient.getHosts() await this.lanClient.startMonitor() } // Initialise each device into HB deviceList.forEach(device => this.initialiseDevice(device)) for (const group of groupList) { // Create the format of a device group.extra = { uiid: 5000 } group.deviceid = group.id group.productModel = `Group [${group.uiid}]` group.brandName = 'eWeLink' group.online = true await this.initialiseDevice(group) } // Check for redundant accessories (in HB but not eWeLink) devicesInHB.forEach((accessory) => { if ( !deviceList.some(el => el.deviceid === accessory.context.eweDeviceId) && !groupList.some(el => el.id === accessory.context.eweDeviceId) ) { this.removeAccessory(accessory) } }) // Set up the LAN listener for device notifications if (this.lanClient) { this.lanClient.receiveUpdate(device => this.receiveDeviceUpdate(device)) } // Set up the WS listener for device notifications if (this.wsClient) { this.wsClient.receiveUpdate(device => this.receiveDeviceUpdate(device)) } // Set up the listener server for the API if the user has this enabled if (this.config.apiPort !== 0 && this.config.password) { this.apiClient = new apiClient(this, devicesInHB) this.apiServer = createServer(async (req, res) => { // The 'homepage' shows a html document with info about the API if (req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }) res.end(this.apiClient.showHome()) return } // Request is not for the homepage so action appropriately res.writeHead(200, { 'Content-Type': 'application/json' }) try { const response = await this.apiClient.action(req) // Actioning the request was successful so respond with a success res.end(JSON.stringify({ success: true, response })) } catch (err) { // An error occurred actioning the request so respond with the error res.end(JSON.stringify({ success: false, error: `${err.message}.` })) } }) // Start listening on the above created server this.apiServer.listen(this.config.apiPort === 1 ? 0 : this.config.apiPort, (err) => { if (err) { this.log.warn('%s [%s].', this.lang.apiListenErr, err) } else { this.log('%s [%s].', this.lang.apiListening, this.apiServer.address().port) } }) } // Setup successful this.log('%s. %s', this.lang.complete, this.lang.welcome) } catch (err) { // Catch any errors during setup const eText = parseError(err, [ this.lang.missingCreds, 'password error! [10001]', 'user does not exists [10003]', ]) this.log.warn('***** %s. *****', this.lang.disabling) this.log.warn('***** %s. *****', eText) this.pluginShutdown() } } pluginShutdown() { // A function that is called when the plugin fails to load or Homebridge restarts try { // Destroy all device handlers devicesInHB.forEach((accessory) => { if (accessory?.control?.destroy) { accessory.control.destroy() } }) // Shutdown the listener server if it's running if (this.apiServer) { this.apiServer.close(() => { this.log.debug(this.lang.apiShutdown) }) } // Clear pending command queue queue.clear() // Stop the LAN monitoring if (this.lanClient) { this.lanClient.closeConnection() } // Close the WS connection if (this.wsClient) { clearInterval(this.wsRefresh) this.wsClient.closeConnection() } } catch (err) { // No need to show errors at this point } } applyAccessoryLogging(accessory) { if (this.isBeta) { accessory.log = msg => this.log('[%s] %s.', accessory.displayName, msg) accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg) accessory.logDebug = msg => this.log('[%s] %s.', accessory.displayName, msg) accessory.logDebugWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg) } else { if (this.config.disableDeviceLogging) { accessory.log = () => {} accessory.logWarn = () => {} } else { accessory.log = msg => this.log('[%s] %s.', accessory.displayName, msg) accessory.logWarn = msg => this.log.warn('[%s] %s.', accessory.displayName, msg) } accessory.logDebug = () => {} accessory.logDebugWarn = () => {} } } async initialiseDevice(device) { try { let accessory const uiid = device?.extra?.uiid || 0 const uuid = this.api.hap.uuid.generate(`${device.deviceid}SWX`) device.showAs = this.deviceConf?.[device.deviceid]?.showAs || 'default' // Clean up stale services if device category changed (e.g. showAs changed) const existingAccessory = devicesInHB.get(uuid) if (existingAccessory && existingAccessory.context.showAs && existingAccessory.context.showAs !== device.showAs) { const hapServ = this.api.hap.Service existingAccessory.services .filter(s => !(s instanceof hapServ.AccessoryInformation)) .forEach(s => existingAccessory.removeService(s)) } // Remove old sub accessories for Accessory Simulations and DUALR3 in motor/meter mode if ( device.showAs !== 'default' || (platformConsts.devices.switchMultiPower.includes(uiid) && [2, 3].includes(device.params.workMode)) ) { for (let i = 0; i <= 4; i += 1) { const uuidsub = this.api.hap.uuid.generate(`${device.deviceid}SW${i}`) if (devicesInHB.has(uuidsub)) { this.removeAccessory(devicesInHB.get(uuidsub)) } } } // Set up the correct instance for this particular device if (platformConsts.devices.switchMultiPower.includes(uiid) && device.params.workMode === 2) { /** *********************** BLINDS [DUALR3 MOTOR MODE] ************************ */ // Check the device has been calibrated if (device.params.calibState === 0) { if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } this.log.warn('[%s] %s.', device.name, this.lang.dualr3NoCalib) this.ignoredDevices.push(device.deviceid) return } accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceMotor(this, accessory) /** */ } else if (uiid === 211 && device.params.workMode === 2) { /** *********************** BLINDS [T5 MOTOR MODE] ************************ */ if (!device.params.calibState) { if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } this.log.warn('[%s] %s.', device.name, this.lang.dualr3NoCalib) this.ignoredDevices.push(device.deviceid) return } accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceMotor(this, accessory) /** */ } else if ( platformConsts.devices.switchMultiPower.includes(uiid) && device.params.workMode === 3 ) { /** *********************** BLINDS [DUALR3 METER MODE] ************************ */ if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } this.log.warn('[%s] %s.', device.name, this.lang.dualr3NoMeter) this.ignoredDevices.push(device.deviceid) return } else if (platformConsts.devices.panel.includes(uiid)) { /** **** NSPANEL ***** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.devicePanel(this, accessory) /** */ } else if (platformConsts.devices.curtain.includes(uiid)) { /** ************************** BLINDS [EWELINK UIID 11 & 67] *************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceCurtain(this, accessory) /** */ } else if (device.showAs === 'blind') { /** ************************** BLINDS [ACCESSORY SIMULATION] *************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceBlind(this, accessory) /** */ } else if (device.showAs === 'door') { /** ************************* DOORS [ACCESSORY SIMULATION] ************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceDoor(this, accessory) /** */ } else if (device.showAs === 'window') { /** *************************** WINDOWS [ACCESSORY SIMULATION] **************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceWindow(this, accessory) /** */ } else if (this.obstructSwitches[device.deviceid]) { /** *************************** OBSTRUCTION DETECTION SWITCHES **************************** */ accessory = this.addAccessory(device, `${device.deviceid}SWX`, true) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceGarageOdSwitch(this, accessory, devicesInHB) /** */ } else if ( Object.values(this.deviceConf).some(el => el.sensorId === device.deviceid) && (platformConsts.devices.garageSensors.includes(uiid) || device.showAs === 'sensor') ) { /** *************************************** SENSORS [AS GARAGE/LOCK SENSOR SIMULATION] **************************************** */ const sim = Object.entries(this.deviceConf).find( ([, el]) => el.sensorId === device.deviceid && ['garage', 'lock'].includes(el.showAs), ) const uuidSub = this.api.hap.uuid.generate(`${sim[0]}SWX`) if (devicesInHB.has(uuidSub)) { const subAccessory = devicesInHB.get(uuidSub) let instance if (sim[1].hideSensor) { // If the sensor exists in Homebridge then remove it as needs to be re-added as hidden if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } instance = deviceTypes.sim.deviceSensorHidden accessory = this.addAccessory(device, `${device.deviceid}SWX`, true) } else { instance = deviceTypes.sim.deviceSensorVisible accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) } this.applyAccessoryLogging(accessory) accessory.control = new instance(this, accessory, subAccessory) } else { accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.logWarn(this.lang.sensorNoDevice) accessory.control = new deviceTypes.deviceSensorContact(this, accessory) } /** */ } else if (device.showAs === 'garage') { /** ************************************** GARAGE DOORS [ONE] [ACCESSORY SIMULATION] *************************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceGarageOne(this, accessory) /** */ } else if (device.showAs === 'garage_two') { /** ************************************** GARAGE DOORS [TWO] [ACCESSORY SIMULATION] *************************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceGarageTwo(this, accessory) /** */ } else if (device.showAs === 'garage_four') { /** *************************************** GARAGE DOORS [FOUR] [ACCESSORY SIMULATION] **************************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceGarageFour(this, accessory) /** */ } else if (device.showAs === 'garage_eachen') { /** ************************* GARAGE DOORS [EACHEN GD-DC5] ************************** */ const instance = this.deviceConf[device.deviceid] && this.deviceConf[device.deviceid].showAsEachen === 'lock' ? deviceTypes.sim.deviceLockEachen : deviceTypes.sim.deviceGarageEachen accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new instance(this, accessory) /** */ } else if (device.showAs === 'gate') { /** ****************************************** GATES (AS GARAGE DOOR) [ACCESSORY SIMULATION] ******************************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceGateOne(this, accessory, devicesInHB) /** */ } else if (device.showAs === 'lock') { /** ************************* LOCKS [ACCESSORY SIMULATION] ************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceLockOne(this, accessory) /** */ } else if (device.showAs === 'switch_valve') { /** ******************************** SWITCH-VALVE [ACCESSORY SIMULATION] ********************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceSwitchValve(this, accessory) /** */ } else if (device.showAs === 'tap') { /** ****************************** TAPS [ONE] [ACCESSORY SIMULATION] ******************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceTapOne(this, accessory) /** */ } else if (device.showAs === 'tap_two') { /** ****************************** TAPS [TWO] [ACCESSORY SIMULATION] ******************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceTapTwo(this, accessory) /** */ } else if (device.showAs === 'valve') { /** ******************************** VALVES [ONE] [ACCESSORY SIMULATION] ********************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceValveOne(this, accessory) /** */ } else if (device.showAs === 'valve_two') { /** ******************************** VALVES [TWO] [ACCESSORY SIMULATION] ********************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceValveTwo(this, accessory) /** */ } else if (device.showAs === 'valve_four') { /** ********************************* VALVES [FOUR] [ACCESSORY SIMULATION] ********************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceValveFour(this, accessory) /** */ } else if (device.showAs === 'sensor') { /** ***************** SENSORS [SIMULATION] ****************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceSensor(this, accessory) /** */ } else if (device.showAs === 'p_button') { /** ***************************** PROGRAMMABLE BUTTON [SIMULATION] ****************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.devicePButton(this, accessory) /** */ } else if (device.showAs === 'doorbell') { /** ****************** DOORBELL [SIMULATION] ******************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceDoorbell(this, accessory) /** */ } else if (device.showAs === 'purifier') { /** ******************* PURIFIERS [SIMULATION] ******************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.devicePurifier(this, accessory) /** */ } else if (device.showAs === 'audio') { /** ************************* AUDIO RECEIVERS [SIMULATION] ************************** */ if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 34) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceTv(this, accessory) /** */ } else if (device.showAs === 'box') { /** ********************* SET-TOP BOX [SIMULATION] ********************** */ if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 35) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceTv(this, accessory) /** */ } else if (device.showAs === 'stick') { /** ************************* STREAMING STICK [SIMULATION] ************************** */ if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } accessory = this.addExternalAccessory(device, `${device.deviceid}SWX`, 36) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceTv(this, accessory) /** */ } else if ( device.showAs === 'sensor_leak' && platformConsts.devices.sensorContact.includes(uiid) ) { /** ************************************ SENSORS [LEAK DW2 ACCESSORY SIMULATION] ************************************* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceSensorLeak(this, accessory) /** */ } else if (platformConsts.devices.sensorContact.includes(uiid)) { /** ***************** SENSORS [SONOFF DW2] ****************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceSensorContact(this, accessory) } else if (platformConsts.devices.fan.includes(uiid)) { /** FANS * */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceFan(this, accessory) /**/ } else if (platformConsts.devices.diffuser.includes(uiid)) { /** ****** DIFFUSERS ******* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceDiffuser(this, accessory) /** */ } else if (platformConsts.devices.humidifier.includes(uiid)) { /** ******** HUMIDIFIERS ********* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceHumidifier(this, accessory) /** */ } else if (platformConsts.devices.thermostat.includes(uiid)) { /** ******** THERMOSTATS ********* */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceThermostat(this, accessory) /** */ } else if (platformConsts.devices.airConditioner.includes(uiid)) { /** *************** AIR CONDITIONERS **************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceAirConditioner(this, accessory) /** */ } else if (platformConsts.devices.virtual.includes(uiid)) { /** *************** VIRTUAL DEVICES **************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceVirtual(this, accessory) /** */ } else if ( device.showAs === 'thermostat' && platformConsts.devices.sensorAmbient.includes(uiid) ) { /** *************************************** THERMOSTATS [TH10/16 ACCESSORY SIMULATION] **************************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceThThermostat(this, accessory) /** */ } else if (device.showAs === 'heater' && platformConsts.devices.sensorAmbient.includes(uiid)) { /** *********************************** HEATERS [TH10/16 ACCESSORY SIMULATION] ************************************ */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceThHeater(this, accessory) /** */ } else if (device.showAs === 'cooler' && platformConsts.devices.sensorAmbient.includes(uiid)) { /** *********************************** COOLERS [TH10/16 ACCESSORY SIMULATION] ************************************ */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceThCooler(this, accessory) /** */ } else if ( device.showAs === 'humidifier' && platformConsts.devices.sensorAmbient.includes(uiid) ) { /** *************************************** HUMIDIFIERS [TH10/16 ACCESSORY SIMULATION] **************************************** */ if (device.params.sensorType === 'DS18B20') { if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } this.log.warn('[%s] %s.', device.name, this.lang.sensorErr) return } accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceThHumidifier(this, accessory) /** */ } else if ( device.showAs === 'dehumidifier' && platformConsts.devices.sensorAmbient.includes(uiid) ) { /** ***************************************** DEHUMIDIFIERS [TH10/16 ACCESSORY SIMULATION] ****************************************** */ if (device.params.sensorType === 'DS18B20') { this.log.warn('[%s] %s.', device.name, this.lang.sensorErr) return } accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.sim.deviceThDehumidifier(this, accessory) /** */ } else if (platformConsts.devices.sensorAmbient.includes(uiid)) { /** ********************* SENSOR [AMBIENT-TH10/16] ********************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) accessory.context.sensorType = device.params.sensorType this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceSensorAmbient(this, accessory) /** */ } else if (platformConsts.devices.sensorTempHumi.includes(uiid)) { /** *********************** SENSOR [AMBIENT-SONOFF SC] ************************ */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceSensorTempHumi(this, accessory) /** */ } else if (platformConsts.devices.sensorAirQuality.includes(uiid)) { /** ************************* SENSOR [AIR QUALITY-SAWF-08P] ************************** */ accessory = devicesInHB.get(uuid) || this.addAccessory(device, `${device.deviceid}SWX`) this.applyAccessoryLogging(accessory) accessory.control = new deviceTypes.deviceSensorAirQuality(this, accessory) /** */ } else if (device.showAs === 'heater') { /** ***************** HEATERS [SIMULATION] ****************** */ if (!this.deviceConf[device.deviceid].temperatureSource) { this.log.warn('[%s] %s.', device.name, this.lang.heaterSimNoSensor) if (devicesInHB.has(uuid)) { this.removeAccessory(devicesInHB.get(uuid)) } return } accessory = devicesInHB.get(uuid) || this.addAccessory(device, `$