From 56f07a473d403a84afcf1e639d499eeacda0952a Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Thu, 15 Feb 2018 07:39:45 +0100 Subject: [PATCH] B-Netz: Add dial sequence generator to emulate call setup from mobile phone --- .gitignore | 1 + src/bnetz/Makefile.am | 26 ++- src/bnetz/dialer.c | 416 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 src/bnetz/dialer.c diff --git a/.gitignore b/.gitignore index b40c22b..23bbc3d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ src/libsample/libsample.a src/anetz/libgermanton.a src/anetz/anetz src/bnetz/bnetz +src/bnetz/bnetz-dialer src/cnetz/cnetz src/nmt/libdmssms.a src/nmt/nmt diff --git a/src/bnetz/Makefile.am b/src/bnetz/Makefile.am index e00c026..bc562e9 100644 --- a/src/bnetz/Makefile.am +++ b/src/bnetz/Makefile.am @@ -1,7 +1,8 @@ AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) bin_PROGRAMS = \ - bnetz + bnetz \ + bnetz-dialer bnetz_SOURCES = \ bnetz.c \ @@ -30,10 +31,29 @@ bnetz_LDADD = \ $(top_builddir)/src/libsample/libsample.a \ -lm +bnetz_dialer_SOURCES = \ + telegramm.c \ + dialer.c +bnetz_dialer_LDADD = \ + $(COMMON_LA) \ + $(top_builddir)/src/libdebug/libdebug.a \ + $(top_builddir)/src/libfsk/libfsk.a \ + $(top_builddir)/src/libfm/libfm.a \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libwave/libwave.a \ + $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsample/libsample.a \ + $(ALSA_LIBS) + -lm + if HAVE_ALSA bnetz_LDADD += \ $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) + +bnetz_dialer_LDADD += \ + $(top_builddir)/src/libsound/libsound.a \ + $(ALSA_LIBS) endif if HAVE_SDR @@ -44,3 +64,7 @@ bnetz_LDADD += \ $(SOAPY_LIBS) endif +if HAVE_ALSA +AM_CPPFLAGS += -DHAVE_ALSA +endif + diff --git a/src/bnetz/dialer.c b/src/bnetz/dialer.c new file mode 100644 index 0000000..a3a292d --- /dev/null +++ b/src/bnetz/dialer.c @@ -0,0 +1,416 @@ +/* B-Netz dialer + * + * (C) 2018 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 "../libsample/sample.h" +#include "../libfsk/fsk.h" +#include "../libwave/wave.h" +#include "../libdebug/debug.h" +#ifdef HAVE_ALSA +#include "../libsound/sound.h" +#endif +#include "telegramm.h" + +#define MAX_PAUSE 0.5 /* pause before and after dialing sequence */ +#define F0 2070.0 +#define F1 1950.0 +#define BIT_RATE 100.0 + +/* presets */ +char start_digit = 's'; +const char *station_id = "50993"; +const char *dialing; +const char *audiodev = "hw:0,0"; +int samplerate = 48000; +const char *write_tx_wave = NULL; +int latency = 50; + +/* states */ +enum tx_mode { + TX_MODE_SILENCE, + TX_MODE_FSK, + TX_MODE_DONE, +} tx_mode = TX_MODE_SILENCE; +int tx_silence_count = 0; +char funkwahl[128]; +int digit_pos = 0; +const char *tx_telegramm = NULL; +int tx_telegramm_pos = 0; +int latspl; + +/* instances */ +fsk_t fsk; +#ifdef HAVE_ALSA +void *audio = NULL; +#endif +wave_rec_t wave_tx_rec; + +/* dummy functions */ +int num_kanal = 1; /* only one channel used for debugging */ +void display_status_limit_scroll() {} +void *get_sender_by_empfangsfrequenz() { return NULL; } +void display_measurements_add() {} +void display_measurements_update() {} + +#define OPT_METERING 1000 +#define OPT_COIN_BOX 1001 + +static void print_help(const char *arg0) +{ + printf("Usage: %s [options] \n\n", arg0); + /* - - */ + printf("This program generates a dialing sequence to make a call via B-Netz base\n"); + printf("station using an amateur radio transceiver.\n"); + printf("Also it can write an audio file (wave) to be fed into B-Netz base station for\n"); + printf("showing how it simulates a B-Netz phone doing an outgoing call.\n\n"); + printf(" -h --help\n"); + printf(" This help\n"); + printf(" -i --station-id \n"); + printf(" 5 Digits of ID of mobile station (default = '%s')\n", station_id); +#ifdef HAVE_ALSA + printf(" -a --audio-device hw:,\n"); + printf(" Sound card and device number (default = '%s')\n", audiodev); +#endif + printf(" Don't set it for SDR!\n"); + printf(" -s --samplerate \n"); + printf(" Sample rate of sound device (default = '%d')\n", samplerate); + printf(" -w --write-tx-wave \n"); + printf(" Write audio to given wave file also.\n"); + printf(" -g --gebuehenimpuls\n"); + printf(" -g --metering\n"); + printf(" Indicate to base station that we have a charge meter on board.\n"); + printf(" This will allow the base station to send billing tones during call.\n"); + printf(" -m --muenztelefon\n"); + printf(" -m --coin-box\n"); + printf(" Indicate to base station that we are a pay phone. ('Muenztelefon')\n"); +} + +static int handle_options(int argc, char **argv) +{ + const char *optstring; + int skip_args = 0; + + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"station-id", 1, 0, 'i'}, + {"audio-device", 1, 0, 'a'}, + {"samplerate", 1, 0, 's'}, + {"write-tx-wave", 1, 0, 'w'}, + {"gebuehrenimpuls", 0, 0, 'g'}, + {"metering", 0, 0, OPT_METERING}, + {"muenztelefon", 0, 0, 'm'}, + {"coin-box", 0, 0, OPT_COIN_BOX}, + {0, 0, 0, 0}, + }; + + optstring = "hi:a:s:w:gm"; + + while (1) { + int option_index = 0, c; + + c = getopt_long(argc, argv, optstring, long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + case 'i': + station_id = strdup(optarg); + skip_args += 2; + break; + case 'a': + audiodev = strdup(optarg); + skip_args += 2; + break; + case 's': + samplerate = atoi(optarg); + skip_args += 2; + break; + case 'w': + write_tx_wave = strdup(optarg); + skip_args += 2; + break; + case 'g': + case OPT_METERING: + start_digit = 'S'; + skip_args += 1; + break; + case 'm': + case OPT_COIN_BOX: + start_digit = 'M'; + skip_args += 1; + break; + default: + break; + } + } + + return skip_args; +} + + +/* process next fsk bit. + * if the dial string terminats, change to SILENCE mode + */ +static int fsk_send_bit(void __attribute__((unused)) *inst) +{ + struct impulstelegramm *impulstelegramm; + + if (!tx_telegramm || tx_telegramm_pos == 16) { + switch (funkwahl[digit_pos]) { + case '\0': + PDEBUG(DBNETZ, DEBUG_INFO, "Done sending dialing sequence\n"); + tx_mode = TX_MODE_SILENCE; + tx_silence_count = 0; + return -1; + case 'w': + if (!tx_telegramm) + PDEBUG(DBNETZ, DEBUG_INFO, "Sending channel allocation tone ('Kanalbelegung')\n"); + tx_telegramm = "0000000000000000"; + tx_telegramm_pos = 0; + digit_pos++; + break; + default: + switch (funkwahl[digit_pos]) { + case 's': + PDEBUG(DBNETZ, DEBUG_INFO, "Sending start digit (no charging meater on board)\n"); + break; + case 'S': + PDEBUG(DBNETZ, DEBUG_INFO, "Sending start digit (with charging meater on board)\n"); + break; + case 'M': + PDEBUG(DBNETZ, DEBUG_INFO, "Sending start digit (Phone is a coin box.)\n"); + break; + case 'e': + PDEBUG(DBNETZ, DEBUG_INFO, "Sending stop digit\n"); + break; + default: + PDEBUG(DBNETZ, DEBUG_INFO, "Sending digit '%c'\n", funkwahl[digit_pos]); + } + impulstelegramm = bnetz_digit2telegramm(funkwahl[digit_pos]); + if (!impulstelegramm) { + PDEBUG(DBNETZ, DEBUG_ERROR, "Illegal digit '%c', please fix!\n", funkwahl[digit_pos]); + abort(); + } + tx_telegramm = impulstelegramm->sequence; + tx_telegramm_pos = 0; + digit_pos++; + } + } + + return tx_telegramm[tx_telegramm_pos++]; +} + +/* encode audio */ +static void encode_audio(sample_t *samples, uint8_t *power, int length) +{ + int count; + + memset(power, 1, length); + +again: + switch (tx_mode) { + case TX_MODE_SILENCE: + memset(samples, 0, length * sizeof(*samples)); + tx_silence_count += length; + if (tx_silence_count >= (int)((double)samplerate * MAX_PAUSE)) { + if (funkwahl[digit_pos]) + tx_mode = TX_MODE_FSK; + else + tx_mode = TX_MODE_DONE; + } + break; + case TX_MODE_FSK: + /* send FSK until it stops, then fill with silence */ + count = fsk_send(&fsk, samples, length, 0); + samples += count; + length -= count; + if (length) + goto again; + break; + default: + break; + } +} + +/* loop that gets audio from encoder and fowards it to sound card. + * alternatively a sound file is written. + */ +static void process_signal(void) +{ + sample_t buff[latspl], *samples[1] = { buff }; + uint8_t pbuff[latspl], *power[1] = { pbuff }; + int count; + int __attribute__((unused)) rc; + + while (tx_mode != TX_MODE_DONE) { +#ifdef HAVE_ALSA + count = sound_get_tosend(audio, latspl); +#else + count = samplerate / 1000; +#endif + if (count < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count); + break; + } + + /* get fsk / silence */ + encode_audio(samples[0], power[0], count); + + /* write wave, if open */ + if (wave_tx_rec.fp) + wave_write(&wave_tx_rec, samples, count); + +#ifdef HAVE_ALSA + /* write audio */ + rc = sound_write(audio, samples, power, count, NULL, NULL, 1); + if (rc < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc); + break; + } +#endif + + /* sleep a while */ + usleep(1000); + } +} + +int main(int argc, char *argv[]) +{ + const char *arg0 = argv[0]; + int skip_args; + int i; + int rc; + + /* init */ + bnetz_init_telegramm(); + memset(&fsk, 0, sizeof(fsk)); + + /* latency of send buffer in samples */ + latspl = samplerate * latency / 1000; + + skip_args = handle_options(argc, argv); + argc -= skip_args; + argv += skip_args; + + if (argc <= 1) { + printf("No phone number given!\n\n"); + print_help(arg0); + goto exit; + } + + /* check for valid station ID */ + if (strlen(station_id) != 5) { + printf("Given station ID '%s' has too many digits!\n", station_id); + goto exit; + } + for (i = 0; station_id[i]; i++) { + if (station_id[i] < '0' || station_id[i] > '9') { + printf("Given station ID '%s' has invalid digits!\n", station_id); + goto exit; + } + } + + /* check for valid phone number */ + dialing = argv[1]; + if (strlen(dialing) < 4) { + printf("Given phone number '%s' has too few digits! (less than minimum of 4 digits)\n", dialing); + goto exit; + } + if (strlen(dialing) > 14) { + printf("Given phone number '%s' has too many digits! (more than allowed 14 digits)\n", dialing); + goto exit; + } + for (i = 0; dialing[i]; i++) { + if (dialing[i] < '0' || dialing[i] > '9') { + printf("Given phone number '%s' has invalid digits!\n", dialing); + goto exit; + } + } + if (dialing[0] != '0') { + printf("Given phone number '%s' does not start with 0!\n", dialing); + goto exit; + } + + /* dial string: 640 ms pause, 640 ms 2070 HZ, 2 * {start, station ID, number, stop}, 640 ms pause */ + sprintf(funkwahl, "wwww%c%s%se%c%s%se", start_digit, station_id, dialing + 1, start_digit, station_id, dialing + 1); + + /* init fsk */ + if (fsk_init(&fsk, NULL, fsk_send_bit, NULL, samplerate, BIT_RATE, F0, F1, 1.0, 0, 0) < 0) { + PDEBUG(DDSP, DEBUG_ERROR, "FSK init failed!\n"); + goto exit; + } + +#ifdef HAVE_ALSA + /* init sound */ + audio = sound_open(audiodev, NULL, NULL, 1, 0.0, samplerate, latspl, 1.0, 4000.0); + if (!audio) { + PDEBUG(DBNETZ, DEBUG_ERROR, "No sound device!\n"); + goto exit; + } +#endif + + /* open wave */ + if (write_tx_wave) { + rc = wave_create_record(&wave_tx_rec, write_tx_wave, samplerate, 1, 1.0); + if (rc < 0) { + PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n"); + goto exit; + } + } +#ifndef HAVE_ALSA + else { + PDEBUG(DBNETZ, DEBUG_ERROR, "No sound support compiled in, so you need to write to a wave file. See help!\n"); + goto exit; + } +#endif + +#ifdef HAVE_ALSA + /* start sound */ + sound_start(audio); +#endif + + PDEBUG(DBNETZ, DEBUG_ERROR, "Start audio after pause...\n"); + + process_signal(); + +exit: + /* close wave */ + wave_destroy_record(&wave_tx_rec); + +#ifdef HAVE_ALSA + /* exit sound */ + if (audio) + sound_close(audio); +#endif + + /* exit fsk */ + fsk_cleanup(&fsk); + + return 0; +} +