@microbit/microbit-connection 0.1.0 → 0.9.0-apps.alpha.1
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/README.md +4 -4
- package/build/cjs/accelerometer-service.d.ts +5 -5
- package/build/cjs/accelerometer-service.js +41 -42
- package/build/cjs/accelerometer-service.js.map +1 -1
- package/build/cjs/async-util.d.ts +9 -0
- package/build/cjs/async-util.js +27 -7
- package/build/cjs/async-util.js.map +1 -1
- package/build/cjs/bluetooth-device-wrapper.d.ts +61 -27
- package/build/cjs/bluetooth-device-wrapper.js +285 -266
- package/build/cjs/bluetooth-device-wrapper.js.map +1 -1
- package/build/cjs/bluetooth-profile.d.ts +43 -35
- package/build/cjs/bluetooth-profile.js +35 -29
- package/build/cjs/bluetooth-profile.js.map +1 -1
- package/build/cjs/bluetooth.d.ts +10 -6
- package/build/cjs/bluetooth.js +286 -100
- package/build/cjs/bluetooth.js.map +1 -1
- package/build/cjs/button-service.d.ts +5 -5
- package/build/cjs/button-service.js +31 -45
- package/build/cjs/button-service.js.map +1 -1
- package/build/cjs/device-information-service.d.ts +6 -0
- package/build/cjs/device-information-service.js +34 -0
- package/build/cjs/device-information-service.js.map +1 -0
- package/build/cjs/device.d.ts +65 -8
- package/build/cjs/device.js +16 -2
- package/build/cjs/device.js.map +1 -1
- package/build/cjs/dfu-service.d.ts +9 -0
- package/build/cjs/dfu-service.js +26 -0
- package/build/cjs/dfu-service.js.map +1 -0
- package/build/cjs/flashing/flashing-full.d.ts +14 -0
- package/build/cjs/flashing/flashing-full.js +87 -0
- package/build/cjs/flashing/flashing-full.js.map +1 -0
- package/build/cjs/flashing/flashing-makecode.d.ts +6 -0
- package/build/cjs/flashing/flashing-makecode.js +48 -0
- package/build/cjs/flashing/flashing-makecode.js.map +1 -0
- package/build/cjs/flashing/flashing-partial.d.ts +9 -0
- package/build/cjs/flashing/flashing-partial.js +98 -0
- package/build/cjs/flashing/flashing-partial.js.map +1 -0
- package/build/cjs/flashing/flashing-v1.d.ts +11 -0
- package/build/cjs/flashing/flashing-v1.js +24 -0
- package/build/cjs/flashing/flashing-v1.js.map +1 -0
- package/build/cjs/flashing/nordic-dfu.d.ts +3 -0
- package/build/cjs/flashing/nordic-dfu.js +214 -0
- package/build/cjs/flashing/nordic-dfu.js.map +1 -0
- package/build/cjs/flashing/zip.d.ts +12 -0
- package/build/cjs/flashing/zip.js +177 -0
- package/build/cjs/flashing/zip.js.map +1 -0
- package/build/cjs/index.d.ts +3 -3
- package/build/cjs/index.js +2 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/led-service.d.ts +5 -7
- package/build/cjs/led-service.js +13 -44
- package/build/cjs/led-service.js.map +1 -1
- package/build/cjs/logging.d.ts +1 -1
- package/build/cjs/logging.js +3 -3
- package/build/cjs/logging.js.map +1 -1
- package/build/cjs/magnetometer-service.d.ts +5 -9
- package/build/cjs/magnetometer-service.js +30 -65
- package/build/cjs/magnetometer-service.js.map +1 -1
- package/build/cjs/partial-flashing-service.d.ts +45 -0
- package/build/cjs/partial-flashing-service.js +110 -0
- package/build/cjs/partial-flashing-service.js.map +1 -0
- package/build/cjs/uart-service.d.ts +4 -5
- package/build/cjs/uart-service.js +19 -40
- package/build/cjs/uart-service.js.map +1 -1
- package/build/cjs/usb-device-wrapper.js +33 -8
- package/build/cjs/usb-device-wrapper.js.map +1 -1
- package/build/cjs/usb-partial-flashing.d.ts +1 -3
- package/build/cjs/usb-partial-flashing.js +4 -3
- package/build/cjs/usb-partial-flashing.js.map +1 -1
- package/build/cjs/usb-radio-bridge.js +1 -2
- package/build/cjs/usb-radio-bridge.js.map +1 -1
- package/build/cjs/usb.d.ts +4 -2
- package/build/cjs/usb.js +31 -16
- package/build/cjs/usb.js.map +1 -1
- package/build/esm/accelerometer-service.d.ts +5 -5
- package/build/esm/accelerometer-service.js +42 -43
- package/build/esm/accelerometer-service.js.map +1 -1
- package/build/esm/async-util.d.ts +9 -0
- package/build/esm/async-util.js +22 -6
- package/build/esm/async-util.js.map +1 -1
- package/build/esm/bluetooth-device-wrapper.d.ts +61 -27
- package/build/esm/bluetooth-device-wrapper.js +284 -265
- package/build/esm/bluetooth-device-wrapper.js.map +1 -1
- package/build/esm/bluetooth-profile.d.ts +43 -35
- package/build/esm/bluetooth-profile.js +35 -29
- package/build/esm/bluetooth-profile.js.map +1 -1
- package/build/esm/bluetooth.d.ts +10 -6
- package/build/esm/bluetooth.js +263 -103
- package/build/esm/bluetooth.js.map +1 -1
- package/build/esm/button-service.d.ts +5 -5
- package/build/esm/button-service.js +32 -46
- package/build/esm/button-service.js.map +1 -1
- package/build/esm/device-information-service.d.ts +6 -0
- package/build/esm/device-information-service.js +30 -0
- package/build/esm/device-information-service.js.map +1 -0
- package/build/esm/device.d.ts +65 -8
- package/build/esm/device.js +15 -1
- package/build/esm/device.js.map +1 -1
- package/build/esm/dfu-service.d.ts +9 -0
- package/build/esm/dfu-service.js +22 -0
- package/build/esm/dfu-service.js.map +1 -0
- package/build/esm/flashing/flashing-full.d.ts +14 -0
- package/build/esm/flashing/flashing-full.js +84 -0
- package/build/esm/flashing/flashing-full.js.map +1 -0
- package/build/esm/flashing/flashing-makecode.d.ts +6 -0
- package/build/esm/flashing/flashing-makecode.js +44 -0
- package/build/esm/flashing/flashing-makecode.js.map +1 -0
- package/build/esm/flashing/flashing-partial.d.ts +9 -0
- package/build/esm/flashing/flashing-partial.js +95 -0
- package/build/esm/flashing/flashing-partial.js.map +1 -0
- package/build/esm/flashing/flashing-v1.d.ts +11 -0
- package/build/esm/flashing/flashing-v1.js +20 -0
- package/build/esm/flashing/flashing-v1.js.map +1 -0
- package/build/esm/flashing/nordic-dfu.d.ts +3 -0
- package/build/esm/flashing/nordic-dfu.js +211 -0
- package/build/esm/flashing/nordic-dfu.js.map +1 -0
- package/build/esm/flashing/zip.d.ts +12 -0
- package/build/esm/flashing/zip.js +174 -0
- package/build/esm/flashing/zip.js.map +1 -0
- package/build/esm/index.d.ts +3 -3
- package/build/esm/index.js +2 -2
- package/build/esm/index.js.map +1 -1
- package/build/esm/led-service.d.ts +5 -7
- package/build/esm/led-service.js +13 -44
- package/build/esm/led-service.js.map +1 -1
- package/build/esm/logging.d.ts +1 -1
- package/build/esm/logging.js +1 -1
- package/build/esm/logging.js.map +1 -1
- package/build/esm/magnetometer-service.d.ts +5 -9
- package/build/esm/magnetometer-service.js +31 -66
- package/build/esm/magnetometer-service.js.map +1 -1
- package/build/esm/partial-flashing-service.d.ts +45 -0
- package/build/esm/partial-flashing-service.js +106 -0
- package/build/esm/partial-flashing-service.js.map +1 -0
- package/build/esm/uart-service.d.ts +4 -5
- package/build/esm/uart-service.js +19 -40
- package/build/esm/uart-service.js.map +1 -1
- package/build/esm/usb-device-wrapper.js +33 -8
- package/build/esm/usb-device-wrapper.js.map +1 -1
- package/build/esm/usb-partial-flashing.d.ts +1 -3
- package/build/esm/usb-partial-flashing.js +4 -3
- package/build/esm/usb-partial-flashing.js.map +1 -1
- package/build/esm/usb-radio-bridge.js +2 -3
- package/build/esm/usb-radio-bridge.js.map +1 -1
- package/build/esm/usb.d.ts +4 -2
- package/build/esm/usb.js +33 -18
- package/build/esm/usb.js.map +1 -1
- package/package.json +7 -3
|
@@ -3,67 +3,45 @@
|
|
|
3
3
|
*
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
|
+
import { BleClient, } from "@capacitor-community/bluetooth-le";
|
|
7
|
+
import { Capacitor } from "@capacitor/core";
|
|
6
8
|
import { AccelerometerService } from "./accelerometer-service.js";
|
|
7
|
-
import {
|
|
9
|
+
import { delay, DisconnectError, disconnectErrorCallback, TimeoutError, timeoutErrorAfter, } from "./async-util.js";
|
|
8
10
|
import { ButtonService } from "./button-service.js";
|
|
11
|
+
import { DeviceInformationService } from "./device-information-service.js";
|
|
9
12
|
import { DeviceError } from "./device.js";
|
|
10
13
|
import { LedService } from "./led-service.js";
|
|
11
|
-
import {
|
|
14
|
+
import { ConsoleLogging } from "./logging.js";
|
|
12
15
|
import { MagnetometerService } from "./magnetometer-service.js";
|
|
13
|
-
import {
|
|
16
|
+
import { MicroBitMode, PartialFlashingService, } from "./partial-flashing-service.js";
|
|
14
17
|
import { UARTService } from "./uart-service.js";
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
enumerable: true,
|
|
41
|
-
configurable: true,
|
|
42
|
-
writable: true,
|
|
43
|
-
value: events
|
|
44
|
-
});
|
|
45
|
-
Object.defineProperty(this, "service", {
|
|
46
|
-
enumerable: true,
|
|
47
|
-
configurable: true,
|
|
48
|
-
writable: true,
|
|
49
|
-
value: void 0
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
get() {
|
|
53
|
-
return this.service;
|
|
54
|
-
}
|
|
55
|
-
async createIfNeeded(gattServer, dispatcher, queueGattOperation, listenerInit) {
|
|
56
|
-
this.service =
|
|
57
|
-
this.service ??
|
|
58
|
-
(await this.serviceFactory(gattServer, dispatcher, queueGattOperation, listenerInit));
|
|
59
|
-
return this.service;
|
|
60
|
-
}
|
|
61
|
-
dispose() {
|
|
62
|
-
this.service = undefined;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
18
|
+
export const bondingTimeoutInMs = 40_000;
|
|
19
|
+
export const connectTimeoutInMs = 10_000;
|
|
20
|
+
export const scanningTimeoutInMs = 10_000;
|
|
21
|
+
export const isAndroid = () => Capacitor.getPlatform() === "android";
|
|
22
|
+
// TODO: We've removed the support for these behaviours as we need to
|
|
23
|
+
// re-evaluate how best to support then via capacitor-ble (or reinstate
|
|
24
|
+
// the direct Web Bluetooth connection code.
|
|
25
|
+
//
|
|
26
|
+
// On ChromeOS and Mac there's no timeout and no clear way to abort
|
|
27
|
+
// device.gatt.connect(), so we accept that sometimes we'll still
|
|
28
|
+
// be trying to connect when we'd rather not be. If it succeeds when
|
|
29
|
+
// we no longer intend to be connected then we disconnect at that
|
|
30
|
+
// point. If we try to connect when a previous connection attempt is
|
|
31
|
+
// still around then we wait for it for our timeout period.
|
|
32
|
+
//
|
|
33
|
+
// On Windows it times out after 7s.
|
|
34
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=684073
|
|
35
|
+
//
|
|
36
|
+
// Additionally we've remove the delay before trying to connect again
|
|
37
|
+
// on Windows.
|
|
38
|
+
//
|
|
39
|
+
// We also used to have a timeout around requestDevice that reloaded the page.
|
|
40
|
+
//
|
|
41
|
+
// > In some situations the Chrome device prompt simply doesn't appear so we time
|
|
42
|
+
// > this out after 30 seconds and reload the page
|
|
65
43
|
export class BluetoothDeviceWrapper {
|
|
66
|
-
constructor(device, logging = new
|
|
44
|
+
constructor(device, logging = new ConsoleLogging(), dispatchTypedEvent, currentEvents, callbacks) {
|
|
67
45
|
Object.defineProperty(this, "device", {
|
|
68
46
|
enumerable: true,
|
|
69
47
|
configurable: true,
|
|
@@ -76,12 +54,6 @@ export class BluetoothDeviceWrapper {
|
|
|
76
54
|
writable: true,
|
|
77
55
|
value: logging
|
|
78
56
|
});
|
|
79
|
-
Object.defineProperty(this, "dispatchTypedEvent", {
|
|
80
|
-
enumerable: true,
|
|
81
|
-
configurable: true,
|
|
82
|
-
writable: true,
|
|
83
|
-
value: dispatchTypedEvent
|
|
84
|
-
});
|
|
85
57
|
Object.defineProperty(this, "currentEvents", {
|
|
86
58
|
enumerable: true,
|
|
87
59
|
configurable: true,
|
|
@@ -102,138 +74,103 @@ export class BluetoothDeviceWrapper {
|
|
|
102
74
|
writable: true,
|
|
103
75
|
value: 0
|
|
104
76
|
});
|
|
105
|
-
|
|
106
|
-
// device.gatt.connect(), so we accept that sometimes we'll still
|
|
107
|
-
// be trying to connect when we'd rather not be. If it succeeds when
|
|
108
|
-
// we no longer intend to be connected then we disconnect at that
|
|
109
|
-
// point. If we try to connect when a previous connection attempt is
|
|
110
|
-
// still around then we wait for it for our timeout period.
|
|
111
|
-
//
|
|
112
|
-
// On Windows it times out after 7s.
|
|
113
|
-
// https://bugs.chromium.org/p/chromium/issues/detail?id=684073
|
|
114
|
-
Object.defineProperty(this, "gattConnectPromise", {
|
|
77
|
+
Object.defineProperty(this, "connected", {
|
|
115
78
|
enumerable: true,
|
|
116
79
|
configurable: true,
|
|
117
80
|
writable: true,
|
|
118
|
-
value:
|
|
119
|
-
});
|
|
120
|
-
Object.defineProperty(this, "disconnectPromise", {
|
|
121
|
-
enumerable: true,
|
|
122
|
-
configurable: true,
|
|
123
|
-
writable: true,
|
|
124
|
-
value: void 0
|
|
81
|
+
value: false
|
|
125
82
|
});
|
|
126
|
-
Object.defineProperty(this, "
|
|
83
|
+
Object.defineProperty(this, "isReconnect", {
|
|
127
84
|
enumerable: true,
|
|
128
85
|
configurable: true,
|
|
129
86
|
writable: true,
|
|
130
87
|
value: false
|
|
131
88
|
});
|
|
132
|
-
|
|
89
|
+
// Only updated after the full connection flow completes not during bond handling.
|
|
90
|
+
Object.defineProperty(this, "serviceIds", {
|
|
133
91
|
enumerable: true,
|
|
134
92
|
configurable: true,
|
|
135
93
|
writable: true,
|
|
136
|
-
value:
|
|
94
|
+
value: new Set()
|
|
137
95
|
});
|
|
138
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "accelerometer", {
|
|
139
97
|
enumerable: true,
|
|
140
98
|
configurable: true,
|
|
141
99
|
writable: true,
|
|
142
100
|
value: void 0
|
|
143
101
|
});
|
|
144
|
-
Object.defineProperty(this, "
|
|
102
|
+
Object.defineProperty(this, "buttons", {
|
|
145
103
|
enumerable: true,
|
|
146
104
|
configurable: true,
|
|
147
105
|
writable: true,
|
|
148
|
-
value:
|
|
149
|
-
"accelerometerdatachanged",
|
|
150
|
-
])
|
|
106
|
+
value: void 0
|
|
151
107
|
});
|
|
152
|
-
Object.defineProperty(this, "
|
|
108
|
+
Object.defineProperty(this, "deviceInformation", {
|
|
153
109
|
enumerable: true,
|
|
154
110
|
configurable: true,
|
|
155
111
|
writable: true,
|
|
156
|
-
value:
|
|
157
|
-
"buttonachanged",
|
|
158
|
-
"buttonbchanged",
|
|
159
|
-
])
|
|
112
|
+
value: void 0
|
|
160
113
|
});
|
|
161
114
|
Object.defineProperty(this, "led", {
|
|
162
115
|
enumerable: true,
|
|
163
116
|
configurable: true,
|
|
164
117
|
writable: true,
|
|
165
|
-
value:
|
|
118
|
+
value: void 0
|
|
166
119
|
});
|
|
167
120
|
Object.defineProperty(this, "magnetometer", {
|
|
168
121
|
enumerable: true,
|
|
169
122
|
configurable: true,
|
|
170
123
|
writable: true,
|
|
171
|
-
value:
|
|
172
|
-
"magnetometerdatachanged",
|
|
173
|
-
])
|
|
124
|
+
value: void 0
|
|
174
125
|
});
|
|
175
126
|
Object.defineProperty(this, "uart", {
|
|
176
127
|
enumerable: true,
|
|
177
128
|
configurable: true,
|
|
178
129
|
writable: true,
|
|
179
|
-
value:
|
|
130
|
+
value: void 0
|
|
180
131
|
});
|
|
181
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Only defined after connection.
|
|
134
|
+
*/
|
|
135
|
+
Object.defineProperty(this, "boardVersion", {
|
|
182
136
|
enumerable: true,
|
|
183
137
|
configurable: true,
|
|
184
138
|
writable: true,
|
|
185
|
-
value:
|
|
186
|
-
this.accelerometer,
|
|
187
|
-
this.buttons,
|
|
188
|
-
this.led,
|
|
189
|
-
this.magnetometer,
|
|
190
|
-
this.uart,
|
|
191
|
-
]
|
|
139
|
+
value: void 0
|
|
192
140
|
});
|
|
193
|
-
Object.defineProperty(this, "
|
|
141
|
+
Object.defineProperty(this, "services", {
|
|
194
142
|
enumerable: true,
|
|
195
143
|
configurable: true,
|
|
196
144
|
writable: true,
|
|
197
145
|
value: void 0
|
|
198
146
|
});
|
|
199
|
-
Object.defineProperty(this, "
|
|
147
|
+
Object.defineProperty(this, "waitingForDisconnectEventCallbacks", {
|
|
200
148
|
enumerable: true,
|
|
201
149
|
configurable: true,
|
|
202
150
|
writable: true,
|
|
203
|
-
value:
|
|
204
|
-
return new DeviceError({
|
|
205
|
-
code: "device-disconnected",
|
|
206
|
-
message: "Error processing gatt operations queue - device disconnected",
|
|
207
|
-
});
|
|
208
|
-
}
|
|
151
|
+
value: []
|
|
209
152
|
});
|
|
210
|
-
Object.defineProperty(this, "
|
|
153
|
+
Object.defineProperty(this, "internalNotificationListeners", {
|
|
211
154
|
enumerable: true,
|
|
212
155
|
configurable: true,
|
|
213
156
|
writable: true,
|
|
214
|
-
value: new
|
|
215
|
-
abortCheck: () => {
|
|
216
|
-
if (!this.device.gatt?.connected) {
|
|
217
|
-
return this.disconnectedRejectionErrorFactory;
|
|
218
|
-
}
|
|
219
|
-
return undefined;
|
|
220
|
-
},
|
|
221
|
-
})
|
|
157
|
+
value: new Map()
|
|
222
158
|
});
|
|
223
159
|
Object.defineProperty(this, "handleDisconnectEvent", {
|
|
224
160
|
enumerable: true,
|
|
225
161
|
configurable: true,
|
|
226
162
|
writable: true,
|
|
227
163
|
value: async () => {
|
|
164
|
+
this.waitingForDisconnectEventCallbacks.forEach((cb) => cb());
|
|
165
|
+
this.waitingForDisconnectEventCallbacks.length = 0;
|
|
166
|
+
this.connected = false;
|
|
228
167
|
try {
|
|
229
168
|
if (!this.duringExplicitConnectDisconnect) {
|
|
230
|
-
this.logging.log("Bluetooth
|
|
231
|
-
// stateOnReconnectionAttempt();
|
|
232
|
-
this.disposeServices();
|
|
169
|
+
this.logging.log("Bluetooth disconnected... automatically trying reconnect");
|
|
233
170
|
await this.reconnect();
|
|
234
171
|
}
|
|
235
172
|
else {
|
|
236
|
-
this.logging.log("Bluetooth
|
|
173
|
+
this.logging.log("Bluetooth disconnect ignored during explicit disconnect");
|
|
237
174
|
}
|
|
238
175
|
}
|
|
239
176
|
catch (e) {
|
|
@@ -241,21 +178,25 @@ export class BluetoothDeviceWrapper {
|
|
|
241
178
|
}
|
|
242
179
|
}
|
|
243
180
|
});
|
|
244
|
-
device.
|
|
181
|
+
this.accelerometer = new AccelerometerService(device.deviceId, dispatchTypedEvent);
|
|
182
|
+
this.buttons = new ButtonService(device.deviceId, dispatchTypedEvent);
|
|
183
|
+
this.deviceInformation = new DeviceInformationService(device.deviceId);
|
|
184
|
+
this.led = new LedService(device.deviceId);
|
|
185
|
+
this.magnetometer = new MagnetometerService(device.deviceId, dispatchTypedEvent);
|
|
186
|
+
this.uart = new UARTService(device.deviceId, dispatchTypedEvent);
|
|
187
|
+
this.services = [
|
|
188
|
+
this.accelerometer,
|
|
189
|
+
this.buttons,
|
|
190
|
+
this.led,
|
|
191
|
+
this.magnetometer,
|
|
192
|
+
this.uart,
|
|
193
|
+
];
|
|
245
194
|
}
|
|
246
195
|
async connect() {
|
|
247
196
|
this.logging.event({
|
|
248
197
|
type: this.isReconnect ? "Reconnect" : "Connect",
|
|
249
198
|
message: "Bluetooth connect start",
|
|
250
199
|
});
|
|
251
|
-
if (this.duringExplicitConnectDisconnect) {
|
|
252
|
-
this.logging.log("Skipping connect attempt when one is already in progress");
|
|
253
|
-
// Wait for the gattConnectPromise while showing a "connecting" dialog.
|
|
254
|
-
// If the user clicks disconnect while the automatic reconnect is in progress,
|
|
255
|
-
// then clicks reconnect, we need to wait rather than return immediately.
|
|
256
|
-
await this.gattConnectPromise;
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
200
|
if (this.isReconnect) {
|
|
260
201
|
this.callbacks.onReconnecting();
|
|
261
202
|
}
|
|
@@ -263,70 +204,19 @@ export class BluetoothDeviceWrapper {
|
|
|
263
204
|
this.callbacks.onConnecting();
|
|
264
205
|
}
|
|
265
206
|
this.duringExplicitConnectDisconnect++;
|
|
266
|
-
if (this.device.gatt === undefined) {
|
|
267
|
-
throw new Error("BluetoothRemoteGATTServer for micro:bit device is undefined");
|
|
268
|
-
}
|
|
269
|
-
if (isWindowsOS) {
|
|
270
|
-
// On Windows, the micro:bit can take around 3 seconds to respond to gatt.disconnect().
|
|
271
|
-
// Attempting to connect/reconnect before the micro:bit has responded results in another
|
|
272
|
-
// gattserverdisconnected event being fired. We then fail to get primaryService on a
|
|
273
|
-
// disconnected GATT server.
|
|
274
|
-
await this.connectReadyPromise;
|
|
275
|
-
}
|
|
276
207
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.gattConnectPromise =
|
|
280
|
-
this.gattConnectPromise ??
|
|
281
|
-
this.device.gatt
|
|
282
|
-
.connect()
|
|
283
|
-
.then(async () => {
|
|
284
|
-
// We always do this even if we might immediately disconnect as disconnecting
|
|
285
|
-
// without using services causes getPrimaryService calls to hang on subsequent
|
|
286
|
-
// reconnect - probably a device-side issue.
|
|
287
|
-
this.boardVersion = await this.getBoardVersion();
|
|
288
|
-
// This connection could be arbitrarily later when our manual timeout may have passed.
|
|
289
|
-
// Do we still want to be connected?
|
|
290
|
-
if (!this.connecting) {
|
|
291
|
-
this.logging.log("Bluetooth GATT server connect after timeout, triggering disconnect");
|
|
292
|
-
this.disconnectPromise = (async () => {
|
|
293
|
-
await this.disconnectInternal(false);
|
|
294
|
-
this.disconnectPromise = undefined;
|
|
295
|
-
})();
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
this.logging.log("Bluetooth GATT server connected when connecting");
|
|
299
|
-
}
|
|
300
|
-
})
|
|
301
|
-
.catch((e) => {
|
|
302
|
-
if (this.connecting) {
|
|
303
|
-
// Error will be logged by main connect error handling.
|
|
304
|
-
throw e;
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
this.logging.error("Bluetooth GATT server connect error after our timeout", e);
|
|
308
|
-
return undefined;
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
.finally(() => {
|
|
312
|
-
this.logging.log("Bluetooth GATT server promise field cleared");
|
|
313
|
-
this.gattConnectPromise = undefined;
|
|
314
|
-
});
|
|
315
|
-
this.connecting = true;
|
|
316
|
-
try {
|
|
317
|
-
const gattConnectResult = await Promise.race([
|
|
318
|
-
this.gattConnectPromise,
|
|
319
|
-
new Promise((resolve) => setTimeout(() => resolve("timeout"), connectTimeoutDuration)),
|
|
320
|
-
]);
|
|
321
|
-
if (gattConnectResult === "timeout") {
|
|
322
|
-
this.logging.log("Bluetooth GATT server connect timeout");
|
|
323
|
-
throw new Error("Bluetooth GATT server connect timeout");
|
|
324
|
-
}
|
|
208
|
+
if (Capacitor.isNativePlatform()) {
|
|
209
|
+
await this.connectHandlingBond();
|
|
325
210
|
}
|
|
326
|
-
|
|
327
|
-
this.
|
|
211
|
+
else {
|
|
212
|
+
await this.connectInternal();
|
|
328
213
|
}
|
|
329
|
-
|
|
214
|
+
await this.getBoardVersion();
|
|
215
|
+
const events = this.currentEvents();
|
|
216
|
+
const services = await BleClient.getServices(this.device.deviceId);
|
|
217
|
+
this.serviceIds = new Set(services.map((s) => s.uuid));
|
|
218
|
+
this.logging.log(`Starting notifications for current events ${events}`);
|
|
219
|
+
events.forEach((e) => this.startNotifications(e));
|
|
330
220
|
this.logging.event({
|
|
331
221
|
type: this.isReconnect ? "Reconnect" : "Connect",
|
|
332
222
|
message: "Bluetooth connect success",
|
|
@@ -341,7 +231,19 @@ export class BluetoothDeviceWrapper {
|
|
|
341
231
|
});
|
|
342
232
|
await this.disconnectInternal(false);
|
|
343
233
|
this.callbacks.onFail();
|
|
344
|
-
|
|
234
|
+
if (e instanceof DeviceError) {
|
|
235
|
+
throw e;
|
|
236
|
+
}
|
|
237
|
+
if (e instanceof TimeoutError) {
|
|
238
|
+
throw new DeviceError({
|
|
239
|
+
code: "timeout-error",
|
|
240
|
+
message: e instanceof Error ? e.message : String(e),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
throw new DeviceError({
|
|
244
|
+
code: "bluetooth-connection-failed",
|
|
245
|
+
message: e instanceof Error ? e.message : String(e),
|
|
246
|
+
});
|
|
345
247
|
}
|
|
346
248
|
finally {
|
|
347
249
|
this.duringExplicitConnectDisconnect--;
|
|
@@ -349,6 +251,13 @@ export class BluetoothDeviceWrapper {
|
|
|
349
251
|
this.isReconnect = false;
|
|
350
252
|
}
|
|
351
253
|
}
|
|
254
|
+
async connectInternal() {
|
|
255
|
+
this.waitingForDisconnectEventCallbacks.length = 0;
|
|
256
|
+
await BleClient.connect(this.device.deviceId, this.handleDisconnectEvent, {
|
|
257
|
+
timeout: connectTimeoutInMs,
|
|
258
|
+
});
|
|
259
|
+
this.connected = true;
|
|
260
|
+
}
|
|
352
261
|
async disconnect() {
|
|
353
262
|
return this.disconnectInternal(true);
|
|
354
263
|
}
|
|
@@ -356,8 +265,8 @@ export class BluetoothDeviceWrapper {
|
|
|
356
265
|
this.logging.log(`Bluetooth disconnect ${userTriggered ? "(user triggered)" : "(programmatic)"}`);
|
|
357
266
|
this.duringExplicitConnectDisconnect++;
|
|
358
267
|
try {
|
|
359
|
-
if (this.
|
|
360
|
-
this.device.
|
|
268
|
+
if (this.connected) {
|
|
269
|
+
await BleClient.disconnect(this.device.deviceId);
|
|
361
270
|
}
|
|
362
271
|
}
|
|
363
272
|
catch (e) {
|
|
@@ -365,98 +274,208 @@ export class BluetoothDeviceWrapper {
|
|
|
365
274
|
// We might have already lost the connection.
|
|
366
275
|
}
|
|
367
276
|
finally {
|
|
368
|
-
this.disposeServices();
|
|
369
277
|
this.duringExplicitConnectDisconnect--;
|
|
370
278
|
}
|
|
371
|
-
this.connectReadyPromise = new Promise((resolve) => setTimeout(resolve, 3_500));
|
|
372
279
|
}
|
|
373
280
|
async reconnect() {
|
|
374
281
|
this.logging.log("Bluetooth reconnect");
|
|
375
282
|
this.isReconnect = true;
|
|
376
283
|
await this.connect();
|
|
377
284
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
285
|
+
async getBoardVersion() {
|
|
286
|
+
// We read this when we connect and it won't change.
|
|
287
|
+
if (this.boardVersion) {
|
|
288
|
+
return this.boardVersion;
|
|
381
289
|
}
|
|
382
|
-
|
|
290
|
+
this.boardVersion = await this.deviceInformation.getBoardVersion();
|
|
291
|
+
return this.boardVersion;
|
|
383
292
|
}
|
|
384
|
-
async
|
|
385
|
-
this.
|
|
386
|
-
|
|
293
|
+
async startNotifications(type) {
|
|
294
|
+
await this.getServicesForEvent(type)?.startNotifications(type);
|
|
295
|
+
}
|
|
296
|
+
async stopNotifications(type) {
|
|
297
|
+
await this.getServicesForEvent(type)?.stopNotifications(type);
|
|
298
|
+
}
|
|
299
|
+
getServicesForEvent(type) {
|
|
300
|
+
return this.services.find((s) => this.serviceIds.has(s.uuid) && s.getRelevantEvents().includes(type));
|
|
301
|
+
}
|
|
302
|
+
async startInternalNotifications(serviceId, characteristicId, options) {
|
|
303
|
+
const key = this.getNotificationKey(serviceId, characteristicId);
|
|
304
|
+
await this.raceDisconnectAndTimeout(BleClient.startNotifications(this.device.deviceId, serviceId, characteristicId, (value) => {
|
|
305
|
+
const bytes = new Uint8Array(value.buffer);
|
|
306
|
+
// Notify all registered callbacks.
|
|
307
|
+
this.internalNotificationListeners
|
|
308
|
+
.get(key)
|
|
309
|
+
?.forEach((cb) => cb(bytes));
|
|
310
|
+
}, options), { actionName: "start notifications" });
|
|
311
|
+
}
|
|
312
|
+
subscribe(serviceId, characteristicId, callback) {
|
|
313
|
+
const key = this.getNotificationKey(serviceId, characteristicId);
|
|
314
|
+
if (!this.internalNotificationListeners.has(key)) {
|
|
315
|
+
this.internalNotificationListeners.set(key, new Set());
|
|
316
|
+
}
|
|
317
|
+
this.internalNotificationListeners.get(key).add(callback);
|
|
318
|
+
}
|
|
319
|
+
unsubscribe(serviceId, characteristicId, callback) {
|
|
320
|
+
const key = this.getNotificationKey(serviceId, characteristicId);
|
|
321
|
+
this.internalNotificationListeners.get(key)?.delete(callback);
|
|
322
|
+
}
|
|
323
|
+
async stopInternalNotifications(serviceId, characteristicId) {
|
|
324
|
+
await BleClient.stopNotifications(this.device.deviceId, serviceId, characteristicId);
|
|
325
|
+
const key = this.getNotificationKey(serviceId, characteristicId);
|
|
326
|
+
this.internalNotificationListeners.delete(key);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Write to characteristic and wait for a notification response.
|
|
330
|
+
*
|
|
331
|
+
* It is the responsibility of the caller to have started notifications
|
|
332
|
+
* for the characteristic.
|
|
333
|
+
*/
|
|
334
|
+
async writeForNotification(serviceId, characteristicId, value, notificationId, isFinalNotification = () => true) {
|
|
335
|
+
let notificationListener;
|
|
336
|
+
const notificationPromise = new Promise((resolve) => {
|
|
337
|
+
notificationListener = (bytes) => {
|
|
338
|
+
if (bytes[0] === notificationId && isFinalNotification(bytes)) {
|
|
339
|
+
resolve(bytes);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
this.subscribe(serviceId, characteristicId, notificationListener);
|
|
343
|
+
});
|
|
387
344
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
345
|
+
await BleClient.writeWithoutResponse(this.device.deviceId, serviceId, characteristicId, value);
|
|
346
|
+
return await this.raceDisconnectAndTimeout(notificationPromise, {
|
|
347
|
+
timeout: 3_000,
|
|
348
|
+
actionName: "flash notification wait",
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (notificationListener) {
|
|
353
|
+
this.unsubscribe(serviceId, characteristicId, notificationListener);
|
|
397
354
|
}
|
|
398
|
-
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async waitForDisconnect(timeout) {
|
|
358
|
+
if (!this.connected) {
|
|
359
|
+
this.log("Waiting for disconnect but not connected");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.log(`Waiting for disconnect (timeout ${timeout})`);
|
|
363
|
+
try {
|
|
364
|
+
await Promise.race([
|
|
365
|
+
this.disconnectErrorPromise("wait"),
|
|
366
|
+
timeoutErrorAfter(timeout),
|
|
367
|
+
]);
|
|
399
368
|
}
|
|
400
369
|
catch (e) {
|
|
401
|
-
|
|
402
|
-
|
|
370
|
+
if (e instanceof TimeoutError) {
|
|
371
|
+
this.error("Timeout waiting for disconnect");
|
|
372
|
+
}
|
|
373
|
+
if (!(e instanceof DisconnectError)) {
|
|
374
|
+
throw e;
|
|
375
|
+
}
|
|
403
376
|
}
|
|
404
377
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Suitable for running a series of BLE interactions with an overall timeout
|
|
380
|
+
* and general disconnection
|
|
381
|
+
*/
|
|
382
|
+
async raceDisconnectAndTimeout(promise, options = {}) {
|
|
383
|
+
if (!this.connected) {
|
|
384
|
+
throw new DisconnectError();
|
|
385
|
+
}
|
|
386
|
+
const actionName = options.actionName ?? "action";
|
|
387
|
+
const errorOnDisconnectPromise = this.disconnectErrorPromise(actionName);
|
|
388
|
+
return await Promise.race([
|
|
389
|
+
promise,
|
|
390
|
+
errorOnDisconnectPromise,
|
|
391
|
+
...(options.timeout ? [timeoutErrorAfter(options.timeout)] : []),
|
|
392
|
+
]);
|
|
409
393
|
}
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
394
|
+
disconnectErrorPromise(actionName) {
|
|
395
|
+
const { promise, callback } = disconnectErrorCallback(`Disconnected during ${actionName}`);
|
|
396
|
+
this.waitingForDisconnectEventCallbacks.push(callback);
|
|
397
|
+
return promise;
|
|
413
398
|
}
|
|
414
|
-
|
|
415
|
-
|
|
399
|
+
event(event) {
|
|
400
|
+
this.logging.event(event);
|
|
416
401
|
}
|
|
417
|
-
|
|
418
|
-
|
|
402
|
+
log(message) {
|
|
403
|
+
this.logging.log(message);
|
|
419
404
|
}
|
|
420
|
-
|
|
421
|
-
|
|
405
|
+
error(message, e) {
|
|
406
|
+
this.logging.error(message, e);
|
|
422
407
|
}
|
|
423
|
-
|
|
424
|
-
return
|
|
408
|
+
getNotificationKey(serviceId, characteristicId) {
|
|
409
|
+
return `${serviceId}:${characteristicId}`;
|
|
425
410
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
411
|
+
/**
|
|
412
|
+
* Bonds with device and handles the post-bond device state only returning
|
|
413
|
+
* when we can reattempt a connection with the device.
|
|
414
|
+
*/
|
|
415
|
+
async connectHandlingBond() {
|
|
416
|
+
const startTime = Date.now();
|
|
417
|
+
const maybeJustBonded = await this.bondConnectDeviceInternal();
|
|
418
|
+
if (maybeJustBonded) {
|
|
419
|
+
// If we did just bond then the device disconnects after 2_000 and then
|
|
420
|
+
// resets after a further 13_000 In future we'd like a firmware change
|
|
421
|
+
// that means it doesn't reset when partial flashing is in progress.
|
|
422
|
+
this.log(isAndroid() ? "New bond" : "Potential new bond");
|
|
423
|
+
// On Android with micro:bit V1 we don't see this disconnect (just the 15s
|
|
424
|
+
// reboot) so we hit the timeout and then continue to reset into pairing
|
|
425
|
+
// mode.
|
|
426
|
+
// TODO: document what happens with iOS micro:bit V1 in the new bond case.
|
|
427
|
+
try {
|
|
428
|
+
await this.waitForDisconnect(3000);
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
if (e instanceof TimeoutError) {
|
|
432
|
+
this.log("No disconnect after bond, assuming connection is stable");
|
|
433
|
+
if (!isAndroid()) {
|
|
434
|
+
// We never knew for sure whether this was a new bond. Assume we
|
|
435
|
+
// were already bonded on the basis we didn't get disconnected.
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
throw e;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
await this.connectInternal();
|
|
444
|
+
// TODO: check this is needed, potentially inline into connect if always needed
|
|
445
|
+
await delay(500);
|
|
446
|
+
this.log("Resetting to pairing mode");
|
|
447
|
+
const pf = new PartialFlashingService(this);
|
|
448
|
+
await pf.resetToMode(MicroBitMode.Pairing);
|
|
449
|
+
await this.waitForDisconnect(10_000);
|
|
450
|
+
await this.connectInternal();
|
|
451
|
+
this.log(`Connection ready; took ${Date.now() - startTime}`);
|
|
434
452
|
}
|
|
435
453
|
}
|
|
436
|
-
async
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
454
|
+
async bondConnectDeviceInternal() {
|
|
455
|
+
const { deviceId } = this.device;
|
|
456
|
+
if (isAndroid()) {
|
|
457
|
+
let justBonded = false;
|
|
458
|
+
// This gets us a nicer pairing dialog than just going straight for the characteristic.
|
|
459
|
+
if (!(await BleClient.isBonded(deviceId))) {
|
|
460
|
+
await BleClient.createBond(deviceId, { timeout: bondingTimeoutInMs });
|
|
461
|
+
justBonded = true;
|
|
462
|
+
}
|
|
463
|
+
await this.connectInternal();
|
|
464
|
+
return justBonded;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// Long timeout as this is the point that the pairing dialog will show.
|
|
468
|
+
// If this responds very quickly maybe we could assume there was a bond?
|
|
469
|
+
// At the moment we always do the disconnect dance so subsequent code will
|
|
470
|
+
// need to call startNotifications again. We need to be connected to
|
|
471
|
+
// startNotifications.
|
|
472
|
+
await this.connectInternal();
|
|
473
|
+
const pf = new PartialFlashingService(this);
|
|
474
|
+
await pf.startNotifications({ timeout: bondingTimeoutInMs });
|
|
475
|
+
// We just did it now to trigger pairing at a well defined point.
|
|
476
|
+
await pf.stopNotifications();
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
445
479
|
}
|
|
446
480
|
}
|
|
447
|
-
export const createBluetoothDeviceWrapper = async (device, logging, dispatchTypedEvent, currentEvents, callbacks) => {
|
|
448
|
-
try {
|
|
449
|
-
// Reuse our connection objects for the same device as they
|
|
450
|
-
// track the GATT connect promise that never resolves.
|
|
451
|
-
const bluetooth = deviceIdToWrapper.get(device.id) ??
|
|
452
|
-
new BluetoothDeviceWrapper(device, logging, dispatchTypedEvent, currentEvents, callbacks);
|
|
453
|
-
deviceIdToWrapper.set(device.id, bluetooth);
|
|
454
|
-
await bluetooth.connect();
|
|
455
|
-
return bluetooth;
|
|
456
|
-
}
|
|
457
|
-
catch (e) {
|
|
458
|
-
logging.error("Bluetooth connect error", e);
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
481
|
//# sourceMappingURL=bluetooth-device-wrapper.js.map
|