我们希望克服的基本问题是通过USB从作为主机和电源的外部源(PC)传输数据到Android设备。由于PC将能够供电,并使产品的最终消费者更容易,我们希望避免OTG电缆,因此在附件模式下运行Android设备。
我设法让Android设备进入附件模式并识别出有USB主机连接到它,甚至能够读取主机的控制数据。但是,我似乎无法弄清楚实际的批量数据传输。
除了简单地设置初始连接之外,官方的android文档似乎在这个主题上提供的很少。
我尽可能多地从关于这个主题的几个来源,主要来自here,以及其他地方,在网上挽救尽可能多的代码。
在Android代码中,usbThread开始运行,甚至可以访问附件并正确打印出其制造商,型号等。但是,从inputStream读取始终会导致IOError。
在主机PC上的c ++代码中,一切都顺利运行并连接到transferTest,其中报告第一次批量传输超时和零字节被传输。
我在运行c ++代码之前总是杀掉ADB。 android文档说要发送一个set_configuration(1),我试图无济于事。他们还谈到ADB有第二个接口,我在输出中没有看到。我确实看到了另一种接口配置,但设置和使用它根本不起作用。
是否需要进行其他设置?我错过了一些基本的USB协议吗? Android代码应该只对inputStream进行连续轮询吗?配件模式是否仍然支持这个时代?
相关代码如下所示
MainActivity.java
//<company> contains actual reversed Internet domain name
private static final String ACTION_USB_PERMISSION = "<company>.USB_PERMISSION";
UsbManager usbManager;
usbThread thread;
boolean usbStarted = false;
boolean resumeRan = false;
boolean threadCreated = false;
UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
private void openAccessory() {
Log.d(TAG, "openAccessory: " + accessory);
fileDescriptor = usbManager.openAccessory(accessory);
if (fileDescriptor != null)
{
usbStarted = true;
FileDescriptor fd = fileDescriptor.getFileDescriptor();
inputStream = new FileInputStream(fd);
outputStream = new FileOutputStream(fd);
thread = new usbThread();
thread.start();
}
}
private final BroadcastReceiver permissionReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action))
{
Log.d(TAG, "Asking for usb permission");
synchronized (this)
{
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
{
if((!usbStarted) && resumeRan)
{
resumeRan = false;
Log.d(TAG, "Model: " + accessory.getModel());
openAccessory();
}
}
else
{
Log.d(TAG, "permission denied for device ");
}
}
}
}
};
Handler usbHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
byte res = msg.getData().getByte("val");
Log.d(TAG, "Byte " + Byte.toString(res));
}
};
private class usbThread extends Thread
{
boolean bueno = true;
usbThread()
{
if(!threadCreated)
{
threadCreated = true;
}
else
{
bueno = false;
Log.d(TAG, "Tried to open second thread");
}
}
public void run()
{
while(usbStarted && bueno)
{
byte[] buffer = new byte[10];
int ret = 0;
try
{
int av = inputStream.available();
ret = inputStream.read(buffer, 0, av);
} catch (IOException e)
{
e.printStackTrace();
//Log.d("MMI_IO_READ", "IOException on buffer read.");
}
if(ret > 0)
{
//Log.d("MMI_IO_READ", "Ret = " +ret);
for(int i = 0; i < ret; i++)
{
Message m = usbHandler.obtainMessage();
Bundle b = new Bundle();
b.putByte("val", buffer[i]);
m.setData(b);
usbHandler.sendMessage(m);
}
}
}
Log.d(TAG, "Thread Shutdown");
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbDetachReceiver , filter);
filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(permissionReceiver, filter);
}
@Override
protected void onResume()
{
super.onResume();
UsbAccessory[] accessoryList = usbManager.getAccessoryList();
if (accessoryList != null)
{
if(!usbStarted)
{
Log.d(TAG, "Resume, usb not started");
accessory = accessoryList[0];
Log.d(TAG, "Manufacturer " + accessory.getManufacturer());
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(ACTION_USB_PERMISSION), 0);
resumeRan = true;
usbManager.requestPermission(accessory, mPermissionIntent);
}
}
}
请注意,正如this thread等建议,以及我自己的经验,USB_ACCESSORY_ATTACH意图永远不会触发。因此,USB检测依赖于一个不稳定的onResume检查。
HostUSB.cpp
#include <stdio.h>
#include <libusb-1.0/libusb.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <iostream>
#define ACCESSORY_VID 0x18d1
#define ACCESSORY_PID 0x2D00
#define ACCESSORY_ADB_PID 0x2D01
#define INTERFACE 0
const char* manufacturer = "PCHost";
const char* modelName = "PCHost1";
const char* description = "Description";
const char* version = "1.0";
const char* uri = "http://example.com";
const char* serialNumber = "666";
//static
static void error(int code)
{
fprintf(stdout, "%s\n", libusb_strerror((libusb_error)code));
}
static int shutdown(libusb_device_handle *handle)
{
if(handle)
{
libusb_release_interface(handle, INTERFACE);
libusb_close(handle);
}
libusb_exit(NULL);
return 0;
}
static int init()
{
libusb_init(NULL);
libusb_set_debug(NULL, 3); //Verbose debugging level
return 0;
}
// Send AOA specified introdction control information.
static int androidIntroduction(libusb_device_handle *handle)
{
unsigned char ioBuffer[2];
int devVersion;
int response;
response = libusb_control_transfer(
handle, //handle
0xC0, //bmRequestType
51, //bRequest
0, //wValue
0, //wIndex
ioBuffer, //data
2, //wLength
100 //timeout
);
fprintf(stdout,"Sent getProtocol\n");
if(response < 0)
{error(response);return -1;}
fprintf(stdout,"Response \n");
devVersion = ioBuffer[1] << 8 | ioBuffer[0];
if(!(devVersion == 1 || devVersion==2))
return -1;
fprintf(stdout,"Version Code Device: %d\n", devVersion);
usleep(1000);//sometimes hangs on the next transfer :(
response = libusb_control_transfer(handle,0x40,52,0,0,(unsigned char*)manufacturer,strlen(manufacturer)+1,0);
if(response < 0)
{error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,1,(unsigned char*)modelName,strlen(modelName)+1,0);
if(response < 0)
{error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,2,(unsigned char*)description,strlen(description)+1,0);
if(response < 0)
{error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,3,(unsigned char*)version,strlen(version)+1,0);
if(response < 0)
{error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,4,(unsigned char*)uri,strlen(uri)+1,0);
if(response < 0)
{error(response);return -1;}
response = libusb_control_transfer(handle,0x40,52,0,5,(unsigned char*)serialNumber,strlen(serialNumber)+1,0);
if(response < 0)
{error(response);return -1;}
fprintf(stdout,"Accessory Identification sent\n");
return 1;
}
// Send introduction information to given handle, then try to put it into
// accessory mode and catch it once it reconnects.
static libusb_device_handle* setupAccessory(libusb_device_handle *handle)
{
int response;
response = androidIntroduction(handle);
if(response < 0)
return NULL;
response = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
if(response < 0){error(response);return NULL;}
fprintf(stdout,"Attempted to put device into accessory mode\n");
libusb_device_handle *androidHandle;
int tries = 4;
while(true)
{
tries--;
if((androidHandle = libusb_open_device_with_vid_pid(NULL, ACCESSORY_VID, ACCESSORY_ADB_PID)) == NULL)
{
if((androidHandle = libusb_open_device_with_vid_pid(NULL, ACCESSORY_VID, ACCESSORY_PID)) == NULL)
{
if(tries < 0)
{
std::cout << "Could not..." << '\n';
return NULL;
}
}
else
{
break;
}
}else
{
break;
}
usleep(1000000);
}
return androidHandle;
}
//Find the first Bulk OUT Enpoint of the given device.
uint8_t findBulkOut (libusb_device *device)
{
libusb_config_descriptor *con_desc;
libusb_get_active_config_descriptor(device, &con_desc);
const libusb_interface *interfaceList = con_desc->interface;
uint16_t numInterface = con_desc->bNumInterfaces;
for(int j = 0; j<numInterface; j++)
{
libusb_interface interface = interfaceList[j];
const libusb_interface_descriptor *intDescList = interface.altsetting;
int numAlt = interface.num_altsetting;
for(int p = 0; p < numAlt; p++)
{
libusb_interface_descriptor intDesc = intDescList[p];
uint8_t numEnd = intDesc.bNumEndpoints;
const libusb_endpoint_descriptor *ends = intDesc.endpoint;
for(int k = 0; k < numEnd; k++)
{
libusb_endpoint_descriptor endpoint = ends[k];
uint8_t type = 0x03 & endpoint.bmAttributes;
uint8_t address = endpoint.bEndpointAddress;
switch (type) {
case LIBUSB_TRANSFER_TYPE_CONTROL:
break;
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
break;
case LIBUSB_TRANSFER_TYPE_BULK:
if(!(address & LIBUSB_ENDPOINT_IN)) //LIBUSB_ENPOINT_OUT is simply 0000, can't AND that...
{
return address;
}
break;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
break;
case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
break;
}
}
}
}
}
//Basically findBulkOut, but with output for EndPoints.
void printEnds (libusb_device *device)
{
libusb_config_descriptor *con_desc;
libusb_get_active_config_descriptor(device, &con_desc);
const libusb_interface *interfaceList = con_desc->interface;
uint16_t numInterface = con_desc->bNumInterfaces;
for(int j = 0; j<numInterface; j++)
{
libusb_interface interface = interfaceList[j];
const libusb_interface_descriptor *intDescList = interface.altsetting;
int numAlt = interface.num_altsetting;
for(int p = 0; p < numAlt; p++)
{
libusb_interface_descriptor intDesc = intDescList[p];
uint8_t numEnd = intDesc.bNumEndpoints;
const libusb_endpoint_descriptor *ends = intDesc.endpoint;
fprintf(stdout, "Interface %d. altSetting %d. Num of endpoints: %d\n", p, intDesc.bInterfaceNumber, numEnd);
for(int k = 0; k < numEnd; k++)
{
libusb_endpoint_descriptor endpoint = ends[k];
uint8_t type = 0x03 & endpoint.bmAttributes;
uint8_t address = endpoint.bEndpointAddress;
fprintf(stdout, "Endpoint type ");
switch (type) {
case LIBUSB_TRANSFER_TYPE_CONTROL:
std::cout << "Control";
break;
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
std::cout << "Isochronus";
break;
case LIBUSB_TRANSFER_TYPE_BULK:
std::cout << "Bulk";
break;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
std::cout << "Interupt";
break;
case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
std::cout << "Bulk Stream";
break;
}
std::cout << " ";
std::cout << (address & LIBUSB_ENDPOINT_IN ? "IN" : "OUT");
std::cout << '\n';
fprintf(stdout, "Address %04X\n", address);
}
}
}
}
// Go through all connected devices. If they are do not have Google PID
// and VID, try to find out if they are Android devices. If they (most likely)
// are, try to put them in accessory mode. If successful, return that handle
libusb_device_handle* getAndroidHandle()
{
libusb_device **list;
ssize_t cnt = libusb_get_device_list(NULL, &list);
ssize_t i = 0;
int err = 0;
if (cnt < 0)
error(0);
for (i = 0; i < cnt; i++)
{
fprintf(stdout,"\nAttempted index %d\n", (int)i);
libusb_device *device = list[i];
libusb_device_descriptor dev_desc;
libusb_get_device_descriptor(device, &dev_desc);
uint16_t VID = dev_desc.idVendor;
uint16_t PID = dev_desc.idProduct;
fprintf(stdout, "VID: %04X. PID: %04X\n", VID, PID);
libusb_device_handle *handle;
int response = libusb_open(device, &handle);
if(response < 0)
{error(response);continue;}
libusb_set_auto_detach_kernel_driver(handle, 1);
libusb_device_handle *androidHandle;
if(VID == ACCESSORY_VID && (PID == ACCESSORY_PID || PID == ACCESSORY_ADB_PID))
{
int r = androidIntroduction(handle);
if (r != 1)
continue;
androidHandle = handle;
}
else
{
androidHandle = setupAccessory(handle);
libusb_close(handle);
}
if (androidHandle)
{
libusb_free_device_list(list, 1);
std::cout << "\n\nAndroid Found:" << '\n';
printEnds(libusb_get_device(androidHandle));
return androidHandle;
}
}
libusb_free_device_list(list, 1);
return NULL;
}
//Try to send data.
static int transferTest(libusb_device_handle *handle)
{
const static int PACKET_BULK_LEN=64;
const static int TIMEOUT=5000;
int r,i;
int transferred;
usleep(1000000); //1s
//libusb_set_configuration(handle, 1);
r = libusb_claim_interface(handle, INTERFACE);
if(r < 0)
{error(r); return -1;}
fprintf(stdout, "Interface claimed, ready to transfer data\n");
// TEST BULK IN/OUT
usleep(100000);// 0.1s
uint8_t outAddress = findBulkOut(libusb_get_device(handle));
fprintf(stdout, "Trying to write to %04X\n", outAddress );
char answer[PACKET_BULK_LEN];
char question[PACKET_BULK_LEN];
for (i=0;i<PACKET_BULK_LEN; i++) question[i]=i;
// ***TIMES OUT HERE***
r = libusb_bulk_transfer(handle, outAddress, (unsigned char*)question, PACKET_BULK_LEN,
&transferred,TIMEOUT);
if (r < 0)
{
fprintf(stderr, "Bulk write error %d\n", r);
error(r);
fprintf(stderr, "Number of bytes written %d\n", transferred);
return r;
}
fprintf(stdout, "Wrote %d bytes", transferred);
/*r = libusb_bulk_transfer(handle, ENDPOINT_BULK_IN, (unsigned char*)answer,PACKET_BULK_LEN,
&transferred, TIMEOUT);
if (r < 0)
{
fprintf(stderr, "Bulk read error %d\n", r);
error(r);
return r;
}
fprintf(stdout, "Read %d bytes", r);
if (transferred < PACKET_BULK_LEN)
{
fprintf(stderr, "Bulk transfer short read (%d)\n", r);
error(r);
return -1;
}
printf("Bulk Transfer Loop Test Result:\n");
// for (i=0;i< PACKET_BULK_LEN;i++) printf("%i, %i,\n ",question[i],answer[i]);
for(i = 0;i < PACKET_BULK_LEN; i++)
{
if(i%8 == 0)
printf("\n");
printf("%02x, %02x; ",question[i],answer[i]);
}
printf("\n\n");
*/return 0;
}
int main (int argc, char *argv[])
{
fprintf(stdout, "OUT flag %04X IN flag %04X\n", LIBUSB_ENDPOINT_OUT, LIBUSB_ENDPOINT_IN);
fprintf(stdout, "Shifted in %04X\n", LIBUSB_ENDPOINT_IN >> 7);
if(init() < 0)
return -1;
libusb_device_handle *handle = getAndroidHandle();
if(!handle)
{
fprintf(stdout, "\nError setting up accessory\n");
shutdown(NULL);
return -1;
};
if(transferTest(handle) < 0)
{
fprintf(stdout, "\nError in transferTest\n");
shutdown(handle);
return -1;
}
shutdown(handle);
fprintf(stdout, "\nFinished\n");
return 0;
}
预先感谢您的回复! :)
修改
似乎是inputStream.available(),它是主要的罪魁祸首,似乎没有正常工作(我从一些随机的谷歌搜索中发现)。无论正确设置如何,它都会抛出IOException。我还取消了libusb_set_configuration的注释,并在确保它正常工作后设置了一点延迟。
旁注:我没有杀死ADB服务器以使USB连接正常工作。如果设备将自身引入0x2D01作为PID,则ADB日志记录工作正常!
答案 0 :(得分:1)
这是我的Java代码,它可以帮助你。
如果在AOA模式之后添加libusb_set_configuartion,上面的C ++代码也可以工作。
import android.app.PendingIntent;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
//import java.util.logging.Logger;
public class MainActivity extends Activity implements Runnable
{
private static final String ACTION_USB_PERMISSION = "com.examples.accessory.controller.action.USB_PERMISSION";
private final String TAG = "_ITE";
private UsbManager mUsbManager;
private PendingIntent mPermissionIntent;
private boolean mPermissionRequestPending;
UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mUsbReceiver, filter);
setContentView(R.layout.activity_main);
//enableControls(false);
}
@Override
public void onResume()
{
super.onResume();
Intent intent = getIntent();
if (mInputStream != null && mOutputStream != null) {
return;
}
UsbAccessory[] accessories = mUsbManager.getAccessoryList();
UsbAccessory accessory = (accessories == null ? null : accessories[0]);
if (accessory != null)
{
if (mUsbManager.hasPermission(accessory))
{
openAccessory(accessory);
}
else
{
synchronized (mUsbReceiver)
{
if (!mPermissionRequestPending)
{
mUsbManager.requestPermission(accessory,mPermissionIntent);
mPermissionRequestPending = true;
}
}
}
}
else
{
Log.d(TAG, "mAccessory is null");
}
}
@Override
public void onPause() {
super.onPause();
closeAccessory();
}
@Override
public void onDestroy() {
unregisterReceiver(mUsbReceiver);
super.onDestroy();
}
private void openAccessory(UsbAccessory accessory)
{
mFileDescriptor = mUsbManager.openAccessory(accessory);
if (mFileDescriptor != null) {
mAccessory = accessory;
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
Thread thread = new Thread(null, this, "AccessoryController");
thread.start();
Log.d(TAG, "accessory opened");
//enableControls(true);
} else {
Log.d(TAG, "accessory open fail");
}
}
private void closeAccessory() {
//enableControls(false);
try {
if (mFileDescriptor != null) {
mFileDescriptor.close();
}
} catch (IOException e) {
} finally {
mFileDescriptor = null;
mAccessory = null;
}
}
/*
* This receiver monitors for the event of a user granting permission to use
* the attached accessory. If the user has checked to always allow, this will
* be generated following attachment without further user interaction.
*/
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
openAccessory(accessory);
} else {
Log.d(TAG, "permission denied for accessory "+ accessory);
}
mPermissionRequestPending = false;
}
} else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null && accessory.equals(mAccessory)) {
closeAccessory();
}
}
}
};
/*
* Runnable block that will poll the accessory data stream
* for regular updates, posting each message it finds to a
* Handler. This is run on a spawned background thread.
*/
public void run() {
int ret = 0;
byte[] buffer = new byte[16384];
int i;
while (ret >= 0)
{
try
{
ret = mInputStream.read(buffer);
} catch (IOException e) {
break;
}
}
}
}
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<uses-library android:name="com.android.future.usb.accessory" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
<action android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" >
</meta-data>
</activity>
</application>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="YourModel" manufacturer="yourname" version="0.0.1"/>
</resources>
答案 1 :(得分:0)
这不是代码问题,您无法在没有OTG的情况下批量传输数据。获得10/15美元的OTG适配器,然后您可以这样做,但这取决于设备及其使用年限。通常,它们不会在没有adb和OTG适配器/电缆的情况下充当主机。我从来没有听说过另一种方式,我认为你最终会得到一个无效的代码,但如果你确实找到了办法,请告诉我。