/****************************************************************************** * GPS HAL (hardware abstraction layer) for HD2/Leo * * leo-gps.c * * Copyright (C) 2006-2009 The Android Open Source Project * Copyright (C) 2009-2010 The XDAndroid Project * Copyright (C) 2010 dan1j3l @ xda-developers * Copyright (C) 2011 tytung @ xda-developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #define LOG_TAG "gps_leo" #define XTRA_BLOCK_SIZE 400 #define ENABLE_NMEA 1 #define DUMP_DATA 0 #define GPS_DEBUG 1 #if GPS_DEBUG # define D(...) LOGD(__VA_ARGS__) #else # define D(...) ((void)0) #endif #if ENABLE_NMEA /* Since NMEA parser requires lcoks */ #define GPS_STATE_LOCK_FIX(_s) \ { \ int ret; \ do { \ ret = sem_wait(&(_s)->fix_sem); \ } while (ret < 0 && errno == EINTR); \ } #define GPS_STATE_UNLOCK_FIX(_s) \ sem_post(&(_s)->fix_sem) static void *gps_timer_thread( void* arg ); #endif static void *gps_get_position_thread( void* arg ); static pthread_mutex_t get_position_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t get_position_cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t get_pos_ready_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t get_pos_ready_cond = PTHREAD_COND_INITIALIZER; static int started = 0; static int active = 0; void update_gps_location(GpsLocation *location); void update_gps_status(GpsStatusValue value); void update_gps_svstatus(GpsSvStatus *svstatus); void update_gps_nmea(GpsUtcTime timestamp, const char* nmea, int length); extern uint8_t get_cleanup_value(); extern uint8_t get_precision_value(); /*****************************************************************/ /*****************************************************************/ /***** *****/ /***** N M E A T O K E N I Z E R *****/ /***** *****/ /*****************************************************************/ /*****************************************************************/ /* this is the state of our connection */ typedef struct { const char* p; const char* end; } Token; #define MAX_NMEA_TOKENS 32 typedef struct { int count; Token tokens[ MAX_NMEA_TOKENS ]; } NmeaTokenizer; static int nmea_tokenizer_init( NmeaTokenizer* t, const char* p, const char* end ) { int count = 0; char* q; // the initial '$' is optional if (p < end && p[0] == '$') p += 1; // remove trailing newline if (end > p && end[-1] == '\n') { end -= 1; if (end > p && end[-1] == '\r') end -= 1; } // get rid of checksum at the end of the sentecne if (end >= p+3 && end[-3] == '*') { end -= 3; } while (p < end) { const char* q = p; q = memchr(p, ',', end-p); if (q == NULL) q = end; if (count < MAX_NMEA_TOKENS) { t->tokens[count].p = p; t->tokens[count].end = q; count += 1; } if (q < end) q += 1; p = q; } t->count = count; return count; } static Token nmea_tokenizer_get( NmeaTokenizer* t, int index ) { Token tok; static const char* dummy = ""; if (index < 0 || index >= t->count) { tok.p = tok.end = dummy; } else tok = t->tokens[index]; return tok; } static int str2int( const char* p, const char* end ) { int result = 0; int len = end - p; if (len == 0) { return -1; } for ( ; len > 0; len--, p++ ) { int c; if (p >= end) goto Fail; c = *p - '0'; if ((unsigned)c >= 10) goto Fail; result = result*10 + c; } return result; Fail: return -1; } static double str2float( const char* p, const char* end ) { int result = 0; int len = end - p; char temp[16]; if (len == 0) { return -1.0; } if (len >= (int)sizeof(temp)) return 0.; memcpy( temp, p, len ); temp[len] = 0; return strtod( temp, NULL ); } /*****************************************************************/ /*****************************************************************/ /***** *****/ /***** N M E A P A R S E R *****/ /***** *****/ /*****************************************************************/ /*****************************************************************/ #define NMEA_MAX_SIZE 255 typedef struct { int pos; int overflow; int utc_year; int utc_mon; int utc_day; int utc_diff; GpsLocation fix; GpsSvStatus sv_status; int sv_status_changed; uint16_t fix_flags_cached; char in[ NMEA_MAX_SIZE+1 ]; } NmeaReader; enum { STATE_QUIT = 0, STATE_INIT = 1, STATE_START = 2 }; typedef struct { int init; int fd; GpsCallbacks callbacks; GpsXtraCallbacks xtra_callbacks; AGpsCallbacks agps_callbacks; GpsStatus status; pthread_t thread; pthread_t pos_thread; #if ENABLE_NMEA pthread_t tmr_thread; sem_t fix_sem; #endif int fix_freq; int control[2]; NmeaReader reader; } GpsState; static GpsState _gps_state[1]; static void nmea_reader_update_utc_diff( NmeaReader* r ) { time_t now = time(NULL); struct tm tm_local; struct tm tm_utc; long time_local, time_utc; gmtime_r( &now, &tm_utc ); localtime_r( &now, &tm_local ); time_local = mktime(&tm_local); time_utc = mktime(&tm_utc); r->utc_diff = time_local - time_utc; D("%s() is called. utc_diff = %d", __FUNCTION__, r->utc_diff); } static void nmea_reader_init( NmeaReader* r ) { D("%s() is called", __FUNCTION__); memset( r, 0, sizeof(*r) ); r->fix_flags_cached = 0; r->pos = 0; r->overflow = 0; r->utc_year = -1; r->utc_mon = -1; r->utc_day = -1; nmea_reader_update_utc_diff( r ); } static int nmea_reader_update_time( NmeaReader* r, Token tok ) { int hour, minute; double seconds; struct tm tm; time_t fix_time; if (tok.p + 6 > tok.end) return -1; if (r->utc_year < 0) { // no date yet, get current one time_t now = time(NULL); gmtime_r( &now, &tm ); r->utc_year = tm.tm_year + 1900; r->utc_mon = tm.tm_mon + 1; r->utc_day = tm.tm_mday; } hour = str2int(tok.p, tok.p+2); minute = str2int(tok.p+2, tok.p+4); seconds = str2float(tok.p+4, tok.end); tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = (int) seconds; tm.tm_year = r->utc_year - 1900; tm.tm_mon = r->utc_mon - 1; tm.tm_mday = r->utc_day; tm.tm_isdst = 0; fix_time = mktime( &tm ) + r->utc_diff; #if DUMP_DATA D("fix_time=%d", fix_time); // UTC time + utc_diff #endif r->fix.timestamp = (long long)fix_time * 1000 + (int)(seconds*1000)%1000;; return 0; } static int nmea_reader_update_date( NmeaReader* r, Token date, Token time ) { Token tok = date; int day, mon, year; if (tok.p + 6 != tok.end) { D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p); return -1; } day = str2int(tok.p, tok.p+2); mon = str2int(tok.p+2, tok.p+4); year = str2int(tok.p+4, tok.p+6) + 2000; if ((day|mon|year) < 0) { D("date not properly formatted: '%.*s'", tok.end-tok.p, tok.p); return -1; } r->utc_year = year; r->utc_mon = mon; r->utc_day = day; return nmea_reader_update_time( r, time ); } static double convert_from_hhmm( Token tok ) { double val = str2float(tok.p, tok.end); int degrees = (int)(floor(val) / 100); double minutes = val - degrees*100.; double dcoord = degrees + minutes / 60.0; return dcoord; } static int nmea_reader_update_latlong( NmeaReader* r, Token latitude, char latitudeHemi, Token longitude, char longitudeHemi ) { double lat, lon; Token tok; tok = latitude; if (tok.p + 6 > tok.end) { D("latitude is too short: '%.*s'", tok.end-tok.p, tok.p); return -1; } lat = convert_from_hhmm(tok); if (latitudeHemi == 'S') lat = -lat; tok = longitude; if (tok.p + 6 > tok.end) { D("longitude is too short: '%.*s'", tok.end-tok.p, tok.p); return -1; } lon = convert_from_hhmm(tok); if (longitudeHemi == 'W') lon = -lon; r->fix.flags |= GPS_LOCATION_HAS_LAT_LONG; r->fix.latitude = lat; r->fix.longitude = lon; return 0; } static int nmea_reader_update_altitude( NmeaReader* r, Token altitude, Token units, Token geoid_height ) { /* * Height can be measured in two ways. * The altitude we get from NMEA is H. * The altitude in gps.h is defined as h. * So the required output must be h = H + N. * * h: Height (h) above the WGS84 reference ellipsoid. * H: Height (H) above Geoid (mean sea level). * N: Height of Geoid (mean sea level) above the WGS84 ellipsoid. */ if (altitude.p >= altitude.end) return -1; if (geoid_height.p >= geoid_height.end) return -1; r->fix.flags |= GPS_LOCATION_HAS_ALTITUDE; r->fix.altitude = str2float(altitude.p, altitude.end) + str2float(geoid_height.p, geoid_height.end); return 0; } static int nmea_reader_update_accuracy( NmeaReader* r, Token accuracy ) { Token tok = accuracy; if (tok.p >= tok.end) return -1; r->fix.flags |= GPS_LOCATION_HAS_ACCURACY; float precision = (float)get_precision_value(); r->fix.accuracy = (float)str2float(tok.p, tok.end) * precision; return 0; } static int nmea_reader_update_bearing( NmeaReader* r, Token bearing ) { Token tok = bearing; if (tok.p >= tok.end) return -1; r->fix.flags |= GPS_LOCATION_HAS_BEARING; r->fix.bearing = (float)str2float(tok.p, tok.end); return 0; } static int nmea_reader_update_speed( NmeaReader* r, Token speed ) { Token tok = speed; if (tok.p >= tok.end) return -1; r->fix.flags |= GPS_LOCATION_HAS_SPEED; // convert knots into m/sec (1 knot equals 1.852 km/h, 1 km/h equals 3.6 m/s) // since 1.852 / 3.6 is an odd value (periodic), we're calculating the quotient on the fly // to obtain maximum precision (we don't want 1.9999 instead of 2) r->fix.speed = (float)str2float(tok.p, tok.end) * 1.852 / 3.6; return 0; } static void nmea_reader_parse( NmeaReader* r ) { /* we received a complete sentence, now parse it to generate * a new GPS fix... */ NmeaTokenizer tzer[1]; Token tok; int report_nmea = 0; #if DUMP_DATA D("Received: %.*s", r->pos, r->in); #endif if (r->pos < 9) { #if DUMP_DATA D("Too short. discarded."); #endif return; } nmea_tokenizer_init(tzer, r->in, r->in + r->pos); /* #if GPS_DEBUG { int n; D("Found %d tokens", tzer->count); for (n = 0; n < tzer->count; n++) { Token tok = nmea_tokenizer_get(tzer,n); D("size of %2d: '%d', ptr=%x", n, tok.end-tok.p, tok.p); D("%2d: '%.*s'", n, tok.end-tok.p, tok.p); } } #endif */ tok = nmea_tokenizer_get(tzer, 0); if (tok.p + 5 > tok.end) { D("sentence id '%.*s' too short, ignored.", tok.end-tok.p, tok.p); return; } // ignore first two characters. tok.p += 2; if ( !memcmp(tok.p, "GSV", 3) ) { // Satellites in View Token tok_num_svs = nmea_tokenizer_get(tzer, 3); int num_svs = str2int(tok_num_svs.p, tok_num_svs.end); //report_nmea = 1; if (num_svs > 0) { Token tok_total_sentences= nmea_tokenizer_get(tzer, 1); Token tok_sentence_no = nmea_tokenizer_get(tzer, 2); int sentence_no = str2int(tok_sentence_no.p, tok_sentence_no.end); int total_sentences = str2int(tok_total_sentences.p, tok_total_sentences.end); int curr; int i; if (sentence_no == 1) { r->sv_status_changed = 0; r->sv_status.num_svs = 0; memset( r->sv_status.sv_list, 0, sizeof(r->sv_status.sv_list) ); } curr = (sentence_no - 1) * 4; i = 0; while (i < 4 && r->sv_status.num_svs < num_svs) { Token tok_prn = nmea_tokenizer_get(tzer, i*4 + 4); Token tok_elevation = nmea_tokenizer_get(tzer, i*4 + 5); Token tok_azimuth = nmea_tokenizer_get(tzer, i*4 + 6); Token tok_snr = nmea_tokenizer_get(tzer, i*4 + 7); float snr = str2float(tok_snr.p, tok_snr.end); if (snr > 0) { r->sv_status.sv_list[curr].prn = str2int(tok_prn.p, tok_prn.end); r->sv_status.sv_list[curr].elevation = str2float(tok_elevation.p, tok_elevation.end); r->sv_status.sv_list[curr].azimuth = str2float(tok_azimuth.p, tok_azimuth.end); r->sv_status.sv_list[curr].snr = snr; r->sv_status.num_svs += 1; } #if DUMP_DATA D("GSV sentence %2d of %d: prn=%2d", curr+1, num_svs, r->sv_status.sv_list[curr].prn); #endif curr += 1; i += 1; } if (sentence_no == total_sentences) { r->sv_status_changed = 1; } } } else if ( !memcmp(tok.p, "GGA", 3) ) { // GPS fix Token tok_fix_status = nmea_tokenizer_get(tzer,6); report_nmea = 1; // Fix quality: {0 = invalid}, {1 = GPS fix}, ... if (tok_fix_status.p[0] > '0') { Token tok_time = nmea_tokenizer_get(tzer,1); Token tok_latitude = nmea_tokenizer_get(tzer,2); Token tok_latitudeHemi = nmea_tokenizer_get(tzer,3); Token tok_longitude = nmea_tokenizer_get(tzer,4); Token tok_longitudeHemi = nmea_tokenizer_get(tzer,5); Token tok_accuracy = nmea_tokenizer_get(tzer,8); Token tok_altitude = nmea_tokenizer_get(tzer,9); Token tok_altitudeUnits = nmea_tokenizer_get(tzer,10); Token tok_geoidHeight = nmea_tokenizer_get(tzer,11); nmea_reader_update_time(r, tok_time); nmea_reader_update_latlong(r, tok_latitude, tok_latitudeHemi.p[0], tok_longitude, tok_longitudeHemi.p[0]); nmea_reader_update_accuracy(r, tok_accuracy); nmea_reader_update_altitude(r, tok_altitude, tok_altitudeUnits, tok_geoidHeight); } } else if ( !memcmp(tok.p, "RMC", 3) ) { // Recommended minimum specific GPS/Transit data Token tok_fix_status = nmea_tokenizer_get(tzer, 2); report_nmea = 1; // Status: {A = active} or {V = void} if (tok_fix_status.p[0] == 'A') { Token tok_time = nmea_tokenizer_get(tzer,1); Token tok_latitude = nmea_tokenizer_get(tzer,3); Token tok_latitudeHemi = nmea_tokenizer_get(tzer,4); Token tok_longitude = nmea_tokenizer_get(tzer,5); Token tok_longitudeHemi = nmea_tokenizer_get(tzer,6); Token tok_speed = nmea_tokenizer_get(tzer,7); Token tok_bearing = nmea_tokenizer_get(tzer,8); Token tok_date = nmea_tokenizer_get(tzer,9); nmea_reader_update_date( r, tok_date, tok_time ); nmea_reader_update_latlong( r, tok_latitude, tok_latitudeHemi.p[0], tok_longitude, tok_longitudeHemi.p[0] ); nmea_reader_update_bearing( r, tok_bearing ); nmea_reader_update_speed ( r, tok_speed ); } } else if ( !memcmp(tok.p, "GSA", 3) ) { // GPS DOP and active satellites. Token tok_fix_status = nmea_tokenizer_get(tzer, 2); report_nmea = 1; r->sv_status.used_in_fix_mask = 0ul; // {3 = 3D fix}, {2 = 2D fix}, {1 = no fix} if (tok_fix_status.p[0] == '3' || tok_fix_status.p[0] == '2') { // We have accuracy in GGA //Token tok_accuracy = nmea_tokenizer_get(tzer, 16); //nmea_reader_update_accuracy(r, tok_accuracy); int i; for (i = 3; i <= 14; ++i) { Token tok_prn = nmea_tokenizer_get(tzer, i); int prn = str2int(tok_prn.p, tok_prn.end); if (prn > 0) r->sv_status.used_in_fix_mask |= (1ul << (prn-1)); } } #if DUMP_DATA D("%s: used_in_fix_mask is 0x%x", __FUNCTION__, r->sv_status.used_in_fix_mask); #endif r->sv_status_changed = 1; } else { tok.p -= 2; #if DUMP_DATA D("unknown sentence '%.*s", tok.end-tok.p, tok.p); #endif } #if DUMP_DATA if (r->fix.flags) { char temp[256]; char* p = temp; char* end = p + sizeof(temp); struct tm utc; p += snprintf( p, end-p, "fix" ); if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) { p += snprintf(p, end-p, " lat=%g lon=%g", r->fix.latitude, r->fix.longitude); } if (r->fix.flags & GPS_LOCATION_HAS_ALTITUDE) { p += snprintf(p, end-p, " altitude=%g", r->fix.altitude); } if (r->fix.flags & GPS_LOCATION_HAS_SPEED) { p += snprintf(p, end-p, " speed=%g", r->fix.speed); } if (r->fix.flags & GPS_LOCATION_HAS_BEARING) { p += snprintf(p, end-p, " bearing=%g", r->fix.bearing); } if (r->fix.flags & GPS_LOCATION_HAS_ACCURACY) { p += snprintf(p, end-p, " accuracy=%g", r->fix.accuracy); } if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) { time_t time = r->fix.timestamp / 1000; p += snprintf(p, end-p, " time=%s", ctime(&time) ); } D("%s", temp); } #endif if (report_nmea) { struct timeval tv; gettimeofday(&tv, NULL); update_gps_nmea(tv.tv_sec*1000+tv.tv_usec/1000, r->in, r->pos); report_nmea = 0; } } static void nmea_reader_addc( NmeaReader* r, int c ) { if (r->overflow) { r->overflow = (c != '\n'); return; } if (r->pos >= (int) sizeof(r->in)-1 ) { r->overflow = 1; r->pos = 0; return; } r->in[r->pos] = (char)c; r->pos += 1; if (c == '\n') { #if ENABLE_NMEA GPS_STATE_LOCK_FIX(_gps_state); nmea_reader_parse( r ); GPS_STATE_UNLOCK_FIX(_gps_state); #endif r->pos = 0; } } /*****************************************************************/ /*****************************************************************/ /***** *****/ /***** C O N N E C T I O N S T A T E *****/ /***** *****/ /*****************************************************************/ /*****************************************************************/ /* commands sent to the gps thread */ enum { CMD_QUIT = 0, CMD_START = 1, CMD_STOP = 2 }; static void gps_state_done( GpsState* s ) { update_gps_status(GPS_STATUS_ENGINE_OFF); // tell the thread to quit, and wait for it char cmd = CMD_QUIT; int ret; void* dummy; do { ret=write( s->control[0], &cmd, 1 ); } while (ret < 0 && errno == EINTR); pthread_join(s->thread, &dummy); pthread_join(s->pos_thread, &dummy); // close the control socket pair close( s->control[0] ); s->control[0] = -1; close( s->control[1] ); s->control[1] = -1; // close connection to the GPS daemon close( s->fd ); s->fd = -1; s->init = STATE_QUIT; #if ENABLE_NMEA sem_destroy(&s->fix_sem); #endif } static void gps_state_start( GpsState* s ) { // Navigation started. update_gps_status(GPS_STATUS_SESSION_BEGIN); char cmd = CMD_START; int ret; do { ret=write( s->control[0], &cmd, 1 ); } while (ret < 0 && errno == EINTR); if (ret != 1) D("%s: could not send CMD_START command: ret=%d: %s", __FUNCTION__, ret, strerror(errno)); } static void gps_state_stop( GpsState* s ) { // Navigation ended. update_gps_status(GPS_STATUS_SESSION_END); char cmd = CMD_STOP; int ret; do { ret=write( s->control[0], &cmd, 1 ); } while (ret < 0 && errno == EINTR); if (ret != 1) D("%s: could not send CMD_STOP command: ret=%d: %s", __FUNCTION__, ret, strerror(errno)); } static int epoll_register( int epoll_fd, int fd ) { struct epoll_event ev; int ret, flags; /* important: make the fd non-blocking */ flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK); ev.events = EPOLLIN; ev.data.fd = fd; do { ret = epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &ev ); } while (ret < 0 && errno == EINTR); return ret; } static int epoll_deregister( int epoll_fd, int fd ) { int ret; do { ret = epoll_ctl( epoll_fd, EPOLL_CTL_DEL, fd, NULL ); } while (ret < 0 && errno == EINTR); return ret; } void update_gps_location(GpsLocation *location) { #if DUMP_DATA D("%s(): GpsLocation=%f, %f", __FUNCTION__, location->latitude, location->longitude); #endif GpsState* state = _gps_state; //Should be made thread safe... if(state->callbacks.location_cb) state->callbacks.location_cb(location); } void update_gps_status(GpsStatusValue value) { D("%s(): GpsStatusValue=%d", __FUNCTION__, value); GpsState* state = _gps_state; //Should be made thread safe... state->status.status=value; if(state->callbacks.status_cb) state->callbacks.status_cb(&state->status); } void update_gps_svstatus(GpsSvStatus *svstatus) { #if DUMP_DATA D("%s(): GpsSvStatus.num_svs=%d", __FUNCTION__, svstatus->num_svs); #endif GpsState* state = _gps_state; //Should be made thread safe... if(state->callbacks.sv_status_cb) state->callbacks.sv_status_cb(svstatus); } void update_gps_nmea(GpsUtcTime timestamp, const char* nmea, int length) { #if DUMP_DATA D("%s(): length=%d, NMEA=%.*s", __FUNCTION__, length, length, nmea); #endif GpsState* state = _gps_state; //Should be made thread safe... if(state->callbacks.nmea_cb) state->callbacks.nmea_cb(timestamp, nmea, length); } /* this is the main thread, it waits for commands from gps_state_start/stop and, * when started, messages from the NMEA SMD. these are simple NMEA sentences * that must be parsed to be converted into GPS fixes sent to the framework */ static void* gps_state_thread( void* arg ) { GpsState* state = (GpsState*) arg; NmeaReader *reader; int epoll_fd = epoll_create(2); int gps_fd = state->fd; int control_fd = state->control[1]; reader = &state->reader; nmea_reader_init( reader ); // register control file descriptors for polling epoll_register( epoll_fd, control_fd ); if (gps_fd > -1) { 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, gps_fd>-1 ? 2 : 1, -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() !?"); goto Exit; } 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"); active = 0; pthread_cond_signal(&get_pos_ready_cond); pthread_cond_signal(&get_position_cond); goto Exit; } else if (cmd == CMD_START) { if (!started) { D("gps thread starting location_cb=%p", state->callbacks.location_cb); started = 1; pthread_cond_signal(&get_position_cond); #if ENABLE_NMEA state->init = STATE_START; if ( pthread_create( &state->tmr_thread, NULL, gps_timer_thread, state ) != 0 ) { LOGE("could not create gps_timer_thread: %s", strerror(errno)); started = 0; state->init = STATE_INIT; goto Exit; } #endif } } else if (cmd == CMD_STOP) { if (started) { D("gps thread stopping"); started = 0; pthread_cond_signal(&get_pos_ready_cond); #if ENABLE_NMEA void* dummy; state->init = STATE_INIT; pthread_join(state->tmr_thread, &dummy); #endif exit_gps_rpc(); } } } else if (fd == gps_fd) { char buf[512]; int nn, ret; #if DUMP_DATA D("gps fd event"); #endif do { ret = read( fd, buf, sizeof(buf) ); } while (ret < 0 && errno == EINTR); if (ret > 0) { for (nn = 0; nn < ret; nn++) { nmea_reader_addc( reader, buf[nn] ); #if DUMP_DATA & 0 D("%2d, nmea_reader_addc() is called", nn+1); #endif } } #if DUMP_DATA D("gps fd event end"); #endif } else { LOGE("epoll_wait() returned unkown fd %d ?", fd); } } } } Exit: return NULL; } #if ENABLE_NMEA static void* gps_timer_thread( void* arg ) { D("%s() running", __FUNCTION__); GpsState *state = (GpsState*) arg; NmeaReader *r = &(state->reader); r->fix.flags = 0; r->fix_flags_cached = 0; r->sv_status_changed = 0; r->sv_status.num_svs = 0; memset( r->sv_status.sv_list, 0, sizeof(r->sv_status.sv_list) ); do { GPS_STATE_LOCK_FIX(state); #if DUMP_DATA D("r->fix.flags = 0x%x", r->fix.flags); #endif if (r->fix.flags & GPS_LOCATION_HAS_LAT_LONG) { if (r->fix_flags_cached > 0) r->fix.flags |= r->fix_flags_cached; r->fix_flags_cached = r->fix.flags; update_gps_location( &r->fix ); #if DUMP_DATA D("r->fix.flags = 0x%x", r->fix.flags); #endif r->fix.flags = 0; } if (r->sv_status_changed) { update_gps_svstatus( &r->sv_status ); r->sv_status_changed = 0; } GPS_STATE_UNLOCK_FIX(state); uint64_t microseconds = (state->fix_freq * 1000000) - 500000; usleep(microseconds); //D("%s() usleep(%ld)", __FUNCTION__, microseconds); } while(state->init == STATE_START); D("%s() destroyed", __FUNCTION__); return NULL; } #endif void pdsm_pd_callback() { pthread_cond_signal(&get_pos_ready_cond); } static void* gps_get_position_thread( void* arg ) { D("%s() running", __FUNCTION__); GpsState* s = _gps_state; while(active) { while(started) { gps_get_position(); pthread_mutex_lock(&get_pos_ready_mutex); pthread_cond_wait(&get_pos_ready_cond, &get_pos_ready_mutex); pthread_mutex_unlock(&get_pos_ready_mutex); } pthread_mutex_lock(&get_position_mutex); pthread_cond_wait(&get_position_cond, &get_position_mutex); pthread_mutex_unlock(&get_position_mutex); } D("%s() destroyed", __FUNCTION__); return NULL; } static void gps_state_init( GpsState* state ) { update_gps_status(GPS_STATUS_ENGINE_ON); state->init = STATE_INIT;; state->control[0] = -1; state->control[1] = -1; state->fix_freq = -1; #if ENABLE_NMEA state->fd = open("/dev/smd27", O_RDONLY); #else state->fd = -1; #endif active = 1; #if ENABLE_NMEA if ( sem_init(&state->fix_sem, 0, 1) != 0 ) { LOGE("gps semaphore initialization failed: %s", strerror(errno)); goto Fail; } #endif if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, state->control ) < 0 ) { LOGE("could not create thread control socket pair: %s", strerror(errno)); goto Fail; } if ( pthread_create( &state->thread, NULL, gps_state_thread, state ) != 0 ) { LOGE("could not create gps thread: %s", strerror(errno)); goto Fail; } if ( pthread_create( &state->pos_thread, NULL, gps_get_position_thread, NULL ) != 0 ) { LOGE("could not create gps_get_position_thread: %s", strerror(errno)); goto Fail; } if(init_gps_rpc()) goto Fail; D("gps state initialized"); return; Fail: gps_state_done( state ); } /*****************************************************************/ /*****************************************************************/ /***** *****/ /***** I N T E R F A C E *****/ /***** *****/ /*****************************************************************/ /*****************************************************************/ /***** GpsXtraInterface *****/ static int gps_xtra_init(GpsXtraCallbacks* callbacks) { D("%s() is called", __FUNCTION__); GpsState* s = _gps_state; s->xtra_callbacks = *callbacks; return 0; } static int gps_xtra_inject_xtra_data(char* data, int length) { D("%s() is called", __FUNCTION__); D("gps_xtra_inject_xtra_data: xtra size = %d, data ptr = 0x%x\n", length, (int) data); GpsState* s = _gps_state; if (!s->init) return 0; int rpc_ret_val = -1; int ret_val = -1; unsigned char *xtra_data_ptr; uint32_t part_len; uint8_t part; uint8_t total_parts; uint16_t len_injected; total_parts = (length / XTRA_BLOCK_SIZE); if ((total_parts % XTRA_BLOCK_SIZE) != 0) { total_parts += 1; } uint8_t part_no = total_parts % 10; if (part_no > 0) part_no = total_parts - part_no; else part_no = total_parts - 5; len_injected = 0; // O bytes injected // XTRA injection starts with part 1 D("gps_xtra_inject_xtra_data: inject part = %d/%d, len = %d\n", 1, total_parts, XTRA_BLOCK_SIZE); D("gps_xtra_inject_xtra_data: ......"); for (part = 1; part <= total_parts; part++) { part_len = XTRA_BLOCK_SIZE; if (XTRA_BLOCK_SIZE > (length - len_injected)) { part_len = length - len_injected; } xtra_data_ptr = data + len_injected; if (part > part_no) // reduce the number of the xtra debugging info D("gps_xtra_inject_xtra_data: inject part = %d/%d, len = %d\n", part, total_parts, part_len); if (part < total_parts) { rpc_ret_val = gps_xtra_set_data(xtra_data_ptr, part_len, part, total_parts); if (rpc_ret_val == -1) { D("gps_xtra_set_data() for xtra returned %d \n", rpc_ret_val); ret_val = EINVAL; // return error break; } } else // part == total_parts { ret_val = gps_xtra_set_data(xtra_data_ptr, part_len, part, total_parts); break; // done with injection } len_injected += part_len; } return ret_val; } void xtra_download_request() { D("%s() is called", __FUNCTION__); GpsState* state = _gps_state; //Should be made thread safe... if(state->xtra_callbacks.download_request_cb) state->xtra_callbacks.download_request_cb(); } static const GpsXtraInterface sGpsXtraInterface = { gps_xtra_init, gps_xtra_inject_xtra_data, }; /***** AGpsInterface *****/ static void agps_init(AGpsCallbacks* callbacks) { D("%s() is called", __FUNCTION__); GpsState* s = _gps_state; s->agps_callbacks = *callbacks; } static int agps_data_conn_open(const char* apn) { D("%s() is called", __FUNCTION__); D("apn=%s", apn); /* not yet implemented */ return 0; } static int agps_data_conn_closed() { D("%s() is called", __FUNCTION__); /* not yet implemented */ return 0; } static int agps_data_conn_failed() { D("%s() is called", __FUNCTION__); /* not yet implemented */ return 0; } static int agps_set_server(AGpsType type, const char* hostname, int port) { D("%s() is called", __FUNCTION__); D("type=%d, hostname=%s, port=%d", type, hostname, port); /* not yet implemented */ return 0; } static const AGpsInterface sAGpsInterface = { agps_init, agps_data_conn_open, agps_data_conn_closed, agps_data_conn_failed, agps_set_server, }; /***** GpsInterface *****/ static int gps_init(GpsCallbacks* callbacks) { D("%s() is called", __FUNCTION__); GpsState* s = _gps_state; if (!s->init) gps_state_init(s); s->callbacks = *callbacks; return 0; } static void gps_cleanup() { D("%s() is called", __FUNCTION__); if (get_cleanup_value()) { GpsState* s = _gps_state; if (s->init) { gps_state_done(s); cleanup_gps_rpc_clients(); } } } static int gps_start() { D("%s: called", __FUNCTION__); GpsState* s = _gps_state; if (!s->init) { D("%s: called with uninitialized state !!", __FUNCTION__); return -1; } gps_state_start(s); return 0; } static int gps_stop() { D("%s: called", __FUNCTION__); GpsState* s = _gps_state; if (!s->init) { D("%s: called with uninitialized state !!", __FUNCTION__); return -1; } gps_state_stop(s); return 0; } static int gps_inject_time(GpsUtcTime time, int64_t timeReference, int uncertainty) { D("%s() is called", __FUNCTION__); D("time=%lld, timeReference=%lld, uncertainty=%d", time, timeReference, uncertainty); GpsState* s = _gps_state; if (!s->init) return 0; int ret_val = -1; ret_val = gps_xtra_inject_time_info(time, timeReference, uncertainty); return ret_val; } static int gps_inject_location(double latitude, double longitude, float accuracy) { D("%s() is called", __FUNCTION__); D("latitude=%f, longitude=%f, accuracy=%f", latitude, longitude, accuracy); /* not yet implemented */ return 0; } static void gps_delete_aiding_data(GpsAidingData flags) { D("%s() is called", __FUNCTION__); D("flags=%d", flags); /* not yet implemented */ } static int gps_set_position_mode(GpsPositionMode mode, int fix_frequency) { D("%s() is called", __FUNCTION__); D("fix_frequency=%d", fix_frequency); GpsState* s = _gps_state; if (!s->init) return 0; if (fix_frequency == 0) { //We don't handle single shot requests atm... //So one every 1 seconds will it be. fix_frequency = 1; } else if (fix_frequency > 1800) { //30mins fix_frequency = 1800; } // fix_frequency is only used by NMEA version s->fix_freq = fix_frequency; return 0; } static const void* gps_get_extension(const char* name) { D("%s('%s') is called", __FUNCTION__, name); if (!strcmp(name, GPS_XTRA_INTERFACE)) { return &sGpsXtraInterface; } else if (!strcmp(name, AGPS_INTERFACE)) { return &sAGpsInterface; } return NULL; } static const GpsInterface hardwareGpsInterface = { gps_init, gps_start, gps_stop, gps_cleanup, gps_inject_time, gps_inject_location, gps_delete_aiding_data, gps_set_position_mode, gps_get_extension, }; const GpsInterface* gps_get_hardware_interface() { D("%s() is called", __FUNCTION__); return &hardwareGpsInterface; } // END OF FILE