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.
@@ -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
+ }