@pipechela/ewelink-api 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -0
- package/index.d.ts +223 -0
- package/main.js +114 -0
- package/package.json +67 -0
- package/src/classes/ChangeStateZeroconf.js +34 -0
- package/src/classes/DevicePowerUsageRaw.js +58 -0
- package/src/classes/WebSocket.js +57 -0
- package/src/classes/Zeroconf.js +91 -0
- package/src/data/constants.js +7 -0
- package/src/data/devices-channel-length.json +20 -0
- package/src/data/devices-type-uuid.json +55 -0
- package/src/data/errors.js +23 -0
- package/src/helpers/device-control.js +58 -0
- package/src/helpers/ewelink.js +68 -0
- package/src/helpers/utilities.js +29 -0
- package/src/mixins/checkDeviceUpdate.js +48 -0
- package/src/mixins/checkDevicesUpdates.js +54 -0
- package/src/mixins/deviceControl.js +272 -0
- package/src/mixins/getCredentials.js +54 -0
- package/src/mixins/getDevice.js +37 -0
- package/src/mixins/getDeviceChannelCount.js +26 -0
- package/src/mixins/getDeviceCurrentTH.js +55 -0
- package/src/mixins/getDeviceIP.js +15 -0
- package/src/mixins/getDevicePowerState.js +47 -0
- package/src/mixins/getDevicePowerUsage.js +27 -0
- package/src/mixins/getDevicePowerUsageRaw.js +30 -0
- package/src/mixins/getDevices.js +37 -0
- package/src/mixins/getFirmwareVersion.js +23 -0
- package/src/mixins/getRegion.js +23 -0
- package/src/mixins/index.js +43 -0
- package/src/mixins/makeRequest.js +54 -0
- package/src/mixins/openWebSocket.js +44 -0
- package/src/mixins/saveDevicesCache.js +29 -0
- package/src/mixins/setDevicePowerState.js +90 -0
- package/src/mixins/toggleDevice.js +13 -0
- package/src/parsers/parseFirmwareUpdates.js +16 -0
- package/src/parsers/parsePowerUsage.js +38 -0
- package/src/payloads/credentialsPayload.js +13 -0
- package/src/payloads/deviceStatus.js +12 -0
- package/src/payloads/wssLoginPayload.js +19 -0
- package/src/payloads/wssUpdatePayload.js +17 -0
- package/src/payloads/zeroConfUpdatePayload.js +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Martin M.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @pipechela/ewelink-api
|
|
2
|
+
> eWeLink API for JavaScript (Fork with fixes)
|
|
3
|
+
|
|
4
|
+
This repository is a fork of [skydiver/ewelink-api](https://github.com/skydiver/ewelink-api) with the following changes:
|
|
5
|
+
- fixed mac address parsing
|
|
6
|
+
|
|
7
|
+
## Key features
|
|
8
|
+
* can run on browsers, node scripts or serverless environment
|
|
9
|
+
* set on/off devices
|
|
10
|
+
* get power consumption on devices like Sonoff POW
|
|
11
|
+
* listen for devices events
|
|
12
|
+
* using zeroconf (LAN mode), no internet connection required
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
```sh
|
|
17
|
+
npm install @pipechela/ewelink-api
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
Check library documentation and examples in the `docs` folder or at the [original repository](https://github.com/skydiver/ewelink-api/tree/master/docs).
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// Type definitions for ewelink-api
|
|
2
|
+
// Definitions by: Alexander Méhes https://github.com/BMXsanko
|
|
3
|
+
|
|
4
|
+
declare module '@pipechela/ewelink-api' {
|
|
5
|
+
export default eWelink;
|
|
6
|
+
|
|
7
|
+
class eWelink {
|
|
8
|
+
constructor({ }: { email: string; password: string; } | { at: string; } | { at: string; apiKey: string; });
|
|
9
|
+
/**
|
|
10
|
+
* Login into eWeLink API and get auth credentials.
|
|
11
|
+
*/
|
|
12
|
+
login(): Promise<LoginInfo>
|
|
13
|
+
/**
|
|
14
|
+
* Opens a socket connection to eWeLink and listen for real-time events.
|
|
15
|
+
*/
|
|
16
|
+
openWebSocket(callback: (data: {}) => void): Promise<any>
|
|
17
|
+
/**
|
|
18
|
+
* Returns a list of devices associated to logged account.
|
|
19
|
+
*/
|
|
20
|
+
getDevices(): Promise<Device[]>
|
|
21
|
+
/**
|
|
22
|
+
* Return information for specified device.
|
|
23
|
+
*/
|
|
24
|
+
getDevice(deviceId: string): Promise<Device>
|
|
25
|
+
/**
|
|
26
|
+
* Query for specified device power status.
|
|
27
|
+
*/
|
|
28
|
+
getDevicePowerState(deviceId: string, channel?: number): Promise<DeviceState>
|
|
29
|
+
/**
|
|
30
|
+
* Change specified device power state.
|
|
31
|
+
*/
|
|
32
|
+
setDevicePowerState(deviceId: string, state?: string, channel?: number): Promise<DeviceState>
|
|
33
|
+
/**
|
|
34
|
+
* Switch specified device current power state.
|
|
35
|
+
*/
|
|
36
|
+
toggleDevice(deviceId: string, channel?: number): Promise<DeviceState>
|
|
37
|
+
/**
|
|
38
|
+
* Returns current month power usage on device who supports electricity records, like Sonoff POW.
|
|
39
|
+
*/
|
|
40
|
+
getDevicePowerUsage(deviceId: string): Promise<PowerUsage>
|
|
41
|
+
/**
|
|
42
|
+
* Return current temperature and humidity for specified device.
|
|
43
|
+
*/
|
|
44
|
+
getDeviceCurrentTH(deviceId: string): Promise<TemperatureHumidity>
|
|
45
|
+
/**
|
|
46
|
+
* Return current temperature for specified device.
|
|
47
|
+
*/
|
|
48
|
+
getDeviceCurrentTemperature(deviceId: string): Promise<TemperatureHumidity>
|
|
49
|
+
/**
|
|
50
|
+
* Return current temperature for specified device.
|
|
51
|
+
*/
|
|
52
|
+
getDeviceCurrentHumidity(deviceId: string): Promise<TemperatureHumidity>
|
|
53
|
+
/**
|
|
54
|
+
* Return total channels for specified device.
|
|
55
|
+
*/
|
|
56
|
+
getDeviceChannelCount(deviceId: string): Promise<SwitchCount>
|
|
57
|
+
/**
|
|
58
|
+
* Return firmware version for specified device.
|
|
59
|
+
*/
|
|
60
|
+
getFirmwareVersion(deviceId: string): Promise<FirmwareVersion>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Device {
|
|
64
|
+
_id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
type: string;
|
|
67
|
+
deviceid: string;
|
|
68
|
+
apikey: string;
|
|
69
|
+
extra: Extra2;
|
|
70
|
+
__v: number;
|
|
71
|
+
onlineTime: string;
|
|
72
|
+
ip: string;
|
|
73
|
+
location: string;
|
|
74
|
+
offlineTime: string;
|
|
75
|
+
deviceStatus: string;
|
|
76
|
+
tags: Tags;
|
|
77
|
+
settings: Settings;
|
|
78
|
+
devGroups: any[];
|
|
79
|
+
groups: any[];
|
|
80
|
+
params: Params;
|
|
81
|
+
online: boolean;
|
|
82
|
+
createdAt: string;
|
|
83
|
+
group: string;
|
|
84
|
+
sharedTo: any[];
|
|
85
|
+
devicekey: string;
|
|
86
|
+
deviceUrl: string;
|
|
87
|
+
brandName: string;
|
|
88
|
+
showBrand: boolean;
|
|
89
|
+
brandLogoUrl: string;
|
|
90
|
+
productModel: string;
|
|
91
|
+
devConfig: DevConfig;
|
|
92
|
+
uiid: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface DevConfig {
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface Params {
|
|
99
|
+
pulseWidth: number;
|
|
100
|
+
pulse: string;
|
|
101
|
+
init: number;
|
|
102
|
+
sledOnline: string;
|
|
103
|
+
version: number;
|
|
104
|
+
timers: any[];
|
|
105
|
+
controlType: string;
|
|
106
|
+
partnerApikey: string;
|
|
107
|
+
bindInfos: BindInfos;
|
|
108
|
+
rssi: number;
|
|
109
|
+
staMac: string;
|
|
110
|
+
startup: string;
|
|
111
|
+
fwVersion: string;
|
|
112
|
+
switch: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface BindInfos {
|
|
116
|
+
gaction: string[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface Settings {
|
|
120
|
+
alarmNotify: number;
|
|
121
|
+
opsHistory: number;
|
|
122
|
+
opsNotify: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface Tags {
|
|
126
|
+
m_4434_sany: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface Extra2 {
|
|
130
|
+
_id: string;
|
|
131
|
+
extra: Extra;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface Extra {
|
|
135
|
+
description: string;
|
|
136
|
+
brandId: string;
|
|
137
|
+
apmac: string;
|
|
138
|
+
mac: string;
|
|
139
|
+
ui: string;
|
|
140
|
+
modelInfo: string;
|
|
141
|
+
model: string;
|
|
142
|
+
manufacturer: string;
|
|
143
|
+
uiid: number;
|
|
144
|
+
staMac: string;
|
|
145
|
+
chipid: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface DeviceState {
|
|
149
|
+
status?: string;
|
|
150
|
+
state?: string;
|
|
151
|
+
error?: number;
|
|
152
|
+
msg?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface SwitchCount {
|
|
156
|
+
status?: string;
|
|
157
|
+
switchesAmount?: number;
|
|
158
|
+
error?: number;
|
|
159
|
+
msg?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface TemperatureHumidity {
|
|
163
|
+
status?: string;
|
|
164
|
+
temperature?: number;
|
|
165
|
+
humidity?: number;
|
|
166
|
+
error?: number;
|
|
167
|
+
msg?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface PowerUsage {
|
|
171
|
+
status?: string;
|
|
172
|
+
monthly?: number;
|
|
173
|
+
daily?: Daily[];
|
|
174
|
+
error?: number;
|
|
175
|
+
msg?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface FirmwareVersion {
|
|
179
|
+
status?: string;
|
|
180
|
+
fwVersion?: string;
|
|
181
|
+
error?: number;
|
|
182
|
+
msg?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface LoginInfo {
|
|
186
|
+
at: string;
|
|
187
|
+
rt: string;
|
|
188
|
+
user: User;
|
|
189
|
+
region: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface User {
|
|
193
|
+
_id: string;
|
|
194
|
+
email: string;
|
|
195
|
+
appId: string;
|
|
196
|
+
lang: string;
|
|
197
|
+
online: boolean;
|
|
198
|
+
onlineTime: string;
|
|
199
|
+
ip: string;
|
|
200
|
+
location: string;
|
|
201
|
+
offlineTime: string;
|
|
202
|
+
userStatus: string;
|
|
203
|
+
appInfos: AppInfo[];
|
|
204
|
+
isAccepEmailAd: boolean;
|
|
205
|
+
bindInfos: LoginBindInfos;
|
|
206
|
+
createdAt: string;
|
|
207
|
+
apikey: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface LoginBindInfos {
|
|
211
|
+
gaction: string[];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface AppInfo {
|
|
215
|
+
appVersion: string;
|
|
216
|
+
os: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface Daily {
|
|
220
|
+
day: number;
|
|
221
|
+
usage: number;
|
|
222
|
+
}
|
|
223
|
+
}
|
package/main.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const {
|
|
2
|
+
APP_ID: DEFAULT_APP_ID,
|
|
3
|
+
APP_SECRET: DEFAULT_APP_SECRET,
|
|
4
|
+
} = require('./src/data/constants');
|
|
5
|
+
|
|
6
|
+
const mixins = require('./src/mixins');
|
|
7
|
+
const errors = require('./src/data/errors');
|
|
8
|
+
|
|
9
|
+
class eWeLink {
|
|
10
|
+
constructor(parameters = {}) {
|
|
11
|
+
const {
|
|
12
|
+
region = 'us',
|
|
13
|
+
email = null,
|
|
14
|
+
phoneNumber = null,
|
|
15
|
+
password = null,
|
|
16
|
+
at = null,
|
|
17
|
+
apiKey = null,
|
|
18
|
+
devicesCache = null,
|
|
19
|
+
arpTable = null,
|
|
20
|
+
APP_ID = DEFAULT_APP_ID,
|
|
21
|
+
APP_SECRET = DEFAULT_APP_SECRET,
|
|
22
|
+
} = parameters;
|
|
23
|
+
|
|
24
|
+
const check = this.checkLoginParameters({
|
|
25
|
+
region,
|
|
26
|
+
email,
|
|
27
|
+
phoneNumber,
|
|
28
|
+
password,
|
|
29
|
+
at,
|
|
30
|
+
apiKey,
|
|
31
|
+
devicesCache,
|
|
32
|
+
arpTable,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (check === false) {
|
|
36
|
+
throw new Error(errors.invalidCredentials);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.region = region;
|
|
40
|
+
this.phoneNumber = phoneNumber;
|
|
41
|
+
this.email = email;
|
|
42
|
+
this.password = password;
|
|
43
|
+
this.at = at;
|
|
44
|
+
this.apiKey = apiKey;
|
|
45
|
+
this.devicesCache = devicesCache;
|
|
46
|
+
this.arpTable = arpTable;
|
|
47
|
+
|
|
48
|
+
this.APP_ID = APP_ID;
|
|
49
|
+
this.APP_SECRET = APP_SECRET;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line class-methods-use-this
|
|
53
|
+
checkLoginParameters(params) {
|
|
54
|
+
const { email, phoneNumber, password, devicesCache, arpTable, at } = params;
|
|
55
|
+
|
|
56
|
+
if (email !== null && phoneNumber !== null) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
(email !== null && password !== null) ||
|
|
62
|
+
(phoneNumber !== null && password !== null) ||
|
|
63
|
+
(devicesCache !== null && arpTable !== null) ||
|
|
64
|
+
at !== null
|
|
65
|
+
) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate eWeLink API URL
|
|
74
|
+
*
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
getApiUrl() {
|
|
78
|
+
return `https://${this.region}-api.coolkit.cc:8080/api`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Generate eWeLink OTA API URL
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
getOtaUrl() {
|
|
86
|
+
return `https://${this.region}-ota.coolkit.cc:8080/otaother`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generate eWeLink WebSocket URL
|
|
91
|
+
*
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
getApiWebSocket() {
|
|
95
|
+
return `wss://${this.region}-pconnect3.coolkit.cc:8080/api/ws`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate Zeroconf URL
|
|
100
|
+
* @param device
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
getZeroconfUrl(device) {
|
|
104
|
+
const ip = this.getDeviceIP(device);
|
|
105
|
+
if (!ip) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return `http://${ip}:8081/zeroconf`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Object.assign(eWeLink.prototype, mixins);
|
|
113
|
+
|
|
114
|
+
module.exports = eWeLink;
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipechela/ewelink-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "eWeLink API for Node.js (Fork by pipechela)",
|
|
5
|
+
"author": "pipechela",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"typings": "./index.d.ts",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pipechela/ewelink-api.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pipechela/ewelink-api/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ewelink",
|
|
17
|
+
"sonoff",
|
|
18
|
+
"api",
|
|
19
|
+
"node",
|
|
20
|
+
"nodejs"
|
|
21
|
+
],
|
|
22
|
+
"main": "main.js",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "jest --runInBand --verbose",
|
|
25
|
+
"coverage": "jest --runInBand --verbose --coverage --coverageDirectory=coverage/jest",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"lint:fix": "eslint . --fix"
|
|
28
|
+
},
|
|
29
|
+
"jest": {
|
|
30
|
+
"setupFilesAfterEnv": [
|
|
31
|
+
"<rootDir>/test/_setup/setupTests.js"
|
|
32
|
+
],
|
|
33
|
+
"testPathIgnorePatterns": [
|
|
34
|
+
"/node_modules/",
|
|
35
|
+
".cache"
|
|
36
|
+
],
|
|
37
|
+
"coverageReporters": [
|
|
38
|
+
"text",
|
|
39
|
+
"html"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"arpping": "github:skydiver/arpping",
|
|
44
|
+
"crypto-js": "^4.0.0",
|
|
45
|
+
"delay": "^4.4.0",
|
|
46
|
+
"node-fetch": "^2.6.1",
|
|
47
|
+
"random": "^2.2.0",
|
|
48
|
+
"websocket": "^1.0.32",
|
|
49
|
+
"websocket-as-promised": "^1.0.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"babel-eslint": "^10.1.0",
|
|
53
|
+
"eslint": "^7.11.0",
|
|
54
|
+
"eslint-config-airbnb": "^18.2.0",
|
|
55
|
+
"eslint-config-prettier": "^6.12.0",
|
|
56
|
+
"eslint-config-wesbos": "0.0.19",
|
|
57
|
+
"eslint-plugin-html": "^6.1.0",
|
|
58
|
+
"eslint-plugin-import": "^2.22.1",
|
|
59
|
+
"eslint-plugin-jsx-a11y": "^6.3.1",
|
|
60
|
+
"eslint-plugin-prettier": "^3.1.4",
|
|
61
|
+
"eslint-plugin-react": "^7.21.4",
|
|
62
|
+
"eslint-plugin-react-hooks": "^4.1.2",
|
|
63
|
+
"jest": "^26.5.3",
|
|
64
|
+
"nock": "^12.0.3",
|
|
65
|
+
"prettier": "^1.19.1"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fetch = require('node-fetch');
|
|
2
|
+
|
|
3
|
+
const WebSocket = require('./WebSocket');
|
|
4
|
+
const zeroConfUpdatePayload = require('../payloads/zeroConfUpdatePayload');
|
|
5
|
+
const { _get } = require('../helpers/utilities');
|
|
6
|
+
|
|
7
|
+
class ChangeStateZeroconf extends WebSocket {
|
|
8
|
+
static async set({ url, device, params, switches, state }) {
|
|
9
|
+
const selfApikey = device.apikey;
|
|
10
|
+
const deviceId = device.deviceid;
|
|
11
|
+
const deviceKey = device.devicekey;
|
|
12
|
+
|
|
13
|
+
const endpoint = switches ? 'switches' : 'switch';
|
|
14
|
+
|
|
15
|
+
const body = zeroConfUpdatePayload(selfApikey, deviceId, deviceKey, params);
|
|
16
|
+
|
|
17
|
+
const request = await fetch(`${url}/${endpoint}`, {
|
|
18
|
+
method: 'post',
|
|
19
|
+
body: JSON.stringify(body),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const response = await request.json();
|
|
23
|
+
|
|
24
|
+
const error = _get(response, 'error', false);
|
|
25
|
+
|
|
26
|
+
if (error === 403) {
|
|
27
|
+
return { error, msg: response.reason };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { status: 'ok', state };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = ChangeStateZeroconf;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const WebSocket = require('./WebSocket');
|
|
2
|
+
const wssLoginPayload = require('../payloads/wssLoginPayload');
|
|
3
|
+
const wssUpdatePayload = require('../payloads/wssUpdatePayload');
|
|
4
|
+
const { _get } = require('../helpers/utilities');
|
|
5
|
+
const errors = require('../data/errors');
|
|
6
|
+
|
|
7
|
+
class DevicePowerUsageRaw extends WebSocket {
|
|
8
|
+
/**
|
|
9
|
+
* Get specific device power usage (raw data)
|
|
10
|
+
*
|
|
11
|
+
* @param apiUrl
|
|
12
|
+
* @param at
|
|
13
|
+
* @param apiKey
|
|
14
|
+
* @param deviceId
|
|
15
|
+
* @returns {Promise<{error: string}|{data: {hundredDaysKwhData: *}, status: string}|{msg: any, error: *}|{msg: string, error: number}>}
|
|
16
|
+
*/
|
|
17
|
+
static async get({ apiUrl, at, apiKey, deviceId }) {
|
|
18
|
+
const payloadLogin = wssLoginPayload({ at, apiKey, appid: this.APP_ID });
|
|
19
|
+
|
|
20
|
+
const payloadUpdate = wssUpdatePayload({
|
|
21
|
+
apiKey,
|
|
22
|
+
deviceId,
|
|
23
|
+
params: { hundredDaysKwh: 'get' },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const response = await this.WebSocketRequest(apiUrl, [
|
|
27
|
+
payloadLogin,
|
|
28
|
+
payloadUpdate,
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
if (response.length === 1) {
|
|
32
|
+
return { error: errors.noPower };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const error = _get(response[1], 'error', false);
|
|
36
|
+
|
|
37
|
+
if (error === 403) {
|
|
38
|
+
return { error, msg: response[1].reason };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const hundredDaysKwhData = _get(
|
|
42
|
+
response[1],
|
|
43
|
+
'config.hundredDaysKwhData',
|
|
44
|
+
false
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!hundredDaysKwhData) {
|
|
48
|
+
return { error: errors.noPower };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
status: 'ok',
|
|
53
|
+
data: { hundredDaysKwhData },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = DevicePowerUsageRaw;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const W3CWebSocket = require('websocket').w3cwebsocket;
|
|
2
|
+
const WebSocketAsPromised = require('websocket-as-promised');
|
|
3
|
+
const delay = require('delay');
|
|
4
|
+
|
|
5
|
+
const errors = require('../data/errors');
|
|
6
|
+
|
|
7
|
+
class WebSocket {
|
|
8
|
+
/**
|
|
9
|
+
* Open WebSocket connection and send provided payloads
|
|
10
|
+
*
|
|
11
|
+
* @param url
|
|
12
|
+
* @param payloads
|
|
13
|
+
* @param delayTime
|
|
14
|
+
*
|
|
15
|
+
* @returns {Array}
|
|
16
|
+
*/
|
|
17
|
+
static async WebSocketRequest(url, payloads, ...{ delayTime = 1000 }) {
|
|
18
|
+
const wsp = new WebSocketAsPromised(url, {
|
|
19
|
+
createWebSocket: wss => new W3CWebSocket(wss),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const responses = [];
|
|
23
|
+
wsp.onMessage.addListener(message => responses.push(JSON.parse(message)));
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await wsp.open();
|
|
27
|
+
|
|
28
|
+
for (const payload of payloads) {
|
|
29
|
+
await wsp.send(payload);
|
|
30
|
+
await delay(delayTime);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await wsp.close();
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return this.customThrowError(e);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return responses;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse WebSocket errors and return user friendly messages
|
|
43
|
+
*
|
|
44
|
+
* @param e
|
|
45
|
+
*
|
|
46
|
+
* @returns {{error: string}|{msg: string, error: number}}
|
|
47
|
+
*/
|
|
48
|
+
static customThrowError(e) {
|
|
49
|
+
const loginError = e.message.indexOf('WebSocket is not opened');
|
|
50
|
+
if (loginError > -1) {
|
|
51
|
+
return { error: 406, msg: errors['406'] };
|
|
52
|
+
}
|
|
53
|
+
return { error: errors.unknown };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = WebSocket;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const arpping = require('arpping')({});
|
|
3
|
+
|
|
4
|
+
class Zeroconf {
|
|
5
|
+
/**
|
|
6
|
+
* Build the ARP table
|
|
7
|
+
* @param ip
|
|
8
|
+
* @returns {Promise<unknown>}
|
|
9
|
+
*/
|
|
10
|
+
static getArpTable(ip = null) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
arpping.discover(ip, (err, hosts) => {
|
|
13
|
+
if (err) {
|
|
14
|
+
return reject(err);
|
|
15
|
+
}
|
|
16
|
+
const arpTable = Zeroconf.fixMacAddresses(hosts);
|
|
17
|
+
return resolve(arpTable);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sometime arp command returns mac addresses without leading zeroes.
|
|
24
|
+
* @param hosts
|
|
25
|
+
*/
|
|
26
|
+
static fixMacAddresses(hosts) {
|
|
27
|
+
return hosts.map(host => {
|
|
28
|
+
const octets = host.mac.split(/[:\-]/);
|
|
29
|
+
|
|
30
|
+
const fixedMac = octets.map(octet => {
|
|
31
|
+
if (octet.length === 1) {
|
|
32
|
+
return `0${octet}`;
|
|
33
|
+
}
|
|
34
|
+
return octet;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
ip: host.ip,
|
|
39
|
+
mac: fixedMac.join(':'),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Save ARP table to local file
|
|
46
|
+
* @param config
|
|
47
|
+
* @returns {Promise<{error: string}|{file: {request: string; resolved: string} | any | string | string, status: string}>}
|
|
48
|
+
*/
|
|
49
|
+
static async saveArpTable(config = {}) {
|
|
50
|
+
const ip = config.ip || null;
|
|
51
|
+
const fileName = config.file || './arp-table.json';
|
|
52
|
+
try {
|
|
53
|
+
const arpTable = await Zeroconf.getArpTable(ip);
|
|
54
|
+
const jsonContent = JSON.stringify(arpTable, null, 2);
|
|
55
|
+
fs.writeFileSync(fileName, jsonContent, 'utf8');
|
|
56
|
+
return { status: 'ok', file: fileName };
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { error: e.toString() };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read ARP table file
|
|
64
|
+
* @param fileName
|
|
65
|
+
* @returns {Promise<{error: string}|any>}
|
|
66
|
+
*/
|
|
67
|
+
static async loadArpTable(fileName = './arp-table.json') {
|
|
68
|
+
try {
|
|
69
|
+
const jsonContent = await fs.readFileSync(fileName);
|
|
70
|
+
return JSON.parse(jsonContent);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return { error: e.toString() };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read devices cache file
|
|
78
|
+
* @param fileName
|
|
79
|
+
* @returns {Promise<{error: string}>}
|
|
80
|
+
*/
|
|
81
|
+
static async loadCachedDevices(fileName = './devices-cache.json') {
|
|
82
|
+
try {
|
|
83
|
+
const jsonContent = await fs.readFileSync(fileName);
|
|
84
|
+
return JSON.parse(jsonContent);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return { error: e.toString() };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = Zeroconf;
|