mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
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:
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021 The Pybricks Authors
|
||||
//
|
||||
// Pybricks uses the standard Device Info service.
|
||||
// Refer to Device Information Service (DIS) at https://www.bluetooth.com/specifications/specs/
|
||||
// and assigned numbers at https://www.bluetooth.com/specifications/assigned-numbers/
|
||||
|
||||
/** Device Information service UUID. */
|
||||
export const serviceUUID = 0x180a;
|
||||
|
||||
/** Firmware Revision String characteristic UUID. */
|
||||
export const firmwareRevisionStringUUID = 0x2a26;
|
||||
|
||||
/** Software Revision String characteristic UUID. */
|
||||
export const softwareRevisionStringUUID = 0x2a28;
|
||||
+87
-3
@@ -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 }));
|
||||
}
|
||||
|
||||
+7
-3
@@ -58,7 +58,8 @@ export enum BleDeviceFailToConnectReasonType {
|
||||
NoBluetooth = 'ble.device.didFailToConnect.noBluetooth',
|
||||
Canceled = 'ble.device.didFailToConnect.canceled',
|
||||
NoGatt = 'ble.device.didFailToConnect.noGatt',
|
||||
NoService = 'ble.device.didFailToConnect.noService',
|
||||
NoDeviceInfoService = 'ble.device.didFailToConnect.noDeviceInfoService',
|
||||
NoPybricksService = 'ble.device.didFailToConnect.noPybricksService',
|
||||
Unknown = 'ble.device.didFailToConnect.unknown',
|
||||
}
|
||||
|
||||
@@ -74,7 +75,9 @@ export type BleDeviceFailToConnectCanceledReason = Reason<BleDeviceFailToConnect
|
||||
|
||||
export type BleDeviceFailToConnectNoGattReason = Reason<BleDeviceFailToConnectReasonType.NoGatt>;
|
||||
|
||||
export type BleDeviceFailToConnectNoServiceReason = Reason<BleDeviceFailToConnectReasonType.NoService>;
|
||||
export type BleDeviceFailToConnectNoDeviceInfoServiceReason = Reason<BleDeviceFailToConnectReasonType.NoDeviceInfoService>;
|
||||
|
||||
export type BleDeviceFailToConnectNoPybricksServiceReason = Reason<BleDeviceFailToConnectReasonType.NoPybricksService>;
|
||||
|
||||
export type BleDeviceFailToConnectUnknownReason = Reason<BleDeviceFailToConnectReasonType.Unknown> & {
|
||||
err: Error;
|
||||
@@ -85,7 +88,8 @@ export type BleDeviceDidFailToConnectReason =
|
||||
| BleDeviceFailToConnectNoBluetoothReason
|
||||
| BleDeviceFailToConnectCanceledReason
|
||||
| BleDeviceFailToConnectNoGattReason
|
||||
| BleDeviceFailToConnectNoServiceReason
|
||||
| BleDeviceFailToConnectNoDeviceInfoServiceReason
|
||||
| BleDeviceFailToConnectNoPybricksServiceReason
|
||||
| BleDeviceFailToConnectUnknownReason;
|
||||
|
||||
export type BleDeviceDidFailToConnectAction = Action<BleDeviceActionType.DidFailToConnect> &
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"ble": {
|
||||
"gattPermission": "The web browser did not give permission to use Bluetooth Low Energy",
|
||||
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service. Try removing the \"{hubName}\" device in your OS Bluetooth settings, then try again.",
|
||||
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service. Ensure that you are using the most recent firmware. If the problem persists, try removing the \"{hubName}\" device in your OS Bluetooth settings, then try connecting again.",
|
||||
"noWebBluetooth": "This web browser does not support Web Bluetooth or it is not enabled.",
|
||||
"noBluetooth": "No Bluetooth adapter could be found. Bluetooth won't work.",
|
||||
"unexpectedError": "Unexpected error while trying to connect: {errorMessage}"
|
||||
|
||||
@@ -31,7 +31,10 @@ test.each([
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoWebBluetooth }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoBluetooth }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoGatt }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoService }),
|
||||
bleDidFailToConnect({
|
||||
reason: BleDeviceFailToConnectReasonType.NoDeviceInfoService,
|
||||
}),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoPybricksService }),
|
||||
bleDidFailToConnect({
|
||||
reason: BleDeviceFailToConnectReasonType.Unknown,
|
||||
err: { name: 'test', message: 'unknown' },
|
||||
|
||||
@@ -168,12 +168,18 @@ function* showBleDeviceDidFailToConnectError(
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattPermission);
|
||||
break;
|
||||
|
||||
case BleDeviceFailToConnectReasonType.NoService:
|
||||
case BleDeviceFailToConnectReasonType.NoPybricksService:
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattServiceNotFound, {
|
||||
serviceName: 'Pybricks',
|
||||
hubName: 'Pybricks Hub',
|
||||
});
|
||||
break;
|
||||
case BleDeviceFailToConnectReasonType.NoDeviceInfoService:
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattServiceNotFound, {
|
||||
serviceName: 'Device Information',
|
||||
hubName: 'Pybricks Hub',
|
||||
});
|
||||
break;
|
||||
case BleDeviceFailToConnectReasonType.NoBluetooth:
|
||||
yield* showSingleton(Level.Error, MessageId.BleNoBluetooth);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user