PTT was being turned off too soon when sending Morse code.

Add capability to send DTMF tones.
-qd option now suppresses complaints about telemetry packets.
Handle more than 150 destinations in tocalls.txt.
This commit is contained in:
WB2OSZ 2016-12-16 20:12:38 -05:00
parent 7a88785fad
commit 200f669bbc
7 changed files with 312 additions and 135 deletions

View File

@ -325,7 +325,7 @@ log2gpx : log2gpx.c textcolor.o misc.a
# Test application to generate sound. # Test application to generate sound.
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c misc.a gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Unit test for AFSK demodulator # Unit test for AFSK demodulator
@ -588,7 +588,7 @@ install-rpi : dw-start.sh
# Combine some unit tests into a single regression sanity check. # Combine some unit tests into a single regression sanity check.
check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800
# Can we encode and decode at popular data rates? # Can we encode and decode at popular data rates?
@ -632,8 +632,8 @@ check-modem4800 : gen_packets atest
# Unit test for inner digipeater algorithm # Unit test for inner digipeater algorithm
.PHONY : dtest .PHONY : dtest
dtest : digipeater.c dedupe.c \ dtest : digipeater.c dedupe.c pfilter.c \
pfilter.o ax25_pad.o fcs_calc.o tq.o textcolor.o \ ax25_pad.o fcs_calc.o tq.o textcolor.o \
decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS)
./dtest ./dtest
@ -717,6 +717,15 @@ xidtest : xid.c textcolor.o misc.a
rm xidtest rm xidtest
# Unit Test for DTMF encode/decode.
.PHONY: dtmftest
dtmftest : dtmf.c textcolor.o
$(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS)
./dtmftest
rm dtmftest
# ----------------------------- Manual tests and experiments --------------------------- # ----------------------------- Manual tests and experiments ---------------------------

View File

@ -437,7 +437,7 @@ log2gpx : log2gpx.c
# Test application to generate sound. # Test application to generate sound.
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c textcolor.c dsp.c gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm
demod.o : tune.h demod.o : tune.h

View File

@ -111,7 +111,7 @@ static void aprs_station_capabilities (decode_aprs_t *A, char *, int);
static void aprs_status_report (decode_aprs_t *A, char *, int); static void aprs_status_report (decode_aprs_t *A, char *, int);
static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet);
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet); static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet);
static void aprs_telemetry (decode_aprs_t *A, char *, int, int quiet); static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet);
static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
static void aprs_morse_code (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int);
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int); static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
@ -3076,7 +3076,7 @@ double get_latitude_8 (char *p, int quiet)
return (G_UNKNOWN); return (G_UNKNOWN);
} }
if (plat->minn[0] >= '0' || plat->minn[0] <= '5') if (plat->minn[0] >= '0' && plat->minn[0] <= '5')
result += ((plat->minn[0]) - '0') * (10. / 60.); result += ((plat->minn[0]) - '0') * (10. / 60.);
else if (plat->minn[0] == ' ') else if (plat->minn[0] == ' ')
; ;
@ -3239,7 +3239,7 @@ double get_longitude_9 (char *p, int quiet)
return (G_UNKNOWN); return (G_UNKNOWN);
} }
if (plon->minn[0] >= '0' || plon->minn[0] <= '5') if (plon->minn[0] >= '0' && plon->minn[0] <= '5')
result += ((plon->minn[0]) - '0') * (10. / 60.); result += ((plon->minn[0]) - '0') * (10. / 60.);
else if (plon->minn[0] == ' ') else if (plon->minn[0] == ' ')
; ;
@ -3683,7 +3683,15 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext)
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#define MAX_TOCALLS 150 // If I was more ambitious, this would dynamically allocate enough
// storage based on the file contents. Just stick in a constant for
// now. This takes an insignificant amount of space and
// I don't anticipate tocalls.txt growing that quickly.
// Version 1.4 - add message if too small instead of silently ignoring the rest.
// Dec. 2016 tocalls.txt has 153 destination addresses.
#define MAX_TOCALLS 200
static struct tocalls_s { static struct tocalls_s {
unsigned char len; unsigned char len;
@ -3780,7 +3788,7 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
if (strlen(tocalls[num_tocalls].prefix) > 2) { if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++; num_tocalls++;
} }
@ -3804,11 +3812,15 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
if (strlen(tocalls[num_tocalls].prefix) > 2) { if (strlen(tocalls[num_tocalls].prefix) > 2) {
tocalls[num_tocalls].description = strdup(stuff+14); tocalls[num_tocalls].description = strdup(stuff+14);
tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
// dw_printf("debug: %d '%s' -> '%s'\n", tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
num_tocalls++; num_tocalls++;
} }
} }
if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some.
text_color_set(DW_COLOR_ERROR);
dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS);
}
} }
fclose(fp); fclose(fp);
@ -3832,9 +3844,12 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
dw_printf("System types in the destination field will not be decoded.\n"); dw_printf("System types in the destination field will not be decoded.\n");
} }
} }
first_time = 0; first_time = 0;
//for (n=0; n<num_tocalls; n++) {
// dw_printf("sorted %d: %d '%s' -> '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description);
//}
} }

