您的位置:首页 > 移动开发 > Android开发

Android usb学习笔记:Android AOA协议设备端 流程总结

2017-04-20 21:14 851 查看

背景

前段时间的项目开发中,由于wifi稳定性的限制,项目采用了Android手机与嵌入式设备通过usb直接连接的方式进行通信。其中Android的usb层使用了Android自身的AOA模式,嵌入式端借助libusb库与Android端通信。在应用层简单实现了一个tcp连接,最终可以抽象为双方socket端口与端口间的通信过程。探索的过程比较曲折,其间受到两位同事也是前辈的帮助指导,收获颇多。

实现

AOA协议实现流程简述

下面是我对AOA通信过程的一些理解

1.Android连接设备,设备可以通过轮询或者注册热插拔事件的方式,检测当前插入的usb设备,查询这个设备是否处在AOA模式,如果不是则开启AOA模式。也就是设备向Android设备写入相应的usb控制信息,写入成功后,我们的Android设备就开启了AOA模式,这时嵌入式端就拿到了Android设备usb的读写描述符,可以对其进行数据传输。

2.Android端接收到嵌入式端写入的信息后,就把自己设置为accessory模式,这时Android会发送一条系统广播,在广播接受者中查询当前是否有accessory连接。如果可以拿到这个accessory,那么就能获取到相应的读写流,也就可以通过这两个流对设备进行读写。

这里设备端底层使用了libusb库,下面是项目地址,可以直接在mac或者linux下编译。在开发Android端代码时网上的参考代码有不少bug,这里给出一份相对比较稳定的Android端例程。

libusb项目链接 https://github.com/libusb/libusb.git

设备aoa模式参考项目链接 https://github.com/timotto/AOA-Proxy.git

本例程设备端项目链接 https://git.oschina.net/vonchenchen/aoa_proxy.git

本例程Android端项目链接 https://git.oschina.net/vonchenchen/aoa_android.git

例程的编译与使用

在编译设备端项目之前需要先编译并安装libusb库。安装完成libusb库后,运行本例程设备端项目中的configure文件,参考日志信息安装其他依赖库,然后执行make,编译完成后将会生成aoaproxy文件,也就是我们能最后生成的可执行文件。

Android端例程可以直接在Android Studio打开运行,直接装入手机即可。

在设备端执行

sudo ./aoaproxy


如果看到如下日志,则说明程序已经正常启动

start_service

start connect device

prepare to connect device

这时,插入android设备,应用会自动启动,设备控制台开始每隔一秒打印如下信息

Start send

recived len 12

usb recive len 12

receive hello

这样就说明例子程序运行起来了。这里每隔1秒设备将向Android端发送一个Hello字符串,Android端收到数据后会原样返回这些数据,这时设备端收到数据后会将这些信息打印在控制台上。

代码分析

设备端代码主要分为一下几个文件,文件对应功能如下

aoaproxy.c —– 主程序

accessory.c —— AOA底层开启

a2spipe.c ——usb与本地server交互管道

tcp.c ——-tcp连接

local_service.c ——- 本地 server 用于数据接收和发送,可以在单独进程中开启

下面我们将分布对这几个文件进行介绍。

main函数

int main(int argc, char** argv) {
int r;
int opt;
int xcount=0;

while ((opt = getopt(argc, argv, "dfh:p:x:")) != -1) {
//参数
....
}

ctx = NULL;
connectedDevices = NULL;

//开启本地socketserver
create_start_service();

if (do_fork)
do_fork_foo();

//注册信号
initSigHandler();

// it is important to init usb after the fork!
if (0 > initUsb()) {
logError("Failed to initialize USB\n");
return 1;
}
//定时任务轮询
if(autoscan) {
struct itimerval timer;
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_usec = 0;
setitimer (ITIMER_REAL, &timer, NULL);
}

//usb设备列表
libusb_device **devs = NULL;
while(!do_exit) {

if (doUpdateUsbInventory == 1) {
doUpdateUsbInventory = 0;
//清空设备
cleanupDeadDevices();
//尝试链接设备
updateUsbInventory(devs);
}
//阻塞等待usb事件
r = libusb_handle_events(ctx);
if (r) {
if (r == LIBUSB_ERROR_INTERRUPTED) {
// ignore
} else {
if(!do_exit)
logDebug("libusb_handle_events_timeout: %d\n", r);

break;
}
}
}

if (devs != NULL)
libusb_free_device_list(devs, 1);

if(autoscan) {
struct itimerval timer;
memset (&timer, 0, sizeof(timer));
setitimer (ITIMER_REAL, &timer, NULL);
}
shutdownEverything();
return EXIT_SUCCESS;
}


