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>
60 static volatile GThread *_gps_thread = NULL;
61 static GMutex *_gps_init_mutex = NULL;
62 static gint _gps_rcvr_retry_count = 0;
64 static gint _gmtoffset = 0;
67 #define MACRO_PARSE_INT(tofill, str) { \
69 (tofill) = strtol((str), &error_check, 10); \
70 if(error_check == (str)) \
72 g_printerr("Line %d: Failed to parse string as int: %s\n", \
74 MACRO_BANNER_SHOW_INFO(_window, \
75 _("Invalid NMEA input from receiver!")); \
79 #define MACRO_PARSE_FLOAT(tofill, str) { \
81 (tofill) = g_ascii_strtod((str), &error_check); \
82 if(error_check == (str)) \
84 g_printerr("Failed to parse string as float: %s\n", str); \
85 MACRO_BANNER_SHOW_INFO(_window, \
86 _("Invalid NMEA input from receiver!")); \
91 gps_parse_rmc(gchar *sentence)
93 /* Recommended Minimum Navigation Information C
95 * 2) Status, V=Navigation receiver warning A=Valid
100 * 7) Speed over ground, knots
101 * 8) Track made good, degrees true
103 * 10) Magnetic Variation, degrees
105 * 12) FAA mode indicator (NMEA 2.3 and later)
108 gchar *token, *dpoint, *gpsdate = NULL;
111 gboolean newly_fixed = FALSE;
112 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
117 token = strsep(&sentence, DELIM);
121 token = strsep(&sentence, DELIM);
122 /* Token is now Status. */
123 if(token && *token == 'A')
126 if(_gps_state < RCVR_FIXED)
129 set_conn_state(RCVR_FIXED);
134 /* Data is invalid - not enough satellites?. */
135 if(_gps_state > RCVR_UP)
137 set_conn_state(RCVR_UP);
138 track_insert_break(FALSE);
142 /* Parse the latitude. */
143 token = strsep(&sentence, DELIM);
146 dpoint = strchr(token, '.');
147 if(!dpoint) /* handle buggy NMEA */
148 dpoint = token + strlen(token);
149 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
151 MACRO_PARSE_INT(tmpi, token);
152 _gps.lat = tmpi + (tmpd * (1.0 / 60.0));
156 token = strsep(&sentence, DELIM);
157 if(token && *token == 'S')
158 _gps.lat = -_gps.lat;
160 /* Parse the longitude. */
161 token = strsep(&sentence, DELIM);
164 dpoint = strchr(token, '.');
165 if(!dpoint) /* handle buggy NMEA */
166 dpoint = token + strlen(token);
167 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
169 MACRO_PARSE_INT(tmpi, token);
170 _gps.lon = tmpi + (tmpd * (1.0 / 60.0));
174 token = strsep(&sentence, DELIM);
175 if(token && *token == 'W')
176 _gps.lon = -_gps.lon;
178 /* Parse speed over ground, knots. */
179 token = strsep(&sentence, DELIM);
182 MACRO_PARSE_FLOAT(_gps.speed, token);
184 _gps.maxspeed = MAX(_gps.maxspeed, _gps.speed);
187 /* Parse heading, degrees from true north. */
188 token = strsep(&sentence, DELIM);
191 MACRO_PARSE_FLOAT(_gps.heading, token);
195 token = strsep(&sentence, DELIM);
196 if(token && *token && gpsdate)
199 gpsdate[6] = '\0'; /* Make sure time is 6 chars long. */
200 strcat(gpsdate, token);
201 strptime(gpsdate, "%H%M%S%d%m%y", &time);
202 _pos.time = mktime(&time) + _gmtoffset;
205 _pos.time = time(NULL);
207 /* Translate data into integers. */
208 latlon2unit(_gps.lat, _gps.lon, _pos.unitx, _pos.unity);
210 /* Add new data to track. */
211 if(_gps_state == RCVR_FIXED)
213 if(track_add(_pos.time, newly_fixed) || newly_fixed)
215 /* Move mark to new location. */
216 map_refresh_mark(FALSE);
224 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
228 gps_parse_gga(gchar *sentence)
230 /* GGA Global Positioning System Fix Data
241 4 = Real Time Kinematic
243 6 = estimated (dead reckoning) (2.3 feature)
244 7 = Manual input mode
246 7. Number of satellites being tracked
247 8. Horizontal dilution of precision
248 9. Altitude, Meters, above mean sea level
249 10. Alt unit (meters)
250 11. Height of geoid (mean sea level) above WGS84 ellipsoid
252 13. (empty field) time in seconds since last DGPS update
253 14. (empty field) DGPS station ID number
254 15. the checksum data
257 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
262 token = strsep(&sentence, DELIM);
264 token = strsep(&sentence, DELIM);
266 token = strsep(&sentence, DELIM);
268 token = strsep(&sentence, DELIM);
270 token = strsep(&sentence, DELIM);
272 /* Parse Fix quality */
273 token = strsep(&sentence, DELIM);
275 MACRO_PARSE_INT(_gps.fixquality, token);
277 /* Skip number of satellites */
278 token = strsep(&sentence, DELIM);
280 /* Parse Horizontal dilution of precision */
281 token = strsep(&sentence, DELIM);
283 MACRO_PARSE_INT(_gps.hdop, token);
286 token = strsep(&sentence, DELIM);
289 MACRO_PARSE_FLOAT(_pos.altitude, token);
294 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
298 gps_parse_gsa(gchar *sentence)
300 /* GPS DOP and active satellites
301 * 1) Auto selection of 2D or 3D fix (M = manual)
302 * 2) 3D fix - values include: 1 = no fix, 2 = 2D, 3 = 2D
303 * 3) PRNs of satellites used for fix
305 * 4) PDOP (dilution of precision)
306 * 5) Horizontal dilution of precision (HDOP)
307 * 6) Vertical dilution of precision (VDOP)
312 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
316 /* Skip Auto selection. */
317 token = strsep(&sentence, DELIM);
320 token = strsep(&sentence, DELIM);
322 MACRO_PARSE_INT(_gps.fix, token);
325 for(i = 0; i < 12; i++)
327 token = strsep(&sentence, DELIM);
329 _gps.satforfix[_gps.satinuse++] = atoi(token);
333 token = strsep(&sentence, DELIM);
335 MACRO_PARSE_FLOAT(_gps.pdop, token);
338 token = strsep(&sentence, DELIM);
340 MACRO_PARSE_FLOAT(_gps.hdop, token);
343 token = strsep(&sentence, DELIM);
345 MACRO_PARSE_FLOAT(_gps.vdop, token);
347 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
351 gps_parse_gsv(gchar *sentence)
353 /* Must be GSV - Satellites in view
354 * 1) total number of messages
356 * 3) satellites in view
357 * 4) satellite number
358 * 5) elevation in degrees (0-90)
359 * 6) azimuth in degrees to true north (0-359)
360 * 7) SNR in dB (0-99)
361 * more satellite infos like 4)-7)
365 gint msgcnt = 0, nummsgs = 0;
366 static gint running_total = 0;
367 static gint num_sats_used = 0;
368 static gint satcnt = 0;
369 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
371 /* Parse number of messages. */
372 token = strsep(&sentence, DELIM);
374 MACRO_PARSE_INT(nummsgs, token);
376 /* Parse message number. */
377 token = strsep(&sentence, DELIM);
379 MACRO_PARSE_INT(msgcnt, token);
381 /* Parse number of satellites in view. */
382 token = strsep(&sentence, DELIM);
385 MACRO_PARSE_INT(_gps.satinview, token);
386 if(_gps.satinview > 12) /* Handle buggy NMEA. */
390 /* Loop until there are no more satellites to parse. */
391 while(sentence && satcnt < 12)
393 /* Get token for Satellite Number. */
394 token = strsep(&sentence, DELIM);
396 _gps_sat[satcnt].prn = atoi(token);
398 /* Get token for elevation in degrees (0-90). */
399 token = strsep(&sentence, DELIM);
401 _gps_sat[satcnt].elevation = atoi(token);
403 /* Get token for azimuth in degrees to true north (0-359). */
404 token = strsep(&sentence, DELIM);
406 _gps_sat[satcnt].azimuth = atoi(token);
408 /* Get token for SNR. */
409 token = strsep(&sentence, DELIM);
410 if(token && *token && (_gps_sat[satcnt].snr = atoi(token)))
412 /* SNR is non-zero - add to total and count as used. */
413 running_total += _gps_sat[satcnt].snr;
419 if(msgcnt == nummsgs)
421 /* This is the last message. Calculate signal strength. */
424 if(_gps_state == RCVR_UP)
426 gdouble fraction = running_total * sqrtf(num_sats_used)
427 / num_sats_used / 150.0;
428 BOUND(fraction, 0.0, 1.0);
429 hildon_banner_set_fraction(
430 HILDON_BANNER(_fix_banner), fraction);
432 /* Keep awake while they watch the progress bar. */
433 UNBLANK_SCREEN(TRUE, FALSE);
441 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
445 gps_handle_error_idle(gchar *error)
447 printf("%s(%s)\n", __PRETTY_FUNCTION__, error);
449 /* Ask for re-try. */
450 if(++_gps_rcvr_retry_count > 2)
453 gchar buffer[BUFFER_SIZE];
455 /* Reset retry count. */
456 _gps_rcvr_retry_count = 0;
458 snprintf(buffer, sizeof(buffer), "%s\nRetry?", error);
459 confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), buffer);
463 if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
464 rcvr_connect(); /* Try again. */
468 gtk_check_menu_item_set_active(
469 GTK_CHECK_MENU_ITEM(_menu_gps_enable_item), FALSE);
472 /* Ask user to re-connect. */
473 gtk_widget_destroy(confirm);
478 rcvr_connect(); /* Try again. */
483 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
488 gps_parse_nmea_idle(gchar *nmea)
490 printf("%s(%s)\n", __PRETTY_FUNCTION__, nmea);
492 if(_enable_gps && _gps_state >= RCVR_DOWN)
494 if(_gps_state < RCVR_UP)
495 set_conn_state(RCVR_UP);
497 if(!strncmp(nmea + 3, "GSV", 3))
499 if(_gps_state == RCVR_UP || _gps_info || _satdetails_on)
500 gps_parse_gsv(nmea + 7);
502 else if(!strncmp(nmea + 3, "RMC", 3))
503 gps_parse_rmc(nmea + 7);
504 else if(!strncmp(nmea + 3, "GGA", 3))
505 gps_parse_gga(nmea + 7);
506 else if(!strncmp(nmea + 3, "GSA", 3))
507 gps_parse_gsa(nmea + 7);
512 gps_display_details();
517 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
522 gri_clone(GpsRcvrInfo *gri)
524 GpsRcvrInfo *ret = g_new(GpsRcvrInfo, 1);
525 ret->type = gri->type;
526 ret->bt_mac = g_strdup(gri->bt_mac);
527 ret->file_path = g_strdup(gri->file_path);
528 ret->gpsd_host = g_strdup(gri->gpsd_host);
529 ret->gpsd_port = gri->gpsd_port;
534 gri_free(GpsRcvrInfo *gri)
537 g_free(gri->file_path);
538 g_free(gri->gpsd_host);
543 thread_read_nmea(GpsRcvrInfo *gri)
545 gchar buf[BUFFER_SIZE];
546 gchar *buf_curr = buf;
547 gchar *buf_last = buf + sizeof(buf) - 1;
548 GnomeVFSFileSize bytes_read;
549 GnomeVFSResult vfs_result;
550 gchar *gpsd_host = NULL;
552 GnomeVFSInetConnection *iconn = NULL;
553 GnomeVFSSocket *socket = NULL;
554 GThread *my_thread = g_thread_self();
555 gboolean error = FALSE;
559 gboolean is_context = FALSE;
561 printf("%s(%d)\n", __PRETTY_FUNCTION__, gri->type);
563 /* Lock/Unlock the mutex to ensure that _gps_thread is done being set. */
564 g_mutex_lock(_gps_init_mutex);
565 g_mutex_unlock(_gps_init_mutex);
572 gchar errstr[BUFFER_SIZE] = "";
573 /* We need to start gpsd (via gpsbt) first. */
574 memset(&gps_context, 0, sizeof(gps_context));
576 if(gpsbt_start(gri->bt_mac, 0, 0, 0, errstr, sizeof(errstr),
577 0, &gps_context) < 0)
579 g_printerr("Error connecting to GPS receiver: (%d) %s (%s)\n",
580 errno, strerror(errno), errstr);
581 g_idle_add((GSourceFunc)gps_handle_error_idle,
582 g_strdup_printf("%s",
583 _("Error connecting to GPS receiver.")));
588 /* Success. Set gpsd_host and gpsd_port. */
589 gpsd_host = "127.0.0.1";
590 gpsd_port = GPSD_PORT_DEFAULT;
598 /* Set gpsd_host and gpsd_port. */
599 gpsd_host = gri->gpsd_host;
600 gpsd_port = gri->gpsd_port;
603 #ifdef HAVE_LIBGPSMGR
606 /* Use gpsmgr to create a GPSD that uses the file. */
608 gchar *gpsd_ctrl_sock;
610 devs[0] = gri->file_path;
613 /* Caller can override the name of the gpsd program and
614 * the used control socket. */
615 gpsd_prog = getenv("GPSD_PROG");
616 gpsd_ctrl_sock = getenv("GPSD_CTRL_SOCK");
619 gpsd_prog = "/usr/sbin/gpsd";
621 gpsd_ctrl_sock = "/tmp/.gpsd_ctrl_sock";
623 memset(&gps_context, 0, sizeof(gps_context));
625 if(gpsmgr_start(gpsd_prog, devs, gpsd_ctrl_sock,
626 0, 0, &gps_context.mgr) < 0)
628 g_printerr("Error opening GPS device: (%d) %s\n",
629 errno, strerror(errno));
630 g_idle_add((GSourceFunc)gps_handle_error_idle,
631 g_strdup_printf("%s",
632 _("Error opening GPS device.")));
637 /* Success. Set gpsd_host and gpsd_port. */
638 gpsd_host = "127.0.0.1";
639 gpsd_port = GPSD_PORT_DEFAULT;
649 if(!error && my_thread == _gps_thread)
653 /* Attempt to connect to GPSD. */
654 for(tryno = 0; tryno < 10; tryno++)
656 /* Create a socket to interact with GPSD. */
657 GTimeVal timeout = { 10, 0 };
659 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
661 gri->type == GPS_RCVR_GPSD ? gri->gpsd_host
663 gri->type == GPS_RCVR_GPSD ? gri->gpsd_port
666 || NULL == (socket = gnome_vfs_inet_connection_to_socket(iconn))
667 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
668 socket, &timeout, NULL))
669 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write(socket,
670 "r+\r\n", sizeof("r+\r\n"), &bytes_read, NULL))
671 || bytes_read != sizeof("r+\r\n"))
681 g_printerr("Error connecting to GPSD server: (%d) %s\n",
682 vfs_result, gnome_vfs_result_to_string(vfs_result));
683 g_idle_add((GSourceFunc)gps_handle_error_idle,
684 g_strdup_printf("%s",
685 _("Error connecting to GPSD server.")));
690 if(!error && my_thread == _gps_thread)
692 while(my_thread == _gps_thread)
696 vfs_result = gnome_vfs_socket_read(
703 if(vfs_result != GNOME_VFS_OK)
705 if(my_thread == _gps_thread)
707 /* Error wasn't user-initiated. */
708 g_idle_add((GSourceFunc)gps_handle_error_idle,
709 g_strdup_printf("%s",
710 _("Error reading GPS data.")));
716 /* Loop through the buffer and read each NMEA sentence. */
717 buf_curr += bytes_read;
718 *buf_curr = '\0'; /* append a \0 so we can read as string */
719 while(my_thread == _gps_thread && (eol = strchr(buf, '\n')))
724 gchar *sptr = buf + 1; /* Skip the $ */
725 /* This is the beginning of a sentence; okay to parse. */
726 *eol = '\0'; /* overwrite \n with \0 */
727 while(*sptr && *sptr != '*')
730 /* If we're at a \0 (meaning there is no checksum), or if
731 * the checksum is good, then parse the sentence. */
732 if(!*sptr || csum == strtol(sptr + 1, NULL, 16))
735 *sptr = '\0'; /* take checksum out of the buffer.*/
736 if(my_thread == _gps_thread)
737 g_idle_add((GSourceFunc)gps_parse_nmea_idle,
742 /* There was a checksum, and it was bad. */
743 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n",
744 __PRETTY_FUNCTION__, buf);
748 /* If eol is at or after (buf_curr - 1) */
749 if(eol >= (buf_curr - 1))
751 /* Last read was a newline - reset read buffer */
757 /* Move the next line to the front of the buffer. */
758 memmove(buf, eol + 1,
759 buf_curr - eol); /* include terminating 0 */
760 /* Subtract _curr so that it's pointing at the new \0. */
761 buf_curr -= (eol - buf + 1);
764 _gps_rcvr_retry_count = 0;
768 /* Error, or we're done reading GPS. */
772 gnome_vfs_inet_connection_free(iconn, NULL);
780 gpsbt_stop(&gps_context);
784 #ifdef HAVE_LIBGPSMGR
786 gpsmgr_stop(&gps_context.mgr);
797 printf("%s(): return\n", __PRETTY_FUNCTION__);
802 * Set the connection state. This function controls all connection-related
806 set_conn_state(ConnState new_conn_state)
808 printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
810 switch(_gps_state = new_conn_state)
816 gtk_widget_destroy(_connect_banner);
817 _connect_banner = NULL;
821 gtk_widget_destroy(_fix_banner);
828 gtk_widget_destroy(_fix_banner);
832 _connect_banner = hildon_banner_show_animation(
833 _window, NULL, _("Searching for GPS receiver"));
838 gtk_widget_destroy(_connect_banner);
839 _connect_banner = NULL;
843 _fix_banner = hildon_banner_show_progress(
844 _window, NULL, _("Establishing GPS fix"));
847 default: ; /* to quell warning. */
851 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
855 * Disconnect from the receiver. This method cleans up any and everything
856 * that might be associated with the receiver.
861 printf("%s()\n", __PRETTY_FUNCTION__);
866 set_conn_state(RCVR_OFF);
867 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
871 * Connect to the receiver.
872 * This method assumes that _fd is -1 and _channel is NULL. If unsure, call
873 * rcvr_disconnect() first.
874 * Since this is an idle function, this function returns whether or not it
875 * should be called again, which is always FALSE.
880 printf("%s(%d)\n", __PRETTY_FUNCTION__, _gps_state);
882 if(_enable_gps && _gps_state == RCVR_OFF)
884 set_conn_state(RCVR_DOWN);
886 /* Lock/Unlock the mutex to ensure that the thread doesn't
887 * start until _gps_thread is set. */
888 g_mutex_lock(_gps_init_mutex);
889 _gps_thread = g_thread_create((GThreadFunc)thread_read_nmea,
890 gri_clone(&_gri), TRUE, NULL); /* Joinable. */
891 g_mutex_unlock(_gps_init_mutex);
894 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
901 printf("%s()\n", __PRETTY_FUNCTION__);
902 if(system("/usr/bin/sudo -l | grep -q '/usr/sbin/hciconfig *hci0 *reset'"
903 " && sudo /usr/sbin/hciconfig hci0 reset"))
905 _("An error occurred while trying to reset the bluetooth "
907 "Did you make sure to modify\nthe /etc/sudoers file?"));
908 else if(_gps_state != RCVR_OFF)
912 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
918 printf("%s()\n", __PRETTY_FUNCTION__);
920 _gps_init_mutex = g_mutex_new();
922 /* Fix a stupid PATH bug in libgps. */
924 gchar *path_env = getenv("PATH");
925 gchar *new_path = g_strdup_printf("%s:%s", path_env, "/usr/sbin");
926 setenv("PATH", new_path, 1);
935 localtime_r(&time1, &time2);
936 _gmtoffset = time2.tm_gmtoff;
939 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
943 gps_destroy(gboolean last)
945 static GThread* tmp = NULL;
946 printf("%s()\n", __PRETTY_FUNCTION__);
952 tmp = (GThread*)_gps_thread;
959 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);