UNPKG

homebridge-wemo

Version:

Homebridge plugin to integrate Wemo devices into HomeKit.

354 lines (296 loc) 11.9 kB
import platformConsts from '../utils/constants.js' import { hasProperty, parseError } from '../utils/functions.js' import platformLang from '../utils/lang-en.js' export default class { constructor(platform, accessory) { // Set up variables from the platform this.eveChar = platform.eveChar this.hapChar = platform.api.hap.Characteristic this.hapErr = platform.api.hap.HapStatusError this.hapServ = platform.api.hap.Service this.platform = platform // Set up variables from the accessory this.accessory = accessory // Set up custom variables for this device type const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {} this.showTodayTC = deviceConf.showTodayTC this.outletInUseTrue = deviceConf.outletInUseTrue this.wattDiff = deviceConf.wattDiff || platformConsts.defaultValues.wattDiff this.timeDiff = deviceConf.timeDiff || platformConsts.defaultValues.timeDiff if (this.timeDiff === 1) { this.timeDiff = false } this.skipTimeDiff = false if (!hasProperty(this.accessory.context, 'cacheLastWM')) { this.accessory.context.cacheLastWM = 0 } if (!hasProperty(this.accessory.context, 'cacheLastTC')) { this.accessory.context.cacheLastTC = 0 } if (!hasProperty(this.accessory.context, 'cacheTotalTC')) { this.accessory.context.cacheTotalTC = 0 } // If the accessory has an air purifier service then remove it if (this.accessory.getService(this.hapServ.AirPurifier)) { this.accessory.removeService(this.accessory.getService(this.hapServ.AirPurifier)) } // If the accessory has a switch service then remove it if (this.accessory.getService(this.hapServ.Switch)) { this.accessory.removeService(this.accessory.getService(this.hapServ.Switch)) } // Add the outlet service if it doesn't already exist this.service = this.accessory.getService(this.hapServ.Outlet) if (!this.service) { this.service = this.accessory.addService(this.hapServ.Outlet) this.service.addCharacteristic(this.eveChar.CurrentConsumption) this.service.addCharacteristic(this.eveChar.TotalConsumption) this.service.addCharacteristic(this.eveChar.ResetTotal) } // Add the set handler to the outlet on/off characteristic this.service .getCharacteristic(this.hapChar.On) .removeOnSet() .onSet(async value => this.internalStateUpdate(value)) // Add the set handler to the switch reset (eve) characteristic this.service.getCharacteristic(this.eveChar.ResetTotal).onSet(() => { this.accessory.context.cacheLastWM = 0 this.accessory.context.cacheLastTC = 0 this.accessory.context.cacheTotalTC = 0 this.service.updateCharacteristic(this.eveChar.TotalConsumption, 0) }) if (this.outletInUseTrue) { // Keep this as true, and throughout this file, to keep the outlet in use as true this.service.updateCharacteristic(this.hapChar.OutletInUse, true) } // Pass the accessory to fakegato to set up the Eve info service this.accessory.historyService = new platform.eveService('energy', this.accessory, { log: () => {}, }) // Output the customised options to the log const opts = JSON.stringify({ showAs: 'outlet', showTodayTC: this.showTodayTC, timeDiff: this.timeDiff, wattDiff: this.wattDiff, }) platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts) // Request a device update immediately this.requestDeviceUpdate() // Start a polling interval if the user has disabled upnp if (this.accessory.context.connection === 'http') { this.pollingInterval = setInterval( () => this.requestDeviceUpdate(), platform.config.pollingInterval * 1000, ) } } receiveDeviceUpdate(attribute) { // Log the receiving update if debug is enabled this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`) // Let's see which attribute has been provided switch (attribute.name) { case 'BinaryState': { // BinaryState is reported as 0=off, 1=on, 8=standby // Send a HomeKit needed true/false argument (0=false, 1,8=true) this.externalStateUpdate(attribute.value !== 0) break } case 'InsightParams': // Send the insight data straight to the function this.externalInsightUpdate( attribute.value.state, attribute.value.power, attribute.value.todayWm, attribute.value.todayOnSeconds, ) break default: } } async sendDeviceUpdate(value) { // Log the sending update if debug is enabled this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`) // Send the update await this.platform.httpClient.sendDeviceUpdate( this.accessory, 'urn:Belkin:service:basicevent:1', 'SetBinaryState', value, ) } async requestDeviceUpdate() { try { // Request the update const data = await this.platform.httpClient.sendDeviceUpdate( this.accessory, 'urn:Belkin:service:basicevent:1', 'GetBinaryState', ) // Check for existence since BinaryState can be int 0 if (hasProperty(data, 'BinaryState')) { this.receiveDeviceUpdate({ name: 'BinaryState', value: Number.parseInt(data.BinaryState, 10), }) } } catch (err) { const eText = parseError(err, [ platformLang.timeout, platformLang.timeoutUnreach, platformLang.noService, ]) this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`) } } async internalStateUpdate(value) { try { // Send the update await this.sendDeviceUpdate({ BinaryState: value ? 1 : 0, }) // Update the cache value this.cacheState = value // Log the change if appropriate this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`) // If turning the switch off then update the outlet-in-use and current consumption if (!value) { // Update the HomeKit characteristics if (!this.outletInUseTrue) { this.service.updateCharacteristic(this.hapChar.OutletInUse, false) } this.service.updateCharacteristic(this.eveChar.CurrentConsumption, 0) // Add an Eve entry for no power this.accessory.historyService.addEntry({ power: 0 }) // Log the change if appropriate this.accessory.log(`${platformLang.curOIU} [no]`) this.accessory.log(`${platformLang.curCons} [0W]`) } } catch (err) { const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach]) this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`) // Throw a 'no response' error and set a timeout to revert this after 2 seconds setTimeout(() => { this.service.updateCharacteristic(this.hapChar.On, this.cacheState) }, 2000) throw new this.hapErr(-70402) } } externalInsightUpdate(value, power, todayWm, todayOnSeconds) { // Update whether the switch is ON (value=1) or OFF (value=0) this.externalStateUpdate(value !== 0) // Update whether the outlet-in-use is YES (value=1) or NO (value=0,8) this.externalInUseUpdate(value === 1) // Update the total consumption this.externalTotalConsumptionUpdate(todayWm, todayOnSeconds) // Update the current consumption this.externalConsumptionUpdate(power) } externalStateUpdate(value) { try { // Check to see if the cache value is different if (value === this.cacheState) { return } // Update the HomeKit characteristics this.service.updateCharacteristic(this.hapChar.On, value) // Update the cache value this.cacheState = value // Log the change if appropriate this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`) // If the device has turned off then update the outlet-in-use and consumption if (!value) { this.externalInUseUpdate(false) this.externalConsumptionUpdate(0) } } catch (err) { // Catch any errors this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`) } } externalInUseUpdate(value) { try { // Check to see if the cache value is different if (value === this.cacheInUse) { return } // Update the HomeKit characteristic if (!this.outletInUseTrue) { this.service.updateCharacteristic(this.hapChar.OutletInUse, value) } // Update the cache value this.cacheInUse = value // Log the change if appropriate this.accessory.log(`${platformLang.curOIU} [${value ? 'yes' : 'no'}]`) } catch (err) { // Catch any errors this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`) } } externalConsumptionUpdate(power) { try { // Divide by 1000 to get the power value in W const powerInWatts = Math.round(power / 1000) // Check to see if the cache value is different if (powerInWatts === this.cachePowerInWatts) { return } // Update the power in watts cache this.cachePowerInWatts = powerInWatts // Update the HomeKit characteristic this.service.updateCharacteristic(this.eveChar.CurrentConsumption, this.cachePowerInWatts) // Add the Eve wattage entry this.accessory.historyService.addEntry({ power: this.cachePowerInWatts }) // Calculate a difference from the last reading const diff = Math.abs(powerInWatts - this.cachePowerInWatts) // Don't continue with logging if the user has set a timeout between entries or a min difference between entries if (!this.skipTimeDiff && diff >= this.wattDiff) { // Log the change if appropriate this.accessory.log(`${platformLang.curCons} [${this.cachePowerInWatts}W]`) // Set the time difference timeout if needed if (this.timeDiff) { this.skipTimeDiff = true setTimeout(() => { this.skipTimeDiff = false }, this.timeDiff * 1000) } } } catch (err) { // Catch any errors this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`) } } externalTotalConsumptionUpdate(todayWm, todayOnSeconds) { try { if (todayWm === this.cacheLastWM) { return } // Update the cache last value this.cacheLastWM = todayWm this.accessory.context.cacheLastWM = todayWm // Convert to Wh (hours) from raw data of Wm (minutes) const todayWh = Math.round(todayWm / 60000) // Convert to kWh const todaykWh = todayWh / 1000 // Convert to hours, minutes and seconds (HH:MM:SS) const todayOnHours = new Date(todayOnSeconds * 1000).toISOString().substr(11, 8) // Calculate the difference (ie extra usage from the last reading) const difference = Math.max(todaykWh - this.accessory.context.cacheLastTC, 0) // Update the caches this.accessory.context.cacheTotalTC += difference this.accessory.context.cacheLastTC = todaykWh // Update the total consumption characteristic this.service.updateCharacteristic( this.eveChar.TotalConsumption, this.showTodayTC ? todaykWh : this.accessory.context.cacheTotalTC, ) if (!this.skipTimeDiff) { this.accessory.log( `${platformLang.insOnTime} [${todayOnHours}] ${platformLang.insCons} [${todaykWh.toFixed(3)} kWh] ${platformLang.insTC} [${this.accessory.context.cacheTotalTC.toFixed(3)} kWh]`, ) } } catch (err) { // Catch any errors this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`) } } }