当前位置: 代码迷 >> Android >> Android 驱动跟系统开发 2. 解析模拟器GPS模块 (原创)
  详细解决方案

Android 驱动跟系统开发 2. 解析模拟器GPS模块 (原创)

热度:23   发布时间:2016-05-01 14:16:39.0
Android 驱动和系统开发 2. 解析模拟器GPS模块 (原创)

好久没有写技术博客了,恰逢今天还感冒了,这破天气,晚上凉风一吹,就感冒了,要加强锻炼呀。

好了,废话不多说,由于工作需要,我要移植一个虚拟的gps模块,于是乎,我就参考了android模拟器的gps模块的实现方法,只需稍微改动就完成了我的工作了,随后我也会附上我做的模块的代码,这里主要还是来解析下模拟器上的gps模块代码吧。

相信做过android location方面应用的同志都知道,android 模拟器虽然没有真正的GPS功能,但是DDMS可以模拟GPS,通过telnet连接到adb,然后发送GPS数据,再转化成NMEA格式的信号给android系统,就可以模拟出location功能了,相信用过的童鞋都知道,没用过的同志去搜索一下就知道了,这里我就不多说了,我主要还是来分析一下这个模拟的功能是如何实现的,这里还是膜拜一下写android源码的大神们,多看看源码,学到的东西很多呢。

首先,我们直入主题,对于移植系统的人来说(比如说我),关注的是中间部分的代码,android的framework层我们需要改动的很少,最多就是加点log来调试,驱动层呢,因为模拟器没有真实的设备,也不可能利用PC上的资源区模拟,因为PC是没有GPS模块的(除非你的电脑很高级),但是我想还是可以通过网络来得到地理位置的,虽然不是非常的准确,希望google的工程师可以去完善,呵呵,题外话了。说了这么多,我就是想说,android 模拟器中gps模块的功能主要依赖于2个东西,一个是ddms中的geo fix命令,还有一个是hal层中的gps_qemu.c中作为硬件抽象层的处理,把虚拟的数据上报给framework层。

主要层次如下图


好了,思路清晰了,咱就看代码,位于源码目录下/sdk/emulator/gps/gps_qemu.c

首先我们要搞清楚,在andrroid中HAL 的一个位置问题,HAL是为了更好的封装好硬件驱动存在的,主要是一些接口,编译成库文件,给framework中国的jni来调用,我们这里的GPS模块会被编译成gps.goldfish.so文件,在同目录下的Android.mk中有写到

LOCAL_CFLAGS += -DQEMU_HARDWARELOCAL_SHARED_LIBRARIES := liblog libcutils libhardwareLOCAL_SRC_FILES := gps_qemu.cLOCAL_MODULE := gps.goldfishLOCAL_MODULE_TAGS := debug

然后呢,在jni中会这样调用

static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {    int err;    hw_module_t* module;    method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(III)V");    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");    method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");    method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification",            "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");    method_requestRefLocation = env->GetMethodID(clazz,"requestRefLocation","(I)V");    method_requestSetID = env->GetMethodID(clazz,"requestSetID","(I)V");    method_requestUtcTime = env->GetMethodID(clazz,"requestUtcTime","()V");    err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);    if (err == 0) {        hw_device_t* device;        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);        if (err == 0) {            gps_device_t* gps_device = (gps_device_t *)device;            sGpsInterface = gps_device->get_gps_interface(gps_device);        }    }    if (sGpsInterface) {        sGpsXtraInterface =            (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);        sAGpsInterface =            (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);        sGpsNiInterface =            (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);        sGpsDebugInterface =            (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);        sAGpsRilInterface =            (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);    }}
这个函数在android设备启动的时候会被调用来初始化GPS模块的一些东西,主要是来的到GPS模块的一些接口函数,重点看这个函数

    err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);

这个函数原型在HAL中的hardware.c中

