]> err.no Git - mapper/blobdiff - src/gps.c
Add header and cast properly.
[mapper] / src / gps.c
index 6e8cea548291cceeb6446d5f3328c73987d4b528..1962f5deef4d126a52a61d7702d68f7a365c9728 100644 (file)
--- a/src/gps.c
+++ b/src/gps.c
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include "config.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stddef.h>
+#include <libintl.h>
+#include <locale.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <glib/gstdio.h>
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
 #include "gps.h"
-#include "utils.h"
+#include "latlon.h"
+#include "map.h"
+#include "gps-conn.h"
+#include "gps-nmea-parse.h"
+
+#ifdef WITH_HILDON_DBUS_BT
+#include <bt-dbus.h>
+#endif
+
+#ifdef WITH_GPSBT
+#include <gpsbt.h>
+static gpsbt_t ctx;
+#endif
+
+static gboolean gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data);
+static gboolean gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data);
+static gboolean gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data);
+static gboolean gps_connect_socket(Gps *gps);
+static gboolean gps_connect_file(Gps *gps, gchar *file);
+
+#define GPSD_NMEA "r+\r\n"
+#define GPSD_PORT (2947)
+#define LOCALHOST "127.0.0.1"
+#define GPS_DEVICE_FILE "/dev/ttyS0"
+
+const GpsTypes gps_types[] = {
+#ifdef WITH_BLUEZ
+       { GPS_IO_RFCOMM,                "Bluetooth connection (old)", TRUE, TRUE, FALSE},
+#endif
+#ifdef WITH_HILDON_DBUS_BT
+       { GPS_IO_HILDON_DBUS,   "Bluetooth connection (btconn)", TRUE, TRUE, FALSE },
+#endif
+#ifdef WITH_BLUEZ_DBUS_BT
+       { GPS_IO_BLUEZ_DBUS,    "Bluetooth connection (new)", TRUE, TRUE, FALSE},
+#endif
+       { GPS_IO_GPSD,                  "GPSD Connection", FALSE, TRUE, TRUE, GPSD_PORT, LOCALHOST },
+       { GPS_IO_FILE,                  "File or device", FALSE, TRUE, FALSE, 0, GPS_DEVICE_FILE },
+       { GPS_IO_TCP,                   "TCP Connection", FALSE, TRUE, TRUE, 1024, LOCALHOST },
+#ifdef WITH_GYPSY
+       { GPS_IO_GYPSY,                 "Gypsy", FALSE, TRUE, FALSE },
+#endif
+       { GPS_IO_SIMULATION,    "Simulation (random)", FALSE, FALSE, FALSE},
+       { 0 }
+};
+
+const GpsTypes *
+gps_get_supported_types(void) 
+{
+return gps_types;
+}
+
+/**
+ * Init GPS structure
+ */
+Gps *
+gps_new(GpsIOSourceType type)
+{
+Gps *gps;
+
+gps=g_slice_new0(Gps);
+gps->data.fix=FIX_NOFIX;
+gps->io.fd=-1;
+gps->io.address=NULL;
+gps->io.type=type;
+gps->io.conn=RCVR_OFF;
+gps->io.nmea=NULL;
+gps->io.nmea_cnt=0;
+gps->data.lat=60.20;
+gps->data.lon=22.20;
+gps->connection_error=NULL;
+gps->connection_retry=NULL;
+gps->connection_progress=NULL;
+gps->update_location=NULL;
+gps->update_info=NULL;
+gps->update_satellite=NULL;
+gps_data_integerize(&gps->data);
+return gps;
+}
+
+void
+gps_set_address(Gps *gps, gchar *address, guint port)
+{
+if (gps->io.address)
+       g_free(gps->io.address);
+if (address)
+       gps->io.address=g_strdup(address);
+gps->io.port=port;
+}
 
 void
