static void xmit_speech (int c, packet_t pp) { int info_len; unsigned char *pinfo; /* * Print spoken packet. Prefix by channel. */ info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.speech] \"%s\"\n", c, pinfo); if (strlen(save_audio_config_p->tts_script) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Text-to-speech script has not been configured.\n"); ax25_delete (pp); return; } /* * Turn on transmitter. */ ptt_set (OCTYPE_PTT, c, 1); /* * Invoke the speech-to-text script. */ xmit_speak_it (save_audio_config_p->tts_script, c, (char*)pinfo); /* * Turn off transmitter. */ ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_speech */
static void send_packet (char *str) { packet_t pp; unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; int flen; int c; pp = ax25_from_text (str, 1); flen = ax25_pack (pp, fbuf); for (c=0; c<modem.adev[0].num_channels; c++) { hdlc_send_flags (c, 8, 0); hdlc_send_frame (c, fbuf, flen); hdlc_send_flags (c, 2, 1); } ax25_delete (pp); }
static void xmit_morse (int c, packet_t pp, int wpm) { int info_len; unsigned char *pinfo; info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.morse] \"%s\"\n", c, pinfo); ptt_set (OCTYPE_PTT, c, 1); morse_send (c, (char*)pinfo, wpm, xmit_txdelay[c] * 10, xmit_txtail[c] * 10); ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_morse */
static void pftest (int test_num, char *filter, char *monitor, int expected) { int result; packet_t pp; text_color_set (DW_COLOR_DEBUG); dw_printf ("test number %d\n", test_num); pp = ax25_from_text (monitor, 1); assert (pp != NULL); result = pfilter (0, 0, filter, pp); if (result != expected) { text_color_set (DW_COLOR_ERROR); dw_printf ("Unexpected result for test number %d\n", test_num); error_count++; } ax25_delete (pp); }
static void test (char *in, char *out) { packet_t pp, result; //int should_repeat; char rec[256]; char xmit[256]; unsigned char *pinfo; int info_len; unsigned char frame[AX25_MAX_PACKET_LEN]; int frame_len; alevel_t alevel; dw_printf ("\n"); /* * As an extra test, change text to internal format back to * text again to make sure it comes out the same. */ pp = ax25_from_text (in, 1); assert (pp != NULL); ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); strlcat (rec, (char*)pinfo, sizeof(rec)); if (strcmp(in, rec) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Text/internal/text error-1 %s -> %s\n", in, rec); } /* * Just for more fun, write as the frame format, read it back * again, and make sure it is still the same. */ frame_len = ax25_pack (pp, frame); ax25_delete (pp); alevel.rec = 50; alevel.mark = 50; alevel.space = 50; pp = ax25_from_frame (frame, frame_len, alevel); assert (pp != NULL); ax25_format_addrs (pp, rec); info_len = ax25_get_info (pp, &pinfo); strlcat (rec, (char*)pinfo, sizeof(rec)); if (strcmp(in, rec) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("internal/frame/internal/text error-2 %s -> %s\n", in, rec); } /* * On with the digipeater test. */ text_color_set(DW_COLOR_REC); dw_printf ("Rec\t%s\n", rec); //TODO: Add filtering to test. // V result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL); if (result != NULL) { dedupe_remember (result, 0); ax25_format_addrs (result, xmit); info_len = ax25_get_info (result, &pinfo); strlcat (xmit, (char*)pinfo, sizeof(xmit)); ax25_delete (result); } else { strlcpy (xmit, "", sizeof(xmit)); } text_color_set(DW_COLOR_XMIT); dw_printf ("Xmit\t%s\n", xmit); if (strcmp(xmit, out) == 0) { text_color_set(DW_COLOR_INFO); dw_printf ("OK\n"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Expect\t%s\n", out); failed++; } dw_printf ("\n"); }
static void xmit_ax25_frames (int c, int p, packet_t pp) { unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; int flen; char stemp[1024]; /* max size needed? */ int info_len; unsigned char *pinfo; int pre_flags, post_flags; int num_bits; /* Total number of bits in transmission */ /* including all flags and bit stuffing. */ int duration; /* Transmission time in milliseconds. */ int already; int wait_more; int maxframe; /* Maximum number of frames for one transmission. */ int numframe; /* Number of frames sent during this transmission. */ /* * These are for timing of a transmission. * All are in usual unix time (seconds since 1/1/1970) but higher resolution */ double time_ptt; /* Time when PTT is turned on. */ double time_now; /* Current time. */ int nb; maxframe = (p == TQ_PRIO_0_HI) ? 1 : 7; /* * Print trasmitted packet. Prefix by channel and priority. * Do this before we get into the time critical part. */ ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); (void)ax25_check_addresses (pp); /* Optional hex dump of packet. */ if (g_debug_xmit_packet) { text_color_set(DW_COLOR_DEBUG); dw_printf ("------\n"); ax25_hex_dump (pp); dw_printf ("------\n"); } /* * Turn on transmitter. * Start sending leading flag bytes. */ time_ptt = dtime_now (); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", c, xmit_bits_per_sec[c]); #endif ptt_set (OCTYPE_PTT, c, 1); pre_flags = MS_TO_BITS(xmit_txdelay[c] * 10, c) / 8; num_bits = hdlc_send_flags (c, pre_flags, 0); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[c], pre_flags, num_bits); #endif /* * Transmit the frame. */ flen = ax25_pack (pp, fbuf); assert (flen >= 1 && flen <= sizeof(fbuf)); nb = hdlc_send_frame (c, fbuf, flen); num_bits += nb; numframe = 1; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); #endif ax25_delete (pp); /* * Additional packets if available and not exceeding max. */ while (numframe < maxframe && tq_count (c,p) > 0) { pp = tq_remove (c, p); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp); #endif ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); (void)ax25_check_addresses (pp); if (g_debug_xmit_packet) { text_color_set(DW_COLOR_DEBUG); dw_printf ("------\n"); ax25_hex_dump (pp); dw_printf ("------\n"); } /* * Transmit the frame. */ flen = ax25_pack (pp, fbuf); assert (flen >= 1 && flen <= sizeof(fbuf)); nb = hdlc_send_frame (c, fbuf, flen); num_bits += nb; numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); #endif ax25_delete (pp); } /* * Need TXTAIL because we don't know exactly when the sound is done. */ post_flags = MS_TO_BITS(xmit_txtail[c] * 10, c) / 8; nb = hdlc_send_flags (c, post_flags, 1); num_bits += nb; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[c], post_flags, nb, num_bits); #endif /* * While demodulating is CPU intensive, generating the tones is not. * Example: on the RPi, with 50% of the CPU taken with two receive * channels, a transmission of more than a second is generated in * about 40 mS of elapsed real time. */ audio_wait(ACHAN2ADEV(c)); /* * Ideally we should be here just about the time when the audio is ending. * However, the innards of "audio_wait" are not satisfactory in all cases. * * Calculate how long the frame(s) should take in milliseconds. */ duration = BITS_TO_MS(num_bits, c); /* * See how long it has been since PTT was turned on. * Wait additional time if necessary. */ time_now = dtime_now(); already = (int) ((time_now - time_ptt) * 1000.); wait_more = duration - already; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: xmit duration=%d, %d already elapsed since PTT, wait %d more\n", duration, already, wait_more ); #endif if (wait_more > 0) { SLEEP_MS(wait_more); } else if (wait_more < -100) { /* If we run over by 10 mSec or so, it's nothing to worry about. */ /* However, if PTT is still on about 1/10 sec after audio */ /* should be done, something is wrong. */ /* Looks like a bug with the RPi audio system. Never an issue with Ubuntu. */ /* This runs over randomly sometimes. TODO: investigate more fully sometime. */ #ifndef __arm__ text_color_set(DW_COLOR_ERROR); dw_printf ("Transmit timing error: PTT is on %d mSec too long.\n", -wait_more); #endif } /* * Turn off transmitter. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); time_now = dtime_now(); dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration); #endif ptt_set (OCTYPE_PTT, c, 0); } /* end xmit_ax25_frames */
static void * xmit_thread (void *arg) #endif { int c = (int)(long)arg; // channel number. packet_t pp; int p; int ok; /* * These are for timing of a transmission. * All are in usual unix time (seconds since 1/1/1970) but higher resolution */ while (1) { tq_wait_while_empty (c); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread, channel %d: woke up\n", c); #endif for (p=0; p<TQ_NUM_PRIO; p++) { pp = tq_remove (c, p); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", c, p, pp); #endif if (pp != NULL) { /* * Wait for the channel to be clear. * For the high priority queue, begin transmitting immediately. * For the low priority queue, wait a random amount of time, in hopes * of minimizing collisions. */ ok = wait_for_clear_channel (c, (p==TQ_PRIO_0_HI), xmit_slottime[c], xmit_persist[c]); if (ok) { /* * Channel is clear and we have lock on output device. * * If destination is "SPEECH" send info part to speech synthesizer. * If destination is "MORSE" send as morse code. */ char dest[AX25_MAX_ADDR_LEN]; int ssid = 0; if (ax25_is_aprs (pp)) { ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest); ssid = ax25_get_ssid(pp, AX25_DESTINATION); } else { strlcpy (dest, "", sizeof(dest)); } if (strcmp(dest, "SPEECH") == 0) { xmit_speech (c, pp); } else if (strcmp(dest, "MORSE") == 0) { int wpm = ssid * 2; if (wpm == 0) wpm = MORSE_DEFAULT_WPM; // This is a bit of a hack so we don't respond too quickly for APRStt. // It will be sent in high priority queue while a beacon wouldn't. // Add a little delay so user has time release PTT after sending #. // This and default txdelay would give us a second. if (p == TQ_PRIO_0_HI) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("APRStt morse xmit delay hack...\n"); SLEEP_MS (700); } xmit_morse (c, pp, wpm); } else { xmit_ax25_frames (c, p, pp); } dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(c)])); } else { /* * Timeout waiting for clear channel. * Discard the packet. * Display with ERROR color rather than XMIT color. */ char stemp[1024]; /* max size needed? */ int info_len; unsigned char *pinfo; text_color_set(DW_COLOR_ERROR); dw_printf ("Waited too long for clear channel. Discarding packet below.\n"); ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_INFO); dw_printf ("[%d%c] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L'); dw_printf ("%s", stemp); /* stations followed by : */ ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); ax25_delete (pp); } /* wait for clear channel. */ } /* for high priority then low priority */ } } return 0; /* unreachable but quiet the warning. */ } /* end xmit_thread */
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 = 0; /* meters */ /* * 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; j<g_misc_config_p->num_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 = KNOTS_TO_MPH * my_speed_knots; my_lat = 42.99; my_lon = 71.99; my_alt = 100; #else if (g_using_gps) { fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt); my_speed_mph = KNOTS_TO_MPH * my_speed_knots; /* 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; } else if (my_speed_mph < g_misc_config_p->sb_slow_speed) { sb_every = g_misc_config_p->sb_slow_rate; } 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 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) { /* Send it now. */ for (j=0; j<g_misc_config_p->num_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; j<g_misc_config_p->num_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]; /* * 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. */ strcpy (mycall, "NOCALL"); if (g_misc_config_p->beacon[j].chan == -1) { strcpy (mycall, g_digi_config_p->mycall[0]); } else { strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].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. */ strcpy (beacon_text, mycall); strcat (beacon_text, ">"); sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION); strcat (beacon_text, stemp); if (g_misc_config_p->beacon[j].via) { 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: encode_position (g_misc_config_p->beacon[j].compress, 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_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].compress, my_lat, my_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, 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; } } 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 IGate server or radio. */ if (g_misc_config_p->beacon[j].chan == -1) { #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); } else { tq_append (g_misc_config_p->beacon[j].chan, TQ_PRIO_1_LO, pp); } } 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 */