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>
44 #include "gps-nmea-parse.h"
50 static gpsbt_t ctx = {0};
53 static gboolean gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data);
54 static gboolean gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data);
55 static gboolean gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data);
56 static gboolean gps_connect_socket(Gps *gps);
57 static gboolean gps_connect_file(Gps *gps);
59 #define GPSD_NMEA "r+\r\n"
60 #define GPSD_PORT (2947)
66 gps_new(GpsIOSourceType type)
70 gps=g_slice_new0(Gps);
71 gps->data.fix=FIX_NOFIX;
75 gps->io.conn=RCVR_OFF;
78 gps_data_integerize(&gps->data);
83 gps_set_address(Gps *gps, gchar *address, guint port)
86 g_free(gps->io.address);
88 gps->io.address=g_strdup(address);
93 gps_set_type(Gps *gps, GpsIOSourceType type)
103 g_free(gps->io.address);
104 g_slice_free(Gps, gps);
108 * Convert the float lat/lon/speed/heading data into integer units.
109 * Also calculate offsets.
112 gps_data_integerize(GpsData *gpsdata)
116 tmp=(gpsdata->heading*(1.f/180.f))*G_PI;
117 latlon2unit(gpsdata->lat, gpsdata->lon, gpsdata->unitx, gpsdata->unity);
118 gpsdata->vel_offsetx=(gint)(floor(gpsdata->speed*sin(tmp)+0.5f));
119 gpsdata->vel_offsety=-(gint)(floor(gpsdata->speed*cos(tmp)+0.5f));
123 * Place a request to connect about 1 second after the function is called.
126 gps_connect_later(Gps *gps)
129 g_assert(gps->io.clater_sid!=0);
130 gps->io.clater_sid=g_timeout_add(1000, (GSourceFunc)gps_connect_now, gps);
134 gps_connect(Gps *gps)
136 switch (gps->io.type) {
138 return gps_connect_file(gps);
143 return gps_connect_socket(gps);
155 * Helper to open a file or device node
158 gps_connect_file(Gps *gps)
160 if (-1==(gps->io.fd=open(gps->io.address, O_RDONLY))) {
162 gps_connect_later(gps);
169 * Helper to connect to a socket file descriptor.
173 gps_connect_socket(Gps *gps)
176 switch (gps->io.type) {
179 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_rc, sizeof(gps->io.rcvr_addr_rc));
184 r=connect(gps->io.fd, (struct sockaddr *)&gps->io.rcvr_addr_in, sizeof(gps->io.rcvr_addr_in));
191 /* The socket is non blocking so handle it */
198 g_printf("*** Connection in progress... %d %d\n", e, r);
203 g_printf("*** Bluetooth/GPS device not found.\n");
206 set_action_activate("gps_enable", FALSE);
207 popup_error(_window, _("No bluetooth or GPS device found."));
212 /* Connection failed. Disconnect and try again later. */
213 g_printf("### Connect failed, retrying... %d %d\n", e, r);
216 gps_connect_later(gps);
225 * Disconnect the GPS device.
226 * - Add channel callbacks are removed
227 * - Channel is close and shutdown
228 * - File descriptor is closed
229 * - Anything special for disconnecting is done
232 gps_disconnect(Gps *gps)
236 /* Remove watches. */
237 if (gps->io.clater_sid) {
238 g_source_remove(gps->io.clater_sid);
239 gps->io.clater_sid=0;
241 if (gps->io.error_sid) {
242 g_source_remove(gps->io.error_sid);
245 if (gps->io.connect_sid) {
246 g_source_remove(gps->io.connect_sid);
247 gps->io.connect_sid=0;
249 if (gps->io.input_sid) {
250 g_source_remove(gps->io.input_sid);
254 /* Destroy the GIOChannel object. */
255 if (gps->io.channel) {
256 g_io_channel_shutdown(gps->io.channel, FALSE, NULL);
257 g_io_channel_unref(gps->io.channel);
258 gps->io.channel=NULL;
261 /* Close the file descriptor. */
262 if (gps->io.fd!=-1) {
268 if (gps->io.type==GPS_IO_GPSD) {
272 g_warning("Failed to stop gpsd\n");
276 #ifdef WITH_HILDON_DBUS_BT
277 if (gps->io.type==GPS_IO_HILDON_DBUS && gps->io.rfcomm_req_proxy) {
278 GError *error = NULL;
280 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);
282 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);
286 #ifdef WITH_BLUEZ_DBUS_BT
287 if (gps->io.type==GPS_IO_BLUEZ_DBUS && gps->io.rfcomm_req_proxy) {
288 GError *error = NULL;
297 gps_simulate_start(Gps *gps)
301 gps->io.conn=RCVR_FIXED;
302 gps->data.fix=FIX_2D;
303 gps_data_integerize(&gps->data);
307 gps_simulate_move(Gps *gps)
309 static gdouble slat=0, slon=0;
313 if (g_random_double()<0.5) {
314 slat=g_random_double_range(-0.0003, 0.0003);
315 slon=g_random_double_range(-0.0003, 0.0003);
321 BOUND(gps->data.lat, -80.0, 80.0);
322 BOUND(gps->data.lon, -80.0, 80.0);
324 g_debug("Sim: %f %f\n", gps->data.lat, gps->data.lon);
326 gps->data.speed=1+g_random_double_range(0.1, 10.0);
327 gps->data.heading=calculate_course(plat, plon, gps->data.lat, gps->data.lon);
328 gps->data.time=time(NULL);
329 gps_data_integerize(&gps->data);
330 track_add(&gps->data, FALSE);
331 gps->data.lheading=gps->data.heading;
334 return gps->io.conn==RCVR_FIXED ? TRUE : FALSE;
338 * Reset GPS read buffer
341 gps_read_buffer_prepare(Gps *gps)
343 gps->io.curr=gps->io.buffer;
344 *gps->io.curr = '\0';
345 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
349 * Connect file descriptor to channel and add watches.
352 gps_read_channel_connect(Gps *gps)
354 gps->io.channel=g_io_channel_unix_new(gps->io.fd);
355 g_io_channel_set_encoding(gps->io.channel, NULL, NULL);
356 g_io_channel_set_flags(gps->io.channel, G_IO_FLAG_NONBLOCK, NULL);
357 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, NULL, NULL);
358 gps->io.connect_sid=g_io_add_watch_full(gps->io.channel, G_PRIORITY_HIGH_IDLE, G_IO_OUT, gps_channel_cb_connect, NULL, NULL);
359 gps->io.clater_sid=0;
363 * Connect to the receiver.
364 * This method assumes that fd is -1 and _channel is NULL. If unsure, call
365 * gps_disconnect() first.
366 * Since this is an idle function, this function returns whether or not it
367 * should be called again, which is always FALSE.
369 * This function prepares for the connection, the gps_connect does the "connecting" if needed.
372 gps_connect_now(Gps *gps)
375 g_debug("GPS: Connecting GPS type %d to %s:%d\n", gps->io.type, gps->io.address, gps->io.port);
377 switch (gps->io.type) {
379 if (!gps->io.address)
381 if (*gps->io.address=='/')
382 gps->io.fd=open(gps->io.address, O_RDONLY);
388 if (gps->io.conn==RCVR_DOWN && gps->io.address) {
389 gps->io.fd=socket(AF_INET, SOCK_STREAM, 0);
390 if (gps->io.fd==-1) {
391 g_debug("Failed to create socket\n");
392 gps_connect_later(gps);
395 memcpy(&gps->io.rcvr_addr_in.sin_addr.s_addr, gps->io.address, strlen(gps->io.address));
396 gps->io.rcvr_addr_in.sin_family = AF_INET;
397 gps->io.rcvr_addr_in.sin_port = htons(gps->io.port);
402 if (gps->io.type==GPS_IO_GPSD) {
405 r=gpsbt_start(NULL, 0, 0, 0, ebuf, 128, 0, &ctx);
407 g_warning("Failed to start gpsd");
413 case GPS_IO_HILDON_DBUS:
414 #ifdef WITH_HILDON_DBUS_BT
415 if (gps->io.rfcomm_req_proxy) {
416 gboolean mybool = TRUE;
417 dbus_g_proxy_begin_call(_rfcomm_req_proxy,
418 BTCOND_RFCOMM_CONNECT_REQ, (DBusGProxyCallNotify)gps_connect_response, NULL, NULL,
419 G_TYPE_STRING, gps->io.address,
420 G_TYPE_STRING, "SPP",
421 G_TYPE_BOOLEAN, &mybool,
430 if (!gps->io.address)
433 if (gps->io.conn==RCVR_DOWN && gps->io.address) {
434 gps->io.fd=socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
436 /* If file descriptor creation failed, try again later. Note that
437 * there is no need to call rcvr_disconnect() because the file
438 * descriptor creation is the first step, so if it fails, there's
439 * nothing to clean up. */
440 if (gps->io.fd==-1) {
441 g_debug("Failed to create socket\n");
442 gps_connect_later(gps);
445 gps->io.rcvr_addr_rc.rc_family=AF_BLUETOOTH;
446 gps->io.rcvr_addr_rc.rc_channel=1;
447 str2ba(gps->io.address, &gps->io.rcvr_addr_rc.rc_bdaddr);
452 case GPS_IO_SIMULATION:
453 /* Set a periodic cb to generate random movement */
454 gps_simulate_start(gps);
455 gps->io.input_sid=g_timeout_add(1000, (GSourceFunc)gps_simulate_move, gps);
463 gps_read_buffer_prepare(gps);
464 gps_read_channel_connect(gps);
470 * Connection callback
473 gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data)
475 gint error, size = sizeof(error);
476 Gps *gps=(Gps *)data;
480 gps->io.curr=gps->io.buffer;
481 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
483 switch (gps->io.type) {
487 if ((getsockopt(gps->io.fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)) {
488 g_printerr("Error connecting to receiver; retrying...\n");
490 gps_connect_later(gps);
491 gps->io.connect_sid=0;
495 /* Set gpsd to NMEA mode */
496 if (gps->io.type==GPS_IO_GPSD) {
499 status=g_io_channel_write_chars(gps->io.channel, GPSD_NMEA, -1, &written, NULL);
500 if (status!=G_IO_STATUS_NORMAL || written!=sizeof(GPSD_NMEA)) {
501 g_printerr("Failed to set gpsd to NMEA mode\n");
503 gps_connect_later(gps);
504 gps->io.connect_sid=0;
514 g_warning("Connection from non-connecting source\n");
518 g_printf("Connected to receiver!\n");
519 gps_conn_set_state(gps, RCVR_UP);
520 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);
522 gps->io.connect_sid=0;
527 gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
529 Gps *gps=(Gps *)data;
532 /* An error has occurred - re-connect(). */
534 track_add(NULL, FALSE);
535 /* _speed_excess = FALSE; */
537 if (gps->io.conn > RCVR_OFF) {
538 gps_conn_set_state(gps, RCVR_DOWN);
539 gps_connect_later(gps);
545 gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
550 Gps *gps=(Gps *)data;
552 g_debug("%s(%d)\n", __PRETTY_FUNCTION__, condition);
555 status=g_io_channel_read_chars(gps->io.channel, gps->io.curr, gps->io.last-gps->io.curr, &bytes_read, NULL);
557 case G_IO_STATUS_NORMAL:
558 gps->io.curr += bytes_read;
559 *gps->io.curr = '\0'; /* append a \0 so we can read as string */
560 while ((eol = strchr(gps->io.buffer, '\n'))) {
561 gchar *sptr = gps->io.buffer + 1; /* Skip the $ */
563 if (*gps->io.buffer=='$') {
564 /* This is the beginning of a sentence; okay to parse. */
565 *eol = '\0'; /* overwrite \n with \0 */
566 while (*sptr && *sptr != '*')
569 /* If we're at a \0 (meaning there is no checksum), or if the
570 * checksum is good, then parse the sentence. */
571 if (!*sptr || csum == strtol(sptr + 1, NULL, 16)) {
575 *sptr = '\0'; /* take checksum out of the buffer. */
577 data=g_strdup(gps->io.buffer);
578 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)gps_nmea_parse, data, NULL);
580 /* There was a checksum, and it was bad. */
581 g_printerr("%s: Bad checksum in NMEA sentence:\n%s\n", __PRETTY_FUNCTION__, gps->io.buffer);
585 /* If eol is at or after (_gps_read_buf_curr - 1) */
586 if (eol >= (gps->io.curr - 1)) {
587 /* Last read was a newline - reset read buffer */
588 gps->io.curr = gps->io.buffer;
589 *gps->io.curr = '\0';
591 /* Move the next line to the front of the buffer. */
592 memmove(gps->io.buffer, eol + 1, gps->io.curr - eol); /* include terminating 0 */
593 /* Subtract _curr so that it's pointing at the new \0. */
594 gps->io.curr -= (eol - gps->io.buffer + 1);
598 case G_IO_STATUS_ERROR:
599 case G_IO_STATUS_EOF:
601 gps_connect_later(gps);
605 case G_IO_STATUS_AGAIN:
609 g_assert_not_reached();