aboutsummaryrefslogtreecommitdiffstats
path: root/src/r2000
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2017-06-10 15:30:20 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2017-08-09 17:27:12 +0200
commitffd3b848e1c2ca5e8449731062acd84f9d7d664b (patch)
tree82df0ebadea1b98628681f372fe93443a866de73 /src/r2000
parent8ce3ff455d83c692f240969a8c9f2d61ba4533e3 (diff)
Implementation of "Radiocom 2000", the analog French mobile network
Diffstat (limited to 'src/r2000')
-rw-r--r--src/r2000/Makefile.am21
-rw-r--r--src/r2000/dsp.c618
-rw-r--r--src/r2000/dsp.h6
-rw-r--r--src/r2000/frame.c573
-rw-r--r--src/r2000/frame.h33
-rw-r--r--src/r2000/image.c84
-rw-r--r--src/r2000/image.h3
-rw-r--r--src/r2000/main.c397
-rw-r--r--src/r2000/r2000.c1589
-rw-r--r--src/r2000/r2000.h135
-rw-r--r--src/r2000/tones.c68
-rw-r--r--src/r2000/tones.h3
12 files changed, 3530 insertions, 0 deletions
diff --git a/src/r2000/Makefile.am b/src/r2000/Makefile.am
new file mode 100644
index 0000000..00500ab
--- /dev/null
+++ b/src/r2000/Makefile.am
@@ -0,0 +1,21 @@
+#AUTOMAKE_OPTIONS = subdir-objects
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+bin_PROGRAMS = \
+ radiocom2000
+
+radiocom2000_SOURCES = \
+ r2000.c \
+ dsp.c \
+ frame.c \
+ tones.c \
+ image.c \
+ main.c
+radiocom2000_LDADD = \
+ $(COMMON_LA) \
+ $(top_builddir)/src/common/libcommon.a \
+ $(ALSA_LIBS) \
+ $(UHD_LIBS) \
+ $(SOAPY_LIBS) \
+ -lm
+
diff --git a/src/r2000/dsp.c b/src/r2000/dsp.c
new file mode 100644
index 0000000..1a1c096
--- /dev/null
+++ b/src/r2000/dsp.c
@@ -0,0 +1,618 @@
+/* Radiocom 2000 audio processing
+ *
+ * (C) 2017 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 r2000->sender.kanal
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include "../common/sample.h"
+#include "../common/debug.h"
+#include "../common/timer.h"
+#include "r2000.h"
+#include "dsp.h"
+
+#define PI M_PI
+
+/* Notes on TX_PEAK_FSK level:
+ *
+ * Applies similar to NMT, read it there!
+ *
+ * I assume that the deviation at 1800 Hz (Bit 0) is +-1700 Hz.
+ *
+ * Notes on TX_PEAK_SUPER level:
+ *
+ * No emphasis applies (done afterwards), so it is 300 Hz deviation.
+ */
+
+/* signaling */
+#define MAX_DEVIATION 2500.0
+#define MAX_MODULATION 2550.0
+#define DBM0_DEVIATION 1500.0 /* deviation of dBm0 at 1 kHz */
+#define COMPANDOR_0DB 1.0 /* A level of 0dBm (1.0) shall be unaccected */
+#define TX_PEAK_FSK (1700.0 / 1800.0 * 1000.0 / DBM0_DEVIATION) /* with emphasis */
+#define TX_PEAK_SUPER (300.0 / DBM0_DEVIATION) /* no emphasis */
+#define BIT_RATE 1200.0
+#define SUPER_RATE 50.0
+#define FILTER_STEP 0.002 /* step every 2 ms */
+#define MAX_DISPLAY 1.4 /* something above dBm0 */
+
+/* two signaling tones */
+static double super_bits[2] = {
+ 136.0,
+ 164.0,
+};
+
+/* table for fast sine generation */
+static sample_t super_sine[65536];
+
+/* global init for FFSK */
+void dsp_init(void)
+{
+ int i;
+
+ ffsk_global_init(TX_PEAK_FSK);
+
+ PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine table.\n");
+ for (i = 0; i < 65536; i++) {
+ super_sine[i] = sin((double)i / 65536.0 * 2.0 * PI) * TX_PEAK_SUPER;
+ }
+}
+
+static void fsk_receive_bit(void *inst, int bit, double quality, double level);
+
+/* Init FSK of transceiver */
+int dsp_init_sender(r2000_t *r2000)
+{
+ sample_t *spl;
+ double fsk_samples_per_bit;
+ int i;
+
+ /* attack (3ms) and recovery time (13.5ms) according to NMT specs */
+ init_compandor(&r2000->cstate, 8000, 3.0, 13.5, COMPANDOR_0DB);
+
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n");
+
+ /* set modulation parameters */
+ sender_set_fm(&r2000->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY);
+
+ PDEBUG(DDSP, DEBUG_DEBUG, "Using FSK level of %.3f\n", TX_PEAK_FSK);
+
+ /* init ffsk */
+ if (ffsk_init(&r2000->ffsk, r2000, fsk_receive_bit, r2000->sender.kanal, r2000->sender.samplerate) < 0) {
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "FFSK init failed!\n");
+ return -EINVAL;
+ }
+ if (r2000->sender.loopback)
+ r2000->rx_max = 176;
+ else
+ r2000->rx_max = 144;
+
+ /* allocate transmit buffer for a complete frame, add 10 to be safe */
+
+ fsk_samples_per_bit = (double)r2000->sender.samplerate / BIT_RATE;
+ r2000->frame_size = 208.0 * fsk_samples_per_bit + 10;
+ spl = calloc(r2000->frame_size, sizeof(*spl));
+ if (!spl) {
+ PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
+ return -ENOMEM;
+ }
+ r2000->frame_spl = spl;
+
+ /* strange: better quality with window size of two bits */
+ r2000->super_samples_per_window = (double)r2000->sender.samplerate / SUPER_RATE * 2.0;
+ r2000->super_filter_step = (double)r2000->sender.samplerate * FILTER_STEP;
+ r2000->super_size = 20.0 * r2000->super_samples_per_window + 10;
+ PDEBUG(DDSP, DEBUG_DEBUG, "Using %d samples per filter step for supervisory signal.\n", r2000->super_filter_step);
+ spl = calloc(r2000->super_size, sizeof(*spl));
+ if (!spl) {
+ PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
+ return -ENOMEM;
+ }
+ r2000->super_spl = spl;
+ spl = calloc(1, r2000->super_samples_per_window * sizeof(*spl));
+ if (!spl) {
+ PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
+ return -ENOMEM;
+ }
+ r2000->super_filter_spl = spl;
+ r2000->super_filter_bit = -1;
+
+ /* count supervisory symbols */
+ for (i = 0; i < 2; i++) {
+ audio_goertzel_init(&r2000->super_goertzel[i], super_bits[i], r2000->sender.samplerate);
+ r2000->super_phaseshift65536[i] = 65536.0 / ((double)r2000->sender.samplerate / super_bits[i]);
+ PDEBUG(DDSP, DEBUG_DEBUG, "phaseshift[%d] = %.4f\n", i, r2000->super_phaseshift65536[i]);
+ }
+ r2000->super_bittime = SUPER_RATE / (double)r2000->sender.samplerate;
+
+ return 0;
+}
+
+/* Cleanup transceiver instance. */
+void dsp_cleanup_sender(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n");
+
+ ffsk_cleanup(&r2000->ffsk);
+
+ if (r2000->frame_spl) {
+ free(r2000->frame_spl);
+ r2000->frame_spl = NULL;
+ }
+ if (r2000->super_spl) {
+ free(r2000->super_spl);
+ r2000->super_spl = NULL;
+ }
+ if (r2000->super_filter_spl) {
+ free(r2000->super_filter_spl);
+ r2000->super_filter_spl = NULL;
+ }
+}
+
+/* Check for SYNC bits, then collect data bits */
+static void fsk_receive_bit(void *inst, int bit, double quality, double level)
+{
+ r2000_t *r2000 = (r2000_t *)inst;
+// uint64_t frames_elapsed;
+ int i;
+
+ /* normalize FSK level */
+ level /= TX_PEAK_FSK;
+
+ r2000->rx_bits_count++;
+
+// printf("bit=%d quality=%.4f\n", bit, quality);
+ if (!r2000->rx_in_sync) {
+ r2000->rx_sync = (r2000->rx_sync << 1) | bit;
+
+ /* level and quality */
+ r2000->rx_level[r2000->rx_count & 0xff] = level;
+ r2000->rx_quality[r2000->rx_count & 0xff] = quality;
+ r2000->rx_count++;
+
+ /* check if pattern 1010111100010010 matches */
+ if (r2000->rx_sync != 0xaf12)
+ return;
+
+ /* average level and quality */
+ level = quality = 0;
+ for (i = 0; i < 16; i++) {
+ level += r2000->rx_level[(r2000->rx_count - 1 - i) & 0xff];
+ quality += r2000->rx_quality[(r2000->rx_count - 1 - i) & 0xff];
+ }
+ level /= 16.0; quality /= 16.0;
+// printf("sync (level = %.2f, quality = %.2f\n", level, quality);
+
+ /* do not accept garbage */
+ if (quality < 0.65)
+ return;
+
+ /* sync time */
+ r2000->rx_bits_count_last = r2000->rx_bits_count_current;
+ r2000->rx_bits_count_current = r2000->rx_bits_count - 32.0;
+
+ /* rest sync register */
+ r2000->rx_sync = 0;
+ r2000->rx_in_sync = 1;
+ r2000->rx_count = 0;
+
+ return;
+ }
+
+ /* read bits */
+ r2000->rx_frame[r2000->rx_count] = bit + '0';
+ r2000->rx_level[r2000->rx_count] = level;
+ r2000->rx_quality[r2000->rx_count] = quality;
+ if (++r2000->rx_count != r2000->rx_max)
+ return;
+
+ /* end of frame */
+ r2000->rx_frame[r2000->rx_max] = '\0';
+ r2000->rx_in_sync = 0;
+
+ /* average level and quality */
+ level = quality = 0;
+ for (i = 0; i < r2000->rx_max; i++) {
+ level += r2000->rx_level[i];
+ quality += r2000->rx_quality[i];
+ }
+ level /= (double)r2000->rx_max; quality /= (double)r2000->rx_max;
+
+ /* send frame to upper layer */
+ r2000_receive_frame(r2000, r2000->rx_frame, quality, level);
+}
+
+static void super_receive_bit(r2000_t *r2000, int bit, double level, double quality)
+{
+ int i;
+
+ /* normalize supervisory level */
+ level /= TX_PEAK_SUPER;
+
+ /* store bit */
+ r2000->super_rx_word = (r2000->super_rx_word << 1) | bit;
+ r2000->super_rx_level[r2000->super_rx_index] = level;
+ r2000->super_rx_quality[r2000->super_rx_index] = quality;
+ r2000->super_rx_index = (r2000->super_rx_index + 1) % 20;
+
+// printf("%d -> %05x\n", bit, r2000->super_rx_word & 0xfffff);
+ /* check for sync 0100000000 01xxxxxxx1 */
+ if ((r2000->super_rx_word & 0xfff01) != 0x40101)
+ return;
+
+ /* average level and quality */
+ level = quality = 0;
+ for (i = 0; i < 20; i++) {
+ level += r2000->super_rx_level[i];
+ quality += r2000->super_rx_quality[i];
+ }
+ level /= 20.0; quality /= 20.0;
+
+ /* send received supervisory digit to call control */
+ r2000_receive_super(r2000, (r2000->super_rx_word >> 1) & 0x7f, quality, level);
+}
+
+//#define DEBUG_FILTER
+//#define DEBUG_QUALITY
+
+/* demodulate supervisory signal
+ * filter one chunk, that is 2ms long (1/10th of a bit) */
+static inline void super_decode_step(r2000_t *r2000, int pos)
+{
+ double level, result[2], softbit, quality;
+ int max;
+ sample_t *spl;
+ int bit;
+
+ max = r2000->super_samples_per_window;
+ spl = r2000->super_filter_spl;
+
+ level = audio_level(spl, max);
+
+ audio_goertzel(r2000->super_goertzel, spl, max, pos, result, 2);
+
+ /* calculate soft bit from both frequencies */
+ softbit = (result[1] / level - result[0] / level + 1.0) / 2.0;
+// /* scale it, since both filters overlap by some percent */
+//#define MIN_QUALITY 0.08
+// softbit = (softbit - MIN_QUALITY) / (0.850 - MIN_QUALITY - MIN_QUALITY);
+ if (softbit > 1)
+ softbit = 1;
+ if (softbit < 0)
+ softbit = 0;
+#ifdef DEBUG_FILTER
+ printf("|%s", debug_amplitude(result[0]/level));
+ printf("|%s| low=%.3f high=%.3f level=%d\n", debug_amplitude(result[1]/level), result[0]/level, result[1]/level, (int)level);
+#endif
+ if (softbit > 0.5)
+ bit = 1;
+ else
+ bit = 0;
+
+// quality = result[bit] / level;
+ if (softbit > 0.5)
+ quality = softbit * 2.0 - 1.0;
+ else
+ quality = 1.0 - softbit * 2.0;
+
+ /* scale quality, because filters overlap */
+ quality /= 0.80;
+
+ if (r2000->super_filter_bit != bit) {
+#ifdef DEBUG_FILTER
+ puts("bit change");
+#endif
+ r2000->super_filter_bit = bit;
+#if 0
+ /* If we have a bit change, move sample counter towards one half bit duration.
+ * We may have noise, so the bit change may be wrong or not at the correct place.
+ * This can cause bit slips.
+ * Therefore we change the sample counter only slightly, so bit slips may not
+ * happen so quickly.
+ */
+ if (r2000->super_filter_sample < 5)
+ r2000->super_filter_sample++;
+ if (r2000->super_filter_sample > 5)
+ r2000->super_filter_sample--;
+#else
+ /* directly center the sample position, because we don't have any sync sequence */
+ r2000->super_filter_sample = 5;
+#endif
+
+ } else if (--r2000->super_filter_sample == 0) {
+ /* if sample counter bit reaches 0, we reset sample counter to one bit duration */
+#ifdef DEBUG_QUALITY
+ printf("|%s| quality=%.2f ", debug_amplitude(softbit), quality);
+ printf("|%s|\n", debug_amplitude(quality);
+#endif
+ /* adjust level, so we get peak of sine curve */
+ super_receive_bit(r2000, bit, level / 0.63662, quality);
+ r2000->super_filter_sample = 10;
+ }
+}
+
+/* get audio chunk out of received stream */
+void super_receive(r2000_t *r2000, sample_t *samples, int length)
+{
+ sample_t *spl;
+ int max, pos, step;
+ int i;
+ /* write received samples to decode buffer */
+ max = r2000->super_samples_per_window;
+ pos = r2000->super_filter_pos;
+ step = r2000->super_filter_step;
+ spl = r2000->super_filter_spl;
+ for (i = 0; i < length; i++) {
+ spl[pos++] = samples[i];
+ if (pos == max)
+ pos = 0;
+ /* if filter step has been reched */
+ if (!(pos % step)) {
+ super_decode_step(r2000, pos);
+ }
+ }
+ r2000->super_filter_pos = pos;
+}
+
+/* Process received audio stream from radio unit. */
+void sender_receive(sender_t *sender, sample_t *samples, int length)
+{
+ r2000_t *r2000 = (r2000_t *) sender;
+ sample_t *spl;
+ int pos;
+ int i;
+
+ /* do dc filter */
+ if (r2000->de_emphasis)
+ dc_filter(&r2000->estate, samples, length);
+
+ /* supervisory signal */
+ if (r2000->dsp_mode == DSP_MODE_AUDIO_TX
+ || r2000->dsp_mode == DSP_MODE_AUDIO_TX_RX
+ || r2000->sender.loopback)
+ super_receive(r2000, samples, length);
+
+ /* do de-emphasis */
+ if (r2000->de_emphasis)
+ de_emphasis(&r2000->estate, samples, length);
+
+ /* fsk signal */
+ ffsk_receive(&r2000->ffsk, samples, length);
+
+ /* we must have audio mode for both ways and a call */
+ if (r2000->dsp_mode == DSP_MODE_AUDIO_TX_RX
+ && r2000->callref) {
+ int count;
+
+ count = samplerate_downsample(&r2000->sender.srstate, samples, length);
+#if 0
+ /* compandor only in direction REL->MS */
+ if (r2000->compandor)
+ expand_audio(&r2000->cstate, samples, count);
+#endif
+ spl = r2000->sender.rxbuf;
+ pos = r2000->sender.rxbuf_pos;
+ for (i = 0; i < count; i++) {
+ spl[pos++] = samples[i];
+ if (pos == 160) {
+ call_tx_audio(r2000->callref, spl, 160);
+ pos = 0;
+ }
+ }
+ r2000->sender.rxbuf_pos = pos;
+ } else
+ r2000->sender.rxbuf_pos = 0;
+}
+
+static int fsk_frame(r2000_t *r2000, sample_t *samples, int length)
+{
+ const char *frame;
+ sample_t *spl;
+ int i;
+ int count, max;
+
+next_frame:
+ if (!r2000->frame_length) {
+ /* request frame */
+ frame = r2000_get_frame(r2000);
+ if (!frame) {
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Stop sending frames.\n");
+ return length;
+ }
+ /* render frame */
+ r2000->frame_length = ffsk_render_frame(&r2000->ffsk, frame, 208, r2000->frame_spl);
+ r2000->frame_pos = 0;
+ if (r2000->frame_length > r2000->frame_size) {
+ PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Frame exceeds buffer, please fix!\n");
+ abort();
+ }
+ }
+
+ /* send audio from frame */
+ max = r2000->frame_length;
+ count = max - r2000->frame_pos;
+ if (count > length)
+ count = length;
+ spl = r2000->frame_spl + r2000->frame_pos;
+ for (i = 0; i < count; i++) {
+ *samples++ = *spl++;
+ }
+ length -= count;
+ r2000->frame_pos += count;
+ /* check for end of telegramm */
+ if (r2000->frame_pos == max) {
+ r2000->frame_length = 0;
+ /* we need more ? */
+ if (length)
+ goto next_frame;
+ }
+
+ return length;
+}
+
+static int super_render_frame(r2000_t *r2000, uint32_t word, sample_t *sample)
+{
+ double phaseshift, phase, bittime, bitpos;
+ int count = 0, i;
+
+ phase = r2000->super_phase65536;
+ bittime = r2000->super_bittime;
+ bitpos = r2000->super_bitpos;
+ for (i = 0; i < 20; i++) {
+ phaseshift = r2000->super_phaseshift65536[(word >> 19) & 1];
+ do {
+ *sample++ = super_sine[(uint16_t)phase];
+ count++;
+ phase += phaseshift;
+ if (phase >= 65536.0)
+ phase -= 65536.0;
+ bitpos += bittime;
+ } while (bitpos < 1.0);
+ bitpos -= 1.0;
+ word <<= 1;
+ }
+ r2000->super_phase65536 = phase;
+ bitpos = r2000->super_bitpos;
+
+ /* return number of samples created for frame */
+ return count;
+}
+
+static int super_frame(r2000_t *r2000, sample_t *samples, int length)
+{
+ sample_t *spl;
+ int i;
+ int count, max;
+
+next_frame:
+ if (!r2000->super_length) {
+ /* render supervisory rame */
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "render word 0x%05x\n", r2000->super_tx_word);
+ r2000->super_length = super_render_frame(r2000, r2000->super_tx_word, r2000->super_spl);
+ r2000->super_pos = 0;
+ if (r2000->super_length > r2000->super_size) {
+ PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Frame exceeds buffer, please fix!\n");
+ abort();
+ }
+ }
+
+ /* send audio from frame */
+ max = r2000->super_length;
+ count = max - r2000->super_pos;
+ if (count > length)
+ count = length;
+ spl = r2000->super_spl + r2000->super_pos;
+ for (i = 0; i < count; i++) {
+ *samples++ += *spl++;
+ }
+ length -= count;
+ r2000->super_pos += count;
+ /* check for end of telegramm */
+ if (r2000->super_pos == max) {
+ r2000->super_length = 0;
+ /* we need more ? */
+ if (length)
+ goto next_frame;
+ }
+
+ return length;
+}
+
+/* Provide stream of audio toward radio unit */
+void sender_send(sender_t *sender, sample_t *samples, int length)
+{
+ r2000_t *r2000 = (r2000_t *) sender;
+ int len;
+
+again:
+ switch (r2000->dsp_mode) {
+ case DSP_MODE_OFF:
+ memset(samples, 0, sizeof(*samples) * length);
+ break;
+ case DSP_MODE_AUDIO_TX:
+ case DSP_MODE_AUDIO_TX_RX:
+ jitter_load(&r2000->sender.dejitter, samples, length);
+ /* do pre-emphasis */
+ if (r2000->pre_emphasis)
+ pre_emphasis(&r2000->estate, samples, length);
+ super_frame(r2000, samples, length);
+ break;
+ case DSP_MODE_FRAME:
+ /* Encode frame into audio stream. If frames have
+ * stopped, process again for rest of stream. */
+ len = fsk_frame(r2000, samples, length);
+ /* do pre-emphasis */
+ if (r2000->pre_emphasis)
+ pre_emphasis(&r2000->estate, samples, length - len);
+ if (len) {
+ samples += length - len;
+ length = len;
+ goto again;
+ }
+ break;
+ }
+}
+
+const char *r2000_dsp_mode_name(enum dsp_mode mode)
+{
+ static char invalid[16];
+
+ switch (mode) {
+ case DSP_MODE_OFF:
+ return "OFF";
+ case DSP_MODE_AUDIO_TX:
+ return "AUDIO-TX";
+ case DSP_MODE_AUDIO_TX_RX:
+ return "AUDIO-TX-RX";
+ case DSP_MODE_FRAME:
+ return "FRAME";
+ }
+
+ sprintf(invalid, "invalid(%d)", mode);
+ return invalid;
+}
+
+void r2000_set_dsp_mode(r2000_t *r2000, enum dsp_mode mode, int super)
+{
+ /* reset telegramm */
+ if (mode == DSP_MODE_FRAME && r2000->dsp_mode != mode) {
+ r2000->frame_length = 0;
+ }
+ if ((mode == DSP_MODE_AUDIO_TX || mode == DSP_MODE_AUDIO_TX_RX)
+ && (r2000->dsp_mode != DSP_MODE_AUDIO_TX && r2000->dsp_mode != DSP_MODE_AUDIO_TX_RX)) {
+ r2000->super_length = 0;
+ }
+
+ if (super >= 0) {
+ /* encode supervisory word 0100000000 01xxxxxxx1 */
+ r2000->super_tx_word = 0x40101 | ((super & 0x7f) << 1);
+ /* clear pending data in rx word */
+ r2000->super_rx_word = 0x00000;
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s (super = 0x%05x)\n", r2000_dsp_mode_name(r2000->dsp_mode), r2000_dsp_mode_name(mode), r2000->super_tx_word);
+ } else if (r2000->dsp_mode != mode)
+ PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", r2000_dsp_mode_name(r2000->dsp_mode), r2000_dsp_mode_name(mode));
+
+ r2000->dsp_mode = mode;
+}
+
+#warning fixme: high pass filter on tx side to prevent desturbance of supervisory signal
diff --git a/src/r2000/dsp.h b/src/r2000/dsp.h
new file mode 100644
index 0000000..401e965
--- /dev/null
+++ b/src/r2000/dsp.h
@@ -0,0 +1,6 @@
+
+void dsp_init(void);
+int dsp_init_sender(r2000_t *r2000);
+void dsp_cleanup_sender(r2000_t *r2000);
+void r2000_set_dsp_mode(r2000_t *r2000, enum dsp_mode mode, int super);
+
diff --git a/src/r2000/frame.c b/src/r2000/frame.c
new file mode 100644
index 0000000..37b0a91
--- /dev/null
+++ b/src/r2000/frame.c
@@ -0,0 +1,573 @@
+/* Radiocom 2000 frame transcoding
+ *
+ * (C) 2017 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 <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <inttypes.h>
+#include "../common/hagelbarger.h"
+#include "../common/debug.h"
+#include "frame.h"
+
+static const char *param_hex(uint64_t value)
+{
+ static char result[32];
+ sprintf(result, "0x%" PRIx64, value);
+
+ return result;
+}
+
+static const char *param_voie_rel(uint64_t value)
+{
+ return (value) ? "Control Channel" : "Traffic Channel";
+}
+
+static const char *param_voie_sm(uint64_t value)
+{
+ return (value) ? "Traffic Channel" : "Control Channel";
+}
+
+const char *param_agi(uint64_t value)
+{
+ switch (value) {
+ case 0:
+ return "Prohibited control channel (no mobile allowed)";
+ case 1:
+ return "New registration prohibited (registered mobiles allowed)";
+ case 2:
+ return "Registration is reserved to test mobiles";
+ case 3:
+ return "Registration for nominal mobiles (home network)";
+ case 4:
+ return "Registration is reserved to special mobiles";
+ case 5:
+ case 6:
+ case 7:
+ return "Registration permissible for all mobile station";
+ }
+ return "<invalid>";
+}
+
+const char *param_aga(uint64_t value)
+{
+ switch (value) {
+ case 0:
+ return "Outgoing calls prohibited";
+ case 1:
+ return "Reserved (Outgoing calls prohibited)";
+ case 2:
+ return "Outgoing call reserved for privileged mobiles";
+ case 3:
+ return "Outgoing calls permissible";
+ }
+ return "<invalid>";
+}
+
+const char *param_power(uint64_t value)
+{
+ switch (value) {
+ case 0:
+ return "Low";
+ case 1:
+ return "High";
+ }
+ return "<invalid>";
+}
+
+const char *param_crins(uint64_t value)
+{
+ switch (value) {
+ case 0:
+ return "Finished or just registering";
+ case 1:
+ return "Localization impossible (queue full)";
+ case 2:
+ return "Mobile station temporarily disabled";
+ case 3:
+ return "Mobile station definitely disabled (WILL BRICK THE PHONE!)";
+ case 4:
+ return "Blocked localization (BS out of order)";
+ case 5:
+ case 6:
+ return "Reserved";
+ case 7:
+ return "Calling subscriber unknown";
+ }
+ return "<invalid>";
+}
+
+const char *param_invitation(uint64_t value)
+{
+ switch (value) {
+ case 3:
+ return "to Answer";
+ case 10:
+ return "to Dial";
+ }
+ return "<unknown>";
+}
+
+static const char *param_digit(uint64_t value)
+{
+ static char result[32];
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ sprintf(result, "'%c'", (int)value + '0');
+ return result;
+ case 10:
+ return "'*'";
+ case 11:
+ return "'#'";
+ case 12:
+ return "'A'";
+ case 13:
+ return "'B'";
+ case 14:
+ return "'C'";
+ case 15:
+ return "'D'";
+ }
+ return "<invalid>";
+}
+
+static struct r2000_element {
+ char element;
+ const char *name;
+ const char *(*decoder_rel)(uint64_t value); /* REL sends to SM */
+ const char *(*decoder_sm)(uint64_t value); /* SM sends to REL */
+} r2000_element[] = {
+ { 'V', "Channel Type", param_voie_rel, param_voie_sm },
+ { 'C', "Channel", NULL, NULL },
+ { 'R', "Relais", NULL, NULL },
+ { 'M', "Message", NULL, NULL },
+ { 'D', "Deport", NULL, NULL },
+ { 'I', "AGI", param_agi, param_agi },
+// { 'A', "AGA", param_aga, param_aga },
+ { 'P', "power", param_power, param_power },
+ { 'T', "taxe", NULL, NULL },
+ { 't', "SM Type", param_hex, param_hex },
+ { 'r', "SM Relais", NULL, NULL },
+ { 'f', "SM Flotte", NULL, NULL },
+ { 'm', "SM ID", NULL, NULL },
+ { 'd', "Called ID", NULL, NULL },
+ { 'c', "CRINS", param_crins, param_crins },
+ { 'a', "Assign Channel", NULL, NULL },
+ { 's', "Sequence Number", param_hex, param_hex },
+ { 'i', "Invitation", param_invitation,param_invitation },
+ { 'n', "NCONV", NULL, NULL },
+ { '0', "1st Digit", param_digit, param_digit },
+ { '1', "2nd Digit", param_digit, param_digit },
+ { '2', "3rd Digit", param_digit, param_digit },
+ { '3', "4th Digit", param_digit, param_digit },
+ { '4', "5th Digit", param_digit, param_digit },
+ { '5', "6th Digit", param_digit, param_digit },
+ { '6', "7th Digit", param_digit, param_digit },
+ { '7', "8th Digit", param_digit, param_digit },
+ { '8', "9th Digit", param_digit, param_digit },
+ { '9', "10th Digit", param_digit, param_digit },
+ { '?', "Unknown", param_hex, param_hex },
+ { 0, NULL, NULL, NULL }
+};
+
+static void print_element(char element, uint64_t value, int dir, int debug)
+{
+ const char *(*decoder)(uint64_t value);
+ int i;
+
+ for (i = 0; r2000_element[i].element; i++) {
+ if (r2000_element[i].element == element)
+ break;
+ }
+ decoder = (dir == REL_TO_SM) ? r2000_element[i].decoder_rel : r2000_element[i].decoder_sm;
+
+ if (!r2000_element[i].element)
+ PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [Unknown]\n", element, value);
+ else if (!decoder)
+ PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [%s]\n", element, value, r2000_element[i].name);
+ else
+ PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 "=%s [%s]\n", element, value, decoder(value), r2000_element[i].name);
+}
+
+static void store_element(frame_t *frame, char element, uint64_t value)
+{
+ switch(element) {
+ case 'V':
+ frame->voie = value;
+ break;
+ case 'C':
+ frame->channel = value;
+ break;
+ case 'R':
+ frame->relais = value;
+ break;
+ case 'M':
+ frame->message = value;
+ break;
+ case 'D':
+ frame->deport = value;
+ break;
+ case 'I':
+ frame->agi = value;
+ break;
+ case 'P':
+ frame->sm_power = value;
+ break;
+ case 'T':
+ frame->taxe = value;
+ break;
+ case 't':
+ frame->sm_type = value;
+ break;
+ case 'r':
+ frame->sm_relais = value;
+ break;
+ case 'f':
+ frame->sm_flotte = value;
+ break;
+ case 'm':
+ frame->sm_mor = value;
+ break;
+ case 'd':
+ frame->sm_mop_demandee = value;
+ break;
+ case 'c':
+ frame->crins = value;
+ break;
+ case 'a':
+ frame->chan_assign = value;
+ break;
+ case 's':
+ frame->sequence = value;
+ break;
+ case 'i':
+ frame->invitation = value;
+ break;
+ case 'n':
+ frame->nconv = value;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ frame->digit[element - '0'] = value;
+ break;
+ }
+}
+
+static uint64_t fetch_element(frame_t *frame, char element)
+{
+ switch(element) {
+ case 'V':
+ return frame->voie;
+ case 'C':
+ return frame->channel;
+ case 'R':
+ return frame->relais;
+ case 'M':
+ return frame->message;
+ case 'D':
+ return frame->deport;
+ case 'I':
+ return frame->agi;
+ case 'P':
+ return frame->sm_power;
+ case 'T':
+ return frame->taxe;
+ case 't':
+ return frame->sm_type;
+ case 'r':
+ return frame->sm_relais;
+ case 'f':
+ return frame->sm_flotte;
+ case 'm':
+ return frame->sm_mor;
+ case 'd':
+ return frame->sm_mop_demandee;
+ case 'c':
+ return frame->crins;
+ case 'a':
+ return frame->chan_assign;
+ case 's':
+ return frame->sequence;
+ case 'i':
+ return frame->invitation;
+ case 'n':
+ return frame->nconv;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return frame->digit[element - '0'];
+ }
+ return 0;
+}
+
+static struct r2000_frame {
+ int dir;
+ uint8_t message;
+ const char *def;
+ const char *name;
+} r2000_frame_def[] = {
+ /* V Channel-Relais---Msg--t--HomeRel--MobieID--------- Supervisory----- */
+ /* messages REL->SM */
+ { REL_TO_SM, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----ccc----DDDIII++---PT---", "INSCRIPTION ACK" }, /* inscription ack */
+ { REL_TO_SM, 2, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "PLEASE WAIT" }, /* waiting on CC */
+ { REL_TO_SM, 1, "V-CCCCCCCCRRRRRRRRRMMMMM----------------------------------------DDDIII++---PT---", "IDLE" }, /* broadcast */
+ { REL_TO_SM, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN INCOMING"}, /* assign incoming call */
+ { REL_TO_SM, 4, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN GROUP"}, /* assign groupp call */
+ { REL_TO_SM, 5, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN OUTGOING"}, /* assign outgoing call */
+ { REL_TO_SM, 9, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "RELEASE ON CC" }, /* release call on CC */
+ { REL_TO_SM, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "IDENTITY REQ"}, /* request identity */
+ { REL_TO_SM, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----nnniiii----------------", "INVITATION"}, /* invitation */
+ { REL_TO_SM, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "RELEASE ON TC"}, /* release call */
+ { REL_TO_SM, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "SUSPEND REQ"}, /* suspend after dialing */
+ /* messages SM->REL */
+ { SM_TO_REL, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "INSCRIPTION REQ" }, /* inscription */
+ { SM_TO_REL, 1, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PRIVATE)" }, /* request call */
+ { SM_TO_REL, 1, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmddddddddssss", "CALL REQ (GROUP)" }, /* request call */
+ { SM_TO_REL, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PUBLIC)" }, /* request call */
+ { SM_TO_REL, 6, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON CC" }, /* release on CC */
+ { SM_TO_REL, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "IDENTITY ACK" }, /* identity response */
+ { SM_TO_REL, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "ANSWER" }, /* answer */
+ { SM_TO_REL, 19, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 1..10" }, /* first 10 digits */
+ { SM_TO_REL, 20, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 11..20" }, /* second 10 digits */
+ { SM_TO_REL, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON TC" }, /* release call on TC */
+ { SM_TO_REL, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "SUSPEND ACK" }, /* release after dialing */
+ { 0, 0, NULL, NULL }
+};
+
+static const char *get_frame_def(uint8_t message, int dir)
+{
+ int i;
+
+ for (i = 0; r2000_frame_def[i].def; i++) {
+ if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir)
+ return r2000_frame_def[i].def;
+ }
+
+ return NULL;
+}
+
+const char *r2000_dir_name(int dir)
+{
+ return (dir == REL_TO_SM) ? "REL->SM" : "SM->REL";
+}
+
+const char *r2000_frame_name(int message, int dir)
+{
+ static char result[32];
+ int i;
+
+ for (i = 0; r2000_frame_def[i].def; i++) {
+ if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir) {
+ sprintf(result, "%s (%d)", r2000_frame_def[i].name, message);
+ return result;
+ }
+ }
+
+ sprintf(result, "UNKNOWN (%d)", message);
+ return result;
+}
+
+static void display_bits(const char *def, const uint8_t *message, int num, int debug)
+{
+ char dispbits[num + 1];
+ int i;
+
+ if (debuglevel > debug)
+ return;
+
+ /* display bits */
+ if (def)
+ PDEBUG(DFRAME, debug, "%s\n", def);
+ for (i = 0; i < num; i++) {
+ dispbits[i] = ((message[i / 8] >> (7 - (i & 7))) & 1) + '0';
+ }
+ dispbits[i] = '\0';
+ PDEBUG(DFRAME, debug, "%s\n", dispbits);
+}
+
+static int dissassemble_frame(frame_t *frame, const uint8_t *message, int num)
+{
+ int i;
+ const char *def;
+ uint64_t value;
+ int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
+
+ memset(frame, 0, sizeof(*frame));
+
+ frame->message = message[2] & 0x1f;
+ def = get_frame_def(frame->message, dir);
+ if (!def) {
+ PDEBUG(DFRAME, DEBUG_NOTICE, "Received unknown message type %d (maybe radio noise)\n", frame->message);
+ display_bits(NULL, message, num, DEBUG_NOTICE);
+ return -EINVAL;
+ }
+
+ PDEBUG(DFRAME, DEBUG_DEBUG, "Decoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
+
+ /* dissassemble elements elements */
+ value = 0;
+ for (i = 0; i < num; i++) {
+ value = (value << 1) | ((message[i / 8] >> (7 - (i & 7))) & 1);
+ if (def[i + 1] != def[i]) {
+ if (def[i] != '-') {
+ print_element(def[i], value, dir, DEBUG_DEBUG);
+ store_element(frame, def[i], value);
+ }
+ value = 0;
+ }
+ }
+
+ display_bits(def, message, num, DEBUG_DEBUG);
+
+ return 0;
+}
+
+static int assemble_frame(frame_t *frame, uint8_t *message, int num, int debug)
+{
+ int i;
+ const char *def;
+ uint64_t value = 0; // make GCC happy
+ char element;
+ int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
+
+ def = get_frame_def(frame->message, dir);
+ if (!def) {
+ PDEBUG(DFRAME, DEBUG_ERROR, "Cannot assemble unknown message type %d, please define/fix!\n", frame->message);
+ abort();
+ }
+ memset(message, 0, (num + 7) / 8);
+
+ if (debug)
+ PDEBUG(DFRAME, DEBUG_DEBUG, "Ccoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
+
+ /* assemble elements elements */
+ element = 0;
+ for (i = num - 1; i >= 0; i--) {
+ if (element != def[i]) {
+ element = def[i];
+ switch (def[i]) {
+ case '-':
+ value = 0;
+ break;
+ case '+':
+ value = 0xffffffffffffffff;
+ break;
+ default:
+ value = fetch_element(frame, element);
+ }
+ }
+ message[i / 8] |= (value & 1) << (7 - (i & 7));
+ value >>= 1;
+ }
+
+ if (debug) {
+ for (i = 0; i < num; i++) {
+ if (def[i + 1] != def[i] && def[i] != '-' && def[i] != '+') {
+ value = fetch_element(frame, def[i]);
+ print_element(def[i], value, dir, DEBUG_DEBUG);
+ }
+ }
+
+ display_bits(def, message, num, DEBUG_DEBUG);
+ }
+
+ return 0;
+}
+
+/* encode frame to bits
+ */
+const char *encode_frame(frame_t *frame, int debug)
+{
+ uint8_t message[11], code[23];
+ static char bits[32 + 176 + 1];
+ int i;
+
+ assemble_frame(frame, message, 80, debug);
+
+ /* hagelbarger code */
+ hagelbarger_encode(message, code, 88);
+ memcpy(bits, "10101010101010101010111100010010", 32);
+ for (i = 0; i < 176; i++)
+ bits[i + 32] = ((code[i / 8] >> (7 - (i & 7))) & 1) + '0';
+ bits[208] = '\0';
+
+ return bits;
+}
+
+//#define GEGENPROBE
+
+/* decode bits to frame */
+int decode_frame(frame_t *frame, const char *bits)
+{
+ uint8_t message[11], code[23];
+ int i, num = strlen(bits);
+
+#ifdef GEGENPROBE
+ printf("bits as received=%s\n", bits);
+#endif
+ /* hagelbarger code */
+ memset(code, 0x00, sizeof(code));
+ for (i = 0; i < num; i++)
+ code[i / 8] |= (bits[i] & 1) << (7 - (i & 7));
+ hagelbarger_decode(code, message, num / 2 - 6);
+
+#if 0
+ for (i = 0; i < num / 2; i++) {
+ printf("%d", (message[i / 8] >> (7 - (i & 7))) & 1);
+ if ((i & 7) == 7)
+ printf(" = 0x%02x\n", message[i / 8]);
+ }
+#endif
+
+#ifdef GEGENPROBE
+ hagelbarger_encode(message, code, num / 2);
+ printf("bits after re-encoding=");
+ for (i = 0; i < num; i++)
+ printf("%d", (code[i / 8] >> (7 - (i & 7))) & 1);
+ printf("\n");
+#endif
+
+ return dissassemble_frame(frame, message, num / 2 - 8);
+}
+
diff --git a/src/r2000/frame.h b/src/r2000/frame.h
new file mode 100644
index 0000000..932d27d
--- /dev/null
+++ b/src/r2000/frame.h
@@ -0,0 +1,33 @@
+
+typedef struct frame {
+ uint8_t voie;
+ uint8_t channel;
+ uint16_t relais;
+ uint8_t message;
+ uint16_t deport;
+ uint16_t agi;
+ uint16_t sm_power;
+ uint16_t taxe;
+ uint8_t sm_type;
+ uint16_t sm_relais;
+ uint16_t sm_flotte;
+ uint16_t sm_mor;
+ uint16_t sm_mop_demandee;
+ uint8_t chan_assign;
+ uint8_t crins; /* inscription response DANGER: never set to 3, it will brick the phone! */
+ uint16_t sequence;
+ uint16_t invitation;
+ uint8_t nconv; /* supervisory digit 0..7 to send via 50 Baud modem */
+ uint8_t digit[10];
+} frame_t;
+
+#define REL_TO_SM 0
+#define SM_TO_REL 1
+
+const char *param_agi(uint64_t value);
+const char *param_aga(uint64_t value);
+const char *param_crins(uint64_t value);
+const char *r2000_frame_name(int message, int dir);
+int decode_frame(frame_t *frame, const char *bits);
+const char *encode_frame(frame_t *frame, int debug);
+
diff --git a/src/r2000/image.c b/src/r2000/image.c
new file mode 100644
index 0000000..53cdf66
--- /dev/null
+++ b/src/r2000/image.c
@@ -0,0 +1,84 @@
+#include <stdio.h>
+#include <string.h>
+#include "image.h"
+
+const char *image[] = {
+ "",
+ " @B/ \\",
+ " / @W/ \\@B \\",
+ " | @W/ @R/ \\@W \\@B |",
+ " | @W| @R| @y|@R |@W |@B |",
+ " | @W\\ @R\\ @y|@R /@W /@B |",
+ " @W__________ @B\\ @W\\ @y/|\\@W /@B /",
+ " @W_( _____) @B\\ @y|###|@B /",
+ " @W(_____ )__ @yHXH",
+ " @W(_____) @y:X:",
+ " @y:X:",
+ " @yIXI @W_________",
+ " @yIXI @W___( ___)",
+ " @yHXH @W(_ __)",
+ " @W____ @yHXH @W(______)",
+ " @W(_ )_ @y'XXX'",
+ " @W(____) @y'XXX'",
+ " @y:XXX:",
+ " @y:XXX:",
+ " @yHXXXH",
+ " @WRadiocom 2000 @y.XXXXX.",
+ " @y:XXXXX:",
+ " @W~ @y_/XXXXXYX\\_",
+ " @W~ @y\\#########/",
+ " @y/XX/XXX\\XX\\",
+ " @y/XX/ \\XX\\ @W~",
+ " @y_/XX/ \\XX\\_",
+ " @y|/|X/|~|~|~|~|\\X|\\| @W~ ~",
+ " @G(###) @y###################",
+ " @G(####)(#####()) @y/XX/X\\_X_X_X_X_X/\\XX\\ @G(#)",
+ " (#################) @y/XX/\\/ \\/\\XX\\ @G((####)#######)",
+ " (#######)(#########) @y/XX// \\\\XX\\ @G(#####))############)",
+ "(############)(######) @y./XX/ @wo @t~@y \\XX\\.@G(####)###############)",
+ "(######)))(############) @y/####\\ @w'O'@y /####\\@G(()(######)(##########)@W",
+ 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 'r': /* red */
+ printf("\033[0;31m");
+ break;
+ case 'R': /* red */
+ printf("\033[1;31m");
+ break;
+ case 'B': /* blue */
+ printf("\033[1;34m");
+ break;
+ case 'w': /* white */
+ printf("\033[0;37m");
+ break;
+ case 't': /* turquoise */
+ printf("\033[0;36m");
+ break;
+ case 'G': /* green */
+ printf("\033[0;32m");
+ break;
+ case 'W': /* white */
+ printf("\033[1;37m");
+ break;
+ case 'y': /* yellow */
+ printf("\033[0;33m");
+ break;
+ }
+ } else
+ printf("%c", image[i][j]);
+ }
+ printf("\n");
+ }
+ printf("\033[0;39m");
+}
+
diff --git a/src/r2000/image.h b/src/r2000/image.h
new file mode 100644
index 0000000..08a6f5b
--- /dev/null
+++ b/src/r2000/image.h
@@ -0,0 +1,3 @@
+
+void print_image(void);
+
diff --git a/src/r2000/main.c b/src/r2000/main.c
new file mode 100644
index 0000000..bb58db1
--- /dev/null
+++ b/src/r2000/main.c
@@ -0,0 +1,397 @@
+/* Radiocom 2000 main
+ *
+ * (C) 2017 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 <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "../common/sample.h"
+#include "../common/main.h"
+#include "../common/debug.h"
+#include "../common/timer.h"
+#include "../common/mncc_sock.h"
+#include "r2000.h"
+#include "dsp.h"
+#include "frame.h"
+#include "tones.h"
+#include "image.h"
+
+/* settings */
+static int band = 1;
+static int num_chan_type = 0;
+static int relais = 32;
+static int deport = 0;
+static int agi = 7;
+static int sm_power = 0;
+static int taxe = 0;
+static int crins = 0, destruction = 0; /* neven set CRINS to 3 and destruction to other than 0 here! */
+static int nconv = 0;
+static int recall = 0;
+enum r2000_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_TC };
+
+void print_help(const char *arg0)
+{
+ print_help_common(arg0, "-R <relais number> [option] ");
+ /* - - */
+ printf(" -B --band <number> | list\n");
+ printf(" -B --bande <number> | list\n");
+ printf(" Give frequency band, use 'list' to get a list. (default = '%d')\n", band);
+ printf(" -T --channel-type <channel type> | list\n");
+ printf(" Give channel type, use 'list' to get a list. (default = '%s')\n", chan_type_short_name(chan_type[0]));
+ printf(" -R --relais <relais number>\n");
+ printf(" Give relais number (base station ID) 1..511. (default = '%d')\n", relais);
+ printf(" Be sure to set the station mobile to the same relais number!\n");
+ printf(" --deport 0..7\n");
+ printf(" Supervisory information to tell about sub-stations.\n");
+ printf(" The functionality is unknown. (default = '%d')\n", deport);
+ printf(" -I --agi 0..7 | list\n");
+ printf(" Supervisory information to tell which phone is allowed to register\n");
+ printf(" Use 'list' to get a list of possible valued.\n");
+ printf(" (default = '%d' = %s)\n", agi, param_agi(agi));
+#if 0
+ printf(" -A --aga 0..3 | list\n");
+ printf(" Supervisory information to tell which phone is allowed to call\n");
+ printf(" Use 'list' to get a list of possible valued.\n");
+ printf(" (default = '%d' = %s)\n", aga, param_aga(aga));
+#endif
+ printf(" -P --sm-power <power level>\n");
+ printf(" Give power level of the station mobile 0..1. (default = '%d')\n", sm_power);
+ printf(" 0 = low (about 1 Watts) 1 = high (up to 10 Watts)\n");
+ printf(" --taxe 0..1\n");
+ printf(" Supervisory information to tell about rate information.\n");
+ printf(" The functionality is unknown. (default = '%d')\n", taxe);
+ printf(" -C --crins 0..7 | list [--destruction YES]\n");
+ printf(" Result that will be returned when the phone registers.\n");
+ printf(" NEVER USE '3', IT WILL DESTROY YOUR PHONE, but shows a warning first!\n");
+ printf(" Use 'list' to get a list of possible valued.\n");
+ printf(" (default = '%d' = %s)\n", crins, param_crins(crins));
+ printf(" -N --nconv 0..7\n");
+ printf(" Supervisory digit, sent during conversation. (default = '%d')\n", nconv);
+ printf(" It is used to detect lost signal. When using multiple traffic\n");
+ printf(" channels, this value is incremented per channel.\n");
+ printf(" -S --recall\n");
+ printf(" Suspend outgoing call after dialing and recall when called party has\n");
+ printf(" answered.\n");
+ printf("\nstation-id: Give 1 digit of station mobile type + 3 digits of home relais ID\n");
+ printf(" + 5 digits of mobile ID.\n");
+ printf(" (e.g. 103200819 = type 1, relais ID 32, mobile ID 819)\n");
+ print_hotkeys_common();
+}
+
+#define OPT_BANDE 256
+#define OPT_DEPORT 257
+#define OPT_TAXE 258
+#define OPT_DESTRUCTION 259
+
+static int handle_options(int argc, char **argv)
+{
+ int skip_args = 0;
+
+ static struct option long_options_special[] = {
+ {"band", 1, 0, 'B'},
+ {"bande", 1, 0, OPT_BANDE},
+ {"channel-type", 1, 0, 'T'},
+ {"relais", 1, 0, 'R'},
+ {"deport", 1, 0, OPT_DEPORT},
+ {"agi", 1, 0, 'I'},
+ {"sm-power", 1, 0, 'P'},
+ {"taxe", 1, 0, OPT_TAXE},
+ {"crins", 1, 0, 'C'},
+ {"destruction", 1, 0, OPT_DESTRUCTION},
+ {"nconv", 1, 0, 'N'},
+ {"recall", 1, 0, 'S'},
+ {0, 0, 0, 0}
+ };
+
+ set_options_common("B:T:R:I:P:C:N:S", long_options_special);
+
+ while (1) {
+ int option_index = 0, c, rc;
+
+ c = getopt_long(argc, argv, optstring, long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'B':
+ case OPT_BANDE:
+ if (!strcmp(optarg, "list")) {
+ r2000_band_list();
+ exit(0);
+ }
+ band = atoi(optarg);
+ skip_args += 2;
+ break;
+ case 'T':
+ if (!strcmp(optarg, "list")) {
+ r2000_channel_list();
+ exit(0);
+ }
+ rc = r2000_channel_by_short_name(optarg);
+ if (rc < 0) {
+ fprintf(stderr, "Error, channel type '%s' unknown. Please use '-t list' to get a list. I suggest to use the default.\n", optarg);
+ exit(0);
+ }
+ OPT_ARRAY(num_chan_type, chan_type, rc)
+ skip_args += 2;
+ break;
+ case 'R':
+ relais = atoi(optarg);
+ if (relais > 511)
+ relais = 511;
+ if (relais < 1)
+ relais = 1;
+ skip_args += 2;
+ break;
+ case OPT_DEPORT:
+ deport = atoi(optarg);
+ if (deport > 7)
+ deport = 7;
+ if (deport < 0)
+ deport = 0;
+ skip_args += 2;
+ break;
+ case 'I':
+ if (!strcmp(optarg, "list")) {
+ int i;
+
+ printf("\nList of possible AGI (inscription permission) codes:\n\n");
+ printf("Value\tDescription\n");
+ printf("------------------------------------------------------------------------\n");
+ for (i = 0; i < 8; i++)
+ printf("%d\t%s\n", i, param_agi(i));
+ exit(0);
+ }
+ agi = atoi(optarg);
+ if (agi < 0 || agi > 7) {
+ fprintf(stderr, "Error, given inscription permission (AGI) %d is invalid, use 'list' to get a list of values!\n", agi);
+ exit(0);
+ }
+ skip_args += 2;
+ break;
+ case 'P':
+ sm_power = atoi(optarg);
+ if (sm_power > 1)
+ sm_power = 1;
+ if (sm_power < 0)
+ sm_power = 0;
+ skip_args += 2;
+ break;
+ case OPT_TAXE:
+ taxe = atoi(optarg);
+ if (taxe > 1)
+ taxe = 1;
+ if (taxe < 0)
+ taxe = 0;
+ skip_args += 2;
+ break;
+#if 0
+ case 'A':
+ if (!strcmp(optarg, "list")) {
+ int i;
+
+ printf("\nList of possible AGA (call permission) codes:\n\n");
+ printf("Value\tDescription\n");
+ printf("------------------------------------------------------------------------\n");
+ for (i = 0; i < 4; i++)
+ printf("%d\t%s\n", i, param_aga(i));
+ exit(0);
+ }
+ aga = atoi(optarg);
+ if (aga < 0 || aga > 3) {
+ fprintf(stderr, "Error, given call permission (AGA) %d is invalid, use 'list' to get a list of values!\n", aga);
+ exit(0);
+ }
+ skip_args += 2;
+ break;
+#endif
+ case 'C':
+ if (!strcmp(optarg, "list")) {
+ int i;
+
+ printf("\nList of possible CRINS (inscription response) codes:\n\n");
+ printf("Value\tDescription\n");
+ printf("------------------------------------------------------------------------\n");
+ for (i = 0; i < 8; i++)
+ printf("%d\t%s\n", i, param_crins(i));
+ exit(0);
+ }
+ crins = atoi(optarg);
+ if (crins < 0 || crins > 7) {
+ fprintf(stderr, "Error, given inscription response (CRINS) %d is invalid, use 'list' to get a list of values!\n", crins);
+ exit(0);
+ }
+ skip_args += 2;
+ break;
+ case OPT_DESTRUCTION:
+ if (!strcmp(optarg, "YES")) {
+ destruction = 2342;
+ }
+ skip_args += 2;
+ break;
+ case 'N':
+ nconv = atoi(optarg);
+ if (nconv > 7)
+ nconv = 7;
+ if (nconv < 0)
+ nconv = 0;
+ skip_args += 2;
+ break;
+ case 'S':
+ recall = 1;
+ skip_args += 1;
+ break;
+ default:
+ opt_switch_common(c, argv[0], &skip_args);
+ }
+ }
+
+ free(long_options);
+
+ return skip_args;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ int skip_args;
+ const char *station_id = "";
+ int mandatory = 0;
+ int i;
+
+ /* init tones */
+ init_radiocom_tones();
+
+ skip_args = handle_options(argc, argv);
+ argc -= skip_args;
+ argv += skip_args;
+
+ if (argc > 1) {
+ station_id = argv[1];
+ if (strlen(station_id) != 9) {
+ printf("Given station ID '%s' does not have 9 digits\n", station_id);
+ return 0;
+ }
+ }
+
+ if (!num_kanal) {
+ printf("No channel (\"Kanal\") is specified, I suggest channel 160 (-k 160).\n\n");
+ mandatory = 1;
+ }
+ if (use_sdr) {
+ /* set audiodev */
+ for (i = 0; i < num_kanal; i++)
+ audiodev[i] = "sdr";
+ num_audiodev = num_kanal;
+ /* set channel types for more than 1 channel */
+ if (num_kanal > 1 && num_chan_type == 0) {
+ chan_type[0] = CHAN_TYPE_CC;
+ for (i = 1; i < num_kanal; i++)
+ chan_type[i] = CHAN_TYPE_TC;
+ num_chan_type = 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);
+ }
+ if (num_kanal == 1 && num_chan_type == 0)
+ num_chan_type = 1; /* use default */
+ if (num_kanal != num_chan_type) {
+ fprintf(stderr, "You need to specify as many channel types as you have channels.\n");
+ exit(0);
+ }
+
+ if (mandatory) {
+ print_help(argv[-skip_args]);
+ return 0;
+ }
+
+ /* check for destruction of the phone (crins 3 will brick it) */
+ if (crins == 3) {
+ fprintf(stderr, "\n*******************************************************************************\n");
+ fprintf(stderr, "You selected inscription response '3'!\n\n");
+ fprintf(stderr, "This feature was used by the operators to destroy a stolen/modified phone.\n");
+ fprintf(stderr, "This will brick/destroy/kill/make ALL PHONES USELESS, if registering!\n");
+ fprintf(stderr, "PHONE WILL LOCK AND/OR SUBSCRIBER DATA WILL BE ERASED!!! IS THAT WHAT YOU WANT?\n");
+ fprintf(stderr, "I had to hack the firmware of my phone to unbrick it. Can you do that too?\n");
+ if (!destruction)
+ fprintf(stderr, "If you can unlock your phone later, then use '--destruction YES' to confirm.\n");
+ else
+ fprintf(stderr, "\n **** PRESS CTRL+c TO ABORT THIS FEATURE, NOW! **** Press enter to continue.\n\n");
+ fprintf(stderr, "*******************************************************************************\n\n");
+ if (!destruction)
+ exit(0);
+ else
+ getchar();
+ }
+
+ if (!loopback && crins != 3)
+ print_image();
+
+ /* init functions */
+ dsp_init();
+
+ /* 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 strongly 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 50 baud supervisory signalling arround 150 Hz will not be tranmitted by\n");
+ fprintf(stderr, "regular radio, use direct input to the PLL of your transmitter (or use SDR).\n");
+ fprintf(stderr, "*******************************************************************************\n");
+ }
+
+ /* create transceiver instance */
+ for (i = 0; i < num_kanal; i++) {
+ rc = r2000_create(band, kanal[i], chan_type[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, relais, deport, agi, sm_power, taxe, crins, destruction, nconv, recall, loopback);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to create transceiver instance. Quitting!\n");
+ goto fail;
+ }
+ printf("base station on channel %d ready, please tune transmitter to %.4f MHz and receiver to %.4f MHz.\n", kanal[i], r2000_channel2freq(band, kanal[i], 0) / 1e6, r2000_channel2freq(band, kanal[i], 1) / 1e6);
+ nconv = (nconv + 1) & 7;
+ }
+
+ r2000_check_channels();
+
+ main_common(&quit, latency, interval, NULL, station_id, 9);
+
+fail:
+ /* destroy transceiver instance */
+ while (sender_head)
+ r2000_destroy(sender_head);
+
+ return 0;
+}
+
diff --git a/src/r2000/r2000.c b/src/r2000/r2000.c
new file mode 100644
index 0000000..d1f570c
--- /dev/null
+++ b/src/r2000/r2000.c
@@ -0,0 +1,1589 @@
+/* Radiocom 2000 protocol handling
+ *
+ * (C) 2017 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 r2000->sender.kanal
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include "../common/sample.h"
+#include "../common/debug.h"
+#include "../common/timer.h"
+#include "../common/cause.h"
+#include "r2000.h"
+//#include "transaction.h"
+#include "frame.h"
+#include "dsp.h"
+
+#define CUT_OFF_EMPHASIS_R2000 300 //FIXME: use real cut-off / time constant
+
+#define PAGE_TRIES 2 /* how many times trying to page */
+#define IDENT_TIME 3.0 /* time to wait for identity response */
+#define ALERT_TIME 60.0 /* time to wait for party to answer */
+#define DIAL1_TIME 1.0 /* time to wait for party to dial digits 1..10 */
+#define DIAL2_TIME 0.5 /* time to wait for party to dial digits 11..20 */
+#define SUSPEND_TIME 1.0 /* time to wait for suspend response */
+#define SUPER_TIME1 4.0 /* time to release if not receiving initial supervisory signal */
+#define SUPER_TIME2 20.0 /* time to release after loosing supervisory signal */
+#define RELEASE_TIME 2.0 /* time to wait for release response */
+
+/* Call reference for calls from station mobile to network
+ This offset of 0x400000000 is required for MNCC interface. */
+static int new_callref = 0x40000000;
+
+/* definiton of bands and channels */
+#define CHANNEL_SPACING 0.0125
+
+static struct r2000_bands {
+ int number;
+ const char *name;
+ double dl_f0; /* first downlink channel (0) */
+ int channels; /* number of channels (including 0) */
+ double duplex; /* duplex distance (uplink below downlink) */
+} r2000_bands[] = {
+ { 1, "UHF", 424.8000, 256, 10.0 },
+ { 3, "VHF A/B", 169.8000, 296, 4.6 },
+ { 4, "VHF 5/6/1", 176.5000, 176, -8.0 },
+ { 5, "VHF 5/6/2", 178.7000, 192, -8.0 },
+ { 6, "VHF 5/6/3", 181.1000, 192, -8.0 },
+ { 7, "VHF 7/8/1", 200.5000, 176, 8.0 },
+ { 8, "VHF 7/8/2", 202.7000, 192, 8.0 },
+ { 9, "VHF 7/8/3", 205.1000, 192, 8.0 },
+ { 10, "VHF 9/10/1", 208.5000, 176, -8.0 },
+ { 11, "VHF 9/10/2", 210.7000, 192, -8.0 },
+ { 12, "VHF 9/10/3", 213.1000, 192, -8.0 },
+ { 0, NULL, 0.0, 0, 0.0 }
+};
+
+void r2000_band_list(void)
+{
+ int i;
+
+ printf("Bande\tName\t\tChannels\tDownlink\t\tUplink\n");
+ printf("--------------------------------------------------------------------------\n");
+ for (i = 0; r2000_bands[i].name; i++) {
+ printf("%d\t%s%s\t0 .. %d\t%.4f..%.4f MHz\t%5.1f MHz\n",
+ r2000_bands[i].number,
+ r2000_bands[i].name,
+ (strlen(r2000_bands[i].name) >= 8) ? "" : "\t",
+ r2000_bands[i].channels - 1,
+ r2000_bands[i].dl_f0,
+ r2000_bands[i].dl_f0 + CHANNEL_SPACING * (double)(r2000_bands[i].channels - 1),
+ -r2000_bands[i].duplex);
+ }
+}
+
+/* Convert band+channel number to frequency number of base station.
+ Set 'uplink' to 1 to get frequency of station mobile. */
+double r2000_channel2freq(int band, int channel, int uplink)
+{
+ int i;
+ double freq;
+
+ for (i = 0; r2000_bands[i].name; i++) {
+ if (r2000_bands[i].number == band)
+ break;
+ }
+
+ if (!r2000_bands[i].name) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Given band number is invalid! (use '-B list' for valid bands)\n");
+ return 0.0;
+ }
+
+ if (channel < 0 || channel > r2000_bands[i].channels - 1) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Given channel number %d invalid! (use '-B list' for valid channels)\n", channel);
+ return 0.0;
+ }
+
+ freq = r2000_bands[i].dl_f0 + CHANNEL_SPACING * (double)(channel);
+ if (uplink)
+ freq -= r2000_bands[i].duplex;
+
+ return freq * 1e6;
+}
+
+const char *r2000_state_name(enum r2000_state state)
+{
+ static char invalid[16];
+
+ switch (state) {
+ case STATE_NULL:
+ return "(NULL)";
+ case STATE_IDLE:
+ return "IDLE";
+ case STATE_INSCRIPTION:
+ return "INSCRIPTION";
+ case STATE_OUT_ASSIGN:
+ return "OUT ASSIGN";
+ case STATE_IN_ASSIGN:
+ return "IN ASSIGN";
+ case STATE_RECALL_ASSIGN:
+ return "RECALL ASSIGN";
+ case STATE_OUT_IDENT:
+ return "OUT IDENT";
+ case STATE_IN_IDENT:
+ return "IN IDENT";
+ case STATE_RECALL_IDENT:
+ return "RECALL IDENT";
+ case STATE_OUT_DIAL1:
+ return "OUT DIAL1";
+ case STATE_OUT_DIAL2:
+ return "OUT DIAL2";
+ case STATE_SUSPEND:
+ return "SUSPEND";
+ case STATE_RECALL_WAIT:
+ return "RECALL WAIT";
+ case STATE_IN_ALERT:
+ return "IN ALERT";
+ case STATE_OUT_ALERT:
+ return "OUT ALERT";
+ case STATE_RECALL_ALERT:
+ return "RECALL ALERT";
+ case STATE_ACTIVE:
+ return "ACTIVE";
+ case STATE_RELEASE_CC:
+ return "RELEASE CC";
+ case STATE_RELEASE_TC:
+ return "RELEASE TC";
+ }
+
+ sprintf(invalid, "invalid(%d)", state);
+ return invalid;
+}
+
+static const char *print_subscriber_subscr(r2000_subscriber_t *subscr);
+
+void r2000_display_status(void)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+
+ display_status_start();
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ display_status_channel(r2000->sender.kanal, chan_type_short_name(r2000->sysinfo.chan_type), r2000_state_name(r2000->state));
+ if (r2000->state != STATE_IDLE) {
+ char result[32];
+ sprintf(result, "%s", print_subscriber_subscr(&r2000->subscriber));
+ display_status_subscriber(result, NULL);
+ }
+ }
+ display_status_end();
+}
+
+static struct r2000_channels {
+ enum r2000_chan_type chan_type;
+ const char *short_name;
+ const char *long_name;
+} r2000_channels[] = {
+ { CHAN_TYPE_CC, "CC", "control channel" },
+ { CHAN_TYPE_TC, "TC", "taffic channel" },
+ { CHAN_TYPE_CC_TC, "CC/TC","combined control & taffic" },
+ { 0, NULL, NULL }
+};
+
+void r2000_channel_list(void)
+{
+ int i;
+
+ printf("Type\t\tDescription\n");
+ printf("------------------------------------------------------------------------\n");
+ for (i = 0; r2000_channels[i].long_name; i++)
+ printf("%s%s\t%s\n", r2000_channels[i].short_name, (strlen(r2000_channels[i].short_name) >= 8) ? "" : "\t", r2000_channels[i].long_name);
+}
+
+int r2000_channel_by_short_name(const char *short_name)
+{
+ int i;
+
+ for (i = 0; r2000_channels[i].short_name; i++) {
+ if (!strcasecmp(r2000_channels[i].short_name, short_name)) {
+ PDEBUG(DR2000, DEBUG_INFO, "Selecting channel '%s' = %s\n", r2000_channels[i].short_name, r2000_channels[i].long_name);
+ return r2000_channels[i].chan_type;
+ }
+ }
+
+ return -1;
+}
+
+const char *chan_type_short_name(enum r2000_chan_type chan_type)
+{
+ int i;
+
+ for (i = 0; r2000_channels[i].short_name; i++) {
+ if (r2000_channels[i].chan_type == chan_type)
+ return r2000_channels[i].short_name;
+ }
+
+ return "invalid";
+}
+
+const char *chan_type_long_name(enum r2000_chan_type chan_type)
+{
+ int i;
+
+ for (i = 0; r2000_channels[i].long_name; i++) {
+ if (r2000_channels[i].chan_type == chan_type)
+ return r2000_channels[i].long_name;
+ }
+
+ return "invalid";
+}
+
+static void r2000_new_state(r2000_t *r2000, enum r2000_state new_state)
+{
+ if (r2000->state == new_state)
+ return;
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "State change: %s -> %s\n", r2000_state_name(r2000->state), r2000_state_name(new_state));
+ r2000->state = new_state;
+ r2000_display_status();
+ r2000->tx_frame_count = 0;
+}
+
+/* used to print station mobile data */
+static const char *print_subscriber_frame(frame_t *frame)
+{
+ static char result[32];
+
+ sprintf(result, "%d,%03d,%05d", frame->sm_type, frame->sm_relais, frame->sm_mor);
+
+ return result;
+}
+static const char *print_subscriber_subscr(r2000_subscriber_t *subscr)
+{
+ static char result[32];
+
+ sprintf(result, "%d,%03d,%05d", subscr->type, subscr->relais, subscr->mor);
+
+ return result;
+}
+
+/* convert station mobile id to 9 digits caller data */
+static const char *subscriber2string(r2000_subscriber_t *subscr)
+{
+ static char result[32];
+
+ sprintf(result, "%d%03d%05d", subscr->type, subscr->relais, subscr->mor);
+
+ return result;
+}
+
+/* convert 9-digits dial string to station mobile data */
+static int string2subscriber(const char *dialstring, r2000_subscriber_t *subscr)
+{
+ char check[6];
+ int type, relais, mor;
+ int i;
+
+ if (strlen(dialstring) != 9) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Wrong number of digits, use 9 digits: TRRRXXXXX (T=type, R=relais, X=mobile number)\n");
+ return -1;
+ }
+
+ for (i = 0; i < (int)strlen(dialstring); i++) {
+ if (dialstring[i] < '0' || dialstring[i] > '9') {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Invalid digit in dial string, use only 0..9.\n");
+ return -1;
+ }
+ }
+
+ memcpy(check, dialstring, 1);
+ check[1] = '\0';
+ type = atoi(check);
+ if (type < 1 || type > 511) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Invalid station type in dial string, use 0..7 as station mobile type.\n");
+ return -1;
+ }
+
+ memcpy(check, dialstring + 1, 3);
+ check[3] = '\0';
+ relais = atoi(check);
+ if (relais < 1 || relais > 511) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Invalid relais number in dial string, use 000..511 as relais number.\n");
+ return -1;
+ }
+
+ memcpy(check, dialstring + 4, 5);
+ check[5] = '\0';
+ mor = atoi(check);
+ if (mor > 65535) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Invalid mobile number in dial string, use 00000..65535 as mobile number.\n");
+ return -1;
+ }
+
+ subscr->type = type;
+ subscr->relais = relais;
+ subscr->mor = mor;
+ return 0;
+}
+
+static int match_voie(r2000_t *r2000, frame_t *frame, uint8_t voie)
+{
+ if (frame->voie == 0 && voie == 1) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Frame for control channel, but expecting traffic channel, ignoring. (maybe radio noise)\n");
+ return 0;
+ }
+ if (frame->voie == 1 && voie == 0) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Frame for traffic channel, but expecting control channel, ignoring. (maybe radio noise)\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int match_channel(r2000_t *r2000, frame_t *frame)
+{
+ if (frame->channel != r2000->sender.kanal) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Frame for different channel %d received, ignoring.\n", frame->channel);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int match_relais(r2000_t *r2000, frame_t *frame)
+{
+ if (frame->relais != r2000->sysinfo.relais) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Frame for different relais %d received, ignoring.\n", frame->relais);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int match_subscriber(r2000_t *r2000, frame_t *frame)
+{
+ if (r2000->subscriber.relais != frame->sm_relais
+ || r2000->subscriber.mor != frame->sm_mor) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Frame for different subscriber '%s' received, ignoring.\n", print_subscriber_frame(frame));
+ return 0;
+ }
+
+ return 1;
+}
+
+/* convert nconv to supervisory digit to be transmitted to phone */
+uint8_t r2000_encode_super(r2000_t *r2000)
+{
+ uint8_t super, nconv, relais;
+
+ nconv = r2000->sysinfo.nconv;
+ relais = r2000->sysinfo.relais & 0xf;
+
+ /* LSB first */
+ super = ((nconv << 2) & 0x04)
+ | (nconv & 0x02)
+ | ((nconv >> 2) & 0x01)
+ | ((relais << 6) & 0x40)
+ | ((relais << 4) & 0x20)
+ | ((relais << 2) & 0x10)
+ | (relais & 0x08);
+
+ PDEBUG_CHAN(DDSP, DEBUG_INFO, "TX Supervisory: NCONV: %d relais (4 lowest bits): %d\n", nconv, relais);
+
+ return super ^ 0x7f;
+}
+
+static void r2000_timeout(struct timer *timer);
+
+/* Create transceiver instance and link to a list. */
+int r2000_create(int band, int channel, enum r2000_chan_type chan_type, 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, uint16_t relais, uint8_t deport, uint8_t agi, uint8_t sm_power, uint8_t taxe, uint8_t crins, int destruction, uint8_t nconv, int recall, int loopback)
+{
+ sender_t *sender;
+ r2000_t *r2000 = NULL;
+ int rc;
+
+ /* check channel matching and set deviation factor */
+ if (r2000_channel2freq(band, channel, 0) == 0.0)
+ return -EINVAL;
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *)sender;
+ if ((r2000->sysinfo.chan_type == CHAN_TYPE_CC || r2000->sysinfo.chan_type == CHAN_TYPE_CC_TC)
+ && (chan_type == CHAN_TYPE_CC || chan_type == CHAN_TYPE_CC_TC)) {
+ PDEBUG(DCNETZ, DEBUG_NOTICE, "More than one control channel is not supported, please use traffic channels!\n");
+ return -EINVAL;
+ }
+ }
+
+ r2000 = calloc(1, sizeof(r2000_t));
+ if (!r2000) {
+ PDEBUG(DR2000, DEBUG_ERROR, "No memory!\n");
+ return -ENOMEM;
+ }
+
+ PDEBUG(DR2000, DEBUG_DEBUG, "Creating 'Radiocom 2000' instance for channel = %d (sample rate %d).\n", channel, samplerate);
+
+ /* init general part of transceiver */
+ rc = sender_create(&r2000->sender, channel, r2000_channel2freq(band, channel, 0), r2000_channel2freq(band, channel, 1), audiodev, use_sdr, samplerate, rx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, 0, PAGING_SIGNAL_NONE);
+ if (rc < 0) {
+ PDEBUG(DR2000, DEBUG_ERROR, "Failed to init transceiver process!\n");
+ goto error;
+ }
+
+ timer_init(&r2000->timer, r2000_timeout, r2000);
+ r2000->sysinfo.relais = relais;
+ r2000->sysinfo.chan_type = chan_type;
+ r2000->sysinfo.deport = deport;
+ r2000->sysinfo.agi = agi;
+ r2000->sysinfo.sm_power = sm_power;
+ r2000->sysinfo.taxe = taxe;
+ r2000->sysinfo.crins = crins;
+ r2000->sysinfo.nconv = nconv;
+ r2000->sysinfo.recall = recall;
+ if (crins == 3 && destruction != 2342) {
+ PDEBUG(DR2000, DEBUG_ERROR, "Crins is 3, but destruction is not confirmed, please fix!\n");
+ abort();
+ }
+ r2000->compandor = 1;
+
+ r2000->pre_emphasis = pre_emphasis;
+ r2000->de_emphasis = de_emphasis;
+ rc = init_emphasis(&r2000->estate, samplerate, CUT_OFF_EMPHASIS_R2000);
+ if (rc < 0)
+ goto error;
+
+ r2000->pre_emphasis = pre_emphasis;
+ r2000->de_emphasis = de_emphasis;
+ rc = init_emphasis(&r2000->estate, samplerate, CUT_OFF_EMPHASIS_R2000);
+ if (rc < 0)
+ goto error;
+
+ /* init audio processing */
+ rc = dsp_init_sender(r2000);
+ if (rc < 0) {
+ PDEBUG(DR2000, DEBUG_ERROR, "Failed to init audio processing!\n");
+ goto error;
+ }
+
+ /* go into idle state */
+ r2000_go_idle(r2000);
+
+ PDEBUG(DR2000, DEBUG_NOTICE, "Created channel #%d of type '%s' = %s\n", channel, chan_type_short_name(chan_type), chan_type_long_name(chan_type));
+
+ return 0;
+
+error:
+ r2000_destroy(&r2000->sender);
+
+ return rc;
+}
+
+void r2000_check_channels(void)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+ int cc = 0, tc = 0, combined = 0;
+ int note = 0;
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->sysinfo.chan_type == CHAN_TYPE_CC)
+ cc = 1;
+ if (r2000->sysinfo.chan_type == CHAN_TYPE_TC)
+ tc = 1;
+ if (r2000->sysinfo.chan_type == CHAN_TYPE_CC_TC) {
+ cc = 1;
+ tc = 1;
+ combined = 1;
+ }
+ }
+ if (cc && !tc) {
+ if (note)
+ PDEBUG(DNMT, DEBUG_NOTICE, "\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for control only.\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** No call from the mobile phone is possible on this channel.\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** Use combined 'CC/TC' instead!\n");
+ note = 1;
+ }
+ if (tc && !cc) {
+ if (note)
+ PDEBUG(DNMT, DEBUG_NOTICE, "\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected channel(s) can be used for traffic only.\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** No call is possible at all!\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** Use combined 'CC/TC' instead!\n");
+ note = 1;
+ }
+ if (combined) {
+ if (note)
+ PDEBUG(DNMT, DEBUG_NOTICE, "\n");
+ PDEBUG(DNMT, DEBUG_NOTICE, "*** Selected combined 'CC/TC' some phones might reject this.\n");
+ note = 1;
+ }
+}
+
+/* Destroy transceiver instance and unlink from list. */
+void r2000_destroy(sender_t *sender)
+{
+ r2000_t *r2000 = (r2000_t *) sender;
+
+ PDEBUG(DR2000, DEBUG_DEBUG, "Destroying 'Radiocom 2000' instance for channel = %d.\n", sender->kanal);
+ dsp_cleanup_sender(r2000);
+ timer_exit(&r2000->timer);
+ sender_destroy(&r2000->sender);
+ free(r2000);
+}
+
+/* go idle and return to frame mode */
+void r2000_go_idle(r2000_t *r2000)
+{
+ timer_stop(&r2000->timer);
+
+ if (r2000->callref) {
+ PDEBUG(DR2000, DEBUG_ERROR, "Going idle, but still having callref, please fix!\n");
+ call_in_release(r2000->callref, CAUSE_NORMAL);
+ r2000->callref = 0;
+ }
+
+ if (r2000->sysinfo.chan_type == CHAN_TYPE_TC) {
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Entering IDLE state, no transmission at relais %d on %s.\n", r2000->sysinfo.relais, chan_type_long_name(r2000->sysinfo.chan_type));
+ r2000_set_dsp_mode(r2000, DSP_MODE_OFF, -1);
+ } else {
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Entering IDLE state, sending idle frames of relais %d on %s.\n", r2000->sysinfo.relais, chan_type_long_name(r2000->sysinfo.chan_type));
+ r2000_set_dsp_mode(r2000, DSP_MODE_FRAME, (r2000->sender.loopback) ? r2000_encode_super(r2000) : -1);
+ }
+ r2000_new_state(r2000, STATE_IDLE);
+
+// r2000_set_dsp_mode(r2000, DSP_MODE_AUDIO, r2000_encode_super(r2000));
+}
+
+/* release towards station mobile */
+void r2000_release(r2000_t *r2000)
+{
+ if (r2000->state == STATE_IDLE
+ || r2000->state == STATE_OUT_ASSIGN
+ || r2000->state == STATE_IN_ASSIGN
+ || r2000->state == STATE_RECALL_ASSIGN
+ || r2000->state == STATE_RECALL_WAIT) {
+ /* release on CC */
+ r2000_new_state(r2000, STATE_RELEASE_CC);
+ timer_start(&r2000->timer, RELEASE_TIME);
+ } else {
+ /* release on TC */
+ r2000_new_state(r2000, STATE_RELEASE_TC);
+ timer_start(&r2000->timer, RELEASE_TIME);
+ }
+ r2000_set_dsp_mode(r2000, DSP_MODE_FRAME, -1);
+}
+
+static void r2000_page(r2000_t *r2000, int try, enum r2000_state state)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Entering paging state (try %d), sending 'Appel' to '%s'.\n", try, print_subscriber_subscr(&r2000->subscriber));
+ r2000_new_state(r2000, state);
+ r2000->page_try = try;
+}
+
+static r2000_t *get_free_chan(enum r2000_chan_type chan_type)
+{
+ sender_t *sender;
+ r2000_t *r2000, *combined = NULL;
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ /* only search for idle channel */
+ if (r2000->state != STATE_IDLE)
+ continue;
+ /* found exactly what we want */
+ if (r2000->sysinfo.chan_type == chan_type)
+ return r2000;
+ /* use combined channel as alternative */
+ if (!combined && r2000->sysinfo.chan_type == CHAN_TYPE_CC_TC)
+ combined = r2000;
+ }
+
+ /* return alternative, if any */
+ return combined;
+}
+
+/* try to move call to given channel, release callref, if not possible */
+static r2000_t *move_call_to_chan(r2000_t *old_r2000, enum r2000_chan_type chan_type)
+{
+ r2000_t *new_r2000 = get_free_chan(chan_type);
+
+ /* no free channel, reuse combined channel, if possible, or release call */
+ if (!new_r2000 && old_r2000->sysinfo.chan_type == CHAN_TYPE_CC_TC) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "No %s found, straying on %s!\n", chan_type_long_name(chan_type), chan_type_long_name(old_r2000->sysinfo.chan_type));
+ return old_r2000;
+ }
+ if (!new_r2000) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Cannot move us to %s, because there is no free channel!\n", chan_type_long_name(chan_type));
+ if (old_r2000->callref) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Failed to assign channel, releasing towards network\n");
+ call_in_release(old_r2000->callref, CAUSE_NOCHANNEL);
+ old_r2000->callref = 0;
+ }
+ r2000_release(old_r2000);
+ return NULL;
+ }
+
+ /* move subscriber */
+ memcpy(&new_r2000->subscriber, &old_r2000->subscriber, sizeof(r2000_subscriber_t));
+
+ /* move callref */
+ new_r2000->callref = old_r2000->callref;
+
+ /* move dsp mode */
+ r2000_set_dsp_mode(new_r2000, old_r2000->dsp_mode, -1);
+
+ /* move call state */
+ r2000_new_state(new_r2000, old_r2000->state);
+
+ /* cleanup old channel */
+ old_r2000->callref = 0;
+ r2000_go_idle(old_r2000);
+
+ return new_r2000;
+}
+
+/*
+ * idle process
+ */
+
+/* trasmit beacon */
+static void tx_idle(r2000_t __attribute__((unused)) *r2000, frame_t *frame)
+{
+ frame->voie = 1;
+ frame->message = 1;
+}
+
+/*
+ * registration process
+ */
+
+/* receive registration */
+static void rx_idle(r2000_t *r2000, frame_t *frame)
+{
+ if (!match_voie(r2000, frame, 0))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 0:
+ /* inscription */
+ r2000->subscriber.type = frame->sm_type;
+ r2000->subscriber.relais = frame->sm_relais;
+ r2000->subscriber.mor = frame->sm_mor;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received inscription from station mobile '%s'\n", print_subscriber_frame(frame));
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, " -> Home Relais: %d'\n", frame->sm_relais);
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, " -> Mobile ID: %d'\n", frame->sm_mor);
+
+ r2000_new_state(r2000, STATE_INSCRIPTION);
+ break;
+ case 1:
+ case 3:
+ /* call request */
+ r2000->subscriber.type = frame->sm_type;
+ r2000->subscriber.relais = frame->sm_relais;
+ r2000->subscriber.mor = frame->sm_mor;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received outgoing call from station mobile '%s'\n", print_subscriber_frame(frame));
+
+ r2000_t *tc = get_free_chan(CHAN_TYPE_TC);
+ if (!tc) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Rejecting mobile originated call, no free traffic channel\n");
+ r2000_release(r2000);
+ return;
+ }
+ r2000_new_state(r2000, STATE_OUT_ASSIGN);
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* confirm registration */
+static void tx_inscription(r2000_t *r2000, frame_t *frame)
+{
+ frame->voie = 1;
+ frame->message = 0;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+ frame->crins = r2000->sysinfo.crins;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending inscription acknowledge\n");
+
+ r2000_go_idle(r2000);
+}
+
+/*
+ * channel assignment process
+ */
+
+/* confirm dialing, assign outgoing call */
+static void tx_out_assign(r2000_t *r2000, frame_t *frame)
+{
+ /* NOTE: We can only send this frame once, because afterwards we
+ * have moved to the new channel already!
+ */
+
+ /* move us to tc */
+ r2000_t *tc = move_call_to_chan(r2000, CHAN_TYPE_TC);
+ if (!tc) {
+ tx_idle(r2000, frame);
+ return;
+ }
+
+ frame->voie = 1;
+ frame->message = 5;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+ frame->chan_assign = tc->sender.kanal;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending outgoing assignment from channel %d to %d\n", r2000->sender.kanal, tc->sender.kanal);
+
+ r2000_new_state(tc, (tc->state == STATE_OUT_ASSIGN) ? STATE_OUT_IDENT : STATE_RECALL_IDENT);
+ timer_start(&tc->timer, IDENT_TIME);
+}
+
+/* page phone, assign incoming call */
+static void tx_in_assign(r2000_t *r2000, frame_t *frame)
+{
+ /* NOTE: We can only send this frame once, because afterwards we
+ * have moved to the new channel already!
+ */
+
+ /* move us to tc */
+ r2000_t *tc = move_call_to_chan(r2000, CHAN_TYPE_TC);
+ if (!tc) {
+ tx_idle(r2000, frame);
+ return;
+ }
+
+ frame->voie = 1;
+ frame->message = 3;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+ frame->chan_assign = tc->sender.kanal;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending incoming assignment from channel %d to %d\n", r2000->sender.kanal, tc->sender.kanal);
+
+ r2000_new_state(tc, STATE_IN_IDENT);
+ timer_start(&tc->timer, IDENT_TIME);
+}
+
+/*
+ * identity process
+ */
+
+/* identity request on assigned channel */
+static void tx_ident(r2000_t *r2000, frame_t *frame)
+{
+ frame->voie = 0;
+ frame->message = 16;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending identity requrest\n");
+}
+
+/* receive identity response */
+static void rx_ident(r2000_t *r2000, frame_t *frame)
+{
+ if (!match_voie(r2000, frame, 1))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+ if (!match_subscriber(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 16:
+ /* identity response */
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received identity response from station mobile '%s'\n", print_subscriber_frame(frame));
+
+ switch (r2000->state) {
+ case STATE_IN_IDENT:
+ /* alert the phone */
+ r2000_new_state(r2000, STATE_IN_ALERT);
+ timer_start(&r2000->timer, ALERT_TIME);
+ call_in_alerting(r2000->callref);
+ break;
+ case STATE_RECALL_IDENT:
+ /* alert the phone */
+ r2000_new_state(r2000, STATE_RECALL_ALERT);
+ timer_start(&r2000->timer, ALERT_TIME);
+ break;
+ case STATE_OUT_IDENT:
+ /* request dial string */
+ r2000_new_state(r2000, STATE_OUT_DIAL1);
+ timer_start(&r2000->timer, DIAL1_TIME);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* no identity response from phone */
+static void timeout_out_ident(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout receiving identity (outgoing call)\n");
+
+ r2000_release(r2000);
+}
+
+static void timeout_in_ident(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout receiving identity (incoming call)\n");
+
+ /* move us back to cc */
+ r2000 = move_call_to_chan(r2000, CHAN_TYPE_CC);
+ if (!r2000)
+ return;
+
+ /* page again ... */
+ if (--r2000->page_try) {
+ r2000_page(r2000, r2000->page_try, (r2000->callref) ? STATE_IN_ASSIGN: STATE_RECALL_ASSIGN);
+ return;
+ }
+
+ /* ... or release */
+ if (r2000->callref) {
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Phone does not response, releasing towards network\n");
+ call_in_release(r2000->callref, CAUSE_OUTOFORDER);
+ r2000->callref = 0;
+ }
+ r2000_release(r2000);
+}
+
+/*
+ * alerting process (mobile rings)
+ */
+
+static void tx_invitation(r2000_t *r2000, frame_t *frame, uint16_t invitation, uint8_t nconv)
+{
+ frame->voie = 0;
+ frame->message = 17;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+ frame->invitation = invitation;
+ frame->nconv = nconv;
+}
+
+/* alert the phone */
+static void tx_alert(r2000_t *r2000, frame_t *frame)
+{
+ tx_invitation(r2000, frame, 3, r2000->sysinfo.nconv);
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending answer invitation to station mobile\n");
+}
+
+static int setup_call(r2000_t *r2000)
+{
+ int callref = ++new_callref;
+ int rc;
+
+ /* make call toward network */
+ PDEBUG(DR2000, DEBUG_INFO, "Setup call to network.\n");
+ rc = call_in_setup(callref, subscriber2string(&r2000->subscriber), r2000->subscriber.dialing);
+ if (rc < 0) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", -rc);
+ r2000_release(r2000);
+ return rc;
+ }
+ r2000->callref = callref;
+
+ return 0;
+}
+
+/* receive answer */
+static void rx_alert(r2000_t *r2000, frame_t *frame)
+{
+ if (!match_voie(r2000, frame, 1))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+ if (!match_subscriber(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 17:
+ /* answer */
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received answer from station mobile '%s'\n", print_subscriber_frame(frame));
+
+ switch (r2000->state) {
+ case STATE_IN_ALERT:
+ /* answer incomming call */
+ PDEBUG(DR2000, DEBUG_INFO, "Answer call to network.\n");
+ call_in_answer(r2000->callref, subscriber2string(&r2000->subscriber));
+ break;
+ case STATE_OUT_ALERT:
+ /* setup call, possible r2000_release() is called there! */
+ if (setup_call(r2000) < 0)
+ return;
+ break;
+ default:
+ /* answer after recall, stop recall tone */
+ call_tone_recall(r2000->callref, 0);
+ break;
+ }
+ /* go active */
+ timer_stop(&r2000->timer);
+ r2000_new_state(r2000, STATE_ACTIVE);
+ r2000_set_dsp_mode(r2000, DSP_MODE_AUDIO_TX, r2000_encode_super(r2000));
+ /* start supervisory timer */
+ timer_start(&r2000->timer, SUPER_TIME1);
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* no answer */
+static void timeout_alert(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout while alerting\n");
+
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Phone does not response, releasing towards network\n");
+ if (r2000->callref) {
+ call_in_release(r2000->callref, CAUSE_NOANSWER);
+ r2000->callref = 0;
+ }
+ r2000_release(r2000);
+}
+
+/*
+ * dialing process (mobile dials)
+ */
+
+/* request digits from the phone */
+static void tx_out_dial(r2000_t *r2000, frame_t *frame)
+{
+ tx_invitation(r2000, frame, 10, 0);
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending dialing invitation to station mobile\n");
+}
+
+/* receive digits */
+static void rx_out_dial1(r2000_t *r2000, frame_t *frame)
+{
+ int i;
+
+ if (!match_voie(r2000, frame, 1))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 19:
+ /* digits */
+ for (i = 0; i < 10; i++)
+ r2000->subscriber.dialing[i] = frame->digit[i] + '0';
+ r2000->subscriber.dialing[10] = '\0';
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received digits 1..10 from station mobile: %s\n", r2000->subscriber.dialing);
+
+ r2000_new_state(r2000, STATE_OUT_DIAL2);
+ timer_start(&r2000->timer, DIAL2_TIME);
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* no digits */
+static void timeout_out_dial1(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout while receiving digits (outgoing call)\n");
+
+ r2000_release(r2000);
+}
+
+/* receive digits */
+static void rx_out_dial2(r2000_t *r2000, frame_t *frame)
+{
+ int i;
+
+ if (!match_voie(r2000, frame, 1))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 20:
+ /* digits */
+ for (i = 0; i < 10; i++)
+ r2000->subscriber.dialing[i + 10] = frame->digit[i] + '0';
+ r2000->subscriber.dialing[20] = '\0';
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received digits 11..20 from station mobile: %s\n", r2000->subscriber.dialing);
+
+ if (r2000->sysinfo.recall) {
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Suspending call until called party has answered\n");
+ r2000_new_state(r2000, STATE_SUSPEND);
+ timer_start(&r2000->timer, SUSPEND_TIME);
+ } else {
+ r2000_new_state(r2000, STATE_OUT_ALERT);
+ timer_start(&r2000->timer, ALERT_TIME);
+ }
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* no additional digits */
+static void timeout_out_dial2(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Phone does not send digits 11..20\n");
+
+ if (r2000->sysinfo.recall) {
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Suspending call until called party has answered\n");
+ r2000_new_state(r2000, STATE_SUSPEND);
+ timer_start(&r2000->timer, SUSPEND_TIME);
+ } else {
+ r2000_new_state(r2000, STATE_OUT_ALERT);
+ timer_start(&r2000->timer, ALERT_TIME);
+ }
+}
+
+/* release after dialing */
+static void tx_suspend(r2000_t *r2000, frame_t *frame)
+{
+ frame->voie = 0;
+ frame->message = 26;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending suspend frame\n");
+}
+
+/* release response */
+static void rx_suspend(r2000_t *r2000, frame_t *frame)
+{
+ if (!match_voie(r2000, frame, 1))
+ return;
+ if (!match_channel(r2000, frame))
+ return;
+ if (!match_relais(r2000, frame))
+ return;
+ if (!match_subscriber(r2000, frame))
+ return;
+
+ switch(frame->message) {
+ case 26:
+ /* suspend ack */
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received suspend response from station mobile '%s'\n", print_subscriber_frame(frame));
+
+ timer_stop(&r2000->timer);
+ /* move us back to cc */
+ r2000 = move_call_to_chan(r2000, CHAN_TYPE_CC);
+ if (!r2000)
+ return;
+ r2000_new_state(r2000, STATE_RECALL_WAIT);
+ /* setup call, possible r2000_release() is called there! */
+ if (setup_call(r2000) < 0)
+ return;
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame->message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+/* response to accept frame */
+static void timeout_suspend(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Phone does not respond to suspend frame\n");
+
+ r2000_release(r2000);
+}
+
+/*
+ * process during active call
+ */
+
+static void timeout_active(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout after loosing supervisory signal, releasing call\n");
+
+ call_in_release(r2000->callref, CAUSE_TEMPFAIL);
+ r2000->callref = 0;
+ r2000_release(r2000);
+}
+
+/*
+ * release process
+ */
+
+static void tx_release_cc(r2000_t *r2000, frame_t *frame)
+{
+ frame->voie = 1;
+ frame->message = 9;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending release towards station mobile\n");
+}
+
+static void timeout_release_cc(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Done sending release, going idle\n");
+
+ r2000_go_idle(r2000);
+}
+
+static void tx_release_tc(r2000_t *r2000, frame_t *frame)
+{
+ frame->voie = 0;
+ frame->message = 24;
+ frame->sm_type = r2000->subscriber.type;
+ frame->sm_relais = r2000->subscriber.relais;
+ frame->sm_mor = r2000->subscriber.mor;
+
+ if (r2000->tx_frame_count == 1)
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Sending release towards station mobile\n");
+
+}
+
+static void timeout_release_tc(r2000_t *r2000)
+{
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Timeout while sending release, going idle\n");
+
+ r2000_go_idle(r2000);
+}
+
+/* FSK processing requests next frame after transmission of previous
+ frame has been finished. */
+const char *r2000_get_frame(r2000_t *r2000)
+{
+ frame_t frame;
+ const char *bits;
+ int debug = 1;
+
+ r2000->tx_frame_count++;
+
+ memset(&frame, 0, sizeof(frame));
+ frame.channel = r2000->sender.kanal;
+ frame.relais = r2000->sysinfo.relais;
+ frame.deport = r2000->sysinfo.deport;
+ frame.agi = r2000->sysinfo.agi;
+ frame.sm_power = r2000->sysinfo.sm_power;
+ frame.taxe = r2000->sysinfo.taxe;
+
+ switch (r2000->state) {
+ case STATE_IDLE:
+ case STATE_RECALL_WAIT:
+ tx_idle(r2000, &frame);
+ debug = 0;
+ break;
+ case STATE_INSCRIPTION:
+ tx_inscription(r2000, &frame);
+ break;
+ case STATE_OUT_ASSIGN:
+ case STATE_RECALL_ASSIGN:
+ tx_out_assign(r2000, &frame);
+ break;
+ case STATE_IN_ASSIGN:
+ tx_in_assign(r2000, &frame);
+ break;
+ case STATE_OUT_IDENT:
+ case STATE_RECALL_IDENT:
+ case STATE_IN_IDENT:
+ tx_ident(r2000, &frame);
+ break;
+ case STATE_OUT_DIAL1:
+ case STATE_OUT_DIAL2:
+ tx_out_dial(r2000, &frame);
+ break;
+ case STATE_SUSPEND:
+ tx_suspend(r2000, &frame);
+ break;
+ case STATE_IN_ALERT:
+ case STATE_OUT_ALERT:
+ case STATE_RECALL_ALERT:
+ tx_alert(r2000, &frame);
+ break;
+ case STATE_RELEASE_CC:
+ tx_release_cc(r2000, &frame);
+ break;
+ case STATE_RELEASE_TC:
+ tx_release_tc(r2000, &frame);
+ break;
+ default:
+ /* in case there is no handling, change to audio mode */
+ /* this should not happen, but prevents an endless loop
+ * when the DSP tries to get next frame. */
+ r2000_set_dsp_mode(r2000, DSP_MODE_AUDIO_TX_RX, -1);
+ }
+
+ /* frame sending aborted (e.g. due to audio) */
+ if (r2000->dsp_mode != DSP_MODE_FRAME)
+ return NULL;
+
+ bits = encode_frame(&frame, debug);
+
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Sending frame %s.\n", r2000_frame_name(frame.message, REL_TO_SM));
+ return bits;
+}
+
+void r2000_receive_frame(r2000_t *r2000, const char *bits, double quality, double level)
+{
+ frame_t frame;
+ int rc;
+
+ PDEBUG_CHAN(DDSP, DEBUG_INFO, "RX Level: %.0f%% Quality=%.0f\n", level * 100.0, quality * 100.0);
+
+ rc = decode_frame(&frame, bits);
+ if (rc < 0) {
+ PDEBUG_CHAN(DR2000, (r2000->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received invalid frame.\n");
+ return;
+ }
+
+ if (r2000->sender.loopback)
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Received frame %s\n", r2000_frame_name(frame.message, REL_TO_SM));
+ else
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Received frame %s\n", r2000_frame_name(frame.message, SM_TO_REL));
+
+ if (r2000->sender.loopback)
+ return;
+
+ /* release */
+ if (frame.message == 6 || frame.message == 24) {
+ if (r2000->state == STATE_IDLE)
+ return;
+ if (!match_voie(r2000, &frame, (frame.message < 16) ? 0 : 1))
+ return;
+ if (!match_channel(r2000, &frame))
+ return;
+ if (!match_relais(r2000, &frame))
+ return;
+ if (!match_subscriber(r2000, &frame))
+ return;
+
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Received release from station mobile\n");
+
+ if (r2000->callref) {
+ call_in_release(r2000->callref, CAUSE_NORMAL);
+ r2000->callref = 0;
+ }
+ r2000_go_idle(r2000);
+ return;
+ }
+
+ switch (r2000->state) {
+ case STATE_IDLE:
+ rx_idle(r2000, &frame);
+ break;
+ case STATE_OUT_IDENT:
+ case STATE_RECALL_IDENT:
+ case STATE_IN_IDENT:
+ rx_ident(r2000, &frame);
+ break;
+ case STATE_OUT_DIAL1:
+ rx_out_dial1(r2000, &frame);
+ break;
+ case STATE_OUT_DIAL2:
+ rx_out_dial2(r2000, &frame);
+ break;
+ case STATE_SUSPEND:
+ rx_suspend(r2000, &frame);
+ break;
+ case STATE_IN_ALERT:
+ case STATE_OUT_ALERT:
+ case STATE_RECALL_ALERT:
+ rx_alert(r2000, &frame);
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_DEBUG, "Dropping frame %s in state %s\n", r2000_frame_name(frame.message, SM_TO_REL), r2000_state_name(r2000->state));
+ }
+}
+
+void r2000_receive_super(r2000_t *r2000, uint8_t super, double quality, double level)
+{
+ uint8_t nconv, relais;
+
+ /* invert, if received from base station */
+ if (r2000->sender.loopback)
+ super ^= 0x7f;
+
+ /* decode supervisory digit (nconv is LSB first) */
+ nconv = ((super >> 2) & 0x01)
+ | (super & 0x02)
+ | ((super << 2) & 0x04);
+ relais = ((super >> 6) & 0x01)
+ | ((super >> 4) & 0x02)
+ | ((super >> 2) & 0x04)
+ | (super & 0x08);
+
+ PDEBUG_CHAN(DDSP, DEBUG_INFO, "RX Supervisory: NCONV: %d Relais (4 lowest bits): %d RX Level: %.0f%% Quality=%.0f\n", nconv, relais, level * 100.0, quality * 100.0);
+
+ if (r2000->sender.loopback)
+ return;
+ if (r2000->state != STATE_ACTIVE)
+ return;
+
+ if (relais != (r2000->sysinfo.relais & 0xf)
+ || nconv != r2000->sysinfo.nconv)
+ return;
+
+ /* unmute RX audio if not already */
+ r2000_set_dsp_mode(r2000, DSP_MODE_AUDIO_TX_RX, -1);
+
+ /* reset supervisory timer */
+ timer_start(&r2000->timer, SUPER_TIME2);
+}
+
+/* Timeout handling */
+static void r2000_timeout(struct timer *timer)
+{
+ r2000_t *r2000 = (r2000_t *)timer->priv;
+
+ switch (r2000->state) {
+ case STATE_OUT_IDENT:
+ timeout_out_ident(r2000);
+ break;
+ case STATE_IN_IDENT:
+ case STATE_RECALL_IDENT:
+ timeout_in_ident(r2000);
+ break;
+ case STATE_OUT_DIAL1:
+ timeout_out_dial1(r2000);
+ break;
+ case STATE_OUT_DIAL2:
+ timeout_out_dial2(r2000);
+ break;
+ case STATE_SUSPEND:
+ timeout_suspend(r2000);
+ break;
+ case STATE_IN_ALERT:
+ case STATE_OUT_ALERT:
+ case STATE_RECALL_ALERT:
+ timeout_alert(r2000);
+ break;
+ case STATE_ACTIVE:
+ timeout_active(r2000);
+ break;
+ case STATE_RELEASE_CC:
+ timeout_release_cc(r2000);
+ break;
+ case STATE_RELEASE_TC:
+ timeout_release_tc(r2000);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * call states received from call control
+ */
+
+/* Call control starts call towards station mobile. */
+int call_out_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
+{
+ sender_t *sender;
+ r2000_t *r2000, *tc;
+ r2000_subscriber_t subscr;
+
+ memset(&subscr, 0, sizeof(subscr));
+
+ /* 1. convert number to station mobile identification, return INVALNUMBER */
+ if (string2subscriber(dialing, &subscr)) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
+ return -CAUSE_INVALNUMBER;
+ }
+
+ /* 2. check if given number is already in a call, return BUSY */
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->state != STATE_IDLE
+ && r2000->subscriber.relais == subscr.relais
+ && r2000->subscriber.mor == subscr.mor)
+ break;
+ }
+ if (sender) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
+ return -CAUSE_BUSY;
+ }
+
+ /* 3. check if all paging (control) channels are busy, return NOCHANNEL */
+ r2000 = get_free_chan(CHAN_TYPE_CC);
+ if (!r2000) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing call, but no free control channel, rejecting!\n");
+ return -CAUSE_NOCHANNEL;
+ }
+ tc = get_free_chan(CHAN_TYPE_TC);
+ if (!tc) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing call, but no free traffic channel, rejecting!\n");
+ return -CAUSE_NOCHANNEL;
+ }
+
+ PDEBUG(DR2000, DEBUG_INFO, "Call to station mobile, paging station id '%s'\n", print_subscriber_subscr(&subscr));
+
+ /* 4. trying to page station mobile */
+ memcpy(&r2000->subscriber, &subscr, sizeof(r2000_subscriber_t));
+ r2000->callref = callref;
+ r2000_page(r2000, PAGE_TRIES, STATE_IN_ASSIGN);
+
+ return 0;
+}
+
+/* Call control answers call toward station mobile. */
+void call_out_answer(int callref)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->callref == callref)
+ break;
+ }
+ if (!sender) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing answer, but no callref!\n");
+ call_in_release(callref, CAUSE_INVALCALLREF);
+ return;
+ }
+
+ switch (r2000->state) {
+ case STATE_RECALL_WAIT:
+ PDEBUG_CHAN(DR2000, DEBUG_INFO, "Call has been answered by network, recalling station mobile.\n");
+ r2000_page(r2000, PAGE_TRIES, STATE_RECALL_ASSIGN);
+ call_tone_recall(callref, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Call control sends disconnect (with tones).
+ * An active call stays active, so tones and annoucements can be received
+ * by station mobile.
+ */
+void call_out_disconnect(int callref, int __attribute__((unused)) cause)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+
+ PDEBUG(DR2000, DEBUG_INFO, "Call has been disconnected by network.\n");
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->callref == callref)
+ break;
+ }
+ if (!sender) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
+ call_in_release(callref, CAUSE_INVALCALLREF);
+ return;
+ }
+
+ /* Release when not active and not waiting for answer */
+ if (r2000->state == STATE_ACTIVE)
+ return;
+ switch (r2000->state) {
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Outgoing disconnect, during call setup, releasing!\n");
+ r2000->callref = 0;
+ r2000_release(r2000);
+ break;
+ }
+
+ call_in_release(callref, cause);
+}
+
+/* Call control releases call toward station mobile. */
+void call_out_release(int callref, int __attribute__((unused)) cause)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+
+ PDEBUG(DR2000, DEBUG_INFO, "Call has been released by network, releasing call.\n");
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->callref == callref)
+ break;
+ }
+ if (!sender) {
+ PDEBUG(DR2000, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
+ /* don't send release, because caller already released */
+ return;
+ }
+
+ r2000->callref = 0;
+
+ switch (r2000->state) {
+ case STATE_ACTIVE:
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Outgoing release, during ringing, releasing!\n");
+ r2000_release(r2000);
+ break;
+ default:
+ PDEBUG_CHAN(DR2000, DEBUG_NOTICE, "Outgoing release, during call setup, releasing!\n");
+ r2000_release(r2000);
+ break;
+ }
+}
+
+/* Receive audio from call instance. */
+void call_rx_audio(int callref, sample_t *samples, int count)
+{
+ sender_t *sender;
+ r2000_t *r2000;
+
+ for (sender = sender_head; sender; sender = sender->next) {
+ r2000 = (r2000_t *) sender;
+ if (r2000->callref == callref)
+ break;
+ }
+ if (!sender)
+ return;
+
+ if (r2000->dsp_mode == DSP_MODE_AUDIO_TX
+ || r2000->dsp_mode == DSP_MODE_AUDIO_TX_RX) {
+ sample_t up[(int)((double)count * r2000->sender.srstate.factor + 0.5) + 10];
+ if (r2000->compandor)
+ compress_audio(&r2000->cstate, samples, count);
+ count = samplerate_upsample(&r2000->sender.srstate, samples, count, up);
+ jitter_save(&r2000->sender.dejitter, up, count);
+ }
+}
+
+void dump_info(void) {}
+
diff --git a/src/r2000/r2000.h b/src/r2000/r2000.h
new file mode 100644
index 0000000..dbafcf2
--- /dev/null
+++ b/src/r2000/r2000.h
@@ -0,0 +1,135 @@
+#include "../common/compandor.h"
+#include "../common/sender.h"
+#include "../common/call.h"
+#include "../common/ffsk.h"
+
+enum dsp_mode {
+ DSP_MODE_OFF, /* no transmission */
+ DSP_MODE_AUDIO_TX, /* stream audio (TX only) */
+ DSP_MODE_AUDIO_TX_RX, /* stream audio */
+ DSP_MODE_FRAME, /* send frames */
+};
+
+enum r2000_chan_type {
+ CHAN_TYPE_CC, /* calling channel */
+ CHAN_TYPE_TC, /* traffic channel */
+ CHAN_TYPE_CC_TC, /* combined CC + TC */
+};
+
+enum r2000_state {
+ STATE_NULL = 0, /* power off state */
+ STATE_IDLE, /* channel is not in use */
+ STATE_INSCRIPTION, /* SM registers */
+ STATE_OUT_ASSIGN, /* assign outgoing call on CC */
+ STATE_IN_ASSIGN, /* assign incomming call on CC */
+ STATE_RECALL_ASSIGN, /* assign outgoing recall on CC */
+ STATE_OUT_IDENT, /* identity outgoing call on TC */
+ STATE_IN_IDENT, /* identity incomming call on TC */
+ STATE_RECALL_IDENT, /* identity outgoing recall on TC */
+ STATE_OUT_DIAL1, /* dialing outgoing call on TC */
+ STATE_OUT_DIAL2, /* dialing outgoing call on TC */
+ STATE_SUSPEND, /* suspend after dialing outgoing call on TC */
+ STATE_RECALL_WAIT, /* wait for calling back the phone */
+ STATE_IN_ALERT, /* alerting incomming call on TC */
+ STATE_OUT_ALERT, /* alerting outgoing call on TC */
+ STATE_RECALL_ALERT, /* alerting outgoing recall on TC */
+ STATE_ACTIVE, /* channel is in use */
+ STATE_RELEASE_CC, /* release call on CC */
+ STATE_RELEASE_TC, /* release call on TC */
+};
+
+typedef struct r2000_subscriber {
+ uint8_t type; /* mobile station type */
+ uint16_t relais; /* home relais */
+ uint16_t mor; /* mobile ID */
+ char dialing[21]; /* dial string */
+} r2000_subscriber_t;
+
+typedef struct r2000_sysinfo {
+ enum r2000_chan_type chan_type; /* channel type */
+ uint8_t deport; /* sub-station number */
+ uint8_t agi; /* inscription parameter */
+ uint8_t sm_power; /* station mobile power 1 = high */
+ uint8_t taxe; /* rate parameter */
+ uint16_t relais; /* relais ID */
+ uint8_t crins; /* response to inscription */
+ uint8_t nconv; /* supervisory value */
+ int recall; /* do a recall when called party answered */
+} r2000_sysinfo_t;
+
+typedef struct r2000 {
+ sender_t sender;
+ r2000_sysinfo_t sysinfo;
+ compandor_t cstate;
+ int pre_emphasis; /* use pre_emphasis by this instance */
+ int de_emphasis; /* use de_emphasis by this instance */
+ emphasis_t estate;
+
+ /* sender's states */
+ enum r2000_state state;
+ int callref;
+ struct timer timer;
+ r2000_subscriber_t subscriber;
+ int page_try; /* the try number of calling the mobile */
+ int tx_frame_count; /* to count repeated frames */
+
+ /* features */
+ int compandor; /* if compandor shall be used */
+
+ /* dsp states */
+ enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */
+ ffsk_t ffsk; /* ffsk processing */
+ uint16_t rx_sync; /* shift register to detect sync */
+ int rx_in_sync; /* if we are in sync and receive bits */
+ int rx_mute; /* mute count down after sync */
+ int rx_max; /* maximum bits to receive (including 32 bits sync sequence) */
+ char rx_frame[177]; /* receive frame (one extra byte to terminate string) */
+ int rx_count; /* next bit to receive */
+ double rx_level[256]; /* level infos */
+ double rx_quality[256]; /* quality infos */
+ sample_t *frame_spl; /* samples to store a complete rendered frame */
+ int frame_size; /* total size of sample buffer */
+ int frame_length; /* current length of data in sample buffer */
+ int frame_pos; /* current sample position in frame_spl */
+ uint64_t rx_bits_count; /* sample counter */
+ uint64_t rx_bits_count_current; /* sample counter of current frame */
+ uint64_t rx_bits_count_last; /* sample counter of last frame */
+
+ /* supervisory dsp states */
+ goertzel_t super_goertzel[2]; /* filter for fsk decoding */
+ int super_samples_per_window;/* how many samples to analyze in one window */
+ sample_t *super_filter_spl; /* array with samples_per_bit */
+ int super_filter_pos; /* current sample position in filter_spl */
+ int super_filter_step; /* number of samples for each analyzation */
+ int super_filter_bit; /* last bit, so we detect a bit change */
+ int super_filter_sample; /* count until it is time to sample bit */
+ sample_t *super_spl; /* samples to store a complete rendered frame */
+ int super_size; /* total size of sample buffer */
+ int super_length; /* current length of data in sample buffer */
+ int super_pos; /* current sample position in frame_spl */
+ double super_phaseshift65536[2];/* how much the phase of sine wave changes per sample */
+ double super_phase65536; /* current phase */
+ uint32_t super_rx_word; /* shift register for received supervisory info */
+ double super_rx_level[20]; /* level infos */
+ double super_rx_quality[20]; /* quality infos */
+ int super_rx_index; /* index for level and quality buffer */
+ uint32_t super_tx_word; /* supervisory info to transmit */
+double super_bittime;
+double super_bitpos;
+
+} r2000_t;
+
+void r2000_channel_list(void);
+int r2000_channel_by_short_name(const char *short_name);
+const char *chan_type_short_name(enum r2000_chan_type chan_type);
+const char *chan_type_long_name(enum r2000_chan_type chan_type);
+int r2000_create(int band, int channel, enum r2000_chan_type chan_type, 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, uint16_t relais, uint8_t deport, uint8_t agi, uint8_t sm_power, uint8_t taxe, uint8_t crins, int destruction, uint8_t nconv, int recall, int loopback);
+void r2000_check_channels(void);
+void r2000_destroy(sender_t *sender);
+void r2000_go_idle(r2000_t *r2000);
+void r2000_band_list(void);
+double r2000_channel2freq(int band, int channel, int uplink);
+const char *r2000_get_frame(r2000_t *r2000);
+void r2000_receive_frame(r2000_t *r2000, const char *bits, double quality, double level);
+void r2000_receive_super(r2000_t *r2000, uint8_t super, double quality, double level);
+
diff --git a/src/r2000/tones.c b/src/r2000/tones.c
new file mode 100644
index 0000000..5763c64
--- /dev/null
+++ b/src/r2000/tones.c
@@ -0,0 +1,68 @@
+
+#include <stdint.h>
+
+static int16_t pattern[] = {
+ 0x0000, 0x1483,
+ 0x269d, 0x3420, 0x3b7d, 0x3bd3, 0x3510, 0x280f, 0x164c, 0x01e5,
+ 0xed4c, 0xdadd, 0xcce2, 0xc4e1, 0xc3ee, 0xca06, 0xd68e, 0xe7f0,
+ 0xfc34, 0x10e4, 0x239a, 0x3217, 0x3aab, 0x3c48, 0x36d1, 0x2ace,
+ 0x19ce, 0x05b0, 0xf0f2, 0xddf4, 0xcf02, 0xc5d6, 0xc390, 0xc86a,
+ 0xd3da, 0xe481, 0xf869, 0x0d36, 0x2074, 0x2fdb, 0x3999, 0x3c89,
+ 0x384f, 0x2d6e, 0x1d2d, 0x097a, 0xf4a7, 0xe12c, 0xd154, 0xc706,
+ 0xc36e, 0xc707, 0xd154, 0xe12c, 0xf4a7, 0x0979, 0x1d2e, 0x2d6d,
+ 0x3850, 0x3c88, 0x399a, 0x2fdb, 0x2072, 0x0d39, 0xf865, 0xe486,
+ 0xd3d4, 0xc871, 0xc389, 0xc5dd, 0xcefb, 0xddfa, 0xf0ee, 0x05b3,
+ 0x19cc, 0x2acf, 0x36d0, 0x3c4a, 0x3aaa, 0x3216, 0x239b, 0x10e3,
+ 0xfc35, 0xe7f1, 0xd68c, 0xca08, 0xc3ec, 0xc4e3, 0xccdf, 0xdae1,
+ 0xed49, 0x01e7, 0x164b, 0x280e, 0x3511, 0x3bd4, 0x3b7b, 0x3422,
+ 0x269c, 0x1481, 0x0003, 0xeb7b, 0xd964, 0xcbe2, 0xc47e, 0xc433,
+ 0xcaea, 0xd7f6, 0xe9b2, 0xfe1a, 0x12b8, 0x251d, 0x3324, 0x3b1a,
+ 0x3c16, 0x35f7, 0x2975, 0x180d, 0x03cf, 0xef18, 0xdc69, 0xcde7,
+ 0xc559, 0xc3b3, 0xc935, 0xd52b, 0xe637, 0xfa4e, 0x0f0f, 0x220b,
+ 0x30ff, 0x3a28, 0x3c73, 0x3793, 0x2c28, 0x1b7d, 0x0799, 0xf2c8,
+ 0xdf8e, 0xd023, 0xc66a, 0xc375, 0xc7b2, 0xd291, 0xe2d2, 0xf689,
+ 0x0b57, 0x1ed7, 0x2ea8, 0x38fc, 0x3c90, 0x38fd, 0x2ea8, 0x1ed7,
+ 0x0b56, 0xf68a, 0xe2d1, 0xd293, 0xc7af, 0xc379, 0xc665, 0xd028,
+ 0xdf8a, 0xf2cc, 0x0795, 0x1b80, 0x2c26, 0x3795, 0x3c71, 0x3a2a,
+ 0x30fd, 0x220d, 0x0f0e, 0xfa4e, 0xe636, 0xd52d, 0xc934, 0xc3b3,
+ 0xc559, 0xcde7, 0xdc69, 0xef18, 0x03d0, 0x180a, 0x297a, 0x35f2,
+ 0x3c1a, 0x3b17, 0x3326, 0x251c, 0x12b9, 0xfe17, 0xe9b6, 0xd7f2,
+ 0xcaef, 0xc42d, 0xc483, 0xcbde, 0xd967, 0xeb7b,
+};
+
+static int16_t tone[12000];
+
+extern int16_t *ringback_spl;
+extern int ringback_size;
+extern int ringback_max;
+extern int16_t *busy_spl;
+extern int busy_size;
+extern int busy_max;
+extern int16_t *congestion_spl;
+extern int congestion_size;
+extern int congestion_max;
+
+void init_radiocom_tones(void)
+{
+ int i, j;
+
+
+ for (i = 0, j = 0; i < 12000; i++) {
+ tone[i] = pattern[j++];
+ if (j == 200)
+ j = 0;
+ }
+
+ ringback_spl = tone;
+ ringback_size = 12000;
+ ringback_max = 8 * 5000; /* 1500 / 3500 */
+
+ busy_spl = tone;
+ busy_size = 4000;
+ busy_max = 8 * 1000; /* 500 / 500 */
+
+ congestion_spl = tone;
+ congestion_size = 4000;
+ congestion_max = 8 * 1000; /* 500 / 500 */
+}
+
diff --git a/src/r2000/tones.h b/src/r2000/tones.h
new file mode 100644
index 0000000..0206d51
--- /dev/null
+++ b/src/r2000/tones.h
@@ -0,0 +1,3 @@
+
+void init_radiocom_tones(void);
+