@homebridge-plugins/homebridge-ewelink
Version:
Homebridge plugin to integrate eWeLink devices into HomeKit.
350 lines (328 loc) • 11.3 kB
JavaScript
import { generateRandomString, sleep } from '../utils/functions.js'
export default class {
constructor(platform, accessory) {
// Set up variables from the platform
this.hapChar = platform.api.hap.Characteristic
this.hapErr = platform.api.hap.HapStatusError
this.hapServ = platform.api.hap.Service
this.lang = platform.lang
this.log = platform.log
this.platform = platform
// Set up variables from the accessory
this.name = accessory.displayName
this.accessory = accessory
// Initially set the online flag as true (to be then updated as false if necessary)
this.isOnline = true
// Set up custom variables for this device type
const deviceConf = platform.deviceConf[accessory.context.eweDeviceId] || {}
this.hideLight = deviceConf.hideLight
// Set the correct logging variables for this accessory
switch (deviceConf.overrideLogging) {
case 'standard':
this.enableLogging = true
this.enableDebugLogging = false
break
case 'debug':
this.enableLogging = true
this.enableDebugLogging = true
break
case 'disable':
this.enableLogging = false
this.enableDebugLogging = false
break
default:
this.enableLogging = !platform.config.disableDeviceLogging
this.enableDebugLogging = platform.config.debug
break
}
// Add the fan service if it doesn't already exist
this.service = this.accessory.getService(this.hapServ.Fan) || this.accessory.addService(this.hapServ.Fan)
// Add the set handler to the fan on/off characteristic
this.service
.getCharacteristic(this.hapChar.On)
.onSet(async value => this.internalStateUpdate(value))
// Add the set handler to the fan rotation speed characteristic
this.service
.getCharacteristic(this.hapChar.RotationSpeed)
.setProps({
maxValue: 3,
minStep: 1,
minValue: 0,
unit: 'unitless', // This is actually from HAP for Bluetooth LE Specification, but fits
})
.onSet(async value => this.internalSpeedUpdate(value))
// Check to see if the user has hidden the light channel
if (this.hideLight) {
// The user has hidden the light channel, so remove it if it exists
if (this.accessory.getService(this.hapServ.Lightbulb)) {
this.accessory.removeService(this.accessory.getService(this.hapServ.Lightbulb))
}
} else {
// The user has not hidden the light channel, so add it if it doesn't exist
this.lightService = this.accessory.getService(this.hapServ.Lightbulb)
|| this.accessory.addService(this.hapServ.Lightbulb)
// Add the set handler to the lightbulb on/off characteristic
this.lightService
.getCharacteristic(this.hapChar.On)
.onSet(async value => this.internalLightUpdate(value))
}
// Set the fan service to the primary service
this.service.setPrimaryService()
// Conversion object eWeLink mode to text label
this.speed2label = {
1: 'low',
2: 'medium',
3: 'high',
}
// Add the get handlers only if the user hasn't disabled the disableNoResponse setting
if (!platform.config.disableNoResponse) {
this.service.getCharacteristic(this.hapChar.On).onGet(() => {
if (!this.isOnline) {
throw new this.hapErr(-70402)
}
return this.service.getCharacteristic(this.hapChar.On).value
})
this.service.getCharacteristic(this.hapChar.RotationSpeed).onGet(() => {
if (!this.isOnline) {
throw new this.hapErr(-70402)
}
return this.service.getCharacteristic(this.hapChar.RotationSpeed).value
})
if (this.lightService) {
this.lightService.getCharacteristic(this.hapChar.On).onGet(() => {
if (!this.isOnline) {
throw new this.hapErr(-70402)
}
return this.lightService.getCharacteristic(this.hapChar.On).value
})
}
}
// Output the customised options to the log
const normalLogging = this.enableLogging ? 'standard' : 'disable'
const opts = JSON.stringify({
hideLight: this.hideLight,
logging: this.enableDebugLogging ? 'debug' : normalLogging,
})
this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
}
async internalStateUpdate(value) {
try {
const newState = value ? 'on' : 'off'
if (newState === this.cacheState) {
return
}
const params = {
switches: [],
}
params.switches.push({
switch: newState,
outlet: 1,
})
if (newState === 'on') {
params.switches.push({
switch: this.cacheSpeed === 2 ? 'on' : 'off',
outlet: 2,
})
params.switches.push({
switch: this.cacheSpeed === 3 ? 'on' : 'off',
outlet: 3,
})
}
await this.platform.sendDeviceUpdate(this.accessory, params)
this.cacheState = newState
if (this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curState, newState)
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, true)
setTimeout(() => {
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
}, 2000)
throw new this.hapErr(-70402)
}
}
async internalSpeedUpdate(value) {
try {
// This acts like a debounce function when endlessly sliding the slider
const updateKey = generateRandomString(5)
this.updateKey = updateKey
await sleep(500)
if (updateKey !== this.updateKey) {
return
}
const params = {
switches: [],
}
const newState = value >= 1 ? 'on' : 'off'
let newSpeed
if (value < 0 || value > 3) {
newSpeed = 0
} else {
newSpeed = value
}
if (newSpeed === this.cacheSpeed) {
return
}
params.switches.push({
switch: newState === 'on' && newSpeed >= 1 ? 'on' : 'off',
outlet: 1,
})
params.switches.push({
switch: newState === 'on' && newSpeed === 2 ? 'on' : 'off',
outlet: 2,
})
params.switches.push({
switch: newState === 'on' && newSpeed === 3 ? 'on' : 'off',
outlet: 3,
})
await this.platform.sendDeviceUpdate(this.accessory, params)
if (newState !== this.cacheState) {
this.cacheState = newState
if (this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curState, this.cacheState)
}
}
if (newSpeed === 0) {
// Update the rotation speed back to the previous value (with the fan still off)
setTimeout(() => {
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
}, 2000)
return
}
this.cacheSpeed = newSpeed
if (this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curSpeed, this.speed2label[this.cacheSpeed])
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, true)
setTimeout(() => {
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
}, 2000)
throw new this.hapErr(-70402)
}
}
async internalLightUpdate(value) {
try {
const params = {
switches: [],
}
const newLight = value ? 'on' : 'off'
if (newLight === this.cacheLight) {
return
}
params.switches.push({
switch: newLight,
outlet: 0,
})
await this.platform.sendDeviceUpdate(this.accessory, params)
this.cacheLight = newLight
if (this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curLight, this.cacheLight)
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, true)
setTimeout(() => {
this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLight === 'on')
}, 2000)
throw new this.hapErr(-70402)
}
}
async externalUpdate(params) {
try {
if (params.light && params.fan && params.speed) {
// LAN update from iFan03
params.switches = {}
params.switches[0] = { switch: params.light }
params.switches[1] = { switch: params.fan }
switch (params.speed) {
case 1:
params.switches[2] = { switch: 'off' }
params.switches[3] = { switch: 'off' }
break
case 2:
params.switches[2] = { switch: 'on' }
params.switches[3] = { switch: 'off' }
break
case 3:
params.switches[2] = { switch: 'off' }
params.switches[3] = { switch: 'on' }
break
default:
return
}
}
if (!params.switches || !params.switches[1] || !params.switches[2] || !params.switches[3]) {
return
}
if (params.switches[1].switch !== this.cacheState) {
this.cacheState = params.switches[1].switch
this.service.updateCharacteristic(this.hapChar.On, this.cacheState === 'on')
if (params.updateSource && this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curState, this.cacheState)
}
}
let speed
switch (params.switches[2].switch + params.switches[3].switch) {
case 'onoff':
speed = 2
break
case 'offon':
speed = 3
break
default:
speed = 1
break
}
if (speed !== this.cacheSpeed && this.cacheState === 'on') {
this.cacheSpeed = speed
this.service.updateCharacteristic(this.hapChar.RotationSpeed, this.cacheSpeed)
if (params.updateSource && this.enableLogging) {
this.log(
'[%s] %s [%s].',
this.name,
this.lang.curSpeed,
this.speed2label[this.cacheSpeed],
)
}
}
if (this.lightService && params.switches[0].switch !== this.cacheLight) {
this.cacheLight = params.switches[0].switch
this.lightService.updateCharacteristic(this.hapChar.On, this.cacheLight === 'on')
if (params.updateSource && this.enableLogging) {
this.log('[%s] %s [%s].', this.name, this.lang.curLight, this.cacheLight)
}
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, false)
}
}
currentState() {
const toReturn = {}
let speedLabel
const speed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
if (speed === 0) {
speedLabel = 'off'
} else if (speed <= 1) {
speedLabel = 'low'
} else if (speed <= 2) {
speedLabel = 'medium'
} else {
speedLabel = 'high'
}
toReturn.services = ['fan']
toReturn.fan = {
state: this.service.getCharacteristic(this.hapChar.On).value ? 'on' : 'off',
speed: speedLabel,
}
if (!this.hideLight) {
toReturn.services.push('light')
toReturn.light = {
state: this.lightService.getCharacteristic(this.hapChar.On).value ? 'on' : 'off',
}
}
return toReturn
}
markStatus(isOnline) {
this.isOnline = isOnline
}
}