* - PORT_STATE_OUT_SETUP: the seizing procedure is started.
* - PORT_STATE_OUT_OVERLAP: the transmitter is sending the digits.
* - PORT_STATE_OUT_PROCEEDING: the digits are sent, we wait until someone answers.
+ * - PORT_STATE_OUT_DISCONNECT: a clear-back is sent, after DISCONNECT was received
* - PORT_STATE_CONNECT: a call is answered on either side.
* - PORT_STATE_IN_SETUP: the seizing is received, we wait for the first digit.
* - PORT_STATE_IN_OVERLAP: the digits are received.
#define BELL_TIMER_RECOG_END (300*8) /* recognize end of digit */
/* ss5 timers */
-#define SS5_TIMER_OVERLAP 5.0 /* timeout for overlap digits received on outgoing exchange */
+#define SS5_TIMER_OVERLAP 10 /* timeout for overlap digits received on incomming exchange */
+#define SS5_TIMER_RELEASE 20 /* timeout after disconnect on incomming exchange */
/*
SS5_DIALING_REQ,
SS5_FORWARD_TRANSFER_IND,
SS5_FORWARD_TRANSFER_REQ,
+ SS5_TIMEOUT_IND,
+ SS5_QUALITY_IND,
};
static struct isdn_message {
const char *name;
{"DIALING SENDING", SS5_DIALING_REQ},
{"FORWARD-TRANSFER RECEIVED", SS5_FORWARD_TRANSFER_IND},
{"FORWARD-TRANSFER SENDING", SS5_FORWARD_TRANSFER_REQ},
+ {"TIMEOUT", SS5_TIMEOUT_IND},
+ {"QUALITY REPORT", SS5_QUALITY_IND},
{NULL, 0},
};
static void ss5_trace_header(struct mISDNport *mISDNport, class PmISDN *port, unsigned int msg, int channel)
memset(&port_settings, 0, sizeof(port_settings));
SCPY(port_settings.tones_dir, options.tones_dir);
- ss5port = new Pss5(PORT_TYPE_SS5_IDLE, mISDNport, portname, &port_settings, i + (i>=15) + 1, 1, B_MODE_TRANSPARENT);
+ ss5port = new Pss5(PORT_TYPE_SS5_IDLE, mISDNport, portname, &port_settings, mISDNport->ifport->interface, i + (i>=15) + 1, 1, B_MODE_TRANSPARENT);
if (!ss5port)
FATAL("No memory for Pss5 class.\n");
if (!ss5port->p_m_b_channel)
return NULL;
}
-int queue_event(struct lcr_work *work, void *instance, int index);
+static int timeout(struct lcr_timer *timer, void *instance, int i)
+{
+ class Pss5 *ss5 = (class Pss5 *)instance;
+
+ ss5->register_timeout();
+
+ return 0;
+}
+
+int queue_event(struct lcr_work *work, void *instance, int index)
+{
+ class Pss5 *ss5port = (class Pss5 *)instance;
+
+ ss5port->process_queue();
+
+ return 0;
+}
/*
* constructor
*/
-Pss5::Pss5(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, int channel, int exclusive, int mode) : PmISDN(type, mISDNport, portname, settings, channel, exclusive, mode)
+Pss5::Pss5(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, struct interface *interface, int channel, int exclusive, int mode) : PmISDN(type, mISDNport, portname, settings, interface, channel, exclusive, mode)
{
p_callerinfo.itype = (mISDNport->ifport->interface->extension)?INFO_ITYPE_ISDN_EXTENSION:INFO_ITYPE_ISDN;
p_m_s_state = SS5_STATE_IDLE;
p_m_s_decoder_count = 0;
//p_m_s_decoder_buffer;
p_m_s_sample_nr = 0;
+ p_m_s_quality_value = 0;
+ p_m_s_quality_count = 0;
p_m_s_recog = 0;
memset(&p_m_s_queue, 0, sizeof(p_m_s_queue));
add_work(&p_m_s_queue, queue_event, this, 0);
- p_m_s_answer = 0;
- p_m_s_busy_flash = 0;
- p_m_s_clear_back = 0;
+ p_m_s_queued_signal = 0;
memset(p_m_s_delay_digits, ' ', sizeof(p_m_s_delay_digits));
memset(p_m_s_delay_mute, ' ', sizeof(p_m_s_delay_mute));
+ memset(&p_m_s_timer, 0, sizeof(p_m_s_timer));
+ add_timer(&p_m_s_timer, timeout, this, 0);
/* turn on signalling receiver */
inband_receive_on();
*/
Pss5::~Pss5()
{
+ del_timer(&p_m_s_timer);
del_work(&p_m_s_queue);
}
/*
+ * timeout trigger
+ */
+void Pss5::register_timeout(void)
+{
+ ss5_trace_header(p_m_mISDNport, this, SS5_TIMEOUT_IND, p_m_b_channel);
+ end_trace();
+ switch(p_state) {
+ case PORT_STATE_IN_SETUP:
+ PDEBUG(DEBUG_SS5, "%s: timeout after seize\n", p_name);
+ do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_BACK_REQ);
+ break;
+ case PORT_STATE_IN_OVERLAP:
+ PDEBUG(DEBUG_SS5, "%s: timeout during dialing\n", p_name);
+ do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_BACK_REQ);
+ break;
+ case PORT_STATE_OUT_DISCONNECT:
+ PDEBUG(DEBUG_SS5, "%s: timeout after sending busy flash / clear forward\n", p_name);
+ /* always send clear forward, because release guard only works as a reply to clear forward */
+ do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_FORWARD_REQ);
+ break;
+ }
+}
+
+/*
+ * change port state
+ */
+void Pss5::new_state(int state)
+{
+ switch(state) {
+ case PORT_STATE_IN_SETUP:
+ case PORT_STATE_IN_OVERLAP:
+ if (SS5_TIMER_OVERLAP == 0)
+ break;
+ PDEBUG(DEBUG_SS5, "%s: starting timeout timer with %d seconds\n", p_name, SS5_TIMER_OVERLAP);
+ schedule_timer(&p_m_s_timer, SS5_TIMER_OVERLAP, 0);
+ break;
+ case PORT_STATE_OUT_DISCONNECT:
+ if (p_type != PORT_TYPE_SS5_IN)
+ break;
+ if (SS5_TIMER_RELEASE == 0)
+ break;
+ PDEBUG(DEBUG_SS5, "%s: starting timeout timer with %d seconds\n", p_name, SS5_TIMER_RELEASE);
+ schedule_timer(&p_m_s_timer, SS5_TIMER_RELEASE, 0);
+ break;
+ default:
+ PDEBUG(DEBUG_SS5, "%s: stopping timeout timer\n", p_name);
+ unsched_timer(&p_m_s_timer);
+ }
+ Port::new_state(state);
+}
+
+/*
* change ss5 states
*/
void Pss5::_new_ss5_state(int state, const char *func, int line)
p_m_s_state = state;
p_m_s_signal = SS5_SIGNAL_NULL;
- if (p_m_s_state == SS5_STATE_IDLE && (p_m_s_answer || p_m_s_busy_flash || p_m_s_clear_back))
+ if (p_m_s_state == SS5_STATE_IDLE && p_m_s_queued_signal)
trigger_work(&p_m_s_queue);
}
-int queue_event(struct lcr_work *work, void *instance, int index)
+void Pss5::process_queue(void)
{
- class Pss5 *ss5port = (class Pss5 *)instance;
-
- if (ss5port->p_m_s_state == SS5_STATE_IDLE) {
- /* if answer signal is queued */
- if (ss5port->p_m_s_answer) {
- ss5port->p_m_s_answer = 0;
- /* start answer */
- ss5_trace_header(ss5port->p_m_mISDNport, ss5port, SS5_ANSWER_REQ, ss5port->p_m_b_channel);
- end_trace();
- ss5port->start_signal(SS5_STATE_ANSWER);
- }
+ /* when clear forward is scheduled, we abort current state */
+ if (p_m_s_queued_signal == SS5_CLEAR_FORWARD_REQ)
+ new_ss5_state(SS5_STATE_IDLE);
+
+ /* if there is an ongoing signal, we wait until done */
+ if (p_m_s_state != SS5_STATE_IDLE)
+ return;
- /* if busy-flash signal is queued */
- if (ss5port->p_m_s_busy_flash) {
- ss5port->p_m_s_busy_flash = 0;
- /* start busy-flash */
- ss5_trace_header(ss5port->p_m_mISDNport, ss5port, SS5_BUSY_FLASH_REQ, ss5port->p_m_b_channel);
- end_trace();
- ss5port->start_signal(SS5_STATE_BUSY_FLASH);
- }
+ /* this shoud not happen */
+ if (!p_m_s_queued_signal)
+ return;
- /* if clear-back signal is queued */
- if (ss5port->p_m_s_clear_back) {
- ss5port->p_m_s_clear_back = 0;
- /* start clear-back */
- ss5_trace_header(ss5port->p_m_mISDNport, ss5port, SS5_CLEAR_BACK_REQ, ss5port->p_m_b_channel);
- end_trace();
- ss5port->start_signal(SS5_STATE_CLEAR_BACK);
- }
+ /* start signal */
+ ss5_trace_header(p_m_mISDNport, this, p_m_s_queued_signal, p_m_b_channel);
+ end_trace();
+ switch(p_m_s_queued_signal) {
+ case SS5_ANSWER_REQ:
+ /* start answer */
+ p_m_s_queued_signal = 0; /* prevent trigger loop */
+ start_signal(SS5_STATE_ANSWER);
+ break;
+ case SS5_BUSY_FLASH_REQ:
+ /* busy flash */
+ p_m_s_queued_signal = 0; /* prevent trigger loop */
+ start_signal(SS5_STATE_BUSY_FLASH);
+ break;
+ case SS5_CLEAR_BACK_REQ:
+ /* clear back */
+ p_m_s_queued_signal = 0; /* prevent trigger loop */
+ start_signal(SS5_STATE_CLEAR_BACK);
+ break;
+ case SS5_CLEAR_FORWARD_REQ:
+ /* clear forward */
+ p_m_s_queued_signal = 0; /* prevent trigger loop */
+ start_signal(SS5_STATE_CLEAR_FORWARD);
+ break;
+ default:
+ PERROR("unhandled event %d\n", p_m_s_queued_signal);
+ p_m_s_queued_signal = 0;
}
-
- return 0;
}
void Pss5::_new_ss5_signal(int signal, const char *func, int line)
{
if (p_m_s_signal)
- PDEBUG(DEBUG_SS5, "%s(%s:%d): changing SS5 signal state from %s to %s\n", p_name, func, line, ss5_signal_name[p_m_s_signal], ss5_signal_name[signal]);
+ PDEBUG(DEBUG_SS5, "%s: changing SS5 signal state from %s to %s\n", p_name, ss5_signal_name[p_m_s_signal], ss5_signal_name[signal]);
else
- PDEBUG(DEBUG_SS5, "%s(%s:%d): changing SS5 signal state to %s\n", p_name, func, line, ss5_signal_name[signal]);
+ PDEBUG(DEBUG_SS5, "%s: changing SS5 signal state to %s\n", p_name, ss5_signal_name[signal]);
p_m_s_signal = signal;
}
{
int count = 0, tocopy, space;
char digit;
+ double quality;
+ int mute = 0;
again:
/* how much to copy ? */
return;
/* decode one frame */
- digit = ss5_decode(p_m_s_decoder_buffer, SS5_DECODER_NPOINTS);
+ digit = ss5_decode(p_m_s_decoder_buffer, SS5_DECODER_NPOINTS, &quality);
p_m_s_decoder_count = 0;
+ /* indicate quality of received digit */
+ if ((p_m_mISDNport->ss5 & SS5_FEATURE_QUALITY)) {
+ if (digit != ' ') {
+ p_m_s_quality_value += quality;
+ p_m_s_quality_count++;
+ } else if (p_m_s_quality_count) {
+ ss5_trace_header(p_m_mISDNport, this, SS5_QUALITY_IND, p_m_b_channel);
+ add_trace("digit", NULL, "%c", p_m_s_last_digit);
+ quality = p_m_s_quality_value/p_m_s_quality_count;
+ add_trace("quality", NULL, "%3d%%", (int)(quality*100.0));
+ end_trace();
+ p_m_s_quality_value = 0;
+ p_m_s_quality_count = 0;
+ }
+ }
+
#ifdef DEBUG_DETECT
if (p_m_s_last_digit != digit && digit != ' ')
PDEBUG(DEBUG_SS5, "%s: detecting signal '%c' start (state=%s signal=%s)\n", p_name, digit, ss5_state_name[p_m_s_state], ss5_signal_name[p_m_s_signal]);
}
p_m_s_last_digit_used = digit;
- /* update mute */
- if ((p_m_mISDNport->ss5 & SS5_FEATURE_SUPPRESS)) {
- int mdigit;
- memcpy(p_m_s_delay_mute, p_m_s_delay_mute+1, sizeof(p_m_s_delay_mute)-1);
- p_m_s_delay_mute[sizeof(p_m_s_delay_mute)-1] = digit;
- mdigit = p_m_s_delay_mute[0];
- if (p_m_mute) {
- /* mute is on */
- if (mdigit != 'A' && mdigit != 'B' && mdigit != 'C')
- mute_off();
- } else {
- /* mute is off */
- if (mdigit == 'A' || mdigit == 'B' || mdigit == 'C')
- mute_on();
- }
- }
-
/* delay decoded tones */
if ((p_m_mISDNport->ss5 & SS5_FEATURE_DELAY)) {
/* shift buffer */
break;
}
+ /* update mute on RX */
+ if ((p_m_mISDNport->ss5 & SS5_FEATURE_MUTE_RX)) {
+ int mdigit;
+ memcpy(p_m_s_delay_mute, p_m_s_delay_mute+1, sizeof(p_m_s_delay_mute)-1);
+ p_m_s_delay_mute[sizeof(p_m_s_delay_mute)-1] = digit;
+ mdigit = p_m_s_delay_mute[0];
+ if (mdigit == 'A' || mdigit == 'B' || mdigit == 'C')
+ mute = 1;
+ }
+
+ /* mute when TX */
+ if ((p_m_mISDNport->ss5 & SS5_FEATURE_MUTE_TX)) {
+ switch(p_m_s_signal) {
+ case SS5_SIGNAL_SEND_ON_RECOG:
+ case SS5_SIGNAL_RECEIVE_RECOG:
+ if (p_m_s_recog > SS5_DELAY_MUTE)
+ mute = 1;
+ break;
+ case SS5_SIGNAL_SEND_OFF:
+ case SS5_SIGNAL_RECEIVE:
+ mute = 1;
+ break;
+ }
+ }
+
+ /* apply mute state */
+ if (p_m_mute) {
+ /* mute is on */
+ if (!mute)
+ mute_off();
+ } else {
+ /* mute is off */
+ if (mute)
+ mute_on();
+ }
+
/* something more to decode ? */
if (count != len)
goto again;
if (duration > 0 && p_m_s_sample_nr >= duration) {
PDEBUG(DEBUG_SS5, "%s: sending tone '%c' complete, starting delay\n", p_name, digit);
if (p_m_s_state == SS5_STATE_DOUBLE_SEIZE) {
- do_release(CAUSE_NOCHANNEL, LOCATION_PRIVATE_LOCAL);
+ do_release(CAUSE_NOCHANNEL, LOCATION_PRIVATE_LOCAL, SS5_CLEAR_FORWARD_REQ);
break;
}
new_ss5_state(SS5_STATE_DELAY);
add_trace("digit", NULL, "%s", dial+1);
add_trace("pulses", NULL, "%d", p_m_s_pulsecount);
end_trace();
- /* special star release feature */
- if ((p_m_mISDNport->ss5 & SS5_FEATURE_STAR_RELEASE) && dial[1] == '*') {
- ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel);
- add_trace("star", NULL, "releases call");
- end_trace();
- goto star_release;
- }
if (p_state == PORT_STATE_IN_SETUP) {
/* sending digit as setup */
do_setup(dial, 0); /* include 'a' == KP1 */
ss5_trace_header(p_m_mISDNport, this, SS5_DIALING_IND, p_m_b_channel);
add_trace("longtone", NULL, "releases call");
end_trace();
- star_release:
/* long pulse is gone, release current connection, if any */
while(p_epointlist) {
message = message_create(p_serial, p_epointlist->epoint_id, PORT_TO_EPOINT, MESSAGE_RELEASE);
/*
* shuts down by sending a clear forward and releasing endpoint
*/
-void Pss5::do_release(int cause, int location)
+void Pss5::do_release(int cause, int location, int signal)
{
struct lcr_msg *message;
free_epointlist(p_epointlist);
}
- /* start clear-forward */
- ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_FORWARD_REQ, p_m_b_channel);
- end_trace();
- start_signal(SS5_STATE_CLEAR_FORWARD);
-
- new_state(PORT_STATE_RELEASE);
+// if (p_state != PORT_STATE_RELEASE) {
+ p_m_s_queued_signal = signal;
+ if (signal == SS5_CLEAR_FORWARD_REQ) {
+ new_state(PORT_STATE_RELEASE);
+ set_tone("", "noise");
+ } else
+ new_state(PORT_STATE_OUT_DISCONNECT);
+ trigger_work(&p_m_s_queue);
+// }
}
FATAL("Incoming call but already got an endpoint.\n");
if (!(epoint = new Endpoint(p_serial, 0)))
FATAL("No memory for Endpoint instance\n");
- if (!(epoint->ep_app = new DEFAULT_ENDPOINT_APP(epoint, 0))) //incoming
- FATAL("No memory for Endpoint Application instance\n");
+ epoint->ep_app = new_endpointapp(epoint, 0, p_m_mISDNport->ifport->interface->app); //incoming
epointlist_new(epoint->ep_serial);
/* send setup message to endpoit */
memcpy(&p_connectinfo, ¶m->connectinfo, sizeof(p_connectinfo));
if (p_state != PORT_STATE_CONNECT) {
+ p_m_s_queued_signal = SS5_ANSWER_REQ;
new_state(PORT_STATE_CONNECT);
- p_m_s_answer = 1;
trigger_work(&p_m_s_queue);
}
/* MESSAGE_DISCONNECT */
void Pss5::message_disconnect(unsigned int epoint_id, int message_id, union parameter *param)
{
- /* disconnect and clear forward (release guard) */
-// if ((p_type==PORT_TYPE_SS5_IN && !p_m_mISDNport->tones) /* incomming exchange with no tones */
-if (0 || p_type==PORT_TYPE_SS5_OUT) { /* outgoing exchange */
- do_release(param->disconnectinfo.cause, param->disconnectinfo.location);
+ /* release and clear forward */
+ if (p_type==PORT_TYPE_SS5_OUT) { /* outgoing exchange */
+ do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_FORWARD_REQ);
+ return;
+ }
+ /* release and clear back */
+ if ((p_m_mISDNport->ss5 & SS5_FEATURE_RELEASE)) {
+ do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_BACK_REQ);
return;
}
- ss5_trace_header(p_m_mISDNport, this, SS5_CLEAR_BACK_REQ, p_m_b_channel);
- end_trace();
- start_signal(SS5_STATE_CLEAR_BACK);
-
- new_state(PORT_STATE_OUT_DISCONNECT);
+ /* disconnect by sending clear back (after answer) or busy flash (before answer) */
+ if (p_state != PORT_STATE_OUT_DISCONNECT) {
+ if (p_state == PORT_STATE_CONNECT)
+ p_m_s_queued_signal = SS5_CLEAR_BACK_REQ;
+ else
+ p_m_s_queued_signal = SS5_BUSY_FLASH_REQ;
+ new_state(PORT_STATE_OUT_DISCONNECT);
+ trigger_work(&p_m_s_queue);
+ }
}
/* MESSAGE_RELEASE */
void Pss5::message_release(unsigned int epoint_id, int message_id, union parameter *param)
{
- do_release(param->disconnectinfo.cause, param->disconnectinfo.location);
-}
-
-void Pss5::register_timeout(void)
-{
- do_release(CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL);
+ /* always send clear forward, because release guard only works as a reply to clear forward */
+ do_release(param->disconnectinfo.cause, param->disconnectinfo.location, SS5_CLEAR_FORWARD_REQ);
}
/*