@homebridge-plugins/homebridge-ewelink
Version:
Homebridge plugin to integrate eWeLink devices into HomeKit.
325 lines (311 loc) • 12.6 kB
JavaScript
import axios from 'axios'
import platformConsts from '../utils/constants.js'
import { hasProperty, parseError, sleep } from '../utils/functions.js'
const rfTrigRegex = /rfTrig/
export default class {
constructor(platform, accessory, devicesInHB) {
// Set up variables from the platform
this.devicesInHB = devicesInHB
this.disableDeviceLogging = platform.config.disableDeviceLogging
this.eveChar = platform.eveChar
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] || {}
const { resetOnStartup } = deviceConf
// 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
}
// Output the customised options to the log
const normalLogging = this.enableLogging ? 'standard' : 'disable'
const opts = JSON.stringify({
logging: this.enableDebugLogging ? 'debug' : normalLogging,
resetOnStartup,
})
this.log('[%s] %s %s.', this.name, this.lang.devInitOpts, opts)
}
async externalUpdate(params) {
try {
if (!hasProperty(params, 'updateSource')) {
return
}
const timeNow = new Date()
let subAccessory = false
let channel
if (
hasProperty(params, 'cmd')
&& params.cmd === 'transmit'
&& hasProperty(params, 'rfChl')
) {
// RF Button
this.devicesInHB.forEach((acc) => {
if (
acc.context.eweDeviceId === this.accessory.context.eweDeviceId
&& hasProperty(acc.context, 'buttons')
&& hasProperty(acc.context.buttons, params.rfChl.toString())
) {
subAccessory = acc
}
})
if (subAccessory) {
const deviceConf = this.platform.rfSubdevices[subAccessory.context.hbDeviceId]
let enableLogging
switch (deviceConf.overrideLogging) {
case 'standard':
case 'debug':
enableLogging = true
break
case 'disable':
enableLogging = false
break
default:
enableLogging = !this.disableDeviceLogging
break
}
const service = subAccessory.context.buttons[params.rfChl]
subAccessory.getService(service).updateCharacteristic(this.hapChar.On, true)
if (params.updateSource && enableLogging) {
this.log(
'[%s] %s [%s].',
subAccessory.displayName,
this.lang.curState,
this.lang.buttonTrig,
)
}
await sleep(3000)
subAccessory.getService(service).updateCharacteristic(this.hapChar.On, false)
} else {
throw new Error(this.lang.buttonNotFound)
}
} else if (
(hasProperty(params, 'cmd') && params.cmd === 'trigger')
|| params.updateSource === 'LAN'
) {
// RF Sensor
Object.keys(params)
.filter(name => rfTrigRegex.test(name))
.forEach(async (chan) => {
try {
this.devicesInHB.forEach((acc) => {
if (
acc.context.eweDeviceId === this.accessory.context.eweDeviceId
&& hasProperty(acc.context, 'buttons')
&& hasProperty(acc.context.buttons, chan.split('g')[1].toString())
) {
subAccessory = acc
channel = chan.split('g')[1].toString()
}
})
if (subAccessory) {
// Avoid duplicate triggers from LAN and WS
if (params[chan] === subAccessory.context.cacheLastAct) {
return
}
const deviceConf = this.platform.rfSubdevices[subAccessory.context.hbDeviceId] || {}
let enableLogging
switch (deviceConf.overrideLogging) {
case 'standard':
case 'debug':
enableLogging = true
break
case 'disable':
enableLogging = false
break
default:
enableLogging = !this.disableDeviceLogging
break
}
switch (subAccessory.context.subType) {
case 'blind':
case 'door':
case 'window':
// Simulations don't support external updates
return
case 'button':
case 'curtain': {
const service = subAccessory.context.buttons[channel]
subAccessory.getService(service).updateCharacteristic(this.hapChar.On, true)
this.log(
'[%s] %s [%s].',
subAccessory.displayName,
this.lang.curState,
this.lang.buttonTrig,
)
await sleep(3000)
subAccessory.getService(service).updateCharacteristic(this.hapChar.On, false)
return
}
default:
break
}
const sensorTimeLength = deviceConf.sensorTimeLength || platformConsts.defaultValues.sensorTimeLength
const sensorTimeDifference = deviceConf.sensorTimeDifference || platformConsts.defaultValues.sensorTimeDifference
subAccessory.context.cacheLastAct = params[chan]
const timeOfMotion = new Date(params[chan])
const diff = (timeNow.getTime() - timeOfMotion.getTime()) / 1000
let serv
let char
if (diff < sensorTimeDifference) {
switch (deviceConf.type) {
case 'water':
serv = this.hapServ.LeakSensor
char = this.hapChar.LeakDetected
break
case 'fire':
case 'smoke':
serv = this.hapServ.SmokeSensor
char = this.hapChar.SmokeDetected
break
case 'co':
serv = this.hapServ.CarbonMonoxideSensor
char = this.hapChar.CarbonMonoxideDetected
break
case 'co2':
serv = this.hapServ.CarbonDioxideSensor
char = this.hapChar.CarbonDioxideDetected
break
case 'contact': {
serv = this.hapServ.ContactSensor
char = this.hapChar.ContactSensorState
const initialTime = subAccessory.eveService.getInitialTime()
subAccessory
.getService(serv)
.updateCharacteristic(
this.eveChar.LastActivation,
Math.round(new Date().valueOf() / 1000) - initialTime,
)
break
}
case 'occupancy':
serv = this.hapServ.OccupancySensor
char = this.hapChar.OccupancyDetected
break
case 'p_button':
serv = this.hapServ.StatelessProgrammableSwitch
char = this.hapChar.ProgrammableSwitchEvent
break
case 'doorbell':
serv = this.hapServ.Doorbell
char = this.hapChar.ProgrammableSwitchEvent
break
default: {
serv = this.hapServ.MotionSensor
char = this.hapChar.MotionDetected
const initialTime = subAccessory.eveService.getInitialTime()
subAccessory
.getService(serv)
.updateCharacteristic(
this.eveChar.LastActivation,
Math.round(new Date().valueOf() / 1000) - initialTime,
)
break
}
}
// Call any webhook defined in the config
if (deviceConf.sensorWebHook) {
try {
await axios.get(deviceConf.sensorWebHook, {
timeout: 5000,
})
if (enableLogging) {
this.log(
'[%s] %s [GET %s].',
subAccessory.displayName,
'http webhook request successful',
deviceConf.sensorWebHook,
)
}
} catch (e) {
if (enableLogging) {
this.log(
'[%s] %s %s.',
subAccessory.displayName,
'http webhook request failed as',
e.message,
)
}
}
}
// Programmable button and doorbell activate differently
if (char === this.hapChar.ProgrammableSwitchEvent) {
subAccessory.getService(serv).updateCharacteristic(char, 0)
if (params.updateSource && enableLogging) {
this.log(
'[%s] %s [%s].',
subAccessory.displayName,
this.lang.curState,
this.lang.buttonSingle,
)
}
return
}
subAccessory.getService(serv).updateCharacteristic(char, 1)
subAccessory.eveService.addEntry({ status: 1 })
if (params.updateSource && enableLogging) {
this.log(
'[%s] %s [%s].',
subAccessory.displayName,
this.lang.curState,
this.lang.rfTrigYes,
)
}
const updateKey = Math.random()
.toString(36)
.substring(2, 10)
subAccessory.context.updateKey = updateKey
await sleep(sensorTimeLength * 1000)
if (updateKey !== subAccessory.context.updateKey) {
return
}
subAccessory.getService(serv).updateCharacteristic(char, 0)
subAccessory.eveService.addEntry({ status: 0 })
if (params.updateSource && enableLogging) {
this.log(
'[%s] %s [%s].',
subAccessory.displayName,
this.lang.curState,
this.lang.rfTrigNo,
)
}
}
} else {
throw new Error(`${this.lang.rfNotFound}[${chan}]`)
}
} catch (err) {
this.log.warn('[%s] %s %s.', this.name, this.lang.devNotRf, parseError(err))
}
})
}
} catch (err) {
this.platform.deviceUpdateError(this.accessory, err, false)
}
}
markStatus(isOnline) {
this.isOnline = isOnline
}
}