Add Device Information Service support

The Pybricks firmware now includes the BLE Device Information Service
to provide firmware and Pybricks protocol versions.

For now, checking that the service is present is enough to know that the
firmware is up to date. But we will need to add additional checks as
soon as the protocol is changed.
This commit is contained in:
David Lechner
2021-04-01 13:29:10 -05:00
parent ac07e4ba61
commit ca1ec0a64e
6 changed files with 121 additions and 9 deletions
+87 -3
View File
@@ -15,6 +15,11 @@ import {
takeEvery,
takeMaybe,
} from 'typed-redux-saga/macro';
import {
serviceUUID as deviceInfoServiceUUID,
firmwareRevisionStringUUID,
softwareRevisionStringUUID,
} from '../ble-device-info-service/protocol';
import {
BlePybricksServiceActionType,
didFailToWriteCommand,
@@ -53,6 +58,8 @@ import {
TxCharUUID as uartTxCharUUID,
} from './protocol';
const decoder = new TextDecoder();
function disconnect(
server: BluetoothRemoteGATTServer,
_action: BleDeviceDisconnectAction,
@@ -109,7 +116,11 @@ function* connect(_action: BleDeviceConnectAction): Generator {
device = yield* call(() =>
navigator.bluetooth.requestDevice({
filters: [{ services: [pybricksServiceUUID] }],
optionalServices: [pybricksServiceUUID, uartServiceUUID],
optionalServices: [
pybricksServiceUUID,
deviceInfoServiceUUID,
uartServiceUUID,
],
}),
);
} catch (err) {
@@ -145,6 +156,79 @@ function* connect(_action: BleDeviceConnectAction): Generator {
yield* takeEvery(BLEDeviceActionType.Disconnect, disconnect, server);
let deviceInfoService: BluetoothRemoteGATTService;
try {
deviceInfoService = yield* call(
[server, 'getPrimaryService'],
deviceInfoServiceUUID,
);
} catch (err) {
server.disconnect();
yield* takeMaybe(disconnectChannel);
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
yield* put(didFailToConnect({ reason: Reason.NoDeviceInfoService }));
} else {
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
}
return;
}
let firmwareVersionChar: BluetoothRemoteGATTCharacteristic;
try {
firmwareVersionChar = yield* call(
[deviceInfoService, 'getCharacteristic'],
firmwareRevisionStringUUID,
);
} catch (err) {
server.disconnect();
yield* takeMaybe(disconnectChannel);
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
return;
}
let firmwareVersion: string;
try {
firmwareVersion = decoder.decode(
yield* call([firmwareVersionChar, 'readValue']),
);
} catch (err) {
server.disconnect();
yield* takeMaybe(disconnectChannel);
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
return;
}
// TODO: save firmware version for later use
console.log(`Hub firmware version: ${firmwareVersion}`);
let softwareVersionChar: BluetoothRemoteGATTCharacteristic;
try {
softwareVersionChar = yield* call(
[deviceInfoService, 'getCharacteristic'],
softwareRevisionStringUUID,
);
} catch (err) {
server.disconnect();
yield* takeMaybe(disconnectChannel);
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
return;
}
let protocolVersion: string;
try {
protocolVersion = decoder.decode(
yield* call([softwareVersionChar, 'readValue']),
);
} catch (err) {
server.disconnect();
yield* takeMaybe(disconnectChannel);
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
return;
}
// TODO: verify that minimum protocol version is met
console.log(`Pybricks protocol version: ${protocolVersion}`);
let pybricksService: BluetoothRemoteGATTService;
try {
pybricksService = yield* call(
@@ -155,7 +239,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
server.disconnect();
yield* takeMaybe(disconnectChannel);
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
yield* put(didFailToConnect({ reason: Reason.NoService }));
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
} else {
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
}
@@ -231,7 +315,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
server.disconnect();
yield* takeMaybe(disconnectChannel);
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
yield* put(didFailToConnect({ reason: Reason.NoService }));
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
} else {
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
}