Add R1 system
This commit is contained in:
parent
2483f1c2b1
commit
d0c08f4f7d
|
@ -44,3 +44,4 @@ src/libselect/libselect.a
|
|||
src/libfilter/libfilter.a
|
||||
src/common/libcommon.a
|
||||
src/ss5/osmo-cc-ss5-endpoint
|
||||
src/r1/osmo-cc-r1-endpoint
|
||||
|
|
|
@ -89,5 +89,6 @@ AC_OUTPUT(
|
|||
src/libfilter/Makefile
|
||||
src/common/Makefile
|
||||
src/ss5/Makefile
|
||||
src/r1/Makefile
|
||||
src/Makefile
|
||||
Makefile)
|
||||
|
|
|
@ -11,5 +11,6 @@ SUBDIRS = \
|
|||
libg711 \
|
||||
libfilter \
|
||||
common \
|
||||
ss5
|
||||
ss5 \
|
||||
r1
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-cc-r1-endpoint
|
||||
|
||||
osmo_cc_r1_endpoint_SOURCES = \
|
||||
r1.c \
|
||||
main.c
|
||||
|
||||
osmo_cc_r1_endpoint_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
../common/libcommon.a \
|
||||
../libdebug/libdebug.a \
|
||||
../liboptions/liboptions.a \
|
||||
../libsample/libsample.a \
|
||||
../libtimer/libtimer.a \
|
||||
../libselect/libselect.a \
|
||||
../libjitter/libjitter.a \
|
||||
../libosmocc/libosmocc.a \
|
||||
../libg711/libg711.a \
|
||||
../libfilter/libfilter.a
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
/* osmo-cc-r1-endpoint main
|
||||
*
|
||||
* (C) 2020 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 <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <termios.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../liboptions/options.h"
|
||||
#include "../libg711/g711.h"
|
||||
#include "r1.h"
|
||||
#include "../common/display.h"
|
||||
#include "../common/sit.h"
|
||||
|
||||
r1_endpoint_t *r1_ep_sunset = NULL, *r1_ep_sunrise = NULL;
|
||||
int num_kanal = 2;
|
||||
|
||||
int endpoints = 1;
|
||||
int links = 0;
|
||||
int pulsedialing = 0;
|
||||
int suppress_disconnect = 1;
|
||||
int clear_back_timeout = 60;
|
||||
int crosstalk = 1;
|
||||
int delay_ms = 0;
|
||||
double sense_db = 0;
|
||||
#define MAX_CC_ARGS 1024
|
||||
static int cc_argc_sunset, cc_argc_sunrise = 0;
|
||||
static const char *cc_argv_sunset[MAX_CC_ARGS], *cc_argv_sunrise[MAX_CC_ARGS];
|
||||
|
||||
static void print_usage(const char *app)
|
||||
{
|
||||
printf("Usage: %s [<options>]\n", app);
|
||||
printf("This will create pairs of R1 channels that are bridged together, so that\n");
|
||||
printf("calls from one link to the other can be made using R1. The a bluebox can be\n");
|
||||
printf("used to play with it.\n");
|
||||
printf("If one endpoint is used (default), its name is 'sunset' and each pair of\n");
|
||||
printf("channels are bridged together. If two endpoints are used, their names are\n");
|
||||
printf("'sunset' and 'sunrise' and same channel index of both endpoints are bridged\n");
|
||||
printf("together.\n");
|
||||
}
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
/* - - */
|
||||
printf(" -h --help\n");
|
||||
printf(" This help\n");
|
||||
printf(" --config [~/]<path to config file>\n");
|
||||
printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
|
||||
printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
|
||||
debug_print_help();
|
||||
printf(" -2 --two\n");
|
||||
printf(" Create two Osmo-CC endpoints instead of one.\n");
|
||||
printf(" -c --channels\n");
|
||||
printf(" Give number of channels per endpoint. If you use a single endpoint,\n");
|
||||
printf(" you must define an even number. By default this is '2' for one\n");
|
||||
printf(" endpoint and '1' for two endpoints.\n");
|
||||
printf(" -s --suppress-disconnect 1 | 0\n");
|
||||
printf(" When a 'busy-flash' or 'release-guard' is received a disconnect is\n");
|
||||
printf(" forwarded towards OsmoCC. Set to 1 to suppress this. (Default is %d.)\n", suppress_disconnect);
|
||||
printf(" -p --pulse-dialing 1 | 0\n");
|
||||
printf(" Send/receive digits via pulse dialing (SF) instead of tones (MF).\n");
|
||||
printf(" -x --crosstalk 1 | 0\n");
|
||||
printf(" Enable or disable some minor crosstalk. This allows you to hear\n");
|
||||
printf(" transmitted tones at a low volume. (Default is %d.)\n", crosstalk);
|
||||
printf(" -d --delay <ms> | 0\n");
|
||||
printf(" Add one-way delay to the connection between two R1 links. This allows\n");
|
||||
printf(" to hear 'acknowlege' tones delayed. (Default is %d ms.)\n", delay_ms);
|
||||
printf(" -T --clear-back-timeout 0 | <seconds>\n");
|
||||
printf(" At the outgoing end: Time to wait while receiving clear-back (hangup)\n");
|
||||
printf(" signal until releasing call. Use 0 to disable. (Default is %d s.)\n", clear_back_timeout);
|
||||
printf(" --sense 0 | <db>\n");
|
||||
printf(" Increase sensitivity of tone detector. A bluebox can have lower level\n");
|
||||
printf(" than what the standard requires. (Default is %.0f dB.)\n", sense_db);
|
||||
printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
|
||||
printf(" --cc2 \"<osmo-cc arg>\" [--cc2 ...]\n");
|
||||
printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
|
||||
printf(" If you select two endpoints, use '--cc2' to pass arguments to the\n");
|
||||
printf(" second endpoint.\n");
|
||||
}
|
||||
|
||||
#define OPT_SENSE 256
|
||||
#define OPT_CC2 257
|
||||
|
||||
static void add_options(void)
|
||||
{
|
||||
option_add('h', "help", 0);
|
||||
option_add('v', "verbose", 1);
|
||||
option_add('2', "two", 0);
|
||||
option_add('c', "channels", 1);
|
||||
option_add('s', "suppress-disconnect", 1);
|
||||
option_add('p', "pulse-dialing", 1);
|
||||
option_add('x', "crosstalk", 1);
|
||||
option_add('d', "delay", 1);
|
||||
option_add('T', "clear-back-timeout", 1);
|
||||
option_add(OPT_SENSE, "sense", 1);
|
||||
option_add('C', "cc", 1);
|
||||
option_add(OPT_CC2, "cc2", 1);
|
||||
}
|
||||
|
||||
static int handle_options(int short_option, int argi, char **argv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
switch (short_option) {
|
||||
case 'h':
|
||||
print_usage(argv[0]);
|
||||
print_help();
|
||||
return 0;
|
||||
case 'v':
|
||||
if (!strcasecmp(argv[argi], "list")) {
|
||||
debug_list_cat();
|
||||
return 0;
|
||||
}
|
||||
rc = parse_debug_opt(argv[argi]);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
|
||||
return rc;
|
||||
}
|
||||
break;
|
||||
case '2':
|
||||
endpoints = 2;
|
||||
break;
|
||||
case 'c':
|
||||
links = atoi(argv[argi]);
|
||||
break;
|
||||
case 's':
|
||||
suppress_disconnect = atoi(argv[argi]);
|
||||
break;
|
||||
case 'p':
|
||||
pulsedialing = atoi(argv[argi]);
|
||||
break;
|
||||
case 'x':
|
||||
crosstalk = atoi(argv[argi]);
|
||||
break;
|
||||
case 'd':
|
||||
delay_ms = atoi(argv[argi]);
|
||||
break;
|
||||
case 'T':
|
||||
clear_back_timeout = atoi(argv[argi]);
|
||||
break;
|
||||
case OPT_SENSE:
|
||||
sense_db = (double)atoi(argv[argi]);
|
||||
break;
|
||||
case 'C':
|
||||
if (!strcasecmp(argv[argi], "help")) {
|
||||
osmo_cc_help();
|
||||
return 0;
|
||||
}
|
||||
if (cc_argc_sunset == MAX_CC_ARGS) {
|
||||
fprintf(stderr, "Too many osmo-cc args!\n");
|
||||
break;
|
||||
}
|
||||
cc_argv_sunset[cc_argc_sunset++] = options_strdup(argv[argi]);
|
||||
break;
|
||||
case OPT_CC2:
|
||||
if (!strcasecmp(argv[argi], "help")) {
|
||||
osmo_cc_help();
|
||||
return 0;
|
||||
}
|
||||
if (cc_argc_sunrise == MAX_CC_ARGS) {
|
||||
fprintf(stderr, "Too many osmo-cc args!\n");
|
||||
break;
|
||||
}
|
||||
cc_argv_sunrise[cc_argc_sunrise++] = options_strdup(argv[argi]);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int quit = 0;
|
||||
void sighandler(int sigset)
|
||||
{
|
||||
if (sigset == SIGHUP || sigset == SIGPIPE)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "\nSignal %d received.\n", sigset);
|
||||
|
||||
quit = 1;
|
||||
}
|
||||
|
||||
static int get_char()
|
||||
{
|
||||
struct timeval tv = {0, 0};
|
||||
fd_set fds;
|
||||
char c = 0;
|
||||
int __attribute__((__unused__)) rc;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(0, &fds);
|
||||
select(0+1, &fds, NULL, NULL, &tv);
|
||||
if (FD_ISSET(0, &fds)) {
|
||||
rc = read(0, &c, 1);
|
||||
return c;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct timer clock_timer;
|
||||
double last_time_clock = 0;
|
||||
|
||||
static void clock_timeout(void __attribute__((unused)) *data)
|
||||
{
|
||||
double now;
|
||||
int c;
|
||||
|
||||
/* add timer to wait for next 20ms */
|
||||
now = get_time();
|
||||
if (now - last_time_clock >= 0.1)
|
||||
last_time_clock = now;
|
||||
last_time_clock += 0.020;
|
||||
if (last_time_clock < now)
|
||||
last_time_clock = now;
|
||||
timer_start(&clock_timer, last_time_clock - now);
|
||||
|
||||
/* call audio clock every 20ms */
|
||||
audio_clock((r1_ep_sunset) ? r1_ep_sunset->dsp_list : NULL, (r1_ep_sunrise) ? r1_ep_sunrise->dsp_list : NULL, 160);
|
||||
|
||||
/* process keyboard input */
|
||||
c = get_char();
|
||||
switch (c) {
|
||||
case 3:
|
||||
printf("CTRL+c received, quitting!\n");
|
||||
quit = 1;
|
||||
break;
|
||||
case 'c':
|
||||
display_status_on(-1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int argi, rc;
|
||||
struct termios term, term_orig;
|
||||
|
||||
/* init MF */
|
||||
mf_init(0);
|
||||
|
||||
/* init codecs */
|
||||
g711_init();
|
||||
|
||||
/* init sit */
|
||||
init_sit(8000);
|
||||
|
||||
/* init dsp */
|
||||
dsp_set_sf(-8.0, -27.0);
|
||||
|
||||
cc_argv_sunset[cc_argc_sunset++] = options_strdup("remote auto");
|
||||
cc_argv_sunrise[cc_argc_sunrise++] = options_strdup("remote auto");
|
||||
|
||||
/* handle options / config file */
|
||||
add_options();
|
||||
rc = options_config_file(argc, argv, "~/.osmocom/r1/r1.conf", handle_options);
|
||||
if (rc < 0)
|
||||
return 0;
|
||||
argi = options_command_line(argc, argv, handle_options);
|
||||
if (argi <= 0)
|
||||
return argi;
|
||||
|
||||
/* check links (per endpoint) */
|
||||
if (!links)
|
||||
links = (endpoints == 2) ? 1 : 2;
|
||||
if (links == 1 && (endpoints % 1)) {
|
||||
PDEBUG(DR1, DEBUG_ERROR, "You must define an even number of channels on a single endpoint!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* create sunset and (optionally) sunrise */
|
||||
r1_ep_sunset = r1_ep_create("sunset", links, suppress_disconnect, clear_back_timeout, crosstalk, delay_ms, sense_db, pulsedialing);
|
||||
if (!r1_ep_sunset)
|
||||
goto error;
|
||||
rc = osmo_cc_new(&r1_ep_sunset->cc_ep, OSMO_CC_VERSION, "sunset", OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_message, NULL, r1_ep_sunset, cc_argc_sunset, cc_argv_sunset);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
if (endpoints == 2) {
|
||||
r1_ep_sunrise = r1_ep_create("sunrise", links, suppress_disconnect, clear_back_timeout, crosstalk, delay_ms, sense_db, pulsedialing);
|
||||
if (!r1_ep_sunrise)
|
||||
goto error;
|
||||
rc = osmo_cc_new(&r1_ep_sunrise->cc_ep, OSMO_CC_VERSION, "sunrise", OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_message, NULL, r1_ep_sunrise, cc_argc_sunrise, cc_argv_sunrise);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
PDEBUG(DR1, DEBUG_NOTICE, "Created endpoints 'sunset' and 'sunrise' with %d links that connect these endpoints.\n", links);
|
||||
} else
|
||||
PDEBUG(DR1, DEBUG_NOTICE, "Created endpoint 'sunset' with %d links, each pair connected.\n", links);
|
||||
refresh_status();
|
||||
|
||||
/* init clock timer for clocking call */
|
||||
timer_init(&clock_timer, clock_timeout, NULL);
|
||||
timer_start(&clock_timer, 0.020);
|
||||
|
||||
/* prepare terminal */
|
||||
tcgetattr(0, &term_orig);
|
||||
term = term_orig;
|
||||
term.c_lflag &= ~(ISIG|ICANON|ECHO);
|
||||
term.c_cc[VMIN]=1;
|
||||
term.c_cc[VTIME]=2;
|
||||
tcsetattr(0, TCSANOW, &term);
|
||||
|
||||
/* catch signals */
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGHUP, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
signal(SIGPIPE, sighandler);
|
||||
|
||||
while (!quit) {
|
||||
int work;
|
||||
double timeout;
|
||||
|
||||
do {
|
||||
work = 0;
|
||||
work |= osmo_cc_handle();
|
||||
} while (work);
|
||||
/* handle all timers
|
||||
* timeout is 0, if there was an event
|
||||
* -> handle FDs without waiting, continue this loop
|
||||
* timeout is not 0, if there was no event
|
||||
* -> wait until FD or timeout
|
||||
*/
|
||||
timeout = process_timer();
|
||||
/* wait for FD event until given timeout */
|
||||
osmo_fd_select(timeout);
|
||||
}
|
||||
|
||||
/* reset signals */
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGTSTP, SIG_DFL);
|
||||
signal(SIGHUP, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
|
||||
/* reset terminal */
|
||||
tcsetattr(0, TCSANOW, &term_orig);
|
||||
|
||||
error:
|
||||
timer_exit(&clock_timer);
|
||||
|
||||
/* destroy endpoints */
|
||||
if (r1_ep_sunset) {
|
||||
osmo_cc_delete(&r1_ep_sunset->cc_ep);
|
||||
r1_ep_destroy(r1_ep_sunset);
|
||||
}
|
||||
if (r1_ep_sunrise) {
|
||||
osmo_cc_delete(&r1_ep_sunrise->cc_ep);
|
||||
r1_ep_destroy(r1_ep_sunrise);
|
||||
}
|
||||
|
||||
/* exit sit */
|
||||
exit_sit();
|
||||
|
||||
/* exit MF */
|
||||
mf_exit();
|
||||
|
||||
options_free();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,837 @@
|
|||
/* R1 process
|
||||
*
|
||||
* (C) 2023 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/>.
|
||||
*/
|
||||
|
||||
#define CHAN r1->name
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "../libdebug/debug.h"
|
||||
#include "../libg711/g711.h"
|
||||
#include "r1.h"
|
||||
#include "../common/display.h"
|
||||
#include "../common/common.h"
|
||||
|
||||
/* names of all R1 states */
|
||||
const char *r1_state_names[] = {
|
||||
"NULL",
|
||||
/* idle line */
|
||||
"IDLE",
|
||||
/* outgoing call */
|
||||
"SEIZING",
|
||||
"OUT DELAY-DIALING",
|
||||
"SEND DIGITS",
|
||||
"OUT PROCEED",
|
||||
"OUT ANSWER",
|
||||
"OUT HANGUP",
|
||||
/* incoming call */
|
||||
"IN DELAY-DIALING",
|
||||
"RECV DIGIT WAIT",
|
||||
"RECV DIGIT ON",
|
||||
"RECV PULSE WAIT",
|
||||
"RECV PULSE ON",
|
||||
"RECV PULSE OFF",
|
||||
"IN PROCEED",
|
||||
"IN ANSWER",
|
||||
"IN HANGUP",
|
||||
};
|
||||
|
||||
/* timers and durations */
|
||||
#define INTERRUPT_RECOGNITION 0.030 /* an interruption of 30 ms is considered to be a stop of digit */
|
||||
#define SPLIT_RECOGNITION 0.015 /* time until received audio is split (band-stop filter turned on) */
|
||||
#define MF_RECOGNITION 0.025
|
||||
#define KP_DIGIT_DURATION 0.100
|
||||
#define OTHER_DIGIT_DURATION 0.068
|
||||
#define DIGIT_PAUSE 0.068
|
||||
#define PULSE_PAUSE 0.600
|
||||
#define SIGN_RECOGNITION_FAST 0.030 /* all tones except clear forward */
|
||||
#define SIGN_RECOGNITION_SLOW 0.300 /* clear forward after providing a register */
|
||||
#define HANGUP_TIMEOUT 0.013 /* timeout after receiving hangup, then disconnect */
|
||||
#define CLEAR_FORWARD 0.300 /* tone on for at least 300ms to recognise as clear-forward */
|
||||
#define TO_SEIZING 5.0 /* timeout waiting for a register (double seizure detection) */
|
||||
#define TO_DELAY_DIALING 0.140 /* time to wait until dialing register is available */
|
||||
#define TO_DIALING 5.0 /* timeout when receiving no additional digits */
|
||||
#define TO_PROCEEDING 60.0 /* timeout when there is no answer */
|
||||
#define TO_PULSE 0.300 /* after this time the pulse is too long or states a new digit */
|
||||
#define TO_GUARD_BUSY 1.250 /* guard time to wait until outgoing calls are allowed */
|
||||
|
||||
static struct osmo_cc_helper_audio_codecs codecs[] = {
|
||||
{ "L16", 8000, 1, encode_l16, decode_l16 },
|
||||
{ "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
|
||||
{ "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
|
||||
{ NULL, 0, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
void refresh_status(void)
|
||||
{
|
||||
osmo_cc_endpoint_t *ep;
|
||||
r1_endpoint_t *r1_ep;
|
||||
dsp_t *dsp;
|
||||
r1_t *r1;
|
||||
int i;
|
||||
|
||||
display_status_start();
|
||||
|
||||
for (ep = osmo_cc_endpoint_list; ep; ep = ep->next) {
|
||||
r1_ep = ep->priv;
|
||||
if (!r1_ep->dsp_list)
|
||||
display_status_line(ep->local_name, 0, NULL, NULL, "");
|
||||
for (i = 0, dsp = r1_ep->dsp_list; dsp; i++, dsp = dsp->next) {
|
||||
r1 = dsp->priv;
|
||||
display_status_line(ep->local_name, i, r1->callerid, r1->dialing, r1_state_names[r1->state]);
|
||||
}
|
||||
}
|
||||
|
||||
display_status_end();
|
||||
}
|
||||
|
||||
#define r1_new_state(r1, state) _r1_new_state(r1, state, __LINE__)
|
||||
void _r1_new_state(r1_t *r1, enum r1_state state, int line)
|
||||
{
|
||||
PDEBUG_CHAN(DR1, DEBUG_DEBUG, "Changing state '%s' -> '%s' (called from line %d)\n", r1_state_names[r1->state], r1_state_names[state], line);
|
||||
r1->state = state;
|
||||
|
||||
/* must update (new state) */
|
||||
refresh_status();
|
||||
}
|
||||
|
||||
/*
|
||||
* endpoints & links
|
||||
*/
|
||||
|
||||
/* reset R1 link, but keep states, so audio generation/processing can continue */
|
||||
static void link_reset(r1_t *r1)
|
||||
{
|
||||
/* unlink callref */
|
||||
r1->dsp.cc_callref = 0;
|
||||
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
|
||||
/* free session description */
|
||||
if (r1->dsp.cc_session) {
|
||||
osmo_cc_free_session(r1->dsp.cc_session);
|
||||
r1->dsp.cc_session = NULL;
|
||||
r1->dsp.codec = NULL;
|
||||
}
|
||||
|
||||
/* reset jitter buffer */
|
||||
jitter_reset(&r1->dsp.tx_dejitter);
|
||||
|
||||
/* set recognition time */
|
||||
set_sig_detect_duration(&r1->dsp, SIGN_RECOGNITION_FAST, 0.0);
|
||||
|
||||
/* make tone gerator non-transparent */
|
||||
set_tone_transparent(&r1->dsp, 0);
|
||||
|
||||
/* reset all other states */
|
||||
r1->callerid[0] = '\0';
|
||||
r1->dialing[0] = '\0';
|
||||
|
||||
/* must update (e.g. caller and dialing) */
|
||||
refresh_status();
|
||||
}
|
||||
|
||||
static void r1_timeout(void *data);
|
||||
|
||||
static r1_t *link_create(r1_endpoint_t *r1_ep, const char *ep_name, int linkid, double samplerate, int crosstalk, int delay_ms, double sense_db, int pulsedialing)
|
||||
{
|
||||
r1_t *r1;
|
||||
dsp_t **dsp_p;
|
||||
|
||||
r1 = calloc(1, sizeof(*r1));
|
||||
if (!r1) {
|
||||
PDEBUG(DR1, DEBUG_ERROR, "No memory!\n");
|
||||
abort();
|
||||
}
|
||||
r1->r1_ep = r1_ep;
|
||||
r1->pulsedialing = pulsedialing;
|
||||
|
||||
/* debug name */
|
||||
snprintf(r1->name, sizeof(r1->name) - 1, "%s/%d", ep_name, linkid);
|
||||
|
||||
/* init dsp instance */
|
||||
dsp_init_inst(&r1->dsp, r1, r1->name, samplerate, crosstalk, 0, delay_ms, sense_db, INTERRUPT_RECOGNITION, SPLIT_RECOGNITION, MF_RECOGNITION, KP_DIGIT_DURATION, OTHER_DIGIT_DURATION, DIGIT_PAUSE, PULSE_PAUSE, 2600.0, 0);
|
||||
|
||||
/* init timer */
|
||||
timer_init(&r1->timer, r1_timeout, r1);
|
||||
|
||||
/* reset instance */
|
||||
link_reset(r1);
|
||||
|
||||
/* Turn on SF */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
|
||||
/* link */
|
||||
dsp_p = &r1_ep->dsp_list;
|
||||
while (*dsp_p)
|
||||
dsp_p = &((*dsp_p)->next);
|
||||
*dsp_p = &r1->dsp;
|
||||
|
||||
PDEBUG_CHAN(DR1, DEBUG_DEBUG, "created R1 instance\n");
|
||||
|
||||
return r1;
|
||||
}
|
||||
|
||||
static void link_destroy(r1_t *r1)
|
||||
{
|
||||
dsp_t **dsp_p;
|
||||
|
||||
/* detach */
|
||||
dsp_p = &r1->r1_ep->dsp_list;
|
||||
while (*dsp_p) {
|
||||
if (*dsp_p == &r1->dsp)
|
||||
break;
|
||||
dsp_p = &((*dsp_p)->next);
|
||||
}
|
||||
*dsp_p = r1->dsp.next;
|
||||
|
||||
/* state idle */
|
||||
r1_new_state(r1, R1_STATE_NULL);
|
||||
|
||||
/* reset instance */
|
||||
link_reset(r1);
|
||||
|
||||
/* exit timer */
|
||||
timer_exit(&r1->timer);
|
||||
|
||||
/* cleanup dsp instance */
|
||||
dsp_cleanup_inst(&r1->dsp);
|
||||
|
||||
PDEBUG_CHAN(DR1, DEBUG_DEBUG, "destroyed R1 instance\n");
|
||||
|
||||
free(r1);
|
||||
}
|
||||
|
||||
r1_endpoint_t *r1_ep_create(const char *ep_name, int links, int suppress_disconnect, int clear_back_timeout, int crosstalk, int delay_ms, double sense_db, int pulsedialing)
|
||||
{
|
||||
r1_endpoint_t *r1_ep;
|
||||
int i;
|
||||
|
||||
r1_ep = calloc(1, sizeof(*r1_ep));
|
||||
if (!r1_ep) {
|
||||
PDEBUG(DR1, DEBUG_ERROR, "No memory!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
r1_ep->suppress_disconnect = suppress_disconnect;
|
||||
r1_ep->clear_back_timeout = clear_back_timeout;
|
||||
|
||||
for (i = 0; i < links; i++)
|
||||
link_create(r1_ep, ep_name, i + 1, 8000.0, crosstalk, delay_ms, sense_db, pulsedialing);
|
||||
|
||||
PDEBUG(DR1, DEBUG_DEBUG, "R1 endpoint instance created\n");
|
||||
|
||||
return r1_ep;
|
||||
}
|
||||
|
||||
void r1_ep_destroy(r1_endpoint_t *r1_ep)
|
||||
{
|
||||
/* destroy all calls */
|
||||
while (r1_ep->dsp_list)
|
||||
link_destroy(r1_ep->dsp_list->priv);
|
||||
|
||||
free(r1_ep);
|
||||
|
||||
PDEBUG(DR1, DEBUG_DEBUG, "R1 endpoint instance destroyed\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* event handling
|
||||
*/
|
||||
|
||||
void setup_call(r1_t *r1)
|
||||
{
|
||||
osmo_cc_msg_t *msg;
|
||||
uint8_t type;
|
||||
char dialing[sizeof(r1->dialing)];
|
||||
int rc;
|
||||
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Dialing '%s' is complete, sending setup message towards call control.\n", r1->dialing);
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* check and convert dial string, only do that for MF dialing */
|
||||
if (r1->pulsedialing) {
|
||||
type = OSMO_CC_TYPE_UNKNOWN;
|
||||
strcpy(dialing, r1->dialing);
|
||||
} else {
|
||||
rc = parse_dial_string(&type, dialing, sizeof(dialing), r1->dialing);
|
||||
if (rc < 0) {
|
||||
/* state hangup */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* setup message */
|
||||
msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND);
|
||||
/* network type */
|
||||
osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_R1_NONE, "");
|
||||
/* called number */
|
||||
osmo_cc_add_ie_called(msg, type, OSMO_CC_PLAN_TELEPHONY, dialing);
|
||||
/* sdp offer */
|
||||
r1->dsp.cc_session = osmo_cc_helper_audio_offer(&r1->r1_ep->cc_ep.session_config, &r1->dsp, codecs, down_audio, msg, 1);
|
||||
if (!r1->dsp.cc_session) {
|
||||
osmo_cc_free_msg(msg);
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "Failed to offer audio, sending 'clear-back'.\n");
|
||||
/* state hangup */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
return;
|
||||
}
|
||||
/* create new call */
|
||||
osmo_cc_call_t *cc_call = osmo_cc_call_new(&r1->r1_ep->cc_ep);
|
||||
r1->dsp.cc_callref = cc_call->callref;
|
||||
/* send message to CC */
|
||||
osmo_cc_ll_msg(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, msg);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IN_PROCEED);
|
||||
}
|
||||
|
||||
/* function that receives the signal or the cease of it (' ' or different signal) */
|
||||
void receive_signal(void *priv, char signal, double dbm)
|
||||
{
|
||||
r1_t *r1 = priv;
|
||||
const char *state_name = r1_state_names[r1->state];
|
||||
int i;
|
||||
|
||||
if (signal > ' ')
|
||||
PDEBUG_CHAN(DR1, DEBUG_DEBUG, "Received signal '%c' in '%s' state. (%.1f dBm)\n", signal, state_name, dbm);
|
||||
else
|
||||
PDEBUG_CHAN(DR1, DEBUG_DEBUG, "Received cease of signal in '%s' state.\n", state_name);
|
||||
|
||||
switch (r1->state) {
|
||||
/* initial SF when link is connected */
|
||||
case R1_STATE_NULL:
|
||||
if (signal == 'B') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'Idle' signal in '%s' state, line is connected, going idle.\n", state_name);
|
||||
/* state idle */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
}
|
||||
break;
|
||||
/* outgoing call sends seize */
|
||||
case R1_STATE_SEIZING:
|
||||
if (signal != 'B') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'Delay-Dialing' signal in '%s' state, waiting for dial register..\n", state_name);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_SEIZING);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_OUT_DELAY_DIALING);
|
||||
}
|
||||
break;
|
||||
/* outgoing call receives delay-dialing */
|
||||
case R1_STATE_OUT_DELAY_DIALING:
|
||||
if (signal == 'B') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'proceed-to-send' signal in '%s' state, send digits.\n", state_name);
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* dial */
|
||||
set_dial_string(&r1->dsp, r1->dialing, r1->pulsedialing);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_SEND_DIGITS);
|
||||
}
|
||||
break;
|
||||
/* outgoing call receives answer */
|
||||
case R1_STATE_SEND_DIGITS:
|
||||
case R1_STATE_OUT_PROCEED:
|
||||
if (signal == ' ') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'answer' signal in '%s' state.\n", state_name);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_OUT_ANSWER);
|
||||
/* indicate answer to upper layer */
|
||||
answer_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref);
|
||||
}
|
||||
break;
|
||||
/* outgoing call receives clear-back */
|
||||
case R1_STATE_OUT_ANSWER:
|
||||
if (signal == 'B') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'clear-back' signal in '%s' state'.\n", state_name);
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* start timer */
|
||||
if (r1->r1_ep->clear_back_timeout)
|
||||
timer_start(&r1->timer, r1->r1_ep->clear_back_timeout);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_OUT_HANGUP);
|
||||
if (!r1->r1_ep->suppress_disconnect) {
|
||||
/* indicate disconnect w/tones to upper layer */
|
||||
disconnect_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
/* outgoing call receives answer after clear-back */
|
||||
case R1_STATE_OUT_HANGUP:
|
||||
if (signal == ' ') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'answer' signal in '%s' state.\n", state_name);
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_OUT_ANSWER);
|
||||
}
|
||||
break;
|
||||
/* incoming call receives seize */
|
||||
case R1_STATE_IDLE:
|
||||
if (signal == ' ') {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'seizing' signal in '%s' state, sending 'delay-dialing'.\n", state_name);
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_DELAY_DIALING);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IN_DELAY_DIALING);
|
||||
/* send proceed-to-send */
|
||||
set_tone(&r1->dsp, ' ', 0);
|
||||
/* make tone gerator transparent */
|
||||
set_tone_transparent(&r1->dsp, 1);
|
||||
}
|
||||
break;
|
||||
/* incoming call receives digits */
|
||||
case R1_STATE_RECV_DIGIT_WAIT:
|
||||
if (signal == 'B')
|
||||
goto clear_forward;
|
||||
if (!(signal >= '0' && signal <= '9') && !(signal >= 'a' && signal <= 'c')) {
|
||||
break;
|
||||
}
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Digit '%c' is detected in '%s' state.\n", signal, state_name);
|
||||
/* add digit */
|
||||
i = strlen(r1->dialing);
|
||||
if (i + 1 == sizeof(r1->dialing))
|
||||
break;
|
||||
r1->dialing[i++] = signal;
|
||||
r1->dialing[i] = '\0';
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_RECV_DIGIT_ON);
|
||||
/* restart timer */
|
||||
timer_start(&r1->timer, TO_DIALING);
|
||||
break;
|
||||
case R1_STATE_RECV_DIGIT_ON:
|
||||
if (signal == 'B')
|
||||
goto clear_forward;
|
||||
if (signal != ' ')
|
||||
break;
|
||||
/* check for end of dialing */
|
||||
i = strlen(r1->dialing) - 1;
|
||||
if (r1->dialing[i] == 'c') {
|
||||
/* setup call */
|
||||
setup_call(r1);
|
||||
break;
|
||||
}
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_RECV_DIGIT_WAIT);
|
||||
break;
|
||||
/* incoming call receives first pulse */
|
||||
case R1_STATE_RECV_PULSE_WAIT:
|
||||
if (signal == 'B') {
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_PULSE);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_RECV_PULSE_ON);
|
||||
/* start pulse counter */
|
||||
r1->pulse_counter = 1;
|
||||
}
|
||||
break;
|
||||
/* pulse goes off */
|
||||
case R1_STATE_RECV_PULSE_ON:
|
||||
if (signal == ' ') {
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_PULSE);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_RECV_PULSE_OFF);
|
||||
}
|
||||
break;
|
||||
/* pulse goes back on */
|
||||
case R1_STATE_RECV_PULSE_OFF:
|
||||
if (signal == 'B') {
|
||||
/* stop timer */
|
||||
timer_stop(&r1->timer);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_PULSE);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_RECV_PULSE_ON);
|
||||
/* increment pulse counter */
|
||||
r1->pulse_counter++;
|
||||
}
|
||||
break;
|
||||
/* got a clear-forward */
|
||||
case R1_STATE_IN_PROCEED:
|
||||
case R1_STATE_IN_ANSWER:
|
||||
case R1_STATE_IN_HANGUP:
|
||||
if (signal == 'B') {
|
||||
clear_forward:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received 'clear-forward' signal in '%s' state'.\n", state_name);
|
||||
/* release outgoing call */
|
||||
if (r1->dsp.cc_callref) {
|
||||
/* send release indication towards CC */
|
||||
release_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
|
||||
/* remove ref */
|
||||
r1->dsp.cc_callref = 0;
|
||||
}
|
||||
/* stop SIT */
|
||||
set_sit(&r1->dsp, 0);
|
||||
/* Turn on SF */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "Received signal '%c' in '%s' state is not handled!\n", signal, state_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* dialing was completed */
|
||||
void dialing_complete(void *priv)
|
||||
{
|
||||
r1_t *r1 = priv;
|
||||
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Dialing is complete in '%s' state, waiting for remote party to answer.\n", r1_state_names[r1->state]);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_PROCEEDING);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_OUT_PROCEED);
|
||||
/* indicate alerting now, because there is no alerting signaling */
|
||||
alert_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref);
|
||||
}
|
||||
|
||||
/* timeouts */
|
||||
static void r1_timeout(void *data)
|
||||
{
|
||||
r1_t *r1 = data;
|
||||
const char *state_name = r1_state_names[r1->state];
|
||||
int i;
|
||||
|
||||
switch (r1->state) {
|
||||
case R1_STATE_OUT_DELAY_DIALING:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Detected 'double seizing', releasing call, going idle.\n");
|
||||
/* send 'idle' signal */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* state idle */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
/* send release indication towards CC */
|
||||
release_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
break;
|
||||
case R1_STATE_OUT_PROCEED:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received answer timeout, releasing call.\n");
|
||||
/* send clear-forward */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
/* release call */
|
||||
release_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_USER_BUSY);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
break;
|
||||
case R1_STATE_OUT_HANGUP:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received clear-back timeout, releasing call.\n");
|
||||
/* send clear-forward */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
/* release call */
|
||||
release_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
break;
|
||||
case R1_STATE_IN_DELAY_DIALING:
|
||||
/* send 'proceed-to-send' signal */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* change state */
|
||||
if (r1->pulsedialing)
|
||||
r1_new_state(r1, R1_STATE_RECV_PULSE_WAIT);
|
||||
else {
|
||||
r1_new_state(r1, R1_STATE_RECV_DIGIT_WAIT);
|
||||
/* set recognition time */
|
||||
set_sig_detect_duration(&r1->dsp, SIGN_RECOGNITION_SLOW, 0.0);
|
||||
}
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_DIALING);
|
||||
break;
|
||||
case R1_STATE_RECV_DIGIT_WAIT:
|
||||
case R1_STATE_RECV_DIGIT_ON:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received dialing timeout in '%s' state, going to hangup state.\n", state_name);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
break;
|
||||
case R1_STATE_RECV_PULSE_WAIT:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received dialing timeout in '%s' state, setting up call.\n", state_name);
|
||||
/* setup call */
|
||||
setup_call(r1);
|
||||
break;
|
||||
case R1_STATE_RECV_PULSE_ON:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received pulse that was too long in '%s' state, assuming clear-forward.\n", state_name);
|
||||
/* Turn on SF */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
break;
|
||||
case R1_STATE_RECV_PULSE_OFF:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received complete pulse sequence, storing digit %c.\n", r1->pulse_counter);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_DIALING);
|
||||
/* add digit */
|
||||
i = strlen(r1->dialing);
|
||||
if (i + 1 == sizeof(r1->dialing))
|
||||
break;
|
||||
if (r1->pulse_counter <= 9)
|
||||
r1->dialing[i++] = '0' + r1->pulse_counter;
|
||||
else if (r1->pulse_counter == 10)
|
||||
r1->dialing[i++] = '0';
|
||||
else if (r1->pulse_counter == 11)
|
||||
r1->dialing[i++] = '*';
|
||||
else if (r1->pulse_counter == 12)
|
||||
r1->dialing[i++] = '#';
|
||||
else
|
||||
r1->dialing[i++] = '?';
|
||||
r1->dialing[i] = '\0';
|
||||
/* change state (after adding digit) */
|
||||
r1_new_state(r1, R1_STATE_RECV_PULSE_WAIT);
|
||||
break;
|
||||
case R1_STATE_IDLE:
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Received dialing timeout in '%s' state, guard timer has expired.\n", state_name);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
/* message from call control */
|
||||
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg)
|
||||
{
|
||||
r1_endpoint_t *r1_ep = ep->priv;
|
||||
dsp_t *dsp;
|
||||
r1_t *r1;
|
||||
const char *state_name;
|
||||
osmo_cc_msg_t *new_msg;
|
||||
uint8_t type, plan, present, screen;
|
||||
char dialing[64];
|
||||
const char *sdp;
|
||||
int rc;
|
||||
|
||||
/* hunt for callref */
|
||||
dsp = r1_ep->dsp_list;
|
||||
while (dsp) {
|
||||
if (dsp->cc_callref == callref)
|
||||
break;
|
||||
dsp = dsp->next;
|
||||
}
|
||||
r1 = (dsp) ? dsp->priv : NULL;
|
||||
|
||||
/* process SETUP */
|
||||
if (!r1) {
|
||||
if (msg->type != OSMO_CC_MSG_SETUP_REQ) {
|
||||
PDEBUG(DR1, DEBUG_ERROR, "received message without r1 instance, please fix!\n");
|
||||
goto done;
|
||||
}
|
||||
/* hunt free r1 instance */
|
||||
dsp = r1_ep->dsp_list;
|
||||
while (dsp) {
|
||||
r1 = dsp->priv;
|
||||
/* Must be IDLE and guard timer expired. */
|
||||
if (r1->state == R1_STATE_IDLE && !timer_running(&r1->timer))
|
||||
break;
|
||||
dsp = dsp->next;
|
||||
}
|
||||
r1 = (dsp) ? dsp->priv : NULL;
|
||||
if (!r1) {
|
||||
PDEBUG(DR1, DEBUG_NOTICE, "No free r1 instance, rejecting.\n");
|
||||
reject_call(ep, callref, OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN);
|
||||
goto done;
|
||||
}
|
||||
/* link with cc */
|
||||
r1->dsp.cc_callref = callref;
|
||||
}
|
||||
state_name = r1_state_names[r1->state];
|
||||
|
||||
switch (msg->type) {
|
||||
case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Outgoing call in '%s' state, sending 'seizing'.\n", state_name);
|
||||
/* caller id */
|
||||
rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, r1->callerid, sizeof(r1->callerid));
|
||||
/* called number */
|
||||
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing));
|
||||
if (rc < 0 || !dialing[0]) {
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "No number given, call is rejected!\n");
|
||||
inv_nr:
|
||||
/* reject call */
|
||||
reject_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
goto done;
|
||||
}
|
||||
rc = generate_dial_string(type, dialing, r1->dialing, sizeof(r1->dialing));
|
||||
if (rc < 0)
|
||||
goto inv_nr;
|
||||
/* sdp accept */
|
||||
sdp = osmo_cc_helper_audio_accept(&r1->r1_ep->cc_ep.session_config, &r1->dsp, codecs, down_audio, msg, &r1->dsp.cc_session, &r1->dsp.codec, 0);
|
||||
if (!sdp) {
|
||||
/* reject call */
|
||||
reject_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
goto done;
|
||||
}
|
||||
/* proceed */
|
||||
proceed_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, sdp);
|
||||
/* send seizing */
|
||||
set_tone(&r1->dsp, 0, 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_SEIZING);
|
||||
break;
|
||||
case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */
|
||||
case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */
|
||||
case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */
|
||||
case OSMO_CC_MSG_PROGRESS_REQ: /* progress */
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &r1->dsp.cc_session, &r1->dsp.codec);
|
||||
if (rc < 0) {
|
||||
codec_failed:
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "Releasing, because codec negotiation failed.\n");
|
||||
/* state hangup */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
/* release call */
|
||||
release_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL);
|
||||
/* reset inst */
|
||||
link_reset(r1);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Incoming call has answered in '%s' state, sending 'answer'.\n", state_name);
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &r1->dsp.cc_session, &r1->dsp.codec);
|
||||
if (rc < 0)
|
||||
goto codec_failed;
|
||||
/* connect acknowledge */
|
||||
setup_comp_call(&r1->r1_ep->cc_ep, r1->dsp.cc_callref);
|
||||
/* not in right state, which should never happen anyway */
|
||||
if (r1->state != R1_STATE_IN_PROCEED)
|
||||
break;
|
||||
/* send answer */
|
||||
set_tone(&r1->dsp, 0, 0);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IN_ANSWER);
|
||||
break;
|
||||
case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */
|
||||
case OSMO_CC_MSG_REL_REQ: /* call has been released */
|
||||
case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */
|
||||
rc = osmo_cc_helper_audio_negotiate(msg, &r1->dsp.cc_session, &r1->dsp.codec);
|
||||
if (rc < 0)
|
||||
goto codec_failed;
|
||||
/* right state */
|
||||
if (r1->state == R1_STATE_IN_PROCEED) {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Incoming call has disconnected in '%s' state'.\n", state_name);
|
||||
/* state hangup */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
if (msg->type == OSMO_CC_MSG_REL_REQ) {
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
}
|
||||
} else
|
||||
if (r1->state == R1_STATE_IN_ANSWER) {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Incoming call has disconnected in '%s' state, sending 'clear-back'.\n", state_name);
|
||||
/* send clear-back */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* state hangup */
|
||||
r1_new_state(r1, R1_STATE_IN_HANGUP);
|
||||
if (msg->type == OSMO_CC_MSG_REL_REQ) {
|
||||
/* send SIT */
|
||||
set_sit(&r1->dsp, 1);
|
||||
}
|
||||
} else {
|
||||
PDEBUG_CHAN(DR1, DEBUG_INFO, "Outgoing call has disconnected in '%s' state, sending 'clear-forward'.\n", state_name);
|
||||
/* send clear-forward */
|
||||
set_tone(&r1->dsp, 'B', 0);
|
||||
/* start timer */
|
||||
timer_start(&r1->timer, TO_GUARD_BUSY);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_IDLE);
|
||||
if (msg->type == OSMO_CC_MSG_DISC_REQ) {
|
||||
/* clone osmo-cc message to preserve cause */
|
||||
new_msg = osmo_cc_clone_msg(msg);
|
||||
new_msg->type = OSMO_CC_MSG_REL_IND;
|
||||
/* send message to osmo-cc */
|
||||
osmo_cc_ll_msg(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, new_msg);
|
||||
/* reset */
|
||||
link_reset(r1);
|
||||
break;
|
||||
}
|
||||
// note: link_reset is called below
|
||||
}
|
||||
/* on release, we confirm */
|
||||
if (msg->type == OSMO_CC_MSG_REL_REQ) {
|
||||
/* clone osmo-cc message to preserve cause */
|
||||
new_msg = osmo_cc_clone_msg(msg);
|
||||
new_msg->type = OSMO_CC_MSG_REL_CNF;
|
||||
/* send message to osmo-cc */
|
||||
osmo_cc_ll_msg(&r1->r1_ep->cc_ep, r1->dsp.cc_callref, new_msg);
|
||||
}
|
||||
/* on reject/release we reset/unlink call */
|
||||
if (msg->type != OSMO_CC_MSG_DISC_REQ)
|
||||
link_reset(r1);
|
||||
break;
|
||||
#if 0
|
||||
case OSMO_CC_MSG_INFO_REQ: /* we are getting overlap dialing */
|
||||
/* called number */
|
||||
rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing));
|
||||
if (rc < 0 || !dialing[0]) {
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "No number given, dialing ignored!\n");
|
||||
break;
|
||||
}
|
||||
if (!pulse) {
|
||||
PDEBUG_CHAN(DR1, DEBUG_NOTICE, "Overlap dialing only supported with pulse dialing.\n");
|
||||
break;
|
||||
}
|
||||
/* concat overlap dialing to overlap register */
|
||||
strcat(r1->overlap, dialing, sizeof(r1->overlap) - 1);
|
||||
/* if inactive, dial the overlap string */
|
||||
if (r1->state == R1_STATE_OUT_INACTIVE) {
|
||||
/* dial */
|
||||
set_dial_string(&r1->dsp, r1->overlap, r1->pulsedialing);
|
||||
/* append the string to the dialed number */
|
||||
strcat(r1->dialing, r1->overlap, sizeof(r1->dialing) - 1);
|
||||
/* change state */
|
||||
r1_new_state(r1, R1_STATE_SEND_DIGITS);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
done:
|
||||
osmo_cc_free_msg(msg);
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
#include "../common/dsp.h"
|
||||
|
||||
enum r1_state {
|
||||
R1_STATE_NULL = 0,
|
||||
/* idle line */
|
||||
R1_STATE_IDLE, /* idle, also sending disconnect signal */
|
||||
/* outgoing call */
|
||||
R1_STATE_SEIZING, /* sending seize, waiting for proceed-to-send */
|
||||
R1_STATE_OUT_DELAY_DIALING, /* delay-dialing received, waiting for register to be available */
|
||||
R1_STATE_SEND_DIGITS, /* proceed-to-send received, sending digits */
|
||||
R1_STATE_OUT_PROCEED, /* waiting for called party to answer or being busy */
|
||||
R1_STATE_OUT_ANSWER, /* detected answer */
|
||||
R1_STATE_OUT_HANGUP, /* detected hangup */
|
||||
/* incoming call */
|
||||
R1_STATE_IN_DELAY_DIALING, /* seize received, waiting for procced-to-send */
|
||||
R1_STATE_RECV_DIGIT_WAIT, /* sending proceed-to-send, waiting for digits */
|
||||
R1_STATE_RECV_DIGIT_ON, /* digit received, waiting to cease */
|
||||
R1_STATE_RECV_PULSE_WAIT, /* sending proceed-to-send, waiting for first pulse of digit */
|
||||
R1_STATE_RECV_PULSE_ON, /* pulse received, waiting to cease */
|
||||
R1_STATE_RECV_PULSE_OFF, /* pulse ceased, waiting to for next pulse of digit */
|
||||
R1_STATE_IN_PROCEED, /* waiting for called party to answer or disconnect */
|
||||
R1_STATE_IN_ANSWER, /* called party answered */
|
||||
R1_STATE_IN_HANGUP, /* called party disconnected, sending hangup */
|
||||
};
|
||||
|
||||
extern const char *r1_state_names[];
|
||||
|
||||
struct r1_endpoint;
|
||||
|
||||
/* R1 link definition */
|
||||
typedef struct r1 {
|
||||
struct r1_endpoint *r1_ep;
|
||||
char name[32]; /* name of link for debugging */
|
||||
|
||||
/* call states */
|
||||
enum r1_state state; /* state of link */
|
||||
struct timer timer; /* for several timeouts */
|
||||
char callerid[65]; /* current caller id (outgoing only) */
|
||||
char dialing[33]; /* current dial string (send or receive) */
|
||||
int pulsedialing; /* if set, pulses are sent/received via SF */
|
||||
int pulse_counter; /* used to count received pulses */
|
||||
|
||||
/* audio processing */
|
||||
dsp_t dsp; /* all dsp processing */
|
||||
} r1_t;
|
||||
|
||||
/* R1 endpoint definition */
|
||||
typedef struct r1_endpoint {
|
||||
osmo_cc_endpoint_t cc_ep;
|
||||
dsp_t *dsp_list;
|
||||
int suppress_disconnect; /* do not forward disconnect towards CC */
|
||||
int clear_back_timeout; /* time to disconnect when receiving clean back */
|
||||
} r1_endpoint_t;
|
||||
|
||||
void refresh_status(void);
|
||||
r1_endpoint_t *r1_ep_create(const char *ep_name, int links, int suppress_disconnect, int clear_back_timeout, int crosstalk, int delay_ms, double sense_db, int pulsedialing);
|
||||
void r1_ep_destroy(r1_endpoint_t *r1_ep);
|
||||
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);
|
||||
|
Loading…
Reference in New Issue