]> err.no Git - mapper/blob - src/gps.c
Do the info update callbacks in as idle
[mapper] / src / gps.c
1 /*
2  * This file is part of mapper
3  *
4  * Copyright (C) 2007 Kaj-Michael Lang
5  * Copyright (C) 2006-2007 John Costigan.
6  *
7  * POI and GPS-Info code originally written by Cezary Jackiewicz.
8  *
9  * Default map data provided by http://www.openstreetmap.org/
10  *
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.
15  *
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.
20  *
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.
24  */
25
26 #include "config.h"
27
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <stddef.h>
33 #include <libintl.h>
34 #include <locale.h>
35 #include <math.h>
36 #include <errno.h>
37 #include <sys/wait.h>
38 #include <glib/gstdio.h>
39 #include <dbus/dbus.h>
40 #include <dbus/dbus-glib.h>
41 #include <fcntl.h>
42
43 #include "gps.h"
44 #include "latlon.h"
45 #include "map.h"
46 #include "gps-conn.h"
47 #include "gps-nmea-parse.h"
48
49 #ifdef WITH_HILDON_DBUS_BT
50 #include <bt-dbus.h>
51 #endif
52
53 #ifdef WITH_GPSBT
54 #include <gpsbt.h>
55 static gpsbt_t ctx = {0};
56 #endif
57
58 static gboolean gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data);
59 static gboolean gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data);
60 static gboolean gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data);
61 static gboolean gps_connect_socket(Gps *gps);
62 static gboolean gps_connect_file(Gps *gps, gchar *file);
63
64 #define GPSD_NMEA "r+\r\n"
65 #define GPSD_PORT (2947)
66
67 const GpsTypes gps_types[] = {
68 #ifdef WITH_BLUEZ
69         { GPS_IO_RFCOMM,                "Bluetooth connection (old)", TRUE, TRUE},
70 #endif
71 #ifdef WITH_HILDON_DBUS_BT
72         { GPS_IO_HILDON_DBUS,   "Bluetooth connection (btconn)", TRUE, TRUE },
73 #endif
74 #ifdef WITH_BLUEZ_DBUS_BT
75         { GPS_IO_BLUEZ_DBUS,    "Bluetooth connection (new)", TRUE, TRUE},
76 #endif
77         { GPS_IO_GPSD,                  "GPSD Connection", FALSE, TRUE},
78         { GPS_IO_FILE,                  "File or device", FALSE, TRUE},
79         { GPS_IO_TCP,                   "TCP Connection", FALSE, TRUE},
80 #ifdef WITH_GYPSY
81         { GPS_IO_GYPSY,                 "Gypsy", FALSE, TRUE},
82 #endif
83         { GPS_IO_SIMULATION,    "Simulation (random)", FALSE, FALSE},
84         { 0 }
85 };
86
87 const GpsTypes *
88 gps_get_supported_types(void) 
89 {
90 return gps_types;
91 }
92
93 /**
94  * Init GPS structure
95  */
96 Gps *
97 gps_new(GpsIOSourceType type)
98 {
99 Gps *gps;
100
101 gps=g_slice_new0(Gps);
102 gps->data.fix=FIX_NOFIX;
103 gps->io.fd=-1;
104 gps->io.address=NULL;
105 gps->io.type=type;
106 gps->io.conn=RCVR_OFF;
107 gps->io.nmea=NULL;
108 gps->data.lat=60.20;
109 gps->data.lon=22.20;
110 gps->connection_error=NULL;
111 gps->connection_retry=NULL;
112 gps->connection_progress=NULL;
113 gps->update_location=NULL;
114 gps->update_info=NULL;
115 gps->update_satellite=NULL;
116 gps_data_integerize(&gps->data);
117 return gps;
118 }
119
120 void
121 gps_set_address(Gps *gps, gchar *address, guint port)
122 {
123 if (gps->io.address)
124         g_free(gps->io.address);
125 if (address)
126         gps->io.address=g_strdup(address);
127 gps->io.port=port;
128 }
129
130 void
131 gps_set_type(Gps *gps, GpsIOSourceType type)
132 {
133 gps->io.type=type;
134 }
135
136 void
137 gps_free(Gps *gps)
138 {
139 gps_disconnect(gps);
140 if (gps->io.address)
141         g_free(gps->io.address);
142 g_slice_free(Gps, gps);
143 }
144
145 /**
146  * Convert the float lat/lon/speed/heading data into integer units.
147  * Also calculate offsets.
148  */
149 void 
150 gps_data_integerize(GpsData *gpsdata)
151 {
152 gdouble tmp;
153
154 tmp=(gpsdata->heading*(1.f/180.f))*G_PI;
155 latlon2unit(gpsdata->lat, gpsdata->lon, gpsdata->unitx, gpsdata->unity);
156 gpsdata->vel_offsetx=(gint)(floor(gpsdata->speed*sin(tmp)+0.5f));
157 gpsdata->vel_offsety=-(gint)(floor(gpsdata->speed*cos(tmp)+0.5f));
158 }
159
160 #ifdef WITH_HILDON_DBUS_BT
161 void
162 gps_connect_response(DBusGProxy *proxy, DBusGProxyCall *call_id, Gps *gps)
163 {
164 GError *error = NULL;
165 gchar *fdpath = NULL;
166
167 if (gps->io.conn==RCVR_DOWN && gps->io.address) {
168         if (!dbus_g_proxy_end_call(gps->io.rfcomm_req_proxy, call_id, &error, G_TYPE_STRING, &fdpath, G_TYPE_INVALID)) {
169                 if (error->domain == DBUS_GERROR && error->code == DBUS_GERROR_REMOTE_EXCEPTION) {
170                         /* If we're already connected, it's not an error, unless
171                          * they don't give us the file descriptor path, in which
172                          * case we re-connect.*/
173                         if (!strcmp(BTCOND_ERROR_CONNECTED, dbus_g_error_get_name(error)) || !fdpath) {
174                                 g_printerr("Caught remote method exception %s: %s", dbus_g_error_get_name(error), error->message);
175                                 gps_disconnect(gps);
176
177                                 if (gps->connection_retry==NULL)
178                                         return;
179
180                                 if (gps->connection_retry(gps, error->message)==TRUE) {
181                                         gps_connect_later(gps);
182                                 } else {
183                                         gps_conn_set_state(gps, RCVR_OFF);
184                                 }
185                                 return;
186                         }
187                 } else {
188                         /* Unknown error. */
189                         g_printerr("Error: %s\n", error->message);
190                         gps_disconnect(_gps);
191                         gps_connect_later(_gps);        /* Try again later. */
192                         return;
193                 }
194         }
195         gps_connect_fd(fdpath);
196 }
197 /* else { Looks like the middle of a disconnect.  Do nothing. } */
198 }
199 #endif
200
201 /**
202  * Place a request to connect about 1 second after the function is called.
203  */
204 void 
205 gps_connect_later(Gps *gps)
206 {
207 g_assert(gps);
208 g_assert(gps->io.clater_sid!=0);
209 gps->io.clater_sid=g_timeout_add(1000, (GSourceFunc)gps_connect_now, gps);
210 }
211
212 gboolean
213 gps_connect(Gps *gps)
214 {
215 switch (gps->io.type) {
216         case GPS_IO_FILE:
217                 return TRUE;
218         break;
219         case GPS_IO_RFCOMM:
220         case GPS_IO_TCP:
221         case GPS_IO_GPSD:
222                 return gps_connect_socket(gps);
223         break;
224         case GPS_IO_GYPSY:
225                 return FALSE;
226         break;
227         default:
228         break;
229 }
230 return FALSE;
231 }
232
233 /**
234  * Helper to open a file or device node
235  */
236 static gboolean 
237 gps_connect_file(Gps *gps, gchar *file)
238 {
239 if (-1==(gps->io.fd=open(file, O_RDONLY))) {
240         gps_disconnect(gps);
241         gps_connect_later(gps);
242         /* Add retry cb */
243         return FALSE;
244 }
245 return TRUE;
246 }
247
248 /**
249  * Helper to connect to a socket file descriptor.
250  * 
251  */
252 static gboolean 
253 gps_connect_socket(Gps *gps)
254 {
255 gint r, e;
256 g_assert(gps);
257
258 switch (gps->io.type) {
259         case GPS_IO_RFCOMM:
260 #ifdef WITH_BLUEZ
261                 g_debug("RFCOMM: %d", gps->io.fd);
262                 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_rc, sizeof(gps->io.rcvr_addr_rc));
263 #endif
264         break;
265         case GPS_IO_TCP:
266         case GPS_IO_GPSD:
267                 g_debug("TCP: %d", gps->io.fd);
268                 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_in, sizeof(gps->io.rcvr_addr_in));
269         break;
270         default:
271                 return FALSE;
272 }
273 e=errno;
274 g_debug("GPS: Error %d", e);
275
276 /* The socket is non blocking so handle it */
277 if (r != 0) {
278         switch (e) {
279         case EAGAIN:
280         case EBUSY:
281         case EINPROGRESS:
282         case EALREADY:
283                 g_printerr("*** Connection in progress... %d %d\n", e, r);
284                 perror("INFO: ");
285                 return TRUE;
286         break;
287         case EHOSTUNREACH:
288                 g_printerr("*** Bluetooth/GPS device not found.\n");
289                 gps_disconnect(gps);
290 #if 0
291         set_action_activate("gps_enable", FALSE);
292         popup_error(_window, _("No bluetooth or GPS device found."));
293 #endif
294                 return FALSE;
295         break;
296         default:
297                 /* Connection failed.  Disconnect and try again later. */
298                 g_printerr("### Connect failed, retrying... %d %d\n", e, r);
299                 perror("ERROR: ");
300                 gps_disconnect(gps);
301                 gps_connect_later(gps);
302                 return FALSE;
303         break;
304         }
305 }
306 return TRUE;
307 }
308
309 /**
310  * Disconnect the GPS device.
311  * - Add channel callbacks are removed
312  * - Channel is close and shutdown
313  * - File descriptor is closed
314  * - Anything special for disconnecting is done
315  */
316 void 
317 gps_disconnect(Gps *gps)
318 {
319 g_assert(gps);
320
321 /* Remove watches. */
322 if (gps->io.clater_sid) {
323         g_source_remove(gps->io.clater_sid);
324         gps->io.clater_sid=0;
325 }
326 if (gps->io.error_sid) {
327         g_source_remove(gps->io.error_sid);
328         gps->io.error_sid=0;
329 }
330 if (gps->io.connect_sid) {
331         g_source_remove(gps->io.connect_sid);
332         gps->io.connect_sid=0;
333 }
334 if (gps->io.input_sid) {
335         g_source_remove(gps->io.input_sid);
336         gps->io.input_sid=0;
337 }
338
339 /* Destroy the GIOChannel object. */
340 if (gps->io.channel) {
341         g_io_channel_shutdown(gps->io.channel, FALSE, NULL);
342         g_io_channel_unref(gps->io.channel);
343         gps->io.channel=NULL;
344 }
345
346 /* Close the file descriptor. */
347 if (gps->io.fd!=-1) {
348         close(gps->io.fd);
349         gps->io.fd=-1;
350 }
351
352 #ifdef WITH_GPSBT
353 if (gps->io.type==GPS_IO_GPSD) {
354         gint r;
355         r=gpsbt_stop(&ctx);
356         if (r!=0)
357                 g_warning("Failed to stop gpsd\n");
358 }
359 #endif
360
361 #ifdef WITH_HILDON_DBUS_BT
362 if (gps->io.type==GPS_IO_HILDON_DBUS && gps->io.rfcomm_req_proxy) {
363         GError *error = NULL;
364
365         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);
366         error = NULL;
367         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);
368 }
369 #endif
370
371 #ifdef WITH_BLUEZ_DBUS_BT
372 if (gps->io.type==GPS_IO_BLUEZ_DBUS && gps->io.rfcomm_req_proxy) {
373         GError *error = NULL;
374         
375 }
376 #endif
377
378 }
379
380 static void
381 gps_simulate_start(Gps *gps)
382 {
383 gps->data.lat=60.45;
384 gps->data.lon=22.26;
385 gps->io.conn=RCVR_FIXED;
386 gps->data.fix=FIX_2D;
387 gps_data_integerize(&gps->data);
388 }
389
390 static gboolean
391 gps_simulate_move(Gps *gps)
392 {
393 static gdouble slat=0, slon=0;
394 gdouble plat, plon;
395 gfloat h;
396 g_assert(gps);
397
398 if (g_random_double()<0.5) {
399         slat=g_random_double_range(-0.0003, 0.0003);
400         slon=g_random_double_range(-0.0003, 0.0003);
401 }
402 plat=gps->data.lat;
403 plon=gps->data.lon;
404 gps->data.lat+=slat;
405 gps->data.lon+=slon;
406 BOUND(gps->data.lat, -80.0, 80.0);
407 BOUND(gps->data.lon, -80.0, 80.0);
408
409 g_debug("Sim: %f %f\n", gps->data.lat, gps->data.lon);
410
411 gps->data.speed=1+g_random_double_range(0.1, 10.0);
412 h=calculate_course(plat, plon, gps->data.lat, gps->data.lon);
413 gps->data.heading=(h<0) ? 360+h : h;
414 gps->data.time=time(NULL);
415 gps_data_integerize(&gps->data);
416 if (gps->update_location!=NULL) {
417         gps->update_location(gps, FALSE);
418 }
419 gps->data.lheading=gps->data.heading;
420
421 return gps->io.conn==RCVR_FIXED ? TRUE : FALSE;
422 }
423
424 /**
425  * Reset GPS read buffer 
426  */
427 static void
428 gps_read_buffer_prepare(Gps *gps)
429 {
430 gps->io.curr=gps->io.buffer;
431 *gps->io.curr = '\0';
432 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
433 }
434
435 /**
436  * Connect file descriptor to channel and add watches. 
437  */
438 static void
439 gps_read_channel_connect(Gps *gps)
440 {
441 gps->io.channel=g_io_channel_unix_new(gps->io.fd);
442 g_io_channel_set_encoding(gps->io.channel, NULL, NULL);
443 g_io_channel_set_flags(gps->io.channel, G_IO_FLAG_NONBLOCK, NULL);
444 gps->io.error_sid=g_io_add_watch_full(gps->io.channel, G_PRIORITY_HIGH_IDLE, G_IO_ERR | G_IO_HUP, gps_channel_cb_error, gps, NULL);
445 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);
446 gps->io.clater_sid=0;
447 }
448
449 /**
450  * Connect to the receiver.
451  * This method assumes that fd is -1 and _channel is NULL.  If unsure, call
452  * gps_disconnect() first.
453  * Since this is an idle function, this function returns whether or not it
454  * should be called again, which is always FALSE.
455  *
456  * This function prepares for the connection, the gps_connect does the "connecting" if needed.
457  */
458 gboolean 
459 gps_connect_now(Gps *gps)
460 {
461 GError *error = NULL;
462
463 g_assert(gps);
464 g_debug("GPS: Connecting GPS type %d to %s:%d\n", gps->io.type, gps->io.address, gps->io.port);
465
466 switch (gps->io.type) {
467         case GPS_IO_FILE:
468                 if (!gps->io.address)
469                         return FALSE;
470                 gps_connect_file(gps, gps->io.address);
471                 return FALSE;
472         break;
473         case GPS_IO_TCP:
474         case GPS_IO_GPSD:
475                 if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
476                         gps->io.fd=socket(AF_INET, SOCK_STREAM, 0);
477                         if (gps->io.fd==-1) {
478                                 g_debug("Failed to create socket\n");
479                                 gps_connect_later(gps);
480                                 return FALSE;
481                         }
482                         memcpy(&gps->io.rcvr_addr_in.sin_addr.s_addr, gps->io.address, strlen(gps->io.address));
483                         gps->io.rcvr_addr_in.sin_family = AF_INET;
484                         gps->io.rcvr_addr_in.sin_port = htons(gps->io.port);
485                 } else {
486                         return FALSE;
487                 }
488 #ifdef WITH_GPSBT
489                 if (gps->io.type==GPS_IO_GPSD) {
490                         gint r;
491                         gchar ebuf[128];
492                         r=gpsbt_start(NULL, 0, 0, 0, ebuf, 128, 0, &ctx);
493                         if (r!=0) {
494                                 g_warning("Failed to start gpsd");
495                                 return FALSE;
496                         }
497                 }
498 #endif
499         break;
500 #ifdef WITH_HILDON_DBUS_BT
501         case GPS_IO_HILDON_DBUS:
502                 if (NULL == (gps->io.dbus_conn=dbus_g_bus_get(DBUS_BUS_SYSTEM, &error))) {
503                         g_printerr("Failed to get D-Bus connection.");
504                         return FALSE;
505                 }
506                 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))) {
507                         g_printerr("Failed to open connection to %s.\n", BTCOND_REQ_INTERFACE);
508                         return FALSE;
509                 }
510
511                 if (gps->io.rfcomm_req_proxy) {
512                         gboolean mybool = TRUE;
513                         dbus_g_proxy_begin_call(gps->io.rfcomm_req_proxy,
514                                 BTCOND_RFCOMM_CONNECT_REQ, (DBusGProxyCallNotify)gps_connect_response, gps, NULL,
515                                 G_TYPE_STRING, gps->io.address,
516                                 G_TYPE_STRING, "SPP",
517                                 G_TYPE_BOOLEAN, &mybool,
518                                 G_TYPE_INVALID);
519                 }
520         break;
521 #endif
522 #ifdef WITH_BLUEZ
523         case GPS_IO_RFCOMM:
524                 if (!gps->io.address)
525                         return FALSE;
526
527                 if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
528                         gps->io.fd=socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
529
530                         /* If file descriptor creation failed, try again later.  Note that
531                          * there is no need to call rcvr_disconnect() because the file
532                          * descriptor creation is the first step, so if it fails, there's
533                          * nothing to clean up. */
534                         if (gps->io.fd==-1) {
535                                 g_debug("Failed to create socket\n");
536                                 gps_connect_later(gps);
537                                 return FALSE;
538                         }
539                         gps->io.rcvr_addr_rc.rc_family=AF_BLUETOOTH;
540                         gps->io.rcvr_addr_rc.rc_channel=1;
541                         str2ba(gps->io.address, &gps->io.rcvr_addr_rc.rc_bdaddr);
542                 }
543 #endif
544         break;
545         case GPS_IO_SIMULATION:
546                 /* Set a periodic cb to generate random movement */
547                 gps_simulate_start(gps);
548                 gps->io.input_sid=g_timeout_add(1000, (GSourceFunc)gps_simulate_move, gps);
549                 return FALSE;
550         break;
551         default:
552                 g_printerr("Unknown GPS connection type\n");
553                 return FALSE;
554         break;
555 }
556
557 gps_read_buffer_prepare(gps);
558 gps_read_channel_connect(gps);
559 gps_connect(gps);
560 return FALSE;
561 }
562
563 /**
564  * Connection callback
565  */
566 static gboolean
567 gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data)
568 {
569 gint error, size = sizeof(error);
570 Gps *gps=(Gps *)data;
571
572 g_assert(data);
573
574 gps->io.curr=gps->io.buffer;
575 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
576
577 switch (gps->io.type) {
578         case GPS_IO_TCP:
579         case GPS_IO_RFCOMM:
580         case GPS_IO_GPSD:
581                 if ((getsockopt(gps->io.fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)) {
582                         g_printerr("Error connecting to receiver; retrying...\n");
583                         gps_disconnect(gps);
584                         gps_connect_later(gps);
585                         gps->io.connect_sid=0;
586                         gps->io.errors++;
587                         return FALSE;
588                 } 
589                 /* Set gpsd to NMEA mode */
590                 if (gps->io.type==GPS_IO_GPSD) {
591                         GIOStatus status;
592                         gsize written;
593                         status=g_io_channel_write_chars(gps->io.channel, GPSD_NMEA, -1, &written, NULL);
594                         if (status!=G_IO_STATUS_NORMAL || written!=sizeof(GPSD_NMEA)) {
595                                 g_printerr("Failed to set gpsd to NMEA mode\n");
596                                 gps_disconnect(gps);
597                                 gps_connect_later(gps);
598                                 gps->io.connect_sid=0;
599                                 gps->io.errors++;
600                                 return FALSE;
601                         }
602                 }
603         break;
604         case GPS_IO_FILE:
605
606         break;
607         default:
608                 g_warning("Connection from non-connecting source\n");
609         break;
610 }
611
612 g_printf("Connected to receiver!\n");
613 gps_conn_set_state(gps, RCVR_UP);
614 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);
615 gps->io.errors=0;
616 gps->io.connect_sid=0;
617 return FALSE;
618 }
619
620 static gboolean
621 gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
622 {
623 Gps *gps=(Gps *)data;
624
625 g_assert(data);
626 /* An error has occurred - re-connect(). */
627 gps_disconnect(gps);
628
629 if (gps->connection_error!=NULL)
630         gps->connection_error(gps, "GPS Connection error");
631
632 if (gps->io.conn > RCVR_OFF) {
633         gps_conn_set_state(gps, RCVR_DOWN);
634         gps_connect_later(gps);
635 }
636 return FALSE;
637 }
638
639 static gboolean
640 gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
641 {
642 gsize bytes_read;
643 GIOStatus status;
644 gchar *eol;
645 Gps *gps=(Gps *)data;
646
647 g_debug("%s(%d)\n", __PRETTY_FUNCTION__, condition);
648 g_assert(data);
649
650 status=g_io_channel_read_chars(gps->io.channel, gps->io.curr, gps->io.last-gps->io.curr, &bytes_read, NULL);
651 switch (status) {
652         case G_IO_STATUS_NORMAL:
653         gps->io.curr += bytes_read;
654         *gps->io.curr = '\0';   /* append a \0 so we can read as string */
655         while ((eol = strchr(gps->io.buffer, '\n'))) {
656                 gchar *sptr = gps->io.buffer + 1;       /* Skip the $ */
657                 guint csum = 0;
658                 if (*gps->io.buffer=='$') {
659                         /* This is the beginning of a sentence; okay to parse. */
660                         *eol = '\0';    /* overwrite \n with \0 */
661                         while (*sptr && *sptr != '*')
662                                 csum ^= *sptr++;
663
664                         /* If we're at a \0 (meaning there is no checksum), or if the
665                          * checksum is good, then parse the sentence. */
666                         if (!*sptr || csum == strtol(sptr + 1, NULL, 16)) {
667                                 gchar *data;
668
669                                 if (*sptr)
670                                         *sptr = '\0';   /* take checksum out of the buffer. */
671
672                                 gps->io.nmea=g_strdup(gps->io.buffer);
673                                 g_assert(gps->io.nmea);
674                                 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)gps_nmea_parse, gps, NULL);
675
676                                 if (gps->update_info)
677                                         g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc)gps->update_info, gps, NULL);
678
679                                 if (gps->update_satellite)
680                                         g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc)gps->update_satellite, gps, NULL);
681                         } else {
682                                 /* There was a checksum, and it was bad. */
683                                 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n", __PRETTY_FUNCTION__, gps->io.buffer);
684                         }
685                 }
686
687                 /* If eol is at or after (_gps_read_buf_curr - 1) */
688                 if (eol >= (gps->io.curr - 1)) {
689                         /* Last read was a newline - reset read buffer */
690                         gps->io.curr = gps->io.buffer;
691                         *gps->io.curr = '\0';
692                 } else {
693                         /* Move the next line to the front of the buffer. */
694                         memmove(gps->io.buffer, eol + 1, gps->io.curr - eol);   /* include terminating 0 */
695                         /* Subtract _curr so that it's pointing at the new \0. */
696                         gps->io.curr -= (eol - gps->io.buffer + 1);
697                 }
698         }
699         gps->io.errors=0;
700         gps->io.again=0;
701         break;
702         case G_IO_STATUS_ERROR:
703         case G_IO_STATUS_EOF:
704                 gps_disconnect(gps);
705                 gps_connect_later(gps);
706                 gps->io.errors++;
707                 if (gps->io.errors>10) {
708                         if (gps->connection_error==NULL)
709                                 return FALSE;
710                         else {
711                                 gps->connection_error(gps, "GPS data read error.");                     
712                         }
713                 }
714                 return FALSE;
715         break;
716         case G_IO_STATUS_AGAIN:
717                 gps->io.again++;
718                 if (gps->io.again>20) {
719                         gps_disconnect(gps);
720                         gps_connect_later(gps);
721                         if (gps->connection_error==NULL)
722                                 return FALSE;
723                         else {
724                                 gps->connection_error(gps, "GPS connection deadlock.");
725                         }
726                 }
727                 return TRUE;
728         break;
729         default:
730                 g_assert_not_reached();
731         break;
732 }
733
734 return TRUE;
735 }
736