@microbit/microbit-connection 0.0.0-alpha.3 → 0.0.0-alpha.30

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 (64) hide show
  1. package/build/accelerometer-service.d.ts +8 -9
  2. package/build/accelerometer-service.js +24 -43
  3. package/build/accelerometer-service.js.map +1 -1
  4. package/build/bluetooth-device-wrapper.d.ts +27 -26
  5. package/build/bluetooth-device-wrapper.js +137 -89
  6. package/build/bluetooth-device-wrapper.js.map +1 -1
  7. package/build/bluetooth.d.ts +14 -11
  8. package/build/bluetooth.js +70 -91
  9. package/build/bluetooth.js.map +1 -1
  10. package/build/board-id.d.ts +1 -0
  11. package/build/board-id.js +8 -0
  12. package/build/board-id.js.map +1 -1
  13. package/build/button-service.d.ts +13 -0
  14. package/build/button-service.js +76 -0
  15. package/build/button-service.js.map +1 -0
  16. package/build/buttons.d.ts +10 -0
  17. package/build/buttons.js +18 -0
  18. package/build/buttons.js.map +1 -0
  19. package/build/constants.d.ts +1 -0
  20. package/build/constants.js +1 -0
  21. package/build/constants.js.map +1 -1
  22. package/build/device.d.ts +30 -39
  23. package/build/device.js +12 -3
  24. package/build/device.js.map +1 -1
  25. package/build/events.d.ts +11 -1
  26. package/build/events.js +86 -1
  27. package/build/events.js.map +1 -1
  28. package/build/hex-flash-data-source.d.ts +6 -8
  29. package/build/hex-flash-data-source.js +16 -45
  30. package/build/hex-flash-data-source.js.map +1 -1
  31. package/build/index.d.ts +10 -5
  32. package/build/index.js +7 -4
  33. package/build/index.js.map +1 -1
  34. package/build/led-service.d.ts +20 -0
  35. package/build/led-service.js +116 -0
  36. package/build/led-service.js.map +1 -0
  37. package/build/led.d.ts +6 -0
  38. package/build/led.js +2 -0
  39. package/build/led.js.map +1 -0
  40. package/build/logging.js +3 -1
  41. package/build/logging.js.map +1 -1
  42. package/build/promise-queue.d.ts +27 -0
  43. package/build/promise-queue.js +74 -0
  44. package/build/promise-queue.js.map +1 -0
  45. package/build/service-events.d.ts +3 -0
  46. package/build/service-events.js +12 -0
  47. package/build/service-events.js.map +1 -1
  48. package/build/usb-device-wrapper.d.ts +7 -1
  49. package/build/usb-device-wrapper.js +40 -3
  50. package/build/usb-device-wrapper.js.map +1 -1
  51. package/build/usb-partial-flashing.d.ts +11 -5
  52. package/build/usb-partial-flashing.js +53 -10
  53. package/build/usb-partial-flashing.js.map +1 -1
  54. package/build/usb-radio-bridge.d.ts +31 -18
  55. package/build/usb-radio-bridge.js +351 -187
  56. package/build/usb-radio-bridge.js.map +1 -1
  57. package/build/usb.d.ts +14 -17
  58. package/build/usb.js +111 -47
  59. package/build/usb.js.map +1 -1
  60. package/package.json +5 -3
  61. package/vite.config.ts +9 -7
  62. package/build/bluetooth-utils.d.ts +0 -5
  63. package/build/bluetooth-utils.js +0 -14
  64. package/build/bluetooth-utils.js.map +0 -1
@@ -1,261 +1,395 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * (c) 2023, Center for Computational Thinking and Design at Aarhus University and contributors
4
3
  *
5
4
  * SPDX-License-Identifier: MIT
6
5
  */
6
+ import { AccelerometerDataEvent } from "./accelerometer.js";
7
+ import { ButtonEvent, ButtonState } from "./buttons.js";
8
+ import { ConnectionStatus, ConnectionStatusEvent, } from "./device.js";
9
+ import { TypedEventTarget } from "./events.js";
10
+ import { NullLogging } from "./logging.js";
7
11
  import * as protocol from "./usb-serial-protocol.js";
8
12
  const connectTimeoutDuration = 10000;
9
13
  class BridgeError extends Error {
10
14
  }
11
15
  class RemoteError extends Error {
12
16
  }
