initial git import
This commit is contained in:
17
src/bnetz/Makefile.am
Normal file
17
src/bnetz/Makefile.am
Normal file
@@ -0,0 +1,17 @@
|
||||
AM_CPPFLAGS = -Wall -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
bnetz
|
||||
|
||||
bnetz_SOURCES = \
|
||||
bnetz.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
ansage-27.c \
|
||||
main.c
|
||||
bnetz_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(ALSA_LIBS) \
|
||||
$(top_builddir)/src/common/libcommon.a \
|
||||
-lm
|
||||
|
5003
src/bnetz/ansage-27.c
Normal file
5003
src/bnetz/ansage-27.c
Normal file
File diff suppressed because it is too large
Load Diff
3
src/bnetz/ansage-27.h
Normal file
3
src/bnetz/ansage-27.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void init_ansage_27(void);
|
||||
|
870
src/bnetz/bnetz.c
Normal file
870
src/bnetz/bnetz.c
Normal file
@@ -0,0 +1,870 @@
|
||||
/* B-Netz protocol handling
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/cause.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
/* Call reference for calls from mobile station to network
|
||||
This offset of 0x400000000 is required for MNCC interface. */
|
||||
static int new_callref = 0x40000000;
|
||||
|
||||
/* mobile originating call */
|
||||
#define CARRIER_TO 0.08 /* 80 ms search for carrier */
|
||||
#define GRUPPE_TO 0.4 /* 400 ms search for "Gruppensignal" */
|
||||
#define DIALING_TO 1.00 /* FIXME: get real value */
|
||||
|
||||
/* radio loss condition */
|
||||
#define LOSS_OF_SIGNAL 9.6 /* duration of carrier loss until release: 9.6 s */
|
||||
|
||||
/* mobile terminating call */
|
||||
#define ALERTING_TO 60 /* timeout after 60 seconds alerting the MS */
|
||||
#define PAGING_TO 4 /* timeout 4 seconds after "Selektivruf" */
|
||||
#define PAGE_TRIES 2 /* two tries */
|
||||
#define SWITCH19_TIME 1.0 /* time to switch channel (radio should be tansmitting after that) */
|
||||
|
||||
/* Convert channel number to frequency number of base station.
|
||||
Set 'unterband' to 1 to get frequency of mobile station. */
|
||||
double bnetz_kanal2freq(int kanal, int unterband)
|
||||
{
|
||||
double freq = 153.010;
|
||||
|
||||
if (kanal >= 50)
|
||||
freq += 9.200 - 0.020 * 49;
|
||||
freq += (kanal - 1) * 0.020;
|
||||
if (unterband)
|
||||
freq -= 4.600;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/* List of message digits */
|
||||
static struct impulstelegramme {
|
||||
int digit;
|
||||
const char *sequence;
|
||||
uint16_t telegramm;
|
||||
const char *description;
|
||||
} impulstelegramme[] = {
|
||||
/* Ziffern */
|
||||
{ '0', "0111011000000011", 0x0000, "Ziffer 0" },
|
||||
{ '1', "0111010100000101", 0x0000, "Ziffer 1" },
|
||||
{ '2', "0111010010001001", 0x0000, "Ziffer 2" },
|
||||
{ '3', "0111010001010001", 0x0000, "Ziffer 3" },
|
||||
{ '4', "0111001100000110", 0x0000, "Ziffer 4" },
|
||||
{ '5', "0111001010001010", 0x0000, "Ziffer 5" },
|
||||
{ '6', "0111001001010010", 0x0000, "Ziffer 6" },
|
||||
{ '7', "0111000110001100", 0x0000, "Ziffer 7" },
|
||||
{ '8', "0111000101010100", 0x0000, "Ziffer 8" },
|
||||
{ '9', "0111000011011000", 0x0000, "Ziffer 9" },
|
||||
/* Signale */
|
||||
{ 's', "0111001000100010", 0x0000, "Funkwahl ohne Gebuehrenuebermittlung" },
|
||||
{ 'S', "0111000100100100", 0x0000, "Funkwahl mit Gebuehrenuebermittlung" },
|
||||
{ 'e', "0111010000100001", 0x0000, "Funkwahlende" },
|
||||
{ 't', "0111010101010101", 0x0000, "Trennsignal/Schlusssignal" },
|
||||
/* Kanalbefehl B1 */
|
||||
{ 1001, "0111011000000101", 0x0000, "Kanalbefehl 1" },
|
||||
{ 1002, "0111011000001001", 0x0000, "Kanalbefehl 2" },
|
||||
{ 1003, "0111011000010001", 0x0000, "Kanalbefehl 3" },
|
||||
{ 1004, "0111011000000110", 0x0000, "Kanalbefehl 4" },
|
||||
{ 1005, "0111011000001010", 0x0000, "Kanalbefehl 5" },
|
||||
{ 1006, "0111011000010010", 0x0000, "Kanalbefehl 6" },
|
||||
{ 1007, "0111011000001100", 0x0000, "Kanalbefehl 7" },
|
||||
{ 1008, "0111011000010100", 0x0000, "Kanalbefehl 8" },
|
||||
{ 1009, "0111011000011000", 0x0000, "Kanalbefehl 9" },
|
||||
{ 1010, "0111010100000011", 0x0000, "Kanalbefehl 10" },
|
||||
{ 1011, "0111001100000101", 0x0000, "Kanalbefehl 11" }, /* 41 */
|
||||
{ 1012, "0111010100001001", 0x0000, "Kanalbefehl 12" },
|
||||
{ 1013, "0111010100010001", 0x0000, "Kanalbefehl 13" },
|
||||
{ 1014, "0111010100000110", 0x0000, "Kanalbefehl 14" },
|
||||
{ 1015, "0111010100001010", 0x0000, "Kanalbefehl 15" },
|
||||
{ 1016, "0111010100010010", 0x0000, "Kanalbefehl 16" },
|
||||
{ 1017, "0111010100001100", 0x0000, "Kanalbefehl 17" },
|
||||
{ 1018, "0111010100010100", 0x0000, "Kanalbefehl 18" },
|
||||
{ 1019, "0111010100011000", 0x0000, "Kanalbefehl 19" },
|
||||
{ 1020, "0111010010000011", 0x0000, "Kanalbefehl 20" },
|
||||
{ 1021, "0111010010000101", 0x0000, "Kanalbefehl 21" },
|
||||
{ 1022, "0111001100001001", 0x0000, "Kanalbefehl 22" }, /* 42 */
|
||||
{ 1023, "0111010010010001", 0x0000, "Kanalbefehl 23" },
|
||||
{ 1024, "0111010010000110", 0x0000, "Kanalbefehl 24" },
|
||||
{ 1025, "0111010010001010", 0x0000, "Kanalbefehl 25" },
|
||||
{ 1026, "0111010010010010", 0x0000, "Kanalbefehl 26" },
|
||||
{ 1027, "0111010010001100", 0x0000, "Kanalbefehl 27" },
|
||||
{ 1028, "0111010010010100", 0x0000, "Kanalbefehl 28" },
|
||||
{ 1029, "0111010010011000", 0x0000, "Kanalbefehl 29" },
|
||||
{ 1030, "0111010001000011", 0x0000, "Kanalbefehl 30" },
|
||||
{ 1031, "0111010001000101", 0x0000, "Kanalbefehl 31" },
|
||||
{ 1032, "0111010001001001", 0x0000, "Kanalbefehl 32" },
|
||||
{ 1033, "0111001100010001", 0x0000, "Kanalbefehl 33" }, /* 43 */
|
||||
{ 1034, "0111010001000110", 0x0000, "Kanalbefehl 34" },
|
||||
{ 1035, "0111010001001010", 0x0000, "Kanalbefehl 35" },
|
||||
{ 1036, "0111010001010010", 0x0000, "Kanalbefehl 36" },
|
||||
{ 1037, "0111010001001100", 0x0000, "Kanalbefehl 37" },
|
||||
{ 1038, "0111010001010100", 0x0000, "Kanalbefehl 38" },
|
||||
{ 1039, "0111010001011000", 0x0000, "Kanalbefehl 39" },
|
||||
/* Kanalbefehl B2 */
|
||||
{ 1050, "0111001010000011", 0x0000, "Kanalbefehl 50" },
|
||||
{ 1051, "0111001010000101", 0x0000, "Kanalbefehl 51" },
|
||||
{ 1052, "0111001010001001", 0x0000, "Kanalbefehl 52" },
|
||||
{ 1053, "0111001010010001", 0x0000, "Kanalbefehl 53" },
|
||||
{ 1054, "0111001010000110", 0x0000, "Kanalbefehl 54" },
|
||||
{ 1055, "0111001100001010", 0x0000, "Kanalbefehl 55" }, /* 45 */
|
||||
{ 1056, "0111001010010010", 0x0000, "Kanalbefehl 56" },
|
||||
{ 1057, "0111001010001100", 0x0000, "Kanalbefehl 57" },
|
||||
{ 1058, "0111001010010100", 0x0000, "Kanalbefehl 58" },
|
||||
{ 1059, "0111001010011000", 0x0000, "Kanalbefehl 59" },
|
||||
{ 1060, "0111001001000011", 0x0000, "Kanalbefehl 60" },
|
||||
{ 1061, "0111001001000101", 0x0000, "Kanalbefehl 61" },
|
||||
{ 1062, "0111001001001001", 0x0000, "Kanalbefehl 62" },
|
||||
{ 1063, "0111001001010001", 0x0000, "Kanalbefehl 63" },
|
||||
{ 1064, "0111001001000110", 0x0000, "Kanalbefehl 64" },
|
||||
{ 1065, "0111001001001010", 0x0000, "Kanalbefehl 65" },
|
||||
{ 1066, "0111001100010010", 0x0000, "Kanalbefehl 66" }, /* 46 */
|
||||
{ 1067, "0111001001001100", 0x0000, "Kanalbefehl 67" },
|
||||
{ 1068, "0111001001010100", 0x0000, "Kanalbefehl 68" },
|
||||
{ 1069, "0111001001011000", 0x0000, "Kanalbefehl 69" },
|
||||
{ 1070, "0111000110000011", 0x0000, "Kanalbefehl 70" },
|
||||
{ 1071, "0111000110000101", 0x0000, "Kanalbefehl 71" },
|
||||
{ 1072, "0111000110001001", 0x0000, "Kanalbefehl 72" },
|
||||
{ 1073, "0111000110010001", 0x0000, "Kanalbefehl 73" },
|
||||
{ 1074, "0111000110000110", 0x0000, "Kanalbefehl 74" },
|
||||
{ 1075, "0111000110001010", 0x0000, "Kanalbefehl 75" },
|
||||
{ 1076, "0111000110010010", 0x0000, "Kanalbefehl 76" },
|
||||
{ 1077, "0111001100001100", 0x0000, "Kanalbefehl 77" }, /* 47 */
|
||||
{ 1078, "0111000110010100", 0x0000, "Kanalbefehl 78" },
|
||||
{ 1079, "0111000110011000", 0x0000, "Kanalbefehl 79" },
|
||||
{ 1080, "0111000101000011", 0x0000, "Kanalbefehl 80" },
|
||||
{ 1081, "0111000101000101", 0x0000, "Kanalbefehl 81" },
|
||||
{ 1082, "0111000101001001", 0x0000, "Kanalbefehl 82" },
|
||||
{ 1083, "0111000101010001", 0x0000, "Kanalbefehl 83" },
|
||||
{ 1084, "0111000101000110", 0x0000, "Kanalbefehl 84" },
|
||||
{ 1085, "0111000101001010", 0x0000, "Kanalbefehl 85" },
|
||||
{ 1086, "0111000101010010", 0x0000, "Kanalbefehl 86" },
|
||||
/* Gruppenfreisignale */
|
||||
{ 2001, "0111000011000101", 0x0000, "Gruppenfreisignal 1" }, /* 91 */
|
||||
{ 2002, "0111000011001001", 0x0000, "Gruppenfreisignal 2" }, /* 92 */
|
||||
{ 2003, "0111000011010001", 0x0000, "Gruppenfreisignal 3" }, /* 93 */
|
||||
{ 2004, "0111000011000110", 0x0000, "Gruppenfreisignal 4" }, /* 94 */
|
||||
{ 2005, "0111000011001010", 0x0000, "Gruppenfreisignal 5" }, /* 95 */
|
||||
{ 2006, "0111000011010010", 0x0000, "Gruppenfreisignal 6" }, /* 96 */
|
||||
{ 2007, "0111000011001100", 0x0000, "Gruppenfreisignal 7" }, /* 97 */
|
||||
{ 2008, "0111000011010100", 0x0000, "Gruppenfreisignal 8" }, /* 98 */
|
||||
{ 2009, "0111000011000011", 0x0000, "Gruppenfreisignal 9" }, /* 90 */
|
||||
{ 2010, "0111000101000011", 0x0000, "Gruppenfreisignal 10" }, /* 80 */
|
||||
{ 2011, "0111000101000101", 0x0000, "Gruppenfreisignal 11" }, /* 81 */
|
||||
{ 2012, "0111000101001001", 0x0000, "Gruppenfreisignal 12" }, /* 82 */
|
||||
{ 2013, "0111000101010001", 0x0000, "Gruppenfreisignal 13" }, /* 83 */
|
||||
{ 2014, "0111000101000110", 0x0000, "Gruppenfreisignal 14" }, /* 84 */
|
||||
{ 2015, "0111000101001010", 0x0000, "Gruppenfreisignal 15" }, /* 85 */
|
||||
{ 2016, "0111000101010010", 0x0000, "Gruppenfreisignal 16" }, /* 86 */
|
||||
{ 2017, "0111000101001100", 0x0000, "Gruppenfreisignal 17" }, /* 87 */
|
||||
{ 2018, "0111000101010100", 0x0000, "Gruppenfreisignal 18" }, /* 88 */
|
||||
{ 2019, "0111000101011000", 0x0000, "Gruppenfreisignal 19 (Kanaele kleiner Leistung)" }, /* 89 */
|
||||
{ 0, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
/* Return bit sequence string by given digit. */
|
||||
static struct impulstelegramme *bnetz_telegramm(int digit)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
if (impulstelegramme[i].digit == digit)
|
||||
return &impulstelegramme[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* switch pilot signal (tone or file) */
|
||||
static void switch_channel_19(bnetz_t *bnetz, int on)
|
||||
{
|
||||
if (bnetz->sender.use_pilot_signal >= 0) {
|
||||
bnetz->sender.pilot_on = on;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bnetz->pilot_file && bnetz->pilot_is_on != on) {
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(bnetz->pilot_file, "w");
|
||||
if (!fp) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to open file '%s' to switch channel 19!\n");
|
||||
return;
|
||||
}
|
||||
fprintf(fp, "%s\n", (on) ? bnetz->pilot_on : bnetz->pilot_off);
|
||||
fclose(fp);
|
||||
bnetz->pilot_is_on = on;
|
||||
}
|
||||
}
|
||||
|
||||
/* global init */
|
||||
int bnetz_init(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
uint16_t telegramm = 0;
|
||||
for (j = 0; j < strlen(impulstelegramme[i].sequence); j++) {
|
||||
telegramm <<= 1;
|
||||
telegramm |= (impulstelegramme[i].sequence[j] == '1');
|
||||
}
|
||||
impulstelegramme[i].telegramm = telegramm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bnetz_timeout(struct timer *timer);
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_factor, const char *pilot)
|
||||
{
|
||||
bnetz_t *bnetz;
|
||||
int use_pilot_tone = -1;
|
||||
char pilot_file[256] = "", pilot_on[256] = "", pilot_off[256] = "";
|
||||
int rc;
|
||||
|
||||
if (!(kanal >= 1 && kanal <= 39) && !(kanal >= 50 && kanal <= 86)) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (kanal == 19) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Selected calling channel ('Rufkanal') number %d can't be used as traffic channel.\n", kanal);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((gfs < 1 || gfs > 19)) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Given 'Gruppenfreisignal' %d invalid.\n", gfs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!strcmp(pilot, "tone"))
|
||||
use_pilot_tone = 2;
|
||||
else
|
||||
if (!strcmp(pilot, "positive"))
|
||||
use_pilot_tone = 1;
|
||||
else
|
||||
if (!strcmp(pilot, "negative"))
|
||||
use_pilot_tone = 0;
|
||||
else {
|
||||
char *p;
|
||||
|
||||
strncpy(pilot_file, pilot, sizeof(pilot_file) - 1);
|
||||
p = strchr(pilot_file, '=');
|
||||
if (!p) {
|
||||
error_pilot:
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Given pilot file (to switch to channel 19) is missing parameters. Use <file>=<on>:<off> format!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
*p++ = '\0';
|
||||
strncpy(pilot_on, p, sizeof(pilot_on) - 1);
|
||||
p = strchr(pilot_file, ':');
|
||||
if (!p)
|
||||
goto error_pilot;
|
||||
*p++ = '\0';
|
||||
strncpy(pilot_off, p, sizeof(pilot_off) - 1);
|
||||
}
|
||||
|
||||
bnetz = calloc(1, sizeof(bnetz_t));
|
||||
if (!bnetz) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Creating 'B-Netz' instance for 'Kanal' = %d 'Gruppenfreisignal' = %d (sample rate %d).\n", kanal, gfs, samplerate);
|
||||
|
||||
/* init general part of transceiver */
|
||||
rc = sender_create(&bnetz->sender, sounddev, samplerate, kanal, loopback, loss_factor, use_pilot_tone);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init transceiver process!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* init audio processing */
|
||||
rc = dsp_init_sender(bnetz);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init audio processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* go into idle state */
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d on channel %d.\n", gfs, kanal);
|
||||
bnetz->state = BNETZ_FREI;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
bnetz->gfs = gfs;
|
||||
strncpy(bnetz->pilot_file, pilot_file, sizeof(bnetz->pilot_file) - 1);
|
||||
strncpy(bnetz->pilot_on, pilot_on, sizeof(bnetz->pilot_on) - 1);
|
||||
strncpy(bnetz->pilot_off, pilot_off, sizeof(bnetz->pilot_off) - 1);
|
||||
timer_init(&bnetz->timer, bnetz_timeout, bnetz);
|
||||
switch_channel_19(bnetz, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
bnetz_destroy(&bnetz->sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void bnetz_destroy(sender_t *sender)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Destroying 'B-Netz' instance for 'Kanal' = %d.\n", sender->kanal);
|
||||
switch_channel_19(bnetz, 0);
|
||||
dsp_cleanup_sender(bnetz);
|
||||
timer_exit(&bnetz->timer);
|
||||
sender_destroy(&bnetz->sender);
|
||||
free(bnetz);
|
||||
}
|
||||
|
||||
/* Abort connection towards mobile station by sending idle digits. */
|
||||
static void bnetz_go_idle(bnetz_t *bnetz)
|
||||
{
|
||||
timer_stop(&bnetz->timer);
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d.\n", bnetz->gfs);
|
||||
bnetz->state = BNETZ_FREI;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
switch_channel_19(bnetz, 0);
|
||||
bnetz->station_id[0] = '\0';
|
||||
}
|
||||
|
||||
/* Release connection towards mobile station by sending release digits. */
|
||||
static void bnetz_release(bnetz_t *bnetz)
|
||||
{
|
||||
timer_stop(&bnetz->timer);
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering release state, sending 'Trennsignal'.\n");
|
||||
bnetz->state = BNETZ_TRENNEN;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
switch_channel_19(bnetz, 0);
|
||||
bnetz->trenn_count = 0;
|
||||
bnetz->station_id[0] = '\0';
|
||||
}
|
||||
|
||||
/* Enter paging state and transmit station ID. */
|
||||
static void bnetz_page(bnetz_t *bnetz, const char *dial_string, int try)
|
||||
{
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering paging state (try %d), sending 'Selektivruf' to '%s'.\n", try, dial_string);
|
||||
bnetz->state = BNETZ_SELEKTIVRUF;
|
||||
bnetz->dsp_mode = DSP_MODE_0;
|
||||
bnetz->page_mode = PAGE_MODE_NUMBER;
|
||||
bnetz->page_try = try;
|
||||
strcpy(bnetz->station_id, dial_string);
|
||||
bnetz->station_id_pos = 0;
|
||||
timer_start(&bnetz->timer, SWITCH19_TIME);
|
||||
switch_channel_19(bnetz, 1);
|
||||
}
|
||||
|
||||
/* FSK processing requests next digit after transmission of previous
|
||||
digit has been finished. */
|
||||
const char *bnetz_get_telegramm(bnetz_t *bnetz)
|
||||
{
|
||||
struct impulstelegramme *it = NULL;
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
bnetz->loopback_time[bnetz->loopback_count] = get_time();
|
||||
it = bnetz_telegramm(bnetz->loopback_count + '0');
|
||||
if (++bnetz->loopback_count > 9)
|
||||
bnetz->loopback_count = 0;
|
||||
} else
|
||||
switch(bnetz->state) {
|
||||
case BNETZ_FREI:
|
||||
it = bnetz_telegramm(2000 + bnetz->gfs);
|
||||
break;
|
||||
case BNETZ_WAHLABRUF:
|
||||
if (bnetz->station_id_pos == 5) {
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
return NULL;
|
||||
}
|
||||
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
if (bnetz->page_mode == PAGE_MODE_KANALBEFEHL) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Paging mobile station %s complete, waiting for answer.\n", bnetz->station_id);
|
||||
bnetz->state = BNETZ_RUFBESTAETIGUNG;
|
||||
bnetz->dsp_mode = DSP_MODE_SILENCE;
|
||||
switch_channel_19(bnetz, 0);
|
||||
timer_start(&bnetz->timer, PAGING_TO);
|
||||
return NULL;
|
||||
}
|
||||
if (bnetz->station_id_pos == 5) {
|
||||
it = bnetz_telegramm(bnetz->sender.kanal + 1000);
|
||||
bnetz->page_mode = PAGE_MODE_KANALBEFEHL;
|
||||
break;
|
||||
}
|
||||
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
|
||||
break;
|
||||
case BNETZ_TRENNEN:
|
||||
if (bnetz->trenn_count++ == 75) {
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Maximum number of release digits sent, going idle.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return NULL;
|
||||
}
|
||||
it = bnetz_telegramm('t');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!it)
|
||||
abort();
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Sending telegramm '%s'.\n", it->description);
|
||||
return it->sequence;
|
||||
}
|
||||
|
||||
/* Loss of signal was detected, release active call. */
|
||||
void bnetz_loss_indication(bnetz_t *bnetz)
|
||||
{
|
||||
if (bnetz->state == BNETZ_GESPRAECH
|
||||
|| bnetz->state == BNETZ_RUFHALTUNG) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n");
|
||||
bnetz_release(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_TEMPFAIL);
|
||||
bnetz->sender.callref = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* A continuous tone was detected or is gone. */
|
||||
void bnetz_receive_tone(bnetz_t *bnetz, int bit)
|
||||
{
|
||||
if (bit >= 0)
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (bit)?1950:2070);
|
||||
else
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Continuous tone is gone.\n");
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_FREI:
|
||||
if (bit == 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Kanalbelegung' from mobile station, sending signal 'Wahlabruf'.\n");
|
||||
bnetz->state = BNETZ_WAHLABRUF;
|
||||
bnetz->dial_mode = DIAL_MODE_START;
|
||||
bnetz->telegramm = NULL;
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
timer_start(&bnetz->timer, DIALING_TO);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
if (bit == 1) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Rufbestaetigung' from mobile station, call is ringing.\n");
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->state = BNETZ_RUFHALTUNG;
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
call_in_alerting(bnetz->sender.callref);
|
||||
timer_start(&bnetz->timer, ALERTING_TO);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
if (bit == 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Beginnsignal' from mobile station, call establised.\n");
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->state = BNETZ_GESPRAECH;
|
||||
bnetz->dsp_mode = DSP_MODE_AUDIO;
|
||||
call_in_answer(bnetz->sender.callref, bnetz->station_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* A digit was received. */
|
||||
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level)
|
||||
{
|
||||
int digit = 0;
|
||||
int i;
|
||||
int quality_percent = quality * 100;
|
||||
int level_percent = level * 100;
|
||||
|
||||
/* drop any telegramm that is too bad */
|
||||
if (quality_percent < 20)
|
||||
return;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
if (impulstelegramme[i].telegramm == telegramm) {
|
||||
digit = impulstelegramme[i].digit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (digit == 0)
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received unknown telegramm '0x%04x'. (quality=%d%% level=%d%%)\n", telegramm, quality_percent, level_percent);
|
||||
else
|
||||
PDEBUG(DBNETZ, (bnetz->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received telegramm '%s'. (quality=%d%% level=%d%%)\n", impulstelegramme[i].description, quality_percent, level_percent);
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
if (digit >= '0' && digit <= '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Round trip delay is %.3f seconds\n", get_time() - bnetz->loopback_time[digit - '0'] - 0.160);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_WAHLABRUF:
|
||||
timer_start(&bnetz->timer, DIALING_TO);
|
||||
switch (bnetz->dial_mode) {
|
||||
case DIAL_MODE_START:
|
||||
if (digit != 's' && digit != 'S') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received digit that is not a start digit ('Funkwahl'), aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (digit == 'S')
|
||||
bnetz->dial_metering = 1;
|
||||
else
|
||||
bnetz->dial_metering = 0;
|
||||
bnetz->dial_mode = DIAL_MODE_STATIONID;
|
||||
memset(bnetz->station_id, 0, sizeof(bnetz->station_id));
|
||||
bnetz->dial_pos = 0;
|
||||
break;
|
||||
case DIAL_MODE_STATIONID:
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->station_id[bnetz->dial_pos++] = digit;
|
||||
if (bnetz->dial_pos == 5) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received station id from mobile phone: %s\n", bnetz->station_id);
|
||||
bnetz->dial_mode = DIAL_MODE_NUMBER;
|
||||
memset(bnetz->dial_number, 0, sizeof(bnetz->dial_number));
|
||||
bnetz->dial_pos = 0;
|
||||
}
|
||||
break;
|
||||
case DIAL_MODE_NUMBER:
|
||||
if (digit == 'e') {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received number from mobile phone: %s\n", bnetz->dial_number);
|
||||
bnetz->dial_mode = DIAL_MODE_START2;
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Sending station id back to phone: %s.\n", bnetz->station_id);
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
bnetz->station_id_pos = 0;
|
||||
break;
|
||||
}
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == sizeof(bnetz->dial_number) - 1) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->dial_number[bnetz->dial_pos++] = digit;
|
||||
break;
|
||||
case DIAL_MODE_START2:
|
||||
if (digit != 's' && digit != 'S') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a start message('Funkwahl'), aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if ((digit == 'S' && bnetz->dial_metering != 1) || (digit == 's' && bnetz->dial_metering != 0)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received start message('Funkwahl') does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->dial_mode = DIAL_MODE_STATIONID2;
|
||||
bnetz->dial_pos = 0;
|
||||
break;
|
||||
case DIAL_MODE_STATIONID2:
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->station_id[bnetz->dial_pos++] != digit) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received station id does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == 5) {
|
||||
bnetz->dial_mode = DIAL_MODE_NUMBER2;
|
||||
bnetz->dial_pos = 0;
|
||||
}
|
||||
break;
|
||||
case DIAL_MODE_NUMBER2:
|
||||
if (digit == 'e') {
|
||||
int callref = ++new_callref;
|
||||
int rc;
|
||||
/* add 0 in front of number */
|
||||
char dialing[sizeof(bnetz->dial_number) + 1] = "0";
|
||||
strcpy(dialing + 1, bnetz->dial_number);
|
||||
|
||||
if (bnetz->dial_pos != strlen(bnetz->dial_number)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too few number digits the second time, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", bnetz->station_id, dialing);
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->dsp_mode = DSP_MODE_AUDIO;
|
||||
bnetz->state = BNETZ_GESPRAECH;
|
||||
|
||||
/* setup call */
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Setup call to network.\n");
|
||||
rc = call_in_setup(callref, bnetz->station_id, dialing);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc);
|
||||
bnetz_release(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->sender.callref = callref;
|
||||
break;
|
||||
}
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == strlen(bnetz->dial_number)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_number[bnetz->dial_pos++] != digit) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received number does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BNETZ_GESPRAECH:
|
||||
/* only good telegramms shall pass */
|
||||
if (quality_percent < 70)
|
||||
return;
|
||||
if (digit == 't') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received 'Schlusssignal' from mobile station\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_NORMAL);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Timeout handling */
|
||||
static void bnetz_timeout(struct timer *timer)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *)timer->priv;
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_WAHLABRUF:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while receiving call setup from mobile station, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Transmitter switched to channel 19, starting paging telegramms.\n");
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
break;
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
if (bnetz->page_try == PAGE_TRIES) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, going idle.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_OUTOFORDER);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
}
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, trying again.\n");
|
||||
bnetz_page(bnetz, bnetz->station_id, bnetz->page_try + 1);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for answer of mobile station, releasing.\n");
|
||||
bnetz_release(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_NOANSWER);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call control starts call towards mobile station. */
|
||||
int call_out_setup(int callref, char *dialing)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
int i;
|
||||
|
||||
/* 1. check if number is invalid, return INVALNUMBER */
|
||||
if (strlen(dialing) != 5) {
|
||||
inval:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
|
||||
return -CAUSE_INVALNUMBER;
|
||||
}
|
||||
for (i = 0; i < 5; i++) {
|
||||
if (dialing[i] < '0' || dialing[i] > '9')
|
||||
goto inval;
|
||||
}
|
||||
|
||||
/* 2. check if given number is already in a call, return BUSY */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (!strcmp(bnetz->station_id, dialing))
|
||||
break;
|
||||
}
|
||||
if (sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
|
||||
return -CAUSE_BUSY;
|
||||
}
|
||||
|
||||
/* 3. check if all senders are busy, return NOCHANNEL */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (bnetz->state == BNETZ_FREI)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
|
||||
return -CAUSE_NOCHANNEL;
|
||||
}
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call to mobile station, paging station id '%s'\n", dialing);
|
||||
|
||||
/* 4. trying to page mobile station */
|
||||
sender->callref = callref;
|
||||
bnetz_page(bnetz, dialing, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Call control sends disconnect (with tones).
|
||||
* An active call stays active, so tones and annoucements can be received
|
||||
* by mobile station.
|
||||
*/
|
||||
void call_out_disconnect(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been disconnected by network.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
|
||||
call_in_release(callref, CAUSE_INVALCALLREF);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Release when not active */
|
||||
if (bnetz->state == BNETZ_GESPRAECH)
|
||||
return;
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during paging, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
call_in_release(callref, cause);
|
||||
|
||||
sender->callref = 0;
|
||||
}
|
||||
|
||||
/* Call control releases call toward mobile station. */
|
||||
void call_out_release(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
|
||||
/* don't send release, because caller already released */
|
||||
return;
|
||||
}
|
||||
|
||||
sender->callref = 0;
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_GESPRAECH:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during call, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during paging, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during alerting, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_rx_audio(int callref, int16_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender)
|
||||
return;
|
||||
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO) {
|
||||
int16_t up[count * bnetz->sender.srstate.factor];
|
||||
count = samplerate_upsample(&bnetz->sender.srstate, samples, count, up);
|
||||
jitter_save(&bnetz->sender.audio, up, count);
|
||||
}
|
||||
}
|
||||
|
99
src/bnetz/bnetz.h
Normal file
99
src/bnetz/bnetz.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "../common/sender.h"
|
||||
|
||||
/* fsk modes of transmission */
|
||||
enum dsp_mode {
|
||||
DSP_MODE_AUDIO, /* stream audio */
|
||||
DSP_MODE_SILENCE, /* sending silence */
|
||||
DSP_MODE_0, /* send tone 0 */
|
||||
DSP_MODE_1, /* send tone 1 */
|
||||
DSP_MODE_TELEGRAMM, /* send "Telegramm" */
|
||||
};
|
||||
|
||||
/* current state of b-netz sender */
|
||||
enum bnetz_state {
|
||||
BNETZ_FREI, /* sending 'Gruppenfreisignal' */
|
||||
BNETZ_WAHLABRUF, /* sending 'Wahlabruf', receiving call setup */
|
||||
BNETZ_SELEKTIVRUF, /* paging phone */
|
||||
BNETZ_RUFBESTAETIGUNG, /* waitig for acknowledge from phone */
|
||||
BNETZ_RUFHALTUNG, /* phone is ringing */
|
||||
BNETZ_GESPRAECH, /* during conversation */
|
||||
BNETZ_TRENNEN, /* release of call */
|
||||
};
|
||||
|
||||
/* current state of received dialing */
|
||||
enum dial_mode {
|
||||
DIAL_MODE_START,
|
||||
DIAL_MODE_STATIONID,
|
||||
DIAL_MODE_NUMBER,
|
||||
DIAL_MODE_START2,
|
||||
DIAL_MODE_STATIONID2,
|
||||
DIAL_MODE_NUMBER2,
|
||||
};
|
||||
|
||||
/* current state of paging mobile station */
|
||||
enum page_mode {
|
||||
PAGE_MODE_NUMBER,
|
||||
PAGE_MODE_KANALBEFEHL,
|
||||
};
|
||||
|
||||
/* instance of bnetz sender */
|
||||
typedef struct bnetz {
|
||||
sender_t sender;
|
||||
|
||||
/* system info */
|
||||
int gfs; /* 'Gruppenfreisignal' */
|
||||
|
||||
/* switch sender to channel 19 */
|
||||
char pilot_file[256]; /* if set, write to given file to switch to channel 19 or back */
|
||||
char pilot_on[256]; /* what to write to switch to channel 19 */
|
||||
char pilot_off[256]; /* what to write to switch back */
|
||||
int pilot_is_on; /* set, if we are on channel 19. also used to switch back on exit */
|
||||
|
||||
/* all bnetz states */
|
||||
enum bnetz_state state; /* main state of sender */
|
||||
enum dial_mode dial_mode; /* sub state while dialing is received */
|
||||
int dial_metering; /* set, if phone supports metering pulses */
|
||||
char dial_number[14]; /* dial string received */
|
||||
int dial_pos; /* current position while receiving digits */
|
||||
char station_id[6]; /* current station ID */
|
||||
int station_id_pos; /* position while transmitting */
|
||||
enum page_mode page_mode; /* sub state while paging */
|
||||
int page_try; /* try number (1 or 2) */
|
||||
struct timer timer;
|
||||
int trenn_count; /* count number of release messages */
|
||||
|
||||
/* dsp states */
|
||||
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, "Telegramm" */
|
||||
int fsk_coeff[2]; /* coefficient k = 2*cos(2*PI*f/samplerate), k << 15 */
|
||||
int samples_per_bit; /* how many samples lasts one bit */
|
||||
int16_t *fsk_filter_spl; /* array with samples_per_bit */
|
||||
int fsk_filter_pos; /* current sample position in filter_spl */
|
||||
int fsk_filter_step; /* number of samples for each analyzation */
|
||||
int fsk_filter_bit; /* last bit, so we detect a bit change */
|
||||
int fsk_filter_sample; /* sample counter for shifting receive bits */
|
||||
uint16_t fsk_filter_telegramm; /* rx shift register for receiveing telegramm */
|
||||
double fsk_filter_quality[16]; /* quality of each bit in telegramm */
|
||||
double fsk_filter_level[16]; /* level of each bit in telegramm */
|
||||
int fsk_filter_qualidx; /* index of quality array above */
|
||||
int tone_detected; /* what tone has been detected */
|
||||
int tone_count; /* how long has that tone been detected */
|
||||
double phaseshift256[2]; /* how much the phase of sine wave changes per sample */
|
||||
double phase256; /* current phase */
|
||||
const char *telegramm; /* current telegramm sequence */
|
||||
int16_t *telegramm_spl; /* 16 * samples_per_bit */
|
||||
int telegramm_pos; /* current sample position in telegramm_spl */
|
||||
|
||||
/* loopback test for latency */
|
||||
int loopback_count; /* count digits from 0 to 9 */
|
||||
double loopback_time[10]; /* time stamp when sending digits 0..9 */
|
||||
} bnetz_t;
|
||||
|
||||
double bnetz_kanal2freq(int kanal, int unterband);
|
||||
int bnetz_init(void);
|
||||
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_volume, const char *pilot);
|
||||
void bnetz_destroy(sender_t *sender);
|
||||
void bnetz_loss_indication(bnetz_t *bnetz);
|
||||
void bnetz_receive_tone(bnetz_t *bnetz, int bit);
|
||||
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level);
|
||||
const char *bnetz_get_telegramm(bnetz_t *bnetz);
|
||||
|
416
src/bnetz/dsp.c
Normal file
416
src/bnetz/dsp.c
Normal file
@@ -0,0 +1,416 @@
|
||||
/* B-Netz signal processing
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/goertzel.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define PI 3.1415927
|
||||
|
||||
/* signalling */
|
||||
#define TX_PEAK 10000.0 /* peak amplitude of sine wave */
|
||||
#define BIT_DURATION 0.010 /* bit length: 10 ms */
|
||||
#define FILTER_STEP 0.001 /* step every 1 ms */
|
||||
#define METERING_HZ 2900 /* metering pulse frequency */
|
||||
|
||||
#define TONE_DETECT_TH 70 /* 70 milliseconds to detect continous tone */
|
||||
|
||||
/* carrier loss detection */
|
||||
#define LOSS_INTERVAL 1000 /* filter steps (milliseconds) for one second interval */
|
||||
#define LOSS_TIME 12 /* duration of signal loss before release */
|
||||
|
||||
/* two signalling tones */
|
||||
static double fsk_bits[2] = {
|
||||
2070.0,
|
||||
1950.0,
|
||||
};
|
||||
|
||||
/* table for fast sine generation */
|
||||
int dsp_sine[256];
|
||||
|
||||
/* global init for FSK */
|
||||
void dsp_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table.\n");
|
||||
for (i = 0; i < 256; i++) {
|
||||
dsp_sine[i] = (int)(sin((double)i / 256.0 * 2.0 * PI) * TX_PEAK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Init transceiver instance. */
|
||||
int dsp_init_sender(bnetz_t *bnetz)
|
||||
{
|
||||
double coeff;
|
||||
int16_t *spl;
|
||||
int i;
|
||||
|
||||
if ((bnetz->sender.samplerate % 1000)) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "Samples rate must be a multiple of 1000 bits per second.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
|
||||
|
||||
audio_init_loss(&bnetz->sender.loss, LOSS_INTERVAL, bnetz->sender.loss_volume, LOSS_TIME);
|
||||
|
||||
bnetz->samples_per_bit = bnetz->sender.samplerate * BIT_DURATION;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per bit duration.\n", bnetz->samples_per_bit);
|
||||
bnetz->fsk_filter_step = bnetz->sender.samplerate * FILTER_STEP;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per filter step.\n", bnetz->fsk_filter_step);
|
||||
spl = calloc(16, bnetz->samples_per_bit << 1);
|
||||
if (!spl) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
bnetz->telegramm_spl = spl;
|
||||
spl = calloc(1, bnetz->samples_per_bit << 1);
|
||||
if (!spl) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
bnetz->fsk_filter_spl = spl;
|
||||
bnetz->fsk_filter_bit = -1;
|
||||
|
||||
bnetz->tone_detected = -1;
|
||||
|
||||
/* count symbols */
|
||||
for (i = 0; i < 2; i++) {
|
||||
coeff = 2.0 * cos(2.0 * PI * fsk_bits[i] / (double)bnetz->sender.samplerate);
|
||||
bnetz->fsk_coeff[i] = coeff * 32768.0;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "coeff[%d] = %d (must be -3601 and 2573 at 8000hz)\n", i, (int)bnetz->fsk_coeff[i]);
|
||||
|
||||
bnetz->phaseshift256[i] = 256.0 / ((double)bnetz->sender.samplerate / fsk_bits[i]);
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "phaseshift[%d] = %.4f (must be arround 64 at 8000hz)\n", i, bnetz->phaseshift256[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void dsp_cleanup_sender(bnetz_t *bnetz)
|
||||
{
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
|
||||
|
||||
if (bnetz->telegramm_spl) {
|
||||
free(bnetz->telegramm_spl);
|
||||
bnetz->telegramm_spl = NULL;
|
||||
}
|
||||
if (bnetz->fsk_filter_spl) {
|
||||
free(bnetz->fsk_filter_spl);
|
||||
bnetz->fsk_filter_spl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Count duration of tone and indicate detection/loss to protocol handler. */
|
||||
static void fsk_receive_tone(bnetz_t *bnetz, int bit, int goodtone, double level)
|
||||
{
|
||||
/* lost tone because it is not good anymore or has changed */
|
||||
if (!goodtone || bit != bnetz->tone_detected) {
|
||||
if (bnetz->tone_count >= TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Lost %.0f Hz tone after %d ms.\n", fsk_bits[bnetz->tone_detected], bnetz->tone_count);
|
||||
bnetz_receive_tone(bnetz, -1);
|
||||
}
|
||||
if (goodtone)
|
||||
bnetz->tone_detected = bit;
|
||||
else
|
||||
bnetz->tone_detected = -1;
|
||||
bnetz->tone_count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bnetz->tone_count++;
|
||||
|
||||
if (bnetz->tone_count >= TONE_DETECT_TH)
|
||||
audio_reset_loss(&bnetz->sender.loss);
|
||||
if (bnetz->tone_count == TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Detecting continous %.0f Hz tone. (level = %d%%)\n", fsk_bits[bnetz->tone_detected], (int)(level * 100));
|
||||
bnetz_receive_tone(bnetz, bnetz->tone_detected);
|
||||
}
|
||||
}
|
||||
|
||||
/* Collect 16 data bits (digit) and check for sync marc '01110'. */
|
||||
static void fsk_receive_bit(bnetz_t *bnetz, int bit, double quality, double level)
|
||||
{
|
||||
int i;
|
||||
|
||||
bnetz->fsk_filter_telegramm = (bnetz->fsk_filter_telegramm << 1) | bit;
|
||||
bnetz->fsk_filter_quality[bnetz->fsk_filter_qualidx] = quality;
|
||||
bnetz->fsk_filter_level[bnetz->fsk_filter_qualidx] = level;
|
||||
if (++bnetz->fsk_filter_qualidx == 16)
|
||||
bnetz->fsk_filter_qualidx = 0;
|
||||
|
||||
/* check if pattern 01110xxxxxxxxxxx matches */
|
||||
if ((bnetz->fsk_filter_telegramm & 0xf800) != 0x7000)
|
||||
return;
|
||||
|
||||
/* get worst bit and average level */
|
||||
level = 0;
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (bnetz->fsk_filter_quality[i] < quality)
|
||||
quality = bnetz->fsk_filter_quality[i];
|
||||
level = bnetz->fsk_filter_level[i];
|
||||
}
|
||||
|
||||
/* send telegramm */
|
||||
bnetz_receive_telegramm(bnetz, bnetz->fsk_filter_telegramm, quality, level);
|
||||
}
|
||||
|
||||
char *show_level(int value)
|
||||
{
|
||||
static char text[22];
|
||||
|
||||
value /= 5;
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 20)
|
||||
value = 20;
|
||||
strcpy(text, " ");
|
||||
text[value] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
//#define DEBUG_FILTER
|
||||
//#define DEBUG_QUALITY
|
||||
|
||||
/* Filter one chunk of audio an detect tone, quality and loss of signal.
|
||||
* The chunk is a window of 10ms. This window slides over audio stream
|
||||
* and is processed every 1ms. (one step) */
|
||||
void fsk_decode_step(bnetz_t *bnetz, int pos)
|
||||
{
|
||||
double level, result[2], softbit, quality;
|
||||
int max;
|
||||
int16_t *spl;
|
||||
int bit;
|
||||
|
||||
max = bnetz->samples_per_bit;
|
||||
spl = bnetz->fsk_filter_spl;
|
||||
|
||||
level = audio_level(spl, max);
|
||||
|
||||
if (audio_detect_loss(&bnetz->sender.loss, level))
|
||||
bnetz_loss_indication(bnetz);
|
||||
|
||||
audio_goertzel(spl, max, pos, bnetz->fsk_coeff, result, 2);
|
||||
|
||||
/* calculate soft bit from both frequencies */
|
||||
softbit = (result[1] / level - result[0] / level + 1.0) / 2.0;
|
||||
/* scale it, since both filters overlap by some percent */
|
||||
#define MIN_QUALITY 0.08
|
||||
softbit = (softbit - MIN_QUALITY) / (0.850 - MIN_QUALITY - MIN_QUALITY);
|
||||
if (softbit > 1)
|
||||
softbit = 1;
|
||||
if (softbit < 0)
|
||||
softbit = 0;
|
||||
#ifdef DEBUG_FILTER
|
||||
printf("|%s", show_level(result[0]/level*100));
|
||||
printf("|%s| low=%.3f high=%.3f level=%d\n", show_level(result[1]/level*100), result[0]/level, result[1]/level, (int)level);
|
||||
#endif
|
||||
if (softbit > 0.5)
|
||||
bit = 1;
|
||||
else
|
||||
bit = 0;
|
||||
|
||||
// FIXME: better threshold
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
if (level / 0.63 > 0.05 && (softbit > 0.75 || softbit < 0.25)) {
|
||||
fsk_receive_tone(bnetz, bit, 1, level / 0.63662);
|
||||
} else
|
||||
fsk_receive_tone(bnetz, bit, 0, level / 0.63662);
|
||||
|
||||
if (bnetz->fsk_filter_bit != bit) {
|
||||
/* if we have a bit change, reset sample counter to one half bit duration */
|
||||
bnetz->fsk_filter_bit = bit;
|
||||
bnetz->fsk_filter_sample = 5;
|
||||
} else if (--bnetz->fsk_filter_sample == 0) {
|
||||
/* if sample counter bit reaches 0, we reset sample counter to one bit duration */
|
||||
// quality = result[bit] / level;
|
||||
if (softbit > 0.5)
|
||||
quality = softbit * 2.0 - 1.0;
|
||||
else
|
||||
quality = 1.0 - softbit * 2.0;
|
||||
#ifdef DEBUG_QUALITY
|
||||
printf("|%s| quality=%.2f ", show_level(softbit * 100), quality);
|
||||
printf("|%s|\n", show_level(quality * 100));
|
||||
#endif
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
fsk_receive_bit(bnetz, bit, quality, level / 0.63662);
|
||||
bnetz->fsk_filter_sample = 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process received audio stream from radio unit. */
|
||||
void sender_receive(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
int16_t *spl;
|
||||
int max, pos, step;
|
||||
int i;
|
||||
|
||||
/* write received samples to decode buffer */
|
||||
max = bnetz->samples_per_bit;
|
||||
pos = bnetz->fsk_filter_pos;
|
||||
step = bnetz->fsk_filter_step;
|
||||
spl = bnetz->fsk_filter_spl;
|
||||
for (i = 0; i < length; i++) {
|
||||
spl[pos++] = samples[i];
|
||||
if (pos == max)
|
||||
pos = 0;
|
||||
/* if filter step has been reched */
|
||||
if (!(pos % step)) {
|
||||
fsk_decode_step(bnetz, pos);
|
||||
}
|
||||
}
|
||||
bnetz->fsk_filter_pos = pos;
|
||||
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO && bnetz->sender.callref) {
|
||||
int16_t down[length]; /* more than enough */
|
||||
int count;
|
||||
|
||||
count = samplerate_downsample(&bnetz->sender.srstate, samples, length, down);
|
||||
spl = bnetz->sender.rxbuf;
|
||||
pos = bnetz->sender.rxbuf_pos;
|
||||
for (i = 0; i < count; i++) {
|
||||
spl[pos++] = down[i];
|
||||
if (pos == 160) {
|
||||
call_tx_audio(bnetz->sender.callref, spl, 160);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
bnetz->sender.rxbuf_pos = pos;
|
||||
} else
|
||||
bnetz->sender.rxbuf_pos = 0;
|
||||
}
|
||||
|
||||
static void fsk_tone(bnetz_t *bnetz, int16_t *samples, int length, int tone)
|
||||
{
|
||||
double phaseshift, phase;
|
||||
int i;
|
||||
|
||||
phase = bnetz->phase256;
|
||||
phaseshift = bnetz->phaseshift256[tone];
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine[((uint8_t)phase) & 0xff];
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
}
|
||||
|
||||
bnetz->phase256 = phase;
|
||||
}
|
||||
|
||||
static int fsk_telegramm(bnetz_t *bnetz, int16_t *samples, int length)
|
||||
{
|
||||
int16_t *spl;
|
||||
int i, j;
|
||||
double phaseshift, phase;
|
||||
int count, max;
|
||||
|
||||
next_telegramm:
|
||||
if (!bnetz->telegramm) {
|
||||
/* request telegramm */
|
||||
// PDEBUG(DFSK, DEBUG_DEBUG, "Request new 'Telegramm'.\n");
|
||||
bnetz->telegramm = bnetz_get_telegramm(bnetz);
|
||||
if (!bnetz->telegramm) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Stop sending 'Telegramm'.\n");
|
||||
return length;
|
||||
}
|
||||
bnetz->telegramm_pos = 0;
|
||||
spl = bnetz->telegramm_spl;
|
||||
/* render telegramm */
|
||||
phase = bnetz->phase256;
|
||||
for (i = 0; i < 16; i++) {
|
||||
phaseshift = bnetz->phaseshift256[bnetz->telegramm[i] == '1'];
|
||||
for (j = 0; j < bnetz->samples_per_bit; j++) {
|
||||
*spl++ = dsp_sine[((uint8_t)phase) & 0xff];
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
}
|
||||
}
|
||||
bnetz->phase256 = phase;
|
||||
}
|
||||
|
||||
/* send audio from telegramm */
|
||||
max = bnetz->samples_per_bit * 16;
|
||||
count = max - bnetz->telegramm_pos;
|
||||
if (count > length)
|
||||
count = length;
|
||||
spl = bnetz->telegramm_spl + bnetz->telegramm_pos;
|
||||
for (i = 0; i < count; i++)
|
||||
*samples++ = *spl++;
|
||||
length -= count;
|
||||
bnetz->telegramm_pos += count;
|
||||
/* check for end of telegramm */
|
||||
if (bnetz->telegramm_pos == max) {
|
||||
bnetz->telegramm = NULL;
|
||||
/* we need more ? */
|
||||
if (length)
|
||||
goto next_telegramm;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/* Provide stream of audio toward radio unit */
|
||||
void sender_send(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
int len;
|
||||
|
||||
again:
|
||||
switch (bnetz->dsp_mode) {
|
||||
case DSP_MODE_AUDIO:
|
||||
jitter_load(&bnetz->sender.audio, samples, length);
|
||||
break;
|
||||
case DSP_MODE_SILENCE:
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
break;
|
||||
case DSP_MODE_0:
|
||||
fsk_tone(bnetz, samples, length, 0);
|
||||
break;
|
||||
case DSP_MODE_1:
|
||||
fsk_tone(bnetz, samples, length, 1);
|
||||
break;
|
||||
case DSP_MODE_TELEGRAMM:
|
||||
/* Encode telegramm into audio stream. If telegramms have
|
||||
* stopped, process again for rest of stream. */
|
||||
len = fsk_telegramm(bnetz, samples, length);
|
||||
if (len) {
|
||||
samples += length - len;
|
||||
length = len;
|
||||
goto again;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
5
src/bnetz/dsp.h
Normal file
5
src/bnetz/dsp.h
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
void dsp_init(void);
|
||||
int dsp_init_sender(bnetz_t *bnetz);
|
||||
void dsp_cleanup_sender(bnetz_t *bnetz);
|
||||
|
65
src/bnetz/image.c
Normal file
65
src/bnetz/image.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "image.h"
|
||||
|
||||
const char *image[] = {
|
||||
"@g",
|
||||
"",
|
||||
"",
|
||||
" \\",
|
||||
" \\",
|
||||
" \\",
|
||||
" \\_ @wB-NETZ@g",
|
||||
" \\ \\",
|
||||
" \\_\\___",
|
||||
" / __ )",
|
||||
" (__\\ _\\________",
|
||||
" / _______ )",
|
||||
" / / \\/",
|
||||
" / / ______\\___",
|
||||
" / / / )",
|
||||
" (__\\ / / @w~@g",
|
||||
" \\/ ___ /",
|
||||
" / / \\/ @w~@g",
|
||||
" (______\\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\",
|
||||
" @w~@g \\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\@G (###)@g",
|
||||
" \\ @G(##))########)",
|
||||
" (#)))################(#))",
|
||||
" (#)#(#######)))#################)",
|
||||
" ((#########)#######################)",
|
||||
"@w=========================================================",
|
||||
NULL
|
||||
};
|
||||
|
||||
void print_image(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; image[i]; i++) {
|
||||
for (j = 0; j < strlen(image[i]); j++) {
|
||||
if (image[i][j] == '@') {
|
||||
j++;
|
||||
switch(image[i][j]) {
|
||||
case 'g':
|
||||
printf("\033[0;37m");
|
||||
break;
|
||||
case 'w':
|
||||
printf("\033[1;37m");
|
||||
break;
|
||||
case 'G':
|
||||
printf("\033[0;32m");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
printf("%c", image[i][j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("\033[0;39m");
|
||||
}
|
||||
|
3
src/bnetz/image.h
Normal file
3
src/bnetz/image.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void print_image(void);
|
||||
|
190
src/bnetz/main.c
Normal file
190
src/bnetz/main.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/* B-Netz main
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/mncc_sock.h"
|
||||
#include "../common/main.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
#include "image.h"
|
||||
#include "ansage-27.h"
|
||||
|
||||
int gfs = 2;
|
||||
const char *pilot = "tone";
|
||||
|
||||
void print_help(const char *arg0)
|
||||
{
|
||||
print_help_common(arg0);
|
||||
/* - - */
|
||||
printf(" -g --gfs <gruppenfreisignal>\n");
|
||||
printf(" Gruppenfreisignal\" 1..9 | 19 | 10..18 (default = '%d')\n", gfs);
|
||||
printf(" -P --pilot tone | positive | negative | <file>=<on>:<off>\n");
|
||||
printf(" Send a tone, give a signal or write to a file when switching to\n");
|
||||
printf(" channel 19. (paging the phone).\n");
|
||||
printf(" 'tone', 'positive', 'negative' is sent on right audio channel.\n");
|
||||
printf(" 'tone' sends a tone whenever channel 19 is switchted.\n");
|
||||
printf(" 'positive' sends a positive signal for channel 19, else negative.\n");
|
||||
printf(" 'negative' sends a negative signal for channel 19, else positive.\n");
|
||||
printf(" Example: /sys/class/gpio/gpio17/value=1:0 writes a '1' to\n");
|
||||
printf(" /sys/class/gpio/gpio17/value to switching to channel 19 and a '0' to\n");
|
||||
printf(" switch back. (default = %s)\n", pilot);
|
||||
printf("\nstation-id: Give 5 digit station-id, you don't need to enter it for every\n");
|
||||
printf(" start of this program.\n");
|
||||
}
|
||||
|
||||
static int handle_options(int argc, char **argv)
|
||||
{
|
||||
int skip_args = 0;
|
||||
|
||||
static struct option long_options_special[] = {
|
||||
{"gfs", 1, 0, 'g'},
|
||||
{"pilot", 1, 0, 'P'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
set_options_common("g:P:", long_options_special);
|
||||
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
|
||||
c = getopt_long(argc, argv, optstring, long_options, &option_index);
|
||||
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'g':
|
||||
gfs = atoi(optarg);
|
||||
skip_args += 2;
|
||||
break;
|
||||
case 'P':
|
||||
pilot = strdup(optarg);
|
||||
skip_args += 2;
|
||||
break;
|
||||
default:
|
||||
opt_switch_common(c, argv[0], &skip_args);
|
||||
}
|
||||
}
|
||||
|
||||
return skip_args;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc;
|
||||
int skip_args;
|
||||
const char *station_id = "";
|
||||
|
||||
skip_args = handle_options(argc, argv);
|
||||
argc -= skip_args;
|
||||
argv += skip_args;
|
||||
|
||||
if (argc > 1) {
|
||||
station_id = argv[1];
|
||||
if (strlen(station_id) != 5) {
|
||||
printf("Given station ID '%s' does not have 5 digits\n", station_id);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kanal) {
|
||||
printf("No channel (\"Kanal\") is specified, I suggest channel 1.\n\n");
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!loopback)
|
||||
print_image();
|
||||
|
||||
/* init functions */
|
||||
if (use_mncc_sock) {
|
||||
rc = mncc_init("/tmp/bsc_mncc");
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
init_ansage_27();
|
||||
dsp_init();
|
||||
bnetz_init();
|
||||
rc = call_init(station_id, call_sounddev, samplerate, latency, loopback);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* create transceiver instance */
|
||||
rc = bnetz_create(sounddev, samplerate, kanal, gfs, loopback, (double)lossdetect / 100.0, pilot);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
printf("Base station ready, please tune transmitter to %.3f MHz and receiver "
|
||||
"to %.3f MHz.\n", bnetz_kanal2freq(kanal, 0),
|
||||
bnetz_kanal2freq(kanal, 1));
|
||||
printf("To call phone, switch transmitter (using pilot signal) to %.3f MHz.\n", bnetz_kanal2freq(19, 0));
|
||||
|
||||
signal(SIGINT,sighandler);
|
||||
signal(SIGHUP,sighandler);
|
||||
signal(SIGTERM,sighandler);
|
||||
signal(SIGPIPE,sighandler);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
int rc;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = rt_prio;
|
||||
rc = sched_setscheduler(0, SCHED_RR, &schedp);
|
||||
if (rc)
|
||||
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
|
||||
}
|
||||
|
||||
main_loop(&quit, latency);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_OTHER, &schedp);
|
||||
}
|
||||
|
||||
fail:
|
||||
/* cleanup functions */
|
||||
call_cleanup();
|
||||
if (use_mncc_sock)
|
||||
mncc_exit();
|
||||
|
||||
/* destroy transceiver instance */
|
||||
while(sender_head)
|
||||
bnetz_destroy(sender_head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user