Android USB通信(accessory)

2023-11-02 04:20
文章标签 android 通信 usb accessory

本文主要是介绍Android USB通信(accessory),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:公司属于北斗通信行业,项目大多都需要和各式各样的硬件设备相结合来满足项目需求,因此所涉及到的各种技术也相对比较冷门。前段时间有个项目用到了一款定制北斗设备,需要用到它自带的 type-c 线连接手机使用,开发时发现它是通过 USB(accessory)来连接手机设备的,现在项目完成了,就在这里记录和分享一下,有任何错漏或可优化之处欢迎大家留言。

一、申请权限

将以下权限申请添加到 AndroidManifest 文件中:
<uses-permission android:name="android.permission.USB_PERMISSION" />
开发时参考的文档中还提到需要另一个权限 “android.hardware.usb.accessory” 但是我这里没有添加也能正常使用,如果调不通的话可以试着把这个权限也加进去

二、直接上代码

复制再把报红的部分直接去掉或者换成自己的就能直接使用

package com.example.SecondProject.Utils.Transfer.USB;import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.widget.Toast;import com.example.SecondProject.Base.MainApplication;
import com.example.SecondProject.BuildConfig;
import com.example.SecondProject.Global.Constant;
import com.example.SecondProject.Global.Variable;
import com.example.SecondProject.Utils.DataUtil;
import com.example.SecondProject.Utils.NotificationCenter;
import com.example.SecondProject.Utils.ProtocolUtil;import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;// USB附件连接工具
public class USBAccessoryTransferUtil {String TAG = "USBAccessoryTransferUtil";MainApplication APP = MainApplication.getInstance();  // 主程序public UsbManager usbManager = (UsbManager) APP.getSystemService(Context.USB_SERVICE);private BroadcastReceiver usbAccessoryReceiver = null;  // 广播监听:判断设备授权操作public UsbAccessory usbAccessory = null;  // 当前连接的 USB附件 对象public ParcelFileDescriptor fileDescriptor = null;public FileInputStream inputStream = null;  // 输入流public FileOutputStream outputStream = null;  // 输出流public ReadThread readThread = null;  // 接收数据线程private final String ACTION_USB_PERMISSION = BuildConfig.APPLICATION_ID + ".INTENT_ACTION_GRANT_USB_ACCESSORY";  // usb权限请求标识private final String IDENTIFICATION = "WCHAccessory1";  // 目标设备的序列号标识// 特定厂商的设备标识,自行修改或删除 -------------------------------------public String ManufacturerString = "mManufacturer=WCH";public String ModelString1 = "mModel=WCHUARTDemo";public String VersionString = "mVersion=1.0";// 单例 -------------------------------------------------------------------private static USBAccessoryTransferUtil usbAccessoryTransferUtil;public static USBAccessoryTransferUtil getInstance() {if(usbAccessoryTransferUtil == null){usbAccessoryTransferUtil = new USBAccessoryTransferUtil();}return usbAccessoryTransferUtil;}public void connect(){// “Variable.isConnectUSBAccessory” 我的变量标识,自行删除或修改if(!Variable.isConnectUSBAccessory){registerReceiver();  // 注册广播监听refreshDevice();  // 拿到设备connectDevice();  // 连接设备}}// 注册usb授权监听广播public void registerReceiver(){usbAccessoryReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();Log.e(TAG, "onReceive: "+action);// 收到 ACTION_USB_PERMISSION 请求权限广播if (ACTION_USB_PERMISSION.equals(action)) {// 确保只有一个线程执行里面的任务,不与其他应用冲突synchronized (this) {usbAccessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);if(usbAccessory==null){Log.e(TAG, "usbAccessory 对象为空" );return;}// 判断是否授予了权限boolean havePermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);if (havePermission) {APP.showToast("授予 USB 权限", Toast.LENGTH_SHORT);connectDevice();  // 授权成功,直接连接}else {APP.showToast("拒绝 USB 权限", Toast.LENGTH_SHORT);}}}// 收到 USB附件 拔出的广播else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {  // android.hardware.usb.action.USB_ACCESSORY_DETACHED// 断开连接disconnect();}else {Log.e(TAG, "registerReceiver/onReceive:其它");}}};IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);  // 当收到 usb附件 插入广播动作APP.registerReceiver(usbAccessoryReceiver, filter);  // 注册}public void refreshDevice(){
//        Log.e(TAG, "refreshDevice: 1");UsbAccessory[] accessories = usbManager.getAccessoryList();  // 可用 UsbAccessory 列表if(accessories == null || accessories.length == 0) {return;}else {Log.e(TAG, "获取到的设备数量: " + accessories.length);// 如果只有一个就直接获取if(accessories.length == 1){usbAccessory = accessories[0];  // 默认拿到他的第1个}// 如果有多个就根据厂商提供的标识判断一下else {for (UsbAccessory accessory : accessories) {if(accessory.getSerial().equals(IDENTIFICATION)){usbAccessory = accessory;}}}}if(usbAccessory == null){return;}// 开始连接设备boolean isMyDevice = checkDevice(usbAccessory);  // 这是我的项目设备的判断方法,直接去掉或者改成你自己的if(isMyDevice){// 判断是否拥有权限,如果没权限就申请if (!usbManager.hasPermission(usbAccessory)) {synchronized (usbAccessoryReceiver) {APP.showToast("请授予 USB 权限", Toast.LENGTH_SHORT);PendingIntent pendingIntent = PendingIntent.getBroadcast(APP, 0, new Intent(ACTION_USB_PERMISSION), 0);usbManager.requestPermission(usbAccessory,pendingIntent);}}}else {Log.e(TAG, "这不是我的设备");usbAccessory = null;}}public void connectDevice(){Log.e(TAG, "connectDevice: 1");if(usbAccessory == null){return;}Log.e(TAG, "connectDevice: 2");if(usbManager.hasPermission(usbAccessory)){Log.e(TAG, "connectDevice: 3");fileDescriptor = usbManager.openAccessory(usbAccessory);if(fileDescriptor != null){Log.e(TAG, "connectDevice: 4");FileDescriptor fd = fileDescriptor.getFileDescriptor();// 拿到输入/输出流inputStream = new FileInputStream(fd);outputStream = new FileOutputStream(fd);// 开启接收数据线程readThread = new ReadThread();readThread.start();}}else {APP.showToast("请先授予权限再连接",0);}}public boolean checkDevice(UsbAccessory usbAccessory){if( -1 == usbAccessory.toString().indexOf(ManufacturerString)) {APP.showToast("Manufacturer is not matched!", Toast.LENGTH_SHORT);return false;}if( -1 == usbAccessory.toString().indexOf(ModelString1) ) {APP.showToast("Model is not matched!", Toast.LENGTH_SHORT);return false;}if( -1 == usbAccessory.toString().indexOf(VersionString)) {APP.showToast("Version is not matched!", Toast.LENGTH_SHORT);return false;}if(Variable.DebugMode){APP.showToast("制造商、型号和版本匹配", Toast.LENGTH_SHORT);}return true;}// 下发数据(16进制字符串)public void write(String data_hex) {if(outputStream==null){return;}try {byte[] data_bytes = DataUtil.hexStringToBytes(data_hex);this.outputStream.write(data_bytes);Log.e(TAG, "write 下发的指令是: " + DataUtil.hex2String(data_hex) );} catch (Exception e) {e.printStackTrace();}}// 下发初始化指令,改成你自己的或删掉public void init_device(){new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}SetConfig(19200,(byte)8,(byte)1,(byte)0,(byte)0);try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}write(ProtocolUtil.CCRMO("PWI",2,9));  // 设置pwi信号输出频度try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}write(ProtocolUtil.CCRMO("MCH",1,0));  // 关闭设备的HCM指令输出try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}write(ProtocolUtil.CCRNS(5,5,5,5,5,5));  // 设置rn指令输出频度try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}write(ProtocolUtil.CCICR(0,"00"));}}).start();}// 断开连接public void disconnect(){try {// 停止数据监听if(readThread != null){readThread.close();readThread = null;}// 关闭输入输出流if(inputStream != null){inputStream.close();inputStream = null;}if(outputStream != null){outputStream.close();outputStream = null;}if(fileDescriptor != null){fileDescriptor.close();fileDescriptor = null;}// 清除设备if(usbAccessory != null){usbAccessory = null;}// 注销广播if(usbAccessoryReceiver != null){APP.unregisterReceiver(usbAccessoryReceiver);usbAccessoryReceiver = null;}}catch (Exception e){e.printStackTrace();}APP.showToast("断开连接",0);Variable.isConnectUSBAccessory = false;  // 修改连接标识NotificationCenter.standard().postNotification(Constant.DISCONNECT_USB_ACCESSORY);  // 发送全局广播}// 读取 USB附件 数据线程private byte[] readBuffer = new byte[1024 * 2];  // 缓冲区private class ReadThread  extends Thread {boolean alive = true;ReadThread(){this.setPriority(Thread.MAX_PRIORITY);  // 设置线程的优先级:最高级}byte[] buf = new byte[2048];  // 每次从输入流读取的最大数据量:这个大小直接影响接收数据的速率,根据需求修改ByteArrayOutputStream baos = new ByteArrayOutputStream();public void run() {if(inputStream == null){return;}init_device();  // 下发初始化指令,根据自己的设备修改或直接删掉Variable.isConnectUSBAccessory = true;  // 修改连接标识NotificationCenter.standard().postNotification(Constant.CONNECT_USB_ACCESSORY);  // 发送广播Log.e(TAG, "开启数据监听");while(alive) {try {int size = inputStream.read(buf);if(size>0){baos.write(buf,0,size);readBuffer = baos.toByteArray();// 根据需求设置停止位:由于我需要接收的是北斗指令,指令格式最后两位为 “回车换行(\r\n)” 所以我只需要判断数据末尾两位// 设置停止位,当最后两位为 \r\n 时就传出去if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {if(onReceiveData!=null){onReceiveData.receiveData(readBuffer);}baos.reset();  // 重置}// 设置停止位:当最后一位为 \n 时就传出去
//                        if (readBuffer.length >= 1 && readBuffer[readBuffer.length - 1] == (byte)'\n') {
//                            if(onReceiveData!=null){
//                                onReceiveData.receiveData(readBuffer);
//                            }
//                            baos.reset();  // 重置
//                        }// 设置停止位:当读取的数据长度为 30 时,就传出去(用这种方法要把每次读取的数据量改小 1-10)
//                        if (readBuffer.length == 30) {
//                            if(onReceiveData!=null){
//                                onReceiveData.receiveData(readBuffer);
//                            }
//                            baos.reset();  // 重置
//                        }}sleep(10);  // 设置循环间隔} catch (Throwable var3) {if(var3.getMessage() != null){Log.e(TAG, "ReadThread:" + var3.getMessage());}return;}}}public void close(){alive = false;this.interrupt();}}// 沁恒设备设置波特率等参数方法public void SetConfig(int baud, byte dataBits, byte stopBits, byte parity, byte flowControl) {Log.e("TAG", "设置: " + baud + "/"  + dataBits + "/" + stopBits + "/" + parity + "/" + flowControl);byte tmp = 0x00;byte baudRate_byte = 0x00;byte []	writeusbdata = new byte[5];writeusbdata[0] = 0x30;switch(baud) {case 300:baudRate_byte = 0x00;break;case 600:baudRate_byte = 0x01;break;case 1200:baudRate_byte = 0x02;break;case 2400:baudRate_byte = 0x03;break;case 4800:baudRate_byte = 0x04;break;case 9600:baudRate_byte = 0x05;break;case 19200:baudRate_byte = 0x06;break;case 38400:baudRate_byte = 0x07;break;case 57600:baudRate_byte = 0x08;break;case 115200:baudRate_byte = 0x09;break;case 230400:baudRate_byte = 0x0A;break;case 460800:baudRate_byte = 0x0B;break;case 921600:baudRate_byte = 0x0C;break;default:baudRate_byte = 0x05;break; // default baudRate "9600"}// prepare the baud rate bufferwriteusbdata[1] = baudRate_byte;switch(dataBits){case 5:tmp |= 0x00;break;  //reservecase 6:tmp |= 0x01;break;  //reservecase 7:tmp |= 0x02;break;case 8:tmp |= 0x03;break;default:tmp |= 0x03;break; // default data bit "8"}switch(stopBits){case 1:tmp &= ~(1 << 2);break;case 2:tmp |= (1 << 2);break;default:tmp &= ~(1 << 2);break; // default stop bit "1"}switch(parity){case 0:tmp &= ~( (1 << 3) | (1 << 4) | (1 << 5) );break; //nonecase 1:tmp |= (1 << 3);break; //oddcase 2:tmp |= ( (1 << 3) | (1 << 4) );break; //eventcase 3:tmp |= ( (1 << 3) | (1 << 5) );break; //markcase 4:tmp |= ( (1 << 3) | (1 << 4) | (1 << 5) );break; //spacedefault:tmp &= ~( (1 << 3) | (1 << 4) | (1 << 5));break;//default parity "NONE"}switch(flowControl){case 0:tmp &= ~(1 << 6);break;case 1:tmp |= (1 << 6);break;default:tmp &= ~(1 << 6);break; //default flowControl "NONE"}// dataBits, stopBits, parity, flowControlwriteusbdata[2] = tmp;writeusbdata[3] = 0x00;writeusbdata[4] = 0x00;write(DataUtil.bytes2Hex(writeusbdata));writeusbdata = null;}// 接口 ---------------------------------------------public interface onReceiveData{void receiveData(byte[] data);}public onReceiveData onReceiveData;public void setOnReceiveData(onReceiveData onReceiveData){this.onReceiveData = onReceiveData;}}

三、使用例子

1. 使用方法

使用 setOnReceiveData 方法设置数据监听处理
使用 connect() 方法连接

使用 write() 方法下发数据

记得要在适当的位置使用 disconnect() 方法断开连接释放资源

设备收到下发的指令数据并做出响应,通信成功

2. 设备接入监听

Android系统在每次拔插 USB 设备时都会广播一个意图,这样如果我们需要在 USB 设备连接时进行某种操作只需要在 manifest 文件里面给对应的 activty 添加一个声明并指定过滤规则即可
<intent-filter>
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"         android:resource="@xml/device_filter" />

在xml资源文件夹中添加 accessory_filter 文件

附上过滤规则文件代码:这里指定了我需要连接的设备标识,根据厂商提供的数据修改即可

<?xml version="1.0" encoding="utf-8"?>
<resources>    <usb-accessory model="WCHUARTDemo" manufacturer="WCH" version="1.0"/>
</resources>

通过声明以上的intent-filter和meta-data,表明它是一个能够处理USB_ACCESSORY设备连接事件的Activity,并且根据res/xml/accessory_filter.xml中的规则对连接的USB_ACCESSORY设备进行过滤。这样,在Android设备连接USB_ACCESSORY设备时,系统就会发出设备接入,这时授权成功后就能直接跳转到对应的 activity。

四、小结

整个连接流程大致是这样的:
获取当前系统可用的 USB 设备列表 → 选中对应的USB设备并申请权限(首次)→ 拿到输入/输出流
这里有一点需要注意的是读取数据要根据自己的实际需求作出修改,例如我要处理的是北斗协议数据,它总是以回车换行(/r/n) 为结尾因此我只要以最后两个字节作为我的判断条件即可。
另附一个北斗协议解析工具:北斗协议解析(北三)_TTTTao2323的博客-CSDN博客

这篇关于Android USB通信(accessory)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/328073

相关文章

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目