tplink-lightbulb-fix
Version:
Control TP-Link smart-home devices from nodejs
311 lines (253 loc) • 7.73 kB
JavaScript
import dgram from 'dgram';
import EventEmitter from 'events';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
class TPLSmartDevice {
constructor(ip) {
this.ip = ip;
} // Scan for lightbulbs on your network
static scan(filter, broadcast = '255.255.255.255') {
const emitter = new EventEmitter();
const client = dgram.createSocket({
type: 'udp4',
reuseAddr: true
});
client.bind(9998, undefined, () => {
client.setBroadcast(true);
const msgBuf = TPLSmartDevice.encrypt(Buffer.from('{"system":{"get_sysinfo":{}}}'));
client.send(msgBuf, 0, msgBuf.length, 9999, broadcast);
});
client.on('message', (msg, rinfo) => {
const decryptedMsg = this.decrypt(msg).toString('ascii');
const jsonMsg = JSON.parse(decryptedMsg);
const sysinfo = jsonMsg.system.get_sysinfo;
if (filter && sysinfo.mic_type !== filter) {
return;
}
const light = new TPLSmartDevice(rinfo.address);
light._info = rinfo;
light._sysinfo = sysinfo;
light.host = rinfo.address;
light.port = rinfo.port;
light.name = sysinfo.alias;
light.deviceId = sysinfo.deviceId;
emitter.emit('light', light);
});
emitter.stop = () => client.close();
return emitter;
} // Send a message to a lightbulb (for RAW JS message objects)
send(msg) {
return new Promise((resolve, reject) => {
if (!this.ip) {
return reject(new Error('IP not set.'));
}
const client = dgram.createSocket('udp4');
const message = this.encrypt(Buffer.from(JSON.stringify(msg)));
client.send(message, 0, message.length, 9999, this.ip, (err, bytes) => {
if (err) {
return reject(err);
}
client.on('message', msg => {
resolve(JSON.parse(this.decrypt(msg).toString()));
client.close();
});
});
});
} // TODO: wifi needs more testing. it seems very broken.
// Scans the wifi networks in range of the device
async listwifi() {
var _r1$netif, _r1$netif$get_scaninf;
const r1 = await this.send({
netif: {
get_scaninfo: {
refresh: 1
}
}
});
if (r1 != null && (_r1$netif = r1.netif) != null && (_r1$netif$get_scaninf = _r1$netif.get_scaninfo) != null && _r1$netif$get_scaninf.ap_list) {
return r1.netif.get_scaninfo.ap_list;
} else {
var _r2$smartlifeIotCom, _r2$smartlifeIotCom$g;
// on fail, try older message-format
const r2 = await this.send({
'smartlife.iot.common.softaponboarding': {
get_scaninfo: {
refresh: 1
}
}
});
if (r2 && (_r2$smartlifeIotCom = r2['smartlife.iot.common.softaponboarding']) != null && (_r2$smartlifeIotCom$g = _r2$smartlifeIotCom.get_scaninfo) != null && _r2$smartlifeIotCom$g.ap_list) {
return r2['smartlife.iot.common.softaponboarding'].get_scaninfo.ap_list;
}
}
} // Connects the device to the access point in the parameters
async connectwifi(ssid, password, keyType = 1, cypherType = 0) {
var _r1$netif2, _r1$netif2$set_stainf;
const r1 = await this.send({
netif: {
set_stainfo: {
cypher_type: cypherType,
key_type: keyType,
password,
ssid
}
}
});
if ((r1 == null ? void 0 : (_r1$netif2 = r1.netif) == null ? void 0 : (_r1$netif2$set_stainf = _r1$netif2.set_stainfo) == null ? void 0 : _r1$netif2$set_stainf.err_code) === 0) {
return true;
} // on fail, try older message-format
const r2 = await this.send({
'smartlife.iot.common.softaponboarding': {
set_stainfo: {
cypher_type: cypherType,
key_type: keyType,
password,
ssid
}
}
});
if (r2['smartlife.iot.common.softaponboarding'] && r2['smartlife.iot.common.softaponboarding'].err_msg) {
throw new Error(r2['smartlife.iot.common.softaponboarding'].err_msg);
} else {
return true;
}
} // Get info about the TPLSmartDevice
async info() {
const r = await this.send({
system: {
get_sysinfo: {}
}
});
return r.system.get_sysinfo;
} // Set power-state of lightbulb
async power(powerState = true, transition = 0, options = {}) {
const info = await this.info();
if (typeof info.relay_state !== 'undefined') {
return this.send({
system: {
set_relay_state: {
state: powerState ? 1 : 0
}
}
});
} else {
const r = await this.send({
'smartlife.iot.smartbulb.lightingservice': {
transition_light_state: _extends({
ignore_default: 1,
on_off: powerState ? 1 : 0,
transition_period: transition
}, options)
}
});
return r['smartlife.iot.smartbulb.lightingservice'].transition_light_state;
}
} // Set led-state of lightbulb
led(ledState = true) {
return this.send({
system: {
set_led_off: {
off: ledState ? 0 : 1
}
}
});
} // Set the name of lightbulb
async name(newAlias) {
const info = await this.info();
return typeof info.dev_name !== 'undefined' ? this.send({
system: {
set_dev_alias: {
alias: newAlias
}
}
}) : this.send({
'smartlife.iot.common.system': {
set_dev_alias: {
alias: newAlias
}
}
});
} // Get schedule info
async daystat(month, year) {
const now = new Date();
month = month || now.getMonth() + 1;
year = year || now.getFullYear();
const r = await this.send({
'smartlife.iot.common.schedule': {
get_daystat: {
month: month,
year: year
}
}
});
return r['smartlife.iot.common.schedule'].get_daystat;
} // Get cloud info from bulb
async cloud() {
const r = await this.send({
'smartlife.iot.common.cloud': {
get_info: {}
}
});
return r['smartlife.iot.common.cloud'].get_info;
} // Get schedule from bulb
async schedule() {
const r = await this.send({
'smartlife.iot.common.schedule': {
get_rules: {}
}
});
return r['smartlife.iot.common.schedule'].get_rules;
} // Get operational details from bulb
details() {
return this.send({
'smartlife.iot.smartbulb.lightingservice': {
get_light_details: {}
}
});
} // Reboot the device
reboot() {
return this.send({
'smartlife.iot.common.system': {
reboot: {
delay: 1
}
}
});
} // Badly encrypt message in format bulbs use
static encrypt(buffer, key = 0xAB) {
for (let i = 0; i < buffer.length; i++) {
const c = buffer[i];
buffer[i] = c ^ key;
key = buffer[i];
}
return buffer;
}
encrypt(buffer, key) {
return TPLSmartDevice.encrypt(buffer, key);
} // Badly decrypt message from format bulbs use
static decrypt(buffer, key = 0xAB) {
for (let i = 0; i < buffer.length; i++) {
const c = buffer[i];
buffer[i] = c ^ key;
key = c;
}
return buffer;
}
decrypt(buffer, key) {
return TPLSmartDevice.decrypt(buffer, key);
}
}
export { TPLSmartDevice as default };
//# sourceMappingURL=tplink-lightbulb.module.js.map