node-switchbot 1.3.0 → 1.4.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/CHANGELOG.md +32 -23
- package/LICENSE +1 -1
- package/README.md +322 -258
- package/lib/parameter-checker.js +271 -213
- package/lib/switchbot-advertising.js +238 -163
- package/lib/switchbot-device-wocontact.js +4 -7
- package/lib/switchbot-device-wocurtain.js +106 -91
- package/lib/switchbot-device-wohand.js +69 -65
- package/lib/switchbot-device-wohumi.js +69 -65
- package/lib/switchbot-device-woplugmini.js +81 -0
- package/lib/switchbot-device-wopresence.js +4 -7
- package/lib/switchbot-device-wosensorth.js +4 -7
- package/lib/switchbot-device.js +188 -149
- package/lib/switchbot.js +271 -233
- package/package.json +3 -3
package/lib/switchbot-device.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
const parameterChecker = require(
|
|
3
|
-
const switchbotAdvertising = require(
|
|
1
|
+
"use strict";
|
|
2
|
+
const parameterChecker = require("./parameter-checker.js");
|
|
3
|
+
const switchbotAdvertising = require("./switchbot-advertising.js");
|
|
4
4
|
|
|
5
5
|
class SwitchbotDevice {
|
|
6
6
|
/* ------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
* Constructor
|
|
8
|
+
*
|
|
9
|
+
* [Arguments]
|
|
10
|
+
* - peripheral | Object | Required | The `peripheral` object of noble,
|
|
11
|
+
* | | | which represents this device
|
|
12
|
+
* - noble | Noble | Required | The Nobel object created by the noble module.
|
|
13
|
+
* ---------------------------------------------------------------- */
|
|
14
14
|
constructor(peripheral, noble) {
|
|
15
15
|
this._peripheral = peripheral;
|
|
16
16
|
this._noble = noble;
|
|
17
17
|
this._chars = null;
|
|
18
18
|
|
|
19
|
-
this._SERV_UUID_PRIMARY =
|
|
20
|
-
this._CHAR_UUID_WRITE =
|
|
21
|
-
this._CHAR_UUID_NOTIFY =
|
|
22
|
-
this._CHAR_UUID_DEVICE =
|
|
19
|
+
this._SERV_UUID_PRIMARY = "cba20d00224d11e69fb80002a5d5c51b";
|
|
20
|
+
this._CHAR_UUID_WRITE = "cba20002224d11e69fb80002a5d5c51b";
|
|
21
|
+
this._CHAR_UUID_NOTIFY = "cba20003224d11e69fb80002a5d5c51b";
|
|
22
|
+
this._CHAR_UUID_DEVICE = "2a00";
|
|
23
23
|
|
|
24
24
|
this._READ_TIMEOUT_MSEC = 3000;
|
|
25
25
|
this._WRITE_TIMEOUT_MSEC = 3000;
|
|
@@ -35,10 +35,10 @@ class SwitchbotDevice {
|
|
|
35
35
|
this._was_connected_explicitly = false;
|
|
36
36
|
this._connected = false;
|
|
37
37
|
|
|
38
|
-
this._onconnect = () => {
|
|
39
|
-
this._ondisconnect = () => {
|
|
40
|
-
this._ondisconnect_internal = () => {
|
|
41
|
-
this._onnotify_internal = () => {
|
|
38
|
+
this._onconnect = () => {};
|
|
39
|
+
this._ondisconnect = () => {};
|
|
40
|
+
this._ondisconnect_internal = () => {};
|
|
41
|
+
this._onnotify_internal = () => {};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Getters
|
|
@@ -55,8 +55,8 @@ class SwitchbotDevice {
|
|
|
55
55
|
return this._modelName;
|
|
56
56
|
}
|
|
57
57
|
get connectionState() {
|
|
58
|
-
if (!this._connected && this._peripheral.state ===
|
|
59
|
-
return
|
|
58
|
+
if (!this._connected && this._peripheral.state === "disconnecting") {
|
|
59
|
+
return "disconnected";
|
|
60
60
|
} else {
|
|
61
61
|
return this._peripheral.state;
|
|
62
62
|
}
|
|
@@ -64,29 +64,29 @@ class SwitchbotDevice {
|
|
|
64
64
|
|
|
65
65
|
// Setters
|
|
66
66
|
set onconnect(func) {
|
|
67
|
-
if (!func || typeof
|
|
68
|
-
throw new Error(
|
|
67
|
+
if (!func || typeof func !== "function") {
|
|
68
|
+
throw new Error("The `onconnect` must be a function.");
|
|
69
69
|
}
|
|
70
70
|
this._onconnect = func;
|
|
71
71
|
}
|
|
72
72
|
set ondisconnect(func) {
|
|
73
|
-
if (!func || typeof
|
|
74
|
-
throw new Error(
|
|
73
|
+
if (!func || typeof func !== "function") {
|
|
74
|
+
throw new Error("The `ondisconnect` must be a function.");
|
|
75
75
|
}
|
|
76
76
|
this._ondisconnect = func;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/* ------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
* connect()
|
|
81
|
+
* - Connect the device
|
|
82
|
+
*
|
|
83
|
+
* [Arguments]
|
|
84
|
+
* - none
|
|
85
|
+
*
|
|
86
|
+
* [Return value]
|
|
87
|
+
* - Promise object
|
|
88
|
+
* Nothing will be passed to the `resolve()`.
|
|
89
|
+
* ---------------------------------------------------------------- */
|
|
90
90
|
connect() {
|
|
91
91
|
this._was_connected_explicitly = true;
|
|
92
92
|
return this._connect();
|
|
@@ -95,28 +95,34 @@ class SwitchbotDevice {
|
|
|
95
95
|
_connect() {
|
|
96
96
|
return new Promise((resolve, reject) => {
|
|
97
97
|
// Check the bluetooth state
|
|
98
|
-
if (this._noble.state !==
|
|
99
|
-
reject(
|
|
98
|
+
if (this._noble.state !== "poweredOn") {
|
|
99
|
+
reject(
|
|
100
|
+
new Error(
|
|
101
|
+
"The Bluetooth status is " + this._noble.state + ", not poweredOn."
|
|
102
|
+
)
|
|
103
|
+
);
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
// Check the connection state
|
|
104
108
|
let state = this.connectionState;
|
|
105
|
-
if (state ===
|
|
109
|
+
if (state === "connected") {
|
|
106
110
|
resolve();
|
|
107
111
|
return;
|
|
108
|
-
} else if (state ===
|
|
109
|
-
reject(
|
|
112
|
+
} else if (state === "connecting" || state === "disconnecting") {
|
|
113
|
+
reject(
|
|
114
|
+
new Error("Now " + state + ". Wait for a few seconds then try again.")
|
|
115
|
+
);
|
|
110
116
|
return;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
// Set event handlers for events fired on the `Peripheral` object
|
|
114
|
-
this._peripheral.once(
|
|
120
|
+
this._peripheral.once("connect", () => {
|
|
115
121
|
this._connected = true;
|
|
116
122
|
this._onconnect();
|
|
117
123
|
});
|
|
118
124
|
|
|
119
|
-
this._peripheral.once(
|
|
125
|
+
this._peripheral.once("disconnect", () => {
|
|
120
126
|
this._connected = false;
|
|
121
127
|
this._chars = null;
|
|
122
128
|
this._peripheral.removeAllListeners();
|
|
@@ -130,15 +136,18 @@ class SwitchbotDevice {
|
|
|
130
136
|
reject(error);
|
|
131
137
|
return;
|
|
132
138
|
}
|
|
133
|
-
this._getCharacteristics()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
this._getCharacteristics()
|
|
140
|
+
.then((chars) => {
|
|
141
|
+
this._chars = chars;
|
|
142
|
+
return this._subscribe();
|
|
143
|
+
})
|
|
144
|
+
.then(() => {
|
|
145
|
+
resolve();
|
|
146
|
+
})
|
|
147
|
+
.catch((error) => {
|
|
148
|
+
this._peripheral.disconnect();
|
|
149
|
+
reject(error);
|
|
150
|
+
});
|
|
142
151
|
});
|
|
143
152
|
});
|
|
144
153
|
}
|
|
@@ -147,9 +156,11 @@ class SwitchbotDevice {
|
|
|
147
156
|
return new Promise((resolve, reject) => {
|
|
148
157
|
// Set timeout timer
|
|
149
158
|
let timer = setTimeout(() => {
|
|
150
|
-
this._ondisconnect_internal = () => {
|
|
159
|
+
this._ondisconnect_internal = () => {};
|
|
151
160
|
timer = null;
|
|
152
|
-
reject(
|
|
161
|
+
reject(
|
|
162
|
+
new Error("Failed to discover services and characteristics: TIMEOUT")
|
|
163
|
+
);
|
|
153
164
|
}, 5000);
|
|
154
165
|
|
|
155
166
|
// Watch the connection state
|
|
@@ -157,22 +168,26 @@ class SwitchbotDevice {
|
|
|
157
168
|
if (timer) {
|
|
158
169
|
clearTimeout(timer);
|
|
159
170
|
timer = null;
|
|
160
|
-
this._ondisconnect_internal = () => {
|
|
171
|
+
this._ondisconnect_internal = () => {};
|
|
161
172
|
}
|
|
162
|
-
reject(
|
|
173
|
+
reject(
|
|
174
|
+
new Error(
|
|
175
|
+
"Failed to discover services and characteristics: DISCONNECTED"
|
|
176
|
+
)
|
|
177
|
+
);
|
|
163
178
|
};
|
|
164
179
|
|
|
165
180
|
// Discover services and characteristics
|
|
166
181
|
(async () => {
|
|
167
182
|
let service_list = await this._discoverServices();
|
|
168
183
|
if (!timer) {
|
|
169
|
-
throw new Error(
|
|
184
|
+
throw new Error("");
|
|
170
185
|
}
|
|
171
186
|
|
|
172
187
|
let chars = {
|
|
173
188
|
write: null,
|
|
174
189
|
notify: null,
|
|
175
|
-
device: null
|
|
190
|
+
device: null,
|
|
176
191
|
};
|
|
177
192
|
|
|
178
193
|
for (let service of service_list) {
|
|
@@ -192,14 +207,13 @@ class SwitchbotDevice {
|
|
|
192
207
|
if (chars.write && chars.notify) {
|
|
193
208
|
resolve(chars);
|
|
194
209
|
} else {
|
|
195
|
-
reject(new Error(
|
|
210
|
+
reject(new Error("No characteristic was found."));
|
|
196
211
|
}
|
|
197
|
-
|
|
198
212
|
})().catch((error) => {
|
|
199
213
|
if (timer) {
|
|
200
214
|
clearTimeout(timer);
|
|
201
215
|
timer = null;
|
|
202
|
-
this._ondisconnect_internal = () => {
|
|
216
|
+
this._ondisconnect_internal = () => {};
|
|
203
217
|
reject(error);
|
|
204
218
|
} else {
|
|
205
219
|
// Do nothing
|
|
@@ -226,7 +240,7 @@ class SwitchbotDevice {
|
|
|
226
240
|
if (service) {
|
|
227
241
|
resolve(service_list);
|
|
228
242
|
} else {
|
|
229
|
-
reject(new Error(
|
|
243
|
+
reject(new Error("No service was found."));
|
|
230
244
|
}
|
|
231
245
|
});
|
|
232
246
|
});
|
|
@@ -248,7 +262,7 @@ class SwitchbotDevice {
|
|
|
248
262
|
return new Promise((resolve, reject) => {
|
|
249
263
|
let char = this._chars.notify;
|
|
250
264
|
if (!char) {
|
|
251
|
-
reject(new Error(
|
|
265
|
+
reject(new Error("No notify characteristic was found."));
|
|
252
266
|
return;
|
|
253
267
|
}
|
|
254
268
|
char.subscribe((error) => {
|
|
@@ -256,11 +270,11 @@ class SwitchbotDevice {
|
|
|
256
270
|
reject(error);
|
|
257
271
|
return;
|
|
258
272
|
}
|
|
259
|
-
char.on(
|
|
273
|
+
char.on("data", (buf) => {
|
|
260
274
|
this._onnotify_internal(buf);
|
|
261
275
|
});
|
|
262
276
|
resolve();
|
|
263
|
-
})
|
|
277
|
+
});
|
|
264
278
|
});
|
|
265
279
|
}
|
|
266
280
|
|
|
@@ -279,26 +293,28 @@ class SwitchbotDevice {
|
|
|
279
293
|
}
|
|
280
294
|
|
|
281
295
|
/* ------------------------------------------------------------------
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
296
|
+
* disconnect()
|
|
297
|
+
* - Disconnect the device
|
|
298
|
+
*
|
|
299
|
+
* [Arguments]
|
|
300
|
+
* - none
|
|
301
|
+
*
|
|
302
|
+
* [Return value]
|
|
303
|
+
* - Promise object
|
|
304
|
+
* Nothing will be passed to the `resolve()`.
|
|
305
|
+
* ---------------------------------------------------------------- */
|
|
292
306
|
disconnect() {
|
|
293
307
|
return new Promise((resolve, reject) => {
|
|
294
308
|
this._was_connected_explicitly = false;
|
|
295
309
|
// Check the connection state
|
|
296
310
|
let state = this._peripheral.state;
|
|
297
|
-
if (state ===
|
|
311
|
+
if (state === "disconnected") {
|
|
298
312
|
resolve();
|
|
299
313
|
return;
|
|
300
|
-
} else if (state ===
|
|
301
|
-
reject(
|
|
314
|
+
} else if (state === "connecting" || state === "disconnecting") {
|
|
315
|
+
reject(
|
|
316
|
+
new Error("Now " + state + ". Wait for a few seconds then try again.")
|
|
317
|
+
);
|
|
302
318
|
return;
|
|
303
319
|
}
|
|
304
320
|
|
|
@@ -323,74 +339,93 @@ class SwitchbotDevice {
|
|
|
323
339
|
}
|
|
324
340
|
|
|
325
341
|
/* ------------------------------------------------------------------
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
* getDeviceName()
|
|
343
|
+
* - Retrieve the device name
|
|
344
|
+
*
|
|
345
|
+
* [Arguments]
|
|
346
|
+
* - none
|
|
347
|
+
*
|
|
348
|
+
* [Return value]
|
|
349
|
+
* - Promise object
|
|
350
|
+
* The device name will be passed to the `resolve()`.
|
|
351
|
+
* ---------------------------------------------------------------- */
|
|
336
352
|
getDeviceName() {
|
|
337
353
|
return new Promise((resolve, reject) => {
|
|
338
|
-
let name =
|
|
339
|
-
this._connect()
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
354
|
+
let name = "";
|
|
355
|
+
this._connect()
|
|
356
|
+
.then(() => {
|
|
357
|
+
if (!this._chars.device) {
|
|
358
|
+
// Some models of Bot don't seem to support this characteristic UUID
|
|
359
|
+
throw new Error(
|
|
360
|
+
"The device does not support the characteristic UUID 0x" +
|
|
361
|
+
this._CHAR_UUID_DEVICE +
|
|
362
|
+
"."
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
return this._read(this._chars.device);
|
|
366
|
+
})
|
|
367
|
+
.then((buf) => {
|
|
368
|
+
name = buf.toString("utf8");
|
|
369
|
+
return this._disconnect();
|
|
370
|
+
})
|
|
371
|
+
.then(() => {
|
|
372
|
+
resolve(name);
|
|
373
|
+
})
|
|
374
|
+
.catch((error) => {
|
|
375
|
+
reject(error);
|
|
376
|
+
});
|
|
353
377
|
});
|
|
354
378
|
}
|
|
355
379
|
|
|
356
380
|
/* ------------------------------------------------------------------
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
381
|
+
* setDeviceName(name)
|
|
382
|
+
* - Set the device name
|
|
383
|
+
*
|
|
384
|
+
* [Arguments]
|
|
385
|
+
* - name | String | Required | Device name. The bytes length of the name
|
|
386
|
+
* | | | must be in the range of 1 to 20 bytes.
|
|
387
|
+
*
|
|
388
|
+
* [Return value]
|
|
389
|
+
* - Promise object
|
|
390
|
+
* Nothing will be passed to the `resolve()`.
|
|
391
|
+
* ---------------------------------------------------------------- */
|
|
368
392
|
setDeviceName(name) {
|
|
369
393
|
return new Promise((resolve, reject) => {
|
|
370
394
|
// Check the parameters
|
|
371
|
-
let valid = parameterChecker.check(
|
|
372
|
-
|
|
373
|
-
|
|
395
|
+
let valid = parameterChecker.check(
|
|
396
|
+
{ name: name },
|
|
397
|
+
{
|
|
398
|
+
name: { required: true, type: "string", minBytes: 1, maxBytes: 100 },
|
|
399
|
+
}
|
|
400
|
+
);
|
|
374
401
|
|
|
375
402
|
if (!valid) {
|
|
376
403
|
reject(new Error(parameterChecker.error.message));
|
|
377
404
|
return;
|
|
378
405
|
}
|
|
379
406
|
|
|
380
|
-
let buf = Buffer.from(name,
|
|
381
|
-
this._connect()
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
407
|
+
let buf = Buffer.from(name, "utf8");
|
|
408
|
+
this._connect()
|
|
409
|
+
.then(() => {
|
|
410
|
+
if (!this._chars.device) {
|
|
411
|
+
// Some models of Bot don't seem to support this characteristic UUID
|
|
412
|
+
throw new Error(
|
|
413
|
+
"The device does not support the characteristic UUID 0x" +
|
|
414
|
+
this._CHAR_UUID_DEVICE +
|
|
415
|
+
"."
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
return this._write(this._chars.device, buf);
|
|
419
|
+
})
|
|
420
|
+
.then(() => {
|
|
421
|
+
return this._disconnect();
|
|
422
|
+
})
|
|
423
|
+
.then(() => {
|
|
424
|
+
resolve();
|
|
425
|
+
})
|
|
426
|
+
.catch((error) => {
|
|
427
|
+
reject(error);
|
|
428
|
+
});
|
|
394
429
|
});
|
|
395
430
|
}
|
|
396
431
|
|
|
@@ -400,24 +435,29 @@ class SwitchbotDevice {
|
|
|
400
435
|
_command(req_buf) {
|
|
401
436
|
return new Promise((resolve, reject) => {
|
|
402
437
|
if (!Buffer.isBuffer(req_buf)) {
|
|
403
|
-
reject(new Error(
|
|
438
|
+
reject(new Error("The specified data is not acceptable for writing."));
|
|
404
439
|
return;
|
|
405
440
|
}
|
|
406
441
|
|
|
407
442
|
let res_buf = null;
|
|
408
443
|
|
|
409
|
-
this._connect()
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
444
|
+
this._connect()
|
|
445
|
+
.then(() => {
|
|
446
|
+
return this._write(this._chars.write, req_buf);
|
|
447
|
+
})
|
|
448
|
+
.then(() => {
|
|
449
|
+
return this._waitCommandResponse();
|
|
450
|
+
})
|
|
451
|
+
.then((buf) => {
|
|
452
|
+
res_buf = buf;
|
|
453
|
+
return this._disconnect();
|
|
454
|
+
})
|
|
455
|
+
.then(() => {
|
|
456
|
+
resolve(res_buf);
|
|
457
|
+
})
|
|
458
|
+
.catch((error) => {
|
|
459
|
+
reject(error);
|
|
460
|
+
});
|
|
421
461
|
});
|
|
422
462
|
}
|
|
423
463
|
|
|
@@ -425,8 +465,8 @@ class SwitchbotDevice {
|
|
|
425
465
|
return new Promise((resolve, reject) => {
|
|
426
466
|
let timer = setTimeout(() => {
|
|
427
467
|
timer = null;
|
|
428
|
-
this._onnotify_internal = () => {
|
|
429
|
-
reject(new Error(
|
|
468
|
+
this._onnotify_internal = () => {};
|
|
469
|
+
reject(new Error("COMMAND_TIMEOUT"));
|
|
430
470
|
}, this._COMMAND_TIMEOUT_MSEC);
|
|
431
471
|
|
|
432
472
|
this._onnotify_internal = (buf) => {
|
|
@@ -434,7 +474,7 @@ class SwitchbotDevice {
|
|
|
434
474
|
clearTimeout(timer);
|
|
435
475
|
timer = null;
|
|
436
476
|
}
|
|
437
|
-
this._onnotify_internal = () => {
|
|
477
|
+
this._onnotify_internal = () => {};
|
|
438
478
|
resolve(buf);
|
|
439
479
|
};
|
|
440
480
|
});
|
|
@@ -445,7 +485,7 @@ class SwitchbotDevice {
|
|
|
445
485
|
return new Promise((resolve, reject) => {
|
|
446
486
|
// Set a timeout timer
|
|
447
487
|
let timer = setTimeout(() => {
|
|
448
|
-
reject(
|
|
488
|
+
reject("READ_TIMEOUT");
|
|
449
489
|
}, this._READ_TIMEOUT_MSEC);
|
|
450
490
|
|
|
451
491
|
// Read charcteristic data
|
|
@@ -468,7 +508,7 @@ class SwitchbotDevice {
|
|
|
468
508
|
return new Promise((resolve, reject) => {
|
|
469
509
|
// Set a timeout timer
|
|
470
510
|
let timer = setTimeout(() => {
|
|
471
|
-
reject(
|
|
511
|
+
reject("WRITE_TIMEOUT");
|
|
472
512
|
}, this._WRITE_TIMEOUT_MSEC);
|
|
473
513
|
|
|
474
514
|
// write charcteristic data
|
|
@@ -485,7 +525,6 @@ class SwitchbotDevice {
|
|
|
485
525
|
});
|
|
486
526
|
});
|
|
487
527
|
}
|
|
488
|
-
|
|
489
528
|
}
|
|
490
529
|
|
|
491
|
-
module.exports = SwitchbotDevice;
|
|
530
|
+
module.exports = SwitchbotDevice;
|