path.c \
poi.c \
settings.c \
- util.c
+ util.c \
+ aprs_decode.c \
+ aprs.c \
+ hashtable.c \
+ hashtable_itr.c \
+ aprs_display.c \
+ aprs_message.c \
+ aprs_kiss.c
# bin_SCRIPTS = maemo-mapper.sh
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "data.h"
+
+#include <pthread.h>
+
+#include <libgnomevfs/gnome-vfs.h>
+#include <libgnomevfs/gnome-vfs-inet-connection.h>
+#include <errno.h>
+#include "aprs_display.h"
+
+#ifndef LEGACY
+# include <hildon/hildon-note.h>
+# include <hildon/hildon-banner.h>
+#else
+# include <hildon-widgets/hildon-note.h>
+# include <hildon-widgets/hildon-banner.h>
+#endif
+
+#include "types.h"
+#include "data.h"
+#include "defines.h"
+
+#include "display.h"
+#include "aprs.h"
+#include "gps.h"
+#include "gpsbt.h"
+#include "path.h"
+#include "util.h"
+
+#include "aprs_decode.h"
+
+static volatile GThread* _aprs_inet_thread = NULL;
+static volatile GThread* _aprs_tty_thread = NULL;
+
+static GMutex* _aprs_inet_init_mutex = NULL;
+static GMutex* _aprs_tty_init_mutex = NULL;
+
+static gint _aprs_rcvr_retry_count = 0;
+
+#define VERSIONFRM "APRS"
+extern AprsDataRow* n_first; // pointer to first element in name sorted station list
+
+
+
+TWriteBuffer _write_buffer[APRS_PORT_COUNT];
+
+gboolean send_line(gchar* text, gint text_len, TAprsPort port);
+
+static gboolean aprs_handle_error_idle(gchar *error)
+{
+ printf("%s(%s)\n", __PRETTY_FUNCTION__, error);
+
+ /* Ask for re-try. */
+ if(++_aprs_rcvr_retry_count > 2)
+ {
+ GtkWidget *confirm;
+ gchar buffer[BUFFER_SIZE];
+
+ /* Reset retry count. */
+ _aprs_rcvr_retry_count = 0;
+
+ snprintf(buffer, sizeof(buffer), "%s\nRetry?", error);
+ confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), buffer);
+
+ aprs_server_disconnect();
+
+ if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
+ {
+ aprs_server_connect(); /* Try again. */
+ }
+ else
+ {
+ /* Reset Connect to APRS menu item. */
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item), FALSE);
+ }
+
+ /* Ask user to re-connect. */
+ gtk_widget_destroy(confirm);
+ }
+ else
+ {
+ aprs_server_disconnect();
+ aprs_server_connect(); /* Try again. */
+ }
+
+ g_free(error);
+
+ vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+ return FALSE;
+}
+
+
+/**
+ * Set the connection state. This function controls all connection-related
+ * banners.
+ */
+void set_aprs_tty_conn_state(ConnState new_conn_state)
+{
+ printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
+
+ switch(_aprs_tty_state = new_conn_state)
+ {
+ case RCVR_OFF:
+ case RCVR_FIXED:
+ case RCVR_UP:
+ if(_connect_banner)
+ {
+ gtk_widget_destroy(_connect_banner);
+ _connect_banner = NULL;
+ }
+ break;
+ case RCVR_DOWN:
+ if(!_connect_banner)
+ _connect_banner = hildon_banner_show_animation(
+ _window, NULL, _("Attempting to connect to TNC"));
+ break;
+
+ default: ; /* to quell warning. */
+ }
+
+ vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Set the connection state. This function controls all connection-related
+ * banners.
+ */
+void set_aprs_inet_conn_state(ConnState new_conn_state)
+{
+ printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
+
+ switch(_aprs_inet_state = new_conn_state)
+ {
+ case RCVR_OFF:
+ case RCVR_FIXED:
+ case RCVR_UP:
+ if(_connect_banner)
+ {
+ gtk_widget_destroy(_connect_banner);
+ _connect_banner = NULL;
+ }
+ if(_fix_banner)
+ {
+ gtk_widget_destroy(_fix_banner);
+ _fix_banner = NULL;
+ }
+ break;
+ case RCVR_DOWN:
+ if(_fix_banner)
+ {
+ gtk_widget_destroy(_fix_banner);
+ _fix_banner = NULL;
+ }
+ if(!_connect_banner)
+ _connect_banner = hildon_banner_show_animation(
+ _window, NULL, _("Attempting to connect to APRS server"));
+ break;
+
+ default: ; /* to quell warning. */
+ }
+
+ vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+
+static gboolean aprs_parse_server_packet(gchar *packet)
+{
+ decode_ax25_line(packet, APRS_PORT_INET);
+
+ g_free(packet);
+
+ return FALSE;
+}
+
+
+void update_aprs_inet_options(gboolean force)
+{
+ // If auto filter is not or we are not connected then stop
+ if(!_aprs_server_auto_filter_on
+ || !_aprs_enable
+ || !_aprs_inet_enable
+ || _aprs_server_auto_filter_km <= 0) return ;
+
+
+ // Disconnect
+ aprs_server_disconnect();
+ //Re-connect
+ aprs_server_connect();
+
+}
+
+gchar *create_aprs_inet_options_string()
+{
+ gint current_lat = (gint)round(_gps.lat);
+ gint current_lon = (gint)round(_gps.lon);
+ gchar *filter = NULL;
+
+ filter = g_strdup_printf("user %s pass %s vers %s v%s filter r/%d/%d/%d \r\n ",
+ _aprs_mycall, _aprs_inet_server_validation, PACKAGE, VERSION,
+ current_lat, current_lon, _aprs_server_auto_filter_km );
+
+ return filter;
+}
+
+
+static void thread_read_server()
+{
+ gchar buf[APRS_BUFFER_SIZE];
+ gchar *buf_curr = buf;
+ gchar *buf_last = buf + sizeof(buf) - 1;
+ GnomeVFSFileSize bytes_read;
+ GnomeVFSResult vfs_result;
+ GnomeVFSInetConnection *iconn = NULL;
+ GnomeVFSSocket *socket = NULL;
+ GThread *my_thread = g_thread_self();
+ gboolean error = FALSE;
+
+ printf("%s(%s)\n", __PRETTY_FUNCTION__, _aprs_server);
+
+
+ //fprintf(stderr, "Starting thread...\n");
+
+ /* Lock/Unlock the mutex to ensure that _aprs_inet_thread is done being set. */
+ g_mutex_lock(_aprs_inet_init_mutex);
+ g_mutex_unlock(_aprs_inet_init_mutex);
+
+ if(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+ {
+ gint tryno;
+
+ /* Attempt to connect to APRS server. */
+ for(tryno = 0; tryno < 10; tryno++)
+ {
+ /* Create a socket to interact with server. */
+ GTimeVal timeout = { 1000, 0 };
+ gchar *filter = create_aprs_inet_options_string();
+ //fprintf(stderr, filter);
+
+ if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
+ &iconn,
+ _aprs_server,
+ _aprs_server_port,
+ NULL))
+ || NULL == ( socket = gnome_vfs_inet_connection_to_socket(iconn))
+ || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
+ socket, &timeout, NULL))
+ || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write( socket,
+ filter, strlen(filter), &bytes_read, NULL))
+ )
+ {
+ g_free(filter);
+ sleep(1);
+ }
+ else
+ {
+ g_free(filter);
+ break;
+ }
+ }
+
+
+ if(!iconn)
+ {
+ g_printerr("Error connecting to APRS server: (%d) %s\n",
+ vfs_result, gnome_vfs_result_to_string(vfs_result));
+ g_idle_add((GSourceFunc)aprs_handle_error_idle,
+ g_strdup_printf("%s",
+ _("Error connecting to APRS server.")));
+ error = TRUE;
+ }
+ }
+
+
+ if(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+ {
+
+ set_aprs_inet_conn_state(RCVR_UP);
+
+ while(my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+ {
+ gchar *eol;
+
+
+ vfs_result = gnome_vfs_socket_read(
+ socket,
+ buf,
+ buf_last - buf_curr,
+ &bytes_read,
+ NULL);
+
+
+ if(vfs_result != GNOME_VFS_OK)
+ {
+ if(my_thread == _aprs_inet_thread)
+ {
+ // Error wasn't user-initiated.
+ g_idle_add((GSourceFunc)aprs_handle_error_idle,
+ g_strdup_printf("%s %u",
+ _("Error reading APRS data."), vfs_result));
+
+ }
+
+ fprintf(stderr, "Read error: %s\n", gnome_vfs_result_to_string(vfs_result));
+ error = TRUE;
+ break;
+ }
+
+ /* Loop through the buffer and read each packet. */
+ buf_curr += bytes_read;
+ *buf_curr = '\0'; /* append a \0 so we can read as string */
+ while(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL
+ && (eol = strchr(buf, '\n')))
+ {
+ /* This is the beginning of a sentence; okay to parse. */
+ *eol = '\0'; /* overwrite \n with \0 */
+
+ if(my_thread == _aprs_inet_thread)
+ //g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)aprs_parse_server_packet, g_strdup(buf), NULL );
+ g_idle_add((GSourceFunc)aprs_parse_server_packet, g_strdup(buf));
+
+ /* If eol is at or after (buf_curr - 1) */
+ if(eol >= (buf_curr - 1))
+ {
+ /* Last read was a newline - reset read buffer */
+ buf_curr = buf;
+ *buf_curr = '\0';
+ }
+ else
+ {
+ /* Move the next line to the front of the buffer. */
+ memmove(buf, eol + 1,
+ buf_curr - eol); /* include terminating 0 */
+ /* Subtract _curr so that it's pointing at the new \0. */
+ buf_curr -= (eol - buf + 1);
+ }
+ }
+ _aprs_rcvr_retry_count = 0;
+
+ // Send any packets queued
+ // try to get lock, otherwise try next time
+ if(g_mutex_trylock (_write_buffer[APRS_PORT_INET].write_lock))
+ {
+ // Store the current end pointer as it may change
+
+ gint quantity = 0;
+ gchar tmp_write_buffer[MAX_DEVICE_BUFFER];
+ while (_write_buffer[APRS_PORT_INET].write_in_pos != _write_buffer[APRS_PORT_INET].write_out_pos) {
+
+ tmp_write_buffer[quantity] = _write_buffer[APRS_PORT_INET].device_write_buffer[_write_buffer[APRS_PORT_INET].write_out_pos];
+
+ _write_buffer[APRS_PORT_INET].write_out_pos++;
+ if (_write_buffer[APRS_PORT_INET].write_out_pos >= MAX_DEVICE_BUFFER)
+ _write_buffer[APRS_PORT_INET].write_out_pos = 0;
+
+ quantity++;
+ }
+
+ if(quantity>0)
+ {
+ sleep(2);
+
+ GnomeVFSFileSize bytes_read = 0;
+ if(GNOME_VFS_OK == gnome_vfs_socket_write( socket,
+ tmp_write_buffer, quantity, &bytes_read, NULL))
+ {
+ // OK
+ //fprintf(stderr, "Send packet success: %s (%u)\n", tmp_write_buffer, quantity);
+ }
+ else
+ {
+ // Failed
+ fprintf(stderr, "Failed to send packet: %s (%u)\n", tmp_write_buffer, quantity);
+ }
+
+ sleep(1);
+ }
+
+ g_mutex_unlock(_write_buffer[APRS_PORT_INET].write_lock);
+ }
+
+ }
+
+ //fprintf(stderr, "Exiting thread...\n");
+ }
+
+ /* Error, or we're done reading APRS data. */
+
+ /* Clean up. */
+ if(iconn)
+ gnome_vfs_inet_connection_destroy(iconn, NULL);
+ //gnome_vfs_inet_connection_free(iconn, NULL);
+
+ iconn = NULL;
+
+ //g_thread_exit(0);
+
+
+ printf("%s(): return\n", __PRETTY_FUNCTION__);
+
+ return;
+}
+
+/**
+ * Disconnect from the receiver. This method cleans up any and everything
+ * that might be associated with the receiver.
+ */
+void aprs_server_disconnect()
+{
+ gboolean exit_now = FALSE;
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ GThread *my_thread = g_thread_self();
+
+ if(my_thread == _aprs_inet_thread)
+ {
+ exit_now = TRUE;
+ }
+
+ g_mutex_lock(_aprs_inet_init_mutex);
+ _aprs_inet_thread = NULL;
+ g_mutex_unlock(_aprs_inet_init_mutex);
+
+
+
+ if(_window)
+ set_aprs_inet_conn_state(RCVR_OFF);
+ vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+
+ if(exit_now)
+ {
+ fprintf(stderr, "Stopping own thread - APRS server\n");
+ exit(0);
+ fprintf(stderr, "Stop Failed\n");
+
+ }
+}
+
+/**
+ * Connect to the server.
+ * This method assumes that _fd is -1 and _channel is NULL. If unsure, call
+ * rcvr_disconnect() first.
+ * Since this is an idle function, this function returns whether or not it
+ * should be called again, which is always FALSE.
+ */
+gboolean aprs_server_connect()
+{
+ printf("%s(%d)\n", __PRETTY_FUNCTION__, _aprs_inet_state);
+
+ if(_aprs_inet_enable && _aprs_inet_state == RCVR_OFF)
+ {
+ set_aprs_inet_conn_state(RCVR_DOWN);
+
+ /* Lock/Unlock the mutex to ensure that the thread doesn't
+ * start until _gps_thread is set. */
+ g_mutex_lock(_aprs_inet_init_mutex);
+
+
+ _aprs_inet_thread = g_thread_create((GThreadFunc)thread_read_server,
+ NULL, TRUE, NULL); /* Joinable. */
+
+// g_thread_set_priority(_aprs_inet_thread, G_THREAD_PRIORITY_LOW);
+
+ g_mutex_unlock(_aprs_inet_init_mutex);
+ }
+
+ vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+ return FALSE;
+}
+
+void aprs_init()
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ _aprs_inet_init_mutex = g_mutex_new();
+ _aprs_tty_init_mutex = g_mutex_new();
+ _write_buffer[APRS_PORT_INET].write_lock = g_mutex_new();
+ _write_buffer[APRS_PORT_TTY].write_lock = g_mutex_new();
+
+ vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+}
+
+void aprs_destroy(gboolean last)
+{
+ static GThread* tmp = NULL;
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ if(!last)
+ {
+ if(_aprs_inet_thread)
+ {
+ tmp = (GThread*)_aprs_inet_thread;
+ _aprs_inet_thread = NULL;
+ }
+ }
+ else if(tmp)
+ g_thread_join(tmp);
+
+ vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+}
+
+gboolean select_aprs(gint unitx, gint unity, gboolean quick)
+{
+ gint x, y;
+ gdouble lat1, lon1, lat2, lon2;
+ static GtkWidget *dialog = NULL;
+ static GtkWidget *list = NULL;
+ static GtkWidget *sw = NULL;
+ static GtkTreeViewColumn *column = NULL;
+ static GtkCellRenderer *renderer = NULL;
+ GtkListStore *store = NULL;
+ GtkTreeIter iter;
+ gboolean selected = FALSE;
+ gint num_stations = 0;
+ AprsStationList *first_station = NULL;
+ AprsStationList *last_station = NULL;
+
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ x = unitx - pixel2unit(3 * _draw_width);
+ y = unity + pixel2unit(3 * _draw_width);
+ unit2latlon(x, y, lat1, lon1);
+
+ x = unitx + pixel2unit(3 * _draw_width);
+ y = unity - pixel2unit(3 * _draw_width);
+ unit2latlon(x, y, lat2, lon2);
+ gdouble lat, lon;
+
+
+ AprsDataRow *p_station = (AprsDataRow *)n_first;
+
+ // Look for all stations in selected area
+ while ( (p_station) != NULL)
+ {
+ lat = convert_lat_l2d(p_station->coord_lat);
+ lon = convert_lon_l2d(p_station->coord_lon);
+
+ if ( ( lat2 >= lat && lat >= lat1 ) && (lon2 >= lon && lon >= lon1) )
+ {
+ // This may have been clicked on
+ AprsStationList * p_list_item = (AprsStationList *)malloc(sizeof(AprsStationList));
+
+ p_list_item->station = p_station;
+ p_list_item->next = NULL;
+
+ if(first_station == NULL)
+ {
+
+ first_station = p_list_item;
+ last_station = p_list_item;
+ }
+ else
+ {
+ last_station->next = p_list_item;
+ last_station = p_list_item;
+ }
+
+ num_stations++;
+ }
+
+ (p_station) = (p_station)->n_next; // Next element in list
+ } // End of while loop
+
+ selected = FALSE;
+
+ if(num_stations==0)
+ {
+ // No station found, maybe a POI was selected?
+ }
+ else if(num_stations == 1)
+ {
+ // Only one station was found, so display it's info
+ if(first_station->station != NULL)
+ {
+ ShowAprsStationPopup(first_station->station);
+ }
+ selected = TRUE;
+ }
+ else
+ {
+ // Multiple possibilities, therefore ask the user which one
+
+ // Initialize store.
+ store = gtk_list_store_new(APRSPOI_NUM_COLUMNS,
+ G_TYPE_BOOLEAN, //Selected
+ G_TYPE_STRING // Callsign
+ );
+
+ AprsStationList * p_list_item = first_station;
+
+ while(p_list_item != NULL)
+ {
+ if(p_list_item->station != NULL)
+ {
+ gtk_list_store_append(store, &iter);
+
+ gtk_list_store_set(store, &iter,
+ APRSPOI_CALLSIGN, g_strdup(p_list_item->station->call_sign),
+ -1);
+ }
+ p_list_item = p_list_item->next;
+ }
+
+
+ if(dialog == NULL)
+ {
+ dialog = gtk_dialog_new_with_buttons(_("Select POI"),
+ GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ NULL);
+
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+ sw, TRUE, TRUE, 0);
+
+ list = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(sw), list);
+
+ gtk_tree_selection_set_mode(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+ GTK_SELECTION_SINGLE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Callsign"), renderer, "text", APRSPOI_CALLSIGN, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+ }
+
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+ g_object_unref(G_OBJECT(store));
+
+ gtk_widget_show_all(dialog);
+
+ if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+ {
+ if(gtk_tree_selection_get_selected(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+ NULL, &iter))
+ {
+ // Find the callsign
+ p_list_item = first_station;
+ while(p_list_item != NULL)
+ {
+ if(p_list_item->station != NULL)
+ {
+ gchar * callsign = NULL;
+ gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+ APRSPOI_CALLSIGN, &(callsign),
+ -1);
+
+ if(strcmp(p_list_item->station->call_sign,callsign) == 0)
+ {
+ gtk_widget_hide(dialog);
+
+ ShowAprsStationPopup(p_list_item->station);
+ selected = TRUE;
+ break;
+
+ }
+ }
+
+ p_list_item = p_list_item->next;
+ }
+
+ }
+
+ }
+
+ // Ensure it has been closed
+ gtk_widget_hide(dialog);
+ }
+
+
+ // Free the list, but not the stations
+ if(first_station)
+ {
+ AprsStationList * p_list_item = first_station;
+
+ while(first_station)
+ {
+ // Store pointer to delete contents after next pointer is stored
+ p_list_item = first_station;
+
+ // Move pointer to next
+ first_station = p_list_item->next;
+
+ free(p_list_item);
+ }
+ }
+
+
+ vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+ return selected;
+
+}
+
+
+
+//*****************************************************************
+// distance_from_my_station - compute distance from my station and
+// course with a given call
+//
+// return distance and course
+//
+// Returns 0.0 for distance if station not found in database or the
+// station hasn't sent out a posit yet.
+//*****************************************************************
+
+double distance_from_my_station(char *call_sign, gchar *course_deg, gint course_len) {
+ AprsDataRow *p_station;
+ double distance;
+ float value;
+ double d_lat, d_lon;
+
+ distance = 0.0;
+ p_station = NULL;
+ if (search_station_name(&p_station,call_sign,1)) {
+ // Check whether we have a posit yet for this station
+ if ( (p_station->coord_lat == 0l)
+ && (p_station->coord_lon == 0l) ) {
+ distance = 0.0;
+ }
+ else {
+ d_lat = convert_lat_l2d(p_station->coord_lat);
+ d_lon = convert_lon_l2d(p_station->coord_lon);
+
+ value = (float)calculate_distance(_gps.lat, _gps.lon, d_lat, d_lon);
+
+ snprintf(course_deg, course_len,
+ "%.01f°",
+ calculate_bearing(_gps.lat, _gps.lon, d_lat, d_lon));
+
+ if(_units == UNITS_KM)
+ distance = value * 1.852; // nautical miles to km
+ else if(_units == UNITS_MI)
+ distance = value * 1.15078; // nautical miles to miles
+ else if(_units == UNITS_NM)
+ distance = value;
+ else
+ distance = 0.0; // Should be unreachable
+
+
+ }
+ }
+ else { // Station not found
+ distance = 0.0;
+ }
+
+
+ return(distance);
+}
+
+
+
+void pad_callsign(char *callsignout, char *callsignin) {
+ int i,l;
+
+ l=(int)strlen(callsignin);
+ for(i=0; i<9;i++) {
+ if(i<l) {
+ if(isalnum((int)callsignin[i]) || callsignin[i]=='-') {
+ callsignout[i]=callsignin[i];
+ }
+ else {
+ callsignout[i] = ' ';
+ }
+ }
+ else {
+ callsignout[i] = ' ';
+ }
+ }
+ callsignout[i] = '\0';
+}
+/////////// TX functionality
+
+
+
+// This routine changes callsign chars to proper uppercase chars or
+// numerals, fixes the callsign to six bytes, shifts the letters left by
+// one bit, and puts the SSID number into the proper bits in the seventh
+// byte. The callsign as processed is ready for inclusion in an
+// AX.25 header.
+//
+void fix_up_callsign(unsigned char *data, int data_size) {
+ unsigned char new_call[8] = " "; // Start with seven spaces
+ int ssid = 0;
+ int i;
+ int j = 0;
+ int digipeated_flag = 0;
+
+
+ // Check whether we've digipeated through this callsign yet.
+ if (strstr((const char *)data,"*") != 0) {
+ digipeated_flag++;
+ }
+
+ // Change callsign to upper-case and pad out to six places with
+ // space characters.
+ for (i = 0; i < (int)strlen((const char *)data); i++) {
+ toupper(data[i]);
+
+ if (data[i] == '-') { // Stop at '-'
+ break;
+ }
+ else if (data[i] == '*') {
+ }
+ else {
+ new_call[j++] = data[i];
+ }
+ }
+ new_call[7] = '\0';
+
+ //fprintf(stderr,"new_call:(%s)\n",new_call);
+
+ // Handle SSID. 'i' should now be pointing at a dash or at the
+ // terminating zero character.
+ if ( (i < (int)strlen((const char *)data)) && (data[i++] == '-') ) { // We might have an SSID
+ if (data[i] != '\0')
+ ssid = atoi((const char *)&data[i]);
+// ssid = data[i++] - 0x30; // Convert from ascii to int
+// if (data[i] != '\0')
+// ssid = (ssid * 10) + (data[i] - 0x30);
+ }
+
+//fprintf(stderr,"SSID:%d\t",ssid);
+
+ if (ssid >= 0 && ssid <= 15) {
+ new_call[6] = ssid | 0x30; // Set 2 reserved bits
+ }
+ else { // Whacko SSID. Set it to zero
+ new_call[6] = 0x30; // Set 2 reserved bits
+ }
+
+ if (digipeated_flag) {
+ new_call[6] = new_call[6] | 0x40; // Set the 'H' bit
+ }
+
+ // Shift each byte one bit to the left
+ for (i = 0; i < 7; i++) {
+ new_call[i] = new_call[i] << 1;
+ new_call[i] = new_call[i] & 0xfe;
+ }
+
+//fprintf(stderr,"Last:%0x\n",new_call[6]);
+
+ // Write over the top of the input string with the newly
+ // formatted callsign
+ xastir_snprintf((char *)data,
+ data_size,
+ "%s",
+ new_call);
+}
+
+
+
+
+// Create an AX25 frame and then turn it into a KISS packet. Dump
+// it into the transmit queue.
+//
+void send_ax25_frame(TAprsPort port, gchar *source, gchar *destination, gchar *path, gchar *data) {
+ unsigned char temp_source[15];
+ unsigned char temp_dest[15];
+ unsigned char temp[15];
+ unsigned char control[2], pid[2];
+ unsigned char transmit_txt[MAX_LINE_SIZE*2];
+ unsigned char transmit_txt2[MAX_LINE_SIZE*2];
+ unsigned char c;
+ int i, j;
+ int erd;
+ int write_in_pos_hold;
+
+
+//fprintf(stderr,"KISS String:%s>%s,%s:%s\n",source,destination,path,data);
+
+ // Check whether transmits are disabled globally
+// if (transmit_disable) {
+// return;
+// }
+
+ // Check whether transmit has been enabled for this interface.
+ // If not, get out while the gettin's good.
+// if (devices[port].transmit_data != 1) {
+// return;
+// }
+
+ transmit_txt[0] = '\0';
+
+ // Format the destination callsign
+ snprintf((char *)temp_dest,
+ sizeof(temp_dest),
+ "%s",
+ destination);
+ fix_up_callsign(temp_dest, sizeof(temp_dest));
+
+ snprintf((char *)transmit_txt,
+ sizeof(transmit_txt),
+ "%s",
+ temp_dest);
+
+ // Format the source callsign
+ snprintf((char *)temp_source,
+ sizeof(temp_source),
+ "%s",
+ source);
+ fix_up_callsign(temp_source, sizeof(temp_source));
+
+ strncat((char *)transmit_txt,
+ (char *)temp_source,
+ sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+ // Break up the path into individual callsigns and send them one
+ // by one to fix_up_callsign(). If we get passed an empty path,
+ // we merely skip this section and no path gets added to
+ // "transmit_txt".
+ j = 0;
+ temp[0] = '\0'; // Start with empty path
+ if ( (path != NULL) && (strlen(path) != 0) ) {
+ while (path[j] != '\0') {
+ i = 0;
+ while ( (path[j] != ',') && (path[j] != '\0') ) {
+ temp[i++] = path[j++];
+ }
+ temp[i] = '\0';
+
+ if (path[j] == ',') { // Skip over comma
+ j++;
+ }
+
+ fix_up_callsign(temp, sizeof(temp));
+ strncat((char *)transmit_txt,
+ (char *)temp,
+ sizeof(transmit_txt) - strlen((char *)transmit_txt));
+ }
+ }
+
+ // Set the end-of-address bit on the last callsign in the
+ // address field
+ transmit_txt[strlen((const char *)transmit_txt) - 1] |= 0x01;
+
+ // Add the Control byte
+ control[0] = 0x03;
+ control[1] = '\0';
+ strncat((char *)transmit_txt,
+ (char *)control,
+ sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+ // Add the PID byte
+ pid[0] = 0xf0;
+ pid[1] = '\0';
+ strncat((char *)transmit_txt,
+ (char *)pid,
+ sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+ // Append the information chars
+ strncat((char *)transmit_txt,
+ data,
+ sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+ //fprintf(stderr,"%s\n",transmit_txt);
+
+ // Add the KISS framing characters and do the proper escapes.
+ j = 0;
+ transmit_txt2[j++] = KISS_FEND;
+
+ // Note: This byte is where different interfaces would be
+ // specified:
+ transmit_txt2[j++] = 0x00;
+
+ for (i = 0; i < (int)strlen((const char *)transmit_txt); i++) {
+ c = transmit_txt[i];
+ if (c == KISS_FEND) {
+ transmit_txt2[j++] = KISS_FESC;
+ transmit_txt2[j++] = KISS_TFEND;
+ }
+ else if (c == KISS_FESC) {
+ transmit_txt2[j++] = KISS_FESC;
+ transmit_txt2[j++] = KISS_TFESC;
+ }
+ else {
+ transmit_txt2[j++] = c;
+ }
+ }
+ transmit_txt2[j++] = KISS_FEND;
+
+ // Terminate the string, but don't increment the 'j' counter.
+ // We don't want to send the NULL byte out the KISS interface,
+ // just make sure the string is terminated in all cases.
+ //
+ transmit_txt2[j] = '\0';
+
+//-------------------------------------------------------------------
+// Had to snag code from port_write_string() below because our string
+// needs to have 0x00 chars inside it. port_write_string() can't
+// handle that case. It's a good thing the transmit queue stuff
+// could handle it.
+//-------------------------------------------------------------------
+
+ erd = 0;
+
+
+
+
+ port_write_string(
+ transmit_txt2,
+ j/*length*/,
+ APRS_PORT_TTY);
+
+
+
+/*
+ g_mutex_lock (_write_buffer[port].write_lock);
+ {
+
+ write_in_pos_hold = _write_buffer[port].write_in_pos;
+
+ for (i = 0; i < j && !erd; i++) {
+ _write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++] = transmit_txt2[i];
+ if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
+ _write_buffer[port].write_in_pos = 0;
+
+ if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {
+
+ // clear this restore original write_in pos and dump this string
+ _write_buffer[port].write_in_pos = write_in_pos_hold;
+ _write_buffer[port].errors++;
+ erd = 1;
+ }
+ }
+
+ g_mutex_unlock (_write_buffer[port].write_lock);
+ }
+*/
+}
+
+
+
+
+// convert latitude from long to string
+// Input is in Xastir coordinate system
+//
+// CONVERT_LP_NOSP = DDMM.MMN
+// CONVERT_HP_NOSP = DDMM.MMMN
+// CONVERT_VHP_NOSP = DDMM.MMMMN
+// CONVERT_LP_NORMAL = DD MM.MMN
+// CONVERT_HP_NORMAL = DD MM.MMMN
+// CONVERT_UP_TRK = NDD MM.MMMM
+// CONVERT_DEC_DEG = DD.DDDDDN
+// CONVERT_DMS_NORMAL = DD MM SS.SN
+// CONVERT_DMS_NORMAL_FORMATED = DD'MM'SS.SN
+// CONVERT_HP_NORMAL_FORMATED = DD'MM.MMMMN
+//
+void convert_lat_l2s(long lat, char *str, int str_len, int type) {
+ char ns;
+ float deg, min, sec;
+ int ideg, imin;
+ long temp;
+
+
+ str[0] = '\0';
+ deg = (float)(lat - 32400000l) / 360000.0;
+
+ // Switch to integer arithmetic to avoid floating-point
+ // rounding errors.
+ temp = (long)(deg * 100000);
+
+ ns = 'S';
+ if (temp <= 0) {
+ ns = 'N';
+ temp = labs(temp);
+ }
+
+ ideg = (int)temp / 100000;
+ min = (temp % 100000) * 60.0 / 100000.0;
+
+ // Again switch to integer arithmetic to avoid floating-point
+ // rounding errors.
+ temp = (long)(min * 1000);
+ imin = (int)(temp / 1000);
+ sec = (temp % 1000) * 60.0 / 1000.0;
+
+ switch (type) {
+
+ case(CONVERT_LP_NOSP): /* do low P w/no space */
+ xastir_snprintf(str,
+ str_len,
+ "%02d%05.2f%c",
+ ideg,
+// min+0.001, // Correct possible unbiased rounding
+ min,
+ ns);
+ break;
+
+ case(CONVERT_LP_NORMAL): /* do low P normal */
+ xastir_snprintf(str,
+ str_len,
+ "%02d %05.2f%c",
+ ideg,
+// min+0.001, // Correct possible unbiased rounding
+ min,
+ ns);
+ break;
+
+ case(CONVERT_HP_NOSP): /* do HP w/no space */
+ xastir_snprintf(str,
+ str_len,
+ "%02d%06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased rounding
+ min,
+ ns);
+ break;
+
+ case(CONVERT_VHP_NOSP): /* do Very HP w/no space */
+ xastir_snprintf(str,
+ str_len,
+ "%02d%07.4f%c",
+ ideg,
+// min+0.00001, // Correct possible unbiased rounding
+ min,
+ ns);
+ break;
+
+ case(CONVERT_UP_TRK): /* for tracklog files */
+ xastir_snprintf(str,
+ str_len,
+ "%c%02d %07.4f",
+ ns,
+ ideg,
+// min+0.00001); // Correct possible unbiased rounding
+ min);
+ break;
+
+ case(CONVERT_DEC_DEG):
+ xastir_snprintf(str,
+ str_len,
+ "%08.5f%c",
+// (ideg+min/60.0)+0.000001, // Correct possible unbiased rounding
+ ideg+min/60.0,
+ ns);
+ break;
+
+ case(CONVERT_DMS_NORMAL):
+ xastir_snprintf(str,
+ str_len,
+ "%02d %02d %04.1f%c",
+ ideg,
+ imin,
+// sec+0.01, // Correct possible unbiased rounding
+ sec,
+ ns);
+ break;
+
+ case(CONVERT_DMS_NORMAL_FORMATED):
+ xastir_snprintf(str,
+ str_len,
+ "%02d°%02d\'%04.1f%c",
+ ideg,
+ imin,
+// sec+0.01, // Correct possible unbiased rounding
+ sec,
+ ns);
+ break;
+
+ case(CONVERT_HP_NORMAL_FORMATED):
+ xastir_snprintf(str,
+ str_len,
+ "%02d°%06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased roundin
+ min,
+ ns);
+ break;
+
+ case(CONVERT_HP_NORMAL):
+ default: /* do HP normal */
+ xastir_snprintf(str,
+ str_len,
+ "%02d %06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased rounding
+ min,
+ ns);
+ break;
+ }
+}
+
+
+
+
+
+// convert longitude from long to string
+// Input is in Xastir coordinate system
+//
+// CONVERT_LP_NOSP = DDDMM.MME
+// CONVERT_HP_NOSP = DDDMM.MMME
+// CONVERT_VHP_NOSP = DDDMM.MMMME
+// CONVERT_LP_NORMAL = DDD MM.MME
+// CONVERT_HP_NORMAL = DDD MM.MMME
+// CONVERT_UP_TRK = EDDD MM.MMMM
+// CONVERT_DEC_DEG = DDD.DDDDDE
+// CONVERT_DMS_NORMAL = DDD MM SS.SN
+// CONVERT_DMS_NORMAL_FORMATED = DDD'MM'SS.SN
+//
+void convert_lon_l2s(long lon, char *str, int str_len, int type) {
+ char ew;
+ float deg, min, sec;
+ int ideg, imin;
+ long temp;
+
+ str[0] = '\0';
+ deg = (float)(lon - 64800000l) / 360000.0;
+
+ // Switch to integer arithmetic to avoid floating-point rounding
+ // errors.
+ temp = (long)(deg * 100000);
+
+ ew = 'E';
+ if (temp <= 0) {
+ ew = 'W';
+ temp = labs(temp);
+ }
+
+ ideg = (int)temp / 100000;
+ min = (temp % 100000) * 60.0 / 100000.0;
+
+ // Again switch to integer arithmetic to avoid floating-point
+ // rounding errors.
+ temp = (long)(min * 1000);
+ imin = (int)(temp / 1000);
+ sec = (temp % 1000) * 60.0 / 1000.0;
+
+ switch(type) {
+
+ case(CONVERT_LP_NOSP): /* do low P w/nospacel */
+ xastir_snprintf(str,
+ str_len,
+ "%03d%05.2f%c",
+ ideg,
+// min+0.001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+
+ case(CONVERT_LP_NORMAL): /* do low P normal */
+ xastir_snprintf(str,
+ str_len,
+ "%03d %05.2f%c",
+ ideg,
+// min+0.001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+
+ case(CONVERT_HP_NOSP): /* do HP w/nospace */
+ xastir_snprintf(str,
+ str_len,
+ "%03d%06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+
+ case(CONVERT_VHP_NOSP): /* do Very HP w/nospace */
+ xastir_snprintf(str,
+ str_len,
+ "%03d%07.4f%c",
+ ideg,
+// min+0.00001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+
+ case(CONVERT_UP_TRK): /* for tracklog files */
+ xastir_snprintf(str,
+ str_len,
+ "%c%03d %07.4f",
+ ew,
+ ideg,
+// min+0.00001); // Correct possible unbiased rounding
+ min);
+ break;
+
+ case(CONVERT_DEC_DEG):
+ xastir_snprintf(str,
+ str_len,
+ "%09.5f%c",
+// (ideg+min/60.0)+0.000001, // Correct possible unbiased rounding
+ ideg+min/60.0,
+ ew);
+ break;
+
+ case(CONVERT_DMS_NORMAL):
+ xastir_snprintf(str,
+ str_len,
+ "%03d %02d %04.1f%c",
+ ideg,
+ imin,
+// sec+0.01, // Correct possible unbiased rounding
+ sec,
+ ew);
+ break;
+
+ case(CONVERT_DMS_NORMAL_FORMATED):
+ xastir_snprintf(str,
+ str_len,
+ "%03d°%02d\'%04.1f%c",
+ ideg,
+ imin,
+// sec+0.01, // Correct possible unbiased rounding
+ sec,
+ ew);
+ break;
+
+ case(CONVERT_HP_NORMAL_FORMATED):
+ xastir_snprintf(str,
+ str_len,
+ "%03d°%06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+
+ case(CONVERT_HP_NORMAL):
+ default: /* do HP normal */
+ xastir_snprintf(str,
+ str_len,
+ "%03d %06.3f%c",
+ ideg,
+// min+0.0001, // Correct possible unbiased rounding
+ min,
+ ew);
+ break;
+ }
+}
+
+
+
+
+
+
+/*************************************************************************/
+/* output_lat - format position with position_amb_chars for transmission */
+/*************************************************************************/
+/*
+char *output_lat(char *in_lat, int comp_pos) {
+ int i,j;
+ int position_amb_chars = 0;
+//fprintf(stderr,"in_lat:%s\n", in_lat);
+
+ if (!comp_pos) {
+ // Don't do this as it results in truncation!
+ //in_lat[7]=in_lat[8]; // Shift N/S down for transmission
+ }
+ else if (position_amb_chars>0) {
+ in_lat[7]='0';
+ }
+
+ j=0;
+ if (position_amb_chars>0 && position_amb_chars<5) {
+ for (i=6;i>(6-position_amb_chars-j);i--) {
+ if (i==4) {
+ i--;
+ j=1;
+ }
+ if (!comp_pos) {
+ in_lat[i]=' ';
+ } else
+ in_lat[i]='0';
+ }
+ }
+
+ if (!comp_pos) {
+ in_lat[8] = '\0';
+ }
+
+ return(in_lat);
+}
+*/
+
+
+
+/**************************************************************************/
+/* output_long - format position with position_amb_chars for transmission */
+/**************************************************************************/
+/*
+char *output_long(char *in_long, int comp_pos) {
+ int i,j;
+int position_amb_chars = 0;
+//fprintf(stderr,"in_long:%s\n", in_long);
+
+ if (!comp_pos) {
+ // Don't do this as it results in truncation!
+ //in_long[8]=in_long[9]; // Shift e/w down for transmission
+ }
+ else if (position_amb_chars>0) {
+ in_long[8]='0';
+ }
+
+ j=0;
+ if (position_amb_chars>0 && position_amb_chars<5) {
+ for (i=7;i>(7-position_amb_chars-j);i--) {
+ if (i==5) {
+ i--;
+ j=1;
+ }
+ if (!comp_pos) {
+ in_long[i]=' ';
+ } else
+ in_long[i]='0';
+ }
+ }
+
+ if (!comp_pos)
+ in_long[9] = '\0';
+
+ return(in_long);
+}
+*/
+
+
+
+//***********************************************************
+// output_my_aprs_data
+// This is the function responsible for sending out my own
+// posits. The next function below this one handles objects,
+// messages and the like (output_my_data).
+//***********************************************************/
+
+/*
+void create_output_lat_long(gchar *my_output_lat, gchar *my_output_long )
+{
+ _aprs_transmit_compressed_posit = FALSE;
+ gchar *my_lat = g_strdup_printf("%lf", _gps.lat);
+ gchar *my_long = g_strdup_printf("%lf", _gps.lon);
+
+
+ // Format latitude string for transmit later
+ if (_aprs_transmit_compressed_posit) { // High res version
+ // TODO - enable compressed beacon
+ snprintf(my_output_lat,
+ sizeof(my_output_lat),
+ "%s",
+ my_lat);
+
+ }
+ else { // Create a low-res version of the latitude string
+ long my_temp_lat;
+ char temp_data[20];
+
+ // Convert to long
+ my_temp_lat = convert_lat_s2l(my_lat);
+
+ // Convert to low-res string
+ convert_lat_l2s(my_temp_lat,
+ temp_data,
+ sizeof(temp_data),
+ CONVERT_LP_NORMAL);
+
+ snprintf(my_output_lat,
+ sizeof(my_output_lat),
+ "%c%c%c%c.%c%c%c",
+ temp_data[0],
+ temp_data[1],
+ temp_data[3],
+ temp_data[4],
+ temp_data[6],
+ temp_data[7],
+ temp_data[8]);
+ }
+
+ (void)output_lat(my_output_lat, _aprs_transmit_compressed_posit);
+
+ // Format longitude string for transmit later
+ if (_aprs_transmit_compressed_posit) { // High res version
+ snprintf(my_output_long,
+ sizeof(my_output_long),
+ "%s",
+ my_long);
+ }
+ else { // Create a low-res version of the longitude string
+ long my_temp_long;
+ char temp_data[20];
+
+ // Convert to long
+ my_temp_long = convert_lon_s2l(my_long);
+
+ // Convert to low-res string
+ convert_lon_l2s(my_temp_long,
+ temp_data,
+ sizeof(temp_data),
+ CONVERT_LP_NORMAL);
+
+ snprintf(my_output_long,
+ sizeof(my_output_long),
+ "%c%c%c%c%c.%c%c%c",
+ temp_data[0],
+ temp_data[1],
+ temp_data[2],
+ temp_data[4],
+ temp_data[5],
+ temp_data[7],
+ temp_data[8],
+ temp_data[9]);
+ }
+
+ (void)output_long(my_output_long, _aprs_transmit_compressed_posit);
+
+}
+
+
+void output_my_aprs_data_tty() {
+//TODO
+ return ;
+
+ gchar my_output_lat[MAX_LAT];
+ gchar my_output_long[MAX_LONG];
+// gchar header_txt[MAX_LINE_SIZE+5];
+// gchar header_txt_save[MAX_LINE_SIZE+5];
+ gchar path_txt[MAX_LINE_SIZE+5];
+ gchar data_txt[MAX_LINE_SIZE+5];
+ gchar temp[MAX_LINE_SIZE+5];
+ gchar *unproto_path = "";
+ gchar data_txt2[5];
+ struct tm *day_time;
+ gchar my_pos[256];
+ gchar output_net[256];
+ gchar output_phg[10];
+ gchar output_cs[10];
+ gchar output_alt[20];
+ gchar output_brk[3];
+ int ok;
+ gchar my_comment_tx[APRS_MAX_COMMENT+1];
+ int interfaces_ok_for_transmit = 0;
+ gchar my_phg[10];
+
+ time_t sec;
+ gchar header_txt[MAX_LINE_SIZE+5];
+ gchar header_txt_save[MAX_LINE_SIZE+5];
+
+ gchar data_txt_save[MAX_LINE_SIZE+5];
+
+
+ if (!(port_data.status == DEVICE_UP
+ && _aprs_tty_enable && _aprs_enable && _aprs_enable_tty_tx)) return ;
+
+ header_txt_save[0] = '\0';
+ data_txt_save[0] = '\0';
+
+ sec = sec_now();
+
+ my_phg[0] = '\0';
+
+ create_output_lat_long(my_output_lat, my_output_long);
+
+
+ // First send any header/path info we might need out the port,
+ // set up TNC's to the proper mode, etc.
+ ok = 1;
+
+ // clear this for a TNC
+ output_net[0] = '\0';
+
+ // Set my call sign
+ // The leading \r is sent to normal serial TNCs. The only
+ // reason for it is that some folks' D700s are getting
+ // garbage in the input buffer, and the result is the mycall
+ // line is rejected. The \r at the beginning clears out the
+ // junk and lets it go through. But data_out_ax25 tries to
+ // parse the MYCALL line, and the presence of a leading \r
+ // breaks it.
+ snprintf(header_txt,
+ sizeof(header_txt),
+ "%c%s %s\r",
+ '\3',
+ ((port_data.device_type !=DEVICE_AX25_TNC)?
+ "\rMYCALL":"MYCALL"),
+ _aprs_mycall);
+
+ // Send the callsign out to the TNC only if the interface is up and tx is enabled???
+ // We don't set it this way for KISS TNC interfaces.
+ if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+ && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+ && (port_data.status == DEVICE_UP)
+ //&& (devices.transmit_data == 1)
+ //&& !transmit_disable
+ //&& !posit_tx_disable
+ ) {
+ port_write_string(header_txt, APRS_PORT_TTY);
+//send_line(gchar* text, gint text_len, TAprsPort port)
+ }
+
+ // Set unproto path: Get next unproto path in
+ // sequence.
+
+ snprintf(header_txt,
+ sizeof(header_txt),
+ "%c%s %s VIA %s\r",
+ '\3',
+ "UNPROTO",
+ VERSIONFRM,
+ _aprs_unproto_path);
+
+ snprintf(header_txt_save,
+ sizeof(header_txt_save),
+ "%s>%s,%s:",
+ _aprs_mycall,
+ VERSIONFRM,
+ _aprs_unproto_path);
+
+ snprintf(path_txt,
+ sizeof(path_txt),
+ "%s",
+ _aprs_unproto_path);
+
+
+ // Send the header data to the TNC. This sets the
+ // unproto path that'll be used by the next packet.
+ // We don't set it this way for KISS TNC interfaces.
+ if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+ && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+ && (port_data.status == DEVICE_UP)
+ //&& (devices.transmit_data == 1)
+ //&& !transmit_disable
+ //&& !posit_tx_disable
+ ) {
+ port_write_string(header_txt, APRS_PORT_TTY);
+ }
+
+
+ // Set converse mode. We don't need to do this for
+ // KISS TNC interfaces. One european TNC (tnc2-ui)
+ // doesn't accept "conv" but does accept the 'k'
+ // command. A Kantronics KPC-2 v2.71 TNC accepts
+ // the "conv" command but not the 'k' command.
+ // Figures!
+ //
+ snprintf(header_txt, sizeof(header_txt), "%c%s\r", '\3', APRS_CONVERSE_MODE);
+
+ if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+ && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+ && (port_data.status == DEVICE_UP)
+ //&& (devices.transmit_data == 1)
+ //&& !transmit_disable
+ //&& !posit_tx_disable
+ ) {
+ port_write_string(header_txt, APRS_PORT_TTY);
+ }
+ // sleep(1);
+
+
+
+ // Set up some more strings for later transmission
+
+ // send station info
+ output_cs[0] = '\0';
+ output_phg[0] = '\0';
+ output_alt[0] = '\0';
+ output_brk[0] = '\0';
+
+
+ if (_aprs_transmit_compressed_posit)
+ {
+ // TOOD - enable compressed beacon support
+
+// snprintf(my_pos,
+// sizeof(my_pos),
+// "%s",
+// compress_posit(my_output_lat,
+// my_group,
+// my_output_long,
+// my_symbol,
+// my_last_course,
+// my_last_speed, // In knots
+// my_phg));
+
+ }
+ else { // standard non compressed mode
+ snprintf(my_pos,
+ sizeof(my_pos),
+ "%s%c%s%c",
+ my_output_lat,
+ _aprs_beacon_group,
+ my_output_long,
+ _aprs_beacon_symbol);
+ // get PHG, if used for output
+ if (strlen(my_phg) >= 6)
+ snprintf(output_phg,
+ sizeof(output_phg),
+ "%s",
+ my_phg);
+
+ // get CSE/SPD, Always needed for output even if 0
+ snprintf(output_cs,
+ sizeof(output_cs),
+ "%03d/%03d/",
+ _gps.heading,
+ _gps.speed); // Speed in knots
+
+ // get altitude
+// TODO
+// if (my_last_altitude_time > 0)
+// snprintf(output_alt,
+// sizeof(output_alt),
+// "A=%06ld/",
+// my_last_altitude);
+ }
+
+
+ // APRS_MOBILE LOCAL TIME
+
+// TODO
+// if((strlen(output_cs) < 8) && (my_last_altitude_time > 0)) {
+// xastir_snprintf(output_brk,
+// sizeof(output_brk),
+// "/");
+// }
+
+ day_time = localtime(&sec);
+
+ snprintf(data_txt_save,
+ sizeof(data_txt_save),
+ "@%02d%02d%02d/%s%s%s%s%s",
+ day_time->tm_mday,
+ day_time->tm_hour,
+ day_time->tm_min,
+ my_pos,
+ output_cs,
+ output_brk,
+ output_alt,
+ my_comment_tx);
+
+//WE7U2:
+ // Truncate at max length for this type of APRS
+ // packet.
+ if (_aprs_transmit_compressed_posit) {
+ if (strlen(data_txt_save) > 61) {
+ data_txt_save[61] = '\0';
+ }
+ }
+ else { // Uncompressed lat/long
+ if (strlen(data_txt_save) > 70) {
+ data_txt_save[70] = '\0';
+ }
+ }
+
+ // Add '\r' onto end.
+ strncat(data_txt_save, "\r", 1);
+
+ snprintf(data_txt,
+ sizeof(data_txt),
+ "%s%s",
+ output_net,
+ data_txt_save);
+
+
+ if (ok) {
+ // Here's where the actual transmit of the posit occurs. The
+ // transmit string has been set up in "data_txt" by this point.
+
+ // If transmit or posits have been turned off, don't transmit posit
+ if ( (port_data.status == DEVICE_UP)
+// && (devices.transmit_data == 1)
+// && !transmit_disable
+// && !posit_tx_disable
+ ) {
+
+ interfaces_ok_for_transmit++;
+
+// WE7U: Change so that path is passed as well for KISS TNC
+// interfaces: header_txt_save would probably be the one to pass,
+// or create a new string just for KISS TNC's.
+
+ if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+ || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+
+ // Note: This one has callsign & destination in the string
+
+ // Transmit the posit out the KISS interface
+ send_ax25_frame(APRS_PORT_TTY,
+ _aprs_mycall, // source
+ VERSIONFRM, // destination
+ path_txt, // path
+ data_txt); // data
+ }
+ else { // Not a Serial KISS TNC interface
+
+
+ port_write_string(data_txt, APRS_PORT_TTY); // Transmit the posit
+ }
+
+
+ // Put our transmitted packet into the Incoming Data
+ // window as well. This way we can see both sides of a
+ // conversation. data_port == -1 for x_spider port,
+ // normal interface number otherwise. -99 to get a "**"
+ // display meaning all ports.
+ //
+ // For packets that we're igating we end up with a CR or
+ // LF on the end of them. Remove that so the display
+ // looks nice.
+ //snprintf(temp,
+ // sizeof(temp),
+ // "%s>%s,%s:%s",
+ // my_callsign,
+ // VERSIONFRM,
+ // unproto_path,
+ // data_txt);
+ //makePrintable(temp);
+ //packet_data_add("TX ", temp, port);
+
+ }
+ } // End of posit transmit: "if (ok)"
+}
+*/
+
+void create_output_pos_packet(TAprsPort port, gchar **packet, int *length)
+{
+
+// gchar encodedPos[MAX_LINE_SIZE];
+
+ if(_aprs_transmit_compressed_posit)
+ {
+ // TODO
+ }
+ else
+ {
+ //!5122.09N/00008.42W&APRS4R IGATE RUNNING ON NSLU2
+
+ // For now just use a simple packet
+
+ gchar slat[10];
+ gchar slon[10];
+
+ gdouble pos = (_gps.lat > 0 ? _gps.lat : _gps.lat*-1);
+
+ gdouble min = (pos - (int)pos)*60.0;
+ sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,
+ ((min - (int)min)*100.0) );
+
+ pos = (_gps.lon > 0 ? _gps.lon : _gps.lon*-1);
+
+ min = (pos - (int)pos)*60.0;
+ sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,
+ ((min - (int)min)*100.0) );
+
+ *packet = g_strdup_printf(
+ //snprintf(encodedPos, MAX_LINE_SIZE,
+ "%c%s%c%c%s%c%c%s%c",
+ '=',
+ slat,
+ (_gps.lat > 0 ? 'N' : 'S'),
+ _aprs_beacon_group,
+ slon,
+ (_gps.lon > 0 ? 'E' : 'W'),
+ _aprs_beacon_symbol,
+ (port == APRS_PORT_INET ? _aprs_inet_beacon_comment : _aprs_beacon_comment),
+ (char)0
+ );
+ }
+
+/* *packet = g_strdup_printf(
+ "%s>%s,%s:%s\r\n",
+ _aprs_mycall,
+ VERSIONFRM,
+ _aprs_unproto_path,
+ encodedPos);
+*/
+ *length = strlen(*packet);
+}
+
+
+void send_packet(TAprsPort port, gchar* to_call, gchar* path, gchar* packet, gint packet_length)
+{
+ if(port == APRS_PORT_INET
+ || !(port_data.device_type == DEVICE_SERIAL_KISS_TNC
+ || port_data.device_type == DEVICE_SERIAL_MKISS_TNC) )
+ {
+ gchar *packet_header
+ = g_strdup_printf(
+ "%s>%s,%s:",
+ _aprs_mycall,
+ to_call,
+ path);
+
+ gchar *full_packet = g_strdup_printf("%s%s\r\n", packet_header, packet);
+
+
+ send_line(full_packet, strlen(packet_header)+packet_length+2, port);
+ }
+ else
+ {
+ send_ax25_frame(port, _aprs_mycall, to_call, path, packet);
+ }
+
+}
+
+void output_my_aprs_data(TAprsPort port) {
+
+
+ gchar *packet;
+ int length = 0;
+
+
+ //create_output_lat_long(my_output_lat, my_output_long);
+ create_output_pos_packet(port, &packet, &length);
+
+
+ send_packet(port, VERSIONFRM, _aprs_unproto_path, packet, length);
+
+}
+
+/////////// TTY functionality
+
+int serial_init();
+
+static void thread_read_tty()
+{
+ set_aprs_tty_conn_state(RCVR_UP);
+
+ serial_init ();
+
+ port_read();
+}
+
+gboolean aprs_tty_connect()
+{
+ printf("%s(%d)\n", __PRETTY_FUNCTION__, _aprs_tty_state);
+
+ if(_aprs_tty_enable && _aprs_tty_state == RCVR_OFF)
+ {
+ set_aprs_tty_conn_state(RCVR_DOWN);
+
+ // Lock/Unlock the mutex to ensure that the thread doesn't
+ // start until _gps_thread is set.
+ g_mutex_lock(_aprs_tty_init_mutex);
+
+ _aprs_tty_thread = g_thread_create((GThreadFunc)thread_read_tty,
+ NULL, TRUE, NULL); // Joinable.
+
+ g_mutex_unlock(_aprs_tty_init_mutex);
+ }
+
+ vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+ return FALSE;
+
+}
+
+void aprs_tty_disconnect()
+{
+ gboolean exit_now = FALSE;
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ GThread *my_thread = g_thread_self();
+
+ if(my_thread == _aprs_tty_thread)
+ {
+ exit_now = TRUE;
+ }
+
+ _aprs_tty_thread = NULL;
+
+ if(_window)
+ set_aprs_tty_conn_state(RCVR_OFF);
+ vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+
+ if(exit_now) exit(0);
+
+}
+
+
+//***********************************************************
+// port_write_string()
+//
+// port is port# used
+// data is the string to write
+//***********************************************************
+
+void port_write_string(gchar *data, gint len, TAprsPort port) {
+ int i,erd, retval;
+ int write_in_pos_hold;
+
+ if (data == NULL)
+ return;
+
+ if (data[0] == '\0')
+ return;
+
+
+
+ if(port == APRS_PORT_TTY)
+ {
+
+ if(g_mutex_trylock (_write_buffer[port].write_lock))
+ {
+ //fprintf(stderr, "TTY Write... ");
+ retval = (int)write(port_data.channel,
+ data,
+ len);
+ //fprintf(stderr, "done... ");
+ g_mutex_unlock (_write_buffer[port].write_lock);
+ //fprintf(stderr, "Unlocked\n");
+ }
+ else
+ fprintf(stderr, "Failed to get lock\n");
+ }
+ else
+ {
+ send_line(data, len, port);
+ }
+}
+
+
+gboolean send_line(gchar* text, gint text_len, TAprsPort port)
+{
+ if(APRS_PORT_INET == port && !_aprs_enable_inet_tx) return FALSE;
+ else if (APRS_PORT_TTY == port && !_aprs_enable_tty_tx) return FALSE;
+
+ if(APRS_PORT_TTY == port)
+ {
+
+ }
+ gboolean error = FALSE;
+ gint i;
+ gint write_in_pos_hold = _write_buffer[port].write_in_pos;
+
+ // Lock the mutex
+ g_mutex_lock(_write_buffer[port].write_lock);
+
+ for (i = 0; i < text_len && !error; i++) {
+ _write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++]
+ = text[i];
+
+ if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
+ _write_buffer[port].write_in_pos = 0;
+
+ if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {
+ fprintf(stderr,"Port %d Buffer overrun\n",port);
+
+ /* clear this restore original write_in pos and dump this string */
+ _write_buffer[port].write_in_pos = write_in_pos_hold;
+ _write_buffer[port].errors++;
+ error = TRUE;
+ }
+ }
+
+ g_mutex_unlock(_write_buffer[port].write_lock);
+
+ return error;
+}
+
+
+gboolean aprs_send_beacon_inet()
+{
+ aprs_send_beacon(APRS_PORT_INET);
+
+}
+gboolean aprs_send_beacon(TAprsPort port)
+{
+ if(_aprs_enable)
+ {
+ output_my_aprs_data(port);
+
+ //fprintf(stderr, "Beacon sent\n" );
+ }
+
+ return TRUE;
+}
+
+#endif //INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_H
+#define MAEMO_MAPPER_APRS_H
+
+#include "types.h"
+#include <termios.h>
+
+#define MAX_LINE_SIZE 512
+#define MAX_STATUS_LINES 20
+#define MAX_COMMENT_LINES 20
+#define EARTH_RADIUS_METERS 6378138.0
+#define MAX_TACTICAL_CALL 20
+#define TACTICAL_HASH_SIZE 1024
+
+
+#define START_STR " }"
+#define LBRACE '{'
+#define RBRACE '}'
+
+
+
+#define MAX_DEVICE_NAME 128
+#define MAX_DEVICE_BUFFER_UNTIL_BINARY_SWITCH 700
+#define MAX_DEVICE_HOSTNM 40
+#define MAX_DEVICE_HOSTPW 40
+
+// KISS Protocol Special Characters & Commands:
+#define KISS_FEND 0xc0 // Frame End
+#define KISS_FESC 0xdb // Frame Escape
+#define KISS_TFEND 0xdc // Transposed Frame End
+#define KISS_TFESC 0xdd // Transposed Frame Escape
+#define KISS_DATA 0x00
+#define KISS_TXDELAY 0x01
+#define KISS_PERSISTENCE 0x02
+#define KISS_SLOTTIME 0x03
+#define KISS_TXTAIL 0x04
+#define KISS_FULLDUPLEX 0x05
+#define KISS_SETHARDWARE 0x06
+#define KISS_RETURN 0xff
+
+
+enum Device_Types {
+ DEVICE_NONE,
+ DEVICE_SERIAL_TNC,
+ DEVICE_NET_STREAM,
+ DEVICE_AX25_TNC,
+ DEVICE_SERIAL_KISS_TNC, // KISS TNC on serial port (not ax.25 kernel device)
+ DEVICE_SERIAL_MKISS_TNC // Multi-port KISS TNC, like the Kantronics KAM
+};
+
+enum Device_Active {
+ DEVICE_NOT_IN_USE,
+ DEVICE_IN_USE
+};
+
+enum Device_Status {
+ DEVICE_DOWN,
+ DEVICE_UP,
+ DEVICE_ERROR
+};
+
+
+
+
+typedef struct {
+ int device_type; /* device type */
+ int active; /* channel in use */
+ int status; /* current status (up or down) */
+ char device_name[MAX_DEVICE_NAME+1]; /* device name */
+ char device_host_name[MAX_DEVICE_HOSTNM+1]; /* device host name for network */
+ unsigned long int address; /* socket address for network */
+ int thread_status; /* thread status for connect thread */
+ int connect_status; /* connect status for connect thread */
+ int decode_errors; /* decode error count, used for data type */
+ int data_type; /* 0=normal 1=wx_binary */
+ int socket_port; /* socket port# for network */
+ char device_host_pswd[MAX_DEVICE_HOSTPW+1]; /* host password */
+ int channel; /* for serial and net ports */
+ int channel2; /* for AX25 ports */
+ char ui_call[30]; /* current call for this port */
+ struct termios t,t_old; /* terminal struct for serial port */
+ int dtr; /* dtr signal for HSP cable (status) */
+ int sp; /* serial port speed */
+ int style; /* serial port style */
+ int scan; /* data read available */
+ int errors; /* errors for this port */
+ int reconnect; /* reconnect on net failure */
+ int reconnects; /* total number of reconnects by this port */
+ unsigned long bytes_input; /* total bytes read by this port */
+ unsigned long bytes_output; /* total bytes written by this port */
+ unsigned long bytes_input_last; /* total bytes read last check */
+ unsigned long bytes_output_last; /* total bytes read last check */
+ int port_activity; /* 0 if no activity between checks */
+ pthread_t read_thread; /* read thread */
+ int read_in_pos; /* current read buffer input pos */
+ int read_out_pos; /* current read buffer output pos */
+ char device_read_buffer[MAX_DEVICE_BUFFER]; /* read buffer for this port */
+ pthread_mutex_t read_lock; /* Lock for reading the port data */
+ pthread_t write_thread; /* write thread */
+ int write_in_pos; /* current write buffer input pos */
+ int write_out_pos; /* current write buffer output pos */
+ pthread_mutex_t write_lock; /* Lock for writing the port data */
+ char device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port */
+} iface;
+
+extern iface port_data; // shared port data
+extern TWriteBuffer _write_buffer[APRS_PORT_COUNT];
+
+// Incoming data queue
+typedef struct _incoming_data_record {
+ int length; // Used for binary strings such as KISS
+ int port;
+ unsigned char data[MAX_LINE_SIZE];
+} incoming_data_record;
+
+gboolean aprs_server_connect(void); // Called from menu.c
+void aprs_server_disconnect(void); // Called from menu.c
+
+void aprs_init(void); // Called form main.c
+
+void map_render_aprs();
+double distance_from_my_station(char *call_sign, char *course_deg, gint course_len);
+
+void pad_callsign(char *callsignout, char *callsignin);
+gboolean aprs_send_beacon(TAprsPort port);
+gboolean aprs_send_beacon_inet();
+void update_aprs_inet_options(gboolean force);
+void port_write_string(gchar *data, gint len, TAprsPort port);
+
+extern AprsDataRow *station_shortcuts[16384];
+
+#endif /* ifndef MAEMO_MAPPER_APRS_H */
+
+#endif // INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_decode.h"
+#include "data.h"
+#include "aprs.h"
+#include "types.h"
+#include "math.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "hashtable.h"
+#include "poi.h"
+#include "display.h"
+#include "aprs_message.h"
+//#include "aprs_weather.h"
+
+#define WX_TYPE 'X'
+
+float gust[60]; // High wind gust for each min. of last hour
+int gust_write_ptr = 0;
+int gust_read_ptr = 0;
+int gust_last_write = 0;
+
+float rain_base[24]; // hundredths of an inch
+float rain_minute[60]; // Total rain for each min. of last hour, hundredths of an inch
+int rain_check = 0; // Flag for re-checking rain_total each hour
+
+int redraw_on_new_data; // Station redraw request
+int wait_to_redraw; /* wait to redraw until system is up */
+time_t posit_next_time;
+
+/////////////
+float rain_minute_total = 0.0; // Total for last hour, hundredths of an inch
+int rain_minute_last_write = -1; // Write pointer for rain_minute[] array, set to an invalid number
+float rain_00 = 0.0; // hundredths of an inch
+float rain_24 = 0.0; // hundredths of an inch
+
+
+int station_count; // number of stored stations
+int station_count_save = 0; // old copy of above
+AprsDataRow *n_first; // pointer to first element in name sorted station list
+AprsDataRow *n_last; // pointer to last element in name sorted station list
+AprsDataRow *t_oldest; // pointer to first element in time sorted station list (oldest)
+AprsDataRow *t_newest; // pointer to last element in time sorted station list (newest)
+time_t last_station_remove; // last time we did a check for station removing
+time_t last_sec,curr_sec; // for comparing if seconds in time have changed
+int next_time_sn; // time serial number for unique time index
+
+int stations_heard = 0;
+
+////////////
+
+double cvt_m2len = 3.28084; // m to ft // from meter
+double cvt_kn2len = 1.0; // knots to knots; // from knots
+double cvt_mi2len = 0.8689607; // mph to knots / mi to nm; // from miles
+double cvt_dm2len = 0.328084; // dm to ft // from decimeter
+double cvt_hm2len = 0.0539957; // hm to nm; // from hectometer
+
+
+int emergency_distance_check = 1;
+float emergency_range = 280.0; // Default is 4hrs @ 70mph distance
+
+
+int decoration_offset_x = 0;
+int decoration_offset_y = 0;
+int last_station_info_x = 0;
+int last_station_info_y = 0;
+int fcc_lookup_pushed = 0;
+int rac_lookup_pushed = 0;
+
+time_t last_object_check = 0; // Used to determine when to re-transmit objects/items
+
+time_t last_emergency_time = 0;
+char last_emergency_callsign[MAX_CALLSIGN+1];
+int st_direct_timeout = 60 * 60; // 60 minutes.
+
+// Used in search_station_name() function. Shortcuts into the
+// station list based on the least-significant 7 bits of the first
+// two letters of the callsign/object name.
+AprsDataRow *station_shortcuts[16384];
+
+// calculate every half hour, display in status line every 5 minutes
+#define ALOHA_CALC_INTERVAL 1800
+#define ALOHA_STATUS_INTERVAL 300
+
+int process_emergency_packet_again = 0;
+
+
+////////////
+
+int altnet;
+char altnet_call[MAX_CALLSIGN+1];
+static struct hashtable *tactical_hash = NULL;
+
+
+char echo_digis[6][MAX_CALLSIGN+1];
+int tracked_stations = 0; // A count variable used in debug code only
+
+int trail_segment_distance; // Segment missing if greater distance
+int trail_segment_time; // Segment missing if above this time (mins)
+int skip_dupe_checking;
+
+
+void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem);
+void delete_station_memory(AprsDataRow *p_del);
+void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type);
+void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather);
+void decode_U2000_L(int from, unsigned char *data, AprsWeatherRow *weather);
+
+void init_weather(AprsWeatherRow *weather) { // clear weather data
+
+ weather->wx_sec_time = (time_t)0;
+ weather->wx_storm = 0;
+ weather->wx_time[0] = '\0';
+ weather->wx_course[0] = '\0';
+ weather->wx_speed[0] = '\0';
+ weather->wx_speed_sec_time = 0; // ??
+ weather->wx_gust[0] = '\0';
+ weather->wx_hurricane_radius[0] = '\0';
+ weather->wx_trop_storm_radius[0] = '\0';
+ weather->wx_whole_gale_radius[0] = '\0';
+ weather->wx_temp[0] = '\0';
+ weather->wx_rain[0] = '\0';
+ weather->wx_rain_total[0] = '\0';
+ weather->wx_snow[0] = '\0';
+ weather->wx_prec_24[0] = '\0';
+ weather->wx_prec_00[0] = '\0';
+ weather->wx_hum[0] = '\0';
+ weather->wx_baro[0] = '\0';
+ weather->wx_fuel_temp[0] = '\0';
+ weather->wx_fuel_moisture[0] = '\0';
+ weather->wx_type = '\0';
+ weather->wx_station[0] = '\0';
+}
+
+
+int tactical_keys_equal(void *key1, void *key2) {
+
+ if (strlen((char *)key1) == strlen((char *)key2)
+ && strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
+ return(1);
+ }
+ else {
+ return(0);
+ }
+}
+
+
+// Multiply all the characters in the callsign, truncated to
+// TACTICAL_HASH_SIZE
+//
+unsigned int tactical_hash_from_key(void *key) {
+ unsigned char *jj = key;
+ unsigned int tac_hash = 1;
+
+ while (*jj != '\0') {
+ tac_hash = tac_hash * (unsigned int)*jj++;
+ }
+
+ tac_hash = tac_hash % TACTICAL_HASH_SIZE;
+
+ return (tac_hash);
+}
+
+
+
+
+/* valid characters for APRS weather data fields */
+int is_aprs_chr(char ch) {
+
+ if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
+ return(1);
+ else
+ return(0);
+}
+
+
+
+
+//
+// Search station record by callsign
+// Returns a station with a call equal or after the searched one
+//
+// We use a doubly-linked list for the stations, so we can traverse
+// in either direction. We also use a 14-bit hash table created
+// from the first two letters of the call to dump us into the
+// beginning of the correct area that may hold the callsign, which
+// reduces search time quite a bit. We end up doing a linear search
+// only through a small area of the linked list.
+//
+// DK7IN: I don't look at case, objects and internet names could
+// have lower case.
+//
+int search_station_name(AprsDataRow **p_name, char *call, int exact) {
+ int kk;
+ int hash_key;
+ int result;
+ int ok = 1;
+
+ (*p_name) = n_first; // start of alphabet
+
+ if (call[0] == '\0') {
+ // If call we're searching for is empty, return n_first as
+ // the pointer.
+ return(0);
+ }
+
+ // We create the hash key out of the lower 7 bits of the first
+ // two characters, creating a 14-bit key (1 of 16384)
+ //
+
+ hash_key = (int)((call[0] & 0x7f) << 7);
+ hash_key = hash_key | (int)(call[1] & 0x7f);
+
+ // Look for a match using hash table lookup
+ //
+ (*p_name) = station_shortcuts[hash_key];
+
+ if ((*p_name) == NULL) { // No hash-table entry found.
+ int mm;
+
+ // No index found for that letter. Walk the array until
+ // we find an entry that is filled. That'll be our
+ // potential insertion point (insertion into the list will
+ // occur just ahead of the hash entry).
+ for (mm = hash_key+1; mm < 16384; mm++) {
+ if (station_shortcuts[mm] != NULL)
+ {
+ (*p_name) = station_shortcuts[mm];
+
+ break;
+ }
+ }
+ }
+
+
+ // If we got to this point, we either have a NULL pointer or a
+ // real hash-table pointer entry. A non-NULL pointer means that
+ // we have a match for the lower seven bits of the first two
+ // characters of the callsign. Check the rest of the callsign,
+ // and jump out of the loop if we get outside the linear search
+ // area (if first two chars are different).
+
+ kk = (int)strlen(call);
+
+ // Search linearly through list. Stop at end of list or break.
+ while ( (*p_name) != NULL) {
+
+ if (exact) {
+ // Check entire string for exact match
+ result = strcmp( call, (*p_name)->call_sign );
+ }
+ else {
+ // Check first part of string for match
+ result = strncmp( call, (*p_name)->call_sign, kk );
+ }
+
+ if (result < 0) { // We went past the right location.
+ // We're done.
+ ok = 0;
+
+ break;
+ }
+ else if (result == 0) { // Found a possible match
+ break;
+ }
+ else { // Result > 0. We haven't found it yet.
+
+ (*p_name) = (*p_name)->n_next; // Next element in list
+ }
+ }
+
+ // Did we find anything?
+ if ( (*p_name) == NULL) {
+ ok = 0;
+ return(ok); // Nope. No match found.
+ }
+
+ // If "exact" is set, check that the string lengths match as
+ // well. If not, we didn't find it.
+ if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
+ ok = 0;
+
+ return(ok); // if not ok: p_name points to correct insert position in name list
+}
+
+
+
+/*
+ * Check if current packet is a delayed echo
+ */
+int is_trailpoint_echo(AprsDataRow *p_station) {
+ int packets = 1;
+ time_t checktime;
+ AprsTrackRow *ptr;
+
+
+ // Check whether we're to skip checking for dupes (reading in
+ // objects/items from file is one such case).
+ //
+ if (skip_dupe_checking) {
+ return(0); // Say that it isn't an echo
+ }
+
+ // Start at newest end of linked list and compare. Return if we're
+ // beyond the checktime.
+ ptr = p_station->newest_trackpoint;
+
+ if (ptr == NULL)
+ return(0); // first point couldn't be an echo
+
+ checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
+
+ while (ptr != NULL) {
+
+ if (ptr->sec < checktime)
+ return(0); // outside time frame, no echo found
+
+ if ((p_station->coord_lon == ptr->trail_long_pos)
+ && (p_station->coord_lat == ptr->trail_lat_pos)
+ && (p_station->speed == '\0' || ptr->speed < 0
+ || (long)(atof(p_station->speed)*18.52) == ptr->speed)
+ // current: char knots, trail: long 0.1m (-1 is undef)
+ && (p_station->course == '\0' || ptr->course <= 0
+ || atoi(p_station->course) == ptr->course)
+ // current: char, trail: int (-1 is undef)
+ && (p_station->altitude == '\0' || ptr->altitude <= -99999l
+ || atoi(p_station->altitude)*10 == ptr->altitude)) {
+ // current: char, trail: int (-99999l is undef)
+
+ return(1); // we found a delayed echo
+ }
+ ptr = ptr->prev;
+ packets++;
+ }
+ return(0); // no echo found
+}
+
+
+/*
+ * Keep track of last six digis that echo my transmission
+ */
+void upd_echo(char *path) {
+ int i,j,len;
+
+ if (echo_digis[5][0] != '\0') {
+ for (i=0;i<5;i++) {
+ xastir_snprintf(echo_digis[i],
+ MAX_CALLSIGN+1,
+ "%s",
+ echo_digis[i+1]);
+
+ }
+ echo_digis[5][0] = '\0';
+ }
+ for (i=0,j=0;i < (int)strlen(path);i++) {
+ if (path[i] == '*')
+ break;
+ if (path[i] == ',')
+ j=i;
+ }
+ if (j > 0)
+ j++; // first char of call
+ if (i > 0 && i-j <= 9) {
+ len = i-j;
+ for (i=0;i<5;i++) { // look for free entry
+ if (echo_digis[i][0] == '\0')
+ break;
+ }
+ substr(echo_digis[i],path+j,len);
+ }
+}
+
+
+//
+// Store one trail point. Allocate storage for the new data.
+//
+// We now store track data in a doubly-linked list. Each record has a
+// pointer to the previous and the next record in the list. The main
+// station record has a pointer to the oldest and the newest end of the
+// chain, and the chain can be traversed in either order.
+//
+int store_trail_point(AprsDataRow *p_station,
+ long lon,
+ long lat,
+ time_t sec,
+ char *alt,
+ char *speed,
+ char *course,
+ short stn_flag) {
+// TODO - trails are currently disabled
+// This seems to fall over!!!
+ return 1;
+
+
+ char flag;
+ AprsTrackRow *ptr;
+
+
+ // Allocate storage for the new track point
+ ptr = malloc(sizeof(AprsTrackRow));
+ if (ptr == NULL) {
+ return(0); // Failed due to malloc
+ }
+
+ // Check whether we have any track data saved
+ if (p_station->newest_trackpoint == NULL) {
+ // new trail, do initialization
+
+ tracked_stations++;
+
+ // Assign a new trail color 'cuz it's a new trail
+ p_station->trail_color = new_trail_color(p_station->call_sign);
+ }
+
+ // Start linking the record to the new end of the chain
+ ptr->prev = p_station->newest_trackpoint; // Link to record or NULL
+ ptr->next = NULL; // Newest end of chain
+
+ // Have an older record already?
+ if (p_station->newest_trackpoint != NULL) { // Yes
+ p_station->newest_trackpoint->next = ptr;
+ }
+ else { // No, this is our first record
+ p_station->oldest_trackpoint = ptr;
+ }
+
+ // Link it in as our newest record
+ p_station->newest_trackpoint = ptr;
+
+ ptr->trail_long_pos = lon;
+ ptr->trail_lat_pos = lat;
+ ptr->sec = sec;
+
+ if (alt[0] != '\0')
+ ptr->altitude = atoi(alt)*10;
+ else
+ ptr->altitude = -99999l;
+
+ if (speed[0] != '\0')
+ ptr->speed = (long)(atof(speed)*18.52);
+ else
+ ptr->speed = -1;
+
+ if (course[0] != '\0')
+ ptr->course = (int)(atof(course) + 0.5); // Poor man's rounding
+ else
+ ptr->course = -1;
+
+ flag = '\0'; // init flags
+
+ if ((stn_flag & ST_DIRECT) != 0)
+ flag |= TR_LOCAL; // set "local" flag
+
+ if (ptr->prev != NULL) { // we have at least two points...
+ // Check whether distance between points is too far. We
+ // must convert from degrees to the Xastir coordinate system
+ // units, which are 100th of a second.
+ if ( abs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
+ abs(lat - ptr->prev->trail_lat_pos) > (trail_segment_distance * 60*60*100) ) {
+
+ // Set "new track" flag if there's
+ // "trail_segment_distance" degrees or more between
+ // points. Originally was hard-coded to one degree, now
+ // set by a slider in the timing dialog.
+ flag |= TR_NEWTRK;
+ }
+ else {
+ // Check whether trail went above our maximum time
+ // between points. If so, don't draw segment.
+ if (abs(sec - ptr->prev->sec) > (trail_segment_time *60)) {
+
+ // Set "new track" flag if long delay between
+ // reception of two points. Time is set by a slider
+ // in the timing dialog.
+ flag |= TR_NEWTRK;
+ }
+ }
+
+ // Since we have more then 1 previous track point, ensure we don't go over the configured max
+ // (_aprs_std_pos_hist)
+ AprsTrackRow *ptr_tmp = ptr;
+ AprsTrackRow *ptr_tmp_previous = ptr;
+ gint trackCount = 0;
+
+ while( (ptr_tmp = ptr_tmp->prev) )
+ {
+ if(trackCount > _aprs_std_pos_hist)
+ {
+ fprintf(stderr, "DEBUG: Deleting old track point\n");
+ // Remove track
+ ptr_tmp->prev->next = ptr_tmp->next;
+ if(ptr_tmp->next)
+ {
+ ptr_tmp->next->prev = ptr_tmp->prev;
+ }
+ ptr_tmp_previous = ptr_tmp->prev;
+ free( ptr_tmp);
+ ptr_tmp = ptr_tmp_previous;
+
+
+ }
+ else
+ {
+ trackCount ++;
+ }
+
+ ptr_tmp = ptr_tmp->prev;
+ }
+
+ }
+ else {
+ // Set "new track" flag for first point received.
+ flag |= TR_NEWTRK;
+ }
+ ptr->flag = flag;
+
+ return(1); // We succeeded
+}
+
+
+
+/*
+ * Substring function WITH a terminating NULL char, needs a string of at least size+1
+ */
+void substr(char *dest, char *src, int size)
+{
+ snprintf(dest, size+1, "%s", src);
+}
+
+
+
+/*
+ * Remove element from name ordered list
+ */
+void remove_name(AprsDataRow *p_rem) { // todo: return pointer to next element
+ int update_shortcuts = 0;
+ int hash_key; // We use a 14-bit hash key
+
+
+ // Do a quick check to see if we're removing a station record
+ // that is pointed to by our pointer shortcuts array.
+ // If so, update our pointer shortcuts after we're done.
+ //
+ // We create the hash key out of the lower 7 bits of the first
+ // two characters, creating a 14-bit key (1 of 16384)
+ //
+ hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
+ hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
+
+ if (station_shortcuts[hash_key] == p_rem) {
+ // Yes, we're trying to remove a record that a hash key
+ // directly points to. We'll need to redo that hash key
+ // after we remove the record.
+ update_shortcuts++;
+ }
+
+
+ // Proceed to the station record removal
+ //
+ if (p_rem->n_prev == NULL) { // Appears to be first element in list
+
+ if (n_first == p_rem) { // Yes, head of list
+
+ // Make list head point to 2nd element in list (or NULL)
+ // so that we can delete the current record.
+ n_first = p_rem->n_next;
+ }
+ else { // No, not first element in list. Problem! The
+ // list pointers are inconsistent for some reason.
+ // The chain has been broken and we have dangling
+ // pointers.
+
+ fprintf(stderr,
+ "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
+
+abort(); // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here? Start
+// at the other end of the chain and navigate back to this end, then
+// fix up n_first to point to it? This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+ }
+ }
+ else { // Not the first element in the list. Fix up pointers
+ // to skip the current record.
+ p_rem->n_prev->n_next = p_rem->n_next;
+ }
+
+
+ if (p_rem->n_next == NULL) { // Appears to be last element in list
+
+ if (n_last == p_rem) { // Yes, tail of list
+
+ // Make list tail point to previous element in list (or
+ // NULL) so that we can delete the current record.
+ n_last = p_rem->n_prev;
+ }
+ else { // No, not last element in list. Problem! The list
+ // pointers are inconsistent for some reason. The
+ // chain has been broken and we have dangling
+ // pointers.
+
+ fprintf(stderr,
+ "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
+
+abort(); // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here? Start
+// at the other end of the chain and navigate back to this end, then
+// fix up n_last to point to it? This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+ }
+ }
+ else { // Not the last element in the list. Fix up pointers to
+ // skip the current record.
+ p_rem->n_next->n_prev = p_rem->n_prev;
+ }
+
+
+ // Update our pointer shortcuts. Pass the removed hash_key to
+ // the function so that we can try to redo just that hash_key
+ // pointer.
+ if (update_shortcuts) {
+//fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
+
+ // The -1 tells the function to redo all of the hash table
+ // pointers because we deleted one of them. Later we could
+ // optimize this so that only the specific pointer is fixed
+ // up.
+ station_shortcuts_update_function(-1, NULL);
+ }
+
+
+}
+
+
+
+/*
+ * Station Capabilities, Queries and Responses [APRS Reference, chapter 15]
+ */
+//
+// According to Bob Bruninga we should wait a random time between 0
+// and 120 seconds before responding to a general query. We use the
+// delayed-ack mechanism to add this randomness.
+//
+// NOTE: We may end up sending these to RF when the query came in
+// over the internet. We should check that.
+//
+int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
+ char temp[100];
+ int ok = 0;
+ float randomize;
+
+
+ // Generate a random number between 0.0 and 1.0
+ randomize = rand() / (float)RAND_MAX;
+
+ // Convert to between 0 and 120 seconds
+ randomize = randomize * 120.0;
+
+ // Check for proper usage of the ?APRS? query
+//
+// NOTE: We need to add support in here for the radius circle as
+// listed in the spec for general queries. Right now we respond to
+// all queries, whether we're inside the circle or not. Spec says
+// this:
+//
+// ?Query?Lat,Long,Radius
+// 1 n 1 n 1 n 1 4 Bytes
+//
+// i.e. ?APRS? 34.02,-117.15,0200
+//
+// Note leading space in latitude as its value is positive.
+// Lat/long are floating point degrees. N/E are positive, indicated
+// by a leading space. S/W are negative. Radius is in miles
+// expressed as a fixed 4-digit number in whole miles. All stations
+// inside the specified circle should respond with a position report
+// and a status report.
+//
+ if (!ok && strncmp(message,"APRS?",5)==0) {
+ //
+ // Initiate a delayed transmit of our own posit.
+ // UpdateTime() uses posit_next_time to decide when to
+ // transmit, so we'll just muck with that.
+ //
+ if ( posit_next_time - sec_now() < randomize ) {
+ // Skip setting it, as we'll transmit soon anyway
+ }
+ else {
+ posit_next_time = (size_t)(sec_now() + randomize);
+ }
+ ok = 1;
+ }
+ // Check for illegal case for the ?APRS? query
+ if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
+ ok = 1;
+// fprintf(stderr,
+// "%s just queried us with an illegal query: %s\n",
+// call_sign,
+// message),
+// fprintf(stderr,
+// "Consider sending a message, asking them to follow the spec\n");
+ }
+
+ // Check for proper usage of the ?WX? query
+ if (!ok && strncmp(message,"WX?",3)==0) {
+ // WX is not supported
+
+ ok = 1;
+ }
+
+ // Check for illegal case for the ?WX? query
+ if (!ok && g_strncasecmp(message,"WX?",3)==0) {
+ ok = 1;
+// fprintf(stderr,
+// "%s just queried us with an illegal query: %s\n",
+// call_sign,
+// message),
+// fprintf(stderr,
+// "Consider sending a message, asking them to follow the spec\n");
+ }
+
+ return(ok);
+}
+
+
+
+/*
+ * Get new trail color for a call
+ */
+int new_trail_color(char *call) {
+ // TODO - stub
+
+ return(0);
+}
+
+
+/*
+ * Insert existing element into time ordered list before p_time
+ * The p_new record ends up being on the "older" side of p_time when
+ * all done inserting (closer in the list to the t_oldest pointer).
+ * If p_time == NULL, insert at newest end of list.
+ */
+void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {
+
+
+ // Set up pointer to next record (or NULL), sorted by time
+ p_new->t_newer = p_time;
+
+ if (p_time == NULL) { // add to end of list (becomes newest station)
+
+ p_new->t_older = t_newest; // connect to previous end of list
+
+ if (t_newest == NULL) // if list empty, create list
+ t_oldest = p_new; // it's now our only station on the list
+ else
+ t_newest->t_newer = p_new; // list not empty, link original last record to our new one
+
+ t_newest = p_new; // end of list (newest record pointer) points to our new record
+ }
+
+ else { // Else we're inserting into the middle of the list somewhere
+
+ p_new->t_older = p_time->t_older;
+
+ if (p_time->t_older == NULL) // add to end of list (new record becomes oldest station)
+ t_oldest = p_new;
+ else
+ p_time->t_older->t_newer = p_new; // else
+
+ p_time->t_older = p_new;
+ }
+
+ // TODO - this may need implementing?
+ /*
+ if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
+ {
+ // Delete the oldest
+ if(t_oldest != NULL)
+ {
+ // fprintf(stderr, "DEBUG: oldest station deleted\n");
+
+ delete_station_memory(t_oldest);
+ }
+ else
+ {
+ stations_heard++;
+ }
+ }
+ else
+ {
+// fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);
+
+ stations_heard++; // Should really be done in the new station method
+ }
+ */
+}
+
+
+
+
+/*
+ * Remove element from time ordered list
+ */
+void remove_time(AprsDataRow *p_rem) { // todo: return pointer to next element
+
+ if (p_rem->t_older == NULL) { // Appears to be first element in list
+
+ if (t_oldest == p_rem) { // Yes, head of list (oldest)
+
+ // Make oldest list head point to 2nd element in list (or NULL)
+ // so that we can delete the current record.
+ t_oldest = p_rem->t_newer;
+ }
+ else { // No, not first (oldest) element in list. Problem!
+ // The list pointers are inconsistent for some
+ // reason. The chain has been broken and we have
+ // dangling pointers.
+
+ fprintf(stderr,
+ "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
+
+abort(); // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here? Start
+// at the other end of the chain and navigate back to this end, then
+// fix up t_oldest to point to it? This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+ }
+ }
+ else { // Not the first (oldest) element in the list. Fix up
+ // pointers to skip the current record.
+ p_rem->t_older->t_newer = p_rem->t_newer;
+ }
+
+
+ if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list
+
+ if (t_newest == p_rem) { // Yes, head of list (newest)
+
+ // Make newest list head point to previous element in
+ // list (or NULL) so that we can delete the current
+ // record.
+ t_newest = p_rem->t_older;
+ }
+ else { // No, not newest element in list. Problem! The
+ // list pointers are inconsistent for some reason.
+ // The chain has been broken and we have dangling
+ // pointers.
+
+ fprintf(stderr,
+ "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
+
+abort(); // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here? Start
+// at the other end of the chain and navigate back to this end, then
+// fix up t_newest to point to it? This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+ }
+ }
+ else { // Not the newest element in the list. Fix up pointers
+ // to skip the current record.
+ p_rem->t_newer->t_older = p_rem->t_older;
+ }
+
+ stations_heard--;
+}
+
+
+
+
+/*
+ * Move station record before p_time in time ordered list
+ */
+void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {
+
+ if (p_curr != NULL) { // need a valid record
+ remove_time(p_curr);
+ insert_time(p_curr,p_time);
+ }
+}
+
+
+/*
+ * Extract powergain and/or range from APRS info field:
+ * "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
+ */
+int extract_powergain_range(char *info, char *phgd) {
+ int i,found,len;
+ char *info2;
+
+
+//fprintf(stderr,"Info:%s\n",info);
+
+ // Check whether two strings of interest are present and snag a
+ // pointer to them.
+ info2 = strstr(info,"RNG");
+ if (!info2)
+ info2 = strstr(info,"PHG");
+ if (!info2) {
+ phgd[0] = '\0';
+ return(0);
+ }
+
+ found=0;
+ len = (int)strlen(info2);
+
+ if (len >= 9 && strncmp(info2,"PHG",3)==0
+ && info2[7]=='/'
+ && info2[8]!='A' // trailing '/' not defined in Reference...
+ && g_ascii_isdigit(info2[3])
+ && g_ascii_isdigit(info2[4])
+ && g_ascii_isdigit(info2[5])
+ && g_ascii_isdigit(info2[6])) {
+ substr(phgd,info2,7);
+ found = 1;
+ for (i=0;i<=len-8;i++) // delete powergain from data extension field
+ info2[i] = info2[i+8];
+ }
+ else {
+ if (len >= 7 && strncmp(info2,"PHG",3)==0
+ && g_ascii_isdigit(info2[3])
+ && g_ascii_isdigit(info2[4])
+ && g_ascii_isdigit(info2[5])
+ && g_ascii_isdigit(info2[6])) {
+ substr(phgd,info2,7);
+ found = 1;
+ for (i=0;i<=len-7;i++) // delete powergain from data extension field
+ info2[i] = info2[i+7];
+ }
+ else if (len >= 7 && strncmp(info2,"RNG",3)==0
+ && g_ascii_isdigit(info2[3])
+ && g_ascii_isdigit(info2[4])
+ && g_ascii_isdigit(info2[5])
+ && g_ascii_isdigit(info2[6])) {
+ substr(phgd,info2,7);
+ found = 1;
+ for (i=0;i<=len-7;i++) // delete powergain from data extension field
+ info2[i] = info2[i+7];
+ }
+ else {
+ phgd[0] = '\0';
+ }
+ }
+ return(found);
+}
+
+
+/*
+ * Extract omnidf from APRS info field "DFS1234/" from APRS data extension
+ */
+int extract_omnidf(char *info, char *phgd) {
+ int i,found,len;
+
+ found=0;
+ len = (int)strlen(info);
+ if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/' // trailing '/' not defined in Reference...
+ && g_ascii_isdigit(info[3]) && g_ascii_isdigit(info[5]) && g_ascii_isdigit(info[6])) {
+ substr(phgd,info,7);
+ for (i=0;i<=len-8;i++) // delete omnidf from data extension field
+ info[i] = info[i+8];
+ return(1);
+ }
+ else {
+ phgd[0] = '\0';
+ return(0);
+ }
+}
+
+
+//
+// Extract speed and/or course from beginning of info field
+//
+// Returns course in degrees, speed in KNOTS.
+//
+int extract_speed_course(char *info, char *speed, char *course) {
+ int i,found,len;
+
+ len = (int)strlen(info);
+ found = 0;
+ if (len >= 7) {
+ found = 1;
+ for(i=0; found && i<7; i++) { // check data format
+ if (i==3) { // check separator
+ if (info[i]!='/')
+ found = 0;
+ }
+ else {
+ if( !( g_ascii_isdigit(info[i])
+ || (info[i] == ' ') // Spaces and periods are allowed. Need these
+ || (info[i] == '.') ) ) // here so that we can get the field deleted
+ found = 0;
+ }
+ }
+ }
+ if (found) {
+ substr(course,info,3);
+ substr(speed,info+4,3);
+ for (i=0;i<=len-7;i++) // delete speed/course from info field
+ info[i] = info[i+7];
+ }
+ if (!found || atoi(course) < 1) { // course 0 means undefined
+// speed[0] ='\0'; // Don't do this! We can have a valid
+// speed without a valid course.
+ course[0]='\0';
+ }
+ else { // recheck data format looking for undefined fields
+ for(i=0; i<2; i++) {
+ if( !(g_ascii_isdigit(speed[i]) ) )
+ speed[0] = '\0';
+ if( !(g_ascii_isdigit(course[i]) ) )
+ course[0] = '\0';
+ }
+ }
+
+ return(found);
+}
+
+
+
+
+
+/*
+ * Extract Area Object
+ */
+void extract_area(AprsDataRow *p_station, char *data) {
+ int i, val, len;
+ unsigned int uval;
+ AprsAreaObject temp_area;
+
+ /* NOTE: If we are here, the symbol was the area symbol. But if this
+ is a slightly corrupted packet, we shouldn't blow away the area info
+ for this station, since it could be from a previously received good
+ packet. So we will work on temp_area and only copy to p_station at
+ the end, returning on any error as we parse. N7TAP */
+
+ //fprintf(stderr,"Area Data: %s\n", data);
+
+ len = (int)strlen(data);
+ val = data[0] - '0';
+ if (val >= 0 && val <= AREA_MAX) {
+ temp_area.type = val;
+ val = data[4] - '0';
+ if (data[3] == '/') {
+ if (val >=0 && val <= 9) {
+ temp_area.color = val;
+ }
+ else {
+ return;
+ }
+ }
+ else if (data[3] == '1') {
+ if (val >=0 && val <= 5) {
+ temp_area.color = 10 + val;
+ }
+ else {
+ return;
+ }
+ }
+
+ val = 0;
+ if (isdigit((int)data[1]) && isdigit((int)data[2])) {
+ val = (10 * (data[1] - '0')) + (data[2] - '0');
+ }
+ else {
+ return;
+ }
+ temp_area.sqrt_lat_off = val;
+
+ val = 0;
+ if (isdigit((int)data[5]) && isdigit((int)data[6])) {
+ val = (10 * (data[5] - '0')) + (data[6] - '0');
+ }
+ else {
+ return;
+ }
+ temp_area.sqrt_lon_off = val;
+
+ for (i = 0; i <= len-7; i++) // delete area object from data extension field
+ data[i] = data[i+7];
+ len -= 7;
+
+ if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT) {
+ if (data[0] == '{') {
+ if (sscanf(data, "{%u}", &uval) == 1) {
+ temp_area.corridor_width = uval & 0xffff;
+ for (i = 0; i <= len; i++)
+ if (data[i] == '}')
+ break;
+ uval = i+1;
+ for (i = 0; i <= (int)(len-uval); i++)
+ data[i] = data[i+uval]; // delete corridor width
+ }
+ else {
+ temp_area.corridor_width = 0;
+ return;
+ }
+ }
+ else {
+ temp_area.corridor_width = 0;
+ }
+ }
+ else {
+ temp_area.corridor_width = 0;
+ }
+ }
+ else {
+ return;
+ }
+
+ memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));
+
+}
+
+
+
+/*
+ * Extract probability_max data from APRS info field: "Pmax1.23,"
+ * Please note the ending comma. We use it to delimit the field.
+ */
+int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
+ int len,done;
+ char *c;
+ char *d;
+
+
+
+ len = (int)strlen(info);
+ if (len < 6) { // Too short
+ prob_max[0] = '\0';
+ return(0);
+ }
+
+ c = strstr(info,"Pmax");
+ if (c == NULL) { // Pmax not found
+ prob_max[0] = '\0';
+ return(0);
+ }
+
+ c = c+4; // Skip the Pmax part
+ // Find the ending comma
+ d = c;
+ done = 0;
+ while (!done) {
+ if (*d == ',') { // We're done
+ done++;
+ }
+ else {
+ d++;
+ }
+
+ // Check for string too long
+ if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
+ prob_max[0] = '\0';
+ return(0);
+ }
+ }
+
+ // Copy the substring across
+ snprintf(prob_max,
+ prob_max_size,
+ "%s",
+ c);
+ prob_max[d-c] = '\0';
+ prob_max[10] = '\0'; // Just to make sure
+
+ // Delete data from data extension field
+ d++; // Skip the comma
+ done = 0;
+ while (!done) {
+ *(c-4) = *d;
+ if (*d == '\0')
+ done++;
+ c++;
+ d++;
+ }
+
+ return(1);
+}
+
+
+
+
+/*
+ * Extract probability_min data from APRS info field: "Pmin1.23,"
+ * Please note the ending comma. We use it to delimit the field.
+ */
+int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
+ int len,done;
+ char *c;
+ char *d;
+
+
+ len = (int)strlen(info);
+ if (len < 6) { // Too short
+ prob_min[0] = '\0';
+ return(0);
+ }
+
+ c = strstr(info,"Pmin");
+ if (c == NULL) { // Pmin not found
+ prob_min[0] = '\0';
+ return(0);
+ }
+
+ c = c+4; // Skip the Pmin part
+ // Find the ending comma
+ d = c;
+ done = 0;
+ while (!done) {
+ if (*d == ',') { // We're done
+ done++;
+ }
+ else {
+ d++;
+ }
+
+ // Check for string too long
+ if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
+ prob_min[0] = '\0';
+ return(0);
+ }
+ }
+
+ // Copy the substring across
+ xastir_snprintf(prob_min,
+ prob_min_size,
+ "%s",
+ c);
+ prob_min[d-c] = '\0';
+ prob_min[10] = '\0'; // Just to make sure
+
+ // Delete data from data extension field
+ d++; // Skip the comma
+ done = 0;
+ while (!done) {
+ *(c-4) = *d;
+ if (*d == '\0')
+ done++;
+ c++;
+ d++;
+ }
+
+ return(1);
+}
+
+
+
+
+/*
+ * Extract signpost data from APRS info field: "{123}", an APRS data extension
+ * Format can be {1}, {12}, or {123}. Letters or digits are ok.
+ */
+int extract_signpost(char *info, char *signpost) {
+ int i,found,len,done;
+
+//0123456
+//{1}
+//{12}
+//{121}
+
+ found=0;
+ len = (int)strlen(info);
+ if ( (len > 2)
+ && (info[0] == '{')
+ && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {
+
+ i = 1;
+ done = 0;
+ while (!done) { // Snag up to three digits
+ if (info[i] == '}') { // We're done
+ found = i; // found = position of '}' character
+ done++;
+ }
+ else {
+ signpost[i-1] = info[i];
+ }
+
+ i++;
+
+ if ( (i > 4) && !done) { // Something is wrong, we should be done by now
+ done++;
+ signpost[0] = '\0';
+ return(0);
+ }
+ }
+ substr(signpost,info+1,found-1);
+ found++;
+ for (i=0;i<=len-found;i++) { // delete omnidf from data extension field
+ info[i] = info[i+found];
+ }
+ return(1);
+ }
+ else {
+ signpost[0] = '\0';
+ return(0);
+ }
+}
+
+
+
+// is_altnet()
+//
+// Returns true if station fits the current altnet description.
+//
+int is_altnet(AprsDataRow *p_station) {
+ char temp_altnet_call[20+1];
+ char temp2[20+1];
+ char *net_ptr;
+ int altnet_match;
+ int result;
+
+
+ // Snag a possible altnet call out of the record for later use
+ if (p_station->node_path_ptr != NULL)
+ substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
+ else
+ temp_altnet_call[0] = '\0';
+
+ // Save for later
+ snprintf(temp2,
+ sizeof(temp2),
+ "%s",
+ temp_altnet_call);
+
+ if ((net_ptr = strchr(temp_altnet_call, ',')))
+ *net_ptr = '\0'; // Chop the string at the first ',' character
+
+ for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
+
+ result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
+ || !strcmp(temp_altnet_call, "local")
+ || !strncmp(temp_altnet_call, "SPC", 3)
+ || !strcmp(temp_altnet_call, "SPECL")
+ || ( is_my_station(p_station) ) ) ; // It's my callsign/SSID
+
+ return(result);
+}
+
+
+
+int is_num_or_sp(char ch)
+{
+ return((int)((ch >= '0' && ch <= '9') || ch == ' '));
+}
+
+
+char *get_time(char *time_here) {
+ struct tm *time_now;
+ time_t timenw;
+
+ (void)time(&timenw);
+ time_now = localtime(&timenw);
+ (void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
+ return(time_here);
+}
+
+
+static void clear_area(AprsDataRow *p_station) {
+ p_station->aprs_symbol.area_object.type = AREA_NONE;
+ p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
+ p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
+ p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
+ p_station->aprs_symbol.area_object.corridor_width = 0;
+}
+
+
+
+
+// Check for valid overlay characters: 'A-Z', '0-9', and 'a-j'. If
+// 'a-j', it's from a compressed posit, and we need to convert it to
+// '0-9'.
+void overlay_symbol(char symbol, char data, AprsDataRow *fill) {
+
+ if ( data != '/' && data !='\\') { // Symbol overlay
+
+ if (data >= 'a' && data <= 'j') {
+ // Found a compressed posit numerical overlay
+ data = data - 'a'+'0'; // Convert to a digit
+ }
+ if ( (data >= '0' && data <= '9')
+ || (data >= 'A' && data <= 'Z') ) {
+ // Found normal overlay character
+ fill->aprs_symbol.aprs_type = '\\';
+ fill->aprs_symbol.special_overlay = data;
+ }
+ else {
+ // Bad overlay character. Don't use it. Insert the
+ // normal alternate table character instead.
+ fill->aprs_symbol.aprs_type = '\\';
+ fill->aprs_symbol.special_overlay='\0';
+ }
+ }
+ else { // No overlay character
+ fill->aprs_symbol.aprs_type = data;
+ fill->aprs_symbol.special_overlay='\0';
+ }
+ fill->aprs_symbol.aprs_symbol = symbol;
+}
+
+
+/*
+ * Extract Time from begin of line [APRS Reference, chapter 6]
+ *
+ * If a time string is found in "data", it is deleted from the
+ * beginning of the string.
+ */
+int extract_time(AprsDataRow *p_station, char *data, int type) {
+ int len, i;
+ int ok = 0;
+
+ // todo: better check of time data ranges
+ len = (int)strlen(data);
+ if (type == APRS_WX2) {
+ // 8 digit time from stand-alone positionless weather stations...
+ if (len > 8) {
+ // MMDDHHMM zulu time
+ // MM 01-12 todo: better check of time data ranges
+ // DD 01-31
+ // HH 01-23
+ // MM 01-59
+ ok = 1;
+ for (i=0;ok && i<8;i++)
+ if (!isdigit((int)data[i]))
+ ok = 0;
+ if (ok) {
+// substr(p_station->station_time,data+2,6);
+// p_station->station_time_type = 'z';
+ for (i=0;i<=len-8;i++) // delete time from data
+ data[i] = data[i+8];
+ }
+ }
+ }
+ else {
+ if (len > 6) {
+ // Status messages only with optional zulu format
+ // DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8 ???
+ if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
+ ok = 1;
+ for (i=0;ok && i<6;i++)
+ if (!isdigit((int)data[i]))
+ ok = 0;
+ if (ok) {
+// substr(p_station->station_time,data,6);
+// p_station->station_time_type = data[6];
+ for (i=0;i<=len-7;i++) // delete time from data
+ data[i] = data[i+7];
+ }
+ }
+ }
+ return(ok);
+}
+
+
+
+// Breaks up a string into substrings using comma as the delimiter.
+// Makes each entry in the array of char ptrs point to one
+// substring. Modifies incoming string and cptr[] array. Send a
+// character constant string to it and you'll get an instant
+// segfault (the function can't modify a char constant string).
+//
+void split_string( char *data, char *cptr[], int max ) {
+ int ii;
+ char *temp;
+ char *current = data;
+
+
+ // NULL each char pointer
+ for (ii = 0; ii < max; ii++) {
+ cptr[ii] = NULL;
+ }
+
+ // Save the beginning substring address
+ cptr[0] = current;
+
+ for (ii = 1; ii < max; ii++) {
+ temp = strchr(current,','); // Find next comma
+
+ if(!temp) { // No commas found
+ return; // All done with string
+ }
+
+ // Store pointer to next substring in array
+ cptr[ii] = &temp[1];
+ current = &temp[1];
+
+ // Overwrite comma with end-of-string char and bump pointer by
+ // one.
+ temp[0] = '\0';
+ }
+}
+
+
+
+/* check data format 123 ___ ... */
+// We wish to count how many ' ' or '.' characters we find. If it
+// equals zero or the field width, it might be a weather field. If
+// not, then it might be part of a comment field or something else.
+//
+int is_weather_data(char *data, int len) {
+ int ok = 1;
+ int i;
+ int count = 0;
+
+ for (i=0;ok && i<len;i++)
+ if (!is_aprs_chr(data[i]))
+ ok = 0;
+
+ // Count filler characters. Must equal zero or field width to
+ // be a weather field. There doesn't appear to be a case where
+ // a single period is allowed in any weather-related fields.
+ //
+ for (i=0;ok && i<len;i++) {
+ if (data[i] == ' ' || data[i] == '.') {
+ count++;
+ }
+ }
+ if (count != 0 && count != len) {
+ ok = 0;
+ }
+
+ return(ok);
+}
+
+
+
+
+
+
+/* convert latitude from string to long with 1/100 sec resolution */
+//
+// Input is in [D]DMM.MM[MM]N format (degrees/decimal
+// minutes/direction)
+//
+long convert_lat_s2l(char *lat) { /* N=0°, Ctr=90°, S=180° */
+ long centi_sec;
+ char copy[15];
+ char n[15];
+ char *p;
+ char offset;
+
+
+ // Find the decimal point if present
+ p = strstr(lat, ".");
+
+ if (p == NULL) // No decimal point found
+ return(0l);
+
+ offset = p - lat; // Arithmetic on pointers
+ switch (offset) {
+ case 0: // .MM[MM]N
+ return(0l); // Bad, no degrees or minutes
+ break;
+ case 1: // M.MM[MM]N
+ return(0l); // Bad, no degrees
+ break;
+ case 2: // MM.MM[MM]N
+ return(0l); // Bad, no degrees
+ break;
+ case 3: // DMM.MM[MM]N
+ xastir_snprintf(copy,
+ sizeof(copy),
+ "0%s", // Add a leading '0'
+ lat);
+ break;
+ case 4: // DDMM.MM[MM]N
+ xastir_snprintf(copy,
+ sizeof(copy),
+ "%s", // Copy verbatim
+ lat);
+ break;
+ default:
+ break;
+ }
+
+ copy[14] = '\0';
+ centi_sec=0l;
+ if (copy[4]=='.'
+ && ( (char)toupper((int)copy[ 5])=='N'
+ || (char)toupper((int)copy[ 6])=='N'
+ || (char)toupper((int)copy[ 7])=='N'
+ || (char)toupper((int)copy[ 8])=='N'
+ || (char)toupper((int)copy[ 9])=='N'
+ || (char)toupper((int)copy[10])=='N'
+ || (char)toupper((int)copy[11])=='N'
+ || (char)toupper((int)copy[ 5])=='S'
+ || (char)toupper((int)copy[ 6])=='S'
+ || (char)toupper((int)copy[ 7])=='S'
+ || (char)toupper((int)copy[ 8])=='S'
+ || (char)toupper((int)copy[ 9])=='S'
+ || (char)toupper((int)copy[10])=='S'
+ || (char)toupper((int)copy[11])=='S')) {
+
+ substr(n, copy, 2); // degrees
+ centi_sec=atoi(n)*60*60*100;
+
+ substr(n, copy+2, 2); // minutes
+ centi_sec += atoi(n)*60*100;
+
+ substr(n, copy+5, 4); // fractional minutes
+ // Keep the fourth digit if present, as it resolves to 0.6
+ // of a 1/100 sec resolution. Two counts make one count in
+ // the Xastir coordinate system.
+
+ // Extend the digits to full precision by adding zeroes on
+ // the end.
+ strncat(n, "0000", sizeof(n) - strlen(n));
+
+ // Get rid of the N/S character
+ if (!isdigit((int)n[2]))
+ n[2] = '0';
+ if (!isdigit((int)n[3]))
+ n[3] = '0';
+
+ // Terminate substring at the correct digit
+ n[4] = '\0';
+//fprintf(stderr,"Lat: %s\n", n);
+
+ // Add 0.5 (Poor man's rounding)
+ centi_sec += (long)((atoi(n) * 0.6) + 0.5);
+
+ if ( (char)toupper((int)copy[ 5])=='N'
+ || (char)toupper((int)copy[ 6])=='N'
+ || (char)toupper((int)copy[ 7])=='N'
+ || (char)toupper((int)copy[ 8])=='N'
+ || (char)toupper((int)copy[ 9])=='N'
+ || (char)toupper((int)copy[10])=='N'
+ || (char)toupper((int)copy[11])=='N') {
+ centi_sec = -centi_sec;
+ }
+
+ centi_sec += 90*60*60*100;
+ }
+ return(centi_sec);
+}
+
+
+
+
+
+/* convert longitude from string to long with 1/100 sec resolution */
+//
+// Input is in [DD]DMM.MM[MM]W format (degrees/decimal
+// minutes/direction).
+//
+long convert_lon_s2l(char *lon) { /* W=0°, Ctr=180°, E=360° */
+ long centi_sec;
+ char copy[16];
+ char n[16];
+ char *p;
+ char offset;
+
+
+ // Find the decimal point if present
+ p = strstr(lon, ".");
+
+ if (p == NULL) // No decimal point found
+ return(0l);
+
+ offset = p - lon; // Arithmetic on pointers
+ switch (offset) {
+ case 0: // .MM[MM]N
+ return(0l); // Bad, no degrees or minutes
+ break;
+ case 1: // M.MM[MM]N
+ return(0l); // Bad, no degrees
+ break;
+ case 2: // MM.MM[MM]N
+ return(0l); // Bad, no degrees
+ break;
+ case 3: // DMM.MM[MM]N
+ xastir_snprintf(copy,
+ sizeof(copy),
+ "00%s", // Add two leading zeroes
+ lon);
+ break;
+ case 4: // DDMM.MM[MM]N
+ xastir_snprintf(copy,
+ sizeof(copy),
+ "0%s", // Add leading '0'
+ lon);
+ break;
+ case 5: // DDDMM.MM[MM]N
+ xastir_snprintf(copy,
+ sizeof(copy),
+ "%s", // Copy verbatim
+ lon);
+ break;
+ default:
+ break;
+ }
+
+ copy[15] = '\0';
+ centi_sec=0l;
+ if (copy[5]=='.'
+ && ( (char)toupper((int)copy[ 6])=='W'
+ || (char)toupper((int)copy[ 7])=='W'
+ || (char)toupper((int)copy[ 8])=='W'
+ || (char)toupper((int)copy[ 9])=='W'
+ || (char)toupper((int)copy[10])=='W'
+ || (char)toupper((int)copy[11])=='W'
+ || (char)toupper((int)copy[12])=='W'
+ || (char)toupper((int)copy[ 6])=='E'
+ || (char)toupper((int)copy[ 7])=='E'
+ || (char)toupper((int)copy[ 8])=='E'
+ || (char)toupper((int)copy[ 9])=='E'
+ || (char)toupper((int)copy[10])=='E'
+ || (char)toupper((int)copy[11])=='E'
+ || (char)toupper((int)copy[12])=='E')) {
+
+ substr(n,copy,3); // degrees 013
+ centi_sec=atoi(n)*60*60*100;
+
+ substr(n,copy+3,2); // minutes 26
+ centi_sec += atoi(n)*60*100;
+ // 01326.66E 01326.660E
+
+ substr(n,copy+6,4); // fractional minutes 66E 660E or 6601
+ // Keep the fourth digit if present, as it resolves to 0.6
+ // of a 1/100 sec resolution. Two counts make one count in
+ // the Xastir coordinate system.
+
+ // Extend the digits to full precision by adding zeroes on
+ // the end.
+ strncat(n, "0000", sizeof(n) - strlen(n));
+
+ // Get rid of the E/W character
+ if (!isdigit((int)n[2]))
+ n[2] = '0';
+ if (!isdigit((int)n[3]))
+ n[3] = '0';
+
+ n[4] = '\0'; // Make sure substring is terminated
+//fprintf(stderr,"Lon: %s\n", n);
+
+ // Add 0.5 (Poor man's rounding)
+ centi_sec += (long)((atoi(n) * 0.6) + 0.5);
+
+ if ( (char)toupper((int)copy[ 6])=='W'
+ || (char)toupper((int)copy[ 7])=='W'
+ || (char)toupper((int)copy[ 8])=='W'
+ || (char)toupper((int)copy[ 9])=='W'
+ || (char)toupper((int)copy[10])=='W'
+ || (char)toupper((int)copy[11])=='W'
+ || (char)toupper((int)copy[12])=='W') {
+ centi_sec = -centi_sec;
+ }
+
+ centi_sec +=180*60*60*100;;
+ }
+ return(centi_sec);
+}
+
+
+
+APRS_Symbol *id_callsign(char *call_sign, char * to_call) {
+ char *ptr;
+ char *id = "/aUfbYX's><OjRkv";
+ char hold[MAX_CALLSIGN+1];
+ int index;
+ static APRS_Symbol symbol;
+
+ symbol.aprs_symbol = '/';
+ symbol.special_overlay = '\0';
+ symbol.aprs_type ='/';
+ ptr=strchr(call_sign,'-');
+ if(ptr!=NULL) /* get symbol from SSID */
+ if((index=atoi(ptr+1))<= 15)
+ symbol.aprs_symbol = id[index];
+
+ if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0)
+ {
+ substr(hold, to_call+3, 3);
+ if ((ptr = strpbrk(hold, "->,")) != NULL)
+ *ptr = '\0';
+
+ if (strlen(hold) >= 2) {
+ switch (hold[0]) {
+ case 'A':
+ symbol.aprs_type = '\\';
+
+ case 'P':
+ if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
+ symbol.aprs_symbol = hold[1];
+
+ break;
+
+ case 'O':
+ symbol.aprs_type = '\\';
+
+ case 'B':
+ switch (hold[1]) {
+ case 'B':
+ symbol.aprs_symbol = '!';
+ break;
+ case 'C':
+ symbol.aprs_symbol = '"';
+ break;
+ case 'D':
+ symbol.aprs_symbol = '#';
+ break;
+ case 'E':
+ symbol.aprs_symbol = '$';
+ break;
+ case 'F':
+ symbol.aprs_symbol = '%';
+ break;
+ case 'G':
+ symbol.aprs_symbol = '&';
+ break;
+ case 'H':
+ symbol.aprs_symbol = '\'';
+ break;
+ case 'I':
+ symbol.aprs_symbol = '(';
+ break;
+ case 'J':
+ symbol.aprs_symbol = ')';
+ break;
+ case 'K':
+ symbol.aprs_symbol = '*';
+ break;
+ case 'L':
+ symbol.aprs_symbol = '+';
+ break;
+ case 'M':
+ symbol.aprs_symbol = ',';
+ break;
+ case 'N':
+ symbol.aprs_symbol = '-';
+ break;
+ case 'O':
+ symbol.aprs_symbol = '.';
+ break;
+ case 'P':
+ symbol.aprs_symbol = '/';
+ break;
+ }
+ break;
+
+ case 'D':
+ symbol.aprs_type = '\\';
+
+ case 'H':
+ switch (hold[1]) {
+ case 'S':
+ symbol.aprs_symbol = '[';
+ break;
+ case 'T':
+ symbol.aprs_symbol = '\\';
+ break;
+ case 'U':
+ symbol.aprs_symbol = ']';
+ break;
+ case 'V':
+ symbol.aprs_symbol = '^';
+ break;
+ case 'W':
+ symbol.aprs_symbol = '_';
+ break;
+ case 'X':
+ symbol.aprs_symbol = '`';
+ break;
+ }
+ break;
+
+ case 'N':
+ symbol.aprs_type = '\\';
+
+ case 'M':
+ switch (hold[1]) {
+ case 'R':
+ symbol.aprs_symbol = ':';
+ break;
+ case 'S':
+ symbol.aprs_symbol = ';';
+ break;
+ case 'T':
+ symbol.aprs_symbol = '<';
+ break;
+ case 'U':
+ symbol.aprs_symbol = '=';
+ break;
+ case 'V':
+ symbol.aprs_symbol = '>';
+ break;
+ case 'W':
+ symbol.aprs_symbol = '?';
+ break;
+ case 'X':
+ symbol.aprs_symbol = '@';
+ break;
+ }
+ break;
+
+ case 'Q':
+ symbol.aprs_type = '\\';
+
+ case 'J':
+ switch (hold[1]) {
+ case '1':
+ symbol.aprs_symbol = '{';
+ break;
+ case '2':
+ symbol.aprs_symbol = '|';
+ break;
+ case '3':
+ symbol.aprs_symbol = '}';
+ break;
+ case '4':
+ symbol.aprs_symbol = '~';
+ break;
+ }
+ break;
+
+ case 'S':
+ symbol.aprs_type = '\\';
+
+ case 'L':
+ if ('A' <= hold[1] && hold[1] <= 'Z')
+ symbol.aprs_symbol = tolower((int)hold[1]);
+
+ break;
+ }
+ if (hold[2]) {
+ if (hold[2] >= 'a' && hold[2] <= 'j') {
+ // Compressed mode numeric overlay
+ symbol.special_overlay = hold[2] - 'a';
+ }
+ else if ( (hold[2] >= '0' && hold[2] <= '9')
+ || (hold[2] >= 'A' && hold[2] <= 'Z') ) {
+ // Normal overlay character
+ symbol.special_overlay = hold[2];
+ }
+ else {
+ // Bad overlay character found
+ symbol.special_overlay = '\0';
+ }
+ }
+ else {
+ // No overlay character found
+ symbol.special_overlay = '\0';
+ }
+ }
+ }
+ return(&symbol);
+}
+
+
+
+/*
+ * See if position is defined
+ * 90°N 180°W (0,0 in internal coordinates) is our undefined position
+ * 0N/0E is excluded from trails, could be excluded from map (#define ACCEPT_0N_0E)
+ */
+
+int position_defined(long lat, long lon, int strict) {
+
+ if (lat == 0l && lon == 0l)
+ return(0); // undefined location
+#ifndef ACCEPT_0N_0E
+ if (strict)
+#endif // ACCEPT_0N_0E
+ if (lat == 90*60*60*100l && lon == 180*60*60*100l) // 0N/0E
+ return(0); // undefined location
+ return(1);
+}
+
+
+//
+// Extract data for $GPRMC, it fails if there is no position!!
+//
+// GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W,Fix-Quality[*CHK]
+// GPRMC,hhmmss[.sss],{A|V},ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},[dd]d.d[ddddd],[dd]d.d[d],ddmmyy,[ddd.d],[{E|W}][,A|D|E|N|S][*CHK]
+//
+// The last field before the checksum is entirely optional, and in
+// fact first appeared in NMEA 2.3 (fairly recently). Most GPS's do
+// not currently put out that field. The field may be null or
+// nonexistent including the comma. Only "A" or "D" are considered
+// to be active and reliable fixes if this field is present.
+// Fix-Quality:
+// A: Autonomous
+// D: Differential
+// E: Estimated
+// N: Not Valid
+// S: Simulator
+//
+// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+// $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
+// $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
+//
+int extract_RMC(AprsDataRow *p_station, char *data, char *call_sign, char *path, int *num_digits) {
+ char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
+ char lat_s[20];
+ char long_s[20];
+ int ok;
+ char *Substring[12]; // Pointers to substrings parsed by split_string()
+ char temp_string[MAX_MESSAGE_LENGTH+1];
+ char temp_char;
+
+
+ // should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
+ ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
+
+ if ( (data == NULL) || (strlen(data) < 34) ) { // Not enough data to parse position from.
+ return(ok);
+ }
+
+ p_station->record_type = NORMAL_GPS_RMC;
+ // Create a timestamp from the current time
+ // get_time saves the time in temp_data
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ /* check aprs type on call sign */
+ p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+ // Make a copy of the incoming data. The string passed to
+ // split_string() gets destroyed.
+ xastir_snprintf(temp_string,
+ sizeof(temp_string),
+ "%s",
+ data);
+ split_string(temp_string, Substring, 12);
+
+ // The Substring[] array contains pointers to each substring in
+ // the original data string.
+
+// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
+// 0 1 2 3 4 5 6 7 8 9 10 11
+
+ if (Substring[0] == NULL) // No GPRMC string
+ return(ok);
+
+ if (Substring[1] == NULL) // No time string
+ return(ok);
+
+ if (Substring[2] == NULL) // No valid fix char
+ return(ok);
+
+ if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
+ return(ok);
+// V is a warning but we can get good data still ?
+// DK7IN: got no position with 'V' !
+
+ if (Substring[3] == NULL) // No latitude string
+ return(ok);
+
+ if (Substring[4] == NULL) // No latitude N/S
+ return(ok);
+
+// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
+// (4801.1234) or leave one out (4801.12). Next character after digits should be a ','
+
+ // Count digits after the decimal point for latitude
+ if (strchr(Substring[3],'.')) {
+ *num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
+ }
+ else {
+ *num_digits = 0;
+ }
+
+ temp_char = toupper((int)Substring[4][0]);
+
+ if (temp_char != 'N' && temp_char != 'S') // Bad N/S
+ return(ok);
+
+ xastir_snprintf(lat_s,
+ sizeof(lat_s),
+ "%s%c",
+ Substring[3],
+ temp_char);
+
+ if (Substring[5] == NULL) // No longitude string
+ return(ok);
+
+ if (Substring[6] == NULL) // No longitude E/W
+ return(ok);
+
+// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
+// digit of precision. (12201.1234). Next character after digits should be a ','
+
+ temp_char = toupper((int)Substring[6][0]);
+
+ if (temp_char != 'E' && temp_char != 'W') // Bad E/W
+ return(ok);
+
+ xastir_snprintf(long_s,
+ sizeof(long_s),
+ "%s%c",
+ Substring[5],
+ temp_char);
+
+ p_station->coord_lat = convert_lat_s2l(lat_s);
+ p_station->coord_lon = convert_lon_s2l(long_s);
+
+ // If we've made it this far, We have enough for a position now!
+ ok = 1;
+
+ // Now that we have a basic position, let's see what other data
+ // can be parsed from the packet. The rest of it can still be
+ // corrupt, so we're proceeding carefully under yellow alert on
+ // impulse engines only.
+
+// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
+// 0 1 2 3 4 5 6 7 8 9 10 11
+
+ if (Substring[7] == NULL) { // No speed string
+ p_station->speed[0] = '\0'; // No speed available
+ return(ok);
+ }
+ else {
+ xastir_snprintf(p_station->speed,
+ MAX_SPEED,
+ "%s",
+ Substring[7]);
+ // Is it always knots, otherwise we need a conversion!
+ }
+
+ if (Substring[8] == NULL) { // No course string
+ xastir_snprintf(p_station->course,
+ sizeof(p_station->course),
+ "000.0"); // No course available
+ return(ok);
+ }
+ else {
+ xastir_snprintf(p_station->course,
+ MAX_COURSE,
+ "%s",
+ Substring[8]);
+ }
+
+ return(ok);
+}
+
+
+//
+// Extract data for $GPGLL
+//
+// $GPGLL,4748.811,N,12219.564,W,033850,A*3C
+// lat, long, UTCtime in hhmmss, A=Valid, checksum
+//
+// GPGLL,4748.811,N,12219.564,W,033850,A*3C
+// 0 1 2 3 4 5 6
+//
+int extract_GLL(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
+ char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
+ char lat_s[20];
+ char long_s[20];
+ int ok;
+ char *Substring[7]; // Pointers to substrings parsed by split_string()
+ char temp_string[MAX_MESSAGE_LENGTH+1];
+ char temp_char;
+
+
+ ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
+
+ if ( (data == NULL) || (strlen(data) < 28) ) // Not enough data to parse position from.
+ return(ok);
+
+ p_station->record_type = NORMAL_GPS_GLL;
+ // Create a timestamp from the current time
+ // get_time saves the time in temp_data
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ /* check aprs type on call sign */
+ p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+ // Make a copy of the incoming data. The string passed to
+ // split_string() gets destroyed.
+ xastir_snprintf(temp_string,
+ sizeof(temp_string),
+ "%s",
+ data);
+ split_string(temp_string, Substring, 7);
+
+ // The Substring[] array contains pointers to each substring in
+ // the original data string.
+
+ if (Substring[0] == NULL) // No GPGGA string
+ return(ok);
+
+ if (Substring[1] == NULL) // No latitude string
+ return(ok);
+
+ if (Substring[2] == NULL) // No N/S string
+ return(ok);
+
+ if (Substring[3] == NULL) // No longitude string
+ return(ok);
+
+ if (Substring[4] == NULL) // No E/W string
+ return(ok);
+
+ temp_char = toupper((int)Substring[2][0]);
+ if (temp_char != 'N' && temp_char != 'S')
+ return(ok);
+
+ xastir_snprintf(lat_s,
+ sizeof(lat_s),
+ "%s%c",
+ Substring[1],
+ temp_char);
+// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
+// (4801.1234). Next character after digits should be a ','
+
+ // Count digits after the decimal point for latitude
+ if (strchr(Substring[1],'.')) {
+ *num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
+ }
+ else {
+ *num_digits = 0;
+ }
+
+ temp_char = toupper((int)Substring[4][0]);
+ if (temp_char != 'E' && temp_char != 'W')
+ return(ok);
+
+ xastir_snprintf(long_s,
+ sizeof(long_s),
+ "%s%c",
+ Substring[3],
+ temp_char);
+// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
+// digit of precision. (12201.1234). Next character after digits should be a ','
+
+ p_station->coord_lat = convert_lat_s2l(lat_s);
+ p_station->coord_lon = convert_lon_s2l(long_s);
+ ok = 1; // We have enough for a position now
+
+ xastir_snprintf(p_station->course,
+ sizeof(p_station->course),
+ "000.0"); // Fill in with dummy values
+ p_station->speed[0] = '\0'; // Fill in with dummy values
+
+ // A is valid, V is a warning but we can get good data still?
+ // We don't currently check the data valid flag.
+
+ return(ok);
+}
+
+
+
+//
+// Extract data for $GPGGA
+//
+// GPGGA,UTC-Time,lat,N/S,long,E/W,GPS-Quality,nsat,HDOP,MSL-Meters,M,Geoidal-Meters,M,DGPS-Data-Age(seconds),DGPS-Ref-Station-ID[*CHK]
+// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
+//
+// GPS-Quality:
+// 0: Invalid Fix
+// 1: GPS Fix
+// 2: DGPS Fix
+// 3: PPS Fix
+// 4: RTK Fix
+// 5: Float RTK Fix
+// 6: Estimated (dead-reckoning) Fix
+// 7: Manual Input Mode
+// 8: Simulation Mode
+//
+// $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75
+// $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
+//
+// nsat=Number of Satellites being tracked
+//
+//
+int extract_GGA(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
+ char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
+ char lat_s[20];
+ char long_s[20];
+ int ok;
+ char *Substring[15]; // Pointers to substrings parsed by split_string()
+ char temp_string[MAX_MESSAGE_LENGTH+1];
+ char temp_char;
+ int temp_num;
+
+
+ ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
+
+ if ( (data == NULL) || (strlen(data) < 32) ) // Not enough data to parse position from.
+ return(ok);
+
+ p_station->record_type = NORMAL_GPS_GGA;
+ // Create a timestamp from the current time
+ // get_time saves the time in temp_data
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ /* check aprs type on call sign */
+ p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+ // Make a copy of the incoming data. The string passed to
+ // split_string() gets destroyed.
+ xastir_snprintf(temp_string,
+ sizeof(temp_string),
+ "%s",
+ data);
+ split_string(temp_string, Substring, 15);
+
+ // The Substring[] array contains pointers to each substring in
+ // the original data string.
+
+
+// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
+// 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1
+// 0 1 2 3 4
+
+ if (Substring[0] == NULL) // No GPGGA string
+ return(ok);
+
+ if (Substring[1] == NULL) // No time string
+ return(ok);
+
+ if (Substring[2] == NULL) // No latitude string
+ return(ok);
+
+ if (Substring[3] == NULL) // No latitude N/S
+ return(ok);
+
+// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
+// (4801.1234). Next character after digits should be a ','
+
+ // Count digits after the decimal point for latitude
+ if (strchr(Substring[2],'.')) {
+ *num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
+ }
+ else {
+ *num_digits = 0;
+ }
+
+ temp_char = toupper((int)Substring[3][0]);
+
+ if (temp_char != 'N' && temp_char != 'S') // Bad N/S
+ return(ok);
+
+ xastir_snprintf(lat_s,
+ sizeof(lat_s),
+ "%s%c",
+ Substring[2],
+ temp_char);
+
+ if (Substring[4] == NULL) // No longitude string
+ return(ok);
+
+ if (Substring[5] == NULL) // No longitude E/W
+ return(ok);
+
+// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
+// digit of precision. (12201.1234). Next character after digits should be a ','
+
+ temp_char = toupper((int)Substring[5][0]);
+
+ if (temp_char != 'E' && temp_char != 'W') // Bad E/W
+ return(ok);
+
+ xastir_snprintf(long_s,
+ sizeof(long_s),
+ "%s%c",
+ Substring[4],
+ temp_char);
+
+ p_station->coord_lat = convert_lat_s2l(lat_s);
+ p_station->coord_lon = convert_lon_s2l(long_s);
+
+ // If we've made it this far, We have enough for a position now!
+ ok = 1;
+
+
+ // Now that we have a basic position, let's see what other data
+ // can be parsed from the packet. The rest of it can still be
+ // corrupt, so we're proceeding carefully under yellow alert on
+ // impulse engines only.
+
+ // Check for valid fix {
+ if (Substring[6] == NULL
+ || Substring[6][0] == '0' // Fix quality
+ || Substring[7] == NULL // Sat number
+ || Substring[8] == NULL // hdop
+ || Substring[9] == NULL) { // Altitude in meters
+ p_station->sats_visible[0] = '\0'; // Store empty sats visible
+ p_station->altitude[0] = '\0';; // Store empty altitude
+ return(ok); // A field between fix quality and altitude is missing
+ }
+
+// Need to check for validity of this number. Should be 0-12? Perhaps a few more with WAAS, GLONASS, etc?
+ temp_num = atoi(Substring[7]);
+ if (temp_num < 0 || temp_num > 30) {
+ return(ok); // Number of satellites not valid
+ }
+ else {
+ // Store
+ xastir_snprintf(p_station->sats_visible,
+ sizeof(p_station->sats_visible),
+ "%d",
+ temp_num);
+ }
+
+
+// Check for valid number for HDOP instead of just throwing it away?
+
+
+ xastir_snprintf(p_station->altitude,
+ sizeof(p_station->altitude),
+ "%s",
+ Substring[9]); // Get altitude
+
+// Need to check for valid altitude before conversion
+
+ // unit is in meters, if not adjust value ???
+
+ if (Substring[10] == NULL) // No units for altitude
+ return(ok);
+
+ if (Substring[10][0] != 'M') {
+ //fprintf(stderr,"ERROR: should adjust altitude for meters\n");
+ //} else { // Altitude units wrong. Assume altitude bad
+ p_station->altitude[0] = '\0';
+ }
+
+ return(ok);
+}
+
+
+
+static void extract_multipoints(AprsDataRow *p_station,
+ char *data,
+ int type,
+ int remove_string) {
+ // If they're in there, the multipoints start with the
+ // sequence <space><rbrace><lower><digit> and end with a <lbrace>.
+ // In addition, there must be no spaces in there, and there
+ // must be an even number of characters (after the lead-in).
+
+ char *p, *p2;
+ int found = 0;
+ char *end;
+ int data_size;
+
+
+ if (data == NULL) {
+ return;
+ }
+
+//fprintf(stderr,"Data: %s\t\t", data);
+
+ data_size = strlen(data);
+
+ end = data + (strlen(data) - 7); // 7 == 3 lead-in chars, plus 2 points
+
+ p_station->num_multipoints = 0;
+
+ /*
+ for (p = data; !found && p <= end; ++p) {
+ if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) &&
+ (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
+ found = 1;
+ }
+ }
+ */
+
+ // Start looking at the beginning of the data.
+
+ p = data;
+
+ // Look for the opening string.
+
+ while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
+ // The opening string was found. Check the following information.
+
+ if (islower((int)*(p+2)) && g_ascii_isdigit(*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
+ // It all looks good!
+
+ found = 1;
+ }
+ else {
+ // The following characters are not right. Advance and
+ // look again.
+
+ ++p;
+ }
+ }
+
+ if (found) {
+ long multiplier;
+ double d;
+ char *m_start = p; // Start of multipoint string
+ char ok = 1;
+
+ // The second character (the lowercase) indicates additional style information,
+ // such as color, line type, etc.
+
+ p_station->style = *(p+2);
+
+ // The third character (the digit) indicates the way the points should be
+ // used. They may be used to draw a closed polygon, a series of line segments,
+ // etc.
+
+ p_station->type = *(p+3);
+
+ // The fourth character indicates the scale of the coordinates that
+ // follow. It may range from '!' to 'z'. The value represents the
+ // unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
+ //
+ // Use the following formula to convert the char to the value:
+ // (10 ^ ((c - 33) / 20)) / 10000 degrees
+ //
+ // Finally we have to convert to Xastir units. Xastir stores coordinates
+ // as hudredths of seconds. There are 360,000 of those per degree, so we
+ // need to multiply by that factor so our numbers will be converted to
+ // Xastir units.
+
+ p = p + 4;
+
+ if (*p < '!' || *p > 'z') {
+ fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
+ ok = 0; // Failure
+ }
+ else {
+
+ d = (double)(*p);
+ d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
+ multiplier = (long)d;
+
+ ++p;
+
+ // The remaining characters are in pairs. Each pair is the
+ // offset lat and lon for one of the points. (The offset is
+ // from the actual location of the object.) Convert each
+ // character to its numeric value and save it.
+
+ while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS) {
+ // The characters are in the range '"' (34 decimal) to 'z' (122). They
+ // encode values in the range -44 to +44. To convert to the correct
+ // value 78 is subtracted from the character's value.
+
+ int lat_val = *p - 78;
+ int lon_val = *(p+1) - 78;
+
+ // Check for correct values.
+
+ if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
+ char temp[MAX_LINE_SIZE+1];
+ int i;
+
+ // Filter the string so we don't send strange
+ // chars to the xterm
+ for (i = 0; i < (int)strlen(data); i++) {
+ temp[i] = data[i] & 0x7f;
+ if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
+ temp[i] = ' ';
+ }
+ temp[strlen(data)] = '\0';
+
+ fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
+ temp,
+ lat_val,
+ lon_val);
+
+ p_station->num_multipoints = 0; // forget any points we already set
+ ok = 0; // Failure to decode
+ break;
+ }
+
+ // Malloc the storage area for this if we don't have
+ // it yet.
+ if (p_station->multipoint_data == NULL) {
+
+ p_station->multipoint_data = malloc(sizeof(AprsMultipointRow));
+ if (p_station->multipoint_data == NULL) {
+ p_station->num_multipoints = 0;
+ fprintf(stderr,"Couldn't malloc AprsMultipointRow'\n");
+ return;
+ }
+ }
+
+ // Add the offset to the object's position to obtain the position of the point.
+ // Note that we're working in Xastir coordinates, and in North America they
+ // are exactly opposite to lat/lon (larger numbers are farther east and south).
+ // An offset with a positive value means that the point should be north and/or
+ // west of the object, so we have to *subtract* the offset to get the correct
+ // placement in Xastir coordinates.
+ // TODO: Consider what we should do in the other geographic quadrants. Should we
+ // check here for the correct sign of the offset? Or should the program that
+ // creates the offsets take that into account?
+
+ p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
+ = p_station->coord_lon - (lon_val * multiplier);
+ p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
+ = p_station->coord_lat - (lat_val * multiplier);
+
+ p += 2;
+ ++p_station->num_multipoints;
+ } // End of while loop
+ }
+
+ if (ok && remove_string) {
+ // We've successfully decoded a multipoint object?
+ // Remove the multipoint strings (and the sequence
+ // number at the end if present) from the data string.
+ // m_start points to the first character (a space). 'p'
+ // should be pointing at the LBRACE character.
+
+ // Make 'p' point to just after the end of the chars
+ while ( (p < data+strlen(data)) && (*p != ' ') ) {
+ p++;
+ }
+ // The string that 'p' points to now may be empty
+
+ // Truncate "data" at the starting brace - 1
+ *m_start = '\0';
+
+ // Now we have two strings inside "data". Copy the 2nd
+ // string directly onto the end of the first.
+ strncat(data, p, data_size+1);
+
+ // The multipoint string and sequence number should be
+ // erased now from "data".
+//fprintf(stderr,"New Data: %s\n", data);
+ }
+
+ }
+
+}
+
+
+
+
+// Returns time in seconds since the Unix epoch.
+//
+time_t sec_now(void) {
+ time_t timenw;
+ time_t ret;
+
+ ret = time(&timenw);
+ return(ret);
+}
+
+
+int is_my_station(AprsDataRow *p_station) {
+ // if station is owned by me (including SSID)
+ return(p_station->flag & ST_MYSTATION);
+}
+
+int is_my_object_item(AprsDataRow *p_station) {
+ // If object/item is owned by me (including SSID)
+ return(p_station->flag & ST_MYOBJITEM);
+}
+
+/*
+ * Display text in the status line, text is removed after timeout
+ */
+void statusline(char *status_text,int update) {
+
+// Maybe useful
+/*
+ XmTextFieldSetString (text, status_text);
+ last_statusline = sec_now(); // Used for auto-ID timeout
+*/
+}
+
+
+/*
+ * Extract text inserted by TNC X-1J4 from start of info line
+ */
+void extract_TNC_text(char *info) {
+ int i,j,len;
+
+ if (strncasecmp(info,"thenet ",7) == 0) { // 1st match
+ len = strlen(info)-1;
+ for (i=7;i<len;i++) {
+ if (info[i] == ')')
+ break;
+ }
+ len++;
+ if (i>7 && info[i] == ')' && info[i+1] == ' ') { // found
+ i += 2;
+ for (j=0;i<=len;i++,j++) {
+ info[j] = info[i];
+ }
+ }
+ }
+}
+
+
+
+/*
+ * Check for valid path and change it to TAPR format
+ * Add missing asterisk for stations that we must have heard via a digi
+ * Extract port for KAM TNCs
+ * Handle igate injection ID formats: "callsign-ssid,I" & "callsign-0ssid"
+ *
+ * TAPR-2 Format:
+ * KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
+ *
+ * AEA Format:
+ * KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
+ */
+
+int valid_path(char *path) {
+ int i,len,hops,j;
+ int type,ast,allast,ins;
+ char ch;
+
+
+ len = (int)strlen(path);
+ type = 0; // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
+ hops = 1;
+ ast = 0;
+ allast = 0;
+
+ // There are some multi-port TNCs that deliver the port at the end
+ // of the path. For now we discard this information. If there is
+ // multi-port TNC support some day, we should write the port into
+ // our database.
+ // KAM: /V /H
+ // KPC-9612: /0 /1 /2
+ if (len > 2 && path[len-2] == '/') {
+ ch = path[len-1];
+ if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
+ path[len-2] = '\0';
+ len = (int)strlen(path);
+ }
+ }
+
+
+ // One way of adding igate injection ID is to add "callsign-ssid,I".
+ // We need to remove the ",I" portion so it doesn't count as another
+ // digi here. This should be at the end of the path.
+ if (len > 2 && path[len-2] == ',' && path[len-1] == 'I') { // Found ",I"
+ //fprintf(stderr,"%s\n",path);
+ //fprintf(stderr,"Found ',I'\n");
+ path[len-2] = '\0';
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n\n",path);
+ }
+ // Now look for the same thing but with a '*' character at the end.
+ // This should be at the end of the path.
+ if (len > 3 && path[len-3] == ',' && path[len-2] == 'I' && path[len-1] == '*') { // Found ",I*"
+ //fprintf(stderr,"%s\n",path);
+ //fprintf(stderr,"Found ',I*'\n");
+ path[len-3] = '\0';
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n\n",path);
+ }
+
+
+ // Another method of adding igate injection ID is to add a '0' in front of
+ // the SSID. For WE7U it would change to WE7U-00, for WE7U-15 it would
+ // change to WE7U-015. Take out this zero so the rest of the decoding will
+ // work. This should be at the end of the path.
+ // Also look for the same thing but with a '*' character at the end.
+ if (len > 6) {
+ for (i=len-1; i>len-6; i--) {
+ if (path[i] == '-' && path[i+1] == '0') {
+ //fprintf(stderr,"%s\n",path);
+ for (j=i+1; j<len; j++) {
+ path[j] = path[j+1]; // Shift everything left by one
+ }
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n\n",path);
+ }
+ // Check whether we just chopped off the '0' from "-0".
+ // If so, chop off the dash as well.
+ if (path[i] == '-' && path[i+1] == '\0') {
+ //fprintf(stderr,"%s\tChopping off dash\n",path);
+ path[i] = '\0';
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n",path);
+ }
+ // Check for "-*", change to '*' only
+ if (path[i] == '-' && path[i+1] == '*') {
+ //fprintf(stderr,"%s\tChopping off dash\n",path);
+ path[i] = '*';
+ path[i+1] = '\0';
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n",path);
+ }
+ // Check for "-0" or "-0*". Change to "" or "*".
+ if ( path[i] == '-' && path[i+1] == '0' ) {
+ //fprintf(stderr,"%s\tShifting left by two\n",path);
+ for (j=i; j<len; j++) {
+ path[j] = path[j+2]; // Shift everything left by two
+ }
+ len = (int)strlen(path);
+ //fprintf(stderr,"%s\n",path);
+ }
+ }
+ }
+
+
+ for (i=0,j=0; i<len; i++) {
+ ch = path[i];
+
+ if (ch == '>' || ch == ',') { // found digi call separator
+ // We're at the start of a callsign entry in the path
+
+ if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
+ return(0); // more than one asterisk in call or wrong call size
+ }
+ ast = 0; // reset local asterisk counter
+
+ j = i+1; // set to start of next call
+ if (ch == ',')
+ type |= 0x02; // set TAPR2 flag
+ else
+ type |= 0x01; // set AEA flag (found '>')
+ hops++; // count hops
+ }
+
+ else { // digi call character or asterisk
+ // We're in the middle of a callsign entry
+
+ if (ch == '*') {
+ ast++; // count asterisks in call
+ allast++; // count asterisks in path
+ }
+ else if ((ch <'A' || ch > 'Z') // Not A-Z
+ && (ch <'a' || ch > 'z') // Not a-z
+ && (ch <'0' || ch > '9') // Not 0-9
+ && ch != '-') {
+ // Note that Q-construct and internet callsigns can
+ // have a-z in them, AX.25 callsigns cannot unless
+ // they are in a 3rd-party packet.
+
+ return(0); // wrong character in path
+ }
+ }
+ }
+ if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
+ return(0); // more than one asterisk or wrong call size
+ }
+
+ if (type == 0x03) {
+ return(0); // wrong format, both '>' and ',' in path
+ }
+
+ if (hops > 9) { // [APRS Reference chapter 3]
+ return(0); // too much hops, destination + 0-8 digipeater addresses
+ }
+
+ if (type == 0x01) {
+ int delimiters[20];
+ int k = 0;
+ char dest[15];
+ char rest[100];
+
+ for (i=0; i<len; i++) {
+ if (path[i] == '>') {
+ path[i] = ','; // Exchange separator character
+ delimiters[k++] = i; // Save the delimiter indexes
+ }
+ }
+
+ // We also need to move the destination callsign to the end.
+ // AEA has them in a different order than TAPR-2 format.
+ // We'll move the destination address between delimiters[0]
+ // and [1] to the end of the string.
+
+ //fprintf(stderr,"Orig. Path:%s\n",path);
+ // Save the destination
+ xastir_snprintf(dest,sizeof(dest),"%s",&path[delimiters[--k]+1]);
+ dest[strlen(path) - delimiters[k] - 1] = '\0'; // Terminate it
+ dest[14] = '\0'; // Just to make sure
+ path[delimiters[k]] = '\0'; // Delete it from the original path
+ //fprintf(stderr,"Destination: %s\n",dest);
+
+ // TAPR-2 Format:
+ // KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
+ //
+ // AEA Format:
+ // KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
+ // 9 15 20
+
+ // We now need to insert the destination into the middle of
+ // the string. Save part of it in another variable first.
+ xastir_snprintf(rest,
+ sizeof(rest),
+ "%s",
+ path);
+ //fprintf(stderr,"Rest:%s\n",rest);
+ xastir_snprintf(path,len+1,"%s,%s",dest,rest);
+ //fprintf(stderr,"New Path:%s\n",path);
+ }
+
+ if (allast < 1) { // try to insert a missing asterisk
+ ins = 0;
+ hops = 0;
+
+ for (i=0; i<len; i++) {
+
+ for (j=i; j<len; j++) { // search for separator
+ if (path[j] == ',')
+ break;
+ }
+
+ if (hops > 0 && (j - i) == 5) { // WIDE3
+ if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
+ && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9') {
+ ins = j;
+ }
+ }
+
+/*
+Don't do this! It can mess up relay/wide1-1 digipeating by adding
+an asterisk later in the path than the first unused digi.
+ if (hops > 0 && (j - i) == 7) { // WIDE3-2
+ if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
+ && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9'
+ && path[i+5] == '-' && path[i+6] >= '0' && path[i+6] <= '9'
+ && (path[i+4] != path[i+6]) ) {
+ ins = j;
+ }
+ }
+*/
+
+ if (hops > 0 && (j - i) == 6) { // TRACE3
+ if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
+ && path[i+3] == 'C' && path[i+4] == 'E'
+ && path[i+5] >= '0' && path[i+5] <= '9') {
+ if (hops == 1)
+ ins = j;
+ else
+ ins = i-1;
+ }
+ }
+
+/*
+Don't do this! It can mess up relay/wide1-1 digipeating by adding
+an asterisk later in the path than the first unused digi.
+ if (hops > 0 && (j - i) == 8) { // TRACE3-2
+ if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
+ && path[i+3] == 'C' && path[i+4] == 'E' && path[i+5] >= '0'
+ && path[i+5] <= '9' && path[i+6] == '-' && path[i+7] >= '0'
+ && path[i+7] <= '9' && (path[i+5] != path[i+7]) ) {
+ if (hops == 1)
+ ins = j;
+ else
+ ins = i-1;
+ }
+ }
+*/
+
+ hops++;
+ i = j; // skip to start of next call
+ }
+ if (ins > 0) {
+ for (i=len;i>=ins;i--) {
+ path[i+1] = path[i]; // generate space for '*'
+ // we work on a separate path copy which is long enough to do it
+ }
+ path[ins] = '*'; // and insert it
+ }
+ }
+ return(1); // Path is good
+}
+
+
+
+
+char *remove_leading_spaces(char *data) {
+ int i,j;
+ int count;
+
+ if (data == NULL)
+ return NULL;
+
+ if (strlen(data) == 0)
+ return NULL;
+
+ count = 0;
+ // Count the leading space characters
+ for (i = 0; i < (int)strlen(data); i++) {
+ if (data[i] == ' ') {
+ count++;
+ }
+ else { // Found a non-space
+ break;
+ }
+ }
+
+ // Check whether entire string was spaces
+ if (count == (int)strlen(data)) {
+ // Empty the string
+ data[0] = '\0';
+ }
+ else if (count > 0) { // Found some spaces
+ i = 0;
+ for( j = count; j < (int)strlen(data); j++ ) {
+ data[i++] = data[j]; // Move string left
+ }
+ data[i] = '\0'; // Terminate the new string
+ }
+
+ return(data);
+}
+
+int is_num_chr(char ch) {
+ return((int)isdigit(ch));
+}
+
+char *remove_trailing_spaces(char *data) {
+ int i;
+
+ if (data == NULL)
+ return NULL;
+
+ if (strlen(data) == 0)
+ return NULL;
+
+ for(i=strlen(data)-1;i>=0;i--)
+ if(data[i] == ' ')
+ data[i] = '\0';
+ else
+ break;
+
+ return(data);
+}
+
+
+
+char *remove_trailing_asterisk(char *data) {
+ int i;
+
+ if (data == NULL)
+ return NULL;
+
+ if (strlen(data) == 0)
+ return NULL;
+
+// Should the test here be i>=0 ??
+ for(i=strlen(data)-1;i>0;i--) {
+ if(data[i] == '*')
+ data[i] = '\0';
+ }
+ return(data);
+}
+
+//--------------------------------------------------------------------
+//Removes all control codes ( <0x20 or >0x7e ) from a string, including
+// CR's, LF's, tab's, etc.
+//
+void makePrintable(char *cp) {
+ int i,j;
+ int len = (int)strlen(cp);
+ unsigned char *ucp = (unsigned char *)cp;
+
+ for (i=0, j=0; i<=len; i++) {
+ ucp[i] &= 0x7f; // Clear 8th bit
+ if ( ((ucp[i] >= (unsigned char)0x20) && (ucp[i] <= (unsigned char)0x7e))
+ || ((char)ucp[i] == '\0') ) // Check for printable or terminating 0
+ ucp[j++] = ucp[i] ; // Copy to (possibly) new location if printable
+ }
+}
+
+
+
+
+/*
+ * Check for a valid AX.25 call
+ * Valid calls consist of up to 6 uppercase alphanumeric characters
+ * plus optional SSID (four-bit integer) [APRS Reference, AX.25 Reference]
+ */
+int valid_call(char *call) {
+ int len, ok;
+ int i, del, has_num, has_chr;
+ char c;
+
+ has_num = 0;
+ has_chr = 0;
+ ok = 1;
+ len = (int)strlen(call);
+
+ if (len == 0)
+ return(0); // wrong size
+
+ while (call[0]=='c' && call[1]=='m' && call[2]=='d' && call[3]==':') {
+ // Erase TNC prompts from beginning of callsign. This may
+ // not be the right place to do this, but it came in handy
+ // here, so that's where I put it. -- KB6MER
+
+ for(i=0; call[i+4]; i++)
+ call[i]=call[i+4];
+
+ call[i++]=0;
+ call[i++]=0;
+ call[i++]=0;
+ call[i++]=0;
+ len=strlen(call);
+
+ }
+
+ if (len > 9)
+ return(0); // Too long for valid call (6-2 max e.g. KB6MER-12)
+
+ del = 0;
+ for (i=len-2;ok && i>0 && i>=len-3;i--) { // search for optional SSID
+ if (call[i] =='-')
+ del = i; // found the delimiter
+ }
+ if (del) { // we have a SSID, so check it
+ if (len-del == 2) { // 2 char SSID
+ if (call[del+1] < '1' || call[del+1] > '9') // -1 ... -9
+ del = 0;
+ }
+ else { // 3 char SSID
+ if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5') // -10 ... -15
+ del = 0;
+ }
+ }
+
+ if (del)
+ len = del; // length of base call
+
+ for (i=0;ok && i<len;i++) { // check for uppercase alphanumeric
+ c = call[i];
+
+ if (c >= 'A' && c <= 'Z')
+ has_chr = 1; // we need at least one char
+ else if (c >= '0' && c <= '9')
+ has_num = 1; // we need at least one number
+ else
+ ok = 0; // wrong character in call
+ }
+
+// if (!has_num || !has_chr) // with this we also discard NOCALL etc.
+ if (!has_chr)
+ ok = 0;
+
+ ok = (ok && strcmp(call,"NOCALL") != 0); // check for errors
+ ok = (ok && strcmp(call,"ERROR!") != 0);
+ ok = (ok && strcmp(call,"WIDE") != 0);
+ ok = (ok && strcmp(call,"RELAY") != 0);
+ ok = (ok && strcmp(call,"MAIL") != 0);
+
+ return(ok);
+}
+
+
+/*
+ * Check whether callsign is mine. "exact == 1" checks the SSID
+ * for a match as well. "exact == 0" checks only the base
+ * callsign.
+ */
+int is_my_call(char *call, int exact) {
+ char *p_del;
+ int ok;
+
+
+ // U.S. special-event callsigns can be as short as three
+ // characters, any less and we don't have a valid callsign. We
+ // don't check for that restriction here though.
+
+ if (exact) {
+ // We're looking for an exact match
+ ok = (int)( !strcmp(call,_aprs_mycall ) );
+ //fprintf(stderr,"My exact call found: %s\n",call);
+ }
+ else {
+ // We're looking for a similar match. Compare only up to
+ // the '-' in each (if present).
+ int len1,len2;
+
+ p_del = index(call,'-');
+ if (p_del == NULL)
+ len1 = (int)strlen(call);
+ else
+ len1 = p_del - call;
+
+ p_del = index(_aprs_mycall,'-');
+ if (p_del == NULL)
+ len2 = (int)strlen(_aprs_mycall);
+ else
+ len2 = p_del - _aprs_mycall;
+
+ ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
+ //fprintf(stderr,"My base call found: %s\n",call);
+ }
+
+ return(ok);
+}
+
+
+
+
+/*
+ * Check for a valid internet name.
+ * Accept darned-near anything here as long as it is the proper
+ * length and printable.
+ */
+int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
+ int len, i, ok;
+ char *ptr;
+
+ len = (int)strlen(name);
+
+ if (len > 9 || len == 0) // max 9 printable ASCII characters
+ return(0); // wrong size
+
+ for (i=0;i<len;i++)
+ if (!isprint((int)name[i]))
+ return(0); // not printable
+
+ // Modifies "origin" if a match found
+ //
+ if (len >= 5 && strncmp(name,"aprsd",5) == 0) {
+ snprintf(origin, origin_size, "INET");
+ origin[4] = '\0'; // Terminate it
+ return(1); // aprsdXXXX is ok
+ }
+
+ // Modifies "origin" if a match found
+ //
+ if (len == 6) { // check for NWS
+ ok = 1;
+ for (i=0;i<6;i++)
+ if (name[i] <'A' || name[i] > 'Z') // 6 uppercase characters
+ ok = 0;
+ ok = ok && (info != NULL); // check if we can test info
+ if (ok) {
+ ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
+ ok = (ptr != NULL);
+
+ if (!ok) {
+ ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
+ ok = (ptr != NULL);
+ }
+ }
+ if (ok) {
+ snprintf(origin, origin_size, "INET-NWS");
+ origin[8] = '\0';
+ return(1); // weather alerts
+ }
+ }
+
+ return(1); // Accept anything else if we get to this point in
+ // the code. After all, the message came from the
+ // internet, not from RF.
+}
+
+
+
+/*
+ * Extract third-party traffic from information field before processing
+ */
+int extract_third_party(char *call,
+ char *path,
+ int path_size,
+ char **info,
+ char *origin,
+ int origin_size) {
+ int ok;
+ char *p_call;
+ char *p_path;
+
+ p_call = NULL; // to make the compiler happy...
+ p_path = NULL; // to make the compiler happy...
+ ok = 0;
+ if (!is_my_call(call,1)) { // Check SSID also
+ // todo: add reporting station call to database ??
+ // but only if not identical to reported call
+ (*info) = (*info) +1; // strip '}' character
+ p_call = strtok((*info),">"); // extract call
+ if (p_call != NULL) {
+ p_path = strtok(NULL,":"); // extract path
+ if (p_path != NULL) {
+ (*info) = strtok(NULL,""); // rest is information field
+ if ((*info) != NULL) // the above looks dangerous, but works on same string
+ if (strlen(p_path) < 100)
+ ok = 1; // we have found all three components
+ }
+ }
+ }
+
+ if (ok) {
+
+ snprintf(path,
+ path_size,
+ "%s",
+ p_path);
+
+ ok = valid_path(path); // check the path and convert it to TAPR format
+ // Note that valid_path() also removes igate injection identifiers
+
+ }
+
+ if (ok) { // check callsign
+ (void)remove_trailing_asterisk(p_call); // is an asterisk valid here ???
+ if (valid_inet_name(p_call,(*info),origin,origin_size)) { // accept some of the names used in internet
+ // Treat it as object with special origin
+ snprintf(call,
+ MAX_CALLSIGN+1,
+ "%s",
+ p_call);
+ }
+ else if (valid_call(p_call)) { // accept real AX.25 calls
+ snprintf(call,
+ MAX_CALLSIGN+1,
+ "%s",
+ p_call);
+ }
+ else {
+ ok = 0;
+ }
+ }
+ return(ok);
+}
+
+
+
+// DK7IN 99
+/*
+ * Extract Compressed Position Report Data Formats from begin of line
+ * [APRS Reference, chapter 9]
+ *
+ * If a position is found, it is deleted from the data. If a
+ * compressed position is found, delete the three csT bytes as well,
+ * even if all spaces.
+ * Returns 0 if the packet is NOT a properly compressed position
+ * packet, returns 1 if ok.
+ */
+int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
+ int ok;
+ int x1, x2, x3, x4, y1, y2, y3, y4;
+ int c = 0;
+ int s = 0;
+ int T = 0;
+ int len;
+ char *my_data;
+ float lon = 0;
+ float lat = 0;
+ float range;
+ int skip = 0;
+ char L;
+
+ my_data = (*info);
+
+ // Check leading char. Must be one of these:
+ // '/'
+ // '\'
+ // A-Z
+ // a-j
+ //
+ L = my_data[0];
+ if ( L == '/'
+ || L == '\\'
+ || ( L >= 'A' && L <= 'Z' )
+ || ( L >= 'a' && L <= 'j' ) ) {
+ // We're good so far
+ }
+ else {
+ // Note one of the symbol table or overlay characters, so
+ // there's something funky about this packet. It's not a
+ // properly formatted compressed position.
+ return(0);
+ }
+
+ //fprintf(stderr,"my_data: %s\n",my_data);
+
+ // If c = space, csT bytes are ignored. Minimum length: 8
+ // bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
+ len = strlen(my_data);
+ ok = (int)(len >= 13);
+
+ if (ok) {
+ y1 = (int)my_data[1] - '!';
+ y2 = (int)my_data[2] - '!';
+ y3 = (int)my_data[3] - '!';
+ y4 = (int)my_data[4] - '!';
+ x1 = (int)my_data[5] - '!';
+ x2 = (int)my_data[6] - '!';
+ x3 = (int)my_data[7] - '!';
+ x4 = (int)my_data[8] - '!';
+
+ // csT bytes
+ if (my_data[10] == ' ') // Space
+ c = -1; // This causes us to ignore csT
+ else {
+ c = (int)my_data[10] - '!';
+ s = (int)my_data[11] - '!';
+ T = (int)my_data[12] - '!';
+ }
+ skip = 13;
+
+ // Convert ' ' to '0'. Not specified in APRS Reference! Do
+ // we need it?
+ if (x1 == -1) x1 = '\0';
+ if (x2 == -1) x2 = '\0';
+ if (x3 == -1) x3 = '\0';
+ if (x4 == -1) x4 = '\0';
+ if (y1 == -1) y1 = '\0';
+ if (y2 == -1) y2 = '\0';
+ if (y3 == -1) y3 = '\0';
+ if (y4 == -1) y4 = '\0';
+
+ ok = (int)(ok && (x1 >= '\0' && x1 < 91)); // /YYYYXXXX$csT
+ ok = (int)(ok && (x2 >= '\0' && x2 < 91)); // 0123456789012
+ ok = (int)(ok && (x3 >= '\0' && x3 < 91));
+ ok = (int)(ok && (x4 >= '\0' && x4 < 91));
+ ok = (int)(ok && (y1 >= '\0' && y1 < 91));
+ ok = (int)(ok && (y2 >= '\0' && y2 < 91));
+ ok = (int)(ok && (y3 >= '\0' && y3 < 91));
+ ok = (int)(ok && (y4 >= '\0' && y4 < 91));
+
+ T &= 0x3F; // DK7IN: force Compression Byte to valid format
+ // mask off upper two unused bits, they should be zero!?
+
+ ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
+
+ if (ok) {
+ lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0: 90°N
+ lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180°W
+ lat *= 60 * 60 * 100; // in 1/100 sec
+ lon *= 60 * 60 * 100; // in 1/100 sec
+
+ // The below check should _not_ be done. Compressed
+ // format can resolve down to about 1 foot worldwide
+ // (0.3 meters).
+ //if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
+ // ok = 0; // check max resolution 0.01 min to
+ // catch even more errors
+ }
+ }
+
+ if (ok) {
+ overlay_symbol(my_data[9], my_data[0], p_station); // Symbol / Table
+
+ // Callsign check here includes checking SSID for an exact
+ // match
+// if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
+ if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
+
+ // Record the uncompressed lat/long that we just
+ // computed.
+ p_station->coord_lat = (long)((lat)); // in 1/100 sec
+ p_station->coord_lon = (long)((lon)); // in 1/100 sec
+ }
+
+ if (c >= 0) { // ignore csT if c = ' '
+ if (c < 90) { // Found course/speed or altitude bytes
+ if ((T & 0x18) == 0x10) { // check for GGA (with altitude)
+ xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
+ }
+ else { // Found compressed course/speed bytes
+
+ // Convert 0 degrees to 360 degrees so that
+ // Xastir will see it as a valid course and do
+ // dead-reckoning properly on this station
+ if (c == 0) {
+ c = 90;
+ }
+
+ // Compute course in degrees
+ xastir_snprintf(p_station->course,
+ sizeof(p_station->course),
+ "%03d",
+ c*4);
+
+ // Compute speed in knots
+ xastir_snprintf(p_station->speed,
+ sizeof(p_station->speed),
+ "%03.0f",
+ pow( 1.08,(double)s ) - 1.0);
+
+ //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
+
+ }
+ }
+ else { // Found pre-calculated radio range bytes
+ if (c == 90) {
+ // pre-calculated radio range
+ range = 2 * pow(1.08,(double)s); // miles
+
+ // DK7IN: dirty hack... but better than nothing
+ if (s <= 5) // 2.9387 mi
+ xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
+ else if (s <= 17) // 7.40 mi
+ xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
+ else if (s <= 36) // 31.936 mi
+ xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
+ else if (s <= 75) // 642.41 mi
+ xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
+ else // max 90: 2037.8 mi
+ xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
+ }
+ }
+ }
+ (*info) += skip; // delete position from comment
+ }
+
+
+ //fprintf(stderr," extract_comp_position end: %s\n",*info);
+
+ return(ok);
+}
+
+
+/*
+ * Extract bearing and number/range/quality from beginning of info field
+ */
+int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
+ int i,found,len;
+
+ len = (int)strlen(info);
+ found = 0;
+ if (len >= 8) {
+ found = 1;
+ for(i=1; found && i<8; i++) // check data format
+ if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
+ found=0;
+ }
+ if (found) {
+ substr(bearing,info+1,3);
+ substr(nrq,info+5,3);
+
+ for (i=0;i<=len-8;i++) // delete bearing/nrq from info field
+ info[i] = info[i+8];
+ }
+
+ if (!found) {
+ bearing[0] ='\0';
+ nrq[0]='\0';
+ }
+ return(found);
+}
+
+
+
+
+// APRS Data Extensions [APRS Reference p.27]
+// .../... Course & Speed, may be followed by others (see p.27)
+// .../... Wind Dir and Speed
+// PHG.... Station Power and Effective Antenna Height/Gain
+// RNG.... Pre-Calculated Radio Range
+// DFS.... DF Signal Strength and Effective Antenna Height/Gain
+// T../C.. Area Object Descriptor
+
+/* Extract one of several possible APRS Data Extensions */
+void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
+ char temp1[7+1];
+ char temp2[3+1];
+ char temp3[10+1];
+ char bearing[3+1];
+ char nrq[3+1];
+
+ if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l') {
+ /* This check needs to come first because the area object extension can look
+ exactly like what extract_speed_course will attempt to decode. */
+ extract_area(p_station, data);
+ }
+ else {
+ clear_area(p_station); // we got a packet with a non area symbol, so clear the data
+
+ if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
+ //fprintf(stderr,"extracted speed/course\n");
+
+ if (atof(temp2) > 0) {
+ //fprintf(stderr,"course is non-zero\n");
+ xastir_snprintf(p_station->speed,
+ sizeof(p_station->speed),
+ "%06.2f",
+ atof(temp1));
+ xastir_snprintf(p_station->course, // in degrees
+ sizeof(p_station->course),
+ "%s",
+ temp2);
+ }
+
+ if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
+ //fprintf(stderr,"extracted bearing and NRQ\n");
+ xastir_snprintf(p_station->bearing,
+ sizeof(p_station->bearing),
+ "%s",
+ bearing);
+ xastir_snprintf(p_station->NRQ,
+ sizeof(p_station->NRQ),
+ "%s",
+ nrq);
+ p_station->signal_gain[0] = '\0'; // And blank out the shgd values
+ }
+ }
+ // Don't try to extract speed & course if a compressed
+ // object. Test for beam headings for compressed packets
+ // here
+ else if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
+
+ //fprintf(stderr,"extracted bearing and NRQ\n");
+ xastir_snprintf(p_station->bearing,
+ sizeof(p_station->bearing),
+ "%s",
+ bearing);
+ xastir_snprintf(p_station->NRQ,
+ sizeof(p_station->NRQ),
+ "%s",
+ nrq);
+ p_station->signal_gain[0] = '\0'; // And blank out the shgd values
+ }
+ else {
+ if (extract_powergain_range(data,temp1)) {
+
+//fprintf(stderr,"Found power_gain: %s\n", temp1);
+
+ xastir_snprintf(p_station->power_gain,
+ sizeof(p_station->power_gain),
+ "%s",
+ temp1);
+
+ if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
+ //fprintf(stderr,"extracted bearing and NRQ\n");
+ xastir_snprintf(p_station->bearing,
+ sizeof(p_station->bearing),
+ "%s",
+ bearing);
+ xastir_snprintf(p_station->NRQ,
+ sizeof(p_station->NRQ),
+ "%s",
+ nrq);
+ p_station->signal_gain[0] = '\0'; // And blank out the shgd values
+ }
+ }
+ else {
+ if (extract_omnidf(data,temp1)) {
+ xastir_snprintf(p_station->signal_gain,
+ sizeof(p_station->signal_gain),
+ "%s",
+ temp1); // Grab the SHGD values
+ p_station->bearing[0] = '\0'; // And blank out the bearing/NRQ values
+ p_station->NRQ[0] = '\0';
+
+ // The spec shows speed/course before DFS, but example packets that
+ // come with DOSaprs show DFSxxxx/speed/course. We'll take care of
+ // that possibility by trying to decode speed/course again.
+ if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
+ //fprintf(stderr,"extracted speed/course\n");
+ if (atof(temp2) > 0) {
+ //fprintf(stderr,"course is non-zero\n");
+ xastir_snprintf(p_station->speed,
+ sizeof(p_station->speed),
+ "%06.2f",
+ atof(temp1));
+ xastir_snprintf(p_station->course,
+ sizeof(p_station->course),
+ "%s",
+ temp2); // in degrees
+ }
+ }
+
+ // The spec shows that omnidf and bearing/NRQ can be in the same
+ // packet, which makes no sense, but we'll try to decode it that
+ // way anyway.
+ if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
+ //fprintf(stderr,"extracted bearing and NRQ\n");
+ xastir_snprintf(p_station->bearing,
+ sizeof(p_station->bearing),
+ "%s",
+ bearing);
+ xastir_snprintf(p_station->NRQ,
+ sizeof(p_station->NRQ),
+ "%s",
+ nrq);
+ //p_station->signal_gain[0] = '\0'; // And blank out the shgd values
+ }
+ }
+ }
+ }
+
+ if (extract_signpost(data, temp2)) {
+ //fprintf(stderr,"extracted signpost data\n");
+ xastir_snprintf(p_station->signpost,
+ sizeof(p_station->signpost),
+ "%s",
+ temp2);
+ }
+
+ if (extract_probability_min(data, temp3, sizeof(temp3))) {
+ //fprintf(stderr,"extracted probability_min data: %s\n",temp3);
+ xastir_snprintf(p_station->probability_min,
+ sizeof(p_station->probability_min),
+ "%s",
+ temp3);
+ }
+
+ if (extract_probability_max(data, temp3, sizeof(temp3))) {
+ //fprintf(stderr,"extracted probability_max data: %s\n",temp3);
+ xastir_snprintf(p_station->probability_max,
+ sizeof(p_station->probability_max),
+ "%s",
+ temp3);
+ }
+ }
+}
+
+
+/*
+ * Extract altitude from APRS info field "/A=012345" in feet
+ */
+int extract_altitude(char *info, char *altitude) {
+ int i,ofs,found,len;
+
+ found=0;
+ len = (int)strlen(info);
+ for(ofs=0; !found && ofs<len-8; ofs++) // search for start sequence
+ if (strncmp(info+ofs,"/A=",3)==0) {
+ found=1;
+ // Are negative altitudes even defined? Yes! In Mic-E spec to -10,000 meters
+ if(!isdigit((int)info[ofs+3]) && info[ofs+3]!='-') // First char must be digit or '-'
+ found=0;
+ for(i=4; found && i<9; i++) // check data format for next 5 chars
+ if(!isdigit((int)info[ofs+i]))
+ found=0;
+ }
+ if (found) {
+ ofs--; // was one too much on exit from for loop
+ substr(altitude,info+ofs+3,6);
+ for (i=ofs;i<=len-9;i++) // delete altitude from info field
+ info[i] = info[i+9];
+ }
+ else
+ altitude[0] = '\0';
+ return(found);
+}
+
+
+
+
+/* extract all available information from info field */
+void process_info_field(AprsDataRow *p_station, char *info, /*@unused@*/ int type) {
+ char temp_data[6+1];
+// char time_data[MAX_TIME];
+
+ if (extract_altitude(info,temp_data)) { // get altitude
+ xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
+ //fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
+ }
+ // do other things...
+}
+
+
+
+
+
+
+/*
+ * Extract Uncompressed Position Report from begin of line
+ *
+ * If a position is found, it is deleted from the data.
+ */
+int extract_position(AprsDataRow *p_station, char **info, int type) {
+ int ok;
+ char temp_lat[8+1];
+ char temp_lon[9+1];
+ char temp_grid[8+1];
+ char *my_data;
+ float gridlat;
+ float gridlon;
+ my_data = (*info);
+
+ if (type != APRS_GRID){ // Not a grid
+ ok = (int)(strlen(my_data) >= 19);
+ ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
+ && (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
+ && (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
+ // errors found: [4]: X [7]: n s [17]: w e
+ if (ok) {
+ ok = is_num_chr(my_data[0]); // 5230.31N/01316.88E>
+ ok = (int)(ok && is_num_chr(my_data[1])); // 0123456789012345678
+ ok = (int)(ok && is_num_or_sp(my_data[2]));
+ ok = (int)(ok && is_num_or_sp(my_data[3]));
+ ok = (int)(ok && is_num_or_sp(my_data[5]));
+ ok = (int)(ok && is_num_or_sp(my_data[6]));
+ ok = (int)(ok && is_num_chr(my_data[9]));
+ ok = (int)(ok && is_num_chr(my_data[10]));
+ ok = (int)(ok && is_num_chr(my_data[11]));
+ ok = (int)(ok && is_num_or_sp(my_data[12]));
+ ok = (int)(ok && is_num_or_sp(my_data[13]));
+ ok = (int)(ok && is_num_or_sp(my_data[15]));
+ ok = (int)(ok && is_num_or_sp(my_data[16]));
+ }
+
+ if (ok) {
+ overlay_symbol(my_data[18], my_data[8], p_station);
+ p_station->pos_amb = 0;
+ // spaces in latitude set position ambiguity, spaces in longitude do not matter
+ // we will adjust the lat/long to the center of the rectangle of ambiguity
+ if (my_data[2] == ' ') { // nearest degree
+ p_station->pos_amb = 4;
+ my_data[2] = my_data[12] = '3';
+ my_data[3] = my_data[5] = my_data[6] = '0';
+ my_data[13] = my_data[15] = my_data[16] = '0';
+ }
+ else if (my_data[3] == ' ') { // nearest 10 minutes
+ p_station->pos_amb = 3;
+ my_data[3] = my_data[13] = '5';
+ my_data[5] = my_data[6] = '0';
+ my_data[15] = my_data[16] = '0';
+ }
+ else if (my_data[5] == ' ') { // nearest minute
+ p_station->pos_amb = 2;
+ my_data[5] = my_data[15] = '5';
+ my_data[6] = '0';
+ my_data[16] = '0';
+ }
+ else if (my_data[6] == ' ') { // nearest 1/10th minute
+ p_station->pos_amb = 1;
+ my_data[6] = my_data[16] = '5';
+ }
+
+ xastir_snprintf(temp_lat,
+ sizeof(temp_lat),
+ "%s",
+ my_data);
+ temp_lat[7] = toupper(my_data[7]);
+ temp_lat[8] = '\0';
+
+ xastir_snprintf(temp_lon,
+ sizeof(temp_lon),
+ "%s",
+ my_data+9);
+ temp_lon[8] = toupper(my_data[17]);
+ temp_lon[9] = '\0';
+
+ // Callsign check here also checks SSID for an exact
+ // match
+// if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
+ if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
+
+ p_station->coord_lat = convert_lat_s2l(temp_lat); // ...in case of position ambiguity
+ p_station->coord_lon = convert_lon_s2l(temp_lon);
+ }
+
+ (*info) += 19; // delete position from comment
+ }
+ }
+ else { // It is a grid
+ // first sanity checks, need more
+ ok = (int)(is_num_chr(my_data[2]));
+ ok = (int)(ok && is_num_chr(my_data[3]));
+ ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
+ ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
+ if (ok) {
+ xastir_snprintf(temp_grid,
+ sizeof(temp_grid),
+ "%s",
+ my_data);
+ // this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
+ // the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
+ if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0)){
+ p_station->pos_amb = 6; // 1deg lat x 2deg lon
+ temp_grid[4] = 'L';
+ temp_grid[5] = 'L';
+ }
+ else {
+ p_station->pos_amb = 5; // 2.5min lat x 5min lon
+ temp_grid[4] = toupper(temp_grid[4]);
+ temp_grid[5] = toupper(temp_grid[5]);
+ }
+ // These equations came from what I read in the qgrid source code and
+ // various mailing list archives.
+ gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
+ gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
+ // could check for my callsign here, and avoid changing it...
+ p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
+ p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
+ p_station->aprs_symbol.aprs_type = '/';
+ p_station->aprs_symbol.aprs_symbol = 'G';
+ } // is it valid grid or not - "ok"
+ // could cut off the grid square from the comment here, but why bother?
+ } // is it grid or not
+ return(ok);
+}
+
+
+
+
+
+// Add a status line to the linked-list of status records
+// associated with a station. Note that a blank status line is
+// allowed, but we don't store that unless we have seen a non-blank
+// status line previously.
+//
+void add_status(AprsDataRow *p_station, char *status_string) {
+ AprsCommentRow *ptr;
+ int add_it = 0;
+ int len;
+
+
+ len = strlen(status_string);
+
+ // Eliminate line-end chars
+ if (len > 1) {
+ if ( (status_string[len-1] == '\n')
+ || (status_string[len-1] == '\r') ) {
+ status_string[len-1] = '\0';
+ }
+ }
+
+ // Shorten it
+ (void)remove_trailing_spaces(status_string);
+ (void)remove_leading_spaces(status_string);
+
+ len = strlen(status_string);
+
+ // Check for valid pointer
+ if (p_station != NULL) {
+
+// We should probably create a new station record for this station
+// if there isn't one. This allows us to collect as much info about
+// a station as we can until a posit comes in for it. Right now we
+// don't do this. If we decide to do this in the future, we also
+// need a method to find out the info about that station without
+// having to click on an icon, 'cuz the symbol won't be on our map
+// until we have a posit.
+
+ //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
+
+ // Check whether we have any data stored for this station
+ if (p_station->status_data == NULL) {
+ if (len > 0) {
+ // No status stored yet and new status is non-NULL,
+ // so add it to the list.
+ add_it++;
+ }
+ }
+ else { // We have status data stored already
+ // Check for an identical string
+ AprsCommentRow *ptr2;
+ int ii = 0;
+
+ ptr = p_station->status_data;
+ ptr2 = ptr;
+ while (ptr != NULL) {
+
+ // Note that both text_ptr and comment_string can be
+ // empty strings.
+
+ if (strcasecmp(ptr->text_ptr, status_string) == 0) {
+ // Found a matching string
+ //fprintf(stderr,"Found match:
+ //%s:%s\n",p_station->call_sign,status_string);
+
+// Instead of updating the timestamp, we'll delete the record from
+// the list and add it to the top in the code below. Make sure to
+// tweak the "ii" pointer so that we don't end up shortening the
+// list unnecessarily.
+ if (ptr == p_station->status_data) {
+
+ // Only update the timestamp: We're at the
+ // beginning of the list already.
+ ptr->sec_heard = sec_now();
+
+ return; // No need to add a new record
+ }
+ else { // Delete the record
+ AprsCommentRow *ptr3;
+
+ // Keep a pointer to the record
+ ptr3 = ptr;
+
+ // Close the chain, skipping this record
+ ptr2->next = ptr3->next;
+
+ // Skip "ptr" over the record we wish to
+ // delete
+ ptr = ptr3->next;
+
+ // Free the record
+ free(ptr3->text_ptr);
+ free(ptr3);
+
+ // Muck with the counter 'cuz we just
+ // deleted one record
+ ii--;
+ }
+ }
+ ptr2 = ptr; // Back one record
+ if (ptr != NULL) {
+ ptr = ptr->next;
+ }
+ ii++;
+ }
+
+
+ // No matching string found, or new timestamp found for
+ // old record. Add it to the top of the list.
+ add_it++;
+ //fprintf(stderr,"No match:
+ //%s:%s\n",p_station->call_sign,status_string);
+
+ // We counted the records. If we have more than
+ // MAX_STATUS_LINES records we'll delete/free the last
+ // one to make room for the next. This keeps us from
+ // storing unique status records ad infinitum for active
+ // stations, limiting the total space used.
+ //
+ if (ii >= MAX_STATUS_LINES) {
+ // We know we didn't get a match, and that our list
+ // is full (as full as we want it to be). Traverse
+ // the list again, looking for ptr2->next->next ==
+ // NULL. If found, free last record and set the
+ // ptr2->next pointer to NULL.
+ ptr2 = p_station->status_data;
+ while (ptr2->next->next != NULL) {
+ ptr2 = ptr2->next;
+ }
+ // At this point, we have a pointer to the last
+ // record in ptr2->next. Free it and the text
+ // string in it.
+ free(ptr2->next->text_ptr);
+ free(ptr2->next);
+ ptr2->next = NULL;
+ }
+ }
+
+ if (add_it) { // We add to the beginning so we don't have
+ // to traverse the linked list. This also
+ // puts new records at the beginning of the
+ // list to keep them in sorted order.
+
+ ptr = p_station->status_data; // Save old pointer to records
+ p_station->status_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
+ CHECKMALLOC(p_station->status_data);
+
+ p_station->status_data->next = ptr; // Link in old records or NULL
+
+ // Malloc the string space we'll need, attach it to our
+ // new record
+ p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
+ CHECKMALLOC(p_station->status_data->text_ptr);
+
+ // Fill in the string
+ xastir_snprintf(p_station->status_data->text_ptr,
+ len+1,
+ "%s",
+ status_string);
+
+ // Fill in the timestamp
+ p_station->status_data->sec_heard = sec_now();
+
+ //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
+ }
+ }
+}
+
+
+
+// Add a comment line to the linked-list of comment records
+// associated with a station. Note that a blank comment is allowed
+// and necessary for the times when we wish to blank out the comment
+// on an object/item, but we don't store that unless we have seen a
+// non-blank comment line previously.
+//
+void add_comment(AprsDataRow *p_station, char *comment_string) {
+ AprsCommentRow *ptr;
+ int add_it = 0;
+ int len;
+
+
+ len = strlen(comment_string);
+
+ // Eliminate line-end chars
+ if (len > 1) {
+ if ( (comment_string[len-1] == '\n')
+ || (comment_string[len-1] == '\r') ) {
+ comment_string[len-1] = '\0';
+ }
+ }
+
+ // Shorten it
+ (void)remove_trailing_spaces(comment_string);
+ (void)remove_leading_spaces(comment_string);
+
+ len = strlen(comment_string);
+
+ // Check for valid pointer
+ if (p_station != NULL) {
+
+ // Check whether we have any data stored for this station
+ if (p_station->comment_data == NULL) {
+ if (len > 0) {
+ // No comments stored yet and new comment is
+ // non-NULL, so add it to the list.
+ add_it++;
+ }
+ }
+ else { // We have comment data stored already
+ // Check for an identical string
+ AprsCommentRow *ptr2;
+ int ii = 0;
+
+ ptr = p_station->comment_data;
+ ptr2 = ptr;
+ while (ptr != NULL) {
+
+ // Note that both text_ptr and comment_string can be
+ // empty strings.
+
+ if (strcasecmp(ptr->text_ptr, comment_string) == 0) {
+ // Found a matching string
+//fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);
+
+// Instead of updating the timestamp, we'll delete the record from
+// the list and add it to the top in the code below. Make sure to
+// tweak the "ii" pointer so that we don't end up shortening the
+// list unnecessarily.
+ if (ptr == p_station->comment_data) {
+ // Only update the timestamp: We're at the
+ // beginning of the list already.
+ ptr->sec_heard = sec_now();
+
+ return; // No need to add a new record
+ }
+ else { // Delete the record
+ AprsCommentRow *ptr3;
+
+ // Keep a pointer to the record
+ ptr3 = ptr;
+
+ // Close the chain, skipping this record
+ ptr2->next = ptr3->next;
+
+ // Skip "ptr" over the record we with to
+ // delete
+ ptr = ptr3->next;
+
+ // Free the record
+ free(ptr3->text_ptr);
+ free(ptr3);
+
+ // Muck with the counter 'cuz we just
+ // deleted one record
+ ii--;
+ }
+ }
+ ptr2 = ptr; // Keep this pointer one record back as
+ // we progress.
+
+ if (ptr != NULL) {
+ ptr = ptr->next;
+ }
+
+ ii++;
+ }
+ // No matching string found, or new timestamp found for
+ // old record. Add it to the top of the list.
+ add_it++;
+ //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
+
+ // We counted the records. If we have more than
+ // MAX_COMMENT_LINES records we'll delete/free the last
+ // one to make room for the next. This keeps us from
+ // storing unique comment records ad infinitum for
+ // active stations, limiting the total space used.
+ //
+ if (ii >= MAX_COMMENT_LINES) {
+
+ // We know we didn't get a match, and that our list
+ // is full (as we want it to be). Traverse the list
+ // again, looking for ptr2->next->next == NULL. If
+ // found, free that last record and set the
+ // ptr2->next pointer to NULL.
+ ptr2 = p_station->comment_data;
+ while (ptr2->next->next != NULL) {
+ ptr2 = ptr2->next;
+ }
+ // At this point, we have a pointer to the last
+ // record in ptr2->next. Free it and the text
+ // string in it.
+ free(ptr2->next->text_ptr);
+ free(ptr2->next);
+ ptr2->next = NULL;
+ }
+ }
+
+ if (add_it) { // We add to the beginning so we don't have
+ // to traverse the linked list. This also
+ // puts new records at the beginning of the
+ // list to keep them in sorted order.
+
+ ptr = p_station->comment_data; // Save old pointer to records
+ p_station->comment_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
+ CHECKMALLOC(p_station->comment_data);
+
+ p_station->comment_data->next = ptr; // Link in old records or NULL
+
+ // Malloc the string space we'll need, attach it to our
+ // new record
+ p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
+ CHECKMALLOC(p_station->comment_data->text_ptr);
+
+ // Fill in the string
+ xastir_snprintf(p_station->comment_data->text_ptr,
+ len+1,
+ "%s",
+ comment_string);
+
+ // Fill in the timestamp
+ p_station->comment_data->sec_heard = sec_now();
+ }
+ }
+}
+
+
+
+// Extract single weather data item from "data". Returns it in
+// "temp". Modifies "data" to remove the found data from the
+// string. Returns a 1 if found, 0 if not found.
+//
+// PE1DNN
+// If the item is contained in the string but does not contain a
+// value then regard the item as "not found" in the weather string.
+//
+int extract_weather_item(char *data, char type, int datalen, char *temp) {
+ int i,ofs,found,len;
+
+
+//fprintf(stderr,"%s\n",data);
+
+ found=0;
+ len = (int)strlen(data);
+ for(ofs=0; !found && ofs<len-datalen; ofs++) // search for start sequence
+ if (data[ofs]==type) {
+ found=1;
+ if (!is_weather_data(data+ofs+1, datalen))
+ found=0;
+ }
+ if (found) { // ofs now points after type character
+ substr(temp,data+ofs,datalen);
+ for (i=ofs-1;i<len-datalen;i++) // delete item from info field
+ data[i] = data[i+datalen+1];
+ if((temp[0] == ' ') || (temp[0] == '.')) {
+ // found it, but it doesn't contain a value!
+ // Clean up and report "not found" - PE1DNN
+ temp[0] = '\0';
+ found = 0;
+ }
+ else
+ {
+// fprintf(stderr,"extract_weather_item: %s\n",temp);
+ }
+ }
+ else
+ temp[0] = '\0';
+ return(found);
+}
+
+
+
+
+
+// test-extract single weather data item from information field. In
+// other words: Does not change the input string, but does test
+// whether the data is present. Returns a 1 if found, 0 if not
+// found.
+//
+// PE1DNN
+// If the item is contained in the string but does not contain a
+// value then regard the item as "not found" in the weather string.
+//
+int test_extract_weather_item(char *data, char type, int datalen) {
+ int ofs,found,len;
+
+ found=0;
+ len = (int)strlen(data);
+ for(ofs=0; !found && ofs<len-datalen; ofs++) // search for start sequence
+ if (data[ofs]==type) {
+ found=1;
+ if (!is_weather_data(data+ofs+1, datalen))
+ found=0;
+ }
+
+ // We really should test for numbers here (with an optional
+ // leading '-'), and test across the length of the substring.
+ //
+ if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.'))) {
+ // found it, but it doesn't contain a value!
+ // report "not found" - PE1DNN
+ found = 0;
+ }
+
+ //fprintf(stderr,"test_extract: %c %d\n",type,found);
+ return(found);
+}
+
+
+int get_weather_record(AprsDataRow *fill) { // get or create weather storage
+ int ok=1;
+
+ if (fill->weather_data == NULL) { // new weather data, allocate storage and init
+ fill->weather_data = malloc(sizeof(AprsWeatherRow));
+ if (fill->weather_data == NULL) {
+ fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
+ ok = 0;
+ }
+ else {
+ init_weather(fill->weather_data);
+ }
+ }
+ return(ok);
+}
+
+
+
+
+// DK7IN 77
+// raw weather report in information field
+// positionless weather report in information field
+// complete weather report with lat/lon
+// see APRS Reference page 62ff
+//
+// Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
+// decode these two new parameters used for RAWS weather station
+// objects.
+//
+// By the time we call this function we've already extracted any
+// time/position info at the beginning of the string.
+//
+int extract_weather(AprsDataRow *p_station, char *data, int compr) {
+ char time_data[MAX_TIME];
+ char temp[5];
+ int ok = 1;
+ AprsWeatherRow *weather;
+ char course[4];
+ char speed[4];
+ int in_knots = 0;
+
+//WE7U
+// Try copying the string to a temporary string, then do some
+// extractions to see if a few weather items are present? This
+// would allow us to have the weather items in any order, and if
+// enough of them were present, we consider it to be a weather
+// packet? We'd need to qualify all of the data to make sure we had
+// the proper number of digits for each. The trick is to make sure
+// we don't decide it's a weather packet if it's not. We don't know
+// what people might send in packets in the future.
+
+ if (compr) { // compressed position report
+ // Look for weather data in fixed locations first
+ if (strlen(data) >= 8
+ && data[0] =='g' && is_weather_data(&data[1],3)
+ && data[4] =='t' && is_weather_data(&data[5],3)) {
+
+ // Snag WX course/speed from compressed position data.
+ // This speed is in knots. This assumes that we've
+ // already extracted speed/course from the compressed
+ // packet. extract_comp_position() extracts
+ // course/speed as well.
+ xastir_snprintf(speed,
+ sizeof(speed),
+ "%s",
+ p_station->speed);
+ xastir_snprintf(course,
+ sizeof(course),
+ "%s",
+ p_station->course);
+ in_knots = 1;
+
+ //fprintf(stderr,"Found compressed wx\n");
+ }
+ // Look for weather data in non-fixed locations (RAWS WX
+ // Stations?)
+ else if ( strlen(data) >= 8
+ && test_extract_weather_item(data,'g',3)
+ && test_extract_weather_item(data,'t',3) ) {
+
+ // Snag WX course/speed from compressed position data.
+ // This speed is in knots. This assumes that we've
+ // already extracted speed/course from the compressed
+ // packet. extract_comp_position() extracts
+ // course/speed as well.
+ xastir_snprintf(speed,
+ sizeof(speed),
+ "%s",
+ p_station->speed);
+ xastir_snprintf(course,
+ sizeof(course),
+ "%s",
+ p_station->course);
+ in_knots = 1;
+
+ //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
+ // p_station->call_sign,data);
+
+ }
+ else { // No weather data found
+ ok = 0;
+
+ //fprintf(stderr,"No compressed wx\n");
+ }
+ }
+ else { // Look for non-compressed weather data
+ // Look for weather data in defined locations first
+ if (strlen(data)>=15 && data[3]=='/'
+ && is_weather_data(data,3) && is_weather_data(&data[4],3)
+ && data[7] =='g' && is_weather_data(&data[8], 3)
+ && data[11]=='t' && is_weather_data(&data[12],3)) { // Complete Weather Report
+
+ // Get speed/course. Speed is in knots.
+ (void)extract_speed_course(data,speed,course);
+ in_knots = 1;
+
+ // Either one not found? Try again.
+ if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+
+ // Try to get speed/course from 's' and 'c' fields
+ // (another wx format). Speed is in mph.
+ (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+ (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
+ in_knots = 0;
+ }
+
+ //fprintf(stderr,"Found Complete Weather Report\n");
+ }
+ // Look for date/time and weather in fixed locations first
+ else if (strlen(data)>=16
+ && data[0] =='c' && is_weather_data(&data[1], 3)
+ && data[4] =='s' && is_weather_data(&data[5], 3)
+ && data[8] =='g' && is_weather_data(&data[9], 3)
+ && data[12]=='t' && is_weather_data(&data[13],3)) { // Positionless Weather Data
+//fprintf(stderr,"Found positionless wx data\n");
+ // Try to snag speed/course out of first 7 bytes. Speed
+ // is in knots.
+ (void)extract_speed_course(data,speed,course);
+ in_knots = 1;
+
+ // Either one not found? Try again.
+ if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+//fprintf(stderr,"Trying again for course/speed\n");
+ // Also try to get speed/course from 's' and 'c' fields
+ // (another wx format)
+ (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+ (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
+ in_knots = 0;
+ }
+
+ //fprintf(stderr,"Found weather\n");
+ }
+ // Look for weather data in non-fixed locations (RAWS WX
+ // Stations?)
+ else if (strlen (data) >= 16
+ && test_extract_weather_item(data,'h',2)
+ && test_extract_weather_item(data,'g',3)
+ && test_extract_weather_item(data,'t',3) ) {
+
+ // Try to snag speed/course out of first 7 bytes. Speed
+ // is in knots.
+ (void)extract_speed_course(data,speed,course);
+ in_knots = 1;
+
+ // Either one not found? Try again.
+ if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+
+ // Also try to get speed/course from 's' and 'c' fields
+ // (another wx format)
+ (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+ (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
+ in_knots = 0;
+ }
+
+ //fprintf(stderr,"Found WX in non-fixed locations! %s:%s\n",
+ // p_station->call_sign,data);
+ }
+ else { // No weather data found
+ ok = 0;
+
+ //fprintf(stderr,"No wx found\n");
+ }
+ }
+
+ if (ok) {
+ ok = get_weather_record(p_station); // get existing or create new weather record
+ }
+
+ if (ok) {
+ weather = p_station->weather_data;
+
+ // Copy into weather speed variable. Convert knots to mph
+ // if necessary.
+ if (in_knots) {
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03.0f",
+ atoi(speed) * 1.1508); // Convert knots to mph
+ }
+ else {
+ // Already in mph. Copy w/no conversion.
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%s",
+ speed);
+ }
+
+ xastir_snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "%s",
+ course);
+
+ if (compr) { // course/speed was taken from normal data, delete that
+ // fix me: we delete a potential real speed/course now
+ // we should differentiate between normal and weather data in compressed position decoding...
+// p_station->speed_time[0] = '\0';
+ p_station->speed[0] = '\0';
+ p_station->course[0] = '\0';
+ }
+
+ (void)extract_weather_item(data,'g',3,weather->wx_gust); // gust (peak wind speed in mph in the last 5 minutes)
+
+ (void)extract_weather_item(data,'t',3,weather->wx_temp); // temperature (in deg Fahrenheit), could be negative
+
+ (void)extract_weather_item(data,'r',3,weather->wx_rain); // rainfall (1/100 inch) in the last hour
+
+ (void)extract_weather_item(data,'p',3,weather->wx_prec_24); // rainfall (1/100 inch) in the last 24 hours
+
+ (void)extract_weather_item(data,'P',3,weather->wx_prec_00); // rainfall (1/100 inch) since midnight
+
+ if (extract_weather_item(data,'h',2,weather->wx_hum)) // humidity (in %, 00 = 100%)
+ xastir_snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);
+
+ if (extract_weather_item(data,'b',5,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
+ xastir_snprintf(weather->wx_baro,
+ sizeof(weather->wx_baro),
+ "%0.1f",
+ (float)(atoi(weather->wx_baro)/10.0));
+
+ // If we parsed a speed/course, a second 's' parameter means
+ // snowfall. Try to parse it, but only in the case where
+ // we've parsed speed out of this packet already.
+ if ( (speed[0] != '\0') && (course[0] != '\0') ) {
+ (void)extract_weather_item(data,'s',3,weather->wx_snow); // snowfall, inches in the last 24 hours
+ }
+
+ (void)extract_weather_item(data,'L',3,temp); // luminosity (in watts per square meter) 999 and below
+
+ (void)extract_weather_item(data,'l',3,temp); // luminosity (in watts per square meter) 1000 and above
+
+ (void)extract_weather_item(data,'#',3,temp); // raw rain counter
+
+ (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in °F (RAWS)
+
+ if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
+ xastir_snprintf(weather->wx_fuel_moisture,
+ sizeof(weather->wx_fuel_moisture),
+ "%03d",
+ (atoi(weather->wx_fuel_moisture)+99)%100+1);
+
+// extract_weather_item(data,'w',3,temp); // ?? text wUII
+
+ // now there should be the name of the weather station...
+
+ // Create a timestamp from the current time
+ xastir_snprintf(weather->wx_time,
+ sizeof(weather->wx_time),
+ "%s",
+ get_time(time_data));
+
+ // Set the timestamp in the weather record so that we can
+ // decide whether or not to "ghost" the weather data later.
+ weather->wx_sec_time=sec_now();
+// weather->wx_data=1; // we don't need this
+
+// case ('.'):/* skip */
+// wx_strpos+=4;
+// break;
+
+// default:
+// wx_done=1;
+// weather->wx_type=data[wx_strpos];
+// if(strlen(data)>wx_strpos+1)
+// xastir_snprintf(weather->wx_station,
+// sizeof(weather->wx_station),
+// "%s",
+// data+wx_strpos+1);
+// break;
+ }
+ return(ok);
+}
+
+
+
+// Initial attempt at decoding tropical storm, tropical depression,
+// and hurricane data.
+//
+// This data can be in an Object report, but can also be in an Item
+// or position report.
+// "/TS" = Tropical Storm
+// "/HC" = Hurricane
+// "/TD" = Tropical Depression
+// "/TY" = Typhoon
+// "/ST" = Super Typhoon
+// "/SC" = Severe Cyclone
+
+// The symbol will be either "\@" for current position, or "/@" for
+// predicted position.
+//
+int extract_storm(AprsDataRow *p_station, char *data, int compr) {
+ char time_data[MAX_TIME];
+ int ok = 1;
+ AprsWeatherRow *weather;
+ char course[4];
+ char speed[4]; // Speed in knots
+ char *p, *p2;
+
+
+// Should probably encode the storm type in the weather object and
+// print it out in plain text in the Station Info dialog.
+
+ if ((p = strstr(data, "/TS")) != NULL) {
+ // We have a Tropical Storm
+//fprintf(stderr,"Tropical Storm! %s\n",data);
+ }
+ else if ((p = strstr(data, "/TD")) != NULL) {
+ // We have a Tropical Depression
+//fprintf(stderr,"Tropical Depression! %s\n",data);
+ }
+ else if ((p = strstr(data, "/HC")) != NULL) {
+ // We have a Hurricane
+//fprintf(stderr,"Hurricane! %s\n",data);
+ }
+ else if ((p = strstr(data, "/TY")) != NULL) {
+ // We have a Typhoon
+//fprintf(stderr,"Hurricane! %s\n",data);
+ }
+ else if ((p = strstr(data, "/ST")) != NULL) {
+ // We have a Super Typhoon
+//fprintf(stderr,"Hurricane! %s\n",data);
+ }
+ else if ((p = strstr(data, "/SC")) != NULL) {
+ // We have a Severe Cyclone
+//fprintf(stderr,"Hurricane! %s\n",data);
+ }
+ else { // Not one of the three we're trying to decode
+ ok = 0;
+ return(ok);
+ }
+
+//fprintf(stderr,"\n%s\n",data);
+
+ // Back up 7 spots to try to extract the next items
+ p2 = p - 7;
+ if (p2 >= data) {
+ // Attempt to extract course/speed. Speed in knots.
+ if (!extract_speed_course(p2,speed,course)) {
+ // No speed/course to extract
+//fprintf(stderr,"No speed/course found\n");
+ ok = 0;
+ return(ok);
+ }
+ }
+ else { // Not enough characters for speed/course. Must have
+ // guessed wrong on what type of data it is.
+//fprintf(stderr,"No speed/course found 2\n");
+ ok = 0;
+ return(ok);
+ }
+
+
+//fprintf(stderr,"%s\n",data);
+
+ if (ok) {
+
+ // If we got this far, we have speed/course and know what type
+ // of storm it is.
+//fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
+
+ ok = get_weather_record(p_station); // get existing or create new weather record
+ }
+
+ if (ok) {
+// p_station->speed_time[0] = '\0';
+
+ p_station->weather_data->wx_storm = 1; // We found a storm
+
+ // Note that speed is in knots. If we were stuffing it into
+ // "wx_speed" we'd have to convert it to MPH.
+ if (strcmp(speed," ") != 0 && strcmp(speed,"...") != 0) {
+ xastir_snprintf(p_station->speed,
+ sizeof(p_station->speed),
+ "%s",
+ speed);
+ }
+ else {
+ p_station->speed[0] = '\0';
+ }
+
+ if (strcmp(course," ") != 0 && strcmp(course,"...") != 0)
+ xastir_snprintf(p_station->course,
+ sizeof(p_station->course),
+ "%s",
+ course);
+ else
+ p_station->course[0] = '\0';
+
+ weather = p_station->weather_data;
+
+ p2++; // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
+
+ // Extract the sustained wind speed in knots
+ if(extract_weather_item(p2,'/',3,weather->wx_speed))
+ // Convert from knots to MPH
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%0.1f",
+ (float)(atoi(weather->wx_speed)) * 1.1508);
+
+//fprintf(stderr,"%s\n",data);
+
+ // Extract gust speed in knots
+ if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
+ // Convert from knots to MPH
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%0.1f",
+ (float)(atoi(weather->wx_gust)) * 1.1508);
+
+//fprintf(stderr,"%s\n",data);
+
+ // Pressure is already in millibars/hPa. No conversion
+ // needed.
+ if (extract_weather_item(p2,'/',4,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
+ xastir_snprintf(weather->wx_baro,
+ sizeof(weather->wx_baro),
+ "%0.1f",
+ (float)(atoi(weather->wx_baro)));
+
+//fprintf(stderr,"%s\n",data);
+
+ (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+ (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+ (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+ // Create a timestamp from the current time
+ xastir_snprintf(weather->wx_time,
+ sizeof(weather->wx_time),
+ "%s",
+ get_time(time_data));
+
+ // Set the timestamp in the weather record so that we can
+ // decide whether or not to "ghost" the weather data later.
+ weather->wx_sec_time=sec_now();
+ }
+ return(ok);
+}
+
+
+
+/*
+ * Free station memory for one entry
+ */
+void delete_station_memory(AprsDataRow *p_del) {
+ if (p_del == NULL)
+ return;
+
+ remove_name(p_del);
+ remove_time(p_del);
+ free(p_del);
+ station_count--;
+}
+
+/*
+ * Create new uninitialized element in station list
+ * and insert it before p_name after p_time entries.
+ *
+ * Returns NULL if malloc error.
+ */
+/*@null@*/ AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
+ AprsDataRow *p_new;
+
+
+ p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));
+
+ if (p_new != NULL) { // we really got the memory
+ p_new->call_sign[0] = '\0'; // just to be sure
+ p_new->n_next = NULL;
+ p_new->n_prev = NULL;
+ p_new->t_newer = NULL;
+ p_new->t_older = NULL;
+ insert_name(p_new,p_name); // insert element into name ordered list
+ insert_time(p_new,p_time); // insert element into time ordered list
+ }
+ else { // p_new == NULL
+ fprintf(stderr,"ERROR: we got no memory for station storage\n");
+ }
+
+
+ return(p_new); // return pointer to new element
+}
+
+
+// Station storage is done in a double-linked list. In fact there are two such
+// pointer structures, one for sorting by name and one for sorting by time.
+// We store both the pointers to the next and to the previous elements. DK7IN
+
+/*
+ * Setup station storage structure
+ */
+void init_station_data(void) {
+
+ station_count = 0; // empty station list
+ n_first = NULL; // pointer to next element in name sorted list
+ n_last = NULL; // pointer to previous element in name sorted list
+ t_oldest = NULL; // pointer to oldest element in time sorted list
+ t_newest = NULL; // pointer to newest element in time sorted list
+ last_sec = sec_now(); // check value for detecting changed seconds in time
+ next_time_sn = 0; // serial number for unique time index
+// current_trail_color = 0x00; // first trail color used will be 0x01
+ last_station_remove = sec_now(); // last time we checked for stations to remove
+
+}
+
+
+/*
+ * Initialize station data
+ */
+void init_station(AprsDataRow *p_station) {
+ // the list pointers should already be set
+
+ p_station->oldest_trackpoint = NULL; // no trail
+ p_station->newest_trackpoint = NULL; // no trail
+ p_station->trail_color = 0;
+ p_station->weather_data = NULL; // no weather
+ p_station->coord_lat = 0l; // 90°N \ undefined
+ p_station->coord_lon = 0l; // 180°W / position
+ p_station->pos_amb = 0; // No ambiguity
+ p_station->error_ellipse_radius = 600; // In cm, default 6 meters
+ p_station->lat_precision = 60; // In 100ths of seconds latitude (60 = 0.01 minutes)
+ p_station->lon_precision = 60; // In 100ths of seconds longitude (60 = 0.01 minutes)
+ p_station->call_sign[0] = '\0'; // ?????
+ p_station->tactical_call_sign = NULL;
+ p_station->sec_heard = 0;
+ p_station->time_sn = 0;
+ p_station->flag = 0; // set all flags to inactive
+ p_station->object_retransmit = -1; // transmit forever
+ p_station->last_transmit_time = sec_now(); // Used for object/item decaying algorithm
+ p_station->transmit_time_increment = 0; // Used in data_add()
+// p_station->last_modified_time = 0; // Used for object/item dead-reckoning
+ p_station->record_type = '\0';
+ p_station->heard_via_tnc_port = 0;
+ p_station->heard_via_tnc_last_time = 0;
+ p_station->last_port_heard = 0;
+ p_station->num_packets = 0;
+ p_station->aprs_symbol.aprs_type = '\0';
+ p_station->aprs_symbol.aprs_symbol = '\0';
+ p_station->aprs_symbol.special_overlay = '\0';
+ p_station->aprs_symbol.area_object.type = AREA_NONE;
+ p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
+ p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
+ p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
+ p_station->aprs_symbol.area_object.corridor_width = 0;
+// p_station->station_time_type = '\0';
+ p_station->origin[0] = '\0'; // no object
+ p_station->packet_time[0] = '\0';
+ p_station->node_path_ptr = NULL;
+ p_station->pos_time[0] = '\0';
+// p_station->altitude_time[0] = '\0';
+ p_station->altitude[0] = '\0';
+// p_station->speed_time[0] = '\0';
+ p_station->speed[0] = '\0';
+ p_station->course[0] = '\0';
+ p_station->bearing[0] = '\0';
+ p_station->NRQ[0] = '\0';
+ p_station->power_gain[0] = '\0';
+ p_station->signal_gain[0] = '\0';
+ p_station->signpost[0] = '\0';
+ p_station->probability_min[0] = '\0';
+ p_station->probability_max[0] = '\0';
+// p_station->station_time[0] = '\0';
+ p_station->sats_visible[0] = '\0';
+ p_station->status_data = NULL;
+ p_station->comment_data = NULL;
+ p_station->df_color = -1;
+
+ // Show that there are no other points associated with this
+ // station. We could also zero all the entries of the
+ // multipoints[][] array, but nobody should be looking there
+ // unless this is non-zero.
+ // KG4NBB
+
+ p_station->num_multipoints = 0;
+ p_station->multipoint_data = NULL;
+}
+
+
+
+void init_tactical_hash(int clobber) {
+
+ // make sure we don't leak
+ if (tactical_hash) {
+ if (clobber) {
+ hashtable_destroy(tactical_hash, 1);
+ tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
+ tactical_hash_from_key,
+ tactical_keys_equal);
+ }
+ }
+ else {
+ tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
+ tactical_hash_from_key,
+ tactical_keys_equal);
+ }
+}
+
+
+
+
+char *get_tactical_from_hash(char *callsign) {
+ char *result;
+
+ if (callsign == NULL || *callsign == '\0') {
+ fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
+ return(NULL);
+ }
+
+ if (!tactical_hash) { // no table to search
+//fprintf(stderr,"Creating hash table\n");
+ init_tactical_hash(1); // so create one
+ return NULL;
+ }
+
+// fprintf(stderr," searching for %s...",callsign);
+
+ result=hashtable_search(tactical_hash,callsign);
+
+ if (result) {
+// fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
+// callsign,
+// strlen(callsign),
+// result);
+ } else {
+// fprintf(stderr,"\t\tNot found, %s, len=%d\n",
+// callsign,
+// strlen(callsign));
+ }
+
+ return (result);
+}
+
+
+// Distance calculation (Great Circle) using the Haversine formula
+// (2-parameter arctan version), which gives better accuracy than
+// the "Law of Cosines" for short distances. It should be
+// equivalent to the "Law of Cosines for Spherical Trigonometry" for
+// longer distances. Haversine is a great-circle calculation.
+//
+//
+// Inputs: lat1/long1/lat2/long2 in radians (double)
+//
+// Outputs: Distance in meters between them (double)
+//
+double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2) {
+ double dlon, dlat;
+ double a, c, d;
+ double R = EARTH_RADIUS_METERS;
+ #define square(x) (x)*(x)
+
+
+ dlon = lon2 - lon1;
+ dlat = lat2 - lat1;
+ a = square((sin(dlat/2.0))) + cos(lat1) * cos(lat2) * square((sin(dlon/2.0)));
+ c = 2.0 * atan2(sqrt(a), sqrt(1.0-a));
+ d = R * c;
+
+ return(d);
+}
+
+
+
+
+
+
+/*
+ * Insert existing element into name ordered list before p_name.
+ * If p_name is NULL then we add it to the end instead.
+ */
+void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {
+
+ // Set up pointer to next record (or NULL), sorted by name
+ p_new->n_next = p_name;
+
+ if (p_name == NULL) { // Add to end of list
+
+ p_new->n_prev = n_last;
+
+ if (n_last == NULL) // If we have an empty list
+ n_first = p_new; // Add it to the head of the list
+
+ else // List wasn't empty, add to the end of the list.
+ n_last->n_next = p_new;
+
+ n_last = p_new;
+ }
+
+ else { // Insert new record ahead of p_name record
+
+ p_new->n_prev = p_name->n_prev;
+
+ if (p_name->n_prev == NULL) // add to begin of list
+ n_first = p_new;
+ else
+ p_name->n_prev->n_next = p_new;
+
+ p_name->n_prev = p_new;
+ }
+}
+
+
+
+// Update all of the pointers so that they accurately reflect the
+// current state of the station database.
+//
+// NOTE: This part of the code could be made smarter so that the
+// pointers are updated whenever they are found to be out of whack,
+// instead of zeroing all of them and starting from scratch each
+// time. Alternate: Follow the current pointer if non-NULL then go
+// up/down the list to find the current switchover point between
+// letters.
+//
+// Better: Tie into the station insert function. If a new letter
+// is inserted, or a new station at the beginning of a letter group,
+// run this function to keep things up to date. That way we won't
+// have to traverse in both directions to find a callsign in the
+// search_station_name() function.
+//
+// If hash_key_in is -1, we need to redo all of the hash keys. If
+// it is between 0 and 16383, then we need to redo just that one
+// hash key. The 2nd parameter is either NULL for a removed record,
+// or a pointer to a new station record in the case of an addition.
+//
+void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
+ int ii;
+ AprsDataRow *ptr;
+ int prev_hash_key = 0x0000;
+ int hash_key;
+
+
+// I just changed the function so that we can pass in the hash_key
+// that we wish to update: We should be able to speed things up by
+// updating one hash key instead of all 16384 pointers.
+
+ if ( (hash_key_in != -1)
+ && (hash_key_in >= 0)
+ && (hash_key_in < 16384) ) {
+
+ // We're adding/changing a hash key entry
+ station_shortcuts[hash_key_in] = p_rem;
+//fprintf(stderr,"%i ",hash_key_in);
+ }
+ else { // We're removing a hash key entry.
+
+ // Clear and rebuild the entire hash table.
+
+//??????????????????????????????????????????????????
+ // Clear all of the pointers before we begin????
+//??????????????????????????????????????????????????
+ for (ii = 0; ii < 16384; ii++) {
+ station_shortcuts[ii] = NULL;
+ }
+
+ ptr = n_first; // Start of list
+
+
+ // Loop through entire list, writing the pointer into the
+ // station_shortcuts array whenever a new character is
+ // encountered. Do this until the end of the array or the end
+ // of the list.
+ //
+ while ( (ptr != NULL) && (prev_hash_key < 16384) ) {
+
+ // We create the hash key out of the lower 7 bits of the
+ // first two characters, creating a 14-bit key (1 of 16384)
+ //
+ hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
+ hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
+
+ if (hash_key > prev_hash_key) {
+
+ // We found the next hash_key. Store the pointer at the
+ // correct location.
+ if (hash_key < 16384) {
+ station_shortcuts[hash_key] = ptr;
+
+ }
+ prev_hash_key = hash_key;
+ }
+ ptr = ptr->n_next;
+ }
+
+ }
+
+}
+
+
+
+
+/*
+ * Create new initialized element for call in station list
+ * and insert it before p_name after p_time entries.
+ *
+ * Returns NULL if mallc error.
+ */
+/*@null@*/ AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
+ AprsDataRow *p_new;
+ int hash_key; // We use a 14-bit hash key
+ char *tactical_call;
+
+
+
+ if (call[0] == '\0') {
+ // Do nothing. No update needed. Callsign is empty.
+ return(NULL);
+ }
+
+
+ if(_aprs_show_new_station_alert)
+ {
+ const gchar *msg = g_strdup_printf("New station: %s", call);
+ hildon_banner_show_information(_window, NULL, msg);
+ }
+
+ p_new = insert_new_station(p_name,p_time); // allocate memory
+
+ if (p_new == NULL) {
+
+ // Couldn't allocate space for the station
+ return(NULL);
+ }
+
+ init_station(p_new); // initialize new station record
+
+
+ xastir_snprintf(p_new->call_sign,
+ sizeof(p_new->call_sign),
+ "%s",
+ call);
+ station_count++;
+
+
+
+ // Do some quick checks to see if we just inserted a new hash
+ // key or inserted at the beginning of a hash key (making the
+ // old pointer incorrect). If so, update our pointers to match.
+
+ // We create the hash key out of the lower 7 bits of the first
+ // two characters, creating a 14-bit key (1 of 16384)
+ //
+ hash_key = (int)((call[0] & 0x7f) << 7);
+ hash_key = hash_key | (int)(call[1] & 0x7f);
+
+ if (station_shortcuts[hash_key] == NULL) {
+ // New hash key entry point found. Fill in the pointer.
+ station_shortcuts_update_function(hash_key, p_new);
+ }
+ else if (p_new->n_prev == NULL) {
+ // We just inserted at the beginning of the list. Assume
+ // that we inserted at the beginning of our hash_key
+ // segment.
+ station_shortcuts_update_function(hash_key, p_new);
+ }
+ else {
+ // Check whether either of the first two chars of the new
+ // callsign and the previous callsign are different. If so,
+ // we need to update the hash table entry for our new record
+ // 'cuz we're at the start of a new hash table entry.
+ if (p_new->n_prev->call_sign[0] != call[0]
+ || p_new->n_prev->call_sign[1] != call[1]) {
+
+ station_shortcuts_update_function(hash_key, p_new);
+ }
+ }
+
+ // Check whether we have a tactical call to assign to this
+ // station in our tactical hash table.
+
+ tactical_call = get_tactical_from_hash(call);
+
+
+ // If tactical call found and not blank
+ if (tactical_call && tactical_call[0] != '\0') {
+
+ // Malloc some memory to hold it in the station record.
+ p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
+ CHECKMALLOC(p_new->tactical_call_sign);
+
+ snprintf(p_new->tactical_call_sign,
+ MAX_TACTICAL_CALL+1,
+ "%s",
+ tactical_call);
+
+ //if (tactical_call[0] == '\0')
+ // fprintf(stderr,"Blank tactical call\n");
+ }
+
+ return(p_new); // return pointer to new element
+}
+
+
+
+/*
+ * Add data from APRS information field to station database
+ * Returns a 1 if successful
+ */
+int data_add(gint type,
+ gchar *call_sign,
+ gchar *path,
+ gchar *data,
+ TAprsPort port,
+ gchar *origin,
+ gint third_party,
+ gint station_is_mine,
+ gint object_is_mine) {
+
+
+ AprsDataRow *p_station;
+ AprsDataRow *p_time;
+ char call[MAX_CALLSIGN+1];
+ char new_station;
+ long last_lat, last_lon;
+ char last_alt[MAX_ALTITUDE];
+ char last_speed[MAX_SPEED+1];
+ char last_course[MAX_COURSE+1];
+ time_t last_stn_sec;
+ short last_flag;
+ char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
+// long l_lat, l_lon;
+ double distance;
+// char station_id[600];
+ int found_pos;
+ float value;
+ AprsWeatherRow *weather;
+ int moving;
+ int changed_pos;
+ int screen_update;
+ int ok, store;
+ int ok_to_display;
+ int compr_pos;
+ char *p = NULL; // KC2ELS - used for WIDEn-N
+ int direct = 0;
+ int object_is_mine_previous = 0;
+ int new_origin_is_mine = 0;
+ int num_digits = 0; // Number of digits after decimal point in NMEA string
+
+// TODO update based on time
+// static time_t lastScreenUpdate;
+
+
+ // call and path had been validated before
+ // Check "data" against the max APRS length, and dump the packet if too long.
+ if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) ) {
+ // Overly long packet. Throw it away.
+
+ return(0); // Not an ok packet
+ }
+
+ // Check for some reasonable string in call_sign parameter
+ if (call_sign == NULL || strlen(call_sign) == 0) {
+
+ return(0);
+ }
+
+
+ if (origin && is_my_call(origin, 1)) {
+ new_origin_is_mine++; // The new object/item is owned by me
+ }
+
+ weather = NULL; // only to make the compiler happy...
+ found_pos = 1;
+ snprintf(call,
+ sizeof(call),
+ "%s",
+ call_sign);
+ p_station = NULL;
+ new_station = (char)FALSE; // to make the compiler happy...
+ last_lat = 0L;
+ last_lon = 0L;
+ last_stn_sec = sec_now();
+ last_alt[0] = '\0';
+ last_speed[0] = '\0';
+ last_course[0] = '\0';
+ last_flag = 0;
+ ok = 0;
+ store = 0;
+ p_time = NULL; // add to end of time sorted list (newest)
+ compr_pos = 0;
+
+
+ if (search_station_name(&p_station,call,1))
+ { // If we found the station in our list
+
+
+// fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);
+
+ // Check whether it's already a locally-owned object/item
+ if (is_my_object_item(p_station)) {
+
+ // We don't want to re-order it in the time-ordered list
+ // so that it'll expire from the queue normally. Don't
+ // call "move_station_time()" here.
+
+ // We need an exception later in this function for the
+ // case where we've moved an object/item (by how much?).
+ // We need to update the time in this case so that it'll
+ // expire later (in fact it could already be expired
+ // when we move it). We should be able to move expired
+ // objects/items to make them active again. Perhaps
+ // some other method as well?
+
+ new_station = (char)FALSE;
+ object_is_mine_previous++;
+ }
+ else {
+ move_station_time(p_station,p_time); // update time, change position in time sorted list
+ new_station = (char)FALSE; // we have seen this one before
+ }
+
+ if (is_my_station(p_station)) {
+ station_is_mine++; // Station/object/item is owned/controlled by me
+ }
+ }
+ else
+ {
+// fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);
+
+ p_station = add_new_station(p_station,p_time,call); // create storage
+ new_station = (char)TRUE; // for new station
+
+ }
+
+
+ if (p_station != NULL)
+ {
+
+ last_lat = p_station->coord_lat; // remember last position
+ last_lon = p_station->coord_lon;
+ last_stn_sec = p_station->sec_heard;
+ snprintf(last_alt,
+ sizeof(last_alt),
+ "%s",
+ p_station->altitude);
+ snprintf(last_speed,
+ sizeof(last_speed),
+ "%s",
+ p_station->speed);
+ snprintf(last_course,
+ sizeof(last_course),
+ "%s",
+ p_station->course);
+ last_flag = p_station->flag;
+
+ // Wipe out old data so that it doesn't hang around forever
+ p_station->altitude[0] = '\0';
+ p_station->speed[0] = '\0';
+ p_station->course[0] = '\0';
+
+ ok = 1; // succeed as default
+
+
+ switch (type) {
+
+ case (APRS_MICE): // Mic-E format
+ case (APRS_FIXED): // '!'
+ case (APRS_MSGCAP): // '='
+
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+
+ if (ok) {
+
+ // Create a timestamp from the current time
+ snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s", get_time(temp_data));
+ (void)extract_storm(p_station,data,compr_pos);
+ (void)extract_weather(p_station,data,compr_pos); // look for weather data first
+ process_data_extension(p_station,data,type); // PHG, speed, etc.
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_comment(p_station,data);
+
+ p_station->record_type = NORMAL_APRS;
+ if (type == APRS_MSGCAP)
+ p_station->flag |= ST_MSGCAP; // set "message capable" flag
+ else
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (type == APRS_MICE || !compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+
+ }
+ break;
+
+/*
+ case (APRS_DOWN): // '/'
+ ok = extract_time(p_station, data, type); // we need a time
+ if (ok) {
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+ }
+
+ if (ok) {
+
+ // Create a timestamp from the current time
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+ process_data_extension(p_station,data,type); // PHG, speed, etc.
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_comment(p_station,data);
+
+ p_station->record_type = DOWN_APRS;
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (!compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+ }
+ break;
+*/
+
+ case (APRS_DF): // '@'
+ case (APRS_MOBILE): // '@'
+
+ ok = extract_time(p_station, data, type); // we need a time
+ if (ok) {
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+ }
+ if (ok) {
+
+ process_data_extension(p_station,data,type); // PHG, speed, etc.
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_comment(p_station,data);
+
+ if(type == APRS_MOBILE)
+ p_station->record_type = MOBILE_APRS;
+ else
+ p_station->record_type = DF_APRS;
+ //@ stations have messaging per spec
+ p_station->flag |= (ST_MSGCAP); // set "message capable" flag
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (!compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+ }
+ break;
+
+ case (APRS_GRID):
+
+ ok = extract_position(p_station, &data, type);
+ if (ok) {
+
+
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_comment(p_station,data);
+
+ // Assign a non-default value for the error
+ // ellipse?
+// p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+
+// WE7U
+// This needs to change based on the number of grid letters/digits specified
+// p_station->lat_precision = 60;
+// p_station->lon_precision = 60;
+ }
+ else {
+ }
+ break;
+
+ case (STATION_CALL_DATA):
+
+ p_station->record_type = NORMAL_APRS;
+ found_pos = 0;
+ break;
+
+ case (APRS_STATUS): // '>' Status Reports [APRS Reference, chapter 16]
+
+ (void)extract_time(p_station, data, type); // we need a time
+ // todo: could contain Maidenhead or beam heading+power
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_status(p_station,data);
+
+ p_station->flag |= (ST_STATUS); // set "Status" flag
+ p_station->record_type = NORMAL_APRS; // ???
+ found_pos = 0;
+ break;
+
+ case (OTHER_DATA): // Other Packets [APRS Reference, chapter 19]
+
+ // non-APRS beacons, treated as status reports until we get a real one
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ if ((p_station->flag & (~ST_STATUS)) == 0) { // only store if no status yet
+
+ add_status(p_station,data);
+
+ p_station->record_type = NORMAL_APRS; // ???
+ }
+ found_pos = 0;
+ break;
+
+ case (APRS_OBJECT):
+
+ // If old match is a killed Object (owner doesn't
+ // matter), new one is an active Object and owned by
+ // us, remove the old record and create a new one
+ // for storing this Object. Do the same for Items
+ // in the next section below.
+ //
+ // The easiest implementation might be to remove the
+ // old record and then call this routine again with
+ // the same parameters, which will cause a brand-new
+ // record to be created.
+ //
+ // The new record we're processing is an active
+ // object, as data_add() won't be called on a killed
+ // object.
+ //
+// if ( is_my_call(origin,1) // If new Object is owned by me (including SSID)
+ if (new_origin_is_mine
+ && !(p_station->flag & ST_ACTIVE)
+ && (p_station->flag & ST_OBJECT) ) { // Old record was a killed Object
+ remove_name(p_station); // Remove old killed Object
+// redo_list = (int)TRUE;
+ return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
+ }
+
+ ok = extract_time(p_station, data, type); // we need a time
+ if (ok) {
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+ }
+ p_station->flag |= ST_OBJECT; // Set "Object" flag
+ if (ok) {
+
+ // If object was owned by me but another station
+ // is transmitting it now, write entries into
+ // the object.log file showing that we don't own
+ // this object anymore.
+
+// if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
+// && (!is_my_call(origin,1)) ) { // But isn't now
+ // TODO
+/*
+ if (is_my_object_item(p_station) // If station was owned by me (include SSID)
+ && !new_origin_is_mine) { // But isn't now
+
+ disown_object_item(call_sign, origin);
+
+ }
+*/
+ // If station is owned by me (including SSID)
+ // but it's a new object/item
+// if ( (is_my_call(p_station->origin,1))
+ if (new_origin_is_mine
+ && (p_station->transmit_time_increment == 0) ) {
+ // This will get us transmitting this object
+ // on the decaying algorithm schedule.
+ // We've transmitted it once if we've just
+ // gotten to this code.
+ p_station->transmit_time_increment = OBJECT_CHECK_RATE;
+//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
+ }
+
+ // Create a timestamp from the current time
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+
+ xastir_snprintf(p_station->origin,
+ sizeof(p_station->origin),
+ "%s",
+ origin); // define it as object
+ (void)extract_storm(p_station,data,compr_pos);
+ (void)extract_weather(p_station,data,compr_pos); // look for wx info
+ process_data_extension(p_station,data,type); // PHG, speed, etc.
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 0);
+
+ add_comment(p_station,data);
+
+ // the last char always was missing...
+ //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
+ // moved that to decode_ax25_line
+ // and don't added a '\n' in interface.c
+ p_station->record_type = NORMAL_APRS;
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (!compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+ }
+ break;
+
+ case (APRS_ITEM):
+
+ // If old match is a killed Item (owner doesn't
+ // matter), new one is an active Item and owned by
+ // us, remove the old record and create a new one
+ // for storing this Item. Do the same for Objects
+ // in the previous section above.
+ //
+ // The easiest implementation might be to remove the
+ // old record and then call this routine again with
+ // the same parameters, which will cause a brand-new
+ // record to be created.
+ //
+ // The new record we're processing is an active
+ // Item, as data_add() won't be called on a killed
+ // Item.
+ //
+// if ( is_my_call(origin,1) // If new Item is owned by me (including SSID)
+ if (new_origin_is_mine
+ && !(p_station->flag & ST_ACTIVE)
+ && (p_station->flag & ST_ITEM) ) { // Old record was a killed Item
+
+ remove_name(p_station); // Remove old killed Item
+// redo_list = (int)TRUE;
+ return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
+ }
+
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+ p_station->flag |= ST_ITEM; // Set "Item" flag
+ if (ok) {
+
+ // If item was owned by me but another station
+ // is transmitting it now, write entries into
+ // the object.log file showing that we don't own
+ // this item anymore.
+// TODO
+/*
+// if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
+// && (!is_my_call(origin,1)) ) { // But isn't now
+ if (is_my_object_item(p_station)
+ && !new_origin_is_mine) { // But isn't now
+
+ disown_object_item(call_sign,origin);
+ }
+ */
+
+ // If station is owned by me (including SSID)
+ // but it's a new object/item
+// if ( (is_my_call(p_station->origin,1))
+ if (is_my_object_item(p_station)
+ && (p_station->transmit_time_increment == 0) ) {
+ // This will get us transmitting this object
+ // on the decaying algorithm schedule.
+ // We've transmitted it once if we've just
+ // gotten to this code.
+ p_station->transmit_time_increment = OBJECT_CHECK_RATE;
+//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
+ }
+
+ // Create a timestamp from the current time
+ xastir_snprintf(p_station->pos_time,
+ sizeof(p_station->pos_time),
+ "%s",
+ get_time(temp_data));
+ xastir_snprintf(p_station->origin,
+ sizeof(p_station->origin),
+ "%s",
+ origin); // define it as item
+ (void)extract_storm(p_station,data,compr_pos);
+ (void)extract_weather(p_station,data,compr_pos); // look for wx info
+ process_data_extension(p_station,data,type); // PHG, speed, etc.
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 0);
+
+ add_comment(p_station,data);
+
+ // the last char always was missing...
+ //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
+ // moved that to decode_ax25_line
+ // and don't added a '\n' in interface.c
+ p_station->record_type = NORMAL_APRS;
+ p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (!compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+ }
+ break;
+
+ case (APRS_WX1): // weather in '@' or '/' packet
+
+ ok = extract_time(p_station, data, type); // we need a time
+ if (ok) {
+ if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
+ compr_pos = 1;
+ if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+ ok = 0;
+ else
+ p_station->pos_amb = 0; // No ambiguity in compressed posits
+ }
+ }
+ if (ok) {
+
+ (void)extract_storm(p_station,data,compr_pos);
+ (void)extract_weather(p_station,data,compr_pos);
+ p_station->record_type = (char)APRS_WX1;
+
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+
+ add_comment(p_station,data);
+
+ // Assign a non-default value for the error
+ // ellipse?
+ if (!compr_pos) {
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ }
+ else {
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ }
+ }
+ break;
+
+ case (APRS_WX2): // '_'
+
+ ok = extract_time(p_station, data, type); // we need a time
+ if (ok) {
+ (void)extract_storm(p_station,data,compr_pos);
+ (void)extract_weather(p_station,data,0); // look for weather data first
+ p_station->record_type = (char)APRS_WX2;
+ found_pos = 0;
+
+ process_info_field(p_station,data,type); // altitude
+
+ if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+ extract_multipoints(p_station, data, type, 1);
+ }
+ break;
+
+ case (APRS_WX4): // '#' Peet Bros U-II (km/h)
+ case (APRS_WX6): // '*' Peet Bros U-II (mph)
+ case (APRS_WX3): // '!' Peet Bros Ultimeter 2000 (data logging mode)
+ case (APRS_WX5): // '$ULTW' Peet Bros Ultimeter 2000 (packet mode)
+
+
+ if (get_weather_record(p_station)) { // get existing or create new weather record
+ weather = p_station->weather_data;
+ if (type == APRS_WX3) // Peet Bros Ultimeter 2000 data logging mode
+ {
+ decode_U2000_L (1,(unsigned char *)data,weather);
+ }
+ else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
+ decode_U2000_P(1,(unsigned char *)data,weather);
+ else // Peet Bros Ultimeter-II
+ decode_Peet_Bros(1,(unsigned char *)data,weather,type);
+ p_station->record_type = (char)type;
+ // Create a timestamp from the current time
+ xastir_snprintf(weather->wx_time,
+ sizeof(weather->wx_time),
+ "%s",
+ get_time(temp_data));
+ weather->wx_sec_time = sec_now();
+ found_pos = 0;
+ }
+
+ break;
+
+
+// GPRMC, digits after decimal point
+// ---------------------------------
+// 2 = 25.5 meter error ellipse
+// 3 = 6.0 meter error ellipse
+// 4+ = 6.0 meter error ellipse
+
+
+ case (GPS_RMC): // $GPRMC
+
+// WE7U
+// Change this function to return HDOP and the number of characters
+// after the decimal point.
+ ok = extract_RMC(p_station,data,call_sign,path,&num_digits);
+
+ if (ok) {
+ // Assign a non-default value for the error
+ // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence. If only
+// 2 digits after decimal point, give it 2550 as a radius (25.5m).
+// Best (smallest) circle should be 600 as we have no augmentation
+// flag to check here for anything better.
+//
+ switch (num_digits) {
+
+ case 0:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 6000;
+ p_station->lon_precision = 6000;
+ break;
+
+ case 1:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 600;
+ p_station->lon_precision = 600;
+ break;
+
+ case 2:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+
+ case 3:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 0;
+ p_station->lon_precision = 0;
+ break;
+
+ default:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+ }
+ }
+ break;
+
+
+// GPGGA, digits after decimal point, w/o augmentation
+// ---------------------------------------------------
+// 2 = 25.5 meter error ellipse
+// 3 = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
+// 4+ = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
+//
+//
+// GPGGA, digits after decimal point, w/augmentation
+// --------------------------------------------------
+// 2 = 25.5 meter error ellipse
+// 3 = 2.5 meter error ellipse unless HDOP>4, then 10.0 meters
+// 4+ = 0.6 meter error ellipse unless HDOP>4, then 10.0 meters
+
+
+ case (GPS_GGA): // $GPGGA
+
+// WE7U
+// Change this function to return HDOP and the number of characters
+// after the decimal point.
+ ok = extract_GGA(p_station,data,call_sign,path,&num_digits);
+
+ if (ok) {
+ // Assign a non-default value for the error
+ // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence. If only
+// 2 digits after decimal point, give it 2550 as a radius (25.5m).
+// 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
+// 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
+//
+ switch (num_digits) {
+
+ case 0:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 6000;
+ p_station->lon_precision = 6000;
+ break;
+
+ case 1:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 600;
+ p_station->lon_precision = 600;
+ break;
+
+ case 2:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+
+ case 3:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 0;
+ p_station->lon_precision = 0;
+ break;
+
+ default:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+ }
+ }
+ break;
+
+
+// GPGLL, digits after decimal point
+// ---------------------------------
+// 2 = 25.5 meter error ellipse
+// 3 = 6.0 meter error ellipse
+// 4+ = 6.0 meter error ellipse
+
+
+ case (GPS_GLL): // $GPGLL
+ ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
+
+ if (ok) {
+ // Assign a non-default value for the error
+ // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence. If only
+// 2 digits after decimal point, give it 2550 as a radius, otherwise
+// give it 600.
+//
+ switch (num_digits) {
+
+ case 0:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 6000;
+ p_station->lon_precision = 6000;
+ break;
+
+ case 1:
+ p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+ p_station->lat_precision = 600;
+ p_station->lon_precision = 600;
+ break;
+
+ case 2:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+
+ case 3:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 6;
+ p_station->lon_precision = 6;
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 0;
+ p_station->lon_precision = 0;
+ break;
+
+ default:
+ p_station->error_ellipse_radius = 600; // Default of 6m
+ p_station->lat_precision = 60;
+ p_station->lon_precision = 60;
+ break;
+ }
+
+ }
+ break;
+
+ default:
+
+ fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
+ ok = 0;
+ break;
+ }
+
+ // Left this one in, just in case. Perhaps somebody might
+ // attach a multipoint string onto the end of a packet we
+ // might not expect. For this case we need to check whether
+ // we have multipoints first, as we don't want to erase the
+ // work we might have done with a previous call to
+ // extract_multipoints().
+ if (ok && (p_station->coord_lat > 0)
+ && (p_station->coord_lon > 0)
+ && (p_station->num_multipoints == 0) ) { // No multipoints found yet
+
+ extract_multipoints(p_station, data, type, 0);
+ }
+ }
+
+ if (!ok) { // non-APRS beacon, treat it as Other Packet [APRS Reference, chapter 19]
+
+ // GPRMC etc. without a position is here too, but it should not be stored as status!
+
+ // store it as status report until we get a real one
+ if ((p_station->flag & (~ST_STATUS)) == 0) { // only store it if no status yet
+
+ add_status(p_station,data-1);
+
+ p_station->record_type = NORMAL_APRS; // ???
+
+ }
+
+ ok = 1;
+ found_pos = 0;
+ }
+
+ curr_sec = sec_now();
+ if (ok) {
+
+ // data packet is valid
+ // announce own echo, we soon discard that packet...
+// if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
+ if (!new_station
+ && is_my_station(p_station) // Check SSID as well
+ && strchr(path,'*') != NULL) {
+
+ upd_echo(path); // store digi that echoes my signal...
+ statusline("Echo from digipeater",0); // Echo from digipeater
+
+ }
+ // check if data is just a secondary echo from another digi
+ if ((last_flag & ST_VIATNC) == 0
+ || (curr_sec - last_stn_sec) > 15
+ || p_station->coord_lon != last_lon
+ || p_station->coord_lat != last_lat)
+
+ store = 1; // don't store secondary echos
+ }
+
+ if (!ok && new_station)
+ delete_station_memory(p_station); // remove unused record
+
+ if (store) {
+
+ // we now have valid data to store into database
+ // make time index unique by adding a serial number
+
+ if (station_is_mine) {
+ // This station/object/item is owned by me. Set the
+ // flag which shows that we own/control this
+ // station/object/item. We use this flag later in lieu
+ // of the is_my_call() function in order to speed things
+ // up.
+ //
+ p_station->flag |= ST_MYSTATION;
+ }
+
+ // Check whether it's a locally-owned object/item
+ if ( object_is_mine
+ || ( new_origin_is_mine
+ && (p_station->flag & ST_ACTIVE)
+ && (p_station->flag & ST_OBJECT) ) ) {
+
+ p_station->flag |= ST_MYOBJITEM;
+
+ // Do nothing else. We don't want to update the
+ // last-heard time so that it'll expire from the queue
+ // normally, unless it is a new object/item.
+ //
+ if (new_station) {
+ p_station->sec_heard = curr_sec;
+ }
+
+ // We need an exception later in this function for the
+ // case where we've moved an object/item (by how much?).
+ // We need to update the time in this case so that it'll
+ // expire later (in fact it could already be expired
+ // when we move it). We should be able to move expired
+ // objects/items to make them active again. Perhaps
+ // some other method as well?.
+ }
+ else {
+ // Reset the "my object" flag
+ p_station->flag &= ~ST_MYOBJITEM;
+
+ p_station->sec_heard = curr_sec; // Give it a new timestamp
+ }
+
+ if (curr_sec != last_sec) { // todo: if old time calculate time_sn from database
+ last_sec = curr_sec;
+ next_time_sn = 0; // restart time serial number
+ }
+
+ p_station->time_sn = next_time_sn++; // todo: warning if serial number too high
+ if (port == APRS_PORT_TTY) { // heard via TNC
+ if (!third_party) { // Not a third-party packet
+ p_station->flag |= ST_VIATNC; // set "via TNC" flag
+ p_station->heard_via_tnc_last_time = curr_sec;
+ p_station->heard_via_tnc_port = port;
+ }
+ else { // Third-party packet
+ // Leave the previous setting of "flag" alone.
+ // Specifically do NOT set the ST_VIATNC flag if it
+ // was a third-party packet.
+ }
+ }
+ else { // heard other than TNC
+ if (new_station) { // new station
+ p_station->flag &= (~ST_VIATNC); // clear "via TNC" flag
+//fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
+ p_station->heard_via_tnc_last_time = 0l;
+ }
+ }
+ p_station->last_port_heard = port;
+
+ // Create a timestamp from the current time
+ snprintf(p_station->packet_time, sizeof(p_station->packet_time),
+ "%s", get_time(temp_data)); // get_time returns value in temp_data
+
+ p_station->flag |= ST_ACTIVE;
+ if (third_party)
+ p_station->flag |= ST_3RD_PT; // set "third party" flag
+ else
+ p_station->flag &= (~ST_3RD_PT); // clear "third party" flag
+ if (origin != NULL && strcmp(origin,"INET") == 0) // special treatment for inet names
+ snprintf(p_station->origin,
+ sizeof(p_station->origin),
+ "%s",
+ origin); // to keep them separated from calls
+ if (origin != NULL && strcmp(origin,"INET-NWS") == 0) // special treatment for NWS
+ snprintf(p_station->origin,
+ sizeof(p_station->origin),
+ "%s",
+ origin); // to keep them separated from calls
+ if (origin == NULL || origin[0] == '\0') // normal call
+ p_station->origin[0] = '\0'; // undefine possible former object with same name
+
+
+ //--------------------------------------------------------------------
+
+ // KC2ELS
+ // Okay, here are the standards for ST_DIRECT:
+ // 1. The packet must have been received via TNC.
+ // 2. The packet must not have any * flags.
+ // 3. If present, the first WIDEn-N (or TRACEn-N) must have n=N.
+ // A station retains the ST_DIRECT setting. If
+ // "st_direct_timeout" seconds have passed since we set
+ // that bit then APRSD queries and displays based on the
+ // ST_DIRECT bit will skip that station.
+
+// In order to make this scheme work for stations that straddle both
+// RF and INET, we need to make sure that node_path_ptr doesn't get
+// overwritten with an INET path if there's an RF path already in
+// there and it has been less than st_direct_timeout seconds since
+// the station was last heard on RF.
+
+ if ((port == APRS_PORT_TTY) // Heard via TNC
+ && !third_party // Not a 3RD-Party packet
+ && path != NULL // Path is not NULL
+ && strchr(path,'*') == NULL) { // No asterisk found
+
+ // Look for WIDE or TRACE
+ if ((((p = strstr(path,"WIDE")) != NULL)
+ && (p+=4)) ||
+ (((p = strstr(path,"TRACE")) != NULL)
+ && (p+=5))) {
+
+ // Look for n=N on WIDEn-N/TRACEn-N digi field
+ if ((*p != '\0') && isdigit((int)*p)) {
+ if ((*(p+1) != '\0') && (*(p+1) == '-')) {
+ if ((*(p+2) != '\0') && g_ascii_isdigit(*(p+2))) {
+ if (*(p) == *(p+2)) {
+ direct = 1;
+ }
+ else {
+ direct = 0;
+ }
+ }
+ else {
+ direct = 0;
+ }
+ }
+ else {
+ direct = 0;
+ }
+ }
+ else {
+ direct = 1;
+ }
+ }
+ else {
+ direct = 1;
+ }
+ }
+ else {
+ direct = 0;
+ }
+
+
+ if (direct == 1) {
+ // This packet was heard direct. Set the ST_DIRECT bit
+ // and save the timestamp away.
+ p_station->direct_heard = curr_sec;
+ p_station->flag |= (ST_DIRECT);
+ }
+ else {
+ // This packet was NOT heard direct. Check whether we
+ // need to expire the ST_DIRECT bit. A lot of fixed
+ // stations transmit every 30 minutes. One hour gives
+ // us time to receive a direct packet from them among
+ // all the digipeated packets.
+
+ if ((p_station->flag & ST_DIRECT) != 0 &&
+ curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+ p_station->flag &= (~ST_DIRECT);
+ }
+ }
+
+ // If heard on TNC, overwrite node_path_ptr if any of these
+ // conditions are met:
+ // *) direct == 1 (packet was heard direct)
+ // *) ST_DIRECT flag == 0 (packet hasn't been heard
+ // direct recently)
+ // *) ST_DIRECT is set, st_direct_timeout has expired
+ // (packet hasn't been heard direct recently)
+ //
+ // These rules will allow us to keep directly heard paths
+ // saved for at least an hour (st_direct_timeout), and not
+ // get overwritten with digipeated paths during that time.
+ //
+ if ((port == APRS_PORT_TTY) // Heard via TNC
+ && !third_party // Not a 3RD-Party packet
+ && path != NULL) { // Path is not NULL
+
+ // Heard on TNC interface and not third party. Check
+ // the other conditions listed in the comments above to
+ // decide whether we should overwrite the node_path_ptr
+ // variable.
+ //
+ if ( direct // This packet was heard direct
+ || (p_station->flag & ST_DIRECT) == 0 // Not heard direct lately
+ || ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
+ && (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) ) {
+
+ // Free any old path we might have
+ if (p_station->node_path_ptr != NULL)
+ free(p_station->node_path_ptr);
+ // Malloc and store the new path
+ p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+ CHECKMALLOC(p_station->node_path_ptr);
+
+ substr(p_station->node_path_ptr,path,strlen(path));
+ }
+ }
+ // If a 3rd-party packet heard on TNC, overwrite
+ // node_path_ptr only if heard_via_tnc_last_time is older
+ // than one hour (zero counts as well!), plus clear the
+ // ST_DIRECT and ST_VIATNC bits in this case. This makes us
+ // keep the RF path around for at least one hour after the
+ // station is heard.
+ //
+ else if ((port == APRS_PORT_TTY) // Heard via TNC
+ && third_party // It's a 3RD-Party packet
+ && path != NULL) { // Path is not NULL
+
+ // 3rd-party packet heard on TNC interface. Check if
+ // heard_via_tnc_last_time is older than an hour. If
+ // so, overwrite the path and clear a few bits to show
+ // that it has timed out on RF and we're now receiving
+ // that station from an igate.
+ //
+ if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
+
+ // Yep, more than one hour old or is a zero,
+ // overwrite the node_path_ptr variable with the new
+ // one. We're only hearing this station on INET
+ // now.
+
+ // Free any old path we might have
+ if (p_station->node_path_ptr != NULL)
+ free(p_station->node_path_ptr);
+ // Malloc and store the new path
+ p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+ CHECKMALLOC(p_station->node_path_ptr);
+
+ substr(p_station->node_path_ptr,path,strlen(path));
+
+ // Clear the ST_VIATNC bit
+ p_station->flag &= ~ST_VIATNC;
+ }
+
+ // If direct_heard is over an hour old, clear the
+ // ST_DIRECT flag. We're only hearing this station on
+ // INET now.
+ //
+ if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+
+ // Yep, more than one hour old or is a zero, clear
+ // the ST_DIRECT flag.
+ p_station->flag &= ~ST_DIRECT;
+ }
+ }
+
+ // If heard on INET then overwrite node_path_ptr only if
+ // heard_via_tnc_last_time is older than one hour (zero
+ // counts as well!), plus clear the ST_DIRECT and ST_VIATNC
+ // bits in this case. This makes us keep the RF path around
+ // for at least one hour after the station is heard.
+ //
+ else if (port != APRS_PORT_TTY // From an INET interface
+ && !third_party // Not a 3RD-Party packet
+ && path != NULL) { // Path is not NULL
+
+ // Heard on INET interface. Check if
+ // heard_via_tnc_last_time is older than an hour. If
+ // so, overwrite the path and clear a few bits to show
+ // that it has timed out on RF and we're now receiving
+ // that station from the INET feeds.
+ //
+ if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
+
+ // Yep, more than one hour old or is a zero,
+ // overwrite the node_path_ptr variable with the new
+ // one. We're only hearing this station on INET
+ // now.
+
+ // Free any old path we might have
+ if (p_station->node_path_ptr != NULL)
+ free(p_station->node_path_ptr);
+ // Malloc and store the new path
+ p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+ CHECKMALLOC(p_station->node_path_ptr);
+
+ substr(p_station->node_path_ptr,path,strlen(path));
+
+ // Clear the ST_VIATNC bit
+ p_station->flag &= ~ST_VIATNC;
+
+ }
+
+ // If direct_heard is over an hour old, clear the
+ // ST_DIRECT flag. We're only hearing this station on
+ // INET now.
+ //
+ if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+
+ // Yep, more than one hour old or is a zero, clear
+ // the ST_DIRECT flag.
+ p_station->flag &= ~ST_DIRECT;
+ }
+ }
+
+
+ //---------------------------------------------------------------------
+
+ p_station->num_packets += 1;
+
+
+ screen_update = 0;
+ if (new_station) {
+ if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
+ p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
+ moving = 1;
+ }
+
+ if (p_station->coord_lat != 0 && p_station->coord_lon != 0) { // discard undef positions from screen
+ if (!altnet || is_altnet(p_station) ) {
+
+ screen_update = 1;
+ // TODO - check needed
+ }
+
+ }
+ }
+ else { // we had seen this station before...
+
+ if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1)) { // ignore undefined and 0N/0E
+ if (p_station->newest_trackpoint != NULL) {
+ moving = 1; // it's moving if it has a trail
+ }
+ else {
+ if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
+ moving = 1; // declare it moving, if it has a speed
+ }
+ else {
+ // Here's where we detect movement
+ if (position_defined(last_lat,last_lon,1)
+ && (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon)) {
+ moving = 1; // it's moving if it has changed the position
+ }
+ else {
+ moving = 0;
+ }
+ }
+ }
+ changed_pos = 0;
+ if (moving == 1) {
+ p_station->flag |= (ST_MOVING);
+ // we have a moving station, process trails
+ if (atoi(p_station->speed) < TRAIL_MAX_SPEED) { // reject high speed data (undef gives 0)
+ // we now may already have the 2nd position, so store the old one first
+ if (p_station->newest_trackpoint == NULL) {
+ if (position_defined(last_lat,last_lon,1)) { // ignore undefined and 0N/0E
+ (void)store_trail_point(p_station,
+ last_lon,
+ last_lat,
+ last_stn_sec,
+ last_alt,
+ last_speed,
+ last_course,
+ last_flag);
+ }
+ }
+ //if ( p_station->coord_lon != last_lon
+ // || p_station->coord_lat != last_lat ) {
+ // we don't store redundant points (may change this
+ // later ?)
+ //
+ // There are often echoes delayed 15 minutes
+ // or so it looks ugly on the trail, so I
+ // want to discard them This also discards
+ // immediate echoes. Duplicates back in time
+ // up to TRAIL_ECHO_TIME minutes are
+ // discarded.
+ //
+ if (!is_trailpoint_echo(p_station)) {
+ (void)store_trail_point(p_station,
+ p_station->coord_lon,
+ p_station->coord_lat,
+ p_station->sec_heard,
+ p_station->altitude,
+ p_station->speed,
+ p_station->course,
+ p_station->flag);
+ changed_pos = 1;
+
+ // Check whether it's a locally-owned object/item
+ if (object_is_mine) {
+
+ // Update time, change position in
+ // time-sorted list to change
+ // expiration time.
+ move_station_time(p_station,p_time);
+
+ // Give it a new timestamp
+ p_station->sec_heard = curr_sec;
+ //fprintf(stderr,"Updating last heard time\n");
+ }
+ }
+ }
+
+// if (track_station_on == 1) // maybe we are tracking a station
+// track_station(da,tracking_station_call,p_station);
+ } // moving...
+
+ // now do the drawing to the screen
+ ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
+// TODO
+// screen_update = 0;
+// if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0)) {
+// if (ok_to_display) {
+// draw_trail(da,p_station,1); // update trail
+// screen_update = 1;
+// }
+
+ }
+
+ if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
+ if (ok_to_display) {
+
+ screen_update = 1; // TODO -check needed
+ }
+
+ }
+ } // defined position
+ }
+
+ if (screen_update) {
+ if (p_station->last_port_heard == APRS_PORT_TTY) { // Data from local TNC
+
+ redraw_on_new_data = 2; // Update all symbols NOW!
+ }
+ else {
+ redraw_on_new_data = 0; // Update each 60 secs
+ }
+ }
+
+
+ if(ok)
+ {
+
+ plot_aprs_station( p_station, TRUE );
+
+ }
+
+ return(ok);
+} // End of data_add() function
+
+
+int is_xnum_or_dash(char *data, int max){
+ int i;
+ int ok;
+
+ ok=1;
+ for(i=0; i<max;i++)
+ if(!(isxdigit((int)data[i]) || data[i]=='-')) {
+ ok=0;
+ break;
+ }
+
+ return(ok);
+}
+
+
+
+/*
+ * Decode Mic-E encoded data
+ */
+int decode_Mic_E(char *call_sign,char *path,char *info,int port,int third_party) {
+ int ii;
+ int offset;
+ unsigned char s_b1;
+ unsigned char s_b2;
+ unsigned char s_b3;
+ unsigned char s_b4;
+ unsigned char s_b5;
+ unsigned char s_b6;
+ unsigned char s_b7;
+ int north,west,long_offset;
+ int d,m,h;
+ char temp[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
+ char new_info[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
+ int course;
+ int speed;
+ int msg1,msg2,msg3,msg;
+ int info_size;
+ long alt;
+ int msgtyp;
+ char rig_type[10];
+ int ok;
+
+ // MIC-E Data Format [APRS Reference, chapter 10]
+
+ // todo: error check
+ // drop wrong positions from receive errors...
+ // drop 0N/0E position (p.25)
+
+ /* First 7 bytes of info[] contains the APRS data type ID, */
+ /* longitude, speed, course. */
+ /* The 6-byte destination field of path[] contains latitude, */
+ /* N/S bit, E/W bit, longitude offset, message code. */
+ /*
+
+ MIC-E Destination Field Format:
+ -------------------------------
+ Ar1DDDD0 Br1DDDD0 Cr1MMMM0 Nr1MMMM0 Lr1HHHH0 Wr1HHHH0 CrrSSID0
+ D = Latitude Degrees.
+ M = Latitude Minutes.
+ H = Latitude Hundredths of Minutes.
+ ABC = Message bits, complemented.
+ N = N/S latitude bit (N=1).
+ W = E/W longitude bit (W=1).
+ L = 100's of longitude degrees (L=1 means add 100 degrees to longitude
+ in the Info field).
+ C = Command/Response flag (see AX.25 specification).
+ r = reserved for future use (currently 0).
+
+ */
+ /****************************************************************************
+ * I still don't handle: *
+ * Custom message bits *
+ * SSID special routing *
+ * Beta versions of the MIC-E (which use a slightly different format). *
+ * *
+ * DK7IN : lat/long with custom msg works, altitude/course/speed works *
+ *****************************************************************************/
+
+
+ // Note that the first MIC-E character was not passed to us, so we're
+ // starting just past it.
+ // Check for valid symbol table character. Should be '/' or '\'
+ // or 0-9, A-Z.
+ //
+ if ( info[7] == '/' // Primary table
+ || info[7] == '\\' // Alternate table
+ || (info[7] >= '0' && info[7] <= '9') // Overlay char
+ || (info[7] >= 'A' && info[7] <= 'Z') ) { // Overlay char
+
+ // We're good, keep going
+
+ }
+ else { // Symbol table or overlay char incorrect
+
+ if (info[6] == '/' || info[6] == '\\') { // Found it back one char in string
+ // Don't print out the full info string here because it
+ // can contain unprintable characters. In fact, we
+ // should check the chars we do print out to make sure
+ // they're printable, else print a space char.
+ }
+
+ return(1); // No good, not MIC-E format or corrupted packet. Return 1
+ // so that it won't get added to the database at all.
+ }
+
+ // Check for valid symbol. Should be between '!' and '~' only.
+ if (info[6] < '!' || info[6] > '~') {
+
+ return(1); // No good, not MIC-E format or corrupted packet. Return 1
+ // so that it won't get added to the database at all.
+ }
+
+ // Check for minimum MIC-E size.
+ if (strlen(info) < 8) {
+
+ return(1); // No good, not MIC-E format or corrupted packet. Return 1
+ // so that it won't get added to the database at all.
+ }
+
+ // Check for 8-bit characters in the first eight slots. Not
+ // allowed per Mic-E chapter of the spec.
+ for (ii = 0; ii < 8; ii++) {
+ if ((unsigned char)info[ii] > 0x7f) {
+ // 8-bit data was found in the lat/long/course/speed
+ // portion. Bad packet. Drop it.
+//fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
+ return(1);
+ }
+ }
+
+ // Check whether we have more data. If flag character is 0x1d
+ // (8-bit telemetry flag) then don't do the 8-bit check below.
+ if (strlen(info) > 8) {
+
+ // Check for the 8-bit telemetry flag
+ if ((unsigned char)info[8] == 0x1d) {
+ // 8-bit telemetry found, skip the check loop below
+ }
+ else { // 8-bit telemetry flag was not found. Check that
+ // we only have 7-bit characters through the rest of
+ // the packet.
+
+ for (ii = 8; ii < (int)strlen(info); ii++) {
+
+ if ((unsigned char)info[ii] > 0x7f) {
+ // 8-bit data was found. Bad packet. Drop it.
+//fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
+ return(1);
+ }
+ }
+ }
+ }
+
+ //fprintf(stderr,"Path1:%s\n",path);
+
+ msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
+ msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
+ msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
+ msg = msg1 | msg2 | msg3; // We now have the complemented message number in one variable
+ msg = msg ^ 0x07; // And this is now the normal message number
+ msgtyp = 0; // DK7IN: Std message, I have to add custom msg decoding
+
+ //fprintf(stderr,"Msg: %d\n",msg);
+
+ /* Snag the latitude from the destination field, Assume TAPR-2 */
+ /* DK7IN: latitude now works with custom message */
+ s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
+ //fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
+ if (path[0] & 0x10) // A-J
+ s_b1 += (unsigned char)1;
+
+ if (s_b1 > (unsigned char)0x39) // K,L,Z
+ s_b1 = (unsigned char)0x20;
+ //fprintf(stderr,"s_b1:%c\n",s_b1);
+
+ s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
+ //fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
+ if (path[1] & 0x10) // A-J
+ s_b2 += (unsigned char)1;
+
+ if (s_b2 > (unsigned char)0x39) // K,L,Z
+ s_b2 = (unsigned char)0x20;
+ //fprintf(stderr,"s_b2:%c\n",s_b2);
+
+ s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
+ if (path[2] & 0x10) // A-J
+ s_b3 += (unsigned char)1;
+
+ if (s_b3 > (unsigned char)0x39) // K,L,Z
+ s_b3 = (unsigned char)0x20;
+
+ s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
+ if (s_b4 > (unsigned char)0x39) // L,Z
+ s_b4 = (unsigned char)0x20;
+
+
+ s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
+ if (s_b5 > (unsigned char)0x39) // L,Z
+ s_b5 = (unsigned char)0x20;
+
+
+ s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
+ //fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
+ if (s_b6 > (unsigned char)0x39) // L,Z
+ s_b6 = (unsigned char)0x20;
+ //fprintf(stderr,"s_b6:%c\n",s_b6);
+
+ s_b7 = (unsigned char)path[6]; // SSID, not used here
+ //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
+
+ //fprintf(stderr,"\n");
+
+ // Special tests for 'L' due to position ambiguity deviances in
+ // the APRS spec table. 'L' has the 0x40 bit set, but they
+ // chose in the spec to have that represent position ambiguity
+ // _without_ the North/West/Long Offset bit being set. Yuk!
+ // Please also note that the tapr.org Mic-E document (not the
+ // APRS spec) has the state of the bit wrong in columns 2 and 3
+ // of their table. Reverse them.
+ if (path[3] == 'L')
+ north = 0;
+ else
+ north = (int)((path[3] & 0x40) == (char)0x40); // N/S Lat Indicator
+
+ if (path[4] == 'L')
+ long_offset = 0;
+ else
+ long_offset = (int)((path[4] & 0x40) == (char)0x40); // Longitude Offset
+
+ if (path[5] == 'L')
+ west = 0;
+ else
+ west = (int)((path[5] & 0x40) == (char)0x40); // W/E Long Indicator
+
+ //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
+
+ /* Put the latitude string into the temp variable */
+ snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
+ (north ? 'N': 'S'), info[7]); // info[7] = symbol table
+
+ /* Compute degrees longitude */
+ snprintf(new_info, sizeof(new_info), "%s", temp);
+ d = (int) info[0]-28;
+
+ if (long_offset)
+ d += 100;
+
+ if ((180<=d)&&(d<=189)) // ??
+ d -= 80;
+
+ if ((190<=d)&&(d<=199)) // ??
+ d -= 190;
+
+ /* Compute minutes longitude */
+ m = (int) info[1]-28;
+ if (m>=60)
+ m -= 60;
+
+ /* Compute hundredths of minutes longitude */
+ h = (int) info[2]-28;
+ /* Add the longitude string into the temp variable */
+ xastir_snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+
+ /* Compute speed in knots */
+ speed = (int)( ( info[3] - (char)28 ) * (char)10 );
+ speed += ( (int)( (info[4] - (char)28) / (char)10) );
+ if (speed >= 800)
+ speed -= 800; // in knots
+
+ /* Compute course */
+ course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
+ if (course >= 400)
+ course -= 400;
+
+ /* ???
+ fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
+ fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
+ */
+ xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+ offset = 8; // start of rest of info
+
+ /* search for rig type in Mic-E data */
+ rig_type[0] = '\0';
+ if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']')) {
+ /* detected type code: > TH-D7 ] TM-D700 */
+ if (info[offset] == '>')
+ xastir_snprintf(rig_type,
+ sizeof(rig_type),
+ " TH-D7");
+ else
+ xastir_snprintf(rig_type,
+ sizeof(rig_type),
+ " TM-D700");
+
+ offset++;
+ }
+
+ info_size = (int)strlen(info);
+ /* search for compressed altitude in Mic-E data */ // {
+ if (info_size >= offset+4 && info[offset+3] == '}') { // {
+ /* detected altitude ___} */
+ alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
+ + (long)info[offset+2] - (long)33) - 10000; // altitude in meters
+ alt /= 0.3048; // altitude in feet, as in normal APRS
+
+ //32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
+ //packet may go. Upper limit is mostly a guess.
+ if ( (alt > 500000) || (alt < -32809) ) { // Altitude is whacko. Skip it.
+ offset += 4;
+ }
+ else { // Altitude is ok
+ xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
+ offset += 4;
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+ }
+ }
+
+ /* start of comment */
+ if (strlen(rig_type) > 0) {
+ xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+ }
+
+ strncat(new_info,
+ " Mic-E ",
+ sizeof(new_info) - strlen(new_info));
+ if (msgtyp == 0) {
+ switch (msg) {
+ case 1:
+ strncat(new_info,
+ "Enroute",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 2:
+ strncat(new_info,
+ "In Service",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 3:
+ strncat(new_info,
+ "Returning",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 4:
+ strncat(new_info,
+ "Committed",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 5:
+ strncat(new_info,
+ "Special",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 6:
+ strncat(new_info,
+ "Priority",
+ sizeof(new_info) - strlen(new_info));
+ break;
+
+ case 7:
+ strncat(new_info,
+ "Emergency",
+ sizeof(new_info) - strlen(new_info));
+
+ // Functionality removed
+
+ break;
+
+ default:
+ strncat(new_info,
+ "Off Duty",
+ sizeof(new_info) - strlen(new_info));
+ }
+ }
+ else {
+ xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+ }
+
+ if (info[offset] != '\0') {
+ /* Append the rest of the message to the expanded MIC-E message */
+ for (ii=offset; ii<info_size; ii++)
+ temp[ii-offset] = info[ii];
+
+ temp[info_size-offset] = '\0';
+ strncat(new_info,
+ " ",
+ sizeof(new_info) - strlen(new_info));
+ strncat(new_info,
+ temp,
+ sizeof(new_info) - strlen(new_info));
+ }
+
+
+ // We don't transmit Mic-E protocol from Xastir, so we know it's
+ // not our station's packets or our object/item packets,
+ // therefore the last two parameters here are both zero.
+ //
+ ok = data_add(APRS_MICE,call_sign,path,new_info,port,NULL,third_party, 0, 0);
+
+
+ return(ok);
+} // End of decode_Mic_E()
+
+
+
+
+void delete_object(char *name) {
+
+ // TODO
+/*
+
+ DataRow *p_station;
+
+//fprintf(stderr,"delete_object\n");
+
+ p_station = NULL;
+ if (search_station_name(&p_station,name,1)) { // find object name
+ p_station->flag &= (~ST_ACTIVE); // clear flag
+ p_station->flag &= (~ST_INVIEW); // clear "In View" flag
+ if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
+ redraw_on_new_data = 2; // redraw now
+ // there is some problem... it is not redrawn immediately! ????
+ // but deleted from list immediatetly
+ redo_list = (int)TRUE; // and update lists
+ }
+ */
+}
+
+
+/*
+ * Decode APRS Information Field and dispatch it depending on the Data Type ID
+ *
+ * call = Callsign or object/item name string
+ * path = Path string
+ * message = Info field (corrupted already if object/item packet)
+ * origin = Originating callsign if object/item, otherwise NULL
+ * from = DATA_VIA_LOCAL/DATA_VIA_TNC/DATA_VIA_NET/DATA_VIA_FILE
+ * port = Port number
+ * third_party = Set to one if third-party packet
+ * orig_message = Unmodified info field
+ *
+ */
+void decode_info_field(gchar *call,
+ gchar *path,
+ gchar *message,
+ gchar *origin,
+ TAprsPort port,
+ gint third_party,
+ gchar *orig_message)
+{
+// char line[MAX_LINE_SIZE+1];
+ int ok_igate_net;
+ int ok_igate_rf;
+ int done, ignore;
+ char data_id;
+ int station_is_mine = 0;
+ int object_is_mine = 0;
+
+ /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
+
+ done = 0; // if 1, packet was decoded
+ ignore = 0; // if 1, don't treat undecoded packets as status text
+ ok_igate_net = 0; // if 1, send packet to internet
+ ok_igate_rf = 0; // if 1, igate packet to RF if "from" is in nws-stations.txt
+
+ if ( is_my_call(call, 1) ) {
+ station_is_mine++; // Station is controlled by me
+ }
+
+ if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) {
+ // Overly long message, throw it away.
+ done = 1;
+ }
+ else if (message == NULL || strlen(message) == 0) {
+ // we could have an empty message
+ (void)data_add(STATION_CALL_DATA,call,path,NULL,port,origin,third_party, station_is_mine, 0);
+ done = 1;
+ // don't report it to internet
+ }
+
+ // special treatment for objects/items.
+ if (!done && origin[0] != '\0') {
+
+ // If station/object/item is owned by me (including SSID)
+ if ( is_my_call(origin, 1) ) {
+ object_is_mine++;
+ }
+
+ if (message[0] == '*') { // set object
+ (void)data_add(APRS_OBJECT,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
+ if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+ ok_igate_net = 1; // report it to internet
+ }
+ ok_igate_rf = 1;
+ done = 1;
+ }
+
+ else if (message[0] == '!') {
+ // set item
+ (void)data_add(APRS_ITEM,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
+ if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+ ok_igate_net = 1; // report it to internet
+ }
+ ok_igate_rf = 1;
+ done = 1;
+ }
+
+ else if (message[0] == '_') { // delete object/item
+// TODO
+/*
+ AprsDataRow *p_station;
+
+ delete_object(call); // ?? does not vanish from map immediately !!???
+
+ // If object was owned by me but another station is
+ // transmitting it now, write entries into the
+ // object.log file showing that we don't own this object
+ // anymore.
+ p_station = NULL;
+
+ if (search_station_name(&p_station,call,1)) {
+ if (is_my_object_item(p_station) // If station was owned by me (including SSID)
+ && (!object_is_mine) ) { // But isn't now
+ disown_object_item(call,origin);
+ }
+ }
+
+ if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+ ok_igate_net = 1; // report it to internet
+ }
+
+ ok_igate_rf = 1;
+*/
+ done = 1;
+
+ }
+ }
+
+ if (!done) {
+ int rdf_type;
+
+ data_id = message[0]; // look at the APRS Data Type ID (first char in information field)
+ message += 1; // extract data ID from information field
+ ok_igate_net = 1; // as default report packet to internet
+
+
+ switch (data_id) {
+ case '=': // Position without timestamp (with APRS messaging)
+
+ //WE7U
+ // Need to check for weather info in this packet type as well?
+
+ done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '!': // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
+ if (message[0] == '!' && is_xnum_or_dash(message+1,40)) // Ultimeter 2000 WX
+ done = data_add(APRS_WX3,call,path,message+1,port,origin,third_party, station_is_mine, 0);
+ else
+ done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '/': // Position with timestamp (no APRS messaging)
+
+//WE7U
+// Need weather decode in this section similar to the '@' section
+// below.
+
+ if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
+ (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
+ if (message[29] == '/') {
+ if (message[33] == 'g' && message[37] == 't')
+ done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else
+ done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else
+ done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else { // compressed format
+ if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
+ if (message[20] == 'g' && message[24] == 't') // Wx data
+ done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else
+ done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else
+ done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+// done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '@': // Position with timestamp (with APRS messaging)
+ // DK7IN: could we need to test the message length first?
+ if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
+ (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
+ if (message[29] == '/') {
+ if (message[33] == 'g' && message[37] == 't')
+ done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else
+ done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else
+ done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else { // compressed format
+ if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
+ if (message[20] == 'g' && message[24] == 't') // Wx data
+ done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else
+ done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ else
+ done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+ }
+ ok_igate_rf = done;
+ break;
+
+ case '[': // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
+ done = data_add(APRS_GRID,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+ case 0x27: // Mic-E Old GPS data (or current GPS data in Kenwood TM-D700)
+ case 0x60: // Mic-E Current GPS data (but not used in Kennwood TM-D700)
+ //case 0x1c:// Mic-E Current GPS data (Rev. 0 beta units only)
+ //case 0x1d:// Mic-E Old GPS data (Rev. 0 beta units only)
+ done = decode_Mic_E(call,path,message,port,third_party);
+ ok_igate_rf = done;
+ break;
+
+ case '_': // Positionless weather data [APRS Reference, chapter 12]
+ done = data_add(APRS_WX2,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '#': // Peet Bros U-II Weather Station (km/h) [APRS Reference, chapter 12]
+ if (is_xnum_or_dash(message,13))
+ done = data_add(APRS_WX4,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '*': // Peet Bros U-II Weather Station (mph)
+ if (is_xnum_or_dash(message,13))
+ done = data_add(APRS_WX6,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '$': // Raw GPS data or Ultimeter 2000
+ if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
+ done = data_add(APRS_WX5,call,path,message+4,port,origin,third_party, station_is_mine, 0);
+ else if (strncmp("GPGGA",message,5) == 0)
+ done = data_add(GPS_GGA,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else if (strncmp("GPRMC",message,5) == 0)
+ done = data_add(GPS_RMC,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else if (strncmp("GPGLL",message,5) == 0)
+ done = data_add(GPS_GLL,call,path,message,port,origin,third_party, station_is_mine, 0);
+ else {
+ // handle VTG and WPT too (APRS Ref p.25)
+ }
+ ok_igate_rf = done;
+ break;
+
+ case ':': // Message
+
+ // Do message logging if that feature is enabled.
+ done = decode_message(call,path,message,port,third_party);
+
+ // there could be messages I should not retransmit to internet... ??? Queries to me...
+ break;
+
+ case '>': // Status [APRS Reference, chapter 16]
+ done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
+ ok_igate_rf = done;
+ break;
+
+ case '?': // Query
+ done = process_query(call,path,message,port,third_party);
+ ignore = 1; // don't treat undecoded packets as status text
+ break;
+
+ case 'T': // Telemetry data [APRS Reference, chapter 13]
+ // We treat these as status packets currently.
+ ok_igate_rf = 1;
+ done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
+ break;
+
+ case '{': // User-defined APRS packet format //}
+ // We treat these as status packets currently.
+ ok_igate_rf = 1;
+ break;
+
+ case '<': // Station capabilities [APRS Reference, chapter 15]
+ //
+ // We could tweak the Incoming Data dialog to add
+ // filter togglebuttons. One such toggle could be
+ // "Station Capabilities". We'd then have a usable
+ // dialog for displaying things like ?IGATE?
+ // responses. In this case we wouldn't have to do
+ // anything special with the packet for decoding,
+ // just let it hit the default block below for
+ // putting them into the status field of the record.
+ // One downside is that we'd only be able to catch
+ // new station capability records in that dialog.
+ // The only way to look at past capability records
+ // would be the Station Info dialog for each
+ // station.
+ //
+ //fprintf(stderr,"%10s: %s\n", call, message);
+
+ // Don't set "done" as we want these to appear in
+ // the status text for the record.
+ break;
+
+ case '%': // Agrelo DFJr / MicroFinder Radio Direction Finding
+
+ // Here is where we'd add a call to an RDF decode
+ // function so that we could display vectors on the
+ // map for each RDF position.
+
+//
+// Agrelo format: "%XXX/Q<cr>"
+//
+// "XXX" is relative bearing to the signal (000-359). Careful here:
+// At least one unit reports in magnetic instead of relative
+// degrees. "000" means no direction info available, 360 means true
+// north.
+//
+// "Q" is bearing quality (0-9). 0 = unsuitable. 9 = manually
+// entered. 1-8 = varying quality with 8 being the best.
+//
+// I've also seen these formats, which may not be Agrelo compatible:
+//
+// "%136.0/9"
+// "%136.0/8/158.0" (That last number is magnetic bearing)
+//
+// These sentences may be sent MULTIPLE times per second, like 20 or
+// more! If we decide to average readings, we'll need to dump our
+// averages and start over if our course changes.
+//
+
+ // Check for Agrelo format:
+ if ( strlen(message) >= 5
+ && is_num_chr(message[0]) // "%136/9"
+ && is_num_chr(message[1])
+ && is_num_chr(message[2])
+ && message[3] == '/'
+ && is_num_chr(message[4]) ) {
+
+ rdf_type = 1;
+
+ fprintf(stderr,
+ "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
+ call,
+ message[0],
+ message[1],
+ message[2],
+ message[4]);
+
+ }
+
+ // Check for extended formats (not
+ // Agrelo-compatible):
+ else if (strlen(message) >= 13
+ && is_num_chr(message[0]) // "%136.0/8/158.0"
+ && is_num_chr(message[1])
+ && is_num_chr(message[2])
+ && message[3] == '.'
+ && is_num_chr(message[4])
+ && message[5] == '/'
+ && is_num_chr(message[6])
+ && message[7] == '/'
+ && is_num_chr(message[8])
+ && is_num_chr(message[9])
+ && is_num_chr(message[10])
+ && message[11] == '.'
+ && is_num_chr(message[12]) ) {
+
+ rdf_type = 3;
+
+ fprintf(stderr,
+ "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
+ call,
+ message[0],
+ message[1],
+ message[2],
+ message[3],
+ message[4],
+ message[6],
+ message[8],
+ message[9],
+ message[10],
+ message[11],
+ message[12]);
+ }
+
+ // Check for extended formats (not
+ // Agrelo-compatible):
+ else if (strlen(message) >= 7
+ && is_num_chr(message[0]) // "%136.0/9"
+ && is_num_chr(message[1])
+ && is_num_chr(message[2])
+ && message[3] == '.'
+ && is_num_chr(message[4])
+ && message[5] == '/'
+ && is_num_chr(message[6]) ) {
+
+ rdf_type = 2;
+
+ fprintf(stderr,
+ "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
+ call,
+ message[0],
+ message[1],
+ message[2],
+ message[3],
+ message[4],
+ message[6]);
+ }
+
+ // Don't set "done" as we want these to appear in
+ // the status text for the record until we get the
+ // full decoding for this type of packet coded up.
+ break;
+
+ case '~': // UI-format messages, not relevant for APRS ("Do not use" in Reference)
+ case ',': // Invalid data or test packets [APRS Reference, chapter 19]
+ case '&': // Reserved -- Map Feature
+ ignore = 1; // Don't treat undecoded packets as status text
+ break;
+ }
+
+
+ // Add most remaining data to the station record as status
+ // info
+ //
+ if (!done && !ignore) { // Other Packets [APRS Reference, chapter 19]
+ done = data_add(OTHER_DATA,call,path,message-1,port,origin,third_party, station_is_mine, 0);
+ ok_igate_net = 0; // don't put data on internet ????
+ }
+
+ if (!done) { // data that we do ignore...
+ //fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
+ ok_igate_net = 0; // don't put data on internet
+ }
+ }
+
+ if (third_party)
+ ok_igate_net = 0; // don't put third party traffic on internet
+
+
+ if (station_is_mine)
+ ok_igate_net = 0; // don't put my data on internet ???
+
+
+ // TODO - Add TX to IGate support
+/*
+ if (ok_igate_net) {
+
+ if ( (from == DATA_VIA_TNC) // Came in via a TNC
+ && (strlen(orig_message) > 0) ) { // Not empty
+
+ // Here's where we inject our own callsign like this:
+ // "WE7U-15,I" in order to provide injection ID for our
+ // igate.
+ snprintf(line,
+ sizeof(line),
+ "%s>%s,%s,I:%s",
+ (strlen(origin)) ? origin : call,
+ path,
+ _aprs_mycall,
+ orig_message);
+
+ output_igate_net(line, port, third_party);
+ }
+ }
+*/
+}
+
+
+
+/*
+ * Check for a valid object name
+ */
+int valid_object(char *name) {
+ int len, i;
+
+ // max 9 printable ASCII characters, case sensitive [APRS
+ // Reference]
+ len = (int)strlen(name);
+ if (len > 9 || len == 0)
+ return(0); // wrong size
+
+ for (i=0;i<len;i++)
+ if (!isprint((int)name[i]))
+ return(0); // not printable
+
+ return(1);
+}
+
+
+
+
+
+
+/*
+ * Extract object or item data from information field before processing
+ *
+ * Returns 1 if valid object found, else returns 0.
+ *
+ */
+int extract_object(char *call, char **info, char *origin)
+{
+ int ok, i;
+
+ // Object and Item Reports [APRS Reference, chapter 11]
+ ok = 0;
+ // todo: add station originator to database
+ if ((*info)[0] == ';') { // object
+ // fixed 9 character object name with any printable ASCII character
+ if (strlen((*info)) > 1+9) {
+ substr(call,(*info)+1,9); // extract object name
+ (*info) = (*info) + 10;
+ // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
+ (void)remove_trailing_spaces(call);
+ if (valid_object(call)) {
+ // info length is at least 1
+ ok = 1;
+ }
+ }
+ }
+ else if ((*info)[0] == ')') { // item
+ // 3 - 9 character item name with any printable ASCII character
+ if (strlen((*info)) > 1+3) {
+ for (i = 1; i <= 9; i++) {
+ if ((*info)[i] == '!' || (*info)[i] == '_') {
+ call[i-1] = '\0';
+ break;
+ }
+ call[i-1] = (*info)[i];
+ }
+ call[9] = '\0'; // In case we never saw '!' || '_'
+ (*info) = &(*info)[i];
+ // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
+ //(void)remove_trailing_spaces(call); // This statement messed up our searching!!! Don't use it!
+ if (valid_object(call)) {
+ // info length is at least 1
+ ok = 1;
+ }
+ }
+ }
+ else
+ {
+ fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
+ call, *info, origin);
+ }
+ return(ok);
+}
+
+
+/*
+ * Decode AX.25 line
+ * \r and \n should already be stripped from end of line
+ * line should not be NULL
+ *
+ * If dbadd is set, add to database. Otherwise, just return true/false
+ * to indicate whether input is valid AX25 line.
+ */
+//
+// Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
+// which is currently set to 4096.
+//
+gint decode_ax25_line(gchar *line, TAprsPort port) {
+ gchar *call_sign;
+ gchar *path0;
+ gchar path[100+1]; // new one, we may add an '*'
+ gchar *info;
+ gchar info_copy[MAX_LINE_SIZE+1];
+ gchar call[MAX_CALLSIGN+1];
+ gchar origin[MAX_CALLSIGN+1];
+ gint ok;
+ gint third_party;
+// gchar backup[MAX_LINE_SIZE+1];
+// gchar tmp_line[MAX_LINE_SIZE+1];
+// gchar tmp_path[100+1];
+// gchar *ViaCalls[10];
+
+ if (line == NULL)
+ {
+ return(FALSE);
+ }
+
+ if ( strlen(line) > MAX_LINE_SIZE )
+ {
+ // Overly long message, throw it away. We're done.
+ return(FALSE);
+ }
+
+ if (line[strlen(line)-1] == '\n') // better: look at other places,
+ // so that we don't get it here...
+ line[strlen(line)-1] = '\0'; // Wipe out '\n', to be sure
+ if (line[strlen(line)-1] == '\r')
+ line[strlen(line)-1] = '\0'; // Wipe out '\r'
+
+ call_sign = NULL;
+ path0 = NULL;
+ info = NULL;
+ origin[0] = '\0';
+ call[0] = '\0';
+ path[0] = '\0';
+ third_party = 0;
+
+ // CALL>PATH:APRS-INFO-FIELD // split line into components
+ // ^ ^
+ ok = 0;
+ call_sign = (gchar*)strtok(line,">"); // extract call from AX.25 line
+ if (call_sign != NULL) {
+ path0 = (gchar*)strtok(NULL,":"); // extract path from AX.25 line
+ if (path0 != NULL) {
+ info = (gchar*)strtok(NULL,""); // rest is info_field
+ if (info != NULL) {
+ if ((info - path0) < 100) { // check if path could be copied
+ ok = 1; // we have found all three components
+ }
+ }
+ }
+ }
+
+ if (ok)
+ {
+ snprintf(path, sizeof(path), "%s", path0);
+
+ snprintf(info_copy, sizeof(info_copy), "%s", info);
+
+ ok = valid_path(path); // check the path and convert it to TAPR format
+ // Note that valid_path() also removes igate injection identifiers
+ }
+
+ if (ok)
+ {
+ extract_TNC_text(info); // extract leading text from TNC X-1J4
+ if (strlen(info) > 256) // first check if information field conforms to APRS specs
+ ok = 0; // drop packets too long
+ }
+
+ if (ok)
+ { // check callsign
+ (void)remove_trailing_asterisk(call_sign); // is an asterisk valid here ???
+
+ if (valid_inet_name(call_sign,info,origin,sizeof(origin)))
+ { // accept some of the names used in internet
+ snprintf(call, sizeof(call), "%s", call_sign);
+ }
+ else if (valid_call(call_sign))
+ { // accept real AX.25 calls
+ snprintf(call, sizeof(call), "%s", call_sign);
+ }
+ else {
+ ok = 0;
+ }
+ }
+
+ if (ok && info[0] == '}')
+ {
+ // look for third-party traffic
+ ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
+ third_party = 1;
+ }
+
+ if (ok && (info[0] == ';' || info[0] == ')'))
+ {
+ // look for objects or items
+ snprintf(origin, sizeof(origin), "%s", call);
+
+ ok = extract_object(call,&info,origin); // extract object data
+ }
+
+ if (ok)
+ {
+ // decode APRS information field, always called with valid call and path
+ // info is a string with 0 - 256 bytes
+ // fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);
+
+ decode_info_field(call,
+ path,
+ info,
+ origin,
+ port,
+ third_party,
+ info_copy);
+ }
+
+
+ return(ok);
+}
+
+
+
+
+
+///////////////////
+double convert_lon_l2d(long lon)
+{
+ double dResult = 0.00;
+
+ int ewpn;
+ float deg, min; //, sec;
+ int ideg; //, imin;
+ long temp;
+
+ deg = (float)(lon - 64800000l) / 360000.0;
+
+ // Switch to integer arithmetic to avoid floating-point rounding
+ // errors.
+ temp = (long)(deg * 100000);
+
+
+ ewpn = 1;
+ if (temp <= 0)
+ {
+ ewpn = -1;
+ temp = labs(temp);
+ }
+
+ ideg = (int)temp / 100000;
+ min = (temp % 100000) * 60.0 / 100000.0;
+
+
+ dResult = ewpn*(ideg+min/60.0);
+
+ return dResult;
+}
+
+
+
+
+// convert latitude from long to double
+// Input is in Xastir coordinate system
+
+double convert_lat_l2d(long lat)
+{
+ double dResult = 0.00;
+
+ int nspn;
+ float deg, min;
+ int ideg;
+ long temp;
+
+ deg = (float)(lat - 32400000l) / 360000.0;
+
+ // Switch to integer arithmetic to avoid floating-point
+ // rounding errors.
+ temp = (long)(deg * 100000);
+
+ nspn = -1;
+ if (temp <= 0) {
+ nspn = 1;
+ temp = labs(temp);
+ }
+
+ ideg = (int)temp / 100000;
+ min = (temp % 100000) * 60.0 / 100000.0;
+
+ dResult = nspn*(ideg+min/60.0);
+
+ return dResult;
+}
+
+
+
+/***********************************************************/
+/* returns the hour (00..23), localtime */
+/***********************************************************/
+int get_hours(void) {
+ struct tm *time_now;
+ time_t secs_now;
+ char shour[5];
+
+ secs_now=sec_now();
+ time_now = localtime(&secs_now);
+ (void)strftime(shour,4,"%H",time_now);
+ return(atoi(shour));
+}
+
+
+
+
+/***********************************************************/
+/* returns the minute (00..59), localtime */
+/***********************************************************/
+int get_minutes(void) {
+ struct tm *time_now;
+ time_t secs_now;
+ char sminute[5];
+
+ secs_now=sec_now();
+ time_now = localtime(&secs_now);
+ (void)strftime(sminute,4,"%M",time_now);
+ return(atoi(sminute));
+}
+
+
+
+
+/**************************************************************/
+/* compute_rain_hour - rolling average for the last 59.x */
+/* minutes of rain. I/O numbers are in hundredths of an inch.*/
+/* Output is in "rain_minute_total", a global variable. */
+/**************************************************************/
+void compute_rain_hour(float rain_total) {
+ int current, j;
+ float lowest;
+
+
+ // Deposit the _starting_ rain_total for each minute into a separate bucket.
+ // Subtract lowest rain_total in queue from current rain_total to get total
+ // for the last hour.
+
+
+ current = get_minutes(); // Fetch the current minute value. Use this as an index
+ // into our minute "buckets" where we store the data.
+
+
+ rain_minute[ (current + 1) % 60 ] = 0.0; // Zero out the next bucket (probably have data in
+ // there from the previous hour).
+
+
+ if (rain_minute[current] == 0.0) // If no rain_total stored yet in this minute's bucket
+ rain_minute[current] = rain_total; // Write into current bucket
+
+
+ // Find the lowest non-zero value for rain_total. The max value is "rain_total".
+ lowest = rain_total; // Set to maximum to get things going
+ for (j = 0; j < 60; j++) {
+ if ( (rain_minute[j] > 0.0) && (rain_minute[j] < lowest) ) { // Found a lower non-zero value?
+ lowest = rain_minute[j]; // Snag it
+ }
+ }
+ // Found it, subtract the two to get total for the last hour
+ rain_minute_total = rain_total - lowest;
+}
+
+
+
+
+/***********************************************************/
+/* compute_rain - compute rain totals from the total rain */
+/* so far. rain_total (in hundredths of an inch) keeps on */
+/* incrementing. */
+/***********************************************************/
+void compute_rain(float rain_total) {
+ int current, i;
+ float lower;
+
+
+ // Skip the routine if input is outlandish (Negative value, zero, or 512 inches!).
+ // We seem to get occasional 0.0 packets from wx200d. This makes them go away.
+ if ( (rain_total <= 0.0) || (rain_total > 51200.0) )
+ return;
+
+ compute_rain_hour(rain_total);
+
+ current = get_hours();
+
+ // Set rain_base: The first rain_total for each hour.
+ if (rain_base[current] == 0.0) { // If we don't have a start value yet for this hour,
+ rain_base[current] = rain_total; // save it away.
+ rain_check = 0; // Set up rain_check so we'll do the following
+ // "else" clause a few times at start of each hour.
+ }
+ else { // rain_base has been set, is it wrong? We recheck three times at start of hour.
+ if (rain_check < 3) {
+ rain_check++;
+ // Is rain less than base? It shouldn't be.
+ if (rain_total < rain_base[current])
+ rain_base[current] = rain_total;
+
+ // Difference greater than 10 inches since last reading? It shouldn't be.
+ if (fabs(rain_total - rain_base[current]) > 1000.0) // Remember: Hundredths of an inch
+ rain_base[current] = rain_total;
+ }
+ }
+ rain_base[ (current + 1) % 24 ] = 0.0; // Clear next hour's index.
+
+
+ // Compute total rain in last 24 hours: Really we'll compute the total rain
+ // in the last 23 hours plus the portion of an hour we've completed (Sum up
+ // all 24 of the hour totals). This isn't the perfect way to do it, but to
+ // really do it right we'd need finer increments of time (to get closer to
+ // the goal of 24 hours of rain).
+ lower = rain_total;
+ for ( i = 0; i < 24; i++ ) { // Find the lowest non-zero rain_base value in last 24 hours
+ if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
+ lower = rain_base[i];
+ }
+ }
+ rain_24 = rain_total - lower; // Hundredths of an inch
+
+
+ // Compute rain since midnight. Note that this uses whatever local time was set
+ // on the machine. It might not be local midnight if your box is set to GMT.
+ lower = rain_total;
+ for ( i = 0; i <= current; i++ ) { // Find the lowest non-zero rain_base value since midnight
+ if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
+ lower = rain_base[i];
+ }
+ }
+ rain_00 = rain_total - lower; // Hundredths of an inch
+
+ // It is the responsibility of the calling program to save
+ // the new totals in the data structure for our station.
+ // We don't return anything except in global variables.
+
+}
+
+
+
+
+/**************************************************************/
+/* compute_gust - compute max wind gust during last 5 minutes */
+/* */
+/**************************************************************/
+float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
+ float computed_gust;
+ int current, j;
+
+
+ // Deposit max gust for each minute into a different bucket.
+ // Check all buckets for max gust within the last five minutes
+ // (Really 4 minutes plus whatever portion of a minute we've completed).
+
+ current = get_minutes(); // Fetch the current minute value. We use this as an index
+ // into our minute "buckets" where we store the data.
+
+ // If we haven't started collecting yet, set up to do so
+ if (gust_read_ptr == gust_write_ptr) { // We haven't started yet
+ gust_write_ptr = current; // Set to write into current bucket
+ gust_last_write = current;
+
+ gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
+ if (gust_read_ptr < 0)
+ gust_read_ptr = 59;
+
+ gust[gust_write_ptr] = 0.0; // Zero the max gust
+ gust[gust_read_ptr] = 0.0; // for both buckets.
+
+//WE7U: Debug
+//gust[gust_write_ptr] = 45.9;
+ }
+
+ // Check whether we've advanced at least one minute yet
+ if (current != gust_write_ptr) { // We've advanced to a different minute
+ gust_write_ptr = current; // Start writing into a new bucket.
+ gust[gust_write_ptr] = 0.0; // Zero the new bucket
+
+ // Check how many bins of real data we have currently. Note that this '5' is
+ // correct, as we just advanced "current" to the next minute. We're just pulling
+ // along the read_ptr behind us if we have 5 bins worth of data by now.
+ if ( ((gust_read_ptr + 5) % 60) == gust_write_ptr) // We have 5 bins of real data
+ gust_read_ptr = (gust_read_ptr + 1) % 60; // So advance the read pointer,
+
+ // Check for really bad pointers, perhaps the weather station got
+ // unplugged for a while or it's just REALLY slow at sending us data?
+ // We're checking to see if gust_last_write happened in the previous
+ // minute. If not, we skipped a minute or more somewhere.
+ if ( ((gust_last_write + 1) % 60) != current ) {
+ // We lost some time somewhere: Reset the pointers, older gust data is
+ // lost. Start over collecting new gust data.
+
+ gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
+ if (gust_read_ptr < 0)
+ gust_read_ptr = 59;
+
+ gust[gust_read_ptr] = 0.0;
+ }
+ gust_last_write = current;
+ }
+
+ // Is current wind speed higher than the current minute bucket?
+ if (wx_speed > gust[gust_write_ptr])
+ gust[gust_write_ptr] = wx_speed; // Save it in the bucket
+
+ // Read the last (up to) five buckets and find the max gust
+ computed_gust=gust[gust_write_ptr];
+ j = gust_read_ptr;
+ while (j != ((gust_write_ptr + 1) % 60) ) {
+ if ( computed_gust < gust[j] )
+ computed_gust = gust[j];
+ j = (j + 1) % 60;
+ }
+
+ *last_speed_time = sec_now();
+ return(computed_gust);
+}
+
+
+
+
+
+//*********************************************************************
+// Decode Peet Brothers Ultimeter 2000 weather data (Data logging mode)
+//
+// This function is called from db.c:data_add() only. Used for
+// decoding incoming packets, not for our own weather station data.
+//
+// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
+// Packet Mode, or Complete Record Mode. This routine handles only
+// the Data Logging Mode.
+//*********************************************************************
+
+void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
+ time_t last_speed_time;
+ float last_speed;
+ float computed_gust;
+ char temp_data1[10];
+ char *temp_conv;
+ char format;
+
+ last_speed = 0.0;
+ last_speed_time = 0;
+ computed_gust = 0.0;
+ format = 0;
+
+ weather->wx_type = WX_TYPE;
+ xastir_snprintf(weather->wx_station,
+ sizeof(weather->wx_station),
+ "U2k");
+
+ /* get last gust speed */
+ if (strlen(weather->wx_gust) > 0 && !from) {
+ /* get last speed */
+ last_speed = (float)atof(weather->wx_gust);
+ last_speed_time = weather->wx_speed_sec_time;
+ }
+
+ // 006B 00 58
+ // 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7 0001 007BM
+ // ^ ^ ^ ^ ^ ^ ^
+ // 0 6 8 12 16 24 40
+ /* wind speed */
+ if (data[0] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)data,4);
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+ if (from) {
+ weather->wx_speed_sec_time = sec_now();
+ } else {
+ /* local station */
+ computed_gust = compute_gust((float)atof(weather->wx_speed),
+ last_speed,
+ &last_speed_time);
+ weather->wx_speed_sec_time = sec_now();
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%03d",
+ (int)(0.5 + computed_gust)); // Cheater's way of rounding
+ }
+ } else {
+ if (!from)
+ weather->wx_speed[0] = 0;
+ }
+
+ /* wind direction */
+ //
+ // Note that the first two digits here may be 00, or may be FF
+ // if a direction calibration has been entered. We should zero
+ // them.
+ //
+ if (data[6] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+6),2);
+ temp_data1[0] = '0';
+ temp_data1[1] = '0';
+ xastir_snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "%03d",
+ (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
+ } else {
+ xastir_snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "000");
+ if (!from)
+ weather->wx_course[0]=0;
+ }
+
+ /* outdoor temp */
+ if (data[8] != '-') { // '-' signifies invalid data
+ int temp4;
+
+ substr(temp_data1,(char *)(data+8),4);
+ temp4 = (int)strtol(temp_data1,&temp_conv,16);
+
+ if (temp_data1[0] > '7') { // Negative value, convert
+ temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+ }
+
+ xastir_snprintf(weather->wx_temp,
+ sizeof(weather->wx_temp),
+ "%03d",
+ (int)((float)((temp4<<16)/65536)/10.0));
+
+ }
+ else {
+ if (!from)
+ weather->wx_temp[0]=0;
+ }
+
+ /* rain total long term */
+ if (data[12] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+12),4);
+ xastir_snprintf(weather->wx_rain_total,
+ sizeof(weather->wx_rain_total),
+ "%0.2f",
+ (float)strtol(temp_data1,&temp_conv,16)/100.0);
+ if (!from) {
+ /* local station */
+ compute_rain((float)atof(weather->wx_rain_total));
+ /*last hour rain */
+ xastir_snprintf(weather->wx_rain,
+ sizeof(weather->wx_rain),
+ "%0.2f",
+ rain_minute_total);
+ /*last 24 hour rain */
+ xastir_snprintf(weather->wx_prec_24,
+ sizeof(weather->wx_prec_24),
+ "%0.2f",
+ rain_24);
+ /* rain since midnight */
+ xastir_snprintf(weather->wx_prec_00,
+ sizeof(weather->wx_prec_00),
+ "%0.2f",
+ rain_00);
+ }
+ } else {
+ if (!from)
+ weather->wx_rain_total[0]=0;
+ }
+
+ /* baro */
+ if (data[16] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+16),4);
+ xastir_snprintf(weather->wx_baro,
+ sizeof(weather->wx_baro),
+ "%0.1f",
+ (float)strtol(temp_data1,&temp_conv,16)/10.0);
+ } else {
+ if (!from)
+ weather->wx_baro[0]=0;
+ }
+
+
+ /* outdoor humidity */
+ if (data[24] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+24),4);
+ xastir_snprintf(weather->wx_hum,
+ sizeof(weather->wx_hum),
+ "%03d",
+ (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
+ } else {
+ if (!from)
+ weather->wx_hum[0]=0;
+ }
+
+ /* todays rain total */
+ if (data[40] != '-') { // '-' signifies invalid data
+ if (from) {
+ substr(temp_data1,(char *)(data+40),4);
+ xastir_snprintf(weather->wx_prec_00,
+ sizeof(weather->wx_prec_00),
+ "%0.2f",
+ (float)strtol(temp_data1,&temp_conv,16)/100.0);
+ }
+ } else {
+ if (!from)
+ weather->wx_prec_00[0] = 0;
+ }
+}
+
+
+
+
+
+//********************************************************************
+// Decode Peet Brothers Ultimeter 2000 weather data (Packet mode)
+//
+// This function is called from db.c:data_add() only. Used for
+// decoding incoming packets, not for our own weather station data.
+//
+// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
+// Packet Mode, or Complete Record Mode. This routine handles only
+// the Packet Mode.
+//********************************************************************
+void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
+ time_t last_speed_time;
+ float last_speed;
+ float computed_gust;
+ char temp_data1[10];
+ char *temp_conv;
+ int len;
+
+ last_speed = 0.0;
+ last_speed_time = 0;
+ computed_gust = 0.0;
+ len = (int)strlen((char *)data);
+
+ weather->wx_type = WX_TYPE;
+ xastir_snprintf(weather->wx_station,
+ sizeof(weather->wx_station),
+ "U2k");
+
+ /* get last gust speed */
+ if (strlen(weather->wx_gust) > 0 && !from) {
+ /* get last speed */
+ last_speed = (float)atof(weather->wx_gust);
+ last_speed_time = weather->wx_speed_sec_time;
+ }
+
+ // $ULTW 0031 00 37 02CE 0069 ---- 0000 86A0 0001 ---- 011901CC 0000 0005
+ // ^ ^ ^ ^ ^ ^ ^ ^
+ // 0 6 8 12 16 32 44 48
+
+ /* wind speed peak over last 5 min */
+ if (data[0] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)data,4);
+ if (from) {
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%03d",
+ (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+ /* this may be the only wind data */
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+ } else {
+ /* local station and may be the only wind data */
+ if (len < 51) {
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+ computed_gust = compute_gust((float)atof(weather->wx_speed),
+ last_speed,
+ &last_speed_time);
+ weather->wx_speed_sec_time = sec_now();
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%03d",
+ (int)(0.5 + computed_gust));
+ }
+ }
+ } else {
+ if (!from)
+ weather->wx_gust[0] = 0;
+ }
+
+ /* wind direction */
+ //
+ // Note that the first two digits here may be 00, or may be FF
+ // if a direction calibration has been entered. We should zero
+ // them.
+ //
+ if (data[6] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+6),2);
+ temp_data1[0] = '0';
+ temp_data1[1] = '0';
+ xastir_snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "%03d",
+ (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
+ } else {
+ xastir_snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "000");
+ if (!from)
+ weather->wx_course[0] = 0;
+ }
+
+ /* outdoor temp */
+ if (data[8] != '-') { // '-' signifies invalid data
+ int temp4;
+
+ substr(temp_data1,(char *)(data+8),4);
+ temp4 = (int)strtol(temp_data1,&temp_conv,16);
+
+ if (temp_data1[0] > '7') { // Negative value, convert
+ temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+ }
+
+ xastir_snprintf(weather->wx_temp,
+ sizeof(weather->wx_temp),
+ "%03d",
+ (int)((float)((temp4<<16)/65536)/10.0));
+ }
+ else {
+ if (!from)
+ weather->wx_temp[0] = 0;
+ }
+ /* todays rain total (on some units) */
+ if ((data[44]) != '-') { // '-' signifies invalid data
+ if (from) {
+ substr(temp_data1,(char *)(data+44),4);
+ xastir_snprintf(weather->wx_prec_00,
+ sizeof(weather->wx_prec_00),
+ "%0.2f",
+ (float)strtol(temp_data1,&temp_conv,16)/100.0);
+ }
+ } else {
+ if (!from)
+ weather->wx_prec_00[0] = 0;
+ }
+
+ /* rain total long term */
+ if (data[12] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+12),4);
+ xastir_snprintf(weather->wx_rain_total,
+ sizeof(weather->wx_rain_total),
+ "%0.2f",
+ (float)strtol(temp_data1,&temp_conv,16)/100.0);
+ if (!from) {
+ /* local station */
+ compute_rain((float)atof(weather->wx_rain_total));
+ /*last hour rain */
+ snprintf(weather->wx_rain,
+ sizeof(weather->wx_rain),
+ "%0.2f",
+ rain_minute_total);
+ /*last 24 hour rain */
+ snprintf(weather->wx_prec_24,
+ sizeof(weather->wx_prec_24),
+ "%0.2f",
+ rain_24);
+ /* rain since midnight */
+ snprintf(weather->wx_prec_00,
+ sizeof(weather->wx_prec_00),
+ "%0.2f",
+ rain_00);
+ }
+ } else {
+ if (!from)
+ weather->wx_rain_total[0] = 0;
+ }
+
+ /* baro */
+ if (data[16] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+16),4);
+ xastir_snprintf(weather->wx_baro,
+ sizeof(weather->wx_baro),
+ "%0.1f",
+ (float)strtol(temp_data1,&temp_conv,16)/10.0);
+ } else {
+ if (!from)
+ weather->wx_baro[0] = 0;
+ }
+
+ /* outdoor humidity */
+ if (data[32] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+32),4);
+ xastir_snprintf(weather->wx_hum,
+ sizeof(weather->wx_hum),
+ "%03d",
+ (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
+ } else {
+ if (!from)
+ weather->wx_hum[0] = 0;
+ }
+
+ /* 1 min wind speed avg */
+ if (len > 48 && (data[48]) != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+48),4);
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+ if (from) {
+ weather->wx_speed_sec_time = sec_now();
+ } else {
+ /* local station */
+ computed_gust = compute_gust((float)atof(weather->wx_speed),
+ last_speed,
+ &last_speed_time);
+ weather->wx_speed_sec_time = sec_now();
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%03d",
+ (int)(0.5 + computed_gust));
+ }
+ } else {
+ if (!from) {
+ if (len > 48)
+ weather->wx_speed[0] = 0;
+ }
+ }
+}
+
+
+
+
+
+//*****************************************************************
+// Decode Peet Brothers Ultimeter-II weather data
+//
+// This function is called from db.c:data_add() only. Used for
+// decoding incoming packets, not for our own weather station data.
+//*****************************************************************
+void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type) {
+ time_t last_speed_time;
+ float last_speed;
+ float computed_gust;
+ char temp_data1[10];
+ char *temp_conv;
+
+ last_speed = 0.0;
+ computed_gust = 0.0;
+ last_speed_time = 0;
+
+ weather->wx_type = WX_TYPE;
+ xastir_snprintf(weather->wx_station,
+ sizeof(weather->wx_station),
+ "UII");
+
+ // '*' = MPH
+ // '#' = km/h
+ //
+ // # 5 0B 75 0082 0082
+ // * 7 00 76 0000 0000
+ // ^ ^ ^ ^
+ // rain [1/100 inch ?]
+ // outdoor temp
+ // wind speed [mph / km/h]
+ // wind dir
+
+ /* wind direction */
+ //
+ // 0x00 is N
+ // 0x04 is E
+ // 0x08 is S
+ // 0x0C is W
+ //
+ substr(temp_data1,(char *)data,1);
+ snprintf(weather->wx_course,
+ sizeof(weather->wx_course),
+ "%03d",
+ (int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));
+
+ /* get last gust speed */
+ if (strlen(weather->wx_gust) > 0 && !from) {
+ /* get last speed */
+ last_speed = (float)atof(weather->wx_gust);
+ last_speed_time = weather->wx_speed_sec_time;
+ }
+
+ /* wind speed */
+ substr(temp_data1,(char *)(data+1),2);
+ if (type == APRS_WX4) { // '#' speed in km/h, convert to mph
+ snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + (float)(strtol(temp_data1,&temp_conv,16)*0.62137)));
+ } else { // type == APRS_WX6, '*' speed in mph
+ xastir_snprintf(weather->wx_speed,
+ sizeof(weather->wx_speed),
+ "%03d",
+ (int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
+ }
+
+ if (from) {
+ weather->wx_speed_sec_time = sec_now();
+ } else {
+ /* local station */
+ computed_gust = compute_gust((float)atof(weather->wx_speed),
+ last_speed,
+ &last_speed_time);
+ weather->wx_speed_sec_time = sec_now();
+ xastir_snprintf(weather->wx_gust,
+ sizeof(weather->wx_gust),
+ "%03d",
+ (int)(0.5 + computed_gust));
+ }
+
+ /* outdoor temp */
+ if (data[3] != '-') { // '-' signifies invalid data
+ int temp4;
+
+ substr(temp_data1,(char *)(data+3),2);
+ temp4 = (int)strtol(temp_data1,&temp_conv,16);
+
+ if (temp_data1[0] > '7') { // Negative value, convert
+ temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+ }
+
+ xastir_snprintf(weather->wx_temp,
+ sizeof(weather->wx_temp),
+ "%03d",
+ temp4-56);
+ } else {
+ if (!from)
+ weather->wx_temp[0] = 0;
+ }
+
+ // Rain divided by 100 for readings in hundredth of an inch
+ if (data[5] != '-') { // '-' signifies invalid data
+ substr(temp_data1,(char *)(data+5),4);
+ xastir_snprintf(weather->wx_rain_total,
+ sizeof(weather->wx_rain_total),
+ "%0.2f",
+ (float)strtol(temp_data1,&temp_conv,16)/100.0);
+ if (!from) {
+ /* local station */
+ compute_rain((float)atof(weather->wx_rain_total));
+ /*last hour rain */
+ xastir_snprintf(weather->wx_rain,
+ sizeof(weather->wx_rain),
+ "%0.2f",
+ rain_minute_total);
+ /*last 24 hour rain */
+ xastir_snprintf(weather->wx_prec_24,
+ sizeof(weather->wx_prec_24),
+ "%0.2f",
+ rain_24);
+ /* rain since midnight */
+ xastir_snprintf(weather->wx_prec_00,
+ sizeof(weather->wx_prec_00),
+ "%0.2f",
+ rain_00);
+ }
+ } else {
+ if (!from)
+ weather->wx_rain_total[0] = 0;
+ }
+}
+
+
+#endif //INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_DECODE_H
+#define MAEMO_MAPPER_APRS_DECODE_H
+
+#define xastir_snprintf snprintf
+
+#include "types.h"
+
+
+
+void substr(char *dest, char *src, int size);
+gint decode_ax25_line(gchar *line, TAprsPort port);
+
+
+int extract_position(AprsDataRow *p_station, char **info, int type);
+time_t sec_now(void);
+void insert_name(AprsDataRow *p_new, AprsDataRow *p_name);
+double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2);
+void init_station_data(void);
+void init_station(AprsDataRow *p_station);
+char *get_tactical_from_hash(char *callsign);
+
+double convert_lat_l2d(long lat);
+double convert_lon_l2d(long lon);
+
+#endif
+
+#endif //INCLUDE_APRS
+
--- /dev/null
+/*
+ * Created by Rob Williams - 10 Aug 2008
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_display.h"
+#include "aprs_message.h"
+#include "types.h"
+#include "aprs.h"
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <dbus/dbus-glib.h>
+#include <bt-dbus.h>
+#include <gconf/gconf-client.h>
+
+#ifndef LEGACY
+# include <hildon/hildon-help.h>
+# include <hildon/hildon-note.h>
+# include <hildon/hildon-color-button.h>
+# include <hildon/hildon-file-chooser-dialog.h>
+# include <hildon/hildon-number-editor.h>
+# include <hildon/hildon-banner.h>
+#else
+# include <osso-helplib.h>
+# include <hildon-widgets/hildon-note.h>
+# include <hildon-widgets/hildon-color-button.h>
+# include <hildon-widgets/hildon-file-chooser-dialog.h>
+# include <hildon-widgets/hildon-number-editor.h>
+# include <hildon-widgets/hildon-banner.h>
+# include <hildon-widgets/hildon-input-mode-hint.h>
+#endif
+
+#include "types.h"
+#include "data.h"
+#include "defines.h"
+
+#include "gps.h"
+#include "display.h"
+#include "gdk-pixbuf-rotate.h"
+#include "maps.h"
+#include "marshal.h"
+#include "poi.h"
+#include "settings.h"
+#include "util.h"
+
+extern AprsDataRow *n_first;
+
+typedef struct _AprsStationSelectInfo AprsStationSelectInfo;
+struct _AprsStationSelectInfo
+{
+ GtkWidget *dialog;
+ GtkWidget *tree_view;
+ gint column_index;
+ gchar *call_sign;
+};
+
+static AprsStationSelectInfo selected_station;
+
+
+
+double convert_lat_l2d(long lat);
+double convert_lon_l2d(long lon);
+static gboolean panto_station(GtkWidget *widget, AprsStationSelectInfo *aprs_station_sel);
+
+void convert_temp_f_to_c(gchar * f, gchar ** c)
+{
+ *c = g_strdup(" ");
+
+ gdouble df = 0.0;
+ gdouble dc = 0.0;
+
+ // Convert fahrenheit to fahrenheit (double)
+ df = g_ascii_strtod ( f, (gchar*)(f + strlen(f)));
+
+ // Convert ff to fc
+ dc = 5*((df - 32.0)/9);
+
+ // Convert fc to celsius
+ snprintf(*c, 8, "%0.1f°C", dc);
+}
+
+void setup_aprs_basic_wx_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(4/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+ label = gtk_label_new(_("WX")));
+
+ /* Last update */
+ gchar last_update_time[26];
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Updated:"),
+ 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+
+ if(p_station->weather_data && p_station->weather_data->wx_sec_time)
+ {
+ strftime(last_update_time, 25, "%x %X", localtime(&p_station->weather_data->wx_sec_time));
+ }
+ else
+ {
+ snprintf(last_update_time, 25, " ");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( last_update_time ),
+ 1, 4, 0, 1, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+ /* Temperature */
+ gchar * temp = NULL;
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Temp:"),
+ 0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ if(p_station->weather_data && p_station->weather_data->wx_temp)
+ {
+ convert_temp_f_to_c(p_station->weather_data->wx_temp, &temp);
+ }
+ else
+ {
+ temp = g_strdup("");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( temp ),
+ 1, 2, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ g_free(temp);
+
+ /////////////////
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( (p_station->weather_data->wx_storm ? "SEVERE STORM" : "") ),
+ 2, 4, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+
+
+ /////
+ gchar course[7];
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( "Wind course:" ),
+ 0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ snprintf(course, 6, "%0.f°",
+ g_ascii_strtod (p_station->weather_data->wx_course, p_station->weather_data->wx_course + strlen(p_station->weather_data->wx_course))
+ );
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( course ),
+ 1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+// g_free(course);
+
+ /////
+ gchar speed[15];
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( "Wind speed:" ),
+ 2, 3, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ snprintf(speed, 14, "%0.f->%0.f MPH",
+ g_ascii_strtod (p_station->weather_data->wx_speed, p_station->weather_data->wx_speed + strlen(p_station->weather_data->wx_speed)),
+ g_ascii_strtod (p_station->weather_data->wx_gust, p_station->weather_data->wx_gust + strlen(p_station->weather_data->wx_gust))
+ );
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( speed ),
+ 3, 4, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+
+
+ /////
+ gchar rain_ph[17];
+ gchar rain_total[17];
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( "Rain fall:" ),
+ 0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ if(p_station->weather_data->wx_rain)
+ {
+ snprintf(rain_ph, 16, "%0.f \"/hr",
+ g_ascii_strtod (p_station->weather_data->wx_rain, p_station->weather_data->wx_rain + strlen(p_station->weather_data->wx_rain))
+ );
+ }
+ else
+ {
+ snprintf(rain_ph, 1, " ");
+ }
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( rain_ph ),
+ 1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( "Total:" ),
+ 2, 3, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ if(p_station->weather_data->wx_rain_total)
+ {
+ snprintf(rain_total, 16, "%0.f \"",
+ g_ascii_strtod (p_station->weather_data->wx_rain_total, p_station->weather_data->wx_rain_total + strlen(p_station->weather_data->wx_rain_total))
+ );
+ }
+ else
+ {
+ snprintf(rain_total, 1, " ");
+ }
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( rain_total ),
+ 3, 4, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+
+ /*
+
+ char wx_hurricane_radius[4]; //nautical miles 3
+ char wx_trop_storm_radius[4]; //nautical miles 3
+ char wx_whole_gale_radius[4]; // nautical miles 3
+ char wx_snow[6]; // in inches/24h 3
+ char wx_prec_24[10]; // in hundredths inch/day 3
+ char wx_prec_00[10]; // in hundredths inch 3
+ char wx_hum[5]; // in % 3
+ char wx_baro[10]; // in hPa 6
+ char wx_fuel_temp[5]; // in °F 3
+ char wx_fuel_moisture[5];// in % 2
+ char wx_type;
+ char wx_station[MAX_WXSTATION];
+*/
+
+}
+
+void setup_aprs_moving_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(4/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+ label = gtk_label_new(_("Moving")));
+
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Speed:"),
+ 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ gchar speed[15];
+
+// snprintf(speed, sizeof(speed), "%.01f %s", atof(p_station->speed) * UNITS_CONVERT[_units],
+// UNITS_ENUM_TEXT[_units]);
+
+ if(_units == UNITS_NM)
+ snprintf(speed, sizeof(speed), "%.01f nmph", atof(p_station->speed));
+ else if(_units == UNITS_KM)
+ snprintf(speed, sizeof(speed), "%.01f kph", atof(p_station->speed)*1.852);
+ else if(_units == UNITS_MI)
+ snprintf(speed, sizeof(speed), "%.01f mph", atof(p_station->speed)*1.1508);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( speed ),
+ 1, 2, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Course:"),
+ 0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( p_station->course ),
+ 1, 2, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Alt (m):"),
+ 0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( p_station->altitude ),
+ 1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Bearing:"),
+ 0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( p_station->bearing ),
+ 1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+
+ /*
+ GdkPixmap *pixmap=gdk_pixmap_new(table->window,100,100,16);
+ gdk_drawable_set_colormap (pixmap,gdk_colormap_get_system ());
+
+
+ GtkWidget *image = NULL;
+ GdkColor color;
+ GdkGC * gc;
+ gc = gdk_gc_new(pixmap);
+ color.red = 0;
+ color.green = 0;
+ color.blue = 0;
+
+ gdk_gc_set_foreground(gc, &color);
+
+ color.red = 255;
+ color.green = 255;
+ color.blue = 255;
+ gdk_gc_set_background(gc, &color);
+
+
+ gdk_gc_set_line_attributes(gc, _draw_width, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
+
+ gdk_draw_arc (
+ pixmap,
+ gc,
+ FALSE,
+ 2, 2,
+ 96, 96,
+ 0, 360*64
+ );
+
+ gdouble heading = deg2rad(atof(p_station->course));
+ gint y = (gint)(48.0 * cosf(heading) );
+ gint x = (gint)(48.0 * sinf(heading) );
+
+ gdk_draw_line (
+ pixmap,
+ gc,
+ 50, 50,
+ 50+x, 50-y);
+
+ gtk_table_attach(GTK_TABLE(table),
+ image = gtk_image_new_from_pixmap(pixmap, NULL),
+ 2, 3, 0, 4, GTK_SHRINK, GTK_SHRINK, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(image), 1.0f, 0.5f);
+ */
+
+// g_object_unref(image);
+// gdk_pixmap_unref(pixmap);
+}
+
+
+void setup_aprs_station_stats_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ gchar distance[15];
+ gchar lat[15], lon[15];
+ gchar course_deg[9];
+
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(4/*rows*/, 5/*columns*/, FALSE/*All cells same size*/),
+ label = gtk_label_new(_("Location")));
+
+ ////////////
+
+ course_deg[0] = '\0';
+ distance[0] = '\0';
+ lat[0] = '\0';
+ lon[0] = '\0';
+
+
+ if(p_station->coord_lat != 0 || p_station->coord_lon != 0)
+ {
+ gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
+ gdouble d_lon = convert_lon_l2d(p_station->coord_lon);
+
+ format_lat_lon(d_lat, d_lon, lat, lon);
+
+ gfloat dist = (float)calculate_distance(_gps.lat, _gps.lon, d_lat, d_lon);
+
+
+ snprintf(distance, sizeof(distance), "%.01f %s", dist * UNITS_CONVERT[_units], UNITS_ENUM_TEXT[_units]);
+
+ snprintf(course_deg, sizeof(course_deg),
+ "%.01f°",
+ calculate_bearing(_gps.lat, _gps.lon, d_lat, d_lon));
+ }
+
+ /* Last heard */
+ gchar last_update_time[26];
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Last Heard:"),
+ 0, 1, 4, 5, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+
+ if(p_station->sec_heard)
+ {
+ strftime(last_update_time, 25, "%x %X", localtime(&p_station->sec_heard));
+ }
+ else
+ {
+ snprintf(last_update_time, 25, " ");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( last_update_time ),
+ 1, 4, 4, 5, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Location:"),
+ 0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( lat ),
+ 1, 3, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( lon ),
+ 3, 5, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ ////////////
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(""),
+ 0, 5, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ ////////////
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Distance:"),
+ 0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( distance ),
+ 1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ //
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Bearing:"),
+ 2, 4, 3, 4, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( course_deg ),
+ 4, 5, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+ ////////////
+
+}
+
+
+void setup_aprs_basic_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(5/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+ label = gtk_label_new(_("Basic")));
+
+ /* Callsign. */
+ // Label
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Callsign:"),
+ 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+
+ gtk_table_attach(GTK_TABLE(table),
+
+ label = gtk_label_new( p_station->call_sign ),
+ 1, 2, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Packets:"),
+ 2, 3, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+ gchar packets[5];
+ snprintf(packets, 4, "%u", p_station->num_packets);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new( packets ),
+ 3, 4, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Comment:"),
+ 0, 1, 1, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+
+ gchar * comment = NULL;
+
+
+ if(p_station->comment_data && p_station->comment_data->text_ptr)
+ {
+ comment = g_strdup(p_station->comment_data->text_ptr);
+ }
+ else
+ {
+ comment = g_strdup("");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(comment),
+ 1, 4, 1, 3, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+ gtk_label_set_width_chars(label, 30);
+
+
+
+ ////
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Status:"),
+ 0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+
+ gchar * status = NULL;
+
+
+ if(p_station->status_data && p_station->status_data->text_ptr)
+ {
+ status = g_strdup(p_station->status_data->text_ptr);
+ }
+ else
+ {
+ status = g_strdup("");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(status),
+ 1, 4, 3, 4, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+
+
+ ////
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new("Path:"),
+ 0, 1, 4, 5, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+
+ gchar * path = NULL;
+
+ if(p_station->node_path_ptr)
+ {
+ path = g_strdup(p_station->node_path_ptr);
+ }
+ else
+ {
+ path = g_strdup("");
+ }
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(path),
+ 1, 4, 4, 5, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+
+
+
+
+}
+
+
+
+void ShowAprsStationPopup(AprsDataRow *p_station)
+{
+ GtkWidget *dialog = NULL;
+ GtkWidget *notebook = NULL;
+ GtkWidget *btn_panto = NULL;
+
+ dialog = gtk_dialog_new_with_buttons(_("Station Details"),
+ GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT,
+// "Send Message...", GTK_RESPONSE_ACCEPT,
+ NULL);
+
+
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+ btn_panto = gtk_button_new_with_mnemonic(_("C_entre Map...")));
+
+
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+ notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+
+
+ selected_station.dialog = NULL;
+ selected_station.tree_view = NULL;
+ selected_station.column_index = 0;
+ selected_station.call_sign = p_station->call_sign;
+
+ g_signal_connect(G_OBJECT(btn_panto), "clicked",
+ G_CALLBACK(panto_station), &selected_station);
+
+
+ setup_aprs_basic_display_page(notebook, p_station);
+
+
+ if(p_station->weather_data )
+ setup_aprs_basic_wx_display_page(notebook, p_station);
+
+
+
+ if( ( p_station->flag & ST_MOVING) == ST_MOVING){
+ setup_aprs_moving_display_page(notebook, p_station);
+ }
+
+
+
+ setup_aprs_station_stats_page(notebook, p_station);
+
+ gtk_widget_show_all(dialog);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_hide(dialog);
+
+
+}
+
+
+
+
+
+void list_stations()
+{
+ static GtkWidget *dialog = NULL;
+ static GtkWidget *list = NULL;
+ static GtkWidget *sw = NULL;
+ static GtkWidget *btn_panto = NULL;
+ static GtkTreeViewColumn *column = NULL;
+ static GtkCellRenderer *renderer = NULL;
+ GtkListStore *store = NULL;
+ GtkTreeIter iter;
+ gint station_count = 0;
+
+ gint num_cats = 0;
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ typedef enum
+ {
+ STATION_CALLSIGN,
+ STATION_DISTANCE,
+ STATION_BEARING,
+ STATION_COMMENT,
+ STATION_DISTANCE_NUM,
+ STATION_NUM_COLUMNS
+ } StationList;
+
+ /* Initialize store. */
+ store = gtk_list_store_new(STATION_NUM_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE);/* Category Label */
+
+ AprsDataRow *p_station = n_first;
+
+ while ( (p_station) != NULL)
+ {
+ station_count++;
+
+ gchar * comment = NULL;
+ gchar * callsign = g_strdup(p_station->call_sign);
+ gchar course_deg[8];
+ gchar * formatted_distance = NULL;
+ gdouble distance = 0;
+
+ course_deg[0] = '\0';
+
+
+ if(p_station->coord_lat != 0 && p_station->coord_lon != 0)
+ {
+ distance = distance_from_my_station(callsign, course_deg, sizeof(course_deg));
+
+ if(_units == UNITS_KM)
+ formatted_distance = g_strdup_printf("%.01f km", distance);
+ else if(_units == UNITS_MI)
+ formatted_distance = g_strdup_printf("%.01f miles", distance);
+ else if(_units == UNITS_NM)
+ formatted_distance = g_strdup_printf("%.01f nm", distance);
+ }
+ else
+ {
+ formatted_distance = g_strdup_printf("");
+ }
+
+ if(p_station->comment_data) comment = g_strdup(p_station->comment_data->text_ptr);
+ else comment = g_strdup("");
+
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter,
+ STATION_CALLSIGN, callsign,
+ STATION_DISTANCE, formatted_distance,
+ STATION_BEARING, course_deg,
+ STATION_COMMENT, comment,
+ STATION_DISTANCE_NUM, distance,
+ -1);
+ num_cats++;
+
+ g_free(comment);
+ g_free(callsign);
+ g_free(formatted_distance);
+
+ (p_station) = (p_station)->n_next; // Next element in list
+ } // End of while loop
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), STATION_DISTANCE_NUM, GTK_SORT_ASCENDING);
+
+
+
+ if(dialog == NULL)
+ {
+ dialog = gtk_dialog_new_with_buttons(_("Stations"),
+ GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+ "Details", GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_REJECT,
+ NULL);
+
+
+ gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+ btn_panto = gtk_button_new_with_mnemonic(_("C_entre Map...")));
+
+
+
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+ sw, TRUE, TRUE, 0);
+
+ list = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(sw), list);
+
+ gtk_tree_selection_set_mode(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+ GTK_SELECTION_SINGLE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+
+ //////
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Callsign"), renderer, "text", STATION_CALLSIGN, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+ ///////
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Distance"), renderer, "text", STATION_DISTANCE, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+ /////////
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Bearing"), renderer, "text", STATION_BEARING, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+
+ /////////
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Comment"), renderer, "text", STATION_COMMENT, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+
+
+ selected_station.dialog = dialog;
+ selected_station.tree_view = list;
+ selected_station.call_sign = NULL;
+ selected_station.column_index = STATION_CALLSIGN;
+
+ g_signal_connect(G_OBJECT(btn_panto), "clicked",
+ G_CALLBACK(panto_station), &selected_station);
+
+ }
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+ g_object_unref(G_OBJECT(store));
+
+
+ //
+ gchar *title = g_strdup_printf("Stations (Total: %u)", station_count);
+ gtk_window_set_title(GTK_WINDOW(dialog), title);
+ g_free(title);
+
+ gtk_widget_show_all(dialog);
+
+ while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+ {
+
+ if(gtk_tree_selection_get_selected(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+ NULL, &iter))
+ {
+ // Find the callsign
+ p_station = n_first;
+ while(p_station != NULL)
+ {
+ gchar * callsign = NULL;
+ gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+ STATION_CALLSIGN, &(callsign),
+ -1);
+
+ if(strcmp(p_station->call_sign,callsign) == 0)
+ {
+ ShowAprsStationPopup(p_station);
+ break;
+
+ }
+
+
+ p_station = p_station->n_next;
+ }
+
+ }
+
+
+
+ }
+
+ gtk_widget_hide(dialog);
+
+ vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+}
+
+
+
+
+static gboolean
+send_message(GtkWidget *widget)
+{
+ fprintf(stderr, "Send message...");
+ return FALSE;
+}
+
+static gboolean
+panto_station(GtkWidget *widget, AprsStationSelectInfo *aprs_station_selected)
+{
+ GtkTreeModel *store;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ gchar * callsign = NULL;
+ AprsDataRow *p_station = n_first;
+
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+
+ if(aprs_station_selected->call_sign != NULL)
+ callsign = aprs_station_selected->call_sign;
+ else
+ {
+ store = gtk_tree_view_get_model(GTK_TREE_VIEW(aprs_station_selected->tree_view));
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(aprs_station_selected->tree_view));
+
+ if(gtk_tree_selection_get_selected(selection, &store, &iter))
+ {
+
+ gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+ aprs_station_selected->column_index, &(callsign),
+ -1);
+
+ }
+ }
+
+ // Now findout the location of callsign
+
+ p_station = n_first;
+ while(p_station != NULL)
+ {
+ if(strcmp(p_station->call_sign,callsign) == 0)
+ {
+ if(p_station->coord_lat == 0 && p_station->coord_lon == 0)
+ {
+ // Invalid position
+ }
+ else
+ {
+ gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
+ gdouble d_lon = convert_lon_l2d(p_station->coord_lon);
+ Point unit;
+
+
+ latlon2unit(d_lat, d_lon, unit.unitx, unit.unity);
+
+ if(_center_mode > 0)
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(_menu_view_ac_none_item), TRUE);
+
+ map_center_unit(unit);
+
+ }
+
+
+ break;
+
+ }
+
+ p_station = p_station->n_next;
+ }
+
+
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+void list_messages()
+{
+ static GtkWidget *dialog = NULL;
+// static GtkWidget *btn_send = NULL;
+ static GtkWidget *list = NULL;
+ static GtkWidget *sw = NULL;
+ static GtkTreeViewColumn *column = NULL;
+ static GtkCellRenderer *renderer = NULL;
+ GtkListStore *store = NULL;
+ GtkTreeIter iter;
+
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ typedef enum
+ {
+ MSG_FROM,
+ MSG_TO,
+ MSG_TEXT,
+ MSG_TIMESTAMP,
+ MSG_NUM_COLUMNS
+ } MessageList;
+
+ /* Initialize store. */
+ store = gtk_list_store_new(MSG_NUM_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_DOUBLE);
+
+
+ // Loop through each message
+
+ gint i = 0;
+ for (i = 0; i < msg_index_end; i++)
+ {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter,
+ MSG_FROM, msg_data[msg_index[i]].from_call_sign,
+ MSG_TO, msg_data[msg_index[i]].call_sign,
+ MSG_TEXT, msg_data[msg_index[i]].message_line,
+ MSG_TIMESTAMP, (gdouble)msg_data[msg_index[i]].sec_heard,
+ -1);
+
+ }
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), MSG_TIMESTAMP, GTK_SORT_ASCENDING);
+
+ if(dialog == NULL)
+ {
+ dialog = gtk_dialog_new_with_buttons(_("Messages"),
+ GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_REJECT,
+ NULL);
+
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 550, 300);
+
+// gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+// btn_send = gtk_button_new_with_label(_("Send...")));
+
+// g_signal_connect(G_OBJECT(btn_send), "clicked",
+// G_CALLBACK(send_message), dialog);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+ sw, TRUE, TRUE, 0);
+
+
+ list = gtk_tree_view_new();
+ gtk_container_add(GTK_CONTAINER(sw), list);
+
+ gtk_tree_selection_set_mode(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+ GTK_SELECTION_SINGLE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("From"), renderer, "text", MSG_FROM, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("To"), renderer, "text", MSG_TO, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(
+ _("Message"), renderer, "text", MSG_TEXT, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+ //////
+ }
+
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+ g_object_unref(G_OBJECT(store));
+
+
+ gtk_widget_show_all(dialog);
+
+ while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+ {
+ }
+
+ gtk_widget_hide(dialog);
+
+ vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+
+}
+
+#endif //INCLUDE_APRS
\ No newline at end of file
--- /dev/null
+
+/*
+ * Created by Rob Williams - 10 Aug 2008
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_DISPLAY_H
+#define MAEMO_MAPPER_APRS_DISPLAY_H
+
+
+#include "types.h"
+
+void ShowAprsStationPopup(AprsDataRow *p_station);
+void list_stations();
+void list_messages();
+
+#endif
+
+#endif //INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_kiss.h"
+#include "aprs.h"
+#include "defines.h"
+#include "data.h"
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <pthread.h>
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+#include <termios.h>
+#include <pwd.h>
+#include <termios.h>
+#include <setjmp.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+
+#include <netinet/in.h> // Moved ahead of inet.h as reports of some *BSD's not
+ // including this as they should.
+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <time.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus-glib.h>
+
+
+#define DISABLE_SETUID_PRIVILEGE do { \
+seteuid(getuid()); \
+setegid(getgid()); \
+} while(0)
+#define ENABLE_SETUID_PRIVILEGE do { \
+seteuid(euid); \
+setegid(egid); \
+} while(0)
+
+
+
+
+#define MAX_INPUT_QUEUE 1000
+gint decode_ax25_line(gchar *line, TAprsPort port);
+
+typedef struct
+{
+ pthread_mutex_t lock;
+ pthread_t threadID;
+} xastir_mutex;
+
+xastir_mutex connect_lock; // Protects port_data[].thread_status and port_data[].connect_status
+
+// Read/write pointers for the circular input queue
+/*
+static int incoming_read_ptr = 0;
+static int incoming_write_ptr = 0;
+static int queue_depth = 0;
+static int push_count = 0;
+static int pop_count = 0;
+*/
+uid_t euid;
+gid_t egid;
+
+
+
+iface port_data; // shared port data
+
+
+//WE7U2
+// We feed a raw 7-byte string into this routine. It decodes the
+// callsign-SSID and tells us whether there are more callsigns after
+// this. If the "asterisk" input parameter is nonzero it'll add an
+// asterisk to the callsign if it has been digipeated. This
+// function is called by the decode_ax25_header() function.
+//
+// Inputs: string Raw input string
+// asterisk 1 = add "digipeated" asterisk
+//
+// Outputs: callsign Processed string
+// returned int 1=more callsigns follow, 0=end of address field
+//
+gint decode_ax25_address(gchar *string, gchar *callsign, gint asterisk) {
+ gint i,j;
+ gchar ssid;
+ gchar t;
+ gint more = 0;
+ gint digipeated = 0;
+
+ // Shift each of the six callsign characters right one bit to
+ // convert to ASCII. We also get rid of the extra spaces here.
+ j = 0;
+ for (i = 0; i < 6; i++) {
+ t = ((unsigned char)string[i] >> 1) & 0x7f;
+ if (t != ' ') {
+ callsign[j++] = t;
+ }
+ }
+
+ // Snag out the SSID byte to play with. We need more than just
+ // the 4 SSID bits out of it.
+ ssid = (unsigned char)string[6];
+
+ // Check the digipeat bit
+ if ( (ssid & 0x80) && asterisk)
+ digipeated++; // Has been digipeated
+
+ // Check whether it is the end of the address field
+ if ( !(ssid & 0x01) )
+ more++; // More callsigns to come after this one
+
+ // Snag the four SSID bits
+ ssid = (ssid >> 1) & 0x0f;
+
+ // Construct the SSID number and add it to the end of the
+ // callsign if non-zero. If it's zero we don't add it.
+ if (ssid) {
+ callsign[j++] = '-';
+ if (ssid > 9) {
+ callsign[j++] = '1';
+ }
+ ssid = ssid % 10;
+ callsign[j++] = '0' + ssid;
+ }
+
+ // Add an asterisk if the packet has been digipeated through
+ // this callsign
+ if (digipeated)
+ callsign[j++] = '*';
+
+ // Terminate the string
+ callsign[j] = '\0';
+
+ return(more);
+}
+
+
+// Function which receives raw AX.25 packets from a KISS interface and
+// converts them to a printable TAPR-2 (more or less) style string.
+// We receive the packet with a KISS Frame End character at the
+// beginning and a "\0" character at the end. We can end up with
+// multiple asterisks, one for each callsign that the packet was
+// digipeated through. A few other TNC's put out this same sort of
+// format.
+//
+// Note about KISS & CRC's: The TNC checks the CRC. If bad, it
+// drops the packet. If good, it sends it to the computer WITHOUT
+// the CRC bytes. There's no way at the computer end to check
+// whether the packet was corrupted over the serial channel between
+// the TNC and the computer. Upon sending a KISS packet to the TNC,
+// the TNC itself adds the CRC bytes back on before sending it over
+// the air. In Xastir we can just assume that we're getting
+// error-free packets from the TNC, ignoring possible corruption
+// over the serial line.
+//
+// Some versions of KISS can encode the radio channel (for
+// multi-port TNC's) in the command byte. How do we know we're
+// running those versions of KISS though? Here are the KISS
+// variants that I've been able to discover to date:
+//
+// KISS No CRC, one radio port
+//
+// SMACK 16-bit CRC, multiport TNC's
+//
+// KISS-CRC
+//
+// 6-PACK
+//
+// KISS Multi-drop (Kantronics) 8-bit XOR Checksum, multiport TNC's (AGWPE compatible)
+// BPQKISS (Multi-drop) 8-bit XOR Checksum, multiport TNC's
+// XKISS (Kantronics) 8-bit XOR Checksum, multiport TNC's
+//
+// JKISS (AGWPE and BPQ32 compatible)
+//
+// MKISS Linux driver which supports KISS/BPQ and
+// hardware handshaking? Also Paccomm command to
+// immediately enter KISS mode.
+//
+// FlexKISS -,
+// FlexCRC -|-- These are all the same!
+// RMNC-KISS -|
+// CRC-RMNC -'
+//
+//
+// It appears that none of the above protocols implement any form of
+// hardware flow control.
+//
+//
+// Compare this function with interface.c:process_ax25_packet() to
+// see if we're missing anything important.
+//
+//
+// Inputs: data_string Raw string (must be MAX_LINE_SIZE or bigger)
+// length Length of raw string (may get changed here)
+//
+// Outputs: int 0 if it is a bad packet,
+// 1 if it is good
+// data_string Processed string
+//
+gint decode_ax25_header(
+ unsigned char *data_string,
+ gint *length) {
+ gchar temp[20];
+ gchar result[MAX_LINE_SIZE+100];
+ gchar dest[15];
+ gint i, ptr;
+ gchar callsign[15];
+ gchar more;
+ gchar num_digis = 0;
+
+
+ // Do we have a string at all?
+ if (data_string == NULL)
+ return(0);
+
+ // Drop the packet if it is too long. Note that for KISS packets
+ // we can't use strlen() as there can be 0x00 bytes in the
+ // data itself.
+ if (*length > 1024) {
+ data_string[0] = '\0';
+ *length = 0;
+ return(0);
+ }
+
+ // Start with an empty string for the result
+ result[0] = '\0';
+
+ ptr = 0;
+
+ // Process the destination address
+ for (i = 0; i < 7; i++)
+ temp[i] = data_string[ptr++];
+ temp[7] = '\0';
+ more = decode_ax25_address(temp, callsign, 0); // No asterisk
+ snprintf(dest,sizeof(dest),"%s",callsign);
+
+ // Process the source address
+ for (i = 0; i < 7; i++)
+ temp[i] = data_string[ptr++];
+ temp[7] = '\0';
+ more = decode_ax25_address(temp, callsign, 0); // No asterisk
+
+ // Store the two callsigns we have into "result" in the correct
+ // order
+ snprintf(result,sizeof(result),"%s>%s",callsign,dest);
+
+ // Process the digipeater addresses (if any)
+ num_digis = 0;
+ while (more && num_digis < 8) {
+ for (i = 0; i < 7; i++)
+ temp[i] = data_string[ptr++];
+ temp[7] = '\0';
+
+ more = decode_ax25_address(temp, callsign, 1); // Add asterisk
+ strncat(result,
+ ",",
+ sizeof(result) - strlen(result));
+
+ strncat(result,
+ callsign,
+ sizeof(result) - strlen(result));
+ num_digis++;
+ }
+
+ strncat(result,
+ ":",
+ sizeof(result) - strlen(result));
+
+
+ // Check the Control and PID bytes and toss packets that are
+ // AX.25 connect/disconnect or information packets. We only
+ // want to process UI packets in Xastir.
+
+
+ // Control byte should be 0x03 (UI Frame). Strip the poll-bit
+ // from the PID byte before doing the comparison.
+ if ( (data_string[ptr++] & (~0x10)) != 0x03) {
+ return(0);
+ }
+
+
+ // PID byte should be 0xf0 (normal AX.25 text)
+ if (data_string[ptr++] != 0xf0)
+ return(0);
+
+
+// WE7U: We get multiple concatenated KISS packets sometimes. Look
+// for that here and flag when it happens (so we know about it and
+// can fix it someplace earlier in the process). Correct the
+// current packet so we don't get the extra garbage tacked onto the
+// end.
+ for (i = ptr; i < *length; i++) {
+ if (data_string[i] == KISS_FEND) {
+ fprintf(stderr,"***Found concatenated KISS packets:***\n");
+ data_string[i] = '\0'; // Truncate the string
+ break;
+ }
+ }
+
+ // Add the Info field to the decoded header info
+ strncat(result,
+ (char *)(&data_string[ptr]),
+ sizeof(result) - strlen(result));
+
+ // Copy the result onto the top of the input data. Note that
+ // the length can sometimes be longer than the input string, so
+ // we can't just use the "length" variable here or we'll
+ // truncate our string. Make sure the data_string variable is
+ // MAX_LINE_SIZE or bigger.
+ //
+ snprintf((char *)data_string,
+ MAX_LINE_SIZE,
+ "%s",
+ result);
+
+ // Write out the new length
+ *length = strlen(result);
+
+//fprintf(stderr,"%s\n",data_string);
+
+ return(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Added by KB6MER for KAM XL(SERIAL_TNC_AUX_GPS) support
+// buf is a null terminated string
+// returns buf as a null terminated string after cleaning.
+// Currently:
+// removes leading 'cmd:' prompts from TNC if needed
+// Can be used to add any additional data cleaning functions desired.
+// Currently only called for SERIAL_TNC_AUX_GPS, but could be added
+// to other device routines to improve packet decode on other devices.
+//
+// Note that the length of "buf" can be up to MAX_DEVICE_BUFFER,
+// which is currently set to 4096.
+//
+void tnc_data_clean(gchar *buf) {
+
+ while (!strncmp(buf,"cmd:",4)) {
+ int ii;
+
+ // We're _shortening_ the string here, so we don't need to
+ // know the length of the buffer unless it has no '\0'
+ // terminator to begin with! In that one case we could run
+ // off the end of the string and get a segfault or cause
+ // other problems.
+ for (ii = 0; ; ii++) {
+ buf[ii] = buf[ii+4];
+ if (buf[ii] == '\0')
+ break;
+ }
+ }
+}
+
+
+
+static gboolean aprs_parse_tty_packet(gchar *packet)
+{
+ decode_ax25_line(packet, APRS_PORT_TTY);
+
+ g_free(packet);
+
+ return FALSE;
+}
+
+
+static gboolean kiss_parse_packet(unsigned char *data_string, gint data_length)
+{
+
+
+ //fprintf(stderr, "Parse: %s\n", data_string);
+
+ gint devicetype = port_data.device_type;
+
+
+ switch(devicetype)
+ {
+ case DEVICE_SERIAL_KISS_TNC:
+ case DEVICE_SERIAL_MKISS_TNC:
+ if ( !decode_ax25_header( data_string,
+ &data_length ) ) {
+ // Had a problem decoding it. Drop
+ // it on the floor.
+ break;
+ }
+ else {
+ // Good decode. Drop through to the
+ // next block to log and decode the
+ // packet.
+ }
+
+ case DEVICE_SERIAL_TNC:
+ tnc_data_clean((char *)data_string);
+
+ case DEVICE_AX25_TNC:
+
+
+
+
+//fprintf(stderr, "Decoded kiss: %s\n", data_string);
+ g_idle_add((GSourceFunc)aprs_parse_tty_packet, data_string);
+// decode_ax25_line(data_string, "T", 0);
+ break;
+
+ default:
+ break;
+ }
+
+
+
+ return FALSE;
+}
+
+// Add one record to the circular queue. Returns 1 if queue is
+// full, 0 if successful.
+//
+/*
+int push_incoming_data(unsigned char *data_string, int length) {
+
+ int next_write_ptr = (incoming_write_ptr + 1) % MAX_INPUT_QUEUE;
+
+ // Check whether queue is full
+ if (incoming_read_ptr == next_write_ptr) {
+ // Yep, it's full!
+ return(1);
+ }
+
+ // Advance the write pointer
+ incoming_write_ptr = next_write_ptr;
+
+ incoming_data_queue[incoming_write_ptr].length = length;
+
+// incoming_data_queue[incoming_write_ptr].port = port;
+
+ snprintf((char *)incoming_data_queue[incoming_write_ptr].data,
+ (length < MAX_LINE_SIZE) ? length : MAX_LINE_SIZE,
+ "%s",
+ data_string);
+
+ queue_depth++;
+ push_count++;
+
+ return(0);
+}
+*/
+
+
+
+
+
+
+
+
+
+//***********************************************************
+// channel_data()
+//
+// Takes data read in from a port and adds it to the
+// incoming_data_queue. If queue is full, waits for queue to have
+// space before continuing.
+//
+// port #
+// string is the string of data
+// length is the length of the string. If 0 then use strlen()
+// on the string itself to determine the length.
+//
+// Note that decode_ax25_header() and perhaps other routines may
+// increase the length of the string while processing. We need to
+// send a COPY of our input string off to the decoding routines for
+// this reason, and the size of the buffer must be MAX_LINE_SIZE
+// for this reason also.
+//***********************************************************
+void channel_data(unsigned char *string, int length) {
+ int max;
+// struct timeval tmv;
+ // Some messiness necessary because we're using xastir_mutex's
+ // instead of pthread_mutex_t's.
+ int process_it = 0;
+
+
+ //fprintf(stderr,"channel_data: %x %d\n",string[0],length);
+
+
+ max = 0;
+
+ if (string == NULL)
+ {
+ return;
+ }
+
+ if (string[0] == '\0')
+ {
+ return;
+ }
+
+ if (length == 0) {
+ // Compute length of string including terminator
+ length = strlen((const char *)string) + 1;
+ }
+
+ // Check for excessively long packets. These might be TCP/IP
+ // packets or concatenated APRS packets. In any case it's some
+ // kind of garbage that we don't want to try to parse.
+
+ // Note that for binary data (WX stations and KISS packets), the
+ // strlen() function may not work correctly.
+ if (length > MAX_LINE_SIZE) { // Too long!
+// fprintf(stderr, "Too long");
+ string[0] = '\0'; // Truncate it to zero length
+ return;
+ }
+
+
+ // This protects channel_data from being run by more than one
+ // thread at the same time.
+
+ if (length > 0) {
+
+
+ // Install the cleanup routine for the case where this
+ // thread gets killed while the mutex is locked. The
+ // cleanup routine initiates an unlock before the thread
+ // dies. We must be in deferred cancellation mode for the
+ // thread to have this work properly. We must first get the
+ // pthread_mutex_t address.
+
+
+
+ // If it's any of three types of GPS ports and is a GPRMC or
+ // GPGGA string, just stick it in one of two global
+ // variables for holding such strings. UpdateTime() can
+ // come along and process/clear-out those strings at the
+ // gps_time interval.
+ //
+ process_it++;
+
+ // Remove the cleanup routine for the case where this thread
+ // gets killed while the mutex is locked. The cleanup
+ // routine initiates an unlock before the thread dies. We
+ // must be in deferred cancellation mode for the thread to
+ // have this work properly.
+ // pthread_cleanup_pop(0);
+
+
+//fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);
+
+ if (process_it) {
+
+ // Wait for empty space in queue
+//fprintf(stderr,"\n== %s", string);
+
+
+/*
+ while (push_incoming_data(string, length) && max < 5400) {
+ sched_yield(); // Yield to other threads
+ tmv.tv_sec = 0;
+ tmv.tv_usec = 2; // 2 usec
+ (void)select(0,NULL,NULL,NULL,&tmv);
+ max++;
+ }
+*/
+
+ kiss_parse_packet(g_strdup(string), length);
+ //g_idle_add((GSourceFunc)kiss_parse_packet, g_strdup(string));
+
+
+ }
+// else
+// {
+// fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);
+// }
+ }
+
+}
+
+
+
+
+//****************************************************************
+// get device name only (the portion at the end of the full path)
+// device_name current full name of device
+//****************************************************************
+
+char *get_device_name_only(char *device_name) {
+ int i,len,done;
+
+ if (device_name == NULL)
+ return(NULL);
+
+ done = 0;
+ len = (int)strlen(device_name);
+ for(i = len; i > 0 && !done; i--){
+ if(device_name[i] == '/'){
+ device_name += (i+1);
+ done = 1;
+ }
+ }
+ return(device_name);
+}
+
+
+int filethere(char *fn) {
+ FILE *f;
+ int ret;
+
+ ret =0;
+ f=fopen(fn,"r");
+ if (f != NULL) {
+ ret=1;
+ (void)fclose(f);
+ }
+ return(ret);
+}
+
+
+
+/*
+ * Close the serial port
+ * */
+gint serial_detach() {
+// char fn[600];
+ int ok;
+ ok = -1;
+
+ if (port_data.active == DEVICE_IN_USE && port_data.status == DEVICE_UP)
+ {
+ // Close port first
+ (void)tcsetattr(port_data.channel, TCSANOW, &port_data.t_old);
+ if (close(port_data.channel) == 0)
+ {
+ port_data.status = DEVICE_DOWN;
+ usleep(200);
+ port_data.active = DEVICE_NOT_IN_USE;
+ ok = 1;
+
+ }
+ else
+ {
+ fprintf(stderr,"Could not close port %s\n",port_data.device_name);
+
+ port_data.status = DEVICE_DOWN;
+ usleep(200);
+ port_data.active = DEVICE_NOT_IN_USE;
+
+ }
+
+ //update_interface_list();
+ /*
+ // Delete lockfile
+ snprintf(fn, sizeof(fn), "/var/lock/LCK..%s", get_device_name_only(port_data.device_name));
+
+ ENABLE_SETUID_PRIVILEGE;
+ (void)unlink(fn);
+ DISABLE_SETUID_PRIVILEGE;
+*/
+ }
+
+ return(ok);
+}
+
+
+typedef struct {
+ char *adapter; /* do not free this, it is freed somewhere else */
+ char *bonding; /* allocated from heap, you must free this */
+} bonding_t;
+
+gboolean open_bluetooth_tty_connection(gchar *bda, gchar **aprs_bt_port)
+{
+ gint i, st, num_bondings = 0, num_rfcomms = 0, num_posdev = 0;
+ //gint j, k, num_classes = 0, bonding_cnt = 0,
+ GError *error = NULL;
+ DBusGConnection *bus = NULL;
+ DBusGProxy *proxy = NULL;
+ //char **str_iter = NULL;
+ //char **tmp_bondings = 0, **tmp_classes = 0;
+ //char **adapters = 0;
+ gchar **rfcomms = 0;
+ bonding_t *bondings = 0; /* points to array of bonding_t */
+ bonding_t *posdev = 0; /* bondings with positioning bit on */
+ gchar *tmp;
+ //gchar *onoff;
+ const gchar const *spp="SPP";
+ //gint timeout;
+// gchar *gpsd_prog;
+// gchar *gpsd_ctrl_sock;
+
+ /* Use the dbus interface to get the BT information */
+
+
+#if (__GNUC__ > 2) && ((__GNUC__ > 3) || (__GNUC_MINOR__ > 2))
+#define ERRSTR(fmt, args...) \
+ if (error_buf && error_buf_max_len>0) { \
+ set_error_msg(error_buf, error_buf_max_len, fmt, args); \
+ } else { \
+ PDEBUG(fmt, args); \
+ }
+#else
+#define ERRSTR(fmt, args...) \
+ if (error_buf && error_buf_max_len>0) { \
+ set_error_msg(error_buf, error_buf_max_len, fmt, ##args); \
+ } else { \
+ PDEBUG(fmt, ##args); \
+ }
+#endif
+
+
+ error = NULL;
+ bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+
+ if (error)
+ {
+ st = -1;
+ errno = ECONNREFUSED; /* close enough :) */
+ //ERRSTR("%s", error->message);
+ //PDEBUG("Cannot get reply message [%s]\n", error->message);
+ goto OUT;
+ }
+
+ /* We need BT information only if the caller does not specify
+ * the BT address. If address is defined, it is assumed that
+ * it is already bonded and we just create RFCOMM connection
+ * to it.
+ */
+ if (!bda)
+ {
+ // May want to support auto detect for serial ports?
+ } else { /* if (!bda) */
+ /* Caller supplied BT address so use it */
+ num_posdev = 1;
+ posdev = calloc(1, sizeof(bonding_t *));
+ if (!posdev)
+ {
+ st = -1;
+ errno = ENOMEM;
+ goto OUT;
+ }
+ posdev[0].bonding = strdup(bda);
+
+ /* Adapter information is not needed */
+ posdev[0].adapter = "<not avail>";
+ }
+
+ /* For each bondend BT GPS device, try to create rfcomm */
+ for (i=0; i<num_posdev; i++)
+ {
+ /* Note that bluez does not provide this interface (its defined but not
+ * yet implemented) so we use btcond for creating rfcomm device(s)
+ */
+
+ proxy = dbus_g_proxy_new_for_name(bus,
+ BTCOND_DBUS, BTCOND_PATH, BTCOND_INTERFACE);
+
+ error = NULL;
+ tmp = NULL;
+ if(!dbus_g_proxy_call(proxy, BTCOND_CONNECT, &error,
+ G_TYPE_STRING, posdev[i].bonding,
+ G_TYPE_STRING, spp,
+ G_TYPE_BOOLEAN, TRUE,
+ G_TYPE_INVALID,
+ G_TYPE_STRING, &tmp,
+ G_TYPE_INVALID)
+ || error || !tmp || !*tmp)
+ {
+ /*PDEBUG("dbus_g_proxy_call returned an error: (error=(%d,%s), tmp=%s\n",
+ error ? error->code : -1,
+ error ? error->message : "<null>",
+ tmp ? tmp : "<null>");
+*/
+ /* No error if already connected */
+ if (error && !strstr(error->message,
+ "com.nokia.btcond.error.connected"))
+ {
+ ERROR:
+ fprintf(stderr, "Cannot send msg (service=%s, object=%s, interface=%s, "
+ "method=%s) [%s]\n",
+ BTCOND_DBUS,
+ BTCOND_PATH,
+ BTCOND_INTERFACE,
+ BTCOND_CONNECT,
+ error->message ? error->message : "<no error msg>");
+/* ERRSTR("Cannot send msg (service=%s, object=%s, interface=%s, "
+ "method=%s) [%s]\n",
+ BTCOND_DBUS,
+ BTCOND_PATH,
+ BTCOND_INTERFACE,
+ BTCOND_CONNECT,
+ error->message ? error->message : "<no error msg>");
+*/
+ continue;
+ }
+ else if(!tmp || !*tmp)
+ {
+ /* hack: rfcommX device name is at the end of error message */
+ char *last_space = strstr(error->message, " rfcomm");
+ if (!last_space)
+ {
+ goto ERROR;
+ }
+
+ g_free(tmp);
+ tmp = g_strdup_printf("/dev/%s", last_space+1);
+ }
+ }
+ g_object_unref(proxy);
+
+ if (tmp && tmp[0])
+ {
+ rfcomms = (char **)realloc(rfcomms, (num_rfcomms+1)*sizeof(char *));
+ if (!rfcomms)
+ {
+ st = -1;
+ errno = ENOMEM;
+ goto OUT;
+ }
+
+ rfcomms[num_rfcomms] = tmp;
+ num_rfcomms++;
+
+ fprintf(stderr, "BT addr=%s, RFCOMM %s now exists (adapter=%s)\n",
+ posdev[i].bonding, tmp, posdev[i].adapter);
+
+ tmp = NULL;
+ }
+ else
+ {
+ g_free(tmp);
+ }
+ }
+
+ if (num_rfcomms==0)
+ {
+ /* serial device creation failed */
+ fprintf(stderr, "No rfcomm created\n");
+ st = -1;
+ errno = EINVAL;
+
+ }
+ else
+ {
+ /* Add null at the end */
+ rfcomms = (char **)realloc(rfcomms, (num_rfcomms+1)*sizeof(char *));
+ if (!rfcomms)
+ {
+ st = -1;
+ errno = ENOMEM;
+
+ }
+ else
+ {
+ rfcomms[num_rfcomms] = NULL;
+
+ /* Just start the beast (to be done if everything is ok) */
+ st = 0;
+
+ *aprs_bt_port = g_strdup_printf("%s",rfcomms[0]);
+
+ }
+ }
+
+OUT:
+/* if (adapters)
+ {
+ g_strfreev(adapters);
+ }
+*/
+ if (posdev)
+ {
+ for (i=0; i<num_posdev; i++)
+ {
+ if (posdev[i].bonding)
+ {
+ free(posdev[i].bonding);
+ memset(&posdev[i], 0, sizeof(bonding_t)); /* just in case */
+ }
+ }
+ free(posdev);
+ posdev = 0;
+ }
+
+ if (bondings)
+ {
+ for (i=0; i<num_bondings; i++)
+ {
+ if (bondings[i].bonding)
+ {
+ free(bondings[i].bonding);
+ memset(&bondings[i], 0, sizeof(bonding_t)); /* just in case */
+ }
+ }
+ free(bondings);
+ bondings = 0;
+ }
+
+ if (rfcomms)
+ {
+ for (i=0; i<num_rfcomms; i++)
+ {
+ if (rfcomms[i])
+ {
+ free(rfcomms[i]);
+ rfcomms[i]=0;
+ }
+ }
+ free(rfcomms);
+ rfcomms = 0;
+ }
+
+ if (bus)
+ {
+ dbus_g_connection_unref(bus);
+ }
+
+ return st>-1;
+}
+
+
+
+
+
+
+
+//***********************************************************
+// Serial port INIT
+//***********************************************************
+int serial_init () {
+// FILE *lock;
+ int speed;
+// pid_t mypid = 0;
+// int myintpid;
+// char fn[600];
+// uid_t user_id;
+// struct passwd *user_info;
+// char temp[100];
+// char temp1[100];
+// pid_t status;
+
+// status = -9999;
+
+
+ euid = geteuid();
+ egid = getegid();
+
+
+ // clear port_channel
+ port_data.channel = -1;
+
+ // clear port active
+ port_data.active = DEVICE_NOT_IN_USE;
+
+ // clear port status
+ port_data.status = DEVICE_DOWN;
+
+ // Show the latest status in the interface control dialog
+// update_interface_list();
+
+
+
+ //gw-obex.h
+ if(_aprs_tnc_method == TNC_CONNECTION_BT)
+ {
+ // Bluetooth connection
+ gchar * aprs_bt_port = NULL;
+
+ if(!open_bluetooth_tty_connection(_aprs_tnc_bt_mac, &aprs_bt_port))
+ {
+ fprintf(stderr, "Failed to connect to BT device\n");
+ // Failed to connect
+ return -1;
+ }
+
+
+ snprintf(port_data.device_name, MAX_DEVICE_NAME, aprs_bt_port);
+ g_free(aprs_bt_port);
+
+ fprintf(stderr, "BT Port: %s\n", port_data.device_name);
+ }
+ else
+ {
+ snprintf(port_data.device_name, MAX_DEVICE_NAME, _aprs_tty_port);
+ }
+
+
+ port_data.device_type = DEVICE_SERIAL_KISS_TNC;
+ port_data.sp = B9600;
+
+
+ // check for lock file
+
+ // Try to open the serial port now
+ ENABLE_SETUID_PRIVILEGE;
+ port_data.channel = open(port_data.device_name, O_RDWR|O_NOCTTY);
+ DISABLE_SETUID_PRIVILEGE;
+ if (port_data.channel == -1){
+
+ fprintf(stderr,"Could not open channel on port!\n");
+
+ return (-1);
+ }
+
+ // Attempt to create the lock file
+/* snprintf(fn, sizeof(fn), "/var/lock/LCK..%s", get_device_name_only(port_data.device_name));
+
+ ENABLE_SETUID_PRIVILEGE;
+ lock = fopen(fn,"w");
+ DISABLE_SETUID_PRIVILEGE;
+ if (lock != NULL) {
+ // get my process id for lock file
+ mypid = getpid();
+
+ // get user info
+ user_id = getuid();
+ user_info = getpwuid(user_id);
+ snprintf(temp,
+ sizeof(temp),
+ "%s",
+ user_info->pw_name);
+
+ fprintf(lock,"%9d %s %s",(int)mypid,"xastir",temp);
+ (void)fclose(lock);
+ // We've successfully created our own lock file
+ }
+ else {
+ // lock failed
+ fprintf(stderr,"Warning: Failed opening LCK file! Continuing on...\n");
+
+ }
+*/
+
+ // get port attributes for new and old
+ if (tcgetattr(port_data.channel, &port_data.t) != 0) {
+ fprintf(stderr,"Could not get t port attributes for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ if (tcgetattr(port_data.channel, &port_data.t_old) != 0) {
+
+ fprintf(stderr,"Could not get t_old port attributes for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ // set time outs
+ port_data.t.c_cc[VMIN] = (cc_t)1;
+ port_data.t.c_cc[VTIME] = (cc_t)2;
+
+ // set port flags
+ port_data.t.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+ port_data.t.c_iflag = (tcflag_t)(IGNBRK | IGNPAR);
+
+ port_data.t.c_oflag = (0);
+ port_data.t.c_lflag = (0);
+
+#ifdef CBAUD
+ speed = (int)(port_data.t.c_cflag & CBAUD);
+#else // CBAUD
+ speed = 0;
+#endif // CBAUD
+
+ port_data.t.c_cflag = (tcflag_t)(HUPCL|CLOCAL|CREAD);
+ port_data.t.c_cflag &= ~PARENB;
+ switch (port_data.style){
+ case(0):
+ // No parity (8N1)
+ port_data.t.c_cflag &= ~CSTOPB;
+ port_data.t.c_cflag &= ~CSIZE;
+ port_data.t.c_cflag |= CS8;
+ break;
+
+ case(1):
+ // Even parity (7E1)
+ port_data.t.c_cflag &= ~PARODD;
+ port_data.t.c_cflag &= ~CSTOPB;
+ port_data.t.c_cflag &= ~CSIZE;
+ port_data.t.c_cflag |= CS7;
+ break;
+
+ case(2):
+ // Odd parity (7O1):
+ port_data.t.c_cflag |= PARODD;
+ port_data.t.c_cflag &= ~CSTOPB;
+ port_data.t.c_cflag &= ~CSIZE;
+ port_data.t.c_cflag |= CS7;
+ break;
+
+ default:
+ break;
+ }
+
+ port_data.t.c_cflag |= speed;
+ // set input and out put speed
+ if (cfsetispeed(&port_data.t, port_data.sp) == -1)
+ {
+ fprintf(stderr,"Could not set port input speed for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ if (cfsetospeed(&port_data.t, port_data.sp) == -1) {
+
+ fprintf(stderr,"Could not set port output speed for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ if (tcflush(port_data.channel, TCIFLUSH) == -1) {
+
+ fprintf(stderr,"Could not flush data for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ if (tcsetattr(port_data.channel,TCSANOW, &port_data.t) == -1)
+ {
+ fprintf(stderr,"Could not set port attributes for port!\n");
+
+ // Here we should close the port and remove the lock.
+ serial_detach();
+
+ return (-1);
+ }
+
+ // clear port active
+ port_data.active = DEVICE_IN_USE;
+
+ // clear port status
+ port_data.status = DEVICE_UP;
+
+ // Show the latest status in the interface control dialog
+// update_interface_list();
+
+ // Ensure we are in KISS mode
+ if(port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+ {
+ // Send KISS init string
+ gchar * cmd = g_strdup("\nINT KISS\nRESTART\n");
+ port_write_string(cmd, strlen(cmd), APRS_PORT_TTY);
+ }
+
+ // return good condition
+ return (1);
+}
+
+
+//***********************************************************
+// port_read()
+//
+//
+// This function becomes the long-running thread that snags
+// characters from an interface and passes them off to the
+// decoding routines. One copy of this is run for each read
+// thread for each interface.
+//***********************************************************
+
+void port_read() {
+ unsigned char cin, last;
+// unsigned char buffer[MAX_DEVICE_BUFFER]; // Only used for AX.25 packets
+ gint i;
+ struct timeval tmv;
+ fd_set rd;
+// gint group;
+// gint max;
+ /*
+ * Some local variables used for checking AX.25 data - PE1DNN
+ *
+ * "from" is used to look up where the data comes from
+ * "from_len" is used to keep the size of sockaddr structure
+ * "dev" is used to keep the name of the interface that
+ * belongs to our port/device_name
+ */
+// struct sockaddr from;
+// socklen_t from_len;
+
+ // group = 0;
+// max = MAX_DEVICE_BUFFER - 1;
+ cin = (unsigned char)0;
+ last = (unsigned char)0;
+
+
+
+ // We stay in this read loop until the port is shut down
+ while(port_data.active == DEVICE_IN_USE){
+
+ if (port_data.status == DEVICE_UP){
+
+ port_data.read_in_pos = 0;
+ port_data.scan = 1;
+
+ while (port_data.scan
+ && (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) )
+ && (port_data.status == DEVICE_UP) ) {
+
+ int skip = 0;
+
+// pthread_testcancel(); // Check for thread termination request
+
+ // Handle all EXCEPT AX25_TNC interfaces here
+ // Get one character
+ port_data.scan = (int)read(port_data.channel,&cin,1);
+//fprintf(stderr,"tty in:%02x ",cin);
+
+
+ // Below is code for ALL types of interfaces
+ if (port_data.scan > 0 && port_data.status == DEVICE_UP ) {
+
+ if (port_data.device_type != DEVICE_AX25_TNC)
+ port_data.bytes_input += port_data.scan; // Add character to read buffer
+
+
+
+ // Handle all EXCEPT AX25_TNC interfaces here
+ if (port_data.device_type != DEVICE_AX25_TNC){
+
+
+ // Do special KISS packet processing here.
+ // We save the last character in
+ // port_data.channel2, as it is
+ // otherwise only used for AX.25 ports.
+
+ if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+ || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+
+
+ if (port_data.channel2 == KISS_FESC) { // Frame Escape char
+ if (cin == KISS_TFEND) { // Transposed Frame End char
+
+ // Save this char for next time
+ // around
+ port_data.channel2 = cin;
+
+ cin = KISS_FEND;
+ }
+ else if (cin == KISS_TFESC) { // Transposed Frame Escape char
+
+ // Save this char for next time
+ // around
+ port_data.channel2 = cin;
+
+ cin = KISS_FESC;
+ }
+ else {
+ port_data.channel2 = cin;
+ }
+ }
+ else if (port_data.channel2 == KISS_FEND) { // Frame End char
+ // Frame start or frame end. Drop
+ // the next character which should
+ // either be another frame end or a
+ // type byte.
+
+// Note this "type" byte is where it specifies which KISS interface
+// the packet came from. We may want to use this later for
+// multi-drop KISS or other types of KISS protocols.
+
+ // Save this char for next time
+ // around
+ port_data.channel2 = cin;
+
+ skip++;
+ }
+ else if (cin == KISS_FESC) { // Frame Escape char
+ port_data.channel2 = cin;
+ skip++;
+ }
+ else {
+ port_data.channel2 = cin;
+ }
+ } // End of first special KISS processing
+
+
+ // We shouldn't see any AX.25 flag
+ // characters on a KISS interface because
+ // they are stripped out by the KISS code.
+ // What we should see though are KISS_FEND
+ // characters at the beginning of each
+ // packet. These characters are where we
+ // should break the data apart in order to
+ // send strings to the decode routines. It
+ // may be just fine to still break it on \r
+ // or \n chars, as the KISS_FEND should
+ // appear immediately afterwards in
+ // properly formed packets.
+
+
+ if ( (!skip)
+ && (cin == (unsigned char)'\r'
+ || cin == (unsigned char)'\n'
+ || port_data.read_in_pos >= (MAX_DEVICE_BUFFER - 1)
+ || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_KISS_TNC) )
+ || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) )
+ && port_data.data_type == 0) { // If end-of-line
+
+// End serial/net type data send it to the decoder Put a terminating
+// zero at the end of the read-in data
+
+ port_data.device_read_buffer[port_data.read_in_pos] = (char)0;
+
+ if (port_data.status == DEVICE_UP && port_data.read_in_pos > 0) {
+ int length;
+
+ // Compute length of string in
+ // circular queue
+
+ //fprintf(stderr,"%d\t%d\n",port_data.read_in_pos,port_data.read_out_pos);
+
+ // KISS TNC sends binary data
+ if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+ || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+
+ length = port_data.read_in_pos - port_data.read_out_pos;
+ if (length < 0)
+ length = (length + MAX_DEVICE_BUFFER) % MAX_DEVICE_BUFFER;
+
+ length++;
+ }
+ else { // ASCII data
+ length = 0;
+ }
+
+ channel_data(
+ (unsigned char *)port_data.device_read_buffer,
+ length); // Length of string
+ }
+
+ for (i = 0; i <= port_data.read_in_pos; i++)
+ port_data.device_read_buffer[i] = (char)0;
+
+ port_data.read_in_pos = 0;
+ }
+ else if (!skip) {
+
+ // Check for binary WX station data
+ if (cin == '\0') // OWW WX daemon sends 0x00's!
+ cin = '\n';
+
+ if (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) ) {
+ port_data.device_read_buffer[port_data.read_in_pos] = (char)cin;
+ port_data.read_in_pos++;
+ port_data.device_read_buffer[port_data.read_in_pos] = (char)0;
+ }
+ else {
+ port_data.read_in_pos = 0;
+ }
+ }
+
+ } // End of non-AX.25 interface code block
+
+
+ }
+ else if (port_data.status == DEVICE_UP) { /* error or close on read */
+ port_data.errors++;
+ if (port_data.scan == 0) {
+ // Should not get this unless the device is down. NOT TRUE!
+ // We seem to also be able to get here if we're closing/restarting
+ // another interface. For that reason I commented out the below
+ // statement so that this interface won't go down. The inactivity
+ // timer solves that issue now anyway. --we7u.
+ port_data.status = DEVICE_ERROR;
+
+ // If the below statement is enabled, it causes an immediate reconnect
+ // after one time-period of inactivity, currently 7.5 minutes, as set in
+ // main.c:UpdateTime(). This means the symbol will never change from green
+ // to red on the status bar, so the operator might not know about a
+ // connection that is being constantly reconnected. By leaving it commented
+ // out we get one time period of red, and then it will reconnect at the 2nd
+ // time period. This means we can reconnect within 15 minutes if a line
+ // goes dead.
+ //
+ port_data.reconnects = -1; // Causes an immediate reconnect
+
+ // Show the latest status in the interface control dialog
+// update_interface_list();
+
+ } else {
+ if (port_data.scan == -1) {
+ /* Should only get this if an real error occurs */
+ port_data.status = DEVICE_ERROR;
+
+ // If the below statement is enabled, it causes an immediate reconnect
+ // after one time-period of inactivity, currently 7.5 minutes, as set in
+ // main.c:UpdateTime(). This means the symbol will never change from green
+ // to red on the status bar, so the operator might not know about a
+ // connection that is being constantly reconnected. By leaving it commented
+ // out we get one time period of red, and then it will reconnect at the 2nd
+ // time period. This means we can reconnect within 15 minutes if a line
+ // goes dead.
+ //
+ port_data.reconnects = -1; // Causes an immediate reconnect
+
+ // Show the latest status in the
+ // interface control dialog
+// update_interface_list();
+
+ }
+ }
+ }
+
+
+ /*
+
+ // Send any packets queued
+ // try to get lock, otherwise try next time
+ if(g_mutex_trylock (_write_buffer[APRS_PORT_TTY].write_lock))
+ {
+ // Store the current end pointer as it may change
+
+ while (_write_buffer[APRS_PORT_TTY].write_in_pos != _write_buffer[APRS_PORT_TTY].write_out_pos) {
+
+ port_write_string(
+ _write_buffer[APRS_PORT_TTY].device_write_buffer[_write_buffer[APRS_PORT_TTY].write_out_pos],
+ APRS_PORT_TTY);
+
+ _write_buffer[APRS_PORT_TTY].write_out_pos++;
+ if (_write_buffer[APRS_PORT_TTY].write_out_pos >= MAX_DEVICE_BUFFER)
+ _write_buffer[APRS_PORT_TTY].write_out_pos = 0;
+
+ }
+
+
+ g_mutex_unlock(_write_buffer[APRS_PORT_TTY].write_lock);
+ }
+*/
+ }
+ }
+ if (port_data.active == DEVICE_IN_USE) {
+
+ // We need to delay here so that the thread doesn't use
+ // high amounts of CPU doing nothing.
+
+// This select that waits on data and a timeout, so that if data
+// doesn't come in within a certain period of time, we wake up to
+// check whether the socket has gone down. Else, we go back into
+// the select to wait for more data or a timeout. FreeBSD has a
+// problem if this is less than 1ms. Linux works ok down to 100us.
+// We don't need it anywhere near that short though. We just need
+// to check whether the main thread has requested the interface be
+// closed, and so need to have this short enough to have reasonable
+// response time to the user.
+
+//sched_yield(); // Yield to other threads
+
+ // Set up the select to block until data ready or 100ms
+ // timeout, whichever occurs first.
+ FD_ZERO(&rd);
+ FD_SET(port_data.channel, &rd);
+ tmv.tv_sec = 0;
+ tmv.tv_usec = 100000; // 100 ms
+ (void)select(0,&rd,NULL,NULL,&tmv);
+ }
+ }
+
+}
+
+#endif //INCLUDE_APRS
\ No newline at end of file
--- /dev/null
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_KISS_H
+#define MAEMO_MAPPER_APRS_KISS_H
+
+
+
+
+
+
+#endif
+
+
+#endif //INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_message.h"
+#include "data.h"
+#include "aprs.h"
+
+
+/////////////////////////////////////////// Messages ///////////////////////////////////////////
+
+
+long *msg_index;
+long msg_index_end;
+static long msg_index_max;
+
+int log_wx_alert_data = 0;
+
+Message *msg_data; // Array containing all messages,
+ // including ones we've transmitted (via
+ // loopback in the code)
+
+time_t last_message_update = 0;
+ack_record *ack_list_head = NULL; // Head of linked list storing most recent ack's
+int satellite_ack_mode;
+
+int new_message_data;
+time_t last_message_remove; // last time we did a check for message removing
+
+// How often update_messages() will run, in seconds.
+// This is necessary because routines like UpdateTime()
+// call update_messages() VERY OFTEN.
+//
+// Actually, we just changed the code around so that we only call
+// update_messages() with the force option, and only when we receive a
+// message. message_update_delay is no longer used, and we don't call
+// update_messages() from UpdateTime() anymore.
+static int message_update_delay = 300;
+
+
+
+
+char *remove_trailing_spaces(char *data);
+void update_messages(int force);
+
+
+
+
+void clear_acked_message(char *from, char *to, char *seq) {
+ // TODO - replace stub
+}
+
+int check_popup_window(char *from_call_sign, int group) {
+ // TODO - replace stub
+ return 1;
+}
+
+void bulletin_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port)
+{
+ // TODO - replace stub
+}
+
+int look_for_open_group_data(char *to) {
+ // TODO - replace stub
+ return 0;
+}
+
+void get_send_message_path(char *callsign, char *path, int path_size)
+{
+ // TODO - replace stub
+}
+
+void transmit_message_data_delayed(char *to, char *message,
+ char *path, time_t when) {
+ // TODO - replace stub
+}
+
+int process_directed_query(char *call,char *path,char *message,TAprsPort port) {
+ // TODO - replace stub
+ return 0;
+}
+
+void transmit_message_data(char *to, char *message, char *path)
+{
+ // TODO - replace stub
+}
+
+void popup_message(char *banner, char *message)
+{
+
+}
+
+// Saves latest ack in a linked list. We need this value in order
+// to use Reply/Ack protocol when sending out messages.
+void store_most_recent_ack(char *callsign, char *ack) {
+ ack_record *p;
+ int done = 0;
+ char call[MAX_CALLSIGN+1];
+ char new_ack[5+1];
+
+ snprintf(call,
+ sizeof(call),
+ "%s",
+ callsign);
+ remove_trailing_spaces(call);
+
+ // Get a copy of "ack". We might need to change it.
+ snprintf(new_ack,
+ sizeof(new_ack),
+ "%s",
+ ack);
+
+ // If it's more than 2 characters long, we can't use it for
+ // Reply/Ack protocol as there's only space enough for two.
+ // In this case we need to make sure that we blank out any
+ // former ack that was 1 or 2 characters, so that communications
+ // doesn't stop.
+ if ( strlen(new_ack) > 2 ) {
+ // It's too long, blank it out so that gets saved as "",
+ // which will overwrite any previously saved ack's that were
+ // short enough to use.
+ new_ack[0] = '\0';
+ }
+
+ // Search for matching callsign through linked list
+ p = ack_list_head;
+ while ( !done && (p != NULL) ) {
+ if (strcasecmp(call,p->callsign) == 0) {
+ done++;
+ }
+ else {
+ p = p->next;
+ }
+ }
+
+ if (done) { // Found it. Update the ack field.
+ //fprintf(stderr,"Found callsign %s on recent ack list, Old:%s, New:%s\n",call,p->ack,new_ack);
+ snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
+ }
+ else { // Not found. Add a new record to the beginning of the
+ // list.
+ //fprintf(stderr,"New callsign %s, adding to list. Ack: %s\n",call,new_ack);
+ p = (ack_record *)malloc(sizeof(ack_record));
+ CHECKMALLOC(p);
+
+ snprintf(p->callsign,sizeof(p->callsign),"%s",call);
+ snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
+ p->next = ack_list_head;
+ ack_list_head = p;
+ }
+}
+
+
+
+
+
+// Gets latest ack by callsign
+char *get_most_recent_ack(char *callsign) {
+ ack_record *p;
+ int done = 0;
+ char call[MAX_CALLSIGN+1];
+
+ snprintf(call,
+ sizeof(call),
+ "%s",
+ callsign);
+ remove_trailing_spaces(call);
+
+ // Search for matching callsign through linked list
+ p = ack_list_head;
+ while ( !done && (p != NULL) ) {
+ if (strcasecmp(call,p->callsign) == 0) {
+ done++;
+ }
+ else {
+ p = p->next;
+ }
+ }
+
+ if (done) { // Found it. Return pointer to ack string.
+ //fprintf(stderr,"Found callsign %s on linked list, returning ack: %s\n",call,p->ack);
+ return(&p->ack[0]);
+ }
+ else {
+ //fprintf(stderr,"Callsign %s not found\n",call);
+ return(NULL);
+ }
+}
+
+
+
+
+
+void init_message_data(void) { // called at start of main
+
+ new_message_data = 0;
+ last_message_remove = sec_now();
+}
+
+
+
+
+
+
+void msg_clear_data(Message *clear) {
+ int size;
+ int i;
+ unsigned char *data_ptr;
+
+ data_ptr = (unsigned char *)clear;
+ size=sizeof(Message);
+ for(i=0;i<size;i++)
+ *data_ptr++ = 0;
+}
+
+
+
+#ifdef MSG_DEBUG
+
+void msg_copy_data(Message *to, Message *from) {
+ int size;
+ int i;
+ unsigned char *data_ptr;
+ unsigned char *data_ptr_from;
+
+ data_ptr = (unsigned char *)to;
+ data_ptr_from = (unsigned char *)from;
+ size=sizeof(Message);
+ for(i=0;i<size;i++)
+ *data_ptr++ = *data_ptr_from++;
+}
+#endif /* MSG_DEBUG */
+
+
+
+
+
+// Returns 1 if it's time to update the messages again
+int message_update_time (void) {
+ if ( sec_now() > (last_message_update + message_update_delay) )
+ return(1);
+ else
+ return(0);
+}
+
+
+
+
+
+int msg_comp_active(const void *a, const void *b) {
+ char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
+ char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
+
+ snprintf(temp_a, sizeof(temp_a), "%c%s%s%s",
+ ((Message*)a)->active, ((Message*)a)->call_sign,
+ ((Message*)a)->from_call_sign,
+ ((Message*)a)->seq);
+ snprintf(temp_b, sizeof(temp_b), "%c%s%s%s",
+ ((Message*)b)->active, ((Message*)b)->call_sign,
+ ((Message*)b)->from_call_sign,
+ ((Message*)b)->seq);
+
+ return(strcmp(temp_a, temp_b));
+}
+
+
+
+
+
+int msg_comp_data(const void *a, const void *b) {
+ char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+ char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+
+ snprintf(temp_a, sizeof(temp_a), "%s%s%s",
+ msg_data[*(long*)a].call_sign, msg_data[*(long *)a].from_call_sign,
+ msg_data[*(long *)a].seq);
+ snprintf(temp_b, sizeof(temp_b), "%s%s%s", msg_data[*(long*)b].call_sign,
+ msg_data[*(long *)b].from_call_sign, msg_data[*(long *)b].seq);
+
+ return(strcmp(temp_a, temp_b));
+}
+
+
+
+
+
+void msg_input_database(Message *m_fill) {
+ void *m_ptr;
+ long i;
+
+// fprintf(stderr, "DEBUG: Message: %s %s\n", m_fill->call_sign, m_fill->message_line);
+
+ if (msg_index_end == msg_index_max) {
+ for (i = 0; i < msg_index_end; i++) {
+
+ // Check for a record that is marked RECORD_NOTACTIVE.
+ // If found, use that record instead of malloc'ing a new
+ // one.
+ if (msg_data[msg_index[i]].active == RECORD_NOTACTIVE) {
+
+ // Found an unused record. Fill it in.
+ memcpy(&msg_data[msg_index[i]], m_fill, sizeof(Message));
+
+// Sort msg_data
+ qsort(msg_data, (size_t)msg_index_end, sizeof(Message), msg_comp_active);
+
+ for (i = 0; i < msg_index_end; i++) {
+ msg_index[i] = i;
+ if (msg_data[i].active == RECORD_NOTACTIVE) {
+ msg_index_end = i;
+ break;
+ }
+ }
+
+// Sort msg_index
+ qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
+
+ // All done with this message.
+ return;
+ }
+ }
+
+ // Didn't find free message record. Fetch some more space.
+ // Get more msg_data space.
+ m_ptr = realloc(msg_data, (msg_index_max+MSG_INCREMENT)*sizeof(Message));
+ if (m_ptr) {
+ msg_data = m_ptr;
+
+ // Get more msg_index space
+ m_ptr = realloc(msg_index, (msg_index_max+MSG_INCREMENT)*sizeof(Message *));
+ if (m_ptr) {
+ msg_index = m_ptr;
+ msg_index_max += MSG_INCREMENT;
+
+//fprintf(stderr, "Max Message Array: %ld\n", msg_index_max);
+
+ }
+ else {
+ // TODO
+ //XtWarning("Unable to allocate more space for message index.\n");
+ }
+ }
+ else {
+ // TODO
+ //XtWarning("Unable to allocate more space for message database.\n");
+ }
+ }
+ if (msg_index_end < msg_index_max) {
+ msg_index[msg_index_end] = msg_index_end;
+
+ // Copy message data into new message record.
+ memcpy(&msg_data[msg_index_end++], m_fill, sizeof(Message));
+
+// Sort msg_index
+ qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
+ }
+}
+
+
+
+
+
+// Does a binary search through a sorted message database looking
+// for a string match.
+//
+// If two or more messages match, this routine _should_ return the
+// message with the latest timestamp. This will ensure that earlier
+// messages don't get mistaken for current messages, for the case
+// where the remote station did a restart and is using the same
+// sequence numbers over again.
+//
+long msg_find_data(Message *m_fill) {
+ long record_start, record_mid, record_end, return_record, done;
+ char tempfile[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+ char tempfill[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+
+
+ snprintf(tempfill, sizeof(tempfill), "%s%s%s",
+ m_fill->call_sign,
+ m_fill->from_call_sign,
+ m_fill->seq);
+
+ return_record = -1L;
+ if (msg_index && msg_index_end >= 1) {
+ /* more than one record */
+ record_start=0L;
+ record_end = (msg_index_end - 1);
+ record_mid=(record_end-record_start)/2;
+
+ done=0;
+ while (!done) {
+
+ /* get data for record start */
+ snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+ msg_data[msg_index[record_start]].call_sign,
+ msg_data[msg_index[record_start]].from_call_sign,
+ msg_data[msg_index[record_start]].seq);
+
+ if (strcmp(tempfill, tempfile) < 0) {
+ /* filename comes before */
+ /*fprintf(stderr,"Before No data found!!\n");*/
+ done=1;
+ break;
+ }
+ else { /* get data for record end */
+
+ snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+ msg_data[msg_index[record_end]].call_sign,
+ msg_data[msg_index[record_end]].from_call_sign,
+ msg_data[msg_index[record_end]].seq);
+
+ if (strcmp(tempfill,tempfile)>=0) { /* at end or beyond */
+ if (strcmp(tempfill, tempfile) == 0) {
+ return_record = record_end;
+//fprintf(stderr,"record %ld",return_record);
+ }
+
+ done=1;
+ break;
+ }
+ else if ((record_mid == record_start) || (record_mid == record_end)) {
+ /* no mid for compare check to see if in the middle */
+ done=1;
+ snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+ msg_data[msg_index[record_mid]].call_sign,
+ msg_data[msg_index[record_mid]].from_call_sign,
+ msg_data[msg_index[record_mid]].seq);
+ if (strcmp(tempfill,tempfile)==0) {
+ return_record = record_mid;
+//fprintf(stderr,"record: %ld",return_record);
+ }
+ }
+ }
+ if (!done) { /* get data for record mid */
+ snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+ msg_data[msg_index[record_mid]].call_sign,
+ msg_data[msg_index[record_mid]].from_call_sign,
+ msg_data[msg_index[record_mid]].seq);
+
+ if (strcmp(tempfill, tempfile) == 0) {
+ return_record = record_mid;
+//fprintf(stderr,"record %ld",return_record);
+ done = 1;
+ break;
+ }
+
+ if(strcmp(tempfill, tempfile)<0)
+ record_end = record_mid;
+ else
+ record_start = record_mid;
+
+ record_mid = record_start+(record_end-record_start)/2;
+ }
+ }
+ }
+ return(return_record);
+}
+
+
+
+
+
+void msg_replace_data(Message *m_fill, long record_num) {
+ memcpy(&msg_data[msg_index[record_num]], m_fill, sizeof(Message));
+}
+
+
+
+
+
+void msg_get_data(Message *m_fill, long record_num) {
+ memcpy(m_fill, &msg_data[msg_index[record_num]], sizeof(Message));
+}
+
+
+
+
+
+void msg_update_ack_stamp(long record_num) {
+
+ //fprintf(stderr,"Attempting to update ack stamp: %ld\n",record_num);
+ if ( (record_num >= 0) && (record_num < msg_index_end) ) {
+ msg_data[msg_index[record_num]].last_ack_sent = sec_now();
+ //fprintf(stderr,"Ack stamp: %ld\n",msg_data[msg_index[record_num]].last_ack_sent);
+ }
+ //fprintf(stderr,"\n\n\n*** Record: %ld ***\n\n\n",record_num);
+}
+
+
+
+
+
+// Called when we receive an ACK. Sets the "acked" field in a
+// Message which gets rid of the highlighting in the Send Message
+// dialog for that message line. This lets us know which messages
+// have been acked and which have not. If timeout is non-zero, then
+// set acked to 2: We use this in update_messages() to flag that
+// "*TIMEOUT*" should prefix the string. If cancelled is non-zero,
+// set acked to 3: We use this in update_messages() to flag that
+// "*CANCELLED*" should prefix the string.
+//
+void msg_record_ack(char *to_call_sign,
+ char *my_call,
+ char *seq,
+ int timeout,
+ int cancel) {
+ Message m_fill;
+ long record;
+ int do_update = 0;
+
+
+ // Find the corresponding message in msg_data[i], set the
+ // "acked" field to one.
+
+ substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+ substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ // Look for a message with the same to_call_sign, my_call,
+ // and seq number
+ record = msg_find_data(&m_fill);
+
+ if (record == -1L) { // No match yet, try another tactic.
+ if (seq[2] == '}' && strlen(seq) == 3) {
+
+ // Try it again without the trailing '}' character
+ m_fill.from_call_sign[2] = '\0';
+
+ // Look for a message with the same to_call_sign,
+ // my_call, and seq number (minus the trailing '}')
+ record = msg_find_data(&m_fill);
+ }
+ }
+
+ if(record != -1L) { // Found a match!
+ // Only cause an update if this is the first ack. This
+ // reduces dialog "flashing" a great deal
+ if ( msg_data[msg_index[record]].acked == 0 ) {
+
+ // Check for my callsign (including SSID). If found,
+ // update any open message dialogs
+ if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {
+
+ //fprintf(stderr,"From: %s\tTo: %s\n",
+ // msg_data[msg_index[record]].from_call_sign,
+ // msg_data[msg_index[record]].call_sign);
+
+ do_update++;
+ }
+ }
+ else { // This message has already been acked.
+ }
+
+ if (cancel)
+ msg_data[msg_index[record]].acked = (char)3;
+ else if (timeout)
+ msg_data[msg_index[record]].acked = (char)2;
+ else
+ msg_data[msg_index[record]].acked = (char)1;
+
+ // Set the interval to zero so that we don't display it
+ // anymore in the dialog. Same for tries.
+ msg_data[msg_index[record]].interval = 0;
+ msg_data[msg_index[record]].tries = 0;
+
+
+ }
+
+
+ if (do_update) {
+
+ update_messages(1); // Force an update
+
+ // Call check_popup_messages() here in order to pop up any
+ // closed Send Message dialogs. For first ack's or
+ // CANCELLED messages it is less important, but for TIMEOUT
+ // messages it is very important.
+ //
+// TODO
+// (void)check_popup_window(m_fill.call_sign, 2); // Calls update_messages()
+ }
+}
+
+
+
+
+
+// Called when we receive a REJ packet (reject). Sets the "acked"
+// field in a Message to 4 to indicate that the message has been
+// rejected by the remote station. This gets rid of the
+// highlighting in the Send Message dialog for that message line.
+// This lets us know which messages have been rejected and which
+// have not. We use this in update_messages() to flag that
+// "*REJECTED*" should prefix the string.
+//
+// The most common source of REJ packets would be from sending to a
+// D700A who's buffers are full, so that it can't take another
+// message.
+//
+void msg_record_rej(char *to_call_sign,
+ char *my_call,
+ char *seq) {
+ Message m_fill;
+ long record;
+ int do_update = 0;
+
+ // Find the corresponding message in msg_data[i], set the
+ // "acked" field to four.
+
+ substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+ substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ // Look for a message with the same to_call_sign, my_call,
+ // and seq number
+ record = msg_find_data(&m_fill);
+
+ if (record == -1L) { // No match yet, try another tactic.
+ if (seq[2] == '}' && strlen(seq) == 3) {
+
+ // Try it again without the trailing '}' character
+ m_fill.from_call_sign[2] = '\0';
+
+ // Look for a message with the same to_call_sign,
+ // my_call, and seq number (minus the trailing '}')
+ record = msg_find_data(&m_fill);
+ }
+ }
+
+ if(record != -1L) { // Found a match!
+ // Only cause an update if this is the first rej. This
+ // reduces dialog "flashing" a great deal
+ if ( msg_data[msg_index[record]].acked == 0 ) {
+
+ // Check for my callsign (including SSID). If found,
+ // update any open message dialogs
+ if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {
+
+ //fprintf(stderr,"From: %s\tTo: %s\n",
+ // msg_data[msg_index[record]].from_call_sign,
+ // msg_data[msg_index[record]].call_sign);
+
+ do_update++;
+ }
+ }
+ else { // This message has already been acked.
+ }
+
+ // Actually record the REJ here
+ msg_data[msg_index[record]].acked = (char)4;
+
+ // Set the interval to zero so that we don't display it
+ // anymore in the dialog. Same for tries.
+ msg_data[msg_index[record]].interval = 0;
+ msg_data[msg_index[record]].tries = 0;
+
+ }
+
+
+ if (do_update) {
+
+ update_messages(1); // Force an update
+
+ // Call check_popup_messages() here in order to pop up any
+ // closed Send Message dialogs. For first ack's or
+ // CANCELLED messages it is less important, but for TIMEOUT
+ // messages it is very important.
+ //
+// TODO
+// (void)check_popup_window(m_fill.call_sign, 2); // Calls update_messages()
+ }
+}
+
+
+
+
+
+// Called from check_and_transmit_messages(). Updates the interval
+// field in our message record for the message currently being
+// transmitted. We'll use this in the Send Message dialog to
+// display the current message interval.
+//
+void msg_record_interval_tries(char *to_call_sign,
+ char *my_call,
+ char *seq,
+ time_t interval,
+ int tries) {
+ Message m_fill;
+ long record;
+
+
+ // Find the corresponding message in msg_data[i]
+
+ substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+ substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ // Look for a message with the same to_call_sign, my_call,
+ // and seq number
+ record = msg_find_data(&m_fill);
+ if(record != -1L) { // Found a match!
+
+ msg_data[msg_index[record]].interval = interval;
+ msg_data[msg_index[record]].tries = tries;
+ }
+
+ update_messages(1); // Force an update
+}
+
+
+
+
+
+// Returns: time_t for last_ack_sent
+// -1 if the message doesn't pass our tests
+// 0 if it is a new message.
+//
+// Also returns the record number found if not passed a NULL pointer
+// in record_out or -1L if it's a new record.
+//
+time_t msg_data_add(char *call_sign, char *from_call, char *data,
+ char *seq, char type, TAprsPort port, long *record_out) {
+ Message m_fill;
+ long record;
+ char time_data[MAX_TIME];
+ int do_msg_update = 0;
+ time_t last_ack_sent;
+ int distance = -1;
+ char temp[10];
+ int group_message = 0;
+
+
+//fprintf(stderr,"from:%s, to:%s, seq:%s\n", from_call, call_sign, seq);
+
+ // Set the default output condition. We'll change this later if
+ // we need to.
+ if (record_out != NULL)
+ *record_out = -1l;
+
+ // Check for some reasonable string in call_sign parameter
+ if (call_sign == NULL || strlen(call_sign) == 0) {
+
+ return((time_t)-1l);
+ }
+//else
+//fprintf(stderr,"msg_data_add():call_sign: %s\n", call_sign);
+
+ if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {
+
+ return((time_t)-1l);
+ }
+
+ substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+// If the sequence number is blank, then it may have been a query,
+// directed query, or group message. Assume it is a new message in
+// each case and add it.
+
+ if (seq[0] != '\0') { // Normal station->station messaging or
+ // bulletins
+ // Look for a message with the same call_sign,
+ // from_call_sign, and seq number
+ record = msg_find_data(&m_fill);
+//fprintf(stderr,"RECORD %ld \n",record);
+//fprintf(stderr,"Normal station->station message\n");
+ }
+ else { // Group message/query/etc.
+ record = -1L;
+ group_message++; // Flag it as a group message
+//fprintf(stderr,"Group message/query/etc\n");
+ }
+ msg_clear_data(&m_fill);
+ if(record != -1L) { /* fill old data */
+ msg_get_data(&m_fill, record);
+ last_ack_sent = m_fill.last_ack_sent;
+ //fprintf(stderr,"Found: last_ack_sent: %ld\n",m_fill.last_ack_sent);
+
+ //fprintf(stderr,"Found a duplicate message. Updating fields, seq %s\n",seq);
+
+ // If message is different this time, do an update to the
+ // send message window and update the sec_heard field. The
+ // remote station must have restarted and is re-using the
+ // sequence numbers. What a pain!
+ if (strcmp(m_fill.message_line,data) != 0) {
+ m_fill.sec_heard = sec_now();
+ last_ack_sent = (time_t)0;
+//fprintf(stderr,"Message is different this time: Setting last_ack_sent to 0\n");
+
+ if (type != MESSAGE_BULLETIN) { // Not a bulletin
+ do_msg_update++;
+ }
+ }
+
+ // If message is the same, but the sec_heard field is quite
+ // old (more than 8 hours), the remote station must have
+ // restarted, is re-using the sequence numbers, and just
+ // happened to send the same message with the same sequence
+ // number. Again, what a pain! Either that, or we
+ // connected to a spigot with a _really_ long queue!
+ if (m_fill.sec_heard < (sec_now() - (8 * 60 * 60) )) {
+ m_fill.sec_heard = sec_now();
+ last_ack_sent = (time_t)0;
+//fprintf(stderr,"Found >8hrs old: Setting last_ack_sent to 0\n");
+
+ if (type != MESSAGE_BULLETIN) { // Not a bulletin
+ do_msg_update++;
+ }
+ }
+
+ // Check for zero time
+ if (m_fill.sec_heard == (time_t)0) {
+ m_fill.sec_heard = sec_now();
+ fprintf(stderr,"Zero time on a previous message.\n");
+ }
+ }
+ else {
+ // Only do this if it's a new message. This keeps things
+ // more in sequence by not updating the time stamps
+ // constantly on old messages that don't get ack'ed.
+ m_fill.sec_heard = sec_now();
+ last_ack_sent = (time_t)0;
+ //fprintf(stderr,"New msg: Setting last_ack_sent to 0\n");
+
+ if (type != MESSAGE_BULLETIN) { // Not a bulletin
+//fprintf(stderr,"Found new message\n");
+ do_msg_update++; // Always do an update to the
+ // message window for new messages
+ }
+ }
+
+ /* FROM */
+ m_fill.port =port;
+ m_fill.active=RECORD_ACTIVE;
+ m_fill.type=type;
+ if (m_fill.heard_via_tnc != VIA_TNC)
+ m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;
+
+ distance = (int)(distance_from_my_station(from_call,temp, sizeof(temp)) + 0.9999);
+
+ if (distance != 0) { // Have a posit from the sending station
+ m_fill.position_known = 1;
+ //fprintf(stderr,"Position known: %s\n",from_call);
+ }
+ else {
+ //fprintf(stderr,"Position not known: %s\n",from_call);
+ }
+
+ substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+ // Update the message field
+ substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
+
+ substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ // Create a timestamp from the current time
+ snprintf(m_fill.packet_time,
+ sizeof(m_fill.packet_time),
+ "%s",
+ get_time(time_data));
+
+ if(record == -1L) { // No old record found
+ if (group_message)
+ m_fill.acked = 1; // Group msgs/queries need no ack
+ else
+ m_fill.acked = 0; // We can't have been acked yet
+
+ m_fill.interval = 0;
+ m_fill.tries = 0;
+
+ // We'll be sending an ack right away if this is a new
+ // message, so might as well set the time now so that we
+ // don't care about failing to set it in
+ // msg_update_ack_stamp due to the record number being -1.
+ m_fill.last_ack_sent = sec_now();
+
+ msg_input_database(&m_fill); // Create a new entry
+ //fprintf(stderr,"No record found: Setting last_ack_sent to sec_now()00\n");
+ }
+ else { // Old record found
+ //fprintf(stderr,"Replacing the message in the database, seq %s\n",seq);
+ msg_replace_data(&m_fill, record); // Copy fields from m_fill to record
+ }
+
+ /* display messages */
+// TODO
+ // if (type == MESSAGE_MESSAGE)
+// all_messages(from,call_sign,from_call,data);
+
+ // Check for my callsign (including SSID). If found, update any
+ // open message dialogs
+ if ( is_my_call(m_fill.from_call_sign, 1)
+ || is_my_call(m_fill.call_sign, 1) ) {
+
+ if (do_msg_update) {
+ update_messages(1); // Force an update
+ }
+ }
+
+
+ // Return the important variables we'll need
+ if (record_out != NULL)
+ *record_out = record;
+
+//fprintf(stderr,"\nrecord_out:%ld record %ld\n",*record_out,record);
+ return(last_ack_sent);
+
+} // End of msg_data_add()
+
+
+
+
+
+// alert_data_add: Function which adds NWS weather alerts to the
+// alert hash.
+//
+// This function adds alerts directly to the alert hash, bypassing
+// the message list and associated message-scan functions.
+//
+void alert_data_add(char *call_sign, char *from_call, char *data,
+ char *seq, char type, TAprsPort port) {
+ Message m_fill;
+ char time_data[MAX_TIME];
+
+
+
+/*
+ if (log_wx_alert_data && from != DATA_VIA_FILE) {
+ char temp_msg[MAX_MESSAGE_LENGTH+1];
+
+ // Attempt to reconstruct the original weather alert packet
+ // here, minus the path.
+ snprintf(temp_msg,
+ sizeof(temp_msg),
+ "%s>APRS::%-9s:%s{%s",
+ from_call,
+ call_sign,
+ data,
+ seq);
+ log_data( get_user_base_dir(LOGFILE_WX_ALERT), temp_msg);
+// fprintf(stderr, "%s\n", temp_msg);
+ }
+*/
+
+ if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {
+ return;
+ }
+
+ substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ m_fill.sec_heard = sec_now();
+
+ /* FROM */
+ m_fill.port=port;
+ m_fill.active=RECORD_ACTIVE;
+ m_fill.type=type;
+
+ // We don't have a value filled in yet here!
+ //if (m_fill.heard_via_tnc != VIA_TNC)
+ m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;
+
+ substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.call_sign);
+
+ substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
+ (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+ // Update the message field
+ substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
+
+ substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
+ (void)remove_trailing_spaces(m_fill.seq);
+ (void)remove_leading_spaces(m_fill.seq);
+
+ // Create a timestamp from the current time
+ snprintf(m_fill.packet_time,
+ sizeof(m_fill.packet_time),
+ "%s",
+ get_time(time_data));
+
+ // Go try to add it to our alert hash. alert_build_list() will
+ // check for duplicates before adding it.
+
+// TODO
+// alert_build_list(&m_fill);
+
+ // This function fills in the Shapefile filename and index
+ // so that we can later draw it.
+// TODO
+// fill_in_new_alert_entries();
+
+
+} // End of alert_data_add()
+
+
+
+
+
+// What I'd like to do for the following routine: Use
+// XmTextGetInsertionPosition() or XmTextGetCursorPosition() to
+// find the last of the text. Could also save the position for
+// each SendMessage window. Compare the timestamps of messages
+// found with the last update time. If newer, then add them to
+// the end. This should stop the incessant scrolling.
+
+// Another idea, easier method: Create a buffer. Snag out the
+// messages from the array and sort by time. Put them into a
+// buffer. Figure out the length of the text widget, and append
+// the extra length of the buffer onto the end of the text widget.
+// Once the message data is turned into a linked list, it might
+// be sorted already by time, so this window will look better
+// anyway.
+
+// Calling update_messages with force == 1 will cause an update
+// no matter what message_update_time() says.
+void update_messages(int force) {
+ // TODO
+}
+/*
+void update_messages(int force) {
+ static XmTextPosition pos;
+ char temp1[MAX_CALLSIGN+1];
+ char temp2[500];
+ char stemp[20];
+ long i;
+ int mw_p;
+ char *temp_ptr;
+
+
+ if ( message_update_time() || force) {
+
+//fprintf(stderr,"update_messages()\n");
+
+ //fprintf(stderr,"Um %d\n",(int)sec_now() );
+
+ // go through all mw_p's!
+
+ // Perform this for each message window
+ for (mw_p=0; msg_index && mw_p < MAX_MESSAGE_WINDOWS; mw_p++) {
+ //pos=0;
+
+begin_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
+
+ if (mw[mw_p].send_message_dialog!=NULL) {
+
+//fprintf(stderr,"\n");
+
+//fprintf(stderr,"found send_message_dialog\n");
+
+ // Clear the text from message window
+ XmTextReplace(mw[mw_p].send_message_text,
+ (XmTextPosition) 0,
+ XmTextGetLastPosition(mw[mw_p].send_message_text),
+ "");
+
+ // Snag the callsign you're dealing with from the message dialogue
+ if (mw[mw_p].send_message_call_data != NULL) {
+ temp_ptr = XmTextFieldGetString(mw[mw_p].send_message_call_data);
+ snprintf(temp1,
+ sizeof(temp1),
+ "%s",
+ temp_ptr);
+ XtFree(temp_ptr);
+
+ new_message_data--;
+ if (new_message_data<0)
+ new_message_data=0;
+
+ if(strlen(temp1)>0) { // We got a callsign from the dialog so
+ // create a linked list of the message indexes in time-sorted order
+
+ typedef struct _index_record {
+ int index;
+ time_t sec_heard;
+ struct _index_record *next;
+ } index_record;
+ index_record *head = NULL;
+ index_record *p_prev = NULL;
+ index_record *p_next = NULL;
+
+ // Allocate the first record (a dummy record)
+ head = (index_record *)malloc(sizeof(index_record));
+ CHECKMALLOC(head);
+
+ head->index = -1;
+ head->sec_heard = (time_t)0;
+ head->next = NULL;
+
+ (void)remove_trailing_spaces(temp1);
+ (void)to_upper(temp1);
+
+ pos = 0;
+ // Loop through looking for messages to/from
+ // that callsign (including SSID)
+ for (i = 0; i < msg_index_end; i++) {
+ if (msg_data[msg_index[i]].active == RECORD_ACTIVE
+ && (strcmp(temp1, msg_data[msg_index[i]].from_call_sign) == 0
+ || strcmp(temp1,msg_data[msg_index[i]].call_sign) == 0)
+ && (is_my_call(msg_data[msg_index[i]].from_call_sign, 1)
+ || is_my_call(msg_data[msg_index[i]].call_sign, 1)
+ || mw[mw_p].message_group ) ) {
+ int done = 0;
+
+ // Message matches our parameters so
+ // save the relevant data about the
+ // message in our linked list. Compare
+ // the sec_heard field to see whether
+ // we're higher or lower, and insert the
+ // record at the correct spot in the
+ // list. We end up with a time-sorted
+ // list.
+ p_prev = head;
+ p_next = p_prev->next;
+ while (!done && (p_next != NULL)) { // Loop until end of list or record inserted
+
+ //fprintf(stderr,"Looping, looking for insertion spot\n");
+
+ if (p_next->sec_heard <= msg_data[msg_index[i]].sec_heard) {
+ // Advance one record
+ p_prev = p_next;
+ p_next = p_prev->next;
+ }
+ else { // We found the correct insertion spot
+ done++;
+ }
+ }
+
+ //fprintf(stderr,"Inserting\n");
+
+ // Add the record in between p_prev and
+ // p_next, even if we're at the end of
+ // the list (in that case p_next will be
+ // NULL.
+ p_prev->next = (index_record *)malloc(sizeof(index_record));
+ CHECKMALLOC(p_prev->next);
+
+ p_prev->next->next = p_next; // Link to rest of records or NULL
+ p_prev->next->index = i;
+ p_prev->next->sec_heard = msg_data[msg_index[i]].sec_heard;
+// Remember to free this entire linked list before exiting the loop for
+// this message window!
+ }
+ }
+ // Done processing the entire list for this
+ // message window.
+
+ //fprintf(stderr,"Done inserting/looping\n");
+
+ if (head->next != NULL) { // We have messages to display
+ int done = 0;
+
+ //fprintf(stderr,"We have messages to display\n");
+
+ // Run through the linked list and dump the
+ // info out. It's now in time-sorted order.
+
+// Another optimization would be to keep a count of records added, then
+// later when we were dumping it out to the window, only dump the last
+// XX records out.
+
+ p_prev = head->next; // Skip the first dummy record
+ p_next = p_prev->next;
+ while (!done && (p_prev != NULL)) { // Loop until end of list
+ int j = p_prev->index; // Snag the index out of the record
+ char prefix[50];
+ char interval_str[50];
+ int offset = 22; // Offset for highlighting
+
+
+ //fprintf(stderr,"\nLooping through, reading messages\n");
+
+//fprintf(stderr,"acked: %d\n",msg_data[msg_index[j]].acked);
+
+ // Message matches so snag the important pieces into a string
+ snprintf(stemp, sizeof(stemp),
+ "%c%c/%c%c %c%c:%c%c",
+ msg_data[msg_index[j]].packet_time[0],
+ msg_data[msg_index[j]].packet_time[1],
+ msg_data[msg_index[j]].packet_time[2],
+ msg_data[msg_index[j]].packet_time[3],
+ msg_data[msg_index[j]].packet_time[8],
+ msg_data[msg_index[j]].packet_time[9],
+ msg_data[msg_index[j]].packet_time[10],
+ msg_data[msg_index[j]].packet_time[11]
+ );
+
+// Somewhere in here we appear to be losing the first message. It
+// doesn't get written to the window later in the QSO. Same for
+// closing the window and re-opening it, putting the same callsign
+// in and pressing "New Call" button. First message is missing.
+
+ // Label the message line with who sent it.
+ // If acked = 2 a timeout has occurred
+ // If acked = 3 a cancel has occurred
+ if (msg_data[msg_index[j]].acked == 2) {
+ snprintf(prefix,
+ sizeof(prefix),
+ "%s ",
+ langcode("WPUPMSB016") ); // "*TIMEOUT*"
+ }
+ else if (msg_data[msg_index[j]].acked == 3) {
+ snprintf(prefix,
+ sizeof(prefix),
+ "%s ",
+ langcode("WPUPMSB017") ); // "*CANCELLED*"
+ }
+ else if (msg_data[msg_index[j]].acked == 4) {
+ snprintf(prefix,
+ sizeof(prefix),
+ "%s ",
+ langcode("WPUPMSB018") ); // "*REJECTED*"
+ }
+ else prefix[0] = '\0';
+
+ if (msg_data[msg_index[j]].interval) {
+ snprintf(interval_str,
+ sizeof(interval_str),
+ ">%d/%lds",
+ msg_data[msg_index[j]].tries + 1,
+ (long)msg_data[msg_index[j]].interval);
+
+ // Don't highlight the interval
+ // value
+ offset = offset + strlen(interval_str);
+ }
+ else {
+ interval_str[0] = '\0';
+ }
+
+ snprintf(temp2, sizeof(temp2),
+ "%s %-9s%s>%s%s\n",
+ // Debug code. Trying to find sorting error
+ //"%ld %s %-9s>%s\n",
+ //msg_data[msg_index[j]].sec_heard,
+ stemp,
+ msg_data[msg_index[j]].from_call_sign,
+ interval_str,
+ prefix,
+ msg_data[msg_index[j]].message_line);
+
+//fprintf(stderr,"message: %s\n", msg_data[msg_index[j]].message_line);
+//fprintf(stderr,"update_messages: %s|%s", temp1, temp2);
+
+ // Replace the text from pos to pos+strlen(temp2) by the string "temp2"
+ if (mw[mw_p].send_message_text != NULL) {
+
+ // Insert the text at the end
+// XmTextReplace(mw[mw_p].send_message_text,
+// pos,
+// pos+strlen(temp2),
+// temp2);
+
+ XmTextInsert(mw[mw_p].send_message_text,
+ pos,
+ temp2);
+
+ // Set highlighting based on the
+ // "acked" field. Callsign
+ // match here includes SSID.
+//fprintf(stderr,"acked: %d\t",msg_data[msg_index[j]].acked);
+ if ( (msg_data[msg_index[j]].acked == 0) // Not acked yet
+ && ( is_my_call(msg_data[msg_index[j]].from_call_sign, 1)) ) {
+//fprintf(stderr,"Setting underline\t");
+ XmTextSetHighlight(mw[mw_p].send_message_text,
+ pos+offset,
+ pos+strlen(temp2),
+ //XmHIGHLIGHT_SECONDARY_SELECTED); // Underlining
+ XmHIGHLIGHT_SELECTED); // Reverse Video
+ }
+ else { // Message was acked, get rid of highlighting
+//fprintf(stderr,"Setting normal\t");
+ XmTextSetHighlight(mw[mw_p].send_message_text,
+ pos+offset,
+ pos+strlen(temp2),
+ XmHIGHLIGHT_NORMAL);
+ }
+
+//fprintf(stderr,"Text: %s\n",temp2);
+
+ pos += strlen(temp2);
+
+ }
+
+ // Advance to the next record in the list
+ p_prev = p_next;
+ if (p_next != NULL)
+ p_next = p_prev->next;
+
+ } // End of while
+ } // End of if
+ else { // No messages matched, list is empty
+ }
+
+// What does this do? Move all of the text?
+// if (pos > 0) {
+// if (mw[mw_p].send_message_text != NULL) {
+// XmTextReplace(mw[mw_p].send_message_text,
+// --pos,
+// XmTextGetLastPosition(mw[mw_p].send_message_text),
+// "");
+// }
+// }
+
+ //fprintf(stderr,"Free'ing list\n");
+
+ // De-allocate the linked list
+ p_prev = head;
+ while (p_prev != NULL) {
+
+ //fprintf(stderr,"You're free!\n");
+
+ p_next = p_prev->next;
+ free(p_prev);
+ p_prev = p_next;
+ }
+
+ // Show the last added message in the window
+ XmTextShowPosition(mw[mw_p].send_message_text,
+ pos);
+ }
+ }
+ }
+
+end_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
+
+ }
+ last_message_update = sec_now();
+
+//fprintf(stderr,"Message index end: %ld\n",msg_index_end);
+
+ }
+}
+*/
+
+
+
+
+void mdelete_messages_from(char *from) {
+ long i;
+
+ // Mark message records with RECORD_NOTACTIVE. This will mark
+ // them for re-use.
+ for (i = 0; msg_index && i < msg_index_end; i++)
+ if (strcmp(msg_data[i].call_sign, _aprs_mycall) == 0
+ && strcmp(msg_data[i].from_call_sign, from) == 0)
+ msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdelete_messages_to(char *to) {
+ long i;
+
+ // Mark message records with RECORD_NOTACTIVE. This will mark
+ // them for re-use.
+ for (i = 0; msg_index && i < msg_index_end; i++)
+ if (strcmp(msg_data[i].call_sign, to) == 0)
+ msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdelete_messages(char *to_from) {
+ long i;
+
+ // Mark message records with RECORD_NOTACTIVE. This will mark
+ // them for re-use.
+ for (i = 0; msg_index && i < msg_index_end; i++)
+ if (strcmp(msg_data[i].call_sign, to_from) == 0 || strcmp(msg_data[i].from_call_sign, to_from) == 0)
+ msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdata_delete_type(const char msg_type, const time_t reference_time) {
+ long i;
+
+ // Mark message records with RECORD_NOTACTIVE. This will mark
+ // them for re-use.
+ for (i = 0; msg_index && i < msg_index_end; i++)
+
+ if ((msg_type == '\0' || msg_type == msg_data[i].type)
+ && msg_data[i].active == RECORD_ACTIVE
+ && msg_data[i].sec_heard < reference_time)
+
+ msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void check_message_remove(time_t curr_sec) { // called in timing loop
+
+ // Time to check for old messages again? (Currently every ten
+ // minutes)
+#ifdef EXPIRE_DEBUG
+ if ( last_message_remove < (curr_sec - DEBUG_MESSAGE_REMOVE_CYCLE) ) {
+#else // EXPIRE_DEBUG
+ if ( last_message_remove < (curr_sec - MESSAGE_REMOVE_CYCLE) ) {
+#endif
+
+ // Yes it is. Mark all messages that are older than
+ // sec_remove with the RECORD_NOTACTIVE flag. This will
+ // mark them for re-use.
+#ifdef EXPIRE_DEBUG
+ mdata_delete_type('\0', curr_sec-DEBUG_MESSAGE_REMOVE);
+#else // EXPIRE_DEBUG
+ mdata_delete_type('\0', curr_sec-_aprs_sec_remove);
+#endif
+
+ last_message_remove = curr_sec;
+ }
+
+ // Should we sort them at this point so that the unused ones are
+ // near the end? It looks like the message input functions do
+ // this, so I guess we don't need to do it here.
+}
+
+
+
+
+
+void mscan_file(char msg_type, void (*function)(Message *)) {
+ long i;
+
+ for (i = 0; msg_index && i < msg_index_end; i++)
+ if ((msg_type == '\0' || msg_type == msg_data[msg_index[i]].type) &&
+ msg_data[msg_index[i]].active == RECORD_ACTIVE)
+ function(&msg_data[msg_index[i]]);
+}
+
+
+
+
+
+void mprint_record(Message *m_fill) {
+/*
+ fprintf(stderr,
+ "%-9s>%-9s %s:%5s %s:%c :%s\n",
+ m_fill->from_call_sign,
+ m_fill->call_sign,
+ langcode("WPUPMSB013"), // "seq"
+ m_fill->seq,
+ langcode("WPUPMSB014"), // "type"
+ m_fill->type,
+ m_fill->message_line);
+*/
+}
+
+
+
+
+
+void mdisplay_file(char msg_type) {
+ fprintf(stderr,"\n\n");
+ mscan_file(msg_type, mprint_record);
+ fprintf(stderr,"\tmsg_index_end %ld, msg_index_max %ld\n", msg_index_end, msg_index_max);
+}
+
+
+
+
+
+int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party)
+{
+ char *temp_ptr;
+ char ipacket_message[300];
+ char message_plus_acks[MAX_MESSAGE_LENGTH + 10];
+ char from_call[MAX_CALLSIGN+1];
+ char ack[20];
+ int ok, len;
+ char addr[9+1];
+ char addr9[9+1];
+ char msg_id[5+1];
+ char orig_msg_id[5+1];
+ char ack_string[6];
+ int done;
+ int reply_ack = 0;
+ int to_my_call = 0;
+ int to_my_base_call = 0;
+ int from_my_call = 0;
+
+
+ // :xxxxxxxxx:____0-67____ message printable, except '|', '~', '{'
+ // :BLNn :____0-67____ general bulletin printable, except '|', '~'
+ // :BLNnxxxxx:____0-67____ + Group Bulletin
+ // :BLNX :____0-67____ Announcement
+ // :NWS-xxxxx:____0-67____ NWS Service Bulletin
+ // :NWS_xxxxx:____0-67____ NWS Service Bulletin
+ // :xxxxxxxxx:ackn1-5n + ack
+ // :xxxxxxxxx:rejn1-5n + rej
+ // :xxxxxxxxx:____0-67____{n1-5n + message
+ // :NTS....
+ // 01234567890123456
+ // 01234567890123456 old
+ // we get message with already extracted data ID
+
+
+ if (is_my_call(call, 1) ) { // Check SSID also
+ from_my_call++;
+ }
+
+ ack_string[0] = '\0'; // Clear out the Reply/Ack result string
+
+ len = (int)strlen(message);
+ ok = (int)(len > 9 && message[9] == ':');
+
+ if (ok) {
+
+//fprintf(stderr,"DEBUG: decode_message: from %s: %s\n", call, message);
+//return 0;
+
+ substr(addr9,message,9); // extract addressee
+ snprintf(addr,
+ sizeof(addr),
+ "%s",
+ addr9);
+ (void)remove_trailing_spaces(addr);
+
+ if (is_my_call(addr,1)) { // Check includes SSID
+ to_my_call++;
+ }
+
+ if (is_my_call(addr,0)) { // Check ignores SSID. We use
+ // this to catch messages to some
+ // of our other SSID's
+ to_my_base_call++;
+ }
+
+ message = message + 10; // pointer to message text
+
+ // Save the message text and the acks/reply-acks before we
+ // extract the acks below.
+ snprintf(message_plus_acks,
+ sizeof(message_plus_acks),
+ "%s",
+ message);
+
+ temp_ptr = strrchr(message,'{'); // look for message ID after
+ //*last* { in message.
+ msg_id[0] = '\0';
+ if (temp_ptr != NULL) {
+ substr(msg_id,temp_ptr+1,5); // extract message ID, could be non-digit
+ temp_ptr[0] = '\0'; // adjust message end (chops off message ID)
+ }
+
+ // Save the original msg_id away.
+ snprintf(orig_msg_id,
+ sizeof(orig_msg_id),
+ "%s",
+ msg_id);
+
+ // Check for Reply/Ack protocol in msg_id, which looks like
+ // this: "{XX}BB", where XX is the sequence number for the
+ // message, and BB is the ack for the previous message from
+ // my station. I've also seen this from APRS+: "{XX}B", so
+ // perhaps this is also possible "{X}B" or "{X}BB}". We can
+ // also get auto-reply responses from APRS+ that just have
+ // "}X" or "}XX" at the end. We decode those as well.
+ //
+
+ temp_ptr = strstr(msg_id,"}"); // look for Reply Ack in msg_id
+
+ if (temp_ptr != NULL) { // Found Reply/Ack protocol!
+
+ reply_ack++;
+
+// Put this code into the UI message area as well (if applicable).
+
+ // Separate out the extra ack so that we can deal with
+ // it properly.
+ snprintf(ack_string,
+ sizeof(ack_string),
+ "%s",
+ temp_ptr+1); // After the '}' character!
+
+ // Terminate it here so that rest of decode works
+ // properly. We can get duplicate messages
+ // otherwise.
+ //
+// Note that we modify msg_id here. Use orig_msg_id if we need the
+// unmodified version (full REPLY-ACK version) later.
+ //
+ temp_ptr[0] = '\0'; // adjust msg_id end
+
+ }
+ else { // Look for Reply Ack in message without sequence
+ // number
+ temp_ptr = strstr(message,"}");
+
+ if (temp_ptr != NULL) {
+ int yy = 0;
+
+ reply_ack++;
+
+// Put this code into the UI message area as well (if applicable).
+ snprintf(ack_string,
+ sizeof(ack_string),
+ "%s",
+ temp_ptr+1); // After the '}' character!
+
+ ack_string[yy] = '\0'; // Terminate the string
+
+ // Terminate it here so that rest of decode works
+ // properly. We can get duplicate messages
+ // otherwise.
+ temp_ptr[0] = '\0'; // adjust message end
+ }
+ }
+
+ done = 0;
+ }
+ else {
+ done = 1; // fall through...
+ }
+
+ len = (int)strlen(message);
+ //--------------------------------------------------------------------------
+ if (!done && len > 3 && strncmp(message,"ack",3) == 0) { // ACK
+
+ // Received an ACK packet. Note that these can carry the
+ // REPLY-ACK protocol or a single ACK sequence number plus
+ // perhaps an extra '}' on the end. They should have one of
+ // these formats:
+ // ack1 Normal ACK
+ // ackY Normal ACK
+ // ack23 Normal ACK
+ // ackfH Normal ACK
+ // ack23{ REPLY-ACK Protocol
+ // ack2Q}3d REPLY-ACK Protocol
+
+ substr(msg_id,message+3,5);
+ // fprintf(stderr,"ACK: %s: |%s| |%s|\n",call,addr,msg_id);
+
+ if (to_my_call) { // Check SSID also
+
+ // Note: This function handles REPLY-ACK protocol just
+ // fine, stripping off the 2nd ack if present. It uses
+ // only the first sequence number.
+ clear_acked_message(call,addr,msg_id); // got an ACK for me
+
+ // This one also handles REPLY-ACK protocol just fine.
+ msg_record_ack(call,addr,msg_id,0,0); // Record the ack for this message
+ }
+ else { // ACK is for another station
+ // Now if I have Igate on and I allow to retransmit station data
+ // check if this message is to a person I have heard on my TNC within an X
+ // time frame. If if is a station I heard and all the conditions are ok
+ // spit the ACK out on the TNC -FG
+/*
+ * TODO - Add igate support
+ if (operate_as_an_igate>1
+ && from==DATA_VIA_NET
+ && !from_my_call // Check SSID also
+ && port != -1) { // Not from a log file
+ char short_path[100];
+
+ shorten_path(path,short_path,sizeof(short_path));
+
+ // Only send '}' and the ack_string if it's not
+ // empty, else just end the packet with the message
+ // string. This keeps us from appending a '}' when
+ // it's not called for.
+ snprintf(ipacket_message,
+ sizeof(ipacket_message),
+ "}%s>%s,TCPIP,%s*::%s:%s",
+
+ call,
+ short_path,
+ my_callsign,
+ addr9,
+ message_plus_acks);
+
+
+ output_igate_rf(call,
+ addr,
+ path,
+ ipacket_message,
+ port,
+ third_party,
+ NULL);
+
+ igate_msgs_tx++;
+ }
+*/
+ }
+ done = 1;
+ }
+ //--------------------------------------------------------------------------
+ if (!done && len > 3 && strncmp(message,"rej",3) == 0) { // REJ
+
+ substr(msg_id,message+3,5);
+
+ if (to_my_call) { // Check SSID also
+
+ // Note: This function handles REPLY-ACK protocol just
+ // fine, stripping off the 2nd ack if present. It uses
+ // only the first sequence number.
+ clear_acked_message(call,addr,msg_id); // got an REJ for me
+
+ // This one also handles REPLY-ACK protocol just fine.
+ msg_record_rej(call,addr,msg_id); // Record the REJ for this message
+ }
+ else { // REJ is for another station
+ /* Now if I have Igate on and I allow to retransmit station data */
+ /* check if this message is to a person I have heard on my TNC within an X */
+ /* time frame. If if is a station I heard and all the conditions are ok */
+ /* spit the REJ out on the TNC */
+
+/*
+ * TODO - Add igate support
+ if (operate_as_an_igate>1
+ && from==DATA_VIA_NET
+ && !from_my_call // Check SSID also
+ && port != -1) { // Not from a log file
+ char short_path[100];
+
+ shorten_path(path,short_path,sizeof(short_path));
+
+ // Only send '}' and the rej_string if it's not
+ // empty, else just end the packet with the message
+ // string. This keeps us from appending a '}' when
+ // it's not called for.
+ snprintf(ipacket_message,
+ sizeof(ipacket_message),
+ "}%s>%s,TCPIP,%s*::%s:%s",
+
+ call,
+ short_path,
+ my_callsign,
+ addr9,
+ message_plus_acks);
+
+
+ output_igate_rf(call,
+ addr,
+ path,
+ ipacket_message,
+ port,
+ third_party,
+ NULL);
+
+ igate_msgs_tx++;
+ }
+*/
+ }
+
+ done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done && strncmp(addr,"BLN",3) == 0) { // Bulletin
+ // fprintf(stderr,"found BLN: |%s| |%s|\n",addr,message);
+ bulletin_data_add(addr,call,message,"",MESSAGE_BULLETIN,port);
+ done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done && strlen(msg_id) > 0 && to_my_call) { // Message for me (including SSID check)
+ // with msg_id (sequence number)
+ time_t last_ack_sent;
+ long record;
+
+
+// Remember to put this code into the UI message area as well (if
+// applicable).
+
+ // Check for Reply/Ack
+ if (reply_ack && strlen(ack_string) != 0) { // Have a free-ride ack to deal with
+
+ clear_acked_message(call,addr,ack_string); // got an ACK for me
+
+ msg_record_ack(call,addr,ack_string,0,0); // Record the ack for this message
+ }
+
+ // Save the ack 'cuz we might need it while talking to this
+ // station. We need it to implement Reply/Ack protocol.
+
+// Note that msg_id has already been truncated by this point.
+// orig_msg_id contains the full REPLY-ACK text.
+
+//fprintf(stderr, "store_most_recent_ack()\n");
+ store_most_recent_ack(call,msg_id);
+
+ // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
+ last_ack_sent = msg_data_add(addr,
+ call,
+ message,
+ msg_id,
+ MESSAGE_MESSAGE,
+ port,
+ &record); // id_fixed
+
+ // Here we need to know if it is a new message or an old.
+ // If we've already received it, we don't want to kick off
+ // the alerts or pop up the Send Message dialog again. If
+ // last_ack_sent == (time_t)0, then it is a new message.
+ //
+ if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before
+
+ new_message_data += 1;
+
+ // Note that the check_popup_window() function will
+ // re-create a Send Message dialog if one doesn't exist
+ // for this QSO. Only call it for the first message
+ // line or the first ack, not for any repeats.
+ //
+ (void)check_popup_window(call, 2); // Calls update_messages()
+
+ //update_messages(1); // Force an update
+
+
+ }
+
+ // Try to only send an ack out once per 30 seconds at the
+ // fastest.
+//WE7U
+// Does this 30-second check work?
+ //
+ if ( (last_ack_sent != (time_t)-1l) // Not an error
+ && (last_ack_sent + 30 ) < sec_now()
+ && !satellite_ack_mode // Disable separate ack's for satellite work
+ && port != -1 ) { // Not from a log file
+
+ char path[MAX_LINE_SIZE+1];
+
+ // Update the last_ack_sent field for the message
+ msg_update_ack_stamp(record);
+
+ pad_callsign(from_call,call); /* ack the message */
+
+
+ // Attempt to snag a custom path out of the Send Message
+ // dialog, if set. If not set, path will contain '\0';
+ get_send_message_path(call, path, MAX_LINE_SIZE+1);
+//fprintf(stderr,"Path: %s\n", path);
+
+
+ // In this case we want to send orig_msg_id back, not
+ // the (possibly) truncated msg_id. This is per Bob B's
+ // Reply/Ack spec, sent to xastir-dev on Nov 14, 2001.
+ snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,orig_msg_id);
+
+//WE7U
+// Need to figure out the reverse path for this one instead of
+// passing a NULL for the path? Probably not, as auto-calculation
+// of paths isn't a good idea.
+//
+// What we need to do here is check whether we have a custom path
+// set for this QSO. If so, pass that path along as the transmit
+// path. messages.h:Message_Window struct has the send_message_path
+// variable in it. If a Message_Window still exists for this QSO
+// then we can snag the user-entered path from there. If the struct
+// has already been destroyed then we have nowhere to snag the
+// custom path from and have to rely on the default paths in each
+// interface properties dialog instead. Then again, we _could_ snag
+// the path out of the last received message in the message database
+// for that case. Might be better to disable the Close button, or
+// warn the user that the custom path will be lost if they close the
+// Send Message dialog.
+
+
+ // Send out the immediate ACK
+ if (path[0] == '\0')
+ transmit_message_data(call,ack,NULL);
+ else
+ transmit_message_data(call,ack,path);
+
+
+ if (record != -1l) { // Msg we've received before
+
+ // It's a message that we've received before,
+ // consider sending an extra ACK in about 30 seconds
+ // to try to get it to the remote station. Perhaps
+ // another one in 60 seconds as well.
+
+ if (path[0] == '\0') {
+ transmit_message_data_delayed(call,ack,NULL,sec_now()+30);
+ transmit_message_data_delayed(call,ack,NULL,sec_now()+60);
+ transmit_message_data_delayed(call,ack,NULL,sec_now()+120);
+ }
+ else {
+ transmit_message_data_delayed(call,ack,path,sec_now()+30);
+ transmit_message_data_delayed(call,ack,path,sec_now()+60);
+ transmit_message_data_delayed(call,ack,path,sec_now()+120);
+ }
+ }
+
+/*
+ * TODO
+ if (auto_reply == 1) {
+
+ snprintf(ipacket_message,
+ sizeof(ipacket_message), "AA:%s", auto_reply_message);
+
+ if (!from_my_call) // Check SSID also
+ output_message(my_callsign, call, ipacket_message, "");
+ }
+*/
+ }
+
+
+ done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done && strlen(msg_id) == 0 && to_my_call) { // Message for me (including SSID check)
+ // but without message-ID.
+ // These should appear in a Send Message dialog and should
+ // NOT get ack'ed. Kenwood radios send this message type as
+ // an auto-answer or a buffer-full message. They look
+ // something like:
+ //
+ // :WE7U-13 :Not at keyboard.
+ //
+
+ time_t last_ack_sent;
+ long record;
+
+
+ if (len > 2
+ && message[0] == '?'
+ && port != -1 // Not from a log file
+ && to_my_call) { // directed query (check SSID also)
+ // Smallest query known is "?WX".
+ done = process_directed_query(call,path,message+1,port);
+ }
+
+ // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
+ last_ack_sent = msg_data_add(addr,
+ call,
+ message,
+ msg_id,
+ MESSAGE_MESSAGE,
+ port,
+ &record); // id_fixed
+
+ // Here we need to know if it is a new message or an old.
+ // If we've already received it, we don't want to kick off
+ // the alerts or pop up the Send Message dialog again. If
+ // last_ack_sent == (time_t)0, then it is a new message.
+ //
+ if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before
+
+ new_message_data += 1;
+
+ // Note that the check_popup_window() function will
+ // re-create a Send Message dialog if one doesn't exist
+ // for this QSO. Only call it for the first message
+ // line or the first ack, not for any repeats.
+ //
+//fprintf(stderr,"***check_popup_window 1\n");
+ (void)check_popup_window(call, 2); // Calls update_messages()
+
+ //update_messages(1); // Force an update
+
+ }
+
+ // Update the last_ack_sent field for the message, even
+ // though we won't be sending an ack in response.
+ msg_update_ack_stamp(record);
+
+
+ done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done
+ && ( (strncmp(addr,"NWS-",4) == 0) // NWS weather alert
+ || (strncmp(addr,"NWS_",4) == 0) ) ) { // NWS weather alert compressed
+
+ // could have sort of line number
+ //fprintf(stderr,"found NWS: |%s| |%s| |%s|\n",addr,message,msg_id);
+
+ (void)alert_data_add(addr,
+ call,
+ message,
+ msg_id,
+ MESSAGE_NWS,
+ port);
+
+ done = 1;
+
+/*
+ * TODO - add igate support
+ if (operate_as_an_igate>1
+ && from==DATA_VIA_NET
+ && !from_my_call // Check SSID also
+ && port != -1) { // Not from a log file
+ char short_path[100];
+
+ shorten_path(path,short_path,sizeof(short_path));
+
+ snprintf(ipacket_message,
+ sizeof(ipacket_message),
+ "}%s>%s,TCPIP,%s*::%s:%s",
+ call,
+ short_path,
+ my_callsign,
+ addr9,
+ message);
+
+ output_nws_igate_rf(call,
+ path,
+ ipacket_message,
+ port,
+ third_party);
+ }
+*/
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done && strncmp(addr,"SKY",3) == 0) { // NWS weather alert additional info
+
+ // could have sort of line number
+ //fprintf(stderr,"found SKY: |%s| |%s| |%s|\n",addr,message,msg_id);
+
+ // We don't wish to record these in memory. They cause an infinite
+ // loop in the current code and a massive memory leak.
+ return(1); // Tell the calling program that the packet was ok so
+ // that it doesn't add it with data_add() itself!
+
+
+ done = 1;
+/*
+ * TODO - add igate support
+ if (operate_as_an_igate>1
+ && from==DATA_VIA_NET
+ && !from_my_call // Check SSID also
+ && port != -1) { // Not from a log file
+ char short_path[100];
+
+ shorten_path(path,short_path,sizeof(short_path));
+
+ snprintf(ipacket_message,
+ sizeof(ipacket_message),
+ "}%s>%s,TCPIP,%s*::%s:%s",
+ call,
+ short_path,
+ my_callsign,
+ addr9,
+ message);
+
+ output_nws_igate_rf(call,
+ path,
+ ipacket_message,
+ port,
+ third_party);
+ }
+*/
+ }
+
+ //--------------------------------------------------------------------------
+ if (!done && strlen(msg_id) > 0) { // Other message with linenumber. This
+ // is either a message for someone else
+ // or a message for another one of my
+ // SSID's.
+ long record_out;
+ time_t last_ack_sent;
+ char message_plus_note[MAX_MESSAGE_LENGTH + 30];
+
+
+ if (to_my_base_call && !from_my_call) {
+ // Special case: We saw a message w/msg_id that was to
+ // one of our other SSID's, but it was not from
+ // ourselves. That last bit (!from_my_call) is
+ // important in the case where we're working an event
+ // with several stations using the same callsign.
+ //
+ // Store as if it came to my callsign, with a zeroed-out
+ // msg_id so we can't try to ack it. We also need some
+ // other indication in the "Send Message" dialog as to
+ // what's happening. Perhaps add the original callsign
+ // to the message itself in a note at the start?
+ //
+ snprintf(message_plus_note,
+ sizeof(message_plus_note),
+ "(Sent to:%s) %s",
+ addr,
+ message);
+ last_ack_sent = msg_data_add(_aprs_mycall,
+ call,
+ message_plus_note,
+ "",
+ MESSAGE_MESSAGE,
+ port,
+ &record_out);
+ }
+ else { // Normal case, messaging between other people
+ last_ack_sent = msg_data_add(addr,
+ call,
+ message,
+ msg_id,
+ MESSAGE_MESSAGE,
+ port,
+ &record_out);
+ }
+
+ new_message_data += look_for_open_group_data(addr);
+
+ // Note that the check_popup_window() function will
+ // re-create a Send Message dialog if one doesn't exist for
+ // this QSO. Only call it for the first message line or the
+ // first ack, not for any repeats.
+ //
+ if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before
+
+ // Callsign check here also checks SSID for exact match
+// We need to do an SSID-non-specific check here so that we can pick
+// up messages intended for other stations of ours.
+
+ if ((to_my_base_call && check_popup_window(call, 2) != -1)
+ || check_popup_window(call, 0) != -1
+ || check_popup_window(addr, 1) != -1) {
+ update_messages(1); // Force an update
+ }
+ }
+
+ /* Now if I have Igate on and I allow to retransmit station data */
+ /* check if this message is to a person I have heard on my TNC within an X */
+ /* time frame. If if is a station I heard and all the conditions are ok */
+ /* spit the message out on the TNC -FG */
+ /*
+ * TODO - add igate support
+ if (operate_as_an_igate>1
+ && last_ack_sent != (time_t)-1l
+ && from==DATA_VIA_NET
+ && !from_my_call // Check SSID also
+ && !to_my_call // Check SSID also
+ && port != -1) { // Not from a log file
+ char short_path[100];
+
+ shorten_path(path,short_path,sizeof(short_path));
+ snprintf(ipacket_message,
+ sizeof(ipacket_message),
+ "}%s>%s,TCPIP,%s*::%s:%s",
+ call,
+ short_path,
+ my_callsign,
+ addr9,
+ message_plus_acks);
+
+
+ output_igate_rf(call,
+ addr,
+ path,
+ ipacket_message,
+ port,
+ third_party,
+ NULL);
+
+ igate_msgs_tx++;
+ }
+*/
+ done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+
+ if (!done) { // message without line number
+ long record_out;
+ time_t last_ack_sent;
+
+
+ last_ack_sent = msg_data_add(addr,
+ call,
+ message,
+ "",
+ MESSAGE_MESSAGE,
+ port,
+ &record_out);
+
+ new_message_data++; // ??????
+
+ // Note that the check_popup_window() function will
+ // re-create a Send Message dialog if one doesn't exist for
+ // this QSO. Only call it for the first message line or the
+ // first ack, not for any repeats.
+ //
+ if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before
+ if (check_popup_window(addr, 1) != -1) {
+ //update_messages(1); // Force an update
+ }
+ }
+
+ // Could be response to a query. Popup a messsage.
+
+// Check addr for my_call and !third_party, then check later in the
+// packet for my_call if it is a third_party message? Depends on
+// what the packet looks like by this point.
+ if ( last_ack_sent != (time_t)-1l
+ && (message[0] != '?')
+ && to_my_call ) { // Check SSID also
+
+ // We no longer wish to have both popups and the Send
+ // Group Message dialogs come up for every query
+ // response, so we use popup_message() here instead of
+ // popup_message_always() so that by default we'll see
+ // the below message in STDERR. If --with-errorpopups
+ // has been configured in, we'll get a popup as well.
+ // Send Group Message dialogs work well for multi-line
+ // query responses, so we'll leave it that way.
+ //
+// TODO
+// popup_message(langcode("POPEM00018"),message);
+
+ // Check for Reply/Ack. APRS+ sends an AA: response back
+ // for auto-reply, with an embedded free-ride Ack.
+ if (strlen(ack_string) != 0) { // Have an extra ack to deal with
+
+ clear_acked_message(call,addr,ack_string); // got an ACK for me
+
+ msg_record_ack(call,addr,ack_string,0,0); // Record the ack for this message
+ }
+ }
+
+ // done = 1;
+ }
+
+ //--------------------------------------------------------------------------
+
+ if (ok)
+ (void)data_add(STATION_CALL_DATA,
+ call,
+ path,
+ message,
+ port,
+ NULL,
+ third_party,
+ 0, // Not a packet from my station
+ 0); // Not my object/item
+
+
+ return(ok);
+}
+
+#endif //INCLUDE_APRS
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
+ */
+
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_MESSAGE
+#define MAEMO_MAPPER_APRS_MESSAGE
+
+#define MSG_INCREMENT 200
+
+#include "types.h"
+
+
+// Used for messages and bulletins
+typedef struct {
+ char active;
+ TAprsPort port;
+ char type;
+ char heard_via_tnc;
+ time_t sec_heard;
+ time_t last_ack_sent;
+ char packet_time[MAX_TIME];
+ char call_sign[MAX_CALLSIGN+1];
+ char from_call_sign[MAX_CALLSIGN+1];
+ char message_line[MAX_MESSAGE_LENGTH+1];
+ char seq[MAX_MESSAGE_ORDER+1];
+ char acked;
+ char position_known;
+ time_t interval;
+ int tries;
+} Message;
+
+
+
+// Struct used to create linked list of most recent ack's
+typedef struct _ack_record {
+ char callsign[MAX_CALLSIGN+1];
+ char ack[5+1];
+ struct _ack_record *next;
+} ack_record;
+
+
+
+
+int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party);
+
+
+extern Message *msg_data;
+extern long msg_index_end;
+extern long *msg_index;
+#endif
+
+#endif //INCLUDE_APRS
cmenu_show_latlon(gint unitx, gint unity)
{
gdouble lat, lon;
+ gint tmp_degformat = _degformat;
+ gint fallback_deg_format = _degformat;
gchar buffer[80], tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
+
printf("%s()\n", __PRETTY_FUNCTION__);
unit2latlon(unitx, unity, lat, lon);
- lat_format(lat, tmp1);
- lon_format(lon, tmp2);
-
- snprintf(buffer, sizeof(buffer),
- "%s: %s\n"
- "%s: %s",
- _("Latitude"), tmp1,
- _("Longitude"), tmp2);
-
+
+ // Check that the current coord system supports the select position
+ if(!coord_system_check_lat_lon (lat, lon, &fallback_deg_format))
+ {
+ _degformat = fallback_deg_format;
+ }
+
+ format_lat_lon(lat, lon, tmp1, tmp2);
+
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ snprintf(buffer, sizeof(buffer),
+ "%s: %s\n"
+ "%s: %s",
+ DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1, tmp1,
+ DEG_FORMAT_ENUM_TEXT[_degformat].long_field_2, tmp2);
+ }
+ else
+ {
+ snprintf(buffer, sizeof(buffer),
+ "%s: %s",
+ DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1, tmp1);
+ }
+
MACRO_BANNER_SHOW_INFO(_window, buffer);
+ _degformat = tmp_degformat;
+
vprintf("%s(): return\n", __PRETTY_FUNCTION__);
}
/* Setup the map context menu. */
gtk_menu_append(submenu, _cmenu_loc_show_latlon_item
- = gtk_menu_item_new_with_label(_("Show Lat/Lon")));
+ = gtk_menu_item_new_with_label(_("Show Position")));
gtk_menu_append(submenu, gtk_separator_menu_item_new());
gtk_menu_append(submenu, _cmenu_loc_distance_to_item
= gtk_menu_item_new_with_label(_("Show Distance to")));
submenu = gtk_menu_new());
gtk_menu_append(submenu, _cmenu_way_show_latlon_item
- = gtk_menu_item_new_with_label(_("Show Lat/Lon")));
+ = gtk_menu_item_new_with_label(_("Show Position")));
gtk_menu_append(submenu, _cmenu_way_show_desc_item
= gtk_menu_item_new_with_label(_("Show Description")));
gtk_menu_append(submenu, _cmenu_way_clip_latlon_item
- = gtk_menu_item_new_with_label(_("Copy Lat/Lon")));
+ = gtk_menu_item_new_with_label(_("Copy Position")));
gtk_menu_append(submenu, _cmenu_way_clip_desc_item
= gtk_menu_item_new_with_label(_("Copy Description")));
gtk_menu_append(submenu, gtk_separator_menu_item_new());
* along with Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
*/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#define _GNU_SOURCE
#include "types.h"
{0, 0x0000, 0xa000, 0x0000}, /* COLORABLE_ROUTE */
{0, 0x0000, 0x8000, 0x0000}, /* COLORABLE_ROUTE_WAY */
{0, 0x0000, 0x6000, 0x0000}, /* COLORABLE_ROUTE_BREAK */
- {0, 0xa000, 0x0000, 0xa000} /* COLORABLE_POI */
+ {0, 0xa000, 0x0000, 0xa000}, /* COLORABLE_POI */
+ {0, 0xa000, 0x0000, 0xa000} /* COLORABLE_APRS_STATION */
};
-gchar *DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
+CoordFormatSetup DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
GList *_repo_list = NULL;
RepoData *_curr_repo = NULL;
+// APRS config
+#ifdef INCLUDE_APRS
+
+gboolean _aprs_enable = FALSE; // APRS support is enabled
+gboolean _aprs_inet_enable = FALSE; // Connection to APRS server is enabled
+gboolean _aprs_tty_enable = FALSE;
+
+gchar * _aprs_tnc_bt_mac = NULL;
+TTncConnection _aprs_tnc_method = TNC_CONNECTION_BT;
+
+gchar * _aprs_tty_port = NULL;
+gchar * _aprs_server = NULL; // Server address
+guint _aprs_server_port = 23; // Server port
+gint _aprs_std_pos_hist = 1; // Not currently implemented, so fix as 1
+gint _aprs_max_stations = 200; // 0 - no limit
+gchar * _aprs_inet_server_validation = NULL; // -1 if not known
+
+gboolean _aprs_enable_inet_tx = FALSE;
+gboolean _aprs_enable_tty_tx = FALSE;
+gchar * _aprs_mycall = NULL; // My APRS call
+gchar * _aprs_beacon_comment = NULL; // My TNC Aprs Beacon comment
+
+gint _aprs_inet_beacon_interval = 0; // 0 = disabled
+gint _aprs_tty_beacon_interval = 0; // 0 = disabled
+gchar * _aprs_inet_beacon_comment = NULL;
+gboolean _aprs_transmit_compressed_posit = TRUE;
+gboolean _aprs_show_new_station_alert = TRUE;
+time_t _aprs_sec_remove; /* Station removed after */
+ConnState _aprs_inet_state; // State of APRS iNet connection
+ConnState _aprs_tty_state;
+gint _aprs_server_auto_filter_km = 100;
+gboolean _aprs_server_auto_filter_on = FALSE;
+//GtkWidget *_aprs_connect_banner = NULL;
+gchar * _aprs_unproto_path = NULL;
+gchar _aprs_beacon_group;
+gchar _aprs_beacon_symbol;
+
+#endif // INCLUDE_APRS
+//----------------------
+
+
/** POI */
gchar *_poi_db_filename = NULL;
gchar *_poi_db_dirname = NULL;
GtkWidget *_menu_gps_details_item = NULL;
GtkWidget *_menu_gps_reset_item = NULL;
+#ifdef INCLUDE_APRS
+/* Menu items for the "APRS" menu items. */
+GtkWidget *_menu_aprs_settings_item = NULL;
+GtkWidget *_menu_enable_aprs_inet_item = NULL;
+GtkWidget *_menu_enable_aprs_tty_item = NULL;
+GtkWidget *_menu_list_aprs_stations_item = NULL;
+GtkWidget *_menu_list_aprs_messages_item = NULL;
+#endif // INCLUDE_APRS
+
/* Menu items for the other menu items. */
GtkWidget *_menu_settings_item = NULL;
GtkWidget *_menu_help_item = NULL;
#ifndef MAEMO_MAPPER_DATA_H
#define MAEMO_MAPPER_DATA_H
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#include <libosso.h>
+#include "types.h"
/* Constants regarding enums and defaults. */
extern gchar *UNITS_ENUM_TEXT[UNITS_ENUM_COUNT];
extern CustomAction CUSTOM_KEY_DEFAULT[CUSTOM_KEY_ENUM_COUNT];
extern gchar *COLORABLE_GCONF[COLORABLE_ENUM_COUNT];
extern GdkColor COLORABLE_DEFAULT[COLORABLE_ENUM_COUNT];
-extern gchar *DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
+extern CoordFormatSetup DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
extern gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
extern gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
* ABOVE: CONTEXT MENU ITEMS *
*****************************/
+#ifdef INCLUDE_APRS
+
+extern gboolean _aprs_enable;
+extern gboolean _aprs_inet_enable;
+extern gboolean _aprs_tty_enable;
+extern gchar * _aprs_server;
+extern gchar * _aprs_tty_port;
+extern guint _aprs_server_port;
+extern gint _aprs_std_pos_hist;
+extern gint _aprs_max_stations;
+extern gchar * _aprs_inet_server_validation;
+extern gchar * _aprs_beacon_comment;
+extern gboolean _aprs_transmit_compressed_posit;
+extern gint _aprs_inet_beacon_interval;
+extern gint _aprs_tty_beacon_interval;
+extern gchar * _aprs_inet_beacon_comment;
+extern gchar _aprs_beacon_group;
+extern gchar _aprs_beacon_symbol;
+
+extern gboolean _aprs_enable_inet_tx;
+extern gboolean _aprs_enable_tty_tx;
+extern gchar * _aprs_mycall;
+extern gchar * _aprs_beacon_comment;
+
+extern gboolean _aprs_show_new_station_alert;
+extern time_t _aprs_sec_remove;
+extern GtkWidget *_menu_enable_aprs_inet_item;
+extern GtkWidget *_menu_enable_aprs_tty_item;
+extern GtkWidget *_menu_list_aprs_stations_item;
+extern GtkWidget *_menu_list_aprs_messages_item;
+extern GtkWidget *_menu_aprs_settings_item;
+extern ConnState _aprs_inet_state;
+extern ConnState _aprs_tty_state;
+extern gchar * _aprs_tnc_bt_mac;
+extern TTncConnection _aprs_tnc_method;
+extern gint _aprs_server_auto_filter_km;
+extern gboolean _aprs_server_auto_filter_on;
+//extern GtkWidget *_aprs_connect_banner;
+extern gchar * _aprs_unproto_path;
+
+#endif // INCLUDE_APRS
+
#endif /* ifndef MAEMO_MAPPER_DATA_H */
#define EARTH_RADIUS (3443.91847)
+/* BT dbus service location */
+#define BASE_PATH "/org/bluez"
+#define BASE_INTERFACE "org.bluez"
+//#define ADAPTER_PATH BASE_PATH
+#define ADAPTER_INTERFACE BASE_INTERFACE ".Adapter"
+#define MANAGER_PATH BASE_PATH
+#define MANAGER_INTERFACE BASE_INTERFACE ".Manager"
+#define ERROR_INTERFACE BASE_INTERFACE ".Error"
+#define SECURITY_INTERFACE BASE_INTERFACE ".Security"
+#define RFCOMM_INTERFACE BASE_INTERFACE ".RFCOMM"
+#define BLUEZ_DBUS BASE_INTERFACE
+
+#define LIST_ADAPTERS "ListAdapters"
+#define LIST_BONDINGS "ListBondings"
+//#define CREATE_BONDING "CreateBonding"
+#define GET_REMOTE_NAME "GetRemoteName"
+#define GET_REMOTE_SERVICE_CLASSES "GetRemoteServiceClasses"
+
+#define BTCOND_PATH "/com/nokia/btcond/request"
+#define BTCOND_BASE "com.nokia.btcond"
+#define BTCOND_INTERFACE BTCOND_BASE ".request"
+#define BTCOND_REQUEST BTCOND_INTERFACE
+#define BTCOND_CONNECT "rfcomm_connect"
+#define BTCOND_DISCONNECT "rfcomm_disconnect"
+#define BTCOND_DBUS BTCOND_BASE
+
+
/** MAX_ZOOM defines the largest map zoom level we will download.
* (MAX_ZOOM - 1) is the largest map zoom level that the user can zoom to.
*/
#define ARRAY_CHUNK_SIZE (1024)
#define BUFFER_SIZE (2048)
+#define APRS_BUFFER_SIZE (4096)
+#define APRS_MAX_COMMENT 80
+#define APRS_CONVERSE_MODE "k"
#define GPSD_PORT_DEFAULT (2947)
_view_height_pixels)
/* Render all on-map metadata an annotations, including POI and paths. */
+#ifdef INCLUDE_APRS
+#define MACRO_MAP_RENDER_DATA() { \
+ if(_show_poi) \
+ map_render_poi(); \
+ if(_show_paths > 0) \
+ map_render_paths(); \
+ if(_aprs_enable) \
+ map_render_aprs(); \
+}
+#else
#define MACRO_MAP_RENDER_DATA() { \
if(_show_poi) \
map_render_poi(); \
if(_show_paths > 0) \
map_render_paths(); \
}
+#endif //INCLUDE_APRS
#define UNBLANK_SCREEN(MOVING, APPROACHING_WAYPOINT) { \
/* Check if we need to unblank the screen. */ \
#include <stdlib.h>
#include <string.h>
#include <math.h>
+#include "aprs.h"
+#include "aprs_decode.h"
+#include "types.h"
#ifndef LEGACY
# include <hildon/hildon-help.h>
PangoLayout *_sat_details_layout = NULL;
PangoLayout *_sat_details_expose_layout = NULL;
+#ifdef INCLUDE_APRS
+PangoLayout *_aprs_label_layout = NULL;
+#endif // INCLUDE_APRS
+
#define SCALE_WIDTH (100)
static gboolean
return TRUE;
}
+#ifdef INCLUDE_APRS
+//// START OF APRS CODE
+gint add_aprs_label(gchar *label, gint label_len, gint x, gint y)
+{
+ gint height = 0;
+ gint width = 0;
+ pango_layout_set_text(_aprs_label_layout, label, label_len);
+
+ pango_layout_get_pixel_size(_aprs_label_layout, &width, &height);
+
+ /* Draw the layout itself. */
+ gdk_draw_layout( _map_pixmap,
+ _gc[COLORABLE_APRS_STATION],
+ x,
+ y - (gint)((gfloat)height/2.0f),
+ _aprs_label_layout);
+
+ return width;
+}
+
+gboolean extract_aprs_symbol(const gchar symbol, const char primary, GdkPixbuf **pixbuf,
+ gint *symbol_size,
+ gint *symbol_column,
+ gint *symbol_row)
+{
+// GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ gchar filename[100];
+
+
+ const gint startSymbol = ' ';
+ const gint symbols_per_column = 16;
+ const gint symbols_per_row = 6;
+ /*const gint*/ *symbol_size = 96/symbols_per_row;
+
+ gint symbol_number = (gint)symbol - startSymbol;
+
+ if(symbol_number<0 || symbol_number > symbols_per_row * symbols_per_column) symbol_number = 0;
+
+ if(primary == '/') snprintf(filename, sizeof(filename), "/usr/share/icons/hicolor/scalable/hildon/allicons.png");
+ else snprintf(filename, sizeof(filename), "/usr/share/icons/hicolor/scalable/hildon/allicon2.png");
+
+ *symbol_column = (gint)(symbol_number/symbols_per_column);
+ *symbol_row = symbol_number - ((*symbol_column)*symbols_per_column);
+
+
+ *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
+
+
+ if(error)
+ {
+ return FALSE;
+ }
+
+ if( (*symbol_size)*(*symbol_column) < 0 || (*symbol_size)*(*symbol_row) < 0 )
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+//
+gboolean draw_aprs_symbol(const gchar symbol, const gchar primary, const gint poix, const gint poiy, gint* imageSize)
+{
+ GdkPixbuf *pixbuf = NULL;
+
+ gint symbol_column = 0;
+ gint symbol_row = 0;
+ gint symbol_size = 0;
+
+ if(!extract_aprs_symbol(symbol, primary, &pixbuf,
+ &symbol_size,
+ &symbol_column,
+ &symbol_row))
+ return FALSE;
+
+
+ /* We found an icon to draw. */
+ gdk_draw_pixbuf(
+ _map_pixmap,
+ _gc[COLORABLE_POI],
+ pixbuf,
+ symbol_size*symbol_column, symbol_size*symbol_row,
+ poix - symbol_size / 2,
+ poiy - symbol_size / 2,
+ symbol_size, symbol_size,
+ GDK_RGB_DITHER_NONE, 0, 0);
+ g_object_unref(pixbuf);
+
+ *imageSize = symbol_size;
+
+
+ return TRUE;
+}
+
+
+void plot_aprs_station(AprsDataRow *p_station, gboolean single )
+{
+ if(_poi_zoom <= _zoom) return ;
+
+ gint image_size = 0;
+ gdouble lat1 = 0;
+ gdouble lon1 = 0;
+ gint poix, poiy;
+ gint unitx, unity;
+
+ // TODO - if this station has been ploted before, then redraw the map at that position
+ /*
+ if(p_station->newest_trackpoint != NULL)
+ {
+ AprsTrackRow *pPreviousPoint = p_station->newest_trackpoint;
+
+ lat1 = convert_lat_l2d(pPreviousPoint->trail_lat_pos);
+ lon1 = convert_lon_l2d(pPreviousPoint->trail_long_pos);
+
+ latlon2unit(lat1, lon1, unitx, unity);
+ unit2buf(unitx, unity, poix, poiy);
+ fprintf(stderr, "Removing old pos.\n");
+
+ poix = poix - 40;
+ poiy = poiy - 25;
+ if(poix<0) poix = 0;
+ if(poiy<0) poiy = 0;
+
+ if(poix+100 > _view_width_pixels) poix = _view_width_pixels;
+ if(poiy+50 > _view_height_pixels) poiy = _view_height_pixels;
+
+ gdk_draw_pixbuf(
+ _map_pixmap,
+ _map_widget->style->fg_gc[GTK_STATE_ACTIVE],
+ _map_pixbuf,
+ poix, poiy,
+ poix, poiy,
+ 100, 50,
+ GDK_RGB_DITHER_NONE, 0, 0);
+
+ // TODO - this will only redraw the background, not any POI´s or other items
+ }
+*/
+ lat1 = convert_lat_l2d(p_station->coord_lat);
+ lon1 = convert_lon_l2d(p_station->coord_lon);
+
+ latlon2unit(lat1, lon1, unitx, unity);
+ unit2buf(unitx, unity, poix, poiy);
+
+ // Ignore this station if it's position is not on the screen
+ if(poix < 0 || poix > _view_width_pixels
+ || poiy < 0 || poiy > _view_height_pixels)
+ return ;
+
+ gchar label[MAX_CALLSIGN+1];
+ snprintf(label, sizeof(label), "%s", p_station->call_sign);
+
+ if(!draw_aprs_symbol(p_station->aprs_symbol.aprs_symbol, p_station->aprs_symbol.aprs_type, poix, poiy, &image_size))
+ {
+ /* No icon for POI or for category or default POI icon file.
+ Draw default purple square. */
+ gdk_draw_rectangle(_map_pixmap, _gc[COLORABLE_APRS_STATION], TRUE,
+ poix - (gint)(1.5f * _draw_width),
+ poiy - (gint)(1.5f * _draw_width),
+ 3 * _draw_width,
+ 3 * _draw_width);
+
+ image_size = 3 * _draw_width;
+ }
+
+ gint label_width = 0;
+
+ gint _aprs_label_zoom = _poi_zoom - 1;
+
+ if(_aprs_label_zoom > _zoom)
+ {
+ label_width = add_aprs_label(label, strlen(label), poix + image_size, poiy);
+ }
+
+ if(single)
+ {
+ gint offset = (gint)((gfloat)image_size/2.0f);
+
+ gtk_widget_queue_draw_area(
+ _map_widget,
+ poix - offset,
+ poiy - offset,
+ (2*image_size) + label_width,
+ image_size
+ );
+ }
+
+
+}
+#endif // INCLUDE_APRS
+
+//// END OF APRS CODE
+
static void
speed_limit(void)
{
gboolean
gps_display_details(void)
{
- gchar *buffer, litbuf[16];
+ gchar *buffer, litbuf[LL_FMT_LEN], buffer2[LL_FMT_LEN];
printf("%s()\n", __PRETTY_FUNCTION__);
if(_gps.fix < 2)
{
gfloat speed = _gps.speed * UNITS_CONVERT[_units];
+ format_lat_lon(_gps.lat, _gps.lon, litbuf, buffer2);
+
/* latitude */
- lat_format(_gps.lat, litbuf);
+ //lat_format(_gps.lat, litbuf);
gtk_label_set_label(GTK_LABEL(_sdi_lat), litbuf);
/* longitude */
- lon_format(_gps.lon, litbuf);
- gtk_label_set_label(GTK_LABEL(_sdi_lon), litbuf);
+ //lon_format(_gps.lon, litbuf);
+ //gtk_label_set_label(GTK_LABEL(_sdi_lon), litbuf);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_label(GTK_LABEL(_sdi_lon), buffer2);
/* speed */
switch(_units)
void
gps_display_data(void)
{
- gchar *buffer, litbuf[LL_FMT_LEN];
+ gchar *buffer, litbuf[LL_FMT_LEN], buffer2[LL_FMT_LEN];
printf("%s()\n", __PRETTY_FUNCTION__);
if(_gps.fix < 2)
{
gfloat speed = _gps.speed * UNITS_CONVERT[_units];
+ format_lat_lon(_gps.lat, _gps.lon, litbuf, buffer2);
+
/* latitude */
- lat_format(_gps.lat, litbuf);
+ //lat_format(_gps.lat, litbuf);
gtk_label_set_label(GTK_LABEL(_text_lat), litbuf);
/* longitude */
- lon_format(_gps.lon, litbuf);
- gtk_label_set_label(GTK_LABEL(_text_lon), litbuf);
+ //lon_format(_gps.lon, litbuf);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_label(GTK_LABEL(_text_lon), buffer2);
/* speed */
switch(_units)
{
gint old = _degformat; /* augh... */
gchar buffer[LL_FMT_LEN];
+ gchar buffer2[LL_FMT_LEN];
_degformat = fmt;
- lat_format(lld->glat, buffer);
+
+ format_lat_lon(lld->glat, lld->glon, buffer, buffer2);
+
+ //lat_format(lld->glat, buffer);
gtk_label_set_label(GTK_LABEL(lld->lat), buffer);
- lon_format(lld->glon, buffer);
- gtk_label_set_label(GTK_LABEL(lld->lon), buffer);
+ //lon_format(lld->glon, buffer);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_label(GTK_LABEL(lld->lon), buffer2);
+ else
+ gtk_label_set_label(GTK_LABEL(lld->lon), g_strdup(""));
+
+
+ // And set the titles
+ gtk_label_set_label(GTK_LABEL(lld->lat_title),
+ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1 );
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_label(GTK_LABEL(lld->lon_title),
+ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2 );
+ else
+ gtk_label_set_label(GTK_LABEL(lld->lon_title), g_strdup(""));
+
+
_degformat = old;
}
}
GtkWidget *dialog;
GtkWidget *table;
GtkWidget *label;
+ GtkWidget *lbl_lon_title;
+ GtkWidget *lbl_lat_title;
GtkWidget *txt_lat;
GtkWidget *txt_lon;
GtkWidget *cmb_format;
GtkWidget *btn_copy = NULL;
+ gint prev_degformat = _degformat;
+ gint fallback_deg_format = _degformat;
+
printf("%s()\n", __PRETTY_FUNCTION__);
+ // Check that the current coord system supports the select position
+ if(!coord_system_check_lat_lon (lat, lon, &fallback_deg_format))
+ {
+ _degformat = fallback_deg_format;
+ }
+
+
dialog = gtk_dialog_new_with_buttons(_("Show Position"),
GTK_WINDOW(_window), GTK_DIALOG_MODAL,
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
table = gtk_table_new(5, 2, FALSE), TRUE, TRUE, 0);
gtk_table_attach(GTK_TABLE(table),
- label = gtk_label_new(_("Lat")),
+ lbl_lat_title = gtk_label_new(/*_("Lat")*/ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1 ),
0, 1, 0, 1, GTK_FILL, 0, 2, 4);
- gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+ gtk_misc_set_alignment(GTK_MISC(lbl_lat_title), 1.f, 0.5f);
gtk_table_attach(GTK_TABLE(table),
txt_lat = gtk_label_new(""),
1, 2, 0, 1, GTK_FILL, 0, 2, 4);
gtk_misc_set_alignment(GTK_MISC(txt_lat), 1.f, 0.5f);
+
gtk_table_attach(GTK_TABLE(table),
- label = gtk_label_new(_("Lon")),
+ lbl_lon_title = gtk_label_new(/*_("Lon")*/ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2 ),
0, 1, 1, 2, GTK_FILL, 0, 2, 4);
- gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+ gtk_misc_set_alignment(GTK_MISC(lbl_lon_title), 1.f, 0.5f);
+
gtk_table_attach(GTK_TABLE(table),
txt_lon = gtk_label_new(""),
1, 2, 1, 2, GTK_FILL, 0, 2, 4);
gtk_misc_set_alignment(GTK_MISC(txt_lon), 1.f, 0.5f);
+
gtk_table_attach(GTK_TABLE(table),
label = gtk_label_new(_("Format")),
0, 1, 2, 3, GTK_FILL, 0, 2, 4);
/* Lat/Lon */
{
gchar buffer[LL_FMT_LEN];
+ gchar buffer2[LL_FMT_LEN];
- lat_format(lat, buffer);
+ format_lat_lon(lat, lon, buffer, buffer2);
+
+ //lat_format(lat, buffer);
gtk_label_set_label(GTK_LABEL(txt_lat), buffer);
- lat_format(lon, buffer);
- gtk_label_set_label(GTK_LABEL(txt_lon), buffer);
+ //lat_format(lon, buffer);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_label(GTK_LABEL(txt_lon), buffer2);
+ else
+ gtk_label_set_label(GTK_LABEL(txt_lon), g_strdup(""));
}
/* Fill in formats */
for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++) {
gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_format),
- DEG_FORMAT_ENUM_TEXT[i]);
+ DEG_FORMAT_ENUM_TEXT[i].name);
}
gtk_combo_box_set_active(GTK_COMBO_BOX(cmb_format), _degformat);
}
lld.lat = txt_lat;
lld.lon = txt_lon;
+ lld.lat_title = lbl_lat_title;
+ lld.lon_title = lbl_lon_title;
+
/* Connect Signals */
g_signal_connect(G_OBJECT(cmb_format), "changed",
G_CALLBACK(latlon_cb_fmt_changed), &lld);
gtk_widget_show_all(dialog);
gtk_dialog_run(GTK_DIALOG(dialog));
+
+ _degformat = prev_degformat; // Put back incase it had to be auto changed
+
gtk_widget_hide(dialog);
vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
pango_font = pango_font_description_new();
pango_font_description_set_size(pango_font, 12 * PANGO_SCALE);
pango_layout_set_font_description(_scale_layout, pango_font);
+
+#ifdef INCLUDE_APRS
+ /* APRS Labels */
+ pango_context = gtk_widget_get_pango_context(_map_widget);
+ _aprs_label_layout = pango_layout_new(pango_context);
+ pango_font = pango_font_description_new();
+ pango_font_description_set_size(pango_font, 10 * PANGO_SCALE);
+ pango_font_description_set_family(pango_font,"Sans Serif");
+ pango_layout_set_font_description(_aprs_label_layout, pango_font);
+#endif // INCLUDE_APRS
/* zoom box */
pango_context = gtk_widget_get_pango_context(_map_widget);
GtkWidget *fmt_combo;
GtkWidget *lat;
GtkWidget *lon;
+ GtkWidget *lat_title;
+ GtkWidget *lon_title;
} LatlonDialog;
void display_init(void);
+#ifdef INCLUDE_APRS
+void plot_aprs_station(AprsDataRow *p_station, gboolean single );
+#endif // INCLUDE_APRS
+
#endif /* ifndef MAEMO_MAPPER_DISPLAY_H */
#define DEFAULT_TIMEOUT (1000*60) /* 60 second timeout (in ms) */
#endif
-/* BT dbus service location */
-#define BASE_PATH "/org/bluez"
-#define BASE_INTERFACE "org.bluez"
-#define ADAPTER_PATH BASE_PATH
-#define ADAPTER_INTERFACE BASE_INTERFACE ".Adapter"
-#define MANAGER_PATH BASE_PATH
-#define MANAGER_INTERFACE BASE_INTERFACE ".Manager"
-#define ERROR_INTERFACE BASE_INTERFACE ".Error"
-#define SECURITY_INTERFACE BASE_INTERFACE ".Security"
-#define RFCOMM_INTERFACE BASE_INTERFACE ".RFCOMM"
-#define BLUEZ_DBUS BASE_INTERFACE
-
-#define LIST_ADAPTERS "ListAdapters"
-#define LIST_BONDINGS "ListBondings"
-#define CREATE_BONDING "CreateBonding"
-#define GET_REMOTE_NAME "GetRemoteName"
-#define GET_REMOTE_SERVICE_CLASSES "GetRemoteServiceClasses"
-
-#define BTCOND_PATH "/com/nokia/btcond/request"
-#define BTCOND_BASE "com.nokia.btcond"
-#define BTCOND_INTERFACE BTCOND_BASE ".request"
-#define BTCOND_REQUEST BTCOND_INTERFACE
-#define BTCOND_CONNECT "rfcomm_connect"
-#define BTCOND_DISCONNECT "rfcomm_disconnect"
-#define BTCOND_DBUS BTCOND_BASE
-
#define GPS_SERVICE_CLASS_STR "positioning"
typedef struct {
--- /dev/null
+
+
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ * Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+
+/*
+Credit for primes table: Aaron Krowne
+ http://br.endernet.org/~akrowne/
+ http://planetmath.org/encyclopedia/GoodHashTablePrimes.html
+*/
+static const unsigned int primes[] = {
+53, 97, 193, 389,
+769, 1543, 3079, 6151,
+12289, 24593, 49157, 98317,
+196613, 393241, 786433, 1572869,
+3145739, 6291469, 12582917, 25165843,
+50331653, 100663319, 201326611, 402653189,
+805306457, 1610612741
+};
+const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]);
+const float max_load_factor = 0.65;
+
+/*****************************************************************************/
+struct hashtable *
+create_hashtable(unsigned int minsize,
+ unsigned int (*hashf) (void*),
+ int (*eqf) (void*,void*))
+{
+ struct hashtable *h;
+ unsigned int pindex, size = primes[0];
+ /* Check requested hashtable isn't too large */
+ if (minsize > (1u << 30)) return NULL;
+ /* Enforce size as prime */
+ for (pindex=0; pindex < prime_table_length; pindex++) {
+ if (primes[pindex] > minsize) { size = primes[pindex]; break; }
+ }
+ h = (struct hashtable *)malloc(sizeof(struct hashtable));
+ if (NULL == h) return NULL; /*oom*/
+ h->table = (struct entry **)malloc(sizeof(struct entry*) * size);
+ if (NULL == h->table) { free(h); return NULL; } /*oom*/
+ memset(h->table, 0, size * sizeof(struct entry *));
+ h->tablelength = size;
+ h->primeindex = pindex;
+ h->entrycount = 0;
+ h->hashfn = hashf;
+ h->eqfn = eqf;
+ h->loadlimit = (unsigned int) ceil(size * max_load_factor);
+ return h;
+}
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k)
+{
+ /* Aim to protect against poor hash functions by adding logic here
+ * - logic taken from java 1.4 hashtable source */
+ unsigned int i = h->hashfn(k);
+ i += ~(i << 9);
+ i ^= ((i >> 14) | (i << 18)); /* >>> */
+ i += (i << 4);
+ i ^= ((i >> 10) | (i << 22)); /* >>> */
+ return i;
+}
+
+/*****************************************************************************/
+static int
+hashtable_expand(struct hashtable *h)
+{
+ /* Double the size of the table to accomodate more entries */
+ struct entry **newtable;
+ struct entry *e;
+ struct entry **pE;
+ unsigned int newsize, i, index;
+ /* Check we're not hitting max capacity */
+ if (h->primeindex == (prime_table_length - 1)) return 0;
+ newsize = primes[++(h->primeindex)];
+
+ newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize);
+ if (NULL != newtable)
+ {
+ memset(newtable, 0, newsize * sizeof(struct entry *));
+ /* This algorithm is not 'stable'. ie. it reverses the list
+ * when it transfers entries between the tables */
+ for (i = 0; i < h->tablelength; i++) {
+ while (NULL != (e = h->table[i])) {
+ h->table[i] = e->next;
+ index = indexFor(newsize,e->h);
+ e->next = newtable[index];
+ newtable[index] = e;
+ }
+ }
+ free(h->table);
+ h->table = newtable;
+ }
+ /* Plan B: realloc instead */
+ else
+ {
+ newtable = (struct entry **)
+ realloc(h->table, newsize * sizeof(struct entry *));
+ if (NULL == newtable) { (h->primeindex)--; return 0; }
+ h->table = newtable;
+ memset(newtable[h->tablelength], 0, newsize - h->tablelength);
+ for (i = 0; i < h->tablelength; i++) {
+ for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) {
+ index = indexFor(newsize,e->h);
+ if (index == i)
+ {
+ pE = &(e->next);
+ }
+ else
+ {
+ *pE = e->next;
+ e->next = newtable[index];
+ newtable[index] = e;
+ }
+ }
+ }
+ }
+ h->tablelength = newsize;
+ h->loadlimit = (unsigned int) ceil(newsize * max_load_factor);
+ return -1;
+}
+
+/*****************************************************************************/
+unsigned int
+hashtable_count(struct hashtable *h)
+{
+ return h->entrycount;
+}
+
+/*****************************************************************************/
+int
+hashtable_insert(struct hashtable *h, void *k, void *v)
+{
+ /* This method allows duplicate keys - but they shouldn't be used */
+ unsigned int index;
+ struct entry *e;
+ if (++(h->entrycount) > h->loadlimit)
+ {
+ /* Ignore the return value. If expand fails, we should
+ * still try cramming just this value into the existing table
+ * -- we may not have memory for a larger table, but one more
+ * element may be ok. Next time we insert, we'll try expanding again.*/
+ hashtable_expand(h);
+ }
+ e = (struct entry *)malloc(sizeof(struct entry));
+ if (NULL == e) { --(h->entrycount); return 0; } /*oom*/
+ e->h = hash(h,k);
+ index = indexFor(h->tablelength,e->h);
+ e->k = k;
+ e->v = v;
+ e->next = h->table[index];
+ h->table[index] = e;
+ return -1;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_search(struct hashtable *h, void *k)
+{
+ struct entry *e;
+ unsigned int hashvalue, index;
+ hashvalue = hash(h,k);
+ index = indexFor(h->tablelength,hashvalue);
+ e = h->table[index];
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v;
+ e = e->next;
+ }
+ return NULL;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_remove(struct hashtable *h, void *k)
+{
+ /* TODO: consider compacting the table when the load factor drops enough,
+ * or provide a 'compact' method. */
+
+ struct entry *e;
+ struct entry **pE;
+ void *v;
+ unsigned int hashvalue, index;
+
+ hashvalue = hash(h,k);
+ index = indexFor(h->tablelength,hash(h,k));
+ pE = &(h->table[index]);
+ e = *pE;
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+ {
+ *pE = e->next;
+ h->entrycount--;
+ v = e->v;
+ freekey(e->k);
+ free(e);
+ return v;
+ }
+ pE = &(e->next);
+ e = e->next;
+ }
+ return NULL;
+}
+
+/*****************************************************************************/
+/* destroy */
+void
+hashtable_destroy(struct hashtable *h, int free_values)
+{
+ unsigned int i;
+ struct entry *e, *f;
+ struct entry **table = h->table;
+ if (free_values)
+ {
+ for (i = 0; i < h->tablelength; i++)
+ {
+ e = table[i];
+ while (NULL != e)
+ { f = e; e = e->next; freekey(f->k); free(f->v); free(f); }
+ }
+ }
+ else
+ {
+ for (i = 0; i < h->tablelength; i++)
+ {
+ e = table[i];
+ while (NULL != e)
+ { f = e; e = e->next; freekey(f->k); free(f); }
+ }
+ }
+ free(h->table);
+ free(h);
+}
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
--- /dev/null
+
+
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ * Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_CWC22_H__
+#define __HASHTABLE_CWC22_H__
+
+struct hashtable;
+
+/* Example of use:
+ *
+ * struct hashtable *h;
+ * struct some_key *k;
+ * struct some_value *v;
+ *
+ * static unsigned int hash_from_key_fn( void *k );
+ * static int keys_equal_fn ( void *key1, void *key2 );
+ *
+ * h = create_hashtable(16, hash_from_key_fn, keys_equal_fn);
+ * k = (struct some_key *) malloc(sizeof(struct some_key));
+ * v = (struct some_value *) malloc(sizeof(struct some_value));
+ *
+ * (initialise k and v to suitable values)
+ *
+ * if (! hashtable_insert(h,k,v) )
+ * { exit(-1); }
+ *
+ * if (NULL == (found = hashtable_search(h,k) ))
+ * { printf("not found!"); }
+ *
+ * if (NULL == (found = hashtable_remove(h,k) ))
+ * { printf("Not found\n"); }
+ *
+ */
+
+/* Macros may be used to define type-safe(r) hashtable access functions, with
+ * methods specialized to take known key and value types as parameters.
+ *
+ * Example:
+ *
+ * Insert this at the start of your file:
+ *
+ * DEFINE_HASHTABLE_INSERT(insert_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_SEARCH(search_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_REMOVE(remove_some, struct some_key, struct some_value);
+ *
+ * This defines the functions 'insert_some', 'search_some' and 'remove_some'.
+ * These operate just like hashtable_insert etc., with the same parameters,
+ * but their function signatures have 'struct some_key *' rather than
+ * 'void *', and hence can generate compile time errors if your program is
+ * supplying incorrect data as a key (and similarly for value).
+ *
+ * Note that the hash and key equality functions passed to create_hashtable
+ * still take 'void *' parameters instead of 'some key *'. This shouldn't be
+ * a difficult issue as they're only defined and passed once, and the other
+ * functions will ensure that only valid keys are supplied to them.
+ *
+ * The cost for this checking is increased code size and runtime overhead
+ * - if performance is important, it may be worth switching back to the
+ * unsafe methods once your program has been debugged with the safe methods.
+ * This just requires switching to some simple alternative defines - eg:
+ * #define insert_some hashtable_insert
+ *
+ */
+
+/*****************************************************************************
+ * create_hashtable
+
+ * @name create_hashtable
+ * @param minsize minimum initial size of hashtable
+ * @param hashfunction function for hashing keys
+ * @param key_eq_fn function for determining key equality
+ * @return newly created hashtable or NULL on failure
+ */
+
+struct hashtable *
+create_hashtable(unsigned int minsize,
+ unsigned int (*hashfunction) (void*),
+ int (*key_eq_fn) (void*,void*));
+
+/*****************************************************************************
+ * hashtable_insert
+
+ * @name hashtable_insert
+ * @param h the hashtable to insert into
+ * @param k the key - hashtable claims ownership and will free on removal
+ * @param v the value - does not claim ownership
+ * @return non-zero for successful insertion
+ *
+ * This function will cause the table to expand if the insertion would take
+ * the ratio of entries to table size over the maximum load factor.
+ *
+ * This function does not check for repeated insertions with a duplicate key.
+ * The value returned when using a duplicate key is undefined -- when
+ * the hashtable changes size, the order of retrieval of duplicate key
+ * entries is reversed.
+ * If in doubt, remove before insert.
+ */
+
+int
+hashtable_insert(struct hashtable *h, void *k, void *v);
+
+#define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \
+int fnname (struct hashtable *h, keytype *k, valuetype *v) \
+{ \
+ return hashtable_insert(h,k,v); \
+}
+
+/*****************************************************************************
+ * hashtable_search
+
+ * @name hashtable_search
+ * @param h the hashtable to search
+ * @param k the key to search for - does not claim ownership
+ * @return the value associated with the key, or NULL if none found
+ */
+
+void *
+hashtable_search(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+ return (valuetype *) (hashtable_search(h,k)); \
+}
+
+/*****************************************************************************
+ * hashtable_remove
+
+ * @name hashtable_remove
+ * @param h the hashtable to remove the item from
+ * @param k the key to search for - does not claim ownership
+ * @return the value associated with the key, or NULL if none found
+ */
+
+void * /* returns value */
+hashtable_remove(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+ return (valuetype *) (hashtable_remove(h,k)); \
+}
+
+
+/*****************************************************************************
+ * hashtable_count
+
+ * @name hashtable_count
+ * @param h the hashtable
+ * @return the number of items stored in the hashtable
+ */
+unsigned int
+hashtable_count(struct hashtable *h);
+
+
+/*****************************************************************************
+ * hashtable_destroy
+
+ * @name hashtable_destroy
+ * @param h the hashtable
+ * @param free_values whether to call 'free' on the remaining values
+ */
+
+void
+hashtable_destroy(struct hashtable *h, int free_values);
+
+#endif /* __HASHTABLE_CWC22_H__ */
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include <stdlib.h> /* defines NULL */
+#include <stdio.h>
+//#include <string.h>
+//#include <math.h>
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+#include "hashtable_itr.h"
+
+// Must be last include file
+//#include "leak_detection.h" /* defines GC_MALLOC/GC_FREE */
+
+
+
+
+
+/*****************************************************************************/
+/* hashtable_iterator - iterator constructor */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h)
+{
+ unsigned int i, tablelength;
+ struct hashtable_itr *itr = (struct hashtable_itr *)
+ malloc(sizeof(struct hashtable_itr));
+ if (NULL == itr) return NULL;
+ itr->h = h;
+ itr->e = NULL;
+ itr->parent = NULL;
+ tablelength = h->tablelength;
+ itr->index = tablelength;
+ if (0 == h->entrycount) return itr;
+
+ for (i = 0; i < tablelength; i++)
+ {
+ if (NULL != h->table[i])
+ {
+ itr->e = h->table[i];
+ itr->index = i;
+ break;
+ }
+ }
+ return itr;
+}
+
+/*****************************************************************************/
+/* key - return the key of the (key,value) pair at the current position */
+/* value - return the value of the (key,value) pair at the current position */
+
+void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{
+ if (!i)
+ return NULL;
+ if (i->e)
+ return i->e->k;
+ else
+ return NULL;
+}
+
+void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{
+ if (!i)
+ return NULL;
+ if (i->e) {
+ return i->e->v;
+ } else {
+ return NULL;
+ }
+
+}
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr)
+{
+ unsigned int j,tablelength;
+ struct entry **table;
+ struct entry *next;
+ if (NULL == itr->e) return 0; /* stupidity check */
+
+ next = itr->e->next;
+ if (NULL != next)
+ {
+ itr->parent = itr->e;
+ itr->e = next;
+ return -1;
+ }
+ tablelength = itr->h->tablelength;
+ itr->parent = NULL;
+ if (tablelength <= (j = ++(itr->index)))
+ {
+ itr->e = NULL;
+ return 0;
+ }
+ table = itr->h->table;
+ while (NULL == (next = table[j]))
+ {
+ if (++j >= tablelength)
+ {
+ itr->index = tablelength;
+ itr->e = NULL;
+ return 0;
+ }
+ }
+ itr->index = j;
+ itr->e = next;
+ return -1;
+}
+
+/*****************************************************************************/
+/* remove - remove the entry at the current iterator position
+ * and advance the iterator, if there is a successive
+ * element.
+ * If you want the value, read it before you remove:
+ * beware memory leaks if you don't.
+ * Returns zero if end of iteration. */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr)
+{
+ struct entry *remember_e, *remember_parent;
+ int ret;
+
+ /* Do the removal */
+ if (NULL == (itr->parent))
+ {
+ /* element is head of a chain */
+ itr->h->table[itr->index] = itr->e->next;
+ } else {
+ /* element is mid-chain */
+ itr->parent->next = itr->e->next;
+ }
+ /* itr->e is now outside the hashtable */
+ remember_e = itr->e;
+ itr->h->entrycount--;
+ freekey(remember_e->k);
+
+ /* Advance the iterator, correcting the parent */
+ remember_parent = itr->parent;
+ ret = hashtable_iterator_advance(itr);
+ if (itr->parent == remember_e) { itr->parent = remember_parent; }
+ free(remember_e);
+ return ret;
+}
+
+/*****************************************************************************/
+int /* returns zero if not found */
+hashtable_iterator_search(struct hashtable_itr *itr,
+ struct hashtable *h, void *k)
+{
+ struct entry *e, *parent;
+ unsigned int hashvalue, index;
+
+ hashvalue = hash(h,k);
+ index = indexFor(h->tablelength,hashvalue);
+
+ e = h->table[index];
+ parent = NULL;
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+ {
+ itr->index = index;
+ itr->e = e;
+ itr->parent = parent;
+ itr->h = h;
+ return -1;
+ }
+ parent = e;
+ e = e->next;
+ }
+ return 0;
+}
+
+
+/*
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
+#endif // INCLUDE_APRS
+
+
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_ITR_CWC22__
+#define __HASHTABLE_ITR_CWC22__
+#include "hashtable.h"
+#include "hashtable_private.h" /* needed to enable inlining */
+
+/*****************************************************************************/
+/* This struct is only concrete here to allow the inlining of two of the
+ * accessor functions. */
+struct hashtable_itr
+{
+ struct hashtable *h;
+ struct entry *e;
+ struct entry *parent;
+ unsigned int index;
+};
+
+
+/*****************************************************************************/
+/* hashtable_iterator
+ */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h);
+
+#if 0
+// BZZZZT! it is very, very wrong to be inlining this this way.
+// If one calls hashtable_iterator on a hash table from which everything
+// has been deleted, the iterator has a null for i->e.
+// It is not good to require the caller to check the internals of the iterator
+// structure just to be sure there are no null pointers inside.
+// For whatever reason, these are defined again in the hashtable_iterator.c
+// file, not inlined. I have modified the ones in hashtable_iterator so they
+// actually check for nulls and don't try to dereference them.
+/*****************************************************************************/
+/* hashtable_iterator_key
+ * - return the value of the (key,value) pair at the current position */
+
+extern inline void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{
+ return i->e->k;
+}
+
+/*****************************************************************************/
+/* value - return the value of the (key,value) pair at the current position */
+
+extern inline void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{
+ return i->e->v;
+}
+#else
+// SO instead of inlining, just declare. No need to be "extern"
+// The ones in the .c file check their arguments and return nulls if they
+// can't comply with the request. Much nicer for the calling routine to
+// check a return value than to monkey with the internals of the struct.
+void * hashtable_iterator_key(struct hashtable_itr *i);
+void * hashtable_iterator_value(struct hashtable_itr *i);
+#endif
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* remove - remove current element and advance the iterator to the next element
+ * NB: if you need the value to free it, read it before
+ * removing. ie: beware memory leaks!
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* search - overwrite the supplied iterator, to point to the entry
+ * matching the supplied key.
+ h points to the hashtable to be searched.
+ * returns zero if not found. */
+int
+hashtable_iterator_search(struct hashtable_itr *itr,
+ struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \
+int fnname (struct hashtable_itr *i, struct hashtable *h, keytype *k) \
+{ \
+ return (hashtable_iterator_search(i,h,k)); \
+}
+
+
+
+#endif /* __HASHTABLE_ITR_CWC22__*/
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
--- /dev/null
+/*
+ *
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper 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.
+ *
+ * Maemo Mapper 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 Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_PRIVATE_CWC22_H__
+#define __HASHTABLE_PRIVATE_CWC22_H__
+
+#include "hashtable.h"
+
+
+/*****************************************************************************/
+struct entry
+{
+ void *k, *v;
+ unsigned int h;
+ struct entry *next;
+};
+
+struct hashtable {
+ unsigned int tablelength;
+ struct entry **table;
+ unsigned int entrycount;
+ unsigned int loadlimit;
+ unsigned int primeindex;
+ unsigned int (*hashfn) (void *k);
+ int (*eqfn) (void *k1, void *k2);
+};
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k);
+
+/*****************************************************************************/
+/* indexFor */
+static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue) {
+ return (hashvalue % tablelength);
+}
+
+/* Only works if tablelength == 2^N */
+/*static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue)
+{
+ return (hashvalue & (tablelength - 1u));
+}
+*/
+
+/*****************************************************************************/
+#define freekey(X) free(X)
+/*define freekey(X) ; */
+
+
+/*****************************************************************************/
+
+#endif /* __HASHTABLE_PRIVATE_CWC22_H__*/
+
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
Point clkpt;
screen2unit(event->x, event->y, clkpt.unitx, clkpt.unity);
+#ifdef INCLUDE_APRS
+ gboolean aprs_found = FALSE;
+
+ if(_aprs_enable && (_poi_zoom > _zoom) )
+ {
+ AprsDisplayData poi;
+ selected_point = select_aprs(
+ clkpt.unitx, clkpt.unity, TRUE);
+
+ aprs_found = selected_point;
+ }
+
+ if(!aprs_found && _show_poi && (_poi_zoom > _zoom))
+#else
if(_show_poi && (_poi_zoom > _zoom))
+#endif // INCLUDE_APRS
{
PoiInfo poi;
selected_point = select_poi(
#include "dbus-ifc.h"
#include "display.h"
#include "gps.h"
+#include "aprs.h"
#include "gpx.h"
#include "input.h"
#include "main.h"
vprintf("%s(): return\n", __PRETTY_FUNCTION__);
}
+// Called once per second
+gboolean timer_callback (gpointer data) {
+
+#ifdef INCLUDE_APRS
+ static int inet_beacon_count = 0;
+ static int inet_aprs_filter = 0;
+ static int tty_beacon_count = 0;
+
+ if(_aprs_enable)
+ {
+ // Check if we need to resent the filter every 5 minutes
+ // Note - this should really be dependent on speed and/or range
+ if(_aprs_inet_enable && inet_aprs_filter > (60*5))
+ {
+ // Currently this only needs to be sent on connection
+ //update_aprs_inet_options(FALSE);
+ inet_aprs_filter = 0;
+ }
+
+ if(_aprs_inet_enable && _aprs_enable_inet_tx && _aprs_inet_beacon_interval > 0 && _aprs_inet_state == RCVR_UP)
+ {
+
+
+ if(inet_beacon_count >= _aprs_inet_beacon_interval)
+ {
+ // Send inet APRS beacon
+ aprs_send_beacon(APRS_PORT_INET);
+ //g_idle_add((GSourceFunc)aprs_send_beacon, APRS_PORT_INET);
+ //fprintf(stderr, "timer %u, %u\n", inet_beacon_count, _aprs_inet_beacon_interval);
+ inet_beacon_count = 0;
+ }
+
+ }
+
+ if(_aprs_tty_enable && _aprs_enable_tty_tx )
+ {
+ // Send tty APRS beacon
+
+ if(tty_beacon_count >= _aprs_tty_beacon_interval && _aprs_tty_beacon_interval > 0)
+ {
+ // Send inet APRS beacon
+ aprs_send_beacon(APRS_PORT_TTY);
+
+ //g_idle_add((GSourceFunc)aprs_send_beacon_inet, NULL);
+
+ tty_beacon_count = 0;
+ }
+
+ }
+
+
+ inet_aprs_filter++;
+ inet_beacon_count++;
+ tty_beacon_count++;
+ }
+#endif //INCLUDE_APRS
+
+ return TRUE; // Continue timer
+}
+
+// Start a one second timer - this can be used to execute any periodic tasks
+void timer_init()
+{
+ g_timeout_add(1000 , timer_callback, NULL);
+}
+
/**
* Initialize everything required in preparation for calling gtk_main().
*/
= _("Reset Bluetooth");
CUSTOM_ACTION_ENUM_TEXT[CUSTOM_ACTION_TOGGLE_LAYERS] = _("Toggle Layers");
- DEG_FORMAT_ENUM_TEXT[DDPDDDDD] = "-dd.ddddd°";
- DEG_FORMAT_ENUM_TEXT[DD_MMPMMM] = "-dd°mm.mmm'";
- DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS] = "-dd°mm'ss.s\"";
- DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW] = "dd.ddddd° S";
- DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW] = "dd°mm.mmm' S";
- DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW] = "dd°mm'ss.s\" S";
- DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD] = "S dd.ddddd°";
- DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM] = "S dd° mm.mmm'";
- DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS] = "S dd° mm' ss.s\"";
-
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].name = "-dd.ddddd°";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].name = "-dd°mm.mmm'";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].name = "-dd°mm'ss.s\"";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].name = "dd.ddddd° S";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].name = "dd°mm.mmm' S";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].name = "dd°mm'ss.s\" S";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].name = "S dd.ddddd°";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].name = "S dd° mm.mmm'";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].name = "S dd° mm' ss.s\"";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].short_field_1 = "Lat";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].long_field_1 = "Latitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].short_field_2 = "Lon";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].long_field_2 = "Longitude";
+ DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].field_2_in_use = TRUE;
+
+ // Used by Radio Amateurs
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].name = "IARU Locator";
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].short_field_1 = "Locator";
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].long_field_1 = "Lacator";
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].short_field_2 = "";
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].long_field_2 = "";
+ DEG_FORMAT_ENUM_TEXT[IARU_LOC].field_2_in_use = FALSE;
+
+
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].name = "OSGB X,Y Grid";
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].short_field_1 = "X";
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].long_field_1 = "OS X";
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].short_field_2 = "Y";
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].long_field_2 = "OS Y";
+ DEG_FORMAT_ENUM_TEXT[UK_OSGB].field_2_in_use = TRUE;
+
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].name = "OSGB Landranger Grid";
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].short_field_1 = "GR";
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].long_field_1 = "OS Grid";
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].short_field_2 = "";
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].long_field_2 = "";
+ DEG_FORMAT_ENUM_TEXT[UK_NGR].field_2_in_use = FALSE;
+
SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_LEFT] = _("Top-Left");
SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_RIGHT] = _("Top-Right");
SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_BOTTOM_RIGHT] = _("Bottom-Right");
poi_db_connect();
display_init();
dbus_ifc_init();
-
+
+#ifdef INCLUDE_APRS
+ aprs_init();
+ timer_init();
+#endif //INCLUDE_APRS
+
/* If present, attempt to load the file specified on the command line. */
if(argc > 1)
{
static GtkWidget *lbl_center_lat = NULL;
static GtkWidget *lbl_center_lon = NULL;
static MapmanInfo mapman_info;
+ static gint last_deg_format = 0;
+
gchar buffer[80];
gdouble lat, lon;
gint z;
+ gint prev_degformat = _degformat;
+ gint fallback_deg_format = _degformat;
+ gdouble top_left_lat, top_left_lon, bottom_right_lat, bottom_right_lon;
+
+
printf("%s()\n", __PRETTY_FUNCTION__);
if(!_curr_repo->db)
return TRUE;
}
+ // - If the coord system has changed then we need to update certain values
+
+ /* Initialize to the bounds of the screen. */
+ unit2latlon(
+ _center.unitx - pixel2unit(MAX(_view_width_pixels,
+ _view_height_pixels) / 2),
+ _center.unity - pixel2unit(MAX(_view_width_pixels,
+ _view_height_pixels) / 2), top_left_lat, top_left_lon);
+
+ BOUND(top_left_lat, -90.f, 90.f);
+ BOUND(top_left_lon, -180.f, 180.f);
+
+
+ unit2latlon(
+ _center.unitx + pixel2unit(MAX(_view_width_pixels,
+ _view_height_pixels) / 2),
+ _center.unity + pixel2unit(MAX(_view_width_pixels,
+ _view_height_pixels) / 2), bottom_right_lat, bottom_right_lon);
+ BOUND(bottom_right_lat, -90.f, 90.f);
+ BOUND(bottom_right_lon, -180.f, 180.f);
+
+
+
+
+ if(!coord_system_check_lat_lon (top_left_lat, top_left_lon, &fallback_deg_format))
+ {
+ _degformat = fallback_deg_format;
+ }
+ else
+ {
+ // top left is valid, also check bottom right
+ if(!coord_system_check_lat_lon (bottom_right_lat, bottom_right_lon, &fallback_deg_format))
+ {
+ _degformat = fallback_deg_format;
+ }
+ }
+
+
+ if(_degformat != last_deg_format)
+ {
+ last_deg_format = _degformat;
+
+ if(dialog != NULL) gtk_widget_destroy(dialog);
+ dialog = NULL;
+ }
+
if(dialog == NULL)
{
mapman_info.dialog = dialog = gtk_dialog_new_with_buttons(
/* Label Columns. */
gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- label = gtk_label_new(_("Latitude")),
+ label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1),
1, 2, 0, 1, GTK_FILL, 0, 4, 0);
- gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
- gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- label = gtk_label_new(_("Longitude")),
- 2, 3, 0, 1, GTK_FILL, 0, 4, 0);
- gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
-
+ gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+ label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].long_field_2),
+ 2, 3, 0, 1, GTK_FILL, 0, 4, 0);
+ gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+ }
+
/* GPS. */
gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
label = gtk_label_new(_("GPS Location")),
1, 2, 1, 2, GTK_FILL, 0, 4, 0);
gtk_label_set_selectable(GTK_LABEL(lbl_gps_lat), TRUE);
gtk_misc_set_alignment(GTK_MISC(lbl_gps_lat), 1.f, 0.5f);
- gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- lbl_gps_lon = gtk_label_new(""),
- 2, 3, 1, 2, GTK_FILL, 0, 4, 0);
- gtk_label_set_selectable(GTK_LABEL(lbl_gps_lon), TRUE);
- gtk_misc_set_alignment(GTK_MISC(lbl_gps_lon), 1.f, 0.5f);
-
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+ lbl_gps_lon = gtk_label_new(""),
+ 2, 3, 1, 2, GTK_FILL, 0, 4, 0);
+ gtk_label_set_selectable(GTK_LABEL(lbl_gps_lon), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(lbl_gps_lon), 1.f, 0.5f);
+ }
+
/* Center. */
gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
label = gtk_label_new(_("View Center")),
1, 2, 2, 3, GTK_FILL, 0, 4, 0);
gtk_label_set_selectable(GTK_LABEL(lbl_center_lat), TRUE);
gtk_misc_set_alignment(GTK_MISC(lbl_center_lat), 1.f, 0.5f);
- gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- lbl_center_lon = gtk_label_new(""),
- 2, 3, 2, 3, GTK_FILL, 0, 4, 0);
- gtk_label_set_selectable(GTK_LABEL(lbl_center_lon), TRUE);
- gtk_misc_set_alignment(GTK_MISC(lbl_center_lon), 1.f, 0.5f);
+
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+ lbl_center_lon = gtk_label_new(""),
+ 2, 3, 2, 3, GTK_FILL, 0, 4, 0);
+ gtk_label_set_selectable(GTK_LABEL(lbl_center_lon), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(lbl_center_lon), 1.f, 0.5f);
+ }
/* default values for Top Left and Bottom Right are defined by the
* rectangle of the current and the previous Center */
FALSE, NULL);
#endif
#endif
- gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- mapman_info.txt_topleft_lon = gtk_entry_new(),
- 2, 3, 3, 4, GTK_FILL, 0, 4, 0);
- gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lon), 12);
- gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lon), 1.f);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+ mapman_info.txt_topleft_lon = gtk_entry_new(),
+ 2, 3, 3, 4, GTK_FILL, 0, 4, 0);
+ gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lon), 12);
+ gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lon), 1.f);
#ifdef MAEMO_CHANGES
- g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
+ g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
#ifndef LEGACY
- "hildon-input-mode",
- HILDON_GTK_INPUT_MODE_FULL, NULL);
+ "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
#else
- HILDON_INPUT_MODE_HINT,
- HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
- g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
- HILDON_AUTOCAP,
- FALSE, NULL);
+ HILDON_INPUT_MODE_HINT,
+ HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
+ g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
+ HILDON_AUTOCAP,
+ FALSE, NULL);
#endif
#endif
+ }
+
/* Bottom Right. */
gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
label = gtk_label_new(_("Bottom-Right")),
FALSE, NULL);
#endif
#endif
- gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
- mapman_info.txt_botright_lon = gtk_entry_new(),
- 2, 3, 4, 5, GTK_FILL, 0, 4, 0);
- gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lat), 12);
- gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lon), 1.f);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+ mapman_info.txt_botright_lon = gtk_entry_new(),
+ 2, 3, 4, 5, GTK_FILL, 0, 4, 0);
+ gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lon), 12);
+ gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lon), 1.f);
#ifdef MAEMO_CHANGES
- g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
+ g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
#ifndef LEGACY
- "hildon-input-mode",
- HILDON_GTK_INPUT_MODE_FULL, NULL);
+ "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
#else
- HILDON_INPUT_MODE_HINT,
- HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
- g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
- HILDON_AUTOCAP,
- FALSE, NULL);
+ HILDON_INPUT_MODE_HINT,
+ HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
+ g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
+ HILDON_AUTOCAP,
+ FALSE, NULL);
#endif
#endif
+ }
+
/* Default action is to download by area. */
gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(mapman_info.rad_by_area), TRUE);
gtk_widget_set_sensitive(mapman_info.rad_by_route,
_route.head != _route.tail);
- lat_format(_gps.lat, buffer);
- gtk_label_set_text(GTK_LABEL(lbl_gps_lat), buffer);
- lon_format(_gps.lon, buffer);
- gtk_label_set_text(GTK_LABEL(lbl_gps_lon), buffer);
-
+
+ gchar buffer1[15];
+ gchar buffer2[15];
+ format_lat_lon(_gps.lat, _gps.lon, buffer1, buffer2);
+ //lat_format(_gps.lat, buffer);
+ gtk_label_set_text(GTK_LABEL(lbl_gps_lat), buffer1);
+ //lon_format(_gps.lon, buffer);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_text(GTK_LABEL(lbl_gps_lon), buffer2);
+
unit2latlon(_center.unitx, _center.unity, lat, lon);
- lat_format(lat, buffer);
- gtk_label_set_text(GTK_LABEL(lbl_center_lat), buffer);
- lon_format(lon, buffer);
- gtk_label_set_text(GTK_LABEL(lbl_center_lon), buffer);
-
- /* Initialize to the bounds of the screen. */
- unit2latlon(
- _center.unitx - pixel2unit(MAX(_view_width_pixels,
- _view_height_pixels) / 2),
- _center.unity - pixel2unit(MAX(_view_width_pixels,
- _view_height_pixels) / 2), lat, lon);
- BOUND(lat, -90.f, 90.f);
- BOUND(lon, -180.f, 180.f);
- lat_format(lat, buffer);
- gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lat), buffer);
- lon_format(lon, buffer);
- gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lon), buffer);
-
- unit2latlon(
- _center.unitx + pixel2unit(MAX(_view_width_pixels,
- _view_height_pixels) / 2),
- _center.unity + pixel2unit(MAX(_view_width_pixels,
- _view_height_pixels) / 2), lat, lon);
- BOUND(lat, -90.f, 90.f);
- BOUND(lon, -180.f, 180.f);
- lat_format(lat, buffer);
- gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lat), buffer);
- lon_format(lon, buffer);
- gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lon), buffer);
+
+ format_lat_lon(lat, lon, buffer1, buffer2);
+ //lat_format(lat, buffer);
+ gtk_label_set_text(GTK_LABEL(lbl_center_lat), buffer1);
+ //lon_format(lon, buffer);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_label_set_text(GTK_LABEL(lbl_center_lon), buffer2);
+
+ format_lat_lon(top_left_lat, top_left_lon, buffer1, buffer2);
+
+ gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lat), buffer1);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lon), buffer2);
+
+ format_lat_lon(bottom_right_lat, bottom_right_lon, buffer1, buffer2);
+ //lat_format(lat, buffer);
+ gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lat), buffer1);
+ //lon_format(lon, buffer);
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lon), buffer2);
/* Initialize zoom levels. */
{
}
else
{
- const gchar *text;
- gchar *error_check;
+ const gchar *text_lon, *text_lat;
+ //gchar *error_check;
gdouble start_lat, start_lon, end_lat, end_lon;
- text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lat));
- start_lat = strdmstod(text, &error_check);
- if(text == error_check || start_lat < -90. || start_lat > 90.) {
- popup_error(dialog, _("Invalid Top-Left Latitude"));
- continue;
- }
-
- text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lon));
- start_lon = strdmstod(text, &error_check);
- if(text == error_check || start_lon < -180. || start_lon>180.) {
- popup_error(dialog, _("Invalid Top-Left Longitude"));
- continue;
+ text_lat = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lat));
+ text_lon = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lon));
+
+ if(!parse_coords(text_lat, text_lon, &start_lat, &start_lon))
+ {
+ popup_error(dialog, _("Invalid Top-Left coordinate specified"));
+ continue;
}
+
+ text_lat = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lat));
+ text_lon = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lon));
- text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lat));
- end_lat = strdmstod(text, &error_check);
- if(text == error_check || end_lat < -90. || end_lat > 90.) {
- popup_error(dialog, _("Invalid Bottom-Right Latitude"));
- continue;
+ if(!parse_coords(text_lat, text_lon, &end_lat, &end_lon))
+ {
+ popup_error(dialog, _("Invalid Bottom-Right coordinate specified"));
+ continue;
}
- text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lon));
- end_lon = strdmstod(text, &error_check);
- if(text == error_check || end_lon < -180. || end_lon > 180.) {
- popup_error(dialog,_("Invalid Bottom-Right Longitude"));
- continue;
- }
+
if(mapman_by_area(start_lat, start_lon, end_lat, end_lon,
&mapman_info, update_type, download_batch_id))
}
gtk_widget_hide(dialog);
+
+ _degformat = prev_degformat;
vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
return TRUE;
#include "display.h"
#include "gps.h"
+#include "aprs.h"
+#include "aprs_kiss.h"
+#include "aprs_display.h"
#include "gdk-pixbuf-rotate.h"
#include "gpx.h"
#include "maps.h"
}
unit2latlon(_pos.unitx, _pos.unity, lat, lon);
- lat_format(lat, tmp1);
- lon_format(lon, tmp2);
+
+ format_lat_lon(lat, lon, tmp1, tmp2);
+ //lat_format(lat, tmp1);
+ //lon_format(lon, tmp2);
p_latlon = g_strdup_printf("%s, %s", tmp1, tmp2);
gtk_label_set_text(GTK_LABEL(lbl_latlon), p_latlon);
g_free(p_latlon);
/* Initialize with the current center position. */
{
- gchar buffer[32];
+ gchar buffer1[32];
+ gchar buffer2[32];
gdouble lat, lon;
unit2latlon(_center.unitx, _center.unity, lat, lon);
- lat_format(lat, buffer);
- gtk_entry_set_text(GTK_ENTRY(txt_lat), buffer);
- lon_format(lon, buffer);
- gtk_entry_set_text(GTK_ENTRY(txt_lon), buffer);
+ //lat_format(lat, buffer1);
+ //lon_format(lon, buffer2);
+ format_lat_lon(lat, lon, buffer1, buffer2);
+ gtk_entry_set_text(GTK_ENTRY(txt_lat), buffer1);
+ gtk_entry_set_text(GTK_ENTRY(txt_lon), buffer2);
}
gtk_widget_show_all(dialog);
return TRUE;
}
+/****************************************************************************
+ * BELOW: APRS MENU *********************************************************
+ ****************************************************************************/
+#ifdef INCLUDE_APRS
+
+static gboolean menu_cb_aprs_settings(GtkMenuItem *item)
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ gboolean aprs_inet_config_changed = FALSE;
+ gboolean aprs_tty_config_changed = FALSE;
+
+ aprs_settings_dialog(&aprs_inet_config_changed, &aprs_tty_config_changed);
+
+ if(aprs_inet_config_changed)
+ {
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_inet_item), _aprs_enable);
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_tty_item), _aprs_enable);
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_stations_item), _aprs_enable);
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_messages_item), _aprs_enable);
+
+
+ aprs_server_disconnect();
+
+ if(_aprs_enable && _aprs_inet_enable)
+ {
+ aprs_server_connect();
+ }
+ }
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+static gboolean menu_cb_enable_tty_aprs(GtkMenuItem *item)
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ if(_aprs_enable)
+ {
+
+ if((_aprs_tty_enable = gtk_check_menu_item_get_active(
+ GTK_CHECK_MENU_ITEM(_menu_enable_aprs_tty_item))))
+ aprs_tty_connect();
+ else
+ aprs_tty_disconnect();
+ }
+
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+
+static gboolean
+menu_cb_enable_inet_aprs(GtkMenuItem *item)
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ if(_aprs_enable)
+ {
+
+ if((_aprs_inet_enable = gtk_check_menu_item_get_active(
+ GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item))))
+ aprs_server_connect();
+ else
+ aprs_server_disconnect();
+
+
+ }
+ else
+ {
+ // APRS not enabled
+ }
+
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+static gboolean menu_cb_list_aprs_messages(GtkMenuItem *item)
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ if(_aprs_enable)
+ {
+ list_messages();
+ }
+ else
+ {
+ // APRS not enabled
+ }
+
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+static gboolean
+menu_cb_list_aprs_stations(GtkMenuItem *item)
+{
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+ if(_aprs_enable)
+ {
+ list_stations();
+ }
+ else
+ {
+ // APRS not enabled
+ }
+
+
+ vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+ return TRUE;
+}
+
+#endif // INCLUDE_APRS
+
/****************************************************************************
* BELOW: GPS MENU **********************************************************
****************************************************************************/
gtk_menu_append(menu, gtk_separator_menu_item_new());
+#ifdef INCLUDE_APRS
+ /* The "APRS" submenu. */
+ gtk_menu_append(menu, menu_item
+ = gtk_menu_item_new_with_label(_("APRS")));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+ submenu = gtk_menu_new());
+
+ gtk_menu_append(submenu, _menu_enable_aprs_inet_item
+ = gtk_check_menu_item_new_with_label(_("Connect to APRS Server")));
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item), _aprs_inet_enable);
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_inet_item), _aprs_enable);
+
+
+ gtk_menu_append(submenu, _menu_enable_aprs_tty_item
+ = gtk_check_menu_item_new_with_label(_("Connect to TNC")));
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(_menu_enable_aprs_tty_item), _aprs_tty_enable);
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_tty_item), _aprs_enable);
+
+
+ gtk_menu_append(submenu, _menu_list_aprs_stations_item
+ = gtk_menu_item_new_with_label(_("List Stations...")));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_stations_item), _aprs_enable);
+
+
+ gtk_menu_append(submenu, _menu_list_aprs_messages_item
+ = gtk_menu_item_new_with_label(_("List Messages...")));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_messages_item), _aprs_enable);
+
+
+ gtk_menu_append(submenu, _menu_aprs_settings_item
+ = gtk_menu_item_new_with_label(_("APRS Settings...")));
+
+
+ gtk_menu_append(menu, gtk_separator_menu_item_new());
+
+#endif // INCLUDE_APRS
+
/* The other menu items. */
gtk_menu_append(menu, _menu_settings_item
= gtk_menu_item_new_with_label(_("Settings...")));
g_signal_connect(G_OBJECT(_menu_gps_reset_item), "activate",
G_CALLBACK(menu_cb_gps_reset), NULL);
+#ifdef INCLUDE_APRS
+ /* Connect the "APRS" signals. */
+ g_signal_connect(G_OBJECT(_menu_enable_aprs_inet_item), "toggled",
+ G_CALLBACK(menu_cb_enable_inet_aprs), NULL);
+
+ g_signal_connect(G_OBJECT(_menu_enable_aprs_tty_item), "toggled",
+ G_CALLBACK(menu_cb_enable_tty_aprs), NULL);
+
+ g_signal_connect(G_OBJECT(_menu_list_aprs_stations_item), "activate",
+ G_CALLBACK(menu_cb_list_aprs_stations), NULL);
+
+ g_signal_connect(G_OBJECT(_menu_list_aprs_messages_item), "activate",
+ G_CALLBACK(menu_cb_list_aprs_messages), NULL);
+
+ g_signal_connect(G_OBJECT(_menu_aprs_settings_item), "activate",
+ G_CALLBACK(menu_cb_aprs_settings), NULL);
+#endif // INCLUDE_APRS
+
/* Connect the other menu item signals. */
g_signal_connect(G_OBJECT(_menu_settings_item), "activate",
G_CALLBACK(menu_cb_settings), NULL);
gdouble lat, lon;
lat = sqlite3_column_double(_stmt_select_poi, 0);
lon = sqlite3_column_double(_stmt_select_poi, 1);
- lat_format(lat, tmp1);
- lon_format(lon, tmp2);
+
+ format_lat_lon(lat, lon, tmp1, tmp2);
+ //lat_format(lat, tmp1);
+ //lon_format(lon, tmp2);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
POI_POIID, sqlite3_column_int(_stmt_select_poi, 2),
{
gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
- lat_format(poi.lat, tmp1);
- lon_format(poi.lon, tmp2);
+ format_lat_lon(poi.lat, poi.lon, tmp1, tmp2);
+ //lat_format(poi.lat, tmp1);
+ //lon_format(poi.lon, tmp2);
gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
static GtkTextIter begin, end;
static DeletePOI dpoi = {NULL, NULL, 0};
static PoiCategoryEditInfo pcedit;
+ static int last_deg_format = 0;
+
printf("%s()\n", __PRETTY_FUNCTION__);
+
+
+ if(_degformat != last_deg_format)
+ {
+ last_deg_format = _degformat;
+
+ if(dialog != NULL) gtk_widget_destroy(dialog);
+ dialog = NULL;
+ }
if(dialog == NULL)
{
table = gtk_table_new(6, 4, FALSE), TRUE, TRUE, 0);
gtk_table_attach(GTK_TABLE(table),
- label = gtk_label_new(_("Lat")),
+ label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1),
0, 1, 0, 1, GTK_FILL, 0, 2, 0);
gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
gtk_table_attach(GTK_TABLE(table),
txt_lat = gtk_entry_new(),
1, 2, 0, 1, GTK_FILL, 0, 2, 0);
- gtk_table_attach(GTK_TABLE(table),
- label = gtk_label_new(_("Lon")),
- 2, 3, 0, 1, GTK_FILL, 0, 2, 0);
- gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
- gtk_table_attach(GTK_TABLE(table),
- txt_lon = gtk_entry_new(),
- 3, 4, 0, 1, GTK_FILL, 0, 2, 0);
-
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ {
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2),
+ 2, 3, 0, 1, GTK_FILL, 0, 2, 0);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+ gtk_table_attach(GTK_TABLE(table),
+ txt_lon = gtk_entry_new(),
+ 3, 4, 0, 1, GTK_FILL, 0, 2, 0);
+ }
+
gtk_table_attach(GTK_TABLE(table),
label = gtk_label_new(_("Label")),
0, 1, 1, 2, GTK_FILL, 0, 2, 0);
{
gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
- lat_format(poi->lat, tmp1);
- lon_format(poi->lon, tmp2);
+ format_lat_lon(poi->lat, poi->lon, tmp1, tmp2);
+ //lat_format(poi->lat, tmp1);
+ //lon_format(poi->lon, tmp2);
gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
- gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
+
+ if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+ gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
+ else
+ gtk_entry_set_text(GTK_ENTRY(txt_lon), g_strdup(""));
}
/* label */
while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
{
- const gchar *text;
+ const gchar *text, *text_lat, *text_lon;
gchar *error_check;
- text = gtk_entry_get_text(GTK_ENTRY(txt_lat));
- poi->lat = strdmstod(text, &error_check);
- if(text == error_check || poi->lat < -90. || poi->lat > 90.) {
- popup_error(dialog, _("Invalid Latitude"));
- continue;
- }
-
- text = gtk_entry_get_text(GTK_ENTRY(txt_lon));
- poi->lon = strdmstod(text, &error_check);
- if(text == error_check || poi->lon < -180. || poi->lon > 180.) {
- popup_error(dialog, _("Invalid Longitude"));
- continue;
+ text_lat = gtk_entry_get_text(GTK_ENTRY(txt_lat));
+ text_lon = gtk_entry_get_text(GTK_ENTRY(txt_lon));
+
+ if(!parse_coords(text_lat, text_lon, &poi->lat, &poi->lon))
+ {
+ popup_error(dialog, _("Invalid coordinate specified"));
+ continue;
}
+
if(strlen(gtk_entry_get_text(GTK_ENTRY(txt_label))))
{
if(poi->label)
poi_info->lat, poi_info->lon,
poi_info->label, poi_info->desc);
- lat_format(poi_info->lat, tmp1);
- lon_format(poi_info->lon, tmp2);
+ format_lat_lon(poi_info->lat, poi_info->lon, tmp1, tmp2);
+ //lat_format(poi_info->lat, tmp1);
+ //lon_format(poi_info->lon, tmp2);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
vprintf("%s(): return\n", __PRETTY_FUNCTION__);
}
+
+#ifdef INCLUDE_APRS
+extern AprsDataRow *n_first; // pointer to first element in name sorted station list
+
+
+/////////////////////
+
+/**
+ * Render all the APRS data.
+ */
+void
+map_render_aprs()
+{
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+ int mm;
+
+ if(_poi_zoom > _zoom)
+ {
+
+ AprsDataRow *p_station = n_first;
+
+ while ( (p_station) != NULL)
+ {
+ if( p_station->coord_lat != 0.0f
+ || p_station->coord_lon != 0.0f )
+ {
+ plot_aprs_station( p_station, FALSE);
+ } // If valid data
+
+ (p_station) = (p_station)->n_next; // Next element in list
+ } // End of while loop
+
+
+ } // check for zoom level
+
+ vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+#endif // INCLUDE_APRS
+
#define GCONF_KEY_POI_DL_URL GCONF_KEY_PREFIX"/poi_dl_url"
#define GCONF_KEY_DEG_FORMAT GCONF_KEY_PREFIX"/deg_format"
+// APRS
+#ifdef INCLUDE_APRS
+#define GCONF_KEY_APRS_SERVER GCONF_KEY_PREFIX"/aprs_server"
+#define GCONF_KEY_APRS_SERVER_PORT GCONF_KEY_PREFIX"/aprs_server_port"
+#define GCONF_KEY_APRS_SERVER_VAL GCONF_KEY_PREFIX"/aprs_mycall_val"
+#define GCONF_KEY_APRS_MYCALL GCONF_KEY_PREFIX"/aprs_mycall"
+#define GCONF_KEY_APRS_INET_BEACON GCONF_KEY_PREFIX"/aprs_inet_beacon"
+#define GCONF_KEY_APRS_INET_BEACON_INTERVAL GCONF_KEY_PREFIX"/aprs_inet_beacon_interval"
+#define GCONF_KEY_APRS_ENABLE GCONF_KEY_PREFIX"/aprs_enable"
+#define GCONF_KEY_APRS_ENABLE_INET_TX GCONF_KEY_PREFIX"/aprs_enable_inet_tx"
+#define GCONF_KEY_APRS_ENABLE_TTY_TX GCONF_KEY_PREFIX"/aprs_enable_tty_tx"
+
+#define GCONF_KEY_APRS_MAX_TRACK_PTS GCONF_KEY_PREFIX"/aprs_max_track_points"
+#define GCONF_KEY_APRS_MAX_STATIONS GCONF_KEY_PREFIX"/aprs_max_stations"
+#define GCONF_KEY_APRS_TTY_PORT GCONF_KEY_PREFIX"/aprs_tty_port"
+#define GCONF_KEY_APRS_TNC_CONN_METHOD GCONF_KEY_PREFIX"/aprs_tnc_conn_method"
+#define GCONF_KEY_APRS_TNC_BT_MAC GCONF_KEY_PREFIX"/aprs_tnc_bt_mac"
+#define GCONF_KEY_APRS_INET_AUTO_FILTER GCONF_KEY_PREFIX"/aprs_inet_auto_filter"
+#define GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE GCONF_KEY_PREFIX"/aprs_inet_auto_filter_range"
+#define GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT GCONF_KEY_PREFIX"/aprs_show_new_station_alert"
+#define GCONF_KEY_APRS_BEACON_COMMENT GCONF_KEY_PREFIX"/aprs_beacon_comment"
+#define GCONF_KEY_APRS_TTY_BEACON_INTERVAL GCONF_KEY_PREFIX"/aprs_tty_beacon_interval"
+
+#define GCONF_KEY_APRS_BEACON_PATH GCONF_KEY_PREFIX"/aprs_beacon_path"
+#define GCONF_KEY_APRS_BEACON_SYM_GROUP GCONF_KEY_PREFIX"/aprs_beacon_symbol_group"
+#define GCONF_KEY_APRS_BEACON_SYMBOL GCONF_KEY_PREFIX"/aprs_beacon_symbol"
+#define GCONF_KEY_APRS_BEACON_COMPRESSED GCONF_KEY_PREFIX"/aprs_beacon_compressed"
+
+//////////
+#endif // INCLUDE_APRS
+
typedef struct _ScanInfo ScanInfo;
struct _ScanInfo {
DBusGProxy *sig_proxy;
};
+#ifdef INCLUDE_APRS
+
+typedef struct
+{
+ GtkWidget *txt_aprs_mycall;
+ GtkWidget *txt_aprs_inet_server_validation;
+ GtkWidget *txt_aprs_server;
+ GtkWidget *txt_aprs_inet_beacon_interval;
+ GtkWidget *txt_aprs_inet_beacon_comment;
+ GtkWidget *txt_aprs_max_stations;
+ GtkWidget *txt_aprs_tty_port;
+ GtkWidget *txt_tnc_bt_mac;
+ GtkWidget *txt_auto_filter_range;
+ GtkWidget *txt_beacon_comment;
+ GtkWidget *num_aprs_server_port;
+ GtkWidget *txt_tty_beacon_interval;
+
+ GtkWidget *rad_aprs_file;
+ GtkWidget *rad_tnc_bt;
+ GtkWidget *rad_tnc_file;
+
+ GtkWidget *chk_enable_inet_auto_filter;
+ GtkWidget *chk_enable_inet_tx;
+ GtkWidget *chk_enable_tty_tx;
+ GtkWidget *chk_aprs_show_new_station_alert;
+ GtkWidget *chk_aprs_enabled;
+
+ GtkWidget *btn_scan_bt_tnc;
+
+ GtkWidget *txt_unproto_path;
+ GtkWidget *chk_compressed_beacon;
+ GtkWidget *txt_beacon_group;
+ GtkWidget *txt_beacon_symbol;
+
+} TAprsSettings;
+#endif // INCLUDE_APRS
+
+
typedef struct _KeysDialogInfo KeysDialogInfo;
struct _KeysDialogInfo {
GtkWidget *cmb[CUSTOM_KEY_ENUM_COUNT];
GtkWidget *col[COLORABLE_ENUM_COUNT];
};
+#ifdef INCLUDE_APRS
+typedef enum
+{
+ ALIGN_TOP_LEFT,
+ ALIGN_TOP_CENTER,
+ ALIGN_TOP_RIGHT,
+ ALIGN_CENTER_LEFT,
+ ALIGN_CENTER_CENTER,
+ ALIGN_CENTER_RIGHT,
+ ALIGN_BOTTOM_LEFT,
+ ALIGN_BOTTOM_CENTER,
+ ALIGN_BOTTOM_RIGHT
+} TCellAlignment;
+
+void set_ctrl_alignment(GtkWidget *ctrl, TCellAlignment alignment)
+{
+ gfloat align_hor = 0.0f, align_vert = 0.0f;
+
+ switch(alignment)
+ {
+ case ALIGN_CENTER_LEFT:
+ case ALIGN_CENTER_CENTER:
+ case ALIGN_CENTER_RIGHT:
+ align_vert = 0.5f;
+ break;
+ case ALIGN_BOTTOM_LEFT:
+ case ALIGN_BOTTOM_CENTER:
+ case ALIGN_BOTTOM_RIGHT:
+ align_vert = 1.0f;
+ break;
+ case ALIGN_TOP_LEFT:
+ case ALIGN_TOP_CENTER:
+ case ALIGN_TOP_RIGHT:
+ default:
+ align_vert = 0.0f;
+ break;
+ }
+
+
+ switch(alignment)
+ {
+ case ALIGN_CENTER_CENTER:
+ case ALIGN_BOTTOM_CENTER:
+ case ALIGN_TOP_CENTER:
+ align_hor = 0.5f;
+ break;
+ case ALIGN_CENTER_RIGHT:
+ case ALIGN_BOTTOM_RIGHT:
+ case ALIGN_TOP_RIGHT:
+ align_hor = 1.0f;
+ break;
+ case ALIGN_CENTER_LEFT:
+ case ALIGN_BOTTOM_LEFT:
+ case ALIGN_TOP_LEFT:
+ default:
+ align_hor = 0.0f;
+ break;
+ }
+
+ gtk_misc_set_alignment(GTK_MISC(ctrl), align_hor, align_vert);
+}
+
+void set_ctrl_width(GtkWidget *ctrl, gint width)
+{
+ gtk_widget_set_size_request(ctrl, width, -1);
+}
+
+
+void add_edit_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width,
+ GtkWidget **ctrl, TCellAlignment alignment, gchar * initial_value)
+{
+ GtkWidget * hbox;
+
+ gtk_table_attach(GTK_TABLE(table),
+ hbox = gtk_hbox_new(FALSE, 4),
+ xmin, xmax, ymin, ymax, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+ gtk_box_pack_start(GTK_BOX(hbox),
+ *ctrl = gtk_entry_new(),
+ TRUE, TRUE, 0);
+
+#ifndef LEGACY
+ g_object_set(G_OBJECT(*ctrl), "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+ g_object_set(G_OBJECT(*ctrl), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+
+ set_ctrl_alignment(*ctrl, alignment);
+ set_ctrl_width(*ctrl, width);
+
+ // Set the initial value
+ if(initial_value)
+ {
+ gtk_entry_set_text(GTK_ENTRY(*ctrl), initial_value);
+ }
+}
+
+void add_check_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width,
+ GtkWidget **ctrl, TCellAlignment alignment, gchar * caption, gboolean initial_value)
+{
+ gtk_table_attach(GTK_TABLE(table),
+ *ctrl = gtk_check_button_new_with_label(caption),
+ xmin, xmax, ymin, ymax, GTK_FILL, 0, 2, 4);
+
+ set_ctrl_alignment(*ctrl, alignment);
+ set_ctrl_width(*ctrl, width);
+
+ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(*ctrl), initial_value);
+}
+
+void add_label_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width,
+ GtkWidget **ctrl, TCellAlignment alignment, gchar * initial_value)
+{
+
+ gtk_table_attach(GTK_TABLE(table),
+ *ctrl = gtk_label_new(initial_value),
+ xmin, xmax, ymin, ymax, GTK_FILL, 0, 2, 4);
+// gtk_misc_set_alignment(GTK_MISC(ctrl), 1.f, 0.5f);
+
+ set_ctrl_alignment(*ctrl, alignment);
+ set_ctrl_width(*ctrl, width);
+}
+
+#endif // INCLUDE_APRS
+
/**
* Save all configuration data to GCONF.
/* Save Deg Format. */
gconf_client_set_string(gconf_client,
- GCONF_KEY_DEG_FORMAT, DEG_FORMAT_ENUM_TEXT[_degformat], NULL);
+ GCONF_KEY_DEG_FORMAT, DEG_FORMAT_ENUM_TEXT[_degformat].name , NULL);
/* Save Speed Limit On flag. */
gconf_client_set_bool(gconf_client,
return TRUE;
}
+#ifdef INCLUDE_APRS
+
+void load_aprs_options(GConfClient *gconf_client)
+{
+ _aprs_server = gconf_client_get_string(
+ gconf_client, GCONF_KEY_APRS_SERVER, NULL);
+
+ _aprs_server_port = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_SERVER_PORT, NULL);
+
+ _aprs_inet_server_validation = gconf_client_get_string(
+ gconf_client, GCONF_KEY_APRS_SERVER_VAL, NULL);
+
+ _aprs_mycall = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_MYCALL, NULL);
+
+
+ _aprs_tty_port = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_TTY_PORT, NULL);
+
+
+
+ _aprs_inet_beacon_comment = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_INET_BEACON, NULL);
+
+ _aprs_inet_beacon_interval = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_INET_BEACON_INTERVAL, NULL);
+
+ _aprs_std_pos_hist = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_MAX_TRACK_PTS, NULL);
+
+ _aprs_max_stations = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_MAX_STATIONS, NULL);
+
+ _aprs_enable = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE, NULL);
+
+ _aprs_show_new_station_alert = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT, NULL);
+
+ _aprs_tnc_method = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_TNC_CONN_METHOD, NULL);
+
+ _aprs_tnc_bt_mac = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_TNC_BT_MAC, NULL);
+
+ _aprs_server_auto_filter_km = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE, NULL);
+
+ _aprs_server_auto_filter_on = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_INET_AUTO_FILTER, NULL);
+
+ _aprs_beacon_comment = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_COMMENT, NULL);
+
+ _aprs_enable_inet_tx = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE_INET_TX, NULL);
+ _aprs_enable_tty_tx = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE_TTY_TX, NULL);
+
+ _aprs_tty_beacon_interval = gconf_client_get_int(
+ gconf_client, GCONF_KEY_APRS_TTY_BEACON_INTERVAL, NULL);
+
+
+ //_aprs_unproto_path = g_strdup_printf("WIDE2-2");
+ _aprs_unproto_path = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_PATH, NULL);
+
+ _aprs_transmit_compressed_posit = gconf_client_get_bool(gconf_client,
+ GCONF_KEY_APRS_BEACON_COMPRESSED, NULL);
+
+ gchar *tmp;
+ tmp = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_SYM_GROUP, NULL);
+ if(tmp && strlen(tmp)>0) _aprs_beacon_group = tmp[0];
+ else _aprs_beacon_group = '/';
+
+ //_aprs_beacon_symbol = 'k';
+ tmp = gconf_client_get_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_SYMBOL, NULL);
+ if(tmp && strlen(tmp)>0) _aprs_beacon_symbol = tmp[0];
+ else _aprs_beacon_symbol = 'l';
+}
+#endif // INCLUDE_APRS
+
/**
* Bring up the Settings dialog. Return TRUE if and only if the recever
* information has changed (MAC or channel).
*/
-gboolean
-settings_dialog()
+gboolean settings_dialog()
{
static GtkWidget *dialog = NULL;
static GtkWidget *notebook = NULL;
cmb_degformat = gtk_combo_box_new_text());
for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++)
gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_degformat),
- DEG_FORMAT_ENUM_TEXT[i]);
+ DEG_FORMAT_ENUM_TEXT[i].name);
gtk_table_attach(GTK_TABLE(table),
label = gtk_label_new(_("Auto-Download Pre-cache")),
COLORABLE_GCONF[COLORABLE_ROUTE_BREAK]
= GCONF_KEY_PREFIX"/color_route_break";
COLORABLE_GCONF[COLORABLE_POI] = GCONF_KEY_PREFIX"/color_poi";
-
+
+#ifdef INCLUDE_APRS
+ COLORABLE_GCONF[COLORABLE_APRS_STATION] = GCONF_KEY_PREFIX"/color_aprs_station";
+#endif // INCLUDE_APRS
+
if(!gconf_client)
{
popup_error(_window, _("Failed to initialize GConf. Quitting."));
gint i = 0;
if(degformat_key_str)
for(i = DEG_FORMAT_ENUM_COUNT - 1; i > 0; i--)
- if(!strcmp(degformat_key_str, DEG_FORMAT_ENUM_TEXT[i]))
+ if(!strcmp(degformat_key_str, DEG_FORMAT_ENUM_TEXT[i].name))
break;
_degformat = i;
}
_unblank_option = i;
}
+#ifdef INCLUDE_APRS
+ load_aprs_options(gconf_client);
+#endif // INCLUDE_APRS
+
/* Get Info Font Size. Default is INFO_FONT_MEDIUM. */
{
gchar *info_font_size_str = gconf_client_get_string(gconf_client,
vprintf("%s(): return\n", __PRETTY_FUNCTION__);
}
+
+#ifdef INCLUDE_APRS
+////// APRS Settings start
+
+void read_aprs_options(TAprsSettings *aprsSettings )
+{
+ // Basic options
+ _aprs_mycall = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_mycall)));
+
+
+ _aprs_enable = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(aprsSettings->chk_aprs_enabled ));
+
+ _aprs_show_new_station_alert = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(aprsSettings->chk_aprs_show_new_station_alert ));
+
+
+ gchar * s_max_stations = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_max_stations)));
+ gint64 i64_max_stations = g_ascii_strtoll (s_max_stations, NULL, 10);
+ _aprs_max_stations = (gint)i64_max_stations;
+
+
+ // Inet options
+ _aprs_server = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_server)));
+
+
+ _aprs_tty_port = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_tty_port)));
+
+ _aprs_inet_server_validation = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_inet_server_validation)));
+
+ gchar * s_port = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->num_aprs_server_port)));
+ gint64 i64_port = g_ascii_strtoll (s_port, NULL, 10);
+ _aprs_server_port = (gint)i64_port;
+
+
+ gchar * s_beacon_interval = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_interval )));
+ gint64 i64_beacon_interval = g_ascii_strtoll (s_beacon_interval, NULL, 10);
+ g_free(s_beacon_interval);
+ _aprs_inet_beacon_interval = (gint)i64_beacon_interval;
+
+ s_beacon_interval = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_tty_beacon_interval )));
+ i64_beacon_interval = g_ascii_strtoll (s_beacon_interval, NULL, 10);
+ g_free(s_beacon_interval);
+ _aprs_tty_beacon_interval = (gint)i64_beacon_interval;
+
+
+ _aprs_inet_beacon_comment = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_comment )));
+
+ _aprs_tnc_bt_mac = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_tnc_bt_mac )));
+
+ _aprs_tnc_method = TNC_CONNECTION_BT;
+ if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_file) ))
+ {
+ _aprs_tnc_method = TNC_CONNECTION_FILE;
+ }
+
+
+ _aprs_server_auto_filter_on = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_inet_auto_filter ));
+
+ gchar * s_server_auto_filter_km = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_auto_filter_range )));
+ gint64 i64_server_auto_filter_km = g_ascii_strtoll (s_server_auto_filter_km, NULL, 10);
+ _aprs_server_auto_filter_km = (gint)i64_server_auto_filter_km;
+
+
+ _aprs_enable_inet_tx = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_inet_tx ));
+
+ _aprs_enable_tty_tx = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_tty_tx ));
+
+ _aprs_beacon_comment = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_beacon_comment )));
+
+ _aprs_beacon_comment = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_beacon_comment )));
+
+
+ _aprs_unproto_path = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_unproto_path )));
+
+ gchar *tmp;
+ tmp = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_beacon_group )));
+ if(strlen(tmp)>0) _aprs_beacon_group = tmp[0];
+
+ tmp = g_strdup(gtk_entry_get_text(
+ GTK_ENTRY(aprsSettings->txt_beacon_symbol )));
+ if(strlen(tmp)>0) _aprs_beacon_symbol = tmp[0];
+
+ _aprs_transmit_compressed_posit = FALSE; // Not currently supported
+
+}
+
+void save_aprs_options()
+{
+ GConfClient *gconf_client = gconf_client_get_default();
+ if(!gconf_client)
+ {
+ popup_error(_window,
+ _("Failed to initialize GConf. Settings were not saved."));
+ return;
+ }
+
+
+ /* APRS */
+ if(_aprs_server)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_SERVER, _aprs_server, NULL);
+
+ if(_aprs_server_port)
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_SERVER_PORT, _aprs_server_port, NULL);
+
+ if(_aprs_inet_server_validation)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_SERVER_VAL, _aprs_inet_server_validation, NULL);
+
+ if(_aprs_mycall)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_MYCALL, _aprs_mycall, NULL);
+
+ if(_aprs_tty_port)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_TTY_PORT, _aprs_tty_port, NULL);
+
+
+
+
+ if(_aprs_inet_beacon_comment)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_INET_BEACON, _aprs_inet_beacon_comment, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_INET_BEACON_INTERVAL, _aprs_inet_beacon_interval, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_MAX_TRACK_PTS, _aprs_std_pos_hist, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_MAX_STATIONS, _aprs_max_stations, NULL);
+
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE, _aprs_enable, NULL);
+
+
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT, _aprs_show_new_station_alert, NULL);
+
+ if(_aprs_tnc_bt_mac)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_TNC_BT_MAC, _aprs_tnc_bt_mac, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_TNC_CONN_METHOD, _aprs_tnc_method, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE, _aprs_server_auto_filter_km, NULL);
+
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_INET_AUTO_FILTER, _aprs_server_auto_filter_on, NULL);
+
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE_INET_TX, _aprs_enable_inet_tx, NULL);
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_ENABLE_TTY_TX, _aprs_enable_tty_tx, NULL);
+
+ if(_aprs_beacon_comment)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_COMMENT, _aprs_beacon_comment, NULL);
+
+ gconf_client_set_int(gconf_client,
+ GCONF_KEY_APRS_TTY_BEACON_INTERVAL, _aprs_tty_beacon_interval, NULL);
+
+ gconf_client_set_bool(gconf_client,
+ GCONF_KEY_APRS_BEACON_COMPRESSED, _aprs_transmit_compressed_posit, NULL);
+
+ if(_aprs_unproto_path)
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_PATH, _aprs_unproto_path, NULL);
+
+ //if(_aprs_beacon_group)
+ {
+ gchar tmp[5];
+ snprintf(tmp, 5, "%c", _aprs_beacon_group);
+
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_SYM_GROUP, tmp, NULL);
+
+ //if(strlen(tmp)> 1) _aprs_beacon_group = tmp[0];
+ }
+
+ //if(_aprs_beacon_symbol)
+ {
+ gchar tmp[5];
+ snprintf(tmp, 5, "%c", _aprs_beacon_symbol);
+
+ gconf_client_set_string(gconf_client,
+ GCONF_KEY_APRS_BEACON_SYMBOL, tmp, NULL);
+
+ //if(strlen(tmp)> 1) _aprs_beacon_symbol = tmp[0];
+ }
+
+}
+
+
+
+
+
+void setup_aprs_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+// GtkWidget *hbox;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(3/*rows*/, 4/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("Station")));
+
+ /* Callsign. */
+ // Label
+ add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Callsign");
+ add_edit_box (table, 1, 2, 0, 1, -1, &(aprsSettings->txt_aprs_mycall), ALIGN_TOP_LEFT, _aprs_mycall);
+
+
+ add_check_box(table, 2, 4, 0, 1, -1, &(aprsSettings->chk_compressed_beacon), ALIGN_TOP_LEFT,
+ "Compress Beacons", _aprs_transmit_compressed_posit);
+ // Not yet supported
+ gtk_widget_set_sensitive (GTK_WIDGET (aprsSettings->chk_compressed_beacon), FALSE);
+
+
+
+ add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Beacon Path");
+ add_edit_box (table, 1, 2, 1, 2, -1, &(aprsSettings->txt_unproto_path), ALIGN_TOP_LEFT, _aprs_unproto_path);
+
+
+ add_check_box(table, 2, 4, 1, 2, -1, &(aprsSettings->chk_aprs_show_new_station_alert), ALIGN_TOP_LEFT,
+ "Show New Station Alerts", _aprs_show_new_station_alert);
+
+
+ add_label_box(table, 0, 1, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Symbol Group");
+ gchar initialValue[2];
+ snprintf(initialValue, 2, "%c", _aprs_beacon_group);
+ add_edit_box (table, 1, 2, 2, 3, -1, &(aprsSettings->txt_beacon_group), ALIGN_TOP_LEFT, initialValue);
+
+ add_label_box(table, 2, 3, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Symbol");
+
+ snprintf(initialValue, 2, "%c", _aprs_beacon_symbol);
+ add_edit_box (table, 3, 4, 2, 3, -1, &(aprsSettings->txt_beacon_symbol), ALIGN_TOP_LEFT, initialValue);
+
+}
+
+
+
+void setup_aprs_tty_page_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *hbox;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(3/*rows*/, 2/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("TNC 1")));
+
+
+ // Receiver MAC Address.
+ gtk_table_attach(GTK_TABLE(table),
+ aprsSettings->rad_tnc_bt = gtk_radio_button_new_with_label(
+ NULL, _("Bluetooth")),
+ 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+// gtk_misc_set_alignment(/*GTK_MISC*/(aprsSettings->rad_tnc_bt), 1.f, 0.5f);
+ gtk_table_attach(GTK_TABLE(table),
+ hbox = gtk_hbox_new(FALSE, 4),
+ 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+
+ gtk_box_pack_start(GTK_BOX(hbox),
+ aprsSettings->txt_tnc_bt_mac = gtk_entry_new(),
+ TRUE, TRUE, 0);
+#ifdef MAEMO_CHANGES
+#ifndef LEGACY
+ g_object_set(G_OBJECT(aprsSettings->txt_tnc_bt_mac), "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+ g_object_set(G_OBJECT(txt_gps_bt_mac), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+#endif
+ gtk_box_pack_start(GTK_BOX(hbox),
+ aprsSettings->btn_scan_bt_tnc = gtk_button_new_with_label(_("Scan...")),
+ FALSE, FALSE, 0);
+
+#ifndef HAVE_LIBGPSBT
+gtk_widget_set_sensitive(aprsSettings->rad_tnc_bt, FALSE);
+gtk_widget_set_sensitive(aprsSettings->txt_tnc_bt_mac, FALSE);
+gtk_widget_set_sensitive(aprsSettings->btn_scan_bt_tnc, FALSE);
+#endif
+
+ // File Path (RFComm).
+ gtk_table_attach(GTK_TABLE(table),
+ aprsSettings->rad_tnc_file = gtk_radio_button_new_with_label_from_widget(
+ GTK_RADIO_BUTTON(aprsSettings->rad_tnc_bt), _("File Path")),
+ 0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+// gtk_misc_set_alignment(/*GTK_MISC*/(aprsSettings->rad_tnc_file), 1.f, 0.5f);
+ gtk_table_attach(GTK_TABLE(table),
+ hbox = gtk_hbox_new(FALSE, 4),
+ 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+ gtk_box_pack_start(GTK_BOX(hbox),
+ aprsSettings->txt_aprs_tty_port = gtk_entry_new(),
+ TRUE, TRUE, 0);
+#ifdef MAEMO_CHANGES
+#ifndef LEGACY
+ g_object_set(G_OBJECT(aprsSettings->txt_aprs_tty_port), "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+ g_object_set(G_OBJECT(txt_gps_file_path), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+#endif
+
+
+
+ add_check_box(table, 0, 1, 2, 3, -1, &(aprsSettings->chk_enable_tty_tx), ALIGN_TOP_LEFT,
+ "Enable TX", _aprs_enable_tty_tx);
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(_("Only KISS TNC's are supported (9600 8N1)")),
+ 1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+
+
+
+ // Init
+
+ if(aprsSettings->txt_aprs_tty_port)
+ gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_tty_port), _aprs_tty_port);
+
+ if(aprsSettings->txt_tnc_bt_mac)
+ gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_tnc_bt_mac), _aprs_tnc_bt_mac);
+
+
+ if(_aprs_tnc_method == TNC_CONNECTION_BT)
+ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_bt), TRUE);
+ else if(_aprs_tnc_method == TNC_CONNECTION_FILE)
+ gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_file), TRUE);
+
+
+}
+
+void setup_aprs_tty2_page_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(2/*rows*/, 1/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("TNC 2")));
+
+ add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Beacon Interval");
+
+ gchar s_interval[8]; s_interval[0] = 0;
+ if(_aprs_tty_beacon_interval>0) snprintf(s_interval, 8, "%u", _aprs_tty_beacon_interval );
+ add_edit_box (table, 1, 3, 0, 1, -1, &(aprsSettings->txt_tty_beacon_interval), ALIGN_TOP_LEFT,
+ s_interval);
+ add_label_box(table, 3, 4, 0, 1, -1, &label, ALIGN_TOP_LEFT, "seconds");
+
+ add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Beacon Text");
+ add_edit_box (table, 1, 4, 1, 2, -1, &(aprsSettings->txt_beacon_comment), ALIGN_TOP_LEFT, _aprs_beacon_comment);
+
+}
+
+
+void setup_aprs_general_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(2/*rows*/, 1/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("APRS")));
+
+ // Notice
+ add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT,
+ "APRS (Automatic/Amateur Position Reporting System) is \na system used by Radio Amateurs to communicate position, \nweather, and short messages.");
+
+ // Enabled
+ add_check_box(table, 0, 1, 1, 2, -1, &(aprsSettings->chk_aprs_enabled), ALIGN_TOP_LEFT,
+ "Enable APRS functionality", _aprs_enable);
+}
+
+void setup_aprs_inet_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(3/*rows*/, 5/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("Internet")));
+
+
+ /*iNet server*/
+ add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Server");
+ add_edit_box (table, 1, 3, 0, 1, -1, &(aprsSettings->txt_aprs_server), ALIGN_TOP_LEFT, _aprs_server);
+
+ /*iNet server port*/
+ add_label_box(table, 3, 4, 0, 1, -1, &label, ALIGN_TOP_RIGHT, "Port");
+ add_edit_box (table, 4, 5, 0, 1, 0.5, &(aprsSettings->num_aprs_server_port), ALIGN_TOP_LEFT,
+ g_strdup_printf("%u", _aprs_server_port));
+
+
+ /* Validation */
+ add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Server Validation");
+ add_edit_box (table, 1, 3, 1, 2, 0.5, &(aprsSettings->txt_aprs_inet_server_validation), ALIGN_TOP_LEFT, _aprs_inet_server_validation);
+ add_check_box(table, 3, 5, 1, 2, -1, &(aprsSettings->chk_enable_inet_tx), ALIGN_TOP_LEFT,
+ "Enable TX", _aprs_enable_inet_tx);
+
+ /*iNet server filter*/
+ add_label_box(table, 0, 1, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Filter data");
+ add_check_box(table, 1, 2, 2, 3, -1, &(aprsSettings->chk_enable_inet_auto_filter), ALIGN_TOP_LEFT, "Enable",
+ _aprs_server_auto_filter_on);
+ add_label_box(table, 2, 3, 2, 3, -1, &label, ALIGN_TOP_RIGHT, "Range");
+ add_edit_box (table, 3, 4, 2, 3, 0.75, &(aprsSettings->txt_auto_filter_range), ALIGN_TOP_LEFT,
+ g_strdup_printf("%u", _aprs_server_auto_filter_km));
+ add_label_box(table, 4, 5, 2, 3, -1, &label, ALIGN_TOP_LEFT, "km");
+
+}
+
+
+
+void setup_aprs_inet2_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+ GtkWidget *table;
+ GtkWidget *label;
+ GtkWidget *hbox;
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ table = gtk_table_new(2/*rows*/, 3/*columns*/, FALSE /*Auto re-size*/),
+ label = gtk_label_new(_("Internet 2")));
+
+
+ /* iNet Beacon interval */
+ // Label
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(_("Beacon interval")),
+ 0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ // Edit box
+ gtk_table_attach(GTK_TABLE(table),
+ hbox = gtk_hbox_new(FALSE, 4),
+ 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+ gtk_box_pack_start(GTK_BOX(hbox),
+ aprsSettings->txt_aprs_inet_beacon_interval = gtk_entry_new(),
+ TRUE, TRUE, 0);
+
+#ifndef LEGACY
+ g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_interval), "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+ g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_interval), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(_("seconds")),
+ 2, 3, 0, 1, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ /* Beacon comment */
+ // Label
+ gtk_table_attach(GTK_TABLE(table),
+ label = gtk_label_new(_("Beacon Comment")),
+ 0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+ gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+ // Edit box
+ gtk_table_attach(GTK_TABLE(table),
+ hbox = gtk_hbox_new(FALSE, 4),
+ 1, 3, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+ gtk_box_pack_start(GTK_BOX(hbox),
+ aprsSettings->txt_aprs_inet_beacon_comment = gtk_entry_new(),
+ TRUE, TRUE, 0);
+
+#ifndef LEGACY
+ g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_comment), "hildon-input-mode",
+ HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+ g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_comment), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+
+ // Init values
+ if(_aprs_inet_beacon_interval)
+ {
+ gchar interval[8];
+ snprintf(interval, 8, "%d", _aprs_inet_beacon_interval);
+
+ gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_interval), interval);
+ }
+
+ if(_aprs_inet_beacon_comment)
+ gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_comment), _aprs_inet_beacon_comment);
+
+}
+
+void aprs_settings_dialog(gboolean *aprs_inet_config_changed, gboolean *aprs_tty_config_changed)
+{
+ static GtkWidget *dialog = NULL;
+ static GtkWidget *notebook = NULL;
+ static TAprsSettings aprs_settings;
+ static ScanInfo scan_info = {0};
+
+ printf("%s()\n", __PRETTY_FUNCTION__);
+
+
+ *aprs_inet_config_changed = FALSE;
+ *aprs_tty_config_changed = FALSE;
+
+ if(dialog == NULL)
+ {
+ dialog = gtk_dialog_new_with_buttons(_("APRS Settings"),
+ GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ /* Enable the help button. */
+#ifndef LEGACY
+ hildon_help_dialog_help_enable(
+#else
+ ossohelp_dialog_help_enable(
+#endif
+ GTK_DIALOG(dialog), HELP_ID_SETTINGS, _osso);
+
+ gtk_dialog_add_button(GTK_DIALOG(dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
+
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+ notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+
+
+
+ // Add enable, and info page
+ setup_aprs_general_options_page(notebook, &aprs_settings);
+
+ // Add station details page
+ setup_aprs_options_page(notebook, &aprs_settings);
+
+ // Add iNet page
+ setup_aprs_inet_options_page(notebook, &aprs_settings);
+ setup_aprs_inet2_options_page(notebook, &aprs_settings);
+
+ // Add TTY page
+ setup_aprs_tty_page_options_page(notebook, &aprs_settings);
+ setup_aprs_tty2_page_options_page(notebook, &aprs_settings);
+
+
+
+ /* Connect signals. */
+ memset(&scan_info, 0, sizeof(scan_info));
+ scan_info.settings_dialog = dialog;
+ scan_info.txt_gps_bt_mac = aprs_settings.txt_tnc_bt_mac;
+ g_signal_connect(G_OBJECT(aprs_settings.btn_scan_bt_tnc), "clicked",
+ G_CALLBACK(scan_bluetooth), &scan_info);
+
+ }
+
+ gtk_widget_show_all(dialog);
+
+ if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+ {
+ // TODO - check if settings have really changed
+ *aprs_inet_config_changed = TRUE;
+ *aprs_tty_config_changed = TRUE;
+
+
+ read_aprs_options(&aprs_settings );
+
+ save_aprs_options();
+
+ }
+
+ gtk_widget_hide(dialog);
+
+ vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, rcvr_changed);
+
+}
+#endif // INCLUDE_APRS
+
gboolean settings_dialog(void);
+#ifdef INCLUDE_APRS
+void aprs_settings_dialog(gboolean *aprs_inet_config_changed, gboolean *aprs_tty_config_changed);
+#endif // INCLUDE_APRS
+
#endif /* ifndef MAEMO_MAPPER_SETTINGS_H */
*
* You should have received a copy of the GNU General Public License
* along with Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ *
+ * * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000 Frank Giannandrea
+ * Copyright (C) 2000-2007 The Xastir Group
+ *
*/
#ifndef MAEMO_MAPPER_TYPES_H
#define MAEMO_MAPPER_TYPES_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#include <time.h>
#include <gdbm.h>
#include <gtk/gtk.h>
#include "sqlite3.h"
#endif
+// Latitude and longitude string formats.
+#define CONVERT_HP_NORMAL 0
+#define CONVERT_HP_NOSP 1
+#define CONVERT_LP_NORMAL 2
+#define CONVERT_LP_NOSP 3
+#define CONVERT_DEC_DEG 4
+#define CONVERT_UP_TRK 5
+#define CONVERT_DMS_NORMAL 6
+#define CONVERT_VHP_NOSP 7
+#define CONVERT_DMS_NORMAL_FORMATED 8
+#define CONVERT_HP_NORMAL_FORMATED 9
+#define CONVERT_DEC_DEG_N 10
+
+#define MAX_DEVICE_BUFFER 4096
+
/** This enumerated type defines the possible connection states. */
typedef enum
{
COLORABLE_ROUTE_WAY,
COLORABLE_ROUTE_BREAK,
COLORABLE_POI,
+#ifdef INCLUDE_APRS
+ COLORABLE_APRS_STATION,
+#endif // INCLUDE_APRS
COLORABLE_ENUM_COUNT
} Colorable;
NSEW_DDPDDDDD,
NSEW_DD_MMPMMM,
NSEW_DD_MM_SSPS,
+ IARU_LOC,
+ UK_OSGB,
+ UK_NGR,
DEG_FORMAT_ENUM_COUNT
} DegFormat;
+typedef struct _CoordFormatSetup CoordFormatSetup;
+struct _CoordFormatSetup
+{
+ gchar *name;
+ gchar *short_field_1;
+ gchar *long_field_1;
+ gchar *short_field_2;
+ gchar *long_field_2;
+ gboolean field_2_in_use;
+} _CoordFormatSetup;
+
typedef enum
{
SPEED_LOCATION_BOTTOM_LEFT,
GtkWidget *txt;
};
+
+#ifdef INCLUDE_APRS
+
+// --------------------------------------------------------------------------------------
+// Start of APRS Types - Code taken from Xastir code 25 March 2008 by Rob Williams
+// Modification made to fit in with Maemo mapper
+// --------------------------------------------------------------------------------------
+
+typedef struct _TWriteBuffer TWriteBuffer;
+struct _TWriteBuffer
+{
+ int write_in_pos; /* current write buffer input pos */
+ int write_out_pos; /* current write buffer output pos */
+ GMutex* write_lock; /* Lock for writing the port data */
+ char device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port */
+ int errors;
+};
+
+
+typedef enum
+{
+ TNC_CONNECTION_BT,
+ TNC_CONNECTION_FILE
+} TTncConnection;
+
+typedef enum
+{
+ APRS_PORT_INET,
+ APRS_PORT_TTY,
+ APRS_PORT_COUNT
+} TAprsPort;
+
+// We should probably be using APRS_DF in extract_bearing_NRQ()
+// and extract_omnidf() functions. We aren't currently.
+/* Define APRS Types */
+enum APRS_Types {
+ APRS_NULL,
+ APRS_MSGCAP,
+ APRS_FIXED,
+ APRS_DOWN, // Not used anymore
+ APRS_MOBILE,
+ APRS_DF,
+ APRS_OBJECT,
+ APRS_ITEM,
+ APRS_STATUS,
+ APRS_WX1,
+ APRS_WX2,
+ APRS_WX3,
+ APRS_WX4,
+ APRS_WX5,
+ APRS_WX6,
+ QM_WX,
+ PEET_COMPLETE,
+ RSWX200,
+ GPS_RMC,
+ GPS_GGA,
+ GPS_GLL,
+ STATION_CALL_DATA,
+ OTHER_DATA,
+ APRS_MICE,
+ APRS_GRID,
+ DALLAS_ONE_WIRE,
+ DAVISMETEO
+};
+
+
+/* Define Record Types */
+#define NORMAL_APRS 'N'
+#define MOBILE_APRS 'M'
+#define DF_APRS 'D'
+#define DOWN_APRS 'Q'
+#define NORMAL_GPS_RMC 'C'
+#define NORMAL_GPS_GGA 'A'
+#define NORMAL_GPS_GLL 'L'
+
+/* define RECORD ACTIVES */
+#define RECORD_ACTIVE 'A'
+#define RECORD_NOTACTIVE 'N'
+#define RECORD_CLOSED 'C'
+
+/* define data from info type */
+#define DATA_VIA_LOCAL 'L'
+#define DATA_VIA_TNC 'T'
+#define DATA_VIA_NET 'I'
+#define DATA_VIA_FILE 'F'
+
+
+/* define Heard info type */
+#define VIA_TNC 'Y'
+#define NOT_VIA_TNC 'N'
+
+/* define Message types */
+#define MESSAGE_MESSAGE 'M'
+#define MESSAGE_BULLETIN 'B'
+#define MESSAGE_NWS 'W'
+
+
+// trail flag definitions
+#define MAX_CALLSIGN 9
+#define MAX_TIME 20
+#define MAX_LONG 12
+#define MAX_LAT 11
+#define MAX_ALTITUDE 10 //-32808.4 to 300000.0? feet
+#define MAX_SPEED 9 /* ?? 3 in knots */
+#define MAX_COURSE 7 /* ?? */
+#define MAX_POWERGAIN 7
+#define MAX_STATION_TIME 10 /* 6+1 */
+#define MAX_SAT 4
+#define MAX_DISTANCE 10
+#define MAX_WXSTATION 50
+#define MAX_TEMP 100
+#define MAX_MULTIPOINTS 35
+
+#define MAX_DEVICE_BUFFER 4096
+
+/* define max size of info field */
+#define MAX_INFO_FIELD_SIZE 256
+
+// Number of times to send killed objects/items before ceasing to
+// transmit them.
+#define MAX_KILLED_OBJECT_RETRANSMIT 20
+
+// Check entire station list at this rate for objects/items that
+// might need to be transmitted via the decaying algorithm. This is
+// the start rate, which gets doubled on each transmit.
+#define OBJECT_CHECK_RATE 20
+
+
+#define MAX_MESSAGE_LENGTH 100
+#define MAX_MESSAGE_ORDER 10
+
+
+#define CHECKMALLOC(m) if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); }
+
+
+#define STATION_REMOVE_CYCLE 300 /* check station remove in seconds (every 5 minutes) */
+#define MESSAGE_REMOVE_CYCLE 600 /* check message remove in seconds (every 10 minutes) */
+#define IN_VIEW_MIN 600l /* margin for off-screen stations, with possible trails on screen, in minutes */
+#define TRAIL_POINT_MARGIN 30l /* margin for off-screen trails points, for segment to be drawn, in minutes */
+#define TRAIL_MAX_SPEED 900 /* max. acceptible speed for drawing trails, in mph */
+#define MY_TRAIL_COLOR 0x16 /* trail color index reserved for my station */
+#define TRAIL_ECHO_TIME 30 /* check for delayed echos during last 30 minutes */
+/* MY_TRAIL_DIFF_COLOR changed to user configurable my_trail_diff_color */
+
+
+typedef struct {
+ int digis;
+ int wxs;
+ int other_mobiles;
+ int mobiles_in_motion;
+ int homes;
+ int total;
+} aloha_stats;
+
+
+// station flag definitions. We have 16 bits available here as
+// "flag" in "DataRow" is defined as a short.
+//
+#define ST_OBJECT 0x01 // station is an object
+#define ST_ITEM 0x02 // station is an item
+#define ST_ACTIVE 0x04 // station is active (deleted objects are
+ // inactive)
+#define ST_MOVING 0x08 // station is moving
+#define ST_DIRECT 0x10 // heard direct (not via digis)
+#define ST_VIATNC 0x20 // station heard via TNC
+#define ST_3RD_PT 0x40 // third party traffic (not used yet)
+#define ST_MSGCAP 0x80 // message capable (not used yet)
+#define ST_STATUS 0x100 // got real status message
+#define ST_INVIEW 0x200 // station is in current screen view
+#define ST_MYSTATION 0x400 // station is owned by my call-SSID
+#define ST_MYOBJITEM 0x800 // object/item owned by me
+
+
+#define TR_LOCAL 0x01 // heard direct (not via digis)
+#define TR_NEWTRK 0x02 // start new track
+
+
+enum AprsAreaObjectTypes {
+ AREA_OPEN_CIRCLE = 0x0,
+ AREA_LINE_LEFT = 0x1,
+ AREA_OPEN_ELLIPSE = 0x2,
+ AREA_OPEN_TRIANGLE = 0x3,
+ AREA_OPEN_BOX = 0x4,
+ AREA_FILLED_CIRCLE = 0x5,
+ AREA_LINE_RIGHT = 0x6,
+ AREA_FILLED_ELLIPSE = 0x7,
+ AREA_FILLED_TRIANGLE = 0x8,
+ AREA_FILLED_BOX = 0x9,
+ AREA_MAX = 0x9,
+ AREA_NONE = 0xF
+};
+
+
+
+enum AprsAreaObjectColors {
+ AREA_BLACK_HI = 0x0,
+ AREA_BLUE_HI = 0x1,
+ AREA_GREEN_HI = 0x2,
+ AREA_CYAN_HI = 0x3,
+ AREA_RED_HI = 0x4,
+ AREA_VIOLET_HI = 0x5,
+ AREA_YELLOW_HI = 0x6,
+ AREA_GRAY_HI = 0x7,
+ AREA_BLACK_LO = 0x8,
+ AREA_BLUE_LO = 0x9,
+ AREA_GREEN_LO = 0xA,
+ AREA_CYAN_LO = 0xB,
+ AREA_RED_LO = 0xC,
+ AREA_VIOLET_LO = 0xD,
+ AREA_YELLOW_LO = 0xE,
+ AREA_GRAY_LO = 0xF
+};
+
+
+typedef struct {
+ unsigned type : 4;
+ unsigned color : 4;
+ unsigned sqrt_lat_off : 8;
+ unsigned sqrt_lon_off : 8;
+ unsigned corridor_width : 16;
+} AprsAreaObject;
+
+typedef struct {
+ char aprs_type;
+ char aprs_symbol;
+ char special_overlay;
+ AprsAreaObject area_object;
+} APRS_Symbol;
+
+// Struct for holding track data. Keeps a dynamically allocated
+// doubly-linked list of track points. The first record should have its
+// "prev" pointer set to NULL and the last record should have its "next"
+// pointer set to NULL. If no track storage exists then the pointers to
+// these structs in the DataRow struct should be NULL.
+typedef struct _AprsTrackRow{
+ long trail_long_pos; // coordinate of trail point
+ long trail_lat_pos; // coordinate of trail point
+ time_t sec; // date/time of position
+ long speed; // in 0.1 km/h undefined: -1
+ int course; // in degrees undefined: -1
+ long altitude; // in 0.1 m undefined: -99999
+ char flag; // several flags, see below
+ struct _AprsTrackRow *prev; // pointer to previous record in list
+ struct _AprsTrackRow *next; // pointer to next record in list
+} AprsTrackRow;
+
+
+
+// Struct for holding current weather data.
+// This struct is pointed to by the DataRow structure.
+// An empty string indicates undefined data.
+typedef struct { // strlen
+ time_t wx_sec_time;
+ int wx_storm; // Set to one if severe storm
+ char wx_time[MAX_TIME];
+ char wx_course[4]; // in ° 3
+ char wx_speed[4]; // in mph 3
+ time_t wx_speed_sec_time;
+ char wx_gust[4]; // in mph 3
+ char wx_hurricane_radius[4]; //nautical miles 3
+ char wx_trop_storm_radius[4]; //nautical miles 3
+ char wx_whole_gale_radius[4]; // nautical miles 3
+ char wx_temp[5]; // in °F 3
+ char wx_rain[10]; // in hundredths inch/h 3
+ char wx_rain_total[10]; // in hundredths inch
+ char wx_snow[6]; // in inches/24h 3
+ char wx_prec_24[10]; // in hundredths inch/day 3
+ char wx_prec_00[10]; // in hundredths inch 3
+ char wx_hum[5]; // in % 3
+ char wx_baro[10]; // in hPa 6
+ char wx_fuel_temp[5]; // in °F 3
+ char wx_fuel_moisture[5];// in % 2
+ char wx_type;
+ char wx_station[MAX_WXSTATION];
+} AprsWeatherRow;
+
+
+// Struct for holding comment/status data. Will keep a dynamically
+// allocated list of text. Every different comment field will be
+// stored in a separate line.
+typedef struct _AprsCommentRow{
+ char *text_ptr; // Ptr to the comment text
+ time_t sec_heard; // Latest timestamp for this comment/status
+ struct _AprsCommentRow *next; // Ptr to next record or NULL
+} AprsCommentRow;
+
+
+
+
+// Struct for holding multipoint data.
+typedef struct _AprsMultipointRow{
+ long multipoints[MAX_MULTIPOINTS][2];
+} AprsMultipointRow;
+
+typedef struct _AprsDisplayData
+{
+ gchar *call_sign; // call sign or name index or object/item
+ // name
+ gdouble coord_lon;
+ gdouble coord_lat;
+
+ gchar *coord_lat_lon;
+ gchar *path;
+
+ gchar *comment;
+ gchar *status;
+} AprsDisplayData;
+
+typedef struct _AprsDataRow {
+
+ struct _AprsDataRow *n_next; // pointer to next element in name ordered list
+ struct _AprsDataRow *n_prev; // pointer to previous element in name ordered
+ // list
+ struct _AprsDataRow *t_newer; // pointer to next element in time ordered
+ // list (newer)
+ struct _AprsDataRow *t_older; // pointer to previous element in time ordered
+ // list (older)
+
+
+ char call_sign[MAX_CALLSIGN+1]; // call sign or name index or object/item
+ // name
+ char *tactical_call_sign; // Tactical callsign. NULL if not assigned
+ APRS_Symbol aprs_symbol;
+ long coord_lon; // Xastir coordinates 1/100 sec, 0 = 180°W
+ long coord_lat; // Xastir coordinates 1/100 sec, 0 = 90°N
+
+ int time_sn; // serial number for making time index unique
+ time_t sec_heard; // time last heard, used also for time index
+ time_t heard_via_tnc_last_time;
+ time_t direct_heard; // KC2ELS - time last heard direct
+
+// Change into time_t structs? It'd save us a bunch of space.
+ char packet_time[MAX_TIME];
+ char pos_time[MAX_TIME];
+
+ short flag; // several flags, see below
+ char pos_amb; // Position ambiguity, 0 = none,
+ // 1 = 0.1 minute...
+
+ unsigned int error_ellipse_radius; // Degrades precision for this
+ // station, from 0 to 65535 cm or
+ // 655.35 meters. Assigned when we
+ // decode each type of packet.
+ // Default is 6.0 meters (600 cm)
+ // unless we know the GPS position
+ // is augmented, or is degraded by
+ // less precision in the packet.
+
+ unsigned int lat_precision; // In 100ths of a second latitude
+ unsigned int lon_precision; // In 100ths of a second longitude
+
+ int trail_color; // trail color (when assigned)
+ char record_type;
+ //char data_via; // L local, T TNC, I internet, F file
+
+// Change to char's to save space?
+ int heard_via_tnc_port; // Current this will always be 0, but keep for future
+ TAprsPort last_port_heard; // Current this will always be 0, but keep for future
+ unsigned int num_packets;
+ char *node_path_ptr; // Pointer to path string
+ char altitude[MAX_ALTITUDE]; // in meters (feet gives better resolution ??)
+ char speed[MAX_SPEED+1]; // in knots (same as nautical miles/hour)
+ char course[MAX_COURSE+1];
+ char bearing[MAX_COURSE+1];
+ char NRQ[MAX_COURSE+1];
+ char power_gain[MAX_POWERGAIN+1]; // Holds the phgd values
+ char signal_gain[MAX_POWERGAIN+1]; // Holds the shgd values (for DF'ing)
+
+ AprsWeatherRow *weather_data; // Pointer to weather data or NULL
+
+ AprsCommentRow *status_data; // Ptr to status records or NULL
+ AprsCommentRow *comment_data; // Ptr to comment records or NULL
+
+ // Below two pointers are NULL if only one position has been received
+ AprsTrackRow *oldest_trackpoint; // Pointer to oldest track point in
+ // doubly-linked list
+ AprsTrackRow *newest_trackpoint; // Pointer to newest track point in
+ // doubly-linked list
+
+ // When the station is an object, it can include coordinates
+ // of related points. Currently these are being used to draw
+ // outlines of NWS severe weather watches and warnings, and
+ // storm regions. The coordinates are stored here in Xastir
+ // coordinate form. Element [x][0] is the latitude, and
+ // element [x][1] is the longitude. --KG4NBB
+ //
+ // Is there anything preventing a multipoint string from being
+ // in other types of packets, in the comment field? --WE7U
+ //
+ int num_multipoints;
+ char type; // from '0' to '9'
+ char style; // from 'a' to 'z'
+ AprsMultipointRow *multipoint_data;
+
+
+///////////////////////////////////////////////////////////////////////
+// Optional stuff for Objects/Items only (I think, needs to be
+// checked). These could be moved into an ObjectRow structure, with
+// only a NULL pointer here if not an object/item.
+///////////////////////////////////////////////////////////////////////
+
+ char origin[MAX_CALLSIGN+1]; // call sign originating an object
+ short object_retransmit; // Number of times to retransmit object.
+ // -1 = forever
+ // Used currently to stop sending killed
+ // objects.
+ time_t last_transmit_time; // Time we last transmitted an object/item.
+ // Used to implement decaying transmit time
+ // algorithm
+ short transmit_time_increment; // Seconds to add to transmit next time
+ // around. Used to implement decaying
+ // transmit time algorithm
+// time_t last_modified_time; // Seconds since the object/item
+ // was last modified. We'll
+ // eventually use this for
+ // dead-reckoning.
+ char signpost[5+1]; // Holds signpost data
+ int df_color;
+ char sats_visible[MAX_SAT];
+ char probability_min[10+1]; // Holds prob_min (miles)
+ char probability_max[10+1]; // Holds prob_max (miles)
+
+} AprsDataRow;
+
+typedef enum
+{
+ APRSPOI_SELECTED,
+ APRSPOI_CALLSIGN,
+// APRSPOI_LAT,
+// APRSPOI_LON,
+// APRSPOI_LATLON,
+// APRSPOI_BEARING,
+// APRSPOI_DISTANCE,
+// APRSPOI_PATH,
+// APRSPOI_COMMENT,
+// APRSPOI_STATUS,
+ APRSPOI_NUM_COLUMNS
+} APRSPOIList;
+
+
+
+typedef struct _AprsStationList{
+ struct _AprsStationList *next; // pointer to next record in list
+ AprsDataRow *station;
+} AprsStationList;
+
+
+
+
+// --------------------------------------------------------------------------------------
+// End of APRS Types - Code taken from Xastir code 25 March 2008 by Rob Williams M1BGT
+// Modification made to fit in with Maemo mapper
+// --------------------------------------------------------------------------------------
+#endif // INCLUDE_APRS
+
+
#endif /* ifndef MAEMO_MAPPER_TYPES_H */
switch(_degformat)
{
+ case IARU_LOC:
+ case UK_OSGB:
+ case UK_NGR:
+ // These formats should not be formatted in the same way
+ // - they need to be converted first, therefore if we reach
+ // this bit of code use the first available format - drop through.
case DDPDDDDD:
sprintf(scoor, "%.5f°", coor);
break;
+
case DDPDDDDD_NSEW:
sprintf(scoor, "%.5f° %c", acoor,
coor < 0.0 ? neg_char : pos_char);
return s * d;
}
+double marc(double bf0, double n, double phi0, double phi)
+{
+ return bf0 * (((1 + n + ((5 / 4) * (n * n)) + ((5 / 4) * (n * n * n))) * (phi - phi0))
+ - (((3 * n) + (3 * (n * n)) + ((21 / 8) * (n * n * n))) * (sin(phi - phi0)) * (cos(phi + phi0)))
+ + ((((15 / 8) * (n * n)) + ((15 / 8) * (n * n * n))) * (sin(2 * (phi - phi0))) * (cos(2 * (phi + phi0))))
+ - (((35 / 24) * (n * n * n)) * (sin(3 * (phi - phi0))) * (cos(3 * (phi + phi0)))));
+}
+
+gboolean os_grid_check_lat_lon(double lat, double lon)
+{
+ // TODO - Check exact OS Grid range
+ if(lat < 50.0 || lat > 62 || lon < -7.5 || lon > 2.2 )
+ {
+ return FALSE;
+ }
+ else
+ {
+ return TRUE;
+ }
+}
+
+gboolean coord_system_check_lat_lon (gdouble lat, gdouble lon, gint *fallback_deg_format)
+{
+ // Is the current coordinate system applicable to the provided lat and lon?
+ gboolean valid = FALSE;
+
+ switch(_degformat)
+ {
+ case UK_OSGB:
+ case UK_NGR:
+ valid = os_grid_check_lat_lon(lat, lon);
+ if(fallback_deg_format != NULL) *fallback_deg_format = DDPDDDDD;
+ break;
+ default:
+ valid = TRUE;
+ break;
+ }
+
+ return valid;
+}
+
+gboolean convert_lat_long_to_os_grid(double lat, double lon, int *easting, int *northing)
+{
+ if(!os_grid_check_lat_lon(lat, lon))
+ {
+ return FALSE;
+ }
+
+ const double deg2rad = (2 * PI / 360);
+
+ const double phi = lat * deg2rad; // convert latitude to radians
+ const double lam = lon * deg2rad; // convert longitude to radians
+ const double a = 6377563.396; // OSGB semi-major axis
+ const double b = 6356256.91; // OSGB semi-minor axis
+ const double e0 = 400000; // easting of false origin
+ const double n0 = -100000; // northing of false origin
+ const double f0 = 0.9996012717; // OSGB scale factor on central meridian
+ const double e2 = 0.0066705397616; // OSGB eccentricity squared
+ const double lam0 = -0.034906585039886591; // OSGB false east
+ const double phi0 = 0.85521133347722145; // OSGB false north
+ const double af0 = a * f0;
+ const double bf0 = b * f0;
+
+ // easting
+ double slat2 = sin(phi) * sin(phi);
+ double nu = af0 / (sqrt(1 - (e2 * (slat2))));
+ double rho = (nu * (1 - e2)) / (1 - (e2 * slat2));
+ double eta2 = (nu / rho) - 1;
+ double p = lam - lam0;
+ double IV = nu * cos(phi);
+ double clat3 = pow(cos(phi), 3);
+ double tlat2 = tan(phi) * tan(phi);
+ double V = (nu / 6) * clat3 * ((nu / rho) - tlat2);
+ double clat5 = pow(cos(phi), 5);
+ double tlat4 = pow(tan(phi), 4);
+ double VI = (nu / 120) * clat5 * ((5 - (18 * tlat2)) + tlat4 + (14 * eta2) - (58 * tlat2 * eta2));
+ double east = e0 + (p * IV) + (pow(p, 3) * V) + (pow(p, 5) * VI);
+
+ // northing
+ double n = (af0 - bf0) / (af0 + bf0);
+ double M = marc(bf0, n, phi0, phi);
+ double I = M + (n0);
+ double II = (nu / 2) * sin(phi) * cos(phi);
+ double III = ((nu / 24) * sin(phi) * pow(cos(phi), 3)) * (5 - pow(tan(phi), 2) + (9 * eta2));
+ double IIIA = ((nu / 720) * sin(phi) * clat5) * (61 - (58 * tlat2) + tlat4);
+ double north = I + ((p * p) * II) + (pow(p, 4) * III) + (pow(p, 6) * IIIA);
+
+ // make whole number values
+ *easting = round(east); // round to whole number
+ *northing = round(north); // round to whole number
+
+ return TRUE;
+}
+
+gboolean convert_os_grid_to_bng(gint easting, gint northing, gchar* bng)
+{
+ gdouble eX = (gdouble)easting / 500000.0;
+ gdouble nX = (gdouble)northing / 500000.0;
+ gdouble tmp = floor(eX) - 5.0 * floor(nX) + 17.0;
+ gchar eing[12];
+ gchar ning[12];
+
+ nX = 5.0 * (nX - floor(nX));
+ eX = 20.0 - 5.0 * floor(nX) + floor(5.0 * (eX - floor(eX)));
+ if (eX > 7.5) eX = eX + 1; // I is not used
+ if (tmp > 7.5) tmp = tmp + 1; // I is not used
+
+ snprintf(eing, 12, "%u", easting);
+ snprintf(ning, 12, "%u", northing);
+
+ gint lnth = strlen(eing);
+ // Note - we only want 4 numbers
+ snprintf(eing, 5, "%s", eing+(lnth-5));
+
+ lnth = strlen(ning);
+ snprintf(ning, 5, "%s", ning+(lnth-5));
+
+ sprintf(bng, "%c%c%s%s",
+ (char)(tmp + 65),
+ (char)(eX + 65),
+ eing, ning
+ );
+
+
+ return TRUE;
+}
+
+gboolean convert_os_xy_to_latlon(const gchar *easting, const gchar *northing, gdouble *d_lat, gdouble *d_lon)
+{
+ gint64 i64_n = g_ascii_strtoll (northing, NULL, 10);
+ gint64 i64_e = g_ascii_strtoll (easting, NULL, 10);
+
+ const double deg2rad = (2 * PI / 360);
+
+ const gdouble N = (gdouble)i64_n;
+ const gdouble E = (gdouble)i64_e;
+
+ const gdouble a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
+ const gdouble F0 = 0.9996012717; // NatGrid scale factor on central meridian
+ const gdouble lat0 = 49*PI/180, lon0 = -2*PI/180; // NatGrid true origin
+ const gdouble N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
+ const gdouble e2 = 1 - (b*b)/(a*a); // eccentricity squared
+ const gdouble n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
+
+ gdouble lat=lat0, M=0;
+ do {
+ lat = (N-N0-M)/(a*F0) + lat;
+
+ const gdouble Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
+ const gdouble Mb = (3*n + 3*n*n + (21/8)*n3) * sin(lat-lat0) * cos(lat+lat0);
+ const gdouble Mc = ((15/8)*n2 + (15/8)*n3) * sin(2*(lat-lat0)) * cos(2*(lat+lat0));
+ const gdouble Md = (35/24)*n3 * sin(3*(lat-lat0)) * cos(3*(lat+lat0));
+ M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
+
+ } while (N-N0-M >= 0.00001); // ie until < 0.01mm
+
+ const gdouble cosLat = cos(lat), sinLat = sin(lat);
+ const gdouble nu = a*F0/sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
+ const gdouble rho = a*F0*(1-e2)/pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
+ const gdouble eta2 = nu/rho-1;
+
+ const gdouble tanLat = tan(lat);
+ const gdouble tan2lat = tanLat*tanLat, tan4lat = tan2lat*tan2lat, tan6lat = tan4lat*tan2lat;
+ const gdouble secLat = 1/cosLat;
+ const gdouble nu3 = nu*nu*nu, nu5 = nu3*nu*nu, nu7 = nu5*nu*nu;
+ const gdouble VII = tanLat/(2*rho*nu);
+ const gdouble VIII = tanLat/(24*rho*nu3)*(5+3*tan2lat+eta2-9*tan2lat*eta2);
+ const gdouble IX = tanLat/(720*rho*nu5)*(61+90*tan2lat+45*tan4lat);
+ const gdouble X = secLat/nu;
+ const gdouble XI = secLat/(6*nu3)*(nu/rho+2*tan2lat);
+ const gdouble XII = secLat/(120*nu5)*(5+28*tan2lat+24*tan4lat);
+ const gdouble XIIA = secLat/(5040*nu7)*(61+662*tan2lat+1320*tan4lat+720*tan6lat);
+
+ const gdouble dE = (E-E0), dE2 = dE*dE, dE3 = dE2*dE, dE4 = dE2*dE2, dE5 = dE3*dE2;
+ const gdouble dE6 = dE4*dE2, dE7 = dE5*dE2;
+ lat = lat - VII*dE2 + VIII*dE4 - IX*dE6;
+ const gdouble lon = lon0 + X*dE - XI*dE3 + XII*dE5 - XIIA*dE7;
+
+ *d_lon = lon / deg2rad;
+ *d_lat = lat / deg2rad;
+
+ return TRUE;
+}
+
+gboolean convert_os_ngr_to_latlon(const gchar *text, gdouble *d_lat, gdouble *d_lon)
+{
+ // get numeric values of letter references, mapping A->0, B->1, C->2, etc:
+ gint l1;
+ gint l2;
+
+ gchar s_e[6], s_n[6];
+ gchar easting[7], northing[7];
+ gint64 i64_e = 0;
+ gint64 i64_n = 0;
+
+
+ if( ((gchar)text[0])>='a' && ((gchar)text[0]) <= 'z' )
+ l1 = text[0] - (gint)'a'; // lower case
+ else if( ((gchar)text[0])>='A' && ((gchar)text[0]) <= 'Z' )
+ l1 = text[0] - (gint)'A'; // upper case
+ else
+ return FALSE; // Not a letter - invalid grid ref
+
+ if( ((gchar)text[1])>='a' && ((gchar)text[1]) <= 'z' )
+ l2 = text[1] - (gint)'a'; // lower case
+ else if( ((gchar)text[1])>='A' && ((gchar)text[1]) <= 'Z' )
+ l2 = text[1] - (gint)'A'; // upper case
+ else
+ return FALSE; // Not a letter - invalid grid ref
+
+
+ // shuffle down letters after 'I' since 'I' is not used in grid:
+ if (l1 > 7) l1--;
+ if (l2 > 7) l2--;
+
+ // convert grid letters into 100km-square indexes from false origin (grid square SV):
+ gdouble e = ((l1-2)%5)*5 + (l2%5);
+ gdouble n = (19-floor(l1/5)*5) - floor(l2/5);
+
+ // skip grid letters to get numeric part of ref, stripping any spaces:
+ gchar *gridref = (gchar*)(text+2);
+
+ // user may have entered a space, so remove any spaces
+ while(gridref[0] == ' ')
+ {
+ gridref = (gchar*)(text+1);
+ }
+
+
+ // floor the length incase a space has been added
+ const gint len = (gint)floor((gdouble)strlen(gridref)/2.00); // normally this will be 4, often 3
+ if(len>5) return FALSE;
+
+ if(len >0)
+ {
+ snprintf(s_e, len+1, "%s", gridref);
+
+ while( (gchar)((gint)gridref+len) == ' ' )
+ gridref = (gchar*)(gridref+1); // Allow for a space
+
+ snprintf(s_n, len+1, "%s", gridref+len);
+
+ i64_e = g_ascii_strtoll (s_e, NULL, 10);
+ i64_n = g_ascii_strtoll (s_n, NULL, 10);
+
+ // Move to most significate values
+ i64_e *= pow(10, 5-len);
+ i64_n *= pow(10, 5-len);
+ }
+
+ // append numeric part of references to grid index:
+ e = (e*100000) + (gdouble)i64_e;
+ n = (n*100000) + (gdouble)i64_n;
+
+ snprintf(easting, 7, "%06u", (gint)e);
+ snprintf(northing, 7, "%06u", (gint)n);
+
+ convert_os_xy_to_latlon(easting, northing, d_lat, d_lon);
+
+ return TRUE;
+}
+
+
+// Attempt to convert any user entered grid reference to a double lat/lon
+// return TRUE on valid
+gboolean parse_coords(const gchar* txt_lat, const gchar* txt_lon, gdouble* lat, gdouble* lon)
+{
+ gboolean valid = FALSE;
+
+ // UK_NGR starts with two letters, and then all numbers - it may contain spaces - no lon will be entered
+ if( _degformat == UK_NGR)
+ {
+ valid = convert_os_ngr_to_latlon(txt_lat, lat, lon);
+
+ if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+ else if(*lon < -180. || *lon > 180.) { valid = FALSE; }
+ }
+ // UK_OSGB contains two 6 digit integers
+ else if( _degformat == UK_OSGB)
+ {
+ valid = convert_os_xy_to_latlon(txt_lat, txt_lon, lat, lon);
+
+ if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+ else if(*lon < -180. || *lon > 180.) { valid = FALSE; }
+
+ }
+ else if( _degformat == IARU_LOC)
+ {
+ valid = convert_iaru_loc_to_lat_lon(txt_lat, lat, lon);
+
+ if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+ else if(*lon < -180. || *lon > 180.) { valid = FALSE; }
+ }
+ // It must either be invalid, or a lat/lon format
+ else
+ {
+ gchar* error_check;
+ *lat = strdmstod(txt_lat, &error_check);
+
+ if(txt_lat == error_check || *lat < -90. || *lat > 90.) { valid = FALSE; }
+ else { valid = TRUE; }
+
+ if(valid == TRUE)
+ {
+ *lon = strdmstod(txt_lon, &error_check);
+
+ if(txt_lon == error_check || *lon < -180. || *lon > 180.) { valid = FALSE; }
+ }
+ }
+
+
+
+ return valid;
+}
+
+gboolean convert_iaru_loc_to_lat_lon(const gchar* txt_lon, gdouble* lat, gdouble* lon)
+{
+ gint u_first = 0;
+ gint u_second = 0;
+ gint u_third = 0;
+ gint u_fourth = 0;
+ gint u_fifth = 0;
+ gint u_sixth = 0;
+ gint u_seventh = 0;
+ gint u_eighth = 0;
+
+ if(strlen(txt_lon) >= 1)
+ {
+ if( ((gchar)txt_lon[0])>='a' && ((gchar)txt_lon[0]) <= 'z' )
+ u_first = txt_lon[0] - (gint)'a'; // lower case
+ else if( ((gchar)txt_lon[0])>='A' && ((gchar)txt_lon[0]) <= 'Z' )
+ u_first = txt_lon[0] - (gint)'A'; // upper case
+ }
+
+ if(strlen(txt_lon) >= 2)
+ {
+ if( ((gchar)txt_lon[1])>='a' && ((gchar)txt_lon[1]) <= 'z' )
+ u_second = txt_lon[1] - (gint)'a'; // lower case
+ else if( ((gchar)txt_lon[1])>='A' && ((gchar)txt_lon[1]) <= 'Z' )
+ u_second = txt_lon[1] - (gint)'A'; // upper case
+ }
+
+ if(strlen(txt_lon) >= 3)
+ u_third = txt_lon[2] - (gint)'0';
+
+ if(strlen(txt_lon) >= 4)
+ u_fourth = txt_lon[3] - (gint)'0';
+
+ if(strlen(txt_lon) >= 5)
+ {
+ if( ((gchar)txt_lon[4])>='a' && ((gchar)txt_lon[4]) <= 'z' )
+ u_fifth = txt_lon[4] - (gint)'a'; // lower case
+ else if( ((gchar)txt_lon[4])>='A' && ((gchar)txt_lon[4]) <= 'Z' )
+ u_fifth = txt_lon[4] - (gint)'A'; // upper case
+ }
+
+ if(strlen(txt_lon) >= 6)
+ {
+ if( ((gchar)txt_lon[5])>='a' && ((gchar)txt_lon[5]) <= 'z' )
+ u_sixth = txt_lon[5] - (gint)'a'; // lower case
+ else if( ((gchar)txt_lon[5])>='A' && ((gchar)txt_lon[5]) <= 'Z' )
+ u_sixth = txt_lon[5] - (gint)'A'; // upper case
+ }
+
+
+ if(strlen(txt_lon) >= 7)
+ u_seventh= txt_lon[6] - (gint)'0';
+
+ if(strlen(txt_lon) >= 8)
+ u_eighth = txt_lon[7] - (gint)'0';
+
+ *lat = ((gdouble)u_first * 20.0) + ((gdouble)u_third * 2.0) + ((gdouble)u_fifth * (2.0/24.0)) + ((gdouble)u_seventh * (2.0/240.0)) - 90.0;
+ *lon = ((gdouble)u_second * 10.0) + ((gdouble)u_fourth) + ((gdouble)u_sixth * (1.0/24.0)) + ((gdouble)u_eighth * (1.0/240.0)) - 180.0;
+
+ return TRUE;
+}
+
+void convert_lat_lon_to_iaru_loc(gdouble d_lat, gdouble d_lon, gchar *loc)
+{
+ const gdouble d_a_lat = (d_lat+90.0);
+ const gdouble d_a_lon = (d_lon+180.0);
+
+ const gint i_first = (gint)floor(d_a_lon/20.0);
+ const gint i_second = (gint)floor(d_a_lat/10.0);
+ const gint i_third = (gint)floor((d_a_lon - (20.0*i_first))/2.0);
+ const gint i_fourth = (gint)floor((d_a_lat - (10.0*i_second)));
+ const gint i_fifth = (gint)floor((d_a_lon - (20.0*i_first) - (2.0*i_third))/(5.0/60.0));
+ const gint i_sixth = (gint)floor((d_a_lat - (10.0*i_second) - (i_fourth))/(2.5/60.0));
+
+ const gint i_seventh = (gint)floor((d_a_lon - (20.0*i_first) - (2.0*i_third)
+ - ((2.0/24.0)*i_fifth))/(2.0/240.0) );
+ const gint i_eighth = (gint)floor((d_a_lat - (10.0*i_second) - (i_fourth)
+ - ((1.0/24.0)*i_sixth))/(1.0/240.0));
+
+
+ sprintf(loc, "%c%c%u%u%c%c%u%u",
+ 'A'+i_first,
+ 'A'+i_second,
+ i_third,
+ i_fourth,
+ 'a' + i_fifth,
+ 'a' + i_sixth,
+ i_seventh,
+ i_eighth);
+}
+
+gboolean convert_lat_lon_to_bng(gdouble lat, gdouble lon, gchar* bng)
+{
+ gint easting, northing;
+
+ if( convert_lat_long_to_os_grid(lat, lon, &easting, &northing) )
+ {
+ if( convert_os_grid_to_bng(easting, northing, bng) )
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+void format_lat_lon(gdouble d_lat, gdouble d_lon, gchar* lat, gchar* lon)
+{
+ gint east = 0;
+ gint north = 0;
+
+ switch (_degformat)
+ {
+ case UK_OSGB:
+
+ if(convert_lat_long_to_os_grid(d_lat, d_lon, &east, &north))
+ {
+ sprintf(lat, "%06d", east);
+ sprintf(lon, "%06d", north);
+ }
+ else
+ {
+ // Failed (possibly out of range), so use defaults
+ lat_format(d_lat, lat);
+ lon_format(d_lon, lon);
+ }
+ break;
+ case UK_NGR:
+
+ if(convert_lat_lon_to_bng(d_lat, d_lon, lat))
+ {
+ lon[0] = 0;
+ }
+ else
+ {
+ // Failed (possibly out of range), so use defaults
+ lat_format(d_lat, lat);
+ lat_format(d_lon, lon);
+ }
+ break;
+
+ case IARU_LOC:
+ convert_lat_lon_to_iaru_loc(d_lat, d_lon, lat);
+
+ break;
+
+ default:
+ lat_format(d_lat, lat);
+ lon_format(d_lon, lon);
+
+ break;
+ }
+}
+
#if 0
struct t_case {
gchar *fmt;
gdouble strdmstod(const gchar *nptr, gchar **endptr);
+gboolean parse_coords(const gchar* txt_lat, const gchar* txt_lon, gdouble* lat, gdouble* lon);
+void format_lat_lon(gdouble d_lat, gdouble d_lon, gchar* lat, gchar* lon);
+
+gboolean coord_system_check_lat_lon (gdouble lat, gdouble lon, gint *fallback_deg_format);
+
#endif /* ifndef MAEMO_MAPPER_UTIL_H */