Add R1 system

This commit is contained in:
Andreas Eversberg 2023-06-11 10:29:39 +02:00
parent 2483f1c2b1
commit d0c08f4f7d
7 changed files with 1301 additions and 1 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -89,5 +89,6 @@ AC_OUTPUT(
src/libfilter/Makefile
src/common/Makefile
src/ss5/Makefile
src/r1/Makefile
src/Makefile
Makefile)

View File

@ -11,5 +11,6 @@ SUBDIRS = \
libg711 \
libfilter \
common \
ss5
ss5 \
r1

22
src/r1/Makefile.am Normal file
View File

@ -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

378
src/r1/main.c Normal file
View File

@ -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;
}

837
src/r1/r1.c Normal file
View File

@ -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);
}

60
src/r1/r1.h Normal file
View File

@ -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);