UNPKG

homebridge-tplink-hs100-lightbulbs

Version:
377 lines (308 loc) 12.4 kB
'use strict'; const Hs100Api = require('hs100-api'); const Bulb = require('tplink-lightbulb'); var PlatformAccessory, Service, Characteristic, UUIDGen; module.exports = function (homebridge) { PlatformAccessory = homebridge.platformAccessory; Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; UUIDGen = homebridge.hap.uuid; homebridge.registerPlatform('homebridge-tplink-hs100-lightbulbs', 'TPLinkWT', TPLinkWT, true); }; class TPLinkWT { constructor (log, config, api) { this.log = log; this.config = config || {}; this.accessories = new Map(); this.client = new Hs100Api.Client(); if(api){ this.api = api; this.plugs = config['plugs'] || []; this.api.on('didFinishLaunching', this.scan.bind(this)); } } scan () { //Find Light Bulbs First Bulb.scan('IOT.SMARTBULB').on("light", (light) => { //Check If It's Cached var accessory = this.accessories.get(light.deviceId); if(accessory === undefined){ this.addNewBulb(light); } else { if(accessory instanceof TplinkLightbulbAccessory){ accessory = new TplinkLightbulbAccessory(this.log, accessory); this.log("Light Bulb: %s [%s] - Online", accessory.platformAccessory.displayName, light.deviceId) this.accessories.set(light.deviceId, accessory) accessory.configure(light); } } }); this.client.on('plug-offline', (plug) => { var accessory = this.accessories.get(plug.deviceId); if (accessory !== undefined && accessory instanceof Hs100Accessory) { this.log('Plug: %s [%s] - Offline', accessory.accessory.displayName, accessory.deviceId); accessory.accessory.updateReachability(false); } }); this.client.on('plug-online', (plug) => { var accessory = this.accessories.get(plug.deviceId); if (accessory === undefined) { this.addNewPlug(plug); } else if(accessory instanceof Hs100Accessory){ this.log('Plug: %s [%s] - Online', accessory.accessory.displayName, plug.deviceId); accessory.accessory.updateReachability(true); } }); this.client.on('plug-new', (plug) => { var accessory = this.accessories.get(plug.deviceId); if (accessory === undefined) { this.addNewPlug(plug); } else { this.log('Plug: %s [%s] - Online (New)', accessory.displayName, plug.deviceId); var hs100Acc = new Hs100Accessory(this.log, accessory, this.client); this.accessories.set(plug.deviceId, hs100Acc); hs100Acc.configure(plug); } }); // setTimeout(() => { // //Now Start Plug Search // this.client.startDiscovery(); // }, 3000) } addNewBulb (light) { var platform = this const name = light.name platform.log('New Light Bulb: %s [%s] - Online', name, light.deviceId) // 5 == Accessory.Categories.LIGHTBULB const platformAccessory = new PlatformAccessory(name, UUIDGen.generate(light.deviceId + light.name), 5) const lightService = platformAccessory.addService(Service.Lightbulb, name) lightService.addCharacteristic(Characteristic.Brightness) const infoService = platformAccessory.getService(Service.AccessoryInformation) infoService.addCharacteristic(Characteristic.FirmwareRevision) infoService.addCharacteristic(Characteristic.HardwareRevision) platformAccessory.context.deviceId = light.deviceId platformAccessory.context.host = light.host platformAccessory.context.port = light.port || 9999 const accessory = new TplinkLightbulbAccessory(this.log, platformAccessory) return accessory.configure(light).then(() => { platform.accessories.set(light.deviceId, accessory) platform.api.registerPlatformAccessories('homebridge-tplink-hs100-lightbulbs', 'TPLinkWT', [platformAccessory]) }).catch((reason) => { this.log(reason); }); } addNewPlug (plug) { const name = plug.name; this.log('New Plug: %s [%s] - Online', name, plug.deviceId); const platformAccessory = new PlatformAccessory(name, UUIDGen.generate(plug.deviceId), 7 /* Accessory.Categories.OUTLET */); const outletService = platformAccessory.addService(Service.Outlet, name); const infoService = platformAccessory.getService(Service.AccessoryInformation); infoService.addCharacteristic(Characteristic.FirmwareRevision); infoService.addCharacteristic(Characteristic.HardwareRevision); platformAccessory.context.deviceId = plug.deviceId; platformAccessory.context.host = plug.host; platformAccessory.context.port = plug.port || 9999; const accessory = new Hs100Accessory(this.log, platformAccessory, this.client); return accessory.configure(plug).then(() => { this.accessories.set(plug.deviceId, accessory); this.api.registerPlatformAccessories('homebridge-tplink-hs100-lightbulbs', 'TPLinkWT', [platformAccessory]); return accessory; }).catch((reason) => { this.log(reason); }); } //Global Functions configureAccessory (accessory) { this.accessories.set(accessory.context.deviceId, accessory); } removeAccessory (accessory) { var type = accessory instanceof Hs100Accessory ? "Plug" : "Light Bulb"; this.log('Remove %s: %s [%s]', type, accessory.accessory.displayName, accessory.context.deviceId); this.accessories.delete(accessory.accessory.deviceId); this.api.unregisterPlatformAccessories('homebridge-tplink-hs100-lightbulbs', 'TPLinkWT', [accessory.accessory]); } } class Hs100Accessory { constructor (log, accessory, client) { this.log = log; this.accessory = accessory; this.client = client; this.plug = client.getPlug({host: accessory.context.host, port: accessory.context.port}); this.deviceId = accessory.context.deviceId; // this.hs100api = hs100api ; // || new Hs100Api({host: accessory.context.host, port: accessory.context.port}) } identify (callback) { // TODO callback(); } configure (plug) { this.log('Configuring: %s', this.accessory.displayName); let plugInfo = plug ? Promise.resolve(plug.getInfo) : this.plug.getInfo(); return plugInfo.then((info) => { const pa = this.accessory; this.refresh(info.sysInfo); const outletService = pa.getService(Service.Outlet); outletService.getCharacteristic(Characteristic.On) .on('get', (callback) => { this.plug.getSysInfo().then((si) => { this.refresh(si); callback(null, si.relay_state === 1); }).catch((reason) => { this.log(reason); }); }) .on('set', (value, callback) => { this.plug.setPowerState(value).then(() => { callback(); }, (reason) => { this.log(reason); }); }); outletService.getCharacteristic(Characteristic.OutletInUse) .on('get', (callback) => { this.plug.getSysInfo().then((si) => { this.refresh(si); // On HS110 model we'll check the current power consumption to determinate the state if(si.model.toLowerCase().indexOf("hs110")) { this.plug.getConsumption().then((consumption) => { callback(null, consumption.get_realtime.power > 0); }); // On HS100 we can just use relay state } else { callback(null, si.relay_state === 1); } }).catch((reason) => { this.log(reason); }); }); }).catch((reason) => { this.log(reason); }); } refresh (sysInfo) { sysInfo = sysInfo ? Promise.resolve(sysInfo) : this.plug.getSysInfo(); return sysInfo.then((si) => { this.accessory.updateReachability(true); const name = si.alias || this.accessory.context.host; this.accessory.displayName = name; const outletService = this.accessory.getService(Service.Outlet); outletService.setCharacteristic(Characteristic.Name, name); const infoService = this.accessory.getService(Service.AccessoryInformation); infoService .setCharacteristic(Characteristic.Name, name) .setCharacteristic(Characteristic.Manufacturer, 'TP-Link') .setCharacteristic(Characteristic.Model, si.model) .setCharacteristic(Characteristic.SerialNumber, si.deviceId) .setCharacteristic(Characteristic.FirmwareRevision, si.sw_ver) .setCharacteristic(Characteristic.HardwareRevision, si.hw_ver); this.accessory.context.lastRefreshed = new Date(); return this; }).catch((reason) => { this.log(reason); }); } } class TplinkLightbulbAccessory { constructor (log, platformAccessory) { this.log = log this.log(platformAccessory); this.log(platformAccessory.PlatformAccessory); this.log(platformAccessory.context.deviceName); this.platformAccessory = platformAccessory this.deviceId = platformAccessory.context.deviceId } identify (callback) { // TODO callback(); } // Request info from bulb, retrying if the request times out getInfo (light) { let self = this return promiseRetry(function(retry, number) { if (number > 1) { self.log.debug('Unable to contact bulb, trying again (attempt #%s)', number) } return promiseTimeout.timeout(light.info(), 2000) .catch(function(err) { if (err instanceof promiseTimeout.TimeoutError) { retry(err) } throw err }) }) } configure (light) { this.light = light this.log(this.platformAccessory); this.log('Configuring:', this.platformAccessory.displayName) const lightService = this.platformAccessory.getService(Service.Lightbulb) const powerCharacteristic = lightService.getCharacteristic(Characteristic.On) powerCharacteristic .on('get', (callback) => { this.getInfo(this.light).then((info) => { this.refresh(info) this.powerState = info.light_state.on_off callback(null, info.light_state.on_off === 1) }).catch((reason) => { this.log(reason) }) }) .on('set', (value, callback) => { if (value === this.powerState) { callback() return } light.set(value).then((status) => { this.powerState = value this.log('Set %s light to %s', light.name, value ? 'on' : 'off') callback() }).catch((reason) => { this.log(reason) }) }) const brightnessCharacteristic = lightService.getCharacteristic(Characteristic.Brightness) brightnessCharacteristic .on('get', (callback) => { this.getInfo(this.light).then((info) => { this.refresh(info) const brightness = info.light_state.brightness || 100 callback(null, brightness) }).catch((reason) => { this.log(reason) }) }) .on('set', (value, callback) => { if (value === brightnessCharacteristic.value) { callback() return } light.set(true, 0, { brightness: value }).then((status) => { this.log('Changed %s brightness from %s% to %s%', light.name, brightnessCharacteristic.value, value) callback() }).catch((reason) => { this.log(reason) }) }) } refresh (info) { info = info ? Promise.resolve(info) : this.light.info() return info.then((info) => { this.platformAccessory.updateReachability(true) const name = info.alias || this.platformAccessory.context.host this.platformAccessory.displayName = name this.platformAccessory.getService(Service.Lightbulb) .setCharacteristic(Characteristic.Name, name) this.platformAccessory.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Name, name) .setCharacteristic(Characteristic.Manufacturer, 'TP-Link') .setCharacteristic(Characteristic.Model, info.model) .setCharacteristic(Characteristic.SerialNumber, info.deviceId) .setCharacteristic(Characteristic.FirmwareRevision, info.sw_ver) .setCharacteristic(Characteristic.HardwareRevision, info.hw_ver) this.platformAccessory.context.lastRefreshed = new Date() return this }).catch((reason) => { this.log(reason) }) } }