ble-device-info-service: add actions

This adds actions that are triggered when the device info service
characteristics are read.
This commit is contained in:
David Lechner
2021-12-27 12:38:51 -06:00
parent 6a9e867fe9
commit e92d5c1511
8 changed files with 90 additions and 45 deletions
+2
View File
@@ -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
+56
View File
@@ -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<BleDIServiceActionType.DidReceiveFirmwareRevision> & {
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<BleDIServiceActionType.DidReceiveSoftwareRevision> & {
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<BleDIServiceActionType.DidReceivePnPId> & {
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;
+13 -23
View File
@@ -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);
+3 -6
View File
@@ -44,16 +44,13 @@ export function connect(): BleDeviceConnectAction {
return { type: BleDeviceActionType.Connect };
}
export type BleDeviceDidConnectAction = Action<BleDeviceActionType.DidConnect> & {
firmwareVersion: string;
};
export type BleDeviceDidConnectAction = Action<BleDeviceActionType.DidConnect>;
/**
* 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 {
+2 -5
View File
@@ -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(
+2 -4
View File
@@ -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);
});
+3 -3
View File
@@ -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([]);
+9 -4
View File
@@ -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);
}