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