react-native-web-serial-api 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/android/build.gradle +62 -0
- package/android/gradle.properties +6 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialModule.java +704 -0
- package/android/src/main/java/dev/webserialapi/NativeUsbSerialPackage.java +46 -0
- package/android/src/main/java/dev/webserialapi/PortPickerActivity.java +235 -0
- package/android/src/main/java/dev/webserialapi/UsbDetachReceiver.java +37 -0
- package/android/src/main/res/xml/device_filter.xml +13 -0
- package/babel.config.js +3 -0
- package/biome.json +35 -0
- package/example/.watchmanconfig +1 -0
- package/example/App.tsx +71 -0
- package/example/__tests__/App.test.tsx +16 -0
- package/example/android/app/build.gradle +120 -0
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +10 -0
- package/example/android/app/src/debug/AndroidManifest.xml +9 -0
- package/example/android/app/src/main/AndroidManifest.xml +38 -0
- package/example/android/app/src/main/java/dev/uzlopak/MainActivity.kt +22 -0
- package/example/android/app/src/main/java/dev/uzlopak/MainApplication.kt +41 -0
- package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/values/strings.xml +3 -0
- package/example/android/app/src/main/res/values/styles.xml +9 -0
- package/example/android/build.gradle +22 -0
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/example/android/gradle.properties +47 -0
- package/example/android/gradlew +252 -0
- package/example/android/gradlew.bat +94 -0
- package/example/android/settings.gradle +6 -0
- package/example/app.json +4 -0
- package/example/babel.config.js +21 -0
- package/example/biome.json +47 -0
- package/example/deploy.sh +11 -0
- package/example/index.html +26 -0
- package/example/index.js +9 -0
- package/example/index.web.js +8 -0
- package/example/jest.config.js +12 -0
- package/example/metro.config.js +58 -0
- package/example/package-lock.json +14510 -0
- package/example/package.json +48 -0
- package/example/react-native.config.js +17 -0
- package/example/src/components/AppBar.tsx +73 -0
- package/example/src/components/Menu.tsx +90 -0
- package/example/src/components/SingleChoiceDialog.tsx +120 -0
- package/example/src/screens/ConnectScreen.tsx +195 -0
- package/example/src/screens/DevicesScreen.tsx +141 -0
- package/example/src/screens/TerminalScreen.tsx +564 -0
- package/example/src/settings.ts +43 -0
- package/example/src/theme.ts +19 -0
- package/example/src/util/TextUtil.ts +129 -0
- package/example/tsconfig.json +10 -0
- package/example/vite.config.mjs +55 -0
- package/lib/commonjs/NativeUsbSerial.js +11 -0
- package/lib/commonjs/NativeUsbSerial.js.map +1 -0
- package/lib/commonjs/NativeUsbSerial.web.js +12 -0
- package/lib/commonjs/NativeUsbSerial.web.js.map +1 -0
- package/lib/commonjs/UsbSerial.js +149 -0
- package/lib/commonjs/UsbSerial.js.map +1 -0
- package/lib/commonjs/WebSerial.js +852 -0
- package/lib/commonjs/WebSerial.js.map +1 -0
- package/lib/commonjs/index.js +44 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/serial.android.js +13 -0
- package/lib/commonjs/serial.android.js.map +1 -0
- package/lib/commonjs/serial.js +13 -0
- package/lib/commonjs/serial.js.map +1 -0
- package/lib/commonjs/serial.web.js +11 -0
- package/lib/commonjs/serial.web.js.map +1 -0
- package/lib/typescript/src/NativeUsbSerial.d.ts +51 -0
- package/lib/typescript/src/NativeUsbSerial.d.ts.map +1 -0
- package/lib/typescript/src/NativeUsbSerial.web.d.ts +3 -0
- package/lib/typescript/src/NativeUsbSerial.web.d.ts.map +1 -0
- package/lib/typescript/src/UsbSerial.d.ts +97 -0
- package/lib/typescript/src/UsbSerial.d.ts.map +1 -0
- package/lib/typescript/src/WebSerial.d.ts +236 -0
- package/lib/typescript/src/WebSerial.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/serial.android.d.ts +2 -0
- package/lib/typescript/src/serial.android.d.ts.map +1 -0
- package/lib/typescript/src/serial.d.ts +2 -0
- package/lib/typescript/src/serial.d.ts.map +1 -0
- package/lib/typescript/src/serial.web.d.ts +4 -0
- package/lib/typescript/src/serial.web.d.ts.map +1 -0
- package/package.json +78 -0
- package/react-native.config.js +9 -0
- package/scripts/deploy-release.sh +127 -0
- package/src/NativeUsbSerial.ts +124 -0
- package/src/NativeUsbSerial.web.ts +5 -0
- package/src/UsbSerial.ts +305 -0
- package/src/WebSerial.ts +1084 -0
- package/src/index.ts +23 -0
- package/src/lib/dom-exception.ts +161 -0
- package/src/lib/event-target.ts +170 -0
- package/src/lib/promise.ts +19 -0
- package/src/serial.android.ts +1 -0
- package/src/serial.ts +1 -0
- package/src/serial.web.ts +6 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +20 -0
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-web-serial-api",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "W3C Web Serial API (navigator.serial) for React Native on Android, backed by a USB-serial TurboModule (built on mik3y/usb-serial-for-android).",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"react-native": "lib/commonjs/index.js",
|
|
7
|
+
"source": "src/index.ts",
|
|
8
|
+
"types": "lib/typescript/src/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepare": "bob build",
|
|
11
|
+
"clean": "node -e \"require('fs').rm(\\\"./lib\\\", { recursive: true }, () => {console.log(\\\"'lib' folder deleted\\\")})\"",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"lint": "biome check src",
|
|
14
|
+
"format": "biome check --write src"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"android",
|
|
19
|
+
"web-serial",
|
|
20
|
+
"web serial api",
|
|
21
|
+
"serial",
|
|
22
|
+
"usb",
|
|
23
|
+
"usb-serial",
|
|
24
|
+
"turbomodule",
|
|
25
|
+
"navigator.serial",
|
|
26
|
+
"ftdi",
|
|
27
|
+
"cp210x",
|
|
28
|
+
"ch340",
|
|
29
|
+
"pl2303",
|
|
30
|
+
"cdc-acm"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/uzlopak/react-native-web-serial-api.git"
|
|
35
|
+
},
|
|
36
|
+
"author": "Aras Abbasi <arasabbasi@gmail.com>",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"codegenConfig": {
|
|
39
|
+
"name": "WebSerialApiSpec",
|
|
40
|
+
"type": "modules",
|
|
41
|
+
"jsSrcsDir": "src",
|
|
42
|
+
"android": {
|
|
43
|
+
"javaPackageName": "dev.webserialapi"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"web-streams-polyfill": "^4.3.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "*",
|
|
51
|
+
"react-native": "*"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@biomejs/biome": "2.4.16",
|
|
55
|
+
"@react-native/babel-preset": "0.85.3",
|
|
56
|
+
"@types/react": "^19.2.6",
|
|
57
|
+
"react": "19.2.3",
|
|
58
|
+
"react-native": "0.85.3",
|
|
59
|
+
"react-native-builder-bob": "^0.41.0",
|
|
60
|
+
"typescript": "6.0.3"
|
|
61
|
+
},
|
|
62
|
+
"react-native-builder-bob": {
|
|
63
|
+
"source": "src",
|
|
64
|
+
"output": "lib",
|
|
65
|
+
"targets": [
|
|
66
|
+
"commonjs",
|
|
67
|
+
[
|
|
68
|
+
"typescript",
|
|
69
|
+
{
|
|
70
|
+
"project": "tsconfig.build.json"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=22"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Build and install a standalone RELEASE build of the example app.
|
|
4
|
+
#
|
|
5
|
+
# A release build embeds the JS bundle (assets/index.android.bundle) into the
|
|
6
|
+
# APK, so the app runs WITHOUT Metro / a dev server. The JS bundler runs once
|
|
7
|
+
# at build time; after install the app is fully self-contained.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/deploy-release.sh # build, install on the connected device, launch
|
|
11
|
+
# ./scripts/deploy-release.sh --build # build the APK only (no install)
|
|
12
|
+
# ./scripts/deploy-release.sh --no-launch
|
|
13
|
+
# DEVICE=192.168.1.50:5555 ./scripts/deploy-release.sh # target a specific adb device
|
|
14
|
+
#
|
|
15
|
+
# Environment overrides (auto-detected if unset):
|
|
16
|
+
# JAVA_HOME - JDK 17 (RN 0.85 / Gradle 8.13 require JDK 17-21)
|
|
17
|
+
# ANDROID_HOME - Android SDK location
|
|
18
|
+
# NODE_BIN - directory containing a Node >= 20 binary (RN 0.85 metro needs it)
|
|
19
|
+
#
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
APP_ID="dev.react_native_web_serial_api.example"
|
|
23
|
+
MAIN_ACTIVITY="${APP_ID}/.MainActivity"
|
|
24
|
+
|
|
25
|
+
# Resolve paths relative to this script so it works from any CWD.
|
|
26
|
+
# This script lives in <repo>/scripts; the buildable app is in <repo>/example
|
|
27
|
+
# (the repo-root android/ is the library module and has no gradlew).
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
30
|
+
EXAMPLE_DIR="$REPO_ROOT/example"
|
|
31
|
+
ANDROID_DIR="$EXAMPLE_DIR/android"
|
|
32
|
+
APK="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
|
33
|
+
|
|
34
|
+
# --- arg parsing ---------------------------------------------------------
|
|
35
|
+
DO_INSTALL=1
|
|
36
|
+
DO_LAUNCH=1
|
|
37
|
+
for arg in "$@"; do
|
|
38
|
+
case "$arg" in
|
|
39
|
+
--build) DO_INSTALL=0; DO_LAUNCH=0 ;;
|
|
40
|
+
--no-launch) DO_LAUNCH=0 ;;
|
|
41
|
+
-h|--help) grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
42
|
+
*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
# --- toolchain auto-detection -------------------------------------------
|
|
47
|
+
# JDK 17 (only override if the current java isn't already 17-21).
|
|
48
|
+
if [ -z "${JAVA_HOME:-}" ]; then
|
|
49
|
+
for d in /usr/lib/jvm/java-17-openjdk-amd64 /usr/lib/jvm/java-21-openjdk-amd64 \
|
|
50
|
+
/usr/lib/jvm/temurin-17-jdk-amd64; do
|
|
51
|
+
[ -d "$d" ] && export JAVA_HOME="$d" && break
|
|
52
|
+
done
|
|
53
|
+
fi
|
|
54
|
+
[ -n "${JAVA_HOME:-}" ] && echo "JAVA_HOME=$JAVA_HOME"
|
|
55
|
+
|
|
56
|
+
# Android SDK.
|
|
57
|
+
if [ -z "${ANDROID_HOME:-}" ]; then
|
|
58
|
+
for d in "$HOME/Android/Sdk" "$HOME/Library/Android/sdk" "${ANDROID_SDK_ROOT:-}"; do
|
|
59
|
+
[ -n "$d" ] && [ -d "$d" ] && export ANDROID_HOME="$d" && break
|
|
60
|
+
done
|
|
61
|
+
fi
|
|
62
|
+
if [ -z "${ANDROID_HOME:-}" ]; then
|
|
63
|
+
echo "ERROR: Android SDK not found. Set ANDROID_HOME." >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
echo "ANDROID_HOME=$ANDROID_HOME"
|
|
67
|
+
export PATH="$ANDROID_HOME/platform-tools:$PATH"
|
|
68
|
+
|
|
69
|
+
# Node >= 20 (RN 0.85 metro-config uses Array.prototype.toReversed()).
|
|
70
|
+
need_node() { node -e 'process.exit(typeof [].toReversed==="function"?0:1)' 2>/dev/null; }
|
|
71
|
+
if [ -n "${NODE_BIN:-}" ]; then
|
|
72
|
+
export PATH="$NODE_BIN:$PATH"
|
|
73
|
+
fi
|
|
74
|
+
if ! command -v node >/dev/null 2>&1 || ! need_node; then
|
|
75
|
+
# Try the newest nvm-managed Node >= 20.
|
|
76
|
+
if [ -d "$HOME/.nvm/versions/node" ]; then
|
|
77
|
+
for v in $(ls -1 "$HOME/.nvm/versions/node" | sort -Vr); do
|
|
78
|
+
if "$HOME/.nvm/versions/node/$v/bin/node" -e 'process.exit(typeof [].toReversed==="function"?0:1)' 2>/dev/null; then
|
|
79
|
+
export PATH="$HOME/.nvm/versions/node/$v/bin:$PATH"
|
|
80
|
+
break
|
|
81
|
+
fi
|
|
82
|
+
done
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
if ! need_node; then
|
|
86
|
+
echo "ERROR: need Node >= 20 (found $(node -v 2>/dev/null || echo none)). Set NODE_BIN." >&2
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
echo "node=$(node -v)"
|
|
90
|
+
|
|
91
|
+
# --- pick adb device -----------------------------------------------------
|
|
92
|
+
adb_target=()
|
|
93
|
+
if [ -n "${DEVICE:-}" ]; then
|
|
94
|
+
adb connect "$DEVICE" >/dev/null 2>&1 || true
|
|
95
|
+
adb_target=(-s "$DEVICE")
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# --- build ---------------------------------------------------------------
|
|
99
|
+
echo "==> Building release APK (embeds JS bundle)…"
|
|
100
|
+
( cd "$ANDROID_DIR" && ./gradlew :app:assembleRelease )
|
|
101
|
+
|
|
102
|
+
if [ ! -f "$APK" ]; then
|
|
103
|
+
echo "ERROR: APK not found at $APK" >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
echo "==> Built: $APK ($(du -h "$APK" | cut -f1))"
|
|
107
|
+
|
|
108
|
+
[ "$DO_INSTALL" -eq 1 ] || { echo "Build-only mode; done."; exit 0; }
|
|
109
|
+
|
|
110
|
+
# --- install -------------------------------------------------------------
|
|
111
|
+
echo "==> Installing on device…"
|
|
112
|
+
# Capture output instead of piping into `grep -q`: under `set -o pipefail`,
|
|
113
|
+
# grep closing the pipe early can make a successful install report failure.
|
|
114
|
+
install_out="$(adb "${adb_target[@]}" install -r "$APK" 2>&1)" || true
|
|
115
|
+
echo "$install_out"
|
|
116
|
+
if ! grep -q "Success" <<<"$install_out"; then
|
|
117
|
+
echo "==> Reinstall failed (likely signature mismatch); uninstalling and retrying…"
|
|
118
|
+
adb "${adb_target[@]}" uninstall "$APP_ID" >/dev/null 2>&1 || true
|
|
119
|
+
adb "${adb_target[@]}" install "$APK"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
[ "$DO_LAUNCH" -eq 1 ] || { echo "Installed (not launched)."; exit 0; }
|
|
123
|
+
|
|
124
|
+
# --- launch --------------------------------------------------------------
|
|
125
|
+
echo "==> Launching $MAIN_ACTIVITY…"
|
|
126
|
+
adb "${adb_target[@]}" shell am start -n "$MAIN_ACTIVITY" >/dev/null
|
|
127
|
+
echo "==> Done. The app runs standalone (no Metro needed)."
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type {TurboModule} from 'react-native';
|
|
2
|
+
import {TurboModuleRegistry} from 'react-native';
|
|
3
|
+
|
|
4
|
+
// UsbSerialPort.ControlLine enum
|
|
5
|
+
export type ControlLine = 'RTS' | 'CTS' | 'DTR' | 'DSR' | 'CD' | 'RI';
|
|
6
|
+
|
|
7
|
+
// UsbSerialPort.FlowControl enum
|
|
8
|
+
export type FlowControl =
|
|
9
|
+
| 'NONE'
|
|
10
|
+
| 'RTS_CTS'
|
|
11
|
+
| 'DTR_DSR'
|
|
12
|
+
| 'XON_XOFF'
|
|
13
|
+
| 'XON_XOFF_INLINE';
|
|
14
|
+
|
|
15
|
+
export type PortId = {
|
|
16
|
+
deviceId: number;
|
|
17
|
+
portNumber: number;
|
|
18
|
+
usbVendorId: number;
|
|
19
|
+
usbProductId: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type PortPickerLabels = {
|
|
23
|
+
titleSelectPort?: string;
|
|
24
|
+
titleNoPortsAvailable?: string;
|
|
25
|
+
messageNoPortsAvailable?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type PortFilter = {
|
|
29
|
+
usbVendorId?: number;
|
|
30
|
+
usbProductId?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface Spec extends TurboModule {
|
|
34
|
+
// UsbSerialProber
|
|
35
|
+
findAllDrivers(): Promise<ReadonlyArray<PortId>>;
|
|
36
|
+
|
|
37
|
+
// Port picker dialog — shows available ports filtered by filters, requests permission for selected port
|
|
38
|
+
showPortPicker(
|
|
39
|
+
filters: ReadonlyArray<PortFilter>,
|
|
40
|
+
labels?: PortPickerLabels,
|
|
41
|
+
): Promise<PortId>;
|
|
42
|
+
|
|
43
|
+
// UsbSerialPort lifecycle
|
|
44
|
+
open(
|
|
45
|
+
deviceId: number,
|
|
46
|
+
portNumber: number,
|
|
47
|
+
baudRate: number,
|
|
48
|
+
dataBits: number,
|
|
49
|
+
stopBits: number,
|
|
50
|
+
parity: number,
|
|
51
|
+
): Promise<void>;
|
|
52
|
+
close(deviceId: number, portNumber: number): Promise<void>;
|
|
53
|
+
isOpen(deviceId: number, portNumber: number): boolean;
|
|
54
|
+
|
|
55
|
+
// Read/Write
|
|
56
|
+
write(
|
|
57
|
+
deviceId: number,
|
|
58
|
+
portNumber: number,
|
|
59
|
+
data: ReadonlyArray<number>,
|
|
60
|
+
timeout: number,
|
|
61
|
+
): Promise<void>;
|
|
62
|
+
startReading(deviceId: number, portNumber: number): Promise<void>;
|
|
63
|
+
stopReading(deviceId: number, portNumber: number): Promise<void>;
|
|
64
|
+
|
|
65
|
+
// Parameters
|
|
66
|
+
setParameters(
|
|
67
|
+
deviceId: number,
|
|
68
|
+
portNumber: number,
|
|
69
|
+
baudRate: number,
|
|
70
|
+
dataBits: number,
|
|
71
|
+
stopBits: number,
|
|
72
|
+
parity: number,
|
|
73
|
+
): Promise<void>;
|
|
74
|
+
|
|
75
|
+
// Control lines
|
|
76
|
+
setDTR(deviceId: number, portNumber: number, value: boolean): Promise<void>;
|
|
77
|
+
setRTS(deviceId: number, portNumber: number, value: boolean): Promise<void>;
|
|
78
|
+
getDTR(deviceId: number, portNumber: number): Promise<boolean>;
|
|
79
|
+
getRTS(deviceId: number, portNumber: number): Promise<boolean>;
|
|
80
|
+
getCD(deviceId: number, portNumber: number): Promise<boolean>;
|
|
81
|
+
getCTS(deviceId: number, portNumber: number): Promise<boolean>;
|
|
82
|
+
getDSR(deviceId: number, portNumber: number): Promise<boolean>;
|
|
83
|
+
getRI(deviceId: number, portNumber: number): Promise<boolean>;
|
|
84
|
+
|
|
85
|
+
getControlLines(
|
|
86
|
+
deviceId: number,
|
|
87
|
+
portNumber: number,
|
|
88
|
+
): Promise<ReadonlyArray<string>>;
|
|
89
|
+
getSupportedControlLines(
|
|
90
|
+
deviceId: number,
|
|
91
|
+
portNumber: number,
|
|
92
|
+
): Promise<ReadonlyArray<string>>;
|
|
93
|
+
|
|
94
|
+
// Flow control
|
|
95
|
+
setFlowControl(
|
|
96
|
+
deviceId: number,
|
|
97
|
+
portNumber: number,
|
|
98
|
+
flowControl: string,
|
|
99
|
+
): Promise<void>;
|
|
100
|
+
getFlowControl(deviceId: number, portNumber: number): Promise<string>;
|
|
101
|
+
getSupportedFlowControl(
|
|
102
|
+
deviceId: number,
|
|
103
|
+
portNumber: number,
|
|
104
|
+
): Promise<ReadonlyArray<string>>;
|
|
105
|
+
|
|
106
|
+
// Misc
|
|
107
|
+
setBreak(deviceId: number, portNumber: number, value: boolean): Promise<void>;
|
|
108
|
+
purgeHwBuffers(
|
|
109
|
+
deviceId: number,
|
|
110
|
+
portNumber: number,
|
|
111
|
+
purgeWriteBuffers: boolean,
|
|
112
|
+
purgeReadBuffers: boolean,
|
|
113
|
+
): Promise<void>;
|
|
114
|
+
getSerial(deviceId: number, portNumber: number): Promise<string>;
|
|
115
|
+
|
|
116
|
+
// USB permission
|
|
117
|
+
requestPermission(deviceId: number): Promise<boolean>;
|
|
118
|
+
|
|
119
|
+
// NativeEventEmitter
|
|
120
|
+
addListener(eventName: string): void;
|
|
121
|
+
removeListeners(count: number): void;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default TurboModuleRegistry.get<Spec>('NativeUsbSerial');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// On web there is no native USB-serial TurboModule. serial.web.ts delegates to
|
|
2
|
+
// the browser's native navigator.serial, and UsbSerial.getUsbSerial() treats a
|
|
3
|
+
// null module as "not available" — so this stub simply avoids touching
|
|
4
|
+
// TurboModuleRegistry (which does not exist under react-native-web).
|
|
5
|
+
export default null;
|
package/src/UsbSerial.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import {NativeEventEmitter, NativeModules} from 'react-native';
|
|
2
|
+
import NativeUsbSerial from './NativeUsbSerial';
|
|
3
|
+
|
|
4
|
+
export type ControlLine = 'RTS' | 'CTS' | 'DTR' | 'DSR' | 'CD' | 'RI';
|
|
5
|
+
export type FlowControl =
|
|
6
|
+
| 'NONE'
|
|
7
|
+
| 'RTS_CTS'
|
|
8
|
+
| 'DTR_DSR'
|
|
9
|
+
| 'XON_XOFF'
|
|
10
|
+
| 'XON_XOFF_INLINE';
|
|
11
|
+
|
|
12
|
+
export type PortFilter = {
|
|
13
|
+
usbVendorId?: number;
|
|
14
|
+
usbProductId?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PortPickerLabels = {
|
|
18
|
+
titleSelectPort?: string;
|
|
19
|
+
titleNoPortsAvailable?: string;
|
|
20
|
+
messageNoPortsAvailable?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type PortId = {
|
|
24
|
+
deviceId: number;
|
|
25
|
+
portNumber: number;
|
|
26
|
+
usbVendorId: number;
|
|
27
|
+
usbProductId: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DataEvent = {
|
|
31
|
+
deviceId: number;
|
|
32
|
+
portNumber: number;
|
|
33
|
+
data: number[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ErrorEvent = {
|
|
37
|
+
deviceId: number;
|
|
38
|
+
portNumber: number;
|
|
39
|
+
error: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ConnectEvent = {
|
|
43
|
+
deviceId: number;
|
|
44
|
+
usbVendorId: number;
|
|
45
|
+
usbProductId: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const Parity = {
|
|
49
|
+
NONE: 0,
|
|
50
|
+
ODD: 1,
|
|
51
|
+
EVEN: 2,
|
|
52
|
+
MARK: 3,
|
|
53
|
+
SPACE: 4,
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
export const DataBits = {
|
|
57
|
+
FIVE: 5,
|
|
58
|
+
SIX: 6,
|
|
59
|
+
SEVEN: 7,
|
|
60
|
+
EIGHT: 8,
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
export const StopBits = {
|
|
64
|
+
ONE: 1,
|
|
65
|
+
ONE_FIVE: 3,
|
|
66
|
+
TWO: 2,
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
export type OpenOptions = {
|
|
70
|
+
baudRate: number;
|
|
71
|
+
dataBits?: number;
|
|
72
|
+
stopBits?: number;
|
|
73
|
+
parity?: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const DEFAULT_OPEN_OPTIONS: Required<Omit<OpenOptions, 'baudRate'>> = {
|
|
77
|
+
dataBits: DataBits.EIGHT,
|
|
78
|
+
stopBits: StopBits.ONE,
|
|
79
|
+
parity: Parity.NONE,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type Subscription = {remove: () => void};
|
|
83
|
+
|
|
84
|
+
export class UsbSerialModule {
|
|
85
|
+
private readonly native: NonNullable<typeof NativeUsbSerial>;
|
|
86
|
+
private readonly emitter: NativeEventEmitter;
|
|
87
|
+
|
|
88
|
+
constructor() {
|
|
89
|
+
if (!NativeUsbSerial) {
|
|
90
|
+
throw new Error('NativeUsbSerial is not available');
|
|
91
|
+
}
|
|
92
|
+
this.native = NativeUsbSerial;
|
|
93
|
+
this.emitter = new NativeEventEmitter(NativeModules.NativeUsbSerial);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
findAllDrivers(): Promise<ReadonlyArray<PortId>> {
|
|
97
|
+
return this.native.findAllDrivers() as Promise<ReadonlyArray<PortId>>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
showPortPicker(
|
|
101
|
+
filter: Readonly<PortFilter[]>,
|
|
102
|
+
labels?: PortPickerLabels,
|
|
103
|
+
): Promise<PortId> {
|
|
104
|
+
return this.native.showPortPicker(filter, labels) as Promise<PortId>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
requestPermission(deviceId: number): Promise<boolean> {
|
|
108
|
+
return this.native.requestPermission(deviceId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
open(
|
|
112
|
+
deviceId: number,
|
|
113
|
+
portNumber: number,
|
|
114
|
+
options: OpenOptions,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const opts = {...DEFAULT_OPEN_OPTIONS, ...options};
|
|
117
|
+
return this.native.open(
|
|
118
|
+
deviceId,
|
|
119
|
+
portNumber,
|
|
120
|
+
opts.baudRate,
|
|
121
|
+
opts.dataBits,
|
|
122
|
+
opts.stopBits,
|
|
123
|
+
opts.parity,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
close(deviceId: number, portNumber: number): Promise<void> {
|
|
128
|
+
return this.native.close(deviceId, portNumber);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
isOpen(deviceId: number, portNumber: number): boolean {
|
|
132
|
+
return this.native.isOpen(deviceId, portNumber);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
write(
|
|
136
|
+
deviceId: number,
|
|
137
|
+
portNumber: number,
|
|
138
|
+
data: number[],
|
|
139
|
+
timeout: number = 2000,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
return this.native.write(deviceId, portNumber, data, timeout);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
startReading(deviceId: number, portNumber: number): Promise<void> {
|
|
145
|
+
return this.native.startReading(deviceId, portNumber);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
stopReading(deviceId: number, portNumber: number): Promise<void> {
|
|
149
|
+
return this.native.stopReading(deviceId, portNumber);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setParameters(
|
|
153
|
+
deviceId: number,
|
|
154
|
+
portNumber: number,
|
|
155
|
+
options: OpenOptions,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const opts = {...DEFAULT_OPEN_OPTIONS, ...options};
|
|
158
|
+
return this.native.setParameters(
|
|
159
|
+
deviceId,
|
|
160
|
+
portNumber,
|
|
161
|
+
opts.baudRate,
|
|
162
|
+
opts.dataBits,
|
|
163
|
+
opts.stopBits,
|
|
164
|
+
opts.parity,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setDTR(deviceId: number, portNumber: number, value: boolean): Promise<void> {
|
|
169
|
+
return this.native.setDTR(deviceId, portNumber, value);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setRTS(deviceId: number, portNumber: number, value: boolean): Promise<void> {
|
|
173
|
+
return this.native.setRTS(deviceId, portNumber, value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
getDTR(deviceId: number, portNumber: number): Promise<boolean> {
|
|
177
|
+
return this.native.getDTR(deviceId, portNumber);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
getRTS(deviceId: number, portNumber: number): Promise<boolean> {
|
|
181
|
+
return this.native.getRTS(deviceId, portNumber);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getCD(deviceId: number, portNumber: number): Promise<boolean> {
|
|
185
|
+
return this.native.getCD(deviceId, portNumber);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getCTS(deviceId: number, portNumber: number): Promise<boolean> {
|
|
189
|
+
return this.native.getCTS(deviceId, portNumber);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getDSR(deviceId: number, portNumber: number): Promise<boolean> {
|
|
193
|
+
return this.native.getDSR(deviceId, portNumber);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getRI(deviceId: number, portNumber: number): Promise<boolean> {
|
|
197
|
+
return this.native.getRI(deviceId, portNumber);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
getControlLines(
|
|
201
|
+
deviceId: number,
|
|
202
|
+
portNumber: number,
|
|
203
|
+
): Promise<ControlLine[]> {
|
|
204
|
+
return this.native.getControlLines(deviceId, portNumber) as Promise<
|
|
205
|
+
ControlLine[]
|
|
206
|
+
>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getSupportedControlLines(
|
|
210
|
+
deviceId: number,
|
|
211
|
+
portNumber: number,
|
|
212
|
+
): Promise<ControlLine[]> {
|
|
213
|
+
return this.native.getSupportedControlLines(
|
|
214
|
+
deviceId,
|
|
215
|
+
portNumber,
|
|
216
|
+
) as Promise<ControlLine[]>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
setFlowControl(
|
|
220
|
+
deviceId: number,
|
|
221
|
+
portNumber: number,
|
|
222
|
+
flowControl: FlowControl,
|
|
223
|
+
): Promise<void> {
|
|
224
|
+
return this.native.setFlowControl(deviceId, portNumber, flowControl);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getFlowControl(deviceId: number, portNumber: number): Promise<FlowControl> {
|
|
228
|
+
return this.native.getFlowControl(
|
|
229
|
+
deviceId,
|
|
230
|
+
portNumber,
|
|
231
|
+
) as Promise<FlowControl>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getSupportedFlowControl(
|
|
235
|
+
deviceId: number,
|
|
236
|
+
portNumber: number,
|
|
237
|
+
): Promise<FlowControl[]> {
|
|
238
|
+
return this.native.getSupportedFlowControl(deviceId, portNumber) as Promise<
|
|
239
|
+
FlowControl[]
|
|
240
|
+
>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
setBreak(
|
|
244
|
+
deviceId: number,
|
|
245
|
+
portNumber: number,
|
|
246
|
+
value: boolean,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
return this.native.setBreak(deviceId, portNumber, value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
purgeHwBuffers(
|
|
252
|
+
deviceId: number,
|
|
253
|
+
portNumber: number,
|
|
254
|
+
purgeWriteBuffers: boolean,
|
|
255
|
+
purgeReadBuffers: boolean,
|
|
256
|
+
): Promise<void> {
|
|
257
|
+
return this.native.purgeHwBuffers(
|
|
258
|
+
deviceId,
|
|
259
|
+
portNumber,
|
|
260
|
+
purgeWriteBuffers,
|
|
261
|
+
purgeReadBuffers,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
getSerial(deviceId: number, portNumber: number): Promise<string> {
|
|
266
|
+
return this.native.getSerial(deviceId, portNumber);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
onData(listener: (event: DataEvent) => void): Subscription {
|
|
270
|
+
return this.emitter.addListener(
|
|
271
|
+
'data',
|
|
272
|
+
listener as (...args: readonly Record<string, unknown>[]) => unknown,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
onError(listener: (event: ErrorEvent) => void): Subscription {
|
|
277
|
+
return this.emitter.addListener(
|
|
278
|
+
'error',
|
|
279
|
+
listener as (...args: readonly Record<string, unknown>[]) => unknown,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
onConnect(listener: (event: ConnectEvent) => void): Subscription {
|
|
284
|
+
return this.emitter.addListener(
|
|
285
|
+
'connect',
|
|
286
|
+
listener as (...args: readonly Record<string, unknown>[]) => unknown,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
onDisconnect(listener: (event: ConnectEvent) => void): Subscription {
|
|
291
|
+
return this.emitter.addListener(
|
|
292
|
+
'disconnect',
|
|
293
|
+
listener as (...args: readonly Record<string, unknown>[]) => unknown,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let instance: UsbSerialModule | null = null;
|
|
299
|
+
|
|
300
|
+
export function getUsbSerial(): UsbSerialModule {
|
|
301
|
+
if (!instance) {
|
|
302
|
+
instance = new UsbSerialModule();
|
|
303
|
+
}
|
|
304
|
+
return instance;
|
|
305
|
+
}
|