当前位置: 首页 > 工具软件 > USBManager > 使用案例 >

记录:UsbManager的使用

滕学义
2023-12-01

摘要:最近一直使用到身份证刷卡器,M1刷卡器。都有相关的demo。很快的使用上了,不过在使用M1的时候,没有循环(类似公交卡可以一直刷卡)读卡的demo,自己随便的开一个线程去无限读。事与愿违,不没有成功,不能抽时间查相关的UsbManager相关的资料。在这里把其简单的记录一下,^_^, 毕竟口号就抄,抄,抄. .(侵删)

  USB背景知识

  USB是一种数据通信方式,也是一种数据总线,而且是最复杂的总线之一

  硬件上,它是用插头连接。一边是公头(plug),一边是母头(receptacle)。例如:PC上的插头就是母头,USB设备使用公头与PC相连

  Host
  USB是由 Host端控制整个总线的数据传输。单个USB上,只能有一个Host

  OTG
  On The Go,这是在 USB2.0引入的一种mode,提出了一个新的概念叫主机协商协议(Host Negotiation Protocol),允许两个设备间商量谁去当Host

  Android中的USB

  Android中对USB支持是3.1开始,显然是加强Android平板的对外扩展能力。而对 USB使用更多的是Android在工业中的使用。Android功能板子一般都会提供多个USB和串口,它们是连接外设手段的桥梁。比如:现在市面上的一体机,Android的板子上面会有很多的USB和串口,但是其中Android板子除了一个系统什么都没有,没有任何的传感器,摄像头等等。。全部是外接,就等同于一台式电脑。

  我们在使用这些外接设备的时候,我们需要来了解一下Android为我们提供的API。其具体在android.hardware.usb包中。我们需要了解一下UsbManagerUsbDeviceUsbDeviceConnection , UsbEndpoint , UsbInterface UsbRequest , UsbConstants , 这几个类,只要使用到USB都要用到它们。


  • UsbManager:获得USB的状态,与连接的USB设备通信


  • UsbDevice:USB设备的抽象,它包含了一个或多个的UsbInterface,而每个UsbInterface包含多个UsbEndpoint。Host与其通信,先打开UsbDeviceConnection,使用UsbRequest在一个端点(UsbEndpoint)发送和接受数据。


  • UsbDeviceConnection:host与device建立的连接,并在endpoint传输数据。


  • UsbEndpoint:endpoint是interface的通信信道。


  • UsbInterface : 定理设备的功能集,一个UsbDevice包含多个UsbInterface,每个UsbInterface都是独立的。


  • UsbRequest:usb请求包。可以在UsbDeviceConnection上异步传输数据。注意是只在异步通信时才会使用到它。


  • UsbConstants:usb常量定义,对应 linux/usb/ch9.h

UsbManager常用方法
getDeviceList() 获得设备列表,返回的是一个HashMap
hasPermission(UsbDevice device) 判断你的应用程序是否有接入此USB设备的权限,如果有则返回真,否则返回false
openDevice(UsbDevice device) 打开USB设备,以便向此USB设备发送和接受数据,返回一个关于此USB设备的连接
requestPermission(UsbDevice device, PendingIntent pi) 向USB设备请求临时的接入权限


UsbDevice常用方法
getDeviceClass() 返回此USB设备的类别,用一个整型来表示
getDeviceId() 返回唯一标识此设备的ID号,也用一个整型来表示
getDeviceName() 返回此设备的名称,用一个字符串来表示
getDeviceProtocol() 返回此设备的协议类别,用一个整型来表示
getDeviceSubclass() 返回此设备的子类别,用一个整型来表示
getVendorId()返回生产商ID
getProductId() 返回产品ID
getInterfaceCount()返回此设备的接口数量
getInterface(int index) 得到此设备的一个接口,返回一个UsbInterface


UsbDeviceConnection常用方法
bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) 通过给定的endpoint来进行大量的数据传输,传输的方向取决于该节点的方向,buffer是要发送或接收的字节数组,length是该字节数组的长度。传输成功则返回所传输的字节数组的长度,失败则返回负数
controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout该方法通过0节点向此设备传输数据,传输的方向取决于请求的类别,如果requestType为USB_DIR_OUT则为写数据,USB_DIR_IN, 则为读数据


UsbEndpoint常用方法
getAddress() 获得此节点的地址
getAttributes() 获得此节点的属性
getDirection() 获得此节点的数据传输方向


