node-switchbot 2.4.0 → 2.5.0-beta.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/.github/npm-version-script.cjs +81 -0
- package/CHANGELOG.md +1 -1
- package/dist/advertising.d.ts +14 -148
- package/dist/advertising.d.ts.map +1 -1
- package/dist/advertising.js +36 -92
- package/dist/advertising.js.map +1 -1
- package/dist/device/woblindtilt.d.ts +98 -13
- package/dist/device/woblindtilt.d.ts.map +1 -1
- package/dist/device/woblindtilt.js +192 -86
- package/dist/device/woblindtilt.js.map +1 -1
- package/dist/device/woblindtilt.test.d.ts +2 -0
- package/dist/device/woblindtilt.test.d.ts.map +1 -0
- package/dist/device/woblindtilt.test.js +26 -0
- package/dist/device/woblindtilt.test.js.map +1 -0
- package/dist/device/wobulb.d.ts +17 -34
- package/dist/device/wobulb.d.ts.map +1 -1
- package/dist/device/wobulb.js +53 -61
- package/dist/device/wobulb.js.map +1 -1
- package/dist/device/wobulb.test.d.ts +2 -0
- package/dist/device/wobulb.test.d.ts.map +1 -0
- package/dist/device/wobulb.test.js +52 -0
- package/dist/device/wobulb.test.js.map +1 -0
- package/dist/device/woceilinglight.d.ts +21 -51
- package/dist/device/woceilinglight.d.ts.map +1 -1
- package/dist/device/woceilinglight.js +53 -64
- package/dist/device/woceilinglight.js.map +1 -1
- package/dist/device/woceilinglight.test.d.ts +2 -0
- package/dist/device/woceilinglight.test.d.ts.map +1 -0
- package/dist/device/woceilinglight.test.js +63 -0
- package/dist/device/woceilinglight.test.js.map +1 -0
- package/dist/device/wocontact.d.ts +1 -14
- package/dist/device/wocontact.d.ts.map +1 -1
- package/dist/device/wocontact.js +13 -13
- package/dist/device/wocontact.js.map +1 -1
- package/dist/device/wocontact.test.d.ts +2 -0
- package/dist/device/wocontact.test.d.ts.map +1 -0
- package/dist/device/wocontact.test.js +34 -0
- package/dist/device/wocontact.test.js.map +1 -0
- package/dist/device/wocurtain.d.ts +5 -16
- package/dist/device/wocurtain.d.ts.map +1 -1
- package/dist/device/wocurtain.js +58 -47
- package/dist/device/wocurtain.js.map +1 -1
- package/dist/device/wocurtain.test.d.ts +2 -0
- package/dist/device/wocurtain.test.d.ts.map +1 -0
- package/dist/device/wocurtain.test.js +33 -0
- package/dist/device/wocurtain.test.js.map +1 -0
- package/dist/device/wohand.d.ts +2 -10
- package/dist/device/wohand.d.ts.map +1 -1
- package/dist/device/wohand.js +31 -33
- package/dist/device/wohand.js.map +1 -1
- package/dist/device/wohand.test.d.ts +2 -0
- package/dist/device/wohand.test.d.ts.map +1 -0
- package/dist/device/wohand.test.js +62 -0
- package/dist/device/wohand.test.js.map +1 -0
- package/dist/device/wohand2.test.d.ts +2 -0
- package/dist/device/wohand2.test.d.ts.map +1 -0
- package/dist/device/wohand2.test.js +50 -0
- package/dist/device/wohand2.test.js.map +1 -0
- package/dist/device/wohub2.d.ts +1 -19
- package/dist/device/wohub2.d.ts.map +1 -1
- package/dist/device/wohub2.js +6 -2
- package/dist/device/wohub2.js.map +1 -1
- package/dist/device/wohumi.d.ts +2 -11
- package/dist/device/wohumi.d.ts.map +1 -1
- package/dist/device/wohumi.js +31 -33
- package/dist/device/wohumi.js.map +1 -1
- package/dist/device/wohumi.test.d.ts +2 -0
- package/dist/device/wohumi.test.d.ts.map +1 -0
- package/dist/device/wohumi.test.js +61 -0
- package/dist/device/wohumi.test.js.map +1 -0
- package/dist/device/woiosensorth.d.ts +1 -19
- package/dist/device/woiosensorth.d.ts.map +1 -1
- package/dist/device/woiosensorth.js +14 -10
- package/dist/device/woiosensorth.js.map +1 -1
- package/dist/device/woiosensorth.test.d.ts +2 -0
- package/dist/device/woiosensorth.test.d.ts.map +1 -0
- package/dist/device/woiosensorth.test.js +39 -0
- package/dist/device/woiosensorth.test.js.map +1 -0
- package/dist/device/woplugmini.d.ts +12 -38
- package/dist/device/woplugmini.d.ts.map +1 -1
- package/dist/device/woplugmini.js +35 -40
- package/dist/device/woplugmini.js.map +1 -1
- package/dist/device/woplugmini.test.d.ts +2 -0
- package/dist/device/woplugmini.test.d.ts.map +1 -0
- package/dist/device/woplugmini.test.js +91 -0
- package/dist/device/woplugmini.test.js.map +1 -0
- package/dist/device/wopresence.d.ts +2 -14
- package/dist/device/wopresence.d.ts.map +1 -1
- package/dist/device/wopresence.js +11 -7
- package/dist/device/wopresence.js.map +1 -1
- package/dist/device/wopresence.test.d.ts +2 -0
- package/dist/device/wopresence.test.d.ts.map +1 -0
- package/dist/device/wopresence.test.js +42 -0
- package/dist/device/wopresence.test.js.map +1 -0
- package/dist/device/wosensorth.d.ts +3 -37
- package/dist/device/wosensorth.d.ts.map +1 -1
- package/dist/device/wosensorth.js +24 -20
- package/dist/device/wosensorth.js.map +1 -1
- package/dist/device/wosensorth.test.d.ts +2 -0
- package/dist/device/wosensorth.test.d.ts.map +1 -0
- package/dist/device/wosensorth.test.js +59 -0
- package/dist/device/wosensorth.test.js.map +1 -0
- package/dist/device/wosmartlock.d.ts +19 -33
- package/dist/device/wosmartlock.d.ts.map +1 -1
- package/dist/device/wosmartlock.js +104 -83
- package/dist/device/wosmartlock.js.map +1 -1
- package/dist/device/wosmartlock.test.d.ts +2 -0
- package/dist/device/wosmartlock.test.d.ts.map +1 -0
- package/dist/device/wosmartlock.test.js +84 -0
- package/dist/device/wosmartlock.test.js.map +1 -0
- package/dist/device/wosmartlockpro.d.ts +13 -27
- package/dist/device/wosmartlockpro.d.ts.map +1 -1
- package/dist/device/wosmartlockpro.js +90 -70
- package/dist/device/wosmartlockpro.js.map +1 -1
- package/dist/device/wosmartlockpro.test.d.ts +2 -0
- package/dist/device/wosmartlockpro.test.d.ts.map +1 -0
- package/dist/device/wosmartlockpro.test.js +124 -0
- package/dist/device/wosmartlockpro.test.js.map +1 -0
- package/dist/device/wostrip.d.ts +18 -32
- package/dist/device/wostrip.d.ts.map +1 -1
- package/dist/device/wostrip.js +58 -70
- package/dist/device/wostrip.js.map +1 -1
- package/dist/device/wostrip.test.d.ts +2 -0
- package/dist/device/wostrip.test.d.ts.map +1 -0
- package/dist/device/wostrip.test.js +115 -0
- package/dist/device/wostrip.test.js.map +1 -0
- package/dist/device.d.ts +237 -39
- package/dist/device.d.ts.map +1 -1
- package/dist/device.js +478 -366
- package/dist/device.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/parameter-checker.d.ts.map +1 -1
- package/dist/parameter-checker.js.map +1 -1
- package/dist/settings.d.ts +8 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +12 -0
- package/dist/settings.js.map +1 -0
- package/dist/switchbot.d.ts +30 -10
- package/dist/switchbot.d.ts.map +1 -1
- package/dist/switchbot.js +39 -86
- package/dist/switchbot.js.map +1 -1
- package/dist/types/bledevicestatus.d.ts +288 -0
- package/dist/types/bledevicestatus.d.ts.map +1 -0
- package/dist/types/bledevicestatus.js +2 -0
- package/dist/types/bledevicestatus.js.map +1 -0
- package/dist/types/devicelist.d.ts +89 -0
- package/dist/types/devicelist.d.ts.map +1 -0
- package/dist/types/devicelist.js +6 -0
- package/dist/types/devicelist.js.map +1 -0
- package/dist/types/deviceresponse.d.ts +13 -0
- package/dist/types/deviceresponse.d.ts.map +1 -0
- package/dist/types/deviceresponse.js +2 -0
- package/dist/types/deviceresponse.js.map +1 -0
- package/dist/types/devicestatus.d.ts +149 -0
- package/dist/types/devicestatus.d.ts.map +1 -0
- package/dist/types/devicestatus.js +3 -0
- package/dist/types/devicestatus.js.map +1 -0
- package/dist/types/devicewebhookstatus.d.ts +156 -0
- package/dist/types/devicewebhookstatus.d.ts.map +1 -0
- package/dist/types/devicewebhookstatus.js +2 -0
- package/dist/types/devicewebhookstatus.js.map +1 -0
- package/dist/types/irdevicelist.d.ts +10 -0
- package/dist/types/irdevicelist.d.ts.map +1 -0
- package/dist/types/irdevicelist.js +2 -0
- package/dist/types/irdevicelist.js.map +1 -0
- package/dist/types/pushbody.d.ts +6 -0
- package/dist/types/pushbody.d.ts.map +1 -0
- package/dist/types/pushbody.js +2 -0
- package/dist/types/pushbody.js.map +1 -0
- package/dist/{types.d.ts → types/types.d.ts} +9 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js.map +1 -0
- package/jest.config.js +3 -0
- package/package.json +22 -14
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dist/{types.js → types/types.js} +0 -0
package/dist/device.js
CHANGED
|
@@ -1,51 +1,46 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { Advertising } from './advertising.js';
|
|
3
3
|
import { parameterChecker } from './parameter-checker.js';
|
|
4
|
+
import { CHAR_UUID_DEVICE, CHAR_UUID_NOTIFY, CHAR_UUID_WRITE, COMMAND_TIMEOUT_MSEC, READ_TIMEOUT_MSEC, SERV_UUID_PRIMARY, WRITE_TIMEOUT_MSEC, } from './settings.js';
|
|
4
5
|
export class SwitchbotDevice {
|
|
5
|
-
_peripheral;
|
|
6
6
|
_noble;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
_CHAR_UUID_WRITE = 'cba20002224d11e69fb80002a5d5c51b';
|
|
10
|
-
_CHAR_UUID_NOTIFY = 'cba20003224d11e69fb80002a5d5c51b';
|
|
11
|
-
_CHAR_UUID_DEVICE = '2a00';
|
|
12
|
-
_READ_TIMEOUT_MSEC = 3000;
|
|
13
|
-
_WRITE_TIMEOUT_MSEC = 3000;
|
|
14
|
-
_COMMAND_TIMEOUT_MSEC = 3000;
|
|
7
|
+
_peripheral;
|
|
8
|
+
_characteristics;
|
|
15
9
|
_id;
|
|
16
10
|
_address;
|
|
17
11
|
_model;
|
|
18
12
|
_modelName;
|
|
19
|
-
|
|
13
|
+
_explicitly;
|
|
20
14
|
_connected;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
15
|
+
onnotify_internal;
|
|
16
|
+
ondisconnect_internal;
|
|
17
|
+
onconnect_internal;
|
|
18
|
+
/**
|
|
19
|
+
* Constructor for the Device class.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} peripheral - The `peripheral` object from noble, representing this device.
|
|
22
|
+
* @param {Noble} noble - The Noble object created by the noble module.
|
|
27
23
|
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
* | | | which represents this device
|
|
31
|
-
* - noble | Noble | Required | The Noble object created by the noble module.
|
|
32
|
-
* ---------------------------------------------------------------- */
|
|
24
|
+
* This constructor initializes a new instance of the Device class with the specified peripheral and noble objects.
|
|
25
|
+
*/
|
|
33
26
|
constructor(peripheral, noble) {
|
|
34
27
|
this._peripheral = peripheral;
|
|
35
28
|
this._noble = noble;
|
|
36
|
-
this.
|
|
29
|
+
this._characteristics = null;
|
|
37
30
|
// Save the device information
|
|
38
31
|
const ad = Advertising.parse(peripheral);
|
|
39
32
|
this._id = ad ? ad.id : null;
|
|
40
33
|
this._address = ad ? ad.address : null;
|
|
41
34
|
this._model = ad ? ad.serviceData.model : null;
|
|
42
35
|
this._modelName = ad ? ad.serviceData.modelName : null;
|
|
43
|
-
this.
|
|
36
|
+
this._explicitly = false;
|
|
44
37
|
this._connected = false;
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
38
|
+
this._explicitly = false;
|
|
39
|
+
this._connected = false;
|
|
40
|
+
this.onconnect = () => { };
|
|
41
|
+
this.ondisconnect = () => { };
|
|
42
|
+
this.ondisconnect_internal = () => { };
|
|
43
|
+
this.onnotify_internal = () => { };
|
|
49
44
|
}
|
|
50
45
|
// Getters
|
|
51
46
|
get id() {
|
|
@@ -68,306 +63,343 @@ export class SwitchbotDevice {
|
|
|
68
63
|
return this._peripheral.state;
|
|
69
64
|
}
|
|
70
65
|
}
|
|
71
|
-
// Setters
|
|
72
66
|
get onconnect() {
|
|
73
|
-
return this.
|
|
67
|
+
return this.onconnect_internal;
|
|
74
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Sets the asynchronous connection handler.
|
|
71
|
+
*
|
|
72
|
+
* This setter assigns a function to be used as the asynchronous connection handler. The handler
|
|
73
|
+
* is expected to be a function that returns a Promise, which resolves once the connection process
|
|
74
|
+
* is complete. The resolution of the Promise does not carry any value.
|
|
75
|
+
*
|
|
76
|
+
* @param func A function that returns a Promise, representing the asynchronous operation of connecting.
|
|
77
|
+
* This function is expected to be called without any arguments.
|
|
78
|
+
* @throws Error if the provided argument is not a function, ensuring type safety.
|
|
79
|
+
*/
|
|
75
80
|
set onconnect(func) {
|
|
76
81
|
if (!func || typeof func !== 'function') {
|
|
77
|
-
throw new Error('The `onconnect` must be a function
|
|
82
|
+
throw new Error('The `onconnect` must be a function that returns a Promise<void>.');
|
|
78
83
|
}
|
|
79
|
-
this.
|
|
84
|
+
this.onconnect_internal = async () => {
|
|
85
|
+
await func();
|
|
86
|
+
};
|
|
80
87
|
}
|
|
81
88
|
get ondisconnect() {
|
|
82
|
-
return this.
|
|
89
|
+
return this.ondisconnect_internal;
|
|
83
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Sets the asynchronous disconnection handler.
|
|
93
|
+
*
|
|
94
|
+
* This setter configures a function to act as the asynchronous disconnection handler. The handler
|
|
95
|
+
* should be a function that returns a Promise, which resolves when the disconnection process
|
|
96
|
+
* is complete. The resolution of the Promise does not carry any value.
|
|
97
|
+
*
|
|
98
|
+
* @param func A function that returns a Promise, representing the asynchronous operation of disconnecting.
|
|
99
|
+
* This function is expected to be called without any arguments.
|
|
100
|
+
* @throws Error if the provided argument is not a function, to ensure that the handler is correctly typed.
|
|
101
|
+
*/
|
|
84
102
|
set ondisconnect(func) {
|
|
85
103
|
if (!func || typeof func !== 'function') {
|
|
86
|
-
throw new Error('The `ondisconnect` must be a function
|
|
104
|
+
throw new Error('The `ondisconnect` must be a function that returns a Promise<void>.');
|
|
87
105
|
}
|
|
88
|
-
this.
|
|
106
|
+
this.ondisconnect_internal = async () => {
|
|
107
|
+
await func();
|
|
108
|
+
};
|
|
89
109
|
}
|
|
90
|
-
|
|
91
|
-
*
|
|
92
|
-
* - Connect the device
|
|
110
|
+
/**
|
|
111
|
+
* Initiates an asynchronous connection process.
|
|
93
112
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
113
|
+
* This method marks the device as being connected explicitly by setting a flag, then proceeds
|
|
114
|
+
* to initiate the actual connection process by calling an internal asynchronous method `connect_internalAsync`.
|
|
115
|
+
* The `connect_internalAsync` method is responsible for handling the low-level connection logic.
|
|
96
116
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
connect() {
|
|
102
|
-
this.
|
|
103
|
-
return this.
|
|
117
|
+
* @returns A Promise that resolves when the connection process initiated by `connect_internalAsync` completes.
|
|
118
|
+
* The resolution of this Promise does not carry any value, indicating that the focus is on
|
|
119
|
+
* the completion of the connection process rather than the result of the connection itself.
|
|
120
|
+
*/
|
|
121
|
+
async connect() {
|
|
122
|
+
this._explicitly = true;
|
|
123
|
+
return await this.connect_internal();
|
|
104
124
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Initiates the device connection process.
|
|
127
|
+
*
|
|
128
|
+
* This method is marked as deprecated and is scheduled for removal in a future version. It is recommended
|
|
129
|
+
* to use `disconnectAsync()` instead. The method returns a Promise that resolves when the device is successfully
|
|
130
|
+
* connected or rejects if the connection cannot be established.
|
|
131
|
+
*
|
|
132
|
+
* The connection process involves several steps:
|
|
133
|
+
* 1. Checks the Bluetooth adapter's state. If it's not powered on, the promise is rejected.
|
|
134
|
+
* 2. Checks the current connection state of the device. If already connected, the promise resolves immediately.
|
|
135
|
+
* If in the process of connecting or disconnecting, the promise is rejected advising to wait.
|
|
136
|
+
* 3. Sets up event handlers for 'connect' and 'disconnect' events on the peripheral device to manage connection state.
|
|
137
|
+
* 4. Initiates the connection. If an error occurs during this step, the promise is rejected.
|
|
138
|
+
* 5. Once connected, retrieves the device's characteristics and subscribes to them. If this process fails, the device
|
|
139
|
+
* is disconnected, and the promise is rejected.
|
|
140
|
+
*
|
|
141
|
+
* @returns A Promise<void> that resolves when the device is connected or rejects with an error.
|
|
142
|
+
*/
|
|
143
|
+
async connect_internal() {
|
|
144
|
+
// Check the bluetooth state
|
|
145
|
+
if (this._noble._state !== 'poweredOn') {
|
|
146
|
+
throw new Error(`The Bluetooth status is ${this._noble._state}, not poweredOn.`);
|
|
147
|
+
}
|
|
148
|
+
// Check the connection state
|
|
149
|
+
const state = this.connectionState;
|
|
150
|
+
if (state === 'connected') {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
else if (state === 'connecting' || state === 'disconnecting') {
|
|
154
|
+
throw new Error(`Now ${state}. Wait for a few seconds then try again.`);
|
|
155
|
+
}
|
|
156
|
+
// Set event handlers for events fired on the `Peripheral` object
|
|
157
|
+
this._peripheral.once('connect', async () => {
|
|
158
|
+
this._connected = true;
|
|
159
|
+
await this.onconnect();
|
|
160
|
+
});
|
|
161
|
+
this._peripheral.once('disconnect', async () => {
|
|
162
|
+
this._connected = false;
|
|
163
|
+
this._characteristics = null;
|
|
164
|
+
this._peripheral.removeAllListeners();
|
|
165
|
+
try {
|
|
166
|
+
await this.ondisconnect_internal();
|
|
167
|
+
await this.ondisconnect();
|
|
117
168
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
169
|
+
catch (error) {
|
|
170
|
+
throw new Error('Error during disconnect:', error);
|
|
121
171
|
}
|
|
122
|
-
// Set event handlers for events fired on the `Peripheral` object
|
|
123
|
-
this._peripheral.once('connect', () => {
|
|
124
|
-
this._connected = true;
|
|
125
|
-
this._onconnect();
|
|
126
|
-
});
|
|
127
|
-
this._peripheral.once('disconnect', () => {
|
|
128
|
-
this._connected = false;
|
|
129
|
-
this._chars = null;
|
|
130
|
-
this._peripheral.removeAllListeners();
|
|
131
|
-
this._ondisconnect_internal();
|
|
132
|
-
this._ondisconnect();
|
|
133
|
-
});
|
|
134
|
-
// Connect
|
|
135
|
-
this._peripheral.connect((error) => {
|
|
136
|
-
if (error) {
|
|
137
|
-
reject(error);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
this._getCharacteristics()
|
|
141
|
-
.then((chars) => {
|
|
142
|
-
this._chars = chars;
|
|
143
|
-
return this._subscribe();
|
|
144
|
-
})
|
|
145
|
-
.then(() => {
|
|
146
|
-
resolve();
|
|
147
|
-
})
|
|
148
|
-
.catch((error) => {
|
|
149
|
-
this._peripheral.disconnect();
|
|
150
|
-
reject(error);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
172
|
});
|
|
173
|
+
// Connect
|
|
174
|
+
await this._peripheral.connectAsync();
|
|
175
|
+
const chars = await this.getCharacteristics();
|
|
176
|
+
this._characteristics = chars;
|
|
177
|
+
await this.subscribe();
|
|
154
178
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Watch the connection state
|
|
164
|
-
this._ondisconnect_internal = () => {
|
|
165
|
-
if (timer) {
|
|
166
|
-
clearTimeout(timer);
|
|
167
|
-
timer = null;
|
|
168
|
-
this._ondisconnect_internal = () => { };
|
|
169
|
-
}
|
|
170
|
-
reject(new Error('Failed to discover services and characteristics: DISCONNECTED'));
|
|
171
|
-
};
|
|
179
|
+
async getCharacteristics() {
|
|
180
|
+
// Set timeout timer
|
|
181
|
+
let timer = setTimeout(async () => {
|
|
182
|
+
await this.ondisconnect_internal();
|
|
183
|
+
timer = null;
|
|
184
|
+
throw new Error('Failed to discover services and characteristics: TIMEOUT');
|
|
185
|
+
}, 5000);
|
|
186
|
+
try {
|
|
172
187
|
// Discover services and characteristics
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
chars.write = char;
|
|
188
|
-
}
|
|
189
|
-
else if (char.uuid === this._CHAR_UUID_NOTIFY) {
|
|
190
|
-
chars.notify = char;
|
|
191
|
-
}
|
|
192
|
-
else if (char.uuid === this._CHAR_UUID_DEVICE) {
|
|
193
|
-
// Some models of Bot don't seem to support this characteristic UUID
|
|
194
|
-
chars.device = char;
|
|
195
|
-
}
|
|
188
|
+
const service_list = await this.discoverServices();
|
|
189
|
+
if (!timer) {
|
|
190
|
+
throw new Error('Failed to discover services and characteristics.');
|
|
191
|
+
}
|
|
192
|
+
const chars = {
|
|
193
|
+
write: null,
|
|
194
|
+
notify: null,
|
|
195
|
+
device: null,
|
|
196
|
+
};
|
|
197
|
+
for (const service of service_list) {
|
|
198
|
+
const char_list = await this.discoverCharacteristics(service);
|
|
199
|
+
for (const char of char_list) {
|
|
200
|
+
if (char.uuid === CHAR_UUID_WRITE) {
|
|
201
|
+
chars.write = char;
|
|
196
202
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
})().catch((error) => {
|
|
205
|
-
if (timer) {
|
|
206
|
-
clearTimeout(timer);
|
|
207
|
-
timer = null;
|
|
208
|
-
this._ondisconnect_internal = () => { };
|
|
209
|
-
reject(error);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
// Do nothing
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
_discoverServices() {
|
|
218
|
-
return new Promise((resolve, reject) => {
|
|
219
|
-
this._peripheral.discoverServices([], (error, service_list) => {
|
|
220
|
-
if (error) {
|
|
221
|
-
reject(error);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
let service = null;
|
|
225
|
-
for (const s of service_list) {
|
|
226
|
-
if (s.uuid === this._SERV_UUID_PRIMARY) {
|
|
227
|
-
service = s;
|
|
228
|
-
break;
|
|
203
|
+
else if (char.uuid === CHAR_UUID_NOTIFY) {
|
|
204
|
+
chars.notify = char;
|
|
205
|
+
}
|
|
206
|
+
else if (char.uuid === CHAR_UUID_DEVICE) {
|
|
207
|
+
// Some models of Bot don't seem to support this characteristic UUID
|
|
208
|
+
chars.device = char;
|
|
229
209
|
}
|
|
230
210
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
211
|
+
}
|
|
212
|
+
if (!chars.write || !chars.notify) {
|
|
213
|
+
throw new Error('No characteristic was found.');
|
|
214
|
+
}
|
|
215
|
+
return chars;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (timer) {
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
this.ondisconnect_internal = () => { };
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
if (timer) {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
this.ondisconnect_internal = () => { };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
239
230
|
}
|
|
240
|
-
|
|
241
|
-
return
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
231
|
+
async discoverServices() {
|
|
232
|
+
return await this._peripheral.discoverServicesAsync([])
|
|
233
|
+
.then((service_list) => {
|
|
234
|
+
const services = service_list.filter((s) => s.uuid === SERV_UUID_PRIMARY);
|
|
235
|
+
if (services.length === 0) {
|
|
236
|
+
throw new Error('No service was found.');
|
|
237
|
+
}
|
|
238
|
+
return services;
|
|
239
|
+
})
|
|
240
|
+
.catch((error) => {
|
|
241
|
+
throw error;
|
|
250
242
|
});
|
|
251
243
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
resolve();
|
|
268
|
-
});
|
|
244
|
+
/**
|
|
245
|
+
* Asynchronously discovers the characteristics of the specified service.
|
|
246
|
+
* This method is an asynchronous version of the deprecated `discoverCharacteristics` method.
|
|
247
|
+
* It attempts to discover the characteristics of the specified service and returns a Promise that resolves with the list of characteristics.
|
|
248
|
+
* If the discovery process fails, the Promise is rejected with an error.
|
|
249
|
+
* @param service The service object for which characteristics will be discovered.
|
|
250
|
+
* @returns A Promise that resolves with the list of characteristics or rejects with an error.
|
|
251
|
+
*/
|
|
252
|
+
async discoverCharacteristics(service) {
|
|
253
|
+
return service.discoverCharacteristicsAsync([])
|
|
254
|
+
.then((char_list) => {
|
|
255
|
+
return char_list;
|
|
256
|
+
})
|
|
257
|
+
.catch((error) => {
|
|
258
|
+
throw error;
|
|
269
259
|
});
|
|
270
260
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
char.
|
|
279
|
-
|
|
280
|
-
resolve();
|
|
261
|
+
async subscribe() {
|
|
262
|
+
const char = this._characteristics ? this._characteristics.notify : null;
|
|
263
|
+
if (!char) {
|
|
264
|
+
throw new Error('No notify characteristic was found.');
|
|
265
|
+
}
|
|
266
|
+
char.subscribeAsync()
|
|
267
|
+
.then(() => {
|
|
268
|
+
char.on('data', (buf) => {
|
|
269
|
+
this.onnotify_internal(buf);
|
|
281
270
|
});
|
|
271
|
+
})
|
|
272
|
+
.catch((error) => {
|
|
273
|
+
throw error;
|
|
282
274
|
});
|
|
283
275
|
}
|
|
284
|
-
|
|
285
|
-
*
|
|
286
|
-
* - Disconnect the device
|
|
276
|
+
/**
|
|
277
|
+
* Asynchronously unsubscribes from the device's notification characteristic.
|
|
287
278
|
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
279
|
+
* This method checks if the notification characteristic object exists within the cached characteristics
|
|
280
|
+
* (`this.chars`). If the characteristic is found, it proceeds to remove all event listeners attached to it
|
|
281
|
+
* to prevent any further handling of incoming data notifications. Then, it asynchronously unsubscribes from
|
|
282
|
+
* the notification characteristic using `unsubscribeAsync()`, effectively stopping the device from sending
|
|
283
|
+
* notifications to the client.
|
|
290
284
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
else if (state === 'connecting' || state === 'disconnecting') {
|
|
305
|
-
reject(new Error(`Now ${state}. Wait for a few seconds then try again.`));
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
// Unsubscribe
|
|
309
|
-
this._unsubscribe().then(() => {
|
|
310
|
-
// Disconnect
|
|
311
|
-
this._peripheral.disconnect(() => {
|
|
312
|
-
resolve();
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
});
|
|
285
|
+
* If the notification characteristic is not found, the method simply returns without performing any action.
|
|
286
|
+
*
|
|
287
|
+
* @return A Promise that resolves to `void` upon successful unsubscription or if the characteristic is not found.
|
|
288
|
+
*/
|
|
289
|
+
async unsubscribe() {
|
|
290
|
+
const char = this._characteristics ? this._characteristics.notify : null;
|
|
291
|
+
if (!char) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
char.removeAllListeners();
|
|
295
|
+
await char.unsubscribeAsync();
|
|
316
296
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Asynchronously disconnects from the device.
|
|
299
|
+
*
|
|
300
|
+
* This method handles the disconnection process by first checking the current
|
|
301
|
+
* connection state of the device. If the device is already disconnected, the
|
|
302
|
+
* method resolves immediately. If the device is in the process of connecting or
|
|
303
|
+
* disconnecting, it throws an error indicating that the operation should be retried
|
|
304
|
+
* after a brief wait. Otherwise, it proceeds to unsubscribe from any subscriptions
|
|
305
|
+
* and then initiates the disconnection process.
|
|
306
|
+
*
|
|
307
|
+
* Note: This method sets a flag to indicate that the disconnection was not initiated
|
|
308
|
+
* by the user explicitly.
|
|
309
|
+
*
|
|
310
|
+
* @returns A Promise that resolves when the disconnection process has completed.
|
|
311
|
+
* The Promise does not pass any value upon resolution.
|
|
312
|
+
*/
|
|
313
|
+
async disconnect() {
|
|
314
|
+
this._explicitly = false;
|
|
315
|
+
const state = this._peripheral.state;
|
|
316
|
+
if (state === 'disconnected') {
|
|
317
|
+
return; // Resolves the promise implicitly
|
|
320
318
|
}
|
|
321
|
-
else {
|
|
322
|
-
|
|
319
|
+
else if (state === 'connecting' || state === 'disconnecting') {
|
|
320
|
+
throw new Error(`Now ${state}. Wait for a few seconds then try again.`);
|
|
323
321
|
}
|
|
322
|
+
await this.unsubscribe(); // Wait for unsubscribe to complete
|
|
323
|
+
await this._peripheral.disconnectAsync();
|
|
324
324
|
}
|
|
325
|
-
|
|
326
|
-
*
|
|
327
|
-
* - Retrieve the device name
|
|
325
|
+
/**
|
|
326
|
+
* Disconnects from the device asynchronously if the connection was not initiated by the user.
|
|
328
327
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
328
|
+
* This method checks the `explicitly` flag to determine if the connection was initiated by the user.
|
|
329
|
+
* If not, it proceeds to disconnect from the device by calling `this.disconnectAsync()`. After the disconnection,
|
|
330
|
+
* it sets `explicitly` to true to prevent future disconnections when the connection is user-initiated.
|
|
331
331
|
*
|
|
332
|
-
*
|
|
333
|
-
* -
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
332
|
+
* This approach ensures that automatic disconnections only occur when the user has not explicitly initiated the connection,
|
|
333
|
+
* avoiding unnecessary disconnections in user-initiated sessions.
|
|
334
|
+
*
|
|
335
|
+
* @returns A Promise that resolves once the device has been successfully disconnected, applicable only when the
|
|
336
|
+
* connection was not user-initiated.
|
|
337
|
+
*/
|
|
338
|
+
async disconnect_internal() {
|
|
339
|
+
if (!this._explicitly) {
|
|
340
|
+
await this.disconnect();
|
|
341
|
+
this._explicitly = true; // Ensure this condition is updated to prevent re-entry or incorrect logic flow.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Asynchronously retrieves the device name.
|
|
346
|
+
* This method is designed to fetch the name of the device asynchronously and return it as a promise.
|
|
347
|
+
* It is marked as deprecated and will be removed in a future version. Use `getDeviceNameAsync` instead.
|
|
348
|
+
*
|
|
349
|
+
* @deprecated since version 2.4.0. Will be removed in version 3.0.0. Use `getDeviceNameAsync()` instead.
|
|
350
|
+
* @returns A Promise that resolves with the device name.
|
|
351
|
+
*/
|
|
352
|
+
async getDeviceName() {
|
|
353
|
+
let name = '';
|
|
354
|
+
await this.connect_internal()
|
|
355
|
+
.then(async () => {
|
|
356
|
+
if (!this._characteristics || !this._characteristics.device) {
|
|
357
|
+
// Some models of Bot don't seem to support this characteristic UUID
|
|
358
|
+
throw new Error(`The device does not support the characteristic UUID 0x${CHAR_UUID_DEVICE}.`);
|
|
359
|
+
}
|
|
360
|
+
return await this.read(this._characteristics.device);
|
|
361
|
+
})
|
|
362
|
+
.then((buf) => {
|
|
363
|
+
name = buf.toString('utf8');
|
|
364
|
+
return this.disconnect_internal();
|
|
365
|
+
})
|
|
366
|
+
.then(() => {
|
|
367
|
+
return name;
|
|
368
|
+
})
|
|
369
|
+
.catch((error) => {
|
|
370
|
+
throw new Error(error);
|
|
357
371
|
});
|
|
358
372
|
}
|
|
359
|
-
|
|
360
|
-
*
|
|
361
|
-
*
|
|
373
|
+
/**
|
|
374
|
+
* Asynchronously retrieves the device name.
|
|
375
|
+
* This method initiates a connection to the device, checks for the presence of a specific characteristic UUID,
|
|
376
|
+
* reads the characteristic to obtain the device name, and then disconnects from the device.
|
|
377
|
+
* It ensures that the device supports the required characteristic for fetching the name. If the characteristic
|
|
378
|
+
* is not supported, it throws an error. The method encapsulates the entire process of connecting, reading,
|
|
379
|
+
* and disconnecting, making it convenient to get the device name with a single call.
|
|
362
380
|
*
|
|
363
|
-
*
|
|
364
|
-
|
|
365
|
-
|
|
381
|
+
* @returns A Promise that resolves with the device name as a string.
|
|
382
|
+
*/
|
|
383
|
+
async getDeviceNameAsnyc() {
|
|
384
|
+
await this.connect_internal();
|
|
385
|
+
if (!this._characteristics || !this._characteristics.device) {
|
|
386
|
+
throw new Error(`The device does not support the characteristic UUID 0x${CHAR_UUID_DEVICE}.`);
|
|
387
|
+
}
|
|
388
|
+
const buf = await this.read(this._characteristics.device);
|
|
389
|
+
const name = buf.toString('utf8');
|
|
390
|
+
await this.disconnect_internal();
|
|
391
|
+
return name;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Asynchronously sets the device name to the specified value.
|
|
395
|
+
* Validates the new name to ensure it meets the criteria of being a string with a byte length between 1 and 100 bytes.
|
|
396
|
+
* If the validation fails, the promise is rejected with an error detailing the validation issue.
|
|
397
|
+
* Upon successful validation, the device name is updated, and the promise resolves without passing any value.
|
|
366
398
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
|
|
399
|
+
* @param name The new device name as a string. Must be 1 to 100 bytes in length.
|
|
400
|
+
* @returns A Promise that resolves to `void` upon successful update of the device name.
|
|
401
|
+
* @deprecated since version 2.4.0. Will be removed in version 3.0.0. Use `setDeviceNameAsync()` instead.
|
|
402
|
+
*/
|
|
371
403
|
setDeviceName(name) {
|
|
372
404
|
return new Promise((resolve, reject) => {
|
|
373
405
|
// Check the parameters
|
|
@@ -379,116 +411,196 @@ export class SwitchbotDevice {
|
|
|
379
411
|
return;
|
|
380
412
|
}
|
|
381
413
|
const buf = Buffer.from(name, 'utf8');
|
|
382
|
-
this.
|
|
414
|
+
this.connect_internal()
|
|
383
415
|
.then(() => {
|
|
384
|
-
if (!this.
|
|
416
|
+
if (!this._characteristics || !this._characteristics.device) {
|
|
385
417
|
// Some models of Bot don't seem to support this characteristic UUID
|
|
386
|
-
throw new Error(`The device does not support the characteristic UUID 0x${
|
|
418
|
+
throw new Error(`The device does not support the characteristic UUID 0x${CHAR_UUID_DEVICE}.`);
|
|
387
419
|
}
|
|
388
|
-
return this.
|
|
420
|
+
return this.write(this._characteristics.device, buf);
|
|
389
421
|
})
|
|
390
422
|
.then(() => {
|
|
391
|
-
return this.
|
|
423
|
+
return this.disconnect_internal();
|
|
392
424
|
})
|
|
393
425
|
.then(() => {
|
|
426
|
+
return;
|
|
394
427
|
resolve();
|
|
395
428
|
})
|
|
396
429
|
.catch((error) => {
|
|
397
|
-
|
|
430
|
+
throw new Error(error);
|
|
398
431
|
});
|
|
399
432
|
});
|
|
400
433
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Asynchronously sets the device name to the specified value.
|
|
436
|
+
* This method begins by validating the provided name to ensure it meets the criteria of being a string with a byte length between 1 and 100 bytes.
|
|
437
|
+
* If the name does not pass validation, the method throws an error with a message detailing the validation issue.
|
|
438
|
+
* After passing validation, the method converts the name into a UTF-8 encoded buffer.
|
|
439
|
+
* It then initiates a connection to the device. If the device does not support the required characteristic UUID for setting the device name,
|
|
440
|
+
* an error is thrown indicating the lack of support.
|
|
441
|
+
* Upon successfully connecting and verifying support, the method writes the new name to the device using the appropriate characteristic.
|
|
442
|
+
* Finally, it disconnects from the device, completing the name update process.
|
|
443
|
+
*
|
|
444
|
+
* @param name The new device name as a string. Must be 1 to 100 bytes in length.
|
|
445
|
+
* @returns A Promise that resolves to `void` upon successful update of the device name.
|
|
446
|
+
*/
|
|
447
|
+
async setDeviceNameAsync(name) {
|
|
448
|
+
// Check the parameters
|
|
449
|
+
const valid = parameterChecker.check({ name }, {
|
|
450
|
+
name: { required: true, type: 'string', minBytes: 1, maxBytes: 100 },
|
|
451
|
+
}, true);
|
|
452
|
+
if (!valid) {
|
|
453
|
+
throw new Error(parameterChecker.error.message);
|
|
454
|
+
}
|
|
455
|
+
const buf = Buffer.from(name, 'utf8');
|
|
456
|
+
await this.connect_internal();
|
|
457
|
+
if (!this._characteristics || !this._characteristics.device) {
|
|
458
|
+
// Some models of Bot don't seem to support this characteristic UUID
|
|
459
|
+
throw new Error(`The device does not support the characteristic UUID 0x${CHAR_UUID_DEVICE}.`);
|
|
460
|
+
}
|
|
461
|
+
await this.write(this._characteristics.device, buf);
|
|
462
|
+
await this.disconnect_internal();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Asynchronously sends a command to the device and awaits a response.
|
|
466
|
+
* This method encapsulates the process of sending a command encapsulated in a Buffer to the device,
|
|
467
|
+
* and then waiting for the device to respond. The method ensures that the device is connected before sending the command,
|
|
468
|
+
* writes the command to the device's write characteristic, waits for a response on the notify characteristic,
|
|
469
|
+
* and finally disconnects from the device. The response from the device is returned as a Buffer.
|
|
470
|
+
*
|
|
471
|
+
* @param req_buf A Buffer containing the command to be sent to the device.
|
|
472
|
+
* @returns A Promise that resolves with a Buffer containing the device's response to the command.
|
|
473
|
+
*/
|
|
474
|
+
async command(req_buf) {
|
|
475
|
+
if (!Buffer.isBuffer(req_buf)) {
|
|
476
|
+
throw new TypeError('The specified data is not acceptable for writing.');
|
|
477
|
+
}
|
|
478
|
+
await this.connect_internal();
|
|
479
|
+
if (!this._characteristics || !this._characteristics.write) {
|
|
480
|
+
throw new Error('No characteristics available.');
|
|
481
|
+
}
|
|
482
|
+
await this.write(this._characteristics.write, req_buf);
|
|
483
|
+
const res_buf = await this._waitCommandResponseAsync();
|
|
484
|
+
await this.disconnect_internal();
|
|
485
|
+
return res_buf;
|
|
432
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Waits for a response from the device after sending a command.
|
|
489
|
+
* This method sets up a promise that resolves when a response is received from the device or rejects if a timeout occurs.
|
|
490
|
+
* A timer is started to enforce a command timeout. If the response is received before the timeout, the timer is cleared.
|
|
491
|
+
* The method sets an internal handler (`onnotify_internal`) to process the incoming response.
|
|
492
|
+
* If the response is not received within the specified timeout period, the promise is rejected with a 'COMMAND_TIMEOUT' error.
|
|
493
|
+
* Once a response is received or a timeout occurs, the internal handler is reset to an empty function to prevent memory leaks.
|
|
494
|
+
* @deprecated since version 2.4.0. Will be removed in version 3.0.0. Use `_waitCommandResponseAsync()` instead.
|
|
495
|
+
*
|
|
496
|
+
* @returns A Promise that resolves with the received Buffer or rejects with an error if a timeout occurs.
|
|
497
|
+
*/
|
|
433
498
|
_waitCommandResponse() {
|
|
434
499
|
return new Promise((resolve, reject) => {
|
|
435
500
|
let timer = setTimeout(() => {
|
|
436
501
|
timer = undefined;
|
|
437
|
-
this.
|
|
502
|
+
this.onnotify_internal = () => { };
|
|
438
503
|
reject(new Error('COMMAND_TIMEOUT'));
|
|
439
|
-
},
|
|
440
|
-
this.
|
|
504
|
+
}, COMMAND_TIMEOUT_MSEC);
|
|
505
|
+
this.onnotify_internal = (buf) => {
|
|
441
506
|
if (timer) {
|
|
442
507
|
clearTimeout(timer);
|
|
443
508
|
timer = undefined;
|
|
444
509
|
}
|
|
445
|
-
this.
|
|
446
|
-
|
|
510
|
+
this.onnotify_internal = () => { };
|
|
511
|
+
return buf;
|
|
447
512
|
};
|
|
448
513
|
});
|
|
449
514
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
else {
|
|
467
|
-
resolve(buf);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
515
|
+
/**
|
|
516
|
+
* Asynchronously waits for a response from the device after sending a command.
|
|
517
|
+
* This method sets up a promise that resolves when a response is received from the device or rejects if a timeout occurs.
|
|
518
|
+
* A timer is started to enforce a command timeout. If the response is received before the timeout, the timer is cleared.
|
|
519
|
+
* The method sets an internal handler (`onnotify_internalAsync`) to process the incoming response.
|
|
520
|
+
* If the response is not received within the specified timeout period, the promise is rejected with a 'COMMAND_TIMEOUT' error.
|
|
521
|
+
* Once a response is received or a timeout occurs, the internal handler is reset to an empty function to prevent memory leaks.
|
|
522
|
+
*
|
|
523
|
+
* @returns A Promise that resolves with the received Buffer or rejects with an error if a timeout occurs.
|
|
524
|
+
*/
|
|
525
|
+
async _waitCommandResponseAsync() {
|
|
526
|
+
const timeout = READ_TIMEOUT_MSEC; // Timeout period in milliseconds
|
|
527
|
+
let timer = null;
|
|
528
|
+
// Setup a timeout to reject the operation if it takes too long
|
|
529
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
530
|
+
timer = setTimeout(() => reject(new Error('READ_TIMEOUT')), timeout);
|
|
470
531
|
});
|
|
532
|
+
// Setup the read operation promise
|
|
533
|
+
const readPromise = await this.onnotify_internal();
|
|
534
|
+
// Wait for either the read operation to complete or the timeout to occur
|
|
535
|
+
const result = await Promise.race([readPromise, timeoutPromise]);
|
|
536
|
+
// Clear the timeout if the read operation completes successfully
|
|
537
|
+
if (timer) {
|
|
538
|
+
clearTimeout(timer);
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
471
541
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
542
|
+
/**
|
|
543
|
+
* Asynchronously reads data from the specified characteristic of the device with a timeout.
|
|
544
|
+
* This method attempts to read data from the device's characteristic and sets a timeout to handle cases where the read operation takes too long.
|
|
545
|
+
* If the read operation does not complete within the specified timeout period, an error is thrown.
|
|
546
|
+
* Once the read operation completes successfully, the timeout is cleared to prevent it from triggering.
|
|
547
|
+
*
|
|
548
|
+
* @param char The characteristic of the device from which data will be read.
|
|
549
|
+
* @returns A Promise that resolves with the data read from the characteristic or rejects with an error if a timeout occurs.
|
|
550
|
+
*
|
|
551
|
+
* @throws Error if the read operation fails or if a timeout occurs.
|
|
552
|
+
*/
|
|
553
|
+
async read(char) {
|
|
554
|
+
let timer = setTimeout(() => {
|
|
555
|
+
throw new Error('READ_TIMEOUT');
|
|
556
|
+
}, READ_TIMEOUT_MSEC);
|
|
557
|
+
// Setup the read operation promise
|
|
558
|
+
const readPromise = await char.readAsync()
|
|
559
|
+
.then((result) => {
|
|
560
|
+
if (timer) {
|
|
561
|
+
clearTimeout(timer);
|
|
562
|
+
timer = undefined;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
throw new Error('READ_TIMEOUT');
|
|
566
|
+
}
|
|
567
|
+
return result;
|
|
568
|
+
});
|
|
569
|
+
// Wait for either the read operation to complete or the timeout to occur
|
|
570
|
+
const result = await Promise.race([readPromise, timer]);
|
|
571
|
+
// Clear the timeout if the read operation completes successfully
|
|
572
|
+
if (timer) {
|
|
573
|
+
clearTimeout(timer);
|
|
574
|
+
}
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Asynchronously writes data to a specified characteristic of the device.
|
|
579
|
+
* This method sends a buffer of data to the device's characteristic and sets a timeout to handle cases where the write operation takes too long.
|
|
580
|
+
* If the write operation does not complete within the specified timeout period, an error is thrown.
|
|
581
|
+
* Once the write operation completes successfully, the timeout is cleared to prevent it from triggering.
|
|
582
|
+
*
|
|
583
|
+
* @param char The characteristic of the device to which the data will be written.
|
|
584
|
+
* @param buf A Buffer containing the data to be written to the device.
|
|
585
|
+
* @returns A Promise that resolves when the write operation completes successfully or rejects with an error if a timeout occurs.
|
|
586
|
+
*/
|
|
587
|
+
async write(char, buf) {
|
|
588
|
+
let timer = setTimeout(() => {
|
|
589
|
+
throw new Error('WRITE_TIMEOUT');
|
|
590
|
+
}, WRITE_TIMEOUT_MSEC);
|
|
591
|
+
// write characteristic data
|
|
592
|
+
await char.writeAsync(buf, false)
|
|
593
|
+
.then(() => {
|
|
594
|
+
if (timer) {
|
|
595
|
+
clearTimeout(timer);
|
|
596
|
+
timer = undefined;
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
throw new Error('READ_TIMEOUT');
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
.catch((error) => {
|
|
603
|
+
throw new Error(`WRITE_TIMEOUT, ${error}`);
|
|
492
604
|
});
|
|
493
605
|
}
|
|
494
606
|
}
|