2 * This file is part of mapper
4 * Copyright (C) 2007 Kaj-Michael Lang
5 * Copyright (C) 2006-2007 John Costigan.
7 * POI and GPS-Info code originally written by Cezary Jackiewicz.
9 * Default map data provided by http://www.openstreetmap.org/
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
38 #include <glib/gstdio.h>
39 #include <dbus/dbus.h>
40 #include <dbus/dbus-glib.h>
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <arpa/inet.h>
50 #include "gps-nmea-parse.h"
53 #ifdef WITH_HILDON_DBUS_BT
62 static gboolean gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data);
63 static gboolean gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data);
64 static gboolean gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data);
65 static gboolean gps_connect_socket(Gps *gps);
66 static gboolean gps_connect_file(Gps *gps, gchar *file);
68 #define GPSD_NMEA "r+\r\n"
69 #define GPSD_PORT (2947)
70 #define LOCALHOST "127.0.0.1"
71 #define GPS_DEVICE_FILE "/dev/ttyS0"
73 const GpsTypes gps_types[] = {
75 { GPS_IO_RFCOMM, "Bluetooth connection (old)", TRUE, TRUE, FALSE},
77 #ifdef WITH_HILDON_DBUS_BT
78 { GPS_IO_HILDON_DBUS, "Bluetooth connection (btconn)", TRUE, TRUE, FALSE },
80 #ifdef WITH_BLUEZ_DBUS_BT
81 { GPS_IO_BLUEZ_DBUS, "Bluetooth connection (new)", TRUE, TRUE, FALSE},
83 { GPS_IO_GPSD, "GPSD Connection", FALSE, TRUE, TRUE, GPSD_PORT, LOCALHOST },
84 { GPS_IO_FILE, "File or device", FALSE, TRUE, FALSE, 0, GPS_DEVICE_FILE },
85 { GPS_IO_TCP, "TCP Connection", FALSE, TRUE, TRUE, 1024, LOCALHOST },
87 { GPS_IO_GYPSY, "Gypsy", FALSE, TRUE, FALSE },
89 { GPS_IO_SIMULATION, "Simulation (random)", FALSE, FALSE, FALSE},
94 gps_get_supported_types(void)
103 gps_new(GpsIOSourceType type)
107 gps=g_slice_new0(Gps);
119 gps->io.conn=RCVR_OFF;
121 memset(&gps->data, 0, sizeof(gps->data));
122 gps->data.fix=FIX_NOFIX;
125 gps->data.satinview=0;
126 gps->data.satinuse=0;
127 gps_data_integerize(&gps->data);
131 gps_set_address(Gps *gps, gchar *address, guint port)
134 g_free(gps->io.address);
136 gps->io.address=g_strdup(address);
141 gps_set_type(Gps *gps, GpsIOSourceType type)
151 g_free(gps->io.address);
152 g_slice_free(Gps, gps);
159 gps_data_integerize(GpsData *gpsdata)
161 gdouble tmp=(gpsdata->heading*(1.f/180.f))*G_PI;
163 gpsdata->vel_offset_lat=-(floor(gpsdata->speed*cos(tmp)+0.5f));
164 gpsdata->vel_offset_lon=(floor(gpsdata->speed*sin(tmp)+0.5f));
167 #ifdef WITH_HILDON_DBUS_BT
169 gps_connect_response(DBusGProxy *proxy, DBusGProxyCall *call_id, Gps *gps)
171 GError *error = NULL;
172 gchar *fdpath = NULL;
174 if (gps->io.conn==RCVR_DOWN && gps->io.address) {
175 if (!dbus_g_proxy_end_call(gps->io.rfcomm_req_proxy, call_id, &error, G_TYPE_STRING, &fdpath, G_TYPE_INVALID)) {
176 if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) {
177 /* If we're already connected, it's not an error, unless
178 * they don't give us the file descriptor path, in which
179 * case we re-connect.*/
180 if (!strcmp(BTCOND_ERROR_CONNECTED, dbus_g_error_get_name(error)) || !fdpath) {
181 g_printerr("Caught remote method exception %s: %s", dbus_g_error_get_name(error), error->message);
184 if (gps->connection_retry==NULL)
187 if (gps->connection_retry(gps, error->message)==TRUE) {
188 gps_connect_later(gps);
190 gps_conn_set_state(gps, RCVR_OFF);
196 g_printerr("Error: %s\n", error->message);
198 gps_connect_later(gps); /* Try again later. */
202 gps_connect_file(gps, fdpath);
204 /* else { Looks like the middle of a disconnect. Do nothing. } */
209 * Place a request to connect about 1 second after the function is called.
212 gps_connect_later(Gps *gps)
215 g_assert(gps->io.clater_sid==0);
216 gps->io.clater_sid=g_timeout_add(1000, (GSourceFunc)gps_connect_now, gps);
220 gps_connect(Gps *gps)
222 switch (gps->io.type) {
229 return gps_connect_socket(gps);
241 * Helper to open a file or device node
244 gps_connect_file(Gps *gps, gchar *file)
246 if (-1==(gps->io.fd=open(file, O_RDONLY))) {
248 gps_connect_later(gps);
256 * Helper to connect to a socket file descriptor.
260 gps_connect_socket(Gps *gps)
265 switch (gps->io.type) {
268 g_debug("RFCOMM: %d", gps->io.fd);
269 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_rc, sizeof(gps->io.rcvr_addr_rc));
274 g_debug("TCP: %d", gps->io.fd);
275 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_in, sizeof(gps->io.rcvr_addr_in));
281 g_debug("GPS: Error %d (%s)", e, strerror(e));
283 /* The socket is non blocking so handle it */
290 g_printerr("*** Connection in progress... %d %d\n", e, r);
295 g_printerr("*** Bluetooth/GPS device not found.\n");
298 set_action_activate("gps_enable", FALSE);
299 popup_error(_window, _("No bluetooth or GPS device found."));
304 g_printerr("*** Connection refused.\n");
309 /* Connection failed. Disconnect and try again later. */
310 g_printerr("### Connect failed, retrying... %d %d\n", e, r);
313 gps_connect_later(gps);
322 * Disconnect the GPS device.
323 * - Add channel callbacks are removed
324 * - Channel is close and shutdown
325 * - File descriptor is closed
326 * - Anything special for disconnecting is done
329 gps_disconnect(Gps *gps)
333 g_debug("GPS: Disconnecting from gps");
334 /* Remove watches. */
335 if (gps->io.clater_sid) {
336 g_source_remove(gps->io.clater_sid);
337 gps->io.clater_sid=0;
339 if (gps->io.error_sid) {
340 g_source_remove(gps->io.error_sid);
343 if (gps->io.connect_sid) {
344 g_source_remove(gps->io.connect_sid);
345 gps->io.connect_sid=0;
347 if (gps->io.input_sid) {
348 g_source_remove(gps->io.input_sid);
352 /* Destroy the GIOChannel object. */
353 if (gps->io.channel) {
354 g_debug("GPS: Shutting down IO channel");
355 g_io_channel_shutdown(gps->io.channel, FALSE, NULL);
356 g_io_channel_unref(gps->io.channel);
357 gps->io.channel=NULL;
360 /* Close the file descriptor. */
361 if (gps->io.fd!=-1) {
362 g_debug("GPS: Closing fd %d", gps->io.fd);
368 if (gps->io.type==GPS_IO_GPSD) {
371 g_debug("GPS: Requesting gpsd shutdown using gpsbt");
374 g_warning("Failed to stop gpsd\n");
378 #ifdef WITH_HILDON_DBUS_BT
379 if (gps->io.type==GPS_IO_HILDON_DBUS && gps->io.rfcomm_req_proxy) {
380 GError *error = NULL;
382 g_debug("GPS: Requesting rfcomm disconnection");
383 dbus_g_proxy_call(gps->io.rfcomm_req_proxy, BTCOND_RFCOMM_CANCEL_CONNECT_REQ, &error, G_TYPE_STRING, gps->io.address, G_TYPE_STRING, "SPP", G_TYPE_INVALID, G_TYPE_INVALID);
385 dbus_g_proxy_call(gps->io.rfcomm_req_proxy, BTCOND_RFCOMM_DISCONNECT_REQ, &error,G_TYPE_STRING, gps->io.address, G_TYPE_STRING, "SPP", G_TYPE_INVALID, G_TYPE_INVALID);
389 #ifdef WITH_BLUEZ_DBUS_BT
390 if (gps->io.type==GPS_IO_BLUEZ_DBUS && gps->io.rfcomm_req_proxy) {
391 GError *error = NULL;
400 gps_simulate_start(Gps *gps)
404 gps->data.lat=_home->lat;
405 gps->data.lon=_home->lon;
411 gps->io.conn=RCVR_FIXED;
412 gps->data.fix=FIX_2D;
413 gps->data.satinview=0;
414 gps->data.satinuse=0;
415 gps_data_integerize(&gps->data);
419 gps_simulate_move(Gps *gps)
421 static gdouble slat=0, slon=0;
422 gdouble plat, plon, accel;
430 c=calculate_course_rad(_dest->lat,_dest->lon, gps->data.lat, gps->data.lon);
433 accel=0.005*g_random_double();
436 g_debug("Sim: %f %f %f %f", slat, slon, accel, _dest->angle);
438 if (g_random_double()<0.2) {
439 slat=g_random_double_range(-0.0009, 0.0009);
440 slon=g_random_double_range(-0.0009, 0.0009);
448 BOUND(gps->data.lat, -80.0, 80.0);
449 BOUND(gps->data.lon, -80.0, 80.0);
451 g_debug("Sim: %f %f", gps->data.lat, gps->data.lon);
455 h=calculate_course(plat, plon, gps->data.lat, gps->data.lon);
456 gps->data.heading=(h<0) ? 360+h : h;
457 gps->data.time=time(NULL);
458 gps_data_integerize(&gps->data);
459 if (gps->update_location!=NULL) {
460 gps->update_location(gps);
462 gps->data.lheading=gps->data.heading;
464 return gps->io.conn==RCVR_FIXED ? TRUE : FALSE;
468 * Reset GPS read buffer
471 gps_read_buffer_prepare(Gps *gps)
473 gps->io.curr=gps->io.buffer;
474 *gps->io.curr = '\0';
475 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
479 * Connect file descriptor to channel and add watches.
482 gps_connect_channel_connect(Gps *gps)
484 g_assert(gps->io.channel==NULL);
485 gps->io.channel=g_io_channel_unix_new(gps->io.fd);
486 g_io_channel_set_encoding(gps->io.channel, NULL, NULL);
487 g_io_channel_set_flags(gps->io.channel, G_IO_FLAG_NONBLOCK, NULL);
488 g_io_channel_set_buffered(gps->io.channel, FALSE);
489 gps->io.error_sid=g_io_add_watch_full(gps->io.channel, G_PRIORITY_HIGH_IDLE, G_IO_ERR | G_IO_HUP | G_IO_NVAL, gps_channel_cb_error, gps, NULL);
490 gps->io.connect_sid=g_io_add_watch_full(gps->io.channel, G_PRIORITY_HIGH_IDLE, G_IO_OUT, gps_channel_cb_connect, gps, NULL);
491 gps->io.clater_sid=0;
495 * Connect to the receiver.
496 * This method assumes that fd is -1 and _channel is NULL. If unsure, call
497 * gps_disconnect() first.
498 * Since this is an idle function, this function returns whether or not it
499 * should be called again, which is always FALSE.
501 * This function prepares for the connection, the gps_connect does the "connecting" if needed.
504 gps_connect_now(Gps *gps)
506 GError *error = NULL;
509 g_debug("GPS: Connecting GPS type %d to %s:%d\n", gps->io.type, gps->io.address, gps->io.port);
511 switch (gps->io.type) {
513 if (!gps->io.address)
515 gps_connect_file(gps, gps->io.address);
520 if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
522 struct hostent *hostinfo;
523 struct in_addr taddr;
525 g_debug("TCP: Preparing to connect to %s:%d", gps->io.address, gps->io.port);
527 gps->io.fd=socket(AF_INET, SOCK_STREAM, 0);
528 if (gps->io.fd==-1) {
529 g_debug("Failed to create socket\n");
530 gps_connect_later(gps);
534 gps->io.rcvr_addr_in.sin_family=AF_INET;
535 gps->io.rcvr_addr_in.sin_port=htons(gps->io.port);
536 if (inet_aton(gps->io.address, &taddr)) {
539 gchar ipbuf[INET_ADDRSTRLEN];
541 hostinfo=gethostbyname(gps->io.address);
546 if (hostinfo->h_addrtype!=AF_INET) {
550 ip=ip = inet_ntop (AF_INET, (struct in_addr *)hostinfo->h_addr_list[0], ipbuf, sizeof (ipbuf));
553 if (inet_pton(AF_INET, ip, &gps->io.rcvr_addr_in.sin_addr.s_addr)<1) {
561 if (gps->io.type==GPS_IO_GPSD) {
564 /* Force correct gpsd binary */
565 setenv ("GPSD_PROG", "/usr/sbin/gpsd", 1);
567 memset(&ctx, 0, sizeof(ctx));
568 memset(&errbuf, 0, sizeof(errbuf));
571 if (gpsbt_start(NULL, 0, 0, 0, errbuf, sizeof(errbuf), 0, &ctx)!=0) {
572 g_warning("Failed to start gpsd: %d (%s) %s\n", errno, strerror(errno), errbuf);
575 g_debug("Waiting for gpsd");
577 g_debug("Done wating");
582 #ifdef WITH_HILDON_DBUS_BT
583 case GPS_IO_HILDON_DBUS:
584 if (NULL == (gps->io.dbus_conn=dbus_g_bus_get(DBUS_BUS_SYSTEM, &error))) {
585 g_printerr("Failed to get D-Bus connection.");
588 if (NULL == (gps->io.rfcomm_req_proxy = dbus_g_proxy_new_for_name(gps->io.dbus_conn, BTCOND_SERVICE, BTCOND_REQ_PATH, BTCOND_REQ_INTERFACE))) {
589 g_printerr("Failed to open connection to %s.\n", BTCOND_REQ_INTERFACE);
593 if (gps->io.rfcomm_req_proxy) {
594 gboolean mybool = TRUE;
595 dbus_g_proxy_begin_call(gps->io.rfcomm_req_proxy,
596 BTCOND_RFCOMM_CONNECT_REQ, (DBusGProxyCallNotify)gps_connect_response, gps, NULL,
597 G_TYPE_STRING, gps->io.address,
598 G_TYPE_STRING, "SPP",
599 G_TYPE_BOOLEAN, &mybool,
606 if (!gps->io.address)
609 if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
610 gps->io.fd=socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
612 /* If file descriptor creation failed, try again later. Note that
613 * there is no need to call rcvr_disconnect() because the file
614 * descriptor creation is the first step, so if it fails, there's
615 * nothing to clean up. */
616 if (gps->io.fd==-1) {
617 g_debug("Failed to create socket");
618 gps_connect_later(gps);
621 gps->io.rcvr_addr_rc.rc_family=AF_BLUETOOTH;
622 gps->io.rcvr_addr_rc.rc_channel=1;
623 str2ba(gps->io.address, &gps->io.rcvr_addr_rc.rc_bdaddr);
627 case GPS_IO_SIMULATION:
628 /* Set a periodic cb to generate random movement */
629 gps_simulate_start(gps);
630 gps->io.input_sid=g_timeout_add(1000, (GSourceFunc)gps_simulate_move, gps);
634 g_printerr("Unknown GPS connection type\n");
639 gps_read_buffer_prepare(gps);
640 gps_connect_channel_connect(gps);
646 * Connection callback
649 gps_channel_cb_connect(GIOChannel *src, GIOCondition condition, gpointer data)
651 gint error, size = sizeof(error);
652 Gps *gps=(Gps *)data;
655 g_assert(gps->io.channel);
657 gps->io.curr=gps->io.buffer;
658 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
660 switch (gps->io.type) {
664 if ((getsockopt(gps->io.fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)) {
665 g_printerr("Error connecting to receiver; retrying...\n");
667 gps_connect_later(gps);
668 gps->io.connect_sid=0;
672 /* Set gpsd to NMEA mode */
673 if (gps->io.type==GPS_IO_GPSD) {
676 GError *error = NULL;
678 g_debug("GPS: Requesting NMEA mode from GPSD using cmd %s", GPSD_NMEA);
679 status=g_io_channel_write_chars(gps->io.channel, GPSD_NMEA, strlen(GPSD_NMEA), &written, &error);
680 if (status==G_IO_STATUS_NORMAL && written==strlen(GPSD_NMEA)) {
681 g_debug("GPS: NMEA mode set (%d %d)", (gint)written, (gint)status);
683 g_printerr("Failed to set gpsd to NMEA mode: %d %d %s\n",(gint)status,(gint)written, error->message);
686 gps_connect_later(gps);
687 gps->io.connect_sid=0;
697 g_warning("Connection from non-connecting source\n");
701 g_printf("Connected to receiver!\n");
702 gps_conn_set_state(gps, RCVR_UP);
703 gps->io.input_sid=g_io_add_watch_full(gps->io.channel, G_PRIORITY_HIGH_IDLE, G_IO_IN | G_IO_PRI, gps_channel_cb_input, gps, NULL);
705 gps->io.connect_sid=0;
710 gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
712 Gps *gps=(Gps *)data;
715 g_debug("GPS: Channel error %d", condition);
717 /* An error has occurred - re-connect(). */
721 if (gps->connection_error!=NULL)
722 gps->connection_error(gps, "GPS Connection error");
724 if (gps->io.conn > RCVR_OFF) {
725 gps_conn_set_state(gps, RCVR_DOWN);
726 gps_connect_later(gps);
733 gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
738 Gps *gps=(Gps *)data;
740 g_debug("%s(%d)\n", __PRETTY_FUNCTION__, condition);
743 status=g_io_channel_read_chars(gps->io.channel, gps->io.curr, gps->io.last-gps->io.curr, &bytes_read, NULL);
745 case G_IO_STATUS_NORMAL:
746 gps->io.curr += bytes_read;
747 *gps->io.curr = '\0'; /* append a \0 so we can read as string */
748 while ((eol = strchr(gps->io.buffer, '\n'))) {
749 gchar *sptr = gps->io.buffer + 1; /* Skip the $ */
751 if (*gps->io.buffer=='$') {
752 /* This is the beginning of a sentence; okay to parse. */
753 *eol = '\0'; /* overwrite \n with \0 */
754 while (*sptr && *sptr != '*')
757 /* If we're at a \0 (meaning there is no checksum), or if the
758 * checksum is good, then parse the sentence. */
759 if (!*sptr || csum == strtol(sptr + 1, NULL, 16)) {
763 *sptr = '\0'; /* take checksum out of the buffer. */
766 gps->io.nmea=gps->io.buffer;
768 g_assert(gps->io.nmea);
769 g_debug("NMEA1 %d: (%s)", gps->io.nmea_cnt, gps->io.nmea);
773 /* There was a checksum, and it was bad. */
774 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n", __PRETTY_FUNCTION__, gps->io.buffer);
778 /* If eol is at or after (_gps_read_buf_curr - 1) */
779 if (eol >= (gps->io.curr - 1)) {
780 /* Last read was a newline - reset read buffer */
781 gps->io.curr = gps->io.buffer;
782 *gps->io.curr = '\0';
784 /* Move the next line to the front of the buffer. */
785 memmove(gps->io.buffer, eol + 1, gps->io.curr - eol); /* include terminating 0 */
786 /* Subtract _curr so that it's pointing at the new \0. */
787 gps->io.curr -= (eol - gps->io.buffer + 1);
793 case G_IO_STATUS_ERROR:
794 case G_IO_STATUS_EOF:
796 gps_connect_later(gps);
798 if (gps->io.errors>10) {
799 if (gps->connection_error==NULL)
802 gps->connection_error(gps, "GPS data read error.");
807 case G_IO_STATUS_AGAIN:
809 if (gps->io.again>20) {
811 gps_connect_later(gps);
812 if (gps->connection_error==NULL)
815 gps->connection_error(gps, "GPS connection deadlock.");
821 g_assert_not_reached();