View File

@ -107,6 +107,7 @@
#include "aprs_tt.h" #include "aprs_tt.h"
#include "tt_user.h" #include "tt_user.h"
#include "igate.h" #include "igate.h"
#include "pfilter.h"
#include "symbols.h" #include "symbols.h"
#include "dwgps.h" #include "dwgps.h"
#include "waypoint.h" #include "waypoint.h"
@ -164,6 +165,8 @@ static struct tt_config_s tt_config;
//struct digi_config_s digi_config; //struct digi_config_s digi_config;
//struct cdigi_config_s cdigi_config; //struct cdigi_config_s cdigi_config;
static const int audio_amplitude = 100; /* % of audio sample range. */
/* This translates to +-32k for 16 bit samples. */
static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in hexadecimal. */
static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */
@ -254,7 +257,7 @@ int main (int argc, char *argv[])
text_color_init(t_opt); text_color_init(t_opt);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "D", __DATE__); dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "E", __DATE__);
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) #if defined(ENABLE_GPSD) || defined(USE_HAMLIB)
@ -693,7 +696,7 @@ int main (int argc, char *argv[])
/* /*
* Initialize the touch tone decoder & APRStt gateway. * Initialize the touch tone decoder & APRStt gateway.
*/ */
dtmf_init (&audio_config); dtmf_init (&audio_config, audio_amplitude);
aprs_tt_init (&tt_config); aprs_tt_init (&tt_config);
tt_user_init (&audio_config, &tt_config); tt_user_init (&audio_config, &tt_config);
@ -702,8 +705,8 @@ int main (int argc, char *argv[])
* Note: This is not the same as a volume control you would see on the screen. * Note: This is not the same as a volume control you would see on the screen.
* It is the range of the digital sound representation. * It is the range of the digital sound representation.
*/ */
gen_tone_init (&audio_config, 100, 0); gen_tone_init (&audio_config, audio_amplitude, 0);
morse_init (&audio_config, 100); morse_init (&audio_config, audio_amplitude);
assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16); assert (audio_config.adev[0].bits_per_sample == 8 || audio_config.adev[0].bits_per_sample == 16);
assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2); assert (audio_config.adev[0].num_channels == 1 || audio_config.adev[0].num_channels == 2);
@ -746,6 +749,7 @@ int main (int argc, char *argv[])
digipeater_init (&audio_config, &digi_config); digipeater_init (&audio_config, &digi_config);
igate_init (&audio_config, &igate_config, &digi_config, d_i_opt); igate_init (&audio_config, &igate_config, &digi_config, d_i_opt);
cdigipeater_init (&audio_config, &cdigi_config); cdigipeater_init (&audio_config, &cdigi_config);
//FIXME//pfilter_init (&igate_config, 0);
ax25_link_init (&misc_config); ax25_link_init (&misc_config);
/* /*
@ -1015,16 +1019,21 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
} }
/* Decode the contents of APRS frames and display in human-readable form. */ /*
/* Suppress decoding if "-q d" option used. */ * Decode the contents of UI frames and display in human-readable form.
* Could be APRS or anything random for old fashioned packet beacons.
*
* Suppress printed decoding if "-q d" option used.
*/
if (ax25_is_aprs(pp)) { if (ax25_is_aprs(pp)) {
decode_aprs_t A; decode_aprs_t A;
decode_aprs (&A, pp, 0); // we still want to decode it for logging and other processing.
// Just be quiet about errors if "-qd" is set.
decode_aprs (&A, pp, q_d_opt);
if ( ! q_d_opt ) { if ( ! q_d_opt ) {
@ -1046,9 +1055,10 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
// temp experiment. // temp experiment.
//log_rr_bits (&A, pp); //log_rr_bits (&A, pp);
// Add to list of stations heard. // Add to list of stations heard over the radio.
mheard_save (chan, &A, pp, alevel, retries); mheard_save (chan, &A, pp, alevel, retries);
//FIXME//mheard_save_rf (chan, &A, pp, alevel, retries);
// Convert to NMEA waypoint sentence if we have a location. // Convert to NMEA waypoint sentence if we have a location.

354
dtmf.c
View File

@ -5,7 +5,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2013, 2014, 2015 John Langner, WB2OSZ // Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -40,9 +40,12 @@
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include <string.h>
#include "dtmf.h" #include "dtmf.h"
#include "hdlc_rec.h" // for dcd_change #include "hdlc_rec.h" // for dcd_change
#include "textcolor.h"
#include "gen_tone.h"
@ -78,8 +81,11 @@ static struct dd_s { /* Separate for each audio channel. */
} dd[MAX_CHANS]; } dd[MAX_CHANS];
static int s_amplitude = 100; // range of 0 .. 100
static void push_button (int chan, char button, int ms);
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -99,17 +105,23 @@ static struct dd_s { /* Separate for each audio channel. */
* In version 1.2, we can have multiple soundcards * In version 1.2, we can have multiple soundcards
* with potentially different sample rates. * with potentially different sample rates.
* *
* amp - Signal amplitude, for transmit, on scale of 0 .. 100.
*
* 100 will produce maximum amplitude of +-32k samples.
*
* Returns: None. * Returns: None.
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
void dtmf_init (struct audio_s *p_audio_config) void dtmf_init (struct audio_s *p_audio_config, int amp)
{ {
int j; /* Loop over all tones frequencies. */ int j; /* Loop over all tones frequencies. */
int c; /* Loop over all audio channels. */ int c; /* Loop over all audio channels. */
s_amplitude = amp;
/* /*
* Pick a suitable processing block size. * Pick a suitable processing block size.
* Larger = narrower bandwidth, slower response. * Larger = narrower bandwidth, slower response.
@ -118,15 +130,16 @@ void dtmf_init (struct audio_s *p_audio_config)
for (c=0; c<MAX_CHANS; c++) { for (c=0; c<MAX_CHANS; c++) {
struct dd_s *D = &(dd[c]); struct dd_s *D = &(dd[c]);
int a = ACHAN2ADEV(c); int a = ACHAN2ADEV(c);
D->sample_rate = p_audio_config->adev[a].samples_per_sec;
if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) { if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) {
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("channel %d:\n", c); dw_printf ("channel %d:\n", c);
#endif #endif
D->sample_rate = p_audio_config->adev[a].samples_per_sec;
D->block_size = (205 * D->sample_rate) / 8000; D->block_size = (205 * D->sample_rate) / 8000;
#if DEBUG #if DEBUG
dw_printf (" freq k coef \n"); dw_printf (" freq k coef \n");
@ -142,9 +155,9 @@ void dtmf_init (struct audio_s *p_audio_config)
k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate); k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate);
D->coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D->block_size)); D->coef[j] = 2.0f * cosf(2.0f * (float)M_PI * (float)k / (float)(D->block_size));
assert (D->coef[j] > 0 && D->coef[j] < 2.0); assert (D->coef[j] > 0.0f && D->coef[j] < 2.0f);
#if DEBUG #if DEBUG
dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]); dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]);
#endif #endif
@ -240,7 +253,7 @@ char dtmf_sample (int c, float input)
*/ */
#define THRESHOLD 1.74 #define THRESHOLD 1.74f
if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0; if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0;
else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1; else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1;
@ -273,7 +286,9 @@ char dtmf_sample (int c, float input)
// Update Data Carrier Detect Indicator. // Update Data Carrier Detect Indicator.
#ifndef DTMF_TEST
dcd_change (c, MAX_SUBCHANS, 0, decoded != ' '); dcd_change (c, MAX_SUBCHANS, 0, decoded != ' ');
#endif
/* Reset timeout timer. */ /* Reset timeout timer. */
if (decoded != ' ') { if (decoded != ' ') {
@ -312,6 +327,182 @@ char dtmf_sample (int c, float input)
} }
/*-------------------------------------------------------------------
*
* Name: dtmf_send
*
* Purpose: Generate DTMF tones from text string.
*
* Inputs: chan - Radio channel number.
* str - Character string to send. 0-9, A-D, *, #
* speed - Number of tones per second. Range 1 to 10.
* txdelay - Delay (ms) from PTT to start.
* txtail - Delay (ms) from end to PTT off.
*
* Returns: Total number of milliseconds to activate PTT.
* This includes delays before the first tone
* and after the last to avoid chopping off part of it.
*
* Description: xmit_thread calls this instead of the usual hdlc_send
* when we have a special packet that means send DTMF.
*
*--------------------------------------------------------------------*/
int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail)
{
char *p;
int len_ms; // Length of tone or gap between.
len_ms = (int) ( ( 500.0f / (float)speed ) + 0.5f);
push_button (chan, ' ', txdelay);
for (p = str; *p != '\0'; p++) {
push_button (chan, *p, len_ms);
push_button (chan, ' ', len_ms);
}
push_button (chan, ' ', txtail);
#ifndef DTMF_TEST
audio_flush(ACHAN2ADEV(chan));
#endif
return (txdelay +
(int) (1000.0f * (float)strlen(str) / (float)speed + 0.5f) +
txtail);
} /* end dtmf_send */
/*------------------------------------------------------------------
*
* Name: push_button
*
* Purpose: Generate DTMF tone for a button push.
*
* Inputs: chan - Radio channel number.
*
* button - One of 0-9, A-D, *, #. Others result in silence.
* '?' is a special case used only for unit testing.
*
* ms - Duration in milliseconds.
* Use 50 ms for tone and 50 ms of silence for max rate of 10 per second.
*
* Outputs: Audio is sent to radio.
*
*----------------------------------------------------------------*/
static void push_button (int chan, char button, int ms)
{
float phasea = 0;
float phaseb = 0;
float fa = 0;
float fb = 0;
int i;
float dtmf; // Audio. Sum of two sine waves.
#if DTMF_TEST
char x;
static char result[100];
static int result_len = 0;
#endif
switch (button) {
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
case 'a':
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
case 'b':
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
case 'c':
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
case 'd':
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
#if DTMF_TEST
case '?': /* check result */
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
text_color_set(DW_COLOR_REC);
dw_printf ("\nSuccess!\n");
}
else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n * Time-out failed, otherwise OK *\n");
dw_printf ("\"%s\"\n", result);
exit (EXIT_FAILURE);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n *** TEST FAILED ***\n");
dw_printf ("\"%s\"\n", result);
exit (EXIT_FAILURE);
}
break;
#endif
}
//dw_printf ("push_button (%d, '%c', %d), fa=%.0f, fb=%.0f. %d samples\n", chan, button, ms, fa, fb, (ms*dd[chan].sample_rate)/1000);
for (i = 0; i < (ms*dd[chan].sample_rate)/1000; i++) {
// This could be more efficient with a precomputed sine wave table
// but I'm not that worried about it.
// With a Raspberry Pi, model 2, default 1200 receiving takes about 14% of one CPU core.
// When transmitting tones, it briefly shoots up to about 33%.
if (fa > 0 && fb > 0) {
dtmf = sinf(phasea) + sinf(phaseb);
phasea += 2.0f * (float)M_PI * fa / dd[chan].sample_rate;
phaseb += 2.0f * (float)M_PI * fb / dd[chan].sample_rate;
}
else {
dtmf = 0;
}
#if DTMF_TEST
/* Make sure it is insensitive to signal amplitude. */
/* (Uncomment each of below when testing.) */
x = dtmf_sample (0, dtmf);
//x = dtmf_sample (0, dtmf * 1000);
//x = dtmf_sample (0, dtmf * 0.001);
if (x != ' ' && x != '.') {
result[result_len] = x;
result_len++;
result[result_len] = '\0';
}
#else
// 'dtmf' can be in range of +-2.0 because it is sum of two sine waves.
// Amplitude of 100 would use full +-32k range.
int sam = (int)(dtmf * 16383.0f * (float)s_amplitude / 100.0f);
gen_tone_put_sample (chan, ACHAN2ADEV(chan), sam);
#endif
}
}
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Name: main * Name: main
@ -319,142 +510,79 @@ char dtmf_sample (int c, float input)
* Purpose: Unit test for functions above. * Purpose: Unit test for functions above.
* *
* Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe * Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe
* or
* make dtmftest
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/
#if DTMF_TEST #if DTMF_TEST
push_button (char button, int ms)
{
static float phasea = 0;
static float phaseb = 0;
float fa, fb;
int i;
float input;
char x;
static char result[100];
static int result_len = 0;
int c = 0; // fake channel number.
switch (button) {
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
case '?':
// TODO: why are timeouts failing. Do we care?
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
dw_printf ("\nSuccess!\n");
}
else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
dw_printf ("\n * Time-out failed, otherwise OK *\n");
dw_printf ("\"%s\"\n", result);
}
else {
dw_printf ("\n *** TEST FAILED ***\n");
dw_printf ("\"%s\"\n", result);
}
break;
default: fa = 0; fb = 0;
}
for (i = 0; i < (ms*dd[c].sample_rate)/1000; i++) {
input = sin(phasea) + sin(phaseb);
phasea += 2 * M_PI * fa / dd[c].sample_rate;
phaseb += 2 * M_PI * fb / dd[c].sample_rate;
/* Make sure it is insensitive to signal amplitude. */
x = dtmf_sample (0, input);
//x = dtmf_sample (0, input * 1000);
//x = dtmf_sample (0, input * 0.001);
if (x != ' ' && x != '.') {
result[result_len] = x;
result_len++;
result[result_len] = '\0';
}
}
}
static struct audio_s my_audio_config; static struct audio_s my_audio_config;
main () int main ()
{ {
int c = 0; // radio channel.
memset (&my_audio_config, 0, sizeof(my_audio_config)); memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.adev[0].defined = 1; my_audio_config.adev[ACHAN2ADEV(c)].defined = 1;
my_audio_config.adev[0].samples_per_sec = 44100; my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100;
my_audio_config.achan[0].valid = 1; my_audio_config.achan[c].valid = 1;
my_audio_config.achan[0].dtmf_decode = DTMF_DECODE_ON; my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON;
dtmf_init(&my_audio_config); dtmf_init(&my_audio_config, 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nFirst, check all button tone pairs. \n\n"); dw_printf ("\nFirst, check all button tone pairs. \n\n");
/* Max auto dialing rate is 10 per second. */ /* Max auto dialing rate is 10 per second. */
push_button ('1', 50); push_button (' ', 50); push_button (c, '1', 50); push_button (c, ' ', 50);
push_button ('2', 50); push_button (' ', 50); push_button (c, '2', 50); push_button (c, ' ', 50);
push_button ('3', 50); push_button (' ', 50); push_button (c, '3', 50); push_button (c, ' ', 50);
push_button ('A', 50); push_button (' ', 50); push_button (c, 'A', 50); push_button (c, ' ', 50);
push_button ('4', 50); push_button (' ', 50); push_button (c, '4', 50); push_button (c, ' ', 50);
push_button ('5', 50); push_button (' ', 50); push_button (c, '5', 50); push_button (c, ' ', 50);
push_button ('6', 50); push_button (' ', 50); push_button (c, '6', 50); push_button (c, ' ', 50);
push_button ('B', 50); push_button (' ', 50); push_button (c, 'B', 50); push_button (c, ' ', 50);
push_button ('7', 50); push_button (' ', 50); push_button (c, '7', 50); push_button (c, ' ', 50);
push_button ('8', 50); push_button (' ', 50); push_button (c, '8', 50); push_button (c, ' ', 50);
push_button ('9', 50); push_button (' ', 50); push_button (c, '9', 50); push_button (c, ' ', 50);
push_button ('C', 50); push_button (' ', 50); push_button (c, 'C', 50); push_button (c, ' ', 50);
push_button ('*', 50); push_button (' ', 50); push_button (c, '*', 50); push_button (c, ' ', 50);
push_button ('0', 50); push_button (' ', 50); push_button (c, '0', 50); push_button (c, ' ', 50);
push_button ('#', 50); push_button (' ', 50); push_button (c, '#', 50); push_button (c, ' ', 50);
push_button ('D', 50); push_button (' ', 50); push_button (c, 'D', 50); push_button (c, ' ', 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nShould reject very short pulses.\n\n"); dw_printf ("\nShould reject very short pulses.\n\n");
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
push_button ('1', 20); push_button (' ', 50); push_button (c, '1', 20); push_button (c, ' ', 50);
text_color_set(DW_COLOR_INFO);
dw_printf ("\nTest timeout after inactivity.\n\n"); dw_printf ("\nTest timeout after inactivity.\n\n");
/* For this test we use 1 second. */ /* For this test we use 1 second. */
/* In practice, it will probably more like 5. */ /* In practice, it will probably more like 5. */
push_button ('1', 250); push_button (' ', 500); push_button (c, '1', 250); push_button (c, ' ', 500);
push_button ('2', 250); push_button (' ', 500); push_button (c, '2', 250); push_button (c, ' ', 500);
push_button ('3', 250); push_button (' ', 1200); push_button (c, '3', 250); push_button (c, ' ', 1200);
push_button ('7', 250); push_button (' ', 500); push_button (c, '7', 250); push_button (c, ' ', 500);
push_button ('8', 250); push_button (' ', 500); push_button (c, '8', 250); push_button (c, ' ', 500);
push_button ('9', 250); push_button (' ', 1200); push_button (c, '9', 250); push_button (c, ' ', 1200);
/* Check for expected results. */ /* Check for expected results. */
push_button ('?', 0); push_button (c, '?', 0);
exit (EXIT_SUCCESS);
} /* end main */ } /* end main */

4
dtmf.h
View File

@ -3,10 +3,12 @@
#include "audio.h" #include "audio.h"
void dtmf_init (struct audio_s *p_audio_config); void dtmf_init (struct audio_s *p_audio_config, int amp);
char dtmf_sample (int c, float input); char dtmf_sample (int c, float input);
int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail);
/* end dtmf.h */ /* end dtmf.h */

View File

@ -74,6 +74,7 @@
#include "gen_tone.h" #include "gen_tone.h"
#include "textcolor.h" #include "textcolor.h"
#include "morse.h" #include "morse.h"
#include "dtmf.h"
/* Own random number generator so we can get */ /* Own random number generator so we can get */
@ -108,6 +109,8 @@ static void send_packet (char *str)
if (g_morse_wpm > 0) { if (g_morse_wpm > 0) {
// TODO: Why not use the destination field instead of command line option?
morse_send (0, str, g_morse_wpm, 100, 100); morse_send (0, str, g_morse_wpm, 100, 100);
} }
else { else {
@ -404,6 +407,7 @@ int main(int argc, char **argv)
case 'M': /* -M for morse code speed */ case 'M': /* -M for morse code speed */
//TODO: document this. //TODO: document this.
// Why not base it on the destination field instead?
g_morse_wpm = atoi(optarg); g_morse_wpm = atoi(optarg);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
@ -465,6 +469,7 @@ int main(int argc, char **argv)
gen_tone_init (&modem, amplitude/2, 1); gen_tone_init (&modem, amplitude/2, 1);
morse_init (&modem, amplitude/2); morse_init (&modem, amplitude/2);
dtmf_init (&modem, amplitude/2);
assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16); assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16);
@ -956,3 +961,11 @@ static int audio_file_close (void)
} /* end audio_close */ } /* end audio_close */
// To keep dtmf.c happy.
#include "hdlc_rec.h" // for dcd_change
void dcd_change (int chan, int subchan, int slice, int state)
{
}