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>
47 #include "gps-nmea-parse.h"
49 #ifdef WITH_HILDON_DBUS_BT
55 static gpsbt_t ctx = {0};
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);
64 #define GPSD_NMEA "r+\r\n"
65 #define GPSD_PORT (2947)
67 const GpsTypes gps_types[] = {
69 { GPS_IO_RFCOMM, "Bluetooth connection (old)", TRUE, TRUE},
71 #ifdef WITH_HILDON_DBUS_BT
72 { GPS_IO_HILDON_DBUS, "Bluetooth connection (btconn)", TRUE, TRUE },
74 #ifdef WITH_BLUEZ_DBUS_BT
75 { GPS_IO_BLUEZ_DBUS, "Bluetooth connection (new)", TRUE, TRUE},
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},
81 { GPS_IO_GYPSY, "Gypsy", FALSE, TRUE},
83 { GPS_IO_SIMULATION, "Simulation (random)", FALSE, FALSE},
88 gps_get_supported_types(void)
97 gps_new(GpsIOSourceType type)
101 gps=g_slice_new0(Gps);
102 gps->data.fix=FIX_NOFIX;
104 gps->io.address=NULL;
106 gps->io.conn=RCVR_OFF;
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);
121 gps_set_address(Gps *gps, gchar *address, guint port)
124 g_free(gps->io.address);
126 gps->io.address=g_strdup(address);
131 gps_set_type(Gps *gps, GpsIOSourceType type)
141 g_free(gps->io.address);
142 g_slice_free(Gps, gps);
146 * Convert the float lat/lon/speed/heading data into integer units.
147 * Also calculate offsets.
150 gps_data_integerize(GpsData *gpsdata)
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));
160 #ifdef WITH_HILDON_DBUS_BT
162 gps_connect_response(DBusGProxy *proxy, DBusGProxyCall *call_id, Gps *gps)
164 GError *error = NULL;
165 gchar *fdpath = NULL;
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);
177 if (gps->connection_retry==NULL)
180 if (gps->connection_retry(gps, error->message)==TRUE) {
181 gps_connect_later(gps);
183 gps_conn_set_state(gps, RCVR_OFF);
189 g_printerr("Error: %s\n", error->message);
190 gps_disconnect(_gps);
191 gps_connect_later(_gps); /* Try again later. */
195 gps_connect_fd(fdpath);
197 /* else { Looks like the middle of a disconnect. Do nothing. } */
202 * Place a request to connect about 1 second after the function is called.
205 gps_connect_later(Gps *gps)
208 g_assert(gps->io.clater_sid!=0);
209 gps->io.clater_sid=g_timeout_add(1000, (GSourceFunc)gps_connect_now, gps);
213 gps_connect(Gps *gps)
215 switch (gps->io.type) {
222 return gps_connect_socket(gps);
234 * Helper to open a file or device node
237 gps_connect_file(Gps *gps, gchar *file)
239 if (-1==(gps->io.fd=open(file, O_RDONLY))) {
241 gps_connect_later(gps);
249 * Helper to connect to a socket file descriptor.
253 gps_connect_socket(Gps *gps)
258 switch (gps->io.type) {
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));
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));
274 g_debug("GPS: Error %d", e);
276 /* The socket is non blocking so handle it */
283 g_printerr("*** Connection in progress... %d %d\n", e, r);
288 g_printerr("*** Bluetooth/GPS device not found.\n");
291 set_action_activate("gps_enable", FALSE);
292 popup_error(_window, _("No bluetooth or GPS device found."));
297 /* Connection failed. Disconnect and try again later. */
298 g_printerr("### Connect failed, retrying... %d %d\n", e, r);
301 gps_connect_later(gps);
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
317 gps_disconnect(Gps *gps)
321 /* Remove watches. */
322 if (gps->io.clater_sid) {
323 g_source_remove(gps->io.clater_sid);
324 gps->io.clater_sid=0;
326 if (gps->io.error_sid) {
327 g_source_remove(gps->io.error_sid);
330 if (gps->io.connect_sid) {
331 g_source_remove(gps->io.connect_sid);
332 gps->io.connect_sid=0;
334 if (gps->io.input_sid) {
335 g_source_remove(gps->io.input_sid);
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;
346 /* Close the file descriptor. */
347 if (gps->io.fd!=-1) {
353 if (gps->io.type==GPS_IO_GPSD) {
357 g_warning("Failed to stop gpsd\n");
361 #ifdef WITH_HILDON_DBUS_BT
362 if (gps->io.type==GPS_IO_HILDON_DBUS && gps->io.rfcomm_req_proxy) {
363 GError *error = NULL;
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);
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);
371 #ifdef WITH_BLUEZ_DBUS_BT
372 if (gps->io.type==GPS_IO_BLUEZ_DBUS && gps->io.rfcomm_req_proxy) {
373 GError *error = NULL;
381 gps_simulate_start(Gps *gps)
385 gps->io.conn=RCVR_FIXED;
386 gps->data.fix=FIX_2D;
387 gps_data_integerize(&gps->data);
391 gps_simulate_move(Gps *gps)
393 static gdouble slat=0, slon=0;
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);
406 BOUND(gps->data.lat, -80.0, 80.0);
407 BOUND(gps->data.lon, -80.0, 80.0);
409 g_debug("Sim: %f %f\n", gps->data.lat, gps->data.lon);
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);
419 gps->data.lheading=gps->data.heading;
421 return gps->io.conn==RCVR_FIXED ? TRUE : FALSE;
425 * Reset GPS read buffer
428 gps_read_buffer_prepare(Gps *gps)
430 gps->io.curr=gps->io.buffer;
431 *gps->io.curr = '\0';
432 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
436 * Connect file descriptor to channel and add watches.
439 gps_read_channel_connect(Gps *gps)
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;
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.
456 * This function prepares for the connection, the gps_connect does the "connecting" if needed.
459 gps_connect_now(Gps *gps)
461 GError *error = NULL;
464 g_debug("GPS: Connecting GPS type %d to %s:%d\n", gps->io.type, gps->io.address, gps->io.port);
466 switch (gps->io.type) {
468 if (!gps->io.address)
470 gps_connect_file(gps, gps->io.address);
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);
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);
489 if (gps->io.type==GPS_IO_GPSD) {
492 r=gpsbt_start(NULL, 0, 0, 0, ebuf, 128, 0, &ctx);
494 g_warning("Failed to start gpsd");
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.");
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);
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,
524 if (!gps->io.address)
527 if (gps->io.conn<=RCVR_DOWN && gps->io.address) {
528 gps->io.fd=socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
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);
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);
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);
552 g_printerr("Unknown GPS connection type\n");
557 gps_read_buffer_prepare(gps);
558 gps_read_channel_connect(gps);
564 * Connection callback
567 gps_channel_cb_connect(GIOChannel * src, GIOCondition condition, gpointer data)
569 gint error, size = sizeof(error);
570 Gps *gps=(Gps *)data;
574 gps->io.curr=gps->io.buffer;
575 gps->io.last=gps->io.buffer+GPS_READ_BUF_SIZE-1;
577 switch (gps->io.type) {
581 if ((getsockopt(gps->io.fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)) {
582 g_printerr("Error connecting to receiver; retrying...\n");
584 gps_connect_later(gps);
585 gps->io.connect_sid=0;
589 /* Set gpsd to NMEA mode */
590 if (gps->io.type==GPS_IO_GPSD) {
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");
597 gps_connect_later(gps);
598 gps->io.connect_sid=0;
608 g_warning("Connection from non-connecting source\n");
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);
616 gps->io.connect_sid=0;
621 gps_channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
623 Gps *gps=(Gps *)data;
626 /* An error has occurred - re-connect(). */
629 if (gps->connection_error!=NULL)
630 gps->connection_error(gps, "GPS Connection error");
632 if (gps->io.conn > RCVR_OFF) {
633 gps_conn_set_state(gps, RCVR_DOWN);
634 gps_connect_later(gps);
640 gps_channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
645 Gps *gps=(Gps *)data;
647 g_debug("%s(%d)\n", __PRETTY_FUNCTION__, condition);
650 status=g_io_channel_read_chars(gps->io.channel, gps->io.curr, gps->io.last-gps->io.curr, &bytes_read, NULL);
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 $ */
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 != '*')
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)) {
670 *sptr = '\0'; /* take checksum out of the buffer. */
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);
676 if (gps->update_info)
677 g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc)gps->update_info, gps, NULL);
679 if (gps->update_satellite)
680 g_idle_add_full(G_PRIORITY_DEFAULT, (GSourceFunc)gps->update_satellite, gps, NULL);
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);
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';
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);
702 case G_IO_STATUS_ERROR:
703 case G_IO_STATUS_EOF:
705 gps_connect_later(gps);
707 if (gps->io.errors>10) {
708 if (gps->connection_error==NULL)
711 gps->connection_error(gps, "GPS data read error.");
716 case G_IO_STATUS_AGAIN:
718 if (gps->io.again>20) {
720 gps_connect_later(gps);
721 if (gps->connection_error==NULL)
724 gps->connection_error(gps, "GPS connection deadlock.");
730 g_assert_not_reached();