From 19465fdbeac07e6c7f4d5dc6613d783b72d5ddbf Mon Sep 17 00:00:00 2001 From: Adam Boggs Date: Wed, 11 Jan 2012 00:49:32 -0700 Subject: [PATCH] Initial checkin of prototype gps plugin support. --- configure.ac | 6 + src/plugins/Makefile.am | 10 + src/plugins/gps-plugin.c | 962 +++++++++++++++++++++++++++++++++++++++ src/plugins/gps-plugin.h | 101 ++++ 4 files changed, 1079 insertions(+) create mode 100644 src/plugins/gps-plugin.c create mode 100644 src/plugins/gps-plugin.h diff --git a/configure.ac b/configure.ac index dab9baa..b3c0d4a 100644 --- a/configure.ac +++ b/configure.ac @@ -16,6 +16,12 @@ PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES(GLIB, glib-2.0) PKG_CHECK_MODULES(GRITS, grits >= 0.6) +# Check for gpsd support +AC_CHECK_LIB(gps, gps_open, GPS_LIBS="-lgps") +AM_CONDITIONAL(HAVE_GPSD, test "$GPS_LIBS" != "") +AC_DEFINE([HAVE_GPSD], [1], [GPSD support]) +AC_SUBST(GPS_LIBS) + # Define odd RSL install location AC_CHECK_LIB(rsl, RSL_wsr88d_to_radar, RSL_LIBS="-lrsl") AM_CONDITIONAL(HAVE_RSL, test "$RSL_LIBS" != "") diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 7cc1b81..691f7fb 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -21,6 +21,16 @@ alert_la_CPPFLAGS = \ -DPKGDATADIR="\"$(DOTS)$(pkgdatadir)\"" alert_la_LIBADD = $(GRITS_LIBS) +if HAVE_GPSD +plugins_LTLIBRARIES += gps.la +gps_la_SOURCES = \ + gps-plugin.c gps-plugin.h +gps_la_CPPFLAGS = \ + -DPKGDATADIR="\"$(dots)$(pkgdatadir)\"" \ + -I$(top_srcdir)/src +gps_la_LIBADD = $(GPS_LIBS) $(GRITS_LIBS) +endif + if HAVE_RSL plugins_LTLIBRARIES += radar.la radar_la_SOURCES = \ diff --git a/src/plugins/gps-plugin.c b/src/plugins/gps-plugin.c new file mode 100644 index 0000000..67d3995 --- /dev/null +++ b/src/plugins/gps-plugin.c @@ -0,0 +1,962 @@ +/* + * Copyright (C) 2012 Adam Boggs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gps-plugin.h" +#include "level2.h" +#include "../aweather-location.h" + +/* interval to update map with new gps data in seconds. */ +#define GPS_UPDATE_INTERVAL (2) + +/* interval to update log file in seconds (default value for slider) */ +#define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30) +#define GPS_LOG_EXT "csv" + +/* interval to update SpotterNetwork in seconds. Should not be less + * than 3 minutes per SpotterNetwork policy. + */ +#define GPS_SN_UPDATE_INTERVAL (5 * 60) +#define GPS_SN_URI "http://www.spotternetwork.org/update.php" + +/* For updating the status bar conveniently */ +#define GPS_STATUSBAR_CONTEXT "GPS" +#if 0 +#define GPS_STATUS(gps_state, format, args...) \ + do { \ + char *buf = g_strdup_printf(format, ##args); \ + gtk_statusbar_push(GTK_STATUSBAR(gps_state->status_bar), \ + gtk_statusbar_get_context_id( \ + GTK_STATUSBAR(gps_state->status_bar), \ + GPS_STATUSBAR_CONTEXT), \ + buf); \ + } while (0) +#endif + +#define GPS_STATUS(gps_state, format, args...) \ + do { \ + char *buf = g_strdup_printf(format, ##args); \ + g_warning("STATUS: %s", buf); \ + } while (0) + + +static gboolean gps_data_is_valid(struct gps_data_t *gps_data); +static char *gps_get_time_string(time_t gps_time); +static char *gps_get_date_string(double gps_time); +static void process_gps( gpointer, gint, GdkInputCondition); + +/* SpotterNetwork support */ +static void gps_init_spotter_network_frame(GritsPluginGPS *gps_state, + GtkWidget *gbox); +static gboolean gps_update_sn(gpointer data); +static void gps_sn_update_complete(SoupSession *, SoupMessage *, gpointer); +static gboolean on_gps_sn_clicked_event (GtkWidget *widget, gpointer user_data); +static gboolean on_gps_sn_active_clicked_event (GtkWidget *widget, gpointer user_data); + +static void gps_init_range_rings(GritsPluginGPS *gps_state, + GtkWidget *gbox); +static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data); + +static void gps_init_status_info(GritsPluginGPS *gps_state, + GtkWidget *gbox); +static void gps_init_control_frame(GritsPluginGPS *gps_state, + GtkWidget *gbox); +static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data); + +/* GPS logging support */ +static void gps_init_track_log_frame(GritsPluginGPS *gps_state, + GtkWidget *gbox); +static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data); +static gboolean on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data); +static gboolean gps_write_log(gpointer data); + +static char *gps_get_status(struct gps_data_t *); +static char *gps_get_latitude(struct gps_data_t *); +static char *gps_get_longitude(struct gps_data_t *); +static char *gps_get_elevation(struct gps_data_t *); +static char *gps_get_heading(struct gps_data_t *); +static char *gps_get_speed(struct gps_data_t *); + +/* Describes a line in the gps table */ +struct gps_status_info { + char *label; + char *initial_val; + char *(*get_data)(struct gps_data_t *); + unsigned int font_size; + GtkWidget *label_widget; + GtkWidget *value_widget; +}; + +struct gps_status_info gps_table[] = { + {"Status:", "No Data", gps_get_status, 14, NULL, NULL}, +// {"Online:", "No Data", gps_get_online, 14, NULL, NULL}, + {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL}, + {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL}, + {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL}, + {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL}, + {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL}, +}; + +static void _gtk_bin_set_child(GtkBin *bin, GtkWidget *new) +{ + GtkWidget *old = gtk_bin_get_child(bin); + if (old) + gtk_widget_destroy(old); + gtk_container_add(GTK_CONTAINER(bin), new); + gtk_widget_show_all(new); +} + +static +gboolean gps_data_is_valid(struct gps_data_t *gps_data) +{ + if (gps_data != NULL && gps_data->online != -1.0 && + gps_data->fix.mode >= MODE_2D && + gps_data->status > STATUS_NO_FIX) { + return TRUE; + } + + return FALSE; +} + +static char * +gps_get_date_string(double gps_time) +{ + static char buf[256]; + time_t int_time = (time_t)gps_time; + struct tm tm_time; + + gmtime_r(&int_time, &tm_time); + + snprintf(buf, sizeof(buf), "%04d-%02d-%02d", + tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday); + + + return buf; +} + +static char * +gps_get_time_string(time_t gps_time) +{ + static char buf[256]; + time_t int_time = (time_t)gps_time; + struct tm tm_time; + + gmtime_r(&int_time, &tm_time); + + snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ", + tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); + + return buf; +} + + +static void +update_gps_status(GritsPluginGPS *gps_state) +{ + struct gps_data_t *gps_data = gps_state->gps_data; + + /* gps table update */ + int i; + gchar *str; + for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) { + gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget), + (str = gps_table[i].get_data(gps_data))); + g_free(str); + } +} + +static void +gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox) +{ + /* Control checkboxes */ + GtkWidget *gps_control_frame = gtk_frame_new ("GPS Control"); + GtkWidget *cbox = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (gps_control_frame), cbox); + gtk_box_pack_start (GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0); + + gps_state->ui.gps_follow_checkbox = gtk_check_button_new_with_label("Follow GPS"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_follow_checkbox), "clicked", + G_CALLBACK (on_gps_follow_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_follow_checkbox, + FALSE, FALSE, 0); + gps_state->ui.gps_track_checkbox = gtk_check_button_new_with_label("Show Track"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_track_checkbox), "clicked", + G_CALLBACK (on_gps_track_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_track_checkbox, + FALSE, FALSE, 0); +} + +static gboolean +on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data) +{ + g_debug("on_gps_track_clicked_event called!"); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { + /* XXX start logging trip history */ + GPS_STATUS(gps_state, "Enabled GPS track."); + } else { + /* XXX stop logging trip history */ + GPS_STATUS(gps_state, "Disabled GPS track."); + } + + return FALSE; +} + +static gboolean +on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + + assert(gps_state); + + g_debug("gps interval changed, value = %f", + gtk_range_get_value(GTK_RANGE(widget))); + + if (gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(gps_state->ui.gps_log_checkbox))) { + assert(gps_state->ui.gps_log_timeout_id != 0); + + /* disable old timeout */ + g_source_remove(gps_state->ui.gps_log_timeout_id); + gps_state->ui.gps_log_timeout_id = 0; + + /* Schedule new log file write */ + gps_state->ui.gps_log_timeout_id = g_timeout_add( + gtk_range_get_value(GTK_RANGE(widget))*1000, + gps_write_log, gps_state); + gps_write_log(gps_state); + } + + return FALSE; +} + +static gboolean +gps_write_log(gpointer data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)data; + struct gps_data_t *gps_data = gps_state->gps_data; + char buf[256]; + char filename[256]; + int fd; + gboolean new_file = FALSE; + + if (gps_data == NULL) { + g_warning("Skipped write to GPS log file: " + "can not get GPS coordinates."); + GPS_STATUS(gps_state, "Skipped write to GPS log file: " + "can not get GPS coordinates."); + return TRUE; + } + + /* get filename from text entry box. If empty, generate a name from + * the date and time and set it. + */ + if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry))) + == 0) { + snprintf(filename, sizeof(filename), + "%sT%s.%s", + gps_get_date_string(gps_state->gps_data->fix.time), + gps_get_time_string(gps_state->gps_data->fix.time), + GPS_LOG_EXT); + gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry), + filename); + } + + strncpy(filename, + gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)), + sizeof (filename)); + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { + new_file = TRUE; + } + + if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) { + g_warning("Error opening log file %s: %s", + filename, strerror(errno)); + return FALSE; + } + + if (new_file) { + /* write header and reset record counter */ + snprintf(buf, sizeof(buf), + "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n"); + if (write(fd, buf, strlen(buf)) == -1) { + g_warning("Error writing header to log file %s: %s", + filename, strerror(errno)); + } + gps_state->ui.gps_log_number = 1; + } + + /* Write log entry. Make sure this matches the header */ + /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */ + /* RTR values: T=time, B=button push, S=speed, D=distance */ + snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n", + gps_state->ui.gps_log_number++, + gps_get_date_string(gps_state->gps_data->fix.time), + gps_get_time_string(gps_state->gps_data->fix.time), +// gps_data->fix.time, + gps_data->fix.latitude, + gps_data->fix.longitude, + gps_data->fix.altitude * METERS_TO_FEET, + gps_data->fix.track, + gps_data->fix.speed * METERS_TO_FEET, + 'T'); /* position due to timer expired */ + + if (write(fd, buf, strlen(buf)) == -1) { + g_warning("Could not write log number %d to log file %s: %s", + gps_state->ui.gps_log_number-1, filename, strerror(errno)); + } + close(fd); + + GPS_STATUS(gps_state, "Updated GPS log file %s.", filename); + + /* reschedule */ + return TRUE; +} + +static gboolean +on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + + g_debug("on_gps_follow_clicked_event called!, button status %d", + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + gps_state->follow_gps = TRUE; + else + gps_state->follow_gps = FALSE; + + return FALSE; +} + + +static void +gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox) +{ + /* Track log box with enable checkbox and filename entry */ + GtkWidget *gps_log_frame = gtk_frame_new ("Track Log"); + GtkWidget *lbox = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox); + gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame, + FALSE, FALSE, 0); + + gps_state->ui.gps_log_checkbox = gtk_check_button_new_with_label("Log Position to File"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_log_checkbox), "clicked", + G_CALLBACK (on_gps_log_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(lbox), gps_state->ui.gps_log_checkbox, + FALSE, FALSE, 0); + + /* Set up filename entry box */ + GtkWidget *fbox = gtk_hbox_new (FALSE, 2); + GtkWidget *filename_label = gtk_label_new ("Filename:"); + gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0); + gps_state->ui.gps_log_filename_entry = gtk_entry_new(); + gtk_box_pack_start (GTK_BOX(fbox), gps_state->ui.gps_log_filename_entry, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0); + + /* set up gps log interval slider */ + GtkWidget *ubox = gtk_hbox_new (FALSE, 4); + GtkWidget *interval_label = gtk_label_new ("Update Interval:"); + gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0); + gps_state->ui.gps_log_interval_slider = + gtk_hscale_new_with_range(1.0, 600.0, 30.0); + gtk_range_set_value (GTK_RANGE(gps_state->ui.gps_log_interval_slider), + GPS_LOG_DEFAULT_UPDATE_INTERVAL); + g_signal_connect (G_OBJECT (gps_state->ui.gps_log_interval_slider), + "value-changed", + G_CALLBACK(on_gps_log_interval_changed_event), + (gpointer)gps_state); + gtk_range_set_increments (GTK_RANGE(gps_state->ui.gps_log_interval_slider), + 10.0 /* step */, 30.0 /* page up/down */); + gtk_range_set_update_policy (GTK_RANGE(gps_state->ui.gps_log_interval_slider), + GTK_UPDATE_DELAYED); + gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider, + TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0); +} + +static gboolean +on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + + g_debug("on_gps_log_clicked_event called!"); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { + gps_write_log(gps_state); + + /* Schedule log file write */ + gps_state->ui.gps_log_timeout_id = g_timeout_add( + gtk_range_get_value( + GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000, + gps_write_log, gps_state); + } else { + /* button unchecked */ + g_source_remove(gps_state->ui.gps_log_timeout_id); + gps_state->ui.gps_log_timeout_id = 0; + g_debug("Closed log file."); + } + + return FALSE; +} + +static void +gps_init_spotter_network_frame(GritsPluginGPS *gps_state, GtkWidget *gbox) +{ + /* Spotter network control box with enable checkbox, active + * checkbox, and sn ID entry + */ + GtkWidget *gps_sn_frame = gtk_frame_new ("SpotterNetwork Control"); + GtkWidget *sbox = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (gps_sn_frame), sbox); + + /* create spotter network update checkbox */ + gps_state->ui.gps_sn_checkbox = gtk_check_button_new_with_label("Update SpotterNetwork"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_sn_checkbox), "clicked", + G_CALLBACK (on_gps_sn_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(sbox), gps_state->ui.gps_sn_checkbox, + FALSE, FALSE, 0); + + /* Create spotter network active checkbox */ + gps_state->ui.gps_sn_active_checkbox = gtk_check_button_new_with_label("Active"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_sn_active_checkbox), + "clicked", + G_CALLBACK (on_gps_sn_active_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(sbox), + gps_state->ui.gps_sn_active_checkbox, + FALSE, FALSE, 0); + + /* Create spotter network ID entry box and label */ + GtkWidget *ebox = gtk_hbox_new (FALSE, 2); + GtkWidget *sn_label = gtk_label_new ("SpotterID:"); + gtk_box_pack_start (GTK_BOX(ebox), sn_label, FALSE, FALSE, 0); + gps_state->ui.gps_sn_entry = gtk_entry_new(); + gtk_box_pack_start (GTK_BOX(ebox), gps_state->ui.gps_sn_entry, + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(sbox), ebox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX(gbox), gps_sn_frame, FALSE, FALSE, 0); +} + +static gboolean +on_gps_sn_clicked_event (GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + g_debug("on_gps_sn_clicked_event called"); + + /* When pressed, log a position report to spotternetwork and + * schedule another one for the future. + */ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { + /* Change naumber of concurrent connections option? */ + assert(gps_state->ui.soup_session == NULL); + gps_state->ui.soup_session = soup_session_async_new_with_options( + SOUP_SESSION_USER_AGENT, + "Mozilla/5.0 (Windows; U; Windows NT 5.1; " + "en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11", + NULL); + + /* XXX don't log right away to give the user a chance to cancel */ + gps_update_sn(gps_state); + gps_state->ui.gps_sn_timeout_id = g_timeout_add( + GPS_SN_UPDATE_INTERVAL*1000, + gps_update_sn, gps_state); + } else { + /* stop any pending timer events */ + g_source_remove(gps_state->ui.gps_sn_timeout_id); + gps_state->ui.gps_sn_timeout_id = 0; + soup_session_abort(gps_state->ui.soup_session); + g_object_unref(gps_state->ui.soup_session); + gps_state->ui.soup_session = NULL; + } + + return FALSE; +} + +static gboolean +on_gps_sn_active_clicked_event (GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { + gps_state->ui.gps_sn_active = TRUE; + } else { + gps_state->ui.gps_sn_active = FALSE; + } + + return FALSE; +} + +/* Send position report to spotternetwork */ +static gboolean +gps_update_sn(gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + struct gps_data_t *gps_data = gps_state->gps_data; + SoupMessage *msg; + char uri[256]; + + /* Make sure gps data is valid */ + if (!gps_data_is_valid(gps_data)) { + g_warning("Skipping SpotterNetwork update, GPS data is invalid!"); + GPS_STATUS(gps_state, "Skipped SpotterNetwork update, " + "GPS data is invalid!"); + return TRUE; + } + + /* Make sure SpotterNetwork ID is valid */ + if (gtk_entry_get_text_length(GTK_ENTRY(gps_state->ui.gps_sn_entry)) == 0) { + g_warning("Skipping SpotterNetwork update, ID \"%s\" is invalid!", + gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry))); + GPS_STATUS(gps_state, "Skipping SpotterNetwork update, " + "ID \"%s\" is invalid!", + gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry))); + return TRUE; + } + + snprintf(uri, sizeof(uri), + "%s?lat=%lf&lon=%lf&elev=%lf&dir=%lf&mph=%lf&gps=1&active=%d&ver=1&id=%s", + GPS_SN_URI, + gps_data->fix.latitude, + gps_data->fix.longitude, + gps_data->fix.altitude * METERS_TO_FEET, + gps_data->fix.track, + gps_data->fix.speed, + gps_state->gps_sn_active == TRUE ? 1 : 0, + gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry))); + + g_debug("gps_update_sn called uri: %s", uri); + + msg = soup_message_new (SOUP_METHOD_GET, uri); + if (msg) { + soup_session_queue_message (gps_state->ui.soup_session, msg, + gps_sn_update_complete, gps_state); + } else { + g_warning("unable to create spotternetwork message"); + } + + /* reschedule next update */ + return TRUE; +} + +static void +gps_sn_update_complete (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + g_debug("gps_sn_update_complete: %s", + msg->response_body->data); + GPS_STATUS(gps_state, "Updated SpotterNetwork."); + } else { + GPS_STATUS(gps_state, "Could not update SpotterNetwork: %s", + msg->reason_phrase); + g_warning("Could not update SpotterNetwork: %s", + msg->reason_phrase); + } +} + + + +static void +gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox) +{ + gps_state->ui.gps_status_frame = gtk_frame_new ("GPS Data"); + gps_state->ui.gps_status_table = gtk_table_new (5, 2, TRUE); + gtk_container_add (GTK_CONTAINER (gps_state->ui.gps_status_frame), + gps_state->ui.gps_status_table); + + /* gps data table setup */ + int i; + for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) { + gps_table[i].label_widget = gtk_label_new (gps_table[i].label); + gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget), + GTK_JUSTIFY_LEFT); + gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table), + gps_table[i].label_widget, 0, 1, i, i+1, 0, 0, 0, 0); + gps_table[i].value_widget = gtk_label_new (gps_table[i].initial_val); + gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table), + gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0); + + PangoFontDescription *font_desc = pango_font_description_new (); + pango_font_description_set_size (font_desc, + gps_table[i].font_size*PANGO_SCALE); + gtk_widget_modify_font (gps_table[i].label_widget, font_desc); + gtk_widget_modify_font (gps_table[i].value_widget, font_desc); + pango_font_description_free (font_desc); + } + gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame, + FALSE, FALSE, 0); + + /* Start UI refresh task, which will reschedule itself periodically. */ + gps_redraw_all(gps_state); + gps_state->gps_update_timeout_id = g_timeout_add( + GPS_UPDATE_INTERVAL*1000, + gps_redraw_all, gps_state); + +} + + +static void +gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox) +{ + GtkWidget *gps_range_ring_frame = gtk_frame_new ("Range Rings"); + GtkWidget *cbox = gtk_vbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (gps_range_ring_frame), cbox); + gtk_box_pack_start (GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0); + + gps_state->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings"); + g_signal_connect (G_OBJECT (gps_state->ui.gps_rangering_checkbox), "clicked", + G_CALLBACK (on_gps_rangering_clicked_event), + (gpointer)gps_state); + gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_rangering_checkbox, + FALSE, FALSE, 0); +} + +static gboolean +on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { + gps_state->gps_rangering_active = TRUE; + } else { + gps_state->gps_rangering_active = FALSE; + } + + /* XXX force a redraw */ + + return FALSE; +} + +/* external interface to update UI from latest GPS data. */ +gboolean gps_redraw_all(gpointer data) +{ + GritsPluginGPS *gps_state = (GritsPluginGPS *)data; + assert(gps_state); + + struct gps_data_t *gps_data = gps_state->gps_data; + + g_debug("gps_redraw_all called"); + + assert(gps_data); + if (!gps_data_is_valid(gps_data)) { + g_debug("gps_data is not valid."); + /* XXX Change marker to indicate data is not valid */ + return TRUE; + } + + /* update position labels */ + update_gps_status(gps_state); + + if (gps_data_is_valid(gps_data)) { + /* Update track and marker position */ + g_debug("Updating track at lat = %f, long = %f, track = %f", + gps_data->fix.latitude, + gps_data->fix.longitude, + gps_data->fix.track); + + if (gps_state->marker) { + grits_viewer_remove(gps_state->viewer, + GRITS_OBJECT(gps_state->marker)); + g_object_unref(gps_state->viewer); + gps_state->marker = NULL; + } + + gps_state->marker = grits_marker_new("gps"); + GRITS_OBJECT(gps_state->marker)->center.lat = gps_data->fix.latitude; + GRITS_OBJECT(gps_state->marker)->center.lon = gps_data->fix.longitude; + GRITS_OBJECT(gps_state->marker)->center.elev = 0.0; + GRITS_OBJECT(gps_state->marker)->lod = EARTH_R; + grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker), + GRITS_LEVEL_OVERLAY, FALSE); + grits_viewer_refresh(gps_state->viewer); + } + + if (gps_state->follow_gps && gps_data_is_valid(gps_data)) { + /* Center map at current GPS position. */ + g_debug("Centering map at lat = %f, long = %f, track = %f", + gps_data->fix.latitude, + gps_data->fix.longitude, + gps_data->fix.track); + + double lat, lon, elev; + grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev); + grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude, + gps_data->fix.longitude, elev); + grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0); + } + + /* reschedule */ + return TRUE; +} + +static +char *gps_get_status(struct gps_data_t *gps_data) +{ + gchar *status_color; + gchar *status_text; + + switch (gps_data->fix.mode) { + case MODE_NOT_SEEN: + status_color = "red"; + status_text = "No Signal"; + break; + case MODE_NO_FIX: + status_color = "red"; + status_text = "No Fix"; + break; + case MODE_2D: + status_color = "yellow"; + status_text = "2D Mode"; + break; + case MODE_3D: + status_color = "green"; + status_text = "3D Mode"; + break; + default: + status_color = "black"; + status_text = "Unknown"; + break; + } + return g_strdup_printf("%s", + status_color, status_text); +} + +#if 0 +static char *gps_get_online(struct gps_data_t *); + +static +char *gps_get_online(struct gps_data_t *gps_data) +{ + char *status_str; + char *online_str; + + if (gps_data->online == -1.0) { + online_str = "Offline"; + } else { + online_str = "Online"; + } + + switch (gps_data->status) { + case 0: + status_str = "No Fix"; + break; + case 1: + status_str = "Fix Acquired"; + break; + case 2: + status_str = "DGPS Fix"; + break; + default: + status_str = "Unknown Status"; + break; + } + + return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str); +} +#endif + + + +static +char *gps_get_latitude(struct gps_data_t *gps_data) +{ + return g_strdup_printf("%3.4f", gps_data->fix.latitude); +} + +static +char *gps_get_longitude(struct gps_data_t *gps_data) +{ + return g_strdup_printf("%3.4f", gps_data->fix.longitude); +} + +static +char *gps_get_elevation(struct gps_data_t *gps_data) +{ + /* XXX Make units (m/ft) settable */ + return g_strdup_printf("%.1lf %s", + (gps_data->fix.altitude * METERS_TO_FEET), "ft"); +} + +static +char *gps_get_heading(struct gps_data_t *gps_data) +{ + /* XXX Make units (m/ft) settable */ + return g_strdup_printf("%03.0lf", gps_data->fix.track); +} + +static +char *gps_get_speed(struct gps_data_t *gps_data) +{ + /* XXX Make units (m/ft) settable */ + return g_strdup_printf("%1.1f %s", + (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph"); +} + + +static +struct gps_data_t *initialize_gpsd(char *device, char *port) +{ + struct gps_data_t *gps_data; + + if ((gps_data = gps_open(device, port)) == NULL) { + g_warning("Unable to open gpsd connection"); + gps_data = NULL; + } else { + +#if GPSD_API_MAJOR_VERSION < 4 + /* set data smothing */ + gps_query(gps_data, "j=1\n"); + + /* Put gpsd in "watcher" mode so it continually tells us of updates */ + gps_query(gps_data, "w+ox"); + + if (gps_query(gps_data, "o\n") == -1) { + g_warning("Error querying gps data\n"); + } + gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data); + gps_clear_fix(&gps_data->fix); + +#else /* Version > 4 uses gps_stream() instead */ + (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL); + g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd); + gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data); +#endif + } + + return gps_data; +} + +static void +process_gps(gpointer data, gint source, GdkInputCondition condition) +{ + struct gps_data_t *gps_data = (struct gps_data_t *)data; + + /* Process any data from the gps and call the hook function */ + g_debug("In process_gps()"); + if (gps_data != NULL) { + int result = gps_read(gps_data); + g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude); + } else { + g_debug("process_gps: gps_data == NULL."); + } +} + +/************************** GPS Object Methods *************************/ + +/* Methods */ +GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs) +{ + /* TODO: move to constructor if possible */ + g_debug("GritsPluginGPS: new"); + GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL); + self->viewer = viewer; + self->prefs = prefs; + + g_debug("grits_plugin_gps_new()"); + + self->gps_data = initialize_gpsd("localhost", DEFAULT_GPSD_PORT); + self->follow_gps = FALSE; + self->gps_sn_active = FALSE; + + gps_init_status_info(self, self->hbox); + gps_init_control_frame(self, self->hbox); + gps_init_track_log_frame(self, self->hbox); + gps_init_spotter_network_frame(self, self->hbox); + gps_init_range_rings(self, self->hbox); + + return self; +} + +static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self) +{ + GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self); + return self->config; +} + +/* GObject code */ +static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface); +G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN, + grits_plugin_gps_plugin_init)); + +static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface) +{ + g_debug("GritsPluginGPS: plugin_init"); + /* Add methods to the interface */ + iface->get_config = grits_plugin_gps_get_config; +} + +static void grits_plugin_gps_init(GritsPluginGPS *self) +{ + g_debug("GritsPluginGPS: in gps_init()"); + + self->config = gtk_notebook_new(); + + self->hbox = gtk_hbox_new(FALSE, 2); + gtk_notebook_insert_page(GTK_NOTEBOOK(self->config), + GTK_WIDGET(self->hbox), + gtk_label_new("GPS"), 0); + /* Need to position on the top because of Win32 bug */ + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT); +} + +static void grits_plugin_gps_dispose(GObject *gobject) +{ + g_debug("GritsPluginGPS: dispose"); + GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject); + g_signal_handler_disconnect(self->config, self->tab_id); + + /* Drop references */ + G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject); +} + +static void grits_plugin_gps_finalize(GObject *gobject) +{ + g_debug("GritsPluginGPS: finalize"); + GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject); + /* Free data */ + gtk_widget_destroy(self->config); + G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject); +} + +static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass) +{ + g_debug("GritsPluginGPS: class_init"); + GObjectClass *gobject_class = (GObjectClass*)klass; + gobject_class->dispose = grits_plugin_gps_dispose; + gobject_class->finalize = grits_plugin_gps_finalize; +} diff --git a/src/plugins/gps-plugin.h b/src/plugins/gps-plugin.h new file mode 100644 index 0000000..ba29136 --- /dev/null +++ b/src/plugins/gps-plugin.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012 Adam Boggs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _GPS_PLUGIN_H +#define _GPS_PLUGIN_H + +gpointer gps_init(GtkWidget *gbox, GtkWidget *status_bar); + +void gps_set_follow(gpointer state, gboolean track); +gboolean gps_key_press_event(gpointer state, GdkEventKey *kevent); +gboolean gps_redraw_all(gpointer data); + +#define GRITS_TYPE_PLUGIN_GPS (grits_plugin_gps_get_type ()) +#define GRITS_PLUGIN_GPS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GRITS_TYPE_PLUGIN_GPS, GritsPluginGPS)) +#define GRITS_IS_PLUGIN_GPS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GRITS_TYPE_PLUGIN_GPS)) +#define GRITS_PLUGIN_GPS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GRITS_TYPE_PLUGIN_GPS, GritsPluginGPSClass)) +#define GRITS_IS_PLUGIN_GPS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GRITS_TYPE_PLUGIN_GPS)) +#define GRITS_PLUGIN_GPS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRITS_TYPE_PLUGIN_GPS, GritsPluginGPSClass)) + +typedef struct _GritsPluginGPS GritsPluginGPS; +typedef struct _GritsPluginGPSClass GritsPluginGPSClass; + +/* All the User Interface objects we need to keep track of. */ +struct gps_ui_t { + /* gps info frame */ + GtkWidget *gps_status_frame; + GtkWidget *gps_status_table; + GtkWidget *gps_status_label; + GtkWidget *gps_latitude_label; + GtkWidget *gps_longitude_label; + GtkWidget *gps_heading_label; + GtkWidget *gps_elevation_label; + + GtkWidget *status_bar; + + /* control frame */ + GtkWidget *gps_follow_checkbox; + GtkWidget *gps_track_checkbox; + + /* log frame */ + GtkWidget *gps_log_checkbox; + GtkWidget *gps_log_filename_entry; + GtkWidget *gps_log_interval_slider; + guint gps_log_timeout_id; /* id of timeout so we can delete it */ + unsigned int gps_log_number; /* sequential log number */ + + /* spotternetwork frame */ + GtkWidget *gps_sn_checkbox; + GtkWidget *gps_sn_active_checkbox; + gboolean gps_sn_active; /* Whether chaser is "active" or not */ + SoupSession *soup_session; /* for sn requests */ + guint gps_sn_timeout_id; /* id of timeout so we can delete it */ + GtkWidget *gps_sn_entry; /* Entry box for spotternetwork ID */ + + /* range ring frame */ + GtkWidget *gps_rangering_checkbox; +}; + +/* GPS private data */ +struct _GritsPluginGPS { + GObject parent_instance; + + /* instance members */ + GritsViewer *viewer; + GritsPrefs *prefs; + GtkWidget *config; + guint tab_id; + GtkWidget *hbox; + GritsMarker *marker; + + struct gps_data_t *gps_data; + gboolean follow_gps; + gboolean gps_sn_active; + gboolean gps_rangering_active; /* range rings are visible or not */ + guint gps_update_timeout_id; /* id of timeout so we can delete it */ + + struct gps_ui_t ui; +}; + +struct _GritsPluginGPSClass { + GObjectClass parent_class; +}; + +GType grits_plugin_gps_get_type(); + + +#endif /* GPS_PLUGIN_H */ + -- 2.43.2