mirror of https://github.com/wb2osz/direwolf.git
1195 lines
31 KiB
C
1195 lines
31 KiB
C
//
|
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
|
//
|
|
// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Module: tt-user.c
|
|
*
|
|
* Purpose: Keep track of the APRStt users.
|
|
*
|
|
* Description: This maintains a list of recently heard APRStt users
|
|
* and prepares "object" format packets for transmission.
|
|
*
|
|
* References: This is based upon APRStt (TM) documents but not 100%
|
|
* compliant due to ambiguities and inconsistencies in
|
|
* the specifications.
|
|
*
|
|
* http://www.aprs.org/aprstt.html
|
|
*
|
|
*---------------------------------------------------------------*/
|
|
|
|
#include "direwolf.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
|
|
#include "version.h"
|
|
#include "ax25_pad.h"
|
|
#include "textcolor.h"
|
|
#include "aprs_tt.h"
|
|
#include "tt_text.h"
|
|
#include "dedupe.h"
|
|
#include "tq.h"
|
|
#include "igate.h"
|
|
#include "tt_user.h"
|
|
#include "encode_aprs.h"
|
|
#include "latlong.h"
|
|
|
|
#include "server.h"
|
|
#include "kiss.h"
|
|
#include "kissserial.h"
|
|
#include "kissnet.h"
|
|
#include "kiss_frame.h"
|
|
|
|
/*
|
|
* Information kept about local APRStt users.
|
|
*
|
|
* For now, just use a fixed size array for simplicity.
|
|
*/
|
|
|
|
#if TT_MAIN
|
|
#define MAX_TT_USERS 3
|
|
#else
|
|
#define MAX_TT_USERS 100
|
|
#endif
|
|
|
|
#define MAX_CALLSIGN_LEN 9 /* "Object Report" names can be up to 9 characters. */
|
|
|
|
#define MAX_COMMENT_LEN 43 /* Max length of comment in "Object Report." */
|
|
|
|
//#define G_UNKNOWN -999999 /* Should be in one place. */
|
|
|
|
#define NUM_XMITS 3
|
|
#define XMIT_DELAY_1 5
|
|
#define XMIT_DELAY_2 8
|
|
#define XMIT_DELAY_3 13
|
|
|
|
|
|
static struct tt_user_s {
|
|
|
|
char callsign[MAX_CALLSIGN_LEN+1]; /* Callsign of station heard. */
|
|
/* Does not include the "-12" SSID added later. */
|
|
/* Possibly other tactical call / object label. */
|
|
/* Null string indicates table position is not used. */
|
|
|
|
int count; /* Number of times we received information for this object. */
|
|
/* Value 1 means first time and could be used to send */
|
|
/* a welcome greeting. */
|
|
|
|
int ssid; /* SSID to add. */
|
|
/* Default of 12 but not always. */
|
|
|
|
char overlay; /* Overlay character. Should be 0-9, A-Z. */
|
|
/* Could be / or \ for general object. */
|
|
|
|
char symbol; /* 'A' for traditional. */
|
|
/* Can be any symbol for extended objects. */
|
|
|
|
char digit_suffix[3+1]; /* Suffix abbreviation as 3 digits. */
|
|
|
|
time_t last_heard; /* Timestamp when last heard. */
|
|
/* User information will be deleted at some */
|
|
/* point after last time being heard. */
|
|
|
|
int xmits; /* Number of remaining times to transmit info */
|
|
/* about the user. This is set to 3 when */
|
|
/* a station is heard and decremented each time */
|
|
/* an object packet is sent. The idea is to send */
|
|
/* 3 within 30 seconds to improve chances of */
|
|
/* being heard while using digipeater duplicate */
|
|
/* removal. */
|
|
// TODO: I think implementation is different.
|
|
|
|
time_t next_xmit; /* Time for next transmit. Meaningful only */
|
|
/* if xmits > 0. */
|
|
|
|
int corral_slot; /* If location is known, set this to 0. */
|
|
/* Otherwise, this is a display offset position */
|
|
/* from the gateway. */
|
|
|
|
char loc_text[24]; /* Text representation of location when a single */
|
|
/* lat/lon point would be deceptive. e.g. */
|
|
/* 32TPP8049 */
|
|
/* 32TPP8179549363 */
|
|
/* 32T 681795 4849363 */
|
|
/* EM29QE78 */
|
|
|
|
double latitude, longitude; /* Location either from user or generated */
|
|
/* position in the corral. */
|
|
|
|
int ambiguity; /* Number of digits to omit from location. */
|
|
/* Default 0, max. 4. */
|
|
|
|
char freq[12]; /* Frequency in format 999.999MHz */
|
|
|
|
char ctcss[5]; /* CTCSS tone. Exactly 3 digits for integer part. */
|
|
/* For example 74.4 Hz becomes "074". */
|
|
|
|
char comment[MAX_COMMENT_LEN+1]; /* Free form comment from user. */
|
|
/* Comment sent in final object report includes */
|
|
/* other information besides this. */
|
|
|
|
char mic_e; /* Position status. */
|
|
/* Should be a character in range of '1' to '9' for */
|
|
/* the predefined status strings or '0' for none. */
|
|
|
|
char dao[8]; /* Enhanced position information. */
|
|
|
|
|
|
} tt_user[MAX_TT_USERS];
|
|
|
|
|
|
static void clear_user(int i);
|
|
|
|
static void xmit_object_report (int i, int first_time);
|
|
|
|
static void tt_setenv (int i);
|
|
|
|
|
|
#if __WIN32__
|
|
|
|
// setenv is missing on Windows!
|
|
|
|
int setenv(const char *name, const char *value, int overwrite)
|
|
{
|
|
char etemp[1000];
|
|
|
|
snprintf (etemp, sizeof(etemp), "%s=%s", name, value);
|
|
putenv (etemp);
|
|
return (0);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_user_init
|
|
*
|
|
* Purpose: Initialize the APRStt gateway at system startup time.
|
|
*
|
|
* Inputs: Configuration options gathered by config.c.
|
|
*
|
|
* Global out: Make our own local copy of the structure here.
|
|
*
|
|
* Returns: None
|
|
*
|
|
* Description: The main program needs to call this at application
|
|
* start up time after reading the configuration file.
|
|
*
|
|
* TT_MAIN is defined for unit testing.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static struct audio_s *save_audio_config_p;
|
|
|
|
static struct tt_config_s *save_tt_config_p;
|
|
|
|
|
|
void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_config)
|
|
{
|
|
int i;
|
|
|
|
save_audio_config_p = p_audio_config;
|
|
|
|
save_tt_config_p = p_tt_config;
|
|
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
clear_user (i);
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_user_search
|
|
*
|
|
* Purpose: Search for user in recent history.
|
|
*
|
|
* Inputs: callsign - full or a old style 3 DIGIT suffix abbreviation
|
|
* overlay
|
|
*
|
|
* Returns: Handle for refering to table position or -1 if not found.
|
|
* This happens to be an index into an array but
|
|
* the implementation could change so the caller should
|
|
* not make any assumptions.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
int tt_user_search (char *callsign, char overlay)
|
|
{
|
|
int i;
|
|
/*
|
|
* First, look for exact match to full call and overlay.
|
|
*/
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (strcmp(callsign, tt_user[i].callsign) == 0 &&
|
|
overlay == tt_user[i].overlay) {
|
|
return (i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for digits only suffix plus overlay.
|
|
*/
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (strcmp(callsign, tt_user[i].digit_suffix) == 0 &&
|
|
overlay != ' ' &&
|
|
overlay == tt_user[i].overlay) {
|
|
return (i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for digits only suffix if no overlay was specified.
|
|
*/
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (strcmp(callsign, tt_user[i].digit_suffix) == 0 &&
|
|
overlay == ' ') {
|
|
return (i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Not sure about the new spelled suffix yet...
|
|
*/
|
|
return (-1);
|
|
|
|
} /* end tt_user_search */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_3char_suffix_search
|
|
*
|
|
* Purpose: Search for new style 3 CHARACTER (vs. 3 digit) suffix in recent history.
|
|
*
|
|
* Inputs: suffix - full or a old style 3 DIGIT suffix abbreviation
|
|
*
|
|
* Outputs: callsign - corresponding full callsign or empty string.
|
|
*
|
|
* Returns: Handle for refering to table position (>= 0) or -1 if not found.
|
|
* This happens to be an index into an array but
|
|
* the implementation could change so the caller should
|
|
* not make any assumptions.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
int tt_3char_suffix_search (char *suffix, char *callsign)
|
|
{
|
|
int i;
|
|
|
|
|
|
/*
|
|
* Look for suffix in list of known calls.
|
|
*/
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
int len = strlen(tt_user[i].callsign);
|
|
|
|
if (len >= 3 && len <= 6 && strcmp(tt_user[i].callsign + len - 3, suffix) == 0) {
|
|
strlcpy (callsign, tt_user[i].callsign, MAX_CALLSIGN_LEN+1);
|
|
return (i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Not found.
|
|
*/
|
|
strlcpy (callsign, "", MAX_CALLSIGN_LEN+1);
|
|
return (-1);
|
|
|
|
} /* end tt_3char_suffix_search */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: clear_user
|
|
*
|
|
* Purpose: Clear specified user table entry.
|
|
*
|
|
* Inputs: handle for user table entry.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static void clear_user(int i)
|
|
{
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
|
|
memset (&(tt_user[i]), 0, sizeof (struct tt_user_s));
|
|
|
|
} /* end clear_user */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: find_avail
|
|
*
|
|
* Purpose: Find an available user table location.
|
|
*
|
|
* Inputs: none
|
|
*
|
|
* Returns: Handle for refering to table position.
|
|
*
|
|
* Description: If table is already full, this should delete the
|
|
* least recently heard user to make room.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static int find_avail (void)
|
|
{
|
|
int i;
|
|
int i_oldest;
|
|
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (tt_user[i].callsign[0] == '\0') {
|
|
clear_user (i);
|
|
return (i);
|
|
}
|
|
}
|
|
|
|
/* Remove least recently heard. */
|
|
|
|
i_oldest = 0;
|
|
|
|
for (i=1; i<MAX_TT_USERS; i++) {
|
|
if (tt_user[i].last_heard < tt_user[i_oldest].last_heard) {
|
|
i_oldest = i;
|
|
}
|
|
}
|
|
|
|
clear_user (i_oldest);
|
|
return (i_oldest);
|
|
|
|
} /* end find_avail */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: corral_slot
|
|
*
|
|
* Purpose: Find an available position in the corral.
|
|
*
|
|
* Inputs: none
|
|
*
|
|
* Returns: Small integer >= 1 not already in use.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static int corral_slot (void)
|
|
{
|
|
int slot, i, used;
|
|
|
|
for (slot=1; ; slot++) {
|
|
used = 0;;
|
|
for (i=0; i<MAX_TT_USERS && ! used; i++) {
|
|
if (tt_user[i].callsign[0] != '\0' && tt_user[i].corral_slot == slot) {
|
|
used = 1;
|
|
}
|
|
}
|
|
if (!used) {
|
|
return (slot);
|
|
}
|
|
}
|
|
|
|
} /* end corral_slot */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: digit_suffix
|
|
*
|
|
* Purpose: Find 3 digit only suffix code for given call.
|
|
*
|
|
* Inputs: callsign
|
|
*
|
|
* Outputs: 3 digit suffix
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static void digit_suffix (char *callsign, char *suffix)
|
|
{
|
|
char two_key[50];
|
|
char *t;
|
|
|
|
|
|
strlcpy (suffix, "000", 5); // TODO: should have proper size
|
|
tt_text_to_two_key (callsign, 0, two_key);
|
|
for (t = two_key; *t != '\0'; t++) {
|
|
if (isdigit(*t)) {
|
|
suffix[0] = suffix[1];
|
|
suffix[1] = suffix[2];
|
|
suffix[2] = *t;
|
|
}
|
|
}
|
|
|
|
|
|
} /* end digit_suffix */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_user_heard
|
|
*
|
|
* Purpose: Record information from an APRStt trasmission.
|
|
*
|
|
* Inputs: callsign - full or an abbreviation
|
|
* ssid
|
|
* overlay - or symbol table identifier
|
|
* symbol
|
|
* loc_text - Original text for non lat/lon location
|
|
* latitude
|
|
* longitude
|
|
* ambiguity
|
|
* freq
|
|
* ctcss
|
|
* comment
|
|
* mic_e
|
|
* dao
|
|
*
|
|
* Outputs: Information is stored in table above.
|
|
* Last heard time is updated.
|
|
* Object Report transmission is scheduled.
|
|
*
|
|
* Returns: 0 for success or one of the TT_ERROR_... codes.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude,
|
|
double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao)
|
|
{
|
|
int i;
|
|
|
|
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("tt_user_heard (%s, %d, %c, %c, %s, ...)\n", callsign, ssid, overlay, symbol, loc_text);
|
|
|
|
/*
|
|
* At this time all messages are expected to contain a callsign.
|
|
* Other types of messages, not related to a particular person/object
|
|
* are a future possibility.
|
|
*/
|
|
if (callsign[0] == '\0') {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
printf ("APRStt tone sequence did not include callsign / object name.\n");
|
|
return (TT_ERROR_NO_CALL);
|
|
}
|
|
|
|
/*
|
|
* Is it someone new or a returning user?
|
|
*/
|
|
i = tt_user_search (callsign, overlay);
|
|
if (i == -1) {
|
|
|
|
/*
|
|
* New person. Create new table entry with all available information.
|
|
*/
|
|
i = find_avail ();
|
|
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
strlcpy (tt_user[i].callsign, callsign, sizeof(tt_user[i].callsign));
|
|
tt_user[i].count = 1;
|
|
tt_user[i].ssid = ssid;
|
|
tt_user[i].overlay = overlay;
|
|
tt_user[i].symbol = symbol;
|
|
digit_suffix(tt_user[i].callsign, tt_user[i].digit_suffix);
|
|
strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text));
|
|
|
|
if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
|
|
/* We have specific location. */
|
|
tt_user[i].corral_slot = 0;
|
|
tt_user[i].latitude = latitude;
|
|
tt_user[i].longitude = longitude;
|
|
}
|
|
else {
|
|
/* Unknown location, put it in the corral. */
|
|
tt_user[i].corral_slot = corral_slot();
|
|
}
|
|
|
|
tt_user[i].ambiguity = ambiguity;
|
|
|
|
strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq));
|
|
strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss));
|
|
strlcpy (tt_user[i].comment, comment, sizeof(tt_user[i].comment));
|
|
tt_user[i].mic_e = mic_e;
|
|
strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao));
|
|
}
|
|
else {
|
|
/*
|
|
* Known user. Update with any new information.
|
|
* Keep any old values where not being updated.
|
|
*/
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
|
|
tt_user[i].count++;
|
|
|
|
/* Any reason to look at ssid here? */
|
|
|
|
/* Update the symbol if not the default. */
|
|
|
|
if (overlay != APRSTT_DEFAULT_SYMTAB || symbol != APRSTT_DEFAULT_SYMBOL) {
|
|
tt_user[i].overlay = overlay;
|
|
tt_user[i].symbol = symbol;
|
|
}
|
|
|
|
if (strlen(loc_text) > 0) {
|
|
strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text));
|
|
}
|
|
|
|
if (latitude != G_UNKNOWN && longitude != G_UNKNOWN) {
|
|
/* We have specific location. */
|
|
tt_user[i].corral_slot = 0;
|
|
tt_user[i].latitude = latitude;
|
|
tt_user[i].longitude = longitude;
|
|
}
|
|
|
|
if (ambiguity != G_UNKNOWN) {
|
|
tt_user[i].ambiguity = ambiguity;
|
|
}
|
|
|
|
if (freq[0] != '\0') {
|
|
strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq));
|
|
}
|
|
|
|
if (ctcss[0] != '\0') {
|
|
strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss));
|
|
}
|
|
|
|
if (comment[0] != '\0') {
|
|
strlcpy (tt_user[i].comment, comment, MAX_COMMENT_LEN);
|
|
tt_user[i].comment[MAX_COMMENT_LEN] = '\0';
|
|
}
|
|
|
|
if (mic_e != ' ') {
|
|
tt_user[i].mic_e = mic_e;
|
|
}
|
|
|
|
if (strlen(dao) > 0) {
|
|
strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In both cases, note last time heard and schedule object report transmission.
|
|
*/
|
|
tt_user[i].last_heard = time(NULL);
|
|
tt_user[i].xmits = 0;
|
|
tt_user[i].next_xmit = tt_user[i].last_heard + save_tt_config_p->xmit_delay[0];
|
|
|
|
/*
|
|
* Send to applications and IGate immediately.
|
|
*/
|
|
|
|
xmit_object_report (i, 1);
|
|
|
|
/*
|
|
* Put properties into environment variables in preparation
|
|
* for calling a user-specified script.
|
|
*/
|
|
|
|
tt_setenv (i);
|
|
|
|
return (0); /* Success! */
|
|
|
|
} /* end tt_user_heard */
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_user_background
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Inputs:
|
|
*
|
|
* Outputs: Append to transmit queue.
|
|
*
|
|
* Returns: None
|
|
*
|
|
* Description: ...... TBD
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
void tt_user_background (void)
|
|
{
|
|
time_t now = time(NULL);
|
|
int i;
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("tt_user_background() now = %d\n", (int)now);
|
|
|
|
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
|
|
if (tt_user[i].callsign[0] != '\0') {
|
|
if (tt_user[i].xmits < save_tt_config_p->num_xmits && tt_user[i].next_xmit <= now) {
|
|
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("tt_user_background() now = %d\n", (int)now);
|
|
//tt_user_dump ();
|
|
|
|
xmit_object_report (i, 0);
|
|
|
|
/* Increase count of number times this one was sent. */
|
|
tt_user[i].xmits++;
|
|
if (tt_user[i].xmits < save_tt_config_p->num_xmits) {
|
|
/* Schedule next one. */
|
|
tt_user[i].next_xmit += save_tt_config_p->xmit_delay[tt_user[i].xmits];
|
|
}
|
|
|
|
//tt_user_dump ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Purge if too old.
|
|
*/
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (tt_user[i].callsign[0] != '\0') {
|
|
if (tt_user[i].last_heard + save_tt_config_p->retain_time < now) {
|
|
|
|
//dw_printf ("debug: purging expired user %d\n", i);
|
|
|
|
clear_user (i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: xmit_object_report
|
|
*
|
|
* Purpose: Create object report packet and put into transmit queue.
|
|
*
|
|
* Inputs: i - Index into user table.
|
|
*
|
|
* first_time - Is this being called immediately after the tone sequence
|
|
* was received or after some delay?
|
|
* For the former, we send to any attached applications
|
|
* and the IGate.
|
|
* For the latter, we transmit over radio.
|
|
*
|
|
* Outputs: Append to transmit queue.
|
|
*
|
|
* Returns: None
|
|
*
|
|
* Description: Details for specified user are converted to
|
|
* "Object Report Format" and added to the transmit queue.
|
|
*
|
|
* If the user did not report a position, we have to make
|
|
* up something so the corresponding object will show up on
|
|
* the map or other list of nearby stations.
|
|
*
|
|
* The traditional approach is to put them in different
|
|
* positions in the "corral" by applying increments of an
|
|
* offset from the starting position. This has two
|
|
* unfortunate properties. It gives the illusion we know
|
|
* where the person is located. Being in the ,,,
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
static void xmit_object_report (int i, int first_time)
|
|
{
|
|
char object_name[20]; // xxxxxxxxx or xxxxxx-nn
|
|
char info_comment[200]; // usercomment [locationtext] /status !DAO!
|
|
char object_info[250]; // info part of Object Report packet
|
|
char stemp[300]; // src>dest,path:object_info
|
|
|
|
double olat, olong;
|
|
int oambig; // Position ambiguity.
|
|
packet_t pp;
|
|
char c4[4];
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//printf ("xmit_object_report (index = %d, first_time = %d) rx = %d, tx = %d\n", i, first_time,
|
|
// save_tt_config_p->obj_recv_chan, save_tt_config_p->obj_xmit_chan);
|
|
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
|
|
/*
|
|
* Prepare the object name.
|
|
* Tack on "-12" if it is a callsign.
|
|
*/
|
|
strlcpy (object_name, tt_user[i].callsign, sizeof(object_name));
|
|
|
|
if (strlen(object_name) <= 6 && tt_user[i].ssid != 0) {
|
|
char stemp8[8];
|
|
snprintf (stemp8, sizeof(stemp8), "-%d", tt_user[i].ssid);
|
|
strlcat (object_name, stemp8, sizeof(object_name));
|
|
}
|
|
|
|
if (tt_user[i].corral_slot == 0) {
|
|
/*
|
|
* Known location.
|
|
*/
|
|
olat = tt_user[i].latitude;
|
|
olong = tt_user[i].longitude;
|
|
oambig = tt_user[i].ambiguity;
|
|
if (oambig == G_UNKNOWN) oambig = 0;
|
|
}
|
|
else {
|
|
/*
|
|
* Use made up position in the corral.
|
|
*/
|
|
double c_lat = save_tt_config_p->corral_lat; // Corral latitude.
|
|
double c_long = save_tt_config_p->corral_lon; // Corral longitude.
|
|
double c_offs = save_tt_config_p->corral_offset; // Corral (latitude) offset.
|
|
|
|
olat = c_lat - (tt_user[i].corral_slot - 1) * c_offs;
|
|
olong = c_long;
|
|
oambig = 0;
|
|
}
|
|
|
|
/*
|
|
* Build comment field from various information.
|
|
*
|
|
* usercomment [locationtext] /status !DAO!
|
|
*
|
|
* Any frequency is inserted at beginning later.
|
|
*/
|
|
strlcpy (info_comment, "", sizeof(info_comment));
|
|
|
|
if (strlen(tt_user[i].comment) != 0) {
|
|
strlcat (info_comment, tt_user[i].comment, sizeof(info_comment));
|
|
}
|
|
|
|
if (strlen(tt_user[i].loc_text) > 0) {
|
|
if (strlen(info_comment) > 0) {
|
|
strlcat (info_comment, " ", sizeof(info_comment));
|
|
}
|
|
strlcat (info_comment, "[", sizeof(info_comment));
|
|
strlcat (info_comment, tt_user[i].loc_text, sizeof(info_comment));
|
|
strlcat (info_comment, "]", sizeof(info_comment));
|
|
}
|
|
|
|
if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') {
|
|
|
|
if (strlen(info_comment) > 0) {
|
|
strlcat (info_comment, " ", sizeof(info_comment));
|
|
}
|
|
|
|
// Insert "/" if status does not already begin with it.
|
|
if (save_tt_config_p->status[tt_user[i].mic_e - '0'][0] != '/') {
|
|
strlcat (info_comment, "/", sizeof(info_comment));
|
|
}
|
|
strlcat (info_comment, save_tt_config_p->status[tt_user[i].mic_e - '0'], sizeof(info_comment));
|
|
}
|
|
|
|
if (strlen(tt_user[i].dao) > 0) {
|
|
if (strlen(info_comment) > 0) {
|
|
strlcat (info_comment, " ", sizeof(info_comment));
|
|
}
|
|
strlcat (info_comment, tt_user[i].dao, sizeof(info_comment));
|
|
}
|
|
|
|
/* Official limit is 43 characters. */
|
|
//info_comment[MAX_COMMENT_LEN] = '\0';
|
|
|
|
/*
|
|
* Packet header is built from mycall (of transmit channel) and software version.
|
|
*/
|
|
|
|
if (save_tt_config_p->obj_xmit_chan >= 0) {
|
|
strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall, sizeof(stemp));
|
|
}
|
|
else {
|
|
strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_recv_chan].mycall, sizeof(stemp));
|
|
}
|
|
strlcat (stemp, ">", sizeof(stemp));
|
|
strlcat (stemp, APP_TOCALL, sizeof(stemp));
|
|
c4[0] = '0' + MAJOR_VERSION;
|
|
c4[1] = '0' + MINOR_VERSION;
|
|
c4[2] = '\0';
|
|
strlcat (stemp, c4, sizeof(stemp));
|
|
|
|
/*
|
|
* Append via path, for transmission, if specified.
|
|
*/
|
|
|
|
if ( ! first_time && save_tt_config_p->obj_xmit_via[0] != '\0') {
|
|
strlcat (stemp, ",", sizeof(stemp));
|
|
strlcat (stemp, save_tt_config_p->obj_xmit_via, sizeof(stemp));
|
|
}
|
|
|
|
strlcat (stemp, ":", sizeof(stemp));
|
|
|
|
encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, oambig,
|
|
tt_user[i].overlay, tt_user[i].symbol,
|
|
0,0,0,NULL, G_UNKNOWN, G_UNKNOWN, /* PHGD, Course/Speed */
|
|
strlen(tt_user[i].freq) > 0 ? atof(tt_user[i].freq) : G_UNKNOWN,
|
|
strlen(tt_user[i].ctcss) > 0 ? atof(tt_user[i].ctcss) : G_UNKNOWN,
|
|
G_UNKNOWN, /* CTCSS */
|
|
info_comment, object_info, sizeof(object_info));
|
|
|
|
strlcat (stemp, object_info, sizeof(stemp));
|
|
|
|
#if TT_MAIN
|
|
|
|
printf ("---> %s\n\n", stemp);
|
|
|
|
#else
|
|
|
|
if (first_time) {
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("[APRStt] %s\n", stemp);
|
|
}
|
|
|
|
/*
|
|
* Convert text to packet.
|
|
*/
|
|
pp = ax25_from_text (stemp, 1);
|
|
|
|
if (pp == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\"%s\"\n", stemp);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Send to one or more of the following depending on configuration:
|
|
* Transmit queue.
|
|
* Any attached application(s).
|
|
* IGate.
|
|
*
|
|
* When transmitting over the radio, it gets sent multipe times, to help
|
|
* probablity of being heard, with increasing delays between.
|
|
*
|
|
* The other methods are reliable so we only want to send it once.
|
|
*/
|
|
|
|
if (first_time && save_tt_config_p->obj_send_to_app) {
|
|
unsigned char fbuf[AX25_MAX_PACKET_LEN];
|
|
int flen;
|
|
|
|
// TODO1.3: Put a wrapper around this so we only call one function to send by all methods.
|
|
// We see the same sequence in direwolf.c.
|
|
|
|
flen = ax25_pack(pp, fbuf);
|
|
|
|
server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen);
|
|
kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
|
|
kissserial_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
|
|
kisspt_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
|
|
}
|
|
|
|
if (first_time && save_tt_config_p->obj_send_to_ig) {
|
|
|
|
//text_color_set(DW_COLOR_DEBUG);
|
|
//dw_printf ("xmit_object_report (): send to IGate\n");
|
|
|
|
igate_send_rec_packet (save_tt_config_p->obj_recv_chan, pp);
|
|
|
|
}
|
|
|
|
if ( ! first_time && save_tt_config_p->obj_xmit_chan >= 0) {
|
|
|
|
/* Remember it so we don't digipeat our own. */
|
|
|
|
dedupe_remember (pp, save_tt_config_p->obj_xmit_chan);
|
|
|
|
tq_append (save_tt_config_p->obj_xmit_chan, TQ_PRIO_1_LO, pp);
|
|
}
|
|
else {
|
|
ax25_delete (pp);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
static const char *letters[26] = {
|
|
"Alpha",
|
|
"Bravo",
|
|
"Charlie",
|
|
"Delta",
|
|
"Echo",
|
|
"Foxtrot",
|
|
"Golf",
|
|
"Hotel",
|
|
"India",
|
|
"Juliet",
|
|
"Kilo",
|
|
"Lima",
|
|
"Mike",
|
|
"November",
|
|
"Oscar",
|
|
"Papa",
|
|
"Quebec",
|
|
"Romeo",
|
|
"Sierra",
|
|
"Tango",
|
|
"Uniform",
|
|
"Victor",
|
|
"Whiskey",
|
|
"X-ray",
|
|
"Yankee",
|
|
"Zulu"
|
|
};
|
|
|
|
static const char *digits[10] = {
|
|
"Zero",
|
|
"One",
|
|
"Two",
|
|
"Three",
|
|
"Four",
|
|
"Five",
|
|
"Six",
|
|
"Seven",
|
|
"Eight",
|
|
"Nine"
|
|
};
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_setenv
|
|
*
|
|
* Purpose: Put information in environment variables in preparation
|
|
* for calling a user-supplied script for custom processing.
|
|
*
|
|
* Inputs: i - Index into tt_user table.
|
|
*
|
|
* Description: Timestamps displayed relative to current time.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
|
static void tt_setenv (int i)
|
|
{
|
|
char stemp[256];
|
|
char t2[2];
|
|
char *p;
|
|
|
|
assert (i >= 0 && i < MAX_TT_USERS);
|
|
|
|
setenv ("TTCALL", tt_user[i].callsign, 1);
|
|
|
|
strlcpy (stemp, "", sizeof(stemp));
|
|
t2[1] = '\0';
|
|
for (p = tt_user[i].callsign; *p != '\0'; p++) {
|
|
t2[0] = *p;
|
|
strlcat (stemp, t2, sizeof(stemp));
|
|
if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp));
|
|
}
|
|
setenv ("TTCALLSP", stemp, 1);
|
|
|
|
strlcpy (stemp, "", sizeof(stemp));
|
|
for (p = tt_user[i].callsign; *p != '\0'; p++) {
|
|
if (isupper(*p)) {
|
|
strlcat (stemp, letters[*p - 'A'], sizeof(stemp));
|
|
}
|
|
else if (islower(*p)) {
|
|
strlcat (stemp, letters[*p - 'a'], sizeof(stemp));
|
|
}
|
|
else if (isdigit(*p)) {
|
|
strlcat (stemp, digits[*p - '0'], sizeof(stemp));
|
|
}
|
|
else {
|
|
t2[0] = *p;
|
|
strlcat (stemp, t2, sizeof(stemp));
|
|
}
|
|
if (p[1] != '\0') strlcat (stemp, " ", sizeof(stemp));
|
|
}
|
|
setenv ("TTCALLPH", stemp, 1);
|
|
|
|
snprintf (stemp, sizeof(stemp), "%d", tt_user[i].ssid);
|
|
setenv ("TTSSID",stemp , 1);
|
|
|
|
snprintf (stemp, sizeof(stemp), "%d", tt_user[i].count);
|
|
setenv ("TTCOUNT",stemp , 1);
|
|
|
|
snprintf (stemp, sizeof(stemp), "%c%c", tt_user[i].overlay, tt_user[i].symbol);
|
|
setenv ("TTSYMBOL",stemp , 1);
|
|
|
|
snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].latitude);
|
|
setenv ("TTLAT",stemp , 1);
|
|
|
|
snprintf (stemp, sizeof(stemp), "%.6f", tt_user[i].longitude);
|
|
setenv ("TTLON",stemp , 1);
|
|
|
|
setenv ("TTFREQ", tt_user[i].freq, 1);
|
|
|
|
// TODO: Should convert to actual frequency. e.g. 074 becomes 74.4
|
|
// There is some code for this in decode_aprs.c but not broken out
|
|
// into a function that we could use from here.
|
|
// TODO: Document this environment variable after converting.
|
|
|
|
setenv ("TTCTCSS", tt_user[i].ctcss, 1);
|
|
|
|
setenv ("TTCOMMENT", tt_user[i].comment, 1);
|
|
|
|
setenv ("TTLOC", tt_user[i].loc_text, 1);
|
|
|
|
if (tt_user[i].mic_e >= '1' && tt_user[i].mic_e <= '9') {
|
|
setenv ("TTSTATUS", save_tt_config_p->status[tt_user[i].mic_e - '0'], 1);
|
|
}
|
|
else {
|
|
setenv ("TTSTATUS", "", 1);
|
|
}
|
|
|
|
setenv ("TTDAO", tt_user[i].dao, 1);
|
|
|
|
} /* end tt_setenv */
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: tt_user_dump
|
|
*
|
|
* Purpose: Print information about known users for debugging.
|
|
*
|
|
* Inputs: None.
|
|
*
|
|
* Description: Timestamps displayed relative to current time.
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
void tt_user_dump (void)
|
|
{
|
|
int i;
|
|
time_t now = time(NULL);
|
|
|
|
printf ("call ov suf lsthrd xmit nxt cor lat long freq ctcss m comment\n");
|
|
for (i=0; i<MAX_TT_USERS; i++) {
|
|
if (tt_user[i].callsign[0] != '\0') {
|
|
printf ("%-6s %c%c %-3s %6d %d %+6d %d %6.2f %7.2f %-10s %-3s %c %s\n",
|
|
tt_user[i].callsign,
|
|
tt_user[i].overlay,
|
|
tt_user[i].symbol,
|
|
tt_user[i].digit_suffix,
|
|
(int)(tt_user[i].last_heard - now),
|
|
tt_user[i].xmits,
|
|
(int)(tt_user[i].next_xmit - now),
|
|
tt_user[i].corral_slot,
|
|
tt_user[i].latitude,
|
|
tt_user[i].longitude,
|
|
tt_user[i].freq,
|
|
tt_user[i].ctcss,
|
|
tt_user[i].mic_e,
|
|
tt_user[i].comment);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* Name: main
|
|
*
|
|
* Purpose: Quick test for some functions in this file.
|
|
*
|
|
* Description: Just a smattering, not an organized test.
|
|
*
|
|
* $ rm a.exe ; gcc -DTT_MAIN -Iregex tt_user.c tt_text.c encode_aprs.c latlong.c textcolor.c misc.a ; ./a.exe
|
|
*
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
|
#if TT_MAIN
|
|
|
|
|
|
static struct audio_s my_audio_config;
|
|
|
|
static struct tt_config_s my_tt_config;
|
|
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
int n;
|
|
|
|
/* Fake audio config - All we care about is mycall for constructing object report packet. */
|
|
|
|
memset (&my_audio_config, 0, sizeof(my_audio_config));
|
|
|
|
strlcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15", sizeof(my_audio_config.achan[0].mycall));
|
|
|
|
/* Fake TT gateway config. */
|
|
|
|
memset (&my_tt_config, 0, sizeof(my_tt_config));
|
|
|
|
/* Don't care about the location translation here. */
|
|
|
|
my_tt_config.retain_time = 20; /* Normally 80 minutes. */
|
|
my_tt_config.num_xmits = 3;
|
|
assert (my_tt_config.num_xmits <= TT_MAX_XMITS);
|
|
my_tt_config.xmit_delay[0] = 3; /* Before initial transmission. */
|
|
my_tt_config.xmit_delay[1] = 5;
|
|
my_tt_config.xmit_delay[2] = 5;
|
|
|
|
my_tt_config.corral_lat = 42.61900;
|
|
my_tt_config.corral_lon = -71.34717;
|
|
my_tt_config.corral_offset = 0.02 / 60;
|
|
my_tt_config.corral_ambiguity = 0;
|
|
|
|
|
|
tt_user_init(&my_audio_config, &my_tt_config);
|
|
|
|
// tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude,
|
|
// double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao);
|
|
|
|
tt_user_heard ("TEST1", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
SLEEP_SEC (1);
|
|
tt_user_heard ("TEST2", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
SLEEP_SEC (1);
|
|
tt_user_heard ("TEST3", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
SLEEP_SEC (1);
|
|
tt_user_heard ("TEST4", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
SLEEP_SEC (1);
|
|
tt_user_heard ("WB2OSZ", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
tt_user_heard ("K2H", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "", ' ', "!T99!");
|
|
tt_user_dump ();
|
|
|
|
tt_user_heard ("679", 12, 'J', 'A', "", 37.25, -71.75, 0, "", " ", " ", ' ', "!T99!");
|
|
tt_user_heard ("WB2OSZ", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "146.520MHz", "", "", ' ', "!T99!");
|
|
tt_user_heard ("WB1GOF", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "146.955MHz", "074", "", ' ', "!T99!");
|
|
tt_user_heard ("679", 12, 'J', 'A', "", G_UNKNOWN, G_UNKNOWN, 0, "", "", "Hello, world", '9', "!T99!");
|
|
tt_user_dump ();
|
|
|
|
for (n=0; n<30; n++) {
|
|
SLEEP_SEC(1);
|
|
tt_user_background ();
|
|
}
|
|
|
|
return(0);
|
|
|
|
} /* end main */
|
|
|
|
#endif /* unit test */
|
|
|
|
|
|
/* end tt-user.c */
|
|
|