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