parent
8ce3ff455d
commit
ffd3b848e1
@ -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
|
||||
|
||||
|
@ -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 |
||||
|
||||
< |