aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/imts/Makefile.am63
-rw-r--r--src/imts/dialer.c354
-rw-r--r--src/imts/dsp.c564
-rw-r--r--src/imts/dsp.h6
-rw-r--r--src/imts/image.c77
-rw-r--r--src/imts/imts.c1326
-rw-r--r--src/imts/imts.h139
-rw-r--r--src/imts/main.c289
-rw-r--r--src/libdebug/debug.c1
-rw-r--r--src/libdebug/debug.h27
11 files changed, 2834 insertions, 13 deletions
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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <math.h>
+#include <errno.h>
+#include "../libsample/sample.h"
+#include "../libwave/wave.h"
+#include "../libdebug/debug.h"
+#ifdef HAVE_ALSA
+#include "../libsound/sound.h"
+#endif
+#include "../liboptions/options.h"
+
+/* presets */
+const char *station_id = "6681739";
+const char *dialing;
+const char *audiodev = "hw:0,0";
+int samplerate = 48000;
+const char *write_tx_wave = NULL;
+int latency = 50;
+
+#define TONE_DONE -1
+#define TONE_SILENCE 0
+#define TONE_GUARD 2150
+#define TONE_CONNECT 1633
+#define TONE_DISCONNECT 1336
+
+/* states */
+static struct dial_string {
+ char console;
+ int tone;
+ int length;
+} dial_string[2048];
+static int dial_pos = 0;
+static int latspl;
+
+/* instances */
+#ifdef HAVE_ALSA
+void *audio = NULL;
+#endif
+wave_rec_t wave_tx_rec;
+
+/* dummy functions */
+int num_kanal = 1; /* only one channel used for debugging */
+void display_status_limit_scroll() {}
+void *get_sender_by_empfangsfrequenz() { return NULL; }
+void display_measurements_add() {}
+void display_measurements_update() {}
+
+static void print_help(const char *arg0)
+{
+ printf("Usage: %s [options] <number> | disconnect\n\n", arg0);
+ /* - - */
+ printf("This program generates a dialing sequence to make a call via IMTS base\n");
+ printf("station using an amateur radio transceiver.\n");
+ printf("Also it can write an audio file (wave) to be fed into IMTS base station for\n");
+ printf("showing how it simulates an IMTS phone doing an outgoing call.\n\n");
+ printf("Use 'disconnect' to generate a disconnect sequence.\n\n");
+ printf(" -h --help\n");
+ printf(" This help\n");
+ printf(" -i --station-id <station ID>\n");
+ printf(" 7 Digits of ID of mobile station (default = '%s')\n", station_id);
+#ifdef HAVE_ALSA
+ printf(" -a --audio-device hw:<card>,<device>\n");
+ printf(" Sound card and device number (default = '%s')\n", audiodev);
+#endif
+ printf(" -s --samplerate <rate>\n");
+ printf(" Sample rate of sound device (default = '%d')\n", samplerate);
+ printf(" -w --write-tx-wave <file>\n");
+ printf(" Write audio to given wave file also.\n");
+}
+
+static void add_options(void)
+{
+ option_add('h', "help", 0);
+ option_add('i', "station-id", 1);
+ option_add('a', "audio-device", 1);
+ option_add('s', "samplerate", 1);
+ option_add('w', "write-tx-wave", 1);
+}
+
+static int handle_options(int short_option, int __attribute__((unused)) argi, char **argv)
+{
+ switch (short_option) {
+ case 'h':
+ print_help(argv[0]);
+ return 0;
+ case 'i':
+ station_id = strdup(argv[argi]);
+ break;
+ case 'a':
+ audiodev = strdup(argv[argi]);
+ break;
+ case 's':
+ samplerate = atoi(argv[argi]);
+ break;
+ case 'w':
+ write_tx_wave = strdup(argv[argi]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static double phase = 0;
+
+/* encode audio */
+static void encode_audio(sample_t *samples, uint8_t *power, int length)
+{
+ int count;
+ int i;
+
+ memset(power, 1, length);
+
+again:
+ count = length;
+ if (dial_string[dial_pos].length && dial_string[dial_pos].length < count)
+ count = dial_string[dial_pos].length;
+
+ switch (dial_string[dial_pos].tone) {
+ case TONE_DONE:
+ case TONE_SILENCE:
+ memset(samples, 0, count * sizeof(*samples));
+ phase = 0;
+ break;
+ default:
+ for (i = 0; i < count; i++) {
+ samples[i] = cos(2.0 * M_PI * (double)dial_string[dial_pos].tone * phase);
+ phase += 1.0 / samplerate;
+ }
+ }
+
+ if (dial_string[dial_pos].length) {
+ dial_string[dial_pos].length -= count;
+ if (dial_string[dial_pos].length == 0) {
+ if (dial_string[dial_pos].console) {
+ printf("%c", dial_string[dial_pos].console);
+ fflush(stdout);
+ }
+ dial_pos++;
+ }
+ }
+
+ samples += count;
+ length -= count;
+
+ if (length)
+ goto again;
+}
+
+/* loop that gets audio from encoder and fowards it to sound card.
+ * alternatively a sound file is written.
+ */
+static void process_signal(void)
+{
+ sample_t buff[latspl], *samples[1] = { buff };
+ uint8_t pbuff[latspl], *power[1] = { pbuff };
+ int count;
+ int __attribute__((unused)) rc;
+
+ while (dial_string[dial_pos].tone != TONE_DONE) {
+#ifdef HAVE_ALSA
+ count = sound_get_tosend(audio, latspl);
+#else
+ count = samplerate / 1000;
+#endif
+ if (count < 0) {
+ PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count);
+ break;
+ }
+
+ /* encode dial_string of tones and lengths */
+ encode_audio(samples[0], power[0], count);
+
+ /* write wave, if open */
+ if (wave_tx_rec.fp)
+ wave_write(&wave_tx_rec, samples, count);
+
+#ifdef HAVE_ALSA
+ /* write audio */
+ rc = sound_write(audio, samples, power, count, NULL, NULL, 1);
+ if (rc < 0) {
+ PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc);
+ break;
+ }
+#endif
+
+ /* sleep a while */
+ usleep(1000);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int i, d, p, pulses, tone = 0;
+ int rc, argi;
+
+ memset(dial_string, 0, sizeof(dial_string));
+
+ /* latency of send buffer in samples */
+ latspl = samplerate * latency / 1000;
+
+ /* handle options / config file */
+ add_options();
+ argi = options_command_line(argc, argv, handle_options);
+ if (argi <= 0)
+ return argi;
+
+ if (argi >= argc) {
+ printf("No phone number given!\n\n");
+ print_help(argv[0]);
+ goto exit;
+ }
+
+ /* check for valid station ID */
+ if (strlen(station_id) != 7) {
+ printf("Given station ID '%s' has invalid number of digits!\n", station_id);
+ goto exit;
+ }
+ for (i = 0; station_id[i]; i++) {
+ if (station_id[i] < '0' || station_id[i] > '9') {
+ printf("Given station ID '%s' has invalid digits!\n", station_id);
+ goto exit;
+ }
+ }
+
+ dialing = argv[argi];
+ d = 0;
+ dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
+ if (!!strcasecmp(dialing, "disconnect")) {
+ /* check for valid phone number */
+ if (strlen(dialing) > 64) {
+ printf("Given phone number '%s' has too many digits! (more than allowed 64 digits)\n", dialing);
+ goto exit;
+ }
+ for (i = 0; dialing[i]; i++) {
+ if (dialing[i] < '0' || dialing[i] > '9') {
+ printf("Given phone number '%s' has invalid digits!\n", dialing);
+ goto exit;
+ }
+ }
+
+ dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.350 * (double)samplerate; /* off-hook */
+ dial_string[d].console = 's';
+ dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.050 * (double)samplerate; /* seize */
+ dial_string[d].console = '-';
+ dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 1.000 * (double)samplerate; /* pause */
+ for (i = 0; station_id[i]; i++) {
+ pulses = station_id[i] - '0';
+ if (pulses == 0)
+ pulses = 10;
+ dial_string[d].console = station_id[i];
+ for (p = 1; p <= pulses; p++) {
+ if ((p & 1) == 1)
+ tone = TONE_SILENCE;
+ else
+ tone = TONE_GUARD;
+ dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
+ dial_string[d].tone = tone; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
+ }
+ dial_string[d].tone = tone; dial_string[d++].length = 0.190 * (double)samplerate; /* after digit */
+ }
+ dial_string[d].console = '-';
+ dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 2.000 * (double)samplerate; /* pause */
+ for (i = 0; dialing[i]; i++) {
+ pulses = dialing[i] - '0';
+ if (pulses == 0)
+ pulses = 10;
+ dial_string[d].console = dialing[i];
+ for (p = 1; p <= pulses; p++) {
+ dial_string[d].tone = TONE_CONNECT; dial_string[d++].length = 0.060 * (double)samplerate; /* mark */
+ dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.040 * (double)samplerate; /* space */
+ }
+ dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.400 * (double)samplerate; /* after digit */
+ }
+ dial_string[d].console = '\n';
+ } else {
+ for (i = 0; i < 750; i += 50) {
+ dial_string[d].tone = TONE_DISCONNECT; dial_string[d++].length = 0.025 * (double)samplerate; /* mark */
+ dial_string[d].tone = TONE_GUARD; dial_string[d++].length = 0.025 * (double)samplerate; /* space */
+ }
+ }
+ dial_string[d].tone = TONE_SILENCE; dial_string[d++].length = 0.600 * (double)samplerate; /* pause */
+ dial_string[d].tone = TONE_DONE; dial_string[d++].length = 0; /* end */
+
+#ifdef HAVE_ALSA
+ /* init sound */
+ audio = sound_open(audiodev, NULL, NULL, 1, 0.0, samplerate, latspl, 1.0, 4000.0);
+ if (!audio) {
+ PDEBUG(DBNETZ, DEBUG_ERROR, "No sound device!\n");
+ goto exit;
+ }
+#endif
+
+ /* open wave */
+ if (write_tx_wave) {
+ rc = wave_create_record(&wave_tx_rec, write_tx_wave, samplerate, 1, 1.0);
+ if (rc < 0) {
+ PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
+ goto exit;
+ }
+ }
+#ifndef HAVE_ALSA
+ else {
+ PDEBUG(DBNETZ, DEBUG_ERROR, "No sound support compiled in, so you need to write to a wave file. See help!\n");
+ goto exit;
+ }
+#endif
+
+#ifdef HAVE_ALSA
+ /* start sound */
+ sound_start(audio);
+#endif
+
+ PDEBUG(DBNETZ, DEBUG_ERROR, "Start audio after pause...\n");
+
+ process_signal();
+
+exit:
+ /* close wave */
+ wave_destroy_record(&wave_tx_rec);
+
+#ifdef HAVE_ALSA
+ /* exit sound */
+ if (audio)
+ sound_close(audio);
+#endif
+
+ return 0;
+}
+
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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define CHAN imts->sender.kanal
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include "../libsample/sample.h"
+#include "../libdebug/debug.h"
+#include "../libtimer/timer.h"
+#include "../libmobile/call.h"
+#include "imts.h"
+#include "dsp.h"
+
+/* uncomment to debug decoder */
+//#define DEBUG_DECODER
+
+#define PI 3.1415927
+
+/* signaling */
+#define MAX_DEVIATION 7000.0 /* signaling tone plus some extra to calibrate */
+#define MAX_MODULATION 3000.0 /* FIXME */
+#define DBM0_DEVIATION 2500.0 /* deviation of dBm0 (with emphasis) */
+#define TX_PEAK_TONE (5000.0 / DBM0_DEVIATION) /* signaling tone level (5khz, no emphasis) */
+#define RX_MIN_AMPL 0.25 /* FIXME: Minimum level to detect tone */
+/* Note that 75 is half of the distance between two tones (2000 and 2150 Hz)
+ * An error of more than 50 causes too much toggeling between two tones,
+ * less would take too long to detect the tone and maybe not detect it, if
+ * it is too far off the expected frequency.
+ */
+#define RX_MIN_FREQ 50.0 /* minimum frequency error to detect tone */
+#define MAX_DISPLAY (MAX_DEVIATION / DBM0_DEVIATION)/* as much as MAX_DEVIATION */
+/* Note that FILTER_BW / SUSTAIN and QUAL_TIME sum up and should not exeed minimum tone length */
+#define RX_FILTER_BW 100.0 /* amplitude filter (causes delay) */
+#define RX_SUSTAIN 0.010 /* how long a tone must sustain until detected (causes delay) */
+#define RX_QUAL_TIME 0.005 /* how long a quality measurement lasts after detecting a tone */
+
+/* carrier loss detection */
+#define MUTE_TIME 0.1 /* time to mute after loosing signal */
+#define DELAY_TIME 0.15 /* delay, so we don't hear the noise before squelch mutes (MTS mode) */
+#define LOSS_TIME 12.0 /* duration of signal loss before release (FIXME: what was the actual duration ???) */
+#define SIGNAL_DETECT 0.3 /* time to have a steady signal to detect phone (used by MTS) */
+
+#define DISPLAY_INTERVAL 0.04
+
+/* signaling tones to be modulated */
+static double tones[NUM_SIG_TONES] = {
+ 2150.0, /* guard */
+ 2000.0, /* idle */
+ 1800.0, /* seize */
+ 1633.0, /* connect */
+ 1336.0, /* disconnect */
+ 600.0, /* MTS 600 Hz */
+ 1500.0, /* MTS 1500 Hz */
+};
+
+/* signaling tones to be demodulated (a subset of tones above) */
+static double tone_response[NUM_SIG_TONES] = {
+ 0.71, /* guard */
+ 1.08, /* idle */
+ 1.01, /* seize */
+ 1.04, /* connect */
+ 0.71, /* disconnect */
+ 0.71, /* MTS 600 Hz */
+ 0.71, /* MTS 1500 Hz */
+};
+
+/* all tone, with signaling tones first */
+static const char *tone_names[] = {
+ "GUARD tone",
+ "IDLE tone",
+ "SEIZE tone",
+ "CONNECT tone",
+ "DISCONNECT tone",
+ "600 Hz Tone",
+ "1500 Hz Tone",
+ "SILENCE",
+ "NOISE",
+ "DIALTONE",
+};
+
+#define DIALTONE1 350.0
+#define DIALTONE2 440.0
+
+/* table for fast sine generation */
+static sample_t dsp_sine_tone[65536];
+
+/* global init for audio processing */
+void dsp_init(void)
+{
+ int i;
+ double s;
+
+ PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine tables.\n");
+ for (i = 0; i < 65536; i++) {
+ s = sin((double)i / 65536.0 * 2.0 * PI);
+ dsp_sine_tone[i] = s * TX_PEAK_TONE;
+ }
+}
+
+/* Init transceiver instance. */
+int dsp_init_transceiver(imts_t *imts, double squelch_db, int ptt)
+{
+ int rc = -1;
+
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n");
+
+ imts->sample_duration = 1.0 / (double)imts->sender.samplerate;
+
+ /* init squelch */
+ squelch_init(&imts->squelch, imts->sender.kanal, squelch_db, MUTE_TIME, LOSS_TIME);
+
+ /* set modulation parameters */
+ sender_set_fm(&imts->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY);
+
+ /* init FM demodulator for tone detection */
+ if (imts->mode == MODE_IMTS) {
+ imts->demod_center = (tones[TONE_GUARD] + tones[TONE_DISCONNECT]) / 2.0;
+ imts->demod_bandwidth = tones[TONE_GUARD] - tones[TONE_DISCONNECT];
+ } else {
+ imts->demod_center = (tones[TONE_1500] + tones[TONE_600]) / 2.0;
+ imts->demod_bandwidth = tones[TONE_1500] - tones[TONE_600];
+ }
+ rc = fm_demod_init(&imts->demod, (double)imts->sender.samplerate, imts->demod_center, imts->demod_bandwidth);
+ if (rc < 0)
+ goto error;
+ /* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
+ /* NOTE: CHANGE TONE RESPONSES ABOVE, IF YOU CHANGE FILTER */
+ iir_lowpass_init(&imts->demod_freq_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
+ iir_lowpass_init(&imts->demod_ampl_lp, RX_FILTER_BW, (double)imts->sender.samplerate, 2);
+
+ /* tones */
+ imts->tone_idle_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_IDLE]);
+ imts->tone_seize_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_SEIZE]);
+ imts->tone_600_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_600]);
+ imts->tone_1500_phaseshift65536 = 65536.0 / ((double)imts->sender.samplerate / tones[TONE_1500]);
+ imts->tone_dialtone_phaseshift65536[0] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE1);
+ imts->tone_dialtone_phaseshift65536[1] = 65536.0 / ((double)imts->sender.samplerate / DIALTONE2);
+
+ /* demod init */
+ imts->demod_current_tone = TONE_SILENCE;
+ imts->demod_sig_tone = 0;
+ imts->demod_last_tone = TONE_SILENCE;
+
+ /* delay buffer */
+ if (ptt) {
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Push to talk: Adding delay buffer to remove noise when singal gets lost.\n");
+ imts->delay_max = (int)((double)imts->sender.samplerate * DELAY_TIME);
+ imts->delay_spl = calloc(imts->delay_max, sizeof(*imts->delay_spl));
+ if (!imts->delay_spl) {
+ PDEBUG(DDSP, DEBUG_ERROR, "No mem for delay buffer!\n");
+ goto error;
+ }
+ }
+
+ imts->dmp_tone_level = display_measurements_add(&imts->sender.dispmeas, "Tone Level", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
+ imts->dmp_tone_quality = display_measurements_add(&imts->sender.dispmeas, "Tone Quality", "%.1f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
+
+ return 0;
+
+error:
+ dsp_cleanup_transceiver(imts);
+ return rc;
+}
+
+/* Cleanup transceiver instance. */
+void dsp_cleanup_transceiver(imts_t *imts)
+{
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n");
+
+ fm_demod_exit(&imts->demod);
+ if (imts->delay_spl) {
+ free(imts->delay_spl);
+ imts->delay_spl = NULL;
+ }
+}
+
+/* Generate audio stream from tone. Keep phase for next call of function. */
+static int generate_tone(imts_t *imts, sample_t *samples, int length)
+{
+ double phaseshift[2] = {0,0}, phase[2]; // make gcc happy
+ int i;
+
+ switch (imts->tone) {
+ case TONE_IDLE:
+ phaseshift[0] = imts->tone_idle_phaseshift65536;
+ break;
+ case TONE_SEIZE:
+ phaseshift[0] = imts->tone_seize_phaseshift65536;
+ break;
+ case TONE_600:
+ phaseshift[0] = imts->tone_600_phaseshift65536;
+ break;
+ case TONE_1500:
+ phaseshift[0] = imts->tone_1500_phaseshift65536;
+ break;
+ case TONE_DIALTONE:
+ phaseshift[0] = imts->tone_dialtone_phaseshift65536[0];
+ phaseshift[1] = imts->tone_dialtone_phaseshift65536[1];
+ break;
+ case TONE_SILENCE:
+ break;
+ default:
+ PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Software error, unsupported tone, please fix!\n");
+ return length;
+ }
+
+ /* don't send more than given length */
+ if (imts->tone_duration && length > imts->tone_duration)
+ length = imts->tone_duration;
+
+ phase[0] = imts->tone_phase65536[0];
+ phase[1] = imts->tone_phase65536[1];
+
+ switch (imts->tone) {
+ case TONE_SILENCE:
+ memset(samples, 0, length * sizeof(*samples));
+ phase[0] = phase[1] = 0;
+ break;
+ case TONE_DIALTONE:
+ for (i = 0; i < length; i++) {
+ *samples = dsp_sine_tone[(uint16_t)phase[0]];
+ *samples += dsp_sine_tone[(uint16_t)phase[1]];
+ *samples++ /= 4.0; /* not full volume */
+ phase[0] += phaseshift[0];
+ if (phase[0] >= 65536)
+ phase[0] -= 65536;
+ phase[1] += phaseshift[1];
+ if (phase[1] >= 65536)
+ phase[1] -= 65536;
+ }
+ break;
+ default:
+ for (i = 0; i < length; i++) {
+ *samples++ = dsp_sine_tone[(uint16_t)phase[0]];
+ phase[0] += phaseshift[0];
+ if (phase[0] >= 65536)
+ phase[0] -= 65536;
+ }
+ }
+
+ imts->tone_phase65536[0] = phase[0];
+ imts->tone_phase65536[1] = phase[1];
+
+ /* if tone has been sent completely, tell IMTS call process */
+ if (imts->tone_duration) {
+ imts->tone_duration -= length;
+ if (imts->tone_duration == 0)
+ imts_tone_sent(imts, imts->tone);
+ }
+
+ return length;
+}
+
+/* Provide stream of audio toward radio unit */
+void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
+{
+ imts_t *imts = (imts_t *) sender;
+ int count;
+
+ memset(power, 1, length);
+
+again:
+ switch (imts->dsp_mode) {
+ case DSP_MODE_OFF:
+ memset(power, 0, length);
+ memset(samples, 0, length * sizeof(*samples));
+ break;
+ case DSP_MODE_TONE:
+ memset(power, 1, length);
+ count = generate_tone(imts, samples, length);
+ samples += count;
+ length -= count;
+ if (length)
+ goto again;
+ break;
+ case DSP_MODE_AUDIO:
+ memset(power, 1, length);
+ jitter_load(&imts->sender.dejitter, samples, length);
+ if (imts->pre_emphasis)
+ pre_emphasis(&imts->estate, samples, length);
+ break;
+ }
+}
+
+static void tone_demod(imts_t *imts, sample_t *samples, int length)
+{
+ sample_t frequency[length], amplitude[length];
+ sample_t I[length], Q[length];
+ double f, amp;
+ int i, t, tone = 0; // make GCC happy
+
+ /* demod frquency */
+ fm_demodulate_real(&imts->demod, frequency, length, samples, I, Q);
+ iir_process(&imts->demod_freq_lp, frequency, length);
+
+ /* demod amplitude
+ * peak amplitude is the length of I/Q vector
+ * since we filter out the unwanted modulation product, the vector is only half of length
+ */
+ for (i = 0; i < length; i++)
+ amplitude[i] = sqrt(I[i] * I[i] + Q[i] * Q[i]) * 2.0;
+ iir_process(&imts->demod_ampl_lp, amplitude, length);
+
+ /* check result to detect tone or loss or change */
+ for (i = 0; i < length; i++) {
+ /* duration until change in tone */
+ imts->demod_duration += imts->sample_duration;
+ /* correct FSK amplitude */
+ amplitude[i] /= TX_PEAK_TONE;
+ /* see what we detect at this moment; tone is set */
+ if (amplitude[i] < RX_MIN_AMPL) {
+ /* silence */
+ tone = TONE_SILENCE;
+ } else {
+ /* tone */
+ f = frequency[i] + imts->demod_center;
+ if (imts->mode == MODE_IMTS) {
+ for (t = TONE_IDLE; t <= TONE_DISCONNECT; t++) {
+ /* check for frequency */
+ if (fabs(f - tones[t]) < RX_MIN_FREQ) {
+ tone = t;
+ break;
+ }
+ }
+ } else {
+ for (t = TONE_600; t <= TONE_1500; t++) {
+ /* check for frequency */
+ if (fabs(f - tones[t]) < RX_MIN_FREQ) {
+ tone = t;
+ break;
+ }
+ }
+ }
+ /* noise */
+ if (t == NUM_SIG_TONES)
+ tone = TONE_NOISE;
+ }
+#ifdef DEBUG_DECODER
+ /* debug decoder */
+ if (tone < NUM_SIG_TONES) {
+ static int debug_interval = 0;
+ if (++debug_interval == 30) {
+ debug_interval = 0;
+ printf("decoder debug: diff=%s %.0f ", debug_amplitude((f - tones[t]) / RX_MIN_FREQ), fabs(f - tones[t]));
+ amp = amplitude[i] / tone_response[t];
+ printf("ampl=%s %.0f%%\n", debug_amplitude(amp), amp * 100.0);
+ }
+ }
+#endif
+ /* display level of tones, or zero at noise/silence */
+ imts->display_interval +=imts->sample_duration;
+ if (imts->display_interval >= DISPLAY_INTERVAL) {
+ if (tone < NUM_SIG_TONES)
+ amp = amplitude[i] / tone_response[tone];
+ else
+ amp = 0.0;
+ display_measurements_update(imts->dmp_tone_level, amp * 100.0, 0.0);
+ imts->display_interval -= DISPLAY_INTERVAL;
+ }
+ /* check for tone change */
+ if (tone != imts->demod_current_tone) {
+#ifdef DEBUG_DECODER
+ printf("decoder debug: %s detected, waiting to sustain\n", tone_names[tone]);
+#endif
+ if (imts->demod_sig_tone) {
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Lost %s (duration %.0f ms)\n", tone_names[imts->demod_current_tone], imts->demod_duration * 1000.0);
+ imts_lost_tone(imts, imts->demod_current_tone, imts->demod_duration);
+ imts->demod_sig_tone = 0;
+ }
+ imts->demod_current_tone = tone;
+ imts->demod_sustain = 0.0;
+ } else if (imts->demod_sustain < RX_SUSTAIN) {
+ imts->demod_sustain += imts->sample_duration;
+ /* when sustained. also tone must change to prevent flapping; lost signaling tone can be detected again */
+ if (imts->demod_sustain >= RX_SUSTAIN && (imts->demod_current_tone != imts->demod_last_tone || !imts->demod_sig_tone)) {
+ if (imts->demod_current_tone < NUM_SIG_TONES) {
+ amp = amplitude[i] / tone_response[imts->demod_current_tone];
+ imts->demod_sig_tone = 1;
+ imts->demod_quality_time = 0.0;
+ imts->demod_quality_count = 0;
+ imts->demod_quality_value = 0.0;
+ } else {
+ amp = amplitude[i];
+ imts->demod_sig_tone = 0;
+ }
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected %s (level %.0f%%)\n", tone_names[imts->demod_current_tone], amp * 100);
+ imts_receive_tone(imts, imts->demod_current_tone, imts->demod_duration, amp);
+ imts->demod_last_tone = imts->demod_current_tone;
+ imts->demod_duration = imts->demod_sustain;
+ } else if (imts->demod_sig_tone && imts->demod_quality_time < RX_QUAL_TIME) {
+ f = frequency[i] + imts->demod_center;
+ imts->demod_quality_time += imts->sample_duration;
+ imts->demod_quality_count++;
+ imts->demod_quality_value += fabs((f - tones[imts->demod_current_tone])) / RX_MIN_FREQ;
+ if (imts->demod_quality_time >= RX_QUAL_TIME) {
+ double quality = 1.0 - imts->demod_quality_value / (double)imts->demod_quality_count * 2.0;
+ if (quality < 0)
+ quality = 0;
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Quality: %.0f%%\n", quality * 100.0);
+ display_measurements_update(imts->dmp_tone_quality, quality * 100.0, 0.0);
+ }
+ }
+ }
+ }
+}
+
+static void delay_audio(imts_t *imts, sample_t *samples, int count)
+{
+ sample_t *spl, s;
+ int pos, max;
+ int i;
+
+ spl = imts->delay_spl;
+ pos = imts->delay_pos;
+ max = imts->delay_max;
+
+ /* feed audio though delay buffer */
+ for (i = 0; i < count; i++) {
+ s = samples[i];
+ samples[i] = spl[pos];
+ spl[pos] = s;
+ if (++pos == max)
+ pos = 0;
+ }
+
+ imts->delay_pos = pos;
+}
+
+/* Process received audio stream from radio unit. */
+void sender_receive(sender_t *sender, sample_t *samples, int length, double rf_level_db)
+{
+ imts_t *imts = (imts_t *) sender;
+
+ /* no processing if off */
+ if (imts->dsp_mode == DSP_MODE_OFF)
+ return;
+
+ /* process signal mute/loss, also for signalling tone */
+ switch (squelch(&imts->squelch, rf_level_db, (double)length * imts->sample_duration)) {
+ case SQUELCH_LOSS:
+ imts_loss_indication(imts, LOSS_TIME);
+ /* FALLTHRU */
+ case SQUELCH_MUTE:
+ if (imts->mode == MODE_MTS && !imts->is_mute) {
+ PDEBUG_CHAN(DDSP, DEBUG_INFO, "Low RF level, muting.\n");
+ memset(imts->delay_spl, 0, sizeof(*samples) * imts->delay_max);
+ imts->is_mute = 1;
+ }
+ memset(samples, 0, sizeof(*samples) * length);
+ /* signal is gone */
+ imts->rf_signal = 0;
+ break;
+ default:
+ if (imts->is_mute) {
+ PDEBUG_CHAN(DDSP, DEBUG_INFO, "High RF level, unmuting.\n");
+ imts->is_mute = 0;
+ }
+ /* detect signal, if it is steady for a while */
+ if (imts->rf_signal < SIGNAL_DETECT * (double)imts->sender.samplerate) {
+ /* only do this, if signal has not been detected yet */
+ imts->rf_signal += length;
+ if (imts->rf_signal >= SIGNAL_DETECT * (double)imts->sender.samplerate)
+ imts_signal_indication(imts);
+ }
+ break;
+ }
+
+ /* FM/AM demod */
+ tone_demod(imts, samples, length);
+
+ /* delay audio to prevent noise before squelch mutes (don't do that for signaling tones) */
+ if (imts->delay_spl)
+ delay_audio(imts, samples, length);
+
+ /* Forward audio to network (call process). */
+ if (imts->dsp_mode == DSP_MODE_AUDIO && imts->callref) {
+ sample_t *spl;
+ int pos;
+ int count;
+ int i;
+
+ if (imts->de_emphasis) {
+ dc_filter(&imts->estate, samples, length);
+ de_emphasis(&imts->estate, samples, length);
+ }
+ count = samplerate_downsample(&imts->sender.srstate, samples, length);
+ spl = imts->sender.rxbuf;
+ pos = imts->sender.rxbuf_pos;
+ for (i = 0; i < count; i++) {
+ spl[pos++] = samples[i];
+ if (pos == 160) {
+ call_up_audio(imts->callref, spl, 160);
+ pos = 0;
+ }
+ }
+ imts->sender.rxbuf_pos = pos;
+ } else
+ imts->sender.rxbuf_pos = 0;
+
+}
+
+const char *imts_dsp_mode_name(enum dsp_mode mode)
+{
+ static char invalid[16];
+
+ switch (mode) {
+ case DSP_MODE_OFF:
+ return "TRX OFF";
+ case DSP_MODE_TONE:
+ return "TONE";
+ case DSP_MODE_AUDIO:
+ return "AUDIO";
+ }
+
+ sprintf(invalid, "invalid(%d)", mode);
+ return invalid;
+}
+
+void imts_set_dsp_mode(imts_t *imts, enum dsp_mode mode, int tone, double duration, int reset_demod)
+{
+ /* reset demod */
+ if (reset_demod) {
+ imts->demod_current_tone = TONE_SILENCE;
+ imts->demod_sig_tone = 0;
+ imts->demod_last_tone = TONE_SILENCE;
+ imts->demod_duration = 0.0;
+ }
+
+ if (imts->dsp_mode != mode) {
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", imts_dsp_mode_name(imts->dsp_mode), imts_dsp_mode_name(mode));
+ imts->dsp_mode = mode;
+ }
+
+ if (mode == DSP_MODE_TONE) {
+ if (duration)
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s for %.3f seconds.\n", tone_names[tone], duration);
+ else
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Start sending %s continuously.\n", tone_names[tone]);
+ imts->tone = tone;
+ imts->tone_duration = duration * (double)imts->sender.samplerate;
+ }
+}
+
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 <stdio.h>
+#include <string.h>
+#include "../libmobile/image.h"
+
+const char *image[] = {
+ "@W",
+ "IMTS / MTS is back!",
+ "",
+ NULL
+};
+
+void print_image(void)
+{
+ int i, j;
+
+ for (i = 0; image[i]; i++) {
+ for (j = 0; j < (int)strlen(image[i]); j++) {
+ if (image[i][j] == '@') {
+ j++;
+ switch(image[i][j]) {
+ case 'k': /* black */
+ printf("\033[0;30m");
+ break;
+ case 'r': /* red */
+ printf("\033[0;31m");
+ break;
+ case 'g': /* green */
+ printf("\033[0;32m");
+ break;
+ case 'y': /* yellow */
+ printf("\033[0;33m");
+ break;
+ case 'b': /* blue */
+ printf("\033[0;34m");
+ break;
+ case 'm': /* magenta */
+ printf("\033[0;35m");
+ break;
+ case 'c': /* cyan */
+ printf("\033[0;36m");
+ break;
+ case 'w': /* white */
+ printf("\033[0;37m");
+ break;
+ case 'K': /* bright black */
+ printf("\033[1;30m");
+ break;
+ case 'R': /* bright red */
+ printf("\033[1;31m");
+ break;
+ case 'G': /* bright green */
+ printf("\033[1;32m");
+ break;
+ case 'Y': /* bright yellow */
+ printf("\033[1;33m");
+ break;
+ case 'B': /* bright blue */
+ printf("\033[1;34m");
+ break;
+ case 'M': /* bright magenta */
+ printf("\033[1;35m");
+ break;
+ case 'C': /* bright cyan */
+ printf("\033[1;36m");
+ break;
+ case 'W': /* bright white */
+ printf("\033[1;37m");
+ break;
+ }
+ } else
+ printf("%c", image[i][j]);
+ }
+ printf("\n");
+ }
+ printf("\033[0;39m");
+}
+
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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include "../libsample/sample.h"
+#include "../libmobile/main_mobile.h"
+#include "../libdebug/debug.h"
+#include "../libtimer/timer.h"
+#include "../libmobile/call.h"
+#include "../liboptions/options.h"
+#include "../amps/tones.h"
+#include "../amps/outoforder.h"
+#include "../amps/noanswer.h"
+#include "../amps/invalidnumber.h"
+#include "../amps/congestion.h"
+#include "imts.h"
+#include "dsp.h"
+
+/* settings */
+static double squelch_db = -INFINITY;
+static int ptt = 0;
+static int station_length = 0; /* defined by mode */
+static double fast_seize = 0.0;
+static enum mode mode = MODE_IMTS;
+static char operator[32] = "010";
+static double detector_test_length_1 = 0.0;
+static double detector_test_length_2 = 0.0;
+static double detector_test_length_3 = 0.0;
+
+void print_help(const char *arg0)
+{
+ main_mobile_print_help(arg0, "-b 5 ");
+ /* - - */
+ printf(" -S --squelch <dB> | auto\n");
+ printf(" Use given RF level to detect loss of signal. When the signal gets lost\n");
+ printf(" and stays below this level, the connection is released.\n");
+ printf(" Use 'auto' to do automatic noise floor calibration to detect loss.\n");
+ printf(" Only works with SDR! (disabled by default)\n");
+ printf(" -P --push-to-talk\n");
+ printf(" Allow push-to-talk operation for IMTS mode. (MTS always uses it.)\n");
+ printf(" This adds extra delay to received audio, to eliminate noise when the\n");
+ printf(" transmitter of the phone is turned off. Also this disables release on\n");
+ printf(" loss of RF signal. (Squelch is required for this to operate.)\n");
+ printf(" -5 --five\n");
+ printf(" -7 --seven\n");
+ printf(" Force station ID length (default is 7 for IMTS, 5 for MTS)\n");
+ printf(" -F --fast-seize <delay in ms>\n");
+ printf(" To compensate audio processing latency, give delay when to respond,\n");
+ printf(" after detection of Guard tone from mobile phone.\n");
+ printf(" Run software in loopback mode '-l 2' to measure round trip delay.\n");
+ printf(" Substract delay from 350 ms. If the phone has different Guard tone\n");
+ printf(" length, substract from that value.\n");
+ printf(" -D --detector-test <idle length> <seize lenght> <silence length>\n");
+ printf(" Transmit detector test signal, to adjust decoder inside mobile phone.\n");
+ printf(" Give length of idle / seize and silence in seconds. Listen to it with\n");
+ printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
+ printf(" Example: '-D 0.5 0.5 0' plays alternating idle/seize tone.\n");
+ printf("\nMTS mode options\n");
+ printf(" -M --mts\n");
+ printf(" Run base station in MTS mode, rather than in IMTS mode.\n");
+ printf(" -O --operator <number>\n");
+ printf(" Give number to dial when mobile station initiated a call in MTS mode.\n");
+ printf(" Because there is no dial on the mobile phone, operator assistance is\n");
+ printf(" required to complete the call.\n");
+ printf(" By default, the operator '%s' is dialed.\n", operator);
+ printf(" -D --detector-test <600 Hz length> <1500 Hz lenght> <silence length>\n");
+ printf(" Transmit detector test signal, to adjust decoder inside MTS phone.\n");
+ printf(" Give length of 600/1500 Hz and silence in seconds. Listen to it with\n");
+ printf(" a radio receiver. To exclude an element, set its length to '0'.\n");
+ printf(" Example: '-D 0.5 0.5 0' plays alternating 600/1500 Hz tone.\n");
+ printf("\nstation-id: Give %d digits of station-id, you don't need to enter it after\n", station_length);
+ printf(" every start of this program.\n");
+ main_mobile_print_hotkeys();
+}
+
+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);