这里比较重要的是updateUsbInventory函数。

updateUsbInventory

下面看一下updateUsbInventory方法做什么的

static int updateUsbInventory(libusb_device **devs) {
static ssize_t cnt = 0;
static ssize_t lastCnt = 0;
//  static libusb_device **devs;
static libusb_device **lastDevs = NULL;
//获取usb设备列表
cnt = libusb_get_device_list(ctx, &devs);
if(cnt < 0) {
logError("Failed to list devices\n");
return -1;
}

ssize_t i, j;
int foundBefore;
for(i = 0; i < cnt; i++) {
foundBefore = 0;
if ( lastDevs != NULL) {
for(j=0;j < lastCnt; j++) {
if (devs[i] == lastDevs[j]) {
foundBefore = 1;
break;
}
}
}
if (!foundBefore) {
logDebug("start connect device\n");
//连接设备 连接本地服务端
if(connectDevice(devs[i]) >= 0)
libusb_ref_device(devs[i]);
}
}

if (lastDevs != NULL) {
//      if (cnt != lastCnt)
//          fprintf(LOG_DEB, "number of USB devices changed from %d to %d\n", lastCnt, cnt);

for (i=0;i<lastCnt;i++) {
foundBefore = 0;
for(j=0;j<cnt;j++) {
if (devs[j] == lastDevs[i]) {
foundBefore = 1;
break;
}
}
if(!foundBefore) {
struct listentry *hit = connectedDevices;

while(hit != NULL) {
if ( hit->usbDevice == lastDevs[i]) {
disconnectDevice(lastDevs[i]);
libusb_unref_device(lastDevs[i]);
break;
}
hit = hit->next;
}
}
}
libusb_free_device_list(lastDevs, 1);
}
lastDevs = devs;
lastCnt = cnt;

return 0;
}


connectDevice

这个函数比较关键,开启了android的accessory,同时也和本地服务器进行连接,这样就打通了usb和本地server的通道。

static int connectDevice(libusb_device *device) {

logDebug("prepare to connect device \n");

struct libusb_device_descriptor desc;
//获取usb设备描述信息
int r = libusb_get_device_descriptor(device, &desc);
if (r < 0) {
logError("failed to get device descriptor: %d", r);
return -1;
}

switch(desc.bDeviceClass) {
case 0x09:
logDebug("device 0x%04X:%04X has wrong deviceClass: 0x%02x",
desc.idVendor, desc.idProduct,
desc.bDeviceClass);
return -1;
}

struct t_excludeList *e = exclude;
while(e != NULL) {
logDebug("comparing device [%04x:%04x] and [%04x:%04x]",
desc.idVendor, desc.idProduct, e->vid, e->pid);
if(e->vid == desc.idVendor && e->pid == desc.idProduct) {
logDebug("device is on exclude list", desc.idVendor, desc.idProduct);
return -1;
}
e = e->next;
}

//检查当前设备是否处于accessory模式
if(!isDroidInAcc(device)) {
logDebug("attempting AOA on device 0x%04X:%04X\n",
desc.idVendor, desc.idProduct);
//写入要启动的应用的信息 开启android的accessory模式
switchDroidToAcc(device, 1, haveAudio);
return -1;
}

//entry管理socket与usb
struct listentry *entry = malloc(sizeof(struct listentry));
if (entry == NULL) {
logError("Not enough RAM");
return -2;
}
bzero(entry, sizeof(struct listentry));

//entry拿到usb句柄device
entry->usbDevice = device;

//entry拿到socket句柄
#ifdef SOCKET_RETRY
//连接本地socketserver, 返回socket客户端的描述符
while((r = connectTcpSocket(hostname, portno)) <= 0) {
logError("failed to setup socket: %d, retrying\n", r);
sleep(1);
}
//记录本地soket链接的描述符
entry->sockfd = r;
entry->socketDead = 0;
#else
r = connectTcpSocket(hostname, portno);
if (r < 0) {
fprintf(LOG_ERR, "failed to setup socket: %d\n", r);
free(entry);
return -4;
}
entry->sockfd = r;
entry->socketDead = 0;
#endif
//如果android设备已经是aoa模式,打开usb
logDebug("start setup droid \n");
//找到accessory接口并用接口信息初始化entry->droid
r = setupDroid(device, &entry->droid);
if (r < 0) {
logError("failed to setup droid: %d\n", r);
free(entry);
return -3;
}

//将entry加入链表
entry->next = NULL;
if (connectedDevices == NULL) {
entry->prev = NULL;
connectedDevices = entry;
} else {
struct listentry *last = connectedDevices;
while(last->next != NULL)
last = last->next;
entry->prev = last;
last->next = entry;
}

//建立usb与socket互相通信的任务
r = startUSBPipe(entry);
if (r < 0) {
logError("failed to start pipe: %d", r);
disconnectDevice(device);
return -5;
}

if (haveAudio && entry->droid.audioendp) {
startAudio(entry);
}

logDebug("new Android connected");
return 0;
}


