Skip to content

Commit ef58f84

Browse files
Upgrade connection library
This has the UICR fix and the last of the breaking changes.
1 parent b35935b commit ef58f84

File tree

12 files changed

+118
-59
lines changed

12 files changed

+118
-59
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@codemirror/view": "^6.26.3",
1717
"@emotion/react": "^11.11.4",
1818
"@emotion/styled": "^11.11.5",
19-
"@microbit/microbit-connection": "^0.9.0-apps.alpha.17",
19+
"@microbit/microbit-connection": "^0.9.0-apps.alpha.18",
2020
"@microbit/microbit-fs": "^0.10.0",
2121
"@sanity/block-content-to-react": "^3.0.0",
2222
"@sanity/image-url": "^1.0.1",

src/device/device-hooks.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ export const useDeviceTraceback = () => {
204204
};
205205
device.addEventListener("serialdata", dataListener);
206206
device.addEventListener("serialreset", clearListener);
207-
device.addEventListener("serialerror", clearListener);
208207
return () => {
209-
device.removeEventListener("serialerror", clearListener);
210208
device.removeEventListener("serialreset", clearListener);
211209
device.removeEventListener("serialdata", dataListener);
212210
};

src/device/mock.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import {
77
BoardVersion,
88
ConnectionAvailabilityStatus,
99
ConnectionStatus,
10-
DeviceConnectionEventMap,
10+
ConnectionStatusChange,
1111
FlashDataSource,
1212
FlashOptions,
1313
ProgressStage,
1414
DeviceError,
1515
DeviceErrorCode,
16-
TypedEventTarget,
1716
} from "@microbit/microbit-connection";
18-
import { SerialConnectionEventMap } from "@microbit/microbit-connection/usb";
1917
import { MicrobitUSBConnection } from "@microbit/microbit-connection/usb";
18+
import { SimpleEventTarget } from "./simple-event-target";
19+
20+
interface MockEventMap {
21+
status: ConnectionStatusChange;
22+
flash: void;
23+
serialdata: { data: string };
24+
}
2025

