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() {
            @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 添加一个声明并指定过滤规则即可
<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博客

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值