From 946c9ce10a92a29584d2e68e9c04fe63dcee7bdc Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Tue, 1 Mar 2016 18:40:38 +0100 Subject: initial git import --- src/anetz/anetz.c | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 src/anetz/anetz.c (limited to 'src/anetz/anetz.c') diff --git a/src/anetz/anetz.c b/src/anetz/anetz.c new file mode 100644 index 0000000..a68faa9 --- /dev/null +++ b/src/anetz/anetz.c @@ -0,0 +1,458 @@ +/* A-Netz protocol handling + * + * (C) 2016 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 "../common/debug.h" +#include "../common/timer.h" +#include "../common/call.h" +#include "../common/cause.h" +#include "anetz.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; + +/* Timers */ +#define PAGING_TO 30 /* Nach dieser Zeit ist der Operator genervt... */ + +/* Convert channel number to frequency number of base station. + Set 'unterband' to 1 to get frequency of mobile station. */ +double anetz_kanal2freq(int kanal, int unterband) +{ + double freq = 162.050; + + freq += (kanal - 30) * 0.050; + if (kanal >= 45) + freq += 6.800; + if (unterband) + freq -= 4.500; + + return freq; +} + +/* Convert paging frequency number to to frequency. */ +static double anetz_dauerruf_frq(int n) +{ + if (n < 1 || n > 30) + abort(); + + return 337.5 + (double)n * 15.0; +} + +/* Table with frequency sets to use for paging. */ +static struct anetz_dekaden { + int dekade[4]; +} anetz_gruppenkennziffer[10] = { + { { 2, 2, 3, 3 } }, /* 0 */ + { { 1, 1, 2, 2 } }, /* 1 */ + { { 1, 1, 3, 3 } }, /* 2 */ + { { 1, 1, 2, 3 } }, /* 3 */ + { { 1, 2, 2, 3 } }, /* 4 */ + { { 1, 2, 3, 3 } }, /* 5 */ + { { 1, 1, 1, 2 } }, /* 6 */ + { { 1, 1, 1, 3 } }, /* 7 */ + { { 2, 2, 2, 3 } }, /* 8 */ + { { 1, 2, 2, 2 } }, /* 9 */ +}; + +/* Takes the last 5 digits of a number and returns 4 paging tones. + If number is invalid, NULL is returned. */ +static double *anetz_nummer2freq(const char *nummer) +{ + int f[4]; + static double freq[4]; + int *dekade; + int i, j, digit; + + /* get last 5 digits */ + if (strlen(nummer) < 5) { + PDEBUG(DANETZ, DEBUG_ERROR, "Number must have at least 5 digits!\n"); + return NULL; + } + nummer = nummer + strlen(nummer) - 5; + + /* check for digits */ + for (i = 0; i < 4; i++) { + if (nummer[i] < '0' || nummer[i] > '9') { + PDEBUG(DANETZ, DEBUG_ERROR, "Number must have digits 0..9!\n"); + return NULL; + } + } + + /* get decade */ + dekade = anetz_gruppenkennziffer[*nummer - '0'].dekade; + PDEBUG(DANETZ, DEBUG_DEBUG, "Dekaden: %d %d %d %d\n", dekade[0], dekade[1], dekade[2], dekade[3]); + nummer++; + + /* get 4 frequencies out of decades */ + for (i = 0; i < 4; i++) { + digit = nummer[i] - '0'; + if (digit == 0) + digit = 10; + f[i] = (dekade[i] - 1) * 10 + digit; + freq[i] = anetz_dauerruf_frq(f[i]); + for (j = 0; j < i; j++) { + if (dekade[i] == dekade[j] && nummer[i] == nummer[j]) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Number invalid, digit #%d and #%d of '%s' use same frequency F%d=%.1f of same decade %d!\n", i+1, j+1, nummer, f[i], freq[i], dekade[i]); + return NULL; + } + } + } + PDEBUG(DANETZ, DEBUG_DEBUG, "Frequencies: F%d=%.1f F%d=%.1f F%d=%.1f F%d=%.1f\n", f[0], freq[0], f[1], freq[1], f[2], freq[2], f[3], freq[3]); + + return freq; +} + +/* global init */ +int anetz_init(void) +{ + return 0; +} + +static void anetz_timeout(struct timer *timer); + +/* Create transceiver instance and link to a list. */ +int anetz_create(const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume) +{ + anetz_t *anetz; + int rc; + + if (kanal < 30 || kanal > 63) { + PDEBUG(DANETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal); + return -EINVAL; + } + + anetz = calloc(1, sizeof(anetz_t)); + if (!anetz) { + PDEBUG(DANETZ, DEBUG_ERROR, "No memory!\n"); + return -EIO; + } + + PDEBUG(DANETZ, DEBUG_DEBUG, "Creating 'A-Netz' instance for 'Kanal' = %d (sample rate %d).\n", kanal, samplerate); + + /* init general part of transceiver */ + rc = sender_create(&anetz->sender, sounddev, samplerate, kanal, loopback, loss_volume, -1); + if (rc < 0) { + PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init 'Sender' processing!\n"); + goto error; + } + + /* init audio processing */ + rc = dsp_init_sender(anetz); + if (rc < 0) { + PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init signal processing!\n"); + goto error; + } + + /* go into idle state */ + PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n"); + anetz->state = ANETZ_FREI; + anetz->dsp_mode = DSP_MODE_TONE; + timer_init(&anetz->timer, anetz_timeout, anetz); + + return 0; + +error: + anetz_destroy(&anetz->sender); + + return rc; +} + +/* Destroy transceiver instance and unlink from list. */ +void anetz_destroy(sender_t *sender) +{ + anetz_t *anetz = (anetz_t *) sender; + + PDEBUG(DANETZ, DEBUG_DEBUG, "Destroying 'A-Netz' instance for 'Kanal' = %d.\n", sender->kanal); + + timer_exit(&anetz->timer); + dsp_cleanup_sender(anetz); + sender_destroy(&anetz->sender); + free(sender); +} + +/* Abort connection towards mobile station by sending idle tone. */ +static void anetz_go_idle(anetz_t *anetz) +{ + timer_stop(&anetz->timer); + + PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n"); + anetz->state = ANETZ_FREI; + anetz->dsp_mode = DSP_MODE_TONE; + anetz->station_id[0] = '\0'; +} + +/* Enter paging state and transmit 4 paging tones. */ +static void anetz_page(anetz_t *anetz, const char *dial_string, double *freq) +{ + PDEBUG(DANETZ, DEBUG_INFO, "Entering paging state, sending 'Selektivruf' to '%s'.\n", dial_string); + anetz->state = ANETZ_ANRUF; + anetz->dsp_mode = DSP_MODE_PAGING; + dsp_set_paging(anetz, freq); + strcpy(anetz->station_id, dial_string); + timer_start(&anetz->timer, PAGING_TO); +} + +/* Loss of signal was detected, release active call. */ +void anetz_loss_indication(anetz_t *anetz) +{ + if (anetz->state == ANETZ_GESPRAECH) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n"); + anetz_go_idle(anetz); + call_in_release(anetz->sender.callref, CAUSE_TEMPFAIL); + anetz->sender.callref = 0; + } +} + +/* A continuous tone was detected or is gone. */ +void anetz_receive_tone(anetz_t *anetz, int tone) +{ + if (tone >= 0) + PDEBUG(DANETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (tone) ? 1750 : 2280); + else + PDEBUG(DANETZ, DEBUG_DEBUG, "Continuous tone is gone.\n"); + + if (anetz->sender.loopback) { + return; + } + + switch (anetz->state) { + case ANETZ_FREI: + /* initiate call on calling tone */ + if (tone == 1) { + PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz calling signal from mobile station, removing idle signal.\n"); + anetz->state = ANETZ_GESPRAECH; + anetz->dsp_mode = DSP_MODE_SILENCE; + break; + } + break; + case ANETZ_GESPRAECH: + /* throughconnect speech when calling/answer tone is gone */ + if (tone != 1) { + if (!anetz->sender.callref) { + int callref = ++new_callref; + int rc; + + PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, setup call.\n"); + rc = call_in_setup(callref, anetz->station_id, "0"); + if (rc < 0) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", -rc); + anetz_go_idle(anetz); + break; + } + anetz->sender.callref = callref; + } else { + PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, answer call.\n"); + call_in_answer(anetz->sender.callref, anetz->station_id); + } + anetz->dsp_mode = DSP_MODE_AUDIO; + } + /* release call */ + if (tone == 1) { + PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz release signal from mobile station, sending idle signal.\n"); + anetz_go_idle(anetz); + call_in_release(anetz->sender.callref, CAUSE_NORMAL); + anetz->sender.callref = 0; + break; + } + break; + case ANETZ_ANRUF: + /* answer call on answer tone */ + if (tone == 1) { + PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz answer signal from mobile station, removing paging tones.\n"); + timer_stop(&anetz->timer); + anetz->state = ANETZ_GESPRAECH; + anetz->dsp_mode = DSP_MODE_SILENCE; + break; + } + default: + break; + } +} + +/* Timeout handling */ +static void anetz_timeout(struct timer *timer) +{ + anetz_t *anetz = (anetz_t *)timer->priv; + + switch (anetz->state) { + case ANETZ_ANRUF: + PDEBUG(DANETZ, DEBUG_NOTICE, "Timeout while waiting for answer, releasing.\n"); + anetz_go_idle(anetz); + call_in_release(anetz->sender.callref, CAUSE_NOANSWER); + anetz->sender.callref = 0; + break; + default: + break; + } +} + +/* Call control starts call towards mobile station. */ +int call_out_setup(int callref, char *dialing) +{ + sender_t *sender; + anetz_t *anetz; + double *freq; + + /* 1. check if number is invalid, return INVALNUMBER */ + if (strlen(dialing) > 7) { +inval: + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing); + return -CAUSE_INVALNUMBER; + } + freq = anetz_nummer2freq(dialing); + if (!freq) + goto inval; + + /* 2. check if given number is already in a call, return BUSY */ + for (sender = sender_head; sender; sender = sender->next) { + anetz = (anetz_t *) sender; + if (strlen(anetz->station_id) < 5) + continue; + if (!strcmp(anetz->station_id + strlen(anetz->station_id) - 5, dialing + strlen(dialing) - 5)) + break; + } + if (sender) { + PDEBUG(DANETZ, 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) { + anetz = (anetz_t *) sender; + if (anetz->state == ANETZ_FREI) + break; + } + if (!sender) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n"); + return -CAUSE_NOCHANNEL; + } + + PDEBUG(DANETZ, DEBUG_INFO, "Call to mobile station, paging with tones: %.1f %.1f %.1f %.1f\n", freq[0], freq[1], freq[2], freq[3]); + + /* 4. trying to page mobile station */ + sender->callref = callref; + anetz_page(anetz, dialing, freq); + + call_in_alerting(callref); + + 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; + anetz_t *anetz; + + PDEBUG(DANETZ, DEBUG_INFO, "Call has been disconnected by network.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + anetz = (anetz_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n"); + call_in_release(callref, CAUSE_INVALCALLREF); + return; + } + + /* Release when not active */ + if (anetz->state == ANETZ_GESPRAECH) + return; + switch (anetz->state) { + case ANETZ_ANRUF: + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, going idle!\n"); + anetz_go_idle(anetz); + 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; + anetz_t *anetz; + + PDEBUG(DANETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n"); + + for (sender = sender_head; sender; sender = sender->next) { + anetz = (anetz_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) { + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n"); + /* don't send release, because caller already released */ + return; + } + + sender->callref = 0; + + switch (anetz->state) { + case ANETZ_GESPRAECH: + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during call, going idle!\n"); + anetz_go_idle(anetz); + break; + case ANETZ_ANRUF: + PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during alerting, going idle!\n"); + anetz_go_idle(anetz); + break; + default: + break; + } +} + +/* Receive audio from call instance. */ +void call_rx_audio(int callref, int16_t *samples, int count) +{ + sender_t *sender; + anetz_t *anetz; + + for (sender = sender_head; sender; sender = sender->next) { + anetz = (anetz_t *) sender; + if (sender->callref == callref) + break; + } + if (!sender) + return; + + if (anetz->dsp_mode == DSP_MODE_AUDIO) { + int16_t up[count * anetz->sender.srstate.factor]; + count = samplerate_upsample(&anetz->sender.srstate, samples, count, up); + jitter_save(&anetz->sender.audio, up, count); + } +} + -- cgit v1.2.3