2126
/**
2227
* A mock device used during end-to-end testing.
@@ -26,9 +31,10 @@ import { MicrobitUSBConnection } from "@microbit/microbit-connection/usb";
2631
* the connected state without a real device.
2732
*/
2833
export class MockDeviceConnection
29-
extends TypedEventTarget<DeviceConnectionEventMap & SerialConnectionEventMap>
34+
extends SimpleEventTarget<MockEventMap>
3035
implements MicrobitUSBConnection
3136
{
37+
readonly type = "usb" as const;
3238
status: ConnectionStatus = ConnectionStatus.NoAuthorizedDevice;
3339

3440
private connectResults: DeviceErrorCode[] = [];

src/device/simple-event-target.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* (c) 2026, Micro:bit Educational Foundation and contributors
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
type Listener<T> = (data: T) => void;
8+
9+
/**
10+
* Lightweight typed event target with Set-based deduplication.
11+
*/
12+
export class SimpleEventTarget<M> {
13+
private listeners = new Map<string, Set<Listener<any>>>();
14+
15+
addEventListener<K extends keyof M & string>(
16+
type: K,
17+
listener: Listener<M[K]>
18+
): void {
19+
let set = this.listeners.get(type);
20+
if (!set) {
21+
set = new Set();
22+
this.listeners.set(type, set);
23+
}
24+
set.add(listener);
25+
}
26+
27+
removeEventListener<K extends keyof M & string>(
28+
type: K,
29+
listener: Listener<M[K]>
30+
): void {
31+
const set = this.listeners.get(type);
32+
if (set?.delete(listener) && set.size === 0) {
33+
this.listeners.delete(type);
34+
}
35+
}
36+
37+
protected dispatchEvent<K extends keyof M & string>(
38+
type: K,
39+
...[data]: M[K] extends void ? [] : [data: M[K]]
40+
): void {
41+
const set = this.listeners.get(type);
42+
if (set) {
43+
for (const listener of set) {
44+
listener(data as M[K]);
45+
}
46+
}
47+
}
48+
}

src/device/simulator.ts

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@
66
import {
77
BoardVersion,
88
ConnectionStatus,
9-
DeviceConnectionEventMap,
10-
TypedEventTarget,
9+
ConnectionStatusChange,
1110
} from "@microbit/microbit-connection";
12-
import { SerialConnectionEventMap } from "@microbit/microbit-connection/usb";
1311
import { MicrobitUSBConnection } from "@microbit/microbit-connection/usb";
12+
import { SimpleEventTarget } from "./simple-event-target";
1413
import { Logging } from "../logging/logging";
1514

1615
// Simulator-only events.
1716

18-
export interface LogDataEvent {
17+
export interface LogData {
1918
log: DataLog;
2019
}
2120

22-
export interface RadioDataEvent {
21+
export interface RadioData {
2322
text: string;
2423
}
2524

26-
export interface StateChangeEvent {
25+
export interface StateChange {
2726
state: SimulatorState;
2827
}
2928

@@ -136,12 +135,19 @@ const initialDataLog = (): DataLog => ({
136135
data: [],
137136
});
138137

139-
interface SimulatorEventMap extends DeviceConnectionEventMap {
140-
log_data: LogDataEvent;
141-
radio_data: RadioDataEvent;
142-
radio_reset: void;
143-
state_change: StateChangeEvent;
144-
request_flash: void;
138+
interface SimulatorEventMap {
139+
status: ConnectionStatusChange;
140+
beforerequestdevice: void;
141+
afterrequestdevice: void;
142+
flash: void;
143+
serialdata: { data: string };
144+
serialreset: void;
145+
// Simulator-specific
146+
logdata: LogData;
147+
radiodata: RadioData;
148+
radioreset: void;
149+
statechange: StateChange;
150+
requestflash: void;
145151
}
146152

147153
/**
@@ -150,9 +156,10 @@ interface SimulatorEventMap extends DeviceConnectionEventMap {
150156
* This communicates with the iframe that is used to embed the simulator.
151157
*/
152158
export class SimulatorDeviceConnection
153-
extends TypedEventTarget<SimulatorEventMap & SerialConnectionEventMap>
159+
extends SimpleEventTarget<SimulatorEventMap>
154160
implements MicrobitUSBConnection
155161
{
162+
readonly type = "usb" as const;
156163
status: ConnectionStatus = ConnectionStatus.NoAuthorizedDevice;
157164
state: SimulatorState | undefined;
158165

@@ -168,26 +175,26 @@ export class SimulatorDeviceConnection
168175
case "ready": {
169176
const newState = event.data.state;
170177
this.state = newState;
171-
this.dispatchEvent("state_change", { state: newState });
178+
this.dispatchEvent("statechange", { state: newState });
172179
if (this.status !== ConnectionStatus.Connected) {
173180
this.setStatus(ConnectionStatus.Connected);
174181
}
175182
break;
176183
}
177-
case "request_flash": {
178-
this.dispatchEvent("request_flash");
184+
case "requestflash": {
185+
this.dispatchEvent("requestflash");
179186
this.logging.event({
180187
type: "sim-user-start",
181188
});
182189
break;
183190
}
184-
case "state_change": {
191+
case "statechange": {
185192
const updated = {
186193
...this.state,
187194
...event.data.change,
188195
};
189196
this.state = updated;
190-
this.dispatchEvent("state_change", { state: updated });
197+
this.dispatchEvent("statechange", { state: updated });
191198
break;
192199
}
193200
case "radio_output": {
@@ -200,7 +207,7 @@ export class SimulatorDeviceConnection
200207
// eslint-disable-next-line no-control-regex
201208
.replace(/^\x01\x00\x01/, "");
202209
if (message instanceof Uint8Array) {
203-
this.dispatchEvent("radio_data", { text });
210+
this.dispatchEvent("radiodata", { text });
204211
}
205212
break;
206213
}
@@ -218,12 +225,12 @@ export class SimulatorDeviceConnection
218225
result.data.push({ data: entry.data });
219226
}
220227
this.log = result;
221-
this.dispatchEvent("log_data", { log: this.log });
228+
this.dispatchEvent("logdata", { log: this.log });
222229
break;
223230
}
224231
case "log_delete": {
225232
this.log = initialDataLog();
226-
this.dispatchEvent("log_data", { log: this.log });
233+
this.dispatchEvent("logdata", { log: this.log });
227234
break;
228235
}
229236
case "serial_output": {
@@ -300,7 +307,7 @@ export class SimulatorDeviceConnection
300307
private notifyResetComms() {
301308
// Might be nice to rework so this was all about connection state changes.
302309
this.dispatchEvent("serialreset");
303-
this.dispatchEvent("radio_reset");
310+
this.dispatchEvent("radioreset");
304311
}
305312

306313
async disconnect(): Promise<void> {
@@ -340,7 +347,7 @@ export class SimulatorDeviceConnection
340347
value,
341348
},
342349
};
343-
this.dispatchEvent("state_change", { state: this.state });
350+
this.dispatchEvent("statechange", { state: this.state });
344351
this.postMessage("set_value", {
345352
id,
346353
value,

src/e2e/connect.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ test.describe("connect", () => {
7373
test("shows the update firmware dialog and connects on try again", async ({
7474
app,
7575
}) => {
76-
await app.mockDeviceConnectFailure("update-req");
76+
await app.mockDeviceConnectFailure("firmware-update-required");
7777
await app.connect();
7878
await app.expectDialog("Firmware update required");
7979
await app.connectViaTryAgain();

src/project/project-actions.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -853,15 +853,15 @@ export class ProjectActions {
853853
// The UI will already reflect the disconnection.
854854
return;
855855
}
856-
case "update-req":
856+
case "firmware-update-required":
857857
await this.handleFirmwareUpdate(e.code, userAction, finalFocusRef);
858858
return;
859-
case "clear-connect":
860-
return this.handleClearConnectError(finalFocusRef);
861-
case "timeout-error":
859+
case "device-in-use":
860+
return this.handleDeviceInUseError(finalFocusRef);
861+
case "timeout":
862862
return this.handleTimeoutError(finalFocusRef);
863-
case "reconnect-microbit":
864-
return this.handleReconnectMicrobitError(finalFocusRef);
863+
case "connection-error":
864+
return this.handleConnectionError(finalFocusRef);
865865
default: {
866866
return this.actionFeedback.unexpectedError(e);
867867
}
@@ -886,7 +886,7 @@ export class ProjectActions {
886886
this.save(finalFocusRef, true);
887887
}
888888

889-
private async handleClearConnectError(finalFocusRef: FinalFocusRef) {
889+
private async handleDeviceInUseError(finalFocusRef: FinalFocusRef) {
890890
return this.dialogs.show<void>((callback) => (
891891
<WebUSBErrorDialog
892892
callback={callback}
@@ -906,7 +906,7 @@ export class ProjectActions {
906906
/>
907907
));
908908
}
909-
private async handleReconnectMicrobitError(finalFocusRef: FinalFocusRef) {
909+
private async handleConnectionError(finalFocusRef: FinalFocusRef) {
910910
return this.dialogs.show<void>((callback) => (
911911
<WebUSBErrorDialog
912912
callback={callback}

src/simulator/SimulatorActionBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ const SimulatorActionBar = ({
6868
setIsMuted(!isMuted);
6969
}, [device, isMuted, setIsMuted]);
7070
useEffect(() => {
71-
device.addEventListener("request_flash", handlePlay);
71+
device.addEventListener("requestflash", handlePlay);
7272
return () => {
73-
device.removeEventListener("request_flash", handlePlay);
73+
device.removeEventListener("requestflash", handlePlay);
7474
};
7575
}, [device, handlePlay]);
7676
const size = "md";

src/simulator/SimulatorModules.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
RangeSensor as RangeSensorType,
2424
SensorStateKey,
2525
SimulatorState,
26-
StateChangeEvent,
26+
StateChange,
2727
} from "../device/simulator";
2828
import { useRouterState } from "../router-hooks";
2929
import ButtonsModule from "./ButtonsModule";
@@ -110,12 +110,12 @@ const SimulatorModules = ({ running, ...props }: SimulatorModulesProps) => {
110110
);
111111
const intl = useIntl();
112112
useEffect(() => {
113-
const listener = (event: StateChangeEvent) => {
113+
const listener = (event: StateChange) => {
114114
setState(event.state);
115115
};
116-
device.addEventListener("state_change", listener);
116+
device.addEventListener("statechange", listener);
117117
return () => {
118-
device.removeEventListener("state_change", listener);
118+
device.removeEventListener("statechange", listener);
119119
};
120120
}, [device, setState]);
121121
const handleSensorChange = useCallback(

0 commit comments

Comments
 (0)