int hw_get_module_by_class(const char *class_id, const char *inst,                           const struct hw_module_t **module){    int status;    int i;    const struct hw_module_t *hmi = NULL;    char prop[PATH_MAX];    char path[PATH_MAX];    char name[PATH_MAX];    if (inst)        snprintf(name, PATH_MAX, "%s.%s", class_id, inst);    else        strlcpy(name, class_id, PATH_MAX);    /*     * Here we rely on the fact that calling dlopen multiple times on     * the same .so will simply increment a refcount (and not load     * a new copy of the library).     * We also assume that dlopen() is thread-safe.     */    /* Loop through the configuration variants looking for a module */    for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {        if (i < HAL_VARIANT_KEYS_COUNT) {            if (property_get(variant_keys[i], prop, NULL) == 0) {                continue;            }            snprintf(path, sizeof(path), "%s/%s.%s.so",                     HAL_LIBRARY_PATH2, name, prop);            if (access(path, R_OK) == 0) break;            snprintf(path, sizeof(path), "%s/%s.%s.so",                     HAL_LIBRARY_PATH1, name, prop);            if (access(path, R_OK) == 0) break;        } else {            snprintf(path, sizeof(path), "%s/%s.default.so",                     HAL_LIBRARY_PATH1, name);            if (access(path, R_OK) == 0) break;        }    }    status = -ENOENT;    if (i < HAL_VARIANT_KEYS_COUNT+1) {        /* load the module, if this fails, we're doomed, and we should not try         * to load a different variant. */        status = load(class_id, path, module);    }    return status;}

当我们编译gps模块之后会在/system/lib/hw/下生成一个gps.goldfish.so文件,这个函数就是去寻找这个库文件,然后调用load函数去打开这个库文件,来得到库中的函数接口

static int load(const char *id,        const char *path,        const struct hw_module_t **pHmi){    int status;    void *handle;    struct hw_module_t *hmi;    /*     * load the symbols resolving undefined symbols before     * dlopen returns. Since RTLD_GLOBAL is not or'd in with     * RTLD_NOW the external symbols will not be global     */    handle = dlopen(path, RTLD_NOW);    if (handle == NULL) {        char const *err_str = dlerror();        LOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");        status = -EINVAL;        goto done;    }    /* Get the address of the struct hal_module_info. */    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;    hmi = (struct hw_module_t *)dlsym(handle, sym);    if (hmi == NULL) {        LOGE("load: couldn't find symbol %s", sym);        status = -EINVAL;        goto done;    }    /* Check that the id matches */    if (strcmp(id, hmi->id) != 0) {        LOGE("load: id=%s != hmi->id=%s", id, hmi->id);        status = -EINVAL;        goto done;    }    hmi->dso = handle;    /* success */    status = 0;    done:    if (status != 0) {        hmi = NULL;        if (handle != NULL) {            dlclose(handle);            handle = NULL;        }    } else {        LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",                id, path, *pHmi, handle);    }    *pHmi = hmi;    return status;}

这里我介绍的比较简洁,因为在我之前的博客中已经介绍过这部分的内容了,可以参考这里:http://blog.csdn.net/zhangjie201412/article/details/7225617

好了,回到我们GPS模块的代码上来

之后就会调用

        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);

来打开设备,来看下HAL中的代码

static int open_gps(const struct hw_module_t* module, char const* name,        struct hw_device_t** device){    struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));    memset(dev, 0, sizeof(*dev));    dev->common.tag = HARDWARE_DEVICE_TAG;    dev->common.version = 0;    dev->common.module = (struct hw_module_t*)module;//    dev->common.close = (int (*)(struct hw_device_t*))close_lights;    dev->get_gps_interface = gps__get_gps_interface;    *device = (struct hw_device_t*)dev;    return 0;}

这里只是做了一些初始化,然后把接口函数挂钩一下

    dev->get_gps_interface = gps__get_gps_interface;

这个回调函数很简单

static const GpsInterface  qemuGpsInterface = {    sizeof(GpsInterface),    qemu_gps_init,    qemu_gps_start,    qemu_gps_stop,    qemu_gps_cleanup,    qemu_gps_inject_time,    qemu_gps_inject_location,    qemu_gps_delete_aiding_data,    qemu_gps_set_position_mode,    qemu_gps_get_extension,};const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev){    return &qemuGpsInterface;}

返回qemuGpsInterface结构体,这个机构提中就是一大堆的回调函数。

下面我们按照调用顺序来一个一个介绍这些回调函数。

首先就是qume_gps_init函数

static intqemu_gps_init(GpsCallbacks* callbacks){    GpsState*  s = _gps_state;    if (!s->init)        gps_state_init(s, callbacks);    if (s->fd < 0)        return -1;    return 0;}

这里我发现了一个很好玩的东西,这里这个GpsState* s是如何得到全局的实例的呢,是通过_gps_state,而_gps_state的定义是这样的

typedef struct {    int                     init;    int                     fd;    GpsCallbacks            callbacks;    pthread_t               thread;    int                     control[2];} GpsState;static GpsState  _gps_state[1];

这里我的理解是在全局静态的定义了一个结构体指针,并分配了内存。

为何不在init函数中使用malloc来分配内存,然后使用呢,有点意思,现在还不知道有什么好处,难道只是卖弄吗?

