如何仅使用单个输入端点将数据写入 Android 中的 USB HID 设备

How to write data to USB HID device in Android with only a single input endpoint

我有一个 USB HID 设备,我想与之通信。我使用 HidSharp 库 (link: https://github.com/treehopper-electronics/HIDSharp) 在 Windows 上成功地做到了这一点。我的 Windows 应用程序是使用 .NET Framework 4.5、C# 和 Visual Studio 开发的。

我现在想从 Android 平板电脑而不是 Windows 台式机与同一个 USB HID 设备通信。我这样做遇到了一些问题。当我将设备插入我的平板电脑时,它会报告具有单个 "read" 端点的单个接口。这是向我报告的内容:

Interface #0 
Class: Human Interaction Device (0x3)
Endpoint: #0
Address        : 0x81 (10000001)
Number         : 1
Direction      : Inbound (0x80)
Type           : Intrrupt (0x3)
Poll Interval  : 1
Max Packet Size: 64
Attributes     : 000000011

如您所见,它只报告一个端点,即入站端点。我需要能够向该设备输出简单的命令,我能够使用 HidSharp 在 Windows 上成功地做到这一点。

HidSharp 将所有内容抽象为一个 "stream" 您可以读取和写入的对象。使用 Android API,没有单个 "stream" 对象,而是似乎有 3 种不同的 reading/writing 方式:批量传输、控制传输和 USB 请求。我试过使用所有 3 个发送数据,但似乎没有成功。

有什么建议吗?为什么我可以在 Windows 上向此设备发送数据,但似乎不能从 Android 发送数据?有没有办法将单个端点同时用作读取和写入端点?有什么我明显遗漏和不理解的东西吗?

我使用 Xamarin 作为我的开发环境(C#,Visual Studio 2017)。由于代码总是有用的,下面是我连接到设备的方式:

int VendorID = 0x04d8;
int ProductID = 0x2742;
UsbManager USB_Manager = null;
UsbDevice USB_Device = null;
UsbDeviceConnection DeviceConnection = null;
UsbInterface DeviceInterface = null;
UsbEndpoint OutputEndpoint = null;
UsbEndpoint InputEndpoint = null;

//Grab the Android USB manager and get a list of connected devices
var USB_Manager = MyMainActivity.ApplicationContext.GetSystemService(Android.Content.Context.UsbService) as Android.Hardware.Usb.UsbManager;
var attached_devices = USB_Manager.DeviceList;

//Find the device in the list of connected devices
foreach (var d in attached_devices.Keys)
{
    if (attached_devices[d].VendorId == VendorID && attached_devices[d].ProductId == ProductID)
    {
        USB_Device = attached_devices[d];
        break;
    }
}

//Assuming we found the correct device, let's set everything up
if (USB_Device != null)
{
    for (int j = 0; j < USB_Device.InterfaceCount; j++)
    {
        DeviceInterface = USB_Device.GetInterface(j);
        for (int i = 0; i < DeviceInterface.EndpointCount; i++)
        {
            var temp_ep = DeviceInterface.GetEndpoint(i);
            if (temp_ep.Type == Android.Hardware.Usb.UsbAddressing.XferInterrupt)
            {
                if (temp_ep.Direction == Android.Hardware.Usb.UsbAddressing.In)
                {
                    InputEndpoint = temp_ep;
                }

                if (temp_ep.Direction == Android.Hardware.Usb.UsbAddressing.Out)
                {
                    OutputEndpoint = temp_ep;
                }
            }
        }
    }

    //Request permission to communicate with this USB device
    UsbReceiver receiver = new UsbReceiver();
    PendingIntent pending_intent = PendingIntent.GetBroadcast(Game.Activity, 0, new Android.Content.Intent(UsbReceiver.ACTION_USB_PERMISSION), 0);
    IntentFilter intent_filter = new IntentFilter(UsbReceiver.ACTION_USB_PERMISSION);
    Game.Activity.RegisterReceiver(receiver, intent_filter);
    USB_Manager.RequestPermission(USB_Device, pending_intent);
    bool has_permission = USB_Manager.HasPermission(USB_Device);
    var device_connection = USB_Manager.OpenDevice(USB_Device);
    device_connection.ClaimInterface(DeviceInterface, true);
    DeviceConnection = device_connection;
}

接下来,这是我尝试从设备读取的方式:

//3 methods of attempting to read from the device

//Method 1:
byte[] inpt = new byte[64];
var request = new UsbRequest();
request.Initialize(DeviceConnection, InputEndpoint);
var byte_buffer = ByteBuffer.Allocate(64);
request.Queue(byte_buffer, 64);
DeviceConnection.RequestWait();
byte_buffer.Rewind();
for(int i = 0; i < 64; i++)
{
    inpt[i] = (byte) byte_buffer.Get();
}

//Method 2:
byte[] inpt = new byte[64];
DeviceConnection.BulkTransfer(InputEndpoint, inpt, inpt.Length, 1000);

//Method 3:
byte[] inpt = new byte[64];
DeviceConnection.ControlTransfer(UsbAddressing.In, 0, 0, 0, inpt, 64, 1000);

最后,这是我尝试将数据写入此设备的方式:

//Method 1:
byte[] output_msg; //This variable is assigned elsewhere in the code
DeviceConnection.BulkTransfer(OutputEndpoint, output_msg, output_msg.Length, 30);

//Method 2:
byte[] output_msg; //This variable is assigned elsewhere in the code
DeviceConnection.ControlTransfer(UsbAddressing.Out, 0, 0, 0, output_msg, output_msg.Length, 1000);

//Method 3:
byte[] output_msg; //This variable is assigned elsewhere in the code
var write_request = new UsbRequest();
write_request.Initialize(DeviceConnection, OutputEndpoint);
var byte_buffer_write = ByteBuffer.Wrap(output_msg);
request.Queue(byte_buffer_write, output_msg.Length);
DeviceConnection.RequestWait();

"OutputEndpoint" 通常为空,因为没有输出端点,所以我经常用 "InputEndpoint" 替换 "OutputEndpoint",但没有成功。

如有任何帮助,我们将不胜感激!谢谢!!!

您正在处理 HID 设备,这意味着您应该进行中断传输。

在 Android 中,您应该使用 UsbRequest 来执行中断传输(就像它执行异步非阻塞 IO 一样)。

端点是单向的,可用于入站和出站(但不能同时使用)

如果端点是入站的,则使用 UsbRequest 提交 Urb 并像您之前尝试的那样排队,但使用具有预期 bufferLength 的空缓冲区。

RequestWait 将 return UsbRequest 对象在完成后返回。

如果 usbRequest.getEndPoint().getDirection() 是入站的,那么您的缓冲区变量将使用来自设备的读取缓冲区进行更新。 如果 usbRequest.getEndpoint().getDirection() 出站,那么您应该传递缓冲区以将数据写入设备