Android usb学习笔记:Android AOA协议设备端 流程总结
来源:互联网 发布:程序员必备生活装备 编辑:程序博客网 时间:2024/04/27 13:46
背景
前段时间的项目开发中,由于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端代码如何对接设备端。
- Android usb学习笔记:Android AOA协议设备端 流程总结
- Android usb学习笔记:Android AOA协议Android端 流程总结
- Android AOA协议Android端 流程总结
- 基于AOA协议实现Android设备的USB通信
- 基于AOA协议实现Android设备的USB通信
- android作为主usb设备,加载流程
- Android Framework学习笔记 -- 蓝牙设备播放流程
- Android外设AOA技术原理
- Android 开放配件 (AOA)调试
- android的usb作为从设备的程序流程
- USB协议学习笔记
- Android USB通信学习 USB Host设备通信实际应用
- Android: 查找USB设备
- Android 设备USB识别
- 设备驱动-----Android关机流程总结
- 设备驱动-----Android关机流程总结2
- android usb流程
- android usb流程
- 关于声网直播sdk的导入的一些坑
- JavaScriptCore 详解
- forward 与 redirect 小case
- TortoiseSVN ,怎么退出登录。
- FFT的学习
- Android usb学习笔记:Android AOA协议设备端 流程总结
- oracle如何修改 序列当前的最大值?
- Unity FairyGUI测试过程
- 二叉树根据前序和中序确定二叉树
- 【机器学习中的数学】贝叶斯概念学习
- JAVA学习路线规划
- mysql相似于oracle的to_char() to_date()方法
- PHP WEB 安全
- [译] 第三章 MCMC