remove use of thunk action in ble and hub

This is a step towards moving these to sagas
This commit is contained in:
David Lechner
2020-05-27 20:57:22 -05:00
committed by David Lechner
parent 99fd62100d
commit 24acab901c
16 changed files with 356 additions and 260 deletions
+39 -119
View File
@@ -2,23 +2,6 @@
// Copyright (c) 2020 The Pybricks Authors
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import {
PolyfillBluetoothRemoteGATTCharacteristic,
polyfillBluetoothRemoteGATTCharacteristic,
} from '../utils/web-bluetooth';
import * as notification from './notification';
const pybricksServiceUUID = 'c5f50001-8280-46da-89f4-6d8051e4aeef';
// nRF UART service (Nus)
const bleNusServiceUUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const bleNusCharRXUUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';
const bleNusCharTXUUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';
const bleNusMaxSize = 20;
let device: BluetoothDevice | undefined;
let rxChar: PolyfillBluetoothRemoteGATTCharacteristic | undefined;
/**
* Bluetooth low energy connection action types.
@@ -27,7 +10,7 @@ export enum BLEConnectActionType {
/**
* Connecting to a device has been requested.
*/
WillConnect = 'ble.action.will.connect',
Connect = 'ble.action.connect',
/**
* The connection completed successfully.
*/
@@ -35,7 +18,7 @@ export enum BLEConnectActionType {
/**
* Disconnecting from a device has been requested.
*/
WillDisconnect = 'ble.action.will.disconnect',
Disconnect = 'ble.action.disconnect',
/**
* End async disconnect (can be sent without sending BeginDisconnect first).
*/
@@ -45,141 +28,78 @@ export enum BLEConnectActionType {
/**
* Common type for all BLE connection actions.
*/
type BLEConnectAction = Action<BLEConnectActionType>;
export type BLEConnectAction = Action<BLEConnectActionType>;
/**
* Creates an action that indicates connecting has been requested.
*/
function willConnect(): BLEConnectAction {
return { type: BLEConnectActionType.WillConnect };
export function connect(): BLEConnectAction {
return { type: BLEConnectActionType.Connect };
}
/**
* Creates an action that indicates a device was connected.
*/
function didConnect(): BLEConnectAction {
export function didConnect(): BLEConnectAction {
return { type: BLEConnectActionType.DidConnect };
}
/**
* Creates an action that indicates disconnecting was requested.
*/
function willDisconnect(): BLEConnectAction {
return { type: BLEConnectActionType.WillDisconnect };
export function disconnect(): BLEConnectAction {
return { type: BLEConnectActionType.Disconnect };
}
/**
* Creates an action that indicates a device was disconnected.
*/
function didDisconnect(): BLEConnectAction {
export function didDisconnect(): BLEConnectAction {
return { type: BLEConnectActionType.DidDisconnect };
}
export enum BLEDataActionType {
/**
* Send data.
* Write data.
*/
SendData = 'ble.data.send',
Write = 'ble.data.write',
/**
* Data was received.
* Notify that data was received.
*/
ReceivedData = 'ble.data.receive',
Notify = 'ble.data.receive',
}
export interface BLEDataAction extends Action<BLEDataActionType> {
export interface BLEDataWriteAction extends Action<BLEDataActionType.Write> {
value: Uint8Array;
}
export function write(value: Uint8Array): BLEDataWriteAction {
return { type: BLEDataActionType.Write, value };
}
export interface BLEDataNotifyAction extends Action<BLEDataActionType.Notify> {
value: DataView;
}
export type BLEThunkAction = ThunkAction<Promise<void>, {}, {}, Action>;
export function connect(): BLEThunkAction {
return async function (dispatch): Promise<void> {
if (device !== undefined) {
dispatch(notification.add('error', 'A device is already connected.'));
return;
}
if (navigator.bluetooth === undefined) {
dispatch(
notification.add(
'error',
'This web browser does not support Web Bluetooth or it is not enabled.',
'https://github.com/WebBluetoothCG/web-bluetooth/blob/master/implementation-status.md',
),
);
return;
}
// TODO: check navigator.bluetooth.getAvailability()
dispatch(willConnect());
try {
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [pybricksServiceUUID] }],
optionalServices: [bleNusServiceUUID],
});
} catch (err) {
if (
err instanceof DOMException &&
err.code === DOMException.NOT_FOUND_ERR
) {
// this can happen if the use cancels the dialog
console.debug('User cancelled connect');
} else {
console.error(err);
dispatch(
notification.add(
'error',
'Unexpected error, check developer console for details.',
),
);
}
dispatch(didDisconnect());
return;
}
if (device.gatt === undefined) {
dispatch(notification.add('error', 'Device does not support GATT.'));
dispatch(didDisconnect());
return;
}
device.addEventListener('gattserverdisconnected', () => {
device = undefined;
rxChar = undefined;
dispatch(didDisconnect());
});
const server = await device.gatt.connect();
try {
const service = await server.getPrimaryService(bleNusServiceUUID);
rxChar = polyfillBluetoothRemoteGATTCharacteristic(
await service.getCharacteristic(bleNusCharRXUUID),
);
const txChar = await service.getCharacteristic(bleNusCharTXUUID);
txChar.addEventListener('characteristicvaluechanged', () => {
if (!txChar.value) {
return;
}
dispatch({ type: BLEDataActionType.ReceivedData, value: txChar.value });
});
await txChar.startNotifications();
} catch (err) {
console.error(err);
dispatch(notification.add('error', 'Getting nRF UART service failed.'));
device.gatt.disconnect();
return;
}
dispatch(didConnect());
};
export function notify(value: DataView): BLEDataNotifyAction {
return { type: BLEDataActionType.Notify, value };
}
export function disconnect(): BLEThunkAction {
return async function (dispatch): Promise<void> {
dispatch(willDisconnect());
device?.gatt?.disconnect();
};
/** Common type for low-level BLE data actions. */
export type BLEDataAction = BLEDataWriteAction | BLEDataNotifyAction;
/**
* High-level BLE actions.
*/
export enum BLEActionType {
Toggle = 'ble.action.toggle',
}
export function write(value: ArrayBuffer): BLEThunkAction {
return async function (): Promise<void> {
// TODO: do we need to dispatch any Action<>s here?
for (let i = 0; i < value.byteLength; i += bleNusMaxSize) {
await rxChar?.xWriteValueWithoutResponse(value.slice(i, i + bleNusMaxSize));
}
};
export type BLEToggleAction = Action<BLEActionType.Toggle>;
export function toggleBluetooth(): BLEToggleAction {
return { type: BLEActionType.Toggle };
}
/** Common type for high-level BLE actions */
export type BLEAction = BLEToggleAction;
+45 -57
View File
@@ -2,11 +2,6 @@
// Copyright (c) 2020 The Pybricks Authors
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { getChecksum } from '../epics/hub';
import { write } from './ble';
export type HubThunkAction = ThunkAction<Promise<void>, {}, {}, HubRuntimeStatusAction>;
export enum HubRuntimeStatusType {
Disconnected = 'disconnected',
@@ -17,83 +12,76 @@ export enum HubRuntimeStatusType {
Error = 'error',
}
export enum HubActionType {
export enum HubMessageActionType {
/**
* MicroPython runtime status changed.
* The hub has send a message indicating the MicroPython runtime status changed.
*/
RuntimeStatus = 'hub.runtime.status',
RuntimeStatus = 'hub.message.action.runtime.status',
/**
* The hub has sent a checksum.
*/
Checksum = 'hub.runtime.checksum',
Checksum = 'hub.message.action.runtime.checksum',
}
export interface HubRuntimeStatusAction extends Action<HubActionType.RuntimeStatus> {
export interface HubRuntimeStatusMessageAction
extends Action<HubMessageActionType.RuntimeStatus> {
readonly newStatus: HubRuntimeStatusType;
}
export interface HubChecksumAction extends Action<HubActionType.Checksum> {
readonly checksum: number;
}
export function updateStatus(newStatus: HubRuntimeStatusType): HubRuntimeStatusAction {
export function updateStatus(
newStatus: HubRuntimeStatusType,
): HubRuntimeStatusMessageAction {
return {
type: HubActionType.RuntimeStatus,
type: HubMessageActionType.RuntimeStatus,
newStatus,
};
}
export function checksum(checksum: number): HubChecksumAction {
export interface HubChecksumMessageAction
extends Action<HubMessageActionType.Checksum> {
readonly checksum: number;
}
export function checksum(checksum: number): HubChecksumMessageAction {
return {
type: HubActionType.Checksum,
type: HubMessageActionType.Checksum,
checksum,
};
}
const downloadChunkSize = 100;
/**
* Common type for low-level hub message actions.
*/
export type HubMessageAction = HubRuntimeStatusMessageAction | HubChecksumMessageAction;
export function downloadAndRun(data: ArrayBuffer): HubThunkAction {
return async function (dispatch): Promise<void> {
// let everyone know the runtime is busy loading the program
dispatch(updateStatus(HubRuntimeStatusType.Loading));
// TODO: might need to flush checksum queue here
// first send payload size as big-endian 32-bit integer
const checksum = getChecksum();
const sizeBuf = new Uint8Array(4);
const sizeView = new DataView(sizeBuf.buffer);
sizeView.setUint32(0, data.byteLength, true);
await dispatch(write(sizeBuf));
// TODO: verify checksum
console.log(await checksum);
// Then send payload in 100 byte chunks waiting for checksum after
// each chunk
for (let i = 0; i < data.byteLength; i += downloadChunkSize) {
// need to subscribe to checksum before writing to prevent race condition
const checksum = getChecksum();
await dispatch(write(data.slice(i, i + downloadChunkSize)));
// TODO: verify checksum
console.log(await checksum);
// TODO: dispatch progress
}
// let everyone know the runtime is done loading the program
dispatch(updateStatus(HubRuntimeStatusType.Loaded));
};
/**
* High-level hub actions.
*/
export enum HubActionType {
DownloadAndRun = 'hub.action.downloadAndRun',
Stop = 'hub.action.stop',
Repl = 'hub.action.repl',
}
// SPACE, SPACE, SPACE, SPACE
const startReplCommand = new Uint8Array([0x20, 0x20, 0x20, 0x20]);
export type HubDownloadAndRunAction = Action<HubActionType.DownloadAndRun>;
export function startRepl(): HubThunkAction {
return write(startReplCommand);
export function downloadAndRun(): HubDownloadAndRunAction {
return { type: HubActionType.DownloadAndRun };
}
// CTRL+C, CTRL+C, CTRL+D
const stopCommand = new Uint8Array([0x03, 0x03, 0x04]);
export type HubStopAction = Action<HubActionType.Stop>;
export function stop(): HubThunkAction {
return write(stopCommand);
export function stop(): HubStopAction {
return { type: HubActionType.Stop };
}
export type HubReplAction = Action<HubActionType.Repl>;
export function repl(): HubReplAction {
return { type: HubActionType.Repl };
}
/**
* Common type for all high-level hub actions.
*/
export type HubAction = HubDownloadAndRunAction | HubStopAction | HubReplAction;
+7
View File
@@ -1,4 +1,5 @@
import { Dispatch as ReduxDispatch } from 'redux';
import { BLEAction, BLEConnectAction, BLEDataAction } from './ble';
import {
BootloaderAction,
BootloaderConnectionAction,
@@ -7,6 +8,7 @@ import {
BootloaderResponseAction,
} from './bootloader';
import { EditorAction } from './editor';
import { HubAction, HubMessageAction } from './hub';
import { NotificationAction } from './notification';
import { ServiceWorkerAction } from './service-worker';
import { TerminalDataAction } from './terminal';
@@ -15,12 +17,17 @@ import { TerminalDataAction } from './terminal';
* Common type for all actions.
*/
export type Action =
| BLEConnectAction
| BLEDataAction
| BLEAction
| BootloaderConnectionAction
| BootloaderRequestAction
| BootloaderDidRequestAction
| BootloaderResponseAction
| BootloaderAction
| EditorAction
| HubMessageAction
| HubAction
| NotificationAction
| ServiceWorkerAction
| TerminalDataAction;