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);