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");
612 memset(&gps_context, 0, sizeof(gps_context));
614 if(gpsmgr_start(gpsd_prog, devs, gpsd_ctrl_sock,
615 0, 0, &gps_context.mgr) < 0)
617 g_printerr("Error opening GPS device: (%d) %s\n",
618 errno, strerror(errno));
619 g_idle_add((GSourceFunc)gps_handle_error_idle,
620 g_strdup_printf("%s",
621 _("Error opening GPS device.")));
626 /* Success. Set gpsd_host and gpsd_port. */
627 gpsd_host = "127.0.0.1";
628 gpsd_port = GPSD_PORT_DEFAULT;
637 if(!error && my_thread == _gps_thread)
641 /* Attempt to connect to GPSD. */
642 for(tryno = 0; tryno < 10; tryno++)
644 /* Create a socket to interact with GPSD. */
645 GTimeVal timeout = { 10, 0 };
647 if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
649 gri->type == GPS_RCVR_GPSD ? gri->gpsd_host
651 gri->type == GPS_RCVR_GPSD ? gri->gpsd_port
654 || NULL == (socket = gnome_vfs_inet_connection_to_socket(iconn))
655 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
656 socket, &timeout, NULL))
657 || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write(socket,
658 "r+\r\n", sizeof("r+\r\n"), &bytes_read, NULL))
659 || bytes_read != sizeof("r+\r\n"))
669 g_printerr("Error connecting to GPSD server: (%d) %s\n",
670 vfs_result, gnome_vfs_result_to_string(vfs_result));
671 g_idle_add((GSourceFunc)gps_handle_error_idle,
672 g_strdup_printf("%s",
673 _("Error connecting to GPSD server.")));
678 if(!error && my_thread == _gps_thread)
680 while(my_thread == _gps_thread)
684 vfs_result = gnome_vfs_socket_read(
691 if(vfs_result != GNOME_VFS_OK)
693 if(my_thread == _gps_thread)
695 /* Error wasn't user-initiated. */
696 g_idle_add((GSourceFunc)gps_handle_error_idle,
697 g_strdup_printf("%s",
698 _("Error reading GPS data.")));
704 /* Loop through the buffer and read each NMEA sentence. */
705 buf_curr += bytes_read;
706 *buf_curr = '\0'; /* append a \0 so we can read as string */
707 while(my_thread == _gps_thread && (eol = strchr(buf, '\n')))
712 gchar *sptr = buf + 1; /* Skip the $ */
713 /* This is the beginning of a sentence; okay to parse. */
714 *eol = '\0'; /* overwrite \n with \0 */
715 while(*sptr && *sptr != '*')
718 /* If we're at a \0 (meaning there is no checksum), or if
719 * the checksum is good, then parse the sentence. */
720 if(!*sptr || csum == strtol(sptr + 1, NULL, 16))
723 *sptr = '\0'; /* take checksum out of the buffer.*/
724 if(my_thread == _gps_thread)
725 g_idle_add((GSourceFunc)gps_parse_nmea_idle,
730 /* There was a checksum, and it was bad. */
731 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n",
732 __PRETTY_FUNCTION__, buf);
736 /* If eol is at or after (buf_curr - 1) */
737 if(eol >= (buf_curr - 1))
739 /* Last read was a newline - reset read buffer */
745 /* Move the next line to the front of the buffer. */
746 memmove(buf, eol + 1,
747 buf_curr - eol); /* include terminating 0 */
748 /* Subtract _curr so that it's pointing at the new \0. */
749 buf_curr -= (eol - buf + 1);
752 _gps_rcvr_retry_count = 0;
756 /* Error, or we're done reading GPS. */
760 gnome_vfs_inet_connection_free(iconn, NULL);
767 gpsbt_stop(&gps_context);
771 gpsmgr_stop(&gps_context.mgr);
781 printf("%s(): return\n", __PRETTY_FUNCTION__);
786 * Set the connection state. This function controls all connection-related
790 set_conn_state(ConnState new_conn_state)
792 printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
794 switch(_gps_state = new_conn_state)
800 gtk_widget_destroy(_connect_banner);
801 _connect_banner = NULL;
805 gtk_widget_destroy(_fix_banner);
812 gtk_widget_destroy(_fix_banner);
816 _connect_banner = hildon_banner_show_animation(
817 _window, NULL, _("Searching for GPS receiver"));
822 gtk_widget_destroy(_connect_banner);
823 _connect_banner = NULL;
827 _fix_banner = hildon_banner_show_progress(
828 _window, NULL, _("Establishing GPS fix"));
831 default: ; /* to quell warning. */
835 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
839 * Disconnect from the receiver. This method cleans up any and everything
840 * that might be associated with the receiver.
845 printf("%s()\n", __PRETTY_FUNCTION__);
850 set_conn_state(RCVR_OFF);
851 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
855 * Connect to the receiver.
856 * This method assumes that _fd is -1 and _channel is NULL. If unsure, call
857 * rcvr_disconnect() first.
858 * Since this is an idle function, this function returns whether or not it
859 * should be called again, which is always FALSE.
864 printf("%s(%d)\n", __PRETTY_FUNCTION__, _gps_state);
866 if(_enable_gps && _gps_state == RCVR_OFF)
868 set_conn_state(RCVR_DOWN);
870 /* Lock/Unlock the mutex to ensure that the thread doesn't
871 * start until _gps_thread is set. */
872 g_mutex_lock(_gps_init_mutex);
873 _gps_thread = g_thread_create((GThreadFunc)thread_read_nmea,
874 gri_clone(&_gri), TRUE, NULL); /* Joinable. */
875 g_mutex_unlock(_gps_init_mutex);
878 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
885 printf("%s()\n", __PRETTY_FUNCTION__);
886 if(system("/usr/bin/sudo -l | grep -q '/usr/sbin/hciconfig *hci0 *reset'"
887 " && sudo /usr/sbin/hciconfig hci0 reset"))
889 _("An error occurred while trying to reset the bluetooth "
891 "Did you make sure to modify\nthe /etc/sudoers file?"));
892 else if(_gps_state != RCVR_OFF)
896 vprintf("%s(): return\n", __PRETTY_FUNCTION__);
902 printf("%s()\n", __PRETTY_FUNCTION__);
904 _gps_init_mutex = g_mutex_new();
906 /* Fix a stupid PATH bug in libgps. */
908 gchar *path_env = getenv("PATH");
909 gchar *new_path = g_strdup_printf("%s:%s", path_env, "/usr/sbin");
910 setenv("PATH", new_path, 1);
918 localtime_r(&time1, &time2);
919 _gmtoffset = time2.tm_gmtoff;
922 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
926 gps_destroy(gboolean last)
928 static GThread* tmp = NULL;
929 printf("%s()\n", __PRETTY_FUNCTION__);
935 tmp = (GThread*)_gps_thread;
942 vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);