@homebridge-plugins/homebridge-ewelink
Version:
Homebridge plugin to integrate eWeLink devices into HomeKit.
1,278 lines (1,229 loc) • 86.7 kB
JavaScript
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, `$