-gps_init(void)
-{
-_gps.lat = 0;
-_gps.lon = 0;
-_gps.heading = 0;
-_gps.lheading = 0;
-_gps.fix = 1;
-_gps.satinuse = 0;
-_gps.satinview = 0;
-_gps.speed = 0.f;
-_pos.unitx = 0;
-_pos.unity = 0;
-
-integerize_data(&_gps, &_pos);
+gps_set_type(Gps *gps, GpsIOSourceType type)
+{
+gps->io.type=type;
+}
+
+void
+gps_free(Gps *gps)
+{
+gps_disconnect(gps);
+if (gps->io.address)
+       g_free(gps->io.address);
+g_slice_free(Gps, gps);
+}
+
+/**
+ * Convert the float lat/lon/speed/heading data into integer units.
+ * Also calculate offsets.
+ */
+void 
+gps_data_integerize(GpsData *gpsdata)
+{
+gdouble tmp;
+
+tmp=(gpsdata->heading*(1.f/180.f))*G_PI;
+latlon2unit(gpsdata->lat, gpsdata->lon, gpsdata->unitx, gpsdata->unity);
+gpsdata->vel_offsetx=(gint)(floor(gpsdata->speed*sin(tmp)+0.5f));
+gpsdata->vel_offsety=-(gint)(floor(gpsdata->speed*cos(tmp)+0.5f));
+}
+
+#ifdef WITH_HILDON_DBUS_BT
+void
+gps_connect_response(DBusGProxy *proxy, DBusGProxyCall *call_id, Gps *gps)
+{
+GError *error = NULL;
+gchar *fdpath = NULL;
+
+if (gps->io.conn==RCVR_DOWN && gps->io.address) {
+       if (!dbus_g_proxy_end_call(gps->io.rfcomm_req_proxy, call_id, &error, G_TYPE_STRING, &fdpath, G_TYPE_INVALID)) {
+               if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) {
+                       /* If we're already connected, it's not an error, unless
+                        * they don't give us the file descriptor path, in which
+                        * case we re-connect.*/
+                       if (!strcmp(BTCOND_ERROR_CONNECTED, dbus_g_error_get_name(error)) || !fdpath) {
+                               g_printerr("Caught remote method exception %s: %s", dbus_g_error_get_name(error), error->message);
+                               gps_disconnect(gps);
+
+                               if (gps->connection_retry==NULL)
+                                       return;
+
+                               if (gps->connection_retry(gps, error->message)==TRUE) {
+                                       gps_connect_later(gps);
+                               } else {
+                                       gps_conn_set_state(gps, RCVR_OFF);
+                               }
+                               return;
+                       }
+               } else {
+                       /* Unknown error. */
+                       g_printerr("Error: %s\n", error->message);
+                       gps_disconnect(gps);
+                       gps_connect_later(gps); /* Try again later. */
+                       return;
+               }
+       }
+       gps_connect_file(gps, fdpath);
+}
+/* else { Looks like the middle of a disconnect.  Do nothing. } */
+}
+#endif
+
+/**
+ * Place a request to connect about 1 second after the function is called.
+ */
+void 
+gps_connect_later(Gps *gps)
+{
+g_assert(gps);
+g_assert(gps->io.clater_sid==0);
+gps->io.clater_sid=g_timeout_add(1000, (GSourceFunc)gps_connect_now, gps);
+}
+
+gboolean
+gps_connect(Gps *gps)
+{
+switch (gps->io.type) {
+       case GPS_IO_FILE:
+               return TRUE;
+       break;
+       case GPS_IO_RFCOMM:
+       case GPS_IO_TCP:
+       case GPS_IO_GPSD:
+               return gps_connect_socket(gps);
+       break;
+       case GPS_IO_GYPSY:
+               return FALSE;
+       break;
+       default:
+       break;
+}
+return FALSE;
+}
+
+/**
+ * Helper to open a file or device node
+ */
+static gboolean 
+gps_connect_file(Gps *gps, gchar *file)
+{
+if (-1==(gps->io.fd=open(file, O_RDONLY))) {
+       gps_disconnect(gps);
+       gps_connect_later(gps);
+       /* Add retry cb */
+       return FALSE;
+}
+return TRUE;
+}
+
+/**
+ * Helper to connect to a socket file descriptor.
+ * 
+ */
+static gboolean 
+gps_connect_socket(Gps *gps)
+{
+gint r, e;
+g_assert(gps);
+
+switch (gps->io.type) {
+       case GPS_IO_RFCOMM:
+#ifdef WITH_BLUEZ
+               g_debug("RFCOMM: %d", gps->io.fd);
+               r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_rc, sizeof(gps->io.rcvr_addr_rc));
+#endif
+       break;
+       case GPS_IO_TCP:
+       case GPS_IO_GPSD:
+               g_debug("TCP: %d", gps->io.fd);
+               r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_in, sizeof(gps->io.rcvr_addr_in));
+       break;
+       default:
+               return FALSE;
+}
+e=errno;
+g_debug("GPS: Error %d (%s)", e, strerror(e));
+
+/* The socket is non blocking so handle it */
+if (r != 0) {
+       switch (e) {
+       case EAGAIN:
+       case EBUSY:
+       case EINPROGRESS:
+       case EALREADY:
+               g_printerr("*** Connection in progress... %d %d\n", e, r);
+               perror("INFO: ");
+               return TRUE;
+       break;
+       case EHOSTUNREACH:
+               g_printerr("*** Bluetooth/GPS device not found.\n");
+               gps_disconnect(gps);
+#if 0
+        set_action_activate("gps_enable", FALSE);
+        popup_error(_window, _("No bluetooth or GPS device found."));
+#endif
+               return FALSE;
+       break;
+       case ECONNREFUSED:
+               g_printerr("*** Connection refused.\n");
+               gps_disconnect(gps);
+               return FALSE;
+       break;
+       default:
+               /* Connection failed.  Disconnect and try again later. */
+               g_printerr("### Connect failed, retrying... %d %d\n", e, r);
+               perror("ERROR: ");
+               gps_disconnect(gps);
+               gps_connect_later(gps);
+               return FALSE;
+       break;
+       }
+}
+return TRUE;
+}
+
+/**
+ * Disconnect the GPS device.
+ * - Add channel callbacks are removed
+ * - Channel is close and shutdown
+ * - File descriptor is closed
+ * - Anything special for disconnecting is done
+ */
+void 
+gps_disconnect(Gps *gps)
+{
+g_assert(gps);
+
+g_debug("GPS: Disconnecting from gps");
+/* Remove watches. */
+if (gps->io.clater_sid) {
+       g_source_remove(gps->io.clater_sid);
+       gps->io.clater_sid=0;
+}
+if (gps->io.error_sid) {
+       g_source_remove(gps->io.error_sid);
+       gps->io.error_sid=0;
+}
+if (gps->io.connect_sid) {
+       g_source_remove(gps->io.connect_sid);
+       gps->io.connect_sid=0;
+}
+if (gps->io.input_sid) {
+       g_source_remove(gps->io.input_sid);
+       gps->io.input_sid=0;
+}
+
+/* Destroy the GIOChannel object. */
+if (gps->io.channel) {
+       g_debug("GPS: Shutting down IO channel");
+       g_io_channel_shutdown(gps->io.channel, FALSE, NULL);
+       g_io_channel_unref(gps->io.channel);
+       gps->io.channel=NULL;
+}
+
+/* Close the file descriptor. */
+if (gps->io.fd!=-1) {
+       g_debug("GPS: Closing fd %d", gps->io.fd);
+       close(gps->io.fd);
+       gps->io.fd=-1;
+}
+
+#ifdef WITH_GPSBT
+if (gps->io.type==GPS_IO_GPSD) {
+       gint r;
+
+       g_debug("GPS: Requesting gpsd shutdown using gpsbt");
+       r=gpsbt_stop(&ctx);
+       if (r!=0)
+               g_warning("Failed to stop gpsd\n");
+}
+#endif
+
+#ifdef WITH_HILDON_DBUS_BT
+if (gps->io.type==GPS_IO_HILDON_DBUS && gps->io.rfcomm_req_proxy) {
+       GError *error = NULL;
+
+       g_debug("GPS: Requesting rfcomm disconnection");
+       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);
+       error = NULL;
+       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);
+}
+#endif
+
+#ifdef WITH_BLUEZ_DBUS_BT
+if (gps->io.type==GPS_IO_BLUEZ_DBUS && gps->io.rfcomm_req_proxy) {
+       GError *error = NULL;
+       
+}
+#endif
+
+}
+
+static void
+gps_simulate_start(Gps *gps)
+{
+gps->data.lat=60.45;
+gps->data.lon=22.26;
+gps->io.conn=RCVR_FIXED;
+gps->data.fix=FIX_2D;
+gps_data_integerize(&gps->data);
+}
+
+static gboolean
+gps_simulate_move(Gps *gps)
+{
+static gdouble slat=0, slon=0;
+gdouble plat, plon;
+gfloat h;
+g_assert(gps);
+
+if (g_random_double()<0.5) {
+       slat=g_random_double_range(-0.0008, 0.0008);
+       slon=g_random_double_range(-0.0008, 0.0008);
+}
+plat=gps->data.lat;
+plon=gps->data.lon;
+gps->data.lat+=slat;
+gps->data.lon+=slon;
+BOUND(gps->data.lat, -80.0, 80.0);
+BOUND(gps->data.lon, -80.0, 80.0);
+
+g_debug("Sim: %f %f\n", gps->data.lat, gps->data.lon);
+
+gps->data.speed=1+g_random_double_range(0.1, 10.0);
+h=calculate_course(plat, plon, gps->data.lat, gps->data.lon);
+gps->data.heading=(h<0) ? 360+h : h;
+gps->data.time=time(NULL);
+gps_data_integerize(&gps->data);
+if (gps->update_location!=NULL) {
+       gps->update_location(gps);
+}
+gps->data.lheading=gps->data.heading;
+
+return gps->io.conn==RCVR_FIXED ? TRUE : FALSE;
+}
+
+/**
+ * Reset GPS read buffer 
+ */
+static void
+gps_read_buffer_prepare(Gps *gps)
+{
+gps->io.curr=gps->io.buffer;
+*gps->io.curr = '\0';
+gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
+}
+
+/**
+ * Connect file descriptor to channel and add watches. 
+ */
+static void
+gps_connect_channel_connect(Gps *gps)
+{
+g_assert(gps->io.channel==NULL);
+gps->io.channel=g_io_channel_unix_new(gps->io.fd);
+g_io_channel_set_encoding(gps->io.channel, NULL, NULL);
+g_io_channel_set_flags(gps->io.channel, G_IO_FLAG_NONBLOCK, NULL);
+g_io_channel_set_buffered(gps->io.channel, FALSE);
+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);
+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);
+gps->io.clater_sid=0;
+}
+
+/**
+ * Connect to the receiver.
+ * This method assumes that fd is -1 and _channel is NULL.  If unsure, call
+ * gps_disconnect() first.
+ * Since this is an idle function, this function returns whether or not it
+ * should be called again, which is always FALSE.
+ *
+ * This function prepares for the connection, the gps_connect does the "connecting" if needed.
+ */
+gboolean 
+gps_connect_now(Gps *gps)
+{
+GError *error = NULL;
+
+g_assert(gps);
+g_debug("GPS: Connecting GPS type %d to %s:%d\n", gps->io.type, gps->io.address, gps->io.port);
+
+switch (gps->io.type) {
+       case GPS_IO_FILE:
+               if (!gps->io.address)
+                       return FALSE;
+               gps_connect_file(gps, gps->io.address);
+               return FALSE;
+       break;
+       case GPS_IO_TCP:
+       case GPS_IO_GPSD:
+               if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
+                       gps->io.fd=socket(AF_INET, SOCK_STREAM, 0);
+                       if (gps->io.fd==-1) {
+                               g_debug("Failed to create socket\n");
+                               gps_connect_later(gps);
+                               return FALSE;
+                       }
+                       g_debug("TCP: Preparing to connect to %s:%d", gps->io.address, gps->io.port);
+                       gps->io.rcvr_addr_in.sin_family = AF_INET;
+                       gps->io.rcvr_addr_in.sin_port = htons(gps->io.port);
+                       if (inet_pton(AF_INET, gps->io.address, &gps->io.rcvr_addr_in.sin_addr.s_addr)<1) {
+                               perror("TCP:");
+                               return FALSE;
+                       }
+               } else {
+                       return FALSE;
+               }
+#ifdef WITH_GPSBT
+               if (gps->io.type==GPS_IO_GPSD) {
+                       gchar errbuf[255];
+
+                       /* Force correct gpsd binary */
+                       setenv ("GPSD_PROG", "/usr/sbin/gpsd", 1);
+
+                       memset(&ctx, 0, sizeof(ctx))
+                       memset(&errbuf, 0, sizeof(errbuf))
+                       errno=0;
+
+                       if (gpsbt_start(NULL, 0, 0, 0, errbuf, sizeof(errbuf), 0, &ctx)!=0)
+                               g_warning("Failed to start gpsd: %d (%s) %s\n", errno, strerror(errno), errbuff);
+                               return FALSE;
+                       } else {
+                               g_debug("Waiting for gpsd");
+                               sleep(2);
+                               g_debug("Done wating");
+                       }
+               }
+#endif
+       break;
+#ifdef WITH_HILDON_DBUS_BT
+       case GPS_IO_HILDON_DBUS:
+               if (NULL == (gps->io.dbus_conn=dbus_g_bus_get(DBUS_BUS_SYSTEM, &error))) {
+                       g_printerr("Failed to get D-Bus connection.");
+                       return FALSE;
+               }
+               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))) {
+                       g_printerr("Failed to open connection to %s.\n", BTCOND_REQ_INTERFACE);
+                       return FALSE;
+                }
+
+               if (gps->io.rfcomm_req_proxy) {
+                       gboolean mybool = TRUE;
+                       dbus_g_proxy_begin_call(gps->io.rfcomm_req_proxy,
+                               BTCOND_RFCOMM_CONNECT_REQ, (DBusGProxyCallNotify)gps_connect_response, gps, NULL,
+                               G_TYPE_STRING, gps->io.address,
+                               G_TYPE_STRING, "SPP",
+                               G_TYPE_BOOLEAN, &mybool,
+                               G_TYPE_INVALID);
+               }
+       break;
+#endif
+#ifdef WITH_BLUEZ
+       case GPS_IO_RFCOMM:
+               if (!gps->io.address)
+                       return FALSE;
+
+               if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
+                       gps->io.fd=socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+
+                       /* If file descriptor creation failed, try again later.  Note that
+                        * there is no need to call rcvr_disconnect() because the file
+                        * descriptor creation is the first step, so if it fails, there's
+                        * nothing to clean up. */
+                       if (gps->io.fd==-1) {
+                               g_debug("Failed to create socket\n");
+                               gps_connect_later(gps);
+                               return FALSE;
+                       }
+                       gps->io.rcvr_addr_rc.rc_family=AF_BLUETOOTH;
+                       gps->io.rcvr_addr_rc.rc_channel=1;
+                       str2ba(gps->io.address, &gps->io.rcvr_addr_rc.rc_bdaddr);
+               }
+#endif
+       break;
+       case GPS_IO_SIMULATION:
+               /* Set a periodic cb to generate random movement */
+               gps_simulate_start(gps);
+               gps->io.input_sid=g_timeout_add(1000, (GSourceFunc)gps_simulate_move, gps);
+               return FALSE;
+       break;
+       default:
+               g_printerr("Unknown GPS connection type\n");
+               return FALSE;
+       break;
+}
+
+gps_read_buffer_prepare(gps);
+gps_connect_channel_connect(gps);
+gps_connect(gps);
+return FALSE;
+}
+
+/**
+ * Connection callback
+ */
+static gboolean
+gps_channel_cb_connect(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+gint error, size = sizeof(error);
+Gps *gps=(Gps *)data;
+
+g_assert(data);
+g_assert(gps->io.channel);
+
+gps->io.curr=gps->io.buffer;
+gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
+
+switch (gps->io.type) {
+       case GPS_IO_TCP:
+       case GPS_IO_RFCOMM:
+       case GPS_IO_GPSD:
+               if ((getsockopt(gps->io.fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)) {
+                       g_printerr("Error connecting to receiver; retrying...\n");
+                       gps_disconnect(gps);
+                       gps_connect_later(gps);
+                       gps->io.connect_sid=0;
+                       gps->io.errors++;
+                       return FALSE;
+               } 
+               /* Set gpsd to NMEA mode */
+               if (gps->io.type==GPS_IO_GPSD) {
+                       GIOStatus status;
+                       gsize written;
+                       GError *error = NULL;
+
+                       g_debug("GPS: Requesting NMEA mode from GPSD using cmd %s", GPSD_NMEA);
+                       status=g_io_channel_write_chars(gps->io.channel, GPSD_NMEA, strlen(GPSD_NMEA), &written, &error);
+                       if (status==G_IO_STATUS_NORMAL && written==strlen(GPSD_NMEA)) {
+                               g_debug("GPS: NMEA mode set (%d %d)", (gint)written, (gint)status);
+                       } else {
+                               g_printerr("Failed to set gpsd to NMEA mode: %d %d %s\n",(gint)status,(gint)written, error->message);
+                               g_error_free(error);
+                               gps_disconnect(gps);
+                               gps_connect_later(gps);
+                               gps->io.connect_sid=0;
+                               gps->io.errors++;
+                               return FALSE;
+                       }
+               }
+       break;
+       case GPS_IO_FILE:
+
+       break;
+       default:
+               g_warning("Connection from non-connecting source\n");
+       break;
+}
+
+g_printf("Connected to receiver!\n");
+gps_conn_set_state(gps, RCVR_UP);
+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);
+gps->io.errors=0;
+gps->io.connect_sid=0;
+return FALSE;
+}
+
+static gboolean
+gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+Gps *gps=(Gps *)data;
+
+g_assert(data);
+g_debug("GPS: Channel error %d", condition);
+
+/* An error has occurred - re-connect(). */
+gps_disconnect(gps);
+
+/* Do error cb */
+if (gps->connection_error!=NULL)
+       gps->connection_error(gps, "GPS Connection error");
+
+if (gps->io.conn > RCVR_OFF) {
+       gps_conn_set_state(gps, RCVR_DOWN);
+       gps_connect_later(gps);
+}
+
+return FALSE;
+}
+
+static gboolean
+gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+gsize bytes_read;
+GIOStatus status;
+gchar *eol;
+Gps *gps=(Gps *)data;
+
+g_debug("%s(%d)\n", __PRETTY_FUNCTION__, condition);
+g_assert(data);
+
+status=g_io_channel_read_chars(gps->io.channel, gps->io.curr, gps->io.last-gps->io.curr, &bytes_read, NULL);
+switch (status) {
+       case G_IO_STATUS_NORMAL:
+       gps->io.curr += bytes_read;
+       *gps->io.curr = '\0';   /* append a \0 so we can read as string */
+       while ((eol = strchr(gps->io.buffer, '\n'))) {
+               gchar *sptr = gps->io.buffer + 1;       /* Skip the $ */
+               guint csum = 0;
+               if (*gps->io.buffer=='$') {
+                       /* This is the beginning of a sentence; okay to parse. */
+                       *eol = '\0';    /* overwrite \n with \0 */
+                       while (*sptr && *sptr != '*')
+                               csum ^= *sptr++;
+
+                       /* If we're at a \0 (meaning there is no checksum), or if the
+                        * checksum is good, then parse the sentence. */
+                       if (!*sptr || csum == strtol(sptr + 1, NULL, 16)) {
+                               gchar *data;
+
+                               if (*sptr)
+                                       *sptr = '\0';   /* take checksum out of the buffer. */
+
+                               gps->io.nmea_cnt++;
+                               gps->io.nmea=gps->io.buffer;
+
+                               g_assert(gps->io.nmea);
+                               g_debug("NMEA1 %d: (%s)", gps->io.nmea_cnt, gps->io.nmea);
+
+                               gps_nmea_parse(gps);
+                       } else {
+                               /* There was a checksum, and it was bad. */
+                               g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n", __PRETTY_FUNCTION__, gps->io.buffer);
+                       }
+               }
+
+               /* If eol is at or after (_gps_read_buf_curr - 1) */
+               if (eol >= (gps->io.curr - 1)) {
+                       /* Last read was a newline - reset read buffer */
+                       gps->io.curr = gps->io.buffer;
+                       *gps->io.curr = '\0';
+               } else {
+                       /* Move the next line to the front of the buffer. */
+                       memmove(gps->io.buffer, eol + 1, gps->io.curr - eol);   /* include terminating 0 */
+                       /* Subtract _curr so that it's pointing at the new \0. */
+                       gps->io.curr -= (eol - gps->io.buffer + 1);
+               }
+       }
+       gps->io.errors=0;
+       gps->io.again=0;
+       break;
+       case G_IO_STATUS_ERROR:
+       case G_IO_STATUS_EOF:
+               gps_disconnect(gps);
+               gps_connect_later(gps);
+               gps->io.errors++;
+               if (gps->io.errors>10) {
+                       if (gps->connection_error==NULL)
+                               return FALSE;
+                       else {
+                               gps->connection_error(gps, "GPS data read error.");                     
+                       }
+               }
+               return FALSE;
+       break;
+       case G_IO_STATUS_AGAIN:
+               gps->io.again++;
+               if (gps->io.again>20) {
+                       gps_disconnect(gps);
+                       gps_connect_later(gps);
+                       if (gps->connection_error==NULL)
+                               return FALSE;
+                       else {
+                               gps->connection_error(gps, "GPS connection deadlock.");
+                       }
+               }
+               return TRUE;
+       break;
+       default:
+               g_assert_not_reached();
+       break;
+}
+
+return TRUE;
 }