node-switchbot 1.0.7
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 +14 -0
- package/README.md +950 -0
- package/lib/parameter-checker.js +471 -0
- package/lib/switchbot-advertising.js +255 -0
- package/lib/switchbot-device-wocontact.js +16 -0
- package/lib/switchbot-device-wocurtain.js +109 -0
- package/lib/switchbot-device-wohand.js +106 -0
- package/lib/switchbot-device-wohumi.js +106 -0
- package/lib/switchbot-device-wopresence.js +16 -0
- package/lib/switchbot-device-wosensorth.js +16 -0
- package/lib/switchbot-device.js +498 -0
- package/lib/switchbot.js +366 -0
- package/package.json +42 -0
package/lib/switchbot.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------
|
|
2
|
+
* node-switchbot - switchbot.js
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2019, Futomi Hatano, All rights reserved.
|
|
5
|
+
* Released under the MIT license
|
|
6
|
+
* Date: 2019-11-18
|
|
7
|
+
* ---------------------------------------------------------------- */
|
|
8
|
+
'use strict';
|
|
9
|
+
const parameterChecker = require('./parameter-checker.js');
|
|
10
|
+
const switchbotAdvertising = require('./switchbot-advertising.js');
|
|
11
|
+
|
|
12
|
+
const SwitchbotDevice = require('./switchbot-device.js');
|
|
13
|
+
const SwitchbotDeviceWoHand = require('./switchbot-device-wohand.js');
|
|
14
|
+
const SwitchbotDeviceWoCurtain = require('./switchbot-device-wocurtain.js');
|
|
15
|
+
const SwitchbotDeviceWoPresence = require('./switchbot-device-wopresence.js');
|
|
16
|
+
const SwitchbotDeviceWoContact = require('./switchbot-device-wocontact.js');
|
|
17
|
+
const SwitchbotDeviceWoSensorTH = require('./switchbot-device-wosensorth.js');
|
|
18
|
+
const SwitchbotDeviceWoHumi = require('./switchbot-device-wohumi.js');
|
|
19
|
+
|
|
20
|
+
class Switchbot {
|
|
21
|
+
/* ------------------------------------------------------------------
|
|
22
|
+
* Constructor
|
|
23
|
+
*
|
|
24
|
+
* [Arguments]
|
|
25
|
+
* - params | Object | Optional |
|
|
26
|
+
* - noble | Noble | Optional | The Nobel object created by the noble module.
|
|
27
|
+
* | | | This parameter is optional.
|
|
28
|
+
* | | | If you don't specify this parameter, this
|
|
29
|
+
* | | | module automatically creates it.
|
|
30
|
+
* ---------------------------------------------------------------- */
|
|
31
|
+
constructor(params) {
|
|
32
|
+
// Check parameters
|
|
33
|
+
let noble = null;
|
|
34
|
+
if (params && params.noble) {
|
|
35
|
+
noble = params.noble;
|
|
36
|
+
} else {
|
|
37
|
+
noble = require('@abandonware/noble');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Plublic properties
|
|
41
|
+
this.noble = noble;
|
|
42
|
+
this.ondiscover = null;
|
|
43
|
+
this.onadvertisement = null;
|
|
44
|
+
|
|
45
|
+
// Private properties
|
|
46
|
+
this._scanning = false;
|
|
47
|
+
this._DEFAULT_DISCOVERY_DURATION = 5000
|
|
48
|
+
this._PRIMARY_SERVICE_UUID_LIST = ['cba20d00224d11e69fb80002a5d5c51b'];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/* ------------------------------------------------------------------
|
|
52
|
+
* discover([params])
|
|
53
|
+
* - Discover switchbot devices
|
|
54
|
+
*
|
|
55
|
+
* [Arguments]
|
|
56
|
+
* - params | Object | Optional |
|
|
57
|
+
* - duration | Integer | Optional | Duration for discovery process (msec).
|
|
58
|
+
* | | | The value must be in the range of 1 to 60000.
|
|
59
|
+
* | | | The default value is 5000 (msec).
|
|
60
|
+
* - model | String | Optional | "H", "T", "M", "CS", or "c".
|
|
61
|
+
* | | | If "H" is specified, this method will discover only Bots.
|
|
62
|
+
* | | | If "T" is specified, this method will discover only Meters.
|
|
63
|
+
* | | | If "M" is specified, this method will discover only Motion Sensors.
|
|
64
|
+
* | | | If "CS" is specified, this method will discover only Contact Sensors.
|
|
65
|
+
* | | | If "C" is specified, this method will discover only Curtains.
|
|
66
|
+
* - id | String | Optional | If this value is set, this method willl discover
|
|
67
|
+
* | | | only a device whose ID is as same as this value.
|
|
68
|
+
* | | | The ID is identical to the MAC address.
|
|
69
|
+
* | | | This parameter is case-insensitive, and
|
|
70
|
+
* | | | colons are ignored.
|
|
71
|
+
* - quick | Boolean | Optional | If this value is true, this method finishes
|
|
72
|
+
* | | | the discovery process when the first device
|
|
73
|
+
* | | | is found, then calls the resolve() function
|
|
74
|
+
* | | | without waiting the specified duration.
|
|
75
|
+
* | | | The default value is false.
|
|
76
|
+
*
|
|
77
|
+
* [Returen value]
|
|
78
|
+
* - Promise object
|
|
79
|
+
* An array will be passed to the `resolve()`, which includes
|
|
80
|
+
* `SwitchbotDevice` objects representing the found devices.
|
|
81
|
+
* ---------------------------------------------------------------- */
|
|
82
|
+
discover(params = {}) {
|
|
83
|
+
let promise = new Promise((resolve, reject) => {
|
|
84
|
+
// Check the parameters
|
|
85
|
+
let valid = parameterChecker.check(params, {
|
|
86
|
+
duration: { required: false, type: 'integer', min: 1, max: 60000 },
|
|
87
|
+
model: { required: false, type: 'string', enum: ['H', 'h','T', 'P', 'C', 'c'] },
|
|
88
|
+
id: { required: false, type: 'string', min: 12, max: 17 },
|
|
89
|
+
quick: { required: false, type: 'boolean' }
|
|
90
|
+
}, false);
|
|
91
|
+
|
|
92
|
+
if (!valid) {
|
|
93
|
+
reject(new Error(parameterChecker.error.message));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!params) {
|
|
98
|
+
params = {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Determine the values of the parameters
|
|
102
|
+
let p = {
|
|
103
|
+
duration: params.duration || this._DEFAULT_DISCOVERY_DURATION,
|
|
104
|
+
model: params.model || '',
|
|
105
|
+
id: params.id || '',
|
|
106
|
+
quick: params.quick ? true : false
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Initialize the noble object
|
|
110
|
+
this._init().then(() => {
|
|
111
|
+
let peripherals = {};
|
|
112
|
+
let timer = null;
|
|
113
|
+
let finishDiscovery = () => {
|
|
114
|
+
if (timer) {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
this.noble.removeAllListeners('discover');
|
|
118
|
+
this.noble.stopScanning();
|
|
119
|
+
let device_list = [];
|
|
120
|
+
for (let addr in peripherals) {
|
|
121
|
+
device_list.push(peripherals[addr]);
|
|
122
|
+
}
|
|
123
|
+
resolve(device_list);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Set an handler for the 'discover' event
|
|
127
|
+
this.noble.on('discover', (peripheral) => {
|
|
128
|
+
let device = this._getDeviceObject(peripheral, p.id, p.model);
|
|
129
|
+
if (!device) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let id = device.id;
|
|
133
|
+
peripherals[id] = device;
|
|
134
|
+
|
|
135
|
+
if (this.ondiscover && typeof (this.ondiscover) === 'function') {
|
|
136
|
+
this.ondiscover(device);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (p.quick) {
|
|
140
|
+
finishDiscovery();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Start scaning
|
|
146
|
+
this.noble.startScanning(this._PRIMARY_SERVICE_UUID_LIST, false, (error) => {
|
|
147
|
+
if (error) {
|
|
148
|
+
reject(error);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
timer = setTimeout(() => {
|
|
152
|
+
finishDiscovery();
|
|
153
|
+
}, p.duration);
|
|
154
|
+
});
|
|
155
|
+
}).catch((error) => {
|
|
156
|
+
reject(error);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
return promise;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_init() {
|
|
163
|
+
let promise = new Promise((resolve, reject) => {
|
|
164
|
+
switch (this.noble.state) {
|
|
165
|
+
case 'poweredOn':
|
|
166
|
+
resolve();
|
|
167
|
+
return;
|
|
168
|
+
case 'unsupported', 'unauthorized', 'poweredOff':
|
|
169
|
+
let err = new Error('Failed to initialize the Noble object: ' + this.noble.state);
|
|
170
|
+
reject(err);
|
|
171
|
+
return;
|
|
172
|
+
default: // 'resetting', 'unknown'
|
|
173
|
+
this.noble.once('stateChange', (state) => {
|
|
174
|
+
if (state === 'poweredOn') {
|
|
175
|
+
resolve();
|
|
176
|
+
} else {
|
|
177
|
+
let err = new Error('Failed to initialize the Noble object: ' + state);
|
|
178
|
+
reject(err);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return promise;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_getDeviceObject(peripheral, id, model) {
|
|
187
|
+
let ad = switchbotAdvertising.parse(peripheral);
|
|
188
|
+
if (this._filterAdvertising(ad, id, model)) {
|
|
189
|
+
let device = null;
|
|
190
|
+
switch (ad.serviceData.model) {
|
|
191
|
+
case 'H':
|
|
192
|
+
device = new SwitchbotDeviceWoHand(peripheral, this.noble);
|
|
193
|
+
break;
|
|
194
|
+
case 'Hu':
|
|
195
|
+
device = new SwitchbotDeviceWoHumi(peripheral, this.noble);
|
|
196
|
+
break;
|
|
197
|
+
case 'T':
|
|
198
|
+
device = new SwitchbotDeviceWoSensorTH(peripheral, this.noble);
|
|
199
|
+
break;
|
|
200
|
+
case 'M':
|
|
201
|
+
device = new SwitchbotDeviceWoPresence(peripheral, this.noble);
|
|
202
|
+
break;
|
|
203
|
+
case 'CS':
|
|
204
|
+
device = new SwitchbotDeviceWoContact(peripheral, this.noble);
|
|
205
|
+
break;
|
|
206
|
+
case 'c':
|
|
207
|
+
device = new SwitchbotDeviceWoCurtain(peripheral, this.noble);
|
|
208
|
+
break;
|
|
209
|
+
default: // 'resetting', 'unknown'
|
|
210
|
+
device = new SwitchbotDevice(peripheral, this.noble);
|
|
211
|
+
}
|
|
212
|
+
return device;
|
|
213
|
+
} else {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_filterAdvertising(ad, id, model) {
|
|
219
|
+
if (!ad) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (id) {
|
|
223
|
+
id = id.toLowerCase().replace(/\:/g, '');
|
|
224
|
+
let ad_id = ad.address.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
225
|
+
if (ad_id !== id) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (model) {
|
|
230
|
+
if (ad.serviceData.model !== model) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* ------------------------------------------------------------------
|
|
238
|
+
* startScan([params])
|
|
239
|
+
* - Start to monitor advertising packets coming from switchbot devices
|
|
240
|
+
*
|
|
241
|
+
* [Arguments]
|
|
242
|
+
* - params | Object | Optional |
|
|
243
|
+
* - model | String | Optional | "H", "T", "M", "CS", or "C".
|
|
244
|
+
* | | | If "H" is specified, the `onadvertisement`
|
|
245
|
+
* | | | event hander will be called only when advertising
|
|
246
|
+
* | | | packets comes from Bots.
|
|
247
|
+
* | | | If "T" is specified, the `onadvertisement`
|
|
248
|
+
* | | | event hander will be called only when advertising
|
|
249
|
+
* | | | packets comes from Meters.
|
|
250
|
+
* | | | If "M" is specified, the `onadvertisement`
|
|
251
|
+
* | | | event hander will be called only when advertising
|
|
252
|
+
* | | | packets comes from Motion Sensor.
|
|
253
|
+
* | | | If "CS" is specified, the `onadvertisement`
|
|
254
|
+
* | | | event hander will be called only when advertising
|
|
255
|
+
* | | | packets comes from Contact Sensor.
|
|
256
|
+
* | | | If "C" is specified, the `onadvertisement`
|
|
257
|
+
* | | | event hander will be called only when advertising
|
|
258
|
+
* | | | packets comes from Curtains.
|
|
259
|
+
* - id | String | Optional | If this value is set, the `onadvertisement`
|
|
260
|
+
* | | | event hander will be called only when advertising
|
|
261
|
+
* | | | packets comes from devices whose ID is as same as
|
|
262
|
+
* | | | this value.
|
|
263
|
+
* | | | The ID is identical to the MAC address.
|
|
264
|
+
* | | | This parameter is case-insensitive, and
|
|
265
|
+
* | | | colons are ignored.
|
|
266
|
+
*
|
|
267
|
+
* [Returen value]
|
|
268
|
+
* - Promise object
|
|
269
|
+
* Nothing will be passed to the `resolve()`.
|
|
270
|
+
* ---------------------------------------------------------------- */
|
|
271
|
+
startScan(params) {
|
|
272
|
+
let promise = new Promise((resolve, reject) => {
|
|
273
|
+
// Check the parameters
|
|
274
|
+
let valid = parameterChecker.check(params, {
|
|
275
|
+
model: { required: false, type: 'string', enum: ['H', 'h', 'T', 'P', 'C', 'c'] },
|
|
276
|
+
id: { required: false, type: 'string', min: 12, max: 17 },
|
|
277
|
+
}, false);
|
|
278
|
+
|
|
279
|
+
if (!valid) {
|
|
280
|
+
reject(new Error(parameterChecker.error.message));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!params) {
|
|
285
|
+
params = {};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Initialize the noble object
|
|
289
|
+
this._init().then(() => {
|
|
290
|
+
|
|
291
|
+
// Determine the values of the parameters
|
|
292
|
+
let p = {
|
|
293
|
+
model: params.model || '',
|
|
294
|
+
id: params.id || ''
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Set an handler for the 'discover' event
|
|
298
|
+
this.noble.on('discover', (peripheral) => {
|
|
299
|
+
let ad = switchbotAdvertising.parse(peripheral);
|
|
300
|
+
if (this._filterAdvertising(ad, p.id, p.model)) {
|
|
301
|
+
if (this.onadvertisement && typeof (this.onadvertisement) === 'function') {
|
|
302
|
+
this.onadvertisement(ad);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Start scaning
|
|
308
|
+
this.noble.startScanning(this._PRIMARY_SERVICE_UUID_LIST, true, (error) => {
|
|
309
|
+
if (error) {
|
|
310
|
+
reject(error);
|
|
311
|
+
} else {
|
|
312
|
+
resolve();
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}).catch((error) => {
|
|
316
|
+
reject(error);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
return promise;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* ------------------------------------------------------------------
|
|
323
|
+
* stopScan()
|
|
324
|
+
* - Stop to monitor advertising packets coming from switchbot devices
|
|
325
|
+
*
|
|
326
|
+
* [Arguments]
|
|
327
|
+
* - none
|
|
328
|
+
*
|
|
329
|
+
* [Returen value]
|
|
330
|
+
* - none
|
|
331
|
+
* ---------------------------------------------------------------- */
|
|
332
|
+
stopScan() {
|
|
333
|
+
this.noble.removeAllListeners('discover');
|
|
334
|
+
this.noble.stopScanning();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* ------------------------------------------------------------------
|
|
338
|
+
* wait(msec) {
|
|
339
|
+
* - Wait for the specified time (msec)
|
|
340
|
+
*
|
|
341
|
+
* [Arguments]
|
|
342
|
+
* - msec | Integer | Required | Msec.
|
|
343
|
+
*
|
|
344
|
+
* [Returen value]
|
|
345
|
+
* - Promise object
|
|
346
|
+
* Nothing will be passed to the `resolve()`.
|
|
347
|
+
* ---------------------------------------------------------------- */
|
|
348
|
+
wait(msec) {
|
|
349
|
+
return new Promise((resolve, reject) => {
|
|
350
|
+
// Check the parameters
|
|
351
|
+
let valid = parameterChecker.check({ msec: msec }, {
|
|
352
|
+
msec: { required: true, type: 'integer', min: 0 }
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (!valid) {
|
|
356
|
+
reject(new Error(parameterChecker.error.message));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// Set a timer
|
|
360
|
+
setTimeout(resolve, msec);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = Switchbot;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-switchbot",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "The node-switchbot is a Node.js module which allows you to move your Switchbot (Bot)'s arm and Switchbot Curtain(Curtain), also monitor the temperature/humidity from SwitchBot Thermometer & Hygrometer (Meter).",
|
|
5
|
+
"main": "./lib/switchbot.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"lib"
|
|
8
|
+
],
|
|
9
|
+
"directories": {
|
|
10
|
+
"lib": "./lib"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"check": "npm install && npm outdated",
|
|
14
|
+
"update": "ncu -u && npm update && npm install"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"switchbot",
|
|
18
|
+
"bot",
|
|
19
|
+
"meter",
|
|
20
|
+
"temperature",
|
|
21
|
+
"humidity",
|
|
22
|
+
"curtain",
|
|
23
|
+
"BLE",
|
|
24
|
+
"Bluetooth Low Energy",
|
|
25
|
+
"Bluetooth smart",
|
|
26
|
+
"Bluetooth"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://github.com/OpenWonderLabs",
|
|
29
|
+
"author": "OpenWonderLabs (https://github.com/OpenWonderLabs)",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/futomi/node-switchbot.git"
|
|
34
|
+
},
|
|
35
|
+
"readmeFilename": "README.md",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@homebridge/noble": "^1.9.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"npm-check-updates": "^11.8.5"
|
|
41
|
+
}
|
|
42
|
+
}
|