UsbInterface常用方法
getId() 得到给接口的id号。
getInterfaceClass() 得到该接口的类别
getInterfaceSubclass()得到该接口的子类
getInterfaceProtocol() 得到该接口的协议类别。
getEndpointCount() 获得关于此接口的节点数量
getEndpoint(int index) 对于指定的index获得此接口的一个节点,返回一个UsbEndpoint



  USB的广播

  可以通过广播接接收器接收USB的插拔信息:当插入USB插头到一个USB端口或从一个USB端口移除一USB插头。都可以获取到


 PendingIntent permissionIntent1 = PendingIntent.getBroadcast(this, 0,new Intent(ACTION_USB_PERMISSION), 0);

        // Broadcast listen for new devices
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        this.registerReceiver(mUsbReceiver, filter);

 /*
     *  BroadcastReceiver when insert/remove the device USB plug into/from a USB port
     *  创建一个广播接收器接收USB插拔信息:当插入USB插头插到一个USB端口,或从一个USB端口,移除装置的USB插头
     */
    BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                //插入事件

            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                //拔出事件

            } else if (ACTION_USB_PERMISSION.equals(action)) {

                //权限请求事件
                synchronized (this)
                {
                    UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
                    {
                        if ((device.getProductId() == 8211 && device.getVendorId() == 1305)
                                || (device.getProductId() == 8213 && device
                                .getVendorId() == 1305))
                        {
                            if(mUsbDriver.openUsbDevice(device))
                            {
                                if(device.getProductId()==8211)
                                    mUsbDev1 = device;
                                else
                                    mUsbDev2 = device;
                            }
                        }
                    }
                    else {
                        Toast.makeText(MainActivity.this,"permission denied for device",
                                Toast.LENGTH_SHORT).show();
                        //Log.d(TAG, "permission denied for device " + device);
                    }
                }
            }
        }
    };



  USB的启动程序和 pid的注册

  时常在有多个视屏播放器,浏览器没有设置为默认的情况下都会弹出一个选择框。usb也一样,需要我们在AndroidManifest.xml进行注册。可以注册在activity标签里面,也可以注册在 application标签里面。不管在那里面,都需要在对应的activity标签里面注册


            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>



  和添加权限

 <uses-feature
        android:name="android.hardware.usb.host"
        android:required="true"/>
    <uses-permission android:name="android.permission.MANAGE_USB"/>
    <uses-permission android:name="android.permission.HARDWARE_TEST"/>

  进行筛选我们自己的 USB的厂家id和产品id。可以在res下新建一个xml。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 以下内容的 vendor-id、product-id就是USB的vid和pid了-->
    <usb-device vendor-id="4292" product-id="33896"/>
    <usb-device vendor-id="1024" product-id="50010"/>
    <usb-device vendor-id="1155" product-id="19799"/>
    <usb-device vendor-id="4292" product-id="33485"/>
</resources>

  再在AndroidManifest.xml的activity或者application标签注册

            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter"/>

  在这样之后,在USB插入的时候,就会自行启动我们的activity。

  USB的通信

   把USB连接成功,就需要进行与它通信了。

bulkTransfer(this.h[this.g], var5, var4, 3000)

  在做好准备之后,就这一个话。哈哈哈哈哈,啊嗝

  这里摘抄两个前辈写的
  前辈一:

