Android Things 现在支持 USB Host(从 DP3 后),它允许用户空间的 Android 应用程序与定制的 USB 设备通话。
为了探索这个功能,我们将创建一个自定义 USB 传感器,并通过 USB 将所有事件转发到 Android Things 板。
您可以在下面看到我们将实现的视频:USB 设备将是一个 Arduino NFC 阅读器。当扫描标签(或者amiibo)时,Arduino 会通过 USB 将 NFC 数据转发到 Android Things 板。
Android Things + USB + Arduino + NFC
有两种不同的方式通过 Android Things 与 USB 设备进行通信。
/dev/tty*
设备句柄,则不需要使用任何 USB Host API。而是尝试调用 PeripheralManagerService.getUartDeviceList()
。如果您可以看到一个新的 UART 结点,这意味着您可以直接使用 UART API 与 USB 设备进行通信。根本不需要使用 USB API(如果你不需要的话)。/dev/tty*
(例如,内核没有内置的驱动程序),则不得不使用 USB 层。您将需要操作 USB Host API,它允许常规用户空间应用程序与 USB 设备进行通信,而无需 root 权限或 Linux 内核所需的支持。在之前的文章中,我们已经看到了如何通过 UART 与串口设备进行通信。这一次,我们将使用 USB Host API,并且将使用第三方库执行串行通信。这样,我们在这里写的代码将不会特定于 Android Things,但也可以在任何Android(3.1及以上)的智能手机上工作。
我们将从简单的事情开始:我们首先要让 Arduino 不断在串行端口上发送 “Hello!” ,波特率为 115200。
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.println("Hello!");
delay(1000);
}
当我们将 Arduino 连接到 Android Things 板时,我们希望我们的应用程序每秒能够收到 “Hello!” 文本。 为此,我们首先要编辑 AndroidManifest.xml
。
我们希望在外部 USB 设备连接到 Android 设备时收到通知。它可以添加一个新的 intent-filter
条目到 activity
中,当 USB 设备插入时应该由系统启动。此外,我们将添加一个元数据元素,指向外部 XML 资源文件(在 res/xml/device_filter.xml
中),该文件声明了有关我们要检测的设备的标识信息。
<activity launchMode="singleTop"...>...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter"/>
</activity>
如果 device_filter.xml
文件具有以下内容,则每次插入任何 USB 设备时都会收到通知:
<resources>
<usb-device/>
</resources>
这不完全是我们想要的。 我们只想在 Arduino 插入时通知,而忽略所有其他 USB 设备,所以我们将添加一个特定的规则。
将 Arduino 连接到 Android Things 板后,我们可以使用 dmesg
查看内核日志以获取一些设备信息:
$ adb shell dmesg
New USB device found, idVendor=2341, idProduct=0001
New USB device strings: Mfr=1, Product=2, SerialNumber=220
Product: Arduino Uno
Manufacturer: Arduino (www.arduino.cc)
我们只想在 Arduino(idVendor = 0x2341)被连接时得到通知,所以我们将这个供应商 id 指定到 usb-device
标签中:
<usb-device vendor-id="9025"/>
请注意,vendor-id
预期十进制值,而不是十六进制值。
这个过滤器对我们来说已经足够了。有关我们可以做什么的完整列表,请参阅 USB Host 文档
现在,我们的 Activity 在每次接入 Arduino 时都会收到一个意图(Intent)。
首先,我们将列出所有连接的 USB 设备,如果发现 Arduino,则打开一个 USB 连接:
UsbManager usbManager = getSystemService(UsbManager.class);
Map<String, UsbDevice> connectedDevices = usbManager.getDeviceList();
for (UsbDevice device : connectedDevices.values()) {
if (device.getVendorId() == 0x2341 && device.getProductId() == 0x0001) {
Log.i(TAG, "Device found: " + device.getDeviceName());
startSerialConnection(usbManager, device);
break;
}
}
startSerialConnection
方法将使用 felHR85 的 USBSerial 库,来打开 Arduino 和 Android 设备之间的串行连接:
void startSerialConnection(UsbManager usbManager, UsbDevice device) {
UsbDeviceConnection connection = usbManager.openDevice(device);
UsbSerialDevice serial = UsbSerialDevice.createUsbSerialDevice(device, connection);
if (serial != null && serial.open()) {
serial.setBaudRate(115200);
serial.setDataBits(UsbSerialInterface.DATA_BITS_8);
serial.setStopBits(UsbSerialInterface.STOP_BITS_1);
serial.setParity(UsbSerialInterface.PARITY_NONE);
serial.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF);
serial.read(mCallback);
}
}
UsbSerialDevice.read()
方法需要一个 UsbReadCallback
引用,每次接收数据时都会调用它。以下是一个简单的实现:
UsbSerialInterface.UsbReadCallback mCallback = (data) -> {
String dataStr = new String(data, "UTF-8");
Log.i(TAG, "Data received: " + dataStr);
};
我们将 byte[]
数据转换为 UTF-8 字符串,并记录这些数据。
现在,每次通过 USB 从 Arduino 发送数据时,都会触发回调,并记录数据。 我们可以运行 adb logcat
来确认,我们每秒收到来自 Arduino 的 “Hello!” 消息:
Data received: Hello!
Data received: Hello!
Data received: Hello!
要获得完整和优化的源代码,请查看 github.com/Nilhcem/usbfun-androingsings。
每一秒输出 “你好!” 到日志都很无聊。
为这个项目添加一些乐趣的快速方法是,将 NFC 模块连接到 Arduino,并通过 USB 将此模块的 NFC 标签数据发送给 Android Things 开发板。
我使用 Elechouse 的 PN532 模块通过 SPI,使用的是这个 Arduino 程序。
为了简化,我只是将标签 ID 转发到 Android Things 开发板。Android 应用根据收到的 ID 显示合适的图像。
正如介绍中所解释的,该项目使用 Android SDK 中的 USB Host API,因此完全兼容任何 Android 智能手机或平板电脑(minSdk=12)。如果您拥有 USB-OTG 电缆,则可以直接将 Arduino 插入手机。
下面是一个 Android 手机的演示(带声音),通过 USB 将数据发送到 Arduino 上,在压电式蜂鸣器上播放音乐。(源代码在这里)
Android smartphone + Arduino + Buzzer
但是,还是有一个区别:连接 USB 设备时,将显示一个 UI 对话框,用户需要授予 USB 访问设备的权限。检查权限是否被授予在我们编写的 Android Things 源代码项目上跳过了,因为与运行时权限类似,您不必检查/请求 Android Things 上的 USB 权限,因为可能没有附加的显示,因此,没有办法让用户授予这些权限。 他们是默认允许的。
USB Host API 可以做的一个更好的例子是官方的 USB 枚举示例。该项目遍历主机发现的所有 USB 设备,并打印其接口和端点。
阅读源代码是有趣的,因为我们可以了解 USB 描述符。
例如,在检测到 USB 设备后,使用我们在本文中使用的相同 API(通过 UsbManager.openDevice()
)打开 USB 设备,然后代码立即查询 USB 设备和配置,而不是打开串行连接 (s)描述符:
connection.controlTransfer(0x80, 0x06, 0x100, 0x00, buffer, length, timeout);
int deviceClass = (buffer[4] & 0xFF);
int deviceProtocol = (buffer[6] & 0xFF);
int vendorId = (buffer[8] & 0xFF) + ((buffer[9] & 0xFF) << 8);
int productId = (buffer[10] & 0xFF) + ((buffer[11] & 0xFF) << 8);
读取 USB 规格时,指定所有 USB 设备必须至少支持一个默认端点。以此端点为目标的传输被称为控制传输,并且是主机获取设备信息的一种方式。
0x80
是数据方向。 这里的意思是 “IN”(从 USB 设备到主机)0x06
是获取描述符的请求类型0x100
用于设备描述符(0x200
用于配置描述符)0x00
是描述符索引(默认描述符)然后,代码接收来自缓冲区的数据,如文档中所述。
+--------+------+---------------------------------------+
| Offset | Size | Description |
+--------+------+---------------------------------------+
| 4 | 1 | Device class code |
| 6 | 1 | Protocol code |
| 8 | 2 | Vendor ID (assigned by USB.org) |
| 10 | 2 | Product ID (assigned by Manufacturer) |
+--------+------+---------------------------------------+
等等。一旦我们明白,示例代码更容易阅读。
这篇文章介绍了如何将 USB Host API 与 Android Things 结合使用的例子。
在真实场景中,您不需要使用 Arduino 将 NFC 支持带到 Android Things,因为您可以直接开发 NFC 驱动程序,即使您需要,也可以使用 Arduino 与 Arduino 进行通信直接的 UART API。您可能不需要使用 USB Host API + 第三方依赖项。
如果您拥有一个USB串行设备,并且如果后者被内核识别,那么使用 UART API 是在两个设备之间进行通信的最简单和推荐的方式。但是,如果您需要从应用程序访问任何类型的 USB 设备,那么您将很高兴在 Android Things 上拥有用户空间 USB 支持。
正如 Marcos Placona 在博客中解释的,一些组件需要超高速的驱动程序(发送几微秒内从 vcc 到地的信号)。尽管 Android Things 还无法实现,但您可以使用微控制器作为代理,并通过 USB 将数据导向 Android Things 板,以便在 Android Things 项目中使用这些组件。
观光\评论区