parent
1bd471a703
commit
3dcb7b34df
Binary file not shown.
@ -0,0 +1,51 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
5-ton-folge
|
||||
|
||||
5_ton_folge_SOURCES = \
|
||||
fuenf.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
main.c
|
||||
5_ton_folge_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../anetz/libgermanton.a \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libmobile/libmobile.a \
|
||||
$(top_builddir)/src/libosmocc/libosmocc.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libgoertzel/libgoertzel.a \
|
||||
$(top_builddir)/src/libjitter/libjitter.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libsamplerate/libsamplerate.a \
|
||||
$(top_builddir)/src/libemphasis/libemphasis.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
$(top_builddir)/src/libg711/libg711.a \
|
||||
$(top_builddir)/src/libaaimage/libaaimage.a \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA
|
||||
5_ton_folge_LDADD += \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(ALSA_LIBS)
|
||||
|
||||
endif
|
||||
|
||||
if HAVE_SDR
|
||||
5_ton_folge_LDADD += \
|
||||
$(top_builddir)/src/libsdr/libsdr.a \
|
||||
$(top_builddir)/src/libam/libam.a \
|
||||
$(top_builddir)/src/libfft/libfft.a \
|
||||
$(UHD_LIBS) \
|
||||
$(SOAPY_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_ALSA
|
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif
|
||||
|
@ -0,0 +1,646 @@
|
||||
/* selective call signal processing
|
||||
*
|
||||
* (C) 2019 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 fuenf->sender.kanal
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libmobile/call.h"
|
||||
#include "fuenf.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define MAX_DISPLAY 1.4 /* something above speech level, no emphasis */
|
||||
#define MAX_MODULATION 3000.0 /* maximum bandwidth of audio signal */
|
||||
|
||||
/* TX and RX parameters */
|
||||
#define TONE_LEVEL 0.5 /* because we have two tones, also applies to digits */
|
||||
|
||||
/* TX parameters */
|
||||
#define TX_LEN_PREAMBLE 0.600 /* duration of preamble */
|
||||
#define TX_LEN_PAUSE 0.600 /* duration of pause */
|
||||
#define TX_LEN_POSTAMBLE 0.070 /* duration of postamble */
|
||||
#define TX_LEN_DIGIT 0.070 /* duration of paging tone */
|
||||
#define TX_NUM_KANAL 10 /* number of 'Kanalbelegungston' */
|
||||
#define TX_LEN_KANAL 0.250 /* duration of 'Kanalbelegungston' */
|
||||
#define TX_LEN_KANAL_PAUSE 0.250 /* pause after 'Kanalbelegungston' */
|
||||
#define TX_LEN_SIGNAL 5.0 /* double tone signal length */
|
||||
|
||||
/* RX parameters */
|
||||
#define RX_MIN_LEVEL 0.1 /* level relative to TONE_LEVEL, below is silence (-20 dB) */
|
||||
#define RX_MIN_PREAMBLE 800 /* duration of silence before detecting first digit (in samples) */
|
||||
#define RX_DIGIT_FILTER 100.0 /* frequency to allow change of tones ( 100 Hz = 5 ms ) */
|
||||
#define RX_TOL_DIGIT_FREQ 0.045 /* maximum frequency error factor allowd to detect a tone (+- 4.5%) */
|
||||
#define RX_LEN_DIGIT_TH 80 /* time to wait for digit being stable ( 10 ms ) */
|
||||
#define RX_LEN_DIGIT_MIN 400 /* minimum length in seconds allowed for a digit (- 20 ms in samples) */
|
||||
#define RX_LEN_DIGIT_MAX 720 /* minimum length in seconds allowed for a digit (+ 20 ms in samples) */
|
||||
#define RX_LEN_TONE_MIN 16000 /* minimum length in seconds to detect double tone (2 seconds in samples) */
|
||||
#define RX_WAIT_TONE_MAX 48000 /* maximum time to wait for double tone (6 seconds in samples) */
|
||||
#define RX_TOL_TONE_FREQ 5.0 /* use +-5 Hz for bandwidth, to make things simpler. (-7.4 dB @ +-5 Hz) */
|
||||
|
||||
static double digit_freq[DSP_NUM_DIGITS] = {
|
||||
1060.0,
|
||||
1160.0,
|
||||
1270.0,
|
||||
1400.0,
|
||||
1530.0,
|
||||
1670.0,
|
||||
1830.0,
|
||||
2000.0,
|
||||
2200.0,
|
||||
2400.0,
|
||||
2600.0, /* repeat digit */
|
||||
};
|
||||
#define DIGIT_FREQ_MIN 1080.0
|
||||
#define DIGIT_FREQ_MAX 2600.0
|
||||
|
||||
#define REPEAT_DIGIT 10
|
||||
|
||||
/* these are the frequencies of tones to be detected */
|
||||
static double tone_freq[DSP_NUM_TONES] = {
|
||||
675.0,
|
||||
825.0,
|
||||
1240.0,
|
||||
1860.0,
|
||||
};
|
||||
|
||||
#define DSP_NUM_SIGNALS 6
|
||||
static struct signals {
|
||||
enum fuenf_funktion funktion;
|
||||
int tone1, tone2;
|
||||
} signals[DSP_NUM_SIGNALS] = {
|
||||
{ FUENF_FUNKTION_FEUER, 0, 2 },
|
||||
{ FUENF_FUNKTION_PROBE, 0, 3 },
|
||||
{ FUENF_FUNKTION_WARNUNG, 0, 1 },
|
||||
{ FUENF_FUNKTION_ABC, 2, 3 },
|
||||
{ FUENF_FUNKTION_ENTWARNUNG, 1, 3 },
|
||||
{ FUENF_FUNKTION_KATASTROPHE, 1, 2 },
|
||||
};
|
||||
|
||||
/* Init transceiver instance. */
|
||||
int dsp_init_sender(fuenf_t *fuenf, int samplerate, double max_deviation, double signal_deviation)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
sample_t *spl;
|
||||
int len;
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Init DSP for transceiver.\n");
|
||||
|
||||
/* set modulation parameters */
|
||||
sender_set_fm(&fuenf->sender, max_deviation, MAX_MODULATION, signal_deviation, MAX_DISPLAY);
|
||||
|
||||
fuenf->sample_duration = 1.0 / (double)samplerate;
|
||||
|
||||
/* init digit demodulator */
|
||||
rc = fm_demod_init(&fuenf->rx_digit_demod, 8000, (DIGIT_FREQ_MIN + DIGIT_FREQ_MAX) / 2.0, DIGIT_FREQ_MAX - DIGIT_FREQ_MIN);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_lowpass_init(&fuenf->rx_digit_lp, RX_DIGIT_FILTER, 8000, 2);
|
||||
|
||||
/* init signal tone filters */
|
||||
for (i = 0; i < DSP_NUM_TONES; i++)
|
||||
audio_goertzel_init(&fuenf->rx_tone_goertzel[i], tone_freq[i], 8000);
|
||||
|
||||
/* allocate buffer */
|
||||
len = (int)(8000.0 * (1.0 / RX_TOL_TONE_FREQ) + 0.5);
|
||||
spl = calloc(1, len * sizeof(*spl));
|
||||
if (!spl) {
|
||||
PDEBUG(DDSP, DEBUG_ERROR, "No memory!\n");
|
||||
goto error;
|
||||
}
|
||||
fuenf->rx_tone_filter_spl = spl;
|
||||
fuenf->rx_tone_filter_size = len;
|
||||
|
||||
/* display values */
|
||||
fuenf->dmp_digit_level = display_measurements_add(&fuenf->sender.dispmeas, "Digit Level", "%.0f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
|
||||
for (i = 0; i < DSP_NUM_TONES; i++) {
|
||||
char name[64];
|
||||
sprintf(name, "%.0f Hz Level", tone_freq[i]);
|
||||
fuenf->dmp_tone_levels[i] = display_measurements_add(&fuenf->sender.dispmeas, name, "%.0f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 150.0, 100.0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dsp_cleanup_sender(fuenf);
|
||||
|
||||
return -rc;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void dsp_cleanup_sender(fuenf_t *fuenf)
|
||||
{
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Cleanup DSP for transceiver.\n");
|
||||
|
||||
/* free tone buffers */
|
||||
if (fuenf->rx_tone_filter_spl)
|
||||
free(fuenf->rx_tone_filter_spl);
|
||||
}
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
/* receive digits and decode */
|
||||
static void digit_decode(fuenf_t *fuenf, sample_t *samples, int length)
|
||||
{
|
||||
sample_t frequency[length], f, a;
|
||||
sample_t I[length], Q[length];
|
||||
int i, d;
|
||||
int change, change_count;
|
||||
|
||||
/* tone demodulation */
|
||||
fm_demodulate_real(&fuenf->rx_digit_demod, frequency, length, samples, I, Q);
|
||||
|
||||
/* reduce bandwidth of tone detector */
|
||||
iir_process(&fuenf->rx_digit_lp, frequency, length);
|
||||
|
||||
/* detect tone */
|
||||
for (i = 0; i < length; i++) {
|
||||
/* get frequency */
|
||||
f = frequency[i] + (DIGIT_FREQ_MIN + DIGIT_FREQ_MAX) / 2.0;
|
||||
|
||||
/* get amplitude (a is a sqaure of the amplitude for faster math) */
|
||||
a = (I[i] * I[i] + Q[i] * Q[i]) * 2.0 * 2.0 / TONE_LEVEL / TONE_LEVEL;
|
||||
|
||||
#ifdef DEBUG
|
||||
if (i == 0) printf("%s %.5f ", debug_amplitude(frequency[i] / (DIGIT_FREQ_MAX - DIGIT_FREQ_MIN) * 2.0), f);
|
||||
if (i == 0) printf("%s %.5f ", debug_amplitude(sqrt(a)), sqrt(a));
|
||||
#endif
|
||||
/* get digit that matches the frequency tolerance */
|
||||
for (d = 0; d < DSP_NUM_DIGITS; d++) {
|
||||
if (f >= digit_freq[d] * (1.0 - RX_TOL_DIGIT_FREQ) && f <= digit_freq[d] * (1.0 + RX_TOL_DIGIT_FREQ))
|
||||
break;
|
||||
}
|
||||
|
||||
/* digit lound enough ? */
|
||||
if (a >= RX_MIN_LEVEL * RX_MIN_LEVEL && d < DSP_NUM_DIGITS) {
|
||||
#ifdef DEBUG
|
||||
if (i == 0 && d < DSP_NUM_DIGITS) printf("digit=%d (%d == no digit detected)", d, DSP_NUM_DIGITS);
|
||||
#endif
|
||||
} else
|
||||
d = -1;
|
||||
#ifdef DEBUG
|
||||
if (i == 0) printf("\n");
|
||||
#endif
|
||||
|
||||
/* correct amplitude at cutoff frequency digit '1' and 'repeat'.*/
|
||||
if (d == 0 || d == DSP_NUM_DIGITS - 1)
|
||||
a = a * 2; /* actually 1.414 at cutoff, but a is a square, so we can use 2 */
|
||||
|
||||
/* count how long this digit sustains, also report if it has changed and when */
|
||||
if (d != fuenf->rx_digit_last) {
|
||||
change = 1;
|
||||
change_count = fuenf->rx_digit_count;
|
||||
fuenf->rx_digit_last = d;
|
||||
fuenf->rx_digit_count = 0;
|
||||
} else
|
||||
change = 0;
|
||||
fuenf->rx_digit_count++;
|
||||
|
||||
/* state machine to detect sequence of 5 tones */
|
||||
switch (fuenf->rx_state) {
|
||||
case RX_STATE_RESET:
|
||||
/* wait for silence */
|
||||
if (d >= 0)
|
||||
break;
|
||||
/* check if we have enought silence */
|
||||
if (fuenf->rx_digit_count == RX_MIN_PREAMBLE) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected silence, waiting for digits.\n");
|
||||
fuenf->rx_state = RX_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RX_STATE_IDLE:
|
||||
/* wait for digit */
|
||||
if (d < 0)
|
||||
break;
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "We have some tone, start receiving digits.\n");
|
||||
fuenf->rx_callsign_count = 0;
|
||||
fuenf->rx_callsign[fuenf->rx_callsign_count] = d;
|
||||
fuenf->rx_state = RX_STATE_DIGIT;
|
||||
break;
|
||||
case RX_STATE_DIGIT:
|
||||
/* wait for change */
|
||||
if (!change) {
|
||||
if (fuenf->rx_digit_count == RX_LEN_DIGIT_TH) {
|
||||
if (d < 0) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Not enough digits received, waiting for next transmission.\n");
|
||||
fuenf->rx_function = 0;
|
||||
fuenf->rx_function_count = 0;
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected digit #%d (amplitude = %.0f%%)\n", d + 1, sqrt(a) * 100.0);
|
||||
display_measurements_update(fuenf->dmp_digit_level, sqrt(a) * 100.0, 0.0);
|
||||
break;
|
||||
}
|
||||
if (fuenf->rx_digit_count == RX_LEN_DIGIT_MAX) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected digit too long, waiting for next transmission.\n");
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* if digit did not become stable (changed) during threshold */
|
||||
if (change_count < RX_LEN_DIGIT_TH) {
|
||||
/* store detected digit and wait for this one to become stable */
|
||||
fuenf->rx_callsign[fuenf->rx_callsign_count] = d;
|
||||
break;
|
||||
}
|
||||
/* if counter (when changed) was too low */
|
||||
if (change_count < RX_LEN_DIGIT_MIN) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected digit too short, waiting for next transmission.\n");
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
/* increment digit and store detected digit */
|
||||
fuenf->rx_callsign_count++;
|
||||
fuenf->rx_callsign[fuenf->rx_callsign_count] = d;
|
||||
/* if 5 tones are received, decode */
|
||||
if (fuenf->rx_callsign_count == 5) {
|
||||
for (i = 0; i < 5; i++) {
|
||||
if (fuenf->rx_callsign[i] == REPEAT_DIGIT) {
|
||||
if (i == 0) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "First digit is a repeat digit, this is not allowed, waiting for next transmission.\n");
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
fuenf->rx_callsign[i] = fuenf->rx_callsign[i - 1];
|
||||
} else
|
||||
if (fuenf->rx_callsign[i] == 9)
|
||||
fuenf->rx_callsign[i] = '0';
|
||||
else
|
||||
fuenf->rx_callsign[i] = '1' + fuenf->rx_callsign[i];
|
||||
}
|
||||
fuenf->rx_callsign[i] = '\0';
|
||||
if (i < 5)
|
||||
break;
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Complete call sign '%s' received, waiting for signal tone(s).\n", fuenf->rx_callsign);
|
||||
fuenf_rx_callsign(fuenf, fuenf->rx_callsign);
|
||||
fuenf->rx_function_count = 0; /* must reset, so we can detect timeout */
|
||||
fuenf->rx_state = RX_STATE_WAIT_SIGNAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* tones are not decoded here */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* receive tones and decode */
|
||||
static void tone_decode(fuenf_t *fuenf, sample_t *samples, int length)
|
||||
{
|
||||
double levels[DSP_NUM_TONES];
|
||||
int tone1 = -1, tone2 = -1;
|
||||
enum fuenf_funktion funktion = 0;
|
||||
int i;
|
||||
|
||||
/* filter tones */
|
||||
audio_goertzel(fuenf->rx_tone_goertzel, samples, length, 0, levels, DSP_NUM_TONES);
|
||||
for (i = 0; i < DSP_NUM_TONES; i++)
|
||||
fuenf->rx_tone_levels[i] = levels[i] / TONE_LEVEL;
|
||||
|
||||
/* find two frequencies */
|
||||
for (i = 0; i < DSP_NUM_TONES; i++) {
|
||||
if (fuenf->rx_tone_levels[i] < RX_MIN_LEVEL)
|
||||
continue;
|
||||
/* accpet only two ones */
|
||||
if (tone1 < 0)
|
||||
tone1 = i;
|
||||
else if (tone2 < 0)
|
||||
tone2 = i;
|
||||
else {
|
||||
/* abort, if more than two tones */
|
||||
tone1 = -1;
|
||||
tone2 = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if exactly two tones */
|
||||
if (tone2 >= 0) {
|
||||
/* select function from signal */
|
||||
for (i = 0; i < DSP_NUM_SIGNALS; i++) {
|
||||
if (tone1 == signals[i].tone1
|
||||
&& tone2 == signals[i].tone2) {
|
||||
funktion = signals[i].funktion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fuenf->rx_function_count += length;
|
||||
|
||||
/* state machine to detect two tones */
|
||||
switch (fuenf->rx_state) {
|
||||
case RX_STATE_WAIT_SIGNAL:
|
||||
/* wait for signal */
|
||||
if (!funktion) {
|
||||
if (fuenf->rx_function_count >= RX_WAIT_TONE_MAX) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "There is no double tone, waiting for next transmission.\n");
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* store signal */
|
||||
fuenf->rx_function = funktion;
|
||||
fuenf->rx_function_count = 0;
|
||||
fuenf->rx_state = RX_STATE_SIGNAL;
|
||||
break;
|
||||
case RX_STATE_SIGNAL:
|
||||
/* if signal ceases too early */
|
||||
if (funktion != fuenf->rx_function) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Signal tones ceased to early, waiting for next transmission.\n");
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
if (fuenf->rx_function_count >= RX_LEN_TONE_MIN) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected tones %.0f+%.0f Hz (amplitude = %.0f%%+%.0f%%)\n", tone_freq[tone1], tone_freq[tone2], fuenf->rx_tone_levels[tone1] * 100.0, fuenf->rx_tone_levels[tone2] * 100.0);
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Signal tones detected, done, waiting for next transmission.\n");
|
||||
fuenf_rx_function(fuenf, fuenf->rx_function);
|
||||
fuenf->rx_state = RX_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* digits are not decoded here */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process received audio stream from radio unit. */
|
||||
void sender_receive(sender_t *sender, sample_t *samples, int length, double __attribute__((unused)) rf_level_db)
|
||||
{
|
||||
fuenf_t *fuenf = (fuenf_t *) sender;
|
||||
|
||||
if (fuenf->rx) {
|
||||
sample_t down[length];
|
||||
int count, i;
|
||||
|
||||
/* downsample */
|
||||
memcpy(down, samples, sizeof(down)); // copy, so audio will not be corrupted at loopback
|
||||
count = samplerate_downsample(&fuenf->sender.srstate, down, length);
|
||||
|
||||
/* decode digit */
|
||||
digit_decode(fuenf, down, count);
|
||||
|
||||
/* decode tone */
|
||||
for (i = 0; i < count; i++) {
|
||||
/* fill buffer and decode when full */
|
||||
fuenf->rx_tone_filter_spl[fuenf->rx_tone_filter_pos] = down[i];
|
||||
if (++fuenf->rx_tone_filter_pos == fuenf->rx_tone_filter_size) {
|
||||
tone_decode(fuenf, fuenf->rx_tone_filter_spl, fuenf->rx_tone_filter_size);
|
||||
fuenf->rx_tone_filter_pos = 0;
|
||||
}
|
||||
}
|
||||
/* display levels */
|
||||
for (i = 0; i < DSP_NUM_TONES; i++)
|
||||
display_measurements_update(fuenf->dmp_tone_levels[i], fuenf->rx_tone_levels[i] * 100.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* set sequence to send */
|
||||
int dsp_setup(fuenf_t *fuenf, const char *rufzeichen, enum fuenf_funktion funktion)
|
||||
{
|
||||
tone_seq_t *seq = fuenf->tx_seq;
|
||||
int index = 0, tone_index;
|
||||
int i;
|
||||
|
||||
fuenf->tx_seq_length = 0;
|
||||
|
||||
if (strlen(rufzeichen) != 5) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Given call sign has invalid length.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Generating sequence for call sign '%s' and function code '%d'.\n", rufzeichen, funktion);
|
||||
|
||||
/* add preamble */
|
||||
seq[index].phasestep1 = 0;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_PREAMBLE;
|
||||
index++;
|
||||
|
||||
/* add tones */
|
||||
tone_index = index;
|
||||
for (i = 0; rufzeichen[i]; i++) {
|
||||
if (rufzeichen[i] < '0' || rufzeichen[i] > '9') {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Given call sign has invalid digit '%c'.\n", rufzeichen[i]);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (rufzeichen[i] == '0')
|
||||
seq[index].phasestep1 = 2.0 * M_PI * digit_freq[9] * fuenf->sample_duration;
|
||||
else
|
||||
seq[index].phasestep1 = 2.0 * M_PI * digit_freq[rufzeichen[i] - '1'] * fuenf->sample_duration;
|
||||
/* use repeat digit, if two subsequent digits are the same */
|
||||
if (i > 0 && seq[index - 1].phasestep1 == seq[index].phasestep1) {
|
||||
seq[index].phasestep1 = 2.0 * M_PI * digit_freq[REPEAT_DIGIT] * fuenf->sample_duration;
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, " -> Adding digit '%c' as tone with %.0f Hz.\n", rufzeichen[i], digit_freq[REPEAT_DIGIT]);
|
||||
} else
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, " -> Adding digit '%c' as tone with %.0f Hz.\n", rufzeichen[i], digit_freq[rufzeichen[i] - '0']);
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_DIGIT;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (funktion != FUENF_FUNKTION_TURBO) {
|
||||
/* add pause */
|
||||
seq[index].phasestep1 = 0;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_PAUSE;
|
||||
index++;
|
||||
|
||||
/* add tones (again) */
|
||||
for (i = 0; rufzeichen[i]; i++) {
|
||||
seq[index].phasestep1 = seq[tone_index + i].phasestep1;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_DIGIT;
|
||||
index++;
|
||||
}
|
||||
|
||||
/* add (second) pause */
|
||||
seq[index].phasestep1 = 0;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_PAUSE;
|
||||
index++;
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
if (funktion == FUENF_FUNKTION_RUF) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, " -> Adding call signal of %.0f Hz.\n", digit_freq[REPEAT_DIGIT]);
|
||||
for (i = 0; i < TX_NUM_KANAL; i++) {
|
||||
/* add tone (double volume) */
|
||||
seq[index].phasestep1 = 2.0 * M_PI * digit_freq[REPEAT_DIGIT] * fuenf->sample_duration;
|
||||
seq[index].phasestep2 = 2.0 * M_PI * digit_freq[REPEAT_DIGIT] * fuenf->sample_duration;
|
||||
seq[index].duration = TX_LEN_KANAL;
|
||||
index++;
|
||||
|
||||
/* add pause after tone */
|
||||
if (i < TX_NUM_KANAL - 1) {
|
||||
seq[index].phasestep1 = 0;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_KANAL_PAUSE;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/* add postamble */
|
||||
seq[index].phasestep1 = 0;
|
||||
seq[index].phasestep2 = 0;
|
||||
seq[index].duration = TX_LEN_POSTAMBLE;
|
||||
index++;
|
||||
} else
|
||||
if (funktion != FUENF_FUNKTION_TURBO) {
|
||||
/* add signal */
|
||||
for (i = 0; i < DSP_NUM_SIGNALS; i++) {
|
||||
if (signals[i].funktion == funktion)
|
||||
break;
|
||||
}
|
||||
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, " -> Adding call signal of %.0f Hz and %.0f Hz.\n", tone_freq[signals[i].tone1], tone_freq[signals[i].tone2]);
|
||||
seq[index].phasestep1 = 2.0 * M_PI * tone_freq[signals[i].tone1] * fuenf->sample_duration;
|
||||
seq[index].phasestep2 = 2.0 * M_PI * tone_freq[signals[i].tone2] * fuenf->sample_duration;
|
||||
seq[index].duration = TX_LEN_SIGNAL;
|
||||
index++;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* check array overflow, if it did not already crashed before */
|
||||
if (index > (int)(sizeof(fuenf->tx_seq) / sizeof(fuenf->tx_seq[0]))) {
|
||||
PDEBUG_CHAN(DDSP, DEBUG_ERROR, "Array size of tx_seq too small, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
fuenf->tx_funktion = funktion;
|
||||
fuenf->tx_seq_length = index;
|
||||
fuenf->tx_seq_index = 0;
|
||||
fuenf->tx_count = 0.0;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/* transmit call tone or pause, return 0, if no sequence */
|
||||
static int encode(fuenf_t *fuenf, sample_t *samples, int length)
|
||||
{
|
||||
tone_seq_t *seq;
|
||||
int count = 0;
|
||||
double value;
|
||||
|
||||
/* no sequence */
|
||||
if (!fuenf->tx_seq_length)
|
||||
return 0;
|
||||
|
||||
seq = &fuenf->tx_seq[fuenf->tx_seq_index];
|
||||
|
||||
/* generate wave */
|
||||
while (count < length && fuenf->tx_count < seq->duration) {
|
||||
value = 0;
|
||||
/* reset phase when not sending sine wave */
|
||||
if (seq->phasestep1) {
|
||||
value += sin(fuenf->tx_phase1);
|
||||
fuenf->tx_phase1 += seq->phasestep1;
|
||||
} else
|
||||
fuenf->tx_phase1 = 0.0;
|
||||
if (seq->phasestep2) {
|
||||
value += sin(fuenf->tx_phase2);
|
||||
fuenf->tx_phase2 += seq->phasestep2;
|
||||
} else
|
||||
fuenf->tx_phase2 = 0.0;
|
||||
fuenf->tx_count += fuenf->sample_duration;
|
||||
*samples++ = value * TONE_LEVEL;
|
||||
count++;
|
||||
}
|
||||
|
||||
/* transition to next segment */
|
||||
if (fuenf->tx_count >= seq->duration) {
|
||||
fuenf->tx_count -= seq->duration;
|
||||
if (++fuenf->tx_seq_index == fuenf->tx_seq_length) {
|
||||
fuenf->tx_seq_length = 0;
|
||||
fuenf_tx_done(fuenf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Provide stream of audio toward radio unit */
|
||||
void sender_send(sender_t *sender, sample_t *samples, uint8_t *power, int length)
|
||||
{
|
||||
fuenf_t *fuenf = (fuenf_t *) sender;
|
||||
sample_t *orig_samples = samples;
|
||||
int orig_length = length;
|
||||
int count;
|
||||
sample_t *spl;
|
||||
int pos;
|
||||
int i;
|
||||
|
||||
/* speak through */
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE && fuenf->callref) {
|
||||
jitter_load(&fuenf->sender.dejitter, samples, length);
|
||||
memset(power, 1, length);
|
||||
} else {
|
||||
/* send if something has to be sent. else turn transmitter off */
|
||||
while ((count = encode(fuenf, samples, length))) {
|
||||
memset(power, 1, count);
|
||||
samples += count;
|
||||
power += count;
|
||||
length -= count;
|
||||
}
|
||||
if (length) {
|
||||
memset(samples, 0, sizeof(samples) * length);
|
||||
memset(power, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
/* Also forward audio to network (call process). */
|
||||
if (fuenf->callref) {
|
||||
sample_t copy_samples[orig_length];
|
||||
// should we always echo back what we talk through???
|
||||
#if 0
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE)
|
||||
memset(copy_samples, 0, sizeof(copy_samples));
|
||||
else
|
||||
#endif
|
||||
memcpy(copy_samples, orig_samples, sizeof(copy_samples));
|
||||
count = samplerate_downsample(&fuenf->sender.srstate, copy_samples, orig_length);
|
||||
spl = fuenf->sender.rxbuf;
|
||||
pos = fuenf->sender.rxbuf_pos;
|
||||
for (i = 0; i < count; i++) {
|
||||
spl[pos++] = copy_samples[i];
|
||||
if (pos == 160) {
|
||||
call_up_audio(fuenf->callref, spl, 160);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
fuenf->sender.rxbuf_pos = pos;
|
||||
} else
|
||||
fuenf->sender.rxbuf_pos = 0;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
|
||||
int dsp_init_sender(fuenf_t *fuenf, int samplerate, double max_deviation, double signal_deviation);
|
||||
void dsp_cleanup_sender(fuenf_t *fuenf);
|
||||
int dsp_setup(fuenf_t *fuenf, const char *callsign, enum fuenf_funktion funktion);
|
||||
|
@ -0,0 +1,414 @@
|
||||
/* 5-Ton-Folge call processing
|
||||
*
|
||||
* (C) 2021 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 fuenf->sender.kanal
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libmobile/call.h"
|
||||
#include "../libmobile/cause.h"
|
||||
#include "../libosmocc/message.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "fuenf.h"
|
||||
#include "dsp.h"
|
||||
|
||||
void bos_list_channels(void)
|
||||
{
|
||||
printf("Channels\tBand\n");
|
||||
printf("------------------------\n");
|
||||
printf("101 - 125\t2 Meter\n");
|
||||
printf(" 1 - 92\t2 Meter\n");
|
||||
printf("347 - 509\t4 Meter\n");
|
||||
printf("-> Give channel number or any frequency in MHz (using a dot, e.g. '169.810').\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Convert channel to frequency */
|
||||
double bos_kanal2freq(const char *kanal)
|
||||
{
|
||||
int k;
|
||||
|
||||
if (strchr(kanal, '.'))
|
||||
return atof(kanal) * 1e6;
|
||||
|
||||
k = atoi(kanal);
|
||||
|
||||
if (k >= 101 && k <= 125)
|
||||
return 169.810e6 + 20e3 * (k - 101);
|
||||
|
||||
if (k >= 1 && k <= 92)
|
||||
return 172.160e6 + 20e3 * (k - 1);
|
||||
|
||||
if (k >= 347 && k <= 509)
|
||||
return 84.015e6 + 20e3 * (k - 347);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/* Convert frequency to channel, if possible */
|
||||
const char *bos_freq2kanal(const char *freq)
|
||||
{
|
||||
double f;
|
||||
char kanal[8];
|
||||
|
||||
if (!strchr(freq, '.'))
|
||||
return options_strdup(freq);
|
||||
|
||||
f = atof(freq) * 1e6;
|
||||
|
||||
if (f >= 169.810e6 && f <= 170.290e6 && fmod(f - 169.810e6, 20e3) == 0.0) {
|
||||
sprintf(kanal, "%.0f", (f - 169.810e6) / 20e3 + 101);
|
||||
return options_strdup(kanal);
|
||||
}
|
||||
|
||||
if (f >= 172.160e6 && f <= 173.980e6 && fmod(f - 172.160e6, 20e3) == 0.0) {
|
||||
sprintf(kanal, "%.0f", (f - 172.160e6) / 20e3 + 1);
|
||||
return options_strdup(kanal);
|
||||
}
|
||||
|
||||
if (f >= 84.015e6 && f <= 87.255e6 && fmod(f - 84.015e6, 20e3) == 0.0) {
|
||||
sprintf(kanal, "%.0f", (f - 84.015e6) / 20e3 + 347);
|
||||
return options_strdup(kanal);
|
||||
}
|
||||
|
||||
return options_strdup(freq);
|
||||
}
|
||||
|
||||
const char *fuenf_state_name[] = {
|
||||
"IDLE",
|
||||
"RUF",
|
||||
"DURCHSAGE",
|
||||
};
|
||||
|
||||
const char *fuenf_funktion_name[8] = {
|
||||
"Ruf",
|
||||
"Feueralarm",
|
||||
"Probealarm",
|
||||
"Warnung der Befoelkerung",
|
||||
"ABC-Alarm",
|
||||
"Entwarnung",
|
||||
"Katastrophenalarm",
|
||||
"Turbo-Scanner",
|
||||
};
|
||||
|
||||
/* check if number is a valid pager ID */
|
||||
const char *bos_number_valid(const char *number)
|
||||
{
|
||||
/* assume that the number has valid length(s) and digits */
|
||||
|
||||
if (number[5] && (number[5] < '0' || number[5] > '6'))
|
||||
return "Illegal 'Sirenenalarm' digit #6 (Use 1..6 only)";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fuenf_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fuenf_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void fuenf_display_status(void)
|
||||
{
|
||||
sender_t *sender;
|
||||
fuenf_t *fuenf;
|
||||
|
||||
display_status_start();
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
fuenf = (fuenf_t *) sender;
|
||||
display_status_channel(fuenf->sender.kanal, NULL, fuenf_state_name[fuenf->state]);
|
||||
}
|
||||
display_status_end();
|
||||
}
|
||||
|
||||
void fuenf_new_state(fuenf_t *fuenf, enum fuenf_state new_state)
|
||||
{
|
||||
if (fuenf->state == new_state)
|
||||
return;
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_DEBUG, "State change: %s -> %s\n", fuenf_state_name[fuenf->state], fuenf_state_name[new_state]);
|
||||
fuenf->state = new_state;
|
||||
fuenf_display_status();
|
||||
}
|
||||
|
||||
static int fuenf_scan_or_loopback(fuenf_t *fuenf)
|
||||
{
|
||||
char rufzeichen[16];
|
||||
|
||||
if (fuenf->scan_from < fuenf->scan_to) {
|
||||
sprintf(rufzeichen, "%05d", fuenf->scan_from++);
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_NOTICE, "Transmitting ID '%s'.\n", rufzeichen);
|
||||
dsp_setup(fuenf, rufzeichen, fuenf->default_funktion);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (fuenf->sender.loopback) {
|
||||
PDEBUG(DFUENF, DEBUG_INFO, "Sending 5-Ton-Ruf for loopback test.\n");
|
||||
dsp_setup(fuenf, "10357", FUENF_FUNKTION_FEUER);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int fuenf_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int tx, int rx, double max_deviation, double signal_deviation, enum fuenf_funktion funktion, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback)
|
||||
{
|
||||
fuenf_t *fuenf;
|
||||
int rc;
|
||||
|
||||
fuenf = calloc(1, sizeof(*fuenf));
|
||||
if (!fuenf) {
|
||||
PDEBUG(DFUENF, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
PDEBUG(DFUENF, DEBUG_DEBUG, "Creating '5-Ton-Folge' instance for 'Kanal' = %s (sample rate %d).\n", kanal, samplerate);
|
||||
|
||||
/* init general part of transceiver */
|
||||
rc = sender_create(&fuenf->sender, kanal, frequency, frequency, device, use_sdr, samplerate, rx_gain, tx_gain, 0, 0, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback, PAGING_SIGNAL_NONE);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DFUENF, DEBUG_ERROR, "Failed to init transceiver process!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* init audio processing */
|
||||
rc = dsp_init_sender(fuenf, samplerate, max_deviation, signal_deviation);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DFUENF, DEBUG_ERROR, "Failed to init audio processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
fuenf->tx = tx;
|
||||
fuenf->rx = rx;
|
||||
fuenf->default_funktion = funktion;
|
||||
fuenf->scan_from = scan_from;
|
||||
fuenf->scan_to = scan_to;
|
||||
|
||||
fuenf_display_status();
|
||||
|
||||
PDEBUG(DFUENF, DEBUG_NOTICE, "Created 'Kanal' %s\n", kanal);
|
||||
|
||||
/* start scanning, if enabled, otherwise send loopback sequence, if enabled */
|
||||
fuenf_scan_or_loopback(fuenf);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
fuenf_destroy(&fuenf->sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void fuenf_destroy(sender_t *sender)
|
||||
{
|
||||
fuenf_t *fuenf = (fuenf_t *) sender;
|
||||
|
||||
PDEBUG(DFUENF, DEBUG_DEBUG, "Destroying '5-Ton-Folge' instance for 'Kanal' = %s.\n", sender->kanal);
|
||||
|
||||
dsp_cleanup_sender(fuenf);
|
||||
sender_destroy(&fuenf->sender);
|
||||
free(fuenf);
|
||||
}
|
||||
|
||||
/* call sign was transmitted */
|
||||
void fuenf_tx_done(fuenf_t *fuenf)
|
||||
{
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Done sending 5-Ton-Ruf.\n");
|
||||
|
||||
/* start scanning, if enabled, otherwise send loopback sequence, if enabled */
|
||||
if (fuenf_scan_or_loopback(fuenf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* go talker state */
|
||||
if (fuenf->callref && fuenf->tx_funktion == FUENF_FUNKTION_RUF) {
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Caller may talk now.\n");
|
||||
fuenf_new_state(fuenf, FUENF_STATE_DURCHSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* go idle */
|
||||
fuenf_new_state(fuenf, FUENF_STATE_IDLE);
|
||||
if (fuenf->callref) {
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Releasing call toward network.\n");
|
||||
call_up_release(fuenf->callref, CAUSE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
void fuenf_rx_callsign(fuenf_t *fuenf, const char *callsign)
|
||||
{
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Received 5-Ton-Ruf with call sign '%s'.\n", callsign);
|
||||
}
|
||||
|
||||
void fuenf_rx_function(fuenf_t *fuenf, enum fuenf_funktion funktion)
|
||||
{
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Received function '%s'.\n", fuenf_funktion_name[funktion]);
|
||||
}
|
||||
|
||||
void call_down_clock(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* Call control starts call towards transmitter. */
|
||||
int call_down_setup(int callref, const char __attribute__((unused)) *caller_id, enum number_type __attribute__((unused)) caller_type, const char *dialing)
|
||||
{
|
||||
char channel = '\0';
|
||||
sender_t *sender;
|
||||
fuenf_t *fuenf;
|
||||
char rufzeichen[6];
|
||||
enum fuenf_funktion funktion;
|
||||
|
||||
/* find transmitter */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
/* skip channels that are different than requested */
|
||||
if (channel && sender->kanal[0] != channel)
|
||||
continue;
|
||||
fuenf = (fuenf_t *) sender;
|
||||
if (fuenf->state != FUENF_STATE_IDLE)
|
||||
continue;
|
||||
/* check if base station cannot transmit */
|
||||
if (!fuenf->tx)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
if (channel)
|
||||
PDEBUG(DFUENF, DEBUG_NOTICE, "Cannot page, because given station not available, rejecting!\n");
|
||||
else
|
||||
PDEBUG(DFUENF, DEBUG_NOTICE, "Cannot page, no trasmitting station idle, rejecting!\n");
|
||||
return -CAUSE_NOCHANNEL;
|
||||
}
|
||||
|
||||
strncpy(rufzeichen, dialing, 5);
|
||||
rufzeichen[5] = '\0';
|
||||
switch (dialing[5]) {
|
||||
case '0':
|
||||
funktion = FUENF_FUNKTION_RUF;
|
||||
break;
|
||||
case '1':
|
||||
funktion = FUENF_FUNKTION_FEUER;
|
||||
break;
|
||||
case '2':
|
||||
funktion = FUENF_FUNKTION_PROBE;
|
||||
break;
|
||||
case '3':
|
||||
funktion = FUENF_FUNKTION_WARNUNG;
|
||||
break;
|
||||
case '4':
|
||||
funktion = FUENF_FUNKTION_ABC;
|
||||
break;
|
||||
case '5':
|
||||
funktion = FUENF_FUNKTION_ENTWARNUNG;
|
||||
break;
|
||||
case '6':
|
||||
funktion = FUENF_FUNKTION_KATASTROPHE;
|
||||
break;
|
||||
case '\0':
|
||||
funktion = fuenf->default_funktion;
|
||||
break;
|
||||
default:
|
||||
return -CAUSE_INVALNUMBER;
|
||||
}
|
||||
|
||||
PDEBUG_CHAN(DFUENF, DEBUG_INFO, "Sending 5-Ton-Ruf with call sign '%s' and function '%s'.\n", rufzeichen, fuenf_funktion_name[funktion]);
|
||||
|
||||
dsp_setup(fuenf, rufzeichen, funktion);
|
||||
|
||||
fuenf_new_state(fuenf, FUENF_STATE_RUF);
|
||||
fuenf->callref = callref;
|
||||
/* must answer to hear paging tones. */
|
||||
call_up_answer(fuenf->callref, "");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void call_down_answer(int __attribute__((unused)) callref)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void _release(int __attribute__((unused)) callref, int __attribute__((unused)) cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
fuenf_t *fuenf;
|
||||
|
||||
PDEBUG(DFUENF, DEBUG_INFO, "Call has been disconnected by network.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
fuenf = (fuenf_t *) sender;
|
||||
if (fuenf->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
|
||||
/* don't send release, because caller already released */
|
||||
return;
|
||||
}
|
||||
|
||||
/* remove call. go idle, if talking */
|
||||
fuenf->callref = 0;
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE)
|
||||
fuenf_new_state(fuenf, FUENF_STATE_IDLE);
|
||||
}
|
||||
|
||||
void call_down_disconnect(int callref, int cause)
|
||||
{
|
||||
_release(callref, cause);
|
||||
|
||||
call_up_release(callref, cause);
|
||||
}
|
||||
|
||||
/* Call control releases call toward mobile station. */
|
||||
void call_down_release(int callref, int cause)
|
||||
{
|
||||
_release(callref, cause);
|
||||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_down_audio(int callref, sample_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
fuenf_t *fuenf;
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
fuenf = (fuenf_t *) sender;
|
||||
if (fuenf->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender)
|
||||
return;
|
||||
|
||||
if (fuenf->state == FUENF_STATE_DURCHSAGE) {
|
||||
sample_t up[(int)((double)count * fuenf->sender.srstate.factor + 0.5) + 10];
|
||||
count = samplerate_upsample(&fuenf->sender.srstate, samples, count, up);
|
||||
jitter_save(&fuenf->sender.dejitter, up, count);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_info(void) {}
|
||||
|
@ -0,0 +1,100 @@
|
||||
#include "../libmobile/sender.h"
|
||||
#include "../libgoertzel/goertzel.h"
|
||||
#include "../libfm/fm.h"
|
||||
|
||||
enum fuenf_state {
|
||||
FUENF_STATE_IDLE = 0,
|
||||
FUENF_STATE_RUF,
|
||||
FUENF_STATE_DURCHSAGE,
|
||||
};
|
||||
|
||||
extern const char *fuenf_funktion_name[8];
|
||||
|
||||
enum fuenf_funktion {
|
||||
FUENF_FUNKTION_RUF = 0,
|
||||
FUENF_FUNKTION_FEUER,
|
||||
FUENF_FUNKTION_PROBE,
|
||||
FUENF_FUNKTION_WARNUNG,
|
||||
FUENF_FUNKTION_ABC,
|
||||
FUENF_FUNKTION_ENTWARNUNG,
|
||||
FUENF_FUNKTION_KATASTROPHE,
|
||||
FUENF_FUNKTION_TURBO, /* used for turbo scanning, where only pause and 5 tones are sent */
|
||||
};
|
||||
|
||||
enum rx_state {
|
||||
RX_STATE_RESET = 0, /* wait for silence (after init, double tone or error) */
|
||||
RX_STATE_IDLE, /* receive silence, wait for digit */
|
||||
RX_STATE_DIGIT, /* wait for end of digit (next digit or silence) */
|
||||
RX_STATE_WAIT_SIGNAL, /* wait for double tone (up to 6 sec) */
|
||||
RX_STATE_SIGNAL, /* receive double tone and wait for minimum length (2 sec) */
|
||||
};
|
||||
|
||||
/* definition for tone sequence */
|
||||
typedef struct tone_seq {
|
||||
double phasestep1, phasestep2;
|
||||
double duration;
|
||||
} tone_seq_t;
|
||||
|
||||
#define DSP_NUM_DIGITS 11
|
||||
#define DSP_NUM_TONES 4
|
||||
|
||||
/* instance of pocsag transmitter/receiver */
|
||||
typedef struct fuenf {
|
||||
sender_t sender;
|
||||
|
||||
/* system info */
|
||||
int tx; /* can transmit */
|
||||
int rx; /* can receive */
|
||||
enum fuenf_funktion default_funktion; /* default function, if not given by caller */
|
||||
|
||||
/* tx states */
|
||||
enum fuenf_state state; /* state (idle, preamble, message) */
|
||||
uint32_t scan_from, scan_to; /* if not equal: scnning mode */
|
||||
|
||||
/* rx states */
|
||||
|
||||
/* calls */
|
||||
int callref;
|
||||
|
||||
/* TX dsp states */
|
||||
double sample_duration; /* length between samples in seconds */
|
||||
enum fuenf_funktion tx_funktion;
|
||||
tone_seq_t tx_seq[64]; /* transmit tone sequence */
|
||||
int tx_seq_length; /* size of current tone sequence */
|
||||
int tx_seq_index; /* current tone that is played */
|
||||
double tx_count; /* counts duration of current tone */
|
||||
double tx_phase1, tx_phase2; /* current phase of tone */
|
||||
|
||||
/* display measurements */
|
||||
dispmeasparam_t *dmp_digit_level;
|
||||
dispmeasparam_t *dmp_tone_levels[DSP_NUM_TONES];
|
||||
|
||||
/* RX dsp states */
|
||||
enum rx_state rx_state; /* current state of decoder */
|
||||
fm_demod_t rx_digit_demod; /* demodulator for frequency */
|
||||
iir_filter_t rx_digit_lp; /* low pass to filter the frequency result */
|
||||
int rx_digit_last; /* track if digit changes */
|
||||
int rx_digit_count; /* count samples after digit changes */
|
||||
goertzel_t rx_tone_goertzel[DSP_NUM_TONES]; /* rx filter */
|
||||
sample_t *rx_tone_filter_spl; /* buffer for rx filter */
|
||||
int rx_tone_filter_size; /* length of buffer, will affect bandwidth of filter */
|
||||
int rx_tone_filter_pos; /* samples in buffer */
|
||||
double rx_tone_levels[DSP_NUM_TONES]; /* last detected levels */
|
||||
char rx_callsign[6]; /* 5 digits + '\0' */
|
||||
int rx_callsign_count; /* number of (complete) digits received */
|
||||
enum fuenf_funktion rx_function; /* received signal */
|
||||
int rx_function_count; /* counts duration in samples */
|
||||
} fuenf_t;
|
||||
|
||||
void bos_list_channels(void);
|
||||
double bos_kanal2freq(const char *kanal);
|
||||
const char *bos_freq2kanal(const char *freq);
|
||||
const char *bos_number_valid(const char *number);
|
||||
int fuenf_init(void);
|
||||
void fuenf_exit(void);
|
||||
int fuenf_create(const char *kanal, double frequency, const char *device, int use_sdr, int samplerate, double rx_gain, double tx_gain, int tx, int rx, double max_deviation, double signal_deviation, enum fuenf_funktion funktion, uint32_t scan_from, uint32_t scan_to, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave, int loopback);
|
||||
void fuenf_destroy(sender_t *sender);
|
||||
void fuenf_tx_done(fuenf_t *fuenf);
|
||||
void fuenf_rx_callsign(fuenf_t *fuenf, const char *callsign);
|
||||
void fuenf_rx_function(fuenf_t *fuenf, enum fuenf_funktion funktion);
|
||||
|
@ -0,0 +1,24 @@
|
||||
#include <stdio.h>
|
||||
|
||||
const char *aaimage[] = {
|
||||
"@w",
|
||||
" @y______",
|
||||
" @y__--- ---__",
|
||||
" @W5-Ton-Folge @y<________________>",
|
||||
" @w/_ _\\",
|
||||
" ||",
|
||||
" _______________________________ ||",
|
||||
" | _____________________________ | ||",
|
||||
" | @W[][][][] @w| ||",
|
||||
" | @W[@y12345 F@w] @W(!) @w| ||",
|
||||
" | @W(1)(2)(3) (S) @w| ||",
|
||||
" | @W(4)(5)(6) (#)(#)(#)(#)(#)(#) @w| ||",
|
||||
" | @W(7)(8)(9) (#)(#)(#)(#)(#)(#) @w| ||",
|
||||
" | @R(A)@W(0)(L) @B(F)(P)(W)(A)(E)@R(A) @w| ||",
|
||||
" |_______________________________| ||",
|
||||
" ||",
|
||||
" @g. ..\\\\@w||@g//. .@w",
|
||||
"===============================================================================",
|
||||
NULL
|
||||
};
|
||||
|
@ -0,0 +1,269 @@
|
||||
/* 5-Ton-Folge main
|
||||
*
|
||||
* (C) 2021 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 <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libmobile/call.h"
|
||||
#include "../libmobile/main_mobile.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "../anetz/besetztton.h"
|
||||
#include "fuenf.h"
|
||||
#include "dsp.h"
|
||||
|
||||
static int tx = 0; /* we transmit */
|
||||
static int rx = 0; /* we receive */
|
||||
static double max_deviation = 4000;
|
||||
static double signal_deviation = 2400;
|
||||
static enum fuenf_funktion funktion = FUENF_FUNKTION_FEUER;
|
||||
static uint32_t scan_from = 0;
|
||||
static uint32_t scan_to = 0;
|
||||
|
||||
void print_help(const char *arg0)
|
||||
{
|
||||
main_mobile_print_help(arg0, "-k <kanal> | -k list");
|
||||
/* - - */
|
||||
printf(" -T --tx\n");
|
||||
printf(" Transmit Eurosignal on given channel, to page a receiver. (default)\n");
|
||||
printf(" -R --rx\n");
|
||||
printf(" Receive Eurosignal on given channel, so we are the receiver.\n");
|
||||
printf(" If none of the options -T nor -R is given, only transmitter is enabled.\n");
|
||||
printf(" -D --deviation <KHz>\n");
|
||||
printf(" Choose deviation of FM signal (default %.0f KHz).\n", signal_deviation / 1000.0);
|
||||
printf(" -F --funktion 0 | ruf | 1 | feuer | 2 | probe | 3 | warnung | 4 | abc\n");
|
||||
printf(" | 5 | entwarnung | 6 | katastrophe | 7 | turbo\n");
|
||||
printf(" Choose default function when 5 digit only number is dialed.\n");
|
||||
printf(" (default %d = %s)\n", funktion, fuenf_funktion_name[funktion]);
|
||||
printf(" -S --scan <from> <to>\n");
|
||||
printf(" Scan through given IDs once (no repetition). This can be useful to find\n");
|
||||
printf(" the call sign of a vintage receiver. Note that scanning all call signs\n");
|
||||
printf(" from 000000 through 99999 would take about 8.7 days.\n");
|
||||
printf(" If 'turbo' function is selected, only a single 5-tone sequence is sent\n");
|
||||
printf(" per call sign. A full scan would takte about 26.4 hours then.\n");
|
||||
main_mobile_print_station_id();
|
||||
main_mobile_print_hotkeys();
|
||||
}
|
||||
|
||||
static void add_options(void)
|
||||
{
|
||||
main_mobile_add_options();
|
||||
option_add('T', "tx", 0);
|
||||
option_add('R', "rx", 0);
|
||||
option_add('D', "deviation", 1);
|
||||
option_add('F', "funktion", 1);
|
||||
option_add('S', "scan", 2);
|
||||
}
|
||||
|
||||
static int handle_options(int short_option, int argi, char **argv)
|
||||
{
|
||||
switch (short_option) {
|
||||
case 'T':
|
||||
tx = 1;
|
||||
break;
|
||||
case 'R':
|
||||
rx = 1;
|
||||
break;
|
||||
case 'D':
|
||||
signal_deviation = atof(argv[argi]) * 1000.0;
|
||||
if (signal_deviation < 1000.0) {
|
||||
fprintf(stderr, "Given deviation is too low, use higher deviation.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (signal_deviation > max_deviation)
|
||||
max_deviation = signal_deviation;
|
||||
break;
|
||||
case 'F':
|
||||
switch (argv[argi][0]) {
|
||||
case '0':
|
||||
case 'r':
|
||||
case 'R':
|
||||
funktion = FUENF_FUNKTION_RUF;
|
||||
break;
|
||||
case '1':
|
||||
case 'f':
|
||||
case 'F':
|
||||
funktion = FUENF_FUNKTION_FEUER;
|
||||
break;
|
||||
case '2':
|
||||
case 'p':
|
||||
case 'P':
|
||||
funktion = FUENF_FUNKTION_PROBE;
|
||||
break;
|
||||
case '3':
|
||||
case 'w':
|
||||
case 'W':
|
||||
funktion = FUENF_FUNKTION_WARNUNG;
|
||||
break;
|
||||
case '4':
|
||||
case 'a':
|
||||
case 'A':
|
||||
funktion = FUENF_FUNKTION_ABC;
|
||||
break;
|
||||
case '5':
|
||||
case 'e':
|
||||
case 'E':
|
||||
funktion = FUENF_FUNKTION_ENTWARNUNG;
|
||||
break;
|
||||
case '6':
|
||||
case 'k':
|
||||
case 'K':
|
||||
funktion = FUENF_FUNKTION_KATASTROPHE;
|
||||
break;
|
||||
case '7':
|
||||
case 't':
|
||||
case 'T':
|
||||
funktion = FUENF_FUNKTION_TURBO;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Given 'Funktion' is invalid, use '-h' for help.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|