Skip to content

Commit

Permalink
BT transport + discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
Nodonisko committed Apr 2, 2024
1 parent 3f37e3e commit b189c0e
Show file tree
Hide file tree
Showing 10 changed files with 839 additions and 18 deletions.
522 changes: 522 additions & 0 deletions packages/transport-native/src/api/bluetoothApi.ts

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions packages/transport-native/src/api/char.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[
{
"value": null,
"isIndicatable": false,
"isNotifiable": false,
"isWritableWithoutResponse": true,
"isWritableWithResponse": true,
"serviceUUID": "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
"deviceID": "6D:CB:AD:D4:70:CA",
"isReadable": false,
"serviceID": 9,
"isNotifying": false,
"uuid": "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
"id": 10,
"_manager": {
"_eventEmitter": {
"_nativeModule": {
"StateChangeEvent": "StateChangeEvent",
"RestoreStateEvent": "RestoreStateEvent",
"ScanEvent": "ScanEvent",
"DisconnectionEvent": "DisconnectionEvent",
"ReadEvent": "ReadEvent"
}
},
"_uniqueId": 7,
"_activePromises": {},
"_activeSubscriptions": { "4": {} },
"_errorCodesToMessagesMapping": {
"0": "Unknown error occurred. This is probably a bug! Check reason property.",
"1": "BleManager was destroyed",
"2": "Operation was cancelled",
"3": "Operation timed out",
"4": "Operation was rejected",
"5": "Invalid UUIDs or IDs were passed: {internalMessage}",
"100": "BluetoothLE is unsupported on this device",
"101": "Device is not authorized to use BluetoothLE",
"102": "BluetoothLE is powered off",
"103": "BluetoothLE is in unknown state",
"104": "BluetoothLE is resetting",
"105": "Bluetooth state change failed",
"200": "Device {deviceID} connection failed",
"201": "Device {deviceID} was disconnected",
"202": "RSSI read failed for device {deviceID}",
"203": "Device {deviceID} is already connected",
"204": "Device {deviceID} not found",
"205": "Device {deviceID} is not connected",
"206": "Device {deviceID} could not change MTU size",
"300": "Services discovery failed for device {deviceID}",
"301": "Included services discovery failed for device {deviceID} and service: {serviceUUID}",
"302": "Service {serviceUUID} for device {deviceID} not found",
"303": "Services not discovered for device {deviceID}",
"400": "Characteristic discovery failed for device {deviceID} and service {serviceUUID}",
"401": "Characteristic {characteristicUUID} write failed for device {deviceID} and service {serviceUUID}",
"402": "Characteristic {characteristicUUID} read failed for device {deviceID} and service {serviceUUID}",
"403": "Characteristic {characteristicUUID} notify change failed for device {deviceID} and service {serviceUUID}",
"404": "Characteristic {characteristicUUID} not found",
"405": "Characteristics not discovered for device {deviceID} and service {serviceUUID}",
"406": "Cannot write to characteristic {characteristicUUID} with invalid data format: {internalMessage}",
"500": "Descriptor {descriptorUUID} discovery failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"501": "Descriptor {descriptorUUID} write failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"502": "Descriptor {descriptorUUID} read failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"503": "Descriptor {descriptorUUID} not found",
"504": "Descriptors not discovered for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"505": "Cannot write to descriptor {descriptorUUID} with invalid data format: {internalMessage}",
"506": "Cannot write to descriptor {descriptorUUID}. It's not allowed by iOS and therefore forbidden on Android as well.",
"600": "Cannot start scanning operation",
"601": "Location services are disabled"
},
"_scanEventSubscription": {}
}
},
{
"value": null,
"isIndicatable": false,
"isNotifiable": true,
"isWritableWithoutResponse": false,
"isWritableWithResponse": false,
"serviceUUID": "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
"deviceID": "6D:CB:AD:D4:70:CA",
"isReadable": false,
"serviceID": 9,
"isNotifying": false,
"uuid": "6e400003-b5a3-f393-e0a9-e50e24dcca9e",
"id": 11,
"_manager": {
"_eventEmitter": {
"_nativeModule": {
"StateChangeEvent": "StateChangeEvent",
"RestoreStateEvent": "RestoreStateEvent",
"ScanEvent": "ScanEvent",
"DisconnectionEvent": "DisconnectionEvent",
"ReadEvent": "ReadEvent"
}
},
"_uniqueId": 7,
"_activePromises": {},
"_activeSubscriptions": { "4": {} },
"_errorCodesToMessagesMapping": {
"0": "Unknown error occurred. This is probably a bug! Check reason property.",
"1": "BleManager was destroyed",
"2": "Operation was cancelled",
"3": "Operation timed out",
"4": "Operation was rejected",
"5": "Invalid UUIDs or IDs were passed: {internalMessage}",
"100": "BluetoothLE is unsupported on this device",
"101": "Device is not authorized to use BluetoothLE",
"102": "BluetoothLE is powered off",
"103": "BluetoothLE is in unknown state",
"104": "BluetoothLE is resetting",
"105": "Bluetooth state change failed",
"200": "Device {deviceID} connection failed",
"201": "Device {deviceID} was disconnected",
"202": "RSSI read failed for device {deviceID}",
"203": "Device {deviceID} is already connected",
"204": "Device {deviceID} not found",
"205": "Device {deviceID} is not connected",
"206": "Device {deviceID} could not change MTU size",
"300": "Services discovery failed for device {deviceID}",
"301": "Included services discovery failed for device {deviceID} and service: {serviceUUID}",
"302": "Service {serviceUUID} for device {deviceID} not found",
"303": "Services not discovered for device {deviceID}",
"400": "Characteristic discovery failed for device {deviceID} and service {serviceUUID}",
"401": "Characteristic {characteristicUUID} write failed for device {deviceID} and service {serviceUUID}",
"402": "Characteristic {characteristicUUID} read failed for device {deviceID} and service {serviceUUID}",
"403": "Characteristic {characteristicUUID} notify change failed for device {deviceID} and service {serviceUUID}",
"404": "Characteristic {characteristicUUID} not found",
"405": "Characteristics not discovered for device {deviceID} and service {serviceUUID}",
"406": "Cannot write to characteristic {characteristicUUID} with invalid data format: {internalMessage}",
"500": "Descriptor {descriptorUUID} discovery failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"501": "Descriptor {descriptorUUID} write failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"502": "Descriptor {descriptorUUID} read failed for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"503": "Descriptor {descriptorUUID} not found",
"504": "Descriptors not discovered for device {deviceID}, service {serviceUUID} and characteristic {characteristicUUID}",
"505": "Cannot write to descriptor {descriptorUUID} with invalid data format: {internalMessage}",
"506": "Cannot write to descriptor {descriptorUUID}. It's not allowed by iOS and therefore forbidden on Android as well.",
"600": "Cannot start scanning operation",
"601": "Location services are disabled"
},
"_scanEventSubscription": {}
}
}
]
2 changes: 2 additions & 0 deletions packages/transport-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { NativeUsbTransport } from './nativeUsb';
export { NativeBluetoothTransport } from './nativeBluetooth';
export { nativeBleManager } from './api/bluetoothApi';
36 changes: 36 additions & 0 deletions packages/transport-native/src/nativeBluetooth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
Transport as AbstractTransport,
AbstractApiTransport,
SessionsClient,
SessionsBackground,
} from '@trezor/transport';

