// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // /*------------------------------------------------------------------ * * Module: xmit.c * * Purpose: Transmit queued up packets when channel is clear. * * Description: Producers of packets to be transmitted call tq_append and then * go merrily on their way, unconcerned about when the packet might * actually get transmitted. * * This thread waits until the channel is clear and then removes * packets from the queue and transmits them. * * * Usage: (1) The main application calls xmit_init. * * This will initialize the transmit packet queue * and create a thread to empty the queue when * the channel is clear. * * (2) The application queues up packets by calling tq_append. * * Packets that are being digipeated should go in the * high priority queue so they will go out first. * * Other packets should go into the lower priority queue. * * (3) xmit_thread removes packets from the queue and transmits * them when other signals are not being heard. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #include "direwolf.h" #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "tq.h" #include "xmit.h" #include "hdlc_send.h" #include "hdlc_rec.h" #include "ptt.h" #include "dtime_now.h" #include "morse.h" /* * Parameters for transmission. * Each channel can have different timing values. * * These are initialized once at application startup time * and some can be changed later by commands from connected applications. */ static int xmit_slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */ static int xmit_persist[MAX_CHANS]; /* Sets probability for transmitting after each */ /* slot time delay. Transmit if a random number */ /* in range of 0 - 255 <= persist value. */ /* Otherwise wait another slot time and try again. */ static int xmit_txdelay[MAX_CHANS]; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ static int xmit_txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ /* are done sending the data. This is to avoid */ /* dropping PTT too soon and chopping off the end */ /* of the frame. Again 10 mS units. */ static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */ /* Often called baud rate which is equivalent in */ /* this case but could be different with other */ /* modulation techniques. */ static int g_debug_xmit_packet; /* print packet in hexadecimal form for debugging. */ #define BITS_TO_MS(b,ch) (((b)*1000)/xmit_bits_per_sec[(ch)]) #define MS_TO_BITS(ms,ch) (((ms)*xmit_bits_per_sec[(ch)])/1000) #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg); #else static void * xmit_thread (void *arg); #endif /* * When an audio device is in stereo mode, we can have two * different channels that want to transmit at the same time. * We are not clever enough to multiplex them so use this * so only one is activte at the same time. */ static dw_mutex_t audio_out_dev_mutex[MAX_ADEVS]; static int wait_for_clear_channel (int channel, int nowait, int slotttime, int persist); static void xmit_ax25_frames (int c, int p, packet_t pp); static void xmit_speech (int c, packet_t pp); static void xmit_morse (int c, packet_t pp, int wpm); /*------------------------------------------------------------------- * * Name: xmit_init * * Purpose: Initialize the transmit process. * * Inputs: modem - Structure with modem and timing parameters. * * * 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(s) to actually send the packets * at the appropriate time. * * Version 1.2: We now allow multiple audio devices with one or two channels each. * Each audio channel has its own thread. * *--------------------------------------------------------------------*/ static struct audio_s *save_audio_config_p; void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) { int j; int ad; #if __WIN32__ HANDLE xmit_th[MAX_CHANS]; #else //pthread_attr_t attr; //struct sched_param sp; pthread_t xmit_tid[MAX_CHANS]; #endif //int e; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init ( ... )\n"); #endif save_audio_config_p = p_modem; g_debug_xmit_packet = debug_xmit_packet; /* * Push to Talk (PTT) control. */ #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to call ptt_init \n"); #endif ptt_init (p_modem); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: back from ptt_init \n"); #endif /* * Save parameters for later use. * TODO1.2: Any reason to use global config rather than making a copy? */ for (j=0; jachan[j].baud; xmit_slottime[j] = p_modem->achan[j].slottime; xmit_persist[j] = p_modem->achan[j].persist; xmit_txdelay[j] = p_modem->achan[j].txdelay; xmit_txtail[j] = p_modem->achan[j].txtail; } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to call tq_init \n"); #endif tq_init (p_modem); for (ad = 0; ad < MAX_ADEVS; ad++) { dw_mutex_init (&(audio_out_dev_mutex[ad])); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: about to create threads \n"); #endif //TODO: xmit thread should be higher priority to avoid // underrun on the audio output device. for (j=0; jachan[j].valid) { #if __WIN32__ xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(long)j, 0, NULL); if (xmit_th[j] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create xmit thread %d\n", j); return; } #else int e; #if 0 //TODO: not this simple. probably need FIFO policy. pthread_attr_init (&attr); e = pthread_attr_getschedparam (&attr, &sp); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("pthread_attr_getschedparam"); } text_color_set(DW_COLOR_ERROR); dw_printf ("Default scheduling priority = %d, min=%d, max=%d\n", sp.sched_priority, sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER)); sp.sched_priority--; e = pthread_attr_setschedparam (&attr, &sp); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("pthread_attr_setschedparam"); } e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j); pthread_attr_destroy (&attr); #else e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(long)j); #endif if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create xmit thread for audio device"); return; } #endif } } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_init: finished \n"); #endif } /* end tq_init */ /*------------------------------------------------------------------- * * Name: xmit_set_txdelay * xmit_set_persist * xmit_set_slottime * xmit_set_txtail * * * Purpose: The KISS protocol, and maybe others, can specify * transmit timing parameters. If the application * specifies these, they will override what was read * from the configuration file. * * Inputs: channel - should be 0 or 1. * * value - time values are in 10 mSec units. * * * Outputs: Remember required information for future use. * * Question: Should we have an option to enable or disable the * application changing these values? * * Bugs: No validity checking other than array subscript out of bounds. * *--------------------------------------------------------------------*/ void xmit_set_txdelay (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_txdelay[channel] = value; } } void xmit_set_persist (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_persist[channel] = value; } } void xmit_set_slottime (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_slottime[channel] = value; } } void xmit_set_txtail (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_txtail[channel] = value; } } /*------------------------------------------------------------------- * * Name: xmit_thread * * Purpose: Process transmit queue for one channel. * * Inputs: transmit packet queue. * * Outputs: * * Description: We have different timing rules for different types of * packets so they are put into different queues. * * High Priority - * * Packets which are being digipeated go out first. * Latest recommendations are to retransmit these * immdediately (after no one else is heard, of course) * rather than waiting random times to avoid collisions. * The KPC-3 configuration option for this is "UIDWAIT OFF". (?) * * Low Priority - * * Other packets are sent after a random wait time * (determined by PERSIST & SLOTTIME) to help avoid * collisions. * * If more than one audio channel is being used, a separate * pair of transmit queues is used for each channel. * * * * Version 1.2: Allow more than one audio device. * each channel has its own thread. * Add speech capability. * *--------------------------------------------------------------------*/ #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg) #else 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= 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 */ /*------------------------------------------------------------------- * * Name: xmit_speech * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as speech. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * Description: Turn on transmitter. * Invoke the text-to-speech script. * Turn off transmitter. * *--------------------------------------------------------------------*/ 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); (void)info_len; 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 */ /* Broken out into separate function so configuration can validate it. */ /* Returns 0 for success. */ int xmit_speak_it (char *script, int c, char *orig_msg) { int err; char cmd[2000]; char *p; char msg[2000]; /* Remove any quotes because it will mess up command line argument parsing. */ strlcpy (msg, orig_msg, sizeof(msg)); for (p=msg; *p!='\0'; p++) { if (*p == '"') *p = ' '; } #if __WIN32__ snprintf (cmd, sizeof(cmd), "%s %d \"%s\" >nul", script, c, msg); #else snprintf (cmd, sizeof(cmd), "%s %d \"%s\"", script, c, msg); #endif //text_color_set(DW_COLOR_DEBUG); //dw_printf ("cmd=%s\n", cmd); err = system (cmd); if (err != 0) { char cwd[1000]; char path[3000]; char *ignore; text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to run text-to-speech script, %s\n", script); ignore = getcwd (cwd, sizeof(cwd)); (void)ignore; strlcpy (path, getenv("PATH"), sizeof(path)); dw_printf ("CWD = %s\n", cwd); dw_printf ("PATH = %s\n", path); } return (err); } /*------------------------------------------------------------------- * * Name: xmit_morse * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as Morse code. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * wpm - Speed in words per minute. * * Description: Turn on transmitter. * Send text as Morse code. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_morse (int c, packet_t pp, int wpm) { int info_len; unsigned char *pinfo; info_len = ax25_get_info (pp, &pinfo); (void)info_len; 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 */ /*------------------------------------------------------------------- * * Name: wait_for_clear_channel * * Purpose: Wait for the radio channel to be clear and any * additional time for collision avoidance. * * * * Inputs: channel - Radio channel number. * * nowait - Should be true for the high priority queue * (packets being digipeated). This will * allow transmission immediately when the * channel is clear rather than waiting a * random amount of time. * * slottime - Amount of time to wait for each iteration * of the waiting algorithm. 10 mSec units. * * persist - Probability of transmitting * * Returns: 1 for OK. 0 for timeout. * * Description: * * New in version 1.2: also obtain a lock on audio out device. * * Transmit delay algorithm: * * Wait for channel to be clear. * Return if nowait is true. * * Wait slottime * 10 milliseconds. * Generate an 8 bit random number in range of 0 - 255. * If random number <= persist value, return. * Otherwise repeat. * * Example: * * For typical values of slottime=10 and persist=63, * * Delay Probability * ----- ----------- * 100 .25 = 25% * 200 .75 * .25 = 19% * 300 .75 * .75 * .25 = 14% * 400 .75 * .75 * .75 * .25 = 11% * 500 .75 * .75 * .75 * .75 * .25 = 8% * 600 .75 * .75 * .75 * .75 * .75 * .25 = 6% * 700 .75 * .75 * .75 * .75 * .75 * .75 * .25 = 4% * etc. ... * *--------------------------------------------------------------------*/ /* Give up if we can't get a clear channel in a minute. */ /* That's a long time to wait for APRS. */ /* Might need to revisit some day for connected mode file transfers. */ #define WAIT_TIMEOUT_MS (60 * 1000) #define WAIT_CHECK_EVERY_MS 10 static int wait_for_clear_channel (int channel, int nowait, int slottime, int persist) { int r; int n; n = 0; start_over_again: while (hdlc_rec_data_detect_any(channel)) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { return 0; } } //TODO1.2: rethink dwait. /* * Added in version 1.2 - for transceivers that can't * turn around fast enough when using squelch and VOX. */ if (save_audio_config_p->achan[channel].dwait > 0) { SLEEP_MS (save_audio_config_p->achan[channel].dwait * 10); } if (hdlc_rec_data_detect_any(channel)) { goto start_over_again; } if ( ! nowait) { while (1) { SLEEP_MS (slottime * 10); if (hdlc_rec_data_detect_any(channel)) { goto start_over_again; } r = rand() & 0xff; if (r <= persist) { break; } } } // TODO1.2 while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(channel)]))) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { return 0; } } return 1; } /* end wait_for_clear_channel */ /* end xmit.c */