上述代码中首先检测当前接口是否为accessory模式,如果不是则将其设置为accessory模式,但是此处将所有设备都设置为accessory,可能有些设备并非android设备。同时connectTcpSocket方法开启了tcp连接。这里用entry记录socket和usb信息,并将其放入全局链表connectedDevices维护。

这里不经会让我们产生疑问,到底usb收发数据是在哪里,又是在什么地方与tcp server进行交互,我们继续往下看。

entry中维护了usb状态,同时也有socket,entry被放入startUSBPipe函数,下面着重看一下startUSBPipe的实现。

startUSBPipe

static int startUSBPipe(struct listentry *device) {
int r;
if(initUsbXferThread(&device->usbRxThread) < 0) {
logError("failed to allocate usb rx transfer\n");
return -1;
}
if(initUsbXferThread(&device->socketRxThread) < 0) {
logError("failed to allocate usb tx transfer\n");
destroyUsbXferThread(&device->usbRxThread);
return -1;
}

//写入到usb任务
r = pthread_create(&device->usbRxThread.thread, NULL, (void*)&a2s_usbRxThread, (void*)device);
if (r < 0) {
logError("failed to start usb rx thread\n");
return -1;
}

//读出到socket任务
r = pthread_create(&device->socketRxThread.thread, NULL, (void*)&a2s_socketRxThread, (void*)device);
if (r < 0) {
// other thread is stopped in disconnectDevice method
logError("failed to start socket rx thread\n");
return -1;
}

return 0;
}


这里开启了两个线程,分别是usb数据写入server任务和server写入usb任务。下面分别看一下这两个任务。

a2s_usbRxThread

//usb写入socket任务
void *a2s_usbRxThread( void *d ) {
logDebug("a2s_usbRxThread started\n");

struct listentry *device = (struct listentry*)d;

unsigned char buffer[device->droid.inpacketsize];
int rxBytes = 0;
int txBytes;
int sent;
int r;

//初始化usbRxThread.xfr ,关联数据buffer   传输完毕后回调a2s_usbrx_cb  解锁device->usbRxThread.condition
libusb_fill_bulk_transfer(device->usbRxThread.xfr, device->droid.usbHandle, device->droid.inendp,
buffer, sizeof(buffer),
(libusb_transfer_cb_fn)&a2s_usbrx_cb, (void*)&device->usbRxThread, 0);

while(!device->usbRxThread.stop && !device->usbDead && !device->socketDead) {

pthread_mutex_lock( &device->usbRxThread.mutex );
device->usbRxThread.usbActive = 1;

//      logDebug("a2s_usbRxThread reading...\n");
//请求数据
r = libusb_submit_transfer(device->usbRxThread.xfr);
if (r < 0) {
logError("a2s usbrx submit transfer failed\n");
device->usbDead = 1;
device->usbRxThread.usbActive = 0;
pthread_mutex_unlock( &device->usbRxThread.mutex );
break;
}

//      waitUsbXferThread(&device->usbRxThread);

//      logDebug("a2s_usbRxThread waiting...\n");
//等待接收数据
pthread_cond_wait( &device->usbRxThread.condition, &device->usbRxThread.mutex);
//      logDebug("a2s_usbRxThread wait over\n");
if (device->usbRxThread.usbActive) {
logError("wait, unlock but usbActive!\n");
}
pthread_mutex_unlock( &device->usbRxThread.mutex );

if (device->usbRxThread.stop || device->usbDead || device->socketDead)
break;

//查看usb接收数据的状态
switch(device->usbRxThread.xfr->status) {
case LIBUSB_TRANSFER_COMPLETED:
//          logDebug("a2s_usbRxThread writing...\n");
rxBytes = device->usbRxThread.xfr->actual_length;

logDebug("usb recive len %d \n", rxBytes);

sent = 0;
txBytes = 0;
while(sent < rxBytes && !device->usbRxThread.stop) {
//将usb接收到的数据全部写入到socket
txBytes = write(device->sockfd, buffer + sent, rxBytes - sent);
if (txBytes <= 0) {
logError("a2s usbrx socket tx failed\n");
device->socketDead = 1;
device->usbRxThread.stop = 1;
} else {
sent += txBytes;
}
}
break;
case LIBUSB_TRANSFER_NO_DEVICE:
device->usbDead = 1;
device->usbRxThread.stop = 1;
break;
default:
//          logDebug("a2s_usbRxThread usb error %d, ignoring\n", device->usbRxThread.xfr->status);
break;
}
}

device->usbRxThread.stopped = 1;
logDebug("a2s_usbRxThread finished\n");
pthread_exit(0);
return NULL;
}


