mirror of https://github.com/wb2osz/direwolf.git
1507 lines
40 KiB
C
1507 lines
40 KiB
C
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
|
|
#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"
|
|
#include "server.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 persistence 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; j<MAX_CHANS; j++) {
|
|
xmit_bits_per_sec[j] = p_modem->achan[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; j<MAX_CHANS; j++) {
|
|
|
|
if (p_modem->chan_medium[j] == 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 */
|
|
// I don't know if this in some official specification
|
|
// somewhere, but it is generally agreed that APRS digipeaters
|
|
// should send only one frame at a time rather than
|
|
// bunding multiple frames into a single transmission.
|
|
// Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html
|
|
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 = layer2_preamble_postamble (chan, pre_flags, 0, save_audio_config_p);
|
|
#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 = layer2_preamble_postamble (chan, post_flags, 1, save_audio_config_p);
|
|
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)
|
|
{
|
|
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);
|
|
#if 0 // FIXME - enable this?
|
|
dw_printf ("[%d%c%s%s] ", c,
|
|
p==TQ_PRIO_0_HI ? 'H' : 'L',
|
|
save_audio_config_p->achan[c].fx25_strength ? "F" : "",
|
|
ts);
|
|
#else
|
|
dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts);
|
|
#endif
|
|
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.
|
|
*/
|
|
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 = layer2_send_frame (c, pp, send_invalid_fcs2, save_audio_config_p);
|
|
|
|
// Optionally send confirmation to AGW client app if monitoring enabled.
|
|
|
|
server_send_monitored (c, pp, 1);
|
|
|
|
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 frequencies. 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 */
|
|
|
|
|
|
|