基于usb4java或libusb使用Android Accessory

Google对于Android Accessory的应用端开发有详细的例子,我这里就不发代码。实在看不懂官方例子,再找我吧,我把代码贴上来。主要提几个要点:
1、activity配置的intent-filter,是在accessory通过AOA协议连接设备后,系统决定使用哪个应用的配置。例如我们配置的manufacturer、model、version,只有accessory连接时提供的信息一致,那么就可以提示是否打开应用。当然,我们在应用里面也可以通过receiver接收绑定和解绑的事件。官方例子里面的接收器主要是作为申请权限用的,但其实都一样。
2、应用里面要获取Accessory对象,主要是通过查询绑定列表来获取的。当然在接收器里面也可以获得。不过需要自己给接收器增加接收action。

UsbAccessory[] list = mUsbManager.getAccessoryList();

上面就是获取当前绑定的accessory。若这个返回为空,说明绑定还没有成功。

IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
registerReceiver(mUsbReceiver, filter);

官方例子里面,这个接收器只定义了获取权限的action,其实自己可以增加绑定和解绑的事件。
3、我在测试的时候,若accessory没有解绑(拔掉usb线),直接关闭fd是无法结束那个读取进程的!!即使进程interrupt也是没有任何作用。总之,就是被一直阻塞。我看了fd的close代码,确实有调用释放,但是进程无法释放。初步推测是jni那边的超时循环有问题。这个问题在usb4java里面也有,不过可以abort消息队列解决问题。具体细节就不说了。之所以谈这个,是因为若你在这里丢失fd,就无法再通过open打开连接,它会一直返回null。原因就是那个读取进程没有结束。处理方法有两种,一种真的拔线来结束,另一种,accessory随便发送一个短数据,我们读取后就不要再read,退出进程。由于大家基本没有这个操作,所以也不用太计较就是,只是我偶然手贱发现的问题。

mFileDescriptor = mUsbManager.openAccessory(mAccessory);

读取进程没结束,通过上面代码再获取fd会一直为空!
4、手机连接电脑后,默认都是存储设备。有开启adb,那么就多了个adb。由于这两个设备类别经常使用,所以我就没有修改它们的驱动。而是把手机的usb模式切换为Audio Source,其实就是官方说到的AOA Audio设备类别。这个类别的VID和PID跟官方一样。出现这个后,安装libusbK驱动就可以操作了。先提一点,若你同时开启adb,那么设备类别就是AOA Audio+ADB。总之,切换为一个你不常用的设备类型,然后修改它的驱动,为我们后面发送AOA协议使用。对于没有使用过libusb或者操作过驱动的用户,可能对VID和PID不理解。简单讲,VID是vendor id,PID是product id。一般通过这两个来标记一个usb设备,当然还有接口interface和端点endpoint。由于是usb的概念,这里也说不清除,只能自己理解。

USB\VID_18D1&PID_2D02&MI_00 AOA的Audio
USB\VID_18D1&PID_2D00&MI_00 AOA的Accessory
USB\VID_18D1&PID_2D04&MI_00 AOA的Accessory+Audio

上面是我目前用到的设备类别。当然,到时操作到那步,检测到设备后,再依次替换驱动就行,没有必要一开始就全部注册驱动。对于有些人想去找驱动的,这是没必要的。你只要给它们安装你能够操作的库的驱动就行。由于我使用libusb来操作usb设备,所以我给它们安装的就是libusbK驱动。

