Release v1.4.0 (#11)

* refactor websocket / power usage

* cooldown between api requests during testing

* refactor power state methods

* remove unused code

* cooldown between api requests during testing

* version bump
This commit is contained in:
Martin M
2019-08-19 23:05:52 -03:00
committed by GitHub
parent 420d6746a6
commit 171faa617b
16 changed files with 217 additions and 182 deletions

View File

@@ -0,0 +1,30 @@
const WebSocket = require('../WebSocket');
const payloads = require('../../lib/payloads');
const { _get } = require('../../lib/helpers');
class ChangeState extends WebSocket {
static async set({ apiUrl, at, apiKey, deviceId, params, state }) {
const payloadLogin = payloads.wssLoginPayload({ at, apiKey });
const payloadUpdate = payloads.wssUpdatePayload({
apiKey,
deviceId,
params,
});
const response = await this.WebSocketRequest(apiUrl, [
payloadLogin,
payloadUpdate,
]);
const error = _get(response[1], 'error', false);
if (error === 403) {
return { error, msg: response[1].reason };
}
return { status: 'ok', state };
}
}
module.exports = ChangeState;

View File

@@ -0,0 +1,5 @@
const ChangeState = require('./ChangeState');
module.exports = {
ChangeState,
};

View File

@@ -0,0 +1,40 @@
class CurrentMonth {
/**
* Return daily power usage
*
* @param hundredDaysKwhData
*
* @returns {{daily: *, monthly: *}}
*/
static parse({ hundredDaysKwhData }) {
const today = new Date();
const days = today.getDate();
let monthlyUsage = 0;
const dailyUsage = [];
for (let day = 0; day < days; day += 1) {
const s = hundredDaysKwhData.substr(6 * day, 2);
const c = hundredDaysKwhData.substr(6 * day + 2, 2);
const f = hundredDaysKwhData.substr(6 * day + 4, 2);
const h = parseInt(s, 16);
const y = parseInt(c, 16);
const I = parseInt(f, 16);
const E = parseFloat(`${h}.${y}${I}`);
dailyUsage.push({
day: days - day,
usage: E,
});
monthlyUsage += E;
}
return {
monthly: monthlyUsage,
daily: dailyUsage,
};
}
}
module.exports = CurrentMonth;

View File

@@ -0,0 +1,52 @@
const WebSocket = require('../WebSocket');
const payloads = require('../../lib/payloads');
const { _get } = require('../../lib/helpers');
class DeviceRaw extends WebSocket {
/**
* Get specific device power usage (raw data)
*
* @param apiUrl
* @param at
* @param apiKey
* @param deviceId
*
* @returns {Promise<{error: string}|{data: {hundredDaysKwhData: *}, status: string}>}
*/
static async get({ apiUrl, at, apiKey, deviceId }) {
const payloadLogin = payloads.wssLoginPayload({ at, apiKey });
const payloadUpdate = payloads.wssUpdatePayload({
apiKey,
deviceId,
params: { hundredDaysKwh: 'get' },
});
const response = await this.WebSocketRequest(apiUrl, [
payloadLogin,
payloadUpdate,
]);
const error = _get(response, 'error', false);
if (error) {
return response;
}
const hundredDaysKwhData = _get(
response[1],
'config.hundredDaysKwhData',
false
);
if (!hundredDaysKwhData) {
return { error: 'No power usage data found.' };
}
return {
status: 'ok',
data: { hundredDaysKwhData },
};
}
}
module.exports = DeviceRaw;

View File

@@ -0,0 +1,7 @@
const DeviceRaw = require('./DeviceRaw');
const CurrentMonth = require('./CurrentMonth');
module.exports = {
DeviceRaw,
CurrentMonth,
};

55
classes/WebSocket.js Normal file
View File

@@ -0,0 +1,55 @@
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const delay = require('delay');
class WebSocket {
/**
* Open WebSocket connection and send provided payloads
*
* @param url
* @param payloads
* @param delayTime
*
* @returns {Array}
*/
static async WebSocketRequest(url, payloads, ...{ delayTime = 1000 }) {
const wsp = new WebSocketAsPromised(url, {
createWebSocket: wss => new W3CWebSocket(wss),
});
const responses = [];
wsp.onMessage.addListener(message => responses.push(JSON.parse(message)));
try {
await wsp.open();
for (const payload of payloads) {
await wsp.send(payload);
await delay(delayTime);
}
await wsp.close();
} catch (e) {
return this.customThrowError(e);
}
return responses;
}
/**
* Parse WebSocket errors and return user friendly messages
*
* @param e
*
* @returns {{error: string}|{msg: string, error: number}}
*/
static customThrowError(e) {
const loginError = e.message.indexOf('WebSocket is not opened');
if (loginError > -1) {
return { error: 401, msg: 'Authentication error' };
}
return { error: 'An unknown error occurred' };
}
}
module.exports = WebSocket;

View File

@@ -1,38 +0,0 @@
/**
* Return daily power usage
*
* @param hundredDaysKwhData
*
* @returns {{daily: *, monthly: *}}
*/
const currentMonthPowerUsage = ({ hundredDaysKwhData }) => {
const today = new Date();
const days = today.getDate();
let monthlyUsage = 0;
const dailyUsage = [];
for (let day = 0; day < days; day += 1) {
const s = hundredDaysKwhData.substr(6 * day, 2);
const c = hundredDaysKwhData.substr(6 * day + 2, 2);
const f = hundredDaysKwhData.substr(6 * day + 4, 2);
const h = parseInt(s, 16);
const y = parseInt(c, 16);
const I = parseInt(f, 16);
const E = parseFloat(`${h}.${y}${I}`);
dailyUsage.push({
day: days - day,
usage: E,
});
monthlyUsage += E;
}
return {
monthly: monthlyUsage,
daily: dailyUsage,
};
};
module.exports = currentMonthPowerUsage;

View File

@@ -1,50 +0,0 @@
const WebSocketRequest = require('../websocket');
const payloads = require('../payloads');
const { _get } = require('../helpers');
/**
* Get specific device power usage (raw data)
*
* @param apiUrl
* @param at
* @param apiKey
* @param deviceId
*
* @returns {Promise<{error: string}|{response: {hundredDaysKwhData: *}, status: string}>}
*/
const deviceRawPowerUsage = async ({ apiUrl, at, apiKey, deviceId }) => {
const payloadLogin = payloads.wssLoginPayload({ at, apiKey });
const payloadUpdate = payloads.wssUpdatePayload({
apiKey,
deviceId,
params: { hundredDaysKwh: 'get' },
});
const response = await WebSocketRequest(apiUrl, [
payloadLogin,
payloadUpdate,
]);
const error = _get(response, 'error', false);
if (error) {
return response;
}
const hundredDaysKwhData = _get(
response[1],
'config.hundredDaysKwhData',
false
);
if (!hundredDaysKwhData) {
return { error: 'No power usage data found.' };
}
return {
status: 'ok',
data: { hundredDaysKwhData },
};
};
module.exports = deviceRawPowerUsage;

View File

@@ -1,7 +0,0 @@
const deviceRawPowerUsage = require('./deviceRawPowerUsage');
const currentMonthPowerUsage = require('./currentMonthPowerUsage');
module.exports = {
deviceRawPowerUsage,
currentMonthPowerUsage,
};

View File

@@ -1,53 +0,0 @@
const W3CWebSocket = require('websocket').w3cwebsocket;
const WebSocketAsPromised = require('websocket-as-promised');
const delay = require('delay');
/**
* Parse WebSocket errors and return user friendly messages
*
* @param e
*
* @returns {{error: string}|{msg: string, error: number}}
*/
const customThrowError = e => {
const loginError = e.message.indexOf('WebSocket is not opened');
if (loginError > -1) {
return { error: 401, msg: 'Authentication error' };
}
return { error: 'An unknown error occurred' };
};
/**
* Open WebSocket connection and send provided payloads
*
* @param url
* @param payloads
* @param delayTime
*
* @returns {Array}
*/
const WebSocketRequest = async (url, payloads, ...{ delayTime = 1000 }) => {
const wsp = new WebSocketAsPromised(url, {
createWebSocket: wss => new W3CWebSocket(wss),
});
const responses = [];
wsp.onMessage.addListener(message => responses.push(JSON.parse(message)));
try {
await wsp.open();
for (const payload of payloads) {
await wsp.send(payload);
await delay(delayTime);
}
await wsp.close();
} catch (e) {
return customThrowError(e);
}
return responses;
};
module.exports = WebSocketRequest;

41
main.js
View File

@@ -1,6 +1,5 @@
const rp = require('request-promise');
const WebSocketRequest = require('./lib/websocket');
const { _get } = require('./lib/helpers');
const {
@@ -10,7 +9,8 @@ const {
const payloads = require('./lib/payloads');
const powerUsage = require('./lib/powerUsage');
const { ChangeState } = require('./classes/PowerState');
const { DeviceRaw, CurrentMonth } = require('./classes/PowerUsage');
class eWeLink {
constructor({ region = 'us', email, password, at, apiKey }) {
@@ -43,19 +43,6 @@ class eWeLink {
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
}
/**
* Return required config for websocket requests
*
* @returns {{at: *, apiUrl: string, apiKey: *}}
*/
getWebSocketConfig() {
return {
apiUrl: this.getApiWebSocket(),
at: this.at,
apiKey: this.apiKey,
};
}
/**
* Generate http requests helpers
*
@@ -234,23 +221,14 @@ class eWeLink {
params.switch = stateToSwitch;
}
const payloadLogin = payloads.wssLoginPayload({
return ChangeState.set({
apiUrl: this.getApiWebSocket(),
at: this.at,
apiKey: this.apiKey,
});
const payloadUpdate = payloads.wssUpdatePayload({
apiKey: this.apiKey,
deviceId,
params,
state: stateToSwitch,
});
await WebSocketRequest(this.getApiWebSocket(), [
payloadLogin,
payloadUpdate,
]);
return { status: 'ok', state };
}
/**
@@ -274,8 +252,11 @@ class eWeLink {
*/
async getDeviceRawPowerUsage(deviceId) {
await this.logIfNeeded();
return powerUsage.deviceRawPowerUsage({
...this.getWebSocketConfig(),
return DeviceRaw.get({
apiUrl: this.getApiWebSocket(),
at: this.at,
apiKey: this.apiKey,
deviceId,
});
}
@@ -299,7 +280,7 @@ class eWeLink {
return {
status: 'ok',
...powerUsage.currentMonthPowerUsage({ hundredDaysKwhData }),
...CurrentMonth.parse({ hundredDaysKwhData }),
};
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "ewelink-api",
"version": "1.3.0",
"version": "1.4.0",
"description": "eWeLink API for Node.js",
"author": "Martín M.",
"license": "MIT",

View File

@@ -1,3 +1,5 @@
const delay = require('delay');
const ewelink = require('../main');
const { email, password, deviceId } = require('./_setup/credentials.json');
const {
@@ -43,6 +45,7 @@ describe('env: node script', () => {
test('set device power state', async () => {
jest.setTimeout(30000);
await delay(3000);
const device = await conn.getDevice(deviceId);
const currentState = device.params.switch;
const newState = currentState === 'on' ? 'off' : 'on';
@@ -57,6 +60,7 @@ describe('env: node script', () => {
test('toggle device power state', async () => {
jest.setTimeout(30000);
await delay(3000);
const device = await conn.getDevice(deviceId);
const currentState = device.params.switch;
const newState = currentState === 'on' ? 'off' : 'on';

View File

@@ -1,3 +1,5 @@
const delay = require('delay');
const ewelink = require('../main');
const { email, password, deviceId } = require('./_setup/credentials.json');
const {
@@ -46,6 +48,7 @@ describe('env: serverless', () => {
test('set device power state', async () => {
jest.setTimeout(30000);
await delay(3000);
const conn = new ewelink({ at: accessToken, apiKey });
const device = await conn.getDevice(deviceId);
const currentState = device.params.switch;
@@ -61,6 +64,7 @@ describe('env: serverless', () => {
test('toggle device power state', async () => {
jest.setTimeout(30000);
await delay(3000);
const conn = new ewelink({ at: accessToken, apiKey });
const device = await conn.getDevice(deviceId);
const currentState = device.params.switch;

View File

@@ -10,6 +10,10 @@ const {
const { loginExpectations } = require('./_setup/expectations');
describe('valid credentials, invalid device', () => {
beforeEach(async () => {
await delay(1000);
});
test('get device power state should fail', async () => {
const conn = new ewelink({ email, password });
const powerState = await conn.getDevicePowerState('invalid deviceid');
@@ -54,7 +58,6 @@ describe('valid credentials, invalid device', () => {
test('raw power on device without electricity monitor should fail', async () => {
jest.setTimeout(30000);
await delay(1000);
const conn = new ewelink({ email, password });
const powerUsage = await conn.getDeviceRawPowerUsage(deviceIdWithoutPower);
expect(typeof powerUsage).toBe('object');

View File

@@ -20,6 +20,10 @@ describe('power usage: node script', () => {
await conn.login();
});
beforeEach(async () => {
await delay(1000);
});
test('should return raw power usage', async () => {
jest.setTimeout(30000);
const powerUsage = await conn.getDeviceRawPowerUsage(deviceIdWithPower);
@@ -30,7 +34,6 @@ describe('power usage: node script', () => {
test('should return current month power usage', async () => {
jest.setTimeout(30000);
await delay(1000);
const days = new Date().getDate();
const powerUsage = await conn.getDevicePowerUsage(deviceIdWithPower);
expect(typeof powerUsage).toBe('object');
@@ -61,7 +64,6 @@ describe('power usage: serverless', () => {
test('should return current month power usage', async () => {
jest.setTimeout(30000);
await delay(1000);
const days = new Date().getDate();
const conn = new ewelink({ at: accessToken, apiKey });
const powerUsage = await conn.getDevicePowerUsage(deviceIdWithPower);