homebridge-tplink-hs100-lightbulbs
Version:
Homebridge Plugin for TP-Link HS100 Outlets & LB120 Light Bulbs
377 lines (308 loc) • 12.4 kB
JavaScript
'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)
})
}
}