// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 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 #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" #include "dtmf.h" #include "xid.h" #include "dlq.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_fulldup[MAX_CHANS]; /* Full duplex if non-zero. */ static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */ /* Often called baud rate which is equivalent for */ /* 1200 & 9600 cases but could be different with other */ /* modulation techniques. */ static int g_debug_xmit_packet; /* print packet in hexadecimal form for debugging. */ // TODO: When this was first written, bits/sec was same as baud. // Need to revisit this for PSK modes where they are not the same. #if 0 // Added during 1.5 beta test static int BITS_TO_MS (int b, int ch) { int bits_per_symbol; switch (save_audio_config_p->achan[ch].modem_type) { case MODEM_QPSK: bits_per_symbol = 2; break; case MODEM_8PSK: bits_per_symbol = 3; break; case default: bits_per_symbol = 1; break; } return ( (b * 1000) / (xmit_bits_per_sec[(ch)] * bits_per_symbol) ); } static int MS_TO_BITS (int ms, int ch) { int bits_per_symbol; switch (save_audio_config_p->achan[ch].modem_type) { case MODEM_QPSK: bits_per_symbol = 2; break; case MODEM_8PSK: bits_per_symbol = 3; break; case default: bits_per_symbol = 1; break; } return ( (ms * xmit_bits_per_sec[(ch)] * bits_per_symbol) / 1000 ); TODO... } #else // OK for 1200, 9600 but wrong for PSK #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) #endif #define MAXX(a,b) (((a)>(b)) ? (a) : (b)) #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 slotttime, int persist, int fulldup); static void xmit_ax25_frames (int c, int p, packet_t pp, int max_bundle); static int send_one_frame (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); static void xmit_dtmf (int c, packet_t pp, int speed); /*------------------------------------------------------------------- * * 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; xmit_fulldup[j] = p_modem->achan[j].fulldup; } #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].medium == MEDIUM_RADIO) { #if __WIN32__ xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(ptrdiff_t)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 *)(ptrdiff_t)j); pthread_attr_destroy (&attr); #else e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(ptrdiff_t)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 * xmit_set_fulldup * * * 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; } } void xmit_set_fulldup (int channel, int value) { if (channel >= 0 && channel < MAX_CHANS) { xmit_fulldup[channel] = value; } } /*------------------------------------------------------------------- * * Name: frame_flavor * * Purpose: Separate frames into different flavors so we can decide * which can be bundled into a single transmission and which should * be sent separately. * * Inputs: pp - Packet object. * * Returns: Flavor, one of: * * FLAVOR_SPEECH - Destination address is SPEECH. * FLAVOR_MORSE - Destination address is MORSE. * FLAVOR_DTMF - Destination address is DTMF. * FLAVOR_APRS_NEW - APRS original, i.e. not digipeating. * FLAVOR_APRS_DIGI - APRS digipeating. * FLAVOR_OTHER - Anything left over, i.e. connected mode. * *--------------------------------------------------------------------*/ typedef enum flavor_e { FLAVOR_APRS_NEW, FLAVOR_APRS_DIGI, FLAVOR_SPEECH, FLAVOR_MORSE, FLAVOR_DTMF, FLAVOR_OTHER } flavor_t; static flavor_t frame_flavor (packet_t pp) { if (ax25_is_aprs (pp)) { // UI frame, PID 0xF0. // It's unfortunate APRS did not use its own special PID. char dest[AX25_MAX_ADDR_LEN]; ax25_get_addr_no_ssid(pp, AX25_DESTINATION, dest); if (strcmp(dest, "SPEECH") == 0) { return (FLAVOR_SPEECH); } if (strcmp(dest, "MORSE") == 0) { return (FLAVOR_MORSE); } if (strcmp(dest, "DTMF") == 0) { return (FLAVOR_DTMF); } /* Is there at least one digipeater AND has first one been used? */ /* I could be the first in the list or later. Doesn't matter. */ if (ax25_get_num_repeaters(pp) >= 1 && ax25_get_h(pp,AX25_REPEATER_1)) { return (FLAVOR_APRS_DIGI); } return (FLAVOR_APRS_NEW); } return (FLAVOR_OTHER); } /* end frame_flavor */ /*------------------------------------------------------------------- * * 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". (?) * * AX.25 connected mode also has a couple cases * where "expedited" frames are sent. * * 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. * * Version 1.4: Rearranged logic for bundling multiple frames into a single transmission. * * The rule is that Speech, Morse Code, DTMF, and APRS digipeated frames * are all sent separately. The rest can be bundled. * *--------------------------------------------------------------------*/ #if __WIN32__ static unsigned __stdcall xmit_thread (void *arg) #else static void * xmit_thread (void *arg) #endif { int chan = (int)(ptrdiff_t)arg; // channel number. packet_t pp; int prio; int ok; while (1) { tq_wait_while_empty (chan); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread, channel %d: woke up\n", chan); #endif // Does this extra loop offer any benefit? while (tq_peek(chan, TQ_PRIO_0_HI) != NULL || tq_peek(chan, TQ_PRIO_1_LO) != NULL) { /* * Wait for the channel to be clear. * If there is something in the high priority queue, begin transmitting immediately. * Otherwise, wait a random amount of time, in hopes of minimizing collisions. */ ok = wait_for_clear_channel (chan, xmit_slottime[chan], xmit_persist[chan], xmit_fulldup[chan]); prio = TQ_PRIO_1_LO; pp = tq_remove (chan, TQ_PRIO_0_HI); if (pp != NULL) { prio = TQ_PRIO_0_HI; } else { pp = tq_remove (chan, TQ_PRIO_1_LO); } #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp); #endif // Shouldn't have NULL here but be careful. if (pp != NULL) { 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. * If destination is "DTMF" send as Touch Tones. */ int ssid, wpm, speed; switch (frame_flavor(pp)) { case FLAVOR_SPEECH: xmit_speech (chan, pp); break; case FLAVOR_MORSE: ssid = ax25_get_ssid(pp, AX25_DESTINATION); wpm = (ssid > 0) ? (ssid * 2) : 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 (prio == TQ_PRIO_0_HI) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("APRStt morse xmit delay hack...\n"); SLEEP_MS (700); } xmit_morse (chan, pp, wpm); break; case FLAVOR_DTMF: speed = ax25_get_ssid(pp, AX25_DESTINATION); if (speed == 0) speed = 5; // default half of maximum if (speed > 10) speed = 10; xmit_dtmf (chan, pp, speed); break; case FLAVOR_APRS_DIGI: xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */ break; case FLAVOR_APRS_NEW: case FLAVOR_OTHER: default: xmit_ax25_frames (chan, prio, pp, 256); break; } // Corresponding lock is in wait_for_clear_channel. dw_mutex_unlock (&(audio_out_dev_mutex[ACHAN2ADEV(chan)])); } 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] ", chan, (prio==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 error. */ } /* Have pp */ } /* while queue not empty */ } /* while 1 */ return 0; /* unreachable but quiet the warning. */ } /* end xmit_thread */ /*------------------------------------------------------------------- * * Name: xmit_ax25_frames * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit one or more frames. * * Inputs: chan - Channel number. * * prio - Priority of the first frame. * Subsequent frames could be different. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * max_bundle - Max number of frames to bundle into one transmission. * * Description: Turn on transmitter. * Send flags for TXDELAY time. * Send the first packet, given by pp. * Possibly send more packets from either queue. * Send flags for TXTAIL time. * Turn off transmitter. * * * How many frames in one transmission? (for APRS) * * Should we send multiple frames in one transmission if we * have more than one sitting in the queue? At first I was thinking * this would help reduce channel congestion. I don't recall seeing * anything in the APRS specifications allowing or disallowing multiple * frames in one transmission. I can think of some scenarios * where it might help. I can think of some where it would * definitely be counter productive. * * What to others have to say about this topic? * * "For what it is worth, the original APRSdos used a several second random * generator each time any kind of packet was generated... This is to avoid * bundling. Because bundling, though good for connected packet, is not good * on APRS. Sometimes the digi begins digipeating the first packet in the * bundle and steps all over the remainder of them. So best to make sure each * packet is isolated in time from others..." * * Bob, WB4APR * * * Version 0.9: Earlier versions always sent one frame per transmission. * This was fine for APRS but more and more people are now * using this as a KISS TNC for connected protocols. * Rather than having a configuration file item, * we try setting the maximum number automatically. * 1 for digipeated frames, 7 for others. * * Version 1.4: Lift the limit. We could theoretically have a window size up to 127. * If another section pumps out that many quickly we shouldn't * break it up here. Empty out both queues with some exceptions. * * Digipeated APRS, Speech, and Morse code should have * their own separate transmissions. * Everything else can be bundled together. * Different priorities can share a single transmission. * Once we have control of the channel, we might as well keep going. * [High] Priority frames will always go to head of the line, * * Version 1.5: Add full duplex option. * *--------------------------------------------------------------------*/ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) { 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 numframe = 0; /* 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; /* * Turn on transmitter. * Start sending leading flag bytes. */ time_ptt = dtime_now (); // TODO: This was written assuming bits/sec = baud. // Does it is need to be scaled differently for PSK? #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, Turn on PTT now for channel %d. speed = %d\n", dtime_now()-time_ptt, chan, xmit_bits_per_sec[chan]); #endif ptt_set (OCTYPE_PTT, chan, 1); // Inform data link state machine that we are now transmitting. dlq_seize_confirm (chan); // C4.2. "This primitive indicates, to the Data-link State // machine, that the transmission opportunity has arrived." pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8; num_bits = hdlc_send_flags (chan, pre_flags, 0); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", dtime_now()-time_ptt, xmit_txdelay[chan], pre_flags, num_bits); double presleep = dtime_now(); #endif SLEEP_MS (10); // Give data link state machine a chance to // to stuff more frames into the transmit queue, // in response to dlq_seize_confirm, so // we don't run off the end too soon. #if DEBUG text_color_set(DW_COLOR_DEBUG); // How long did sleep last? dw_printf ("xmit_thread: t=%.3f, Should be 0.010 second after the above.\n", dtime_now()-time_ptt); double naptime = dtime_now() - presleep; if (naptime > 0.015) { text_color_set(DW_COLOR_ERROR); dw_printf ("Sleep for 10 ms actually took %.3f second!\n", naptime); } #endif /* * Transmit the frame. */ nb = send_one_frame (chan, prio, pp); num_bits += nb; if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, nb=%d, num_bits=%d, numframe=%d\n", dtime_now()-time_ptt, nb, num_bits, numframe); #endif ax25_delete (pp); /* * See if we can bundle additional frames into this transmission. */ int done = 0; while (numframe < max_bundle && ! done) { /* * Peek at what is available. * Don't remove from queue yet because it might not be eligible. */ prio = TQ_PRIO_1_LO; pp = tq_peek (chan, TQ_PRIO_0_HI); if (pp != NULL) { prio = TQ_PRIO_0_HI; } else { pp = tq_peek (chan, TQ_PRIO_1_LO); } if (pp != NULL) { switch (frame_flavor(pp)) { case FLAVOR_SPEECH: case FLAVOR_MORSE: case FLAVOR_DTMF: case FLAVOR_APRS_DIGI: default: done = 1; // not eligible for bundling. break; case FLAVOR_APRS_NEW: case FLAVOR_OTHER: pp = tq_remove (chan, prio); #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, tq_remove(chan=%d, prio=%d) returned %p\n", dtime_now()-time_ptt, chan, prio, pp); #endif nb = send_one_frame (chan, prio, pp); num_bits += nb; if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, nb=%d, num_bits=%d, numframe=%d\n", dtime_now()-time_ptt, nb, num_bits, numframe); #endif ax25_delete (pp); break; } } else { done = 1; } } /* * Need TXTAIL because we don't know exactly when the sound is done. */ post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8; nb = hdlc_send_flags (chan, post_flags, 1); num_bits += nb; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("xmit_thread: t=%.3f, txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", dtime_now()-time_ptt, xmit_txtail[chan], post_flags, nb, num_bits); #endif /* * While demodulating is CPU intensive, generating the tones is not. * Example: on the RPi model 1, 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(chan)); /* * 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, chan); /* * 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: t=%.3f, xmit duration=%d, %d already elapsed since PTT, wait %d more\n", dtime_now()-time_ptt, 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: t=%.3f, Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", dtime_now()-time_ptt, (int) ((time_now - time_ptt) * 1000.), duration); #endif ptt_set (OCTYPE_PTT, chan, 0); } /* end xmit_ax25_frames */ /*------------------------------------------------------------------- * * Name: send_one_frame * * Purpose: Send one AX.25 frame. * * Inputs: c - Channel number. * * p - Priority. * * pp - Packet object pointer. Caller will delete it. * * Returns: Number of bits transmitted. * * Description: Caller is responsible for activiating PTT, TXDELAY, * deciding how many frames can be in one transmission, * deactivating PTT. * *--------------------------------------------------------------------*/ static int send_one_frame (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 nb; if (ax25_is_null_frame(pp)) { // Issue 132 - We could end up in a situation where: // Transmitter is already on. // Application wants to send a frame. // dl_seize_request turns into this null frame. // It was being ignored here so the data got stuck in the queue. // I think the solution is to send back a seize confirm here. // It shouldn't hurt if we send it redundantly. // Added for 1.5 beta test 4. dlq_seize_confirm (c); // C4.2. "This primitive indicates, to the Data-link State // machine, that the transmission opportunity has arrived." SLEEP_MS (10); // Give data link state machine a chance to // to stuff more frames into the transmit queue, // in response to dlq_seize_confirm, so // we don't run off the end too soon. return(0); } char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } ax25_format_addrs (pp, stemp); info_len = ax25_get_info (pp, &pinfo); text_color_set(DW_COLOR_XMIT); dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts); dw_printf ("%s", stemp); /* stations followed by : */ /* Demystify non-APRS. Use same format for received frames in direwolf.c. */ if ( ! ax25_is_aprs(pp)) { ax25_frame_type_t ftype; cmdres_t cr; char desc[80]; int pf; int nr; int ns; ftype = ax25_frame_type (pp, &cr, desc, &pf, &nr, &ns); dw_printf ("(%s)", desc); if (ftype == frame_type_U_XID) { struct xid_param_s param; char info2text[150]; xid_parse (pinfo, info_len, ¶m, info2text, sizeof(info2text)); dw_printf (" %s\n", info2text); } else { ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); dw_printf ("\n"); } } else { 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"); } /* * Transmit the frame. */ flen = ax25_pack (pp, fbuf); assert (flen >= 1 && flen <= (int)(sizeof(fbuf))); int send_invalid_fcs2 = 0; if (save_audio_config_p->xmit_error_rate != 0) { float r = (float)(rand()) / (float)RAND_MAX; // Random, 0.0 to 1.0 if (save_audio_config_p->xmit_error_rate / 100.0 > r) { send_invalid_fcs2 = 1; text_color_set(DW_COLOR_INFO); dw_printf ("Intentionally sending invalid CRC for frame above. Xmit Error rate = %d per cent.\n", save_audio_config_p->xmit_error_rate); } } nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2); return (nb); } /* end send_one_frame */ /*------------------------------------------------------------------- * * 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. */ char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.speech%s] \"%s\"\n", c, ts, 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 msg[2000]; char cmd[sizeof(msg) + 16]; char *p; /* 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. * A small amount of quiet padding will appear at start and end. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_morse (int c, packet_t pp, int wpm) { int info_len; unsigned char *pinfo; int length_ms, wait_ms; double start_ptt, wait_until, now; char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.morse%s] \"%s\"\n", c, ts, pinfo); ptt_set (OCTYPE_PTT, c, 1); start_ptt = dtime_now(); // make txdelay at least 300 and txtail at least 250 ms. length_ms = morse_send (c, (char*)pinfo, wpm, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); // there is probably still sound queued up in the output buffers. wait_until = start_ptt + length_ms * 0.001; now = dtime_now(); wait_ms = (int) ( ( wait_until - now ) * 1000 ); if (wait_ms > 0) { SLEEP_MS(wait_ms); } ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_morse */ /*------------------------------------------------------------------- * * Name: xmit_dtmf * * Purpose: After we have a clear channel, and possibly waited a random time, * we transmit information part of frame as DTMF tones. * * Inputs: c - Channel number. * * pp - Packet object pointer. * It will be deleted so caller should not try * to reference it after this. * * speed - Button presses per second. * * Description: Turn on transmitter. * Send text as touch tones. * A small amount of quiet padding will appear at start and end. * Turn off transmitter. * *--------------------------------------------------------------------*/ static void xmit_dtmf (int c, packet_t pp, int speed) { int info_len; unsigned char *pinfo; int length_ms, wait_ms; double start_ptt, wait_until, now; char ts[100]; // optional time stamp. if (strlen(save_audio_config_p->timestamp_format) > 0) { char tstmp[100]; timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); strlcpy (ts, " ", sizeof(ts)); // space after channel. strlcat (ts, tstmp, sizeof(ts)); } else { strlcpy (ts, "", sizeof(ts)); } info_len = ax25_get_info (pp, &pinfo); (void)info_len; text_color_set(DW_COLOR_XMIT); dw_printf ("[%d.dtmf%s] \"%s\"\n", c, ts, pinfo); ptt_set (OCTYPE_PTT, c, 1); start_ptt = dtime_now(); // make txdelay at least 300 and txtail at least 250 ms. length_ms = dtmf_send (c, (char*)pinfo, speed, MAXX(xmit_txdelay[c] * 10, 300), MAXX(xmit_txtail[c] * 10, 250)); // there is probably still sound queued up in the output buffers. wait_until = start_ptt + length_ms * 0.001; now = dtime_now(); wait_ms = (int) ( ( wait_until - now ) * 1000 ); if (wait_ms > 0) { SLEEP_MS(wait_ms); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Oops. CPU too slow to keep up with DTMF generation.\n"); } ptt_set (OCTYPE_PTT, c, 0); ax25_delete (pp); } /* end xmit_dtmf */ /*------------------------------------------------------------------- * * Name: wait_for_clear_channel * * Purpose: Wait for the radio channel to be clear and any * additional time for collision avoidance. * * Inputs: chan - Radio channel number. * * slottime - Amount of time to wait for each iteration * of the waiting algorithm. 10 mSec units. * * persist - Probability of transmitting. * * fulldup - Full duplex. Just start sending immediately. * * Returns: 1 for OK. 0 for timeout. * * Description: New in version 1.2: also obtain a lock on audio out device. * * New in version 1.5: full duplex. * Just start transmitting rather than waiting for clear channel. * This would only be appropriate when transmit and receive are * using different radio freqencies. e.g. VHF up, UHF down satellite. * * Transmit delay algorithm: * * Wait for channel to be clear. * If anything in high priority queue, bail out of the following. * * 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 chan, int slottime, int persist, int fulldup) { int n = 0; /* * For dull duplex we skip the channel busy check and random wait. * We still need to wait if operating in stereo and the other audio * half is busy. */ if ( ! fulldup) { start_over_again: while (hdlc_rec_data_detect_any(chan)) { SLEEP_MS(WAIT_CHECK_EVERY_MS); n++; if (n > (WAIT_TIMEOUT_MS / WAIT_CHECK_EVERY_MS)) { return 0; } } //TODO: 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[chan].dwait > 0) { SLEEP_MS (save_audio_config_p->achan[chan].dwait * 10); } if (hdlc_rec_data_detect_any(chan)) { goto start_over_again; } /* * Wait random time. * Proceed to transmit sooner if anything shows up in high priority queue. */ while (tq_peek(chan, TQ_PRIO_0_HI) == NULL) { int r; SLEEP_MS (slottime * 10); if (hdlc_rec_data_detect_any(chan)) { goto start_over_again; } r = rand() & 0xff; if (r <= persist) { break; } } } /* * This is to prevent two channels from transmitting at the same time * thru a stereo audio device. * We are not clever enough to combine two audio streams. * They must go out one at a time. * Documentation recommends using separate audio device for each channel rather than stereo. * That also allows better use of multiple cores for receiving. */ // TODO: review this. while ( ! dw_mutex_try_lock(&(audio_out_dev_mutex[ACHAN2ADEV(chan)]))) { 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 */