libusb_fill_bulk_transfer


这个方法使用entry中的数据,开启usb通路,如果接收到了usb数据,就会回调a2s_usbrx_cb。

之后调用

libusb_submit_transfer


请求接收数据。请求完数据线程被锁,如果a2s_usbrx_cb被回调,则会发送一个信号量,线程锁打开。此时usb数据已经传入到了缓冲区,entry中存储的socket的描述符,直接将buffer写入这个描述符,server就会收到usb信息。数据发送任务也是同理。到此,usb和server通道就已经打通。

另外,AOA模式可以直接打开应用,下面看一下AOA模式是如何打开的。

isDroidInAcc

这个函数用于设备检测是否处于AOA模式

int isDroidInAcc(libusb_device *dev) {
struct libusb_device_descriptor desc;
int r = libusb_get_device_descriptor(dev, &desc);
if (r < 0) {
logError("failed to get device descriptor\n");
//      fprintf(LOG_ERR, ERR_USB_DEVDESC);
return 0;
}

if (desc.idVendor == VID_GOOGLE) {
switch(desc.idProduct) {
case PID_AOA_ACC:
case PID_AOA_ACC_ADB:
case PID_AOA_ACC_AU:
case PID_AOA_ACC_AU_ADB:
return 1;
case PID_AOA_AU:
case PID_AOA_AU_ADB:
logDebug("device is audio-only\n");
//          logDebug( "device is audio-only\n");
break;
default:
break;
}
}

return 0;
}


通过libusb_device获取当前usb设备的状态信息,用来判断usb的厂商和模式等信息。

switchDroidToAcc

这个函数是将设备设置为Accessory模式,这里可以配置我们的设备连接手机后启动哪个android设备。