import { BluetoothApi } from './api/bluetoothApi';

export class NativeBluetoothTransport extends AbstractApiTransport {
// TODO: Not sure how to solve this type correctly.
public name = 'NativeUsbTransport' as any;

constructor(params?: ConstructorParameters<typeof AbstractTransport>[0]) {
const { messages, logger } = params || {};
const sessionsBackground = new SessionsBackground();

const sessionsClient = new SessionsClient({
requestFn: args => sessionsBackground.handleMessage(args),
registerBackgroundCallbacks: () => {},
});

sessionsBackground.on('descriptors', descriptors => {
sessionsClient.emit('descriptors', descriptors);
});

super({
messages,
api: new BluetoothApi({
logger,
}),

sessionsClient,
});
}
}
1 change: 1 addition & 0 deletions suite-native/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export default ({ config }: ConfigContext): ExpoConfig => {
},
},
],
['react-native-ble-plx', {}],
'@trezor/react-native-usb/plugins/withUSBDevice.js',
// Define FLIPPER_VERSION
'./plugins/withGradleProperties.js',
Expand Down
1 change: 1 addition & 0 deletions suite-native/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"react": "18.2.0",
"react-intl": "^6.6.2",
"react-native": "0.73.2",
"react-native-ble-plx": "^3.1.2",
"react-native-flipper": "^0.212.0",
"react-native-gesture-handler": "2.15.0",
"react-native-keyboard-aware-scroll-view": "0.9.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ReactNode } from 'react';
import { ReactNode, useState } from 'react';
import { Device, BleError } from 'react-native-ble-plx';

import { Box } from '@suite-native/atoms';
import { Screen } from '@suite-native/navigation';
import { prepareNativeStyle, useNativeStyles, NativeStyleObject } from '@trezor/styles';
import { nativeBleManager } from '@trezor/transport-native';

import { ConnectDeviceScreenHeader } from './ConnectDeviceScreenHeader';