private void initCommunication(UsbDevice device) {
        tvInfo.append("initCommunication in\n");
        if(1234 == device.getVendorId() && 5678 == device.getProductId()) {
            tvInfo.append("initCommunication in right device\n");
            int interfaceCount = device.getInterfaceCount();
            for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
                UsbInterface usbInterface = device.getInterface(interfaceIndex);
                if ((UsbConstants.USB_CLASS_CDC_DATA != usbInterface.getInterfaceClass())
                        && (UsbConstants.USB_CLASS_COMM != usbInterface.getInterfaceClass())) {
                    continue;
                }

                for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
                    UsbEndpoint ep = usbInterface.getEndpoint(i);
                    if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                        if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                            mUsbEndpointIn = ep;
                        } else {
                            mUsbEndpointOut = ep;
                        }
                    }
                }

                if ((null == mUsbEndpointIn) || (null == mUsbEndpointOut)) {
                    tvInfo.append("endpoint is null\n");
                    mUsbEndpointIn = null;
                    mUsbEndpointOut = null;
                    mUsbInterface = null;
                } else {
                    tvInfo.append("\nendpoint out: " + mUsbEndpointOut + ",endpoint in: " +
                            mUsbEndpointIn.getAddress()+"\n");
                    mUsbInterface = usbInterface;
                    mUsbDeviceConnection = mUsbManager.openDevice(device);
                    break;
                }
            }
        }
    }

  前辈二 : (一个jar,这里只截取其获取getEndpoint)

        private UsbInterface[] e = new UsbInterface[2];
        private UsbDeviceConnection[] f = new UsbDeviceConnection[2];
        private int g = -1;
        private UsbEndpoint[] h = new UsbEndpoint[2];
        private UsbEndpoint[] i = new UsbEndpoint[2];


        int var2 = this.d[this.g].getInterfaceCount();
            Log.i("UsbDriver", " m_Device[m_UsbDevIdx].getInterfaceCount():" + var2);
            if(var2 == 0) {
                return false;
            } else {
                if(var2 > 0) {
                    this.e[this.g] = this.d[this.g].getInterface(0);
                }

                if(this.e[this.g].getEndpoint(1) != null) {
                    this.i[this.g] = this.e[this.g].getEndpoint(1);
                }

                if(this.e[this.g].getEndpoint(0) != null) {
                    this.h[this.g] = this.e[this.g].getEndpoint(0);
                }

  这个指示一部分,可以看出里面获取的手法就简化了很多

  上面的可以愉快的玩耍了,就讲一下把其转化成一直读卡遇到的问题。代码如下:

    public boolean init(Context context) {
        UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        HashMap<String, UsbDevice> devices = manager.getDeviceList();
        LoggerUtil.i(TAG, "device.size = " + devices.size());
        if (devices.size() <= 0) {
            return false;
        }
        UsbDevice myUsbDevice = null;
        Iterator<UsbDevice> iterator = devices.values().iterator();
        while (iterator.hasNext()) {
            UsbDevice device = iterator.next();
            LoggerUtil.i(TAG, "device vid = " + device.getVendorId() + " , pid = " + device.getProductId());
            if (ReaderAndroidUsb.isSupported(device)) {
                myUsbDevice = device;
                break;
            }
        }
        if (myUsbDevice == null) {
            return false;
        }
        // 判断是否拥有该设备的连接权限
        if (!manager.hasPermission(myUsbDevice)) {
            PendingIntent pd = PendingIntent.getBroadcast(context, 0,
                    new Intent(Device_USB), PendingIntent.FLAG_UPDATE_CURRENT);
                        /*
                         * 展示征求用户同意连接这个设备的权限的对话框。 当用户回应这个对话框时,
                         * 广播接收器就会收到一个包含用一个boolean值来表示结果的EXTRA_PERMISSION_GRANTED字段的意图。
                         * 在连接设备之前检查这个字段的值是否为true和设备之间的“交流”
                         */
            manager.requestPermission(myUsbDevice, pd);
        }
        if (!manager.hasPermission(myUsbDevice)) {
            return false;
        }
        // 如果已经拥有该设备的连接权限,直接对该设备操作
        ReaderAndroidUsb readerAndroidUsb = new ReaderAndroidUsb(manager);
        readerAndroidUsb.closeReader();
        try {
                    /*
                     * 函数说明: 直接打开 USB 接口读写器
                     *   返回值:
                     *   >=0 表示打开读写器成功
                     *   <0 表示打开读写器失败。
                     */
            int st = readerAndroidUsb.openReader(myUsbDevice);
            if (st >= 0) {
                reader = readerAndroidUsb;
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            return false;
        }
    }

  ReaderAndroidUsb 为我使用读卡器中的一个打开卡的方法。最初的时候我没有在 init( )方法里面传递 context,其中的 USBmanager也只在类的初始化的时候,进行初始化一次。后面的循环读卡中,刷卡一段时间就再也刷不起了。

  后面的而修改办法就是,同上。初始化传入context,重新获取 UsbManager,一切重新开始。在刷卡成功后,关闭当前的usb的连接。等同于每一次都是单次刷卡,只是把单次刷卡需要调用的东西通过代码实时完成。

参考链接

  1. USB基础知识概论
  2. usb中的endpoint(端点)和传输模式
  3. Android实战技巧之四十九:Usb通信之USB Host
  4. Android下的USB Host介绍和开发
  5. Android开发之USB数据通信
 类似资料: