Skip to content

Commit 95d12d8

Browse files
Simulator iframe API changes (#40)
Breaking API change to accommodate radio/data log. See updated README.md for detail. A corresponding editor PR will be merged shortly.
1 parent 3184a44 commit 95d12d8

File tree

11 files changed

+444
-243
lines changed

11 files changed

+444
-243
lines changed

README.md

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,24 @@ The following sections documents the messages supported via postMessage.
6464
```javascript
6565
{
6666
"kind": "ready",
67-
"sensors": [
68-
{
67+
"state": {
68+
"lightLevel": {
6969
"id": "lightLevel",
7070
"type": "range",
7171
"min": 0,
7272
"max": 255
73+
},
74+
"soundLevel": {
75+
"id": "soundLevel",
76+
"type": "range",
77+
"min": 0,
78+
"max": 255
79+
// Microphone sensor has low (quiet) and high (loud) thresholds:
80+
"lowThreshold": 50,
81+
"highThreshold": 150
7382
}
74-
// More sensors here.
75-
]
83+
// Full state continues here.
84+
}
7685
}
7786
```
7887

@@ -84,20 +93,36 @@ The following sections documents the messages supported via postMessage.
8493

8594
```javascript
8695
{
87-
"kind": "sensor_change",
88-
"sensors": [
89-
{
90-
"id": "lightLevel",
91-
"type": "range",
92-
"min": 0,
93-
"max": 255
94-
}
95-
// More sensors here.
96+
"kind": "state_change",
97+
"change": {
98+
"soundLevel": {
99+
"id": "soundLevel",
100+
"type": "range",
101+
"min": 0,
102+
"max": 255
103+
// Microphone sensor has low (quiet) and high (loud) thresholds:
104+
"lowThreshold": 50,
105+
"highThreshold": 150
106+
}
107+
// Optionally, further top-level keys here.
108+
}
96109
]
97110
}
98111
```
99112

100-
<td>Sent when one or more sensors change. Note min/max and threshold values can change.
113+
<td>Sent when the simulator state changes. The keys are a subset of the original state. The values are always sent in full.
114+
115+
<tr>
116+
<td>request_flash
117+
<td>
118+
119+
```javascript
120+
{
121+
"kind": "request_flash",
122+
}
123+
```
124+
125+
<td>Sent when the user requests the simulator starts. The embedder should flash the latest code via the <code>flash</code> message.
101126

102127
<tr>
103128
<td>serial_output
@@ -112,6 +137,20 @@ The following sections documents the messages supported via postMessage.
112137

113138
<td>Serial output suitable for a terminal or other use.
114139

140+
<tr>
141+
<td>radio_output
142+
<td>
143+
144+
```javascript
145+
{
146+
"kind": "radio_output",
147+
"data": new Uint8Array([])
148+
}
149+
```
150+
151+
<td>Radio output (sent from the user's program) as bytes.
152+
If you send string data from the program then it will be prepended with the three bytes 0x01, 0x00, 0x01.
153+
115154
</table>
116155

117156
## Messages supported by the iframe
@@ -197,20 +236,36 @@ The following sections documents the messages supported via postMessage.
197236
}
198237
```
199238

200-
<td>Serial input. If the REPL is active it will echo this text via `serial_write`.
239+
<td>Serial input. If the REPL is active it will echo this text via <code>serial_write</code>.
201240
<tr>
202241
<td>sensor_set
203242
<td>
204243

205244
```javascript
206245
{
207-
"kind": "sensor_set",
208-
"sensor": "lightLevel",
246+
"kind": "set_value",
247+
"id": "lightLevel",
209248
"value": 255
210249
}
211250
```
212251

213-
<td>Set a sensor value.
252+
<td>Set a sensor, button or pin value. The sensor, button or pin is identified by the top-level key in the state. Buttons and pins (touch state) have 0 and 1 values. In future, analog values will be supported for pins.
253+
254+
<tr>
255+
<td>radio_input
256+
<td>
257+
258+
```javascript
259+
{
260+
"kind": "radio_input",
261+
"data": new Uint8Array([])
262+
}
263+
```
264+
265+
<td>Radio input (received by the user's program as if sent from another micro:bit) as bytes.
266+
If you want to send string data then prepend the byte array with the three bytes <code>0x01</code>, <code>0x00</code>, <code>0x01</code>.
267+
Otherwise, the user will need to use <code>radio.receive_bytes</code> or <code>radio.receive_full</code>. The input is assumed to be sent to the currently configured radio group.
268+
214269
</table>
215270

216271
## Web Assembly debugging

src/board/accelerometer.ts

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,81 @@
11
import { convertAccelerometerStringToNumber } from "./conversions";
2-
import { EnumSensor, RangeSensor } from "./sensors";
2+
import { EnumSensor, RangeSensor, State } from "./state";
33
import { clamp } from "./util";
44

5+
type StateKeys =
6+
| "accelerometerX"
7+
| "accelerometerY"
8+
| "accelerometerZ"
9+
| "gesture";
10+
11+
type GestureCallback = (v: number) => void;
12+
513
export class Accelerometer {
6-
private gesture: EnumSensor;
7-
private x: RangeSensor;
8-
private y: RangeSensor;
9-
private z: RangeSensor;
10-
constructor(private onSensorChange: () => void) {
11-
this.gesture = new EnumSensor(
12-
"gesture",
13-
[
14-
"none",
15-
"up",
16-
"down",
17-
"left",
18-
"right",
19-
"face up",
20-
"face down",
21-
"freefall",
22-
"3g",
23-
"6g",
24-
"8g",
25-
"shake",
26-
],
27-
"none"
28-
);
14+
state: Pick<
15+
State,
16+
"accelerometerX" | "accelerometerY" | "accelerometerZ" | "gesture"
17+
>;
18+
19+
private gestureCallback: GestureCallback | undefined;
20+
21+
constructor(private onChange: (changes: Partial<State>) => void) {
2922
const min = -2000;
3023
const max = 2000;
31-
this.x = new RangeSensor("accelerometerX", min, max, 0, "mg");
32-
this.y = new RangeSensor("accelerometerY", min, max, 0, "mg");
33-
this.z = new RangeSensor("accelerometerZ", min, max, 0, "mg");
24+
this.state = {
25+
accelerometerX: new RangeSensor("accelerometerX", min, max, 0, "mg"),
26+
accelerometerY: new RangeSensor("accelerometerY", min, max, 0, "mg"),
27+
accelerometerZ: new RangeSensor("accelerometerZ", min, max, 0, "mg"),
28+
gesture: new EnumSensor(
29+
"gesture",
30+
[
31+
"none",
32+
"up",
33+
"down",
34+
"left",
35+
"right",
36+
"face up",
37+
"face down",
38+
"freefall",
39+
"3g",
40+
"6g",
41+
"8g",
42+
"shake",
43+
],
44+
"none"
45+
),
46+
};
3447
}
3548

36-
get sensors() {
37-
return [this.gesture, this.x, this.y, this.z];
49+
setValue(id: StateKeys, value: any) {
50+
this.state[id].setValue(value);
51+
if (id === "gesture") {
52+
this.gestureCallback!(
53+
convertAccelerometerStringToNumber(this.state.gesture.value)
54+
);
55+
}
3856
}
3957

4058
setRange(range: number) {
4159
const min = -1000 * range;
4260
const max = +1000 * range;
43-
for (const sensor of [this.x, this.y, this.z]) {
61+
const { accelerometerX, accelerometerY, accelerometerZ } = this.state;
62+
for (const sensor of [accelerometerX, accelerometerY, accelerometerZ]) {
4463
sensor.value = clamp(sensor.value, min, max);
4564
sensor.min = min;
4665
sensor.max = max;
4766
}
48-
this.onSensorChange();
67+
this.onChange({
68+
accelerometerX,
69+
accelerometerY,
70+
accelerometerZ,
71+
});
4972
}
5073

51-
initialize(gestureCallback: (v: number) => void) {
52-
this.gesture.onchange = (v: string) =>
53-
gestureCallback(convertAccelerometerStringToNumber(v));
74+
initialize(gestureCallback: GestureCallback) {
75+
this.gestureCallback = gestureCallback;
5476
}
5577

56-
dispose() {}
78+
dispose() {
79+
this.gestureCallback = undefined;
80+
}
5781
}

src/board/buttons.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
1-
import { RangeSensor } from "./sensors";
1+
import { RangeSensor, State } from "./state";
22

33
export class Button {
4-
public button: RangeSensor;
4+
public state: RangeSensor;
5+
56
private _presses: number = 0;
67
private _mouseDown: boolean = false;
7-
private _internalChange: boolean = false;
8+
89
private keyListener: (e: KeyboardEvent) => void;
910
private mouseDownListener: (e: MouseEvent) => void;
1011
private mouseUpListener: (e: MouseEvent) => void;
1112
private mouseLeaveListener: (e: MouseEvent) => void;
1213

1314
constructor(
15+
private id: "buttonA" | "buttonB",
1416
private element: SVGElement,
1517
label: string,
16-
private onSensorChange: () => void
18+
private onChange: (change: Partial<State>) => void
1719
) {
1820
this._presses = 0;
19-
this.button = new RangeSensor(label, 0, 1, 0, undefined);
20-
this.button.onchange = (_, curr: number): void => {
21-
if (this._internalChange == true) {
22-
this.onSensorChange();
23-
this._internalChange = false;
24-
}
25-
if (curr) {
26-
this._presses++;
27-
}
28-
this.render();
29-
};
21+
this.state = new RangeSensor(id, 0, 1, 0, undefined);
3022

3123
this.element.setAttribute("role", "button");
3224
this.element.setAttribute("tabindex", "0");
@@ -39,10 +31,8 @@ export class Button {
3931
case " ":
4032
e.preventDefault();
4133
if (e.type === "keydown") {
42-
this._internalChange = true;
4334
this.press();
4435
} else {
45-
this._internalChange = true;
4636
this.release();
4737
}
4838
}
@@ -51,21 +41,18 @@ export class Button {
5141
this.mouseDownListener = (e) => {
5242
e.preventDefault();
5343
this._mouseDown = true;
54-
this._internalChange = true;
5544
this.press();
5645
};
5746
this.mouseUpListener = (e) => {
5847
e.preventDefault();
5948
if (this._mouseDown) {
6049
this._mouseDown = false;
61-
this._internalChange = true;
6250
this.release();
6351
}
6452
};
6553
this.mouseLeaveListener = (e) => {
6654
if (this._mouseDown) {
6755
this._mouseDown = false;
68-
this._internalChange = true;
6956
this.release();
7057
}
7158
};
@@ -77,24 +64,43 @@ export class Button {
7764
this.element.addEventListener("mouseleave", this.mouseLeaveListener);
7865
}
7966

67+
setValue(value: any) {
68+
this.setValueInternal(value, false);
69+
}
70+
71+
private setValueInternal(value: any, internalChange: boolean) {
72+
this.state.setValue(value);
73+
if (value) {
74+
this._presses++;
75+
}
76+
if (internalChange) {
77+
this.onChange({
78+
[this.id]: this.state,
79+
});
80+
}
81+
this.render();
82+
}
83+
8084
press() {
81-
this.button.setValue(
82-
this.button.value === this.button.min ? this.button.max : this.button.min
85+
this.setValueInternal(
86+
this.state.value === this.state.min ? this.state.max : this.state.min,
87+
true
8388
);
8489
}
8590

8691
release() {
87-
this.button.setValue(
88-
this.button.value === this.button.max ? this.button.min : this.button.max
92+
this.setValueInternal(
93+
this.state.value === this.state.max ? this.state.min : this.state.max,
94+
true
8995
);
9096
}
9197

9298
isPressed() {
93-
return !!this.button.value;
99+
return !!this.state.value;
94100
}
95101

96102
render() {
97-
const fill = !!this.button.value ? "#d3b12c" : "none";
103+
const fill = !!this.state.value ? "#d3b12c" : "none";
98104
this.element.querySelectorAll("circle").forEach((c) => {
99105
c.style.fill = fill;
100106
});

src/board/display.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
// This mapping is designed to give a set of 10 visually distinct levels.
22

3-
import { RangeSensor } from "./sensors";
3+
import { RangeSensor } from "./state";
44
import { clamp } from "./util";
55

66
// Carried across from microbit_hal_display_set_pixel.
77
const brightMap = [0, 20, 40, 60, 80, 120, 160, 190, 220, 255];
88

99
export class Display {
10-
public lightLevel: RangeSensor = new RangeSensor(
10+
lightLevel: RangeSensor = new RangeSensor(
1111
"lightLevel",
1212
0,
1313
255,

0 commit comments

Comments
 (0)