parent
cbfc818cce
commit
57caa536cf
@ -1,3 +1,3 @@ |
||||
AUTOMAKE_OPTIONS = foreign
|
||||
SUBDIRS = common anetz bnetz
|
||||
SUBDIRS = common anetz bnetz nmt
|
||||
|
||||
|
@ -0,0 +1,19 @@ |
||||
AM_CPPFLAGS = -Wall -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
nmt
|
||||
|
||||
nmt_SOURCES = \
|
||||
nmt.c \
|
||||
dsp.c \
|
||||
frame.c \
|
||||
image.c \
|
||||
tones.c \
|
||||
announcement.c \
|
||||
main.c
|
||||
nmt_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(ALSA_LIBS) \
|
||||
$(top_builddir)/src/common/libcommon.a \
|
||||
-lm
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
||||
|
||||
void init_announcement(void); |
||||
|
@ -0,0 +1,596 @@ |
||||
/* NMT audio processing
|
||||
* |
||||
* (C) 2016 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 <stdlib.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include "../common/debug.h" |
||||
#include "../common/timer.h" |
||||
#include "../common/call.h" |
||||
#include "../common/goertzel.h" |
||||
#include "nmt.h" |
||||
#include "dsp.h" |
||||
|
||||
#define PI M_PI |
||||
|
||||
/* signalling */ |
||||
#define TX_PEAK_FSK 16384 /* peak amplitude of signalling FSK */ |
||||
#define TX_PEAK_SUPER 1638 /* peak amplitude of supervisory signal */ |
||||
#define BIT_RATE 1200 /* baud rate */ |
||||
#define STEPS_PER_BIT 10 /* step every 1/12000 sec */ |
||||
#define DIALTONE_HZ 425.0 /* dial tone frequency */ |
||||
#define TX_PEAK_DIALTONE 16000 /* dial tone peak */ |
||||
#define SUPER_DURATION 0.25 /* duration of supervisory signal measurement */ |
||||
#define SUPER_DETECT_COUNT 4 /* number of measures to detect supervisory signal */ |
||||
#define MUTE_DURATION 0.280 /* a tiny bit more than two frames */ |
||||
|
||||
/* two signalling tones */ |
||||
static double fsk_bits[2] = { |
||||
1800.0, |
||||
1200.0, |
||||
}; |
||||
|
||||
/* two supervisory tones */ |
||||
static double super_freq[5] = { |
||||
3955.0, /* 0-Signal 1 */ |
||||
3985.0, /* 0-Signal 2 */ |
||||
4015.0, /* 0-Signal 3 */ |
||||
4045.0, /* 0-Signal 4 */ |
||||
3900.0, /* noise level to check against */ |
||||
}; |
||||
|
||||
/* table for fast sine generation */ |
||||
int dsp_sine_super[256]; |
||||
int dsp_sine_dialtone[256]; |
||||
|
||||
/* global init for FSK */ |
||||
void dsp_init(void) |
||||
{ |
||||
int i; |
||||
double s; |
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table for supervisory signal.\n"); |
||||
for (i = 0; i < 256; i++) { |
||||
s = sin((double)i / 256.0 * 2.0 * PI); |
||||
dsp_sine_super[i] = (int)(s * TX_PEAK_SUPER); |
||||
dsp_sine_dialtone[i] = (int)(s * TX_PEAK_DIALTONE); |
||||
} |
||||
} |
||||
|
||||
/* Init FSK of transceiver */ |
||||
int dsp_init_sender(nmt_t *nmt) |
||||
{ |
||||
double coeff; |
||||
int16_t *spl; |
||||
int i; |
||||
|
||||
init_compander(&nmt->cstate, 8000, 3.0, 13.5); |
||||
|
||||
if ((nmt->sender.samplerate % (BIT_RATE * STEPS_PER_BIT))) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "Sample rate must be a multiple of %d bits per second.\n", BIT_RATE * STEPS_PER_BIT); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
/* this should not happen. it is implied by previous check */ |
||||
if (nmt->supervisory && nmt->sender.samplerate < 12000) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "Sample rate must be at least 12000 Hz to process supervisory signal.\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for Transceiver.\n"); |
||||
|
||||
/* allocate sample for 2 bits with 2 polarities */ |
||||
nmt->samples_per_bit = nmt->sender.samplerate / BIT_RATE; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per bit duration.\n", nmt->samples_per_bit); |
||||
nmt->fsk_filter_step = nmt->samples_per_bit / STEPS_PER_BIT; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per filter step.\n", nmt->fsk_filter_step); |
||||
nmt->fsk_sine[0][0] = calloc(4, nmt->samples_per_bit * sizeof(int16_t)); |
||||
nmt->fsk_sine[0][1] = nmt->fsk_sine[0][0] + nmt->samples_per_bit; |
||||
nmt->fsk_sine[1][0] = nmt->fsk_sine[0][1] + nmt->samples_per_bit; |
||||
nmt->fsk_sine[1][1] = nmt->fsk_sine[1][0] + nmt->samples_per_bit; |
||||
if (!nmt->fsk_sine[0][0]) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n"); |
||||
return -ENOMEM; |
||||
} |
||||
|
||||
/* generate sines */ |
||||
for (i = 0; i < nmt->samples_per_bit; i++) { |
||||
nmt->fsk_sine[0][0][i] = TX_PEAK_FSK * sin(3.0 * PI * (double)i / (double)nmt->samples_per_bit); /* 1.5 waves */ |
||||
nmt->fsk_sine[0][1][i] = TX_PEAK_FSK * sin(2.0 * PI * (double)i / (double)nmt->samples_per_bit); /* 1 wave */ |
||||
nmt->fsk_sine[1][0][i] = -nmt->fsk_sine[0][0][i]; |
||||
nmt->fsk_sine[1][1][i] = -nmt->fsk_sine[0][1][i]; |
||||
} |
||||
|
||||
/* allocate ring buffers, one bit duration */ |
||||
spl = calloc(1, nmt->samples_per_bit * sizeof(*spl)); |
||||
if (!spl) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n"); |
||||
return -ENOMEM; |
||||
} |
||||
nmt->fsk_filter_spl = spl; |
||||
nmt->fsk_filter_bit = -1; |
||||
|
||||
/* allocate transmit buffer for a complete frame */ |
||||
spl = calloc(166, nmt->samples_per_bit * sizeof(*spl)); |
||||
if (!spl) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n"); |
||||
return -ENOMEM; |
||||
} |
||||
nmt->frame_spl = spl; |
||||
|
||||
/* allocate ring buffer for supervisory signal detection */ |
||||
nmt->super_samples = (int)((double)nmt->sender.samplerate * SUPER_DURATION + 0.5); |
||||
spl = calloc(166, nmt->super_samples * sizeof(*spl)); |
||||
if (!spl) { |
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n"); |
||||
return -ENOMEM; |
||||
} |
||||
nmt->super_filter_spl = spl; |
||||
|
||||
/* count symbols */ |
||||
for (i = 0; i < 2; i++) { |
||||
coeff = 2.0 * cos(2.0 * PI * fsk_bits[i] / (double)nmt->sender.samplerate); |
||||
nmt->fsk_coeff[i] = coeff * 32768.0; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "coeff[%d] = %d\n", i, (int)nmt->fsk_coeff[i]); |
||||
} |
||||
|
||||
/* count supervidory tones */ |
||||
for (i = 0; i < 5; i++) { |
||||
coeff = 2.0 * cos(2.0 * PI * super_freq[i] / (double)nmt->sender.samplerate); |
||||
nmt->super_coeff[i] = coeff * 32768.0; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "supervisory coeff[%d] = %d\n", i, (int)nmt->super_coeff[i]); |
||||
|
||||
if (i < 4) { |
||||
nmt->super_phaseshift256[i] = 256.0 / ((double)nmt->sender.samplerate / super_freq[i]); |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "phaseshift_super[%d] = %.4f\n", i, nmt->super_phaseshift256[i]); |
||||
} |
||||
} |
||||
super_reset(nmt); |
||||
|
||||
/* dial tone */ |
||||
nmt->dial_phaseshift256 = 256.0 / ((double)nmt->sender.samplerate / DIALTONE_HZ); |
||||
|
||||
/* dtmf */ |
||||
dtmf_init(&nmt->dtmf, 8000); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* Cleanup transceiver instance. */ |
||||
void dsp_cleanup_sender(nmt_t *nmt) |
||||
{ |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n"); |
||||
|
||||
if (nmt->frame_spl) { |
||||
free(nmt->frame_spl); |
||||
nmt->frame_spl = NULL; |
||||
} |
||||
if (nmt->fsk_filter_spl) { |
||||
free(nmt->fsk_filter_spl); |
||||
nmt->fsk_filter_spl = NULL; |
||||
} |
||||
if (nmt->super_filter_spl) { |
||||
free(nmt->super_filter_spl); |
||||
nmt->super_filter_spl = NULL; |
||||
} |
||||
} |
||||
|
||||
/* Check for SYNC bits, then collect data bits */ |
||||
static void fsk_receive_bit(nmt_t *nmt, int bit, double quality, double level) |
||||
{ |
||||
double frames_elapsed; |
||||
|
||||
// printf("bit=%d quality=%.4f\n", bit, quality);
|
||||
if (!nmt->fsk_filter_in_sync) { |
||||
nmt->fsk_filter_sync = (nmt->fsk_filter_sync << 1) | bit; |
||||
|
||||
/* check if pattern 1010111100010010 matches */ |
||||
if (nmt->fsk_filter_sync != 0xaf12) |
||||
return; |
||||
// printf("sync\n");
|
||||
|
||||
/* sync time */ |
||||
nmt->rx_sample_count_last = nmt->rx_sample_count_current; |
||||
nmt->rx_sample_count_current = nmt->rx_sample_count - nmt->samples_per_bit * 26; |
||||
|
||||
/* rest sync register */ |
||||
nmt->fsk_filter_sync = 0; |
||||
nmt->fsk_filter_in_sync = 1; |
||||
nmt->fsk_filter_count = 0; |
||||
nmt->fsk_filter_levelsum = 0; |
||||
nmt->fsk_filter_qualitysum = 0; |
||||
|
||||
/* set muting of receive path */ |
||||
nmt->fsk_filter_mute = (int)((double)nmt->sender.samplerate * MUTE_DURATION); |
||||
return; |
||||
} |
||||
|
||||
/* read bits */ |
||||
nmt->fsk_filter_frame[nmt->fsk_filter_count++] = bit + '0'; |
||||
nmt->fsk_filter_levelsum += level; |
||||
nmt->fsk_filter_qualitysum += quality; |
||||
if (nmt->fsk_filter_count != 140) |
||||
return; |
||||
|
||||
/* end of frame */ |
||||
nmt->fsk_filter_frame[140] = '\0'; |
||||
nmt->fsk_filter_in_sync = 0; |
||||
|
||||
/* send telegramm */ |
||||
frames_elapsed = (double)(nmt->rx_sample_count_current - nmt->rx_sample_count_last) / (double)(nmt->samples_per_bit * 166); |
||||
nmt_receive_frame(nmt, nmt->fsk_filter_frame, nmt->fsk_filter_qualitysum / 140.0, nmt->fsk_filter_levelsum / 140.0, frames_elapsed); |
||||
} |
||||
|
||||
char *show_level(int value) |
||||
{ |
||||
static char text[22]; |
||||
|
||||
value /= 5; |
||||
if (value < 0) |
||||
value = 0; |
||||
if (value > 20) |
||||
value = 20; |
||||
strcpy(text, " "); |
||||
text[value] = '*'; |
||||
|
||||
return text; |
||||
} |
||||
|
||||
//#define DEBUG_MODULATOR
|
||||
//#define DEBUG_FILTER
|
||||
//#define DEBUG_QUALITY
|
||||
|
||||
/* Filter one chunk of audio an detect tone, quality and loss of signal.
|
||||
* The chunk is a window of 10ms. This window slides over audio stream |
||||
* and is processed every 1ms. (one step) */ |
||||
static inline void fsk_decode_step(nmt_t *nmt, int pos) |
||||
{ |
||||
double level, result[2], softbit, quality; |
||||
int max; |
||||
int16_t *spl; |
||||
int bit; |
||||
|
||||
max = nmt->samples_per_bit; |
||||
spl = nmt->fsk_filter_spl; |
||||
|
||||
/* count time in samples*/ |
||||
nmt->rx_sample_count += nmt->fsk_filter_step; |
||||
|
||||
level = audio_level(spl, max); |
||||
// level = 0.63662 / 2.0;
|
||||
|
||||
audio_goertzel(spl, max, pos, nmt->fsk_coeff, 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.33 |
||||
softbit = (softbit - MIN_QUALITY) / (1.0 - MIN_QUALITY - MIN_QUALITY); |
||||
if (softbit > 1) |
||||
softbit = 1; |
||||
if (softbit < 0) |
||||
softbit = 0; |
||||
#ifdef DEBUG_FILTER |
||||
// printf("|%s", show_level(result[0]/level*100));
|
||||
// printf("|%s| low=%.3f high=%.3f level=%d\n", show_level(result[1]/level*100), result[0]/level, result[1]/level, (int)level);
|
||||
printf("|%s| softbit=%.3f\n", show_level(softbit * 100), softbit); |
||||
#endif |
||||
if (softbit > 0.5) |
||||
bit = 1; |
||||
else |
||||
bit = 0; |
||||
|
||||
if (nmt->fsk_filter_bit != bit) { |
||||
#ifdef DEBUG_FILTER |
||||
puts("bit change"); |
||||
#endif |
||||
/* if we have a bit change, reset sample counter to one half bit duration */ |
||||
nmt->fsk_filter_bit = bit; |
||||
nmt->fsk_filter_sample = 5; |
||||
} else if (--nmt->fsk_filter_sample == 0) { |
||||
#ifdef DEBUG_FILTER |
||||
puts("sample"); |
||||
#endif |
||||
/* if sample counter bit reaches 0, we reset sample counter to one bit duration */ |
||||
// quality = result[bit] / level;
|
||||
if (softbit > 0.5) |
||||
quality = softbit * 2.0 - 1.0; |
||||
else |
||||
quality = 1.0 - softbit * 2.0; |
||||
#ifdef DEBUG_QUALITY |
||||
printf("|%s| quality=%.2f ", show_level(softbit * 100), quality); |
||||
printf("|%s|\n", show_level(quality * 100)); |
||||
#endif |
||||
/* adjust level, so a peak level becomes 100% */ |
||||
fsk_receive_bit(nmt, bit, quality, level / 0.63662); |
||||
nmt->fsk_filter_sample = 10; |
||||
} |
||||
} |
||||
|
||||
/* compare supervisory signal against noise floor on 3900 Hz */ |
||||
static void super_decode(nmt_t *nmt, int16_t *samples, int length) |
||||
{ |
||||
int coeff[2]; |
||||
double result[2], quality; |
||||
|
||||
coeff[0] = nmt->super_coeff[nmt->supervisory - 1]; |
||||
coeff[1] = nmt->super_coeff[4]; /* noise floor detection */ |
||||
audio_goertzel(samples, length, 0, coeff, result, 2); |
||||
|
||||
#if 0 |
||||
/* normalize levels */ |
||||
result[0] *= 32768.0 / (double)TX_PEAK_SUPER / 0.63662; |
||||
result[1] *= 32768.0 / (double)TX_PEAK_SUPER / 0.63662; |
||||
printf("signal=%.4f noise=%.4f\n", result[0], result[1]); |
||||
#endif |
||||
|
||||
quality = (result[0] - result[1]) / result[0]; |
||||
if (quality < 0) |
||||
quality = 0; |
||||
|
||||
if (nmt->sender.loopback) |
||||
PDEBUG(DFSK, DEBUG_NOTICE, "Supervisory level %.2f%% quality %.0f%%\n", result[0] / 0.63662 * 100.0, quality * 100.0); |
||||
if (quality > 0.5) { |
||||
if (nmt->super_detected == 0) { |
||||
nmt->super_detect_count++; |
||||
if (nmt->super_detect_count == SUPER_DETECT_COUNT) { |
||||
nmt->super_detected = 1; |
||||
nmt->super_detect_count = 0; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Supervisory signal detected with level=%.0f%%, quality=%.0f%%.\n", result[0] / 0.63662 * 100.0, quality * 100.0); |
||||
nmt_rx_super(nmt, 1, quality); |
||||
} |
||||
} else |
||||
nmt->super_detect_count = 0; |
||||
} else { |
||||
if (nmt->super_detected == 1) { |
||||
nmt->super_detect_count++; |
||||
if (nmt->super_detect_count == SUPER_DETECT_COUNT) { |
||||
nmt->super_detected = 0; |
||||
nmt->super_detect_count = 0; |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Supervisory signal lost.\n"); |
||||
nmt_rx_super(nmt, 0, 0.0); |
||||
} |
||||
} else |
||||
nmt->super_detect_count = 0; |
||||
} |
||||
} |
||||
|
||||
/* Reset supervisory detection states, so ongoing tone will be detected again. */ |
||||
void super_reset(nmt_t *nmt) |
||||
{ |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Supervisory detector reset.\n"); |
||||
nmt->super_detected = 0; |
||||
nmt->super_detect_count = 0; |
||||
} |
||||
|
||||
/* Process received audio stream from radio unit. */ |
||||
void sender_receive(sender_t *sender, int16_t *samples, int length) |
||||
{ |
||||
nmt_t *nmt = (nmt_t *) sender; |
||||
int16_t *spl; |
||||
int max, pos, step; |
||||
int i; |
||||
|
||||
/* write received samples to decode buffer */ |
||||
max = nmt->super_samples; |
||||
spl = nmt->super_filter_spl; |
||||
pos = nmt->super_filter_pos; |
||||
for (i = 0; i < length; i++) { |
||||
spl[pos++] = samples[i]; |
||||
if (pos == max) { |
||||
pos = 0; |
||||
if (nmt->supervisory) |
||||
super_decode(nmt, spl, max); |
||||
} |
||||
} |
||||
nmt->super_filter_pos = pos; |
||||
|
||||
/* write received samples to decode buffer */ |
||||
max = nmt->samples_per_bit; |
||||
pos = nmt->fsk_filter_pos; |
||||
step = nmt->fsk_filter_step; |
||||
spl = nmt->fsk_filter_spl; |
||||
for (i = 0; i < length; i++) { |
||||
#ifdef DEBUG_MODULATOR |
||||
printf("|%s|\n", show_level((int)((samples[i] / (double)TX_PEAK_FSK) * 50)+50)); |
||||
#endif |
||||
spl[pos++] = samples[i]; |
||||
if (nmt->fsk_filter_mute) { |
||||
samples[i] = 0; |
||||
nmt->fsk_filter_mute--; |
||||
} |
||||
if (pos == max) |
||||
pos = 0; |
||||
/* if filter step has been reched */ |
||||
if (!(pos % step)) { |
||||
fsk_decode_step(nmt, pos); |
||||
} |
||||
} |
||||
nmt->fsk_filter_pos = pos; |
||||
|
||||
if ((nmt->dsp_mode == DSP_MODE_AUDIO || nmt->dsp_mode == DSP_MODE_DTMF) |
||||
&& nmt->sender.callref) { |
||||
int16_t down[length]; /* more than enough */ |
||||
int count; |
||||
|
||||
count = samplerate_downsample(&nmt->sender.srstate, samples, length, down); |
||||
if (nmt->compander) |
||||
expand_audio(&nmt->cstate, down, count); |
||||
if (nmt->dsp_mode == DSP_MODE_DTMF) |
||||
dtmf_tone(&nmt->dtmf, down, count); |
||||
spl = nmt->sender.rxbuf; |
||||
pos = nmt->sender.rxbuf_pos; |
||||
for (i = 0; i < count; i++) { |
||||
spl[pos++] = down[i]; |
||||
if (pos == 160) { |
||||
call_tx_audio(nmt->sender.callref, spl, 160); |
||||
pos = 0; |
||||
} |
||||
} |
||||
nmt->sender.rxbuf_pos = pos; |
||||
} else |
||||
nmt->sender.rxbuf_pos = 0; |
||||
} |
||||
|
||||
static int fsk_frame(nmt_t *nmt, int16_t *samples, int length) |
||||
{ |
||||
int16_t *spl; |
||||
const char *frame; |
||||
int i; |
||||
int bit, polarity; |
||||
int count, max; |
||||
|
||||
next_frame: |
||||
if (!nmt->frame) { |
||||
/* request frame */ |
||||
frame = nmt_get_frame(nmt); |
||||
if (!frame) { |
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Stop sending frames.\n"); |
||||
return length; |
||||
} |
||||
nmt->frame = 1; |
||||
nmt->frame_pos = 0; |
||||
spl = nmt->frame_spl; |
||||
/* render frame */ |
||||
polarity = nmt->fsk_polarity; |
||||
for (i = 0; i < 166; i++) { |
||||
bit = (frame[i] == '1'); |
||||
memcpy(spl, nmt->fsk_sine[polarity][bit], nmt->samples_per_bit * sizeof(*spl)); |
||||
spl += nmt->samples_per_bit; |
||||
/* flip polarity when we have 1.5 sine waves */ |
||||
if (bit == 0) |
||||
polarity = 1 - polarity; |
||||
} |
||||
nmt->fsk_polarity = polarity; |
||||
} |
||||
|
||||
/* send audio from frame */ |
||||
max = nmt->samples_per_bit * 166; |
||||
count = max - nmt->frame_pos; |
||||
if (count > length) |
||||
count = length; |
||||
spl = nmt->frame_spl + nmt->frame_pos; |
||||
for (i = 0; i < count; i++) { |
||||
*samples++ = *spl++; |
||||
} |
||||
length -= count; |
||||
nmt->frame_pos += count; |
||||
/* check for end of telegramm */ |
||||
if (nmt->frame_pos == max) { |
||||
nmt->frame = 0; |
||||
/* we need more ? */ |
||||
if (length) |
||||
goto next_frame; |
||||
} |
||||
|
||||
return length; |
||||
} |
||||
|
||||
/* Generate audio stream with supervisory signal. Keep phase for next call of function. */ |
||||
static void super_encode(nmt_t *nmt, int16_t *samples, int length) |
||||
{ |
||||
double phaseshift, phase; |
||||
int32_t sample; |
||||
int i; |
||||
|
||||
phaseshift = nmt->super_phaseshift256[nmt->supervisory - 1]; |
||||
phase = nmt->super_phase256; |
||||
|
||||
for (i = 0; i < length; i++) { |
||||
sample = *samples; |
||||
sample += dsp_sine_super[((uint8_t)phase) & 0xff]; |
||||
if (sample > 32767) |
||||
sample = 32767; |
||||
else if (sample < -32767) |
||||
sample = -32767; |
||||
*samples++ = sample; |
||||
phase += phaseshift; |
||||
if (phase >= 256) |
||||
phase -= 256; |
||||
} |
||||
|
||||
nmt->super_phase256 = phase; |
||||
} |
||||
|
||||
/* Generate audio stream from dial tone. Keep phase for next call of function. */ |
||||
static void dial_tone(nmt_t *nmt, int16_t *samples, int length) |
||||
{ |
||||
double phaseshift, phase; |
||||
int i; |
||||
|
||||
phaseshift = nmt->dial_phaseshift256; |
||||
phase = nmt->dial_phase256; |
||||
|
||||
for (i = 0; i < length; i++) { |
||||
*samples++ = dsp_sine_dialtone[((uint8_t)phase) & 0xff]; |
||||
phase += phaseshift; |
||||
if (phase >= 256) |
||||
phase -= 256; |
||||
} |
||||
|
||||
nmt->dial_phase256 = phase; |
||||
} |
||||
|
||||
/* Provide stream of audio toward radio unit */ |
||||
void sender_send(sender_t *sender, int16_t *samples, int length) |
||||
{ |
||||
nmt_t *nmt = (nmt_t *) sender; |
||||
int len; |
||||
|
||||
again: |
||||
switch (nmt->dsp_mode) { |
||||
case DSP_MODE_AUDIO: |
||||
case DSP_MODE_DTMF: |
||||
jitter_load(&nmt->sender.audio, samples, length); |
||||
if (nmt->supervisory) |
||||
super_encode(nmt, samples, length); |
||||
break; |
||||
case DSP_MODE_DIALTONE: |
||||
dial_tone(nmt, samples, length); |
||||
break; |
||||
case DSP_MODE_SILENCE: |
||||
memset(samples, 0, length * sizeof(*samples)); |
||||
break; |
||||
case DSP_MODE_FRAME: |
||||
/* Encode frame into audio stream. If frames have
|
||||
* stopped, process again for rest of stream. */ |
||||
len = fsk_frame(nmt, samples, length); |
||||
/* special case: add supervisory signal to frame at loop test */ |
||||
if (nmt->sender.loopback && nmt->supervisory) |
||||
super_encode(nmt, samples, length); |
||||
if (len) { |
||||
samples += length - len; |
||||
length = len; |
||||
goto again; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void nmt_set_dsp_mode(nmt_t *nmt, enum dsp_mode mode) |
||||
{ |
||||
/* reset telegramm */ |
||||
if (mode == DSP_MODE_FRAME && nmt->dsp_mode != mode) |
||||
nmt->frame = 0; |
||||
nmt->dsp_mode = mode; |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
|
||||
void dsp_init(void); |
||||
int dsp_init_sender(nmt_t *nmt); |
||||
void dsp_cleanup_sender(nmt_t *nmt); |
||||
void nmt_set_dsp_mode(nmt_t *nmt, enum dsp_mode mode); |
||||
void super_reset(nmt_t *nmt); |
||||
|
@ -0,0 +1,977 @@ |
||||
/* NMT frame transcoding
|
||||
* |
||||
* (C) 2016 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 <stdlib.h> |
||||
#include <string.h> |
||||
#include <inttypes.h> |
||||
#include "../common/debug.h" |
||||
#include "../common/timer.h" |
||||
#include "nmt.h" |
||||
#include "frame.h" |
||||
|
||||
uint64_t nmt_encode_channel(int channel, int power) |
||||
{ |
||||
uint64_t value = 0; |
||||
|
||||
if (channel >= 200) { |
||||
value |= 0x800; |
||||
channel -= 200; |
||||
} |
||||
if (channel >= 100) { |
||||
value |= 0x100; |
||||
channel -= 100; |
||||
} |
||||
value |= channel; |
||||
value |= power << 9; |
||||
|
||||
return value; |
||||
} |
||||
|
||||
int nmt_decode_channel(uint64_t value, int *channel, int *power) |
||||
{ |
||||
if ((value & 0xff) > 99) |
||||
return -1; |
||||
|
||||
*channel = (value & 0xff) + |
||||
((value & 0x100) >> 8) * 100 + |
||||
((value & 0x800) >> 11) * 200; |
||||
*power = (value & 0x600) >> 9; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
void nmt_value2digits(uint64_t value, char *digits, int num) |
||||
{ |
||||
int digit, i; |
||||
|
||||
for (i = 0; i < num; i++) { |
||||
digit = (value >> ((num - 1 - i) << 2)) & 0xf; |
||||
if (digit == 10) |
||||
digits[i] = '0'; |
||||
else if (digit == 0 || digit > 10) |
||||
digits[i] = '?'; |
||||
else |
||||
digits[i] = digit + '0'; |
||||
} |
||||
} |
||||
|
||||
uint64_t nmt_digits2value(const char *digits, int num) |
||||
{ |
||||
int digit, i; |
||||
uint64_t value = 0; |
||||
|
||||
for (i = 0; i < num; i++) { |
||||
value <<= 4; |
||||
digit = *digits++; |
||||
if (digit >= '1' && digit <= '9') |
||||
value |= digit - '0'; |
||||
else |
||||
value |= 10; |
||||
} |
||||
|
||||
return value; |
||||
} |
||||
|
||||
char nmt_value2digit(uint64_t value) |
||||
{ |
||||
return "D1234567890*#ABC"[value & 0x0000f]; |
||||
} |
||||
|
||||
uint16_t nmt_encode_area_no(uint8_t area_no) |
||||
{ |
||||
switch (area_no) { |
||||
case 1: |
||||
return 0x3f3; |
||||
case 2: |
||||
return 0x3f4; |
||||
case 3: |
||||
return 0x3f5; |
||||
case 4: |
||||
return 0x3f6; |
||||
default: |
||||
return 0x000; |
||||
} |
||||
} |
||||
|
||||
/* NMT Doc 450-1 4.3.2 */ |
||||
static struct nmt_frame { |
||||
const char *digits; |
||||
enum nmt_direction direction; |
||||
int prefix; |
||||
const char *nr; |
||||
const char *description; |
||||
} nmt_frame[] = { |
||||
/* Digits Dir. Prefix Nr. Description */ |
||||
/*0*/ { "NNNPYYHHHHHHHHHH", MTX_TO_MS, 12, "1a", "Calling channel indication" }, |
||||
/*1*/ { "NNNPYYHHHHHHHHHH", MTX_TO_MS, 4, "1b", "Combined calling and traffic channel indication" }, |
||||
/*2*/ { "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2a", "Call to mobile subscriber on calling channel" }, |
||||
/*3*/ { "NNNPYYZXXXXXXnnn", MTX_TO_MS, 12, "2b", "Traffic channel allocation on calling channel" }, |
||||
/*4*/ { "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2c", "Queueing information to MS with priority on calling channel" }, |
||||
/*5*/ { "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2d", "Traffic channel scanning order on calling channel" }, |
||||
/*6*/ { "NNNPYYZXXXXXXHHH", MTX_TO_MS, 12, "2f", "Queuing information to ordinary MS" }, |
||||
/*7*/ { "NNNPYYZXXXXXXnnn", MTX_TO_MS, 5, "3a", "Traffic channel allocation on traffic channel" }, |
||||
/*8*/ { "NNNPYYZXXXXXXHHH", MTX_TO_MS, 5, "3b", "Identity request on traffic channel" }, |
||||
/*9*/ { "NNNPYYZXXXXXXnnn", MTX_TO_MS, 9, "3c", "Traffic channel allocation on traffic channel, short procedure" }, |
||||
/*10*/ { "NNNPYYJJJJJJJHHH", MTX_TO_MS, 3, "4", "Free traffic channel indication" }, |
||||
/*11*/ { "NNNPYYZXXXXXXLLL", MTX_TO_MS, 6, "5a", "Line signal" }, |
||||
/*12*/ { "NNNPYYZXXXXXXLQQ", MTX_TO_MS, 6, "5b", "Line signal: Answer to coin-box" }, |
||||
/*13*/ { "JJJPJJJJJJJJJJJJ", MTX_TO_XX, 0, "6", "Idle frame" }, |
||||
/*14*/ { "NNNPYYCCCCCCCJJJ", MTX_TO_MS, 8, "7", "Authentication request" }, |
||||
/*15*/ { "NNNPZXXXXXXTJJJJ", MS_TO_MTX, 1, "10a", "Call acknowledgement from MS on calling channel (shortened frame)" }, |
||||
/*16*/ { "NNNPZXXXXXXTYKKK", MS_TO_MTX, 1, "10b", "Seizure from ordinary MS and identity on traffic channel" }, |
||||
/*17*/ { "NNNPZXXXXXXTYKKK", MS_TO_MTX, 6, "10c", "Seizure and identity from called MS on traffic channel" }, |
||||
/*18*/ { "NNNPZXXXXXXTYKKK", MS_TO_MTX, 14, "11a", "Roaming updating seizure and identity on traffic channel" }, |
||||
/*19*/ { "NNNPZXXXXXXTYKKK", MS_TO_MTX, 15, "11b", "Seizure and call achnowledgement on calling channel from MS with priority (shortened frame)" }, |
||||
/*20*/ { "NNNPZXXXXXXTYKKK", MS_TO_MTX, 11, "12", "Seizure from coin-box on traffic channel" }, |
||||
/*21*/ { "NNNPZXXXXXXLLLLL", MS_TO_MTX, 8, "13a", "Line signal" }, |
||||
/*22*/ { "NNNPZXXXXXXLLLQQ", MS_TO_MTX, 8, "13b", "Line signal: Answer acknowledgement from coin box" }, |
||||
/*23*/ { "NNNPZXXXXXXSSSSS", MS_TO_MTX, 7, "14a", "Digit signal (1st, 3rd, 5th ........digit)" }, |
||||
/*24*/ { "NNNPZXXXXXXSSSSS", MS_TO_MTX, 7, "14b", "Digit signal (2nd, 4th, 6th ........digit)" }, |
||||
/*25*/ { "JJJPJJJJJJJJJJJJ", XX_TO_MTX, 0, "15", "Idle frame" }, |
||||
/*26*/ { "NNNPRRRRRRRRRRRR", MS_TO_MTX, 12, "16", "Signed response" }, |
||||
/*27*/ { "NNNPYYZJJJAfffff", MTX_TO_BS, 15, "20", "Channel activation order" }, |
||||
/*28*/ { "NNNPYYZJJJAJJJJJ", MTX_TO_BS, 15, "20", "Channel activation order" }, |
||||
/*29*/ { "NNNPYYZJJJAfffff", MTX_TO_BS, 15, "20", "Channel activation order" }, |
||||
/*30*/ { "NNNPYYZJJJAlllff", MTX_TO_BS, 15, "20", "Channel activation order" }, |
||||
/*31*/ { "NNNPYYZJJJAlllJJ", MTX_TO_BS, 15, "20", "Channel activation order" }, |
||||
/*32*/ { "NNNPYYZJJJVJJnnn", MTX_TO_BS, 3, "21b", "Signal strength measurement order on data channel or idle or free marked traffic channel" }, |
||||
/*33*/ { "NNNPYYZJJJVJJnnn", MTX_TO_BS, 5, "21c", "Signal strength measurement order on traffic actually used" }, |
||||
/*34*/ { "NNNPYYZJJJVVVVVV", MTX_TO_BS, 14, "22", "Order management/maintenance order on idle channel or data channel" }, |
||||
/*35*/ { "NNNPZJJAJJJJJJJJ", BS_TO_MTX, 9, "25", "Channel status information" }, |
||||
/*36*/ { "NNNPZJJAJJJfllJJ", BS_TO_MTX, 9, "25", "Channel status information" }, |
||||
/*37*/ { "NNNPZJJAJJJJllJJ", BS_TO_MTX, 9, "25", "Channel status information" }, |
||||
/*38*/ { "NNNPZJJAJJJcccJJ", BS_TO_MTX, 9, "25", "Channel status information" }, |
||||
/*39*/ { "NNNPZJJnnnrrrrrr", BS_TO_MTX, 2, "26", "Signal strength measurement result" }, |
||||
/*40*/ { "NNNPZJJVVVVJJJJJ", BS_TO_MTX, 4, "27", "Response on other management/maintenance order on idle channel or data channel" }, |
||||
/*41*/ { "NNNPZJJVVVVJJJJJ", BS_TO_MTX, 13, "28", "Other maintenance information from BS" }, |
||||
/*42*/ { "NNNPYYJJJJJJJHHH", MTX_TO_MS, 10, "30", "Test channel indication" }, |
||||
/*43*/ { "---P------------", MTX_TO_XX, 0, "", "illegal (Spare)" }, |
||||
/*44*/ { "---P------------", XX_TO_MTX, 0, "", "illegal (Spare)" }, |
||||
{ NULL, 0, 0, NULL, NULL } |
||||
}; |
||||
|
||||
/* store actual number of frames for run-time range check */ |
||||
static int num_frames; |
||||
|
||||
const char *nmt_frame_name(int index) |
||||
{ |
||||
if (index < 0 || index >= num_frames) |
||||
return "invalid"; |
||||
return nmt_frame[index].nr; |
||||
} |
||||
|
||||
static const char *param_integer(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
sprintf(result, "%" PRIu64, value); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_hex(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
sprintf(result, "0x%" PRIx64, value); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_channel_no(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
int rc, channel, power; |
||||
|
||||
rc = nmt_decode_channel(value, &channel, &power); |
||||
if (rc < 0) |
||||
sprintf(result, "invalid(%" PRIu64 ")", value); |
||||
else |
||||
sprintf(result, "channel=%d power=%d", channel, power); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_country(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
|
||||
switch (value) { |
||||
case 0: |
||||
return "no additional info"; |
||||
case 4: |
||||
return "Iceland"; |
||||
case 5: |
||||
return "Denmark"; |
||||
case 6: |
||||
return "Sweden"; |
||||
case 7: |
||||
return "Norway"; |
||||
case 8: |
||||
return "Finland"; |
||||
case 9: |
||||
return "nordic country"; |
||||
case 14: |
||||
return "additional info"; |
||||
case 15: |
||||
return "information to/from BS"; |
||||
default: |
||||
sprintf(result, "%" PRIu64 " (unknown)", value); |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
static const char *param_number(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
|
||||
nmt_value2digits(value, result, ndigits); |
||||
result[ndigits] = '\0'; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_ta(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
|
||||
nmt_value2digits(value, result, ndigits); |
||||
result[ndigits] = '\0'; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_line_signal(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[64], *desc = "Spare"; |
||||
|
||||
if (direction == MTX_TO_MS) { |
||||
switch (value & 0xf) { |
||||
case 0: |
||||
desc = "Answer to coin-box"; |
||||
break; |
||||
case 3: |
||||
desc = "Proceed to send unencrypted digits"; |
||||
break; |
||||
case 4: |
||||
desc = "Acknowledge MFT converter in"; |
||||
break; |
||||
case 5: |
||||
desc = "Switch compander in"; |
||||
break; |
||||
case 6: |
||||
desc = "Address complete"; |
||||
break; |
||||
case 7: |
||||
desc = "Switch compander out"; |
||||
break; |
||||
case 9: |
||||
desc = "Ringing order"; |
||||
break; |
||||
case 10: |
||||
desc = "Acknowledge MFT converter out"; |
||||
break; |
||||
case 11: |
||||
desc = "Proceed to send enctrypted digits"; |
||||
break; |
||||
case 13: |
||||
desc = "Clearing, call transfer activated"; |
||||
break; |
||||
case 15: |
||||
desc = "Clearing, call transfer not activated"; |
||||
break; |
||||
} |
||||
} else { |
||||
switch (value & 0xf) { |
||||
case 1: |
||||
desc = "Clearing, release guard"; |
||||
break; |
||||
case 2: |
||||
desc = "Answer acknowledgement, (coin-box)"; |
||||
break; |
||||
case 5: |
||||
desc = "Register recall "; |
||||
break; |
||||
case 7: |
||||
desc = "MFT converter out acknowledge"; |
||||
break; |
||||
case 8: |
||||
desc = "MFT converter in"; |
||||
break; |
||||
case 14: |
||||
desc = "Answer"; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
sprintf(result, "L(%" PRIu64 ") %s", value & 0xf, desc); |
||||
return result; |
||||
} |
||||
|
||||
static const char *param_digit(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
|
||||
if ((value & 0xf0000) != 0x00000 && (value & 0xf0000) != 0xf0000) |
||||
return "Invalid digit"; |
||||
if ((value & 0xf0000) != ((value & 0x0f000) << 4) |
||||
|| (value & 0x00f00) != ((value & 0x000f0) << 4) |
||||
|| (value & 0x000f0) != ((value & 0x0000f) << 4)) |
||||
return "Inconsistent digit"; |
||||
|
||||
result[0] = nmt_value2digit(value); |
||||
result[1] = '\0'; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static const char *param_supervisory(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
switch (value) { |
||||
case 0: |
||||
return "Reserved"; |
||||
case 3: |
||||
return "Frequency 1"; |
||||
case 12: |
||||
return "Frequency 2"; |
||||
case 9: |
||||
return "Frequency 3"; |
||||
case 6: |
||||
return "Frequency 4"; |
||||
default: |
||||
return "Invalid"; |
||||
} |
||||
} |
||||
|
||||
static const char *param_password(uint64_t value, int ndigits, enum nmt_direction direction) |
||||
{ |
||||
static char result[32]; |
||||
|
||||
nmt_value2digits(value, result, ndigits); |
||||
result[ndigits] = '\0'; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static struct nmt_parameter { |
||||
char digit; |
||||
const char *description; |
||||
const char *(*decoder)(uint64_t value, int ndigits, enum nmt_direction direction); |
||||
} nmt_parameter[] = { |
||||
{ 'N', "Channel No.", param_channel_no }, |
||||
{ 'n', "TC No.", param_channel_no }, |
||||
{ 'Y', "Traffic area", param_ta }, |
||||
{ 'Z', "Mobile subscriber country", param_country }, |
||||
{ 'X', "Mobile subscriber No.", param_number }, |
||||
{ 'Q', "Tariff class", param_integer }, |
||||
{ 'L', "Line signal", param_line_signal }, |
||||
{ 'S', "Digit signals", param_digit }, |
||||
{ 'J', "Idle information", param_hex }, |
||||
{ 'A', "Channel activation", param_hex }, |
||||
{ 'V', "Management order", param_hex }, |
||||
{ 'r', "Measurement results", param_hex }, |
||||
{ 'P', "Prefix", param_integer }, |
||||
{ 'f', "Supervisory signal", param_supervisory }, |
||||
{ 'K', "Mobile subscriber password", param_password }, |
||||
{ 'T', "Area info", param_hex }, |
||||
{ 'H', "Additional info", param_hex }, |
||||
{ 'C', "Random challenge", param_hex }, |
||||
{ 'R', "Signed response", param_hex }, |
||||
{ 'l', "Limit strength evaluation", param_hex }, |
||||
{ 'c', "c", param_hex }, |
||||
{ 0, NULL } |
||||
}; |
||||
|
||||
/* Depending on P-value, direction and additional info, frame index (used for
|
||||
* nmt_frame[]) is recoded. |
||||
*/ |
||||
static int decode_frame_index(const uint8_t *digits, enum nmt_direction direction, int callack) |
||||
{ |
||||
if (direction == MS_TO_MTX || direction == BS_TO_MTX || direction == XX_TO_MTX) { |
||||
/* MS/BS TO MTX */ |
||||
switch (digits[3]) { |
||||
case 0: |
||||
return 25; |
||||
case 1: |
||||
if (callack) |
||||
return 15; |
||||
return 16; |
||||
case 2: |
||||
return 39; |
||||
case 3: |
||||
break; |
||||
case 4: |
||||
return 40; |
||||
case 5: |
||||
break; |
||||
case 6: |
||||
return 17; |
||||
case 7: |
||||
if (digits[11] == 0) |
||||
return 23; |
||||
if (digits[11] == 15) |
||||
return 24; |
||||
return -1; |
||||
case 8: |
||||
if (digits[11] == 2) |
||||
return 22; |
||||
return 21; |
||||
case 9: |
||||
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) { |
||||
case 2: |
||||
case 6: |
||||
return 36; |
||||
case 14: |
||||
return 37; |
||||
case 7: |
||||
case 8: |
||||
return 38; |
||||
default: |
||||
return 35; |
||||
} |
||||
case 10: |
||||
break; |
||||
case 11: |
||||
return 20; |
||||
case 12: |
||||
return 26; |
||||
case 13: |
||||
return 41; |
||||
case 14: |
||||
return 18; |
||||
case 15: |
||||
return 19; |
||||
} |
||||
return 44; |
||||
} else { |
||||
/* MTX to MS/BS */ |
||||
switch (digits[3]) { |
||||
case 0: |
||||
return 13; |
||||
case 1: |
||||
break; |
||||
case 2: |
||||
break; |
||||
case 3: |
||||
if (digits[6] == 15) |
||||
return 32; |
||||
return 10; |
||||
case 4: |
||||
return 1; |
||||
case 5: |
||||
if (digits[6] == 15) |
||||
return 33; |
||||
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) { |
||||
case 0x3f3: |
||||
case 0x3f4: |
||||
case 0x3f5: |
||||
case 0x3f6: |
||||
case 0x000: |
||||
return 8; |
||||
default: |
||||
return 7; |
||||
} |
||||
case 6: |
||||
if (digits[13] == 0) |
||||
return 12; |
||||
return 11; |
||||
case 7: |
||||
break; |
||||
case 8: |
||||
return 14; |
||||
case 9: |
||||
return 9; |
||||
case 10: |
||||
return 42; |
||||
case 11: |
||||
break; |
||||
case 12: |
||||
/* no subscriber */ |
||||
if (digits[6] == 0) |
||||
return 0; |
||||
/* battery saving */ |
||||
if (digits[6] == 14) |
||||
return 0; |
||||
/* info to BS (should not happen here) */ |
||||
if (digits[6] == 15) |
||||
return 0; |
||||
switch((digits[13] << 8) + (digits[14] << 4) + digits[15]) { |
||||
case 0x3f3: |
||||
case 0x3f4: |
||||
case 0x3f5: |
||||
case 0x3f6: |
||||
case 0x000: |
||||
return 2; |
||||
case 0x3f0: |
||||
return 6; |
||||
case 0x3f1: |
||||
return 4; |
||||
case 0x3f2: |
||||
return 5; |
||||
default: |
||||
return 3; |
||||
} |
||||
case 13: |
||||
break; |
||||
case 14: |
||||
if (digits[13] != 15) |
||||
break; |
||||
return 34; |
||||
case 15: |
||||
if (digits[13] != 15) |
||||
break; |
||||
switch (digits[10]) { |
||||
case 3: |
||||
return 27; |
||||
case 6: |
||||
case 13: |
||||
return 29; |
||||
case 7: |
||||
case 14: |
||||
return 30; |
||||
case 15: |
||||
return 31; |
||||
default: |
||||
return 28; |
||||
} |
||||
} |
||||
return 43; |
||||
} |
||||
} |
||||
|
||||
int init_frame(void) |
||||
{ |
||||
int i, j, k; |
||||
char digit; |
||||
|
||||
/* check if all digits actually exist */ |
||||
for (i = 0; nmt_frame[i].digits; i++) { |
||||
for (j = 0; j < 16; j++) { |
||||
digit = nmt_frame[i].digits[j]; |
||||
if (digit == '-') |
||||
continue; |
||||
for (k = 0; nmt_parameter[k].digit; k++) { |
||||
if (nmt_parameter[k].digit == digit) |
||||
break; |
||||
} |
||||
if (!nmt_parameter[k].digit) { |
||||
PDEBUG(DFRAME, DEBUG_ERROR, "Digit '%c' in message index %d does not exist, please fix!\n", digit, i); |
||||
return -1; |
||||
} |
||||
} |
||||
} |
||||
num_frames = i; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* decode 16 digits frame */ |
||||
static void disassemble_frame(frame_t *frame, const uint8_t *digits, enum nmt_direction direction, int callack) |
||||
{ |
||||
int index; |
||||
int i, j, ndigits; |
||||
char digit; |
||||
uint64_t value; |
||||
|
||||
memset(frame, 0, sizeof(*frame)); |
||||
|
||||
/* index of frame */ |
||||
index = decode_frame_index(digits, direction, callack); |
||||
frame->index = index; |
||||
|
||||
/* update direction */ |
||||
direction = nmt_frame[index].direction; |
||||
|
||||
PDEBUG(DFRAME, DEBUG_DEBUG, "Decoding %s %s %s\n", nmt_dir_name(direction), nmt_frame[index].nr, nmt_frame[index].description); |
||||
|
||||
for (i = 0; i < 16; i++) { |
||||
digit = nmt_frame[index].digits[i]; |
||||
if (digit == '-') |
||||
continue; |
||||
value = digits[i]; |
||||
ndigits = 1; |
||||
for (j = i + 1; j < 16; j++) { |
||||
if (nmt_frame[index].digits[j] != digit) |
||||
break; |
||||
value = (value << 4) | digits[j]; |
||||
ndigits++; |
||||
i++; |
||||
} |
||||
switch (digit) { |
||||
case 'N': |
||||
frame->channel_no = value; |
||||
break; |
||||
case 'n': |
||||
frame->tc_no = value; |
||||
break; |
||||
case 'Y': |
||||
frame->traffic_area = value; |
||||
break; |
||||
case 'Z': |
||||
frame->ms_country = value; |
||||
break; |
||||
case 'X': |
||||
frame->ms_number = value; |
||||
break; |
||||
case 'Q': |
||||
frame->tariff_class = value; |
||||
break; |
||||
case 'L': |
||||
frame->line_signal = value; |
||||
break; |
||||
case 'S': |
||||
frame->digit = value; |
||||
break; |
||||
case 'J': |
||||
frame->idle = value; |
||||
break; |
||||
case 'A': |
||||
frame->chan_act = value; |
||||
break; |
||||
case 'V': |
||||
frame->meas_order = value; |
||||
break; |
||||
case 'r': |
||||
frame->meas = value; |
||||
break; |
||||
case 'P': |
||||
frame->prefix = value; |
||||
break; |
||||
case 'f': |
||||
frame->supervisory = value; |
||||
break; |
||||
case 'K': |
||||
frame->ms_password = value; |
||||
break; |
||||
case 'T': |
||||
frame->area_info = value; |
||||
break; |
||||
case 'H': |
||||
frame->additional_info = value; |
||||
break; |
||||
case 'C': |
||||
frame->rand = value; |
||||
break; |
||||
case 'R': |
||||
frame->sres = value; |
||||
break; |
||||
case 'l': |
||||
frame- |