mirror of
https://github.com/skydiver/ewelink-api.git
synced 2025-12-21 21:33:11 +01:00
Added multi-channel devices and get/set power state (#2)
* added multi-channel device * added get and set power state * added some fixes * add “this” to call class methods * create params object before build payload * typo fixed on “switches” param * added new test cases * improved error message * added more test cases * added authentication error * added fix channel number too high * added region change if wrong * linting + small fixes * linting + renamed method * linting
This commit is contained in:
@@ -63,9 +63,99 @@ const makeAuthorizationSign = body =>
|
||||
.update(JSON.stringify(body))
|
||||
.digest('base64');
|
||||
|
||||
const getDeviceTypeByUiid = uiid => {
|
||||
const MAPPING = {
|
||||
1: 'SOCKET',
|
||||
2: 'SOCKET_2',
|
||||
3: 'SOCKET_3',
|
||||
4: 'SOCKET_4',
|
||||
5: 'SOCKET_POWER',
|
||||
6: 'SWITCH',
|
||||
7: 'SWITCH_2',
|
||||
8: 'SWITCH_3',
|
||||
9: 'SWITCH_4',
|
||||
10: 'OSPF',
|
||||
11: 'CURTAIN',
|
||||
12: 'EW-RE',
|
||||
13: 'FIREPLACE',
|
||||
14: 'SWITCH_CHANGE',
|
||||
15: 'THERMOSTAT',
|
||||
16: 'COLD_WARM_LED',
|
||||
17: 'THREE_GEAR_FAN',
|
||||
18: 'SENSORS_CENTER',
|
||||
19: 'HUMIDIFIER',
|
||||
22: 'RGB_BALL_LIGHT',
|
||||
23: 'NEST_THERMOSTAT',
|
||||
24: 'GSM_SOCKET',
|
||||
25: 'AROMATHERAPY',
|
||||
26: 'BJ_THERMOSTAT',
|
||||
27: 'GSM_UNLIMIT_SOCKET',
|
||||
28: 'RF_BRIDGE',
|
||||
29: 'GSM_SOCKET_2',
|
||||
30: 'GSM_SOCKET_3',
|
||||
31: 'GSM_SOCKET_4',
|
||||
32: 'POWER_DETECTION_SOCKET',
|
||||
33: 'LIGHT_BELT',
|
||||
34: 'FAN_LIGHT',
|
||||
35: 'EZVIZ_CAMERA',
|
||||
36: 'SINGLE_CHANNEL_DIMMER_SWITCH',
|
||||
38: 'HOME_KIT_BRIDGE',
|
||||
40: 'FUJIN_OPS',
|
||||
41: 'CUN_YOU_DOOR',
|
||||
42: 'SMART_BEDSIDE_AND_NEW_RGB_BALL_LIGHT',
|
||||
43: '',
|
||||
44: '',
|
||||
45: 'DOWN_CEILING_LIGHT',
|
||||
46: 'AIR_CLEANER',
|
||||
49: 'MACHINE_BED',
|
||||
51: 'COLD_WARM_DESK_LIGHT',
|
||||
52: 'DOUBLE_COLOR_DEMO_LIGHT',
|
||||
53: 'ELECTRIC_FAN_WITH_LAMP',
|
||||
55: 'SWEEPING_ROBOT',
|
||||
56: 'RGB_BALL_LIGHT_4',
|
||||
57: 'MONOCHROMATIC_BALL_LIGHT',
|
||||
59: 'MEARICAMERA',
|
||||
1001: 'BLADELESS_FAN',
|
||||
1002: 'NEW_HUMIDIFIER',
|
||||
1003: 'WARM_AIR_BLOWER',
|
||||
};
|
||||
return MAPPING[uiid] || '';
|
||||
};
|
||||
|
||||
const getDeviceChannelCountByType = deviceType => {
|
||||
const DEVICE_CHANNEL_LENGTH = {
|
||||
SOCKET: 1,
|
||||
SWITCH_CHANGE: 1,
|
||||
GSM_UNLIMIT_SOCKET: 1,
|
||||
SWITCH: 1,
|
||||
THERMOSTAT: 1,
|
||||
SOCKET_POWER: 1,
|
||||
GSM_SOCKET: 1,
|
||||
POWER_DETECTION_SOCKET: 1,
|
||||
SOCKET_2: 2,
|
||||
GSM_SOCKET_2: 2,
|
||||
SWITCH_2: 2,
|
||||
SOCKET_3: 3,
|
||||
GSM_SOCKET_3: 3,
|
||||
SWITCH_3: 3,
|
||||
SOCKET_4: 4,
|
||||
GSM_SOCKET_4: 4,
|
||||
SWITCH_4: 4,
|
||||
CUN_YOU_DOOR: 4,
|
||||
};
|
||||
return DEVICE_CHANNEL_LENGTH[deviceType] || 0;
|
||||
};
|
||||
|
||||
const getDeviceTotalChannels = deviceUUID => {
|
||||
const deviceType = getDeviceTypeByUiid(deviceUUID);
|
||||
const channels = getDeviceChannelCountByType(deviceType);
|
||||
return channels;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
makeAuthorizationSign,
|
||||
loginPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
getDeviceTotalChannels,
|
||||
};
|
||||
|
||||
114
main.js
114
main.js
@@ -10,6 +10,7 @@ const {
|
||||
loginPayload,
|
||||
wssLoginPayload,
|
||||
wssUpdatePayload,
|
||||
getDeviceTotalChannels,
|
||||
} = require('./lib/ewelink-helper');
|
||||
|
||||
class eWeLink {
|
||||
@@ -18,14 +19,21 @@ class eWeLink {
|
||||
return { error: 'No credentials provided' };
|
||||
}
|
||||
|
||||
this.apiUrl = `https://${region}-api.coolkit.cc:8080/api`;
|
||||
this.apiWebSocket = `wss://${region}-pconnect3.coolkit.cc:8080/api/ws`;
|
||||
this.region = region;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.at = at;
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
getApiUrl() {
|
||||
return `https://${this.region}-api.coolkit.cc:8080/api`;
|
||||
}
|
||||
|
||||
getApiWebSocket() {
|
||||
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
|
||||
}
|
||||
|
||||
async makeRequest({ method = 'GET', uri, body = {}, qs = {} }) {
|
||||
const { at } = this;
|
||||
|
||||
@@ -35,13 +43,18 @@ class eWeLink {
|
||||
|
||||
const response = await rp({
|
||||
method,
|
||||
uri: `${this.apiUrl}${uri}`,
|
||||
uri: `${this.getApiUrl()}${uri}`,
|
||||
headers: { Authorization: `Bearer ${this.at}` },
|
||||
body,
|
||||
qs,
|
||||
json: true,
|
||||
});
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
if (error && [401, 402].indexOf(parseInt(error)) !== -1) {
|
||||
return { error, msg: 'Authentication error' };
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -50,13 +63,31 @@ class eWeLink {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
});
|
||||
const response = await rp({
|
||||
|
||||
let response = await rp({
|
||||
method: 'POST',
|
||||
uri: `${this.apiUrl}/user/login`,
|
||||
uri: `${this.getApiUrl()}/user/login`,
|
||||
headers: { Authorization: `Sign ${makeAuthorizationSign(body)}` },
|
||||
body,
|
||||
json: true,
|
||||
});
|
||||
|
||||
const error = _get(response, 'error', false);
|
||||
const region = _get(response, 'region', false);
|
||||
|
||||
if (error && [400, 401, 404].indexOf(parseInt(error)) !== -1) {
|
||||
return { error, msg: 'Authentication error' };
|
||||
}
|
||||
|
||||
if (error && parseInt(error) === 301 && region) {
|
||||
if (this.region !== region) {
|
||||
this.region = region;
|
||||
response = await this.login();
|
||||
return response;
|
||||
}
|
||||
return { error, msg: 'Region does not exist' };
|
||||
}
|
||||
|
||||
this.apiKey = _get(response, 'user.apikey', '');
|
||||
this.at = _get(response, 'at', '');
|
||||
return response;
|
||||
@@ -78,24 +109,58 @@ class eWeLink {
|
||||
return response;
|
||||
}
|
||||
|
||||
async toggleDevice(deviceId) {
|
||||
async getDevicePowerState(deviceId, channel = 1) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const status = _get(device, 'params.switch', false);
|
||||
const error = _get(device, 'error', false);
|
||||
const switchesAmount = getDeviceTotalChannels(device.uiid);
|
||||
let state = _get(device, 'params.switch', false);
|
||||
const switches = _get(device, 'params.switches', false);
|
||||
|
||||
if (!status) {
|
||||
return { error: 'Device does not exist' };
|
||||
if (error || switchesAmount < channel || (!state && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
const state = status === 'on' ? 'off' : 'on';
|
||||
if (switches) {
|
||||
state = switches[channel - 1].switch;
|
||||
}
|
||||
|
||||
return { status: 'ok', state };
|
||||
}
|
||||
|
||||
async setDevicePowerState(deviceId, state, channel = 1) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const error = _get(device, 'error', false);
|
||||
const switchesAmount = getDeviceTotalChannels(device.uiid);
|
||||
const status = _get(device, 'params.switch', false);
|
||||
const switches = _get(device, 'params.switches', false);
|
||||
|
||||
if (error || switchesAmount < channel || (!status && !switches)) {
|
||||
if (error && parseInt(error) === 401) {
|
||||
return device;
|
||||
}
|
||||
return { error, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
const params = {};
|
||||
|
||||
if (switches) {
|
||||
params.switches = switches;
|
||||
params.switches[channel - 1].switch = state;
|
||||
} else {
|
||||
params.switch = state;
|
||||
}
|
||||
|
||||
const payloadLogin = wssLoginPayload({ at: this.at, apiKey: this.apiKey });
|
||||
const payloadUpdate = wssUpdatePayload({
|
||||
apiKey: this.apiKey,
|
||||
deviceId,
|
||||
params: { switch: state },
|
||||
params,
|
||||
});
|
||||
|
||||
const wsp = new WebSocketAsPromised(this.apiWebSocket, {
|
||||
const wsp = new WebSocketAsPromised(this.getApiWebSocket(), {
|
||||
createWebSocket: url => new W3CWebSocket(url),
|
||||
});
|
||||
|
||||
@@ -107,6 +172,31 @@ class eWeLink {
|
||||
|
||||
return { status: 'ok', state };
|
||||
}
|
||||
|
||||
async toggleDevice(deviceId, channel = 1) {
|
||||
const powerState = await this.getDevicePowerState(deviceId, channel);
|
||||
const state = _get(powerState, 'state', false);
|
||||
|
||||
if (!state) {
|
||||
return { error: powerState.error, msg: 'Device does not exist' };
|
||||
}
|
||||
|
||||
const newState = state === 'on' ? 'off' : 'on';
|
||||
|
||||
const newResponse = await this.setDevicePowerState(
|
||||
deviceId,
|
||||
newState,
|
||||
channel
|
||||
);
|
||||
|
||||
return newResponse;
|
||||
}
|
||||
|
||||
async getDeviceChannelCount(deviceId) {
|
||||
const device = await this.getDevice(deviceId);
|
||||
const switchesAmount = getDeviceTotalChannels(device.uiid);
|
||||
return switchesAmount;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = eWeLink;
|
||||
|
||||
@@ -14,9 +14,9 @@ const allDevicesExpectations = {
|
||||
};
|
||||
|
||||
const specificDeviceExpectations = {
|
||||
apikey: expect.any(String),
|
||||
deviceid: expect.any(String),
|
||||
name: expect.any(String),
|
||||
deviceid: expect.any(String),
|
||||
apikey: expect.any(String),
|
||||
online: expect.any(Boolean),
|
||||
uiid: expect.any(Number),
|
||||
};
|
||||
|
||||
@@ -31,15 +31,40 @@ describe('env: node script', () => {
|
||||
expect(device.deviceid).toBe(deviceId);
|
||||
expect(device).toMatchObject(specificDeviceExpectations);
|
||||
});
|
||||
|
||||
test('get device power state', async () => {
|
||||
const device = await conn.getDevice(deviceId);
|
||||
const currentState = device.params.switch;
|
||||
const powerState = await conn.getDevicePowerState(deviceId);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.status).toBe('ok');
|
||||
expect(powerState.state).toBe(currentState);
|
||||
});
|
||||
|
||||
test('set device power state', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const device = await conn.getDevice(deviceId);
|
||||
const currentState = device.params.switch;
|
||||
const newState = currentState === 'on' ? 'off' : 'on';
|
||||
const powerState = await conn.setDevicePowerState(deviceId, newState);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.status).toBe('ok');
|
||||
expect(powerState.state).toBe(newState);
|
||||
const deviceVerify = await conn.getDevice(deviceId);
|
||||
const currentStateVerify = deviceVerify.params.switch;
|
||||
expect(newState).toBe(currentStateVerify);
|
||||
});
|
||||
});
|
||||
|
||||
describe('env: serverless', () => {
|
||||
let accessToken;
|
||||
let apiKey;
|
||||
|
||||
test('login into ewelink', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const login = await conn.login();
|
||||
accessToken = login.at;
|
||||
apiKey = login.user.apikey;
|
||||
expect(typeof login).toBe('object');
|
||||
expect(login).toMatchObject(loginExpectations);
|
||||
});
|
||||
@@ -58,6 +83,61 @@ describe('env: serverless', () => {
|
||||
expect(device.deviceid).toBe(deviceId);
|
||||
expect(device).toMatchObject(specificDeviceExpectations);
|
||||
});
|
||||
|
||||
test('get device power state', async () => {
|
||||
const conn = new ewelink({ at: accessToken });
|
||||
const device = await conn.getDevice(deviceId);
|
||||
const currentState = device.params.switch;
|
||||
const powerState = await conn.getDevicePowerState(deviceId);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.status).toBe('ok');
|
||||
expect(powerState.state).toBe(currentState);
|
||||
});
|
||||
|
||||
test('set device power state', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ at: accessToken, apiKey });
|
||||
const device = await conn.getDevice(deviceId);
|
||||
const currentState = device.params.switch;
|
||||
const newState = currentState === 'on' ? 'off' : 'on';
|
||||
const powerState = await conn.setDevicePowerState(deviceId, newState);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.status).toBe('ok');
|
||||
expect(powerState.state).toBe(newState);
|
||||
const deviceVerify = await conn.getDevice(deviceId);
|
||||
const currentStateVerify = deviceVerify.params.switch;
|
||||
expect(newState).toBe(currentStateVerify);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valid credentials, invalid device', () => {
|
||||
test('get device power state should fail', async () => {
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.getDevicePowerState('invalid deviceid');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device does not exist');
|
||||
expect(powerState.error).toBe(500);
|
||||
});
|
||||
|
||||
test('set device power state should fail', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ email, password });
|
||||
const powerState = await conn.setDevicePowerState('invalid deviceid', 'on');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Device does not exist');
|
||||
expect(powerState.error).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valid credentials, wrong region', () => {
|
||||
test('login into ewelink should fail', async () => {
|
||||
const conn = new ewelink({ region: 'eu', email, password });
|
||||
expect(conn.region).toBe('eu');
|
||||
const login = await conn.login();
|
||||
expect(typeof login).toBe('object');
|
||||
expect(login).toMatchObject(loginExpectations);
|
||||
expect(conn.region).toBe('us');
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid credentials', () => {
|
||||
@@ -71,9 +151,7 @@ describe('invalid credentials', () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const login = await conn.login();
|
||||
expect(typeof login).toBe('object');
|
||||
expect(login).toMatchObject({
|
||||
error: expect.any(Number),
|
||||
});
|
||||
expect(login.msg).toBe('Authentication error');
|
||||
expect(login.error).toBe(400);
|
||||
});
|
||||
|
||||
@@ -81,19 +159,32 @@ describe('invalid credentials', () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const devices = await conn.getDevices();
|
||||
expect(typeof devices).toBe('object');
|
||||
expect(devices).toMatchObject({
|
||||
msg: expect.any(String),
|
||||
});
|
||||
expect(devices.msg).toBe('Authentication error');
|
||||
expect(devices.error).toBe(401);
|
||||
});
|
||||
|
||||
test('get error response on specific device', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const device = await conn.getDevice('invalid device id');
|
||||
const device = await conn.getDevice(deviceId);
|
||||
expect(typeof device).toBe('object');
|
||||
expect(device).toMatchObject({
|
||||
msg: expect.any(String),
|
||||
});
|
||||
expect(device.msg).toBe('Authentication error');
|
||||
expect(device.error).toBe(401);
|
||||
});
|
||||
|
||||
test('get device power state should fail', async () => {
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const powerState = await conn.getDevicePowerState(deviceId);
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Authentication error');
|
||||
expect(powerState.error).toBe(401);
|
||||
});
|
||||
|
||||
test('set device power state should fail', async () => {
|
||||
jest.setTimeout(30000);
|
||||
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||
const powerState = await conn.setDevicePowerState(deviceId, 'on');
|
||||
expect(typeof powerState).toBe('object');
|
||||
expect(powerState.msg).toBe('Authentication error');
|
||||
expect(powerState.error).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user