13
- export class MicrobitRadioBridgeConnection {
14
- constructor(usb, logging, remoteDeviceId) {
15
- Object.defineProperty(this, "usb", {
17
+ /**
18
+ * Wraps around a USB connection to implement a subset of services over a serial protocol.
19
+ *
20
+ * When it connects/disconnects it affects the delegate connection.
21
+ */
22
+ export class MicrobitRadioBridgeConnection extends TypedEventTarget {
23
+ constructor(delegate, options) {
24
+ super();
25
+ Object.defineProperty(this, "delegate", {
16
26
  enumerable: true,
17
27
  configurable: true,
18
28
  writable: true,
19
- value: usb
29
+ value: delegate
20
30
  });
21
- Object.defineProperty(this, "logging", {
31
+ Object.defineProperty(this, "status", {
22
32
  enumerable: true,
23
33
  configurable: true,
24
34
  writable: true,
25
- value: logging
35
+ value: void 0
26
36
  });
27
- Object.defineProperty(this, "remoteDeviceId", {
37
+ Object.defineProperty(this, "logging", {
28
38
  enumerable: true,
29
39
  configurable: true,
30
40
  writable: true,
31
- value: remoteDeviceId
41
+ value: void 0
32
42
  });
33
- Object.defineProperty(this, "responseMap", {
43
+ Object.defineProperty(this, "serialSession", {
34
44
  enumerable: true,
35
45
  configurable: true,
36
46
  writable: true,
37
- value: new Map()
47
+ value: void 0
38
48
  });
39
- // To avoid concurrent connect attempts
40
- Object.defineProperty(this, "isConnecting", {
49
+ Object.defineProperty(this, "remoteDeviceId", {
41
50
  enumerable: true,
42
51
  configurable: true,
43
52
  writable: true,
44
- value: false
53
+ value: void 0
45
54
  });
46
- Object.defineProperty(this, "connectionCheckIntervalId", {
55
+ Object.defineProperty(this, "disconnectPromise", {
47
56
  enumerable: true,
48
57
  configurable: true,
49
58
  writable: true,
50
59
  value: void 0
51
60
  });
52
- Object.defineProperty(this, "lastReceivedMessageTimestamp", {
61
+ Object.defineProperty(this, "serialSessionOpen", {
53
62
  enumerable: true,
54
63
  configurable: true,
55
64
  writable: true,
56
- value: void 0
65
+ value: false
57
66
  });
58
- Object.defineProperty(this, "isReconnect", {
67
+ Object.defineProperty(this, "ignoreDelegateStatus", {
59
68
  enumerable: true,
60
69
  configurable: true,
61
70
  writable: true,
62
71
  value: false
63
72
  });
64
- // Whether this is the final reconnection attempt.
65
- Object.defineProperty(this, "finalAttempt", {
73
+ Object.defineProperty(this, "delegateStatusListener", {
66
74
  enumerable: true,
67
75
  configurable: true,
68
76
  writable: true,
69
- value: false
77
+ value: (e) => {
78
+ if (this.ignoreDelegateStatus) {
79
+ return;
80
+ }
81
+ const currentStatus = this.status;
82
+ if (e.status !== ConnectionStatus.CONNECTED) {
83
+ this.setStatus(e.status);
84
+ this.serialSession?.dispose();
85
+ }
86
+ else {
87
+ this.status = ConnectionStatus.DISCONNECTED;
88
+ if (currentStatus === ConnectionStatus.DISCONNECTED &&
89
+ this.serialSessionOpen) {
90
+ this.serialSession?.connect();
91
+ }
92
+ }
93
+ }
70
94
  });
95
+ this.logging = options?.logging ?? new NullLogging();
96
+ this.status = this.statusFromDelegate();
97
+ }
98
+ getBoardVersion() {
99
+ return this.delegate.getBoardVersion();
100
+ }
101
+ serialWrite(data) {
102
+ return this.delegate.serialWrite(data);
103
+ }
104
+ async initialize() {
105
+ await this.delegate.initialize();
106
+ this.setStatus(this.statusFromDelegate());
107
+ this.delegate.addEventListener("status", this.delegateStatusListener);
108
+ }
109
+ dispose() {
110
+ this.delegate.removeEventListener("status", this.delegateStatusListener);
111
+ this.delegate.dispose();
112
+ }
113
+ clearDevice() {
114
+ this.delegate.clearDevice();
115
+ }
116
+ setRemoteDeviceId(remoteDeviceId) {
117
+ this.remoteDeviceId = remoteDeviceId;
71
118
  }
72
119
  async connect() {
120
+ if (this.disconnectPromise) {
121
+ await this.disconnectPromise;
122
+ }
123
+ // TODO: previously this skipped overlapping connect attempts but that seems awkward
124
+ // can we... just not do that? or wait?
125
+ if (this.remoteDeviceId === undefined) {
126
+ throw new BridgeError(`Missing remote micro:bit ID`);
127
+ }
73
128
  this.logging.event({
74
- type: this.isReconnect ? "Reconnect" : "Connect",
129
+ type: "Connect",
75
130
  message: "Serial connect start",
76
131
  });
77
- if (this.isConnecting) {
78
- this.logging.log("Skipping connect attempt when one is already in progress");
79
- return;
80
- }
81
- this.isConnecting = true;
82
- let unprocessedData = "";
83
- let previousButtonState = { A: 0, B: 0 };
84
- let onPeriodicMessageRecieved;
85
- const handleError = (e) => {
86
- this.logging.error("Serial error", e);
87
- void this.disconnectInternal(false, "bridge");
88
- };
89
- const processMessage = (data) => {
90
- const messages = protocol.splitMessages(unprocessedData + data);
91
- unprocessedData = messages.remainingInput;
92
- messages.messages.forEach(async (msg) => {
93
- this.lastReceivedMessageTimestamp = Date.now();
94
- // Messages are either periodic sensor data or command/response
95
- const sensorData = protocol.processPeriodicMessage(msg);
96
- if (sensorData) {
97
- // stateOnReconnected();
98
- // if (onPeriodicMessageRecieved) {
99
- // onPeriodicMessageRecieved();
100
- // onPeriodicMessageRecieved = undefined;
101
- // }
102
- // onAccelerometerChange(
103
- // sensorData.accelerometerX,
104
- // sensorData.accelerometerY,
105
- // sensorData.accelerometerZ
106
- // );
107
- // if (sensorData.buttonA !== previousButtonState.A) {
108
- // previousButtonState.A = sensorData.buttonA;
109
- // onButtonChange(sensorData.buttonA, "A");
110
- // }
111
- // if (sensorData.buttonB !== previousButtonState.B) {
112
- // previousButtonState.B = sensorData.buttonB;
113
- // onButtonChange(sensorData.buttonB, "B");
114
- // }
115
- }
116
- else {
117
- const messageResponse = protocol.processResponseMessage(msg);
118
- if (!messageResponse) {
119
- return;
120
- }
121
- const responseResolve = this.responseMap.get(messageResponse.messageId);
122
- if (responseResolve) {
123
- this.responseMap.delete(messageResponse.messageId);
124
- responseResolve(messageResponse);
125
- }
126
- }
127
- });
128
- };
132
+ this.ignoreDelegateStatus = false;
133
+ await this.delegate.connect();
129
134
  try {
130
- await this.usb.startSerial(processMessage, handleError);
131
- await this.handshake();
132
- // stateOnConnected(DeviceRequestStates.INPUT);
133
- // Check for connection lost
134
- if (this.connectionCheckIntervalId === undefined) {
135
- this.connectionCheckIntervalId = setInterval(async () => {
136
- if (this.lastReceivedMessageTimestamp &&
137
- Date.now() - this.lastReceivedMessageTimestamp > 1_000) {
138
- // stateOnReconnectionAttempt();
135
+ this.serialSession = new RadioBridgeSerialSession(this.logging, this.remoteDeviceId, this.delegate, this.dispatchTypedEvent.bind(this), {
136
+ onConnecting: () => this.setStatus(ConnectionStatus.CONNECTING),
137
+ onReconnecting: () => {
138
+ // Leave serial connection running in case the remote device comes back.
139
+ if (this.status !== ConnectionStatus.RECONNECTING) {
140
+ this.setStatus(ConnectionStatus.RECONNECTING);
139
141
  }
140
- if (this.lastReceivedMessageTimestamp &&
141
- Date.now() - this.lastReceivedMessageTimestamp >
142
- connectTimeoutDuration) {
143
- await this.handleReconnect();
142
+ },
143
+ onRestartConnection: () => {
144
+ // So that serial session does not get repetitively disposed in
145
+ // delegate status listener when delegate is disconnected for restarting connection
146
+ this.ignoreDelegateStatus = true;
147
+ },
148
+ onFail: () => {
149
+ if (this.status !== ConnectionStatus.DISCONNECTED) {
150
+ this.setStatus(ConnectionStatus.DISCONNECTED);
144
151
  }
145
- }, 1000);
146
- }
147
- this.logging.log(`Serial: using remote device id ${this.remoteDeviceId}`);
148
- const remoteMbIdCommand = protocol.generateCmdRemoteMbId(this.remoteDeviceId);
149
- const remoteMbIdResponse = await this.sendCmdWaitResponse(remoteMbIdCommand);
150
- if (remoteMbIdResponse.type === protocol.ResponseTypes.Error ||
151
- remoteMbIdResponse.value !== this.remoteDeviceId) {
152
- throw new BridgeError(`Failed to set remote micro:bit ID. Expected ${this.remoteDeviceId}, got ${remoteMbIdResponse.value}`);
153
- }
154
- // For now we only support input state.
155
- // TODO: when do we do this?
156
- if (false) {
157
- // Request the micro:bit to start sending the periodic messages
158
- const startCmd = protocol.generateCmdStart({
159
- accelerometer: true,
160
- buttons: true,
161
- });
162
- const periodicMessagePromise = new Promise((resolve, reject) => {
163
- onPeriodicMessageRecieved = resolve;
164
- setTimeout(() => {
165
- onPeriodicMessageRecieved = undefined;
166
- reject(new Error("Failed to receive data from remote micro:bit"));
167
- }, 500);
168
- });
169
- const startCmdResponse = await this.sendCmdWaitResponse(startCmd);
170
- if (startCmdResponse.type === protocol.ResponseTypes.Error) {
171
- throw new RemoteError(`Failed to start streaming sensors data. Error response received: ${startCmdResponse.message}`);
172
- }
173
- if (this.isReconnect) {
174
- await periodicMessagePromise;
175
- }
176
- else {
177
- periodicMessagePromise.catch(async (e) => {
178
- this.logging.error("Failed to initialise serial protocol", e);
179
- await this.disconnectInternal(false, "remote");
180
- this.isConnecting = false;
181
- });
182
- }
183
- }
184
- // stateOnAssigned(DeviceRequestStates.INPUT, this.usb.getModelNumber());
185
- // stateOnReady(DeviceRequestStates.INPUT);
152
+ this.ignoreDelegateStatus = false;
153
+ this.serialSessionOpen = false;
154
+ },
155
+ onSuccess: () => {
156
+ if (this.status !== ConnectionStatus.CONNECTED) {
157
+ this.setStatus(ConnectionStatus.CONNECTED);
158
+ }
159
+ this.ignoreDelegateStatus = false;
160
+ this.serialSessionOpen = true;
161
+ },
162
+ });
163
+ await this.serialSession.connect();
186
164
  this.logging.event({
187
- type: this.isReconnect ? "Reconnect" : "Connect",
165
+ type: "Connect",
188
166
  message: "Serial connect success",
189
167
  });
168
+ return this.status;
190
169
  }
191
170
  catch (e) {
171
+ this.serialSessionOpen = false;
192
172
  this.logging.error("Failed to initialise serial protocol", e);
193
173
  this.logging.event({
194
- type: this.isReconnect ? "Reconnect" : "Connect",
174
+ type: "Connect",
195
175
  message: "Serial connect failed",
196
176
  });
197
- const reconnectHelp = e instanceof BridgeError ? "bridge" : "remote";
198
- await this.disconnectInternal(false, reconnectHelp);
199
177
  throw e;
200
178
  }
201
- finally {
202
- this.finalAttempt = false;
203
- this.isConnecting = false;
204
- }
205
179
  }
206
180
  async disconnect() {
207
- return this.disconnectInternal(true, "bridge");
181
+ if (this.disconnectPromise) {
182
+ return this.disconnectPromise;
183
+ }
184
+ this.serialSessionOpen = false;
185
+ this.disconnectPromise = (async () => {
186
+ await this.serialSession?.dispose(true);
187
+ this.disconnectPromise = undefined;
188
+ })();
208
189
  }
209
- stopConnectionCheck() {
210
- clearInterval(this.connectionCheckIntervalId);
211
- this.connectionCheckIntervalId = undefined;
212
- this.lastReceivedMessageTimestamp = undefined;
190
+ log(v) {
191
+ this.logging.log(v);
213
192
  }
214
- async disconnectInternal(userDisconnect) {
215
- this.stopConnectionCheck();
193
+ setStatus(status) {
194
+ this.status = status;
195
+ this.log("Radio connection status " + status);
196
+ this.dispatchTypedEvent("status", new ConnectionStatusEvent(status));
197
+ }
198
+ statusFromDelegate() {
199
+ return this.delegate.status == ConnectionStatus.CONNECTED
200
+ ? ConnectionStatus.DISCONNECTED
201
+ : this.delegate.status;
202
+ }
203
+ }
204
+ /**
205
+ * Wraps a connected delegate for a single session from attempted serial handshake to error/dispose.
206
+ */
207
+ class RadioBridgeSerialSession {
208
+ processButton(button, type, sensorData) {
209
+ if (sensorData[button] !== this.previousButtonState[button]) {
210
+ this.previousButtonState[button] = sensorData[button];
211
+ this.dispatchTypedEvent(type, new ButtonEvent(type, sensorData[button] ? ButtonState.ShortPress : ButtonState.NotPressed));
212
+ }
213
+ }
214
+ constructor(logging, remoteDeviceId, delegate, dispatchTypedEvent, callbacks) {
215
+ Object.defineProperty(this, "logging", {
216
+ enumerable: true,
217
+ configurable: true,
218
+ writable: true,
219
+ value: logging
220
+ });
221
+ Object.defineProperty(this, "remoteDeviceId", {
222
+ enumerable: true,
223
+ configurable: true,
224
+ writable: true,
225
+ value: remoteDeviceId
226
+ });
227
+ Object.defineProperty(this, "delegate", {
228
+ enumerable: true,
229
+ configurable: true,
230
+ writable: true,
231
+ value: delegate
232
+ });
233
+ Object.defineProperty(this, "dispatchTypedEvent", {
234
+ enumerable: true,
235
+ configurable: true,
236
+ writable: true,
237
+ value: dispatchTypedEvent
238
+ });
239
+ Object.defineProperty(this, "callbacks", {
240
+ enumerable: true,
241
+ configurable: true,
242
+ writable: true,
243
+ value: callbacks
244
+ });
245
+ Object.defineProperty(this, "unprocessedData", {
246
+ enumerable: true,
247
+ configurable: true,
248
+ writable: true,
249
+ value: ""
250
+ });
251
+ Object.defineProperty(this, "previousButtonState", {
252
+ enumerable: true,
253
+ configurable: true,
254
+ writable: true,
255
+ value: { buttonA: 0, buttonB: 0 }
256
+ });
257
+ Object.defineProperty(this, "onPeriodicMessageReceived", {
258
+ enumerable: true,
259
+ configurable: true,
260
+ writable: true,
261
+ value: void 0
262
+ });
263
+ Object.defineProperty(this, "lastReceivedMessageTimestamp", {
264
+ enumerable: true,
265
+ configurable: true,
266
+ writable: true,
267
+ value: void 0
268
+ });
269
+ Object.defineProperty(this, "connectionCheckIntervalId", {
270
+ enumerable: true,
271
+ configurable: true,
272
+ writable: true,
273
+ value: void 0
274
+ });
275
+ Object.defineProperty(this, "isRestartingConnection", {
276
+ enumerable: true,
277
+ configurable: true,
278
+ writable: true,
279
+ value: false
280
+ });
281
+ Object.defineProperty(this, "serialErrorListener", {
282
+ enumerable: true,
283
+ configurable: true,
284
+ writable: true,
285
+ value: (event) => {
286
+ this.logging.error("Serial error", event.error);
287
+ void this.dispose();
288
+ }
289
+ });
290
+ Object.defineProperty(this, "serialDataListener", {
291
+ enumerable: true,
292
+ configurable: true,
293
+ writable: true,
294
+ value: (event) => {
295
+ const { data } = event;
296
+ const messages = protocol.splitMessages(this.unprocessedData + data);
297
+ this.unprocessedData = messages.remainingInput;
298
+ messages.messages.forEach(async (msg) => {
299
+ this.lastReceivedMessageTimestamp = Date.now();
300
+ // Messages are either periodic sensor data or command/response
301
+ const sensorData = protocol.processPeriodicMessage(msg);
302
+ if (sensorData) {
303
+ this.onPeriodicMessageReceived?.();
304
+ this.dispatchTypedEvent("accelerometerdatachanged", new AccelerometerDataEvent({
305
+ x: sensorData.accelerometerX,
306
+ y: sensorData.accelerometerY,
307
+ z: sensorData.accelerometerZ,
308
+ }));
309
+ this.processButton("buttonA", "buttonachanged", sensorData);
310
+ this.processButton("buttonB", "buttonbchanged", sensorData);
311
+ }
312
+ else {
313
+ const messageResponse = protocol.processResponseMessage(msg);
314
+ if (!messageResponse) {
315
+ return;
316
+ }
317
+ const responseResolve = this.responseMap.get(messageResponse.messageId);
318
+ if (responseResolve) {
319
+ this.responseMap.delete(messageResponse.messageId);
320
+ responseResolve(messageResponse);
321
+ }
322
+ }
323
+ });
324
+ }
325
+ });
326
+ Object.defineProperty(this, "responseMap", {
327
+ enumerable: true,
328
+ configurable: true,
329
+ writable: true,
330
+ value: new Map()
331
+ });
332
+ }
333
+ async connect() {
334
+ this.delegate.addEventListener("serialdata", this.serialDataListener);
335
+ this.delegate.addEventListener("serialerror", this.serialErrorListener);
216
336
  try {
217
- await this.sendCmdWaitResponse(protocol.generateCmdStop());
337
+ if (this.isRestartingConnection) {
338
+ this.callbacks.onReconnecting();
339
+ }
340
+ else {
341
+ this.callbacks.onConnecting();
342
+ }
343
+ await this.handshake();
344
+ this.logging.log(`Serial: using remote device id ${this.remoteDeviceId}`);
345
+ const remoteMbIdCommand = protocol.generateCmdRemoteMbId(this.remoteDeviceId);
346
+ const remoteMbIdResponse = await this.sendCmdWaitResponse(remoteMbIdCommand);
347
+ if (remoteMbIdResponse.type === protocol.ResponseTypes.Error ||
348
+ remoteMbIdResponse.value !== this.remoteDeviceId) {
349
+ throw new BridgeError(`Failed to set remote micro:bit ID. Expected ${this.remoteDeviceId}, got ${remoteMbIdResponse.value}`);
350
+ }
351
+ // Request the micro:bit to start sending the periodic messages
352
+ const startCmd = protocol.generateCmdStart({
353
+ accelerometer: true,
354
+ buttons: true,
355
+ });
356
+ const periodicMessagePromise = new Promise((resolve, reject) => {
357
+ this.onPeriodicMessageReceived = resolve;
358
+ setTimeout(() => {
359
+ this.onPeriodicMessageReceived = undefined;
360
+ reject(new Error("Failed to receive data from remote micro:bit"));
361
+ }, 500);
362
+ });
363
+ const startCmdResponse = await this.sendCmdWaitResponse(startCmd);
364
+ if (startCmdResponse.type === protocol.ResponseTypes.Error) {
365
+ throw new RemoteError(`Failed to start streaming sensors data. Error response received: ${startCmdResponse.message}`);
366
+ }
367
+ // TODO: in the first-time connection case we used to move the error/disconnect to the background here, why? timing?
368
+ await periodicMessagePromise;
369
+ this.isRestartingConnection = false;
370
+ await this.startConnectionCheck();
371
+ this.callbacks.onSuccess();
218
372
  }
219
373
  catch (e) {
220
- // If this fails the remote micro:bit has already gone away.
374
+ this.callbacks.onFail();
375
+ await this.dispose();
221
376
  }
222
- this.responseMap.clear();
223
- await this.usb.stopSerial();
224
- // stateOnDisconnected(
225
- // DeviceRequestStates.INPUT,
226
- // userDisconnect || this.finalAttempt
227
- // ? false
228
- // : this.isReconnect
229
- // ? "autoReconnect"
230
- // : "connect",
231
- // reconnectHelp
232
- // );
233
377
  }
234
- async handleReconnect() {
235
- if (this.isConnecting) {
236
- this.logging.log("Serial disconnect ignored... reconnect already in progress");
237
- return;
238
- }
378
+ async dispose(disconnect = false) {
379
+ this.stopConnectionCheck();
239
380
  try {
240
- this.stopConnectionCheck();
241
- this.logging.log("Serial disconnected... automatically trying to reconnect");
242
- this.responseMap.clear();
243
- await this.usb.stopSerial();
244
- await this.usb.softwareReset();
245
- await this.reconnect();
381
+ await this.sendCmdWaitResponse(protocol.generateCmdStop());
246
382
  }
247
383
  catch (e) {
248
- this.logging.error("Serial connect triggered by disconnect listener failed", e);
384
+ // If this fails the remote micro:bit has already gone away.
249
385
  }
250
- finally {
251
- this.isConnecting = false;
386
+ this.responseMap.clear();
387
+ this.delegate.removeEventListener("serialdata", this.serialDataListener);
388
+ this.delegate.removeEventListener("serialerror", this.serialErrorListener);
389
+ if (disconnect) {
390
+ await this.delegate.disconnect();
252
391
  }
253
- }
254
- async reconnect(finalAttempt = false) {
255
- this.finalAttempt = finalAttempt;
256
- this.logging.log("Serial reconnect");
257
- this.isReconnect = true;
258
- await this.connect();
392
+ await this.delegate.softwareReset();
259
393
  }
260
394
  async sendCmdWaitResponse(cmd) {
261
395
  const responsePromise = new Promise((resolve, reject) => {
@@ -265,9 +399,49 @@ export class MicrobitRadioBridgeConnection {
265
399
  reject(new Error(`Timeout waiting for response ${cmd.messageId}`));
266
400
  }, 1_000);
267
401
  });
268
- await this.usb.serialWrite(cmd.message);
402
+ await this.delegate.serialWrite(cmd.message);
269
403
  return responsePromise;
270
404
  }
405
+ async startConnectionCheck() {
406
+ // Check for connection lost
407
+ if (this.connectionCheckIntervalId === undefined) {
408
+ this.connectionCheckIntervalId = setInterval(async () => {
409
+ if (this.lastReceivedMessageTimestamp &&
410
+ Date.now() - this.lastReceivedMessageTimestamp <= 1_000) {
411
+ this.callbacks.onSuccess();
412
+ }
413
+ if (this.lastReceivedMessageTimestamp &&
414
+ Date.now() - this.lastReceivedMessageTimestamp > 1_000) {
415
+ this.logging.event({
416
+ type: "Serial",
417
+ message: "Serial connection lost...attempt to reconnect",
418
+ });
419
+ this.callbacks.onReconnecting();
420
+ }
421
+ if (this.lastReceivedMessageTimestamp &&
422
+ Date.now() - this.lastReceivedMessageTimestamp >
423
+ connectTimeoutDuration) {
424
+ await this.restartConnection();
425
+ }
426
+ }, 1000);
427
+ }
428
+ }
429
+ async restartConnection() {
430
+ this.isRestartingConnection = true;
431
+ this.logging.event({
432
+ type: "Serial",
433
+ message: "Serial connection lost...restart connection",
434
+ });
435
+ this.callbacks.onRestartConnection();
436
+ await this.dispose(true);
437
+ await this.delegate.connect();
438
+ await this.connect();
439
+ }
440
+ stopConnectionCheck() {
441
+ clearInterval(this.connectionCheckIntervalId);
442
+ this.connectionCheckIntervalId = undefined;
443
+ this.lastReceivedMessageTimestamp = undefined;
444
+ }
271
445
  async handshake() {
272
446
  // There is an issue where we cannot read data out from the micro:bit serial
273
447
  // buffer until the buffer has been filled.
@@ -305,14 +479,4 @@ export class MicrobitRadioBridgeConnection {
305
479
  }
306
480
  }
307
481
  }
308
- export const startSerialConnection = async (logging, usb, remoteDeviceId) => {
309
- try {
310
- const serial = new MicrobitRadioBridgeConnection(usb, logging, remoteDeviceId);
311
- await serial.connect();
312
- return serial;
313
- }
314
- catch (e) {
315
- return undefined;
316
- }
317
- };
318
482
  //# sourceMappingURL=usb-radio-bridge.js.map