Skip to content

Commit bb2eb6b

Browse files
Add magnetometer service (#48)
1 parent 47594f1 commit bb2eb6b

File tree

8 files changed

+324
-2
lines changed

8 files changed

+324
-2
lines changed

lib/accelerometer-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class AccelerometerService implements Service {
9696
// Allowed values: 2, 5, 10, 20, 40, 100, 1000
9797
// Values passed are rounded up to the allowed values on device.
9898
// Documentation for allowed values looks wrong.
99-
// https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html
99+
// https://lancaster-university.github.io/microbit-docs/ble/profile/#about-the-accelerometer-service
100100
const dataView = new DataView(new ArrayBuffer(2));
101101
dataView.setUint16(0, value, true);
102102
return this.queueGattOperation(() =>

lib/bluetooth-device-wrapper.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ButtonService } from "./button-service.js";
1010
import { BoardVersion, DeviceError } from "./device.js";
1111
import { LedService } from "./led-service.js";
1212
import { Logging, NullLogging } from "./logging.js";
13+
import { MagnetometerService } from "./magnetometer-service.js";
1314
import { PromiseQueue } from "./promise-queue.js";
1415
import {
1516
ServiceConnectionEventMap,
@@ -117,9 +118,18 @@ export class BluetoothDeviceWrapper {
117118
"buttonbchanged",
118119
]);
119120
private led = new ServiceInfo(LedService.createService, []);
121+
private magnetometer = new ServiceInfo(MagnetometerService.createService, [
122+
"magnetometerdatachanged",
123+
]);
120124
private uart = new ServiceInfo(UARTService.createService, ["uartdata"]);
121125

122-
private serviceInfo = [this.accelerometer, this.buttons, this.led, this.uart];
126+
private serviceInfo = [
127+
this.accelerometer,
128+
this.buttons,
129+
this.led,
130+
this.magnetometer,
131+
this.uart,
132+
];
123133

124134
boardVersion: BoardVersion | undefined;
125135

@@ -387,6 +397,10 @@ export class BluetoothDeviceWrapper {
387397
return this.createIfNeeded(this.led, false);
388398
}
389399

400+
async getMagnetometerService(): Promise<MagnetometerService | undefined> {
401+
return this.createIfNeeded(this.magnetometer, false);
402+
}
403+
390404
async getUARTService(): Promise<UARTService | undefined> {
391405
return this.createIfNeeded(this.uart, false);
392406
}

lib/bluetooth.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { TypedEventTarget } from "./events.js";
2222
import { LedMatrix } from "./led.js";
2323
import { Logging, NullLogging } from "./logging.js";
24+
import { MagnetometerData } from "./magnetometer.js";
2425
import {
2526
ServiceConnectionEventMap,
2627
TypedServiceEvent,
@@ -274,6 +275,31 @@ export class MicrobitWebBluetoothConnection
274275
ledService?.setLedMatrix(matrix);
275276
}
276277

278+
async getMagnetometerData(): Promise<MagnetometerData | undefined> {
279+
const magnetometerService = await this.connection?.getMagnetometerService();
280+
return magnetometerService?.getData();
281+
}
282+
283+
async getMagnetometerPeriod(): Promise<number | undefined> {
284+
const magnetometerService = await this.connection?.getMagnetometerService();
285+
return magnetometerService?.getPeriod();
286+
}
287+
288+
async setMagnetometerPeriod(value: number): Promise<void> {
289+
const magnetometerService = await this.connection?.getMagnetometerService();
290+
return magnetometerService?.setPeriod(value);
291+
}
292+
293+
async getMagnetometerBearing(): Promise<number | undefined> {
294+
const magnetometerService = await this.connection?.getMagnetometerService();
295+
return magnetometerService?.getBearing();
296+
}
297+
298+
async triggerMagnetometerCalibration(): Promise<void> {
299+
const magnetometerService = await this.connection?.getMagnetometerService();
300+
return magnetometerService?.triggerCalibration();
301+
}
302+
277303
async writeUART(data: Uint8Array): Promise<void> {
278304
const uartService = await this.connection?.getUARTService();
279305
uartService?.writeData(data);

lib/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from "./device.js";
2222
import { TypedEventTarget } from "./events.js";
2323
import { createUniversalHexFlashDataSource } from "./hex-flash-data-source.js";
24+
import { MagnetometerData, MagnetometerDataEvent } from "./magnetometer.js";
2425
import { ServiceConnectionEventMap } from "./service-events.js";
2526
import { MicrobitRadioBridgeConnection } from "./usb-radio-bridge.js";
2627
import { MicrobitWebUSBConnection } from "./usb.js";
@@ -56,4 +57,6 @@ export type {
5657
DeviceConnection,
5758
DeviceErrorCode,
5859
FlashDataSource,
60+
MagnetometerData,
61+
MagnetometerDataEvent,
5962
};

lib/magnetometer-service.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { MagnetometerData, MagnetometerDataEvent } from "./magnetometer.js";
2+
import { Service } from "./bluetooth-device-wrapper.js";
3+
import { profile } from "./bluetooth-profile.js";
4+
import { BackgroundErrorEvent, DeviceError } from "./device.js";
5+
import {
6+
CharacteristicDataTarget,
7+
TypedServiceEvent,
8+
TypedServiceEventDispatcher,
9+
} from "./service-events.js";
10+
11+
export class MagnetometerService implements Service {
12+
constructor(
13+
private magnetometerDataCharacteristic: BluetoothRemoteGATTCharacteristic,
14+
private magnetometerPeriodCharacteristic: BluetoothRemoteGATTCharacteristic,
15+
private magnetometerBearingCharacteristic: BluetoothRemoteGATTCharacteristic,
16+
private magnetometerCalibrationCharacteristic: BluetoothRemoteGATTCharacteristic,
17+
private dispatchTypedEvent: TypedServiceEventDispatcher,
18+
private queueGattOperation: <R>(action: () => Promise<R>) => Promise<R>,
19+
) {
20+
this.magnetometerDataCharacteristic.addEventListener(
21+
"characteristicvaluechanged",
22+
(event: Event) => {
23+
const target = event.target as CharacteristicDataTarget;
24+
const data = this.dataViewToData(target.value);
25+
this.dispatchTypedEvent(
26+
"magnetometerdatachanged",
27+
new MagnetometerDataEvent(data),
28+
);
29+
},
30+
);
31+
}
32+
33+
static async createService(
34+
gattServer: BluetoothRemoteGATTServer,
35+
dispatcher: TypedServiceEventDispatcher,
36+
queueGattOperation: <R>(action: () => Promise<R>) => Promise<R>,
37+
listenerInit: boolean,
38+
): Promise<MagnetometerService | undefined> {
39+
let magnetometerService: BluetoothRemoteGATTService;
40+
try {
41+
magnetometerService = await gattServer.getPrimaryService(
42+
profile.magnetometer.id,
43+
);
44+
} catch (err) {
45+
if (listenerInit) {
46+
dispatcher("backgrounderror", new BackgroundErrorEvent(err as string));
47+
return;
48+
} else {
49+
throw new DeviceError({
50+
code: "service-missing",
51+
message: err as string,
52+
});
53+
}
54+
}
55+
const magnetometerDataCharacteristic =
56+
await magnetometerService.getCharacteristic(
57+
profile.magnetometer.characteristics.data.id,
58+
);
59+
const magnetometerPeriodCharacteristic =
60+
await magnetometerService.getCharacteristic(
61+
profile.magnetometer.characteristics.period.id,
62+
);
63+
const magnetometerBearingCharacteristic =
64+
await magnetometerService.getCharacteristic(
65+
profile.magnetometer.characteristics.bearing.id,
66+
);
67+
const magnetometerCalibrationCharacteristic =
68+
await magnetometerService.getCharacteristic(
69+
profile.magnetometer.characteristics.calibration.id,
70+
);
71+
return new MagnetometerService(
72+
magnetometerDataCharacteristic,
73+
magnetometerPeriodCharacteristic,
74+
magnetometerBearingCharacteristic,
75+
magnetometerCalibrationCharacteristic,
76+
dispatcher,
77+
queueGattOperation,
78+
);
79+
}
80+
81+
private dataViewToData(dataView: DataView): MagnetometerData {
82+
return {
83+
x: dataView.getInt16(0, true),
84+
y: dataView.getInt16(2, true),
85+
z: dataView.getInt16(4, true),
86+
};
87+
}
88+
89+
async getData(): Promise<MagnetometerData> {
90+
const dataView = await this.queueGattOperation(() =>
91+
this.magnetometerDataCharacteristic.readValue(),
92+
);
93+
return this.dataViewToData(dataView);
94+
}
95+
96+
async getPeriod(): Promise<number> {
97+
const dataView = await this.queueGattOperation(() =>
98+
this.magnetometerPeriodCharacteristic.readValue(),
99+
);
100+
return dataView.getUint16(0, true);
101+
}
102+
103+
async setPeriod(value: number): Promise<void> {
104+
if (value === 0) {
105+
// Writing 0 causes the device to crash.
106+
return;
107+
}
108+
// Allowed values: 10, 20, 50, 100
109+
// Values passed are rounded up to the allowed values on device.
110+
// Documentation for allowed values looks wrong.
111+
// https://lancaster-university.github.io/microbit-docs/ble/profile/#about-the-magnetometer-service
112+
const dataView = new DataView(new ArrayBuffer(2));
113+
dataView.setUint16(0, value, true);
114+
return this.queueGattOperation(() =>
115+
this.magnetometerPeriodCharacteristic.writeValue(dataView),
116+
);
117+
}
118+
119+
async getBearing(): Promise<number> {
120+
const dataView = await this.queueGattOperation(() =>
121+
this.magnetometerBearingCharacteristic.readValue(),
122+
);
123+
return dataView.getUint16(0, true);
124+
}
125+
126+
async triggerCalibration(): Promise<void> {
127+
const dataView = new DataView(new ArrayBuffer(1));
128+
dataView.setUint8(0, 1);
129+
return this.queueGattOperation(() =>
130+
this.magnetometerCalibrationCharacteristic.writeValue(dataView),
131+
);
132+
}
133+
134+
async startNotifications(type: TypedServiceEvent): Promise<void> {
135+
await this.characteristicForEvent(type)?.startNotifications();
136+
}
137+
138+
async stopNotifications(type: TypedServiceEvent): Promise<void> {
139+
await this.characteristicForEvent(type)?.stopNotifications();
140+
}
141+
142+
private characteristicForEvent(type: TypedServiceEvent) {
143+
switch (type) {
144+
case "magnetometerdatachanged": {
145+
return this.magnetometerDataCharacteristic;
146+
}
147+
default: {
148+
return undefined;
149+
}
150+
}
151+
}
152+
}

lib/magnetometer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface MagnetometerData {
2+
x: number;
3+
y: number;
4+
z: number;
5+
}
6+
7+
export class MagnetometerDataEvent extends Event {
8+
constructor(public readonly data: MagnetometerData) {
9+
super("magnetometerdatachanged");
10+
}
11+
}

lib/service-events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { AccelerometerDataEvent } from "./accelerometer.js";
22
import { ButtonEvent } from "./buttons.js";
33
import { DeviceConnectionEventMap } from "./device.js";
4+
import { MagnetometerDataEvent } from "./magnetometer.js";
45
import { UARTDataEvent } from "./uart.js";
56

67
export class ServiceConnectionEventMap {
78
"accelerometerdatachanged": AccelerometerDataEvent;
89
"buttonachanged": ButtonEvent;
910
"buttonbchanged": ButtonEvent;
11+
"magnetometerdatachanged": MagnetometerDataEvent;
1012
"uartdata": UARTDataEvent;
1113
}
1214

0 commit comments

Comments
 (0)