Implementation of "Radiocom 2000", the analog French mobile network

This commit is contained in:
Andreas Eversberg 2017-06-10 15:30:20 +02:00
parent 8ce3ff455d
commit ffd3b848e1
20 changed files with 3591 additions and 13 deletions

1
.gitignore vendored
View File

@ -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

8
README
View File

@ -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.

View File

@ -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

View File

@ -77,6 +77,7 @@ Implemented networks:
<li><a href="nmt.html">NMT - Nordic Mobile Telephone</a> (Scandinavia)</li>
<li><a href="amps.html">AMPS - Advanced Mobile Phone Service</a> (USA)</li>
<li><a href="tacs.html">TACS - Total Access Communication System</a> (UK/Italy)</li>
<li><a href="radiocom2000.html">Radiocom 2000</a> (France)</li>
</ul>
</center>

39
docs/radiocom2000.html Normal file
View File

@ -0,0 +1,39 @@
<html>
<head>
<link href="style.css" rel="stylesheet" type="text/css" />
<title>osmocom-analog</title>
</head>
<body>
<center><table><tr><td>
<h2><center>Radiocom 2000</center></h2>
<center><img src="radiocom2000.jpg"/></center>
<center><h1>*this doc is under construction*</h1></center>
<ul>
<li><a href="#history">History</a>
<li><a href="#howitworks">How it works</a>
<li><a href="#basestation">Setup of a base station</a>
</ul>
<p class="toppic">
<a name="history"></a>
History
</p>
<p class="toppic">
<a name="howitworks"></a>
How it works
</p>
<p class="toppic">
<a name="basestation"></a>
Setup of a base station
</p>
<hr><center>[<a href="index.html">Back to main page</a>]</center><hr>
</td></tr></table></center>
</body>
</html>

View File

@ -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

View File

@ -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" },

View File

@ -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)

21
src/r2000/Makefile.am Normal file
View File

@ -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

618
src/r2000/dsp.c Normal file
View File

