From 10148fa9cf5bce7e51ecc608a7e31629b90535ea Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 23 Jun 2021 12:31:19 -0500 Subject: [PATCH] ble-uart: decode PnP ID if it exists --- src/ble-device-info-service/protocol.ts | 37 +++++++++++++++++++++++++ src/ble-uart/sagas.ts | 32 +++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/ble-device-info-service/protocol.ts b/src/ble-device-info-service/protocol.ts index f1517a86..b94296ae 100644 --- a/src/ble-device-info-service/protocol.ts +++ b/src/ble-device-info-service/protocol.ts @@ -13,3 +13,40 @@ export const firmwareRevisionStringUUID = 0x2a26; /** Software Revision String characteristic UUID. */ export const softwareRevisionStringUUID = 0x2a28; + +/** PnP ID characteristic UUID. */ +export const pnpIdUUID = 0x2a50; + +/** + * Parameters for the PnP ID characteristic vendor ID source field. + */ +export enum PnpIdVendorIdSource { + /** The vendor ID was assigned by the Bluetooth SIG. */ + BluetoothSig = 1, + /** The vendor and product IDs were assigned by the USB implementors forum. */ + UsbImpForum = 2, +} + +/** + * Decoded data from the PnP ID characteristic. + */ +export type PnpId = { + vendorIdSource: PnpIdVendorIdSource; + vendorId: number; + productId: number; + productVersion: number; +}; + +/** + * Decodes data read from the PnP ID characteristics. + * @param data The data read from the characteristic. + * @returns The decoded data. + */ +export function decodePnpId(data: DataView): PnpId { + return { + vendorIdSource: data.getUint8(0), + vendorId: data.getUint16(1, true), + productId: data.getUint16(3, true), + productVersion: data.getUint16(5, true), + }; +} diff --git a/src/ble-uart/sagas.ts b/src/ble-uart/sagas.ts index a16e0d2c..acf1ab40 100644 --- a/src/ble-uart/sagas.ts +++ b/src/ble-uart/sagas.ts @@ -16,8 +16,11 @@ import { takeMaybe, } from 'typed-redux-saga/macro'; import { + PnpId, + decodePnpId, serviceUUID as deviceInfoServiceUUID, firmwareRevisionStringUUID, + pnpIdUUID, softwareRevisionStringUUID, } from '../ble-device-info-service/protocol'; import { @@ -45,6 +48,7 @@ import { } from '../ble/actions'; import { BleConnectionState } from '../ble/reducers'; import { RootState } from '../reducers'; +import { hex } from '../utils'; import { BleUartActionType, BleUartWriteAction, @@ -229,6 +233,34 @@ function* connect(_action: BleDeviceConnectAction): Generator { // TODO: verify that minimum protocol version is met console.log(`Pybricks protocol version: ${protocolVersion}`); + let pnpIdChar: BluetoothRemoteGATTCharacteristic | undefined = undefined; + try { + pnpIdChar = yield* call([deviceInfoService, 'getCharacteristic'], pnpIdUUID); + } catch (err) { + console.warn( + 'PnP ID characteristic requires Pybricks firmware v3.1.0a1 or later', + ); + } + + if (pnpIdChar) { + let pnpId: PnpId; + try { + pnpId = decodePnpId(yield* call([pnpIdChar, 'readValue'])); + } catch (err) { + server.disconnect(); + yield* takeMaybe(disconnectChannel); + yield* put(didFailToConnect({ reason: Reason.Unknown, err })); + return; + } + + console.log( + `Vendor: ${hex(pnpId.vendorId, 4)}, Product: ${hex( + pnpId.productId, + 4, + )}, Version: ${hex(pnpId.productVersion, 4)}`, + ); + } + let pybricksService: BluetoothRemoteGATTService; try { pybricksService = yield* call(