好了,不多说了,接下去看调用的gps_state_init函数

在这之前,我来介绍下GpsState结构体中成员的作用吧

int init:

一个初始化的标志,为1表示初始化了,为0表示未初始化

int fd:

socket读写的文件描述符,如果是真实的硬件的话,应该是串口读写的描述符

callbacks:

这个是从jni传下来的回调函数,得到数据之后就回调

thread:

这个没什么好说的,就是一个线程

int control[2]:

本地使用的socket来进程间通信,会面会讲到。

继续init函数

static voidgps_state_init( GpsState*  state, GpsCallbacks* callbacks ){    state->init       = 1;    state->control[0] = -1;    state->control[1] = -1;    state->fd         = -1;    state->fd = qemud_channel_open(QEMU_CHANNEL_NAME);    if (state->fd < 0) {        D("no gps emulation detected");        return;    }    D("gps emulation will read from '%s' qemud channel", QEMU_CHANNEL_NAME );    if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) {        LOGE("could not create thread control socket pair: %s", strerror(errno));        goto Fail;    }    state->thread = callbacks->create_thread_cb( "gps_state_thread", gps_state_thread, state );    if ( !state->thread ) {        LOGE("could not create gps thread: %s", strerror(errno));        goto Fail;    }    state->callbacks = *callbacks;    D("gps state initialized");    return;Fail:    gps_state_done( state );}

首先书初始化赋值工作,看到没,把init变量赋值为1了。然后调用了qemud_channel_open函数来得到了adb tcp的socket文件描述符。然后调用socketpair创建本地的socket通信对来实现进程间通信,然后创建了线程,赋值回调函数,下图描述了代码执行的流程。


这图有点丑,不过大体思路还是清楚的,可以对照着代码看,这里使用的是event poll技术进行事件的处理,在线程中,把fd和control[1]加入了epoll中,设置为POLLIIN模式,当有事件发生是,就会调用相应的代码,这里的control[1],在这里做控制作用,只要是控制gps的开始和停止的,所以在线程外面对control[0]进行写操作的话,对应的control[1]就会收到相应的指令,然后采取措施。具体代码如下

