Implementation of "Radiocom 2000", the analog French mobile network
This commit is contained in:
parent
8ce3ff455d
commit
ffd3b848e1
|
@ -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
8
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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
void print_image(void);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
|
|
@ -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 */
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
void init_radiocom_tones(void);
|
||||
|
Loading…
Reference in New Issue