@ -0,0 +1,618 @@
/* Radiocom 2000 audio processing
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CHAN r2000->sender.kanal
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../common/sample.h"
#include "../common/debug.h"
#include "../common/timer.h"
#include "r2000.h"
#include "dsp.h"
#define PI M_PI
/* Notes on TX_PEAK_FSK level:
*
* Applies similar to NMT, read it there!
*
* I assume that the deviation at 1800 Hz (Bit 0) is +-1700 Hz.
*
* Notes on TX_PEAK_SUPER level:
*
* No emphasis applies (done afterwards), so it is 300 Hz deviation.
*/
/* signaling */
#define MAX_DEVIATION 2500.0
#define MAX_MODULATION 2550.0
#define DBM0_DEVIATION 1500.0 /* deviation of dBm0 at 1 kHz */
#define COMPANDOR_0DB 1.0 /* A level of 0dBm (1.0) shall be unaccected */
#define TX_PEAK_FSK (1700.0 / 1800.0 * 1000.0 / DBM0_DEVIATION) /* with emphasis */
#define TX_PEAK_SUPER (300.0 / DBM0_DEVIATION) /* no emphasis */
#define BIT_RATE 1200.0
#define SUPER_RATE 50.0
#define FILTER_STEP 0.002 /* step every 2 ms */
#define MAX_DISPLAY 1.4 /* something above dBm0 */
/* two signaling tones */
static double super_bits[2] = {
136.0,
164.0,
};
/* table for fast sine generation */
static sample_t super_sine[65536];
/* global init for FFSK */
void dsp_init(void)
{
int i;
ffsk_global_init(TX_PEAK_FSK);
PDEBUG(DDSP, DEBUG_DEBUG, "Generating sine table.\n");
for (i = 0; i < 65536; i++) {
super_sine[i] = sin((double)i / 65536.0 * 2.0 * PI) * TX_PEAK_SUPER;
}
}
static void fsk_receive_bit(void *inst, int bit, double quality, double level);
/* Init FSK of transceiver */
int dsp_init_sender(r2000_t *r2000)
{
sample_t *spl;
double fsk_samples_per_bit;
int i;
/* attack (3ms) and recovery time (13.5ms) according to NMT specs */
init_compandor(&r2000->cstate, 8000, 3.0, 13.5, COMPANDOR_0DB);
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for Transceiver.\n");
/* set modulation parameters */
sender_set_fm(&r2000->sender, MAX_DEVIATION, MAX_MODULATION, DBM0_DEVIATION, MAX_DISPLAY);
PDEBUG(DDSP, DEBUG_DEBUG, "Using FSK level of %.3f\n", TX_PEAK_FSK);
/* init ffsk */
if (ffsk_init(&r2000->ffsk, r2000, fsk_receive_bit, r2000->sender.kanal, r2000->sender.samplerate) < 0) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "FFSK init failed!\n");
return -EINVAL;
}
if (r2000->sender.loopback)
r2000->rx_max = 176;
else
r2000->rx_max = 144;
/* allocate transmit buffer for a complete frame, add 10 to be safe */
fsk_samples_per_bit = (double)r2000->sender.samplerate / BIT_RATE;
r2000->frame_size = 208.0 * fsk_samples_per_bit + 10;
spl = calloc(r2000->frame_size, sizeof(*spl));
if (!spl) {
PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
r2000->frame_spl = spl;
/* strange: better quality with window size of two bits */
r2000->super_samples_per_window = (double)r2000->sender.samplerate / SUPER_RATE * 2.0;
r2000->super_filter_step = (double)r2000->sender.samplerate * FILTER_STEP;
r2000->super_size = 20.0 * r2000->super_samples_per_window + 10;
PDEBUG(DDSP, DEBUG_DEBUG, "Using %d samples per filter step for supervisory signal.\n", r2000->super_filter_step);
spl = calloc(r2000->super_size, sizeof(*spl));
if (!spl) {
PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
r2000->super_spl = spl;
spl = calloc(1, r2000->super_samples_per_window * sizeof(*spl));
if (!spl) {
PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
return -ENOMEM;
}
r2000->super_filter_spl = spl;
r2000->super_filter_bit = -1;
/* count supervisory symbols */
for (i = 0; i < 2; i++) {
audio_goertzel_init(&r2000->super_goertzel[i], super_bits[i], r2000->sender.samplerate);
r2000->super_phaseshift65536[i] = 65536.0 / ((double)r2000->sender.samplerate / super_bits[i]);
PDEBUG(DDSP, DEBUG_DEBUG, "phaseshift[%d] = %.4f\n", i, r2000->super_phaseshift65536[i]);
}
r2000->super_bittime = SUPER_RATE / (double)r2000->sender.samplerate;
return 0;
}
/* Cleanup transceiver instance. */
void dsp_cleanup_sender(r2000_t *r2000)
{
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for Transceiver.\n");
ffsk_cleanup(&r2000->ffsk);
if (r2000->frame_spl) {
free(r2000->frame_spl);
r2000->frame_spl = NULL;
}
if (r2000->super_spl) {
free(r2000->super_spl);
r2000->super_spl = NULL;
}
if (r2000->super_filter_spl) {
free(r2000->super_filter_spl);
r2000->super_filter_spl = NULL;
}
}
/* Check for SYNC bits, then collect data bits */
static void fsk_receive_bit(void *inst, int bit, double quality, double level)
{
r2000_t *r2000 = (r2000_t *)inst;
// uint64_t frames_elapsed;
int i;
/* normalize FSK level */
level /= TX_PEAK_FSK;
r2000->rx_bits_count++;
// printf("bit=%d quality=%.4f\n", bit, quality);
if (!r2000->rx_in_sync) {
r2000->rx_sync = (r2000->rx_sync << 1) | bit;
/* level and quality */
r2000->rx_level[r2000->rx_count & 0xff] = level;
r2000->rx_quality[r2000->rx_count & 0xff] = quality;
r2000->rx_count++;
/* check if pattern 1010111100010010 matches */
if (r2000->rx_sync != 0xaf12)
return;
/* average level and quality */
level = quality = 0;
for (i = 0; i < 16; i++) {
level += r2000->rx_level[(r2000->rx_count - 1 - i) & 0xff];
quality += r2000->rx_quality[(r2000->rx_count - 1 - i) & 0xff];
}
level /= 16.0; quality /= 16.0;
// printf("sync (level = %.2f, quality = %.2f\n", level, quality);
/* do not accept garbage */
if (quality < 0.65)
return;
/* sync time */
r2000->rx_bits_count_last = r2000->rx_bits_count_current;
r2000->rx_bits_count_current = r2000->rx_bits_count - 32.0;
/* rest sync register */
r2000->rx_sync = 0;
r2000->rx_in_sync = 1;
r2000->rx_count = 0;
return;
}
/* read bits */
r2000->rx_frame[r2000->rx_count] = bit + '0';
r2000->rx_level[r2000->rx_count] = level;
r2000->rx_quality[r2000->rx_count] = quality;
if (++r2000->rx_count != r2000->rx_max)
return;
/* end of frame */
r2000->rx_frame[r2000->rx_max] = '\0';
r2000->rx_in_sync = 0;
/* average level and quality */
level = quality = 0;
for (i = 0; i < r2000->rx_max; i++) {
level += r2000->rx_level[i];
quality += r2000->rx_quality[i];
}
level /= (double)r2000->rx_max; quality /= (double)r2000->rx_max;
/* send frame to upper layer */
r2000_receive_frame(r2000, r2000->rx_frame, quality, level);
}
static void super_receive_bit(r2000_t *r2000, int bit, double level, double quality)
{
int i;
/* normalize supervisory level */
level /= TX_PEAK_SUPER;
/* store bit */
r2000->super_rx_word = (r2000->super_rx_word << 1) | bit;
r2000->super_rx_level[r2000->super_rx_index] = level;
r2000->super_rx_quality[r2000->super_rx_index] = quality;
r2000->super_rx_index = (r2000->super_rx_index + 1) % 20;
// printf("%d -> %05x\n", bit, r2000->super_rx_word & 0xfffff);
/* check for sync 0100000000 01xxxxxxx1 */
if ((r2000->super_rx_word & 0xfff01) != 0x40101)
return;
/* average level and quality */
level = quality = 0;
for (i = 0; i < 20; i++) {
level += r2000->super_rx_level[i];
quality += r2000->super_rx_quality[i];
}
level /= 20.0; quality /= 20.0;
/* send received supervisory digit to call control */
r2000_receive_super(r2000, (r2000->super_rx_word >> 1) & 0x7f, quality, level);
}
//#define DEBUG_FILTER
//#define DEBUG_QUALITY
/* demodulate supervisory signal
* filter one chunk, that is 2ms long (1/10th of a bit) */
static inline void super_decode_step(r2000_t *r2000, int pos)
{
double level, result[2], softbit, quality;
int max;
sample_t *spl;
int bit;
max = r2000->super_samples_per_window;
spl = r2000->super_filter_spl;
level = audio_level(spl, max);
audio_goertzel(r2000->super_goertzel, spl, max, pos, result, 2);
/* calculate soft bit from both frequencies */
softbit = (result[1] / level - result[0] / level + 1.0) / 2.0;
// /* scale it, since both filters overlap by some percent */
//#define MIN_QUALITY 0.08
// softbit = (softbit - MIN_QUALITY) / (0.850 - MIN_QUALITY - MIN_QUALITY);
if (softbit > 1)
softbit = 1;
if (softbit < 0)
softbit = 0;
#ifdef DEBUG_FILTER
printf("|%s", debug_amplitude(result[0]/level));
printf("|%s| low=%.3f high=%.3f level=%d\n", debug_amplitude(result[1]/level), result[0]/level, result[1]/level, (int)level);
#endif
if (softbit > 0.5)
bit = 1;
else
bit = 0;
// quality = result[bit] / level;
if (softbit > 0.5)
quality = softbit * 2.0 - 1.0;
else
quality = 1.0 - softbit * 2.0;
/* scale quality, because filters overlap */
quality /= 0.80;
if (r2000->super_filter_bit != bit) {
#ifdef DEBUG_FILTER
puts("bit change");
#endif
r2000->super_filter_bit = bit;
#if 0
/* If we have a bit change, move sample counter towards one half bit duration.
* We may have noise, so the bit change may be wrong or not at the correct place.
* This can cause bit slips.
* Therefore we change the sample counter only slightly, so bit slips may not
* happen so quickly.
*/
if (r2000->super_filter_sample < 5)
r2000->super_filter_sample++;
if (r2000->super_filter_sample > 5)
r2000->super_filter_sample--;
#else
/* directly center the sample position, because we don't have any sync sequence */
r2000->super_filter_sample = 5;
#endif
} else if (--r2000->super_filter_sample == 0) {
/* if sample counter bit reaches 0, we reset sample counter to one bit duration */
#ifdef DEBUG_QUALITY
printf("|%s| quality=%.2f ", debug_amplitude(softbit), quality);
printf("|%s|\n", debug_amplitude(quality);
#endif
/* adjust level, so we get peak of sine curve */
super_receive_bit(r2000, bit, level / 0.63662, quality);
r2000->super_filter_sample = 10;
}
}
/* get audio chunk out of received stream */
void super_receive(r2000_t *r2000, sample_t *samples, int length)
{
sample_t *spl;
int max, pos, step;
int i;
/* write received samples to decode buffer */
max = r2000->super_samples_per_window;
pos = r2000->super_filter_pos;
step = r2000->super_filter_step;
spl = r2000->super_filter_spl;
for (i = 0; i < length; i++) {
spl[pos++] = samples[i];
if (pos == max)
pos = 0;
/* if filter step has been reched */
if (!(pos % step)) {
super_decode_step(r2000, pos);
}
}
r2000->super_filter_pos = pos;
}
/* Process received audio stream from radio unit. */
void sender_receive(sender_t *sender, sample_t *samples, int length)
{
r2000_t *r2000 = (r2000_t *) sender;
sample_t *spl;
int pos;
int i;
/* do dc filter */
if (r2000->de_emphasis)
dc_filter(&r2000->estate, samples, length);
/* supervisory signal */
if (r2000->dsp_mode == DSP_MODE_AUDIO_TX
|| r2000->dsp_mode == DSP_MODE_AUDIO_TX_RX
|| r2000->sender.loopback)
super_receive(r2000, samples, length);
/* do de-emphasis */
if (r2000->de_emphasis)
de_emphasis(&r2000->estate, samples, length);
/* fsk signal */
ffsk_receive(&r2000->ffsk, samples, length);
/* we must have audio mode for both ways and a call */
if (r2000->dsp_mode == DSP_MODE_AUDIO_TX_RX
&& r2000->callref) {
int count;
count = samplerate_downsample(&r2000->sender.srstate, samples, length);
#if 0
/* compandor only in direction REL->MS */
if (r2000->compandor)
expand_audio(&r2000->cstate, samples, count);
#endif
spl = r2000->sender.rxbuf;
pos = r2000->sender.rxbuf_pos;
for (i = 0; i < count; i++) {
spl[pos++] = samples[i];
if (pos == 160) {
call_tx_audio(r2000->callref, spl, 160);
pos = 0;
}
}
r2000->sender.rxbuf_pos = pos;
} else
r2000->sender.rxbuf_pos = 0;
}
static int fsk_frame(r2000_t *r2000, sample_t *samples, int length)
{
const char *frame;
sample_t *spl;
int i;
int count, max;
next_frame:
if (!r2000->frame_length) {
/* request frame */
frame = r2000_get_frame(r2000);
if (!frame) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Stop sending frames.\n");
return length;
}
/* render frame */
r2000->frame_length = ffsk_render_frame(&r2000->ffsk, frame, 208, r2000->frame_spl);
r2000->frame_pos = 0;
if (r2000->frame_length > r2000->frame_size) {
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Frame exceeds buffer, please fix!\n");
abort();
}
}
/* send audio from frame */
max = r2000->frame_length;
count = max - r2000->frame_pos;
if (count > length)
count = length;
spl = r2000->frame_spl + r2000->frame_pos;
for (i = 0; i < count; i++) {
*samples++ = *spl++;
}
length -= count;
r2000->frame_pos += count;
/* check for end of telegramm */
if (r2000->frame_pos == max) {
r2000->frame_length = 0;
/* we need more ? */
if (length)
goto next_frame;
}
return length;
}
static int super_render_frame(r2000_t *r2000, uint32_t word, sample_t *sample)
{
double phaseshift, phase, bittime, bitpos;
int count = 0, i;
phase = r2000->super_phase65536;
bittime = r2000->super_bittime;
bitpos = r2000->super_bitpos;
for (i = 0; i < 20; i++) {
phaseshift = r2000->super_phaseshift65536[(word >> 19) & 1];
do {
*sample++ = super_sine[(uint16_t)phase];
count++;
phase += phaseshift;
if (phase >= 65536.0)
phase -= 65536.0;
bitpos += bittime;
} while (bitpos < 1.0);
bitpos -= 1.0;
word <<= 1;
}
r2000->super_phase65536 = phase;
bitpos = r2000->super_bitpos;
/* return number of samples created for frame */
return count;
}
static int super_frame(r2000_t *r2000, sample_t *samples, int length)
{
sample_t *spl;
int i;
int count, max;
next_frame:
if (!r2000->super_length) {
/* render supervisory rame */
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "render word 0x%05x\n", r2000->super_tx_word);
r2000->super_length = super_render_frame(r2000, r2000->super_tx_word, r2000->super_spl);
r2000->super_pos = 0;
if (r2000->super_length > r2000->super_size) {
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Frame exceeds buffer, please fix!\n");
abort();
}
}
/* send audio from frame */
max = r2000->super_length;
count = max - r2000->super_pos;
if (count > length)
count = length;
spl = r2000->super_spl + r2000->super_pos;
for (i = 0; i < count; i++) {
*samples++ += *spl++;
}
length -= count;
r2000->super_pos += count;
/* check for end of telegramm */
if (r2000->super_pos == max) {
r2000->super_length = 0;
/* we need more ? */
if (length)
goto next_frame;
}
return length;
}
/* Provide stream of audio toward radio unit */
void sender_send(sender_t *sender, sample_t *samples, int length)
{
r2000_t *r2000 = (r2000_t *) sender;
int len;
again:
switch (r2000->dsp_mode) {
case DSP_MODE_OFF:
memset(samples, 0, sizeof(*samples) * length);
break;
case DSP_MODE_AUDIO_TX:
case DSP_MODE_AUDIO_TX_RX:
jitter_load(&r2000->sender.dejitter, samples, length);
/* do pre-emphasis */
if (r2000->pre_emphasis)
pre_emphasis(&r2000->estate, samples, length);
super_frame(r2000, samples, length);
break;
case DSP_MODE_FRAME:
/* Encode frame into audio stream. If frames have
* stopped, process again for rest of stream. */
len = fsk_frame(r2000, samples, length);
/* do pre-emphasis */
if (r2000->pre_emphasis)
pre_emphasis(&r2000->estate, samples, length - len);
if (len) {
samples += length - len;
length = len;
goto again;
}
break;
}
}
const char *r2000_dsp_mode_name(enum dsp_mode mode)
{
static char invalid[16];
switch (mode) {
case DSP_MODE_OFF:
return "OFF";
case DSP_MODE_AUDIO_TX:
return "AUDIO-TX";
case DSP_MODE_AUDIO_TX_RX:
return "AUDIO-TX-RX";
case DSP_MODE_FRAME:
return "FRAME";
}
sprintf(invalid, "invalid(%d)", mode);
return invalid;
}
void r2000_set_dsp_mode(r2000_t *r2000, enum dsp_mode mode, int super)
{
/* reset telegramm */
if (mode == DSP_MODE_FRAME && r2000->dsp_mode != mode) {
r2000->frame_length = 0;
}
if ((mode == DSP_MODE_AUDIO_TX || mode == DSP_MODE_AUDIO_TX_RX)
&& (r2000->dsp_mode != DSP_MODE_AUDIO_TX && r2000->dsp_mode != DSP_MODE_AUDIO_TX_RX)) {
r2000->super_length = 0;
}
if (super >= 0) {
/* encode supervisory word 0100000000 01xxxxxxx1 */
r2000->super_tx_word = 0x40101 | ((super & 0x7f) << 1);
/* clear pending data in rx word */
r2000->super_rx_word = 0x00000;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s (super = 0x%05x)\n", r2000_dsp_mode_name(r2000->dsp_mode), r2000_dsp_mode_name(mode), r2000->super_tx_word);
} else if (r2000->dsp_mode != mode)
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "DSP mode %s -> %s\n", r2000_dsp_mode_name(r2000->dsp_mode), r2000_dsp_mode_name(mode));
r2000->dsp_mode = mode;
}
#warning fixme: high pass filter on tx side to prevent desturbance of supervisory signal

