Android开发笔记[4]-串口控制esp32及使用摄像头

发布时间 2023-09-23 16:18:58作者: qsBye

摘要

无需root权限,Android使用串口与esp32通信控制小灯开关;开启Android摄像头预览.

平台信息

  • Android Studio: Electric Eel | 2022.1.1 Patch 2
  • Gradle:distributionUrl=https://services.gradle.org/distributions/gradle-7.5-bin.zip
  • jvmTarget = '1.8'
  • minSdk 21
  • targetSdk 33
  • compileSdk 33
  • 开发语言:Kotlin,Java
  • ndkVersion = '25.2.9519653'

Android串口及其使用方式简介

[https://gitcode.net/mirrors/mik3y/usb-serial-for-android]
[https://github.com/mik3y/usb-serial-for-android]
[https://zhuanlan.zhihu.com/p/358212044]

usb-serial-for-android库支持的usb芯片如下:

This library supports USB to serial converter chips:
FTDI FT232R, FT232H, FT2232H, FT4232H, FT230X, FT231X, FT234XD
Prolific PL2303
Silabs CP2102 and all other CP210x
Qinheng CH340, CH341A, CH9102
and devices implementing the CDC/ACM protocol like
Arduino using ATmega32U4
Digispark using V-USB software USB
BBC micro:bit using ARM mbed DAPLink firmware

使用串口库的示例工程为[https://gitcode.net/mirrors/mik3y/usb-serial-for-android/-/tree/master/usbSerialExamples].

This is a driver library for communication with Arduinos and other USB serial hardware on
Android, using the
Android USB Host Mode (OTG)
available since Android 3.1 and working reliably since Android 4.2.
No root access, ADK, or special kernel drivers are required; all drivers are implemented in
Java. You get a raw serial port with read(), write(), and other functions for use with your own protocols.

Android摄像头使用方式

[https://developer.android.google.cn/training/camerax?hl=zh-cn]
[https://developer.android.google.cn/training/camerax/preview?hl=zh-cn]

  • 使用CameraX库
    CameraX 是一个 Jetpack 库,旨在帮助您更轻松地开发相机应用。如果您要开发新应用,我们建议您从 CameraX 开始。它提供了一个一致且易于使用的 API,该 API 适用于绝大多数 Android 设备,并向后兼容 Android 5.0(API 级别 21)。如果您要将应用从 Camera1 迁移到 CameraX,请参阅从 Camera1 迁移到 CameraX 的迁移指南。
    在向应用添加预览时,请使用 PreviewView,这是一种可以剪裁、缩放和旋转以确保正确显示的 View。
    当相机处于活动状态时,图片预览会流式传输到 PreviewView 中的 Surface。
    如需使用 PreviewView 实现 CameraX 预览,请按以下步骤操作(稍后将对这些步骤进行说明):
  1. (可选)配置 CameraXConfig.Provider。
  2. 将 PreviewView 添加到布局。
  3. 请求 ProcessCameraProvider。
  4. 在创建 View 时,请检查 ProcessCameraProvider。
  5. 选择相机并绑定生命周期和用例。

实现

步骤

  1. esp32实现串口收发及命令解析
  2. Android实现串口收发

Android设备接入typeC-typeA集线器可以扩展出USB接口,然后接入带有CH340(USB转TTL)芯片的esp32即可实现串口通信.

Android关键代码

[https://gitcode.net/mirrors/mik3y/usb-serial-for-android]

  1. settings.gradle引入串口库依赖jitpack.io
dependencyResolutionManagement {
    repositories {
        //省略
        maven { url 'https://jitpack.io' }
    }
}
  1. build.gradle(app)引入串口库及CameraX库;
dependencies {
    //省略

    //串口库
    implementation 'com.github.mik3y:usb-serial-for-android:3.5.1'

    //CameraX库
    def camerax_version = "1.1.0-beta03"
    // CameraX core library
    implementation "androidx.camera:camera-core:$camerax_version"
    // CameraX Camera2 extensions[可选]拓展库可实现人像、HDR、夜间和美颜、滤镜但依赖于OEM
    implementation "androidx.camera:camera-camera2:$camerax_version"
    // CameraX Lifecycle library[可选]避免手动在生命周期释放和销毁数据
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    // CameraX View class[可选]最佳实践,最好用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现
    implementation 'androidx.camera:camera-view:1.1.0-beta03'
}
  1. MainActivity.kt
//APP包名
package com.mbeddev.androidkotlinvirtualjoystick

//蓝牙:Rxble库
//串口:usb-serial-for-android库
//摄像头:CameraX库
import android.Manifest
import android.app.PendingIntent
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.os.Bundle
import android.os.Handler
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.util.Log
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import android_serialport_api.SerialPortFinder
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatTextView
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.app.ActivityCompat
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.lifecycle.LifecycleOwner
import com.billin.www.rxble.ble.BluetoothClient
import com.billin.www.rxble.ble.BluetoothClientBLEV2Adapter
import com.billin.www.rxble.ble.bean.BLEDevice
import com.billin.www.rxble.ble.callback.BaseResultCallback
import com.billin.www.rxble.ble.originV2.BluetoothLeInitialization
import com.google.common.util.concurrent.ListenableFuture
import com.hoho.android.usbserial.driver.UsbSerialDriver
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.util.HexDump
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import java.io.IOException
import java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executors
import kotlin.properties.Delegates

/*
串口相关:
- USB-VID:0x1A86
- USB-PID:0x55D3
- 波特率:115200
- 字符编码:us-ascii
*/

class MainActivity : AppCompatActivity(), JoystickView.JoyStickListener,BluetoothActivity.BluetoothActivityListener{
    //时间戳显示
    private lateinit var logArea: TextView
    private lateinit var enterTime: String

    //手柄消抖
    private var joystickLastResponseTime by Delegates.notNull<Long>()
    private var joystickDebounceInterval by Delegates.notNull<Long>() // 1秒

    init {
        joystickLastResponseTime = 0
        joystickDebounceInterval = 1000
    }

    //动态申请权限
    private var permission = false

    //start 摄像头相关
    private val CAMERA_PERMISSION_CODE = 101
    private lateinit var cameraProviderFuture : ListenableFuture<ProcessCameraProvider>
    //end 摄像头相关

    //start 串口相关
    private val WRITE_EXTERNAL_STORAGE_CODE: Int = 100
    private val USB_PERMISSION_CODE = 2
    private val ACTION_USB_PERMISSION = "cn.qsbye.USB_PERMISSION"
    private val serial_connected = false //串口连接标志
    lateinit var usbIoManager: SerialInputOutputManager //串口io管理器
    //end 串口相关

    //start 串口命令相关
    val cmd_serial_led_on="<led on;>"//开灯
    val cmd_serial_led_off="<led off;>"//关灯
    //end 串口命令相关

    //可供外部访问
    companion object {
        // Global screen dimensions.
        var screenHeight: Int = 0
        var screenWidth: Int = 0
        const val REQUEST_PERMISSION = 1
        private const val PERMISSION_REQUEST_CODE = 123
    }

    var joystickId = -1

    private fun getCurrentTime(): String {
        val currentTime = SimpleDateFormat("HH:mm:ss").format(Date())
        return currentTime
    }

    private fun displayTimestamp(timestamp: String): String {
        val inputFormat = SimpleDateFormat("HH:mm:ss")
        val outputFormat = SimpleDateFormat("hh:mm:ss a")
        val date = inputFormat.parse(timestamp)
        return outputFormat.format(date)
    }

    private fun displayLogMessage(logText: String) {
        val logArea = findViewById<TextView>(R.id.log_area_text)
        val time = this.getCurrentTime()
        val logMessage = "$time $logText"
        val newText = "${logArea.text}\n$logMessage"

        // 设置日志区最大行数
        val maxLines = 25
        val lines = newText.lines()
        val trimmedLines = lines.takeLast(maxLines)
        val trimmedText = trimmedLines.joinToString("\n")

        logArea.text = trimmedText
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //start 串口相关
        val serialPortFinder = SerialPortFinder()
        val serial_list=serialPortFinder.allDevicesPath;//列出串口列表
        //end 串口相关

        //start 摄像头相关
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)//请求 CameraProvider
        cameraProviderFuture.addListener(Runnable {
            val cameraProvider = cameraProviderFuture.get()
            bindPreview(cameraProvider)//调用自定义函数bindPreview
        }, ContextCompat.getMainExecutor(this))
        //end 摄像头相关

        //日志区显示进入
        displayLogMessage("?进入应用!")

        //日志区显示欢迎信息
        displayLogMessage("?欢迎!")
        displayLogMessage("帮助:\n1. 物理连接串口; \n2. 点击连接串口按钮;")

        //监听'打开串口'按钮
        val connectSerialTextView = findViewById<AppCompatTextView>(R.id.connect_serial)
        connectSerialTextView.setOnClickListener {
            displayLogMessage("尝试打开串口中...")
            // 检查摄像头权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
                // 如果没有权限,申请权限
                ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.CAMERA),
                    CAMERA_PERMISSION_CODE)
            } else {
                displayLogMessage( "已有摄像头权限,开启摄像头中...")
            }

            //检查串口权限
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
                // 如果没有权限,申请权限
                ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    WRITE_EXTERNAL_STORAGE_CODE)
            } else {
                displayLogMessage( "已有串口权限,开启串口中...")
                // 如果有存储权限,继续检查USB设备权限
                checkUsbDevicePermission()
            } //end else

        }//end 监听'打开串口'按钮

        // This lambda is for specific procedures that need to be done when the layout is created.
        val cLayout: LinearLayout = findViewById(R.id.constraintLayout)
        cLayout.doOnLayout {
            screenHeight = it.measuredHeight
            screenWidth = it.measuredWidth
        }

        val joystick: JoystickView = findViewById(R.id.my_joystick)
        joystick.setJoystickListener(this)

        val lp: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
            ConstraintLayout.LayoutParams.WRAP_CONTENT,
            ConstraintLayout.LayoutParams.WRAP_CONTENT
        )

        val set = ConstraintSet()

        //省略
    }

    override fun onJoystickMoved(xPercent: Float, yPercent: Float, id: Int) {
        //记录上一次响应的时间
        val currentTime = System.currentTimeMillis()
        val elapsedTime = currentTime - joystickLastResponseTime

        //符合消抖规则
        if (elapsedTime >= joystickDebounceInterval) {
            //记录时间
            joystickLastResponseTime = System.currentTimeMillis()

            if (id == R.id.my_joystick) {
                val threshold = 0.1 // 阈值,用于判断方向

                if (xPercent < -threshold) {
                    if (yPercent < -threshold) {
                        // 左上角
                        if (xPercent < yPercent) {
                            displayLogMessage("Forward") // 更接近上方,显示"Forward"

                            try{
                                writeData(hexToString(cmd_forward))
                            }
                            catch(e:Exception){
                                //pass
                            }

                            try{
                                usbIoManager.serialSend(cmd_serial_led_on)//开灯
                            }
                            catch(e:Exception){
                                //pass
                            }

                        } else {
                            displayLogMessage("Left") // 更接近左方,显示"Left"

                            try{
                                writeData(hexToString(cmd_left))
                            }
                            catch(e:Exception){

                            }

                        }
                    } else if (yPercent > threshold) {
                        // 左下角
                        if (xPercent < -yPercent) {
                            displayLogMessage("Backward") // 更接近下方,显示"Backward"

                            try{
                                writeData(hexToString(cmd_backward))
                            }
                            catch(e:Exception){
                                //pass
                            }

                            try{
                                usbIoManager.serialSend(cmd_serial_led_off)//关灯
                            }catch(e:Exception){
                                //pass
                            }

                        } else {
                            displayLogMessage("Left") // 更接近左方,显示"Left"

                            try{
                                writeData(hexToString(cmd_left))
                            }
                            catch(e:Exception){

                            }

                        }
                    } else {
                        displayLogMessage("Left") // 向左移动,显示"Left"

                        try{
                            writeData(hexToString(cmd_left))
                        }
                        catch(e:Exception){

                        }

                    }
                } else if (xPercent > threshold) {
                    if (yPercent < -threshold) {
                        // 右上角
                        if (-xPercent < yPercent) {
                            displayLogMessage("Forward") // 更接近上方,显示"Forward"

                            try{
                                writeData(hexToString(cmd_forward))
                            }
                            catch(e:Exception){
                                //pass
                            }

                            try{
                                usbIoManager.serialSend(cmd_serial_led_on)//开灯
                            }
                            catch(e:Exception){
                                //pass
                            }

                        } else {
                            displayLogMessage("Right") // 更接近右方,显示"Right"

                            try{
                                writeData(hexToString(cmd_right))
                            }
                            catch(e:Exception){

                            }

                        }
                    } else if (yPercent > threshold) {
                        // 右下角
                        if (-xPercent < -yPercent) {
                            displayLogMessage("Backward") // 更接近下方,显示"Backward"

                            try{
                                writeData(hexToString(cmd_backward))
                            }
                            catch(e:Exception){
                                //pass
                            }

                            try{
                                usbIoManager.serialSend(cmd_serial_led_off)//关灯
                            }catch(e:Exception){
                                //pass
                            }

                        } else {
                            displayLogMessage("Right") // 更接近右方,显示"Right"

                            try{
                                writeData(hexToString(cmd_right))
                            }
                            catch(e:Exception){
                                //pass
                            }

                        }
                    } else {
                        displayLogMessage("Right") // 向右移动,显示"Right"

                        try{
                            writeData(hexToString(cmd_right))
                        }
                        catch(e:Exception){

                        }

                    }
                } else {
                    if (yPercent < -threshold) {
                        displayLogMessage("Forward") // 向上移动,显示"Forward"

                        try{
                            writeData(hexToString(cmd_forward))
                        }
                        catch(e:Exception){
                            //pass
                        }

                        try{
                            usbIoManager.serialSend(cmd_serial_led_on)//开灯
                        }
                        catch(e:Exception){
                            //pass
                        }

                    } else if (yPercent > threshold) {
                        displayLogMessage("Backward") // 向下移动,显示"Backward"

                        try{
                            writeData(hexToString(cmd_backward))
                        }
                        catch(e:Exception){
                            //pass
                        }

                        try{
                            usbIoManager.serialSend(cmd_serial_led_off)//关灯
                        }catch(e:Exception){
                            //pass
                        }

                    }
                }
            }
        }
    }

    // start 权限检查

    //动态申请权限
    private fun requestPermission() {
        displayLogMessage( "申请权限中...")
        val state1 =
            ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        val state2 =
            ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH_SCAN)
        val state3 =
            ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH)
        val state4 =
            ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH_CONNECT)
        val state5 =
            ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH_ADMIN)
        val state6 =
            ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)

        if (state1 == PackageManager.PERMISSION_GRANTED) {
                displayLogMessage( "已开启定位权限")
                permission = true
            if (state2 == PackageManager.PERMISSION_GRANTED) {
                displayLogMessage( "已开启蓝牙扫描权限")

                if (state3 == PackageManager.PERMISSION_GRANTED) {
                    displayLogMessage( "已开启蓝牙权限")
                    permission = true
                    if (state4 == PackageManager.PERMISSION_GRANTED) {
                        displayLogMessage( "已开启蓝牙连接权限")
                        //调用蓝牙扫描函数
                        displayLogMessage("调用蓝牙扫描函数")
                        //******这里是蓝牙入口*******
                         //1. 初始化RxBle
                            mClient = BluetoothClientBLEV2Adapter(
                                BluetoothLeInitialization.getInstance(this)
                            )
                            mClient.openBluetooth()
                            //2. RxBle扫描设备
                            mClient.search(3000, false)
                                .observeOn(AndroidSchedulers.mainThread())
                                .subscribe(object : Observer<BLEDevice> {
                                    override fun onSubscribe(d: Disposable) {
                                        Log.e("RxBle", "start")
                                        //mTextView.text = "start\n"
                                    }

                                    override fun onNext(value: BLEDevice) {
                                        Log.e("RxBle", "device $value")
                                        //mTextView.text = "${mTextView.text}\n\n$value"

                                        //判断有没有需要的设备
                                        if (value.toString().contains(BLE_DEVICE_NAME)) {
                                            this@MainActivity.displayLogMessage("已查找到所需的设备:\n"+BLE_DEVICE_NAME)
                                            /***/

                                            // 注册通知的操作,只执行一次
                                             fun registerNotify() {
                                                mClient.registerNotify(
                                                    BLE_DEVICE_MAC,
                                                    BLE_SERVICE_UUID,
                                                    BLE_CHARACTERISTIC_UUID_TX,
                                                    object : BaseResultCallback<ByteArray> {
                                                        override fun onSuccess(data: ByteArray) {
                                                            Log.e("RxBle", "I have receive a new message: " + data.contentToString())
                                                            val result = "E/RxBle: I have receive a new message: ${convertIntArrayToCharArray(data).contentToString()}"
                                                            runOnUiThread {
                                                                displayLogMessage("接收到:$result")
                                                            }
                                                        }

                                                        override fun onFail(msg: String) {
                                                            Log.e("RxBle", "oop! setting register is failed!")
                                                        }
                                                    }
                                                )
                                            }

                                            // 连接成功后的回调函数
                                             fun onConnectSuccess() {
                                                Log.e("RxBle", "connect test: on connect success")
                                                // 注册通知
                                                registerNotify()
                                            }

                                            // 连接并注册通知
                                            mClient.connect(BLE_DEVICE_MAC)
                                                .subscribe(object : Observer<String> {
                                                    override fun onSubscribe(d: Disposable) {
                                                        Log.e("RxBle", "connect test onSubscribe: ")
                                                    }

                                                    override fun onNext(value: String) {
                                                        Log.e("RxBle", "connect test onNext: ")
                                                        // 连接成功后注册通知
                                                        onConnectSuccess()
                                                        // 执行多次写入数据
                                                        writeData(hexToString(cmd_led_toggle))
                                                        //writeData("Fine")
                                                        // 可以根据需要继续写入数据
                                                    }

                                                    override fun onError(e: Throwable) {
                                                        Log.e("RxBle", "connect test onError: ", e)
                                                    }

                                                    override fun onComplete() {
                                                        Log.e("RxBle", "connect test onComplete: ")
                                                    }
                                                })

                                            /***/
                                        }
                                    }

                                    override fun onError(e: Throwable) {
                                        Log.e("RxBle","onError"+e)
                                    }

                                    override fun onComplete() {
                                        Log.e("RxBle","onComplete: search")
                                    }// end onComplete
                                })

                        //******结束蓝牙入口*******
                    }else{
                        displayLogMessage( "请授予权限后重新点击按钮!")
                        ActivityCompat.requestPermissions(
                            this, arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
                            REQUEST_PERMISSION)
                    }
                }
                else{
                    displayLogMessage( "请授予权限后重新点击按钮!")
                    ActivityCompat.requestPermissions(
                        this, arrayOf(Manifest.permission.BLUETOOTH),
                        REQUEST_PERMISSION)
                }

            }else{
                displayLogMessage( "请授予权限后重新点击按钮!")
                ActivityCompat.requestPermissions(
                    this, arrayOf(Manifest.permission.BLUETOOTH_SCAN),
                    REQUEST_PERMISSION)
            }

        } else {
            displayLogMessage( "请授予权限后重新点击按钮!")
            ActivityCompat.requestPermissions(
                this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                REQUEST_PERMISSION)
        }
    }

    //检查权限
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_PERMISSION) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { //蓝牙权限开启失败
                displayLogMessage( "未开启蓝牙权限")
            } else {
                startActivity(Intent(this, MainActivity::class.java))
                finish()
            }
        }

        if (requestCode == CAMERA_PERMISSION_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 用户授予了摄像头权限,初始化摄像头
                //initializeCamera()
                displayLogMessage( "已有摄像头权限,开启摄像头中...")
            } else {
                // 用户拒绝了摄像头权限,可以显示一个提示或采取适当的措施
                displayLogMessage( "重新点击按钮申请权限")
            }
        }

        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    //end 权限检查

    //start 摄像头相关

    //选择相机并绑定生命周期和用例
    fun bindPreview(cameraProvider : ProcessCameraProvider) {
        //1. 创建 Preview
        var preview : Preview = Preview.Builder()
            .build()

        //2. 指定所需的相机 LensFacing 选项。
        var cameraSelector : CameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()

        //3. 将所选相机和任意用例绑定到生命周期
        val viewFinder = findViewById<PreviewView>(R.id.camera_preview)
        preview.setSurfaceProvider(viewFinder.getSurfaceProvider())

        //4. 将 Preview 连接到 PreviewView
        var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)

        //将预览流渲染到目标 View 上
        //viewFinder.implementationMode = PreviewView.ImplementationMode.COMPATIBLE
    }
    //end 摄像头相关

    //start 串口相关
    //以指定参数打开串口
    fun openAndConfigureSerialPort(
        context: Context,
        deviceVID: Int,
        devicePID: Int,
        baudRate: Int,
        dataBits: Int,
        stopBits: Int,
        parity: Int,
        onDataReceived: (String) -> Unit
    ) {
        val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        val usbSerialDriver = findUsbSerialDriver(usbManager, deviceVID, devicePID) ?: return

        val connection = usbManager.openDevice(usbSerialDriver.device)
        if (connection == null) {
            //申请对应usb设备的权限,然后重新点击按钮
            return
        }

        val port = usbSerialDriver.ports[0] // Most devices have just one port (port 0)
        try {
            port.open(connection)
            try {
                port.setParameters(
                    baudRate,
                    dataBits,
                    UsbSerialPort.STOPBITS_1,
                    UsbSerialPort.PARITY_NONE
                )
            }
            catch(e: UnsupportedOperationException){
                Log.e("USB Serial", "不支持的串口参数")
            }
        } catch (e: IOException) {
            // Handle any exceptions here
            e.printStackTrace()
            return
        }

        usbIoManager = SerialInputOutputManager(port, onDataReceived)
        Executors.newSingleThreadExecutor().submit(usbIoManager)
    }

    //查找指定usb设备
    private fun findUsbSerialDriver(
        usbManager: UsbManager,
        deviceVID: Int,
        devicePID: Int
    ): UsbSerialDriver? {
        val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)
        for (driver in availableDrivers) {
            val device = driver.device
            if (device.vendorId == deviceVID && device.productId == devicePID) {
                return driver
            }
        }
        return null
    }

    //管理usb串口输入输出
    class SerialInputOutputManager(
        private val port: UsbSerialPort,
        private val onDataReceived: (String) -> Unit
    ) : Runnable {

        override fun run() {
            val buffer = ByteArray(8192)
            while (true) {
                try {
                    //一直接收
                    val bytesRead = port.read(buffer, 2000)
                    if (bytesRead > 0) {
                        val data = String(buffer.copyOf(bytesRead), Charset.forName("US-ASCII"))
                        Log.e("USB Serial", "$data")
                        onDataReceived(data)
                        //port.close();
                    }
                    else{
                        Log.e("USB Serial", "没有数据")
                        //Log.e("USB Serial", "$bytesRead")
                    }
                } catch (e: IOException) {
                    // Handle any exceptions here
                    e.printStackTrace()
                    return
                }
            }
        }//end run()

        //发送数据
       fun serialSend(str: String) {
            try {
                //val data = (str + '\n').toByteArray()
                val data = str.toByteArray()
                val spn = SpannableStringBuilder()
                spn.append("send ${data.size} bytes\n")
                spn.append(HexDump.dumpHexString(data)).append("\n")
                port.write(data, 2000)
                Log.e("USB Serial", "发送:$data")
            } catch (e: Exception) {
                Log.e("USB Serial", "发送数据失败")
            }
        }//end serialSend

    }//end class

    //检查权限
    private fun checkUsbDevicePermission() {
        val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
        val usbSerialDriver = findUsbSerialDriver(usbManager, 0x1A86, 0x55D3)

        if (usbSerialDriver != null) {
            val device = usbSerialDriver.device

            if (usbManager.hasPermission(device)) {
                // 已经有USB设备权限,可以继续操作
                displayLogMessage("已有USB设备权限,开启串口中...")
                try {
                        openAndConfigureSerialPort(this, 0x1A86, 0x55D3, 115200, 8,1,0) { receivedData ->
                            // 处理接收到的数据,例如显示在UI上
                            runOnUiThread {
                                displayLogMessage("收到:$receivedData")
                            }
                        }
                    } catch (e: Exception) {
                    // 捕获异常并显示错误信息
                    runOnUiThread {
                        displayLogMessage("发生错误: ${e.message}")
                    }
                }//end catch
            } else {
                // 没有USB设备权限,请求权限
                val permissionIntent = PendingIntent.getBroadcast(
                    this,
                    0,
                    Intent(ACTION_USB_PERMISSION),
                    0
                )
                usbManager.requestPermission(device, permissionIntent)
            }
        } else {
            // 没有找到匹配的USB设备
            displayLogMessage("未找到匹配的USB设备")
        }
    }

    //end 串口相关

}// end MainActivity
  1. activity_main.xml
<!--省略-->
        <!-- 摄像头画面 -->
        <androidx.camera.view.PreviewView
            android:id="@+id/camera_preview"
            android:layout_height="match_parent"
            android:layout_width="100dp" />
<!--省略-->
  1. AndroidManifest.xml
<!--省略-->
    <uses-permission android:name="android.permission.SERIAL_PORT_PERMISSION" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-feature android:name="android.hardware.camera.any" />
<!--省略-->

效果

开灯 关灯