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