homebridge-roborock-vacuum 1.2.3 → 1.3.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/Plan.md +54 -0
- package/config.schema.json +15 -6
- package/dist/crypto.js +62 -0
- package/dist/crypto.js.map +1 -0
- package/dist/platform.js +16 -3
- package/dist/platform.js.map +1 -1
- package/dist/ui/index.js +161 -0
- package/dist/ui/index.js.map +1 -0
- package/homebridge-ui/public/index.html +58 -0
- package/homebridge-ui/public/index.js +250 -0
- package/homebridge-ui/public/styles.css +184 -0
- package/homebridge-ui/server.js +3 -0
- package/package.json +8 -6
- package/roborockLib/lib/deviceFeatures.js +40 -0
- package/roborockLib/lib/localConnector.js +50 -6
- package/roborockLib/lib/roborockAuth.js +147 -0
- package/roborockLib/roborockAPI.js +186 -29
- package/roborockLib/test.js +2 -1
- package/roborockLib/data/UserData +0 -4
- package/roborockLib/data/clientID +0 -4
- package/roborockLib/userdata.json +0 -24
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const axios = require("axios");
|
|
5
|
+
|
|
6
|
+
const API_V3_SIGN = "api/v3/key/sign";
|
|
7
|
+
const API_V4_LOGIN_CODE = "api/v4/auth/email/login/code";
|
|
8
|
+
const API_V4_LOGIN_PASSWORD = "api/v4/auth/email/login/pwd";
|
|
9
|
+
const API_V4_EMAIL_CODE = "api/v4/email/code/send";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_HEADERS = {
|
|
12
|
+
header_appversion: "4.54.02",
|
|
13
|
+
header_clientlang: "en",
|
|
14
|
+
header_phonemodel: "Pixel 7",
|
|
15
|
+
header_phonesystem: "Android",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function normalizeBaseURL(baseURL) {
|
|
19
|
+
if (!baseURL) {
|
|
20
|
+
return "usiot.roborock.com";
|
|
21
|
+
}
|
|
22
|
+
return baseURL.replace(/^https?:\/\//i, "").replace(/\/+$/, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getRegionConfig(baseURL) {
|
|
26
|
+
const lower = normalizeBaseURL(baseURL).toLowerCase();
|
|
27
|
+
if (lower.includes("euiot")) {
|
|
28
|
+
return { country: "DE", countryCode: "49" };
|
|
29
|
+
}
|
|
30
|
+
if (lower.includes("usiot")) {
|
|
31
|
+
return { country: "US", countryCode: "1" };
|
|
32
|
+
}
|
|
33
|
+
if (lower.includes("cniot")) {
|
|
34
|
+
return { country: "CN", countryCode: "86" };
|
|
35
|
+
}
|
|
36
|
+
if (lower.includes("api.roborock.com")) {
|
|
37
|
+
return { country: "SG", countryCode: "65" };
|
|
38
|
+
}
|
|
39
|
+
return { country: "US", countryCode: "1" };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function encryptPassword(password, k) {
|
|
43
|
+
const derivedKey = k.slice(4) + k.slice(0, 4);
|
|
44
|
+
const cipher = crypto.createCipheriv("aes-128-ecb", Buffer.from(derivedKey, "utf-8"), null);
|
|
45
|
+
cipher.setAutoPadding(true);
|
|
46
|
+
let encrypted = cipher.update(password, "utf8", "base64");
|
|
47
|
+
encrypted += cipher.final("base64");
|
|
48
|
+
return encrypted;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createLoginApi({ baseURL, username, clientID, language }) {
|
|
52
|
+
return axios.create({
|
|
53
|
+
baseURL: `https://${normalizeBaseURL(baseURL)}`,
|
|
54
|
+
headers: {
|
|
55
|
+
header_clientid: crypto.createHash("md5").update(username).update(clientID).digest().toString("base64"),
|
|
56
|
+
header_clientlang: language || DEFAULT_HEADERS.header_clientlang,
|
|
57
|
+
header_appversion: DEFAULT_HEADERS.header_appversion,
|
|
58
|
+
header_phonemodel: DEFAULT_HEADERS.header_phonemodel,
|
|
59
|
+
header_phonesystem: DEFAULT_HEADERS.header_phonesystem,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function signRequest(loginApi, s) {
|
|
65
|
+
const res = await loginApi.post(`${API_V3_SIGN}?s=${s}`);
|
|
66
|
+
return res.data && res.data.data ? res.data.data : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function requestEmailCode(loginApi, email) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
params.append("type", "login");
|
|
72
|
+
params.append("email", email);
|
|
73
|
+
params.append("platform", "");
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const res = await loginApi.post(API_V4_EMAIL_CODE, params.toString());
|
|
77
|
+
if (res.data && res.data.code !== 200) {
|
|
78
|
+
throw new Error(`Send code failed: ${res.data.msg || "Unknown error"} (Code: ${res.data.code})`);
|
|
79
|
+
}
|
|
80
|
+
return res.data;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error.response && error.response.data) {
|
|
83
|
+
throw new Error(`Send code failed: ${JSON.stringify(error.response.data)}`);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function loginWithCode(loginApi, { email, code, country, countryCode, k, s }) {
|
|
90
|
+
const headers = {
|
|
91
|
+
"x-mercy-k": k,
|
|
92
|
+
"x-mercy-ks": s,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const params = new URLSearchParams({
|
|
96
|
+
country,
|
|
97
|
+
countryCode,
|
|
98
|
+
email,
|
|
99
|
+
code,
|
|
100
|
+
majorVersion: "14",
|
|
101
|
+
minorVersion: "0",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const res = await loginApi.post(API_V4_LOGIN_CODE, params.toString(), { headers });
|
|
106
|
+
return res.data;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.response && error.response.data) {
|
|
109
|
+
return error.response.data;
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function loginByPassword(loginApi, { email, password, k, s }) {
|
|
116
|
+
const headers = {
|
|
117
|
+
"x-mercy-k": k,
|
|
118
|
+
"x-mercy-ks": s,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const params = new URLSearchParams({
|
|
122
|
+
email,
|
|
123
|
+
password: encryptPassword(password, k),
|
|
124
|
+
majorVersion: "14",
|
|
125
|
+
minorVersion: "0",
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const res = await loginApi.post(API_V4_LOGIN_PASSWORD, params.toString(), { headers });
|
|
130
|
+
return res.data;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error.response && error.response.data) {
|
|
133
|
+
return error.response.data;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
createLoginApi,
|
|
141
|
+
getRegionConfig,
|
|
142
|
+
normalizeBaseURL,
|
|
143
|
+
signRequest,
|
|
144
|
+
requestEmailCode,
|
|
145
|
+
loginWithCode,
|
|
146
|
+
loginByPassword,
|
|
147
|
+
};
|
|
@@ -8,6 +8,8 @@ const express = require("express");
|
|
|
8
8
|
const { debug } = require("console");
|
|
9
9
|
const { get } = require("http");
|
|
10
10
|
|
|
11
|
+
const roborockAuth = require("./lib/roborockAuth");
|
|
12
|
+
|
|
11
13
|
const rrLocalConnector = require("./lib/localConnector").localConnector;
|
|
12
14
|
const roborock_mqtt_connector = require("./lib/roborock_mqtt_connector").roborock_mqtt_connector;
|
|
13
15
|
const rrMessage = require("./lib/message").message;
|
|
@@ -67,6 +69,13 @@ class Roborock {
|
|
|
67
69
|
this.name = "roborock";
|
|
68
70
|
this.deviceNotify = null;
|
|
69
71
|
this.baseURL = options.baseURL || "usiot.roborock.com";
|
|
72
|
+
|
|
73
|
+
this.userData = options.userData || null;
|
|
74
|
+
this.authState = {
|
|
75
|
+
twoFactorRequired: false,
|
|
76
|
+
statusMessage: "",
|
|
77
|
+
};
|
|
78
|
+
this.pendingAuth = null;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
isInited() {
|
|
@@ -113,7 +122,7 @@ class Roborock {
|
|
|
113
122
|
|
|
114
123
|
try {
|
|
115
124
|
if(id == "UserData" || id == "clientID"){
|
|
116
|
-
return JSON.parse(fs.readFileSync(
|
|
125
|
+
return JSON.parse(fs.readFileSync(this.getPersistPath(id), 'utf8'));
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
return this.states[id];
|
|
@@ -129,7 +138,8 @@ class Roborock {
|
|
|
129
138
|
try {
|
|
130
139
|
|
|
131
140
|
if(id == "UserData" || id == "clientID"){
|
|
132
|
-
fs.
|
|
141
|
+
fs.mkdirSync(path.dirname(this.getPersistPath(id)), { recursive: true });
|
|
142
|
+
fs.writeFileSync(this.getPersistPath(id), JSON.stringify(state, null, 2, 'utf8'));
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
this.states[id] = state;
|
|
@@ -151,7 +161,7 @@ class Roborock {
|
|
|
151
161
|
try {
|
|
152
162
|
|
|
153
163
|
if(id == "UserData" || id == "clientID"){
|
|
154
|
-
fs.unlinkSync(
|
|
164
|
+
fs.unlinkSync(this.getPersistPath(id));
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
delete this.states[id];
|
|
@@ -166,6 +176,30 @@ class Roborock {
|
|
|
166
176
|
this.log.debug(`subscribeStates: ${id}`);
|
|
167
177
|
}
|
|
168
178
|
|
|
179
|
+
getPersistPath(id) {
|
|
180
|
+
const storagePath = this.config.storagePath;
|
|
181
|
+
if (storagePath) {
|
|
182
|
+
return path.join(storagePath, `roborock.${id}`);
|
|
183
|
+
}
|
|
184
|
+
return path.resolve(__dirname, `./data/${id}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
parseSkipDevices(value) {
|
|
188
|
+
if (!value) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
return value.map((entry) => `${entry}`.trim()).filter((entry) => entry);
|
|
193
|
+
}
|
|
194
|
+
if (typeof value === "string") {
|
|
195
|
+
return value
|
|
196
|
+
.split(",")
|
|
197
|
+
.map((entry) => entry.trim())
|
|
198
|
+
.filter((entry) => entry);
|
|
199
|
+
}
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
|
|
169
203
|
/**
|
|
170
204
|
* Is called when databases are connected and adapter received configuration.
|
|
171
205
|
*/
|
|
@@ -190,24 +224,33 @@ class Roborock {
|
|
|
190
224
|
this.log.error(`Error while retrieving or setting clientID: ${error.message}`);
|
|
191
225
|
}
|
|
192
226
|
|
|
193
|
-
if (!this.config.username
|
|
194
|
-
this.log.error("
|
|
227
|
+
if (!this.config.username) {
|
|
228
|
+
this.log.error("Email is missing!");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!this.config.password && !this.isValidUserData(this.userData)) {
|
|
232
|
+
this.log.error("Password or valid token is missing!");
|
|
195
233
|
return;
|
|
196
234
|
}
|
|
197
235
|
|
|
198
236
|
this.instance = clientID;
|
|
199
237
|
|
|
200
238
|
// Initialize the login API (which is needed to get access to the real API).
|
|
201
|
-
this.loginApi =
|
|
202
|
-
baseURL:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
239
|
+
this.loginApi = roborockAuth.createLoginApi({
|
|
240
|
+
baseURL: this.baseURL,
|
|
241
|
+
username: this.config.username,
|
|
242
|
+
clientID,
|
|
243
|
+
language: this.language,
|
|
206
244
|
});
|
|
207
245
|
await this.setStateAsync("info.connection", { val: true, ack: true });
|
|
208
246
|
// api/v1/getUrlByEmail(email = ...)
|
|
209
247
|
|
|
210
248
|
const userdata = await this.getUserData(this.loginApi);
|
|
249
|
+
if (!userdata) {
|
|
250
|
+
this.log.error("Login failed or requires 2FA. Please complete authentication in the Config UI.");
|
|
251
|
+
await this.setStateAsync("info.connection", { val: false, ack: true });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
211
254
|
|
|
212
255
|
try {
|
|
213
256
|
this.loginApi.defaults.headers.common["Authorization"] = userdata.token;
|
|
@@ -258,12 +301,14 @@ class Roborock {
|
|
|
258
301
|
ack: true,
|
|
259
302
|
});
|
|
260
303
|
|
|
261
|
-
// skip devices that sn in
|
|
304
|
+
// skip devices that sn in ignoredDevices or skipDevices
|
|
262
305
|
const ignoredDevices = this.config.ignoredDevices || [];
|
|
306
|
+
const skipDevices = this.parseSkipDevices(this.config.skipDevices);
|
|
307
|
+
const ignoredSet = new Set([...ignoredDevices, ...skipDevices]);
|
|
263
308
|
// create devices and set states
|
|
264
309
|
this.products = homedataResult.products;
|
|
265
310
|
this.devices = homedataResult.devices;
|
|
266
|
-
this.devices = this.devices.filter((device) => !
|
|
311
|
+
this.devices = this.devices.filter((device) => !ignoredSet.has(device.sn));
|
|
267
312
|
this.localKeys = new Map(this.devices.map((device) => [device.duid, device.localKey]));
|
|
268
313
|
|
|
269
314
|
// this.adapter.log.debug(`initUser test: ${JSON.stringify(Array.from(this.adapter.localKeys.entries()))}`);
|
|
@@ -352,26 +397,62 @@ class Roborock {
|
|
|
352
397
|
|
|
353
398
|
async getUserData(loginApi) {
|
|
354
399
|
try {
|
|
355
|
-
|
|
356
|
-
"
|
|
357
|
-
|
|
358
|
-
username: this.config.username,
|
|
359
|
-
password: this.config.password,
|
|
360
|
-
needtwostepauth: "false",
|
|
361
|
-
}).toString()
|
|
362
|
-
);
|
|
363
|
-
const userdata = response.data.data;
|
|
364
|
-
|
|
365
|
-
if (!userdata) {
|
|
366
|
-
throw new Error("Login returned empty userdata.");
|
|
400
|
+
if (this.isValidUserData(this.userData)) {
|
|
401
|
+
this.log.info("Using session from config.");
|
|
402
|
+
return this.userData;
|
|
367
403
|
}
|
|
368
404
|
|
|
369
|
-
await this.
|
|
370
|
-
|
|
371
|
-
|
|
405
|
+
const cachedState = await this.getStateAsync("UserData");
|
|
406
|
+
if (cachedState && cachedState.val) {
|
|
407
|
+
try {
|
|
408
|
+
const cached = JSON.parse(cachedState.val);
|
|
409
|
+
if (this.isValidUserData(cached)) {
|
|
410
|
+
this.userData = cached;
|
|
411
|
+
this.log.info("Using cached session from disk.");
|
|
412
|
+
return cached;
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
this.log.warn("Cached session is invalid and will be ignored.");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!this.config.password) {
|
|
420
|
+
this.log.error("Password is missing and no valid token is available.");
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const signData = await this.ensureAuthSignature();
|
|
425
|
+
if (!signData) {
|
|
426
|
+
throw new Error("Failed to obtain login signature.");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const loginResult = await roborockAuth.loginByPassword(loginApi, {
|
|
430
|
+
email: this.config.username,
|
|
431
|
+
password: this.config.password,
|
|
432
|
+
k: signData.k,
|
|
433
|
+
s: signData.s,
|
|
372
434
|
});
|
|
373
435
|
|
|
374
|
-
|
|
436
|
+
if (loginResult && loginResult.code === 200 && loginResult.data) {
|
|
437
|
+
this.userData = loginResult.data;
|
|
438
|
+
this.pendingAuth = null;
|
|
439
|
+
await this.setStateAsync("UserData", {
|
|
440
|
+
val: JSON.stringify(this.userData),
|
|
441
|
+
ack: true,
|
|
442
|
+
});
|
|
443
|
+
this.authState.twoFactorRequired = false;
|
|
444
|
+
this.authState.statusMessage = "";
|
|
445
|
+
return this.userData;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (loginResult && loginResult.code === 2031) {
|
|
449
|
+
this.authState.twoFactorRequired = true;
|
|
450
|
+
this.authState.statusMessage = "Two-factor authentication required.";
|
|
451
|
+
this.log.error("Two-factor authentication required. Use the Config UI to continue.");
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
throw new Error(`Login failed: ${JSON.stringify(loginResult)}`);
|
|
375
456
|
} catch (error) {
|
|
376
457
|
this.log.error(`Error in getUserData: ${error.message}`);
|
|
377
458
|
await this.deleteStateAsync("HomeData");
|
|
@@ -380,6 +461,81 @@ class Roborock {
|
|
|
380
461
|
}
|
|
381
462
|
}
|
|
382
463
|
|
|
464
|
+
isValidUserData(userdata) {
|
|
465
|
+
return userdata && userdata.token && userdata.rriot;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async ensureAuthSignature() {
|
|
469
|
+
if (this.pendingAuth && this.pendingAuth.k && this.pendingAuth.s) {
|
|
470
|
+
return this.pendingAuth;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (!this.loginApi) {
|
|
474
|
+
throw new Error("Login API is not initialized.");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const s = crypto.randomBytes(12).toString("base64").substring(0, 16).replace(/\+/g, "X").replace(/\//g, "Y");
|
|
478
|
+
const signData = await roborockAuth.signRequest(this.loginApi, s);
|
|
479
|
+
if (!signData || !signData.k) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.pendingAuth = { k: signData.k, s };
|
|
484
|
+
return this.pendingAuth;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async sendTwoFactorEmail() {
|
|
488
|
+
if (!this.loginApi) {
|
|
489
|
+
throw new Error("Login API is not initialized.");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
await roborockAuth.requestEmailCode(this.loginApi, this.config.username);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
this.log.error(`2FA email request failed: ${error.message}`);
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
this.authState.twoFactorRequired = true;
|
|
499
|
+
this.authState.statusMessage = "Verification email sent.";
|
|
500
|
+
return { ok: true };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async verifyTwoFactorCode(code) {
|
|
504
|
+
if (!this.loginApi) {
|
|
505
|
+
throw new Error("Login API is not initialized.");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const signData = await this.ensureAuthSignature();
|
|
509
|
+
if (!signData) {
|
|
510
|
+
throw new Error("Missing login signature.");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const region = roborockAuth.getRegionConfig(this.baseURL);
|
|
514
|
+
const loginResult = await roborockAuth.loginWithCode(this.loginApi, {
|
|
515
|
+
email: this.config.username,
|
|
516
|
+
code,
|
|
517
|
+
country: region.country,
|
|
518
|
+
countryCode: region.countryCode,
|
|
519
|
+
k: signData.k,
|
|
520
|
+
s: signData.s,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
if (loginResult && loginResult.code === 200 && loginResult.data) {
|
|
524
|
+
this.userData = loginResult.data;
|
|
525
|
+
this.pendingAuth = null;
|
|
526
|
+
await this.setStateAsync("UserData", {
|
|
527
|
+
val: JSON.stringify(this.userData),
|
|
528
|
+
ack: true,
|
|
529
|
+
});
|
|
530
|
+
this.authState.twoFactorRequired = false;
|
|
531
|
+
this.authState.statusMessage = "Two-factor authentication completed.";
|
|
532
|
+
return this.userData;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
this.log.error(`2FA verification failed: ${JSON.stringify(loginResult)}`);
|
|
536
|
+
throw new Error(`2FA verification failed: ${loginResult?.msg || "Unknown error"}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
383
539
|
async getNetworkInfo() {
|
|
384
540
|
const devices = this.devices;
|
|
385
541
|
for (const device in devices) {
|
|
@@ -761,6 +917,8 @@ class Roborock {
|
|
|
761
917
|
case "roborock.vacuum.a08":
|
|
762
918
|
case "roborock.vacuum.a10":
|
|
763
919
|
case "roborock.vacuum.a40":
|
|
920
|
+
case "roborock.vacuum.a140":
|
|
921
|
+
case "roborock.vacuum.ss07":
|
|
764
922
|
//do nothing
|
|
765
923
|
break;
|
|
766
924
|
case "roborock.vacuum.s6":
|
|
@@ -1439,4 +1597,3 @@ class Roborock {
|
|
|
1439
1597
|
module.exports = {Roborock};
|
|
1440
1598
|
|
|
1441
1599
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1442
|
-
|
package/roborockLib/test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const Roborock = require('./roborockAPI.js').Roborock;
|
|
2
2
|
|
|
3
|
-
var roborock = new Roborock({username: "tasict@gmail.com",
|
|
3
|
+
var roborock = new Roborock({username: "tasict@gmail.com", debug: true});
|
|
4
|
+
|
|
4
5
|
|
|
5
6
|
roborock.setDeviceNotify(function(id, homeData){
|
|
6
7
|
console.info(`${id} deviceNotify:${JSON.stringify(homeData)}`);
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"val": "{\"uid\":1466357,\"tokentype\":\"\",\"token\":\"rr6238c899155830:1JZxT5AINkUhb5JDyL8S+w==:0197442b8fc07eeab8ec99362de5bcc3\",\"rruid\":\"rr6238c899155830\",\"region\":\"us\",\"countrycode\":\"886\",\"country\":\"TW\",\"nickname\":\"tasict\",\"rriot\":{\"u\":\"1RQsQJ2o8bxMCo6F45pICu\",\"s\":\"DSeoyC\",\"h\":\"A6whcnODPe\",\"k\":\"kmat3nT2\",\"r\":{\"r\":\"US\",\"a\":\"https://api-us.roborock.com\",\"m\":\"ssl://mqtt-us.roborock.com:8883\",\"l\":\"https://wood-us.roborock.com\"}},\"tuyaDeviceState\":0,\"avatarurl\":\"https://files.roborock.com/iottest/default_avatar.png\"}",
|
|
3
|
-
"ack": true
|
|
4
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"uid": 1466357,
|
|
3
|
-
"tokentype": "",
|
|
4
|
-
"token": "d23f4a8b14d34fb9b1dbae6c36778b7c-6DLl6x7HW8TjEYdXLipfLA==",
|
|
5
|
-
"rruid": "rr6238c899155830",
|
|
6
|
-
"region": "us",
|
|
7
|
-
"countrycode": "886",
|
|
8
|
-
"country": "TW",
|
|
9
|
-
"nickname": "tasict",
|
|
10
|
-
"rriot": {
|
|
11
|
-
"u": "1RQsQJ2o8bxMCo6F45pICu",
|
|
12
|
-
"s": "RHsYyP",
|
|
13
|
-
"h": "XeItWF64Ik",
|
|
14
|
-
"k": "VR1VI17T",
|
|
15
|
-
"r": {
|
|
16
|
-
"r": "US",
|
|
17
|
-
"a": "https://api-us.roborock.com",
|
|
18
|
-
"m": "ssl://mqtt-us.roborock.com:8883",
|
|
19
|
-
"l": "https://wood-us.roborock.com"
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"tuyaDeviceState": 0,
|
|
23
|
-
"avatarurl": "https://files.roborock.com/iottest/default_avatar.png"
|
|
24
|
-
}
|