/* 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); } }