]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
d783c370a1d995eb7e1fb8cd6a91fb018005d19d
[aweather] / src / plugins / gps-plugin.c
1 /*
2  * Copyright (C) 2012 Adam Boggs <boggs@aircrafter.org>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _XOPEN_SOURCE
19 #include <stdio.h>
20 #include <fcntl.h>
21 #include <errno.h>
22 #include <time.h>
23 #include <config.h>
24 #include <assert.h>
25 #include <string.h>
26 #include <glib/gstdio.h>
27 #include <gtk/gtk.h>
28 #include <gio/gio.h>
29 #include <math.h>
30
31 #include <grits.h>
32 #include <gps.h>
33
34 #include "gps-plugin.h"
35 #include "level2.h"
36 #include "../aweather-location.h"
37
38 /* interval to update map with new gps data in seconds. */
39 #define GPS_UPDATE_INTERVAL (2)
40
41 /* interval to update log file in seconds (default value for slider) */
42 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
43 #define GPS_LOG_EXT "csv"
44
45 /* For updating the status bar conveniently */
46 #define GPS_STATUSBAR_CONTEXT "GPS"
47 #if 0
48 #define GPS_STATUS(gps_state, format, args...) \
49     do { \
50         char *buf = g_strdup_printf(format, ##args); \
51         gtk_statusbar_push(GTK_STATUSBAR(gps_state->status_bar), \
52             gtk_statusbar_get_context_id( \
53                 GTK_STATUSBAR(gps_state->status_bar), \
54                 GPS_STATUSBAR_CONTEXT), \
55             buf); \
56     } while (0)
57 #endif
58
59 #define GPS_STATUS(gps_state, format, args...) \
60     do { \
61         char *buf = g_strdup_printf(format, ##args); \
62         g_warning("STATUS: %s", buf); \
63     } while (0)
64
65
66 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
67 static char *gps_get_time_string(time_t gps_time);
68 static char *gps_get_date_string(double gps_time);
69 static void process_gps( gpointer, gint, GdkInputCondition);
70
71 #ifdef GPS_RANGE_RINGS
72 static void gps_init_range_rings(GritsPluginGPS *gps_state,
73             GtkWidget *gbox);
74 static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data);
75 #endif
76
77 static void gps_init_status_info(GritsPluginGPS *gps_state,
78             GtkWidget *gbox);
79 static void gps_init_control_frame(GritsPluginGPS *gps_state,
80             GtkWidget *gbox);
81 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data);
82
83 /* GPS logging support */
84 static void gps_init_track_log_frame(GritsPluginGPS *gps_state,
85             GtkWidget *gbox);
86 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data);
87 static gboolean on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data);
88 static gboolean gps_write_log(gpointer data);
89
90 static char *gps_get_status(struct gps_data_t *);
91 static char *gps_get_latitude(struct gps_data_t *);
92 static char *gps_get_longitude(struct gps_data_t *);
93 static char *gps_get_elevation(struct gps_data_t *);
94 static char *gps_get_heading(struct gps_data_t *);
95 static char *gps_get_speed(struct gps_data_t *);
96
97 /* Describes a line in the gps table */
98 struct gps_status_info {
99     char *label;
100     char *initial_val;
101     char *(*get_data)(struct gps_data_t *);
102     unsigned int font_size;
103     GtkWidget *label_widget;
104     GtkWidget *value_widget;
105 };
106
107 struct gps_status_info gps_table[] = {
108     {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
109 //    {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
110     {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
111     {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
112     {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
113     {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
114     {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
115 };
116
117 static
118 gboolean gps_data_is_valid(struct gps_data_t *gps_data)
119 {
120     if (gps_data != NULL && gps_data->online != -1.0 &&
121         gps_data->fix.mode >= MODE_2D &&
122         gps_data->status > STATUS_NO_FIX) {
123         return TRUE;
124     }
125
126     return FALSE;
127 }
128
129 static char *
130 gps_get_date_string(double gps_time)
131 {
132     static char buf[256];
133     time_t      int_time = (time_t)gps_time;
134     struct tm   tm_time;
135
136     gmtime_r(&int_time, &tm_time);
137
138     snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
139                  tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
140
141
142     return buf;
143 }
144
145 static char *
146 gps_get_time_string(time_t gps_time)
147 {
148     static char buf[256];
149     time_t      int_time = (time_t)gps_time;
150     struct tm   tm_time;
151
152     gmtime_r(&int_time, &tm_time);
153
154     snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
155                  tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
156
157     return buf;
158 }
159
160
161 static void
162 update_gps_status(GritsPluginGPS *gps_state)
163 {
164     struct gps_data_t *gps_data = &gps_state->gps_data;
165
166     /* gps table update */
167     int i;
168     gchar *str;
169     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
170         gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
171                 (str = gps_table[i].get_data(gps_data)));
172         g_free(str);
173     }
174 }
175
176 static void
177 gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
178 {
179     /* Control checkboxes */
180     GtkWidget *gps_control_frame = gtk_frame_new ("GPS Control");
181     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
182     gtk_container_add (GTK_CONTAINER (gps_control_frame), cbox);
183     gtk_box_pack_start (GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
184
185     gps_state->ui.gps_follow_checkbox = gtk_check_button_new_with_label("Follow GPS");
186     g_signal_connect (G_OBJECT (gps_state->ui.gps_follow_checkbox), "clicked",
187                       G_CALLBACK (on_gps_follow_clicked_event),
188                       (gpointer)gps_state);
189     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_follow_checkbox,
190                         FALSE, FALSE, 0);
191     gps_state->ui.gps_track_checkbox = gtk_check_button_new_with_label("Show Track");
192     g_signal_connect (G_OBJECT (gps_state->ui.gps_track_checkbox), "clicked",
193                       G_CALLBACK (on_gps_track_clicked_event),
194                       (gpointer)gps_state);
195     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_track_checkbox,
196                         FALSE, FALSE, 0);
197 }
198
199 static gboolean
200 on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data)
201 {
202     g_debug("on_gps_track_clicked_event called!");
203     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
204         /* XXX start logging trip history */
205         GPS_STATUS(gps_state, "Enabled GPS track.");
206     } else {
207         /* XXX stop logging trip history */
208         GPS_STATUS(gps_state, "Disabled GPS track.");
209     }
210
211     return FALSE;
212 }
213
214 static gboolean
215 on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
216 {
217     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
218
219     assert(gps_state);
220
221     g_debug("gps interval changed, value = %f",
222         gtk_range_get_value(GTK_RANGE(widget)));
223
224     if (gtk_toggle_button_get_active(
225                         GTK_TOGGLE_BUTTON(gps_state->ui.gps_log_checkbox))) {
226         assert(gps_state->ui.gps_log_timeout_id != 0);
227
228         /* disable old timeout */
229         g_source_remove(gps_state->ui.gps_log_timeout_id);
230         gps_state->ui.gps_log_timeout_id = 0;
231
232         /* Schedule new log file write */
233         gps_state->ui.gps_log_timeout_id = g_timeout_add(
234                                 gtk_range_get_value(GTK_RANGE(widget))*1000,
235                                 gps_write_log, gps_state);
236         gps_write_log(gps_state);
237     }
238
239     return FALSE;
240 }
241
242 static gboolean
243 gps_write_log(gpointer data)
244 {
245     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
246     struct gps_data_t *gps_data = &gps_state->gps_data;
247     char buf[256];
248     char filename[256];
249     int fd;
250     gboolean new_file = FALSE;
251
252     if (gps_data == NULL) {
253         g_warning("Skipped write to GPS log file: "
254                 "can not get GPS coordinates.");
255         GPS_STATUS(gps_state, "Skipped write to GPS log file: "
256                 "can not get GPS coordinates.");
257         return TRUE;
258     }
259
260     /* get filename from text entry box.  If empty, generate a name from
261      * the date and time and set it.
262      */
263     if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)))
264                                                                     == 0) {
265         snprintf(filename, sizeof(filename),
266                             "%sT%s.%s",
267                             gps_get_date_string(gps_state->gps_data.fix.time),
268                             gps_get_time_string(gps_state->gps_data.fix.time),
269                             GPS_LOG_EXT);
270         gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry),
271                             filename);
272     }
273
274     strncpy(filename,
275             gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)),
276             sizeof (filename));
277
278     if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
279         new_file = TRUE;
280     }
281
282     if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
283         g_warning("Error opening log file %s: %s",
284                         filename, strerror(errno));
285         return FALSE;
286     }
287
288     if (new_file) {
289         /* write header and reset record counter */
290         snprintf(buf, sizeof(buf),
291                 "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
292         if (write(fd, buf, strlen(buf)) == -1) {
293             g_warning("Error writing header to log file %s: %s",
294                             filename, strerror(errno));
295         }
296         gps_state->ui.gps_log_number = 1;
297     }
298
299     /* Write log entry.  Make sure this matches the header */
300     /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
301     /* RTR values: T=time, B=button push, S=speed, D=distance */
302     snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
303         gps_state->ui.gps_log_number++,
304         gps_get_date_string(gps_state->gps_data.fix.time),
305         gps_get_time_string(gps_state->gps_data.fix.time),
306 //      gps_data->fix.time,
307         gps_data->fix.latitude,
308         gps_data->fix.longitude,
309         gps_data->fix.altitude * METERS_TO_FEET,
310         gps_data->fix.track,
311         gps_data->fix.speed * METERS_TO_FEET,
312         'T'); /* position due to timer expired  */
313
314     if (write(fd, buf, strlen(buf)) == -1) {
315         g_warning("Could not write log number %d to log file %s: %s",
316             gps_state->ui.gps_log_number-1, filename, strerror(errno));
317     }
318     close(fd);
319
320     GPS_STATUS(gps_state, "Updated GPS log file %s.", filename);
321
322     /* reschedule */
323     return TRUE;
324 }
325
326 static gboolean
327 on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
328 {
329     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
330
331     g_debug("on_gps_follow_clicked_event called!, button status %d",
332         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
333     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
334         gps_state->follow_gps = TRUE;
335     else
336         gps_state->follow_gps = FALSE;
337
338     return FALSE;
339 }
340
341
342 static void
343 gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
344 {
345     /* Track log box with enable checkbox and filename entry */
346     GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
347     GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
348     gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
349     gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
350                         FALSE, FALSE, 0);
351
352     gps_state->ui.gps_log_checkbox = gtk_check_button_new_with_label("Log Position to File");
353     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_checkbox), "clicked",
354                       G_CALLBACK (on_gps_log_clicked_event),
355                       (gpointer)gps_state);
356     gtk_box_pack_start (GTK_BOX(lbox), gps_state->ui.gps_log_checkbox,
357                         FALSE, FALSE, 0);
358
359     /* Set up filename entry box */
360     GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
361     GtkWidget *filename_label = gtk_label_new ("Filename:");
362     gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
363     gps_state->ui.gps_log_filename_entry = gtk_entry_new();
364     gtk_box_pack_start (GTK_BOX(fbox), gps_state->ui.gps_log_filename_entry,
365                         TRUE, TRUE, 0);
366     gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
367
368     /* set up gps log interval slider */
369     GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
370     GtkWidget *interval_label = gtk_label_new ("Update Interval:");
371     gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
372     gps_state->ui.gps_log_interval_slider =
373                     gtk_hscale_new_with_range(1.0, 600.0, 30.0);
374     gtk_range_set_value (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
375                     GPS_LOG_DEFAULT_UPDATE_INTERVAL);
376     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_interval_slider),
377                     "value-changed",
378                     G_CALLBACK(on_gps_log_interval_changed_event),
379                     (gpointer)gps_state);
380     gtk_range_set_increments (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
381                     10.0 /* step */, 30.0 /* page up/down */);
382     gtk_range_set_update_policy (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
383                     GTK_UPDATE_DELAYED);
384     gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider,
385                     TRUE, TRUE, 0);
386     gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
387 }
388
389 static gboolean
390 on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
391 {
392     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
393
394     g_debug("on_gps_log_clicked_event called!");
395
396     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
397         gps_write_log(gps_state);
398
399         /* Schedule log file write */
400         gps_state->ui.gps_log_timeout_id = g_timeout_add(
401                     gtk_range_get_value(
402                         GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000,
403                         gps_write_log, gps_state);
404     } else {
405         /* button unchecked */
406         g_source_remove(gps_state->ui.gps_log_timeout_id);
407         gps_state->ui.gps_log_timeout_id = 0;
408         g_debug("Closed log file.");
409     }
410
411     return FALSE;
412 }
413
414
415 static void
416 gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox)
417 {
418     gps_state->ui.gps_status_frame = gtk_frame_new ("GPS Data");
419     gps_state->ui.gps_status_table = gtk_table_new (5, 2, TRUE);
420     gtk_container_add (GTK_CONTAINER (gps_state->ui.gps_status_frame),
421                         gps_state->ui.gps_status_table);
422
423     /* gps data table setup */
424     int i;
425     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
426         gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
427         gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
428                                         GTK_JUSTIFY_LEFT);
429         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
430                         gps_table[i].label_widget, 0, 1, i, i+1, 0, 0, 0, 0);
431         gps_table[i].value_widget = gtk_label_new (gps_table[i].initial_val);
432         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
433                         gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
434
435         PangoFontDescription *font_desc = pango_font_description_new ();
436         pango_font_description_set_size (font_desc,
437                         gps_table[i].font_size*PANGO_SCALE);
438         gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
439         gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
440         pango_font_description_free (font_desc);
441     }
442     gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame,
443                         FALSE, FALSE, 0);
444
445     /* Start UI refresh task, which will reschedule itself periodically. */
446     gps_redraw_all(gps_state);
447     gps_state->gps_update_timeout_id = g_timeout_add(
448                     GPS_UPDATE_INTERVAL*1000,
449                     gps_redraw_all, gps_state);
450
451 }
452
453
454 #ifdef GPS_RANGE_RINGS
455 static void
456 gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox)
457 {
458     GtkWidget *gps_range_ring_frame = gtk_frame_new ("Range Rings");
459     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
460     gtk_container_add (GTK_CONTAINER (gps_range_ring_frame), cbox);
461     gtk_box_pack_start (GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
462
463     gps_state->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
464     g_signal_connect (G_OBJECT (gps_state->ui.gps_rangering_checkbox), "clicked",
465                       G_CALLBACK (on_gps_rangering_clicked_event),
466                       (gpointer)gps_state);
467     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_rangering_checkbox,
468                         FALSE, FALSE, 0);
469 }
470
471 static gboolean
472 on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data)
473 {
474     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
475
476     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
477         gps_state->gps_rangering_active = TRUE;
478     } else {
479         gps_state->gps_rangering_active = FALSE;
480     }
481
482     /* XXX force a redraw */
483
484     return FALSE;
485 }
486 #endif /* GPS_RANGE_RINGS */
487
488 /* external interface to update UI from latest GPS data. */
489 gboolean gps_redraw_all(gpointer data)
490 {
491     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
492     assert(gps_state);
493
494     struct gps_data_t *gps_data = &gps_state->gps_data;
495
496     g_debug("gps_redraw_all called");
497
498     assert(gps_data);
499     if (!gps_data_is_valid(gps_data)) {
500         g_debug("gps_data is not valid.");
501         /* XXX Change marker to indicate data is not valid */
502         return TRUE;
503     }
504
505     /* update position labels */
506     update_gps_status(gps_state);
507
508     if (gps_data_is_valid(gps_data)) {
509         /* Update track and marker position */
510         g_debug("Updating track at lat = %f, long = %f, track = %f",
511                         gps_data->fix.latitude,
512                         gps_data->fix.longitude,
513                         gps_data->fix.track);
514
515         if (gps_state->marker) {
516             grits_viewer_remove(gps_state->viewer,
517                     GRITS_OBJECT(gps_state->marker));
518             g_object_unref(gps_state->viewer);
519             gps_state->marker = NULL;
520         }
521
522         gps_state->marker = grits_marker_new("gps");
523                 
524         GRITS_OBJECT(gps_state->marker)->center.lat  = gps_data->fix.latitude;
525         GRITS_OBJECT(gps_state->marker)->center.lon  = gps_data->fix.longitude;
526         GRITS_OBJECT(gps_state->marker)->center.elev =   0.0;
527         GRITS_OBJECT(gps_state->marker)->lod         = EARTH_R;
528
529         grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker),
530                         GRITS_LEVEL_OVERLAY, FALSE);
531         grits_viewer_refresh(gps_state->viewer);
532     }
533
534     if (gps_state->follow_gps && gps_data_is_valid(gps_data)) {
535         /* Center map at current GPS position. */
536         g_debug("Centering map at lat = %f, long = %f, track = %f",
537                         gps_data->fix.latitude,
538                         gps_data->fix.longitude,
539                         gps_data->fix.track);
540
541         double lat, lon, elev;
542         grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev);
543         grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude,
544                                           gps_data->fix.longitude, elev);
545         grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0);
546     }
547
548     /* reschedule */
549     return TRUE;
550 }
551
552 static
553 char *gps_get_status(struct gps_data_t *gps_data)
554 {
555     gchar *status_color;
556     gchar *status_text;
557
558     switch (gps_data->fix.mode) {
559         case MODE_NOT_SEEN:
560             status_color = "red";
561             status_text = "No Signal";
562             break;
563         case MODE_NO_FIX:
564             status_color = "red";
565             status_text = "No Fix";
566             break;
567         case MODE_2D:
568             status_color = "yellow";
569             status_text = "2D Mode";
570             break;
571         case MODE_3D:
572             status_color = "green";
573             status_text = "3D Mode";
574             break;
575         default:
576             status_color = "black";
577             status_text = "Unknown";
578             break;
579     }
580     return g_strdup_printf("<span foreground=\"%s\">%s</span>",
581                                 status_color, status_text);
582 }
583
584 #if 0
585 static char *gps_get_online(struct gps_data_t *);
586
587 static
588 char *gps_get_online(struct gps_data_t *gps_data)
589 {
590     char *status_str;
591     char *online_str;
592
593     if (gps_data->online == -1.0) {
594         online_str = "Offline";
595     } else {
596         online_str = "Online";
597     }
598
599     switch (gps_data->status) {
600     case 0:
601         status_str = "No Fix";
602         break;
603     case 1:
604         status_str = "Fix Acquired";
605         break;
606     case 2:
607         status_str = "DGPS Fix";
608         break;
609     default:
610         status_str = "Unknown Status";
611         break;
612     }
613
614     return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
615 }
616 #endif
617
618
619
620 static
621 char *gps_get_latitude(struct gps_data_t *gps_data)
622 {
623     return g_strdup_printf("%3.4f", gps_data->fix.latitude);
624 }
625
626 static
627 char *gps_get_longitude(struct gps_data_t *gps_data)
628 {
629     return g_strdup_printf("%3.4f", gps_data->fix.longitude);
630 }
631
632 static
633 char *gps_get_elevation(struct gps_data_t *gps_data)
634 {
635     /* XXX Make units (m/ft) settable */
636     return g_strdup_printf("%.1lf %s",
637                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
638 }
639
640 static
641 char *gps_get_heading(struct gps_data_t *gps_data)
642 {
643     /* XXX Make units (m/ft) settable */
644     return g_strdup_printf("%03.0lf", gps_data->fix.track);
645 }
646
647 static
648 char *gps_get_speed(struct gps_data_t *gps_data)
649 {
650     /* XXX Make units (m/ft) settable */
651     return g_strdup_printf("%1.1f %s",
652         (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
653 }
654
655
656 static
657 gint initialize_gpsd(char *server, char *port,
658         struct gps_data_t *gps_data)
659 {
660 #if GPSD_API_MAJOR_VERSION < 5
661 #error "GPSD protocol version 5 or greater required."
662 #endif
663     int result;
664
665     if ((result = gps_open(server, port, gps_data)) != 0) {
666         g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
667         server, port, result, errno, gps_errstr(errno));
668     } else {
669         (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
670         g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd);
671         gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
672     }
673
674     return result;
675 }
676
677 static void
678 process_gps(gpointer data, gint source, GdkInputCondition condition)
679 {
680     struct gps_data_t *gps_data = (struct gps_data_t *)data;
681
682     /* Process any data from the gps and call the hook function */
683     g_debug("In process_gps()");
684     if (gps_data != NULL) {
685         int result = gps_read(gps_data);
686         g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude);
687     } else {
688         g_debug("process_gps: gps_data == NULL.");
689     }
690 }
691
692 /************************** GPS Object Methods *************************/
693
694 /* Methods */
695 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
696 {
697         /* TODO: move to constructor if possible */
698         g_debug("GritsPluginGPS: new");
699         GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
700         self->viewer = viewer;
701         self->prefs  = prefs;
702
703         g_debug("grits_plugin_gps_new()");
704
705         initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &self->gps_data);
706         self->follow_gps = FALSE;
707
708         gps_init_status_info(self, self->hbox);
709         gps_init_control_frame(self, self->hbox);
710         gps_init_track_log_frame(self, self->hbox);
711 #ifdef GPS_RANGE_RINGS
712         gps_init_range_rings(self, self->hbox);
713 #endif
714
715         return self;
716 }
717
718 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
719 {
720         GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
721         return self->config;
722 }
723
724 /* GObject code */
725 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
726 G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT,
727                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
728                         grits_plugin_gps_plugin_init));
729
730 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
731 {
732         g_debug("GritsPluginGPS: plugin_init");
733         /* Add methods to the interface */
734         iface->get_config = grits_plugin_gps_get_config;
735 }
736
737 static void grits_plugin_gps_init(GritsPluginGPS *self)
738 {
739         g_debug("GritsPluginGPS: in gps_init()");
740
741         self->config     = gtk_notebook_new();
742
743          self->hbox = gtk_hbox_new(FALSE, 2);
744          gtk_notebook_insert_page(GTK_NOTEBOOK(self->config),
745                                 GTK_WIDGET(self->hbox),
746                                 gtk_label_new("GPS"), 0);
747         /* Need to position on the top because of Win32 bug */
748         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT);
749 }
750
751 static void grits_plugin_gps_dispose(GObject *gobject)
752 {
753         g_debug("GritsPluginGPS: dispose");
754
755         /* Drop references */
756         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
757 }
758
759 static void grits_plugin_gps_finalize(GObject *gobject)
760 {
761         g_debug("GritsPluginGPS: finalize");
762         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
763         /* Free data */
764         gtk_widget_destroy(self->config);
765         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
766 }
767
768 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
769 {
770         g_debug("GritsPluginGPS: class_init");
771         GObjectClass *gobject_class = (GObjectClass*)klass;
772         gobject_class->dispose  = grits_plugin_gps_dispose;
773         gobject_class->finalize = grits_plugin_gps_finalize;
774 }