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));    }    //把socketsocket地址结构联系起来    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端代码如何对接设备端。

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 视频已过期或已被清理怎么办 小孩作业不会老婆天天吵骂打怎么办 苹果手机在太阳下屏幕变暗怎么办 斗鱼的鱼丸竞猜主播结算了怎么办 附近有个小姐姐想加她好友怎么办 孩子出现听别人说话语速很快怎么办 苹果手机上的邮件删了怎么办 手机qq邮箱独立密码忘记了怎么办 哺乳期吃了人参回奶了怎么办? 扣扣邮箱里的邮件过期了怎么办 一体机的管理员账号被删除了怎么办 手机里的邮箱重要吗删除了怎么办 华为荣耀10账号邮箱忘记了怎么办 大陆微信号在台湾登录不上怎么办 威纶触摸屏被禁止到反编译了怎么办 微信公众号邮箱被占用怎么办 京东绑定的手机号不用了怎么办 绑定微信的手机号不用了怎么办 绑定支付宝的手机号不用了怎么办 百度账号手机号换了密码忘了怎么办 换手机好了华为账号密码忘了怎么办 苹果手机忘了id账号和密码怎么办 金立手机账号密码忘了怎么办 乐视手机账号密码忘了怎么办 企业邮箱发出去邮件撤不回来怎么办 餐厅加热保温设备零线带电怎么办 小米手机不小心把照片删了怎么办 华为手机不小心把照片删了怎么办 网易邮箱被改成别人的姓名怎么办 苹果手机忘记id密码和邮箱怎么办 163邮箱下载的附件没有了怎么办 小米自带浏览器打开网页太慢怎么办 再歪一点授权码忘记了怎么办 注册支付宝的手机号不用了怎么办 手机号被别人注册了支付宝怎么办 支付宝账号密码都忘了怎么办 申请微信公众号邮箱被占用怎么办 邮箱注册微博需要手机验证怎么办 苹果手机的ad码忘记了怎么办 苹果手机酷狗音乐没有声音怎么办 在手机屏上不能缷载软件怎么办?