static voidgps_state_thread( void*  arg ){    GpsState*   state = (GpsState*) arg;    NmeaReader  reader[1];    int         epoll_fd   = epoll_create(2);    int         started    = 0;    int         gps_fd     = state->fd;    int         control_fd = state->control[1];    nmea_reader_init( reader );    // register control file descriptors for polling    epoll_register( epoll_fd, control_fd );    epoll_register( epoll_fd, gps_fd );    D("gps thread running");    // now loop    for (;;) {        struct epoll_event   events[2];        int                  ne, nevents;        nevents = epoll_wait( epoll_fd, events, 2, -1 );        if (nevents < 0) {            if (errno != EINTR)                LOGE("epoll_wait() unexpected error: %s", strerror(errno));            continue;        }        D("gps thread received %d events", nevents);        for (ne = 0; ne < nevents; ne++) {            if ((events[ne].events & (EPOLLERR|EPOLLHUP)) != 0) {                LOGE("EPOLLERR or EPOLLHUP after epoll_wait() !?");                return;            }            if ((events[ne].events & EPOLLIN) != 0) {                int  fd = events[ne].data.fd;                if (fd == control_fd)                {                    char  cmd = 255;                    int   ret;                    D("gps control fd event");                    do {                        ret = read( fd, &cmd, 1 );                    } while (ret < 0 && errno == EINTR);                    if (cmd == CMD_QUIT) {                        D("gps thread quitting on demand");                        return;                    }                    else if (cmd == CMD_START) {                        if (!started) {                            D("gps thread starting  location_cb=%p", state->callbacks.location_cb);                            started = 1;                            nmea_reader_set_callback( reader, state->callbacks.location_cb );                        }                    }                    else if (cmd == CMD_STOP) {                        if (started) {                            D("gps thread stopping");                            started = 0;                            nmea_reader_set_callback( reader, NULL );                        }                    }                }                else if (fd == gps_fd)                {                    char  buff[32];                    D("gps fd event");                    for (;;) {                        int  nn, ret;                        ret = read( fd, buff, sizeof(buff) );                        if (ret < 0) {                            if (errno == EINTR)                                continue;                            if (errno != EWOULDBLOCK)                                LOGE("error while reading from gps daemon socket: %s:", strerror(errno));                            break;                        }                        D("received %d bytes: %.*s", ret, ret, buff);                        for (nn = 0; nn < ret; nn++)                            nmea_reader_addc( reader, buff[nn] );                    }                    D("gps fd event end");                }                else                {                    LOGE("epoll_wait() returned unkown fd %d ?", fd);                }            }        }    }}


好了,android 模拟器的虚拟hal层就介绍到这边,下面来看一下geo fix命令的实现源码,我也是找了好久才找到的,在external/qemu/android/console.c中

static intdo_geo_fix( ControlClient  client, char*  args ){    // GEO_SAT2 provides bug backwards compatibility.    enum { GEO_LONG = 0, GEO_LAT, GEO_ALT, GEO_SAT, GEO_SAT2, NUM_GEO_PARAMS };    char*   p = args;    int     top_param = -1;    double  params[ NUM_GEO_PARAMS ];    int     n_satellites = 1;    static  int     last_time = 0;    static  double  last_altitude = 0.;    if (!p)        p = "";    /* tokenize */    while (*p) {        char*   end;        double  val = strtod( p, &end );        if (end == p) {            control_write( client, "KO: argument '%s' is not a number\n", p );            return -1;        }        params[++top_param] = val;        if (top_param + 1 == NUM_GEO_PARAMS)            break;        p = end;        while (*p && (p[0] == ' ' || p[0] == '\t'))            p += 1;    }    /* sanity check */    if (top_param < GEO_LAT) {        control_write( client, "KO: not enough arguments: see 'help geo fix' for details\r\n" );        return -1;    }    /* check number of satellites, must be integer between 1 and 12 */    if (top_param >= GEO_SAT) {        int sat_index = (top_param >= GEO_SAT2) ? GEO_SAT2 : GEO_SAT;        n_satellites = (int) params[sat_index];        if (n_satellites != params[sat_index]            || n_satellites < 1 || n_satellites > 12) {            control_write( client, "KO: invalid number of satellites. Must be an integer between 1 and 12\r\n");            return -1;        }    }    /* generate an NMEA sentence for this fix */    {        STRALLOC_DEFINE(s);        double   val;        int      deg, min;        char     hemi;        /* format overview:         *    time of fix      123519     12:35:19 UTC         *    latitude         4807.038   48 degrees, 07.038 minutes         *    north/south      N or S         *    longitude        01131.000  11 degrees, 31. minutes         *    east/west        E or W         *    fix quality      1          standard GPS fix         *    satellites       1 to 12    number of satellites being tracked         *    HDOP             <dontcare> horizontal dilution         *    altitude         546.       altitude above sea-level         *    altitude units   M          to indicate meters         *    diff             <dontcare> height of sea-level above ellipsoid         *    diff units       M          to indicate meters (should be <dontcare>)         *    dgps age         <dontcare> time in seconds since last DGPS fix         *    dgps sid         <dontcare> DGPS station id         */        /* first, the time */        stralloc_add_format( s, "$GPGGA,%06d", last_time );        last_time ++;        /* then the latitude */        hemi = 'N';        val  = params[GEO_LAT];        if (val < 0) {            hemi = 'S';            val  = -val;        }        deg = (int) val;        val = 60*(val - deg);        min = (int) val;        val = 10000*(val - min);        stralloc_add_format( s, ",%02d%02d.%04d,%c", deg, min, (int)val, hemi );        /* the longitude */        hemi = 'E';        val  = params[GEO_LONG];        if (val < 0) {            hemi = 'W';            val  = -val;        }        deg = (int) val;        val = 60*(val - deg);        min = (int) val;        val = 10000*(val - min);        stralloc_add_format( s, ",%02d%02d.%04d,%c", deg, min, (int)val, hemi );        /* bogus fix quality, satellite count and dilution */        stralloc_add_format( s, ",1,%02d,", n_satellites );        /* optional altitude + bogus diff */        if (top_param >= GEO_ALT) {            stralloc_add_format( s, ",%.1g,M,0.,M", params[GEO_ALT] );            last_altitude = params[GEO_ALT];        } else {            stralloc_add_str( s, ",,,," );        }        /* bogus rest and checksum */        stralloc_add_str( s, ",,,*47" );        /* send it, then free */        android_gps_send_nmea( stralloc_cstr(s) );        stralloc_reset( s );    }    return 0;}

通过穿进去的经纬度,海拔等信息转化成NMEA格式的gps数据,然后通过socket发出去。


这部分就介绍到这里,之后会更精彩,哈哈。

希望这篇文章对读者有帮助,完全是参考android源码的,对我来说源码是最好的学习途径。


1楼zhangjie201412昨天 10:45
自己先顶一个,希望对大家有帮助
  相关解决方案