1 //
2 //    This file is part of Dire Wolf, an amateur radio packet TNC.
3 //
4 //    Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017  John Langner, WB2OSZ
5 //
6 //    This program is free software: you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation, either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 
21 /*------------------------------------------------------------------
22  *
23  * Module:      beacon.c
24  *
25  * Purpose:   	Transmit messages on a fixed schedule.
26  *
27  * Description:	Transmit periodic messages as specified in the config file.
28  *
29  *---------------------------------------------------------------*/
30 
31 //#define DEBUG 1
32 
33 #include "direwolf.h"
34 
35 
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <assert.h>
40 #include <string.h>
41 #include <math.h>
42 #include <time.h>
43 
44 
45 #include "ax25_pad.h"
46 #include "textcolor.h"
47 #include "audio.h"
48 #include "tq.h"
49 #include "xmit.h"
50 #include "config.h"
51 #include "version.h"
52 #include "encode_aprs.h"
53 #include "beacon.h"
54 #include "latlong.h"
55 #include "dwgps.h"
56 #include "log.h"
57 #include "dlq.h"
58 #include "aprs_tt.h"		// for dw_run_cmd - should relocate someday.
59 #include "mheard.h"
60 
61 
62 /*
63  * Save pointers to configuration settings.
64  */
65 
66 static struct audio_s        *g_modem_config_p;
67 static struct misc_config_s  *g_misc_config_p;
68 static struct igate_config_s *g_igate_config_p;
69 
70 
71 #if __WIN32__
72 static unsigned __stdcall beacon_thread (void *arg);
73 #else
74 static void * beacon_thread (void *arg);
75 #endif
76 
77 static int g_tracker_debug_level = 0;	// 1 for data from gps.
78 					// 2 + Smart Beaconing logic.
79 					// 3 + Send transmissions to log file.
80 
81 
beacon_tracker_set_debug(int level)82 void beacon_tracker_set_debug (int level)
83 {
84 	g_tracker_debug_level = level;
85 }
86 
87 static time_t sb_calculate_next_time (time_t now,
88 			float current_speed_mph, float current_course,
89 			time_t last_xmit_time, float last_xmit_course);
90 
91 static void beacon_send (int j, dwgps_info_t *gpsinfo);
92 
93 
94 /*-------------------------------------------------------------------
95  *
96  * Name:        beacon_init
97  *
98  * Purpose:     Initialize the beacon process.
99  *
100  * Inputs:	pmodem		- Audio device and modem configuration.
101  *				  Used only to find valid channels.
102  *
103  *		pconfig		- misc. configuration from config file.
104  *				  Beacon stuff ended up here.
105  *
106  *		pigate		- IGate configuration.
107  *				  Need this for calculating IGate statistics.
108  *
109  *
110  * Outputs:	Remember required information for future use.
111  *
112  * Description:	Do some validity checking on the beacon configuration.
113  *
114  *		Start up beacon_thread to actually send the packets
115  *		at the appropriate time.
116  *
117  *--------------------------------------------------------------------*/
118 
119 
120 
beacon_init(struct audio_s * pmodem,struct misc_config_s * pconfig,struct igate_config_s * pigate)121 void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct igate_config_s *pigate)
122 {
123 	time_t now;
124 	struct tm tm;
125 	int j;
126 	int count;
127 #if __WIN32__
128 	HANDLE beacon_th;
129 #else
130 	pthread_t beacon_tid;
131 #endif
132 
133 
134 
135 #if DEBUG
136 	text_color_set(DW_COLOR_DEBUG);
137 	dw_printf ("beacon_init ( ... )\n");
138 #endif
139 
140 
141 
142 /*
143  * Save parameters for later use.
144  */
145 	g_modem_config_p = pmodem;
146 	g_misc_config_p = pconfig;
147 	g_igate_config_p = pigate;
148 
149 /*
150  * Precompute the packet contents so any errors are
151  * Reported once at start up time rather than for each transmission.
152  * If a serious error is found, set type to BEACON_IGNORE and that
153  * table entry should be ignored later on.
154  */
155 
156 // TODO: Better checking.
157 // We should really have a table for which keywords are are required,
158 // optional, or not allowed for each beacon type.  Options which
159 // are not applicable are often silently ignored, causing confusion.
160 
161 	for (j=0; j<g_misc_config_p->num_beacons; j++) {
162 	  int chan = g_misc_config_p->beacon[j].sendto_chan;
163 
164 	  if (chan < 0) chan = 0;	/* For IGate, use channel 0 call. */
165 
166 	  if (g_modem_config_p->achan[chan].medium == MEDIUM_RADIO ||
167 	      g_modem_config_p->achan[chan].medium == MEDIUM_NETTNC) {
168 
169 	    if (strlen(g_modem_config_p->achan[chan].mycall) > 0 &&
170 			 strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 &&
171 			 strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) {
172 
173               switch (g_misc_config_p->beacon[j].btype) {
174 
175 	        case BEACON_OBJECT:
176 
177 		  /* Object name is required. */
178 
179 		  if (strlen(g_misc_config_p->beacon[j].objname) == 0) {
180 	            text_color_set(DW_COLOR_ERROR);
181 	            dw_printf ("Config file, line %d: OBJNAME is required for OBEACON.\n", g_misc_config_p->beacon[j].lineno);
182 		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
183 		    continue;
184 		  }
185 		  /* Fall thru.  Ignore any warning about missing break. */
186 
187 	        case BEACON_POSITION:
188 
189 		  /* Location is required. */
190 
191 		  if (g_misc_config_p->beacon[j].lat == G_UNKNOWN || g_misc_config_p->beacon[j].lon == G_UNKNOWN) {
192 	            text_color_set(DW_COLOR_ERROR);
193 	            dw_printf ("Config file, line %d: Latitude and longitude are required.\n", g_misc_config_p->beacon[j].lineno);
194 		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
195 		    continue;
196 		  }
197 
198 		  /* INFO and INFOCMD are only for Custom Beacon. */
199 
200 		  if (g_misc_config_p->beacon[j].custom_info != NULL || g_misc_config_p->beacon[j].custom_infocmd != NULL) {
201 	            text_color_set(DW_COLOR_ERROR);
202 	            dw_printf ("Config file, line %d: INFO or INFOCMD are allowed only for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
203 	            dw_printf ("INFO and INFOCMD allow you to specify contents of the Information field so it\n");
204 	            dw_printf ("so it would not make sense to use these with other beacon types which construct\n");
205 	            dw_printf ("the Information field. Perhaps you want to use COMMENT or COMMENTCMD option.\n");
206 		    //g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
207 		    continue;
208 		  }
209 		  break;
210 
211 	        case BEACON_TRACKER:
212 
213 	          {
214 	            dwgps_info_t gpsinfo;
215 	            dwfix_t fix;
216 
217 	            fix = dwgps_read (&gpsinfo);
218 		    if (fix == DWFIX_NOT_INIT) {
219 
220 	              text_color_set(DW_COLOR_ERROR);
221 	              dw_printf ("Config file, line %d: GPS must be configured to use TBEACON.\n", g_misc_config_p->beacon[j].lineno);
222 	              g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
223 #if __WIN32__
224 	              dw_printf ("You must specify the GPSNMEA command in your configuration file.\n");
225 	              dw_printf ("This contains the name of the serial port where the receiver is connected.\n");
226 #else
227 	              dw_printf ("You must specify the source of the GPS data in your configuration file.\n");
228 	              dw_printf ("It can be either GPSD, meaning the gpsd daemon, or GPSNMEA for\n");
229 	              dw_printf ("for a serial port connection with exclusive use.\n");
230 #endif
231 
232 	            }
233 	          }
234 
235 		  /* INFO and INFOCMD are only for Custom Beacon. */
236 
237 		  if (g_misc_config_p->beacon[j].custom_info != NULL || g_misc_config_p->beacon[j].custom_infocmd != NULL) {
238 	            text_color_set(DW_COLOR_ERROR);
239 	            dw_printf ("Config file, line %d: INFO or INFOCMD are allowed only for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
240 	            dw_printf ("INFO and INFOCMD allow you to specify contents of the Information field so it\n");
241 	            dw_printf ("so it would not make sense to use these with other beacon types which construct\n");
242 	            dw_printf ("the Information field. Perhaps you want to use COMMENT or COMMENTCMD option.\n");
243 		    //g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
244 		    continue;
245 		  }
246 		  break;
247 
248 	        case BEACON_CUSTOM:
249 
250 		  /* INFO or INFOCMD is required. */
251 
252 		  if (g_misc_config_p->beacon[j].custom_info == NULL && g_misc_config_p->beacon[j].custom_infocmd == NULL) {
253 	            text_color_set(DW_COLOR_ERROR);
254 	            dw_printf ("Config file, line %d: INFO or INFOCMD is required for custom beacon.\n", g_misc_config_p->beacon[j].lineno);
255 		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
256 		    continue;
257 		  }
258 		  break;
259 
260 	        case BEACON_IGATE:
261 
262 		  /* Doesn't make sense if IGate is not configured. */
263 
264 	          if (strlen(g_igate_config_p->t2_server_name) == 0 ||
265 	              strlen(g_igate_config_p->t2_login) == 0 ||
266 	              strlen(g_igate_config_p->t2_passcode) == 0) {
267 
268 	            text_color_set(DW_COLOR_ERROR);
269 	            dw_printf ("Config file, line %d: Doesn't make sense to use IBEACON without IGate Configured.\n", g_misc_config_p->beacon[j].lineno);
270 	            dw_printf ("IBEACON has been disabled.\n");
271 		    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
272 		    continue;
273 		  }
274 		  break;
275 
276 	        case BEACON_IGNORE:
277 		  break;
278 	      }
279 	    }
280 	    else {
281 	      text_color_set(DW_COLOR_ERROR);
282 	      dw_printf ("Config file, line %d: MYCALL must be set for beacon on channel %d. \n", g_misc_config_p->beacon[j].lineno, chan);
283 	      g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
284 	    }
285 	  }
286 	  else {
287 	    text_color_set(DW_COLOR_ERROR);
288 	    dw_printf ("Config file, line %d: Invalid channel number %d for beacon. \n", g_misc_config_p->beacon[j].lineno, chan);
289 	    g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
290 	  }
291 	}
292 
293 /*
294  * Calculate first time for each beacon from the 'slot' or 'delay' value.
295  */
296 
297 	now = time(NULL);
298 	localtime_r (&now, &tm);
299 
300 	for (j=0; j<g_misc_config_p->num_beacons; j++) {
301 	  struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
302 #if DEBUG
303 
304 	  text_color_set(DW_COLOR_DEBUG);
305 	  dw_printf ("beacon[%d] chan=%d, delay=%d, slot=%d, every=%d\n",
306 		j,
307 		bp->sendto_chan,
308 		bp->delay,
309 		bp->slot,
310 		bp->every);
311 #endif
312 
313 /*
314  * If timeslots, there must be a full number of beacon intervals per hour.
315  */
316 #define IS_GOOD(x) ((3600/(x))*(x) == 3600)
317 
318 	  if (bp->slot != G_UNKNOWN) {
319 
320 	    if ( ! IS_GOOD(bp->every)) {
321 	      text_color_set(DW_COLOR_ERROR);
322 	      dw_printf ("Config file, line %d: When using timeslots, there must be a whole number of beacon intervals per hour.\n", bp->lineno);
323 
324 	      // Try to make it valid by adjusting up or down.
325 
326 	      int n;
327 	      for (n=1; ; n++) {
328 	        int e;
329 	        e = bp->every + n;
330 	        if (e > 3600) {
331 	          bp->every = 3600;
332 	          break;
333 	        }
334 	        if (IS_GOOD(e)) {
335 	          bp->every = e;
336 	          break;
337 	        }
338 	        e = bp->every - n;
339 	        if (e < 1) {
340 	          bp->every = 1;	// Impose a larger minimum?
341 	          break;
342 	        }
343 	        if (IS_GOOD(e)) {
344 	          bp->every = e;
345 	          break;
346 	        }
347 	      }
348 	      text_color_set(DW_COLOR_ERROR);
349 	      dw_printf ("Config file, line %d: Time between slotted beacons has been adjusted to %d seconds.\n", bp->lineno, bp->every);
350 	    }
351 /*
352  * Determine when next slot time will arrive.
353  */
354 	    bp->delay = bp->slot - (tm.tm_min * 60 + tm.tm_sec);
355 	    while (bp->delay > bp->every) bp->delay -= bp->every;
356 	    while (bp->delay < 5) bp->delay += bp->every;
357 	  }
358 
359 	  g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].delay;
360 	}
361 
362 
363 /*
364  * Start up thread for processing only if at least one is valid.
365  */
366 
367 	count = 0;
368 	for (j=0; j<g_misc_config_p->num_beacons; j++) {
369           if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
370 	    count++;
371 	  }
372 	}
373 
374 	if (count >= 1) {
375 
376 #if __WIN32__
377 	  beacon_th = (HANDLE)_beginthreadex (NULL, 0, &beacon_thread, NULL, 0, NULL);
378 	  if (beacon_th == NULL) {
379 	    text_color_set(DW_COLOR_ERROR);
380 	    dw_printf ("Could not create beacon thread\n");
381 	    return;
382 	  }
383 #else
384 	  int e;
385 
386 	  e = pthread_create (&beacon_tid, NULL, beacon_thread, NULL);
387 	  if (e != 0) {
388 	    text_color_set(DW_COLOR_ERROR);
389 	    perror("Could not create beacon thread");
390 	    return;
391 	  }
392 #endif
393 	}
394 
395 
396 } /* end beacon_init */
397 
398 
399 
400 
401 
402 /*-------------------------------------------------------------------
403  *
404  * Name:        beacon_thread
405  *
406  * Purpose:     Transmit beacons when it is time.
407  *
408  * Inputs:	g_misc_config_p->beacon
409  *
410  * Outputs:	g_misc_config_p->beacon[].next_time
411  *
412  * Description:	Go to sleep until it is time for the next beacon.
413  *		Transmit any beacons scheduled for now.
414  *		Repeat.
415  *
416  *--------------------------------------------------------------------*/
417 
418 #define MIN(x,y) ((x) < (y) ? (x) : (y))
419 
420 
421 
422 
423 #if __WIN32__
beacon_thread(void * arg)424 static unsigned __stdcall beacon_thread (void *arg)
425 #else
426 static void * beacon_thread (void *arg)
427 #endif
428 {
429 	int j;				/* Index into array of beacons. */
430 	time_t earliest;
431 	time_t now;			/* Current time. */
432 	int number_of_tbeacons;		/* Number of tracker beacons. */
433 
434 
435 /*
436  * SmartBeaconing state.
437  */
438 	time_t sb_prev_time = 0;	/* Time of most recent transmission. */
439 	float sb_prev_course = 0;	/* Most recent course reported. */
440 
441 
442 #if DEBUG
443 	struct tm tm;
444 	char hms[20];
445 
446 	now = time(NULL);
447 
448 	localtime_r (&now, &tm);
449 
450 	strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
451 	text_color_set(DW_COLOR_DEBUG);
452 	dw_printf ("beacon_thread: started %s\n", hms);
453 #endif
454 
455 /*
456  * See if any tracker beacons are configured.
457  * No need to obtain GPS data if none.
458  */
459 
460 	number_of_tbeacons = 0;
461 	for (j=0; j<g_misc_config_p->num_beacons; j++) {
462 	  if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
463 	    number_of_tbeacons++;
464 	  }
465 	}
466 
467 	now = time(NULL);
468 
469 	while (1) {
470 
471 	  dwgps_info_t gpsinfo;
472 
473 /*
474  * Sleep until time for the earliest scheduled or
475  * the soonest we could transmit due to corner pegging.
476  */
477 
478 	  earliest = now + 60 * 60;
479 	  for (j=0; j<g_misc_config_p->num_beacons; j++) {
480 	    if (g_misc_config_p->beacon[j].btype != BEACON_IGNORE) {
481 	      earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
482 	    }
483 	  }
484 
485 	  if (g_misc_config_p->sb_configured && number_of_tbeacons > 0) {
486 	    earliest = MIN(now + g_misc_config_p->sb_turn_time, earliest);
487             earliest = MIN(now + g_misc_config_p->sb_fast_rate, earliest);
488 	  }
489 
490 	  if (earliest > now) {
491 	    SLEEP_SEC (earliest - now);
492 	  }
493 
494 /*
495  * Woke up.  See what needs to be done.
496  */
497 	  now = time(NULL);
498 
499 #if DEBUG
500 	  localtime_r (&now, &tm);
501 	  strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
502 	  text_color_set(DW_COLOR_DEBUG);
503 	  dw_printf ("beacon_thread: woke up %s\n", hms);
504 #endif
505 
506 /*
507  * Get information from GPS if being used.
508  * This needs to be done before the next scheduled tracker
509  * beacon because corner pegging make it sooner.
510  */
511 
512 	  if (number_of_tbeacons > 0) {
513 
514 	    dwfix_t fix = dwgps_read (&gpsinfo);
515 	    float my_speed_mph = DW_KNOTS_TO_MPH(gpsinfo.speed_knots);
516 
517 	    if (g_tracker_debug_level >= 1) {
518 	      struct tm tm;
519 	      char hms[20];
520 
521 
522 	      localtime_r (&now, &tm);
523 	      strftime (hms, sizeof(hms), "%H:%M:%S", &tm);
524 	      text_color_set(DW_COLOR_DEBUG);
525 	      if (fix == 3) {
526 	        dw_printf ("%s  3D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0, %.1f m\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track, gpsinfo.altitude);
527 	      }
528 	      else if (fix == 2) {
529 	        dw_printf ("%s  2D, %.6f, %.6f, %.1f mph, %.0f\xc2\xb0\n", hms, gpsinfo.dlat, gpsinfo.dlon, my_speed_mph, gpsinfo.track);
530 	      }
531 	      else {
532 	        dw_printf ("%s  No GPS fix\n", hms);
533 	      }
534 	    }
535 
536 	    /* Don't complain here for no fix. */
537 	    /* Possibly at the point where about to transmit. */
538 
539 /*
540  * Run SmartBeaconing calculation if configured and GPS data available.
541  */
542 	    if (g_misc_config_p->sb_configured && fix >= DWFIX_2D) {
543 
544 	      time_t tnext = sb_calculate_next_time (now,
545 			DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
546 			sb_prev_time, sb_prev_course);
547 
548 	      for (j=0; j<g_misc_config_p->num_beacons; j++) {
549 	        if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
550 	          /* Haven't thought about the consequences of SmartBeaconing */
551 	          /* and having more than one tbeacon configured. */
552 	          if (tnext < g_misc_config_p->beacon[j].next) {
553 	             g_misc_config_p->beacon[j].next = tnext;
554 	          }
555 	        }
556 	      }  /* Update next time if sooner. */
557 	    }  /* apply SmartBeaconing */
558 	  }  /* tbeacon(s) configured. */
559 
560 /*
561  * Send if the time has arrived.
562  */
563 	  for (j=0; j<g_misc_config_p->num_beacons; j++) {
564 
565 	    struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
566 
567 	    if (bp->btype == BEACON_IGNORE)
568 	      continue;
569 
570 	    if (bp->next <= now) {
571 
572 	      /* Send the beacon. */
573 
574 	      beacon_send (j, &gpsinfo);
575 
576 	      /* Calculate when the next one should be sent. */
577 	      /* Easy for fixed interval.  SmartBeaconing takes more effort. */
578 
579 	      if (bp->btype == BEACON_TRACKER) {
580 
581 	        if (gpsinfo.fix < DWFIX_2D) {
582 	          /* Fix not available so beacon was not sent. */
583 
584 		  if (g_misc_config_p->sb_configured) {
585 	            /* Try again in a couple seconds. */
586 	            bp->next = now + 2;
587 	          }
588 	          else {
589 	            /* Stay with the schedule. */
590 	            /* Important for slotted.  Might reconsider otherwise. */
591 	            bp->next += bp->every;
592 	          }
593 	        }
594 	        else if (g_misc_config_p->sb_configured) {
595 
596 		  /* Remember most recent tracker beacon. */
597 	          /* Compute next time if not turning. */
598 
599 		  sb_prev_time = now;
600 		  sb_prev_course = gpsinfo.track;
601 
602 	          bp->next = sb_calculate_next_time (now,
603 			DW_KNOTS_TO_MPH(gpsinfo.speed_knots), gpsinfo.track,
604 			sb_prev_time, sb_prev_course);
605 	        }
606 	        else {
607 	          /* Tracker beacon, fixed spacing. */
608 	          bp->next += bp->every;
609 	        }
610 	      }
611 	      else {
612 	        /* Non-tracker beacon, fixed spacing. */
613 		/* Increment by 'every' so slotted times come out right. */
614 	        /* i.e. Don't take relative to now in case there was some delay. */
615 
616 	        bp->next += bp->every;
617 	      }
618 
619 	    }  /* if time to send it */
620 
621 	  }  /* for each configured beacon */
622 
623 	}  /* do forever */
624 
625 #if __WIN32__
626 	return(0);	/* unreachable but warning if not here. */
627 #else
628 	return(NULL);
629 #endif
630 
631 } /* end beacon_thread */
632 
633 
634 /*-------------------------------------------------------------------
635  *
636  * Name:        sb_calculate_next_time
637  *
638  * Purpose:     Calculate next transmission time using the SmartBeaconing algorithm.
639  *
640  * Inputs:	now			- Current time.
641  *
642  *		current_speed_mph	- Current speed from GPS.
643  *				  	  Not expecting G_UNKNOWN but should check for it.
644  *
645  *		current_course		- Current direction of travel.
646  *				  	  Could be G_UNKNOWN if stationary.
647  *
648  *		last_xmit_time		- Time of most recent transmission.
649  *
650  *		last_xmit_course	- Direction included in most recent transmission.
651  *
652  * Global In:	g_misc_config_p->
653  *			sb_configured	TRUE if SmartBeaconing is configured.
654  *			sb_fast_speed	MPH
655  *			sb_fast_rate	seconds
656  *			sb_slow_speed	MPH
657  *			sb_slow_rate	seconds
658  *			sb_turn_time	seconds
659  *			sb_turn_angle	degrees
660  *			sb_turn_slope	degrees * MPH
661  *
662  * Returns:	Time of next transmission.
663  *		Could vary from now to sb_slow_rate in the future.
664  *
665  * Caution:	The algorithm is defined in MPH units.    GPS uses knots.
666  *		The caller must be careful about using the proper conversions.
667  *
668  *--------------------------------------------------------------------*/
669 
670 /* Difference between two angles. */
671 
heading_change(float a,float b)672 static float heading_change (float a, float b)
673 {
674 	float diff;
675 
676 	diff = fabs(a - b);
677 	if (diff <= 180.)
678 	  return (diff);
679 	else
680 	  return (360. - diff);
681 }
682 
sb_calculate_next_time(time_t now,float current_speed_mph,float current_course,time_t last_xmit_time,float last_xmit_course)683 static time_t sb_calculate_next_time (time_t now,
684 			float current_speed_mph, float current_course,
685 			time_t last_xmit_time, float last_xmit_course)
686 {
687 	int beacon_rate;
688 	time_t next_time;
689 
690 /*
691  * Compute time between beacons for travelling in a straight line.
692  */
693 
694 	if (current_speed_mph == G_UNKNOWN) {
695 	  beacon_rate = (int)roundf((g_misc_config_p->sb_fast_rate + g_misc_config_p->sb_slow_rate) / 2.);
696 	}
697 	else if (current_speed_mph > g_misc_config_p->sb_fast_speed) {
698 	  beacon_rate = g_misc_config_p->sb_fast_rate;
699 	}
700 	else if (current_speed_mph < g_misc_config_p->sb_slow_speed) {
701 	  beacon_rate = g_misc_config_p->sb_slow_rate;
702 	}
703 	else {
704 	  /* Can't divide by 0 assuming sb_slow_speed > 0. */
705 	  beacon_rate = (int)roundf(( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / current_speed_mph);
706 	}
707 
708 	if (g_tracker_debug_level >= 2) {
709 	  text_color_set(DW_COLOR_DEBUG);
710 	  dw_printf ("SmartBeaconing: Beacon Rate = %d seconds for %.1f MPH\n", beacon_rate, current_speed_mph);
711 	}
712 
713 	next_time = last_xmit_time + beacon_rate;
714 
715 /*
716  * Test for "Corner Pegging" if moving.
717  */
718 	if (current_speed_mph != G_UNKNOWN && current_speed_mph >= 1.0 &&
719 		current_course != G_UNKNOWN && last_xmit_course != G_UNKNOWN) {
720 
721 	  float change = heading_change(current_course, last_xmit_course);
722 	  float turn_threshold = g_misc_config_p->sb_turn_angle +
723 			g_misc_config_p->sb_turn_slope / current_speed_mph;
724 
725 	  if (change > turn_threshold &&
726 		  now >= last_xmit_time + g_misc_config_p->sb_turn_time) {
727 
728 	    if (g_tracker_debug_level >= 2) {
729 	      text_color_set(DW_COLOR_DEBUG);
730 	      dw_printf ("SmartBeaconing: Send now for heading change of %.0f\n", change);
731 	    }
732 
733 	    next_time = now;
734 	  }
735 	}
736 
737 	return (next_time);
738 
739 } /* end sb_calculate_next_time */
740 
741 
742 /*-------------------------------------------------------------------
743  *
744  * Name:        beacon_send
745  *
746  * Purpose:     Transmit one beacon after it was determined to be time.
747  *
748  * Inputs:	j			Index into beacon configuration array below.
749  *
750  *		gpsinfo			Information from GPS.  Used only for TBEACON.
751  *
752  * Global In:	g_misc_config_p->beacon		Array of beacon configurations.
753  *
754  * Outputs:	Destination(s) specified:
755  *		 - Transmit queue.
756  *		 - IGate.
757  *		 - Simulated reception.
758  *
759  * Description:	Prepare text in monitor format.
760  *		Convert to packet object.
761  *		Send to desired destination(s).
762  *
763  *--------------------------------------------------------------------*/
764 
beacon_send(int j,dwgps_info_t * gpsinfo)765 static void beacon_send (int j, dwgps_info_t *gpsinfo)
766 {
767 
768 	struct beacon_s *bp = & (g_misc_config_p->beacon[j]);
769 
770 	      int strict = 1;	/* Strict packet checking because they will go over air. */
771 	      char stemp[20];
772 	      char info[AX25_MAX_INFO_LEN];
773 	      char beacon_text[AX25_MAX_PACKET_LEN];
774 	      packet_t pp = NULL;
775 	      char mycall[AX25_MAX_ADDR_LEN];
776 
777 	      char super_comment[AX25_MAX_INFO_LEN];	// Fixed part + any dynamic part.
778 
779 /*
780  * Obtain source call for the beacon.
781  * This could potentially be different on different channels.
782  * When sending to IGate server, use call from first radio channel.
783  *
784  * Check added in version 1.0a.  Previously used index of -1.
785  *
786  * Version 1.1 - channel should now be 0 for IGate.
787  * Type of destination is encoded separately.
788  */
789 	      strlcpy (mycall, "NOCALL", sizeof(mycall));
790 
791 	      assert (bp->sendto_chan >= 0);
792 
793 	      strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall));
794 
795 	      if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
796 	        text_color_set(DW_COLOR_ERROR);
797 	        dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno);
798 		return;
799 	      }
800 
801 /*
802  * Prepare the monitor format header.
803  *
804  * 	src > dest [ , via ]
805  */
806 
807 	      strlcpy (beacon_text, mycall, sizeof(beacon_text));
808 	      strlcat (beacon_text, ">", sizeof(beacon_text));
809 
810 	      if (bp->dest != NULL) {
811 	        strlcat (beacon_text, bp->dest, sizeof(beacon_text));
812 	      }
813 	      else {
814 	         snprintf (stemp, sizeof(stemp), "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
815 	         strlcat (beacon_text, stemp, sizeof(beacon_text));
816 	      }
817 
818 	      if (bp->via != NULL) {
819 	        strlcat (beacon_text, ",", sizeof(beacon_text));
820 	        strlcat (beacon_text, bp->via, sizeof(beacon_text));
821 	      }
822 	      strlcat (beacon_text, ":", sizeof(beacon_text));
823 
824 
825 /*
826  * If the COMMENTCMD option was specified, run specified command to get variable part.
827  * Result is any fixed part followed by any variable part.
828  */
829 
830 // TODO: test & document.
831 
832 	      strlcpy (super_comment, "", sizeof(super_comment));
833 	      if (bp->comment != NULL) {
834 	        strlcpy (super_comment, bp->comment, sizeof(super_comment));
835 	      }
836 
837 	      if (bp->commentcmd != NULL) {
838 	        char var_comment[AX25_MAX_INFO_LEN];
839 	        int k;
840 
841 	        /* Run given command to get variable part of comment. */
842 
843 	        k = dw_run_cmd (bp->commentcmd, 2, var_comment, sizeof(var_comment));
844 	        if (k > 0) {
845 	          strlcat (super_comment, var_comment, sizeof(super_comment));
846 	        }
847 	        else {
848 		  text_color_set(DW_COLOR_ERROR);
849 	          dw_printf ("xBEACON, config file line %d, COMMENTCMD failure.\n", bp->lineno);
850 	        }
851 	      }
852 
853 
854 /*
855  * Add the info part depending on beacon type.
856  */
857 	      switch (bp->btype) {
858 
859 		case BEACON_POSITION:
860 
861 		  encode_position (bp->messaging, bp->compress,
862 			bp->lat, bp->lon, bp->ambiguity,
863 			(int)roundf(DW_METERS_TO_FEET(bp->alt_m)),
864 			bp->symtab, bp->symbol,
865 			bp->power, bp->height, bp->gain, bp->dir,
866 			G_UNKNOWN, G_UNKNOWN, /* course, speed */
867 			bp->freq, bp->tone, bp->offset,
868 			super_comment,
869 			info, sizeof(info));
870 		  strlcat (beacon_text, info, sizeof(beacon_text));
871 		  break;
872 
873 		case BEACON_OBJECT:
874 
875 		  encode_object (bp->objname, bp->compress, 0, bp->lat, bp->lon, bp->ambiguity,
876 			bp->symtab, bp->symbol,
877 			bp->power, bp->height, bp->gain, bp->dir,
878 			G_UNKNOWN, G_UNKNOWN, /* course, speed */
879 			bp->freq, bp->tone, bp->offset, super_comment,
880 			info, sizeof(info));
881 		  strlcat (beacon_text, info, sizeof(beacon_text));
882 		  break;
883 
884 		case BEACON_TRACKER:
885 
886 		  if (gpsinfo->fix >= DWFIX_2D) {
887 
888 		    int coarse;		/* Round to nearest integer. retaining unknown state. */
889 	            int my_alt_ft;
890 
891 	            /* Transmit altitude only if user asked for it. */
892 		    /* A positive altitude in the config file enables */
893 	            /* transmission of altitude from GPS. */
894 
895 	            my_alt_ft = G_UNKNOWN;
896 	            if (gpsinfo->fix >= 3 && gpsinfo->altitude != G_UNKNOWN && bp->alt_m > 0) {
897 	              my_alt_ft = (int)roundf(DW_METERS_TO_FEET(gpsinfo->altitude));
898 	            }
899 
900 		    coarse = G_UNKNOWN;
901 		    if (gpsinfo->track != G_UNKNOWN) {
902 	              coarse = (int)roundf(gpsinfo->track);
903 	            }
904 
905 		    encode_position (bp->messaging, bp->compress,
906 			gpsinfo->dlat, gpsinfo->dlon, bp->ambiguity, my_alt_ft,
907 			bp->symtab, bp->symbol,
908 			bp->power, bp->height, bp->gain, bp->dir,
909 			coarse, (int)roundf(gpsinfo->speed_knots),
910 			bp->freq, bp->tone, bp->offset,
911 			super_comment,
912 			info, sizeof(info));
913 		    strlcat (beacon_text, info, sizeof(beacon_text));
914 
915 		    /* Write to log file for testing. */
916 		    /* The idea is to run log2gpx and map the result rather than */
917 		    /* actually transmitting and relying on someone else to receive */
918 		    /* the signals. */
919 
920 	            if (g_tracker_debug_level >= 3) {
921 
922 		      decode_aprs_t A;
923 		      alevel_t alevel;
924 
925 		      memset (&A, 0, sizeof(A));
926 	  	      A.g_freq   = G_UNKNOWN;
927 	  	      A.g_offset = G_UNKNOWN;
928 	  	      A.g_tone   = G_UNKNOWN;
929 	  	      A.g_dcs    = G_UNKNOWN;
930 
931 		      strlcpy (A.g_src, mycall, sizeof(A.g_src));
932 		      A.g_symbol_table = bp->symtab;
933 		      A.g_symbol_code = bp->symbol;
934 		      A.g_lat = gpsinfo->dlat;
935 		      A.g_lon = gpsinfo->dlon;
936 		      A.g_speed_mph = DW_KNOTS_TO_MPH(gpsinfo->speed_knots);
937 		      A.g_course = coarse;
938 		      A.g_altitude_ft = DW_METERS_TO_FEET(gpsinfo->altitude);
939 
940 		      /* Fake channel of 999 to distinguish from real data. */
941 		      memset (&alevel, 0, sizeof(alevel));
942 		      log_write (999, &A, NULL, alevel, 0);
943 		    }
944 	 	  }
945 	          else {
946 	            return;   /* No fix.  Skip this time. */
947 		  }
948 		  break;
949 
950 		case BEACON_CUSTOM:
951 
952 		  if (bp->custom_info != NULL) {
953 
954 		    /* Fixed handcrafted text. */
955 
956 	            strlcat (beacon_text, bp->custom_info, sizeof(beacon_text));
957 		  }
958 		  else if (bp->custom_infocmd != NULL) {
959 		    char info_part[AX25_MAX_INFO_LEN];
960 		    int k;
961 
962 	            /* Run given command to obtain the info part for packet. */
963 
964 		    k = dw_run_cmd (bp->custom_infocmd, 2, info_part, sizeof(info_part));
965 		    if (k > 0) {
966 	              strlcat (beacon_text, info_part, sizeof(beacon_text));
967 	            }
968 	            else {
969 		      text_color_set(DW_COLOR_ERROR);
970 	              dw_printf ("CBEACON, config file line %d, INFOCMD failure.\n", bp->lineno);
971 		      strlcpy (beacon_text, "", sizeof(beacon_text));  // abort!
972 	            }
973 		  }
974 		  else {
975 		    text_color_set(DW_COLOR_ERROR);
976 	    	    dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
977 		    strlcpy (beacon_text, "", sizeof(beacon_text));  // abort!
978 	          }
979 		  break;
980 
981 		case BEACON_IGATE:
982 
983 	          {
984 	            int last_minutes = 30;
985 	            char stuff[256];
986 
987 		    snprintf (stuff, sizeof(stuff), "<IGATE,MSG_CNT=%d,PKT_CNT=%d,DIR_CNT=%d,LOC_CNT=%d,RF_CNT=%d,UPL_CNT=%d,DNL_CNT=%d",
988 						igate_get_msg_cnt(),
989 						igate_get_pkt_cnt(),
990 						mheard_count(0,last_minutes),
991 						mheard_count(g_igate_config_p->max_digi_hops,last_minutes),
992 						mheard_count(8,last_minutes),
993 						igate_get_upl_cnt(),
994 						igate_get_dnl_cnt());
995 
996 		    strlcat (beacon_text, stuff, sizeof(beacon_text));
997 	          }
998 		  break;
999 
1000 		case BEACON_IGNORE:
1001 	        default:
1002 		  break;
1003 
1004 	      } /* switch beacon type. */
1005 
1006 /*
1007  * Parse monitor format into form for transmission.
1008  */
1009 	      if (strlen(beacon_text) == 0) {
1010 		return;
1011 	      }
1012 
1013 	      pp = ax25_from_text (beacon_text, strict);
1014 
1015               if (pp != NULL) {
1016 
1017 		/* Send to desired destination. */
1018 
1019 	        alevel_t alevel;
1020 
1021 
1022 	        switch (bp->sendto_type) {
1023 
1024 	          case SENDTO_IGATE:
1025 
1026 	  	    text_color_set(DW_COLOR_XMIT);
1027 	  	    dw_printf ("[ig] %s\n", beacon_text);
1028 
1029 		    igate_send_rec_packet (0, pp);
1030 		    ax25_delete (pp);
1031 	            break;
1032 
1033 		  case SENDTO_XMIT:
1034 		  default:
1035 
1036 	            tq_append (bp->sendto_chan, TQ_PRIO_1_LO, pp);
1037 		    break;
1038 
1039 		  case SENDTO_RECV:
1040 
1041 	            /* Simulated reception from radio. */
1042 
1043 		    memset (&alevel, 0xff, sizeof(alevel));
1044 	            dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, 0, "");
1045 	            break;
1046 		}
1047 	      }
1048 	      else {
1049 	        text_color_set(DW_COLOR_ERROR);
1050 	        dw_printf ("Config file: Failed to parse packet constructed from line %d.\n", bp->lineno);
1051 	        dw_printf ("%s\n", beacon_text);
1052 	      }
1053 
1054 } /* end beacon_send */
1055 
1056 
1057 /* end beacon.c */
1058