mirror of
https://github.com/skydiver/ewelink-api.git
synced 2025-12-21 13:23:05 +01:00
Release v2.0.0 (#44)
* Added arpTableSolver (#18) * Added arpTableSolver * fix package import * linting class * changed arp library * refactor arp class * using arpping fork * refactor arpTableSolver class * Added Zero Conf functionality (LAN mode) (#46) * added crypto-js * zeroconf helper functions * zeroconf update payload * new method to save devices cache file * class renamed * refactor Zeroconf class * return cached device if exists * moved method to get local ip address * fix mac addresses without leading zeroes * refactor Zeroconf class * using new zeroconf functionality * zeroconf working with single and multichannel devices * save device mixin enhancement * working on zeroconf test cases * catch errors on filesystem methods * zeroconf: added extra test cases * better error handling * zeroconf: 100% code coverage * removed deprecated login method * updates on credentials file * version bump * Docs for v2.0 (#52) * added v1 docs * added zeroconf docs * updated readme * docs updated * removed zeroconf article warning * updated vscode config Co-authored-by: Luis Llamas <luisllamas@hotmail.com>
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -90,4 +90,9 @@ typings/
|
|||||||
|
|
||||||
# End of https://www.gitignore.io/api/node
|
# End of https://www.gitignore.io/api/node
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
demo.js
|
||||||
|
arp-table.json
|
||||||
|
devices-cache.json
|
||||||
|
test/_setup/credentials.js
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -2,5 +2,8 @@
|
|||||||
"eslint.validate": [
|
"eslint.validate": [
|
||||||
"javascript"
|
"javascript"
|
||||||
],
|
],
|
||||||
"eslint.autoFixOnSave": true
|
"eslint.autoFixOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,13 +7,14 @@
|
|||||||
* set on/off devices
|
* set on/off devices
|
||||||
* get power consumption on devices like Sonoff POW
|
* get power consumption on devices like Sonoff POW
|
||||||
* listen for devices events
|
* listen for devices events
|
||||||
|
* using zeroconf (LAN mode), no internet connection required
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
``` sh
|
```sh
|
||||||
npm install ewelink-api
|
npm install ewelink-api
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Check docs at https://ewelink-api.now.sh
|
Check library documentation and examples at https://github.com/skydiver/ewelink-api/docs
|
||||||
40
classes/PowerState/ChangeStateZeroconf.js
Normal file
40
classes/PowerState/ChangeStateZeroconf.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const rp = require('request-promise');
|
||||||
|
|
||||||
|
const WebSocket = require('../WebSocket');
|
||||||
|
const payloads = require('../../lib/payloads');
|
||||||
|
const { _get } = require('../../lib/helpers');
|
||||||
|
|
||||||
|
class ChangeStateZeroconf extends WebSocket {
|
||||||
|
static async set({ url, device, params, switches, state }) {
|
||||||
|
const selfApikey = device.apikey;
|
||||||
|
const deviceId = device.deviceid;
|
||||||
|
const deviceKey = device.devicekey;
|
||||||
|
|
||||||
|
const endpoint = switches ? 'switches' : 'switch';
|
||||||
|
const localUrl = `${url}/${endpoint}`;
|
||||||
|
|
||||||
|
const body = payloads.zeroConfUpdatePayload(
|
||||||
|
selfApikey,
|
||||||
|
deviceId,
|
||||||
|
deviceKey,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await rp({
|
||||||
|
method: 'POST',
|
||||||
|
uri: localUrl,
|
||||||
|
body,
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = _get(response, 'error', false);
|
||||||
|
|
||||||
|
if (error === 403) {
|
||||||
|
return { error, msg: response.reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: 'ok', state };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ChangeStateZeroconf;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
const ChangeState = require('./ChangeState');
|
const ChangeState = require('./ChangeState');
|
||||||
|
const ChangeStateZeroconf = require('./ChangeStateZeroconf');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ChangeState,
|
ChangeState,
|
||||||
|
ChangeStateZeroconf,
|
||||||
};
|
};
|
||||||
|
|||||||
91
classes/Zeroconf.js
Normal file
91
classes/Zeroconf.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const arpping = require('arpping')({});
|
||||||
|
|
||||||
|
class Zeroconf {
|
||||||
|
/**
|
||||||
|
* Build the ARP table
|
||||||
|
* @param ip
|
||||||
|
* @returns {Promise<unknown>}
|
||||||
|
*/
|
||||||
|
static getArpTable(ip = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
arpping.discover(ip, (err, hosts) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const arpTable = Zeroconf.fixMacAddresses(hosts);
|
||||||
|
return resolve(arpTable);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sometime arp command returns mac addresses without leading zeroes.
|
||||||
|
* @param hosts
|
||||||
|
*/
|
||||||
|
static fixMacAddresses(hosts) {
|
||||||
|
return hosts.map(host => {
|
||||||
|
const octets = host.mac.split(':');
|
||||||
|
|
||||||
|
const fixedMac = octets.map(octet => {
|
||||||
|
if (octet.length === 1) {
|
||||||
|
return `0${octet}`;
|
||||||
|
}
|
||||||
|
return octet;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ip: host.ip,
|
||||||
|
mac: fixedMac.join(':'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save ARP table to local file
|
||||||
|
* @param config
|
||||||
|
* @returns {Promise<{error: string}|{file: {request: string; resolved: string} | any | string | string, status: string}>}
|
||||||
|
*/
|
||||||
|
static async saveArpTable(config = {}) {
|
||||||
|
const ip = config.ip || null;
|
||||||
|
const fileName = config.file || './arp-table.json';
|
||||||
|
try {
|
||||||
|
const arpTable = await Zeroconf.getArpTable(ip);
|
||||||
|
const jsonContent = JSON.stringify(arpTable, null, 2);
|
||||||
|
fs.writeFileSync(fileName, jsonContent, 'utf8');
|
||||||
|
return { status: 'ok', file: fileName };
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e.toString() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read ARP table file
|
||||||
|
* @param fileName
|
||||||
|
* @returns {Promise<{error: string}|any>}
|
||||||
|
*/
|
||||||
|
static async loadArpTable(fileName = './arp-table.json') {
|
||||||
|
try {
|
||||||
|
const jsonContent = await fs.readFileSync(fileName);
|
||||||
|
return JSON.parse(jsonContent);
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e.toString() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read devices cache file
|
||||||
|
* @param fileName
|
||||||
|
* @returns {Promise<{error: string}>}
|
||||||
|
*/
|
||||||
|
static async loadCachedDevices(fileName = './devices-cache.json') {
|
||||||
|
try {
|
||||||
|
const jsonContent = await fs.readFileSync(fileName);
|
||||||
|
return JSON.parse(jsonContent);
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e.toString() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Zeroconf;
|
||||||
27
docs/README.md
Normal file
27
docs/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Documentation
|
||||||
|
|
||||||
|
* [Introduction](introduction.md)
|
||||||
|
* [Quickstart](quickstart.md)
|
||||||
|
* [Class Instantiation](class-instantiation.md)
|
||||||
|
* [Demos](demos/README.md)
|
||||||
|
* [node script](demos/node.md)
|
||||||
|
* [serverless](demos/serverless.md)
|
||||||
|
* [Available Methods](available-methods/README.md)
|
||||||
|
* [getCredentials](available-methods/getcredentials.md)
|
||||||
|
* [openWebSocket](available-methods/openwebsocket.md)
|
||||||
|
* [getDevice](available-methods/getdevice.md)
|
||||||
|
* [getDevices](available-methods/getdevices.md)
|
||||||
|
* [getDevicePowerState](available-methods/getdevicepowerstate.md)
|
||||||
|
* [setDevicePowerState](available-methods/setdevicepowerstate.md)
|
||||||
|
* [toggleDevice](available-methods/toggledevice.md)
|
||||||
|
* [getDevicePowerUsage](available-methods/getdevicepowerusage.md)
|
||||||
|
* [getDeviceCurrentTH](available-methods/getdevicecurrentth.md)
|
||||||
|
* [getDeviceCurrentTemperature](available-methods/getdevicecurrenttemperature.md)
|
||||||
|
* [getDeviceCurrentHumidity](available-methods/getdevicecurrenthumidity.md)
|
||||||
|
* [getDeviceChannelCount](available-methods/getdevicechannelcount.md)
|
||||||
|
* [getRegion](available-methods/getregion.md)
|
||||||
|
* [getFirmwareVersion](available-methods/getfirmwareversion.md)
|
||||||
|
* [saveDevicesCache](available-methods/savedevicescache.md)
|
||||||
|
* [login](available-methods/login.md) <sup>_*deprecated_</sup>
|
||||||
|
* [Zeroconf (LAN mode)](zeroconf.md)
|
||||||
|
* [Testing](testing.md)
|
||||||
24
docs/available-methods/README.md
Normal file
24
docs/available-methods/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Available Methods
|
||||||
|
|
||||||
|
Here is the list of available methods.
|
||||||
|
|
||||||
|
* [getCredentials](getcredentials.md)
|
||||||
|
* [openWebSocket](openwebsocket.md)
|
||||||
|
* [getDevice](getdevice.md)
|
||||||
|
* [getDevices](getdevices.md)
|
||||||
|
* [getDevicePowerState](getdevicepowerstate.md)
|
||||||
|
* [setDevicePowerState](setdevicepowerstate.md)
|
||||||
|
* [toggleDevice](toggledevice.md)
|
||||||
|
* [getDevicePowerUsage](getdevicepowerusage.md)
|
||||||
|
* [getDeviceCurrentTH](getdevicecurrentth.md)
|
||||||
|
* [getDeviceCurrentTemperature](getdevicecurrenttemperature.md)
|
||||||
|
* [getDeviceCurrentHumidity](getdevicecurrenthumidity.md)
|
||||||
|
* [getDeviceChannelCount](getdevicechannelcount.md)
|
||||||
|
* [getRegion](getregion.md)
|
||||||
|
* [getFirmwareVersion](getfirmwareversion.md)
|
||||||
|
* [saveDevicesCache](savedevicescache.md)
|
||||||
|
* [login](login.md) <sup>_*deprecated_</sup>
|
||||||
|
|
||||||
|
Remember to instantiate class before usage.
|
||||||
|
|
||||||
|
Also, take a look at the provided demos for [node script](../demos/node.md) and [serverless](../demos/serverless.md).
|
||||||
20
docs/available-methods/getcredentials.md
Normal file
20
docs/available-methods/getcredentials.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# getCredentials
|
||||||
|
|
||||||
|
Get your access token, api key and region.
|
||||||
|
|
||||||
|
This method is useful on serverless context, where you need to obtain auth credentials to make individual requests.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const auth = await connection.getCredentials();
|
||||||
|
|
||||||
|
console.log('access token: ', auth.at);
|
||||||
|
console.log('api key: ', auth.user.apikey);
|
||||||
|
console.log('region: ', auth.region);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
> Access token and api key will be invalidate after you login again using email and password.
|
||||||
13
docs/available-methods/getdevice.md
Normal file
13
docs/available-methods/getdevice.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# getDevice
|
||||||
|
|
||||||
|
Return information for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
/* get specific device information */
|
||||||
|
const device = await connection.getDevice('<your device id>');
|
||||||
|
console.log(device);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
12
docs/available-methods/getdevicechannelcount.md
Normal file
12
docs/available-methods/getdevicechannelcount.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# getDeviceChannelCount
|
||||||
|
|
||||||
|
Return total channels for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const channels = await connection.getDeviceChannelCount('<your device id>');
|
||||||
|
console.log(channels);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
21
docs/available-methods/getdevicecurrenthumidity.md
Normal file
21
docs/available-methods/getdevicecurrenthumidity.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# getDeviceCurrentHumidity
|
||||||
|
|
||||||
|
Return current humidity for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const humidity = await connection.getDeviceCurrentHumidity('<your device id>');
|
||||||
|
console.log(humidity);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
humidity: '76'
|
||||||
|
}
|
||||||
|
```
|
||||||
21
docs/available-methods/getdevicecurrenttemperature.md
Normal file
21
docs/available-methods/getdevicecurrenttemperature.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# getDeviceCurrentTemperature
|
||||||
|
|
||||||
|
Return current temperature for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const temperature = await connection.getDeviceCurrentTemperature('<your device id>');
|
||||||
|
console.log(temperature);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
temperature: '20'
|
||||||
|
}
|
||||||
|
```
|
||||||
22
docs/available-methods/getdevicecurrentth.md
Normal file
22
docs/available-methods/getdevicecurrentth.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# getDeviceCurrentTH
|
||||||
|
|
||||||
|
Return current temperature and humidity for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const temphumd = await connection.getDeviceCurrentTH('<your device id>');
|
||||||
|
console.log(temphumd);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
temperature: '20',
|
||||||
|
humidity: '76'
|
||||||
|
}
|
||||||
|
```
|
||||||
27
docs/available-methods/getdevicepowerstate.md
Normal file
27
docs/available-methods/getdevicepowerstate.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# getDevicePowerState
|
||||||
|
|
||||||
|
Query for specified device power status.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```js
|
||||||
|
const status = await connection.getDevicePowerState('<your device id>');
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// multi-channel devices like Sonoff 4CH
|
||||||
|
const status = await connection.getDevicePowerState('<your device id>', <channel>);
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
state: 'off'
|
||||||
|
}
|
||||||
|
```
|
||||||
50
docs/available-methods/getdevicepowerusage.md
Normal file
50
docs/available-methods/getdevicepowerusage.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# getDevicePowerUsage
|
||||||
|
|
||||||
|
Returns current month power usage on device who supports electricity records, like Sonoff POW.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const usage = await connection.getDevicePowerUsage('<your device id>');
|
||||||
|
console.log(usage);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
monthly: 109.78,
|
||||||
|
daily:
|
||||||
|
[
|
||||||
|
{ day: 26, usage: 4.19 },
|
||||||
|
{ day: 25, usage: 2.11 },
|
||||||
|
{ day: 24, usage: 3.74 },
|
||||||
|
{ day: 23, usage: 8.23 },
|
||||||
|
{ day: 22, usage: 3.16 },
|
||||||
|
{ day: 21, usage: 3.95 },
|
||||||
|
{ day: 20, usage: 3.38 },
|
||||||
|
{ day: 19, usage: 4.56 },
|
||||||
|
{ day: 18, usage: 1.51 },
|
||||||
|
{ day: 17, usage: 2.4 },
|
||||||
|
{ day: 16, usage: 1.5 },
|
||||||
|
{ day: 15, usage: 7.28 },
|
||||||
|
{ day: 14, usage: 7.44 },
|
||||||
|
{ day: 13, usage: 3.21 },
|
||||||
|
{ day: 12, usage: 5.5 },
|
||||||
|
{ day: 11, usage: 4.43 },
|
||||||
|
{ day: 10, usage: 3.15 },
|
||||||
|
{ day: 9, usage: 1.33 },
|
||||||
|
{ day: 8, usage: 2.9 },
|
||||||
|
{ day: 7, usage: 6.03 },
|
||||||
|
{ day: 6, usage: 7.48 },
|
||||||
|
{ day: 5, usage: 5.94 },
|
||||||
|
{ day: 4, usage: 3.64 },
|
||||||
|
{ day: 3, usage: 2.39 },
|
||||||
|
{ day: 2, usage: 3.10 },
|
||||||
|
{ day: 1, usage: 7.23 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
13
docs/available-methods/getdevices.md
Normal file
13
docs/available-methods/getdevices.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# getDevices
|
||||||
|
|
||||||
|
Returns a list of devices associated to logged account.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
/* get all devices */
|
||||||
|
const devices = await connection.getDevices();
|
||||||
|
console.log(devices);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
12
docs/available-methods/getfirmwareversion.md
Normal file
12
docs/available-methods/getfirmwareversion.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# getFirmwareVersion
|
||||||
|
|
||||||
|
Return firmware version for specified device.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const firmware = await connection.getFirmwareVersion('<your device id>');
|
||||||
|
console.log(firmware);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
23
docs/available-methods/getregion.md
Normal file
23
docs/available-methods/getregion.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# getRegion
|
||||||
|
|
||||||
|
Return logged user region.
|
||||||
|
|
||||||
|
> This method only works if class is initialized using email and password.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const region = await connection.getRegion();
|
||||||
|
console.log(region);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
email: 'user@email.com',
|
||||||
|
region: 'us'
|
||||||
|
}
|
||||||
|
```
|
||||||
20
docs/available-methods/login.md
Normal file
20
docs/available-methods/login.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Login
|
||||||
|
|
||||||
|
> DEPRECATED: use [getCredentials](available-methods/getcredentials.md) method instead
|
||||||
|
|
||||||
|
Login into eWeLink API and get auth credentials.
|
||||||
|
|
||||||
|
This method is useful on serverless context, where you need to obtain auth credentials to make individual requests.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const auth = await connection.login();
|
||||||
|
|
||||||
|
console.log('access token: ', auth.at);
|
||||||
|
console.log('api key: ', auth.user.apikey);
|
||||||
|
console.log('region: ', auth.region);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
70
docs/available-methods/openwebsocket.md
Normal file
70
docs/available-methods/openwebsocket.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# openWebSocket
|
||||||
|
|
||||||
|
Opens a socket connection to eWeLink and listen for realtime events.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
The `openWebSocket` method requires a callback function as an argument.
|
||||||
|
|
||||||
|
Once an event is received, the callback function will be executed with the server message as argument.
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
// instantiate class
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
// login into eWeLink
|
||||||
|
await connection.login();
|
||||||
|
|
||||||
|
// call openWebSocket method with a callback as argument
|
||||||
|
const socket = await connection.openWebSocket(async data => {
|
||||||
|
// data is the message from eWeLink
|
||||||
|
console.log(data)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
|
||||||
|
If everything went well, the first message will have the following format:
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
error: 0,
|
||||||
|
apikey: '12345678-9012-3456-7890-123456789012',
|
||||||
|
config: {
|
||||||
|
hb: 1,
|
||||||
|
hbInterval: 12345
|
||||||
|
},
|
||||||
|
sequence: '1234567890123'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When a device change his status, a similar message will be returned:
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
action: 'update',
|
||||||
|
deviceid: '1234567890',
|
||||||
|
apikey: '12345678-9012-3456-7890-123456789012',
|
||||||
|
userAgent: 'device',
|
||||||
|
sequence: '1234567890123'
|
||||||
|
ts: 0,
|
||||||
|
params: {
|
||||||
|
switch: 'on'
|
||||||
|
},
|
||||||
|
from: 'device',
|
||||||
|
seq: '11'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
* Because of the nature of a socket connection, the script will keep running until the connection gets closed.
|
||||||
|
* `openWebSocket` will return the socket instance
|
||||||
|
* if you need to manually kill the connection, just run `socket.close()` (where socket is the variable used).
|
||||||
19
docs/available-methods/savedevicescache.md
Normal file
19
docs/available-methods/savedevicescache.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# saveDevicesCache
|
||||||
|
|
||||||
|
Save devices cache file (required when using zeroconf)
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```
|
||||||
|
const ewelink = require('ewelink-api');
|
||||||
|
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
await connection.saveDevicesCache();
|
||||||
|
```
|
||||||
|
|
||||||
|
A file named devices-cache.json will be created.
|
||||||
29
docs/available-methods/setdevicepowerstate.md
Normal file
29
docs/available-methods/setdevicepowerstate.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# setDevicePowerState
|
||||||
|
|
||||||
|
Change specified device power state.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```js
|
||||||
|
const status = await connection.setDevicePowerState('<your device id>', 'on');
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// multi-channel devices like Sonoff 4CH
|
||||||
|
const status = await connection.setDevicePowerState('<your device id>', 'toggle', <channel>);
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible states: `on`, `off`, `toggle`.
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
state: 'on'
|
||||||
|
}
|
||||||
|
```
|
||||||
27
docs/available-methods/toggledevice.md
Normal file
27
docs/available-methods/toggledevice.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# toggleDevice
|
||||||
|
|
||||||
|
Switch specified device current power state.
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```js
|
||||||
|
const status = await connection.toggleDevice('<your device id>');
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// multi-channel devices like Sonoff 4CH
|
||||||
|
const status = await connection.toggleDevice('<your device id>', <channel>);
|
||||||
|
console.log(status);
|
||||||
|
```
|
||||||
|
|
||||||
|
<sup>* _Remember to instantiate class before use_</sup>
|
||||||
|
|
||||||
|
|
||||||
|
### Response example
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
status: 'ok',
|
||||||
|
state: 'off'
|
||||||
|
}
|
||||||
|
```
|
||||||
24
docs/class-instantiation.md
Normal file
24
docs/class-instantiation.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Class Instantiation
|
||||||
|
|
||||||
|
> Default region of this library is `us`. If your are in a different one, **you must** specify region parameter or error 400/401 will be returned.
|
||||||
|
|
||||||
|
**_Using email and password_**
|
||||||
|
```
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**_Using access token and api key_**
|
||||||
|
```
|
||||||
|
const connection = new ewelink({
|
||||||
|
at: '<valid access token>',
|
||||||
|
apiKey: '<valid api key>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> * If you don't know your region, use [getRegion](available-methods/getregion) method
|
||||||
|
> * To get your access token and api key, use [getCredentials](available-methods/getcredentials) method
|
||||||
8
docs/demos/README.md
Normal file
8
docs/demos/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Demos
|
||||||
|
|
||||||
|
### node script
|
||||||
|
* [Read](node.md) how to use this library on a nodejs script.
|
||||||
|
|
||||||
|
|
||||||
|
### serverless
|
||||||
|
* [Read](serverless.md) how to integrate on your serverles environment, like a lambda function.
|
||||||
30
docs/demos/node.md
Normal file
30
docs/demos/node.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# node script
|
||||||
|
|
||||||
|
> Default region of this library is `us`. If your are in a different one, **you must** specify region parameter or error 400/401 will be returned.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ewelink = require('ewelink-api');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* get all devices */
|
||||||
|
const devices = await connection.getDevices();
|
||||||
|
console.log(devices);
|
||||||
|
|
||||||
|
/* get specific devide info */
|
||||||
|
const device = await connection.getDevice('<your device id>');
|
||||||
|
console.log(device);
|
||||||
|
|
||||||
|
/* toggle device */
|
||||||
|
await connection.toggleDevice('<your device id>');
|
||||||
|
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
> If you don't know your region, use [getRegion](/docs/available-methods/getregion) method
|
||||||
73
docs/demos/serverless.md
Normal file
73
docs/demos/serverless.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# serverless
|
||||||
|
|
||||||
|
On a serverless scenario you need to instantiate the class on every request.
|
||||||
|
|
||||||
|
So, instead of using email and password on every api call, you can login the first time then use auth credentials for future requests.
|
||||||
|
|
||||||
|
> Default region of this library is `us`. If your are in a different one, **you must** specify region parameter or error 400/401 will be returned.
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
/* first request: get access token and api key */
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
const login = await connection.login();
|
||||||
|
|
||||||
|
const accessToken = login.at;
|
||||||
|
const apiKey = login.user.apikey
|
||||||
|
const region = login.region;
|
||||||
|
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
/* second request: use access token to request devices */
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const newConnection = new ewelink({
|
||||||
|
at: accessToken,
|
||||||
|
region: region
|
||||||
|
});
|
||||||
|
|
||||||
|
const devices = await newConnection.getDevices();
|
||||||
|
console.log(devices);
|
||||||
|
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
/* third request: use access token to request specific device info */
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const thirdConnection = new ewelink({
|
||||||
|
at: accessToken,
|
||||||
|
region: region
|
||||||
|
});
|
||||||
|
|
||||||
|
const device = await thirdConnection.getDevice('<your device id>');
|
||||||
|
console.log(device);
|
||||||
|
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
/* fourth request: use access token and api key to toggle specific device info */
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const anotherNewConnection = new ewelink({
|
||||||
|
at: accessToken,
|
||||||
|
region: region
|
||||||
|
});
|
||||||
|
|
||||||
|
await anotherNewConnection.toggleDevice('<your device id>');
|
||||||
|
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
> If you don't know your region, use [getRegion](/docs/available-methods/getregion) method
|
||||||
25
docs/introduction.md
Normal file
25
docs/introduction.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
**eWeLink API for JavaScript** is a module who let you interact directly with eWeLink API using your regular credentials.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install package on your node project:
|
||||||
|
```
|
||||||
|
npm install ewelink-api
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Key features
|
||||||
|
|
||||||
|
* can run on browsers, node scripts or serverless environment
|
||||||
|
* set on/off devices
|
||||||
|
* get power consumption on devices like Sonoff POW
|
||||||
|
* listen for devices events
|
||||||
|
|
||||||
|
|
||||||
|
## Help, comments, contributions?
|
||||||
|
|
||||||
|
* Repo: https://github.com/skydiver/ewelink-api
|
||||||
|
* Issue tracker: https://github.com/skydiver/ewelink-api/issues
|
||||||
22
docs/quickstart.md
Normal file
22
docs/quickstart.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Quickstart
|
||||||
|
|
||||||
|
Here is a basic node script to start working with the module:
|
||||||
|
|
||||||
|
> Default region of this library is `us`. If your are in a different one, **you must** specify region parameter or error 400/401 will be returned.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ewelink = require('ewelink-api');
|
||||||
|
|
||||||
|
/* instantiate class */
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
/* get all devices */
|
||||||
|
const devices = await connection.getDevices();
|
||||||
|
console.log(devices);
|
||||||
|
```
|
||||||
|
|
||||||
|
> If you don't know your region, use [getRegion](available-methods/getregion.md) method
|
||||||
8
docs/testing.md
Normal file
8
docs/testing.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
Open `test/_setup/credentials.json` and update parameters.
|
||||||
|
|
||||||
|
In a terminal, `npm run test` or `npm run coverage`.
|
||||||
|
|
||||||
|
|
||||||
|
> tests needs to be performed serially, so if run jest manually, add `--runInBand` parameter.
|
||||||
66
docs/zeroconf.md
Normal file
66
docs/zeroconf.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Zeroconf (LAN mode)
|
||||||
|
|
||||||
|
> Zeroconf only works if you're connected to the same network of the device you wanna control.
|
||||||
|
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
* at this time, only turn on/off action is available.
|
||||||
|
* after initial setup, internet connection is not required to turn on/off your devices.
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Before start, you will need to create 2 files with information about your devices (the library includes methods to generate both files).
|
||||||
|
1. a cache file with information about your devices.
|
||||||
|
2. an "arp table" cache file with info from your network connected devices.
|
||||||
|
3. toggle specific device power state
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Generate devices cache file
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ewelink = require('ewelink-api');
|
||||||
|
|
||||||
|
const connection = new ewelink({
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
});
|
||||||
|
|
||||||
|
await connection.saveDevicesCache();
|
||||||
|
```
|
||||||
|
|
||||||
|
A file named `devices-cache.json` will be created.
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Generate arp table cache file
|
||||||
|
|
||||||
|
```js
|
||||||
|
const Zeroconf = require('ewelink-api/classes/Zeroconf');
|
||||||
|
|
||||||
|
await Zeroconf.saveArpTable({
|
||||||
|
ip: '<your network addres, ex: 192.168.5.1>'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
A file named `arp-table.json` will be created.
|
||||||
|
|
||||||
|
|
||||||
|
## 3. toggle device power state
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ewelink = require('ewelink-api');
|
||||||
|
const Zeroconf = require('ewelink-api/classes/Zeroconf');
|
||||||
|
|
||||||
|
/* load cache files */
|
||||||
|
const devicesCache = await Zeroconf.loadCachedDevices();
|
||||||
|
const arpTable = await Zeroconf.loadArpTable();
|
||||||
|
|
||||||
|
/* create the connection using cache files */
|
||||||
|
const connection = new ewelink({ devicesCache, arpTable });
|
||||||
|
|
||||||
|
/* turn device on */
|
||||||
|
await connection.setDevicePowerState('<your device id>', 'on');
|
||||||
|
|
||||||
|
/* turn device off */
|
||||||
|
await connection.setDevicePowerState('<your device id>', 'off');
|
||||||
|
```
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const CryptoJS = require('crypto-js');
|
||||||
const random = require('random');
|
const random = require('random');
|
||||||
|
|
||||||
const DEVICE_TYPE_UUID = require('./data/devices-type-uuid');
|
const DEVICE_TYPE_UUID = require('./data/devices-type-uuid');
|
||||||
@@ -26,8 +27,49 @@ const getDeviceChannelCount = deviceUUID => {
|
|||||||
return getDeviceChannelCountByType(deviceType);
|
return getDeviceChannelCountByType(deviceType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const create16Uiid = () => {
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < 16; i += 1) {
|
||||||
|
result += random.int(0, 9);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptionBase64 = t =>
|
||||||
|
CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(t));
|
||||||
|
|
||||||
|
const decryptionBase64 = t =>
|
||||||
|
CryptoJS.enc.Base64.parse(t).toString(CryptoJS.enc.Utf8);
|
||||||
|
|
||||||
|
const encryptationData = (data, key) => {
|
||||||
|
const encryptedMessage = {};
|
||||||
|
const uid = create16Uiid();
|
||||||
|
const iv = encryptionBase64(uid);
|
||||||
|
const code = CryptoJS.AES.encrypt(data, CryptoJS.MD5(key), {
|
||||||
|
iv: CryptoJS.enc.Utf8.parse(uid),
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
encryptedMessage.uid = uid;
|
||||||
|
encryptedMessage.iv = iv;
|
||||||
|
encryptedMessage.data = code.ciphertext.toString(CryptoJS.enc.Base64);
|
||||||
|
return encryptedMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decryptionData = (data, key, iv) => {
|
||||||
|
const iv64 = decryptionBase64(iv);
|
||||||
|
const code = CryptoJS.AES.decrypt(data, CryptoJS.MD5(key), {
|
||||||
|
iv: CryptoJS.enc.Utf8.parse(iv64),
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
return code.toString(CryptoJS.enc.Utf8);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
makeAuthorizationSign,
|
makeAuthorizationSign,
|
||||||
makeFakeIMEI,
|
makeFakeIMEI,
|
||||||
getDeviceChannelCount,
|
getDeviceChannelCount,
|
||||||
|
encryptationData,
|
||||||
|
decryptionData,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ const firmwareUpdate = require('./firmwareUpdate');
|
|||||||
const credentialsPayload = require('./credentialsPayload');
|
const credentialsPayload = require('./credentialsPayload');
|
||||||
const wssLoginPayload = require('./wssLoginPayload');
|
const wssLoginPayload = require('./wssLoginPayload');
|
||||||
const wssUpdatePayload = require('./wssUpdatePayload');
|
const wssUpdatePayload = require('./wssUpdatePayload');
|
||||||
|
const zeroConfUpdatePayload = require('./zeroConfUpdatePayload');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
firmwareUpdate,
|
firmwareUpdate,
|
||||||
credentialsPayload,
|
credentialsPayload,
|
||||||
wssLoginPayload,
|
wssLoginPayload,
|
||||||
wssUpdatePayload,
|
wssUpdatePayload,
|
||||||
|
zeroConfUpdatePayload,
|
||||||
};
|
};
|
||||||
|
|||||||
17
lib/payloads/zeroConfUpdatePayload.js
Normal file
17
lib/payloads/zeroConfUpdatePayload.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const { encryptationData } = require('../ewelink-helper');
|
||||||
|
|
||||||
|
const zeroConfUpdatePayload = (selfApikey, deviceId, deviceKey, params) => {
|
||||||
|
const encryptedData = encryptationData(JSON.stringify(params), deviceKey);
|
||||||
|
const timeStamp = new Date() / 1000;
|
||||||
|
const sequence = Math.floor(timeStamp * 1000);
|
||||||
|
return {
|
||||||
|
sequence: sequence.toString(),
|
||||||
|
deviceid: deviceId,
|
||||||
|
selfApikey,
|
||||||
|
iv: encryptedData.iv,
|
||||||
|
encrypt: true,
|
||||||
|
data: encryptedData.data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = zeroConfUpdatePayload;
|
||||||
30
main.js
30
main.js
@@ -3,8 +3,16 @@ const rp = require('request-promise');
|
|||||||
const { _get } = require('./lib/helpers');
|
const { _get } = require('./lib/helpers');
|
||||||
|
|
||||||
class eWeLink {
|
class eWeLink {
|
||||||
constructor({ region = 'us', email, password, at, apiKey }) {
|
constructor({
|
||||||
if (!at && (!email && !password)) {
|
region = 'us',
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
at,
|
||||||
|
apiKey,
|
||||||
|
devicesCache,
|
||||||
|
arpTable,
|
||||||
|
}) {
|
||||||
|
if (!devicesCache && !arpTable && !at && (!email && !password)) {
|
||||||
return { error: 'No credentials provided' };
|
return { error: 'No credentials provided' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,6 +21,8 @@ class eWeLink {
|
|||||||
this.password = password;
|
this.password = password;
|
||||||
this.at = at;
|
this.at = at;
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
|
this.devicesCache = devicesCache;
|
||||||
|
this.arpTable = arpTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +51,16 @@ class eWeLink {
|
|||||||
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
|
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Zeroconf URL
|
||||||
|
* @param device
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getZeroconfUrl(device) {
|
||||||
|
const ip = this.getLocalIp(device);
|
||||||
|
return `http://${ip}:8081/zeroconf`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate http requests helpers
|
* Generate http requests helpers
|
||||||
*
|
*
|
||||||
@@ -103,6 +123,8 @@ const getTHMixin = require('./mixins/temphumd/getTHMixin');
|
|||||||
const getDevicesMixin = require('./mixins/devices/getDevicesMixin');
|
const getDevicesMixin = require('./mixins/devices/getDevicesMixin');
|
||||||
const getDeviceMixin = require('./mixins/devices/getDeviceMixin');
|
const getDeviceMixin = require('./mixins/devices/getDeviceMixin');
|
||||||
const getDeviceChannelCountMixin = require('./mixins/devices/getDeviceChannelCountMixin');
|
const getDeviceChannelCountMixin = require('./mixins/devices/getDeviceChannelCountMixin');
|
||||||
|
const getLocalIpMixin = require('./mixins/devices/getLocalIpMixin');
|
||||||
|
const saveDevicesCacheMixin = require('./mixins/devices/saveDevicesCacheMixin');
|
||||||
|
|
||||||
/* LOAD MIXINS: firmware */
|
/* LOAD MIXINS: firmware */
|
||||||
const getFirmwareVersionMixin = require('./mixins/firmware/getFirmwareVersionMixin');
|
const getFirmwareVersionMixin = require('./mixins/firmware/getFirmwareVersionMixin');
|
||||||
@@ -133,7 +155,9 @@ Object.assign(
|
|||||||
eWeLink.prototype,
|
eWeLink.prototype,
|
||||||
getDevicesMixin,
|
getDevicesMixin,
|
||||||
getDeviceMixin,
|
getDeviceMixin,
|
||||||
getDeviceChannelCountMixin
|
getDeviceChannelCountMixin,
|
||||||
|
getLocalIpMixin,
|
||||||
|
saveDevicesCacheMixin
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.assign(
|
Object.assign(
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ const getDeviceMixin = {
|
|||||||
* @returns {Promise<{msg: string, error: *}>}
|
* @returns {Promise<{msg: string, error: *}>}
|
||||||
*/
|
*/
|
||||||
async getDevice(deviceId) {
|
async getDevice(deviceId) {
|
||||||
|
if (this.devicesCache) {
|
||||||
|
return this.devicesCache.find(dev => dev.deviceid === deviceId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
const devices = await this.getDevices();
|
const devices = await this.getDevices();
|
||||||
|
|
||||||
const error = _get(devices, 'error', false);
|
const error = _get(devices, 'error', false);
|
||||||
|
|||||||
17
mixins/devices/getLocalIpMixin.js
Normal file
17
mixins/devices/getLocalIpMixin.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const getLocalIpMixin = {
|
||||||
|
/**
|
||||||
|
* Get local IP address from a given MAC
|
||||||
|
*
|
||||||
|
* @param device
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
getLocalIp(device) {
|
||||||
|
const mac = device.extra.extra.staMac;
|
||||||
|
const arpItem = this.arpTable.find(
|
||||||
|
item => item.mac.toLowerCase() === mac.toLowerCase()
|
||||||
|
);
|
||||||
|
return arpItem.ip;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getLocalIpMixin;
|
||||||
32
mixins/devices/saveDevicesCacheMixin.js
Normal file
32
mixins/devices/saveDevicesCacheMixin.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const { _get } = require('../../lib/helpers');
|
||||||
|
|
||||||
|
const saveDevicesCacheMixin = {
|
||||||
|
/**
|
||||||
|
* Save devices cache file (useful for using zeroconf)
|
||||||
|
* @returns {Promise<string|{msg: string, error: number}|*|Device[]|{msg: string, error: number}>}
|
||||||
|
*/
|
||||||
|
async saveDevicesCache(fileName = './devices-cache.json') {
|
||||||
|
const devices = await this.getDevices();
|
||||||
|
|
||||||
|
const error = _get(devices, 'error', false);
|
||||||
|
|
||||||
|
if (error || !devices) {
|
||||||
|
console.log(devices);
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonContent = JSON.stringify(devices, null, 2);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(fileName, jsonContent, 'utf8');
|
||||||
|
return { status: 'ok', file: fileName };
|
||||||
|
} catch (e) {
|
||||||
|
console.log('An error occured while writing JSON Object to File.');
|
||||||
|
return { error: e.toString() };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = saveDevicesCacheMixin;
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
const { _get } = require('../../lib/helpers');
|
const { _get } = require('../../lib/helpers');
|
||||||
|
|
||||||
const { getDeviceChannelCount } = require('../../lib/ewelink-helper');
|
const { getDeviceChannelCount } = require('../../lib/ewelink-helper');
|
||||||
const { ChangeState } = require('../../classes/PowerState');
|
const {
|
||||||
|
ChangeState,
|
||||||
|
ChangeStateZeroconf,
|
||||||
|
} = require('../../classes/PowerState');
|
||||||
|
|
||||||
const setDevicePowerState = {
|
const setDevicePowerState = {
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +56,16 @@ const setDevicePowerState = {
|
|||||||
params.switch = stateToSwitch;
|
params.switch = stateToSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.devicesCache) {
|
||||||
|
return ChangeStateZeroconf.set({
|
||||||
|
url: this.getZeroconfUrl(device),
|
||||||
|
device,
|
||||||
|
params,
|
||||||
|
switches,
|
||||||
|
state: stateToSwitch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const actionParams = {
|
const actionParams = {
|
||||||
apiUrl: this.getApiWebSocket(),
|
apiUrl: this.getApiWebSocket(),
|
||||||
at: this.at,
|
at: this.at,
|
||||||
|
|||||||
@@ -44,16 +44,6 @@ const getCredentialsMixin = {
|
|||||||
this.at = _get(response, 'at', '');
|
this.at = _get(response, 'at', '');
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* DEPRECATED: keept for backward compatibility
|
|
||||||
* Will be removed on version 2.0
|
|
||||||
*
|
|
||||||
* @returns {Promise<{msg: string, error: *}>}
|
|
||||||
*/
|
|
||||||
async login() {
|
|
||||||
return this.getCredentials();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = getCredentialsMixin;
|
module.exports = getCredentialsMixin;
|
||||||
|
|||||||
87
package-lock.json
generated
87
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ewelink-api",
|
"name": "ewelink-api",
|
||||||
"version": "1.10.0",
|
"version": "2.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -610,6 +610,14 @@
|
|||||||
"commander": "^2.11.0"
|
"commander": "^2.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"arpping": {
|
||||||
|
"version": "github:skydiver/arpping#7fa7085fc55e8a0c5b3893d25505e79ba93fdb31",
|
||||||
|
"from": "github:skydiver/arpping",
|
||||||
|
"requires": {
|
||||||
|
"child_process": "^1.0.2",
|
||||||
|
"os": "^0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"arr-diff": {
|
"arr-diff": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||||
@@ -1053,6 +1061,11 @@
|
|||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"child_process": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o="
|
||||||
|
},
|
||||||
"chnl": {
|
"chnl": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/chnl/-/chnl-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/chnl/-/chnl-0.5.0.tgz",
|
||||||
@@ -1233,6 +1246,11 @@
|
|||||||
"which": "^1.2.9"
|
"which": "^1.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"crypto-js": {
|
||||||
|
"version": "3.1.9-1",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz",
|
||||||
|
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
|
||||||
|
},
|
||||||
"cssom": {
|
"cssom": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||||
@@ -2171,7 +2189,8 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"aproba": {
|
"aproba": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
@@ -2192,12 +2211,14 @@
|
|||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
@@ -2212,17 +2233,20 @@
|
|||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -2339,7 +2363,8 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
@@ -2351,6 +2376,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -2365,6 +2391,7 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
@@ -2372,12 +2399,14 @@
|
|||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
@@ -2396,6 +2425,7 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
@@ -2476,7 +2506,8 @@
|
|||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@@ -2488,6 +2519,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
@@ -2573,7 +2605,8 @@
|
|||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
@@ -2609,6 +2642,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@@ -2628,6 +2662,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -2671,12 +2706,14 @@
|
|||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2768,9 +2805,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"handlebars": {
|
"handlebars": {
|
||||||
"version": "4.2.0",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
|
||||||
"integrity": "sha512-Kb4xn5Qh1cxAKvQnzNWZ512DhABzyFNmsaJf3OAkWNa4NkaqWcNI8Tao8Tasi0/F4JD9oyG0YxuFyvyR57d+Gw==",
|
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"neo-async": "^2.6.0",
|
"neo-async": "^2.6.0",
|
||||||
@@ -4417,6 +4454,11 @@
|
|||||||
"wordwrap": "~1.0.0"
|
"wordwrap": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"os": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M="
|
||||||
|
},
|
||||||
"os-tmpdir": {
|
"os-tmpdir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||||
@@ -5732,16 +5774,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.6.0",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.0.tgz",
|
||||||
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
|
"integrity": "sha512-PC/ee458NEMITe1OufAjal65i6lB58R1HWMRcxwvdz1UopW0DYqlRL3xdu3IcTvTXsB02CRHykidkTRL+A3hQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"commander": "~2.20.0",
|
"commander": "~2.20.3",
|
||||||
"source-map": "~0.6.1"
|
"source-map": "~0.6.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ewelink-api",
|
"name": "ewelink-api",
|
||||||
"version": "1.10.0",
|
"version": "2.0.0",
|
||||||
"description": "eWeLink API for Node.js",
|
"description": "eWeLink API for Node.js",
|
||||||
"author": "Martín M.",
|
"author": "Martín M.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -37,6 +37,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"arpping": "github:skydiver/arpping",
|
||||||
|
"crypto-js": "^3.1.9-1",
|
||||||
"delay": "^4.3.0",
|
"delay": "^4.3.0",
|
||||||
"nonce": "^1.0.4",
|
"nonce": "^1.0.4",
|
||||||
"random": "^2.1.1",
|
"random": "^2.1.1",
|
||||||
|
|||||||
20
test/_setup/credentials.example.js
Normal file
20
test/_setup/credentials.example.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
// eWeLink credentials
|
||||||
|
email: '<your ewelink email>',
|
||||||
|
password: '<your ewelink password>',
|
||||||
|
region: '<your ewelink region>',
|
||||||
|
|
||||||
|
// zeroconf
|
||||||
|
localIp: '<your network ip addreess>',
|
||||||
|
localIpInvalid: '<an invalid network address>',
|
||||||
|
|
||||||
|
// devices
|
||||||
|
singleChannelDeviceId: '<your device id>',
|
||||||
|
deviceIdWithPower: '<your device id>',
|
||||||
|
deviceIdWithoutPower: '<your device id>',
|
||||||
|
deviceIdWithTempAndHum: '<your device id>',
|
||||||
|
deviceIdWithoutTempAndHum: '<your device id>',
|
||||||
|
fourChannelsDevice: '<your device id>',
|
||||||
|
outdatedFirmwareDevice: '<your device id>',
|
||||||
|
updatedFirmwareDevice: '<your device id>',
|
||||||
|
};
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"email": "<your ewelink email>",
|
|
||||||
"password": "<your ewelink password>",
|
|
||||||
"region": "<your ewelink region>",
|
|
||||||
"singleChannelDeviceId": "<your device id>",
|
|
||||||
"deviceIdWithPower": "<your device id>",
|
|
||||||
"deviceIdWithoutPower": "<your device id>",
|
|
||||||
"deviceIdWithTempAndHum": "<your device id>",
|
|
||||||
"deviceIdWithoutTempAndHum": "<your device id>",
|
|
||||||
"fourChannelsDevice": "<your device id>",
|
|
||||||
"outdatedFirmwareDevice": "<your device id>",
|
|
||||||
"updatedFirmwareDevice": "<your device id>"
|
|
||||||
}
|
|
||||||
@@ -56,7 +56,7 @@ const regionExpectations = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
credentialsExpectations: credentialsExpectations,
|
credentialsExpectations,
|
||||||
allDevicesExpectations,
|
allDevicesExpectations,
|
||||||
specificDeviceExpectations,
|
specificDeviceExpectations,
|
||||||
rawPowerUsageExpectations,
|
rawPowerUsageExpectations,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
password,
|
password,
|
||||||
singleChannelDeviceId,
|
singleChannelDeviceId,
|
||||||
fourChannelsDevice,
|
fourChannelsDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
credentialsExpectations,
|
credentialsExpectations,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
password,
|
password,
|
||||||
singleChannelDeviceId,
|
singleChannelDeviceId,
|
||||||
fourChannelsDevice,
|
fourChannelsDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
credentialsExpectations,
|
credentialsExpectations,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const {
|
|||||||
singleChannelDeviceId,
|
singleChannelDeviceId,
|
||||||
outdatedFirmwareDevice,
|
outdatedFirmwareDevice,
|
||||||
updatedFirmwareDevice,
|
updatedFirmwareDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const { firmwareExpectations } = require('./_setup/expectations');
|
const { firmwareExpectations } = require('./_setup/expectations');
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const {
|
|||||||
singleChannelDeviceId,
|
singleChannelDeviceId,
|
||||||
deviceIdWithPower,
|
deviceIdWithPower,
|
||||||
fourChannelsDevice,
|
fourChannelsDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
describe('invalid credentials', () => {
|
describe('invalid credentials', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const {
|
|||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
deviceIdWithPower,
|
deviceIdWithPower,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rawPowerUsageExpectations,
|
rawPowerUsageExpectations,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
password,
|
password,
|
||||||
deviceIdWithoutTempAndHum,
|
deviceIdWithoutTempAndHum,
|
||||||
deviceIdWithTempAndHum: thDevice,
|
deviceIdWithTempAndHum: thDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
describe('current temperature and humidity: node script', () => {
|
describe('current temperature and humidity: node script', () => {
|
||||||
let conn;
|
let conn;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const ewelink = require('../main');
|
const ewelink = require('../main');
|
||||||
|
|
||||||
const { email, password, region } = require('./_setup/credentials.json');
|
const { email, password, region } = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const { regionExpectations } = require('./_setup/expectations');
|
const { regionExpectations } = require('./_setup/expectations');
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const {
|
|||||||
password,
|
password,
|
||||||
deviceIdWithoutPower,
|
deviceIdWithoutPower,
|
||||||
fourChannelsDevice,
|
fourChannelsDevice,
|
||||||
} = require('./_setup/credentials.json');
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
const { credentialsExpectations } = require('./_setup/expectations');
|
const { credentialsExpectations } = require('./_setup/expectations');
|
||||||
|
|
||||||
|
|||||||
120
test/zeroconf.spec.js
Normal file
120
test/zeroconf.spec.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
const ewelink = require('../main');
|
||||||
|
const Zeroconf = require('../classes/Zeroconf');
|
||||||
|
|
||||||
|
const {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
region,
|
||||||
|
localIp,
|
||||||
|
localIpInvalid,
|
||||||
|
} = require('./_setup/credentials.js');
|
||||||
|
|
||||||
|
const { allDevicesExpectations } = require('./_setup/expectations');
|
||||||
|
|
||||||
|
describe('zeroconf: save devices to cache file', () => {
|
||||||
|
test('can save cached devices file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const file = './test/_setup/devices-cache.json';
|
||||||
|
const conn = new ewelink({ region, email, password });
|
||||||
|
const result = await conn.saveDevicesCache(file);
|
||||||
|
expect(typeof result).toBe('object');
|
||||||
|
expect(result.status).toBe('ok');
|
||||||
|
expect(result.file).toBe(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error saving cached devices file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const file = '/tmp/non-existent-folder/devices-cache.json';
|
||||||
|
const conn = new ewelink({ region, email, password });
|
||||||
|
const result = await conn.saveDevicesCache(file);
|
||||||
|
expect(typeof result).toBe('object');
|
||||||
|
expect(result.error).toContain('ENOENT: no such file or directory');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid credentials trying to create cached devices file', async () => {
|
||||||
|
const file = '/tmp/non-existent-folder/devices-cache.json';
|
||||||
|
const conn = new ewelink({ email: 'invalid', password: 'credentials' });
|
||||||
|
const result = await conn.saveDevicesCache(file);
|
||||||
|
expect(typeof result).toBe('object');
|
||||||
|
expect(result.msg).toBe('Authentication error');
|
||||||
|
expect(result.error).toBe(401);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('zeroconf: save arp table to file', () => {
|
||||||
|
test('can save arp table file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const file = './test/_setup/arp-table.json';
|
||||||
|
const arpTable = await Zeroconf.saveArpTable({
|
||||||
|
ip: localIp,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
expect(typeof arpTable).toBe('object');
|
||||||
|
expect(arpTable.status).toBe('ok');
|
||||||
|
expect(arpTable.file).toBe(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error saving arp table file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const file = '/tmp/non-existent-folder/arp-table.json';
|
||||||
|
const arpTable = await Zeroconf.saveArpTable({
|
||||||
|
ip: localIp,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
expect(typeof arpTable).toBe('object');
|
||||||
|
expect(arpTable.error).toContain('ENOENT: no such file or directory');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error saving arp table file with invalid local network', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const file = './test/_setup/arp-table.json';
|
||||||
|
const arpTable = await Zeroconf.saveArpTable({
|
||||||
|
ip: localIpInvalid,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
expect(typeof arpTable).toBe('object');
|
||||||
|
expect(arpTable.error).toBe('Error: range must not be empty');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('zeroconf: load devices to cache file', () => {
|
||||||
|
test('can load cached devices file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const conn = new ewelink({ region, email, password });
|
||||||
|
const devices = await conn.getDevices();
|
||||||
|
const devicesCache = await Zeroconf.loadCachedDevices(
|
||||||
|
'./test/_setup/devices-cache.json'
|
||||||
|
);
|
||||||
|
expect(typeof devicesCache).toBe('object');
|
||||||
|
expect(devicesCache.length).toBe(devices.length);
|
||||||
|
expect(devices[0]).toMatchObject(allDevicesExpectations);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error trying to load invalidcached devices file', async () => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
const devicesCache = await Zeroconf.loadCachedDevices('file-not-found');
|
||||||
|
expect(typeof devicesCache).toBe('object');
|
||||||
|
expect(devicesCache.error).toContain('ENOENT: no such file or directory');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('zeroconf: load arp table file', () => {
|
||||||
|
test('can load arp table file', async () => {
|
||||||
|
const arpTable = await Zeroconf.loadArpTable(
|
||||||
|
'./test/_setup/arp-table.json'
|
||||||
|
);
|
||||||
|
expect(typeof arpTable).toBe('object');
|
||||||
|
expect(arpTable[0]).toMatchObject({
|
||||||
|
ip: expect.any(String),
|
||||||
|
mac: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error trying to load invalidcached devices file', async () => {
|
||||||
|
const arpTable = await Zeroconf.loadArpTable(
|
||||||
|
'/tmp/non-existent-folder/arp-table.json'
|
||||||
|
);
|
||||||
|
expect(typeof arpTable).toBe('object');
|
||||||
|
expect(arpTable.error).toContain('ENOENT: no such file or directory');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user