Implementation of the 0G Mobile Phone Network of US and Canada MTS or IMTS mode is selectable, als well as 5 or 7 digit mode.pull/1/head
parent
31fca59294
commit
99bafb6880
@ -0,0 +1,63 @@ |
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
imts \
|
||||
imts-dialer
|
||||
|
||||
imts_SOURCES = \
|
||||
imts.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
main.c
|
||||
imts_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../amps/libusatone.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libsquelch/libsquelch.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libmncc/libmncc.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
-lm
|
||||
|
||||
imts_dialer_SOURCES = \
|
||||
dialer.c
|
||||
|
||||
imts_dialer_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA |
||||
imts_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
|
||||
imts_dialer_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
endif |
||||
|
||||
if HAVE_SDR |
||||
imts_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif |
||||
|
||||
if HAVE_ALSA |
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif |
||||
|
@ -0,0 +1,354 @@ |
||||
/* IMTS dialer
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* 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 3 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/>.
|
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <math.h> |
||||
#include <errno.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libwave/wave.h" |
||||
#include "../libdebug/debug.h" |
||||
#ifdef HAVE_ALSA |
||||
#include "../libsound/sound.h" |
||||
#endif |
||||
#include "../liboptions/options.h" |
||||
|
||||
/* presets */ |
||||
const char *station_id = "6681739"; |
||||
const char *dialing; |
||||
const char *audiodev = "hw:0,0"; |
||||
int samplerate = 48000; |
||||
const char *write_tx_wave = NULL; |
||||
int latency = 50; |
||||
|
||||
#define TONE_DONE -1 |
||||
#define TONE_SILENCE 0 |
||||
#define TONE_GUARD 2150 |
||||
#define TONE_CONNECT 1633 |
||||
#define TONE_DISCONNECT 1336 |
||||
|
||||
/* states */ |
||||
static struct dial_string { |
||||
char console; |
||||
int tone; |
||||
int length; |
||||
} dial_string[2048]; |
||||
static int dial_pos = 0; |
||||
static int latspl; |
||||
|
||||
/* instances */ |
||||
#ifdef HAVE_ALSA |
||||
void *audio = NULL; |
||||
#endif |
||||
wave_rec_t wave_tx_rec; |
||||
|
||||
/* dummy functions */ |
||||
int num_kanal = 1; /* only one channel used for debugging */ |
||||
void display_status_limit_scroll() {} |
||||
void *get_sender_by_empfangsfrequenz() { return NULL; } |
||||
void display_measurements_add() {} |
||||
void display_measurements_update() {} |
||||
|
||||
static void print_help(const char *arg0) |
||||
{ |
||||
printf("Usage: %s [options] <number> | disconnect\n\n", arg0); |
||||
/* - - */ |
||||
printf("This program generates a dialing sequence to make a call via IMTS base\n"); |
||||
printf("station using an amateur radio transceiver.\n"); |
||||
printf("Also it can write an audio file (wave) to be fed into IMTS base station for\n"); |
||||
printf("showing how it simulates an IMTS phone doing an outgoing call.\n\n"); |
||||
printf("Use 'disconnect' to generate a disconnect sequence.\n\n"); |
||||
printf(" -h --help\n"); |
||||
printf(" This help\n"); |
||||
printf(" -i --station-id <station ID>\n"); |
||||
printf(" 7 Digits of ID of mobile station (default = '%s')\n", station_id); |
||||
#ifdef HAVE_ALSA |
||||
printf(" -a --audio-device hw:<card>,<device>\n"); |
||||
printf(" Sound card and device number (default = '%s')\n", audiodev); |
||||
#endif |
||||
printf(" -s --samplerate <rate>\n"); |
||||
printf(" Sample rate of sound device (default = '%d')\n", samplerate); |
||||
printf(" -w --write-tx-wave <file>\n"); |
||||
printf(" Write audio to given wave file also.\n"); |
||||
} |
||||
|
||||
static void add_options(void) |
||||
{ |
||||
option_add('h', "help", 0); |
||||
option_add('i', "station-id", 1); |
||||
option_add('a', "audio-device", 1); |
||||
option_add('s', "samplerate", 1); |
||||
option_add('w', "write-tx-wave", 1); |
||||
} |
||||
|
||||
static int handle_options(int short_option, int __attribute__((unused)) argi, char **argv) |
||||
{ |
||||
switch (short_option) { |
||||
case 'h': |
||||
print_help(argv[0]); |
||||
return 0; |
||||
case 'i': |
||||
station_id = strdup(argv[argi]); |
||||
break; |
||||
case 'a': |
||||
audiodev = strdup(argv[argi]); |
||||
break; |
||||
case 's': |
||||
samplerate = atoi(argv[argi]); |
||||
break; |
||||
case 'w': |
||||
write_tx_wave = strdup(argv[argi]); |
||||
break; |
||||
default: |
||||
return -EINVAL; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
static double phase = 0; |
||||
|
||||
/* encode audio */ |
||||
static void encode_audio(sample_t *samples, uint8_t *power, int length) |
||||
{ |
||||
int count; |
||||
int i; |
||||
|
||||
memset(power, 1, length); |
||||
|
||||
again: |
||||
count = length; |
||||
if (dial_string[dial_pos].length && dial_string[dial_pos].length < count) |
||||
count = dial_string[dial_pos].length; |
||||
|
||||
switch (dial_string[dial_pos].tone) { |
||||
case TONE_DONE: |
||||
case TONE_SILENCE: |
||||
memset(samples, 0, count * sizeof(*samples)); |
||||
phase = 0; |
||||
break; |
||||
default: |
||||
for (i = 0; i < count; i++) { |
||||
samples[i] = cos(2.0 * M_PI * (double)dial_string[dial_pos].tone * phase); |
||||
phase += 1.0 / samplerate; |
||||
} |
||||
} |
||||
|
||||
if (dial_string[dial_pos].length) { |
||||
dial_string[dial_pos].length -= count; |
||||
if (dial_string[dial_pos].length == 0) { |
||||
if (dial_string[dial_pos].console) { |
||||
printf("%c", dial_string[dial_pos].console); |
||||
fflush(stdout); |
||||
} |
||||
dial_pos++; |
||||
} |
||||
} |
||||
|
||||
samples += count; |
||||
length -= count; |
||||
|
||||
if (length) |
||||
goto again; |
||||
} |
||||
|
||||
/* loop that gets audio from encoder and fowards it to sound card.
|
||||
* alternatively a sound file is written. |
||||
*/ |
||||
static void process_signal(void) |
||||
{ |
||||
sample_t buff[latspl], *samples[1] = { buff }; |
||||
uint8_t pbuff[latspl], *power[1] = { pbuff }; |
||||
int count; |
||||
int __attribute__((unused)) rc; |
||||
|
||||
while (dial_string[dial_pos].tone != TONE_DONE) { |
||||
#ifdef HAVE_ALSA |
||||
count = sound_get_tosend(audio, latspl); |
||||
#else |
||||
count = samplerate / 1000; |
||||
#endif |
||||
if (count < 0) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count); |
||||
break; |
||||
} |
||||
|
||||
/* encode dial_string of tones and lengths */ |
||||
encode_audio(samples[0], power[0], count); |
||||
|
||||
/* write wave, if open */ |
||||
if (wave_tx_rec.fp) |
||||
wave_write(&wave_tx_rec, samples, count); |
||||
|
||||
#ifdef HAVE_ALSA |
||||
/* write audio */ |
||||
rc = sound_write(audio, samples, power, count, NULL, NULL, 1); |
||||
if (rc < 0) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc); |
||||
break; |
||||
} |
||||
#endif |
||||
|
||||
/* sleep a while */ |
||||
usleep(1000); |
||||
} |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int i, d, p, pulses, tone = 0; |
||||
int rc, argi; |
||||
|
||||
memset(dial_string, 0, sizeof(dial_string)); |
||||
|
||||
/* latency of send buffer in samples */ |
||||
latspl = samplerate * latency / 1000; |
||||
|
||||
/* handle options / config file */ |
||||
add_options(); |
||||
argi = options_command_line(argc, argv, handle_options); |
||||
if (argi <= 0) |
||||
return argi; |
||||
|
||||
if (argi >= argc) { |
||||
printf("No phone number given!\n\n"); |
||||
print_help(argv[0]); |
||||
goto exit; |
||||
} |
||||
|
||||
/* check for valid station ID */ |
||||
if (strlen(station_id) != 7) { |
||||
printf("Given station ID '%s' has invalid number of digits!\n", station_id); |
||||
goto exit; |
||||
} |
||||
for (i = 0; station_id[i]; i++) { |
||||
if (station_id[i] < '0' || station_id[i] > '9') { |
||||
printf("Given station ID '%s' has invalid digits!\n", station_id); |
||||
goto exit; |
||||
} |
||||
} |
||||
|
||||
dialing = argv[argi]; |
||||
d = 0; |
||||
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */ |
||||
if (!!strcasecmp(dialing, "disconnect")) { |
||||
/* check for valid phone number */ |
||||
if (strlen(dialing) > 64) { |
||||
printf("Given phone number '%s' has too many digits! (more than allowed 64 digits)\n", dialing); |
||||
goto exit; |
||||
} |
||||
for (i = 0; dialing[i]; i++) { |
||||
if (dialing[i] < '0' || dialing[i] > '9') { |
||||
printf("Given phone number '%s' has invalid digits!\n", dialing); |
||||
goto exit; |
||||
} |
||||
} |
||||
|
||||
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.350 * (double)samplerate; /* off-hook */ |
||||
dial_string[d].console = 's'; |
||||
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.050 * (double)samplerate; /* seize */ |
||||
dial_string[d].console = '-'; |
||||
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 1.000 * (double)samplerate; /* pause */ |
||||
for (i = 0; station_id[i]; i++) { |
||||
pulses = station_id[i] - '0'; |
||||
if (pulses == 0) |
||||
pulses = 10; |
||||
dial_string[d].console = station_id[i]; |
||||
for (p = 1; p <= pulses; p++) { |
||||
if ((p & 1) == 1) |
||||
tone = TONE_SILENCE; |
||||
else |
||||
tone = TONE_GUARD; |
||||
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */ |
||||
dial_string[d].tone = tone; dial_string[d++].length = 0.025 * (double)samplerate; /* space */ |
||||
} |
||||
dial_string[d].tone = tone; dial_string[d++].length = 0.190 * (double)samplerate; /* after digit */ |
||||
} |
||||
dial_string[d].console = '-'; |
||||
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 2.000 * (double)samplerate; /* pause */ |
||||
for (i = 0; dialing[i]; i++) { |
||||
pulses = dialing[i] - '0'; |
||||
if (pulses == 0) |
||||
pulses = 10; |
||||
dial_string[d].console = dialing[i]; |
||||
for (p = 1; p <= pulses; p++) { |
||||
dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.060 * (double)samplerate; /* mark */ |
||||
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.040 * (double)samplerate; /* space */ |
||||
} |
||||
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.400 * (double)samplerate; /* after digit */ |
||||
} |
||||
dial_string[d].console = '\n'; |
||||
} else { |
||||
for (i = 0; i < 750; i += 50) { |
||||
dial_string[d].tone = TONE_DISCONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */ |
||||
dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.025 * (double)samplerate; /* space */ |
||||
} |
||||
} |
||||
dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */ |
||||
dial_string[d].tone = TONE_DONE; dial_string[d++].length = 0; /* end */ |
||||
|
||||
#ifdef HAVE_ALSA |
||||
/* init sound */ |
||||
audio = sound_open(audiodev, NULL, NULL, 1, 0.0, samplerate, latspl, 1.0, 4000.0); |
||||
if (!audio) { |
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound device!\n"); |
||||
goto exit; |
||||
} |
||||
#endif |
||||
|
||||
/* open wave */ |
||||
if (write_tx_wave) { |
||||
rc = wave_create_record(&wave_tx_rec, write_tx_wave, samplerate, 1, 1.0); |
||||
if (rc < 0) { |
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n"); |
||||
goto exit; |
||||
} |
||||
} |
||||
#ifndef HAVE_ALSA |
||||
else { |
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "No sound support compiled in, so you need to write to a wave file. See help!\n"); |
||||
goto exit; |
||||
} |
||||
#endif |
||||
|
||||
#ifdef HAVE_ALSA |
||||
/* start sound */ |
||||
sound_start(audio); |
||||
#endif |
||||
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Start audio after pause...\n"); |
||||
|
||||
process_signal(); |
||||
|
||||
exit: |
||||
/* close wave */ |
||||
wave_destroy_record(&wave_tx_rec); |
||||
|
||||
#ifdef HAVE_ALSA |
||||
/* exit sound */ |
||||
if (audio) |
||||
sound_close(audio); |
||||
#endif |
||||
|
||||
return 0; |
||||
} |
||||
|
@ -0,0 +1,564 @@ |
||||
/* MTS/IMTS signal processing
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* 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 3 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/>.
|
||||
*/ |
||||
|
||||
#define CHAN imts->sender.kanal |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../libmobile/call.h" |
||||
#include "imts.h" |
||||
#include "dsp.h" |
||||
|
||||
/* uncomment to debug decoder */ |
||||
//#define DEBUG_DECODER
|
||||
|
||||
#define PI 3.1415927 |
||||
|
||||
/* signaling */ |
||||
#define MAX_DEVIATION 7000.0 /* signaling tone plus some extra to calibrate */ |
||||
#define MAX_MODULATION 3000.0 /* FIXME */ |
||||
#define DBM0_DEVIATION 2500.0 /* deviation of dBm0 (with emphasis) */ |
||||
#define TX_PEAK_TONE (5000.0 / DBM0_DEVIATION) /* signaling tone level (5khz, no emphasis) */ |
||||
#define RX_MIN_AMPL 0.25 /* FIXME: Minimum level to detect tone */ |
||||
/* Note that 75 is half of the distance between two tones (2000 and 2150 Hz)
|
||||
* An error of more than 50 causes too much toggeling between two tones, |
||||
* less would take too long to detect the tone and maybe not detect it, if |
||||
* it is too far off the expected frequency. |
||||
*/ |
||||
#define RX_MIN_FREQ 50.0 /* minimum frequency error to detect tone */ |
||||
#define MAX_DISPLAY (MAX_DEVIATION / DBM0_DEVIATION)/* as much as MAX_DEVIATION */ |
||||
/* Note that FILTER_BW / SUSTAIN and QUAL_TIME sum up and should not exeed minimum tone length */ |
||||
#define RX_FILTER_BW 100.0 /* amplitude filter (causes delay) */ |
||||
#define RX_SUSTAIN 0.010 /* how long a tone must sustain until detected (causes delay) */ |
||||
#define RX_QUAL_TIME 0.005 /* how long a quality measurement lasts after detecting a tone */ |
||||
|
||||
/* carrier loss detection */ |
||||
#define MUTE_TIME 0.1 /* time to mute after loosing signal */ |
||||
#define DELAY_TIME 0.15 /* delay, so we don't hear the noise before squelch mutes (MTS mode) */ |
||||
#define LOSS_TIME 12.0 /* duration of signal loss before release (FIXME: what was the actual duration ???) */ |
||||
#define SIGNAL_DETECT 0.3 /* time to have a steady signal to detect phone (used by MTS) */ |
||||
|
||||
#define DISPLAY_INTERVAL 0.04 |
||||
|
||||
/* signaling tones to be modulated */ |
||||
static double tones[NUM_SIG_TONES] = { |
||||
2150.0, /* guard */ |
||||
2000.0, /* idle */ |
||||
1800.0, /* seize */ |
||||
1633.0, /* connect */ |
||||
1336.0, /* disconnect */ |
||||
600.0, /* MTS 600 Hz */ |
||||
1500.0, /* MTS 1500 Hz */ |
||||
}; |
||||
|
||||
/* signaling tones to be demodulated (a subset of tones above) */ |
||||
static double tone_response[NUM_SIG_TONES] = { |
||||
0.71, /* guard */ |
||||
1.08, /* idle */ |
||||
1.01, /* seize */ |
||||
1.04, /* connect */ |
||||
0.71, /* disconnect */ |
||||
0.71, /* MTS 600 Hz */ |
||||
0.71, /* MTS 1500 Hz */ |
||||
}; |
||||
|
||||
/* all tone, with signaling tones first */ |
||||
static const char *tone_names[] = { |
||||
"GUARD tone", |
||||
"IDLE tone", |
||||
"SEIZE tone", |
||||
"CONNECT tone", |
||||
"DISCONNECT tone", |
||||
"600 Hz Tone", |
||||
"1500 Hz Tone", |
||||
"SILENCE", |
||||
"NOISE", |
||||
"DIALTONE", |
||||
}; |
||||
|
||||
#define DIALTONE1 350.0 |
||||
#define DIALTONE2 440.0 |
||||
|
||||
/* table for fast sine generation */ |
||||
static sample_t dsp_sine_tone[65536]; |
||||
|
||||
/* global init for audio processing */ |
||||
void dsp_init(void) |
||||
{ |
||||
int i; |
||||
double s; |
||||
|
||||
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine tables.\n"); |
||||
for (i = 0; i < 65536; i++) { |
||||
s = sin((double)i / 65536.0 * 2.0 * PI); |
||||
dsp_sine_tone[i] = s * TX_PEAK_TONE; |
||||
} |
||||
} |
||||
|
||||
/* Init transceiver instance. */ |
||||
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt) |
||||
{ |
||||
int rc = -1; |
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n"); |
||||
|
||||
imts->sample_duration = 1.0 / (double)imts->sender.samplerate; |
||||
|
||||
/* init squelch */ |
||||
squelch_init(&imts->squelch, imts->sender.kanal, squelch_db, MUTE_TIME, LOSS_TIME); |
||||
|
||||
/* set modulation parameters */ |
||||
sender_set_fm(&imts->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY); |
||||
|
||||
/* init FM demodulator for tone detection */ |
||||
if (imts->mode == MODE_IMTS) { |
||||
imts->demod_center = (tones[TONE_GUARD] + tones[TONE_DISCONNECT]) / 2.0; |
||||
imts->demod_bandwidth = tones[TONE_GUARD] - tones[TONE_DISCONNECT]; |
||||
} else { |
||||
imts->demod_center = (tones[TONE_1500] + tones[TONE_600]) / 2.0; |
||||
imts->demod_bandwidth = tones[TONE_1500] - tones[TONE_600]; |
||||
} |
||||
rc = fm_demod_init(&imts->demod, (double)imts->sender.samplerate, imts->demod_center, imts->demod_bandwidth); |
||||
if (rc < 0) |
||||
goto error; |
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */ |
||||
/* NOTE: CHANGE TONE RESPONSES ABOVE, IF YOU CHANGE FILTER */ |
||||
iir_lowpass_init(&imts->demod_freq_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2); |
||||
iir_lowpass_init(&imts->demod_ampl_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2); |
||||
|
||||
/* tones */ |
||||
imts->tone_idle_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_IDLE]); |
||||
imts->tone_seize_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_SEIZE]); |
||||
imts->tone_600_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_600]); |
||||
imts->tone_1500_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_1500]); |
||||
imts->tone_dialtone_phaseshift65536[0] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE1); |
||||
imts->tone_dialtone_phaseshift65536[1] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE2); |
||||
|
||||
/* demod init */ |
||||
imts->demod_current_tone = TONE_SILENCE; |
||||
imts->demod_sig_tone = 0; |
||||
imts->demod_last_tone = TONE_SILENCE; |
||||
|
||||
/* delay buffer */ |
||||
if (ptt) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Push to talk: Adding delay buffer to remove noise when singal gets lost.\n"); |
||||
imts->delay_max = (int)((double)imts->sender.samplerate * DELAY_TIME); |
||||
imts->delay_spl = calloc(imts->delay_max, sizeof(*imts->delay_spl)); |
||||
if (!imts->delay_spl) { |
||||
PDEBUG(DDSP, DEBUG_ERROR, "No mem for delay buffer!\n"); |
||||
goto error; |
||||
} |
||||
} |
||||
|
||||
imts->dmp_tone_level = display_measurements_add(&imts->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0); |
||||
imts->dmp_tone_quality = display_measurements_add(&imts->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0); |
||||
|
||||
return 0; |
||||
|
||||
error: |
||||
dsp_cleanup_transceiver(imts); |
||||
return rc; |
||||
} |
||||
|
||||
/* Cleanup transceiver instance. */ |
||||
void dsp_cleanup_transceiver(imts_t *imts) |
||||
{ |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n"); |
||||
|
||||
fm_demod_exit(&imts->demod); |
||||
if (imts->delay_spl) { |
||||
free(imts->delay_spl); |
||||
imts->delay_spl = NULL; |
||||
} |
||||
} |
||||
|
||||
/* Generate audio stream from tone. Keep phase for next call of function. */ |
||||
static int generate_tone(imts_t *imts, sample_t *samples, int length) |
||||
{ |
||||
double phaseshift[2] = {0,0}, phase[2]; // make gcc happy
|
||||
int i; |
||||
|
||||
switch (imts->tone) { |
||||
case TONE_IDLE: |
||||
phaseshift[0] = imts->tone_idle_phaseshift65536; |
||||
break; |
||||
case TONE_SEIZE: |
||||
phaseshift[0] = imts->tone_seize_phaseshift65536; |
||||
break; |
||||
case TONE_600: |
||||
phaseshift[0] = imts->tone_600_phaseshift65536; |
||||
break; |
||||
case TONE_1500: |
||||
phaseshift[0] = imts->tone_1500_phaseshift65536; |
||||
break; |
||||
case TONE_DIALTONE: |
||||
phaseshift[0] = imts->tone_dialtone_phaseshift65536[0]; |
||||
phaseshift[1] = imts->tone_dialtone_phaseshift65536[1]; |
||||
break; |
||||
case TONE_SILENCE: |
||||
break; |
||||
default: |
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Software error, unsupported tone, please fix!\n"); |
||||
return length; |
||||
} |
||||
|
||||
/* don't send more than given length */ |
||||
if (imts->tone_duration && length > imts->tone_duration) |
||||
length = imts->tone_duration; |
||||
|
||||
phase[0] = imts->tone_phase65536[0]; |
||||
phase[1] = imts->tone_phase65536[1]; |
||||
|
||||
switch (imts->tone) { |
||||
case TONE_SILENCE: |
||||
memset(samples, 0, length * sizeof(*samples)); |
||||
phase[0] = phase[1] = 0; |
||||
break; |
||||
case TONE_DIALTONE: |
||||
for (i = 0; i < length; i++) { |
||||
*samples = dsp_sine_tone[(uint16_t)phase[0]]; |
||||
*samples += dsp_sine_tone[(uint16_t)phase[1]]; |
||||
*samples++ /= 4.0; /* not full volume */ |
||||
phase[0] += phaseshift[0]; |
||||
if (phase[0] >= 65536) |
||||
phase[0] -= 65536; |
||||
phase[1] += phaseshift[1]; |
||||
if (phase[1] >= 65536) |
||||
phase[1] -= 65536; |
||||
} |
||||
break; |
||||
default: |
||||
for (i = 0; i < length; i++) { |
||||
*samples++ = dsp_sine_tone[(uint16_t)phase[0]]; |
||||
phase[0] += phaseshift[0]; |
||||
if (phase[0] >= 65536) |
||||
phase[0] -= 65536; |
||||
} |
||||
} |
||||
|
||||
imts->tone_phase65536[0] = phase[0]; |
||||
imts->tone_phase65536[1] = phase[1]; |
||||
|
||||
/* if tone has been sent completely, tell IMTS call process */ |
||||
if (imts->tone_duration) { |
||||
imts->tone_duration -= length; |
||||
if (imts->tone_duration == 0) |
||||
imts_tone_sent(imts, imts->tone); |
||||
} |
||||
|
||||
return length; |
||||
} |
||||
|
||||
/* Provide stream of audio toward radio unit */ |
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length) |
||||
{ |
||||
imts_t *imts = (imts_t *) sender; |
||||
int count; |
||||
|
||||
memset(power, 1, length); |
||||
|
||||
again: |
||||
switch (imts->dsp_mode) { |
||||
case DSP_MODE_OFF: |
||||
memset(power, 0, length); |
||||
memset(samples, 0, length * sizeof(*samples)); |
||||
break; |
||||
case DSP_MODE_TONE: |
||||
memset(power, 1, length); |
||||
count = generate_tone(imts, samples, length); |
||||
samples += count; |
||||
length -= count; |
||||
if (length) |
||||
goto again; |
||||
break; |
||||
case DSP_MODE_AUDIO: |
||||
memset(power, 1, length); |
||||
jitter_load(&imts->sender.dejitter, samples, length); |
||||
if (imts->pre_emphasis) |
||||
pre_emphasis(&imts->estate, samples, length); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
static void tone_demod(imts_t *imts, sample_t *samples, int length) |
||||
{ |
||||
sample_t frequency[length], amplitude[length]; |
||||
sample_t I[length], Q[length]; |
||||
double f, amp; |
||||
int i, t, tone = 0; // make GCC happy
|
||||
|
||||
/* demod frquency */ |
||||
fm_demodulate_real(&imts->demod, frequency, length, samples, I, Q); |
||||
iir_process(&imts->demod_freq_lp, frequency, length); |
||||
|
||||
/* demod amplitude
|
||||
* peak amplitude is the length of I/Q vector |
||||
* since we filter out the unwanted modulation product, the vector is only half of length |
||||
*/ |
||||
for (i = 0; i < length; i++) |
||||
amplitude[i] = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0; |
||||
iir_process(&imts->demod_ampl_lp, amplitude, length); |
||||
|
||||
/* check result to detect tone or loss or change */ |
||||
for (i = 0; i < length; i++) { |
||||
/* duration until change in tone */ |
||||
imts->demod_duration += imts->sample_duration; |
||||
/* correct FSK amplitude */ |
||||
amplitude[i] /= TX_PEAK_TONE; |
||||
/* see what we detect at this moment; tone is set */ |
||||
if (amplitude[i] < RX_MIN_AMPL) { |
||||
/* silence */ |
||||
tone = TONE_SILENCE; |
||||
} else { |
||||
/* tone */ |
||||
f = frequency[i] + imts->demod_center; |
||||
if (imts->mode == MODE_IMTS) { |
||||
for (t = TONE_IDLE; t <= TONE_DISCONNECT; t++) { |
||||
/* check for frequency */ |
||||
if (fabs(f - tones[t]) < RX_MIN_FREQ) { |
||||
tone = t; |
||||
break; |
||||
} |
||||
} |
||||
} else { |
||||
for (t = TONE_600; t <= TONE_1500; t++) { |
||||
/* check for frequency */ |
||||
if (fabs(f - tones[t]) < RX_MIN_FREQ) { |
||||
tone = t; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
/* noise */ |
||||
if (t == NUM_SIG_TONES) |
||||
tone = TONE_NOISE; |
||||
} |
||||
#ifdef DEBUG_DECODER |
||||
/* debug decoder */ |
||||
if (tone < NUM_SIG_TONES) { |
||||
static int debug_interval = 0; |
||||
if (++debug_interval == 30) { |
||||
debug_interval = 0; |
||||
printf("decoder debug: diff=%s %.0f ", debug_amplitude((f - tones[t]) / RX_MIN_FREQ), fabs(f - tones[t])); |
||||
amp = amplitude[i] / tone_response[t]; |
||||
printf("ampl=%s %.0f%%\n", debug_amplitude(amp), amp * 100.0); |
||||
} |
||||
} |
||||
#endif |
||||
/* display level of tones, or zero at noise/silence */ |
||||
imts->display_interval +=imts->sample_duration; |
||||
if (imts->display_interval >= DISPLAY_INTERVAL) { |
||||
if (tone < NUM_SIG_TONES) |
||||
amp = amplitude[i] / tone_response[tone]; |
||||
else |
||||
amp = 0.0; |
||||
display_measurements_update(imts->dmp_tone_level, amp * 100.0, 0.0); |
||||
imts->display_interval -= DISPLAY_INTERVAL; |
||||
} |
||||
/* check for tone change */ |
||||
if (tone != imts->demod_current_tone) { |
||||
#ifdef DEBUG_DECODER |
||||
printf("decoder debug: %s detected, waiting to sustain\n", tone_names[tone]); |
||||
#endif |
||||
if (imts->demod_sig_tone) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Lost %s (duration %.0f ms)\n", tone_names[imts->demod_current_tone], imts->demod_duration * 1000.0); |
||||
imts_lost_tone(imts, imts->demod_current_tone, imts->demod_duration); |
||||
imts->demod_sig_tone = 0; |
||||
} |
||||
imts->demod_current_tone = tone; |
||||
imts->demod_sustain = 0.0; |
||||
} else if (imts->demod_sustain < RX_SUSTAIN) { |
||||
imts->demod_sustain += imts->sample_duration; |
||||
/* when sustained. also tone must change to prevent flapping; lost signaling tone can be detected again */ |
||||
if (imts->demod_sustain >= RX_SUSTAIN && (imts->demod_current_tone != imts->demod_last_tone || !imts->demod_sig_tone)) { |
||||
if (imts->demod_current_tone < NUM_SIG_TONES) { |
||||
amp = amplitude[i] / tone_response[imts->demod_current_tone]; |
||||
imts->demod_sig_tone = 1; |
||||
imts->demod_quality_time = 0.0; |
||||
imts->demod_quality_count = 0; |
||||
imts->demod_quality_value = 0.0; |
||||
} else { |
||||
amp = amplitude[i]; |
||||
imts->demod_sig_tone = 0; |
||||
} |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected %s (level %.0f%%)\n", tone_names[imts->demod_current_tone], amp * 100); |
||||
imts_receive_tone(imts, imts->demod_current_tone, imts->demod_duration, amp); |
||||
imts->demod_last_tone = imts->demod_current_tone; |
||||
imts->demod_duration = imts->demod_sustain; |
||||
} else if (imts->demod_sig_tone && imts->demod_quality_time < RX_QUAL_TIME) { |
||||
f = frequency[i] + imts->demod_center; |
||||
imts->demod_quality_time += imts->sample_duration; |
||||
imts->demod_quality_count++; |
||||
imts->demod_quality_value += fabs((f - tones[imts->demod_current_tone])) / RX_MIN_FREQ; |
||||
if (imts->demod_quality_time >= RX_QUAL_TIME) { |
||||
double quality = 1.0 - imts->demod_quality_value / (double)imts->demod_quality_count * 2.0; |
||||
if (quality < 0) |
||||
quality = 0; |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Quality: %.0f%%\n", quality * 100.0); |
||||
display_measurements_update(imts->dmp_tone_quality, quality * 100.0, 0.0); |
||||
} |
||||
}
|
||||
} |
||||
} |
||||
} |
||||
|
||||
static void delay_audio(imts_t *imts, sample_t *samples, int count) |
||||
{ |
||||
sample_t *spl, s; |
||||
int pos, max; |
||||
int i; |
||||
|
||||
spl = imts->delay_spl; |
||||
pos = imts->delay_pos; |
||||
max = imts->delay_max; |
||||
|
||||
/* feed audio though delay buffer */ |
||||
for (i = 0; i < count; i++) { |
||||
s = samples[i]; |
||||
samples[i] = spl[pos]; |
||||
spl[pos] = s; |
||||
if (++pos == max) |
||||
pos = 0; |
||||
} |
||||
|
||||
imts->delay_pos = pos; |
||||
} |
||||
|
||||
/* Process received audio stream from radio unit. */ |
||||
void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_level_db) |
||||
{ |
||||
imts_t *imts = (imts_t *) sender; |
||||
|
||||
/* no processing if off */ |
||||
if (imts->dsp_mode == DSP_MODE_OFF) |
||||
return; |
||||
|
||||
/* process signal mute/loss, also for signalling tone */ |
||||
switch (squelch(&imts->squelch, rf_level_db, (double)length * imts->sample_duration)) { |
||||
case SQUELCH_LOSS: |
||||
imts_loss_indication(imts, LOSS_TIME); |
||||
/* FALLTHRU */ |
||||
case SQUELCH_MUTE: |
||||
if (imts->mode == MODE_MTS && !imts->is_mute) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "Low RF level, muting.\n"); |
||||
memset(imts->delay_spl, 0, sizeof(*samples) * imts->delay_max); |
||||
imts->is_mute = 1; |
||||
} |
||||
memset(samples, 0, sizeof(*samples) * length); |
||||
/* signal is gone */ |
||||
imts->rf_signal = 0; |
||||
break; |
||||
default: |
||||
if (imts->is_mute) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_INFO, "High RF level, unmuting.\n"); |
||||
imts->is_mute = 0; |
||||
} |
||||
/* detect signal, if it is steady for a while */ |
||||
if (imts->rf_signal < SIGNAL_DETECT * (double)imts->sender.samplerate) { |
||||
/* only do this, if signal has not been detected yet */ |
||||
imts->rf_signal += length; |
||||
if (imts->rf_signal >= SIGNAL_DETECT * (double)imts->sender.samplerate) |
||||
imts_signal_indication(imts); |
||||
} |
||||
break; |
||||
} |
||||
|
||||
/* FM/AM demod */ |
||||
tone_demod(imts, samples, length); |
||||
|
||||
/* delay audio to prevent noise before squelch mutes (don't do that for signaling tones) */ |
||||
if (imts->delay_spl) |
||||
delay_audio(imts, samples, length); |
||||
|
||||
/* Forward audio to network (call process). */ |
||||
if (imts->dsp_mode == DSP_MODE_AUDIO && imts->callref) { |
||||
sample_t *spl; |
||||
int pos; |
||||
int count; |
||||
int i; |
||||
|
||||
if (imts->de_emphasis) { |
||||
dc_filter(&imts->estate, samples, length); |
||||
de_emphasis(&imts->estate, samples, length); |
||||
} |
||||
count = samplerate_downsample(&imts->sender.srstate, samples, length); |
||||
spl = imts->sender.rxbuf; |
||||
pos = imts->sender.rxbuf_pos; |
||||
for (i = 0; i < count; i++) { |
||||
spl[pos++] = samples[i]; |
||||
if (pos == 160) { |
||||
call_up_audio(imts->callref, spl, 160); |
||||
pos = 0; |
||||
} |
||||
} |
||||
imts->sender.rxbuf_pos = pos; |
||||
} else |
||||
imts->sender.rxbuf_pos = 0; |
||||
|
||||
} |
||||
|
||||
const char *imts_dsp_mode_name(enum dsp_mode mode) |
||||
{ |
||||
static char invalid[16]; |
||||
|
||||
switch (mode) { |
||||
case DSP_MODE_OFF: |
||||
return "TRX OFF"; |
||||
case DSP_MODE_TONE: |
||||
return "TONE"; |
||||
case DSP_MODE_AUDIO: |
||||
return "AUDIO"; |
||||
} |
||||
|
||||
sprintf(invalid, "invalid(%d)", mode); |
||||
return invalid; |
||||
} |
||||
|
||||
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod) |
||||
{ |
||||
/* reset demod */ |
||||
if (reset_demod) { |
||||
imts->demod_current_tone = TONE_SILENCE; |
||||
imts->demod_sig_tone = 0; |
||||
imts->demod_last_tone = TONE_SILENCE; |
||||
imts->demod_duration = 0.0; |
||||
} |
||||
|
||||
if (imts->dsp_mode != mode) { |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", imts_dsp_mode_name(imts->dsp_mode), imts_dsp_mode_name(mode)); |
||||
imts->dsp_mode = mode; |
||||
} |
||||
|
||||
if (mode == DSP_MODE_TONE) { |
||||
if (duration) |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s for %.3f seconds.\n", tone_names[tone], duration); |
||||
else |
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s continuously.\n", tone_names[tone]); |
||||
imts->tone = tone; |
||||
imts->tone_duration = duration * (double)imts->sender.samplerate; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,6 @@ |
||||
|
||||
void dsp_init(void); |
||||
int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt); |
||||
void dsp_cleanup_transceiver(imts_t *imts); |
||||
void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod); |
||||
|
@ -0,0 +1,77 @@ |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include "../libmobile/image.h" |
||||
|
||||
const char *image[] = { |
||||
"@W", |
||||
"IMTS / MTS is back!", |
||||
"", |
||||
NULL |
||||
}; |
||||
|
||||
void print_image(void) |
||||
{ |
||||
int i, j; |
||||
|
||||
for (i = 0; image[i]; i++) { |
||||
for (j = 0; j < (int)strlen(image[i]); j++) { |
||||
if (image[i][j] == '@') { |
||||
j++; |
||||
switch(image[i][j]) { |
||||
case 'k': /* black */ |
||||
printf("\033[0;30m"); |
||||
break; |
||||
case 'r': /* red */ |
||||
printf("\033[0;31m"); |
||||
break; |
||||
case 'g': /* green */ |
||||
printf("\033[0;32m"); |
||||
break; |
||||
case 'y': /* yellow */ |
||||
printf("\033[0;33m"); |
||||
break; |
||||
case 'b': /* blue */ |
||||
printf("\033[0;34m"); |
||||
break; |
||||
case 'm': /* magenta */ |
||||
printf("\033[0;35m"); |
||||
break; |
||||
case 'c': /* cyan */ |
||||
printf("\033[0;36m"); |
||||
break; |
||||
case 'w': /* white */ |
||||
printf("\033[0;37m"); |
||||
break; |
||||
case 'K': /* bright black */ |
||||
printf("\033[1;30m"); |
||||
break; |
||||
case 'R': /* bright red */ |
||||
printf("\033[1;31m"); |
||||
break; |
||||
case 'G': /* bright green */ |
||||
printf("\033[1;32m"); |
||||
break; |
||||
case 'Y': /* bright yellow */ |
||||
printf("\033[1;33m"); |
||||
break; |
||||
case 'B': /* bright blue */ |
||||
printf("\033[1;34m"); |
||||
break; |
||||
case 'M': /* bright magenta */ |
||||
printf("\033[1;35m"); |
||||
break; |
||||
case 'C': /* bright cyan */ |
||||
printf("\033[1;36m"); |
||||
break; |
||||
case 'W': /* bright white */ |
||||
printf("\033[1;37m"); |
||||
break; |
||||
} |
||||
} else |
||||
printf("%c", image[i][j]); |
||||
} |
||||
printf("\n"); |
||||
} |
||||
printf("\033[0;39m"); |
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,139 @@ |
||||
#include "../libsquelch/squelch.h" |
||||
#include "../libfm/fm.h" |
||||
#include "../libmobile/sender.h" |
||||
|
||||
enum dsp_mode { |
||||
DSP_MODE_OFF = 0, /* transmitter off */ |
||||
DSP_MODE_TONE, /* send tone or silence */ |
||||
DSP_MODE_AUDIO, /* send audio */ |
||||
}; |
||||
|
||||
#define TONE_GUARD 0 |
||||
#define TONE_IDLE 1 |
||||
#define TONE_SEIZE 2 |
||||
#define TONE_CONNECT 3 |
||||
#define TONE_DISCONNECT 4 |
||||
|
||||
#define TONE_600 5 |
||||
#define TONE_1500 6 |
||||
|
||||
#define TONE_SILENCE 7 |
||||
#define TONE_NOISE 8 |
||||
#define TONE_DIALTONE 9 |
||||
|
||||
#define NUM_SIG_TONES 7 |
||||
|
||||
enum mode { |
||||
MODE_IMTS = 0, |
||||
MODE_MTS, |
||||
}; |
||||
|
||||
enum imts_state { |
||||
IMTS_NULL = 0, |
||||
/* channel is idle */ |
||||
IMTS_OFF, /* base station not in use and turned off */ |
||||
IMTS_IDLE, /* base station not in use and sending 2000 Hz Idle tone */ |
||||
/* mobile originated */ |
||||
IMTS_SEIZE, /* base station sends seize to acknowldege call from mobile */ |
||||
IMTS_ANI, /* base station is receiving ANI from mobile */ |
||||
IMTS_DIALING, /* base station is receiving dial digits */ |
||||
/* mobile terminated */ |
||||
IMTS_PAGING, /* base station is paging mobile */ |
||||
IMTS_RINGING, /* base station is ringing mobile */ |
||||
/* active call */ |
||||
IMTS_CONVERSATION, /* base station and mobile have conversation */ |
||||
/* releasing call */ |
||||
IMTS_RELEASE, /* base station turned off */ |
||||
/* loopback test */ |
||||
IMTS_PAGING_TEST, /* loopback test sequence */ |
||||
/* detector test */ |
||||
IMTS_DETECTOR_TEST, /* detector test sequence */ |
||||
}; |
||||
|
||||
typedef struct imts { |
||||
sender_t sender; |
||||
|
||||
/* channel's states */ |
||||
enum imts_state state; /* current sender's state */ |
||||
int pre_emphasis; /* use pre_emphasis by this instance */ |
||||
int de_emphasis; /* use de_emphasis by this instance */ |
||||
emphasis_t estate; |
||||
int callref; /* call reference */ |
||||
char station_id[11]; /* current station ID (also used for test pattern) */ |
||||
int station_length; /* digit length of station ID */ |
||||
char dial_number[33]; /* number dialing */ |
||||
struct timer timer; |
||||
int last_tone; /* last tone received */ |
||||
double last_sigtone_amplitude; /* amplitude of last signaling tone received */ |
||||
double fast_seize; /* fast seize: guard-length - roundtrip-delay */ |
||||
double rx_guard_timestamp; /* start of guard tone (seize by mobile) */ |
||||
double rx_guard_duration; /* duration of guard (only long guards are detected) */ |
||||
int rx_ani_pulse; /* current pulse # receiving */ |
||||
int rx_ani_index; /* current digit # receiving */ |
||||
int rx_ani_totpulses; /* total pulses count receiving */ |
||||
int rx_dial_pulse; /* current pulse # receiving */ |
||||
int rx_dial_index; /* current digit # receiving */ |
||||
int rx_disc_pulse; /* current pulse # receiving */ |
||||
int tx_page_pulse; /* current pulse # transmitting */ |
||||
int tx_page_index; /* current digit # transmitting */ |
||||
double tx_page_timestamp; /* last pulse of digit transmitting */ |
||||
int tx_ring_pulse; /* current pulse # transmitting */ |
||||
int rx_page_pulse; /* current pulse # receiving */ |
||||
double detector_test_length_1; /* detector test tone duration */ |
||||
double detector_test_length_2; /* detector test tone duration */ |
||||
double detector_test_length_3; /* detector test tone duration */ |
||||
|
||||
/* MTS additional states */ |
||||
enum mode mode; /* set if MTS mode is used */ |
||||
const char *operator; /* operator's number to call when seizing the channel */ |
||||
|
||||
/* display measurements */ |
||||
dispmeasparam_t *dmp_tone_level; |
||||
dispmeasparam_t *dmp_tone_quality; |
||||
|
||||
/* dsp states */ |
||||
double sample_duration; /* 1 / samplerate */ |
||||
double demod_center; /* center frequency for tone demodulation */ |
||||
double demod_bandwidth; /* bandwidth for tone demodulation */ |
||||
fm_demod_t demod; /* demodulator for frequency / amplitude */ |
||||
iir_filter_t demod_freq_lp; /* filter for frequency response */ |
||||
iir_filter_t demod_ampl_lp; /* filter for amplitude response */ |
||||
int demod_current_tone; /* current tone being detected */ |
||||
int demod_sig_tone; /* current tone is a signaling tone */ |
||||
int demod_last_tone; /* last tone being detected */ |
||||
double demod_sustain; /* how long a tone must sustain */ |
||||
double demod_duration; /* duration of last tone */ |
||||
double demod_quality_time; /* time counter to measure quality */ |
||||
int demod_quality_count; /* counter to measure quality */ |
||||
double demod_quality_value; /* sum of quality samples (must be divided by count) */ |
||||
double display_interval; /* used to update tone levels */ |
||||
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */ |
||||
int ptt; /* set, if push to talk is used (transmitter of phone off) */ |
||||
int tone; /* current tone to send */ |
||||
int tone_duration; /* if set, tone is limited to this duration (in samples) */ |
||||
double tone_idle_phaseshift65536;/* how much the phase of sine wave changes per sample */ |
||||
double tone_seize_phaseshift65536;/* how much the phase of sine wave changes per sample */ |
||||
double tone_600_phaseshift65536;/* how much the phase of sine wave changes per sample */ |
||||
double tone_1500_phaseshift65536;/* how much the phase of sine wave changes per sample */ |
||||
double tone_dialtone_phaseshift65536[2];/* how much the phase of sine wave changes per sample */ |
||||
double tone_phase65536[2]; /* current phase */ |
||||
squelch_t squelch; /* squelch detection process */ |
||||
int is_mute; /* set if quelch has muted */ |
||||
int rf_signal; /* set if we have currently an RF signal */ |
||||
sample_t *delay_spl; /* delay buffer for delaying audio */ |
||||
int delay_pos; /* position in delay buffer */ |
||||
int delay_max; /* number of samples in delay buffer */ |
||||
} imts_t; |
||||
|
||||
|
||||
void imts_list_channels(void); |
||||
double imts_channel2freq(const char *kanal, int uplink); |
||||
int imts_init(void); |
||||
int imts_create(const char *channel, const char *audiodev, int use_sdr, int samplerate, double rx_gain, int pre_emphasis, int de_emphasis, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback, double squelch_db, int ptt, int station_length, double fast_seize, enum mode mode, const char *operator, double length_1, double length_2, double length_3); |
||||
void imts_destroy(sender_t *sender); |
||||
void imts_loss_indication(imts_t *imts, double loss_time); |
||||
void imts_signal_indication(imts_t *imts); |
||||
void imts_receive_tone(imts_t *imts, int tone, double elapsed, double amplitude); |
||||
void imts_lost_tone(imts_t *imts, int tone, double elapsed); |
||||
void imts_tone_sent(imts_t *imts, int tone); |
||||
|
@ -0,0 +1,289 @@ |
||||
/* MTS/IMTS main
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* 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 3 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/>.
|
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libmobile/main_mobile.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../libmobile/call.h" |
||||
#include "../liboptions/options.h" |
||||
#include "../amps/tones.h" |
||||
#include "../amps/outoforder.h" |
||||
#include "../amps/noanswer.h" |
||||
#include "../amps/invalidnumber.h" |
||||
#include "../amps/congestion.h" |
||||
#include "imts.h" |
||||
#include "dsp.h" |
||||
|
||||
/* settings */ |
||||
static double squelch_db = -INFINITY; |
||||
static int ptt = 0; |
||||
static int station_length = 0; /* defined by mode */ |
||||
static double fast_seize = 0.0; |
||||
static enum mode mode = MODE_IMTS; |
||||
static char operator[32] = "010"; |
||||
static double detector_test_length_1 = 0.0; |
||||
static double detector_test_length_2 = 0.0; |
||||
static double detector_test_length_3 = 0.0; |
||||
|
||||
void print_help(const char *arg0) |
||||
{ |
||||
main_mobile_print_help(arg0, "-b 5 "); |
||||
/* - - */ |
||||
printf(" -S --squelch <dB> | auto\n"); |
||||
printf(" Use given RF level to detect loss of signal. When the signal gets lost\n"); |
||||
printf(" and stays below this level, the connection is released.\n"); |
||||
printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n"); |
||||
printf(" Only works with SDR! (disabled by default)\n"); |
||||
printf(" -P --push-to-talk\n"); |
||||
printf(" Allow push-to-talk operation for IMTS mode. (MTS always uses it.)\n"); |
||||
printf(" This adds extra delay to received audio, to eliminate noise when the\n"); |
||||
printf(" transmitter of the phone is turned off. Also this disables release on\n"); |
||||
printf(" loss of RF signal. (Squelch is required for this to operate.)\n"); |
||||
printf(" -5 --five\n"); |
||||
printf(" -7 --seven\n"); |
||||
printf(" Force station ID length (default is 7 for IMTS, 5 for MTS)\n"); |
||||
printf(" -F --fast-seize <delay in ms>\n"); |
||||
printf(" To compensate audio processing latency, give delay when to respond,\n"); |
||||
printf(" after detection of Guard tone from mobile phone.\n"); |
||||
printf(" Run software in loopback mode '-l 2' to measure round trip delay.\n"); |
||||
printf(" Substract delay from 350 ms. If the phone has different Guard tone\n"); |
||||
printf(" length, substract from that value.\n"); |
||||
printf(" -D --detector-test <idle length> <seize lenght> <silence length>\n"); |
||||
printf(" Transmit detector test signal, to adjust decoder inside mobile phone.\n"); |
||||
printf(" Give length of idle / seize and silence in seconds. Listen to it with\n"); |
||||
printf(" a radio receiver. To exclude an element, set its length to '0'.\n"); |
||||
printf(" Example: '-D 0.5 0.5 0' plays alternating idle/seize tone.\n"); |
||||
printf("\nMTS mode options\n"); |
||||
printf(" -M --mts\n"); |
||||
printf(" Run base station in MTS mode, rather than in IMTS mode.\n"); |
||||
printf(" -O --operator <number>\n"); |
||||
printf(" Give number to dial when mobile station initiated a call in MTS mode.\n"); |
||||
printf(" Because there is no dial on the mobile phone, operator assistance is\n"); |
||||
printf(" required to complete the call.\n"); |
||||
printf(" By default, the operator '%s' is dialed.\n", operator); |
||||
printf(" -D --detector-test <600 Hz length> <1500 Hz lenght> <silence length>\n"); |
||||
printf(" Transmit detector test signal, to adjust decoder inside MTS phone.\n"); |
||||
printf(" Give length of 600/1500 Hz and silence in seconds. Listen to it with\n"); |
||||
printf(" a radio receiver. To exclude an element, set its length to '0'.\n"); |
||||
printf(" Example: '-D 0.5 0.5 0' plays alternating 600/1500 Hz tone.\n"); |
||||
printf("\nstation-id: Give %d digits of station-id, you don't need to enter it after\n", station_length); |
||||
printf(" every start of this program.\n"); |
||||
main_mobile_print_hotkeys(); |
||||
} |
||||
< |