安卓系统通过控制传输实现与 USB_HID 设备的通信
前言:世上几百年旧家,无非积德;天下第一件好事,还是读书。
引言
这次记录一下安卓如何实现与USB_HID设备通过控制传输通信,以及说明一些我在开发过程中遇到的坑以及注意点,如果对USB的知识没有多少了解的话,建议小伙伴可以先看看我的上一篇文章:安卓实现与USB_HID设备实现控制传输通信所需要具备的USB知识,上一篇文章讲了作为安卓工程师,如果要开发USB_HID设备的话需要了解的一些USB基础知识。写这篇文章的目的是以此作为一个记录,也希望可以帮到有需要的小伙伴们,话不多说,我们开始。
目录
一、用到的与USB相关API
1.1 UsbManager
1.2 UsbDevice
1.3 UsbInterface
1.4 UsbEndpoint
二、实现安卓与USB_HID设备通信的步骤
2.1 准备工作
2.1.1 定义通讯接口
2.1.2 定义usb连接开启成功或失败的回调接口
2.1.3 定义usb接收数据成功或失败的回调接口
2.1.4 定义usb发送数据成功或失败的回调接口
2.1.5 定义一个usb设备连接成功的广播
2.1.6 定义一个usb设备连断开连接的广播
2.1.7编写接口的实现类UsbCommunication
2.2 找到设备
2.2.1 添加权限
2.2.2 检查手机是否支持USB_HID
2.2.3初始化USB_HID连接对象
2.2.4找到USB_HID连接
2.3 获取权限
2.4 使用控制传输发送数据
2.5 使用控制传输接收数据
三、总结
3.1 MainActivity.java中的代码
3.2 activity_main.xml中的代码
一、用到的与USB相关API
1.1 UsbManager
该类允许我们访问USB的状态与USB设备通信。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
其中有几个方法是需要了解的:
①getDeviceList():
获取设备列表,返回HashMap;一般来说设备只有一个,因为手机就一个usb口,如果没有连接设备,或者USB主机模式处于非活动状态或不受支持,结果将为空。
②hasPermission():
如果调用者有权访问附件,则返回true。权限可能已通过requestPermission(UsbAccessory,PendingIntent)或用户选择主叫方作为附件的默认应用暂时授予。就是判断app是否有接入usb的权限,一般都要在工程的AndroidManifest.xml添加usb通信权限,给予权限则函数返回true,否则返回false。
③openDevice(UsbDevice device):
打开usb设备,后续使其可以使用UsbRequest进行数据发送和接收。
④requestPermission(UsbDevice device,PendingIntent pi):
请求临时接入权限访问设备。如果尚未授予权限,则可能会导致系统对话框显示给用户。成功或失败通过PendingIntent pi返回,如果成功,则允许主叫方访问设备,直到设备断开连接。
1.2 UsbDevice
这个类代表一个USB设备,安卓设备作为USB主机。每个设备包含一个或多个UsbInterface,其中的每一个包含若干UsbEndpoint。
方法:
①getDeviceClass():
返回usb设备的类别,返回类型为整型。
②getDeviceId():
返回设备的唯一整数ID。
③getDeviceName():
返回一个字符串类型的设备名称。
④getDeviceProtocol():
返回一个整型类型的协议类别。
⑤getDeviceSubclass():
返回一个整型类型的设备子类别。
⑥getVendorId():
返回一个整型类型的生产商ID。
⑦getProductId():
返回一个整型类型的产品ID。
⑧getInterfaceCount():
返回一个整型类型的接口数量值。
⑨getInterface(int index):
得到一个UsbInterface类型的接口
1.3 UsbInterface
代表UsbDevice上的接口,USB设备可以具有一个或多个接口,每个接口提供与其他接口分离的不同功能块。一个接口可以有一个或多个UsbEndpoint,这是主机通过设备传输数据的通道。
方法:
①getId():
返回一个整型类型接口的bInterfaceNumber字段。
②getInterfaceClass():
返回一个整型类型接口的类字段。
③getInterfaceSubclass():
返回一个整型类型接口的子类字段。
④getEndpointCount():
返回一个整型类型此接口包含的UsbEndpoint(端点)数量。
⑤getEndpoint(int index):
返回一个UsbEndpoint(类型)类型给定索引处的端点。
1.4 UsbEndpoint
表示一个接口某个端点的类,USB_HID主要就是通过端点进行通信的。端点相当于USB发送和接收数据的通道。一般情况下,批量端点用于发送不重要的数据量;中断端点用于与主数据流分开发送少量数据,通常是事件;端点0是从主机发送到设备的控制消息的特殊端点;同步端点当前不受支持。
方法:
①getAddress():
返回一个整型类型的端点地址。地址是一个包含端点号码和端点数据方向的位域。
②getAttributes():
返回一个整型类型的端点属性字段。
③getDirection():
返回一个整型类型的端点数据传输方向。USB_DIR_OUT—方向是主机设备;USB_DIR_IN—方向是设备到主机。
1.5 UsbDeviceConnection
Usb连接类,用这个连接类和USB设备进行数据发送和接收。这个类的实例由openDevice(UsbDevice)创建。
方法:
①bulkTransfer(UsbEndpoint endpoint,byte[] buffer,int length,int timeout):
通过给定的endpoint节点进行大量数据传输,传输方向取决于端点方向,一般自己获取输入输出的方向,buffer是发送或接收的数组,length是数组长度,失败会返回负数(-1)。
②controlTransfer(int requestType,int request,int value,int index,byte[] buffer,int length,int timeout):
通过端点0向设备传输和接收数据,如果使用控制传输用的就是这个方法,这个方法的具体参数很重要,后面代码中会特别指出通过使用SET_FEATURE和GET_FEATURE通信时的参数选择,至于其他选择,方法是一样的,会了这两个请求的参数设置,别的也都一样,我代码中将会使用的是以下格式:
SET_FEATURE如下图:
意思是使用SET_FEATURE请求(0x21),HID类的请求描述符为0x09,ReportID为03(value的低位),报告类型为特征报告(03—即value的高位),HID的接口索引值为0(要特别注意这个,要知道设备接口的具体值。安卓设备是不会去自动寻找的),后面三个参数分别是byte[]类型的数据,数据长度(length),通信超时时间(timeout),该方法通过端点0向HID设备发送数据。
GET_FEATURE如下图:
意思是使用GET_FEATURE请求(0xA1),HID类的请求描述符为0x01,ReportID为03(value的低位),报告类型为特征报告(03—即value的高位),HID的接口索引值为0(要特别注意这个,要知道设备接口的具体值。安卓设备是不会去自动寻找的),后面三个参数分别是byte[]类型的数据,数据长度(length),通信超时时间(timeout),其中的receiveData为事先定义好的一个byte数组,这个数组长度和参数length一样。当HID设备向APP发送数据,APP有数据接收时会把数据存储在receiveData数组里,该方法通过端点0向HID设备请求数据。
③claimInterface(UsbInterface intf,boolean force):
声明独家访问UsbInterface。这必须在发送或接收属于接口的任何UsbEndpoint的数据之前完成。
二、实现安卓与USB_HID设备通信的步骤
2.1准备工作
2.1.1 定义通讯接口
public interface BaseCommunication {
/**
* 开启通讯接口
* @param listener
*/
public void openCommunication(CommunicationListener listener,int VID,int PID);
/**
* 关闭通讯接口
*/
public void closeCommunication();
}
2.1.2 定义usb连接开启成功或失败的回调接口
public interface CommunicationListener {
void onSuccess(int code,String msg);
void onFaild(String msg);
}
2.1.3 定义usb接收数据成功或失败的回调接口
public interface ReciverMessageListener {
void onSuccess(byte[] bytes);
void onFaild(String msg);
}
2.1.4 定义usb发送数据成功或失败的回调接口
public interface SendMessageListener {
void onSuccess();
void onFaild(String msg);
}
2.1.5 定义一个usb设备连接成功的广播
usb设备连接成功时这个广播就能监听到。
public class OpenDevicesReceiver extends BroadcastReceiver {
private OpenDevicesListener mOpenDevicesListener;//usb设备连接的回调接口
public OpenDevicesReceiver(OpenDevicesListener openDevicesListener) {
mOpenDevicesListener = openDevicesListener;
}
private CommunicationListener listener;//sdk中用户打开usb连接成功的回调
public void setCommunicationListener(CommunicationListener listener) {
this.listener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);//获取附件设备
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,false)) {//判断设备是否有usb
if (usbDevice != null) {
mOpenDevicesListener.openAccessoryModel(usbDevice);
listener.onSuccess(2,"usb权限开启成功");
Toast.makeText(context,"usb权限开启成功",Toast.LENGTH_SHORT).show();
}else {
mOpenDevicesListener.openDevicesError();
listener.onFaild("USB设备连接异常");
}
}else {
//打开权限失败
mOpenDevicesListener.openDevicesError();
listener.onFaild("用户未授权USB权限");
}
}
public interface OpenDevicesListener {
/**
* 打开Accessory模式
*/
void openAccessoryModel(UsbDevice usbDevice);
/**
* 打开设备(手机)失败
*/
void openDevicesError();
}
}
2.1.6 定义一个usb设备连断开连接的广播
usb设备连接断开时这个广播就能监听到。
public class UsbDetacheReceiver extends BroadcastReceiver {
private UsbDetachedListener mUsbDetachedListener;//usb连接断开的回调
/**
* 构造方法中添加回调参数
* @param UsbDetachedListener
*/
public UsbDetacheReceiver(UsbDetachedListener UsbDetachedListener) {
mUsbDetachedListener = UsbDetachedListener;
}
@Override
public void onReceive(Context context, Intent intent) {
mUsbDetachedListener.usbDetached();
}
public interface UsbDetachedListener{
/**
* usb断开连接
*/
void usbDetached();
}
}
2.1.7编写接口的实现类UsbCommunication
public class UsbCommunication implements BaseCommunication, UsbDetacheReceiver.UsbDetachedListener,
OpenDevicesReceiver.OpenDevicesListener{
//usb设备连接广播
private OpenDevicesReceiver mOpenDevicesReceiver;
//usb连接断开广播
private UsbDetacheReceiver mUsbDetacheReceiver;
//需要使用context初始化相关usb资源
private Context mContext;
//usb设备管理器
private UsbManager mUsbManager;
//自定义usb权限action
private static final String USB_ACTION = "com.wiseasy.communication.usb.hostchart";
/**
*Handler相关判断的变量,对应:usb连接初始化成功、失败,usb连接接收消息成功、失败,usb连接发送消息成功、失败
*/
private static final int RECEIVER_MESSAGE_SUCCESS = 1;
private static final int SEND_MESSAGE_SUCCESS = 2;
private static final int RECEIVER_MESSAGE_FAILED = 3;
private static final int SEND_MESSAGE_FAILED = 4;
private static final int INIT_FAILED = 5;
private static final int INIT_SUCCESS = 6;
private int devicePid = 0;
private int deviceVid = 0;
//接收消息监听回调
private ReciverMessageListener reciverMessageListener;
//发送消息监听回调
private SendMessageListener sendMessageListener;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case RECEIVER_MESSAGE_SUCCESS://成功接收到数据
reciverMessageListener.onSuccess((byte[]) msg.obj);
break;
case RECEIVER_MESSAGE_FAILED://接收消息失败
reciverMessageListener.onFaild("接收消息失败");
break;
case SEND_MESSAGE_SUCCESS://发送数据成功
sendMessageListener.onSuccess();
break;
case SEND_MESSAGE_FAILED://发送数据失败
sendMessageListener.onFaild("发送消息失败");
break;
case INIT_SUCCESS://usb主附设备连接成功
communicationListener.onSuccess(1,"初始化成功");
break;
case INIT_FAILED://usb主附设备连接失败
communicationListener.onFaild("初始化失败");
break;
default:
break;
}
}
};
/**
* 单例模式 初始化
* @param context
*/
private UsbCommunication(Context context) {
/**
* 为了避免在单利模式下的内存泄露,这里将context统一转换为ApplicationContext
*/
this.mContext = context.getApplicationContext();
//注册usb连接断开的广播
mUsbDetacheReceiver = new UsbDetacheReceiver(this);
IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
mContext.registerReceiver(mUsbDetacheReceiver,intentFilter);
//通过context获取到当前系统的USB管理器
mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
}
//单例变量
private static volatile UsbCommunication Instance;
/**
* 单例模式双重检查
* @param context
* @return
*/
public static UsbCommunication getInstance(Context context) {
if (Instance == null) {
synchronized (UsbCommunication.class) {
if (Instance == null) {
Instance = new UsbCommunication(context);
}
}
}
return Instance;
}
//usb连接开启成功或失败的回调
private CommunicationListener communicationListener;
/**
* 主设备开启成功,进行从设备唤醒
* @param usbDevice
*/
@Override
public void openAccessoryModel(UsbDevice usbDevice) {
initDevice(deviceVid,devicePid);
}
/**
* 主设备权限被拒绝
*/
@Override
public void openDevicesError() {
Toast.makeText(mContext,"USB权限被拒绝",Toast.LENGTH_SHORT).show();
}
/**
* usb连接断开 释放资源
*/
@Override
public void usbDetached() {
if (mUsbDeviceConnection != null) {
mUsbDeviceConnection.releaseInterface(mUsbInterface);
mUsbDeviceConnection.close();
mUsbDeviceConnection = null;
}
mUsbEndpointIn = null;
mUsbEndpointOut = null;
isReceiverMessage = false;
mContext.unregisterReceiver(mUsbDetacheReceiver);
mContext.unregisterReceiver(mOpenDevicesReceiver);
}
//usb设备连接通道
private UsbDeviceConnection mUsbDeviceConnection;
//usb接口
private UsbInterface mUsbInterface;
//usb输出端点
private UsbEndpoint mUsbEndpointOut;
//usb输入端点
private UsbEndpoint mUsbEndpointIn;
private void initDevice(int VID,int PID) {
/**
* 使用usbManager遍历设备集合,通过producetId找出匹配accessory模式的设备
*/
//通过context获取到当前系统的USB管理器
mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
HashMap<String,UsbDevice> deviceList = mUsbManager.getDeviceList();
Collection<UsbDevice> values = deviceList.values();
if (!values.isEmpty()) {
for (UsbDevice usbDevice : values) {
if (usbDevice.getVendorId() == VID && usbDevice.getProductId() == PID) {
if (mUsbManager.hasPermission(usbDevice)) {
//获取当前usb设备的通讯连接
mUsbDeviceConnection = mUsbManager.openDevice(usbDevice);
if (mUsbDeviceConnection != null) {
//获取通讯连接接口
mUsbInterface = usbDevice.getInterface(0);
//获取通讯连接端点数量
int endpointCount = mUsbInterface.getEndpointCount();
for (int i = 0;i < endpointCount;i++) {
//遍历所有端点,找到输入端点与输出端点
UsbEndpoint usbEndpoint = mUsbInterface.getEndpoint(i);
// if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
mUsbEndpointOut = usbEndpoint;//赋值输出端点
}else if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
mUsbEndpointIn = usbEndpoint;//赋值输入端点
}
// }
}
//当输出端点和输入端点都不为空时,表示usb连接成功,初始化完成,可以进行数据收发
if (mUsbEndpointOut != null && mUsbEndpointIn != null) {
mHandler.sendEmptyMessage(INIT_SUCCESS);
}
}
}else {
//申请usb权限
mUsbManager.requestPermission(usbDevice,PendingIntent.getBroadcast(mContext,0,new Intent(USB_ACTION),0));
}
}
}
}else {
//初始化失败
mHandler.sendEmptyMessage(INIT_FAILED);
}
}
/**
* 开启usb连接的接口实现
* @param communicationListener
*/
@Override
public void openCommunication(CommunicationListener communicationListener, int VID, int PID) {
devicePid = VID;
deviceVid = PID;
this.communicationListener = communicationListener;
//用来申请usb权限
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,0,new Intent(USB_ACTION),0);
//注册usb连接开启的广播
mOpenDevicesReceiver = new OpenDevicesReceiver(this);
IntentFilter intentFilter = new IntentFilter(USB_ACTION);
mContext.registerReceiver(mOpenDevicesReceiver,intentFilter);
//设置连接成功的监听回调
mOpenDevicesReceiver.setCommunicationListener(communicationListener);
/**
* 通过usbManager获取当前连接的设备集合
* 遍历集合通过productId过滤不符合条件的设备
*/
HashMap<String,UsbDevice> deviceList = mUsbManager.getDeviceList();
communicationListener.onFaild("获取设备:" + deviceList);
if (deviceList != null) {
for (UsbDevice usbDevice : deviceList.values()) {
int productId = usbDevice.getProductId();
if (usbDevice.getVendorId() == VID && usbDevice.getProductId() == PID) {
communicationListener.onFaild("获取设备到:" + PID);
if (mUsbManager.hasPermission(usbDevice)) {
//子设备初始化
initDevice(deviceVid,devicePid);
}else {
//申请usb权限
mUsbManager.requestPermission(usbDevice,pendingIntent);
}
}
}
}else {
//连接失败回调
communicationListener.onFaild("请连接USB");
}
}
/**
* 关闭usb连接
* 释放所有资源
*/
@Override
public void closeCommunication() {
if (mUsbDeviceConnection != null) {
mUsbDeviceConnection.releaseInterface(mUsbInterface);
mUsbDeviceConnection.close();
mUsbDeviceConnection = null;
}
mUsbEndpointIn = null;
mUsbEndpointOut = null;
isReceiverMessage = false;
mContext.unregisterReceiver(mUsbDetacheReceiver);
mContext.unregisterReceiver(mOpenDevicesReceiver);
}
/**
* 发送数据接口实现
* 发送byte[]类型数据
* 通过回调监听成功或失败
* @param bytes
* @param listener
*/
@Override
public void sendMessage(final byte[] bytes, SendMessageListener listener) {
this.sendMessageListener = listener;
if (bytes != null) {
/**
* 耗时操作在子线程中执行
*/
new Thread(new Runnable() {
@Override
public void run() {
/**
* 发送数据的地方,只接收byte数据类型的数据
*/
int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut,bytes,bytes.length,3000);
if (i > 0) {//大于0表示发送成功
mHandler.sendEmptyMessage(SEND_MESSAGE_SUCCESS);
}else {
mHandler.sendEmptyMessage(SEND_MESSAGE_FAILED);
}
}
}).start();
}else {
listener.onFaild("发送数据为null");
}
}
//接收数据循环变量,usb连接成功后需要一直监听用户发送的数据
private boolean isReceiverMessage = true;
//暂定的接收数据的大小,需要优化
private byte[] mBytes = new byte[1024];
/**
* 接收数据的实现
* 通过回调来返回接收的数据
* 使用handler来传递接收的数据到主线程
* @param listener
*/
@Override
public void receiveMessage(ReciverMessageListener listener) {
this.reciverMessageListener = listener;
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(1000);
while (isReceiverMessage) {
/**
* 循环接受数据的地方 , 只接受byte数据类型的数据
*/
if (mUsbDeviceConnection != null && mUsbEndpointIn != null) {
int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, mBytes, mBytes.length, 3000);
if (i > 0) {
Message message = Message.obtain();
message.what = RECEIVER_MESSAGE_SUCCESS;
message.obj = mBytes;
mHandler.sendMessage(message);
}
} else {
mHandler.sendEmptyMessage(RECEIVER_MESSAGE_FAILED);
}
}
}
}).start();
}
}
2.2 找到设备
2.2.1 添加权限
在AndroidManifest.xml下添加以下权限:
<uses-feature android:name="android.hardware.usb.host"/>
<uses-permission android:name="android.permission.usb.accessory"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
在AndroidManifest.xml的<activity android:name=”.MainActivity”></activity>中添加以下注册信息:
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
2.2.2 检查手机是否支持USB_HID
在进行通信前,需要检查手机是否支持USB_HID设备,即手机是否支持OTG,只有手机支持OTG时才可以通过USB_HID和外部进行通信。
在MainActivity.java的onCreate()方法中写入以下代码用来检查手机是否支持OTG:
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (manager == null) {
Log.e(TAG,"不支持OTG");
Toast.makeText(MainActivity.this,"不支持OTG",Toast.LENGTH_SHORT).show();
return;
}else {
Log.e(TAG,"usb设备:" + manager.toString());
Toast.makeText(MainActivity.this,manager.toString(),Toast.LENGTH_SHORT).show();
mTvState.setText(manager.toString());
}
2.2.3初始化USB_HID连接对象
在MainActivity.java的onCreate()方法中初始化usb连接对象。
usbCommunication = UsbCommunication.getInstance(this);//初始化usb连接对象
2.2.4找到USB_HID连接
在初始化连接对象后根据VID和PID找到对应的设备,如果不知道PID和VID可以向硬件工程师问,也可以通过API方法获取(就在前面)。
usbCommunication.openCommunication(new CommunicationListener() {
@Override
public void onSuccess(int code, String msg) {
//连接成功回调
}
@Override
public void onFaild(String msg) {
//连接失败回调
}
},0x04d9,0xf541);
2.3 获取权限
要使APP和USB_HID设备能够通信,需要申请usb临时权限,如果mUsbManager.hasPermission(usbDevice)的返回值为true,那么说明权限已获取,如果为false,则说明未获取权限,也就不能够通信。获取权限使用动态申请的方式获取,方法为:
//申请usb权限
mUsbManager.requestPermission(usbDevice,pendingIntent);
执行requestPermission()方法后界面会弹出一个申请权限框,效果如下图所示:
2.4 使用控制传输发送数据
通过控制传输发送数据用的是设备端点0进行发送,在发送之前得执行以下步骤:
①获取设备
HashMap<String,UsbDevice> deviceList = manager.getDeviceList();
if (!deviceList.isEmpty()) {
for (UsbDevice device : deviceList.values()) {
if (device.getVendorId() == 0x04d9 && device.getProductId() == 0xf541) {//获取设备列表,根据PID和VID找到设备
mUsbDevice = device;
}
}
}
②判断是否获取通信权限
if (manager.hasPermission(mUsbDevice)) {//如果已经获取到了通信授权,那么返回的是true。
}
③获取通信
mUsbDeviceConnection = manager.openDevice(mUsbDevice);//打开设备获取通信连接
④选择接口
mUsbInterface = mUsbDevice.getInterface(0);
选择通信的接口,有些设备里面可能有多个接口,但是每个接口都会有一个端点0,一定要选择存在的接口,不能选择不存在的接口,因为安卓不会像电脑一样自动寻找接口,这点上一篇文章中已经提到。
⑤声明接口
mUsbDeviceConnection.claimInterface(mUsbInterface,true);
⑥发送数据
final byte[] conToBootloaderData = {0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
new Thread(new Runnable() {
@Override
public void run() {
int j = mUsbDeviceConnection.controlTransfer(0x21,0x09,0x0303,0,conToBootloaderData,8,3000);
if (j > 0) {
//发送成功
showResponse("发送成功");
}else {
//发送失败
showResponse("发送失败");
}
}
}).start();
数据发送过去的数据得定义好,当数据发送成功时会返回一个整型类型的数值,即程序里面的j。发送失败返回的会是一个负数。
2.5 使用控制传输接收数据
接收数据的方法和发送数据的方法是一样的,只不过是方法里面的参数取值不一样。
new Thread(new Runnable() {
@Override
public void run() {
int j = mUsbDeviceConnection.controlTransfer(0xA1,0x01,0x0303,0,receiveData,8,3000);
if (j > 0) {
//接收成功
showResponse("接收成功:" + j);
}else {
//接收失败
showResponse("接收失败:" + j);
}
}
}).start();
当发送成功时,同样会返回一个int类型的非负数,发送失败会返回一个int类型的负数,可以根据此判断是否发送成功。
三、总结
3.1 MainActivity.java中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "UsbHid";
private final byte[] receiveData = new byte[8];
private UsbManager manager;//USB管理器
private UsbDevice mUsbDevice;//找到的USB设备
private UsbInterface mUsbInterface;//设备接口
private UsbDeviceConnection mUsbDeviceConnection;//用于打开连接设备,发送或接收数据
private UsbCommunication usbCommunication;
private TextView mTvState;
private TextView mTvDataReceive;
private Button mButton;
private Button mBtnReceive;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvState = findViewById(R.id.tv_state);
mTvDataReceive = findViewById(R.id.tv_data_receive);
mButton = findViewById(R.id.btn_send);
mButton.setOnClickListener(this);
mBtnReceive = findViewById(R.id.btn_receive);
mBtnReceive.setOnClickListener(this);
manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (manager == null) {
Log.e(TAG,"不支持OTG");
Toast.makeText(MainActivity.this,"不支持OTG",Toast.LENGTH_SHORT).show();
return;
}else {
Log.e(TAG,"usb设备:" + manager.toString());
Toast.makeText(MainActivity.this,manager.toString(),Toast.LENGTH_SHORT).show();
mTvState.setText(manager.toString());
}
usbCommunication = UsbCommunication.getInstance(this);//初始化usb连接对象
mTvState.setText("初始化usb连接对象");
//第一次是为了获取通信权限,目的是获取VID为0x04d9、PID为0xf541设备的通信权限
usbCommunication.openCommunication(new CommunicationListener() {
@Override
public void onSuccess(int code, String msg) {
}
@Override
public void onFaild(String msg) {
}
},0x04d9,0xf541);
HashMap<String,UsbDevice> deviceList = manager.getDeviceList();//获取设备列表
if (!deviceList.isEmpty()) {
for (UsbDevice device : deviceList.values()) {
if (device.getVendorId() == 0x04d9 && device.getProductId() == 0xf541) {//根据PID和VID找到设备
mUsbDevice = device;
}
}
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_send:
if (manager != null) {
if (mUsbDevice != null) {
if (manager.hasPermission(mUsbDevice)) {//判断是否获取到了通信权限
//获取当前usb设备的通讯连接
mUsbDeviceConnection = manager.openDevice(mUsbDevice);
if (mUsbDeviceConnection != null) {
mUsbInterface = mUsbDevice.getInterface(0);//选择接口0
mUsbDeviceConnection.claimInterface(mUsbInterface,true);//声明接口
final byte[] conToBootloaderData = {0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
new Thread(new Runnable() {
@Override
public void run() {//发送数据
int j = mUsbDeviceConnection.controlTransfer(0x21,0x09,0x0303,0,conToBootloaderData,8,3000);
if (j > 0) {
//接收成功
showResponse("发送成功");
}else {
//接收失败
showResponse("发送失败");
}
}
}).start();
}
}
}
}
break;
case R.id.btn_receive://接收数据
if (manager.hasPermission(mUsbDevice)) {
new Thread(new Runnable() {
@Override
public void run() {
int j = mUsbDeviceConnection.controlTransfer(0xA1,0x01,0x0303,0,receiveData,8,3000);
if (j > 0) {
//接收成功
showResponse("接收成功");
}else {
//接收失败
showResponse("接收失败");
}
}
}).start();
}
break;
default:
break;
}
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (response) {
case "发送成功":
mTvDataReceive.setText("发送成功");
break;
case "发送失败":
mTvDataReceive.setText("发送失败");
break;
case "接收成功":
mTvDataReceive.setText("接收成功");
break;
case "接收失败":
mTvDataReceive.setText("接收失败");
break;
default:
break;
}
}
});
}
}
3.2 activity_main.xml中的代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_send"
android:text="发送"
android:layout_alignParentRight="true"
android:textAllCaps="false"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_receive"
android:text="接收"
android:textAllCaps="false"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_data_receive"
android:layout_centerHorizontal="true"
android:text="接收到的数据"
android:layout_marginTop="90dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_state"
android:text="Hello World!"
android:layout_centerInParent="true"/>
</RelativeLayout>
以上就是我如何用APP实现与USB_HID设备通过控制传输通信的过程,从一开始USB是什么都不知道,到后面怎么使用控制传输通信,特别是控制传输方法中的参数选择,每个设备都有不一样的接口、ReportID等等,网上有很多文章都没说明这些参数如何选择,所以本文中特别说明了这些参数是如何选取的,这些都是要跟硬件工程师那边进行交流沟通才知道的。如果有大佬看到文章中哪里写得不对的地方,恳请指出,及时修改。最后,看到这里,如果对你有所帮助,给个赞呗~
上一篇: 基于安卓系统的视频可控推车
下一篇: 各种定位方式
推荐阅读
-
安卓系统通过控制传输实现与 USB_HID 设备的通信
-
windows下进程间通信的(13种方法)-摘 要 本文讨论了进程间通信与应用程序间通信的含义及相应的实现技术,并对这些技术的原理、特性等进行了深入的分析和比较。 ---- 关键词 信号 管道 消息队列 共享存储段 信号灯 远程过程调用 Socket套接字 MQSeries 1 引言 ---- 进程间通信的主要目的是实现同一计算机系统内部的相互协作的进程之间的数据共享与信息交换,由于这些进程处于同一软件和硬件环境下,利用操作系统提供的的编程接口,用户可以方便地在程序中实现这种通信;应用程序间通信的主要目的是实现不同计算机系统中的相互协作的应用程序之间的数据共享与信息交换,由于应用程序分别运行在不同计算机系统中,它们之间要通过网络之间的协议才能实现数据共享与信息交换。进程间通信和应用程序间通信及相应的实现技术有许多相同之处,也各有自己的特色。即使是同一类型的通信也有多种的实现方法,以适应不同情况的需要。 ---- 为了充分认识和掌握这两种通信及相应的实现技术,本文将就以下几个方面对这两种通信进行深入的讨论:问题的由来、解决问题的策略和方法、每种方法的工作原理和实现、每种实现方法的特点和适用的范围等。 2 进程间的通信及其实现技术 ---- 用户提交给计算机的任务最终都是通过一个个的进程来完成的。在一组并发进程中的任何两个进程之间,如果都不存在公共变量,则称该组进程为不相交的。在不相交的进程组中,每个进程都独立于其它进程,它的运行环境与顺序程序一样,而且它的运行环境也不为别的进程所改变。运行的结果是确定的,不会发生与时间相关的错误。 ---- 但是,在实际中,并发进程的各个进程之间并不是完全互相独立的,它们之间往往存在着相互制约的关系。进程之间的相互制约关系表现为两种方式: ---- (1) 间接相互制约:共享CPU ---- (2) 直接相互制约:竞争和协作 ---- 竞争——进程对共享资源的竞争。为保证进程互斥地访问共享资源,各进程必须互斥地进入各自的临界段。 ---- 协作——进程之间交换数据。为完成一个共同任务而同时运行的一组进程称为同组进程,它们之间必须交换数据,以达到协作完成任务的目的,交换数据可以通知对方可以做某事或者委托对方做某事。 ---- 共享CPU问题由操作系统的进程调度来实现,进程间的竞争和协作由进程间的通信来完成。进程间的通信一般由操作系统提供编程接口,由程序员在程序中实现。UNIX在这个方面可以说最具特色,它提供了一整套进程间的数据共享与信息交换的处理方法——进程通信机制(IPC)。因此,我们就以UNIX为例来分析进程间通信的各种实现技术。 ---- 在UNIX中,文件(File)、信号(Signal)、无名管道(Unnamed Pipes)、有名管道(FIFOs)是传统IPC功能;新的IPC功能包括消息队列(Message queues)、共享存储段(Shared memory segment)和信号灯(Semapores)。 ---- (1) 信号 ---- 信号机制是UNIX为进程中断处理而设置的。它只是一组预定义的值,因此不能用于信息交换,仅用于进程中断控制。例如在发生浮点错、非法内存访问、执行无效指令、某些按键(如ctrl-c、del等)等都会产生一个信号,操作系统就会调用有关的系统调用或用户定义的处理过程来处理。 ---- 信号处理的系统调用是signal,调用形式是: ---- signal(signalno,action) ---- 其中,signalno是规定信号编号的值,action指明当特定的信号发生时所执行的动作。 ---- (2) 无名管道和有名管道 ---- 无名管道实际上是内存中的一个临时存储区,它由系统安全控制,并且独立于创建它的进程的内存区。管道对数据采用先进先出方式管理,并严格按顺序操作,例如不能对管道进行搜索,管道中的信息只能读一次。 ---- 无名管道只能用于两个相互协作的进程之间的通信,并且访问无名管道的进程必须有共同的祖先。 ---- 系统提供了许多标准管道库函数,如: pipe——打开一个可以读写的管道; close——关闭相应的管道; read——从管道中读取字符; write——向管道中写入字符; ---- 有名管道的操作和无名管道类似,不同的地方在于使用有名管道的进程不需要具有共同的祖先,其它进程,只要知道该管道的名字,就可以访问它。管道非常适合进程之间快速交换信息。 ---- (3) 消息队列(MQ) ---- 消息队列是内存中独立于生成它的进程的一段存储区,一旦创建消息队列,任何进程,只要具有正确的的访问权限,都可以访问消息队列,消息队列非常适合于在进程间交换短信息。 ---- 消息队列的每条消息由类型编号来分类,这样接收进程可以选择读取特定的消息类型——这一点与管道不同。消息队列在创建后将一直存在,直到使用msgctl系统调用或iqcrm -q命令删除它为止。 ---- 系统提供了许多有关创建、使用和管理消息队列的系统调用,如: ---- int msgget(key,flag)——创建一个具有flag权限的MQ及其相应的结构,并返回一个唯一的正整数msqid(MQ的标识符); ---- int msgsnd(msqid,msgp,msgsz,msgtyp,flag)——向队列中发送信息; ---- int msgrcv(msqid,cmd,buf)——从队列中接收信息; ---- int msgctl(msqid,cmd,buf)——对MQ的控制操作; ---- (4) 共享存储段(SM) ---- 共享存储段是主存的一部分,它由一个或多个独立的进程共享。各进程的数据段与共享存储段相关联,对每个进程来说,共享存储段有不同的虚拟地址。系统提供的有关SM的系统调用有: ---- int shmget(key,size,flag)——创建大小为size的SM段,其相应的数据结构名为key,并返回共享内存区的标识符shmid; ---- char shmat(shmid,address,flag)——将当前进程数据段的地址赋给shmget所返回的名为shmid的SM段; ---- int shmdr(address)——从进程地址空间删除SM段; ---- int shmctl (shmid,cmd,buf)——对SM的控制操作; ---- SM的大小只受主存限制,SM段的访问及进程间的信息交换可以通过同步读写来完成。同步通常由信号灯来实现。SM非常适合进程之间大量数据的共享。 ---- (5) 信号灯 ---- 在UNIX中,信号灯是一组进程共享的数据结构,当几个进程竞争同一资源时(文件、共享内存或消息队列等),它们的操作便由信号灯来同步,以防止互相干扰。 ---- 信号灯保证了某一时刻只有一个进程访问某一临界资源,所有请求该资源的其它进程都将被挂起,一旦该资源得到释放,系统才允许其它进程访问该资源。信号灯通常配对使用,以便实现资源的加锁和解锁。 ---- 进程间通信的实现技术的特点是:操作系统提供实现机制和编程接口,由用户在程序中实现,保证进程间可以进行快速的信息交换和大量数据的共享。但是,上述方式主要适合在同一台计算机系统内部的进程之间的通信。 3 应用程序间的通信及其实现技术 ---- 同进程之间的相互制约一样,不同的应用程序之间也存在竞争和协作的关系。UNIX操作系统也提供一些可用于应用程序之间实现数据共享与信息交换的编程接口,程序员可以通过自己编程来实现。如远程过程调用和基于TCP/IP协议的套接字(Socket)编程。但是,相对普通程序员来说,它们涉及的技术比较深,编程也比较复杂,实现起来困难较大。 ---- 于是,一种新的技术应运而生——通过将有关通信的细节完全掩盖在某个独立软件内部,即底层的通讯工作和相应的维护管理工作由该软件内部来实现,用户只需要将通信任务提交给该软件去完成,而不必理会它的具体工作过程——这就是所谓的中间件技术。 ---- 我们在这里分别讨论这三种常用的应用程序间通信的实现技术——远程过程调用、会话编程技术和MQSeries消息队列技术。其中远程过程调用和会话编程属于比较低级的方式,程序员参与的程度较深,而MQSeries消息队列则属于比较高级的方式,即中间件方式,程序员参与的程度较浅。 ---- 4.1 远程过程调用(RPC)