Android USB通信(accessory)

Android USB通信(accessory)

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

2024年10月10日更新:之前发的博客也陆陆续续有网友提出问题,最近有网友提醒可以创建一个群聊方便大家简单地交流一下工作中遇见的问题和开发经验,群里主要涉及到的是Iot开发中的通信链路连接和北斗相关(目前只有我)如果你有这方面的问题或者感兴趣的话欢迎加入,初创的群再加上这方面的开发相对冷门所以人数不多,如果有相关问题的话可以在群里留言,大家在工作之余也会根据自身的经验尽力去解答,如果群二维码图片挂了或者过期可以私信一下我哈

一、申请权限

将以下权限申请添加到 AndroidManifest 文件中: 开发时参考的文档中还提到需要另一个权限 “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() {

@Override

public 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() {

@Override

public 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 buffer

writeusbdata[1] = baudRate_byte;

switch(dataBits){

case 5:tmp |= 0x00;break; //reserve

case 6:tmp |= 0x01;break; //reserve

case 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; //none

case 1:tmp |= (1 << 3);break; //odd

case 2:tmp |= ( (1 << 3) | (1 << 4) );break; //event

case 3:tmp |= ( (1 << 3) | (1 << 5) );break; //mark

case 4:tmp |= ( (1 << 3) | (1 << 4) | (1 << 5) );break; //space

default: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, flowControl

writeusbdata[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 添加一个声明并指定过滤规则即可 在xml资源文件夹中添加 accessory_filter 文件 附上过滤规则文件代码:这里指定了我需要连接的设备标识,根据厂商提供的数据修改即可

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

四、小结

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

相关推荐

阿根廷足球奇迹历程回顾世界杯夺冠次数与战绩详解
沙袋十大品牌排行榜
365信息网

沙袋十大品牌排行榜

📅 07-27 👁️ 5767
《经典怀旧•新天龙八部》9月18日全服更新维护公告
英超365bet体育投注

《经典怀旧•新天龙八部》9月18日全服更新维护公告

📅 10-11 👁️ 9989
还记得“我为自己代言”的广告词吗?80,90后们
365bet世界杯欢迎您

还记得“我为自己代言”的广告词吗?80,90后们

📅 09-20 👁️ 490
达飞云贷怎么提额 达飞云贷提额方法
英超365bet体育投注

达飞云贷怎么提额 达飞云贷提额方法

📅 07-04 👁️ 2229
柯洁历年的八次世界赛争冠
英超365bet体育投注

柯洁历年的八次世界赛争冠

📅 08-16 👁️ 6145
彻底卸载32位Office办公软件的教程(简单易懂,助您轻松清理Office残留文件)
汽车之家
英超365bet体育投注

汽车之家

📅 07-15 👁️ 9366
蓝牙耳机的开关在哪里
365信息网

蓝牙耳机的开关在哪里

📅 10-18 👁️ 8238