void switchDroidToAcc(libusb_device *dev, int force, int audio) {
struct libusb_device_handle* handle;
unsigned char ioBuffer[2];
int r;
int deviceProtocol;

//打开设备
if(0 > libusb_open(dev, &handle)){
logError("Failed to connect to device\n");
return;
}

//写入控制信息
if(libusb_kernel_driver_active(handle, 0) > 0) {
if(!force) {
logError("kernel driver active, ignoring device");
libusb_close(handle);
return;
}
if(libusb_detach_kernel_driver(handle, 0)!=0) {
logError("failed to detach kernel driver, ignoring device");
libusb_close(handle);
return;
}
}
if(0> (r = libusb_control_transfer(handle,
0xC0, //bmRequestType
51, //Get Protocol
0,
0,
ioBuffer,
2,
2000))) {
logError("get protocol call failed %d \n", r);
libusb_close(handle);
return;
}

deviceProtocol = ioBuffer[1] << 8 | ioBuffer[0];
if (deviceProtocol < AOA_PROTOCOL_MIN || deviceProtocol > AOA_PROTOCOL_MAX) {
//      logDebug("Unsupported AOA protocol %d\n", deviceProtocol);
logDebug( "Unsupported AOA protocol %d\n", deviceProtocol);
libusb_close(handle);
return;
}

//这些量用于指定启动app,我们在app中也会写入同样的信息
const char *setupStrings[6];
setupStrings[0] = vendor;
setupStrings[1] = model;
setupStrings[2] = description;
setupStrings[3] = version;
setupStrings[4] = uri;
setupStrings[5] = serial;

int i;
for(i=0;i<6;i++) {
if(0 > (r = libusb_control_transfer(handle,
0x40,
52,
0,
(uint16_t)i,
(unsigned char*)setupStrings[i],
strlen(setupStrings[i]),2000))) {
logDebug( "send string %d call failed\n", i);
libusb_close(handle);
return;
}
}

if (deviceProtocol >= 2) {
if(0 > (r = libusb_control_transfer(handle,
0x40, //厂商的请求
58,
#ifdef USE_AUDIO
audio, // 0=no audio, 1=2ch,16bit PCM, 44.1kHz
#else
0,
#endif
0,
NULL,
0,
2000))) {
logDebug( "set audio call failed\n");
libusb_close(handle);
return;
}
}

if(0 > (r = libusb_control_transfer(handle,0x40,53,0,0,NULL,0,2000))) {
logDebug( "start accessory call failed\n");
libusb_close(handle);
return;
}

libusb_close(handle);
}


server例程

下面是我们自己实现的socket server,用于接收和发送数据,这里我们会向usb发送“hello“字符串,然后打印收到的数据。当然,在使用时可以把上面的例子作为单独进程开启,作为单独模块,而数据发送和接收在我们自己的应用的进程中。

//数据接收线程
void *recvThread(void *arg){

int length;

struct thread_param *param = (struct thread_param *)arg;
int socket = param->socket_id;

printf("recvThread %d\n", socket);

char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);

while(loop_flag){

length = recv(socket,buffer,BUFFER_SIZE,0 );
if (length < 0)
{
printf("Server Recieve Data Failed!\n");
loop_flag = 0;
break;
}
//打印接收到的数据内容
printf("receive %s\n", buffer);
}
}

//数据发送线程 每隔一秒发送
void *sendThread(void *arg){

struct thread_param *param = (struct thread_param *)arg;
int socket = param->socket_id;

printf("sendThread %d\n", socket);

char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);

while(loop_flag){

printf("\nStart send\n");

int length = 12;
buffer[0] = 'h';
buffer[1] = 'e';
buffer[2] = 'l';
buffer[3] = 'l';
buffer[4] = 'o';
buffer[5] = 0;
//发送buffer中的字符串到new_server_socket,实际是给客户端
if(send(socket,buffer,length,0)<0)
{
printf("Send faield\n");
loop_flag = 0;
break;
}
sleep(1);
}
}

//开启自定义服务
int start_service(int argc, char **argv)
{
printf("start_service\n");
//设置一个socket地址结构server_addr,代表服务器internet地址, 端口
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

//创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
int server_socket = socket(PF_INET,SOCK_STREAM,0);
if( server_socket < 0)
{
printf("Create Socket Failed!");
exit(1);
}
{
int opt =1;
setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
}

//把socket和socket地址结构联系起来
if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
{
printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
exit(1);
}

//server_socket用于监听
if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
{
printf("Server Listen Failed!");
exit(1);
}
while (1)
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
if ( new_server_socket < 0)
{
printf("Server Accept Failed!\n");
break;
}

loop_flag = 1;

threadParam.socket_id = new_server_socket;
pthread_create(&recvThreadId, NULL, recvThread, &threadParam);
pthread_create(&sendThreadId, NULL, sendThread, &threadParam);

char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);

while(1){
sleep(1);
if(loop_flag == 0){
break;
}
}

printf("close server socket \n");
//关闭与客户端的连接
close(new_server_socket);
}
//关闭监听用的socket
close(server_socket);
return 0;
}

void create_start_service(){

pthread_create(&mainThreadId, NULL, start_service, &mainThreadParam);
}


总结

上文介绍了Android AOA协议设备端实现的基本流程,首先介绍了项目的使用方法,之后梳理了本地server和usb通信的流程,最后介绍了设备端开启app的方法,下一篇文章我们将分析android端代码如何对接设备端。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: