在 Android 系統上要使用 usb serial port 可以透過 usb-serial-for-android , 它是用 JAVA 語言寫的, 上官網打包先將它下載回來: https://github.com/mik3y/usb-serial-for-android, 將它解壓縮
unzip usb-serial-for-android-master.zip
1. 打開終端機用 flutter 命令產生一個新專案(例如 usbadc):
flutter create usbadc
2. 將上述 usb-serial-for-android 解開的檔案, 只要把目錄 usbSerialForAndroid/src/main/java/com 整個驅動程式的目錄複製到新專案: usbadc/android/app/src/main/java 內, 之後透過 kotin 程式碼呼叫它
3. 編輯 usbadc/android/app/src/main/AndroidManifest.xml 把以下綠色內容貼到標籤 <activity> </activity> 標籤內
<activity>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
4. 接著要產生一個 device_filter 檔案, 把它放到目錄 usbadc/android/app/src/main/res/xml 內:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 0x0403 / 0x60??: FTDI -->
<usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
<usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
<usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
<usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
<usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->
<!-- 0x10C4 / 0xEA??: Silabs CP210x -->
<usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
<usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
<usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->
<!-- 0x067B / 0x23?3: Prolific PL2303x -->
<usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
<usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
<usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
<usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
<usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
<usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
<usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->
<!-- 0x1a86 / 0x?523: Qinheng CH34x -->
<usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
<usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->
<!-- CDC driver -->
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
<usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino -->
<usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
<usb-device vendor-id="7855" product-id="4" /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
<usb-device vendor-id="3368" product-id="516" /> <!-- 0x0d28 / 0x0204: ARM mbed -->
<usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
<usb-device vendor-id="11914" product-id="5" /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
<usb-device vendor-id="11914" product-id="10" /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
<usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources>
5. 編寫 android 端的主程式 usbadc/android/app/src/main/kotlin/com/example/usbadc/MainActivity.kt 如下:
package com.example.usbadc
import java.nio.ByteBuffer
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import androidx.annotation.NonNull
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.app.PendingIntent
import android.content.Intent
import android.hardware.usb.UsbManager
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.driver.UsbSerialPort
class MainActivity: FlutterActivity() {
private lateinit var usbManager: UsbManager
override fun onCreate(savedInstanceState: Bundle?) {// ← ↑ → ↓
super.onCreate(savedInstanceState)
usbManager = getSystemService(USB_SERVICE) as UsbManager
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.apply {
val eventHandler = object: EventChannel.StreamHandler {
override fun onCancel(args: Any?) { }
override fun onListen(args: Any?, sink: EventChannel.EventSink?) {
sink ?. apply {
val flutterSink = this
setupTTY(115200) ?. apply {
val usbSerialPort: UsbSerialPort = this
val packetSize: Int = usbSerialPort.readEndpoint.maxPacketSize
if (packetSize <= 0) return
flutterSink.success("Usb serial port is ready (packet Size = $packetSize).")
val usbBuffer: ByteBuffer = ByteBuffer.allocate(packetSize)
val usbPacket: ByteArray = usbBuffer.array()
var readLoop : Boolean = false
val mainHandler = object: Handler(Looper.getMainLooper()) { // to run ttyRead() in the message loop
override fun handleMessage(msg: Message) {// binding sink: EventChannel.EventSink
when(msg.what) {
0 ->{
flutterSink.endOfStream() // event channel close
looper.quit() // mainHandler quit
}
1 ->{ // read loop
val size = usbSerialPort.read(usbPacket, 1000) // read from usbSerialPort
if (size == packetSize) {
flutterSink.success(usbPacket) // send to flutter
} else if (size > 0) {// retrive part of data in usbPacket
val u8list = ByteArray(size)
System.arraycopy(usbPacket, 0, u8list, 0, size)
flutterSink.success(u8list) // send to flutter
}
sendEmptyMessage(1) // loopback to continue
}
2 ->{ // optional message handle for usbSerialPort write
if (msg.obj is String) {
val bytes: ByteArray = (msg.obj as String).toByteArray()
usbSerialPort.write(bytes, 1000)
}
if (!readLoop) {
readLoop = true// only send once
sendEmptyMessage(1) // trigger to begin read loop
}
}
else -> { }
}
}
}
val msg = mainHandler.obtainMessage().apply {
what = 2
obj = "hi\n"
}
mainHandler.sendMessage(msg)
}
}
}
}
EventChannel(dartExecutor.binaryMessenger, "com.example.ttyUSB").setStreamHandler(eventHandler)
}
}
private fun setupTTY(baudRate: Int): UsbSerialPort? {
val prob = UsbSerialProber.getDefaultProber()
for (device in usbManager.deviceList.values) { // scan all available devices
prob.probeDevice(device) ?. apply { // check only when driver is up
val driver = this
usbManager.openDevice(driver.device) ?. apply {// try to open usb connection
val usbConnection = this
for (port in driver.ports.indices) { // try all available ports
try {
driver.ports[port] . apply { // this is UsbSerialPort
open(usbConnection)
setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE) // set com: baudRate, n, 8, 1
}
return driver.ports[port] // finally UsbSerialPort is ready.
} catch (e: Exception) { }
}
}
}
}
return null
}
}
6. 編寫 flutter 端主程式 usbadc/lib/main.dart 如下
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) => MaterialApp(
title: "demo",// id, not title name!
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
home : const MyHomePage(),
);
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String history = "Hello\n";// init history to show
int total = 0;
int count2Clear = 0;
@override void initState() {
super.initState();
const event = EventChannel('com.example.ttyUSB');
event.receiveBroadcastStream().listen((data) async {
if (count2Clear ++ == 20) {
count2Clear = 0;
history = "";
}
var txt = (data is Uint8List) ? String.fromCharCodes(data) : "$data";// convert data to String
txt = txt.replaceAll("\r","") ;// remove CR
txt = txt.replaceAll("\n",";") ;// change LF to ';'
history += "${++ total}: $txt\n";// history add line number and txt
setState(() {}) ;// update screen
});
}
@override Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("ttyUSB demo"),
actions: [PopupMenuButton<int>(
itemBuilder: (context) => List.generate(1, (idx) => PopupMenuItem(value: idx, child: const Text("關閉"))),
onSelected: (idx) => SystemNavigator.pop() // app finish
)]
),
body: SingleChildScrollView(scrollDirection: Axis.vertical, child: Text(history))
);
}
}
進入專案目錄, 編譯上傳並執行
cd usbadc && flutter run
沒有留言:
張貼留言