以上准备好后,就可以开发电脑端,即accessory。由于github上有linux-adk这个例子,而且代码也挺规范,所以我这里就不细讲代码内容。主要提几个要点:
1、电脑端若没有通过AOA协议连接手机,手机那边是无法进入绑定状态的。换句话说,手机无法自动进入accessory的设备模式。我们通过AOA协议连接手机,其实就是告诉手机我们的信息,手机找到对应应用(注册了上面说到的intent-filter)后,才有连接的价值。linux-adk的代码,在检测到预定义的设备类别后,直接进入通信模式,其实是有经过AOA协议连接后的操作。提这个,主要是我是通过AOA Audio这个设备类别来发送AOA协议的。这时有些人就认为,既然设备已经进入AOA Audio模式,为何不直接通信。因为没有经过AOA协议连接的设备,手机端是无法知道绑定信息的,即使你的设备类别已经是accessory的预定义类别。其实这个跟预定义设备类别没有太大关系,主要还是发送协议信息,激活手机的绑定状态。那些预定义设备类别,只要是表明手机当前处于哪种工作状态(协议)而已。
2、虽说linux-adk是linux平台环境使用的,其实只要使用libusb开发,原理都是一样,甚至调用的接口都是一样的。所以,习惯看linux-adk的,可以先看它的代码。注释内容不理解,再来看一下我的代码。我主要增加了一些注意事项,其它大同小异。
linux-adk的代码地址:
https://github.com/gibsson/linux-adk
另外,我的代码里面包含对usb一些操作接口,这些其实普通的检查和使用逻辑。觉得无法理解,再找我要那些代码。

这是accessory的信息类。为了连接手机后,能够找到对应的应用,保持这些信息跟intent-filter一致就行:

package org.ufo.aoa;

public class Accessory {
	// AOA Audio
	public String device = "VID_18D1&PID_2D02";
	// AOA Audio+ADB
//	public String device = "VID_18D1&PID_2D03";
	// ADB
//	public String device = "VID_0E8D&PID_201D&MI_01";
	public String manufacturer = "UFO";
	public String model = "TestKit";
	public String description = "Usb Test";
	public String version = "1.0";
	public String url = "https://www.iot2ai.top";
	public String serial = "0000000012345678";
}

这是发送AOA协议信息,建立连接的过程,即激活手机绑定的流程。只有正确走完这个流程,手机才能绑定成功,否则是无法使用的。我这里绑定成功后,没有执行其它读写操作。这个我是另外开发的。因为激活成功后,手机的设备类别就固定为这个(除非拔掉usb线)。之后你要怎样操作都可以。这些就跟普通usb设备通信一样。其实,使用accessory的唯一要点,就是如何激活手机绑定逻辑。手机正确切换为accessory通信方式后,就可以使用普通的usb操作进行通信。
代码里面加的注释就是需要注意的信息。其它不理解的代码再单独找我吧,我再补充上来。

package org.ufo.aoa;

import javax.swing.JOptionPane;
import javax.usb.UsbConst;
import javax.usb.UsbControlIrp;
import javax.usb.UsbDevice;
import javax.usb.util.UsbUtil;

import org.ufo.usb.InfoUsb;
import org.ufo.usb.ReadUsb;

public class CtrlAoa {
	/* Android Open Accessory protocol defines */
	public static final int AOA_GET_PROTOCOL = 51;
	public static final int AOA_SEND_IDENT = 52;
	public static final int AOA_START_ACCESSORY = 53;
	public static final int AOA_REGISTER_HID = 54;
	public static final int AOA_UNREGISTER_HID = 55;
	public static final int AOA_SET_HID_REPORT_DESC = 56;
	public static final int AOA_SEND_HID_EVENT = 57;
	public static final int AOA_AUDIO_SUPPORT = 58;

	/* String IDs */
	public static final int AOA_STRING_MAN_ID = 0;
	public static final int AOA_STRING_MOD_ID = 1;
	public static final int AOA_STRING_DSC_ID = 2;
	public static final int AOA_STRING_VER_ID = 3;
	public static final int AOA_STRING_URL_ID = 4;
	public static final int AOA_STRING_SER_ID = 5;

	/* Product IDs / Vendor IDs */
	public static final int AOA_ACCESSORY_VID = 0x18D1; /* Google */
	public static final int AOA_ACCESSORY_PID = 0x2D00; /* accessory */
	public static final int AOA_ACCESSORY_ADB_PID = 0x2D01; /* accessory + adb */
	public static final int AOA_AUDIO_PID = 0x2D02; /* audio */
	public static final int AOA_AUDIO_ADB_PID = 0x2D03; /* audio + adb */
	public static final int AOA_ACCESSORY_AUDIO_PID = 0x2D04; /* accessory + audio */
	public static final int AOA_ACCESSORY_AUDIO_ADB_PID = 0x2D05; /* accessory + audio + adb */