6
src/r2000/dsp.h Normal file
View File

@ -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);

573
src/r2000/frame.c Normal file
View File

@ -0,0 +1,573 @@
/* Radiocom 2000 frame transcoding
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <inttypes.h>
#include "../common/hagelbarger.h"
#include "../common/debug.h"
#include "frame.h"
static const char *param_hex(uint64_t value)
{
static char result[32];
sprintf(result, "0x%" PRIx64, value);
return result;
}
static const char *param_voie_rel(uint64_t value)
{
return (value) ? "Control Channel" : "Traffic Channel";
}
static const char *param_voie_sm(uint64_t value)
{
return (value) ? "Traffic Channel" : "Control Channel";
}
const char *param_agi(uint64_t value)
{
switch (value) {
case 0:
return "Prohibited control channel (no mobile allowed)";
case 1:
return "New registration prohibited (registered mobiles allowed)";
case 2:
return "Registration is reserved to test mobiles";
case 3:
return "Registration for nominal mobiles (home network)";
case 4:
return "Registration is reserved to special mobiles";
case 5:
case 6:
case 7:
return "Registration permissible for all mobile station";
}
return "<invalid>";
}
const char *param_aga(uint64_t value)
{
switch (value) {
case 0:
return "Outgoing calls prohibited";
case 1:
return "Reserved (Outgoing calls prohibited)";
case 2:
return "Outgoing call reserved for privileged mobiles";
case 3:
return "Outgoing calls permissible";
}
return "<invalid>";
}
const char *param_power(uint64_t value)
{
switch (value) {
case 0:
return "Low";
case 1:
return "High";
}
return "<invalid>";
}
const char *param_crins(uint64_t value)
{
switch (value) {
case 0:
return "Finished or just registering";
case 1:
return "Localization impossible (queue full)";
case 2:
return "Mobile station temporarily disabled";
case 3:
return "Mobile station definitely disabled (WILL BRICK THE PHONE!)";
case 4:
return "Blocked localization (BS out of order)";
case 5:
case 6:
return "Reserved";
case 7:
return "Calling subscriber unknown";
}
return "<invalid>";
}
const char *param_invitation(uint64_t value)
{
switch (value) {
case 3:
return "to Answer";
case 10:
return "to Dial";
}
return "<unknown>";
}
static const char *param_digit(uint64_t value)
{
static char result[32];
switch (value) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
sprintf(result, "'%c'", (int)value + '0');
return result;
case 10:
return "'*'";
case 11:
return "'#'";
case 12:
return "'A'";
case 13:
return "'B'";
case 14:
return "'C'";
case 15:
return "'D'";
}
return "<invalid>";
}
static struct r2000_element {
char element;
const char *name;
const char *(*decoder_rel)(uint64_t value); /* REL sends to SM */
const char *(*decoder_sm)(uint64_t value); /* SM sends to REL */
} r2000_element[] = {
{ 'V', "Channel Type", param_voie_rel, param_voie_sm },
{ 'C', "Channel", NULL, NULL },
{ 'R', "Relais", NULL, NULL },
{ 'M', "Message", NULL, NULL },
{ 'D', "Deport", NULL, NULL },
{ 'I', "AGI", param_agi, param_agi },
// { 'A', "AGA", param_aga, param_aga },
{ 'P', "power", param_power, param_power },
{ 'T', "taxe", NULL, NULL },
{ 't', "SM Type", param_hex, param_hex },
{ 'r', "SM Relais", NULL, NULL },
{ 'f', "SM Flotte", NULL, NULL },
{ 'm', "SM ID", NULL, NULL },
{ 'd', "Called ID", NULL, NULL },
{ 'c', "CRINS", param_crins, param_crins },
{ 'a', "Assign Channel", NULL, NULL },
{ 's', "Sequence Number", param_hex, param_hex },
{ 'i', "Invitation", param_invitation,param_invitation },
{ 'n', "NCONV", NULL, NULL },
{ '0', "1st Digit", param_digit, param_digit },
{ '1', "2nd Digit", param_digit, param_digit },
{ '2', "3rd Digit", param_digit, param_digit },
{ '3', "4th Digit", param_digit, param_digit },
{ '4', "5th Digit", param_digit, param_digit },
{ '5', "6th Digit", param_digit, param_digit },
{ '6', "7th Digit", param_digit, param_digit },
{ '7', "8th Digit", param_digit, param_digit },
{ '8', "9th Digit", param_digit, param_digit },
{ '9', "10th Digit", param_digit, param_digit },
{ '?', "Unknown", param_hex, param_hex },
{ 0, NULL, NULL, NULL }
};
static void print_element(char element, uint64_t value, int dir, int debug)
{
const char *(*decoder)(uint64_t value);
int i;
for (i = 0; r2000_element[i].element; i++) {
if (r2000_element[i].element == element)
break;
}
decoder = (dir == REL_TO_SM) ? r2000_element[i].decoder_rel : r2000_element[i].decoder_sm;
if (!r2000_element[i].element)
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [Unknown]\n", element, value);
else if (!decoder)
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 " [%s]\n", element, value, r2000_element[i].name);
else
PDEBUG(DFRAME, debug, "Element '%c' %" PRIu64 "=%s [%s]\n", element, value, decoder(value), r2000_element[i].name);
}
static void store_element(frame_t *frame, char element, uint64_t value)
{
switch(element) {
case 'V':
frame->voie = value;
break;
case 'C':
frame->channel = value;
break;
case 'R':
frame->relais = value;
break;
case 'M':
frame->message = value;
break;
case 'D':
frame->deport = value;
break;
case 'I':
frame->agi = value;
break;
case 'P':
frame->sm_power = value;
break;
case 'T':
frame->taxe = value;
break;
case 't':
frame->sm_type = value;
break;
case 'r':
frame->sm_relais = value;
break;
case 'f':
frame->sm_flotte = value;
break;
case 'm':
frame->sm_mor = value;
break;
case 'd':
frame->sm_mop_demandee = value;
break;
case 'c':
frame->crins = value;
break;
case 'a':
frame->chan_assign = value;
break;
case 's':
frame->sequence = value;
break;
case 'i':
frame->invitation = value;
break;
case 'n':
frame->nconv = value;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
frame->digit[element - '0'] = value;
break;
}
}
static uint64_t fetch_element(frame_t *frame, char element)
{
switch(element) {
case 'V':
return frame->voie;
case 'C':
return frame->channel;
case 'R':
return frame->relais;
case 'M':
return frame->message;
case 'D':
return frame->deport;
case 'I':
return frame->agi;
case 'P':
return frame->sm_power;
case 'T':
return frame->taxe;
case 't':
return frame->sm_type;
case 'r':
return frame->sm_relais;
case 'f':
return frame->sm_flotte;
case 'm':
return frame->sm_mor;
case 'd':
return frame->sm_mop_demandee;
case 'c':
return frame->crins;
case 'a':
return frame->chan_assign;
case 's':
return frame->sequence;
case 'i':
return frame->invitation;
case 'n':
return frame->nconv;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return frame->digit[element - '0'];
}
return 0;
}
static struct r2000_frame {
int dir;
uint8_t message;
const char *def;
const char *name;
} r2000_frame_def[] = {
/* V Channel-Relais---Msg--t--HomeRel--MobieID--------- Supervisory----- */
/* messages REL->SM */
{ REL_TO_SM, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----ccc----DDDIII++---PT---", "INSCRIPTION ACK" }, /* inscription ack */
{ REL_TO_SM, 2, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "PLEASE WAIT" }, /* waiting on CC */
{ REL_TO_SM, 1, "V-CCCCCCCCRRRRRRRRRMMMMM----------------------------------------DDDIII++---PT---", "IDLE" }, /* broadcast */
{ REL_TO_SM, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN INCOMING"}, /* assign incoming call */
{ REL_TO_SM, 4, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN GROUP"}, /* assign groupp call */
{ REL_TO_SM, 5, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmmaaaaaaaa----DDDIII++---PT---", "ASSIGN OUTGOING"}, /* assign outgoing call */
{ REL_TO_SM, 9, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------DDDIII++---PT---", "RELEASE ON CC" }, /* release call on CC */
{ REL_TO_SM, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "IDENTITY REQ"}, /* request identity */
{ REL_TO_SM, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm-----nnniiii----------------", "INVITATION"}, /* invitation */
{ REL_TO_SM, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "RELEASE ON TC"}, /* release call */
{ REL_TO_SM, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm----------------------------", "SUSPEND REQ"}, /* suspend after dialing */
/* messages SM->REL */
{ SM_TO_REL, 0, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "INSCRIPTION REQ" }, /* inscription */
{ SM_TO_REL, 1, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PRIVATE)" }, /* request call */
{ SM_TO_REL, 1, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrfffffffffmmmmmmmddddddddssss", "CALL REQ (GROUP)" }, /* request call */
{ SM_TO_REL, 3, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "CALL REQ (PUBLIC)" }, /* request call */
{ SM_TO_REL, 6, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON CC" }, /* release on CC */
{ SM_TO_REL, 16, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm--------ssss", "IDENTITY ACK" }, /* identity response */
{ SM_TO_REL, 17, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "ANSWER" }, /* answer */
{ SM_TO_REL, 19, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 1..10" }, /* first 10 digits */
{ SM_TO_REL, 20, "V-CCCCCCCCRRRRRRRRRMMMMM1111000033332222555544447777666699998888", "DIAL 11..20" }, /* second 10 digits */
{ SM_TO_REL, 24, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "RELEASE ON TC" }, /* release call on TC */
{ SM_TO_REL, 26, "V-CCCCCCCCRRRRRRRRRMMMMMtttrrrrrrrrrmmmmmmmmmmmmmmmm------------", "SUSPEND ACK" }, /* release after dialing */
{ 0, 0, NULL, NULL }
};
static const char *get_frame_def(uint8_t message, int dir)
{
int i;
for (i = 0; r2000_frame_def[i].def; i++) {
if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir)
return r2000_frame_def[i].def;
}
return NULL;
}
const char *r2000_dir_name(int dir)
{
return (dir == REL_TO_SM) ? "REL->SM" : "SM->REL";
}
const char *r2000_frame_name(int message, int dir)
{
static char result[32];
int i;
for (i = 0; r2000_frame_def[i].def; i++) {
if (r2000_frame_def[i].message == message && r2000_frame_def[i].dir == dir) {
sprintf(result, "%s (%d)", r2000_frame_def[i].name, message);
return result;
}
}
sprintf(result, "UNKNOWN (%d)", message);
return result;
}
static void display_bits(const char *def, const uint8_t *message, int num, int debug)
{
char dispbits[num + 1];
int i;
if (debuglevel > debug)
return;
/* display bits */
if (def)
PDEBUG(DFRAME, debug, "%s\n", def);
for (i = 0; i < num; i++) {
dispbits[i] = ((message[i / 8] >> (7 - (i & 7))) & 1) + '0';
}
dispbits[i] = '\0';
PDEBUG(DFRAME, debug, "%s\n", dispbits);
}
static int dissassemble_frame(frame_t *frame, const uint8_t *message, int num)
{
int i;
const char *def;
uint64_t value;
int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
memset(frame, 0, sizeof(*frame));
frame->message = message[2] & 0x1f;
def = get_frame_def(frame->message, dir);
if (!def) {
PDEBUG(DFRAME, DEBUG_NOTICE, "Received unknown message type %d (maybe radio noise)\n", frame->message);
display_bits(NULL, message, num, DEBUG_NOTICE);
return -EINVAL;
}
PDEBUG(DFRAME, DEBUG_DEBUG, "Decoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
/* dissassemble elements elements */
value = 0;
for (i = 0; i < num; i++) {
value = (value << 1) | ((message[i / 8] >> (7 - (i & 7))) & 1);
if (def[i + 1] != def[i]) {
if (def[i] != '-') {
print_element(def[i], value, dir, DEBUG_DEBUG);
store_element(frame, def[i], value);
}
value = 0;
}
}
display_bits(def, message, num, DEBUG_DEBUG);
return 0;
}
static int assemble_frame(frame_t *frame, uint8_t *message, int num, int debug)
{
int i;
const char *def;
uint64_t value = 0; // make GCC happy
char element;
int dir = (num == 80) ? REL_TO_SM : SM_TO_REL;
def = get_frame_def(frame->message, dir);
if (!def) {
PDEBUG(DFRAME, DEBUG_ERROR, "Cannot assemble unknown message type %d, please define/fix!\n", frame->message);
abort();
}
memset(message, 0, (num + 7) / 8);
if (debug)
PDEBUG(DFRAME, DEBUG_DEBUG, "Ccoding frame %s %s\n", r2000_dir_name(dir), r2000_frame_name(frame->message, dir));
/* assemble elements elements */
element = 0;
for (i = num - 1; i >= 0; i--) {
if (element != def[i]) {
element = def[i];
switch (def[i]) {
case '-':
value = 0;
break;
case '+':
value = 0xffffffffffffffff;
break;
default:
value = fetch_element(frame, element);
}
}
message[i / 8] |= (value & 1) << (7 - (i & 7));
value >>= 1;
}
if (debug) {
for (i = 0; i < num; i++) {
if (def[i + 1] != def[i] && def[i] != '-' && def[i] != '+') {
value = fetch_element(frame, def[i]);
print_element(def[i], value, dir, DEBUG_DEBUG);
}
}
display_bits(def, message, num, DEBUG_DEBUG);
}
return 0;
}
/* encode frame to bits
*/
const char *encode_frame(frame_t *frame, int debug)
{
uint8_t message[11], code[23];
static char bits[32 + 176 + 1];
int i;
assemble_frame(frame, message, 80, debug);
/* hagelbarger code */
hagelbarger_encode(message, code, 88);
memcpy(bits, "10101010101010101010111100010010", 32);
for (i = 0; i < 176; i++)
bits[i + 32] = ((code[i / 8] >> (7 - (i & 7))) & 1) + '0';
bits[208] = '\0';
return bits;
}
//#define GEGENPROBE
/* decode bits to frame */
int decode_frame(frame_t *frame, const char *bits)
{
uint8_t message[11], code[23];
int i, num = strlen(bits);
#ifdef GEGENPROBE
printf("bits as received=%s\n", bits);
#endif
/* hagelbarger code */
memset(code, 0x00, sizeof(code));
for (i = 0; i < num; i++)
code[i / 8] |= (bits[i] & 1) << (7 - (i & 7));
hagelbarger_decode(code, message, num / 2 - 6);
#if 0
for (i = 0; i < num / 2; i++) {
printf("%d", (message[i / 8] >> (7 - (i & 7))) & 1);
if ((i & 7) == 7)
printf(" = 0x%02x\n", message[i / 8]);
}
#endif
#ifdef GEGENPROBE
hagelbarger_encode(message, code, num / 2);
printf("bits after re-encoding=");
for (i = 0; i < num; i++)
printf("%d", (code[i / 8] >> (7 - (i & 7))) & 1);
printf("\n");
#endif
return dissassemble_frame(frame, message, num / 2 - 8);
}

33
src/r2000/frame.h Normal file
View File

@ -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);

84
src/r2000/image.c Normal file
View File

@ -0,0 +1,84 @@
#include <stdio.h>
#include <string.h>
#include "image.h"
const char *image[] = {
"",
" @B/ \\",
" / @W/ \\@B \\",
" | @W/ @R/ \\@W \\@B |",
" | @W| @R| @y|@R |@W |@B |",
" | @W\\ @R\\ @y|@R /@W /@B |",
" @W__________ @B\\ @W\\ @y/|\\@W /@B /",
" @W_( _____) @B\\ @y|###|@B /",
" @W(_____ )__ @yHXH",
" @W(_____) @y:X:",
" @y:X:",
" @yIXI @W_________",
" @yIXI @W___( ___)",
" @yHXH @W(_ __)",
" @W____ @yHXH @W(______)",
" @W(_ )_ @y'XXX'",
" @W(____) @y'XXX'",
" @y:XXX:",
" @y:XXX:",
" @yHXXXH",
" @WRadiocom 2000 @y.XXXXX.",
" @y:XXXXX:",
" @W~ @y_/XXXXXYX\\_",
" @W~ @y\\#########/",
" @y/XX/XXX\\XX\\",
" @y/XX/ \\XX\\ @W~",
" @y_/XX/ \\XX\\_",
" @y|/|X/|~|~|~|~|\\X|\\| @W~ ~",
" @G(###) @y###################",
" @G(####)(#####()) @y/XX/X\\_X_X_X_X_X/\\XX\\ @G(#)",
" (#################) @y/XX/\\/ \\/\\XX\\ @G((####)#######)",
" (#######)(#########) @y/XX// \\\\XX\\ @G(#####))############)",
"(############)(######) @y./XX/ @wo @t~@y \\XX\\.@G(####)###############)",
"(######)))(############) @y/####\\ @w'O'@y /####\\@G(()(######)(##########)@W",
NULL
};
void print_image(void)
{
int i, j;
for (i = 0; image[i]; i++) {
for (j = 0; j < (int)strlen(image[i]); j++) {
if (image[i][j] == '@') {
j++;
switch(image[i][j]) {
case 'r': /* red */
printf("\033[0;31m");
break;
case 'R': /* red */
printf("\033[1;31m");
break;
case 'B': /* blue */
printf("\033[1;34m");
break;
case 'w': /* white */
printf("\033[0;37m");
break;
case 't': /* turquoise */
printf("\033[0;36m");
break;
case 'G': /* green */
printf("\033[0;32m");
break;
case 'W': /* white */
printf("\033[1;37m");
break;
case 'y': /* yellow */
printf("\033[0;33m");
break;
}
} else
printf("%c", image[i][j]);
}
printf("\n");
}
printf("\033[0;39m");
}

3
src/r2000/image.h Normal file
View File

@ -0,0 +1,3 @@
void print_image(void);

397
src/r2000/main.c Normal file
View File

@ -0,0 +1,397 @@
/* Radiocom 2000 main
*
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../common/sample.h"
#include "../common/main.h"
#include "../common/debug.h"
#include "../common/timer.h"
#include "../common/mncc_sock.h"
#include "r2000.h"
#include "dsp.h"
#include "frame.h"
#include "tones.h"
#include "image.h"
/* settings */
static int band = 1;
static int num_chan_type = 0;
static int relais = 32;
static int deport = 0;
static int agi = 7;
static int sm_power = 0;
static int taxe = 0;
static int crins = 0, destruction = 0; /* neven set CRINS to 3 and destruction to other than 0 here! */
static int nconv = 0;
static int recall = 0;
enum r2000_chan_type chan_type[MAX_SENDER] = { CHAN_TYPE_CC_TC };
void print_help(const char *arg0)
{
print_help_common(arg0, "-R <relais number> [option] ");
/* - - */
printf(" -B --band <number> | list\n");
printf(" -B --bande <number> | list\n");
printf(" Give frequency band, use 'list' to get a list. (default = '%d')\n", band);
printf(" -T --channel-type <channel type> | list\n");
printf(" Give channel type, use 'list' to get a list. (default = '%s')\n", chan_type_short_name(chan_type[0]));
printf(" -R --relais <relais number>\n");
printf(" Give relais number (base station ID) 1..511. (default = '%d')\n", relais);
printf(" Be sure to set the station mobile to the same relais number!\n");
printf(" --deport 0..7\n");
printf(" Supervisory information to tell about sub-stations.\n");
printf(" The functionality is unknown. (default = '%d')\n", deport);
printf(" -I --agi 0..7 | list\n");
printf(" Supervisory information to tell which phone is allowed to register\n");
printf(" Use 'list' to get a list of possible valued.\n");
printf(" (default = '%d' = %s)\n", agi, param_agi(agi));
#if 0
printf(" -A --aga 0..3 | list\n");
printf(" Supervisory information to tell which phone is allowed to call\n");
printf(" Use 'list' to get a list of possible valued.\n");
printf(" (default = '%d' = %s)\n", aga, param_aga(aga));
#endif
printf(" -P --sm-power <power level>\n");
printf(" Give power level of the station mobile 0..1. (default = '%d')\n", sm_power);
printf(" 0 = low (about 1 Watts) 1 = high (up to 10 Watts)\n");
printf(" --taxe 0..1\n");
printf(" Supervisory information to tell about rate information.\n");
printf(" The functionality is unknown. (default = '%d')\n", taxe);
printf(" -C --crins 0..7 | list [--destruction YES]\n");
printf(" Result that will be returned when the phone registers.\n");
printf(" NEVER USE '3', IT WILL DESTROY YOUR PHONE, but shows a warning first!\n");
printf(" Use 'list' to get a list of possible valued.\n");
printf(" (default = '%d' = %s)\n", crins, param_crins(crins));
printf(" -N --nconv 0..7\n");
printf(" Supervisory digit, sent during conversation. (default = '%d')\n", nconv);
printf(" It is used to detect lost signal. When using multiple traffic\n");
printf(" channels, this value is incremented per channel.\n");
printf(" -S --recall\n");
printf(" Suspend outgoing call after dialing and recall when called party has\n");
printf(" answered.\n");
printf("\nstation-id: Give 1 digit of station mobile type + 3 digits of home relais ID\n");
printf(" + 5 digits of mobile ID.\n");
printf(" (e.g. 103200819 = type 1, relais ID 32, mobile ID 819)\n");
print_hotkeys_common();
}
#define OPT_BANDE 256
#define OPT_DEPORT 257
#define OPT_TAXE 258
#define OPT_DESTRUCTION 259
static int handle_options(int argc, char **argv)
{
int skip_args = 0;
static struct option long_options_special[] = {
{"band", 1, 0, 'B'},
{"bande", 1, 0, OPT_BANDE},
{"channel-type", 1, 0, 'T'},
{"relais", 1, 0, 'R'},
{"deport", 1, 0, OPT_DEPORT},
{"agi", 1, 0, 'I'},
{"sm-power", 1, 0, 'P'},
{"taxe", 1, 0, OPT_TAXE},
{"crins", 1, 0, 'C'},
{"destruction", 1, 0, OPT_DESTRUCTION},
{"nconv", 1, 0, 'N'},
{"recall", 1, 0, 'S'},
{0, 0, 0, 0}
};
set_options_common("B:T:R:I:P:C:N:S", long_options_special);
while (1) {
int option_index = 0, c, rc;
c = getopt_long(argc, argv, optstring, long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'B':
case OPT_BANDE:
if (!strcmp(optarg, "list")) {
r2000_band_list();
exit(0);
}
band = atoi(optarg);
skip_args += 2;
break;
case 'T':
if (!strcmp(optarg, "list")) {
r2000_channel_list();
exit(0);
}
rc = r2000_channel_by_short_name(optarg);
if (rc < 0) {
fprintf(stderr, "Error, channel type '%s' unknown. Please use '-t list' to get a list. I suggest to use the default.\n", optarg);
exit(0);
}
OPT_ARRAY(num_chan_type, chan_type, rc)
skip_args += 2;
break;
case 'R':
relais = atoi(optarg);
if (relais > 511)
relais = 511;
if (relais < 1)
relais = 1;
skip_args += 2;
break;
case OPT_DEPORT:
deport = atoi(optarg);
if (deport > 7)
deport = 7;
if (deport < 0)
deport = 0;
skip_args += 2;
break;
case 'I':
if (!strcmp(optarg, "list")) {
int i;
printf("\nList of possible AGI (inscription permission) codes:\n\n");
printf("Value\tDescription\n");
printf("------------------------------------------------------------------------\n");
for (i = 0; i < 8; i++)
printf("%d\t%s\n", i, param_agi(i));
exit(0);
}
agi = atoi(optarg);
if (agi < 0 || agi > 7) {
fprintf(stderr, "Error, given inscription permission (AGI) %d is invalid, use 'list' to get a list of values!\n", agi);
exit(0);
}
skip_args += 2;
break;
case 'P':
sm_power = atoi(optarg);
if (sm_power > 1)
sm_power = 1;
if (sm_power < 0)
sm_power = 0;
skip_args += 2;
break;
case OPT_TAXE:
taxe = atoi(optarg);
if (taxe > 1)
taxe = 1;
if (taxe < 0)
taxe = 0;
skip_args += 2;
break;
#if 0
case 'A':
if (!strcmp(optarg, "list")) {
int i;
printf("\nList of possible AGA (call permission) codes:\n\n");
printf("Value\tDescription\n");
printf("------------------------------------------------------------------------\n");
for (i = 0; i < 4; i++)
printf("%d\t%s\n", i, param_aga(i));
exit(0);
}
aga = atoi(optarg);
if (aga < 0 || aga > 3) {
fprintf(stderr, "Error, given call permission (AGA) %d is invalid, use 'list' to get a list of values!\n", aga);
exit(0);
}
skip_args += 2;
break;
#endif
case 'C':
if (!strcmp(optarg, "list")) {
int i;
printf("\nList of possible CRINS (inscription response) codes:\n\n");
printf("Value\tDescription\n");
printf("------------------------------------------------------------------------\n");
for (i = 0; i < 8; i++)
printf("%d\t%s\n", i, param_crins(i));
exit(0);
}
crins = atoi(optarg);
if (crins < 0 || crins > 7) {
fprintf(stderr, "Error, given inscription response (CRINS) %d is invalid, use 'list' to get a list of values!\n", crins);
exit(0);
}
skip_args += 2;
break;
case OPT_DESTRUCTION:
if (!strcmp(optarg, "YES")) {
destruction = 2342;
}
skip_args += 2;
break;
case 'N':
nconv = atoi(optarg);
if (nconv > 7)
nconv = 7;
if (nconv < 0)
nconv = 0;
skip_args += 2;
break;
case 'S':
recall = 1;
skip_args += 1;
break;
default:
opt_switch_common(c, argv[0], &skip_args);
}
}
free(long_options);
return skip_args;
}
int main(int argc, char *argv[])
{
int rc;
int skip_args;
const char *station_id = "";
int mandatory = 0;
int i;
/* init tones */
init_radiocom_tones();
skip_args = handle_options(argc, argv);
argc -= skip_args;
argv += skip_args;
if (argc > 1) {
station_id = argv[1];
if (strlen(station_id) != 9) {
printf("Given station ID '%s' does not have 9 digits\n", station_id);
return 0;
}
}
if (!num_kanal) {
printf("No channel (\"Kanal\") is specified, I suggest channel 160 (-k 160).\n\n");
mandatory = 1;
}
if (use_sdr) {
/* set audiodev */
for (i = 0; i < num_kanal; i++)
audiodev[i] = "sdr";
num_audiodev = num_kanal;
/* set channel types for more than 1 channel */
if (num_kanal > 1 && num_chan_type == 0) {
chan_type[0] = CHAN_TYPE_CC;
for (i = 1; i < num_kanal; i++)
chan_type[i] = CHAN_TYPE_TC;
num_chan_type = num_kanal;
}
}
if (num_kanal == 1 && num_audiodev == 0)
num_audiodev = 1; /* use default */
if (num_kanal != num_audiodev) {
fprintf(stderr, "You need to specify as many sound devices as you have channels.\n");
exit(0);
}
if (num_kanal == 1 && num_chan_type == 0)
num_chan_type = 1; /* use default */
if (num_kanal != num_chan_type) {
fprintf(stderr, "You need to specify as many channel types as you have channels.\n");
exit(0);
}
if (mandatory) {
print_help(argv[-skip_args]);
return 0;
}
/* check for destruction of the phone (crins 3 will brick it) */
if (crins == 3) {
fprintf(stderr, "\n*******************************************************************************\n");
fprintf(stderr, "You selected inscription response '3'!\n\n");
fprintf(stderr, "This feature was used by the operators to destroy a stolen/modified phone.\n");
fprintf(stderr, "This will brick/destroy/kill/make ALL PHONES USELESS, if registering!\n");
fprintf(stderr, "PHONE WILL LOCK AND/OR SUBSCRIBER DATA WILL BE ERASED!!! IS THAT WHAT YOU WANT?\n");
fprintf(stderr, "I had to hack the firmware of my phone to unbrick it. Can you do that too?\n");
if (!destruction)
fprintf(stderr, "If you can unlock your phone later, then use '--destruction YES' to confirm.\n");
else
fprintf(stderr, "\n **** PRESS CTRL+c TO ABORT THIS FEATURE, NOW! **** Press enter to continue.\n\n");
fprintf(stderr, "*******************************************************************************\n\n");
if (!destruction)
exit(0);
else
getchar();
}
if (!loopback && crins != 3)
print_image();
/* init functions */
dsp_init();
/* SDR always requires emphasis */
if (use_sdr) {
do_pre_emphasis = 1;
do_de_emphasis = 1;
}
if (!do_pre_emphasis || !do_de_emphasis) {
fprintf(stderr, "*******************************************************************************\n");
fprintf(stderr, "I strongly suggest to let me do pre- and de-emphasis (options -p -d)!\n");
fprintf(stderr, "Use a transmitter/receiver without emphasis and let me do that!\n");
fprintf(stderr, "Because 50 baud supervisory signalling arround 150 Hz will not be tranmitted by\n");
fprintf(stderr, "regular radio, use direct input to the PLL of your transmitter (or use SDR).\n");
fprintf(stderr, "*******************************************************************************\n");
}
/* create transceiver instance */
for (i = 0; i < num_kanal; i++) {
rc = r2000_create(band, kanal[i], chan_type[i], audiodev[i], use_sdr, samplerate, rx_gain, do_pre_emphasis, do_de_emphasis, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, relais, deport, agi, sm_power, taxe, crins, destruction, nconv, recall, loopback);
if (rc < 0) {
fprintf(stderr, "Failed to create transceiver instance. Quitting!\n");
goto fail;
}
printf("base station on channel %d ready, please tune transmitter to %.4f MHz and receiver to %.4f MHz.\n", kanal[i], r2000_channel2freq(band, kanal[i], 0) / 1e6, r2000_channel2freq(band, kanal[i], 1) / 1e6);
nconv = (nconv + 1) & 7;
}
r2000_check_channels();
main_common(&quit, latency, interval, NULL, station_id, 9);
fail:
/* destroy transceiver instance */
while (sender_head)
r2000_destroy(sender_head);
return 0;
}

1589
src/r2000/r2000.c Normal file

File diff suppressed because it is too large Load Diff

135
src/r2000/r2000.h Normal file
View File

@ -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);

68
src/r2000/tones.c Normal file
View File

@ -0,0 +1,68 @@
#include <stdint.h>
static int16_t pattern[] = {
0x0000, 0x1483,
0x269d, 0x3420, 0x3b7d, 0x3bd3, 0x3510, 0x280f, 0x164c, 0x01e5,
0xed4c, 0xdadd, 0xcce2, 0xc4e1, 0xc3ee, 0xca06, 0xd68e, 0xe7f0,
0xfc34, 0x10e4, 0x239a, 0x3217, 0x3aab, 0x3c48, 0x36d1, 0x2ace,
0x19ce, 0x05b0, 0xf0f2, 0xddf4, 0xcf02, 0xc5d6, 0xc390, 0xc86a,
0xd3da, 0xe481, 0xf869, 0x0d36, 0x2074, 0x2fdb, 0x3999, 0x3c89,
0x384f, 0x2d6e, 0x1d2d, 0x097a, 0xf4a7, 0xe12c, 0xd154, 0xc706,
0xc36e, 0xc707, 0xd154, 0xe12c, 0xf4a7, 0x0979, 0x1d2e, 0x2d6d,
0x3850, 0x3c88, 0x399a, 0x2fdb, 0x2072, 0x0d39, 0xf865, 0xe486,
0xd3d4, 0xc871, 0xc389, 0xc5dd, 0xcefb, 0xddfa, 0xf0ee, 0x05b3,
0x19cc, 0x2acf, 0x36d0, 0x3c4a, 0x3aaa, 0x3216, 0x239b, 0x10e3,
0xfc35, 0xe7f1, 0xd68c, 0xca08, 0xc3ec, 0xc4e3, 0xccdf, 0xdae1,
0xed49, 0x01e7, 0x164b, 0x280e, 0x3511, 0x3bd4, 0x3b7b, 0x3422,
0x269c, 0x1481, 0x0003, 0xeb7b, 0xd964, 0xcbe2, 0xc47e, 0xc433,
0xcaea, 0xd7f6, 0xe9b2, 0xfe1a, 0x12b8, 0x251d, 0x3324, 0x3b1a,
0x3c16, 0x35f7, 0x2975, 0x180d, 0x03cf, 0xef18, 0xdc69, 0xcde7,
0xc559, 0xc3b3, 0xc935, 0xd52b, 0xe637, 0xfa4e, 0x0f0f, 0x220b,
0x30ff, 0x3a28, 0x3c73, 0x3793, 0x2c28, 0x1b7d, 0x0799, 0xf2c8,
0xdf8e, 0xd023, 0xc66a, 0xc375, 0xc7b2, 0xd291, 0xe2d2, 0xf689,
0x0b57, 0x1ed7, 0x2ea8, 0x38fc, 0x3c90, 0x38fd, 0x2ea8, 0x1ed7,
0x0b56, 0xf68a, 0xe2d1, 0xd293, 0xc7af, 0xc379, 0xc665, 0xd028,
0xdf8a, 0xf2cc, 0x0795, 0x1b80, 0x2c26, 0x3795, 0x3c71, 0x3a2a,
0x30fd, 0x220d, 0x0f0e, 0xfa4e, 0xe636, 0xd52d, 0xc934, 0xc3b3,
0xc559, 0xcde7, 0xdc69, 0xef18, 0x03d0, 0x180a, 0x297a, 0x35f2,
0x3c1a, 0x3b17, 0x3326, 0x251c, 0x12b9, 0xfe17, 0xe9b6, 0xd7f2,
0xcaef, 0xc42d, 0xc483, 0xcbde, 0xd967, 0xeb7b,
};
static int16_t tone[12000];
extern int16_t *ringback_spl;
extern int ringback_size;
extern int ringback_max;
extern int16_t *busy_spl;
extern int busy_size;
extern int busy_max;
extern int16_t *congestion_spl;
extern int congestion_size;
extern int congestion_max;
void init_radiocom_tones(void)
{
int i, j;
for (i = 0, j = 0; i < 12000; i++) {
tone[i] = pattern[j++];
if (j == 200)
j = 0;
}
ringback_spl = tone;
ringback_size = 12000;
ringback_max = 8 * 5000; /* 1500 / 3500 */
busy_spl = tone;
busy_size = 4000;
busy_max = 8 * 1000; /* 500 / 500 */
congestion_spl = tone;
congestion_size = 4000;
congestion_max = 8 * 1000; /* 500 / 500 */
}

3
src/r2000/tones.h Normal file
View File

@ -0,0 +1,3 @@
void init_radiocom_tones(void);