2 * Copyright (C) 2006, 2007 John Costigan.
4 * POI and GPS-Info code originally written by Cezary Jackiewicz.
6 * Default map data provided by http://www.openstreetmap.org/
8 * This file is part of Maemo Mapper.
10 * Maemo Mapper is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * Maemo Mapper is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
34 #include <libgnomevfs/gnome-vfs.h>
35 #include <libgnomevfs/gnome-vfs-inet-connection.h>
39 # include <hildon/hildon-note.h>
40 # include <hildon/hildon-banner.h>
42 # include <hildon-widgets/hildon-note.h>
43 # include <hildon-widgets/hildon-banner.h>
64 static volatile GThread *_gps_thread = NULL;
65 static GMutex *_gps_init_mutex = NULL;
66 static gint _gps_rcvr_retry_count = 0;
68 static gint _gmtoffset = 0;
71 #define MACRO_PARSE_INT(tofill, str) { \
73 (tofill) = strtol((str), &error_check, 10); \
74 if(error_check == (str)) \
76 g_printerr("Line %d: Failed to parse string as int: %s\n", \
78 MACRO_BANNER_SHOW_INFO(_window, \
79 _("Invalid NMEA input from receiver!")); \
83 #define MACRO_PARSE_FLOAT(tofill, str) { \
85 (tofill) = g_ascii_strtod((str), &error_check); \
86 if(error_check == (str)) \
88 g_printerr("Failed to parse string as float: %s\n", str); \
89 MACRO_BANNER_SHOW_INFO(_window, \
90 _("Invalid NMEA input from receiver!")); \
95 gps_parse_rmc(gchar *sentence)
97 /* Recommended Minimum Navigation Information C
99 * 2) Status, V=Navigation receiver warning A=Valid
104 * 7) Speed over ground, knots
105 * 8) Track made good, degrees true
107 * 10) Magnetic Variation, degrees
109 * 12) FAA mode indicator (NMEA 2.3 and later)
112 gchar *token, *dpoint, *gpsdate = NULL;
115 gboolean newly_fixed = FALSE;
116 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
121 token = strsep(&sentence, DELIM);
125 token = strsep(&sentence, DELIM);
126 /* Token is now Status. */
127 if(token && *token == 'A')
130 if(_gps_state < RCVR_FIXED)
133 set_conn_state(RCVR_FIXED);
138 /* Data is invalid - not enough satellites?. */
139 if(_gps_state > RCVR_UP)
141 set_conn_state(RCVR_UP);
142 track_insert_break(FALSE);
146 /* Parse the latitude. */
147 token = strsep(&sentence, DELIM);
150 dpoint = strchr(token, '.');
151 if(!dpoint) /* handle buggy NMEA */
152 dpoint = token + strlen(token);
153 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
155 MACRO_PARSE_INT(tmpi, token);
156 _gps.lat = tmpi + (tmpd * (1.0 / 60.0));
160 token = strsep(&sentence, DELIM);
161 if(token && *token == 'S')
162 _gps.lat = -_gps.lat;
164 /* Parse the longitude. */
165 token = strsep(&sentence, DELIM);
168 dpoint = strchr(token, '.');
169 if(!dpoint) /* handle buggy NMEA */
170 dpoint = token + strlen(token);
171 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
173 MACRO_PARSE_INT(tmpi, token);
174 _gps.lon = tmpi + (tmpd * (1.0 / 60.0));
178 token = strsep(&sentence, DELIM);
179 if(token && *token == 'W')
180 _gps.lon = -_gps.lon;
182 /* Parse speed over ground, knots. */
183 token = strsep(&sentence, DELIM);
186 MACRO_PARSE_FLOAT(_gps.speed, token);
188 _gps.maxspeed = MAX(_gps.maxspeed, _gps.speed);
191 /* Parse heading, degrees from true north. */
192 token = strsep(&sentence, DELIM);
195 MACRO_PARSE_FLOAT(_gps.heading, token);
199 token = strsep(&sentence, DELIM);
200 if(token && *token && gpsdate)
203 gpsdate[6] = '\0'; /* Make sure time is 6 chars long. */
204 strcat(gpsdate, token);
205 strptime(gpsdate, "%H%M%S%d%m%y", &time);
206 _pos.time = mktime(&time) + _gmtoffset;
209 _pos.time = time(NULL);
211 /* Translate data into integers. */
212 latlon2unit(_gps.lat, _gps.lon, _pos.unitx, _pos.unity);
214 /* Add new data to track. */
215 if(_gps_state == RCVR_FIXED)
217 if(track_add(_pos.time, newly_fixed) || newly_fixed)
219 /* Move mark to new location. */
220 map_refresh_mark(FALSE);
228 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
232 gps_parse_gga(gchar *sentence)
234 /* GGA Global Positioning System Fix Data
245 4 = Real Time Kinematic
247 6 = estimated (dead reckoning) (2.3 feature)
248 7 = Manual input mode
250 7. Number of satellites being tracked
251 8. Horizontal dilution of precision
252 9. Altitude, Meters, above mean sea level
253 10. Alt unit (meters)
254 11. Height of geoid (mean sea level) above WGS84 ellipsoid
256 13. (empty field) time in seconds since last DGPS update
257 14. (empty field) DGPS station ID number
258 15. the checksum data
261 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
266 token = strsep(&sentence, DELIM);
268 token = strsep(&sentence, DELIM);
270 token = strsep(&sentence, DELIM);
272 token = strsep(&sentence, DELIM);
274 token = strsep(&sentence, DELIM);
276 /* Parse Fix quality */
277 token = strsep(&sentence, DELIM);
279 MACRO_PARSE_INT(_gps.fixquality, token);
281 /* Skip number of satellites */
282 token = strsep(&sentence, DELIM);
284 /* Parse Horizontal dilution of precision */
285 token = strsep(&sentence, DELIM);
287 MACRO_PARSE_INT(_gps.hdop, token);
290 token = strsep(&sentence, DELIM);
293 MACRO_PARSE_FLOAT(_pos.altitude, token);
298 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
302 gps_parse_gsa(gchar *sentence)
304 /* GPS DOP and active satellites
305 * 1) Auto selection of 2D or 3D fix (M = manual)
306 * 2) 3D fix - values include: 1 = no fix, 2 = 2D, 3 = 2D
307 * 3) PRNs of satellites used for fix
309 * 4) PDOP (dilution of precision)
310 * 5) Horizontal dilution of precision (HDOP)
311 * 6) Vertical dilution of precision (VDOP)
316 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
320 /* Skip Auto selection. */
321 token = strsep(&sentence, DELIM);
324 token = strsep(&sentence, DELIM);
326 MACRO_PARSE_INT(_gps.fix, token);
329 for(i = 0; i < 12; i++)
331 token = strsep(&sentence, DELIM);
333 _gps.satforfix[_gps.satinuse++] = atoi(token);
337 token = strsep(&sentence, DELIM);
339 MACRO_PARSE_FLOAT(_gps.pdop, token);
342 token = strsep(&sentence, DELIM);
344 MACRO_PARSE_FLOAT(_gps.hdop, token);
347 token = strsep(&sentence, DELIM);
349 MACRO_PARSE_FLOAT(_gps.vdop, token);
351 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
355 gps_parse_gsv(gchar *sentence)
357 /* Must be GSV - Satellites in view
358 * 1) total number of messages
360 * 3) satellites in view
361 * 4) satellite number
362 * 5) elevation in degrees (0-90)
363 * 6) azimuth in degrees to true north (0-359)
364 * 7) SNR in dB (0-99)
365 * more satellite infos like 4)-7)
369 gint msgcnt = 0, nummsgs = 0;
370 static gint running_total = 0;
371 static gint num_sats_used = 0;
372 static gint satcnt = 0;
373 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
375 /* Parse number of messages. */
376 token = strsep(&sentence, DELIM);
378 MACRO_PARSE_INT(nummsgs, token);
380 /* Parse message number. */
381 token = strsep(&sentence, DELIM);
383 MACRO_PARSE_INT(msgcnt, token);
385 /* Parse number of satellites in view. */
386 token = strsep(&sentence, DELIM);
389 MACRO_PARSE_INT(_gps.satinview, token);
390 if(_gps.satinview > 12) /* Handle buggy NMEA. */
394 /* Loop until there are no more satellites to parse. */
395 while(sentence && satcnt < 12)
397 /* Get token for Satellite Number. */
398 token = strsep(&sentence, DELIM);
400 _gps_sat[satcnt].prn = atoi(token);
402 /* Get token for elevation in degrees (0-90). */
403 token = strsep(&sentence, DELIM);
405 _gps_sat[satcnt].elevation = atoi(token);
407 /* Get token for azimuth in degrees to true north (0-359). */
408 token = strsep(&sentence, DELIM);
410 _gps_sat[satcnt].azimuth = atoi(token);
412 /* Get token for SNR. */
413 token = strsep(&sentence, DELIM);
414 if(token && *token && (_gps_sat[satcnt].snr = atoi(token)))
416 /* SNR is non-zero - add to total and count as used. */
417 running_total += _gps_sat[satcnt].snr;
423 if(msgcnt == nummsgs)
425 /* This is the last message. Calculate signal strength. */
428 if(_gps_state == RCVR_UP)
430 gdouble fraction = running_total * sqrtf(num_sats_used)
431 / num_sats_used / 150.0;
432 BOUND(fraction, 0.0, 1.0);
433 hildon_banner_set_fraction(
434 HILDON_BANNER(_fix_banner), fraction);
436 /* Keep awake while they watch the progress bar. */
437 UNBLANK_SCREEN(TRUE, FALSE);
445 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
449 gps_handle_error_idle(gchar *error)
451 printf("%s(%s)\n", __PRETTY_FUNCTION__, error);
453 /* Ask for re-try. */
454 if(++_gps_rcvr_retry_count > 2)
457 gchar buffer[BUFFER_SIZE];
459 /* Reset retry count. */
460 _gps_rcvr_retry_count = 0;
462 snprintf(buffer, sizeof(buffer), "%s\nRetry?", error);
463 confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), buffer);
467 if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
468 rcvr_connect(); /* Try again. */
472 gtk_check_menu_item_set_active(
473 GTK_CHECK_MENU_ITEM(_menu_gps_enable_item), FALSE);
476 /* Ask user to re-connect. */
477 gtk_widget_destroy(confirm);
482 rcvr_connect(); /* Try again. */
487 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
492 gps_parse_nmea_idle(gchar *nmea)
494 printf("%s(%s)\n", __PRETTY_FUNCTION__, nmea);
496 if(_enable_gps && _gps_state >= RCVR_DOWN)
498 if(_gps_state < RCVR_UP)
499 set_conn_state(RCVR_UP);
501 if(!strncmp(nmea + 3, "GSV", 3))
503 if(_gps_state == RCVR_UP || _gps_info || _satdetails_on)
504 gps_parse_gsv(nmea + 7);
506 else if(!strncmp(nmea + 3, "RMC", 3))
507 gps_parse_rmc(nmea + 7);
508 else if(!strncmp(nmea + 3, "GGA", 3))
509 gps_parse_gga(nmea + 7);
510 else if(!strncmp(nmea + 3, "GSA", 3))
511 gps_parse_gsa(nmea + 7);
516 gps_display_details();
521 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
526 gri_clone(GpsRcvrInfo *gri)
528 GpsRcvrInfo *ret = g_new(GpsRcvrInfo, 1);
529 ret->type = gri->type;
530 ret->bt_mac = g_strdup(gri->bt_mac);
531 ret->file_path = g_strdup(gri->file_path);
532 ret->gpsd_host = g_strdup(gri->gpsd_host);
533 ret->gpsd_port = gri->gpsd_port;
538 gri_free(GpsRcvrInfo *gri)
541 g_free(gri->file_path);
542 g_free(gri->gpsd_host);
547 thread_read_nmea(GpsRcvrInfo *gri)
549 gchar buf[BUFFER_SIZE];
550 gchar *buf_curr = buf;
551 gchar *buf_last = buf + sizeof(buf) - 1;
552 GnomeVFSFileSize bytes_read;
553 GnomeVFSResult vfs_result;
554 gchar *gpsd_host = NULL;
556 GnomeVFSInetConnection *iconn = NULL;
557 GnomeVFSSocket *socket = NULL;
558 GThread *my_thread = g_thread_self();
559 gboolean error = FALSE;
561 gpsbt_t gpsbt_context;
563 #ifdef HAVE_LIBGPSMGR
564 gpsmgr_t gpsmgr_context;
566 gboolean is_context = FALSE;
568 printf("%s(%d)\n", __PRETTY_FUNCTION__, gri->type);
570 /* Lock/Unlock the mutex to ensure that _gps_thread is done being set. */
571 g_mutex_lock(_gps_init_mutex);
572 g_mutex_unlock(_gps_init_mutex);
579 gchar errstr[BUFFER_SIZE] = "";
580 /* We need to start gpsd (via gpsbt) first. */
581 memset(&gpsbt_context, 0, sizeof(gpsbt_context));
583 if(gpsbt_start(gri->bt_mac, 0, 0, 0, errstr, sizeof(errstr),
584 0, &gpsbt_context) < 0)
586 g_printerr("Error connecting to GPS receiver: (%d) %s (%s)\n",
587 errno, strerror(errno), errstr);
588 g_idle_add((GSourceFunc)gps_handle_error_idle,
589 g_strdup_printf("%s",
590 _("Error connecting to GPS receiver.")));
595 /* Success. Set gpsd_host and gpsd_port. */
596 gpsd_host = "127.0.0.1";
597 gpsd_port = GPSD_PORT_DEFAULT;
605 /* Set gpsd_host and gpsd_port. */
606 gpsd_host = gri->gpsd_host;
607 gpsd_port = gri->gpsd_port;
610 #ifdef HAVE_LIBGPSMGR
613 /* Use gpsmgr to create a GPSD that uses the file. */
615 gchar *gpsd_ctrl_sock;
617 devs[0] = gri->file_path;
620 /* Caller can override the name of the gpsd program and
621 * the used control socket. */
622 gpsd_prog = getenv("GPSD_PROG");
623 gpsd_ctrl_sock = getenv("GPSD_CTRL_SOCK");
626 gpsd_prog = "/usr/sbin/gpsd";
628 gpsd_ctrl_sock = "/tmp/.gpsd_ctrl_sock";
630 memset(&gpsmgr_context, 0, sizeof(gpsmgr_context));
632 if(gpsmgr_start(gpsd_prog, devs, gpsd_ctrl_sock,
633 0, 0, &gpsmgr_context) < 0)
635 g_printerr("Error opening GPS device: (%d) %s\n",
636 errno, strerror(errno));
637 g_idle_add((GSourceFunc)gps_handle_error_idle,
638 g_strdup_printf("%s",
639 _("Error opening GPS device.")));
644 /* Success. Set gpsd_host and gpsd_port. */
645 gpsd_host = "127.0.0.1";
646 gpsd_port = GPSD_PORT_DEFAULT;
656 if(!error && my_thread == _gps_thread)
660 /* Attempt to connect to GPSD. */
661 for(tryno = 0; tryno < 10; tryno++)
663 /* Create a socket to interact with GPSD. */
664 GTimeVal timeout = { 10, 0 };
666 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
668 gri->type == GPS_RCVR_GPSD ? gri->gpsd_host
670 gri->type == GPS_RCVR_GPSD ? gri->gpsd_port
673 || NULL == (socket = gnome_vfs_inet_connection_to_socket(iconn))
674 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
675 socket, &timeout, NULL))
676 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write(socket,
677 "r+\r\n", sizeof("r+\r\n"), &bytes_read, NULL))
678 || bytes_read != sizeof("r+\r\n"))
688 g_printerr("Error connecting to GPSD server: (%d) %s\n",
689 vfs_result, gnome_vfs_result_to_string(vfs_result));
690 g_idle_add((GSourceFunc)gps_handle_error_idle,
691 g_strdup_printf("%s",
692 _("Error connecting to GPSD server.")));
697 if(!error && my_thread == _gps_thread)
699 while(my_thread == _gps_thread)
703 vfs_result = gnome_vfs_socket_read(
710 if(vfs_result != GNOME_VFS_OK)
712 if(my_thread == _gps_thread)
714 /* Error wasn't user-initiated. */
715 g_idle_add((GSourceFunc)gps_handle_error_idle,
716 g_strdup_printf("%s",
717 _("Error reading GPS data.")));
723 /* Loop through the buffer and read each NMEA sentence. */
724 buf_curr += bytes_read;
725 *buf_curr = '\0'; /* append a \0 so we can read as string */
726 while(my_thread == _gps_thread && (eol = strchr(buf, '\n')))
731 gchar *sptr = buf + 1; /* Skip the $ */
732 /* This is the beginning of a sentence; okay to parse. */
733 *eol = '\0'; /* overwrite \n with \0 */
734 while(*sptr && *sptr != '*')
737 /* If we're at a \0 (meaning there is no checksum), or if
738 * the checksum is good, then parse the sentence. */
739 if(!*sptr || csum == strtol(sptr + 1, NULL, 16))
742 *sptr = '\0'; /* take checksum out of the buffer.*/
743 if(my_thread == _gps_thread)
744 g_idle_add((GSourceFunc)gps_parse_nmea_idle,
749 /* There was a checksum, and it was bad. */
750 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n",
751 __PRETTY_FUNCTION__, buf);
755 /* If eol is at or after (buf_curr - 1) */
756 if(eol >= (buf_curr - 1))
758 /* Last read was a newline - reset read buffer */
764 /* Move the next line to the front of the buffer. */
765 memmove(buf, eol + 1,
766 buf_curr - eol); /* include terminating 0 */
767 /* Subtract _curr so that it's pointing at the new \0. */
768 buf_curr -= (eol - buf + 1);
771 _gps_rcvr_retry_count = 0;
775 /* Error, or we're done reading GPS. */
779 gnome_vfs_inet_connection_free(iconn, NULL);
787 gpsbt_stop(&gpsbt_context);
791 #ifdef HAVE_LIBGPSMGR
793 gpsmgr_stop(&gpsmgr_context);
804 printf("%s(): return\n", __PRETTY_FUNCTION__);
809 * Set the connection state. This function controls all connection-related
813 set_conn_state(ConnState new_conn_state)
815 printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
817 switch(_gps_state = new_conn_state)
823 gtk_widget_destroy(_connect_banner);
824 _connect_banner = NULL;
828 gtk_widget_destroy(_fix_banner);
835 gtk_widget_destroy(_fix_banner);
839 _connect_banner = hildon_banner_show_animation(
840 _window, NULL, _("Searching for GPS receiver"));
845 gtk_widget_destroy(_connect_banner);
846 _connect_banner = NULL;
850 _fix_banner = hildon_banner_show_progress(
851 _window, NULL, _("Establishing GPS fix"));
854 default: ; /* to quell warning. */
858 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
862 * Disconnect from the receiver. This method cleans up any and everything
863 * that might be associated with the receiver.
868 printf("%s()\n", __PRETTY_FUNCTION__);
873 set_conn_state(RCVR_OFF);
874 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
878 * Connect to the receiver.
879 * This method assumes that _fd is -1 and _channel is NULL. If unsure, call
880 * rcvr_disconnect() first.
881 * Since this is an idle function, this function returns whether or not it
882 * should be called again, which is always FALSE.
887 printf("%s(%d)\n", __PRETTY_FUNCTION__, _gps_state);
889 if(_enable_gps && _gps_state == RCVR_OFF)
891 set_conn_state(RCVR_DOWN);
893 /* Lock/Unlock the mutex to ensure that the thread doesn't
894 * start until _gps_thread is set. */
895 g_mutex_lock(_gps_init_mutex);
896 _gps_thread = g_thread_create((GThreadFunc)thread_read_nmea,
897 gri_clone(&_gri), TRUE, NULL); /* Joinable. */
898 g_mutex_unlock(_gps_init_mutex);
901 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
908 printf("%s()\n", __PRETTY_FUNCTION__);
909 if(system("/usr/bin/sudo -l | grep -q '/usr/sbin/hciconfig *hci0 *reset'"
910 " && sudo /usr/sbin/hciconfig hci0 reset"))
912 _("An error occurred while trying to reset the bluetooth "
914 "Did you make sure to modify\nthe /etc/sudoers file?"));
915 else if(_gps_state != RCVR_OFF)
919 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
925 printf("%s()\n", __PRETTY_FUNCTION__);
927 _gps_init_mutex = g_mutex_new();
929 /* Fix a stupid PATH bug in libgps. */
931 gchar *path_env = getenv("PATH");
932 gchar *new_path = g_strdup_printf("%s:%s", path_env, "/usr/sbin");
933 setenv("PATH", new_path, 1);
942 localtime_r(&time1, &time2);
943 _gmtoffset = time2.tm_gmtoff;
946 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
950 gps_destroy(gboolean last)
952 static GThread* tmp = NULL;
953 printf("%s()\n", __PRETTY_FUNCTION__);
959 tmp = (GThread*)_gps_thread;
966 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);