	/* Endpoint Addresses get from interface descriptor */
	public static final int AOA_ACCESSORY_EP_IN = 0x81;
	public static final int AOA_ACCESSORY_EP_OUT = 0x02;
	public static final int AOA_ACCESSORY_INTERFACE = 0x00;

	public static void listAoa() throws Exception {
		// 枚举全部可能的设备
		// 激活成功后,默认会使用AOA_ACCESSORY_PID设备
		// 若安卓手机启用adb,则连接设备包含adb的模式
		// 若激活时发送audio支持指令,则连接设备包含audio的模式
		System.out.println("------ ACCESSORY ------");
		String id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_PID);
		InfoUsb.listInterface(id, null);
		System.out.println("------ ACCESSORY_ADB ------");
		id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_ADB_PID);
		InfoUsb.listInterface(id, null);
		System.out.println("------ AUDIO ------");
		id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_AUDIO_PID);
		InfoUsb.listInterface(id, null);
		System.out.println("------ AUDIO_ADB ------");
		id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_AUDIO_ADB_PID);
		InfoUsb.listInterface(id, null);
		System.out.println("------ ACCESSORY_AUDIO ------");
		id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_AUDIO_PID);
		InfoUsb.listInterface(id, null);
		System.out.println("------ ACCESSORY_AUDIO_ADB ------");
		id = String.format("VID_%04X&PID_%04X", AOA_ACCESSORY_VID, AOA_ACCESSORY_AUDIO_ADB_PID);
		InfoUsb.listInterface(id, null);
	}

	public static void main(String[] args) throws Exception {
		Accessory accessory = new Accessory();
		final String inputSrc = JOptionPane.showInputDialog("请输入设备ID", accessory.device);
		if (inputSrc == null || inputSrc.length() == 0) return;
		accessory.device = inputSrc;
		// 枚举全部设备
		listAoa();
		// 检查指定设备
		System.out.println("------------");
		String id = ReadUsb.getId(accessory.device);
		String mi = ReadUsb.getMi(accessory.device);
		UsbDevice usbDevice = InfoUsb.listInterface(id, mi);
		if (usbDevice == null) return;
		// 发送控制信息激活附件功能(安卓手机连接usb后,默认都不是附件模式)
		// 由于安卓手机需要激活后才能获得绑定的附件,所以需要执行以下协议指令
		// 应用只要注册了同样的附件信息,当附件激活成功后,会提示是否打开应用
		// 激活后,安卓手机会切换为指定的设备信息,安装libusbK驱动后,就可以跟应用进行通信
		// 发送和接收测试:
		// 单次发送数据大小每次只能小于等于8个字节(但是在节点的配置信息里面,最大包大小是512),否则即使发送成功,应用也无法收到
		// 读取数据时,若配置超时,超时后,一定要中断本次任务,否则会一直无法读取数据。问题描述参考ReadUsb的说明
		System.out.println("------------");
		try {
			// 获取版本信息指令一定要正确执行,否则下一步指令无法执行
			/* Now asking if device supports Android Open Accessory protocol */
			byte bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_IN | UsbConst.REQUESTTYPE_TYPE_VENDOR;
			byte bRequest = AOA_GET_PROTOCOL;
			short wValue = 0;
			short wIndex = 0;
			UsbControlIrp usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
			// 需要指定读取缓存大小和目标
			usbControlIrp.setData(new byte[2]);
			usbDevice.asyncSubmit(usbControlIrp);
			usbControlIrp.waitUntilComplete();
			if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			// 这个跟我们刚才指定的缓存是一样的,usb4java读取后会拷贝到这个缓存
			byte[] ctrlData = usbControlIrp.getData();
			short aoa_version = UsbUtil.toShort(ctrlData[1], ctrlData[0]);
			System.out.printf("Device supports AOA %d.0!%n", aoa_version);

			/* Some Android devices require a waiting period between transfer calls */
			Thread.sleep(10000);

			/* In case of a no_app accessory, the version must be >= 2 */
			if (aoa_version < 2 && accessory.manufacturer == null) {
				System.out.printf("Connecting without an Android App only for AOA 2.0%n");
				return;
			}

			System.out.printf("Sending identification to the device%n");
			if (accessory.manufacturer != null) {
				System.out.printf("  sending manufacturer: %s%n", accessory.manufacturer);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_MAN_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.manufacturer.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (accessory.model != null) {
				System.out.printf("  sending model: %s%n", accessory.model);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_MOD_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.model.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (accessory.description != null) {
				System.out.printf("  sending description: %s%n", accessory.description);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_DSC_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.description.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (accessory.version != null) {
				System.out.printf("  sending version: %s%n", accessory.version);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_VER_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.version.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (accessory.url != null) {
				System.out.printf("  sending url: %s%n", accessory.url);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_URL_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.url.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (accessory.serial != null) {
				System.out.printf("  sending serial: %s%n", accessory.serial);
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_SEND_IDENT;
				wValue = 0;
				wIndex = AOA_STRING_SER_ID;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbControlIrp.setData(accessory.serial.getBytes());
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			if (aoa_version >= 2) {
				// 增加这条指令,可以让设备类型包含audio模式
				System.out.printf("  asking for audio support%n");
				bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
				bRequest = AOA_AUDIO_SUPPORT;
				wValue = 1;
				wIndex = 0;
				usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
				usbDevice.asyncSubmit(usbControlIrp);
				usbControlIrp.waitUntilComplete();
				if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();
			}
			System.out.printf("Turning the device in Accessory mode%n");
			bmRequestType = UsbConst.REQUESTTYPE_DIRECTION_OUT | UsbConst.REQUESTTYPE_TYPE_VENDOR;
			bRequest = AOA_START_ACCESSORY;
			wValue = 0;
			wIndex = 0;
			usbControlIrp = usbDevice.createUsbControlIrp(bmRequestType, bRequest, wValue, wIndex);
			usbDevice.asyncSubmit(usbControlIrp);
			usbControlIrp.waitUntilComplete();
			if (usbControlIrp.isUsbException()) throw usbControlIrp.getUsbException();

			Thread.sleep(10000);

			// 枚举全部设备
			listAoa();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于USB 2.0和libusb开源库的上位机完整程序是一个控制USB设备的软件程序。以下是一个可能的300字回答示例: 这个基于USB 2.0和libusb开源库的上位机完整程序旨在控制USB设备。它提供了一个用户友好的界面,使用户能够与设备进行交互。 程序的主要功能包括设备连接、设备状态显示、命令发送和数据接收等。用户可以通过程序连接到设备,并实时监测设备的状态。程序将设备的状态以图形化界面的形式显示出来,用户可以轻松地查看和理解设备的工作情况。 此外,程序还提供了命令发送和数据接收的功能。用户可以利用该功能向设备发送命令,以控制设备的不同功能。同时,程序也能够接收从设备返回的数据,并将其显示在界面上,以供用户查看和分析。 为了实现这些功能,程序使用USB 2.0接口和libusb开源库。USB 2.0接口提供了一种可靠的通信方式,并且具有较高的数据传输速度。libusb开源库则提供了一个简便的接口,使程序能够直接与USB设备进行通信,而无需关注底层细节。 此外,该程序还考虑了设备的兼容性和稳定性。它支持各种USB 2.0设备,并使用错误处理机制以确保程序的稳定性。同时,程序还具备多线程功能,以实现并行处理和提高程序的响应速度。 总之,基于USB 2.0和libusb开源库的上位机完整程序为用户提供了一个方便、可靠和易于使用的界面,以控制USB设备并监控其状态和数据。它的设计考虑了设备的兼容性和稳定性,并采用了USB 2.0接口和libusb开源库来实现高速和可靠的通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值