//#define DEBUG 1
//#define DEBUG_SIM 1
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011,2013,2014 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 .
//
/*------------------------------------------------------------------
*
* Module: beacon.c
*
* Purpose: Transmit messages on a fixed schedule.
*
* Description: Transmit periodic messages as specified in the config file.
*
*---------------------------------------------------------------*/
#include
#include
#include
#include
#include
#include
#include
#if __WIN32__
#include
#endif
#include "direwolf.h"
#include "ax25_pad.h"
#include "textcolor.h"
#include "audio.h"
#include "tq.h"
#include "xmit.h"
#include "config.h"
#include "digipeater.h"
#include "version.h"
#include "encode_aprs.h"
#include "beacon.h"
#include "latlong.h"
#include "dwgps.h"
#include "log.h"
/*
* Are we using GPS data?
* Incremented if tracker beacons configured.
* Cleared if dwgps_init fails.
*/
static int g_using_gps = 0;
/*
* Save pointers to configuration settings.
*/
static struct misc_config_s *g_misc_config_p;
static struct digi_config_s *g_digi_config_p;
#if __WIN32__
static unsigned __stdcall beacon_thread (void *arg);
#else
static void * beacon_thread (void *arg);
#endif
static int g_tracker_debug_level = 0; // 1 for data from gps.
// 2 + Smart Beaconing logic.
// 3 + Send transmissions to log file.
void beacon_tracker_set_debug (int level)
{
g_tracker_debug_level = level;
}
/*-------------------------------------------------------------------
*
* Name: beacon_init
*
* Purpose: Initialize the beacon process.
*
* Inputs: pconfig - misc. configuration from config file.
* pdigi - digipeater configuration from config file.
* Use to obtain "mycall" for each channel.
*
*
* Outputs: Remember required information for future use.
*
* Description: Initialize the queue to be empty and set up other
* mechanisms for sharing it between different threads.
*
* Start up xmit_thread to actually send the packets
* at the appropriate time.
*
*--------------------------------------------------------------------*/
void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi)
{
time_t now;
int j;
int count;
#if __WIN32__
HANDLE beacon_th;
#else
pthread_t beacon_tid;
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_init ( ... )\n");
#endif
/*
* Save parameters for later use.
*/
g_misc_config_p = pconfig;
g_digi_config_p = pdigi;
/*
* Precompute the packet contents so any errors are
* Reported once at start up time rather than for each transmission.
* If a serious error is found, set type to BEACON_IGNORE and that
* table entry should be ignored later on.
*/
for (j=0; jnum_beacons; j++) {
int chan = g_misc_config_p->beacon[j].sendto_chan;
if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */
if (chan < pdigi->num_chans) {
if (strlen(pdigi->mycall[chan]) > 0 && strcasecmp(pdigi->mycall[chan], "NOCALL") != 0) {
switch (g_misc_config_p->beacon[j].btype) {
case BEACON_OBJECT:
/* Object name is required. */
if (strlen(g_misc_config_p->beacon[j].objname) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
/* Fall thru. Ignore any warning about missing break. */
case BEACON_POSITION:
/* Location is required. */
if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_TRACKER:
#if defined(ENABLE_GPS) || defined(DEBUG_SIM)
g_using_gps++;
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: GPS tracker feature is not enabled.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
#endif
break;
case BEACON_CUSTOM:
/* INFO is required. */
if (g_misc_config_p->beacon[j].custom_info == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: INFO is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
continue;
}
break;
case BEACON_IGNORE:
break;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan);
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
/*
* Calculate next time for each beacon.
*/
now = time(NULL);
for (j=0; jnum_beacons; j++) {
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon[%d] chan=%d, delay=%d, every=%d\n",
j,
g_misc_config_p->beacon[j].chan,
g_misc_config_p->beacon[j].delay,
g_misc_config_p->beacon[j].every);
#endif
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
}
/*
* Connect to GPS receiver if any tracker beacons are configured.
* If open fails, disable all tracker beacons.
*/
#if DEBUG_SIM
g_using_gps = 1;
#elif ENABLE_GPS
if (g_using_gps > 0) {
int err;
err = dwgps_init();
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("All tracker beacons disabled.\n");
g_using_gps = 0;
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
}
}
}
}
#endif
/*
* Start up thread for processing only if at least one is valid.
*/
count = 0;
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
count++;
}
}
if (count >= 1) {
#if __WIN32__
beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL);
if (beacon_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create beacon thread\n");
return;
}
#else
int e;
e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create beacon thread");
return;
}
#endif
}
} /* end beacon_init */
/*-------------------------------------------------------------------
*
* Name: beacon_thread
*
* Purpose: Transmit beacons when it is time.
*
* Inputs: g_misc_config_p->beacon
*
* Outputs: g_misc_config_p->beacon[].next_time
*
* Description: Go to sleep until it is time for the next beacon.
* Transmit any beacons scheduled for now.
* Repeat.
*
*--------------------------------------------------------------------*/
#define MIN(x,y) ((x) < (y) ? (x) : (y))
/* Difference between two angles. */
static inline float heading_change (float a, float b)
{
float diff;
diff = fabs(a - b);
if (diff <= 180.)
return (diff);
else
return (360. - diff);
}
#if __WIN32__
static unsigned __stdcall beacon_thread (void *arg)
#else
static void * beacon_thread (void *arg)
#endif
{
int j;
time_t earliest;
time_t now;
/*
* Information from GPS.
*/
int fix = 0; /* 0 = none, 2 = 2D, 3 = 3D */
double my_lat = 0; /* degrees */
double my_lon = 0;
float my_course = 0; /* degrees */
float my_speed_knots = 0;
float my_speed_mph = 0;
float my_alt_m = G_UNKNOWN; /* meters */
int my_alt_ft = G_UNKNOWN;
/*
* SmartBeaconing state.
*/
time_t sb_prev_time = 0; /* Time of most recent transmission. */
float sb_prev_course = 0; /* Most recent course reported. */
//float sb_prev_speed_mph; /* Most recent speed reported. */
int sb_every; /* Calculated time between transmissions. */
#if DEBUG
struct tm tm;
char hms[20];
now = time(NULL);
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_thread: started %s\n", hms);
#endif
now = time(NULL);
while (1) {
assert (g_misc_config_p->num_beacons >= 1);
/*
* Sleep until time for the earliest scheduled or
* the soonest we could transmit due to corner pegging.
*/
earliest = g_misc_config_p->beacon[0].next;
for (j=1; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
continue;
earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
}
if (g_misc_config_p->sb_configured && g_using_gps) {
earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest);
earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest);
}
if (earliest > now) {
SLEEP_SEC (earliest - now);
}
/*
* Woke up. See what needs to be done.
*/
now = time(NULL);
#if DEBUG
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("beacon_thread: woke up %s\n", hms);
#endif
/*
* Get information from GPS if being used.
* This needs to be done before the next scheduled tracker
* beacon because corner pegging make it sooner.
*/
#if DEBUG_SIM
FILE *fp;
char cs[40];
fp = fopen ("c:\\cygwin\\tmp\\cs", "r");
if (fp != NULL) {
fscanf (fp, "%f %f", &my_course, &my_speed_knots);
fclose (fp);
}
else {
fprintf (stderr, "Can't read /tmp/cs.\n");
}
fix = 3;
my_speed_mph = DW_KNOTS_TO_MPH(my_speed_knots);
my_lat = 42.99;
my_lon = 71.99;
my_alt_m = 100;
#else
if (g_using_gps) {
fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt_m);
my_speed_mph = DW_KNOTS_TO_MPH(my_speed_knots);
if (g_tracker_debug_level >= 1) {
struct tm tm;
char hms[20];
localtime_r (&now, &tm);
strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
text_color_set(DW_COLOR_DEBUG);
if (fix == 3) {
dw_printf ("%s 3D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0, %.1f m\n", hms, my_lat, my_lon, my_speed_mph, my_course, my_alt_m);
}
else if (fix == 2) {
dw_printf ("%s 2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, my_lat, my_lon, my_speed_mph, my_course);
}
else {
dw_printf ("%s No GPS fix\n", hms);
}
}
/* Transmit altitude only if 3D fix and user asked for it. */
my_alt_ft = G_UNKNOWN;
if (fix >= 3 && my_alt_m != G_UNKNOWN && g_misc_config_p->beacon[j].alt_m != G_UNKNOWN) {
my_alt_ft = DW_METERS_TO_FEET(my_alt_m);
}
/* Don't complain here for no fix. */
/* Possibly at the point where about to transmit. */
}
#endif
/*
* Run SmartBeaconing calculation if configured and GPS data available.
*/
if (g_misc_config_p->sb_configured && g_using_gps && fix >= 2) {
if (my_speed_mph > g_misc_config_p->sb_fast_speed) {
sb_every = g_misc_config_p->sb_fast_rate;
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("my speed %.1f > fast %d mph, interval = %d sec\n", my_speed_mph, g_misc_config_p->sb_fast_speed, sb_every);
}
}
else if (my_speed_mph < g_misc_config_p->sb_slow_speed) {
sb_every = g_misc_config_p->sb_slow_rate;
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("my speed %.1f < slow %d mph, interval = %d sec\n", my_speed_mph, g_misc_config_p->sb_slow_speed, sb_every);
}
}
else {
/* Can't divide by 0 assuming sb_slow_speed > 0. */
sb_every = ( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / my_speed_mph;
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("my speed %.1f mph, interval = %d sec\n", my_speed_mph, sb_every);
}
}
#if DEBUG_SIM
text_color_set(DW_COLOR_DEBUG);
dw_printf ("SB: fast %d %d slow %d %d speed=%.1f every=%d\n",
g_misc_config_p->sb_fast_speed, g_misc_config_p->sb_fast_rate,
g_misc_config_p->sb_slow_speed, g_misc_config_p->sb_slow_rate,
my_speed_mph, sb_every);
#endif
/*
* Test for "Corner Pegging" if moving.
*/
if (my_speed_mph >= 1.0) {
int turn_threshold = g_misc_config_p->sb_turn_angle +
g_misc_config_p->sb_turn_slope / my_speed_mph;
#if DEBUG_SIM
text_color_set(DW_COLOR_DEBUG);
dw_printf ("SB-moving: course %.0f prev %.0f thresh %d\n",
my_course, sb_prev_course, turn_threshold);
#endif
if (heading_change(my_course, sb_prev_course) > turn_threshold &&
now >= sb_prev_time + g_misc_config_p->sb_turn_time) {
if (g_tracker_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("heading change (%.0f, %.0f) > threshold %d and %d since last >= turn time %d\n",
my_course, sb_prev_course, turn_threshold,
(int)(now - sb_prev_time), g_misc_config_p->sb_turn_time);
}
/* Send it now. */
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
g_misc_config_p->beacon[j].next = now;
}
}
} /* significant change in direction */
} /* is moving */
} /* apply SmartBeaconing */
for (j=0; jnum_beacons; j++) {
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
continue;
if (g_misc_config_p->beacon[j].next <= now) {
int strict = 1; /* Strict packet checking because they will go over air. */
char stemp[20];
char info[AX25_MAX_INFO_LEN];
char beacon_text[AX25_MAX_PACKET_LEN];
packet_t pp = NULL;
char mycall[AX25_MAX_ADDR_LEN];
int alt_ft;
/*
* Obtain source call for the beacon.
* This could potentially be different on different channels.
* When sending to IGate server, use call from first radio channel.
*
* Check added in version 1.0a. Previously used index of -1.
*
* Version 1.1 - channel should now be 0 for IGate.
* Type of destination is encoded separately.
*/
strcpy (mycall, "NOCALL");
assert (g_misc_config_p->beacon[j].sendto_chan >= 0);
strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].sendto_chan]);
if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("MYCALL not set for beacon in config file line %d.\n", g_misc_config_p->beacon[j].lineno);
continue;
}
/*
* Prepare the monitor format header.
*
* src > dest [ , via ]
*/
strcpy (beacon_text, mycall);
strcat (beacon_text, ">");
if (g_misc_config_p->beacon[j].dest != NULL) {
strcat (beacon_text, g_misc_config_p->beacon[j].dest);
}
else {
sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
strcat (beacon_text, stemp);
}
if (g_misc_config_p->beacon[j].via != NULL) {
strcat (beacon_text, ",");
strcat (beacon_text, g_misc_config_p->beacon[j].via);
}
strcat (beacon_text, ":");
/*
* Add the info part depending on beacon type.
*/
switch (g_misc_config_p->beacon[j].btype) {
case BEACON_POSITION:
alt_ft = DW_METERS_TO_FEET(g_misc_config_p->beacon[j].alt_m);
encode_position (g_misc_config_p->beacon[j].messaging,
g_misc_config_p->beacon[j].compress, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon, alt_ft,
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
0, 0, /* course, speed */
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
g_misc_config_p->beacon[j].comment,
info);
strcat (beacon_text, info);
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
break;
case BEACON_OBJECT:
encode_object (g_misc_config_p->beacon[j].objname, g_misc_config_p->beacon[j].compress, 0, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon,
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
0, 0, /* course, speed */
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, g_misc_config_p->beacon[j].comment,
info);
strcat (beacon_text, info);
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
break;
case BEACON_TRACKER:
if (fix >= 2) {
int coarse; /* APRS encoder wants 1 - 360. */
/* 0 means none or unknown. */
coarse = (int)roundf(my_course);
if (coarse == 0) {
coarse = 360;
}
encode_position (g_misc_config_p->beacon[j].messaging,
g_misc_config_p->beacon[j].compress,
my_lat, my_lon, my_alt_ft,
g_misc_config_p->beacon[j].symtab, g_misc_config_p->beacon[j].symbol,
g_misc_config_p->beacon[j].power, g_misc_config_p->beacon[j].height, g_misc_config_p->beacon[j].gain, g_misc_config_p->beacon[j].dir,
coarse, (int)roundf(my_speed_knots),
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
g_misc_config_p->beacon[j].comment,
info);
strcat (beacon_text, info);
/* Remember most recent tracker beacon. */
sb_prev_time = now;
sb_prev_course = my_course;
//sb_prev_speed_mph = my_speed_mph;
/* Calculate time for next transmission. */
if (g_misc_config_p->sb_configured) {
g_misc_config_p->beacon[j].next = now + sb_every;
}
else {
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
}
/* Write to log file for testing. */
/* The idea is to run log2gpx and map the result rather than */
/* actually transmitting and relying on someone else to receive */
/* the signals. */
if (g_tracker_debug_level >= 3) {
decode_aprs_t A;
memset (&A, 0, sizeof(A));
A.g_freq = G_UNKNOWN;
A.g_offset = G_UNKNOWN;
A.g_tone = G_UNKNOWN;
A.g_dcs = G_UNKNOWN;
strcpy (A.g_src, mycall);
A.g_symbol_table = g_misc_config_p->beacon[j].symtab;
A.g_symbol_code = g_misc_config_p->beacon[j].symbol;
A.g_lat = my_lat;
A.g_lon = my_lon;
A.g_speed = DW_KNOTS_TO_MPH(my_speed_knots);
A.g_course = coarse;
A.g_altitude = my_alt_ft;
/* Fake channel of 999 to distinguish from real data. */
log_write (999, &A, NULL, 0, 0);
}
}
else {
g_misc_config_p->beacon[j].next = now + 2;
continue; /* No fix. Try again in a couple seconds. */
}
break;
case BEACON_CUSTOM:
if (g_misc_config_p->beacon[j].custom_info != NULL) {
strcat (beacon_text, g_misc_config_p->beacon[j].custom_info);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
continue;
}
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
break;
case BEACON_IGNORE:
default:
break;
} /* switch beacon type. */
/*
* Parse monitor format into form for transmission.
*/
pp = ax25_from_text (beacon_text, strict);
if (pp != NULL) {
/* Send to desired destination. */
switch (g_misc_config_p->beacon[j].sendto_type) {
case SENDTO_IGATE:
#if 1
text_color_set(DW_COLOR_XMIT);
dw_printf ("[ig] %s\n", beacon_text);
#endif
igate_send_rec_packet (0, pp);
ax25_delete (pp);
break;
case SENDTO_XMIT:
default:
tq_append (g_misc_config_p->beacon[j].sendto_chan, TQ_PRIO_1_LO, pp);
break;
case SENDTO_RECV:
// TODO: Put into receive queue rather than calling directly.
app_process_rec_packet (g_misc_config_p->beacon[j].sendto_chan, 0, pp, -1, 0, "");
break;
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", g_misc_config_p->beacon[j].lineno);
dw_printf ("%s\n", beacon_text);
}
} /* if time to send it */
} /* for each configured beacon */
} /* do forever */
} /* end beacon_thread */
/* end beacon.c */