mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-04-14 16:15:04 +00:00
Fixes "warning: comparison of unsigned expression >= 0 is always true". Signed-off-by: Stefan Knoblich <stkn@openisdn.net>
1268 lines
41 KiB
C
1268 lines
41 KiB
C
/*
|
|
* Copyright (c) 2007-2012, Anthony Minessale II
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of the original author; nor the names of any contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "private/ftdm_core.h"
|
|
#include "ftdm_analog.h"
|
|
|
|
#ifndef localtime_r
|
|
struct tm * localtime_r(const time_t *clock, struct tm *result);
|
|
#endif
|
|
|
|
static void *ftdm_analog_channel_run(ftdm_thread_t *me, void *obj);
|
|
|
|
/**
|
|
* \brief Starts an FXO channel thread (outgoing call)
|
|
* \param ftdmchan Channel to initiate call on
|
|
* \return Success or failure
|
|
*
|
|
* Initialises state, starts tone progress detection and runs the channel in a new a thread.
|
|
*/
|
|
static FIO_CHANNEL_OUTGOING_CALL_FUNCTION(analog_fxo_outgoing_call)
|
|
{
|
|
ftdm_analog_data_t *analog_data = ftdmchan->span->signal_data;
|
|
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK) && !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_INTHREAD)) {
|
|
ftdm_channel_clear_needed_tones(ftdmchan);
|
|
ftdm_channel_clear_detected_tones(ftdmchan);
|
|
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_OFFHOOK, NULL);
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_ENABLE_PROGRESS_DETECT, NULL);
|
|
if (analog_data->wait_dialtone_timeout) {
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_DIAL] = 1;
|
|
}
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DIALING);
|
|
ftdm_thread_create_detached(ftdm_analog_channel_run, ftdmchan);
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
/**
|
|
* \brief Starts an FXS channel thread (outgoing call)
|
|
* \param ftdmchan Channel to initiate call on
|
|
* \return Success or failure
|
|
*
|
|
* Indicates call waiting if channel is already in use, otherwise runs the channel in a new thread.
|
|
*/
|
|
static FIO_CHANNEL_OUTGOING_CALL_FUNCTION(analog_fxs_outgoing_call)
|
|
{
|
|
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_INTHREAD)) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_CALLWAITING);
|
|
} else {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_GENRING);
|
|
ftdm_thread_create_detached(ftdm_analog_channel_run, ftdmchan);
|
|
}
|
|
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the signalling status on a channel
|
|
* \param ftdmchan Channel to get status on
|
|
* \param status Pointer to set signalling status
|
|
* \return Success or failure
|
|
*/
|
|
|
|
static FIO_CHANNEL_GET_SIG_STATUS_FUNCTION(analog_get_channel_sig_status)
|
|
{
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_IN_ALARM)) {
|
|
*status = FTDM_SIG_STATE_DOWN;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
*status = FTDM_SIG_STATE_UP;
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the signalling status on a span
|
|
* \param span Span to get status on
|
|
* \param status Pointer to set signalling status
|
|
* \return Success or failure
|
|
*/
|
|
|
|
static FIO_SPAN_GET_SIG_STATUS_FUNCTION(analog_get_span_sig_status)
|
|
{
|
|
ftdm_iterator_t *citer = NULL;
|
|
ftdm_iterator_t *chaniter = ftdm_span_get_chan_iterator(span, NULL);
|
|
if (!chaniter) {
|
|
ftdm_log(FTDM_LOG_CRIT, "Failed to allocate channel iterator for span %s!\n", span->name);
|
|
return FTDM_FAIL;
|
|
}
|
|
/* if ALL channels are in alarm, report DOWN, UP otherwise. */
|
|
*status = FTDM_SIG_STATE_DOWN;
|
|
for (citer = chaniter; citer; citer = ftdm_iterator_next(citer)) {
|
|
ftdm_channel_t *fchan = ftdm_iterator_current(citer);
|
|
ftdm_channel_lock(fchan);
|
|
if (!ftdm_test_flag(fchan, FTDM_CHANNEL_IN_ALARM)) {
|
|
*status = FTDM_SIG_STATE_UP;
|
|
ftdm_channel_unlock(fchan);
|
|
break;
|
|
}
|
|
ftdm_channel_unlock(fchan);
|
|
}
|
|
ftdm_iterator_free(chaniter);
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Starts an analog span thread (monitor)
|
|
* \param span Span to monitor
|
|
* \return Success or failure
|
|
*/
|
|
static ftdm_status_t ftdm_analog_start(ftdm_span_t *span)
|
|
{
|
|
ftdm_analog_data_t *analog_data = span->signal_data;
|
|
ftdm_set_flag(analog_data, FTDM_ANALOG_RUNNING);
|
|
return ftdm_thread_create_detached(ftdm_analog_run, span);
|
|
}
|
|
|
|
/**
|
|
* \brief Stops the analog span thread (monitor)
|
|
* \param span Span to stop
|
|
* \return Success or failure
|
|
*/
|
|
static ftdm_status_t ftdm_analog_stop(ftdm_span_t *span)
|
|
{
|
|
ftdm_analog_data_t *analog_data = span->signal_data;
|
|
int32_t sanity = 100;
|
|
while (ftdm_test_flag(analog_data, FTDM_ANALOG_RUNNING) && sanity--) {
|
|
ftdm_sleep(100);
|
|
ftdm_log(FTDM_LOG_DEBUG, "Waiting for analog thread for span %s to stop\n", span->name);
|
|
}
|
|
|
|
if (!sanity) {
|
|
ftdm_log(FTDM_LOG_ERROR, "The analog thread for span %s is probably still running, we may crash :(\n", span->name);
|
|
return FTDM_FAIL;
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Initialises an analog span from configuration variables
|
|
* \param span Span to configure
|
|
* \param sig_cb Callback function for event signals
|
|
* \param ap List of configuration variables
|
|
* \return Success or failure
|
|
*/
|
|
static FIO_SIG_CONFIGURE_FUNCTION(ftdm_analog_configure_span)
|
|
//ftdm_status_t ftdm_analog_configure_span(ftdm_span_t *span, char *tonemap, uint32_t digit_timeout, uint32_t max_dialstr, fio_signal_cb_t sig_cb)
|
|
{
|
|
ftdm_analog_data_t *analog_data;
|
|
const char *tonemap = "us";
|
|
const char *hotline = "";
|
|
uint32_t digit_timeout = 10;
|
|
uint32_t wait_dialtone_timeout = 5000;
|
|
uint32_t max_dialstr = MAX_DTMF;
|
|
uint32_t polarity_delay = 600;
|
|
const char *var, *val;
|
|
int *intval;
|
|
uint32_t flags = FTDM_ANALOG_CALLERID;
|
|
int callwaiting = 1;
|
|
unsigned int i = 0;
|
|
|
|
assert(sig_cb != NULL);
|
|
ftdm_log(FTDM_LOG_DEBUG, "Configuring span %s for analog signaling ...\n", span->name);
|
|
|
|
if (span->signal_type) {
|
|
ftdm_log(FTDM_LOG_ERROR, "Span %s is already configured for signaling %d\n", span->name, span->signal_type);
|
|
snprintf(span->last_error, sizeof(span->last_error), "Span is already configured for signalling.");
|
|
return FTDM_FAIL;
|
|
}
|
|
|
|
analog_data = ftdm_malloc(sizeof(*analog_data));
|
|
|
|
ftdm_assert_return(analog_data != NULL, FTDM_FAIL, "malloc failure\n");
|
|
|
|
memset(analog_data, 0, sizeof(*analog_data));
|
|
|
|
while ((var = va_arg(ap, char *))) {
|
|
ftdm_log(FTDM_LOG_DEBUG, "Analog config var = %s\n", var);
|
|
if (!strcasecmp(var, "tonemap")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
tonemap = val;
|
|
} else if (!strcasecmp(var, "digit_timeout")) {
|
|
if (!(intval = va_arg(ap, int *))) {
|
|
break;
|
|
}
|
|
digit_timeout = *intval;
|
|
} else if (!strcasecmp(var, "wait_dialtone_timeout")) {
|
|
if (!(intval = va_arg(ap, int *))) {
|
|
break;
|
|
}
|
|
wait_dialtone_timeout = ftdm_max(0, *intval);
|
|
ftdm_log(FTDM_LOG_DEBUG, "Wait dial tone ms = %d\n", wait_dialtone_timeout);
|
|
} else if (!strcasecmp(var, "enable_callerid")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
|
|
if (ftdm_true(val)) {
|
|
flags |= FTDM_ANALOG_CALLERID;
|
|
} else {
|
|
flags &= ~FTDM_ANALOG_CALLERID;
|
|
}
|
|
} else if (!strcasecmp(var, "answer_polarity_reverse")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
if (ftdm_true(val)) {
|
|
flags |= FTDM_ANALOG_ANSWER_POLARITY_REVERSE;
|
|
} else {
|
|
flags &= ~FTDM_ANALOG_ANSWER_POLARITY_REVERSE;
|
|
}
|
|
} else if (!strcasecmp(var, "hangup_polarity_reverse")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
if (ftdm_true(val)) {
|
|
flags |= FTDM_ANALOG_HANGUP_POLARITY_REVERSE;
|
|
} else {
|
|
flags &= ~FTDM_ANALOG_HANGUP_POLARITY_REVERSE;
|
|
}
|
|
} else if (!strcasecmp(var, "polarity_delay")) {
|
|
if (!(intval = va_arg(ap, int *))) {
|
|
break;
|
|
}
|
|
polarity_delay = *intval;
|
|
} else if (!strcasecmp(var, "callwaiting")) {
|
|
if (!(intval = va_arg(ap, int *))) {
|
|
break;
|
|
}
|
|
callwaiting = *intval;
|
|
} else if (!strcasecmp(var, "max_dialstr")) {
|
|
if (!(intval = va_arg(ap, int *))) {
|
|
break;
|
|
}
|
|
max_dialstr = *intval;
|
|
} else if (!strcasecmp(var, "hotline")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
hotline = val;
|
|
} else if (!strcasecmp(var, "polarity_callerid")) {
|
|
if (!(val = va_arg(ap, char *))) {
|
|
break;
|
|
}
|
|
if (ftdm_true(val)) {
|
|
flags |= FTDM_ANALOG_POLARITY_CALLERID;
|
|
} else {
|
|
flags &= ~FTDM_ANALOG_POLARITY_CALLERID;
|
|
}
|
|
} else {
|
|
ftdm_log(FTDM_LOG_ERROR, "Unknown parameter %s in span %s\n", var, span->name);
|
|
}
|
|
}
|
|
|
|
if (digit_timeout < 2000 || digit_timeout > 10000) {
|
|
digit_timeout = 2000;
|
|
}
|
|
|
|
if ((max_dialstr < 1 && !strlen(hotline)) || max_dialstr > MAX_DTMF) {
|
|
max_dialstr = MAX_DTMF;
|
|
}
|
|
|
|
if (callwaiting) {
|
|
for (i = 1; i <= span->chan_count; i++) {
|
|
ftdm_log_chan_msg(span->channels[i], FTDM_LOG_DEBUG, "Enabled call waiting\n");
|
|
ftdm_channel_set_feature(span->channels[i], FTDM_CHANNEL_FEATURE_CALLWAITING);
|
|
}
|
|
}
|
|
|
|
span->start = ftdm_analog_start;
|
|
span->stop = ftdm_analog_stop;
|
|
analog_data->flags = flags;
|
|
analog_data->digit_timeout = digit_timeout;
|
|
analog_data->wait_dialtone_timeout = wait_dialtone_timeout;
|
|
analog_data->polarity_delay = polarity_delay;
|
|
analog_data->max_dialstr = max_dialstr;
|
|
span->signal_cb = sig_cb;
|
|
strncpy(analog_data->hotline, hotline, sizeof(analog_data->hotline));
|
|
span->signal_type = FTDM_SIGTYPE_ANALOG;
|
|
span->signal_data = analog_data;
|
|
span->outgoing_call = span->trunk_type == FTDM_TRUNK_FXS ? analog_fxs_outgoing_call : analog_fxo_outgoing_call;
|
|
span->get_channel_sig_status = analog_get_channel_sig_status;
|
|
span->get_span_sig_status = analog_get_span_sig_status;
|
|
|
|
ftdm_span_load_tones(span, tonemap);
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "Configuration of analog signaling for span %s is done\n", span->name);
|
|
return FTDM_SUCCESS;
|
|
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieves tone generation output to be sent
|
|
* \param ts Teletone generator
|
|
* \param map Tone map
|
|
* \return -1 on error, 0 on success
|
|
*/
|
|
static int teletone_handler(teletone_generation_session_t *ts, teletone_tone_map_t *map)
|
|
{
|
|
ftdm_buffer_t *dt_buffer = ts->user_data;
|
|
int wrote;
|
|
|
|
if (!dt_buffer) {
|
|
return -1;
|
|
}
|
|
wrote = teletone_mux_tones(ts, map);
|
|
ftdm_buffer_write(dt_buffer, ts->buffer, wrote * 2);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Sends caller id on an analog channel (FSK coded)
|
|
* \param ftdmchan Channel to send caller id on
|
|
*/
|
|
static void send_caller_id(ftdm_channel_t *ftdmchan)
|
|
{
|
|
ftdm_fsk_data_state_t fsk_data;
|
|
uint8_t databuf[1024] = "";
|
|
char time_str[9];
|
|
struct tm tm;
|
|
time_t now;
|
|
ftdm_mdmf_type_t mt = MDMF_INVALID;
|
|
|
|
time(&now);
|
|
#ifdef WIN32
|
|
_tzset();
|
|
_localtime64_s(&tm, &now);
|
|
#else
|
|
localtime_r(&now, &tm);
|
|
#endif
|
|
strftime(time_str, sizeof(time_str), "%m%d%H%M", &tm);
|
|
|
|
ftdm_fsk_data_init(&fsk_data, databuf, sizeof(databuf));
|
|
ftdm_fsk_data_add_mdmf(&fsk_data, MDMF_DATETIME, (uint8_t *) time_str, 8);
|
|
|
|
if (ftdm_strlen_zero(ftdmchan->caller_data.cid_num.digits)) {
|
|
mt = MDMF_NO_NUM;
|
|
ftdm_set_string(ftdmchan->caller_data.cid_num.digits, "O");
|
|
} else if (!strcasecmp(ftdmchan->caller_data.cid_num.digits, "P") || !strcasecmp(ftdmchan->caller_data.cid_num.digits, "O")) {
|
|
mt = MDMF_NO_NUM;
|
|
} else {
|
|
mt = MDMF_PHONE_NUM;
|
|
}
|
|
ftdm_fsk_data_add_mdmf(&fsk_data, mt, (uint8_t *) ftdmchan->caller_data.cid_num.digits, (uint8_t)strlen(ftdmchan->caller_data.cid_num.digits));
|
|
|
|
if (ftdm_strlen_zero(ftdmchan->caller_data.cid_name)) {
|
|
mt = MDMF_NO_NAME;
|
|
ftdm_set_string(ftdmchan->caller_data.cid_name, "O");
|
|
} else if (!strcasecmp(ftdmchan->caller_data.cid_name, "P") || !strcasecmp(ftdmchan->caller_data.cid_name, "O")) {
|
|
mt = MDMF_NO_NAME;
|
|
} else {
|
|
mt = MDMF_PHONE_NAME;
|
|
}
|
|
ftdm_fsk_data_add_mdmf(&fsk_data, mt, (uint8_t *) ftdmchan->caller_data.cid_name, (uint8_t)strlen(ftdmchan->caller_data.cid_name));
|
|
|
|
ftdm_fsk_data_add_checksum(&fsk_data);
|
|
ftdm_channel_send_fsk_data(ftdmchan, &fsk_data, -14);
|
|
}
|
|
|
|
static void analog_dial(ftdm_channel_t *ftdmchan, uint32_t *state_counter, uint32_t *dial_timeout)
|
|
{
|
|
if (ftdm_strlen_zero(ftdmchan->caller_data.dnis.digits)) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "No digits to send, moving to UP!\n");
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
|
|
} else {
|
|
if (ftdm_channel_command(ftdmchan, FTDM_COMMAND_SEND_DTMF, ftdmchan->caller_data.dnis.digits) != FTDM_SUCCESS) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "Send Digits Failed [%s]\n", ftdmchan->last_error);
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_BUSY);
|
|
} else {
|
|
*state_counter = 0;
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_RING] = 1;
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_BUSY] = 1;
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_FAIL1] = 1;
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_FAIL2] = 1;
|
|
ftdmchan->needed_tones[FTDM_TONEMAP_FAIL3] = 1;
|
|
*dial_timeout = (uint32_t)((ftdmchan->dtmf_on + ftdmchan->dtmf_off) * strlen(ftdmchan->caller_data.dnis.digits)) + 2000;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Main thread function for analog channel (outgoing call)
|
|
* \param me Current thread
|
|
* \param obj Channel to run in this thread
|
|
*/
|
|
static void *ftdm_analog_channel_run(ftdm_thread_t *me, void *obj)
|
|
{
|
|
ftdm_channel_t *ftdmchan = (ftdm_channel_t *) obj;
|
|
ftdm_buffer_t *dt_buffer = NULL;
|
|
teletone_generation_session_t ts;
|
|
uint8_t frame[1024];
|
|
ftdm_size_t len, rlen;
|
|
ftdm_tone_type_t tt = FTDM_TONE_DTMF;
|
|
char dtmf[MAX_DTMF+1] = "";
|
|
ftdm_size_t dtmf_offset = 0;
|
|
ftdm_analog_data_t *analog_data = ftdmchan->span->signal_data;
|
|
ftdm_channel_t *closed_chan;
|
|
uint32_t state_counter = 0, elapsed = 0, collecting = 0, interval = 0, last_digit = 0, indicate = 0, dial_timeout = analog_data->wait_dialtone_timeout;
|
|
uint32_t answer_on_polarity_counter = 0;
|
|
ftdm_sigmsg_t sig;
|
|
|
|
ftdm_unused_arg(me);
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "ANALOG CHANNEL thread starting.\n");
|
|
|
|
ts.buffer = NULL;
|
|
|
|
if (ftdm_channel_open_chan(ftdmchan) != FTDM_SUCCESS) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_ERROR, "OPEN ERROR [%s]\n", ftdmchan->last_error);
|
|
goto done;
|
|
}
|
|
|
|
if (ftdm_buffer_create(&dt_buffer, 1024, 3192, 0) != FTDM_SUCCESS) {
|
|
snprintf(ftdmchan->last_error, sizeof(ftdmchan->last_error), "memory error!");
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "MEM ERROR\n");
|
|
goto done;
|
|
}
|
|
|
|
if (ftdm_channel_command(ftdmchan, FTDM_COMMAND_ENABLE_DTMF_DETECT, &tt) != FTDM_SUCCESS) {
|
|
snprintf(ftdmchan->last_error, sizeof(ftdmchan->last_error), "error initilizing tone detector!");
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "failed to initialize DTMF detector\n");
|
|
goto done;
|
|
}
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Initialized DTMF detection\n");
|
|
|
|
ftdm_set_flag_locked(ftdmchan, FTDM_CHANNEL_INTHREAD);
|
|
teletone_init_session(&ts, 0, teletone_handler, dt_buffer);
|
|
ts.rate = 8000;
|
|
#if 0
|
|
ts.debug = 1;
|
|
ts.debug_stream = stdout;
|
|
#endif
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GET_INTERVAL, &interval);
|
|
ftdm_buffer_set_loops(dt_buffer, -1);
|
|
|
|
memset(&sig, 0, sizeof(sig));
|
|
sig.chan_id = ftdmchan->chan_id;
|
|
sig.span_id = ftdmchan->span_id;
|
|
sig.channel = ftdmchan;
|
|
|
|
ftdm_assert(interval != 0, "Invalid interval");
|
|
|
|
if (!dial_timeout) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Not waiting for dial tone to dial number %s\n", ftdmchan->caller_data.dnis.digits);
|
|
}
|
|
|
|
while (ftdm_running() && ftdm_test_flag(ftdmchan, FTDM_CHANNEL_INTHREAD)) {
|
|
ftdm_wait_flag_t flags = FTDM_READ;
|
|
ftdm_size_t dlen = 0;
|
|
|
|
len = sizeof(frame);
|
|
|
|
elapsed += interval;
|
|
state_counter += interval;
|
|
|
|
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_STATE_CHANGE)) {
|
|
switch(ftdmchan->state) {
|
|
case FTDM_CHANNEL_STATE_GET_CALLERID:
|
|
{
|
|
if (state_counter > 5000 || !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_CALLERID_DETECT)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_DISABLE_CALLERID_DETECT, NULL);
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_RING);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_DIALING:
|
|
{
|
|
if (state_counter > dial_timeout) {
|
|
if (ftdmchan->needed_tones[FTDM_TONEMAP_DIAL]) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_BUSY);
|
|
} else {
|
|
/* do not go up if we're waiting for polarity reversal */
|
|
if (ftdm_test_flag(analog_data, FTDM_ANALOG_ANSWER_POLARITY_REVERSE)) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_PROGRESS_MEDIA);
|
|
} else {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_GENRING:
|
|
{
|
|
if (state_counter > 60000) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
|
|
} else if (!ftdmchan->fsk_buffer || !ftdm_buffer_inuse(ftdmchan->fsk_buffer)) {
|
|
ftdm_sleep(interval);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_DIALTONE:
|
|
{
|
|
if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_HOLD) && state_counter > 10000) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_BUSY);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_BUSY:
|
|
{
|
|
if (state_counter > 20000) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_ATTN);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_ATTN:
|
|
{
|
|
if (state_counter > 20000) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_HANGUP:
|
|
{
|
|
if (state_counter > 500) {
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_RINGING)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GENERATE_RING_OFF, NULL);
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXS &&
|
|
ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK) &&
|
|
(ftdmchan->last_state == FTDM_CHANNEL_STATE_RINGING
|
|
|| ftdmchan->last_state == FTDM_CHANNEL_STATE_DIALTONE
|
|
|| ftdmchan->last_state == FTDM_CHANNEL_STATE_RING
|
|
|| ftdmchan->last_state == FTDM_CHANNEL_STATE_UP)) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_BUSY);
|
|
} else {
|
|
ftdmchan->caller_data.hangup_cause = FTDM_CAUSE_NORMAL_CLEARING;
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_CALLWAITING:
|
|
{
|
|
int done = 0;
|
|
|
|
if (ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK] == 1) {
|
|
send_caller_id(ftdmchan);
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK]++;
|
|
} else if (state_counter > 600 && !ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK]) {
|
|
send_caller_id(ftdmchan);
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK]++;
|
|
} else if (state_counter > 1000 && !ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK]) {
|
|
done = 1;
|
|
} else if (state_counter > 10000) {
|
|
if (ftdmchan->fsk_buffer) {
|
|
ftdm_buffer_zero(ftdmchan->fsk_buffer);
|
|
} else {
|
|
ftdm_buffer_create(&ftdmchan->fsk_buffer, 128, 128, 0);
|
|
}
|
|
|
|
ts.user_data = ftdmchan->fsk_buffer;
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_CALLWAITING_SAS]);
|
|
ts.user_data = dt_buffer;
|
|
done = 1;
|
|
}
|
|
|
|
if (done) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
|
|
ftdm_clear_flag_locked(ftdmchan->span, FTDM_SPAN_STATE_CHANGE);
|
|
ftdm_channel_complete_state(ftdmchan);
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK] = 0;
|
|
}
|
|
}
|
|
case FTDM_CHANNEL_STATE_UP:
|
|
case FTDM_CHANNEL_STATE_RING:
|
|
case FTDM_CHANNEL_STATE_PROGRESS_MEDIA:
|
|
{
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND) &&
|
|
ftdmchan->state == FTDM_CHANNEL_STATE_PROGRESS_MEDIA &&
|
|
ftdm_test_sflag(ftdmchan, AF_POLARITY_REVERSE)) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Answering on polarity reverse\n");
|
|
ftdm_clear_sflag(ftdmchan, AF_POLARITY_REVERSE);
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
|
|
answer_on_polarity_counter = state_counter;
|
|
} else if (ftdmchan->state == FTDM_CHANNEL_STATE_UP
|
|
&& ftdm_test_sflag(ftdmchan, AF_POLARITY_REVERSE)){
|
|
/* if this polarity reverse is close to the answer polarity reverse, ignore it */
|
|
if (answer_on_polarity_counter
|
|
&& (state_counter - answer_on_polarity_counter) > analog_data->polarity_delay) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_NOTICE, "Hanging up on polarity reverse\n");
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_HANGUP);
|
|
} else {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_WARNING,
|
|
"Not hanging up on polarity reverse, too close to Answer reverse\n");
|
|
}
|
|
ftdm_clear_sflag(ftdmchan, AF_POLARITY_REVERSE);
|
|
} else {
|
|
ftdm_sleep(interval);
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_DOWN:
|
|
{
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
ftdm_clear_flag_locked(ftdmchan->span, FTDM_SPAN_STATE_CHANGE);
|
|
ftdm_channel_complete_state(ftdmchan);
|
|
indicate = 0;
|
|
state_counter = 0;
|
|
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Executing state handler on %d:%d for %s\n",
|
|
ftdmchan->span_id, ftdmchan->chan_id,
|
|
ftdm_channel_state2str(ftdmchan->state));
|
|
switch(ftdmchan->state) {
|
|
case FTDM_CHANNEL_STATE_UP:
|
|
{
|
|
ftdm_channel_use(ftdmchan);
|
|
ftdm_channel_clear_needed_tones(ftdmchan);
|
|
ftdm_channel_flush_dtmf(ftdmchan);
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXO && !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_OFFHOOK, NULL);
|
|
}
|
|
|
|
if (ftdmchan->fsk_buffer && ftdm_buffer_inuse(ftdmchan->fsk_buffer)) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Cancel FSK transmit due to early answer.\n");
|
|
ftdm_buffer_zero(ftdmchan->fsk_buffer);
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXS && ftdm_test_flag(ftdmchan, FTDM_CHANNEL_RINGING)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GENERATE_RING_OFF, NULL);
|
|
}
|
|
|
|
if (ftdmchan->token_count == 1) {
|
|
ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_HOLD);
|
|
}
|
|
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_HOLD)) {
|
|
ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_HOLD);
|
|
sig.event_id = FTDM_SIGEVENT_ADD_CALL;
|
|
} else {
|
|
sig.event_id = FTDM_SIGEVENT_UP;
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXS &&
|
|
!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND) &&
|
|
ftdm_test_flag(analog_data, FTDM_ANALOG_ANSWER_POLARITY_REVERSE)) {
|
|
ftdm_polarity_t polarity = FTDM_POLARITY_REVERSE;
|
|
if (ftdmchan->polarity == FTDM_POLARITY_FORWARD) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Reversing polarity on answer\n");
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_POLARITY, &polarity);
|
|
} else {
|
|
/* the polarity may be already reversed if this is the second time we
|
|
* answer (ie, due to 2 calls being on the same line) */
|
|
}
|
|
}
|
|
|
|
ftdm_span_send_signal(ftdmchan->span, &sig);
|
|
continue;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_DIALING:
|
|
{
|
|
ftdm_channel_use(ftdmchan);
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_RING:
|
|
{
|
|
ftdm_channel_use(ftdmchan);
|
|
sig.event_id = FTDM_SIGEVENT_START;
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXO) {
|
|
ftdm_set_string(ftdmchan->caller_data.dnis.digits, ftdmchan->chan_number);
|
|
} else {
|
|
ftdm_set_string(ftdmchan->caller_data.dnis.digits, dtmf);
|
|
}
|
|
|
|
ftdm_span_send_signal(ftdmchan->span, &sig);
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case FTDM_CHANNEL_STATE_HANGUP:
|
|
/* this state is only used when the user hangup, if the device hang up (onhook) we currently
|
|
* go straight to DOWN. If we ever change this (as other signaling modules do) by using this
|
|
* state for both user and device hangup, we should check here for the type of hangup since
|
|
* some actions (polarity reverse) do not make sense if the device hung up */
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXS &&
|
|
ftdmchan->last_state == FTDM_CHANNEL_STATE_UP &&
|
|
ftdm_test_flag(analog_data, FTDM_ANALOG_HANGUP_POLARITY_REVERSE)) {
|
|
ftdm_polarity_t polarity = ftdmchan->polarity == FTDM_POLARITY_REVERSE
|
|
? FTDM_POLARITY_FORWARD : FTDM_POLARITY_REVERSE;
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Reversing polarity on hangup\n");
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_POLARITY, &polarity);
|
|
}
|
|
break;
|
|
|
|
case FTDM_CHANNEL_STATE_DOWN:
|
|
{
|
|
sig.event_id = FTDM_SIGEVENT_STOP;
|
|
ftdm_span_send_signal(ftdmchan->span, &sig);
|
|
goto done;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_DIALTONE:
|
|
{
|
|
memset(&ftdmchan->caller_data, 0, sizeof(ftdmchan->caller_data));
|
|
*dtmf = '\0';
|
|
dtmf_offset = 0;
|
|
ftdm_buffer_zero(dt_buffer);
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_DIAL]);
|
|
indicate = 1;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_CALLWAITING:
|
|
{
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK] = 0;
|
|
if (ftdmchan->fsk_buffer) {
|
|
ftdm_buffer_zero(ftdmchan->fsk_buffer);
|
|
} else {
|
|
ftdm_buffer_create(&ftdmchan->fsk_buffer, 128, 128, 0);
|
|
}
|
|
|
|
ts.user_data = ftdmchan->fsk_buffer;
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_CALLWAITING_SAS]);
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_CALLWAITING_CAS]);
|
|
ts.user_data = dt_buffer;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_GENRING:
|
|
{
|
|
ftdm_sigmsg_t sig;
|
|
|
|
send_caller_id(ftdmchan);
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GENERATE_RING_ON, NULL);
|
|
|
|
memset(&sig, 0, sizeof(sig));
|
|
sig.chan_id = ftdmchan->chan_id;
|
|
sig.span_id = ftdmchan->span_id;
|
|
sig.channel = ftdmchan;
|
|
sig.event_id = FTDM_SIGEVENT_PROGRESS;
|
|
ftdm_span_send_signal(ftdmchan->span, &sig);
|
|
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_GET_CALLERID:
|
|
{
|
|
memset(&ftdmchan->caller_data, 0, sizeof(ftdmchan->caller_data));
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Initializing cid data!\n");
|
|
ftdm_set_string(ftdmchan->caller_data.ani.digits, "unknown");
|
|
ftdm_set_string(ftdmchan->caller_data.cid_name, ftdmchan->caller_data.ani.digits);
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_ENABLE_CALLERID_DETECT, NULL);
|
|
continue;
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_RINGING:
|
|
{
|
|
ftdm_buffer_zero(dt_buffer);
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_RING]);
|
|
indicate = 1;
|
|
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_BUSY:
|
|
{
|
|
ftdmchan->caller_data.hangup_cause = FTDM_CAUSE_NORMAL_CIRCUIT_CONGESTION;
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK) && !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND)) {
|
|
ftdm_buffer_zero(dt_buffer);
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_BUSY]);
|
|
indicate = 1;
|
|
} else {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_CHANNEL_STATE_ATTN:
|
|
{
|
|
if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK) && !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OUTBOUND)) {
|
|
ftdm_buffer_zero(dt_buffer);
|
|
teletone_run(&ts, ftdmchan->span->tone_map[FTDM_TONEMAP_ATTN]);
|
|
indicate = 1;
|
|
} else {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (ftdmchan->state == FTDM_CHANNEL_STATE_DIALTONE || ftdmchan->state == FTDM_CHANNEL_STATE_COLLECT) {
|
|
if ((dlen = ftdm_channel_dequeue_dtmf(ftdmchan, dtmf + dtmf_offset, sizeof(dtmf) - strlen(dtmf)))) {
|
|
|
|
if (ftdmchan->state == FTDM_CHANNEL_STATE_DIALTONE) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_COLLECT);
|
|
collecting = 1;
|
|
}
|
|
dtmf_offset = strlen(dtmf);
|
|
last_digit = elapsed;
|
|
sig.event_id = FTDM_SIGEVENT_COLLECTED_DIGIT;
|
|
ftdm_set_string(sig.ev_data.collected.digits, dtmf);
|
|
if (ftdm_span_send_signal(ftdmchan->span, &sig) == FTDM_BREAK) {
|
|
collecting = 0;
|
|
}
|
|
}
|
|
else if(!analog_data->max_dialstr)
|
|
{
|
|
last_digit = elapsed;
|
|
collecting = 0;
|
|
strcpy(dtmf, analog_data->hotline);
|
|
}
|
|
}
|
|
|
|
|
|
if (last_digit && (!collecting || ((elapsed - last_digit > analog_data->digit_timeout) || strlen(dtmf) >= analog_data->max_dialstr))) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Number obtained [%s]\n", dtmf);
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_RING);
|
|
last_digit = 0;
|
|
collecting = 0;
|
|
}
|
|
|
|
if (ftdm_channel_wait(ftdmchan, &flags, interval * 2) != FTDM_SUCCESS) {
|
|
continue;
|
|
}
|
|
|
|
if (!(flags & FTDM_READ)) {
|
|
continue;
|
|
}
|
|
|
|
if (ftdm_channel_read(ftdmchan, frame, &len) != FTDM_SUCCESS) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_WARNING, "read error [%s]\n", ftdmchan->last_error);
|
|
continue;
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXO && ftdmchan->detected_tones[0]) {
|
|
int i;
|
|
|
|
for (i = 1; i < FTDM_TONEMAP_INVALID; i++) {
|
|
if (ftdmchan->detected_tones[i]) {
|
|
ftdm_log_chan(ftdmchan, FTDM_LOG_DEBUG, "Detected tone %s on %d:%d\n", ftdm_tonemap2str(i), ftdmchan->span_id, ftdmchan->chan_id);
|
|
}
|
|
}
|
|
|
|
if (ftdmchan->detected_tones[FTDM_TONEMAP_BUSY] ||
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_FAIL1] ||
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_FAIL2] ||
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_FAIL3] ||
|
|
ftdmchan->detected_tones[FTDM_TONEMAP_ATTN]
|
|
) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_ERROR, "Failure indication detected!\n");
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_BUSY);
|
|
} else if (ftdmchan->detected_tones[FTDM_TONEMAP_DIAL]) {
|
|
analog_dial(ftdmchan, &state_counter, &dial_timeout);
|
|
} else if (ftdmchan->detected_tones[FTDM_TONEMAP_RING]) {
|
|
ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_UP);
|
|
}
|
|
|
|
ftdm_channel_clear_detected_tones(ftdmchan);
|
|
} else if (!dial_timeout) {
|
|
/* we were requested not to wait for dial tone, we can dial immediately */
|
|
analog_dial(ftdmchan, &state_counter, &dial_timeout);
|
|
}
|
|
|
|
if ((ftdmchan->dtmf_buffer && ftdm_buffer_inuse(ftdmchan->dtmf_buffer)) || (ftdmchan->fsk_buffer && ftdm_buffer_inuse(ftdmchan->fsk_buffer))) {
|
|
//rlen = len;
|
|
//memset(frame, 0, len);
|
|
//ftdm_channel_write(ftdmchan, frame, sizeof(frame), &rlen);
|
|
continue;
|
|
}
|
|
|
|
if (!indicate) {
|
|
continue;
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXO && !ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_OFFHOOK, NULL);
|
|
}
|
|
|
|
if (ftdmchan->effective_codec != FTDM_CODEC_SLIN) {
|
|
len *= 2;
|
|
}
|
|
|
|
rlen = ftdm_buffer_read_loop(dt_buffer, frame, len);
|
|
|
|
if (ftdmchan->effective_codec != FTDM_CODEC_SLIN) {
|
|
fio_codec_t codec_func = NULL;
|
|
|
|
if (ftdmchan->native_codec == FTDM_CODEC_ULAW) {
|
|
codec_func = fio_slin2ulaw;
|
|
} else if (ftdmchan->native_codec == FTDM_CODEC_ALAW) {
|
|
codec_func = fio_slin2alaw;
|
|
}
|
|
|
|
if (codec_func) {
|
|
codec_func(frame, sizeof(frame), &rlen);
|
|
} else {
|
|
snprintf(ftdmchan->last_error, sizeof(ftdmchan->last_error), "codec error!");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
ftdm_channel_write(ftdmchan, frame, sizeof(frame), &rlen);
|
|
}
|
|
|
|
done:
|
|
|
|
closed_chan = ftdmchan;
|
|
|
|
ftdm_channel_lock(closed_chan);
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXO && ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK)) {
|
|
ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Going onhook");
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_ONHOOK, NULL);
|
|
}
|
|
|
|
if (ftdmchan->type == FTDM_CHAN_TYPE_FXS && ftdm_test_flag(ftdmchan, FTDM_CHANNEL_RINGING)) {
|
|
ftdm_channel_command(ftdmchan, FTDM_COMMAND_GENERATE_RING_OFF, NULL);
|
|
}
|
|
|
|
|
|
ftdm_clear_sflag(ftdmchan, AF_POLARITY_REVERSE);
|
|
|
|
ftdm_channel_close(&ftdmchan);
|
|
|
|
ftdm_channel_command(closed_chan, FTDM_COMMAND_SET_NATIVE_CODEC, NULL);
|
|
|
|
if (ts.buffer) {
|
|
teletone_destroy_session(&ts);
|
|
}
|
|
|
|
if (dt_buffer) {
|
|
ftdm_buffer_destroy(&dt_buffer);
|
|
}
|
|
|
|
if (closed_chan->state != FTDM_CHANNEL_STATE_DOWN) {
|
|
ftdm_set_state_locked(closed_chan, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
|
|
ftdm_log_chan(closed_chan, FTDM_LOG_DEBUG, "ANALOG CHANNEL %d:%d thread ended.\n", closed_chan->span_id, closed_chan->chan_id);
|
|
|
|
ftdm_clear_flag(closed_chan, FTDM_CHANNEL_INTHREAD);
|
|
|
|
ftdm_channel_unlock(closed_chan);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Processes freetdm event
|
|
* \param span Span on which the event was fired
|
|
* \param event Event to be treated
|
|
* \return Success or failure
|
|
*/
|
|
static __inline__ ftdm_status_t process_event(ftdm_span_t *span, ftdm_event_t *event)
|
|
{
|
|
ftdm_sigmsg_t sig;
|
|
ftdm_analog_data_t *analog_data = event->channel->span->signal_data;
|
|
int locked = 0;
|
|
|
|
memset(&sig, 0, sizeof(sig));
|
|
sig.chan_id = event->channel->chan_id;
|
|
sig.span_id = event->channel->span_id;
|
|
sig.channel = event->channel;
|
|
|
|
|
|
ftdm_log_chan(event->channel, FTDM_LOG_DEBUG, "Received event [%s] in state [%s]\n", ftdm_oob_event2str(event->enum_id), ftdm_channel_state2str(event->channel->state));
|
|
|
|
ftdm_mutex_lock(event->channel->mutex);
|
|
locked++;
|
|
|
|
/* MAINTENANCE WARNING:
|
|
* 1. Be aware you are working on the locked channel
|
|
* 2. We should not be calling ftdm_span_send_signal or ftdm_set_state when there is already a channel thread running
|
|
* however, since this is old code I am not changing it now, but new code should adhere to that convention
|
|
* otherwise, we have possible races where we compete with the user for state changes, ie, the user requests
|
|
* a state change and then we process an event, the state change from the user is pending so our ftdm_set_state
|
|
* operation will fail. In cases where we win the race, our state change will be accepted but if a user requests
|
|
* a state change before the state change we requested here is processed by the channel thread, we'll end up
|
|
* rejecting the user request.
|
|
*
|
|
* See docs/locking.txt for further information about what guarantees should signaling modules provide when
|
|
* locking/unlocking a channel
|
|
* */
|
|
switch(event->enum_id) {
|
|
case FTDM_OOB_RING_START:
|
|
{
|
|
if (event->channel->type != FTDM_CHAN_TYPE_FXO) {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_ERROR, "Cannot get a RING_START event on a non-fxo channel, please check your config.\n");
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_DOWN);
|
|
goto end;
|
|
}
|
|
if (!event->channel->ring_count && (event->channel->state == FTDM_CHANNEL_STATE_DOWN && !ftdm_test_flag(event->channel, FTDM_CHANNEL_INTHREAD))) {
|
|
if (ftdm_test_flag(analog_data, FTDM_ANALOG_CALLERID)) {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_GET_CALLERID);
|
|
} else {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_RING);
|
|
}
|
|
event->channel->ring_count = 1;
|
|
ftdm_mutex_unlock(event->channel->mutex);
|
|
locked = 0;
|
|
ftdm_thread_create_detached(ftdm_analog_channel_run, event->channel);
|
|
} else {
|
|
event->channel->ring_count++;
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_OOB_ONHOOK:
|
|
{
|
|
if (ftdm_test_flag(event->channel, FTDM_CHANNEL_RINGING)) {
|
|
ftdm_channel_command(event->channel, FTDM_COMMAND_GENERATE_RING_OFF, NULL);
|
|
}
|
|
|
|
if (event->channel->state != FTDM_CHANNEL_STATE_DOWN) {
|
|
if (event->channel->state == FTDM_CHANNEL_STATE_HANGUP &&
|
|
ftdm_test_flag(event->channel, FTDM_CHANNEL_STATE_CHANGE)) {
|
|
/* we do not need to process HANGUP since the device also hangup already */
|
|
ftdm_channel_complete_state(event->channel);
|
|
}
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
if (event->channel->type == FTDM_CHAN_TYPE_FXS) {
|
|
/* we always return to forward when the device goes onhook */
|
|
ftdm_polarity_t forward_polarity = FTDM_POLARITY_FORWARD;
|
|
ftdm_channel_command(event->channel, FTDM_COMMAND_SET_POLARITY, &forward_polarity);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_OOB_FLASH:
|
|
{
|
|
if (event->channel->state == FTDM_CHANNEL_STATE_CALLWAITING) {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_UP);
|
|
ftdm_clear_flag(event->channel->span, FTDM_SPAN_STATE_CHANGE);
|
|
ftdm_channel_complete_state(event->channel);
|
|
event->channel->detected_tones[FTDM_TONEMAP_CALLWAITING_ACK] = 0;
|
|
}
|
|
|
|
ftdm_channel_rotate_tokens(event->channel);
|
|
|
|
if (ftdm_test_flag(event->channel, FTDM_CHANNEL_HOLD) && event->channel->token_count != 1) {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_UP);
|
|
} else {
|
|
sig.event_id = FTDM_SIGEVENT_FLASH;
|
|
ftdm_span_send_signal(span, &sig);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_OOB_OFFHOOK:
|
|
{
|
|
if (event->channel->type == FTDM_CHAN_TYPE_FXS) {
|
|
if (ftdm_test_flag(event->channel, FTDM_CHANNEL_INTHREAD)) {
|
|
if (ftdm_test_flag(event->channel, FTDM_CHANNEL_RINGING)) {
|
|
ftdm_channel_command(event->channel, FTDM_COMMAND_GENERATE_RING_OFF, NULL);
|
|
}
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_UP);
|
|
} else {
|
|
if(!analog_data->max_dialstr) {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_COLLECT);
|
|
} else {
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_DIALTONE);
|
|
}
|
|
ftdm_mutex_unlock(event->channel->mutex);
|
|
locked = 0;
|
|
ftdm_thread_create_detached(ftdm_analog_channel_run, event->channel);
|
|
}
|
|
} else {
|
|
if (!ftdm_test_flag(event->channel, FTDM_CHANNEL_INTHREAD)) {
|
|
if (ftdm_test_flag(event->channel, FTDM_CHANNEL_OFFHOOK)) {
|
|
ftdm_channel_command(event->channel, FTDM_COMMAND_ONHOOK, NULL);
|
|
}
|
|
}
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_DOWN);
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_OOB_ALARM_TRAP:
|
|
{
|
|
sig.event_id = FTDM_SIGEVENT_SIGSTATUS_CHANGED;
|
|
sig.ev_data.sigstatus.status = FTDM_SIG_STATE_DOWN;
|
|
ftdm_span_send_signal(span, &sig);
|
|
}
|
|
break;
|
|
case FTDM_OOB_ALARM_CLEAR:
|
|
{
|
|
sig.event_id = FTDM_SIGEVENT_SIGSTATUS_CHANGED;
|
|
sig.ev_data.sigstatus.status = FTDM_SIG_STATE_UP;
|
|
ftdm_span_send_signal(span, &sig);
|
|
}
|
|
break;
|
|
case FTDM_OOB_POLARITY_REVERSE:
|
|
{
|
|
if (event->channel->type != FTDM_CHAN_TYPE_FXO) {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_WARNING,
|
|
"Ignoring polarity reversal, this should not happen in non-FXO channels!\n");
|
|
break;
|
|
}
|
|
if (!ftdm_test_flag(event->channel, FTDM_CHANNEL_INTHREAD) &&
|
|
ftdm_test_flag(event->channel, FTDM_CHANNEL_OFFHOOK)) {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_WARNING,
|
|
"Forcing onhook in channel not in thread after polarity reversal\n");
|
|
ftdm_channel_command(event->channel, FTDM_COMMAND_ONHOOK, NULL);
|
|
break;
|
|
}
|
|
if (!ftdm_test_flag(analog_data, FTDM_ANALOG_ANSWER_POLARITY_REVERSE)
|
|
&& !ftdm_test_flag(analog_data, FTDM_ANALOG_HANGUP_POLARITY_REVERSE)) {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_DEBUG,
|
|
"Ignoring polarity reversal because this channel is not configured for it\n");
|
|
break;
|
|
}
|
|
if (event->channel->state == FTDM_CHANNEL_STATE_DOWN) {
|
|
if (ftdm_test_flag(analog_data, FTDM_ANALOG_CALLERID)
|
|
&& ftdm_test_flag(analog_data, FTDM_ANALOG_POLARITY_CALLERID)) {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_DEBUG, "Polarity reversal detected while down, getting caller id now\n");
|
|
ftdm_set_state(event->channel, FTDM_CHANNEL_STATE_GET_CALLERID);
|
|
event->channel->ring_count = 1;
|
|
ftdm_mutex_unlock(event->channel->mutex);
|
|
locked = 0;
|
|
ftdm_thread_create_detached(ftdm_analog_channel_run, event->channel);
|
|
} else {
|
|
ftdm_log_chan_msg(event->channel, FTDM_LOG_DEBUG,
|
|
"Ignoring polarity reversal because this channel is down\n");
|
|
}
|
|
break;
|
|
}
|
|
/* we have a good channel, set the polarity flag and let the channel thread deal with it */
|
|
ftdm_set_sflag(event->channel, AF_POLARITY_REVERSE);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
ftdm_log_chan(event->channel, FTDM_LOG_DEBUG, "Ignoring event [%s] in state [%s]\n", ftdm_oob_event2str(event->enum_id), ftdm_channel_state2str(event->channel->state));
|
|
}
|
|
break;
|
|
}
|
|
|
|
end:
|
|
|
|
if (locked) {
|
|
ftdm_mutex_unlock(event->channel->mutex);
|
|
}
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief Main thread function for analog span (monitor)
|
|
* \param me Current thread
|
|
* \param obj Span to run in this thread
|
|
*/
|
|
static void *ftdm_analog_run(ftdm_thread_t *me, void *obj)
|
|
{
|
|
ftdm_span_t *span = (ftdm_span_t *) obj;
|
|
ftdm_analog_data_t *analog_data = span->signal_data;
|
|
int errs = 0;
|
|
|
|
ftdm_unused_arg(me);
|
|
ftdm_log(FTDM_LOG_DEBUG, "ANALOG thread starting.\n");
|
|
|
|
while(ftdm_running() && ftdm_test_flag(analog_data, FTDM_ANALOG_RUNNING)) {
|
|
int waitms = 1000;
|
|
ftdm_status_t status;
|
|
|
|
if ((status = ftdm_span_poll_event(span, waitms, NULL)) != FTDM_FAIL) {
|
|
errs = 0;
|
|
}
|
|
|
|
switch(status) {
|
|
case FTDM_SUCCESS:
|
|
{
|
|
ftdm_event_t *event;
|
|
while (ftdm_span_next_event(span, &event) == FTDM_SUCCESS) {
|
|
if (event->enum_id == FTDM_OOB_NOOP) {
|
|
continue;
|
|
}
|
|
if (process_event(span, event) != FTDM_SUCCESS) {
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case FTDM_FAIL:
|
|
{
|
|
ftdm_log(FTDM_LOG_ERROR, "Failure Polling event! [%s]\n", span->last_error);
|
|
if (++errs > 300) {
|
|
ftdm_log(FTDM_LOG_CRIT, "Too Many Errors!\n");
|
|
goto end;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
ftdm_clear_flag(analog_data, FTDM_ANALOG_RUNNING);
|
|
|
|
ftdm_log(FTDM_LOG_DEBUG, "ANALOG thread ending.\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief FreeTDM analog signaling module initialisation
|
|
* \return Success
|
|
*/
|
|
static FIO_SIG_LOAD_FUNCTION(ftdm_analog_init)
|
|
{
|
|
return FTDM_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* \brief FreeTDM analog signaling module definition
|
|
*/
|
|
EX_DECLARE_DATA ftdm_module_t ftdm_module = {
|
|
"analog",
|
|
NULL,
|
|
NULL,
|
|
ftdm_analog_init,
|
|
ftdm_analog_configure_span,
|
|
NULL
|
|
};
|
|
|
|
|
|
/* For Emacs:
|
|
* Local Variables:
|
|
* mode:c
|
|
* indent-tabs-mode:t
|
|
* tab-width:4
|
|
* c-basic-offset:4
|
|
* End:
|
|
* For VIM:
|
|
* vim:set softtabstop=4 shiftwidth=4 tabstop=4:
|
|
*/
|