diff --git a/.gitignore b/.gitignore index 2676b61..4ecf366 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ src/nmt/nmt src/amps/libamps.a src/amps/amps src/tacs/tacs +src/r2000/radiocom2000 sim/cnetz_sim src/test/test_filter src/test/test_compandor diff --git a/README b/README index 7c12a66..492d5fa 100644 --- a/README +++ b/README @@ -6,9 +6,10 @@ and from mobile phone. Currently supported networks: * A-Netz * B-Netz (ATF-1) * C-Netz - * NMT 450 (Nordic Mobile Telephone) + * NMT 450 / 900 (Nordic Mobile Telephone) * AMPS (Advanced Mobile Phone System) * TACS (Total Access Communication System) + * Radiocom 2000 (French network) USE AT YOUR OWN RISK! @@ -46,5 +47,8 @@ Association." Eric from Smart Card World and Karsten Niehusen from cardomatic.de for providing memory cards to be programmed for older C-Netz phone. -Dieter Spaar prividing TACS recordings to verify and debug TACS support. +Dieter Spaar providing TACS recordings to verify and debug TACS support. + +Hans Wigger providing Radiocom 2000 recordings to reverse-enigeer the signalling +system. diff --git a/configure.ac b/configure.ac index 10c1d15..5596bf3 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,7 @@ AC_OUTPUT( src/nmt/Makefile src/amps/Makefile src/tacs/Makefile + src/r2000/Makefile src/test/Makefile src/Makefile sim/Makefile diff --git a/docs/index.html b/docs/index.html index 66212b1..bf99d87 100644 --- a/docs/index.html +++ b/docs/index.html @@ -77,6 +77,7 @@ Implemented networks:
  • NMT - Nordic Mobile Telephone (Scandinavia)
  • AMPS - Advanced Mobile Phone Service (USA)
  • TACS - Total Access Communication System (UK/Italy)
  • +
  • Radiocom 2000 (France)
  • diff --git a/docs/radiocom2000.html b/docs/radiocom2000.html new file mode 100644 index 0000000..32685bc --- /dev/null +++ b/docs/radiocom2000.html @@ -0,0 +1,39 @@ + + + +osmocom-analog + + +
    + +

    Radiocom 2000

    + +
    + +

    *this doc is under construction*

    + + + +

    + +History +

    + +

    + +How it works +

    + +

    + +Setup of a base station +

    + +
    [Back to main page]

    +
    + + diff --git a/src/Makefile.am b/src/Makefile.am index 4a858f5..0aa9353 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = foreign -SUBDIRS = common anetz bnetz cnetz nmt amps tacs test +SUBDIRS = common anetz bnetz cnetz nmt amps tacs r2000 test diff --git a/src/common/debug.c b/src/common/debug.c index b3df3cb..512596a 100644 --- a/src/common/debug.c +++ b/src/common/debug.c @@ -49,6 +49,7 @@ struct debug_cat { { "cnetz", "\033[1;34m" }, { "nmt", "\033[1;34m" }, { "amps", "\033[1;34m" }, + { "r2000", "\033[1;34m" }, { "frame", "\033[0;36m" }, { "call", "\033[1;37m" }, { "mncc", "\033[1;32m" }, diff --git a/src/common/debug.h b/src/common/debug.h index b412df2..bb72c62 100644 --- a/src/common/debug.h +++ b/src/common/debug.h @@ -12,16 +12,17 @@ #define DCNETZ 5 #define DNMT 6 #define DAMPS 7 -#define DFRAME 8 -#define DCALL 9 -#define DMNCC 10 -#define DDB 11 -#define DTRANS 12 -#define DDMS 13 -#define DSMS 14 -#define DSDR 15 -#define DUHD 16 -#define DSOAPY 17 +#define DR2000 8 +#define DFRAME 9 +#define DCALL 10 +#define DMNCC 11 +#define DDB 12 +#define DTRANS 13 +#define DDMS 14 +#define DSMS 15 +#define DSDR 16 +#define DUHD 17 +#define DSOAPY 18 #define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, -1, fmt, ## arg) #define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg) 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 + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define CHAN r2000->sender.kanal + +#include +#include +#include +#include +#include +#include +#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 + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "../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 ""; +} + +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 ""; +} + +const char *param_power(uint64_t value) +{ + switch (value) { + case 0: + return "Low"; + case 1: + return "High"; + } + return ""; +} + +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 ""; +} + +const char *param_invitation(uint64_t value) +{ + switch (value) { + case 3: + return "to Answer"; + case 10: + return "to Dial"; + } + return ""; +} + +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 ""; +} + +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 +#include +#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 + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 [option] "); + /* - - */ + printf(" -B --band | list\n"); + printf(" -B --bande | list\n"); + printf(" Give frequency band, use 'list' to get a list. (default = '%d')\n", band); + printf(" -T --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 \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 \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 + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define CHAN r2000->sender.kanal + +#include +#include +#include +#include +#include +#include +#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 + +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); +