From cc4d2808cec990bf07f9f60ced4b815ceb885073 Mon Sep 17 00:00:00 2001 From: camel Date: Fri, 19 Dec 2008 18:28:23 +0000 Subject: [PATCH] Added basic APRS support - Can be disabled by removing definition of INCLUDE_APRS Added support for UK OS Grid system git-svn-id: svn+ssh://garage/var/lib/gforge/svnroot/maemo-mapper/trunk@230 6c538b50-5814-0410-93ad-8bdf4c0149d1 --- src/Makefile.am | 9 +- src/aprs.c | 2121 ++++++++++ src/aprs.h | 165 + src/aprs_decode.c | 8597 +++++++++++++++++++++++++++++++++++++++ src/aprs_decode.h | 60 + src/aprs_display.c | 1106 +++++ src/aprs_display.h | 41 + src/aprs_kiss.c | 1534 +++++++ src/aprs_kiss.h | 19 + src/aprs_message.c | 2312 +++++++++++ src/aprs_message.h | 82 + src/cmenu.c | 45 +- src/data.c | 59 +- src/data.h | 49 +- src/defines.h | 41 + src/display.c | 304 +- src/display.h | 6 + src/gpsbt.c | 26 - src/hashtable.c | 296 ++ src/hashtable.h | 220 + src/hashtable_itr.c | 236 ++ src/hashtable_itr.h | 149 + src/hashtable_private.h | 107 + src/input.c | 15 + src/main.c | 170 +- src/maps.c | 273 +- src/menu.c | 199 +- src/poi.c | 122 +- src/settings.c | 893 +++- src/settings.h | 4 + src/types.h | 501 +++ src/util.c | 477 +++ src/util.h | 5 + 33 files changed, 20024 insertions(+), 219 deletions(-) create mode 100644 src/aprs.c create mode 100644 src/aprs.h create mode 100644 src/aprs_decode.c create mode 100644 src/aprs_decode.h create mode 100644 src/aprs_display.c create mode 100644 src/aprs_display.h create mode 100644 src/aprs_kiss.c create mode 100644 src/aprs_kiss.h create mode 100644 src/aprs_message.c create mode 100644 src/aprs_message.h create mode 100644 src/hashtable.c create mode 100644 src/hashtable.h create mode 100644 src/hashtable_itr.c create mode 100644 src/hashtable_itr.h create mode 100644 src/hashtable_private.h diff --git a/src/Makefile.am b/src/Makefile.am index b48e018..e474aa1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,6 +40,13 @@ maemo_mapper_SOURCES = \ 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 diff --git a/src/aprs.c b/src/aprs.c new file mode 100644 index 0000000..76e62cb --- /dev/null +++ b/src/aprs.c @@ -0,0 +1,2121 @@ +/* + * + * 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 . + * + * + * 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 +#include +#include +#include "data.h" + +#include + +#include +#include +#include +#include "aprs_display.h" + +#ifndef LEGACY +# include +# include +#else +# include +# include +#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= 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 diff --git a/src/aprs.h b/src/aprs.h new file mode 100644 index 0000000..618aea9 --- /dev/null +++ b/src/aprs.h @@ -0,0 +1,165 @@ +/* + * + * 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 . + * + * + * 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 + +#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 diff --git a/src/aprs_decode.c b/src/aprs_decode.c new file mode 100644 index 0000000..9f7ee23 --- /dev/null +++ b/src/aprs_decode.c @@ -0,0 +1,8597 @@ +/* + * + * 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 . + * + * + * 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 +#include +#include +#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,")) != 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 and end with a . + // 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;i7 && 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' || 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') { + 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 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= '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= 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= 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 && ofsweather_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= '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; iiflag &= (~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" +// +// "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 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 diff --git a/src/aprs_decode.h b/src/aprs_decode.h new file mode 100644 index 0000000..b871141 --- /dev/null +++ b/src/aprs_decode.h @@ -0,0 +1,60 @@ +/* + * + * 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 . + * + * + * 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 + diff --git a/src/aprs_display.c b/src/aprs_display.c new file mode 100644 index 0000000..b5c1818 --- /dev/null +++ b/src/aprs_display.c @@ -0,0 +1,1106 @@ +/* + * 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 . + * + */ + +#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 +#include +#include +#include +#include +#include + +#ifndef LEGACY +# include +# include +# include +# include +# include +# include +#else +# include +# include +# include +# include +# include +# include +# include +#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 diff --git a/src/aprs_display.h b/src/aprs_display.h new file mode 100644 index 0000000..e7b3d3e --- /dev/null +++ b/src/aprs_display.h @@ -0,0 +1,41 @@ + +/* + * 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 . + * + */ + + +#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 diff --git a/src/aprs_kiss.c b/src/aprs_kiss.c new file mode 100644 index 0000000..62238a5 --- /dev/null +++ b/src/aprs_kiss.c @@ -0,0 +1,1534 @@ +/* + * + * 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 . + * + * + * 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 +#include + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // Moved ahead of inet.h as reports of some *BSD's not + // including this as they should. + + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DBUS_API_SUBJECT_TO_CHANGE +#include + + +#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 = ""; + } + + /* For each bondend BT GPS device, try to create rfcomm */ + for (i=0; icode : -1, + error ? error->message : "", + tmp ? tmp : ""); +*/ + /* 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 : ""); +/* 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 : ""); +*/ + 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-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 diff --git a/src/aprs_kiss.h b/src/aprs_kiss.h new file mode 100644 index 0000000..0efd9f2 --- /dev/null +++ b/src/aprs_kiss.h @@ -0,0 +1,19 @@ + +#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 diff --git a/src/aprs_message.c b/src/aprs_message.c new file mode 100644 index 0000000..1975a4d --- /dev/null +++ b/src/aprs_message.c @@ -0,0 +1,2312 @@ +/* + * + * 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 . + * + * + * 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 (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 diff --git a/src/aprs_message.h b/src/aprs_message.h new file mode 100644 index 0000000..301a213 --- /dev/null +++ b/src/aprs_message.h @@ -0,0 +1,82 @@ +/* + * + * 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 . + * + * + * 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 diff --git a/src/cmenu.c b/src/cmenu.c index 1c5612d..b579fd0 100644 --- a/src/cmenu.c +++ b/src/cmenu.c @@ -61,21 +61,42 @@ static void 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__); } @@ -651,7 +672,7 @@ void cmenu_init() /* 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"))); @@ -685,11 +706,11 @@ void cmenu_init() 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()); diff --git a/src/data.c b/src/data.c index fc68bab..08df291 100644 --- a/src/data.c +++ b/src/data.c @@ -21,6 +21,10 @@ * along with Maemo Mapper. If not, see . */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #define _GNU_SOURCE #include "types.h" @@ -58,9 +62,10 @@ GdkColor COLORABLE_DEFAULT[COLORABLE_ENUM_COUNT] = {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]; @@ -210,6 +215,47 @@ InfoFontSize _info_font_size = INFO_FONT_MEDIUM; 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; @@ -302,6 +348,15 @@ GtkWidget *_menu_gps_show_info_item = 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; diff --git a/src/data.h b/src/data.h index 1ac3b3d..393d2dd 100644 --- a/src/data.h +++ b/src/data.h @@ -24,7 +24,12 @@ #ifndef MAEMO_MAPPER_DATA_H #define MAEMO_MAPPER_DATA_H +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include +#include "types.h" /* Constants regarding enums and defaults. */ extern gchar *UNITS_ENUM_TEXT[UNITS_ENUM_COUNT]; @@ -39,7 +44,7 @@ extern gchar *CUSTOM_KEY_ICON[CUSTOM_KEY_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]; @@ -334,5 +339,47 @@ extern GtkWidget *_cmenu_poi_goto_nearpoi_item; * 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 */ diff --git a/src/defines.h b/src/defines.h index c156afb..42fd470 100644 --- a/src/defines.h +++ b/src/defines.h @@ -50,6 +50,33 @@ #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. */ @@ -63,6 +90,9 @@ #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) @@ -264,12 +294,23 @@ _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. */ \ diff --git a/src/display.c b/src/display.c index 381b923..cd79d39 100644 --- a/src/display.c +++ b/src/display.c @@ -30,6 +30,9 @@ #include #include #include +#include "aprs.h" +#include "aprs_decode.h" +#include "types.h" #ifndef LEGACY # include @@ -106,6 +109,10 @@ PangoLayout *_sat_info_layout = NULL; 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 @@ -122,6 +129,200 @@ speed_excess(void) 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) { @@ -197,7 +398,7 @@ 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) @@ -214,13 +415,17 @@ gps_display_details(void) { 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) @@ -337,7 +542,7 @@ gps_display_details(void) 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) @@ -353,13 +558,16 @@ gps_display_data(void) { 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) @@ -2471,12 +2679,33 @@ latlon_cb_fmt_changed(GtkWidget *widget, LatlonDialog *lld) { { 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; } } @@ -2488,12 +2717,24 @@ latlon_dialog(gdouble lat, gdouble lon) 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, @@ -2504,23 +2745,26 @@ latlon_dialog(gdouble lat, gdouble lon) 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); @@ -2537,11 +2781,17 @@ latlon_dialog(gdouble lat, gdouble lon) /* 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 */ @@ -2550,7 +2800,7 @@ latlon_dialog(gdouble lat, gdouble lon) 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); } @@ -2563,6 +2813,9 @@ latlon_dialog(gdouble lat, gdouble lon) 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); @@ -2572,6 +2825,9 @@ latlon_dialog(gdouble lat, gdouble lon) 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__); @@ -2686,6 +2942,16 @@ display_init() 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); diff --git a/src/display.h b/src/display.h index 76973af..7f60fb0 100644 --- a/src/display.h +++ b/src/display.h @@ -29,6 +29,8 @@ typedef struct { GtkWidget *fmt_combo; GtkWidget *lat; GtkWidget *lon; + GtkWidget *lat_title; + GtkWidget *lon_title; } LatlonDialog; @@ -74,4 +76,8 @@ gboolean display_open_file(GtkWindow *parent, gchar **bytes_out, 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 */ diff --git a/src/gpsbt.c b/src/gpsbt.c index 5dad65b..1a1deed 100644 --- a/src/gpsbt.c +++ b/src/gpsbt.c @@ -94,32 +94,6 @@ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WAR #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 { diff --git a/src/hashtable.c b/src/hashtable.c new file mode 100644 index 0000000..e005277 --- /dev/null +++ b/src/hashtable.c @@ -0,0 +1,296 @@ + + +/* + * + * 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 . + * + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef INCLUDE_APRS + +#include +#include +#include +#include + +#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 + * + * 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. + * */ + + diff --git a/src/hashtable.h b/src/hashtable.h new file mode 100644 index 0000000..eb31085 --- /dev/null +++ b/src/hashtable.h @@ -0,0 +1,220 @@ + + +/* + * + * 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 . + * + * + * 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 + */ + +#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 + * + * 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. + * */ + + diff --git a/src/hashtable_itr.c b/src/hashtable_itr.c new file mode 100644 index 0000000..f2ca872 --- /dev/null +++ b/src/hashtable_itr.c @@ -0,0 +1,236 @@ +/* + * + * 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 . + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef INCLUDE_APRS + +#include /* defines NULL */ +#include +//#include +//#include + +#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 + * + * 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 + + diff --git a/src/hashtable_itr.h b/src/hashtable_itr.h new file mode 100644 index 0000000..35dc8ea --- /dev/null +++ b/src/hashtable_itr.h @@ -0,0 +1,149 @@ +/* + * + * 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 . + * + * + * 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 + * + */ + +#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 + * + * 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. + * */ + + diff --git a/src/hashtable_private.h b/src/hashtable_private.h new file mode 100644 index 0000000..74796e1 --- /dev/null +++ b/src/hashtable_private.h @@ -0,0 +1,107 @@ +/* + * + * 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 . + * + * + * 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 + * + */ + +#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 + * + * 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. + * */ + + diff --git a/src/input.c b/src/input.c index bc48c1f..4af8e21 100644 --- a/src/input.c +++ b/src/input.c @@ -643,7 +643,22 @@ map_cb_button_release(GtkWidget *widget, GdkEventButton *event) 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( diff --git a/src/main.c b/src/main.c index 5ce2abf..0a30384 100644 --- a/src/main.c +++ b/src/main.c @@ -60,6 +60,7 @@ #include "dbus-ifc.h" #include "display.h" #include "gps.h" +#include "aprs.h" #include "gpx.h" #include "input.h" #include "main.h" @@ -232,6 +233,72 @@ maemo_mapper_destroy() 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(). */ @@ -345,16 +412,92 @@ maemo_mapper_init(gint argc, gchar **argv) = _("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"); @@ -513,7 +656,12 @@ maemo_mapper_init(gint argc, gchar **argv) 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) { diff --git a/src/maps.c b/src/maps.c index 2ac2b43..d304458 100644 --- a/src/maps.c +++ b/src/maps.c @@ -3389,9 +3389,16 @@ mapman_dialog() 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) @@ -3402,6 +3409,52 @@ mapman_dialog() 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( @@ -3520,14 +3573,18 @@ mapman_dialog() /* 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")), @@ -3538,12 +3595,16 @@ mapman_dialog() 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")), @@ -3554,11 +3615,16 @@ mapman_dialog() 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 */ @@ -3586,25 +3652,30 @@ mapman_dialog() 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")), @@ -3628,25 +3699,30 @@ mapman_dialog() 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); @@ -3667,41 +3743,38 @@ mapman_dialog() 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. */ { @@ -3755,37 +3828,29 @@ mapman_dialog() } 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)) @@ -3794,6 +3859,8 @@ mapman_dialog() } gtk_widget_hide(dialog); + + _degformat = prev_degformat; vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__); return TRUE; diff --git a/src/menu.c b/src/menu.c index 0d9171c..f9e0600 100644 --- a/src/menu.c +++ b/src/menu.c @@ -47,6 +47,9 @@ #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" @@ -295,8 +298,10 @@ menu_cb_track_insert_mark(GtkMenuItem *item) } 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); @@ -853,13 +858,15 @@ menu_cb_view_goto_latlon(GtkMenuItem *item) /* 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); @@ -1242,6 +1249,125 @@ menu_cb_view_fullscreen(GtkMenuItem *item) 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 ********************************************************** ****************************************************************************/ @@ -1757,6 +1883,47 @@ menu_init() 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..."))); @@ -1905,6 +2072,24 @@ menu_init() 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); diff --git a/src/poi.c b/src/poi.c index 70abfff..38f1f33 100644 --- a/src/poi.c +++ b/src/poi.c @@ -420,8 +420,10 @@ select_poi(gint unitx, gint unity, PoiInfo *poi, gboolean quick) 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), @@ -1232,8 +1234,9 @@ poi_add_dialog(GtkWidget *parent, gint unitx, gint unity) { 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); @@ -1386,7 +1389,18 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi) 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) { @@ -1406,21 +1420,24 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi) 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); @@ -1484,11 +1501,16 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi) { 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 */ @@ -1513,23 +1535,19 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi) 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) @@ -2307,8 +2325,9 @@ poi_list_dialog(GtkWidget *parent, gint unitx, gint unity, GList *poi_list) 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, @@ -3261,3 +3280,44 @@ poi_destroy() 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 + diff --git a/src/settings.c b/src/settings.c index ba46750..a0b8146 100644 --- a/src/settings.c +++ b/src/settings.c @@ -131,6 +131,37 @@ #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 { @@ -145,6 +176,44 @@ 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]; @@ -155,6 +224,131 @@ struct _ColorsDialogInfo { 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. @@ -281,7 +475,7 @@ settings_save() /* 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, @@ -1127,12 +1321,96 @@ settings_dialog_colors(GtkWidget *widget, GtkWidget *parent) 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; @@ -1476,7 +1754,7 @@ settings_dialog() 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")), @@ -1965,7 +2243,11 @@ settings_init() 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.")); @@ -2149,7 +2431,7 @@ settings_init() 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; } @@ -2190,6 +2472,10 @@ settings_init() _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, @@ -2551,3 +2837,598 @@ settings_init() 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 + diff --git a/src/settings.h b/src/settings.h index e707991..7850670 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,4 +31,8 @@ void settings_save(void); 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 */ diff --git a/src/types.h b/src/types.h index 08fa064..85bdb9b 100644 --- a/src/types.h +++ b/src/types.h @@ -19,11 +19,24 @@ * * You should have received a copy of the GNU General Public License * along with Maemo Mapper. If not, see . + * + * + * 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 #include #include @@ -37,6 +50,21 @@ #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 { @@ -224,6 +252,9 @@ typedef enum COLORABLE_ROUTE_WAY, COLORABLE_ROUTE_BREAK, COLORABLE_POI, +#ifdef INCLUDE_APRS + COLORABLE_APRS_STATION, +#endif // INCLUDE_APRS COLORABLE_ENUM_COUNT } Colorable; @@ -238,9 +269,23 @@ typedef enum 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, @@ -457,4 +502,460 @@ struct _BrowseInfo { 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 */ diff --git a/src/util.c b/src/util.c index e947914..16f04be 100644 --- a/src/util.c +++ b/src/util.c @@ -71,9 +71,16 @@ deg_format(gdouble coor, gchar *scoor, gchar neg_char, gchar pos_char) 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); @@ -485,6 +492,476 @@ strdmstod(const gchar *nptr, gchar **endptr) 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; diff --git a/src/util.h b/src/util.h index c4c2c7c..18d0625 100644 --- a/src/util.h +++ b/src/util.h @@ -51,4 +51,9 @@ void deg_format(gdouble coor, gchar *scoor, gchar neg_char, gchar pos_char); 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 */ -- 2.44.0