@microbit/microbit-connection 0.0.0-alpha.2 → 0.0.0-alpha.21
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/build/accelerometer-service.d.ts +8 -9
- package/build/accelerometer-service.js +41 -44
- package/build/accelerometer-service.js.map +1 -1
- package/build/bluetooth-device-wrapper.d.ts +26 -20
- package/build/bluetooth-device-wrapper.js +134 -75
- package/build/bluetooth-device-wrapper.js.map +1 -1
- package/build/bluetooth.d.ts +14 -10
- package/build/bluetooth.js +68 -78
- package/build/bluetooth.js.map +1 -1
- package/build/board-id.d.ts +1 -0
- package/build/board-id.js +8 -0
- package/build/board-id.js.map +1 -1
- package/build/button-service.d.ts +13 -0
- package/build/button-service.js +76 -0
- package/build/button-service.js.map +1 -0
- package/build/buttons.d.ts +10 -0
- package/build/buttons.js +18 -0
- package/build/buttons.js.map +1 -0
- package/build/constants.d.ts +1 -0
- package/build/constants.js +1 -0
- package/build/constants.js.map +1 -1
- package/build/device.d.ts +22 -27
- package/build/device.js +29 -3
- package/build/device.js.map +1 -1
- package/build/events.d.ts +11 -1
- package/build/events.js +86 -1
- package/build/events.js.map +1 -1
- package/build/hex-flash-data-source.d.ts +6 -8
- package/build/hex-flash-data-source.js +16 -45
- package/build/hex-flash-data-source.js.map +1 -1
- package/build/index.d.ts +7 -4
- package/build/index.js +3 -2
- package/build/index.js.map +1 -1
- package/build/led-service.d.ts +20 -0
- package/build/led-service.js +116 -0
- package/build/led-service.js.map +1 -0
- package/build/led.d.ts +6 -0
- package/build/led.js +2 -0
- package/build/led.js.map +1 -0
- package/build/logging.js +3 -1
- package/build/logging.js.map +1 -1
- package/build/promise-queue.d.ts +27 -0
- package/build/promise-queue.js +74 -0
- package/build/promise-queue.js.map +1 -0
- package/build/service-events.d.ts +6 -2
- package/build/service-events.js +12 -0
- package/build/service-events.js.map +1 -1
- package/build/usb-device-wrapper.d.ts +7 -1
- package/build/usb-device-wrapper.js +40 -3
- package/build/usb-device-wrapper.js.map +1 -1
- package/build/usb-partial-flashing.d.ts +11 -5
- package/build/usb-partial-flashing.js +53 -10
- package/build/usb-partial-flashing.js.map +1 -1
- package/build/usb-radio-bridge.d.ts +31 -18
- package/build/usb-radio-bridge.js +351 -187
- package/build/usb-radio-bridge.js.map +1 -1
- package/build/usb.d.ts +13 -13
- package/build/usb.js +98 -46
- package/build/usb.js.map +1 -1
- package/package.json +6 -4
- package/vite.config.ts +10 -8
- package/build/bluetooth-utils.d.ts +0 -5
- package/build/bluetooth-utils.js +0 -14
- 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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:
|
|
29
|
+
value: delegate
|
|
20
30
|
});
|
|
21
|
-
Object.defineProperty(this, "
|
|
31
|
+
Object.defineProperty(this, "status", {
|
|
22
32
|
enumerable: true,
|
|
23
33
|
configurable: true,
|
|
24
34
|
writable: true,
|
|
25
|
-
value:
|
|
35
|
+
value: void 0
|
|
26
36
|
});
|
|
27
|
-
Object.defineProperty(this, "
|
|
37
|
+
Object.defineProperty(this, "logging", {
|
|
28
38
|
enumerable: true,
|
|
29
39
|
configurable: true,
|
|
30
40
|
writable: true,
|
|
31
|
-
value:
|
|
41
|
+
value: void 0
|
|
32
42
|
});
|
|
33
|
-
Object.defineProperty(this, "
|
|
43
|
+
Object.defineProperty(this, "serialSession", {
|
|
34
44
|
enumerable: true,
|
|
35
45
|
configurable: true,
|
|
36
46
|
writable: true,
|
|
37
|
-
value:
|
|
47
|
+
value: void 0
|
|
38
48
|
});
|
|
39
|
-
|
|
40
|
-
Object.defineProperty(this, "isConnecting", {
|
|
49
|
+
Object.defineProperty(this, "remoteDeviceId", {
|
|
41
50
|
enumerable: true,
|
|
42
51
|
configurable: true,
|
|
43
52
|
writable: true,
|
|
44
|
-
value:
|
|
53
|
+
value: void 0
|
|
45
54
|
});
|
|
46
|
-
Object.defineProperty(this, "
|
|
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, "
|
|
61
|
+
Object.defineProperty(this, "serialSessionOpen", {
|
|
53
62
|
enumerable: true,
|
|
54
63
|
configurable: true,
|
|
55
64
|
writable: true,
|
|
56
|
-
value:
|
|
65
|
+
value: false
|
|
57
66
|
});
|
|
58
|
-
Object.defineProperty(this, "
|
|
67
|
+
Object.defineProperty(this, "ignoreDelegateStatus", {
|
|
59
68
|
enumerable: true,
|
|
60
69
|
configurable: true,
|
|
61
70
|
writable: true,
|
|
62
71
|
value: false
|
|
63
72
|
});
|
|
64
|
-
|
|
65
|
-
Object.defineProperty(this, "finalAttempt", {
|
|
73
|
+
Object.defineProperty(this, "delegateStatusListener", {
|
|
66
74
|
enumerable: true,
|
|
67
75
|
configurable: true,
|
|
68
76
|
writable: true,
|
|
69
|
-
value:
|
|
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:
|
|
129
|
+
type: "Connect",
|
|
75
130
|
message: "Serial connect start",
|
|
76
131
|
});
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
this.connectionCheckIntervalId = undefined;
|
|
212
|
-
this.lastReceivedMessageTimestamp = undefined;
|
|
190
|
+
log(v) {
|
|
191
|
+
this.logging.log(v);
|
|
213
192
|
}
|
|
214
|
-
|
|
215
|
-
this.
|
|
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: (e) => {
|
|
286
|
+
this.logging.error("Serial error", e);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
235
|
-
|
|
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.
|
|
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
|
|
384
|
+
// If this fails the remote micro:bit has already gone away.
|
|
249
385
|
}
|
|
250
|
-
|
|
251
|
-
|
|
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.
|
|
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
|