From 99bafb6880d2b07233bbd336d60acec4f61e3193 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 21 Jul 2019 21:19:54 +0200 Subject: [PATCH] MTS/IMTS: (Improved) Mobile Telephone Service 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. --- .gitignore | 2 + README | 1 + configure.ac | 1 + docs/index.html | 1 + src/Makefile.am | 1 + src/imts/Makefile.am | 63 ++ src/imts/dialer.c | 354 +++++++++++ src/imts/dsp.c | 564 ++++++++++++++++++ src/imts/dsp.h | 6 + src/imts/image.c | 77 +++ src/imts/imts.c | 1326 ++++++++++++++++++++++++++++++++++++++++++ src/imts/imts.h | 139 +++++ src/imts/main.c | 289 +++++++++ src/libdebug/debug.c | 1 + src/libdebug/debug.h | 27 +- 15 files changed, 2839 insertions(+), 13 deletions(-) create mode 100644 src/imts/Makefile.am create mode 100644 src/imts/dialer.c create mode 100644 src/imts/dsp.c create mode 100644 src/imts/dsp.h create mode 100644 src/imts/image.c create mode 100644 src/imts/imts.c create mode 100644 src/imts/imts.h create mode 100644 src/imts/main.c diff --git a/.gitignore b/.gitignore index d5f1943..8dc08ca 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,8 @@ src/amps/amps src/tacs/tacs src/jtacs/jtacs src/r2000/radiocom2000 +src/imts/imts +src/imts/imts-dialer src/jolly/jollycom src/tv/osmotv src/radio/osmoradio diff --git a/README b/README index a60667b..96372fc 100644 --- a/README +++ b/README @@ -13,6 +13,7 @@ generated simultaniously using SDR. Currently supported networks: * TACS (Total Access Communication System) * JTACS (Japanese version of TACS) * Radiocom 2000 (French network) + * IMTS / MTS ((Improved) Mobile Telephone Service) * JollyCom (Unofficial network, invented by the author) diff --git a/configure.ac b/configure.ac index dc71f53..ed78ff0 100644 --- a/configure.ac +++ b/configure.ac @@ -84,6 +84,7 @@ AC_OUTPUT( src/tacs/Makefile src/jtacs/Makefile src/r2000/Makefile + src/imts/Makefile src/jolly/Makefile src/tv/Makefile src/radio/Makefile diff --git a/docs/index.html b/docs/index.html index 29cb093..568749b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -60,6 +60,7 @@ It started with the idea to make a base station for the German B-Netz, but more

This project is pure software that requires a transmitter and a receiver connected to the sound card of a Linux PC. +Alternatively (more suggested) you can use a full duplex SDR to even generate multiple channels at a time. A second sound card or ISDN card is used to route calls from and to the mobile phone.

diff --git a/src/Makefile.am b/src/Makefile.am index 4f1751f..3950a81 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -46,6 +46,7 @@ SUBDIRS += \ tacs \ jtacs \ r2000 \ + imts \ jolly \ tv \ radio \ diff --git a/src/imts/Makefile.am b/src/imts/Makefile.am new file mode 100644 index 0000000..c9f7dce --- /dev/null +++ b/src/imts/Makefile.am @@ -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 + diff --git a/src/imts/dialer.c b/src/imts/dialer.c new file mode 100644 index 0000000..a68ae05 --- /dev/null +++ b/src/imts/dialer.c @@ -0,0 +1,354 @@ +/* IMTS dialer + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#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] | 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 \n"); + printf(" 7 Digits of ID of mobile station (default = '%s')\n", station_id); +#ifdef HAVE_ALSA + printf(" -a --audio-device hw:,\n"); + printf(" Sound card and device number (default = '%s')\n", audiodev); +#endif + printf(" -s --samplerate \n"); + printf(" Sample rate of sound device (default = '%d')\n", samplerate); + printf(" -w --write-tx-wave \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; +} + diff --git a/src/imts/dsp.c b/src/imts/dsp.c new file mode 100644 index 0000000..52b388f --- /dev/null +++ b/src/imts/dsp.c @@ -0,0 +1,564 @@ +/* MTS/IMTS signal processing + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#define CHAN imts->sender.kanal + +#include +#include +#include +#include +#include +#include +#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; + } +} + diff --git a/src/imts/dsp.h b/src/imts/dsp.h new file mode 100644 index 0000000..921d2d9 --- /dev/null +++ b/src/imts/dsp.h @@ -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); + diff --git a/src/imts/image.c b/src/imts/image.c new file mode 100644 index 0000000..8557f1b --- /dev/null +++ b/src/imts/image.c @@ -0,0 +1,77 @@ +#include +#include +#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"); +} + diff --git a/src/imts/imts.c b/src/imts/imts.c new file mode 100644 index 0000000..2056806 --- /dev/null +++ b/src/imts/imts.c @@ -0,0 +1,1326 @@ +/* MTS/IMTS protocol handling + * + * (C) 2019 by Andreas Eversberg + * 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 . + * + * + * How it works: + * + * (There should be a more detailed description of the call process!) + * + * There are call states defined by imts->state. + * imts_receive_tone() is called whenever a tone/silence/noise is detected. + * imts_lost_tone() is calles as soon as (only) a tone is gone. + * imts_timeout() is called when the timer has timed out. + * All these callbacks are used to process the call setup and disconnect. + * The imts_timeout() function will not only handle failures due to timeouts, + * but also E.g. end of pulsed digit or seize detection. + * + */ + +#define CHAN imts->sender.kanal + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "../libmobile/call.h" +#include "../libmncc/cause.h" +#include "imts.h" +#include "dsp.h" + +#define CUT_OFF_EMPHASIS_IMTS 796.0 /* FIXME: really 200 uS time constant? */ + +/* Call reference for calls from mobile station to network + This offset of 0x400000000 is required for MNCC interface. */ +static int new_callref = 0x40000000; + +/* band info */ +#define VHF_LOW 0 +#define VHF_HIGH 1 +#define UHF 2 +static const char *band_name[] = { + "VHF-Low Band", + "VHF-High Band", + "UHF Band", +}; + +/* I measured VHF_HIGH deviation with 5000, others told me that all bands are 5000. */ +#define DEV_VHF_LOW 5000.0 +#define DEV_VHF_HIGH 5000.0 +#define DEV_UHF 5000.0 + +static struct channel_info { + int band; /* which band it belongs to */ + char *name; /* name of channel */ + double downlink_mhz; /* base station frequency */ + double uplink_mhz; /* mobile station frequency */ + int canada_only; /* channel used in canada only */ +} channel_info[] = { + { VHF_LOW, "ZO", 35.26, 43.26, 0 }, + { VHF_LOW, "ZF", 35.30, 43.30, 0 }, + { VHF_LOW, "ZM", 35.38, 43.38, 0 }, + { VHF_LOW, "ZH", 35.34, 43.34, 0 }, + { VHF_LOW, "ZA", 35.42, 43.32, 0 }, + { VHF_LOW, "ZY", 35.46, 43.46, 0 }, + { VHF_LOW, "ZR", 35.50, 43.50, 0 }, + { VHF_LOW, "ZB", 35.54, 43.54, 0 }, + { VHF_LOW, "ZW", 35.62, 43.62, 0 }, + { VHF_LOW, "ZL", 35.66, 43.66, 0 }, + { VHF_HIGH, "JJ", 152.48, 157.74, 1 }, + { VHF_HIGH, "JL", 152.51, 157.77, 0 }, + { VHF_HIGH, "YL", 152.54, 157.80, 0 }, + { VHF_HIGH, "JP", 152.57, 157.83, 0 }, + { VHF_HIGH, "YP", 152.60, 157.86, 0 }, + { VHF_HIGH, "YJ", 152.63, 157.89, 0 }, + { VHF_HIGH, "YK", 152.66, 157.92, 0 }, + { VHF_HIGH, "JS", 152.69, 157.95, 0 }, + { VHF_HIGH, "YS", 152.72, 157.98, 0 }, + { VHF_HIGH, "YR", 152.75, 158.01, 0 }, + { VHF_HIGH, "JK", 152.78, 158.04, 0 }, + { VHF_HIGH, "JR", 152.81, 158.07, 0 }, + { VHF_HIGH, "JW", 152.84, 158.10, 1 }, + { UHF, "QC", 454.375, 459.375, 0 }, + { UHF, "QJ", 454.40, 459.40, 0 }, + { UHF, "QD", 454.425, 459.425, 0 }, + { UHF, "QA", 454.45, 459.45, 0 }, + { UHF, "QE", 454.475, 459.475, 0 }, + { UHF, "QP", 454.50, 459.50, 0 }, + { UHF, "QK", 454.525, 459.525, 0 }, + { UHF, "QB", 454.55, 459.55, 0 }, + { UHF, "QO", 454.575, 459.575, 0 }, + { UHF, "QR", 454.60, 459.60, 0 }, + { UHF, "QY", 454.625, 459.625, 0 }, + { UHF, "QF", 454.65, 459.65, 0 }, + { 0, NULL, 0.0, 0.0, 0} +}; + +void imts_list_channels(void) +{ + int last_band = -1; + int i; + + for (i = 0; channel_info[i].name; i++) { + if (last_band != channel_info[i].band) { + last_band = channel_info[i].band; + printf("\n%s:\n\n", band_name[channel_info[i].band]); + printf("Channel\t\tDownlink\tUplink\t\tCountry\n"); + printf("----------------------------------------------------------------\n"); + } + printf("%s\t\t%.3f MHz\t%.3f MHz\t%s\n", channel_info[i].name, channel_info[i].downlink_mhz, channel_info[i].uplink_mhz, (channel_info[i].canada_only) ? "USA + Canada" : "USA"); + } + printf("\n"); +} + +/* Timers */ +#define PAGING_TO 4.0 /* Time to wait for the phone to respond */ +#define RINGING_TO 45.0 /* Time to wait for the mobile user to answer */ +#define SEIZE_TO 1.0 /* Time to wait for the phone to seize (Connect tone) */ +#define ANI_TO 1.000 /* Time to wait for first / next digit */ +#define DIALTONE_TO 10.0 /* Time to wait until dialing must be performed */ +#define DIALING_TO 3.0 /* Time to wait until number is recognized as complete */ +#define RELEASE_TO 0.350 /* Time to turn off transmitter before going idle ".. for about 300 ms .." */ +#define ANI_PULSE_TO 0.100 /* Time to detect end of digit */ +#define DIAL_PULSE_TO 0.200 /* Time to detect end of digit */ +#define DISC_PULSE_TO 0.100 /* Time until aborting disconnect detection */ +#define PAGE_PULSE_TO 0.200 /* Time to detect end of digit */ + +/* Counters */ +#define DISC_COUNT 6 /* Number of pulses to detect disconnect FIXME */ +#define RING_PULSES 40 /* 2 seconds ringer on */ + +/* Durations */ +#define IDLE_DETECT 0.500 /* Time to detect Idle signal (loopback) */ +#define PAGE_SEIZE 0.400 /* Time to seize channel until start paging pulses FIXME */ +#define PAGE_PAUSE 0.400 /* Time to pause after each digit FIXME */ +#define PAGE_MARK 0.050 /* Mark duration of page pulse */ +#define PAGE_SPACE 0.050 /* Space duration of page pulse */ +#define PAGE_PULSE 0.100 /* Duration of a complete pulse (MTS) */ +#define RING_MARK 0.025 /* Mark duration of ring pulse */ +#define RING_SPACE 0.025 /* Space duration of ring pulse */ +#define RING_OFF 4.0 /* 4 seconds ringer off */ +#define GUARD_TIME 0.200 /* Time until detecting Guard tone from mobile */ +#define SEIZE_TIME 0.300 /* Time until sending Seize tone >= 250 */ +#define SEIZE_LENGTH 0.250 /* Length of Seize */ +#define RECEIVE_TIME 0.200 /* Time until detecting receive signal (Guard tone) from mobile */ +#define ANSWER_TIME 0.200 /* Time until detecting answer signal (Connect tone) from mobile */ + +const char *imts_state_name(enum imts_state state) +{ + static char invalid[16]; + + switch (state) { + case IMTS_NULL: + return "(NULL)"; + case IMTS_OFF: + return "IDLE (off)"; + case IMTS_IDLE: + return "IDLE (tone)"; + case IMTS_SEIZE: + return "SEIZE (mobile call)"; + case IMTS_ANI: + return "ANI (mobile call)"; + case IMTS_DIALING: + return "DIALING (mobile call)"; + case IMTS_PAGING: + return "PAGING (station call)"; + case IMTS_RINGING: + return "RINGING (station call)"; + case IMTS_CONVERSATION: + return "CONVERSATION"; + case IMTS_RELEASE: + return "RELEASE"; + case IMTS_PAGING_TEST: + return "PAGING TEST"; + case IMTS_DETECTOR_TEST: + return "DETECTOR TEST"; + } + + sprintf(invalid, "invalid(%d)", state); + return invalid; +} + +static void imts_display_status(void) +{ + sender_t *sender; + imts_t *imts; + + display_status_start(); + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + display_status_channel(imts->sender.kanal, NULL, imts_state_name(imts->state)); + if (imts->station_id[0]) + display_status_subscriber(imts->station_id, NULL); + } + display_status_end(); +} + +static void imts_new_state(imts_t *imts, enum imts_state new_state) +{ + if (imts->state == new_state) + return; + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "State change: %s -> %s\n", imts_state_name(imts->state), imts_state_name(new_state)); + imts->state = new_state; + imts_display_status(); +} + +/* Convert channel name to frequency number of base station. + Set 'uplink' to 1 to get frequency of mobile station. */ +double imts_channel2freq(const char *kanal, int uplink) +{ + int i; + + for (i = 0; channel_info[i].name; i++) { + if (!strcasecmp(channel_info[i].name, kanal)) { + if (uplink == 2) + return (channel_info[i].downlink_mhz - channel_info[i].uplink_mhz) * 1e6; + else if (uplink) + return channel_info[i].uplink_mhz * 1e6; + else + return channel_info[i].downlink_mhz * 1e6; + } + } + + return 0.0; +} + +int imts_channel2band(const char *kanal) +{ + int i; + + for (i = 0; channel_info[i].name; i++) { + if (!strcasecmp(channel_info[i].name, kanal)) + return channel_info[i].band; + } + + return 0.0; +} + +double imts_is_canada_only(const char *kanal) +{ + int i; + + for (i = 0; channel_info[i].name; i++) { + if (!strcasecmp(channel_info[i].name, kanal)) + return channel_info[i].canada_only; + } + + return 0; +} + +/* global init */ +int imts_init(void) +{ + return 0; +} + +static void imts_timeout(struct timer *timer); +static void imts_go_idle(imts_t *imts); +static void imts_paging(imts_t *imts, const char *dial_string, int loopback); +static void imts_detector_test(imts_t *imts, double length_1, double length_2, double length_3); + +/* Create transceiver instance and link to a list. */ +int imts_create(const char *kanal, 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) +{ + imts_t *imts; + int rc; + + if (imts_channel2freq(kanal, 0) == 0.0) { + PDEBUG(DIMTS, DEBUG_ERROR, "Channel number %s invalid.\n", kanal); + return -EINVAL; + } + if (imts_is_canada_only(kanal)) { + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + PDEBUG(DIMTS, DEBUG_NOTICE, "Given channel '%s' was only available in Canada with Canadian phones.\n", kanal); + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + } + if (mode == MODE_IMTS && imts_channel2band(kanal) == VHF_LOW) { + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + PDEBUG(DIMTS, DEBUG_NOTICE, "Given channel '%s' was only available at MTS network.\n", kanal); + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + return -EINVAL; + } + if (mode == MODE_MTS && imts_channel2band(kanal) == UHF) { + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + PDEBUG(DIMTS, DEBUG_NOTICE, "Given channel '%s' was only available at IMTS network.\n", kanal); + PDEBUG(DIMTS, DEBUG_NOTICE, "*******************************************************************************\n"); + return -EINVAL; + } + + imts = calloc(1, sizeof(imts_t)); + if (!imts) { + PDEBUG(DIMTS, DEBUG_ERROR, "No memory!\n"); + return -EIO; + } + + PDEBUG(DIMTS, DEBUG_DEBUG, "Creating 'IMTS' instance for channel = %s (sample rate %d).\n", kanal, samplerate); + + imts->station_length = station_length; + imts->fast_seize = fast_seize; + imts->mode = mode; + imts->operator = operator; + imts->ptt = ptt; + + /* init general part of transceiver */ + /* do not enable emphasis, since it is done by imts code, not by common sender code */ + rc = sender_create(&imts->sender, kanal, imts_channel2freq(kanal, 0), imts_channel2freq(kanal, 1), audiodev, use_sdr, samplerate, rx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE); + if (rc < 0) { + PDEBUG(DIMTS, DEBUG_ERROR, "Failed to init 'Sender' processing!\n"); + goto error; + } + + /* init audio processing */ + rc = dsp_init_transceiver(imts, squelch_db, ptt); + if (rc < 0) { + PDEBUG(DIMTS, DEBUG_ERROR, "Failed to init signal processing!\n"); + goto error; + } + + timer_init(&imts->timer, imts_timeout, imts); + + imts->pre_emphasis = pre_emphasis; + imts->de_emphasis = de_emphasis; + rc = init_emphasis(&imts->estate, samplerate, CUT_OFF_EMPHASIS_IMTS, CUT_OFF_HIGHPASS_DEFAULT, CUT_OFF_LOWPASS_DEFAULT); + if (rc < 0) + goto error; + + if (length_1 > 0.0 || length_2 > 0.0 || length_3 > 0.0) { + imts_detector_test(imts, length_1, length_2, length_3); + /* go into detector test state */ + } else if (loopback) { + /* go into loopback test state */ + imts_paging(imts, "1234567890", loopback); + } else { + /* go into idle state */ + imts_go_idle(imts); + } + + PDEBUG(DIMTS, DEBUG_NOTICE, "Created channel #%s\n", kanal); + + return 0; + +error: + imts_destroy(&imts->sender); + + return rc; +} + +/* Destroy transceiver instance and unlink from list. */ +void imts_destroy(sender_t *sender) +{ + imts_t *imts = (imts_t *) sender; + + PDEBUG(DIMTS, DEBUG_DEBUG, "Destroying 'IMTS' instance for channel = %s.\n", sender->kanal); + + timer_exit(&imts->timer); + dsp_cleanup_transceiver(imts); + sender_destroy(&imts->sender); + free(sender); +} + +/* Return to IDLE */ +static void imts_go_idle(imts_t *imts) +{ + sender_t *sender; + imts_t *idle; + + timer_stop(&imts->timer); + imts->station_id[0] = '\0'; /* remove station ID before state change, so status is shown correctly */ + + for (sender = sender_head; sender; sender = sender->next) { + idle = (imts_t *) sender; + if (idle == imts) + continue; + if (idle->state == IMTS_IDLE) + break; + } + if (sender) { + PDEBUG(DIMTS, DEBUG_INFO, "Entering IDLE state on channel %s, turning transmitter off.\n", imts->sender.kanal); + imts_new_state(imts, IMTS_OFF); + imts_set_dsp_mode(imts, DSP_MODE_OFF, 0, 0.0, 0); + } else { + if (imts->mode == MODE_IMTS) { + PDEBUG(DIMTS, DEBUG_INFO, "Entering IDLE state on channel %s, sending 2000 Hz tone.\n", imts->sender.kanal); + imts_new_state(imts, IMTS_IDLE); + /* also reset detector, so if there is a new call it is answered */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, 0.0, 1); + } else { + PDEBUG(DIMTS, DEBUG_INFO, "Entering IDLE state on channel %s, sending 600 Hz tone.\n", imts->sender.kanal); + imts_new_state(imts, IMTS_IDLE); + /* also reset detector, so if there is a new call it is answered */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_600, 0.0, 1); + } + } +} + +/* If a channel is occupied, we need to hunt for an IDLE channel to be turned on. */ +static void imts_activate_idle(void) +{ + sender_t *sender; + imts_t *idle; + + for (sender = sender_head; sender; sender = sender->next) { + idle = (imts_t *) sender; + if (idle->state == IMTS_OFF) + break; + } + if (sender) + imts_go_idle(idle); + else + PDEBUG(DIMTS, DEBUG_INFO, "All channels are busy now, cannot activate any other channel.\n"); +} + +/* Release connection towards mobile station by sending pause for a while. */ +static void imts_release(imts_t *imts) +{ + timer_stop(&imts->timer); + /* remove station ID before state change, so status is shown correctly */ + imts->station_id[0] = '\0'; + + if (imts->mode == MODE_MTS && imts->state == IMTS_RINGING) { + /* + * In MTS mode we abort ringing by sending pulse. + * Afterwards we call imts_release again and turn transmitter off. + */ + int tone; + if (imts->tone == TONE_1500) + tone = TONE_600; + else + tone = TONE_1500; + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Sending pulse to stop ringing of the phone.\n"); + imts_new_state(imts, IMTS_RELEASE); + imts_set_dsp_mode(imts, DSP_MODE_TONE, tone, PAGE_PAUSE, 0); + } else { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Turing transmitter off.\n"); + if (imts->state != IMTS_RELEASE) + imts_new_state(imts, IMTS_RELEASE); + imts_set_dsp_mode(imts, DSP_MODE_OFF, 0, 0.0, 0); + timer_start(&imts->timer, RELEASE_TO); + } +} + +/* Enter detector test state */ +static void imts_detector_test(imts_t *imts, double length_1, double length_2, double length_3) +{ + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Entering detector test state, sending test sequence.\n"); + imts->detector_test_length_1 = length_1; + imts->detector_test_length_2 = length_2; + imts->detector_test_length_3 = length_3; + imts_new_state(imts, IMTS_DETECTOR_TEST); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, 1.0, 0); + +} + +/* Enter paging state */ +static void imts_paging(imts_t *imts, const char *dial_string, int loopback) +{ + /* stop timer, since it may be running while measuring Guard tone at IDLE state */ + timer_stop(&imts->timer); + + if (loopback) + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Entering paging test state, sending digits %s.\n", dial_string); + else + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Entering paging state, sending phone's ID '%s'.\n", dial_string); + /* set station ID before state change, so status is shown correctly */ + strncpy(imts->station_id, dial_string, sizeof(imts->station_id) - 1); + imts->tx_page_index = 0; + imts->tx_page_pulse = 0; + imts_new_state(imts, (loopback) ? IMTS_PAGING_TEST : IMTS_PAGING); + imts_activate_idle(); /* must activate another channel right after station is not idle anymore */ + if (imts->mode == MODE_IMTS) { + /* seize channel before dialing */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, PAGE_SEIZE, 0); + } else { + /* send single pulse + pause, which causes the call selector to reset */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_1500, PAGE_PAUSE, 0); + /* for loopback test, we need time stamp */ + imts->tx_page_timestamp = get_time(); + } +} + +/* Enter ringing state */ +static void imts_ringing(imts_t *imts) +{ + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received response from mobile phone, ringing.\n"); + imts->tx_ring_pulse = 0; + imts_new_state(imts, IMTS_RINGING); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, RING_MARK, 0); + timer_start(&imts->timer, RINGING_TO); +} + +/* Enter conversation state */ +static void imts_answer(imts_t *imts) +{ + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received answer from mobile phone, conversation started.\n"); + timer_stop(&imts->timer); + imts_new_state(imts, IMTS_CONVERSATION); + imts_set_dsp_mode(imts, DSP_MODE_AUDIO, 0, 0.0, 0); + imts->rx_disc_pulse = 0; +} + +/* Loss of signal was detected, release active call. */ +void imts_loss_indication(imts_t *imts, double loss_time) +{ + /* stop timer */ + if (imts->mode == MODE_MTS && (imts->state == IMTS_IDLE || imts->state == IMTS_RINGING)) + timer_stop(&imts->timer); + + if (!imts->ptt && imts->state == IMTS_CONVERSATION) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Detected loss of signal after %.1f seconds, releasing.\n", loss_time); + imts_release(imts); + call_up_release(imts->callref, CAUSE_TEMPFAIL); + imts->callref = 0; + } +} + +/* if signal is detected, phone picked up */ +void imts_signal_indication(imts_t *imts) +{ + /* setup a call from mobile to base station */ + if (imts->mode == MODE_MTS && imts->state == IMTS_IDLE) { + int callref = ++new_callref; + int rc; + + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Detectes RF signal in IDLE mode, calling the opterator at '%s'.\n", imts->operator); + rc = call_up_setup(callref, NULL, imts->operator); + if (rc < 0) { + PDEBUG_CHAN(DANETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing!\n", -rc); + imts_release(imts); + return; + } + imts->callref = callref; + imts_new_state(imts, IMTS_CONVERSATION); + imts_set_dsp_mode(imts, DSP_MODE_AUDIO, 0, 0.0, 0); + } + + /* answer a call from base station to mobile */ + if (imts->mode == MODE_MTS && imts->state == IMTS_RINGING) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Detected RF signal, mobile is now transmitting.\n"); + call_up_answer(imts->callref, imts->station_id); + imts_answer(imts); + } +} + +/* decode seize from mobile */ +static void imts_receive_seize(imts_t *imts, int tone) +{ + /* other tone stops IDLE / GUARD timer */ + timer_stop(&imts->timer); + + switch (tone) { + case TONE_IDLE: + case TONE_600: + timer_start(&imts->timer, IDLE_DETECT); + break; + case TONE_GUARD: + imts->rx_guard_timestamp = get_time(); + if (imts->fast_seize) + timer_start(&imts->timer, imts->fast_seize); + break; + case TONE_CONNECT: + if (imts->last_tone == TONE_GUARD && imts->rx_guard_duration >= GUARD_TIME) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received seize (Guard + Connect tone) from mobile phone.\n"); + PDEBUG_CHAN(DIMTS, DEBUG_INFO, " -> Guard tone duration: %.0f ms (level %.0f%%)\n", (get_time() - imts->rx_guard_timestamp) * 1000.0, imts->last_sigtone_amplitude * 100.0); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, 0.0, 0); + imts_new_state(imts, IMTS_SEIZE); + imts_activate_idle(); /* must activate another channel right after station is not idle anymore */ + timer_start(&imts->timer, SEIZE_TIME); + } + break; + default: + ; + } +} + +/* decode ANI digits */ +static void imts_receive_ani(imts_t *imts, int tone) +{ + /* wait for connect tone the first time */ + if (!imts->rx_ani_totpulses && tone != TONE_CONNECT) + return; + + switch (tone) { + case TONE_CONNECT: + /* pulse detected */ + imts->rx_ani_pulse++; + imts->rx_ani_totpulses++; + if (imts->rx_ani_pulse > 10) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received too many pulses, releasing!\n"); + imts_release(imts); + break; + } + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Detected ANI pulse #%d.\n", imts->rx_ani_pulse); + timer_start(&imts->timer, ANI_PULSE_TO); + break; + case TONE_GUARD: + /* even pulse completed */ + if ((imts->rx_ani_totpulses & 1)) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Parity error: Received Guard tone after %d (odd) pulses, releasing!\n", imts->rx_ani_totpulses); + imts_release(imts); + break; + } + timer_start(&imts->timer, ANI_PULSE_TO); + break; + case TONE_SILENCE: + /* odd pulse completed */ + if (!(imts->rx_ani_totpulses & 1)) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Parity error: Received silence after %d (even) pulses, releasing!\n", imts->rx_ani_totpulses); + imts_release(imts); + break; + } + timer_start(&imts->timer, ANI_PULSE_TO); + break; + default: + /* received noise */ + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received noise while dialing, releasing!\n"); + imts_release(imts); + } +} + +/* decode dialing digits */ +static void imts_receive_dialing(imts_t *imts, int tone) +{ + switch (tone) { + case TONE_CONNECT: + /* turn off dialtone */ + if (!imts->dial_number[0] && !imts->rx_dial_pulse) + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, 0.0, 0); + /* pulse detected */ + imts->rx_dial_pulse++; + if (imts->rx_dial_pulse > 10) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received too many pulses, releasing!\n"); + imts_release(imts); + break; + } + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Detected dialing pulse #%d.\n", imts->rx_dial_pulse); + timer_start(&imts->timer, DIAL_PULSE_TO); + break; + case TONE_GUARD: + /* pulse completed */ + if (imts->rx_dial_pulse) + timer_start(&imts->timer, DIAL_PULSE_TO); + break; + default: + ; +#if 0 + /* received noise */ + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received noise while dialing, releasing!\n"); + imts_release(imts); +#endif + } +} + +/* check for disconnect */ +static void imts_receive_disconnect(imts_t *imts, int tone, double elapsed, double amplitude) +{ + /* reset disc counter on timeout */ + if (elapsed > DISC_PULSE_TO) { + if (imts->rx_disc_pulse) { + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Timeout Disconnect sequence\n"); + imts->rx_disc_pulse = 0; + } + return; + } + + switch (tone) { + case TONE_DISCONNECT: + imts->rx_disc_pulse++; + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Detected Disconnect pulse #%d.\n", imts->rx_disc_pulse); + break; + case TONE_GUARD: + if (imts->rx_disc_pulse == DISC_COUNT) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received disconnect sequence from mobile phone (level %.0f%%).\n", amplitude * 100.0); + if (imts->state == IMTS_SEIZE + || imts->state == IMTS_ANI + || imts->state == IMTS_DIALING + || imts->state == IMTS_PAGING + || imts->state == IMTS_RINGING + || imts->state == IMTS_CONVERSATION) { + if (imts->callref) + call_up_release(imts->callref, CAUSE_NORMAL); + imts_release(imts); + } + break; + } + break; + default: + if (imts->rx_disc_pulse) { + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Disconnect sequence not detected anymore\n"); + imts->rx_disc_pulse = 0; + } + } +} + +/* decode page test digits */ +static void receive_page_imts(imts_t *imts, int tone) +{ + switch (tone) { + case TONE_IDLE: + /* pulse detected */ + imts->rx_page_pulse++; + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Detected page test pulse #%d.\n", imts->rx_page_pulse); + if (imts->rx_page_pulse > 10) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received too many pulses!\n"); + } + timer_start(&imts->timer, PAGE_PULSE_TO); // use for page test timeout + break; + case TONE_SEIZE: + /* pulse completed */ + timer_start(&imts->timer, PAGE_PULSE_TO); // use for page test timeout + break; + default: + ; + } +} + +/* decode page test digits */ +static void receive_page_mts(imts_t *imts, int tone) +{ + if (tone == TONE_600 || tone == TONE_1500) { + /* pulse detected */ + imts->rx_page_pulse++; + PDEBUG_CHAN(DIMTS, DEBUG_DEBUG, "Detected page test pulse #%d.\n", imts->rx_page_pulse); + if (imts->rx_page_pulse > 10) { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received too many pulses!\n"); + } + timer_start(&imts->timer, PAGE_PULSE_TO); // use for page test timeout + } +} + +/* A tone was detected or is gone. */ +void imts_receive_tone(imts_t *imts, int tone, double elapsed, double amplitude) +{ + /* used for several states */ + imts_receive_disconnect(imts, tone, elapsed, amplitude); + + switch (imts->state) { + case IMTS_IDLE: + imts_receive_seize(imts, tone); + break; + case IMTS_ANI: + imts_receive_ani(imts, tone); + break; + case IMTS_DIALING: + imts_receive_dialing(imts, tone); + break; + case IMTS_CONVERSATION: + break; + case IMTS_PAGING_TEST: + if (imts->mode == MODE_IMTS) + receive_page_imts(imts, tone); + else + receive_page_mts(imts, tone); + break; + default: + ; + } + + /* remember last tone, also store amplitude of last signaling tone */ + imts->last_tone = tone; + if (imts->last_tone < NUM_SIG_TONES) + imts->last_sigtone_amplitude = amplitude; +} + +void imts_lost_tone(imts_t *imts, int tone, double elapsed) +{ + switch (imts->state) { + case IMTS_IDLE: + timer_stop(&imts->timer); + imts->rx_guard_duration = elapsed; + break; + case IMTS_PAGING: + if (elapsed >= 0.300 && tone == TONE_GUARD && timer_running(&imts->timer)) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received acknowledge (Guard tone) from mobile phone (level %.0f%%).\n", imts->last_sigtone_amplitude * 100.0); + call_up_alerting(imts->callref); + imts_ringing(imts); + break; + } + break; + case IMTS_RINGING: + if (elapsed >= 0.190 && tone == TONE_CONNECT) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received answer (Connect tone) from mobile phone (level %.0f%%).\n", imts->last_sigtone_amplitude * 100.0); + call_up_answer(imts->callref, imts->station_id); + imts_answer(imts); + break; + } + break; + default: + ; + } +} + +static void ani_after_digit(imts_t *imts) +{ + /* timeout after pulses: digit complete + * timeout after digit: ANI in complete + */ + if (imts->rx_ani_pulse) { + if (imts->rx_ani_pulse == 10) + imts->rx_ani_pulse = 0; + imts->station_id[imts->rx_ani_index] = imts->rx_ani_pulse + '0'; + imts->rx_ani_pulse = 0; + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received ANI digit '%c' from mobile phone (level %.0f%%).\n", imts->station_id[imts->rx_ani_index], imts->last_sigtone_amplitude * 100.0); + imts->station_id[++imts->rx_ani_index] = '\0'; + /* update status while receiving station ID */ + imts_display_status(); + /* if all digits have been received */ + if (imts->rx_ani_index == imts->station_length) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "ANI '%s' complete, sending dial tone.\n", imts->station_id); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_DIALTONE, 0.0, 0); + timer_start(&imts->timer, DIALTONE_TO); + imts->dial_number[0] = '\0'; + imts->rx_dial_index = 0; + imts->rx_dial_pulse = 0; + imts_new_state(imts, IMTS_DIALING); + return; + } + timer_start(&imts->timer, ANI_TO); + } else { + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Timeout receiving ANI from mobile phone, releasing!\n"); + imts_release(imts); + } +} + +static void dial_after_digit(imts_t *imts) +{ + /* special case where nothing happens after dial tone */ + if (!imts->rx_dial_pulse && !imts->rx_dial_index) { + PDEBUG_CHAN(DANETZ, DEBUG_NOTICE, "Mobile phone does not start dialing, releasing!\n"); + imts_release(imts); + return; + } + + /* timeout after pulses: digit complete + * timeout after digit: number complete + */ + if (imts->rx_dial_pulse) { + if (imts->rx_dial_index == sizeof(imts->dial_number) - 1) { + PDEBUG_CHAN(DANETZ, DEBUG_NOTICE, "Mobile phone dials too many digits, releasing!\n"); + imts_release(imts); + return; + } + if (imts->rx_dial_pulse == 10) + imts->rx_dial_pulse = 0; + imts->dial_number[imts->rx_dial_index] = imts->rx_dial_pulse + '0'; + imts->rx_dial_pulse = 0; + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received dial digit '%c' from mobile phone. (level %.0f%%)\n", imts->dial_number[imts->rx_dial_index], imts->last_sigtone_amplitude * 100.0); + imts->dial_number[++imts->rx_dial_index] = '\0'; + timer_start(&imts->timer, DIALING_TO); + } else { + int callref = ++new_callref; + int rc; + + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Timeout receiving dialing from mobile phone, number complete.\n"); + rc = call_up_setup(callref, imts->station_id, imts->dial_number); + if (rc < 0) { + PDEBUG_CHAN(DANETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing!\n", -rc); + imts_release(imts); + return; + } + imts->callref = callref; + imts_new_state(imts, IMTS_CONVERSATION); + imts_set_dsp_mode(imts, DSP_MODE_AUDIO, 0, 0.0, 0); + imts->rx_disc_pulse = 0; + } +} + +static void page_after_digit(imts_t *imts) +{ + char digit; + double delay; + + /* timeout after pulses: digit complete */ + if (imts->rx_page_pulse) { + if (imts->rx_page_pulse < 11) { + if (imts->rx_page_pulse == 10) + imts->rx_page_pulse = 0; + digit = imts->rx_page_pulse + '0'; + delay = get_time() - imts->tx_page_timestamp - PAGE_PULSE_TO; + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Received paging test digit '%c' (level %.0f%% delay %.0f ms).\n", digit, imts->last_sigtone_amplitude * 100.0, delay * 1000.0); + } + imts->rx_page_pulse = 0; + } +} + +/* Timeout handling */ +static void imts_timeout(struct timer *timer) +{ + imts_t *imts = (imts_t *)timer->priv; + + switch (imts->state) { + case IMTS_IDLE: + switch (imts->last_tone) { + case TONE_IDLE: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received idle tone (level of %.0f%%), loopback?\n", imts->last_sigtone_amplitude * 100.0); + /* trigger reset of decoder to force detection again and again */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, 0.0, 1); + break; + case TONE_600: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received 600 Hz tone with level of %.0f%%, loopback?\n", imts->last_sigtone_amplitude * 100.0); + /* trigger reset of decoder to force detection again and again */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_600, 0.0, 1); + break; + case TONE_GUARD: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Received Guard tone, turning off IDLE tone\n"); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, 0.5, 0); + break; + } + break; + case IMTS_PAGING: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "No response from mobile phone.\n"); + imts_go_idle(imts); + call_up_release(imts->callref, CAUSE_OUTOFORDER); + imts->callref = 0; + break; + case IMTS_RINGING: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "No answer from mobile phone's user, releasing.\n"); + imts_release(imts); + call_up_release(imts->callref, CAUSE_NOANSWER); + imts->callref = 0; + break; + case IMTS_RELEASE: + imts_go_idle(imts); + break; + case IMTS_SEIZE: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending Seize to mobile phone.\n"); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, SEIZE_LENGTH, 0); + timer_start(&imts->timer, SEIZE_LENGTH + ANI_TO); + imts->station_id[0] = '\0'; + imts->rx_ani_index = 0; + imts->rx_ani_pulse = 0; + imts->rx_ani_totpulses = 0; + imts_new_state(imts, IMTS_ANI); + break; + case IMTS_ANI: + ani_after_digit(imts); + break; + case IMTS_DIALING: + dial_after_digit(imts); + break; + case IMTS_PAGING_TEST: + page_after_digit(imts); + break; + default: + ; + } +} + +/* generate pulse sequence to page phone */ +static void paging_pulses_imts(imts_t *imts, int tone) +{ + double duration; + int pulses; + + pulses = imts->station_id[imts->tx_page_index] - '0'; + if (pulses == 0) + pulses = 10; + + if (tone == TONE_SEIZE) { + if (imts->tx_page_pulse == 0) + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Sending paging digit '%c' as pulses.\n", imts->station_id[imts->tx_page_index]); + /* send mark (pulse start) */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, PAGE_MARK, 0); + imts->tx_page_pulse++; + } else { + if (imts->tx_page_pulse <= pulses) { + /* send space (pulse end), use long space after last pulse */ + if (imts->tx_page_pulse < pulses) + duration = PAGE_SPACE; + else { + imts->tx_page_pulse = 0; + imts->tx_page_index++; + imts->tx_page_timestamp = get_time(); + /* restart test pattern */ + if (!imts->station_id[imts->tx_page_index] && imts->state == IMTS_PAGING_TEST) + imts->tx_page_index = 0; + if (imts->station_id[imts->tx_page_index]) + duration = PAGE_PAUSE; + else { + duration = 0; + timer_start(&imts->timer, PAGING_TO); + } + } + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, duration, 0); + } + } +} +static void paging_pulses_mts(imts_t *imts, int tone) +{ + double duration; + int pulses; + + pulses = imts->station_id[imts->tx_page_index] - '0'; + if (pulses == 0) + pulses = 10; + + if (tone == TONE_1500) + tone = TONE_600; + else + tone = TONE_1500; + + if (imts->tx_page_pulse == 0) { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Sending paging digit '%c' as pulses.\n", imts->station_id[imts->tx_page_index]); + } + imts->tx_page_pulse++; + if (imts->tx_page_pulse < pulses) + duration = PAGE_PULSE; + else { + imts->tx_page_pulse = 0; + imts->tx_page_index++; + imts->tx_page_timestamp = get_time(); + /* restart test pattern */ + if (!imts->station_id[imts->tx_page_index] && imts->state == IMTS_PAGING_TEST) + imts->tx_page_index = 0; + if (imts->station_id[imts->tx_page_index]) + duration = PAGE_PAUSE; + else { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Digits complete, assuming the phone is ringing.\n"); + duration = 0; + imts_new_state(imts, IMTS_RINGING); + call_up_alerting(imts->callref); + } + } + imts_set_dsp_mode(imts, DSP_MODE_TONE, tone, duration, 0); +} + +/* generate pulse sequence to ring phone */ +static void ringing_pulses(imts_t *imts, int tone) +{ + if (tone == TONE_IDLE) { + if (imts->tx_ring_pulse == 0) + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Sending ringing signal as pulses.\n"); + /* send space (pulse end) */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, RING_SPACE, 0); + imts->tx_ring_pulse++; + } else { + if (imts->tx_ring_pulse < RING_PULSES) { + /* send mark (pulse start) */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, RING_MARK, 0); + } else { + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Sending pause after ringing.\n"); + /* send long space after last pulse */ + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, RING_OFF, 0); + imts->tx_ring_pulse = 0; + } + } +} + +/* after sending Seize tone switch to silence and await ANI */ +static void seize_sent(imts_t *imts, int tone) +{ + if (tone == TONE_SEIZE) { + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, 0.0, 0); + } +} + +/* send test pattern (cycle through tones, skip if length is 0) */ +static void detector_test_imts(imts_t *imts, int tone) +{ + switch (tone) { + case TONE_SILENCE: +tone_idle: + if (imts->detector_test_length_1 <= 0) + goto tone_seize; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs IDLE tone.\n", imts->detector_test_length_1); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, imts->detector_test_length_1, 0); + break; + case TONE_IDLE: +tone_seize: + if (imts->detector_test_length_2 <= 0) + goto tone_silence; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs SEIZE tone.\n", imts->detector_test_length_2); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SEIZE, imts->detector_test_length_2, 0); + break; + case TONE_SEIZE: +tone_silence: + if (imts->detector_test_length_3 <= 0) + goto tone_idle; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs SILENCE.\n", imts->detector_test_length_3); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, imts->detector_test_length_3, 0); + break; + } +} +static void detector_test_mts(imts_t *imts, int tone) +{ + switch (tone) { + case TONE_SILENCE: +tone_idle: + if (imts->detector_test_length_1 <= 0) + goto tone_seize; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs 600 Hz tone.\n", imts->detector_test_length_1); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_600, imts->detector_test_length_1, 0); + break; + case TONE_600: +tone_seize: + if (imts->detector_test_length_2 <= 0) + goto tone_silence; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs 1500 Hz tone.\n", imts->detector_test_length_2); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_1500, imts->detector_test_length_2, 0); + break; + case TONE_1500: +tone_silence: + if (imts->detector_test_length_3 <= 0) + goto tone_idle; + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Sending %.3fs SILENCE.\n", imts->detector_test_length_3); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_SILENCE, imts->detector_test_length_3, 0); + break; + } +} + +/* whenever a tone has been sent (only for tones with given duration) */ +void imts_tone_sent(imts_t *imts, int tone) +{ + switch (imts->state) { + case IMTS_IDLE: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "No Seize tone after Guard tone, turning on IDLE tone\n"); + imts_set_dsp_mode(imts, DSP_MODE_TONE, TONE_IDLE, 0.0, 0); + break; + case IMTS_ANI: + seize_sent(imts, tone); + break; + case IMTS_PAGING: + case IMTS_PAGING_TEST: + if (imts->mode == MODE_IMTS) + paging_pulses_imts(imts, tone); + else + paging_pulses_mts(imts, tone); + break; + case IMTS_RINGING: + ringing_pulses(imts, tone); + break; + case IMTS_RELEASE: + imts_release(imts); + break; + case IMTS_DETECTOR_TEST: + if (imts->mode == MODE_IMTS) + detector_test_imts(imts, tone); + else + detector_test_mts(imts, tone); + break; + default: + ; + } +} + +/* Call control starts call towards mobile station. */ +int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing) +{ + char number[8]; + sender_t *sender; + imts_t *imts; + int i; + + /* 1. check if given number is already in a call, return BUSY */ + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + if (!strcmp(imts->station_id, dialing)) + break; + } + if (sender) { + PDEBUG(DIMTS, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n"); + return -CAUSE_BUSY; + } + + /* 2. check if all channels are busy, return NOCHANNEL */ + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + if (imts->state == IMTS_IDLE) + break; + } + if (!sender) { + PDEBUG(DIMTS, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); + return -CAUSE_NOCHANNEL; + } + + /* 3. check if number is invalid, return INVALNUMBER */ + if (strlen(dialing) == 12 && !strncmp(dialing, "+1", 2)) + dialing += 2; + if (strlen(dialing) == 11 && !strncmp(dialing, "1", 1)) + dialing += 1; + if (strlen(dialing) == 10 && imts->station_length == 7) { + strncpy(number, dialing, 3); + strcpy(number + 3, dialing + 6); + dialing = number; + } + if (strlen(dialing) == 10 && imts->station_length == 5) + dialing += 5; + if ((int)strlen(dialing) != imts->station_length) { +inval: + PDEBUG(DIMTS, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); + return -CAUSE_INVALNUMBER; + } + for (i = 0; i < (int)strlen(dialing); i++) { + if (dialing[i] < '0' || dialing[i] > '9') + goto inval; + } + + PDEBUG_CHAN(DIMTS, DEBUG_INFO, "Call to mobile station, paging number: %s\n", dialing); + + /* 4. trying to page mobile station */ + imts->callref = callref; + imts_paging(imts, dialing, 0); + + return 0; +} + +void call_down_answer(int __attribute__((unused)) callref) +{ +} + +/* Call control sends disconnect (with tones). + * An active call stays active, so tones and annoucements can be received + * by mobile station. + */ +void call_down_disconnect(int callref, int cause) +{ + sender_t *sender; + imts_t *imts; + + PDEBUG(DIMTS, DEBUG_INFO, "Call has been disconnected by network.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + if (imts->callref == callref) + break; + } + if (!sender) { + PDEBUG(DIMTS, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); + call_up_release(callref, CAUSE_INVALCALLREF); + return; + } + + /* Release when not active */ + if (imts->state == IMTS_CONVERSATION) + return; + switch (imts->state) { + case IMTS_PAGING: + case IMTS_RINGING: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Outgoing disconnect, during paging/alerting, releasing!\n"); + imts_release(imts); + break; + default: + break; + } + + call_up_release(callref, cause); + + imts->callref = 0; + +} + +/* Call control releases call toward mobile station. */ +void call_down_release(int callref, __attribute__((unused)) int cause) +{ + sender_t *sender; + imts_t *imts; + + PDEBUG(DIMTS, DEBUG_INFO, "Call has been released by network, releasing call.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + if (imts->callref == callref) + break; + } + if (!sender) { + PDEBUG(DIMTS, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); + /* don't send release, because caller already released */ + return; + } + + imts->callref = 0; + + switch (imts->state) { + case IMTS_CONVERSATION: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Outgoing release, during call, releasing!\n"); + imts_release(imts); + break; + case IMTS_PAGING: + case IMTS_RINGING: + PDEBUG_CHAN(DIMTS, DEBUG_NOTICE, "Outgoing release, during paging/alerting, releasing!\n"); + imts_release(imts); + break; + default: + break; + } +} + +/* Receive audio from call instance. */ +void call_down_audio(int callref, sample_t *samples, int count) +{ + sender_t *sender; + imts_t *imts; + + for (sender = sender_head; sender; sender = sender->next) { + imts = (imts_t *) sender; + if (imts->callref == callref) + break; + } + if (!sender) + return; + + if (imts->dsp_mode == DSP_MODE_AUDIO) { + sample_t up[(int)((double)count * imts->sender.srstate.factor + 0.5) + 10]; + count = samplerate_upsample(&imts->sender.srstate, samples, count, up); + jitter_save(&imts->sender.dejitter, up, count); + } +} + +void dump_info(void) {} + diff --git a/src/imts/imts.h b/src/imts/imts.h new file mode 100644 index 0000000..88b3da8 --- /dev/null +++ b/src/imts/imts.h @@ -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); + diff --git a/src/imts/main.c b/src/imts/main.c new file mode 100644 index 0000000..7263542 --- /dev/null +++ b/src/imts/main.c @@ -0,0 +1,289 @@ +/* MTS/IMTS main + * + * (C) 2019 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#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 | 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 \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 \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 \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> \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(); +} + +static void add_options(void) +{ + main_mobile_add_options(); + option_add('S', "squelch", 1); + option_add('P', "push-to-talk", 0); + option_add('5', "five", 0); + option_add('7', "seven", 0); + option_add('F', "fast-seize", 1); + option_add('D', "decoder-test", 3); + option_add('M', "mts", 0); + option_add('O', "operator", 1); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + switch (short_option) { + case 'S': + if (!strcasecmp(argv[argi], "auto")) + squelch_db = 0.0; + else + squelch_db = atof(argv[argi]); + break; + case 'P': + ptt = 1; + break; + case '5': + station_length = 5; + break; + case '7': + station_length = 7; + break; + case 'F': + fast_seize = atof(argv[argi]) / 1000.0; + if (fast_seize < 0.0) + fast_seize = 0.0; + break; + case 'D': + detector_test_length_1 = atof(argv[argi++]); + detector_test_length_2 = atof(argv[argi++]); + detector_test_length_3 = atof(argv[argi++]); + break; + case 'M': + mode = MODE_MTS; + ptt = 1; + break; + case 'O': + strncpy(operator, argv[argi], sizeof(operator) - 1); + operator[sizeof(operator) - 1] = '\0'; + break; + default: + return main_mobile_handle_options(short_option, argi, argv); + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + int rc, argi; + const char *station_id = ""; + int i; + + /* init common tones */ + init_tones(); + init_outoforder(); + init_noanswer(); + init_invalidnumber(); + init_congestion(); + + main_mobile_init(); + + /* handle options / config file */ + add_options(); + rc = options_config_file("~/.osmocom/analog/imts.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + if (!station_length) { + if (mode == MODE_IMTS) + station_length = 7; + else + station_length = 5; + } + + if (argi < argc) { + station_id = argv[argi]; + if ((int)strlen(station_id) != station_length) { + printf("Given station ID '%s' does not have %d digits\n", station_id, station_length); + return 0; + } + } + + if (!num_kanal) { + printf("No channel (\"Kanal\") is specified, Use '-k list' to get a list of all channels.\n\n"); + print_help(argv[0]); + return 0; + } + if (!strcasecmp(kanal[0], "list")) { + imts_list_channels(); + goto fail; + } + if (use_sdr) { + /* set audiodev */ + for (i = 0; i < num_kanal; i++) + audiodev[i] = "sdr"; + num_audiodev = num_kanal; + } + if (num_kanal == 1 && num_audiodev == 0) + num_audiodev = 1; /* use default */ + if (num_kanal != num_audiodev) { + fprintf(stderr, "You need to specify as many sound devices as you have channels.\n"); + exit(0); + } + + /* SDR always requires emphasis */ + if (use_sdr) { + do_pre_emphasis = 1; + do_de_emphasis = 1; + } + + if (!do_pre_emphasis || !do_de_emphasis) { + fprintf(stderr, "*******************************************************************************\n"); + fprintf(stderr, "I suggest to let me do pre- and de-emphasis (options -p -d)!\n"); + fprintf(stderr, "Use a transmitter/receiver without emphasis and let me do that!\n"); + fprintf(stderr, "Because FSK signaling does not use emphasis, I like to control emphasis by\n"); + fprintf(stderr, "myself for best results.\n"); + fprintf(stderr, "*******************************************************************************\n"); + } + + if (mode == MODE_IMTS && !fast_seize && latency > 5 && loopback == 0) { + fprintf(stderr, "*******************************************************************************\n"); + fprintf(stderr, "It is required to have a low latency in order to respond to phone's seizure\n"); + fprintf(stderr, "fast enough! Please reduce buffer size to 5 ms via option: '-b 5'\n"); + fprintf(stderr, "If this causes buffer underruns, use the 'Fast Seize' mode, see help.\n"); + fprintf(stderr, "*******************************************************************************\n"); + exit(0); + } + + if (mode == MODE_MTS && !use_sdr && loopback == 0) { + fprintf(stderr, "*******************************************************************************\n"); + fprintf(stderr, "MTS mode requires use of SDR, because base station is controlled by Squelch.\n"); + fprintf(stderr, "*******************************************************************************\n"); + exit(0); + } + if (mode == MODE_MTS && isinf(squelch_db) < 0 && loopback == 0) { + fprintf(stderr, "*******************************************************************************\n"); + fprintf(stderr, "MTS mode requires use of Squelch. Please set Squelch level, see help.\n"); + fprintf(stderr, "*******************************************************************************\n"); + exit(0); + } + + if (ptt && isinf(squelch_db) < 0 && loopback == 0) { + fprintf(stderr, "*******************************************************************************\n"); + fprintf(stderr, "Cannot use push-to-talk feature without Squelch option.\n"); + fprintf(stderr, "*******************************************************************************\n"); + exit(0); + } + + /* no squelch in loopback mode */ + if (loopback) + squelch_db = -INFINITY; + + /* inits */ + fm_init(fast_math); + dsp_init(); + imts_init(); + + /* create transceiver instance */ + for (i = 0; i < num_kanal; i++) { + rc = imts_create(kanal[i], audiodev[i], use_sdr, samplerate, rx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, squelch_db, ptt, station_length, fast_seize, mode, operator, detector_test_length_1, detector_test_length_2, detector_test_length_3); + if (rc < 0) { + fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n"); + goto fail; + } + printf("Base station on channel %s ready, please tune transmitter to %.3f MHz and receiver to %.3f MHz. (%.3f MHz offset)\n", kanal[i], imts_channel2freq(kanal[i], 0) / 1e6, imts_channel2freq(kanal[i], 1) / 1e6, imts_channel2freq(kanal[i], 2) / 1e6); + } + + main_mobile(&quit, latency, interval, NULL, station_id, station_length); + +fail: + /* destroy transceiver instance */ + while (sender_head) + imts_destroy(sender_head); + + /* exits */ + fm_exit(); + + return 0; +} + diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c index cbafb4f..c22445f 100644 --- a/src/libdebug/debug.c +++ b/src/libdebug/debug.c @@ -51,6 +51,7 @@ struct debug_cat { { "nmt", "\033[1;34m" }, { "amps", "\033[1;34m" }, { "r2000", "\033[1;34m" }, + { "imts", "\033[1;34m" }, { "jollycom", "\033[1;34m" }, { "frame", "\033[0;36m" }, { "call", "\033[0;37m" }, diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h index 6da992f..c399c4b 100644 --- a/src/libdebug/debug.h +++ b/src/libdebug/debug.h @@ -14,19 +14,20 @@ #define DNMT 7 #define DAMPS 8 #define DR2000 9 -#define DJOLLY 10 -#define DFRAME 11 -#define DCALL 12 -#define DMNCC 13 -#define DDB 14 -#define DTRANS 15 -#define DDMS 16 -#define DSMS 17 -#define DSDR 18 -#define DUHD 19 -#define DSOAPY 20 -#define DWAVE 21 -#define DRADIO 22 +#define DIMTS 10 +#define DJOLLY 11 +#define DFRAME 12 +#define DCALL 13 +#define DMNCC 14 +#define DDB 15 +#define DTRANS 16 +#define DDMS 17 +#define DSMS 18 +#define DSDR 19 +#define DUHD 20 +#define DSOAPY 21 +#define DWAVE 22 +#define DRADIO 23 void get_win_size(int *w, int *h);