forked from cellular-infrastructure/osmocom-analog
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]); |
||||