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

Posted vonchenchen1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android usb学习笔记:Android AOA协议设备端 流程总结相关的知识,希望对你有一定的参考价值。

背景

前段时间的项目开发中,由于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端代码如何对接设备端。

以上是关于Android usb学习笔记:Android AOA协议设备端 流程总结的主要内容,如果未能解决你的问题,请参考以下文章

Android学习笔记-----------布局

Android学习笔记(33):Android对话框

Android学习笔记(34):Android菜单

android学习笔记

Android学习笔记:Android Service组件深入解析

Android学习笔记