Skip to content

Commit

Permalink
feat: add basic support for KL430 Light Strip
Browse files Browse the repository at this point in the history
  • Loading branch information
plasticrake committed Feb 1, 2022
1 parent 73d1151 commit 7e1db57
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 27 deletions.
6 changes: 6 additions & 0 deletions src/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Device implements DeviceType {
'lb100',
'lb120',
'lb130',
'kl430',
];

api: ApiType;
Expand All @@ -91,6 +92,7 @@ class Device implements DeviceType {
api: Record<string, unknown>;
children: Array<{ sysinfo: { id: string } }>;
data: DataType;
get mac(): string;
reset: () => void;
constructor: { errors: Record<string, unknown> };
};
Expand Down Expand Up @@ -155,6 +157,10 @@ class Device implements DeviceType {
return this.#deviceInfo.children;
}

get mac() {
return this.#deviceInfo.mac;
}

processMessage(
msg: Buffer | string,
encryptFn: (arg: string) => Buffer,
Expand Down
6 changes: 5 additions & 1 deletion src/devices/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Base {
this.api = {
system: {
get_sysinfo: errCode(() => {
return this.data.system.sysinfo;
return this.sysinfo;
}),
},
cnCloud: {
Expand All @@ -41,6 +41,10 @@ class Base {
};
}

get sysinfo() {
return this.data.system.sysinfo;
}

get emeterContext() {
return this.data.emeter;
}
Expand Down
71 changes: 71 additions & 0 deletions src/devices/data/kl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const base = require('./base');

const kl = { ...base };
module.exports = kl;

Object.assign(kl, {
cnCloud: {
info: {
username: '',
server: 'n-devs.tplinkcloud.com',
binded: 0,
cld_connection: 1,
illegalType: 0,
stopConnect: 0,
tcspStatus: 1,
fwDlPage: '',
tcspInfo: '',
fwNotifyType: -1,
err_code: 0,
},
},

emeter: {
realtime: {
power_mw: 10800,
},
daystat: {
day_list: [],
},
},

'smartlife.iot.lightStrip': {
fade_on_off: { fadeOnTime: 500, fadeOffTime: 500 },

light_state: {
transition: 0,
length: 16,
on_off: 0,
dft_on_state: { mode: 'normal', groups: [[0, 15, 0, 0, 100, 9000]] },
},

dft_on_state: {
mode: 'normal',
hue: 0,
saturation: 0,
color_temp: 9000,
brightness: 100,
groups: [[0, 15, 0, 0, 100, 9000]],
},

get_light_details: {
lamp_beam_angle: 270,
min_voltage: 100,
max_voltage: 120,
wattage: 17,
incandescent_equivalent: 100,
max_lumens: 1400,
color_rendering_index: 85,
},

get_default_behavior: {
soft_on: {
mode: 'last_status',
},
hard_on: {
mode: 'last_status',
},
err_code: 0,
},
},
});
50 changes: 50 additions & 0 deletions src/devices/data/kl430.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const defaultsDeep = require('lodash.defaultsdeep');

const kl = require('./kl');

const kl430 = {
colorTempRange: { min: 2500, max: 9000 },

system: {
sysinfo: {
sw_ver: '1.0.9 Build 200305 Rel.090639',
hw_ver: '1.0',
mic_type: 'IOT.SMARTBULB',
model: 'KL430(US)',
deviceId: '801222BAA511374991036875D0A280AD1D35A16F',
oemId: '1A3F21A5B9AE0ED6C80ED1A107885DB2',
hwId: '375D4CCE7C909516CFD57BA93A304404',
description: 'Kasa Smart Light Strip, Multicolor',
length: 16,
is_dimmable: 1,
is_color: 1,
is_variable_color_temp: 1,

status: 'new',
rssi: -58,

is_factory: false,
disco_ver: '1.0',

ctrl_protocols: {
name: 'Linkie',
version: '1.0',
},

lighting_effect_state: {
enable: 1,
name: 'Aurora',
custom: 0,
id: 'xqUxDhbAhNLqulcuRMyPBmVGyTOyEMEu',
brightness: 63,
},

dev_state: 'normal',
active_mode: 'none',
preferred_state: [],
},
},
};
defaultsDeep(kl430, kl);

module.exports = kl430;
7 changes: 2 additions & 5 deletions src/devices/hs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ class Hs extends Base {
super(data);
defaultsDeep(this.data, defaultData);

this.api.system = {
get_sysinfo: errCode(() => {
return this.sysinfo;
}),
Object.assign(this.api.system, {
set_dev_alias: errCode(({ alias }) => {
this.alias = alias;
}),
Expand Down Expand Up @@ -92,7 +89,7 @@ class Hs extends Base {
set_dev_icon: errCode((data) => {
this.data.system.dev_icon = data;
}),
};
});

this.api.cnCloud = {
get_info: errCode(() => {
Expand Down
151 changes: 151 additions & 0 deletions src/devices/kl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable camelcase */
const defaultsDeep = require('lodash.defaultsdeep');

const { errCode, pick, randomMac } = require('../utils');

const Base = require('./base');
const Hs = require('./hs');

const defaultData = require('./data/base');

class Kl extends Base {
static get errors() {
return {
MODULE_NOT_SUPPORT: { err_code: -1, err_msg: 'module not support' },
METHOD_NOT_SUPPORT: { err_code: -2, err_msg: 'member not support' },
INVALID_ARGUMENT: null,
MISSING_ARGUMENT: {
err_code: -10002,
/* cspell:disable-next-line */
err_msg: 'Missing neccesary argument',
},
};
}

constructor(data) {
super(data);
this.hs = new Hs(data);
this.data = defaultsDeep(data, defaultData);

const { get_sysinfo } = this.api.system;

this.api.system = this.hs.api.system;
this.api.system.get_sysinfo = get_sysinfo;

this.api['smartlife.iot.common.system'] = this.api.system;

this.api['smartlife.iot.common.cloud'] = this.hs.api.cnCloud;

this.api['smartlife.iot.common.schedule'] = this.hs.api.schedule;

this.api['smartlife.iot.common.timesetting'] = this.hs.api.time;

this.api['smartlife.iot.common.timesetting'].set_time = errCode(
// eslint-disable-next-line no-unused-vars
({ year, month, mday, hour, min, sec }) => {
// TODO
}
);

this.api.netif = this.hs.api.netif;

this.api['smartlife.iot.common.emeter'] = {
get_realtime: errCode(() => {
return this.hs.emeterContext.realtime;
}),
get_daystat: this.hs.api.emeter.get_daystat,
get_monthstat: this.hs.api.emeter.get_monthstat,
erase_emeter_stat: this.hs.api.emeter.erase_emeter_stat,
};

this.api['smartlife.iot.lightStrip'] = {
get_fade_on_off: errCode(() => {
return this.data['smartlife.iot.lightStrip'].fade_on_off;
}),

set_light_state: errCode((options) => {
const ls = this.data['smartlife.iot.lightStrip'].light_state;

Object.entries(options).forEach(([k, v]) => {
switch (k) {
case 'color_temp':
if (
v === 0 ||
(v >= this.data.colorTempRange.min &&
v <= this.data.colorTempRange.max)
) {
Object.assign(ls, { [k]: v });
} else {
throw { err_code: -10000, err_msg: 'Invalid input argument' }; // eslint-disable-line no-throw-literal
}
break;
case 'mode':
case 'hue':
case 'on_off':
case 'saturation':
case 'brightness':
Object.assign(ls, { [k]: v });
break;
default:
// do nothing
}
});

return pick(ls, ['on_off', 'mode', 'groups']);
}),

get_light_state: errCode(() => {
return this.data['smartlife.iot.lightStrip'].light_state;
}),

get_light_details: errCode(() => {
return this.data['smartlife.iot.lightStrip'].get_light_details;
}),

get_default_behavior: errCode(() => {
return this.data['smartlife.iot.lightStrip'].get_default_behavior;
}),
};
}

// eslint-disable-next-line class-methods-use-this
get endSocketAfterResponse() {
return false;
}

get sysinfo() {
this.data.system.sysinfo.on_time = this.onTime;

this.data.system.sysinfo.light_state = pick(
this.data['smartlife.iot.lightStrip'].light_state,
['on_off', 'mode', 'hue', 'saturation', 'color_temp', 'brightness']
);

this.data.system.sysinfo.light_state.dft_on_state = pick(
this.data['smartlife.iot.lightStrip'].dft_on_state,
['mode', 'hue', 'saturation', 'color_temp', 'brightness']
);

return this.data.system.sysinfo;
}

get mac() {
return this.data.system.sysinfo.mic_mac;
}

set mac(value) {
this.data.system.sysinfo.mic_mac = value;
}

initDefaults() {
super.initDefaults();

if (this.data.mac) this.mac = this.data.mac;

if (!this.mac) {
this.mac = randomMac();
}
}
}

module.exports = Kl;
15 changes: 15 additions & 0 deletions src/devices/kl430.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const defaultsDeep = require('lodash.defaultsdeep');

const Kl = require('./kl');

const defaultData = require('./data/kl430');

class Kl430 extends Kl {
constructor(data) {
super(data);
this.data.cnCloud = defaultData.cnCloud;
defaultsDeep(this.data, defaultData);
}
}

module.exports = Kl430;
19 changes: 14 additions & 5 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ function generateId(len) {
.toUpperCase();
}

function pick(object, keys) {
return keys.reduce((obj, key) => {
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
// eslint-disable-next-line no-param-reassign
obj[key] = object[key];
}
return obj;
}, {});
}

function parseJsonStream(json) {
const parser = new Parser();
const results = [];
Expand Down Expand Up @@ -114,11 +124,9 @@ function processCommands(json, api, errors, customizerFn) {
if (module.name === 'context' && !foundFirstContext) {
foundFirstContext = true;
if ('context' in api && 'child_ids' in api.context) {
const childIds = module.methods.find(
(v) => v.name === 'child_ids'
).args;
if (childIds !== undefined) {
api.context.child_ids(childIds);
const childIds = module.methods.find((v) => v.name === 'child_ids');
if (childIds !== undefined && childIds.args !== undefined) {
api.context.child_ids(childIds.args);
}
}
} else {
Expand Down Expand Up @@ -285,6 +293,7 @@ module.exports = {
randomMac,
generateId,
parseJsonStream,
pick,
processCommands,
randomInt,
randomFloat,
Expand Down
Loading

0 comments on commit 7e1db57

Please sign in to comment.