diff --git a/.gitignore b/.gitignore
index 383bec8..05d15bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ src/mpt1327/mpt1327
src/jolly/jollycom
src/eurosignal/eurosignal
src/pocsag/pocsag
+src/fuenf/5-ton-folge
src/tv/osmotv
src/radio/osmoradio
src/datenklo/datenklo
diff --git a/README b/README
index 7c5a379..474650f 100644
--- a/README
+++ b/README
@@ -16,6 +16,7 @@ generated simultaniously using SDR. Currently supported networks:
* IMTS / MTS ((Improved) Mobile Telephone Service)
* MPT1327 (Trunked Radio) aka known as 'Buendelfunk'
* Eurosignal (ERuRD paging service)
+ * 5-Ton-Folge (ZVEI 5 tone paging code for pager and siren trigger)
* JollyCom (Unofficial network, invented by the author)
* C-Netz BSC (Connecting to a C-Netz Base Station)
@@ -26,6 +27,7 @@ Additionally the following communication services are implemented:
* Analog Modem Emulation (AM7911)
* German classic 'Zeitansage' (talking clock)
* POCSAG transmitter / receiver
+ * 5-Ton-Folge + Sirenensteuerung
USE AT YOUR OWN RISK!
diff --git a/configure.ac b/configure.ac
index ddc29cc..e82f821 100644
--- a/configure.ac
+++ b/configure.ac
@@ -106,6 +106,7 @@ AC_OUTPUT(
src/jolly/Makefile
src/eurosignal/Makefile
src/pocsag/Makefile
+ src/fuenf/Makefile
src/tv/Makefile
src/radio/Makefile
src/datenklo/Makefile
diff --git a/docs/index.html b/docs/index.html
index a89074f..5e96f7a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -80,6 +80,10 @@ Please go to project's hompage at h
Thanx to Laf0rge, there is a mailing list: https://lists.osmocom.org/mailman/listinfo/osmocom-analog.
+
+If you have questions, then write to: jolly@eversberg.eu
+
+
General information:
@@ -126,6 +130,7 @@ Additional features:
Zeitansage (German talking clock)
C-Netz FuVSt (MSC to control a real base station)
POCSAG
+ 5-Ton-Ruf (firefighter's pagers and siren control)
diff --git a/docs/standard_r2000_by_jolly.pdf b/docs/standard_r2000_by_jolly.pdf
index ffb6870..713d702 100644
Binary files a/docs/standard_r2000_by_jolly.pdf and b/docs/standard_r2000_by_jolly.pdf differ
diff --git a/src/Makefile.am b/src/Makefile.am
index 200323e..665a6a8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,6 +56,7 @@ SUBDIRS += \
jolly \
eurosignal \
pocsag \
+ fuenf \
tv \
radio \
zeitansage \
diff --git a/src/fuenf/Makefile.am b/src/fuenf/Makefile.am
new file mode 100644
index 0000000..4c90762
--- /dev/null
+++ b/src/fuenf/Makefile.am
@@ -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
+
diff --git a/src/fuenf/dsp.c b/src/fuenf/dsp.c
new file mode 100644
index 0000000..b3f10f8
--- /dev/null
+++ b/src/fuenf/dsp.c
@@ -0,0 +1,646 @@
+/* selective call signal processing
+ *
+ * (C) 2019 by Andreas Eversberg
+ * 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 .
+ */
+
+#define CHAN fuenf->sender.kanal
+
+#include
+#include
+#include
+#include
+#include
+#include
+#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;
+
+}
+
diff --git a/src/fuenf/dsp.h b/src/fuenf/dsp.h
new file mode 100644
index 0000000..7ccb3e8
--- /dev/null
+++ b/src/fuenf/dsp.h
@@ -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);
+
diff --git a/src/fuenf/fuenf.c b/src/fuenf/fuenf.c
new file mode 100644
index 0000000..4a3eb90
--- /dev/null
+++ b/src/fuenf/fuenf.c
@@ -0,0 +1,414 @@
+/* 5-Ton-Folge call processing
+ *
+ * (C) 2021 by Andreas Eversberg
+ * 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 .
+ */
+
+#define CHAN fuenf->sender.kanal
+
+#include
+#include
+#include
+#include
+#include
+#include
+#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) {}
+
diff --git a/src/fuenf/fuenf.h b/src/fuenf/fuenf.h
new file mode 100644
index 0000000..cb335c7
--- /dev/null
+++ b/src/fuenf/fuenf.h
@@ -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);
+
diff --git a/src/fuenf/image.c b/src/fuenf/image.c
new file mode 100644
index 0000000..dcfaebc
--- /dev/null
+++ b/src/fuenf/image.c
@@ -0,0 +1,24 @@
+#include
+
+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
+};
+
diff --git a/src/fuenf/main.c b/src/fuenf/main.c
new file mode 100644
index 0000000..ab7fa1c
--- /dev/null
+++ b/src/fuenf/main.c
@@ -0,0 +1,269 @@
+/* 5-Ton-Folge main
+ *
+ * (C) 2021 by Andreas Eversberg
+ * 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 .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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 | -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 \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 \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;
+ case 'S':
+ scan_from = atoi(argv[argi++]);
+ if (scan_from > 99999) {
+ fprintf(stderr, "Given call sign to scan from is out of range!\n");
+ return -EINVAL;
+ }
+ scan_to = atoi(argv[argi++]) + 1;
+ if (scan_to > 99999) {
+ fprintf(stderr, "Given call sign to scan to is out of range!\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return main_mobile_handle_options(short_option, argi, argv);
+ }
+
+ return 1;
+}
+
+static const struct number_lengths number_lengths[] = {
+ { 5, "5-Ton-Folge" },
+ { 6, "5-Ton-Folge mit Ruf (0) oder Sirenenalarm (1..6)" },
+ { 0, NULL }
+};
+
+int main(int argc, char *argv[])
+{
+ int rc, argi;
+ const char *station_id = "";
+ int i;
+ const char *k;
+ double f;
+
+ /* BOS does not use emphasis, so disable it */
+ uses_emphasis = 0;
+
+ /* init common tones */
+ init_besetzton();
+
+ /* init mobile interface */
+ main_mobile_init("0123456789", number_lengths, NULL, bos_number_valid);
+
+ /* handle options / config file */
+ add_options();
+ rc = options_config_file(argc, argv, "~/.osmocom/analog/5-ton-folge.conf", handle_options);
+ if (rc < 0)
+ return 0;
+ argi = options_command_line(argc, argv, handle_options);
+ if (argi <= 0)
+ return argi;
+
+ if (argi < argc) {
+ station_id = argv[argi];
+ rc = main_mobile_number_ask(station_id, "station ID (RIC)");
+ if (rc)
+ return rc;
+ }
+
+ if (!num_kanal) {
+ printf("No channel is specified, Use '-k list' to get a list of all channels.\n\n");
+ print_help(argv[0]);
+ return 0;
+ }
+ if (!strcasecmp(kanal[0], "list")) {
+ bos_list_channels();
+ goto fail;
+ }
+ if (use_sdr) {
+ /* set device */
+ for (i = 0; i < num_kanal; i++)
+ dsp_device[i] = "sdr";
+ num_device = num_kanal;
+ }
+ if (num_kanal == 1 && num_device == 0)
+ num_device = 1; /* use default */
+ if (num_kanal != num_device) {
+ fprintf(stderr, "You need to specify as many sound devices as you have channels.\n");
+ exit(0);
+ }
+
+ /* TX is default */
+ if (!tx && !rx)
+ tx = 1;
+
+ /* TX & RX if loopback */
+ if (loopback)
+ tx = rx = 1;
+
+ /* inits */
+ fm_init(fast_math);
+ fuenf_init();
+
+ /* create transceiver instance */
+ for (i = 0; i < num_kanal; i++) {
+ k = bos_freq2kanal(kanal[i]);
+ f = bos_kanal2freq(k);
+ if (f == 0.0) {
+ printf("Invalid channel '%s', Use '-k list' to get a list of all channels.\n\n", k);
+ goto fail;
+ }
+ rc = fuenf_create(k, f, dsp_device[i], use_sdr, dsp_samplerate, rx_gain, tx_gain, tx, rx, max_deviation, signal_deviation, funktion, scan_from, scan_to, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave, loopback);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
+ goto fail;
+ }
+ printf("Base station ready, please tune transmitter (or receiver) to %.4f MHz\n", f / 1e6);
+ }
+
+ main_mobile_loop("5-ton-folge", &quit, NULL, station_id);
+
+fail:
+ /* destroy transceiver instance */
+ while(sender_head)
+ fuenf_destroy(sender_head);
+
+ /* exits */
+ fm_exit();
+ fuenf_exit();
+
+ options_free();
+
+ return 0;
+}
+
diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c
index 67ba00c..47dd37a 100755
--- a/src/libdebug/debug.c
+++ b/src/libdebug/debug.c
@@ -57,6 +57,7 @@ struct debug_cat {
{ "jollycom", "\033[1;34m" },
{ "eurosignal", "\033[1;34m" },
{ "pocsag", "\033[1;34m" },
+ { "5-ton-folge", "\033[1;34m" },
{ "frame", "\033[0;36m" },
{ "call", "\033[0;37m" },
{ "cc", "\033[1;32m" },
diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h
index ae16857..f7dd54b 100644
--- a/src/libdebug/debug.h
+++ b/src/libdebug/debug.h
@@ -19,38 +19,39 @@
#define DJOLLY 12
#define DEURO 13
#define DPOCSAG 14
-#define DFRAME 15
-#define DCALL 16
-#define DCC 17
-#define DDB 18
-#define DTRANS 19
-#define DDMS 20
-#define DSMS 21
-#define DSDR 22
-#define DUHD 23
-#define DSOAPY 24
-#define DWAVE 25
-#define DRADIO 26
-#define DAM791X 27
-#define DUART 28
-#define DDEVICE 29
-#define DDATENKLO 30
-#define DZEIT 31
-#define DSIM1 32
-#define DSIM2 33
-#define DSIMI 34
-#define DSIM7 35
-#define DMTP2 36
-#define DMTP3 37
-#define DMUP 38
-#define DROUTER 39
-#define DSTDERR 40
-#define DSS5 41
-#define DISDN 42
-#define DMISDN 43
-#define DDSS1 44
-#define DSIP 45
-#define DTEL 46
+#define DFUENF 15
+#define DFRAME 16
+#define DCALL 17
+#define DCC 18
+#define DDB 19
+#define DTRANS 20
+#define DDMS 21
+#define DSMS 22
+#define DSDR 23
+#define DUHD 24
+#define DSOAPY 25
+#define DWAVE 26
+#define DRADIO 27
+#define DAM791X 28
+#define DUART 29
+#define DDEVICE 30
+#define DDATENKLO 31
+#define DZEIT 32
+#define DSIM1 33
+#define DSIM2 34
+#define DSIMI 35
+#define DSIM7 36
+#define DMTP2 37
+#define DMTP3 38
+#define DMUP 39
+#define DROUTER 40
+#define DSTDERR 41
+#define DSS5 42
+#define DISDN 43
+#define DMISDN 44
+#define DDSS1 45
+#define DSIP 46
+#define DTEL 47
void get_win_size(int *w, int *h);