Expand All @@ -22,6 +24,42 @@ export const ConnectDeviceSreenView = ({
shouldDisplayCancelButton,
}: ConnectDeviceSreenViewProps) => {
const { applyStyle } = useNativeStyles();
const [devices, setDevices] = useState<Device[]>([]);
const [scanError, setScanError] = useState<BleError | null>();
const [isScanRunning, setIsScanRunning] = useState<boolean>(false);

const scanDevices = async () => {
setScanError(null);
setIsScanRunning(true);
setDevices([]);

nativeBle;

console.log('Starting device scan');
const bleManager = bleManagerInstance();
bleManager.startDeviceScan(devicesUUIDs, scanOptions, (error, scannedDevice) => {
if (error) {
console.log('Scan error');
console.error(error);
setScanError(error);
// TODO: is scan stopped automatically if error occurs?
setIsScanRunning(false);

return;
}
if (scannedDevice) {
console.log('Scanned device: ', scannedDevice.id, scannedDevice.localName);

setDevices(devices => {
if (devices.find(d => d.id === scannedDevice.id)) {
return devices;
}

return [...devices, scannedDevice];
});
}
});
};

return (
<Screen
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useEffect } from 'react';
import { Dimensions } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useState } from 'react';
import { BleError, Device } from 'react-native-ble-plx';

import { useIsFocused } from '@react-navigation/native';

import { Text, VStack } from '@suite-native/atoms';
import { authorizeDevice, selectDevice, selectIsDeviceAuthorized } from '@suite-common/wallet-core';
import { Box, Button, Loader, Text, VStack } from '@suite-native/atoms';
import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { useTranslate } from '@suite-native/intl';
import { ConnectDeviceAnimation } from '@suite-native/device';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { Screen } from '@suite-native/navigation';
import { selectDevice, selectIsDeviceAuthorized, authorizeDevice } from '@suite-common/wallet-core';
import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { nativeBleManager } from '@trezor/transport-native';

import { ConnectDeviceScreenHeader } from '../components/ConnectDeviceScreenHeader';

Expand Down Expand Up @@ -45,6 +47,31 @@ export const ConnectAndUnlockDeviceScreen = () => {
}
}, [isDeviceAuthorized, device, dispatch, isFocused]);

const [devices, setDevices] = useState<Device[]>([]);
const [scanError, setScanError] = useState<BleError | null>();
const [isScanRunning, setIsScanRunning] = useState<boolean>(false);

const scanDevices = async () => {
setScanError(null);
setIsScanRunning(true);
setDevices([]);

nativeBleManager.scanDevices(scannedDevices => {
setDevices(scannedDevices);
});
};

const stopScanning = async () => {
nativeBleManager.stopDeviceScan();
setIsScanRunning(false);
};

useEffect(() => {
return () => {
nativeBleManager.stopDeviceScan();
};
}, []);

return (
<Screen
screenHeader={<ConnectDeviceScreenHeader />}
Expand All @@ -57,7 +84,44 @@ export const ConnectAndUnlockDeviceScreen = () => {
<Text variant="titleMedium" textAlign="center">
{translate('moduleConnectDevice.connectAndUnlockScreen.title')}
</Text>
<ConnectDeviceAnimation style={applyStyle(animationStyle)} />
{!isScanRunning ? (
<Button onPress={scanDevices}>Scan devices</Button>
) : (
<Button onPress={stopScanning}>Stop devices scan</Button>
)}
{isScanRunning && (
<Box
marginTop="large"
flexDirection="row"
alignItems="center"
justifyContent="center"
>
<Text>Scanning for devices...</Text>
<Loader />
</Box>
)}
{scanError && (
<Box
marginTop="large"
flexDirection="row"
alignItems="center"
justifyContent="center"
>
<Text>Scan error: {scanError.message}</Text>
</Box>
)}
<Box>
{devices.map(device => (
<Box key={device.id}>
<Text>{device.name}</Text>
<Button
onPress={() => nativeBleManager.openDevice({ deviceOrId: device })}
>
Connect
</Button>
</Box>
))}
</Box>
</VStack>
</Screen>
);
Expand Down
20 changes: 11 additions & 9 deletions suite-native/state/src/extraDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@ import { selectDevices } from '@suite-common/wallet-core';
import { selectFiatCurrencyCode, setFiatCurrency } from '@suite-native/module-settings';
import { PROTO } from '@trezor/connect';
import { mergeDeepObject } from '@trezor/utils';
import { NativeUsbTransport } from '@trezor/transport-native';
import { NativeBluetoothTransport, NativeUsbTransport } from '@trezor/transport-native';

const deviceType = Device.isDevice ? 'device' : 'emulator';

const transportsPerDeviceType = {
device: Platform.select({
ios: ['BridgeTransport', 'UdpTransport'],
android: [new NativeUsbTransport()],
}),
emulator: ['BridgeTransport', 'UdpTransport'],
} as const;
// const transportsPerDeviceType = {
// device: Platform.select({
// ios: ['BridgeTransport', 'UdpTransport'],
// android: [new NativeUsbTransport()],
// }),
// emulator: ['BridgeTransport', 'UdpTransport'],
// } as const;

const transports = transportsPerDeviceType[deviceType];
// const transports = transportsPerDeviceType[deviceType];

const transports = [new NativeBluetoothTransport()];

export const extraDependencies: ExtraDependencies = mergeDeepObject(extraDependenciesMock, {
selectors: {
Expand Down
Loading

0 comments on commit b189c0e

Please sign in to comment.