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>
56 static volatile GThread *_gps_thread = NULL;
57 static GMutex *_gps_init_mutex = NULL;
58 static gint _gps_rcvr_retry_count = 0;
60 static gint _gmtoffset = 0;
63 #define MACRO_PARSE_INT(tofill, str) { \
65 (tofill) = strtol((str), &error_check, 10); \
66 if(error_check == (str)) \
68 g_printerr("Line %d: Failed to parse string as int: %s\n", \
70 MACRO_BANNER_SHOW_INFO(_window, \
71 _("Invalid NMEA input from receiver!")); \
75 #define MACRO_PARSE_FLOAT(tofill, str) { \
77 (tofill) = g_ascii_strtod((str), &error_check); \
78 if(error_check == (str)) \
80 g_printerr("Failed to parse string as float: %s\n", str); \
81 MACRO_BANNER_SHOW_INFO(_window, \
82 _("Invalid NMEA input from receiver!")); \
87 gps_parse_rmc(gchar *sentence)
89 /* Recommended Minimum Navigation Information C
91 * 2) Status, V=Navigation receiver warning A=Valid
96 * 7) Speed over ground, knots
97 * 8) Track made good, degrees true
99 * 10) Magnetic Variation, degrees
101 * 12) FAA mode indicator (NMEA 2.3 and later)
104 gchar *token, *dpoint, *gpsdate = NULL;
107 gboolean newly_fixed = FALSE;
108 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
113 token = strsep(&sentence, DELIM);
117 token = strsep(&sentence, DELIM);
118 /* Token is now Status. */
119 if(token && *token == 'A')
122 if(_gps_state < RCVR_FIXED)
125 set_conn_state(RCVR_FIXED);
130 /* Data is invalid - not enough satellites?. */
131 if(_gps_state > RCVR_UP)
133 set_conn_state(RCVR_UP);
134 track_insert_break(FALSE);
138 /* Parse the latitude. */
139 token = strsep(&sentence, DELIM);
142 dpoint = strchr(token, '.');
143 if(!dpoint) /* handle buggy NMEA */
144 dpoint = token + strlen(token);
145 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
147 MACRO_PARSE_INT(tmpi, token);
148 _gps.lat = tmpi + (tmpd * (1.0 / 60.0));
152 token = strsep(&sentence, DELIM);
153 if(token && *token == 'S')
154 _gps.lat = -_gps.lat;
156 /* Parse the longitude. */
157 token = strsep(&sentence, DELIM);
160 dpoint = strchr(token, '.');
161 if(!dpoint) /* handle buggy NMEA */
162 dpoint = token + strlen(token);
163 MACRO_PARSE_FLOAT(tmpd, dpoint - 2);
165 MACRO_PARSE_INT(tmpi, token);
166 _gps.lon = tmpi + (tmpd * (1.0 / 60.0));
170 token = strsep(&sentence, DELIM);
171 if(token && *token == 'W')
172 _gps.lon = -_gps.lon;
174 /* Parse speed over ground, knots. */
175 token = strsep(&sentence, DELIM);
178 MACRO_PARSE_FLOAT(_gps.speed, token);
180 _gps.maxspeed = MAX(_gps.maxspeed, _gps.speed);
183 /* Parse heading, degrees from true north. */
184 token = strsep(&sentence, DELIM);
187 MACRO_PARSE_FLOAT(_gps.heading, token);
191 token = strsep(&sentence, DELIM);
192 if(token && *token && gpsdate)
195 gpsdate[6] = '\0'; /* Make sure time is 6 chars long. */
196 strcat(gpsdate, token);
197 strptime(gpsdate, "%H%M%S%d%m%y", &time);
198 _pos.time = mktime(&time) + _gmtoffset;
201 _pos.time = time(NULL);
203 /* Translate data into integers. */
204 latlon2unit(_gps.lat, _gps.lon, _pos.unitx, _pos.unity);
206 /* Add new data to track. */
207 if(_gps_state == RCVR_FIXED)
209 if(track_add(_pos.time, newly_fixed) || newly_fixed)
211 /* Move mark to new location. */
212 map_refresh_mark(FALSE);
220 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
224 gps_parse_gga(gchar *sentence)
226 /* GGA Global Positioning System Fix Data
237 4 = Real Time Kinematic
239 6 = estimated (dead reckoning) (2.3 feature)
240 7 = Manual input mode
242 7. Number of satellites being tracked
243 8. Horizontal dilution of precision
244 9. Altitude, Meters, above mean sea level
245 10. Alt unit (meters)
246 11. Height of geoid (mean sea level) above WGS84 ellipsoid
248 13. (empty field) time in seconds since last DGPS update
249 14. (empty field) DGPS station ID number
250 15. the checksum data
253 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
258 token = strsep(&sentence, DELIM);
260 token = strsep(&sentence, DELIM);
262 token = strsep(&sentence, DELIM);
264 token = strsep(&sentence, DELIM);
266 token = strsep(&sentence, DELIM);
268 /* Parse Fix quality */
269 token = strsep(&sentence, DELIM);
271 MACRO_PARSE_INT(_gps.fixquality, token);
273 /* Skip number of satellites */
274 token = strsep(&sentence, DELIM);
276 /* Parse Horizontal dilution of precision */
277 token = strsep(&sentence, DELIM);
279 MACRO_PARSE_INT(_gps.hdop, token);
282 token = strsep(&sentence, DELIM);
285 MACRO_PARSE_FLOAT(_pos.altitude, token);
290 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
294 gps_parse_gsa(gchar *sentence)
296 /* GPS DOP and active satellites
297 * 1) Auto selection of 2D or 3D fix (M = manual)
298 * 2) 3D fix - values include: 1 = no fix, 2 = 2D, 3 = 2D
299 * 3) PRNs of satellites used for fix
301 * 4) PDOP (dilution of precision)
302 * 5) Horizontal dilution of precision (HDOP)
303 * 6) Vertical dilution of precision (VDOP)
308 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
312 /* Skip Auto selection. */
313 token = strsep(&sentence, DELIM);
316 token = strsep(&sentence, DELIM);
318 MACRO_PARSE_INT(_gps.fix, token);
321 for(i = 0; i < 12; i++)
323 token = strsep(&sentence, DELIM);
325 _gps.satforfix[_gps.satinuse++] = atoi(token);
329 token = strsep(&sentence, DELIM);
331 MACRO_PARSE_FLOAT(_gps.pdop, token);
334 token = strsep(&sentence, DELIM);
336 MACRO_PARSE_FLOAT(_gps.hdop, token);
339 token = strsep(&sentence, DELIM);
341 MACRO_PARSE_FLOAT(_gps.vdop, token);
343 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
347 gps_parse_gsv(gchar *sentence)
349 /* Must be GSV - Satellites in view
350 * 1) total number of messages
352 * 3) satellites in view
353 * 4) satellite number
354 * 5) elevation in degrees (0-90)
355 * 6) azimuth in degrees to true north (0-359)
356 * 7) SNR in dB (0-99)
357 * more satellite infos like 4)-7)
361 gint msgcnt = 0, nummsgs = 0;
362 static gint running_total = 0;
363 static gint num_sats_used = 0;
364 static gint satcnt = 0;
365 vprintf("%s(): %s\n", __PRETTY_FUNCTION__, sentence);
367 /* Parse number of messages. */
368 token = strsep(&sentence, DELIM);
370 MACRO_PARSE_INT(nummsgs, token);
372 /* Parse message number. */
373 token = strsep(&sentence, DELIM);
375 MACRO_PARSE_INT(msgcnt, token);
377 /* Parse number of satellites in view. */
378 token = strsep(&sentence, DELIM);
381 MACRO_PARSE_INT(_gps.satinview, token);
382 if(_gps.satinview > 12) /* Handle buggy NMEA. */
386 /* Loop until there are no more satellites to parse. */
387 while(sentence && satcnt < 12)
389 /* Get token for Satellite Number. */
390 token = strsep(&sentence, DELIM);
392 _gps_sat[satcnt].prn = atoi(token);
394 /* Get token for elevation in degrees (0-90). */
395 token = strsep(&sentence, DELIM);
397 _gps_sat[satcnt].elevation = atoi(token);
399 /* Get token for azimuth in degrees to true north (0-359). */
400 token = strsep(&sentence, DELIM);
402 _gps_sat[satcnt].azimuth = atoi(token);
404 /* Get token for SNR. */
405 token = strsep(&sentence, DELIM);
406 if(token && *token && (_gps_sat[satcnt].snr = atoi(token)))
408 /* SNR is non-zero - add to total and count as used. */
409 running_total += _gps_sat[satcnt].snr;
415 if(msgcnt == nummsgs)
417 /* This is the last message. Calculate signal strength. */
420 if(_gps_state == RCVR_UP)
422 gdouble fraction = running_total * sqrtf(num_sats_used)
423 / num_sats_used / 150.0;
424 BOUND(fraction, 0.0, 1.0);
425 hildon_banner_set_fraction(
426 HILDON_BANNER(_fix_banner), fraction);
428 /* Keep awake while they watch the progress bar. */
429 UNBLANK_SCREEN(TRUE, FALSE);
437 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
441 gps_handle_error_idle(gchar *error)
443 printf("%s(%s)\n", __PRETTY_FUNCTION__, error);
445 /* Ask for re-try. */
446 if(++_gps_rcvr_retry_count > 2)
449 gchar buffer[BUFFER_SIZE];
451 /* Reset retry count. */
452 _gps_rcvr_retry_count = 0;
454 snprintf(buffer, sizeof(buffer), "%s\nRetry?", error);
455 confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), buffer);
459 if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
460 rcvr_connect(); /* Try again. */
464 gtk_check_menu_item_set_active(
465 GTK_CHECK_MENU_ITEM(_menu_enable_gps_item), FALSE);
468 /* Ask user to re-connect. */
469 gtk_widget_destroy(confirm);
474 rcvr_connect(); /* Try again. */
479 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
484 gps_parse_nmea_idle(gchar *nmea)
486 printf("%s(%s)\n", __PRETTY_FUNCTION__, nmea);
488 if(_enable_gps && _gps_state >= RCVR_DOWN)
490 if(_gps_state < RCVR_UP)
491 set_conn_state(RCVR_UP);
493 if(!strncmp(nmea + 3, "GSV", 3))
495 if(_gps_state == RCVR_UP || _gps_info || _satdetails_on)
496 gps_parse_gsv(nmea + 7);
498 else if(!strncmp(nmea + 3, "RMC", 3))
499 gps_parse_rmc(nmea + 7);
500 else if(!strncmp(nmea + 3, "GGA", 3))
501 gps_parse_gga(nmea + 7);
502 else if(!strncmp(nmea + 3, "GSA", 3))
503 gps_parse_gsa(nmea + 7);
508 gps_display_details();
513 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
518 gri_clone(GpsRcvrInfo *gri)
520 GpsRcvrInfo *ret = g_new(GpsRcvrInfo, 1);
521 ret->type = gri->type;
522 ret->bt_mac = g_strdup(gri->bt_mac);
523 ret->file_path = g_strdup(gri->file_path);
524 ret->gpsd_host = g_strdup(gri->gpsd_host);
525 ret->gpsd_port = gri->gpsd_port;
530 gri_free(GpsRcvrInfo *gri)
533 g_free(gri->file_path);
534 g_free(gri->gpsd_host);
539 thread_read_nmea(GpsRcvrInfo *gri)
541 gchar buf[BUFFER_SIZE];
542 gchar *buf_curr = buf;
543 gchar *buf_last = buf + sizeof(buf) - 1;
544 GnomeVFSFileSize bytes_read;
545 GnomeVFSResult vfs_result;
546 gchar *gpsd_host = NULL;
548 GnomeVFSInetConnection *iconn = NULL;
549 GnomeVFSSocket *socket = NULL;
550 GThread *my_thread = g_thread_self();
551 gboolean error = FALSE;
553 gboolean is_context = FALSE;
555 printf("%s(%d)\n", __PRETTY_FUNCTION__, gri->type);
557 /* Lock/Unlock the mutex to ensure that _gps_thread is done being set. */
558 g_mutex_lock(_gps_init_mutex);
559 g_mutex_unlock(_gps_init_mutex);
565 gchar errstr[BUFFER_SIZE] = "";
566 /* We need to start gpsd (via gpsbt) first. */
567 memset(&gps_context, 0, sizeof(gps_context));
569 if(gpsbt_start(gri->bt_mac, 0, 0, 0, errstr, sizeof(errstr),
570 0, &gps_context) < 0)
572 g_printerr("Error connecting to GPS receiver: (%d) %s (%s)\n",
573 errno, strerror(errno), errstr);
574 g_idle_add((GSourceFunc)gps_handle_error_idle,
575 g_strdup_printf("%s",
576 _("Error connecting to GPS receiver.")));
581 /* Success. Set gpsd_host and gpsd_port. */
582 gpsd_host = "127.0.0.1";
583 gpsd_port = GPSD_PORT_DEFAULT;
590 /* Set gpsd_host and gpsd_port. */
591 gpsd_host = gri->gpsd_host;
592 gpsd_port = gri->gpsd_port;
597 /* Use gpsmgr to create a GPSD that uses the file. */
599 gchar *gpsd_ctrl_sock;
601 devs[0] = gri->file_path;
604 /* Caller can override the name of the gpsd program and
605 * the used control socket. */
606 gpsd_prog = getenv("GPSD_PROG");
607 gpsd_ctrl_sock = getenv("GPSD_CTRL_SOCK");
610 gpsd_prog = "/usr/sbin/gpsd";
612 gpsd_ctrl_sock = "/tmp/.gpsd_ctrl_sock";
614 memset(&gps_context, 0, sizeof(gps_context));
616 if(gpsmgr_start(gpsd_prog, devs, gpsd_ctrl_sock,
617 0, 0, &gps_context.mgr) < 0)
619 g_printerr("Error opening GPS device: (%d) %s\n",
620 errno, strerror(errno));
621 g_idle_add((GSourceFunc)gps_handle_error_idle,
622 g_strdup_printf("%s",
623 _("Error opening GPS device.")));
628 /* Success. Set gpsd_host and gpsd_port. */
629 gpsd_host = "127.0.0.1";
630 gpsd_port = GPSD_PORT_DEFAULT;
639 if(!error && my_thread == _gps_thread)
643 /* Attempt to connect to GPSD. */
644 for(tryno = 0; tryno < 10; tryno++)
646 /* Create a socket to interact with GPSD. */
647 GTimeVal timeout = { 10, 0 };
649 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
651 gri->type == GPS_RCVR_GPSD ? gri->gpsd_host
653 gri->type == GPS_RCVR_GPSD ? gri->gpsd_port
656 || NULL == (socket = gnome_vfs_inet_connection_to_socket(iconn))
657 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
658 socket, &timeout, NULL))
659 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write(socket,
660 "r+\r\n", sizeof("r+\r\n"), &bytes_read, NULL))
661 || bytes_read != sizeof("r+\r\n"))
671 g_printerr("Error connecting to GPSD server: (%d) %s\n",
672 vfs_result, gnome_vfs_result_to_string(vfs_result));
673 g_idle_add((GSourceFunc)gps_handle_error_idle,
674 g_strdup_printf("%s",
675 _("Error connecting to GPSD server.")));
680 if(!error && my_thread == _gps_thread)
682 while(my_thread == _gps_thread)
686 vfs_result = gnome_vfs_socket_read(
693 if(vfs_result != GNOME_VFS_OK)
695 if(my_thread == _gps_thread)
697 /* Error wasn't user-initiated. */
698 g_idle_add((GSourceFunc)gps_handle_error_idle,
699 g_strdup_printf("%s",
700 _("Error reading GPS data.")));
706 /* Loop through the buffer and read each NMEA sentence. */
707 buf_curr += bytes_read;
708 *buf_curr = '\0'; /* append a \0 so we can read as string */
709 while(my_thread == _gps_thread && (eol = strchr(buf, '\n')))
714 gchar *sptr = buf + 1; /* Skip the $ */
715 /* This is the beginning of a sentence; okay to parse. */
716 *eol = '\0'; /* overwrite \n with \0 */
717 while(*sptr && *sptr != '*')
720 /* If we're at a \0 (meaning there is no checksum), or if
721 * the checksum is good, then parse the sentence. */
722 if(!*sptr || csum == strtol(sptr + 1, NULL, 16))
725 *sptr = '\0'; /* take checksum out of the buffer.*/
726 if(my_thread == _gps_thread)
727 g_idle_add((GSourceFunc)gps_parse_nmea_idle,
732 /* There was a checksum, and it was bad. */
733 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n",
734 __PRETTY_FUNCTION__, buf);
738 /* If eol is at or after (buf_curr - 1) */
739 if(eol >= (buf_curr - 1))
741 /* Last read was a newline - reset read buffer */
747 /* Move the next line to the front of the buffer. */
748 memmove(buf, eol + 1,
749 buf_curr - eol); /* include terminating 0 */
750 /* Subtract _curr so that it's pointing at the new \0. */
751 buf_curr -= (eol - buf + 1);
754 _gps_rcvr_retry_count = 0;
758 /* Error, or we're done reading GPS. */
762 gnome_vfs_inet_connection_free(iconn, NULL);
769 gpsbt_stop(&gps_context);
773 gpsmgr_stop(&gps_context.mgr);
783 printf("%s(): return\n", __PRETTY_FUNCTION__);
788 * Set the connection state. This function controls all connection-related
792 set_conn_state(ConnState new_conn_state)
794 printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
796 switch(_gps_state = new_conn_state)
802 gtk_widget_destroy(_connect_banner);
803 _connect_banner = NULL;
807 gtk_widget_destroy(_fix_banner);
814 gtk_widget_destroy(_fix_banner);
818 _connect_banner = hildon_banner_show_animation(
819 _window, NULL, _("Searching for GPS receiver"));
824 gtk_widget_destroy(_connect_banner);
825 _connect_banner = NULL;
829 _fix_banner = hildon_banner_show_progress(
830 _window, NULL, _("Establishing GPS fix"));
833 default: ; /* to quell warning. */
837 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
841 * Disconnect from the receiver. This method cleans up any and everything
842 * that might be associated with the receiver.
847 printf("%s()\n", __PRETTY_FUNCTION__);
852 set_conn_state(RCVR_OFF);
853 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
857 * Connect to the receiver.
858 * This method assumes that _fd is -1 and _channel is NULL. If unsure, call
859 * rcvr_disconnect() first.
860 * Since this is an idle function, this function returns whether or not it
861 * should be called again, which is always FALSE.
866 printf("%s(%d)\n", __PRETTY_FUNCTION__, _gps_state);
868 if(_enable_gps && _gps_state == RCVR_OFF)
870 set_conn_state(RCVR_DOWN);
872 /* Lock/Unlock the mutex to ensure that the thread doesn't
873 * start until _gps_thread is set. */
874 g_mutex_lock(_gps_init_mutex);
875 _gps_thread = g_thread_create((GThreadFunc)thread_read_nmea,
876 gri_clone(&_gri), TRUE, NULL); /* Joinable. */
877 g_mutex_unlock(_gps_init_mutex);
880 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
887 printf("%s()\n", __PRETTY_FUNCTION__);
888 if(system("/usr/bin/sudo -l | grep -q '/usr/sbin/hciconfig *hci0 *reset'"
889 " && sudo /usr/sbin/hciconfig hci0 reset"))
891 _("An error occurred while trying to reset the bluetooth "
893 "Did you make sure to modify\nthe /etc/sudoers file?"));
894 else if(_gps_state != RCVR_OFF)
898 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
904 printf("%s()\n", __PRETTY_FUNCTION__);
906 _gps_init_mutex = g_mutex_new();
908 /* Fix a stupid PATH bug in libgps. */
910 gchar *path_env = getenv("PATH");
911 gchar *new_path = g_strdup_printf("%s:%s", path_env, "/usr/sbin");
912 setenv("PATH", new_path, 1);
921 localtime_r(&time1, &time2);
922 _gmtoffset = time2.tm_gmtoff;
925 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
929 gps_destroy(gboolean last)
931 static GThread* tmp = NULL;
932 printf("%s()\n", __PRETTY_FUNCTION__);
938 tmp = (GThread*)_gps_thread;
945 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);