diff --git a/src/actions.ts b/src/actions.ts index dbd59b18..bdfb00c3 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -3,6 +3,7 @@ import { Dispatch as ReduxDispatch } from 'redux'; import { AppAction } from './app/actions'; +import { BleDIServiceAction } from './ble-device-info-service/actions'; import { BlePybricksServiceAction, BlePybricksServiceCommandAction, @@ -34,6 +35,7 @@ export type Action = | AppAction | BLEAction | BLEConnectAction + | BleDIServiceAction | BlePybricksServiceAction | BlePybricksServiceCommandAction | BlePybricksServiceEventAction diff --git a/src/ble-device-info-service/actions.ts b/src/ble-device-info-service/actions.ts new file mode 100644 index 00000000..e7d7b404 --- /dev/null +++ b/src/ble-device-info-service/actions.ts @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors + +import { Action } from 'redux'; +import { PnpId } from './protocol'; + +export enum BleDIServiceActionType { + DidReceiveFirmwareRevision = 'action.bleDIService.didReceiveFirmwareRevision', + DidReceiveSoftwareRevision = 'action.bleDIService.didReceiveSoftwareRevision', + DidReceivePnPId = 'action.bleDIService.didReceivePnPId', +} + +/** Action that indicates the firmware revision characteristic was read. */ +export type BleDIServiceDidReceiveFirmwareRevisionAction = + Action & { + version: string; + }; + +/** Action that indicates the firmware revision characteristic was read. */ +export function bleDIServiceDidReceiveFirmwareRevision( + version: string, +): BleDIServiceDidReceiveFirmwareRevisionAction { + return { type: BleDIServiceActionType.DidReceiveFirmwareRevision, version }; +} + +/** Action that indicates the software revision characteristic was read. */ +export type BleDIServiceDidReceiveSoftwareRevisionAction = + Action & { + version: string; + }; + +/** Action that indicates the software revision characteristic was read. */ +export function bleDIServiceDidReceiveSoftwareRevision( + version: string, +): BleDIServiceDidReceiveSoftwareRevisionAction { + return { type: BleDIServiceActionType.DidReceiveSoftwareRevision, version }; +} + +/** Action that indicates the PnP ID characteristic was read. */ +export type BleDIServiceDidReceivePnPIdAction = + Action & { + pnpId: PnpId; + }; + +/** Action that indicates the PnP ID characteristic was read. */ +export function bleDIServiceDidReceivePnPId( + pnpId: PnpId, +): BleDIServiceDidReceivePnPIdAction { + return { type: BleDIServiceActionType.DidReceivePnPId, pnpId }; +} + +/** Common type for all device info service actions. */ +export type BleDIServiceAction = + | BleDIServiceDidReceiveFirmwareRevisionAction + | BleDIServiceDidReceiveSoftwareRevisionAction + | BleDIServiceDidReceivePnPIdAction; diff --git a/src/ble-uart/sagas.ts b/src/ble-uart/sagas.ts index 30728564..b071be1c 100644 --- a/src/ble-uart/sagas.ts +++ b/src/ble-uart/sagas.ts @@ -16,7 +16,11 @@ import { takeMaybe, } from 'typed-redux-saga/macro'; import { - PnpId, + bleDIServiceDidReceiveFirmwareRevision, + bleDIServiceDidReceivePnPId, + bleDIServiceDidReceiveSoftwareRevision, +} from '../ble-device-info-service/actions'; +import { decodePnpId, serviceUUID as deviceInfoServiceUUID, firmwareRevisionStringUUID, @@ -48,7 +52,7 @@ import { } from '../ble/actions'; import { BleConnectionState } from '../ble/reducers'; import { RootState } from '../reducers'; -import { ensureError, hex } from '../utils'; +import { ensureError } from '../utils'; import { BleUartActionType, BleUartWriteAction, @@ -194,11 +198,9 @@ function* connect(_action: BleDeviceConnectAction): Generator { return; } - let firmwareVersion: string; try { - firmwareVersion = decoder.decode( - yield* call([firmwareVersionChar, 'readValue']), - ); + const version = decoder.decode(yield* call([firmwareVersionChar, 'readValue'])); + yield* put(bleDIServiceDidReceiveFirmwareRevision(version)); } catch (err) { server.disconnect(); yield* takeMaybe(disconnectChannel); @@ -219,11 +221,9 @@ function* connect(_action: BleDeviceConnectAction): Generator { return; } - let protocolVersion: string; try { - protocolVersion = decoder.decode( - yield* call([softwareVersionChar, 'readValue']), - ); + const version = decoder.decode(yield* call([softwareVersionChar, 'readValue'])); + yield* put(bleDIServiceDidReceiveSoftwareRevision(version)); } catch (err) { server.disconnect(); yield* takeMaybe(disconnectChannel); @@ -231,9 +231,6 @@ function* connect(_action: BleDeviceConnectAction): Generator { return; } - // 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); @@ -244,9 +241,9 @@ function* connect(_action: BleDeviceConnectAction): Generator { } if (pnpIdChar) { - let pnpId: PnpId; try { - pnpId = decodePnpId(yield* call([pnpIdChar, 'readValue'])); + const pnpId = decodePnpId(yield* call([pnpIdChar, 'readValue'])); + yield* put(bleDIServiceDidReceivePnPId(pnpId)); } catch (err) { server.disconnect(); yield* takeMaybe(disconnectChannel); @@ -255,13 +252,6 @@ function* connect(_action: BleDeviceConnectAction): Generator { ); return; } - - console.log( - `Vendor: ${hex(pnpId.vendorId, 4)}, Product: ${hex( - pnpId.productId, - 4, - )}, Version: ${hex(pnpId.productVersion, 4)}`, - ); } let pybricksService: BluetoothRemoteGATTService; @@ -419,7 +409,7 @@ function* connect(_action: BleDeviceConnectAction): Generator { tasks.push(yield* takeEvery(BleUartActionType.Write, writeUart, uartRxChar)); - yield* put(didConnect(firmwareVersion)); + yield* put(didConnect()); // wait for disconnection yield* takeMaybe(disconnectChannel); diff --git a/src/ble/actions.ts b/src/ble/actions.ts index 27751ddd..78ca1d70 100644 --- a/src/ble/actions.ts +++ b/src/ble/actions.ts @@ -44,16 +44,13 @@ export function connect(): BleDeviceConnectAction { return { type: BleDeviceActionType.Connect }; } -export type BleDeviceDidConnectAction = Action & { - firmwareVersion: string; -}; +export type BleDeviceDidConnectAction = Action; /** * Creates an action that indicates a device was connected. - * @param firmwareVersion The firmware version of the hub (e.g. 3.0.0a1) */ -export function didConnect(firmwareVersion: string): BleDeviceDidConnectAction { - return { type: BleDeviceActionType.DidConnect, firmwareVersion }; +export function didConnect(): BleDeviceDidConnectAction { + return { type: BleDeviceActionType.DidConnect }; } export enum BleDeviceFailToConnectReasonType { diff --git a/src/ble/reducers.test.ts b/src/ble/reducers.test.ts index 88daf64a..51389e75 100644 --- a/src/ble/reducers.test.ts +++ b/src/ble/reducers.test.ts @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2021 The Pybricks Authors -import { firmwareVersion } from '@pybricks/firmware'; import { Action } from '../actions'; import { BleDeviceDidFailToConnectReason, @@ -30,10 +29,8 @@ test('connection', () => { .connection, ).toBe(BleConnectionState.Connecting); expect( - reducers( - { connection: BleConnectionState.Connecting } as State, - didConnect(firmwareVersion), - ).connection, + reducers({ connection: BleConnectionState.Connecting } as State, didConnect()) + .connection, ).toBe(BleConnectionState.Connected); expect( reducers( diff --git a/src/hub/reducers.test.ts b/src/hub/reducers.test.ts index aef743d0..c0bfd702 100644 --- a/src/hub/reducers.test.ts +++ b/src/hub/reducers.test.ts @@ -28,10 +28,8 @@ test('initial state', () => { describe('runtime', () => { test('', () => { expect( - reducers( - { runtime: HubRuntimeState.Disconnected } as State, - didConnect(firmwareVersion), - ).runtime, + reducers({ runtime: HubRuntimeState.Disconnected } as State, didConnect()) + .runtime, ).toBe(HubRuntimeState.Unknown); }); diff --git a/src/notifications/sagas.test.ts b/src/notifications/sagas.test.ts index 8091bcdf..977301b5 100644 --- a/src/notifications/sagas.test.ts +++ b/src/notifications/sagas.test.ts @@ -10,10 +10,10 @@ import { import { AsyncSaga } from '../../test'; import { Action } from '../actions'; import { didCheckForUpdate } from '../app/actions'; +import { bleDIServiceDidReceiveFirmwareRevision } from '../ble-device-info-service/actions'; import { BleDeviceFailToConnectReasonType, didFailToConnect as bleDidFailToConnect, - didConnect, } from '../ble/actions'; import { didFailToSaveAs, storageChanged } from '../editor/actions'; import { @@ -81,7 +81,7 @@ test.each([ didFailToFinish(FailToFinishReasonType.FirmwareSize), didFailToFinish(FailToFinishReasonType.Unknown, new Error('test error')), didCheckForUpdate(false), - didConnect('3.0.0'), + bleDIServiceDidReceiveFirmwareRevision('3.0.0'), didFailToSaveAs(new DOMException('test message', 'NotAllowedError')), ])('actions that should show notification: %o', async (action: Action) => { const getToasts = jest.fn().mockReturnValue([]); @@ -113,7 +113,7 @@ test.each([ didFailToFinish(FailToFinishReasonType.FailedToConnect), didSucceed({} as ServiceWorkerRegistration), didCheckForUpdate(true), - didConnect(firmwareVersion), + bleDIServiceDidReceiveFirmwareRevision(firmwareVersion), didFailToSaveAs(new DOMException('test message', 'AbortError')), ])('actions that should not show a notification: %o', async (action: Action) => { const getToasts = jest.fn().mockReturnValue([]); diff --git a/src/notifications/sagas.ts b/src/notifications/sagas.ts index cd39b28c..4bd22855 100644 --- a/src/notifications/sagas.ts +++ b/src/notifications/sagas.ts @@ -12,9 +12,12 @@ import * as semver from 'semver'; import { delay, getContext, put, take, takeEvery } from 'typed-redux-saga/macro'; import { AppActionType, AppDidCheckForUpdateAction, reload } from '../app/actions'; import { appName } from '../app/constants'; +import { + BleDIServiceActionType, + BleDIServiceDidReceiveFirmwareRevisionAction, +} from '../ble-device-info-service/actions'; import { BleDeviceActionType, - BleDeviceDidConnectAction, BleDeviceDidFailToConnectAction, BleDeviceFailToConnectReasonType, } from '../ble/actions'; @@ -393,12 +396,14 @@ function* showNoUpdateInfo(action: AppDidCheckForUpdateAction): Generator { }); } -function* checkVersion(action: BleDeviceDidConnectAction): Generator { +function* checkVersion( + action: BleDIServiceDidReceiveFirmwareRevisionAction, +): Generator { // ensure the actual hub firmware version is the same as the shipped // firmware version or newer if ( !semver.satisfies( - pythonVersionToSemver(action.firmwareVersion), + pythonVersionToSemver(action.version), `>=${pythonVersionToSemver(firmwareVersion)}`, ) ) { @@ -423,5 +428,5 @@ export default function* (): Generator { yield* takeEvery(NotificationActionType.Add, addNotification); yield* takeEvery(ServiceWorkerActionType.DidUpdate, showServiceWorkerUpdate); yield* takeEvery(AppActionType.DidCheckForUpdate, showNoUpdateInfo); - yield* takeEvery(BleDeviceActionType.DidConnect, checkVersion); + yield* takeEvery(BleDIServiceActionType.DidReceiveFirmwareRevision, checkVersion); }