From acaf278f7f616d264afe480e4f9c64768540941b Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 16 Dec 2012 09:31:36 +0100 Subject: [PATCH 1/1] Add FXS support This requires FXS support to mISDN too. --- Makefile.am | 2 +- action.cpp | 392 +++++++++++++++++++++++++++ appbridge.cpp | 7 +- apppbx.cpp | 369 ++++++++++++++++++++++++-- apppbx.h | 13 +- fxs.cpp | 835 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fxs.h | 48 ++++ interface.c | 66 +++++ interface.h | 3 + mISDN.cpp | 201 ++++++++++---- mISDN.h | 2 + main.h | 1 + message.h | 3 + port.h | 10 + route.c | 127 ++++++++- route.h | 11 + 16 files changed, 2020 insertions(+), 70 deletions(-) create mode 100644 fxs.cpp create mode 100644 fxs.h diff --git a/Makefile.am b/Makefile.am index 8da7b3f..e802edc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,7 +46,7 @@ INSTALLATION_DEFINES = \ if ENABLE_MISDN MISDN_INCLUDE = -DWITH_MISDN -DWITH_CRYPT -MISDN_SOURCE = mISDN.cpp dss1.cpp crypt.cpp +MISDN_SOURCE = mISDN.cpp fxs.cpp dss1.cpp crypt.cpp MISDN_LIB = -lmisdn endif diff --git a/action.cpp b/action.cpp index 7b60126..3fc6449 100644 --- a/action.cpp +++ b/action.cpp @@ -2049,6 +2049,398 @@ void EndpointAppPBX::action_dialing_password_wr(void) } +/* process pots-retrieve + */ +void EndpointAppPBX::action_init_pots_retrieve(void) +{ + struct route_param *rparam; + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs; + int count = 0; + class Endpoint *epoint; + + /* check given call */ + if (!(rparam = routeparam(e_action, PARAM_POTS_CALL))) { + trace_header("ACTION pots-retrieve (no call given)", DIRECTION_NONE); + end_trace(); + + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-retrieve (call not of FXS type)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + count++; + if (count == rparam->integer_value) + break; + } + } + port = port->next; + } + if (!port) { + trace_header("ACTION pots-retrieve (call # does not exist)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#ifdef ISDN_P_FXS_POTS + /* release our call */ + ourfxs->hangup_ind(0); + + /* retrieve selected call */ + fxs->retrieve_ind(0); +#endif + + /* split if selected call is member of a 3pty */ + epoint = find_epoint_id(ACTIVE_EPOINT(fxs->p_epointlist)); + if (epoint && epoint->ep_app_type == EAPP_TYPE_PBX) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) try spliting 3pty. this may fail because we don't have a 3pty.\n", epoint->ep_serial); + ((class EndpointAppPBX *)epoint->ep_app)->split_3pty(); + } +} + + +/* process pots-release + */ +void EndpointAppPBX::action_init_pots_release(void) +{ + struct route_param *rparam; + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs; + int count = 0; + + /* check given call */ + if (!(rparam = routeparam(e_action, PARAM_POTS_CALL))) { + trace_header("ACTION pots-release (no call given)", DIRECTION_NONE); + end_trace(); + + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-release (call not of FXS type)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + count++; + if (count == rparam->integer_value) + break; + } + } + port = port->next; + } + if (!port) { + trace_header("ACTION pots-release (call # does not exist)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#if 0 + /* disconnect our call */ + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_NORMAL, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "hangup"); + e_action = NULL; +#endif + +#ifdef ISDN_P_FXS_POTS + /* release selected call */ + fxs->hangup_ind(0); +#endif + + /* indicate timeout, so next action will be processed */ + process_dialing(1); +} + + +/* process pots-reject + */ +void EndpointAppPBX::action_init_pots_reject(void) +{ + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs; + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-reject (call not of FXS type)", DIRECTION_NONE); + end_trace(); + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (fxs->p_state == PORT_STATE_OUT_ALERTING) + break; + } + } + port = port->next; + } + if (!port) { + trace_header("ACTION pots-reject (no call waiting)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#ifdef ISDN_P_FXS_POTS + /* reject alerting call */ + fxs->reject_ind(0); +#endif + + /* indicate timeout, so next action will be processed */ + process_dialing(1); +} + + +/* process pots-answer + */ +void EndpointAppPBX::action_init_pots_answer(void) +{ + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs; + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-answer (call not of FXS type)", DIRECTION_NONE); + end_trace(); + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (fxs->p_state == PORT_STATE_OUT_ALERTING) + break; + } + } + port = port->next; + } + if (!port) { + trace_header("ACTION pots-answer (no call waiting)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#ifdef ISDN_P_FXS_POTS + /* release our call */ + ourfxs->hangup_ind(0); + + /* answer alerting call */ + fxs->answer_ind(0); +#endif +} + + +/* process pots-3pty + */ +void EndpointAppPBX::action_init_pots_3pty(void) +{ + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs, *fxs1 = NULL, *fxs2 = NULL; + class Endpoint *epoint; + int count = 0; + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-3pty (call not of FXS type)", DIRECTION_NONE); + end_trace(); + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (count == 0) + fxs1 = fxs; + if (count == 1) + fxs2 = fxs; + count++; + } + } + port = port->next; + } + if (count != 2) { + trace_header("ACTION pots-3pty (exactly two calls don't exist)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#ifdef ISDN_P_FXS_POTS + /* release our call */ + ourfxs->hangup_ind(0); +#endif + +#ifdef ISDN_P_FXS_POTS + /* retrieve latest active call */ + if (fxs2->p_m_fxs_age > fxs1->p_m_fxs_age) { + fxs2->retrieve_ind(0); + epoint = find_epoint_id(ACTIVE_EPOINT(fxs2->p_epointlist)); + } else { + fxs1->retrieve_ind(0); + epoint = find_epoint_id(ACTIVE_EPOINT(fxs2->p_epointlist)); + } +#else + epoint = NULL; +#endif + + if (!epoint) { + trace_header("ACTION pots-3pty (interal error: no endpoint)", DIRECTION_NONE); + end_trace(); + return; + } + + if (epoint->ep_app_type != EAPP_TYPE_PBX) { + trace_header("ACTION pots-3pty (interal error: endpoint not PBX type)", DIRECTION_NONE); + end_trace(); + return; + } + + /* bridge calls */ + if (((class EndpointAppPBX *)epoint->ep_app)->join_3pty_fxs()) { + trace_header("ACTION pots-3pty (interal error: join_3pty_fsx failed)", DIRECTION_NONE); + end_trace(); + return; + } +} + +/* process pots-transfer + */ +void EndpointAppPBX::action_init_pots_transfer(void) +{ + struct route_param *rparam; + struct port_list *portlist = ea_endpoint->ep_portlist; + class Port *port; + class Pfxs *ourfxs, *fxs, *fxs1 = NULL, *fxs2 = NULL; + int count = 0; + + /* check given call */ + if (!(rparam = routeparam(e_action, PARAM_POTS_CALL))) { + trace_header("ACTION pots-transfer (no call given)", DIRECTION_NONE); + end_trace(); + + disconnect: + new_state(EPOINT_STATE_OUT_DISCONNECT); + message_disconnect_port(portlist, CAUSE_UNSPECIFIED, LOCATION_PRIVATE_LOCAL, ""); + set_tone(portlist, "cause_3f"); + e_action = NULL; + return; + } + + /* find call */ + port = find_port_id(portlist->port_id); + if (!port) + goto disconnect; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + trace_header("ACTION pots-transfer (call not of FXS type)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + ourfxs = (class Pfxs *)port; + + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (count == 0) + fxs1 = fxs; + if (count == 1) + fxs2 = fxs; + count++; + } + } + port = port->next; + } + if (count != 2) { + trace_header("ACTION pots-transfer (exactly two calls don't exist)", DIRECTION_NONE); + end_trace(); + goto disconnect; + } + +#ifdef ISDN_P_FXS_POTS + /* retrieve call */ + if (fxs2->p_m_fxs_age > fxs1->p_m_fxs_age) + fxs2->retrieve_ind(0); + else + fxs1->retrieve_ind(0); +#endif + /* bridge calls */ + join_join_fxs(); +} + + /* general process dialing of incoming call * depending on the detected prefix, subfunctions above (action_*) will be * calles. diff --git a/appbridge.cpp b/appbridge.cpp index 1202fc9..bc93a84 100644 --- a/appbridge.cpp +++ b/appbridge.cpp @@ -193,8 +193,13 @@ fail: port = ss5_hunt_line(mISDNport); else #endif - port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); earlyb = mISDNport->earlyb; +#ifdef ISDN_P_FXS_POTS + if (mISDNport->pots) + port = new Pfxs(PORT_TYPE_POTS_FXS_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, mode); + else +#endif + port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); #else trace_header("INTERFACE (has no function)", DIRECTION_NONE); add_trace("interface", NULL, "%s", ifname); diff --git a/apppbx.cpp b/apppbx.cpp index 2cf4826..8df43f6 100644 --- a/apppbx.cpp +++ b/apppbx.cpp @@ -500,6 +500,7 @@ void EndpointAppPBX::notify_active(void) */ void EndpointAppPBX::keypad_function(char digit) { + class Port *port; /* we must be in a call, in order to send messages to the call */ if (e_ext.number[0] == '\0') { @@ -515,7 +516,13 @@ void EndpointAppPBX::keypad_function(char digit) break; } PDEBUG(DEBUG_EPOINT, "EPOINT(%d) join call with call on hold\n", ea_endpoint->ep_serial); - join_join(); + port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!port) + break; + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) + join_join_fxs(); + else if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1) + join_join_dss1(); break; #ifdef WITH_CRYPT @@ -796,7 +803,12 @@ void EndpointAppPBX::out_setup(int cfnr) port = ss5_hunt_line(mISDNport); else #endif - port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); +#ifdef ISDN_P_FXS_POTS + if (mISDNport->pots) + port = new Pfxs(PORT_TYPE_POTS_FXS_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, mode); + else +#endif + port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); earlyb = mISDNport->earlyb; #else trace_header("INTERFACE (has no function)", DIRECTION_NONE); @@ -917,6 +929,11 @@ void EndpointAppPBX::out_setup(int cfnr) port = ss5_hunt_line(mISDNport); else #endif +#ifdef ISDN_P_FXS_POTS + if (mISDNport->pots) + port = new Pfxs(PORT_TYPE_POTS_FXS_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, mode); + else +#endif port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); if (!port) FATAL("No memory for Port instance\n"); @@ -1079,7 +1096,12 @@ void EndpointAppPBX::out_setup(int cfnr) port = ss5_hunt_line(mISDNport); else #endif - port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); +#ifdef ISDN_P_FXS_POTS + if (mISDNport->pots) + port = new Pfxs(PORT_TYPE_POTS_FXS_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, mode); + else +#endif + port = new Pdss1((mISDNport->ntmode)?PORT_TYPE_DSS1_NT_OUT:PORT_TYPE_DSS1_TE_OUT, mISDNport, portname, &port_settings, mISDNport->ifport->interface, channel, mISDNport->ifport->channel_force, mode); earlyb = mISDNport->earlyb; #else trace_header("INTERFACE (has no function)", DIRECTION_NONE); @@ -2320,23 +2342,13 @@ void EndpointAppPBX::port_3pty(struct port_list *portlist, int message_type, uni struct lcr_msg *message; int rc; -#if 0 - /* bridge for real */ - if (param->threepty.begin) - rc = join_join(); - else if (param->threepty.end) - rc = -ENOTSUP; - else - return; -#else /* 3PTY bridge */ if (param->threepty.begin) - rc = join_3pty(); + rc = join_3pty_dss1(); else if (param->threepty.end) rc = split_3pty(); else return; -#endif message = message_create(ea_endpoint->ep_serial, portlist->port_id, EPOINT_TO_PORT, MESSAGE_3PTY); message->param.threepty.begin = param->threepty.begin; @@ -2350,6 +2362,22 @@ void EndpointAppPBX::port_3pty(struct port_list *portlist, int message_type, uni message_put(message); } +/* port MESSAGE_TRANSFER */ +void EndpointAppPBX::port_transfer(struct port_list *portlist, int message_type, union parameter *param) +{ + logmessage(message_type, param, portlist->port_id, DIRECTION_IN); + + class Port *port; + + /* bridge for real */ + if (!(port = find_port_id(portlist->port_id))) + return; + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) + join_join_fxs(); + else if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_DSS1) + join_join_dss1(); +} + /* port MESSAGE_SUSPEND */ /* NOTE: before supending, the inactive-notification must be done in order to set call mixer */ void EndpointAppPBX::port_suspend(struct port_list *portlist, int message_type, union parameter *param) @@ -2470,6 +2498,11 @@ void EndpointAppPBX::ea_message_port(unsigned int port_id, int message_type, uni port_3pty(portlist, message_type, param); break; + case MESSAGE_TRANSFER: + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) incoming TRANSFER request (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, e_ext.number, e_callerinfo.id); + port_transfer(portlist, message_type, param); + break; + /* PORT sends DTMF message */ case MESSAGE_DTMF: /* dtmf digits received */ PDEBUG(DEBUG_EPOINT, "EPOINT(%d) dtmf digit=%c (terminal '%s', caller id '%s')\n", ea_endpoint->ep_serial, param->dtmf, e_ext.number, e_callerinfo.id); @@ -3533,7 +3566,7 @@ reject: /* join calls (look for a join that is on hold (same isdn interface/terminal)) */ -int EndpointAppPBX::join_join(void) +int EndpointAppPBX::join_join_dss1(void) { #ifdef WITH_MISDN struct lcr_msg *message; @@ -3711,7 +3744,186 @@ int EndpointAppPBX::join_join(void) return 0; } -int EndpointAppPBX::join_3pty(void) +/* join calls (look for a join that is on hold (same fxs interface/terminal)) + */ +int EndpointAppPBX::join_join_fxs(void) +{ +#ifdef WITH_MISDN + struct lcr_msg *message; + struct join_relation *add_relation, *remove_relation; + struct join_relation **add_relation_pointer, **remove_relation_pointer; + class Join *our_join, *other_join, *add_join, *remove_join; + class JoinPBX *our_joinpbx, *other_joinpbx, *add_joinpbx, *remove_joinpbx; + class EndpointAppPBX *other_eapp, *remove_eapp; + class Port *our_port, *other_port; + class Pfxs *our_fxs, *other_fxs; + class Endpoint *temp_epoint; + + /* are we a candidate to join a join? */ + our_join = find_join_id(ea_endpoint->ep_join_id); + if (!our_join) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if (our_join->j_type != JOIN_TYPE_PBX) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: join is not a pbx join.\n", ea_endpoint->ep_serial); + return -1; + } + our_joinpbx = (class JoinPBX *)our_join; + if (!ea_endpoint->ep_portlist) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we have no port.\n", ea_endpoint->ep_serial); + return -1; + } + if (!e_ext.number[0]) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we are not internal extension.\n", ea_endpoint->ep_serial); + return -1; + } + our_port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!our_port) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if ((our_port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port is not fxs.\n", ea_endpoint->ep_serial); + return -1; + } + our_fxs = (class Pfxs *)our_port; + + /* find an endpoint that has the same mISDNport that we are on */ + other_eapp = apppbx_first; + while(other_eapp) { + if (other_eapp == this) { + other_eapp = other_eapp->next; + continue; + } + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint candiate: (ep%d) terminal='%s' port=%s join=%d.\n", ea_endpoint->ep_serial, other_eapp->ea_endpoint->ep_serial, other_eapp->e_ext.number, (other_eapp->ea_endpoint->ep_portlist)?"YES":"NO", other_eapp->ea_endpoint->ep_join_id); + if (other_eapp->e_ext.number[0] /* has terminal */ + && other_eapp->ea_endpoint->ep_portlist /* has port */ + && other_eapp->ea_endpoint->ep_join_id) { /* has join */ + other_port = find_port_id(other_eapp->ea_endpoint->ep_portlist->port_id); + if (other_port) { /* port still exists */ + if (other_port->p_type==PORT_TYPE_POTS_FXS_OUT + || other_port->p_type==PORT_TYPE_POTS_FXS_IN) { /* port is FXS */ + other_fxs = (class Pfxs *)other_port; + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type isdn! comparing our portnum=%d with other's portnum=%d hold=%s state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_m_hold)?"YES":"NO", other_fxs->p_state); + if (1 //other_fxs->p_m_hold /* port is on hold */ + && other_fxs->p_m_mISDNport == our_fxs->p_m_mISDNport) /* same isdn interface */ + break; + } else { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of other type!\n", ea_endpoint->ep_serial); + } + } else { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port doesn't exist enymore.\n", ea_endpoint->ep_serial); + } + } + other_eapp = other_eapp->next; + } + if (!other_eapp) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no other endpoint on same FXS terminal.\n", ea_endpoint->ep_serial); + return -1; + } + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) port with same terminal found.\n", ea_endpoint->ep_serial); + + /* if we have the same join */ + if (other_eapp->ea_endpoint->ep_join_id == ea_endpoint->ep_join_id) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we and the other have the same join.\n", ea_endpoint->ep_serial); + return -1; + } + other_join = find_join_id(other_eapp->ea_endpoint->ep_join_id); + if (!other_join) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if (other_join->j_type != JOIN_TYPE_PBX) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join is not a pbx join.\n", ea_endpoint->ep_serial); + return -1; + } + other_joinpbx = (class JoinPBX *)other_join; + if (our_joinpbx->j_partyline && other_joinpbx->j_partyline) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: both joins are partylines.\n", ea_endpoint->ep_serial); + return -1; + } + + /* now find out which is ACTIVE-IDLE and which is ACTIVE-HELD */ + if (our_fxs->p_m_hold && !other_fxs->p_m_hold) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) our relation is on hold and other is active, so we move our relations to other relations\n", ea_endpoint->ep_serial); + remove_eapp = this; + remove_join = our_join; + remove_joinpbx = our_joinpbx; + add_join = other_join; + add_joinpbx = other_joinpbx; + } else { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) our relation is active or other is on hold, so we move ohter relations to our relations\n", ea_endpoint->ep_serial); + remove_eapp = other_eapp; + remove_join = other_join; + remove_joinpbx = other_joinpbx; + add_join = our_join; + add_joinpbx = our_joinpbx; + } + + /* remove relation to endpoint for join on hold */ + remove_relation = remove_joinpbx->j_relation; + remove_relation_pointer = &remove_joinpbx->j_relation; + while(remove_relation) { + if (remove_relation->epoint_id == remove_eapp->ea_endpoint->ep_serial) { + /* detach other endpoint */ + *remove_relation_pointer = remove_relation->next; + FREE(remove_relation, sizeof(struct join_relation)); + cmemuse--; + remove_relation = *remove_relation_pointer; + remove_eapp->ea_endpoint->ep_join_id = 0; + continue; + } + + /* change join/hold pointer of endpoint to the new join */ + temp_epoint = find_epoint_id(remove_relation->epoint_id); + if (temp_epoint) { + if (temp_epoint->ep_join_id == remove_join->j_serial) + temp_epoint->ep_join_id = add_join->j_serial; + } + + remove_relation_pointer = &remove_relation->next; + remove_relation = remove_relation->next; + } + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) endpoint removed, other enpoints on join relinked.\n", ea_endpoint->ep_serial); + + /* join call relations */ + add_relation = add_joinpbx->j_relation; + add_relation_pointer = &add_joinpbx->j_relation; + while(add_relation) { + add_relation_pointer = &add_relation->next; + add_relation = add_relation->next; + } + *add_relation_pointer = remove_joinpbx->j_relation; + remove_joinpbx->j_relation = NULL; + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) relations joined.\n", ea_endpoint->ep_serial); + + /* release endpoint */ + message = message_create(remove_joinpbx->j_serial, remove_eapp->ea_endpoint->ep_serial, JOIN_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = CAUSE_NORMAL; /* normal */ + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + + /* if we are not a partyline, we get partyline state from other join */ + add_joinpbx->j_partyline += remove_joinpbx->j_partyline; + + /* remove empty join */ + delete remove_join; + PDEBUG(DEBUG_EPOINT, "EPOINT(%d)join completely removed!\n", ea_endpoint->ep_serial); + + /* mixer must update */ + trigger_work(&add_joinpbx->j_updatebridge); + + /* we send a retrieve to that endpoint */ + // mixer will update the hold-state of the join and send it to the endpoints is changes +#else + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial); +#endif + + return 0; +} + +int EndpointAppPBX::join_3pty_dss1(void) { #ifdef WITH_MISDN class Join *our_join, *other_join; @@ -3832,6 +4044,126 @@ int EndpointAppPBX::join_3pty(void) return 0; } +int EndpointAppPBX::join_3pty_fxs(void) +{ +#ifdef WITH_MISDN + class Join *our_join, *other_join; + class JoinPBX *our_joinpbx, *other_joinpbx; + class EndpointAppPBX *other_eapp; + class Port *our_port, *other_port; + class Pfxs *our_fxs, *other_fxs; + + /* are we a candidate to join a join? */ + our_join = find_join_id(ea_endpoint->ep_join_id); + if (!our_join) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if (our_join->j_type != JOIN_TYPE_PBX) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: join is not a pbx join.\n", ea_endpoint->ep_serial); + return -1; + } + our_joinpbx = (class JoinPBX *)our_join; + if (!ea_endpoint->ep_portlist) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we have no port.\n", ea_endpoint->ep_serial); + return -1; + } + if (!e_ext.number[0]) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we are not internal extension.\n", ea_endpoint->ep_serial); + return -1; + } + our_port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!our_port) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if ((our_port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our port is not FXS pots.\n", ea_endpoint->ep_serial); + return -1; + } + our_fxs = (class Pfxs *)our_port; + + /* find an endpoint that has the same mISDNport that we are on */ + other_eapp = apppbx_first; + while(other_eapp) { + if (other_eapp == this) { + other_eapp = other_eapp->next; + continue; + } + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint candiate: (ep%d) terminal='%s' port=%s join=%d.\n", ea_endpoint->ep_serial, other_eapp->ea_endpoint->ep_serial, other_eapp->e_ext.number, (other_eapp->ea_endpoint->ep_portlist)?"YES":"NO", other_eapp->ea_endpoint->ep_join_id); + if (other_eapp->e_ext.number[0] /* has terminal */ + && other_eapp->ea_endpoint->ep_portlist /* has port */ + && other_eapp->ea_endpoint->ep_join_id) { /* has join */ + other_port = find_port_id(other_eapp->ea_endpoint->ep_portlist->port_id); + if (other_port) { /* port still exists */ + if (other_port->p_type==PORT_TYPE_POTS_FXS_OUT + || other_port->p_type==PORT_TYPE_POTS_FXS_IN) { /* port is isdn nt-mode */ + other_fxs = (class Pfxs *)other_port; + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of type FXS! comparing our portnum=%d with other's portnum=%d hold=%s state=%d\n", ea_endpoint->ep_serial, our_fxs->p_m_mISDNport->portnum, other_fxs->p_m_mISDNport->portnum, (other_fxs->p_m_hold)?"YES":"NO", other_fxs->p_state); + if (1 //other_fxs->p_m_hold /* port is on hold */ + && other_fxs->p_m_mISDNport == our_fxs->p_m_mISDNport) /* same pots interface */ + break; + } else { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port is of other type!\n", ea_endpoint->ep_serial); + } + } else { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) comparing other endpoint's port doesn't exist enymore.\n", ea_endpoint->ep_serial); + } + } + other_eapp = other_eapp->next; + } + if (!other_eapp) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no other endpoint on same FXS terminal.\n", ea_endpoint->ep_serial); + return -1; + } + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) port with same terminal found.\n", ea_endpoint->ep_serial); + + /* if we have the same join */ + if (other_eapp->ea_endpoint->ep_join_id == ea_endpoint->ep_join_id) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: we and the other have the same join.\n", ea_endpoint->ep_serial); + return -1; + } + other_join = find_join_id(other_eapp->ea_endpoint->ep_join_id); + if (!other_join) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join doesn't exist anymore.\n", ea_endpoint->ep_serial); + return -1; + } + if (other_join->j_type != JOIN_TYPE_PBX) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join is not a pbx join.\n", ea_endpoint->ep_serial); + return -1; + } + other_joinpbx = (class JoinPBX *)other_join; + if (our_joinpbx->j_partyline && other_joinpbx->j_partyline) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: both joins are partylines.\n", ea_endpoint->ep_serial); + return -1; + } + + if (our_joinpbx->j_3pty) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: our join already doing 3PTY.\n", ea_endpoint->ep_serial); + return -1; + } + if (other_joinpbx->j_3pty) { + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: other join already doing 3PTY.\n", ea_endpoint->ep_serial); + return -1; + } + + /* set 3PTY bridge */ + other_joinpbx->j_3pty = our_joinpbx->j_serial; + our_joinpbx->j_3pty = other_joinpbx->j_serial; + + /* mixer must update */ + trigger_work(&our_joinpbx->j_updatebridge); + trigger_work(&other_joinpbx->j_updatebridge); + + /* we send a retrieve to that endpoint */ + // mixer will update the hold-state of the join and send it to the endpoints is changes +#else + PDEBUG(DEBUG_EPOINT, "EPOINT(%d) cannot join: no mISDN support anyway.\n", ea_endpoint->ep_serial); +#endif + + return 0; +} + int EndpointAppPBX::split_3pty(void) { #ifdef WITH_MISDN @@ -4391,6 +4723,11 @@ void EndpointAppPBX::logmessage(int message_type, union parameter *param, unsign end_trace(); break; + case MESSAGE_TRANSFER: + trace_header("TRANSFER", dir); + end_trace(); + break; + case MESSAGE_DISABLE_DEJITTER: trace_header("DISBALE_DEJITTER", dir); if (param->queue) diff --git a/apppbx.h b/apppbx.h index e42651f..1d372b1 100644 --- a/apppbx.h +++ b/apppbx.h @@ -212,6 +212,7 @@ class EndpointAppPBX : public EndpointApp void port_progress(struct port_list *portlist, int message_type, union parameter *param); void port_facility(struct port_list *portlist, int message_type, union parameter *param); void port_3pty(struct port_list *portlist, int message_type, union parameter *param); + void port_transfer(struct port_list *portlist, int message_type, union parameter *param); void port_suspend(struct port_list *portlist, int message_type, union parameter *param); void port_resume(struct port_list *portlist, int message_type, union parameter *param); void port_enablekeypad(struct port_list *portlist, int message_type, union parameter *param); @@ -287,6 +288,12 @@ class EndpointAppPBX : public EndpointApp void action_init_play(void); void action_init_vbox_play(void); void action_init_efi(void); + void action_init_pots_retrieve(void); + void action_init_pots_release(void); + void action_init_pots_reject(void); + void action_init_pots_answer(void); + void action_init_pots_3pty(void); + void action_init_pots_transfer(void); void action_dialing_vbox_play(void); void action_dialing_calculator(void); void action_dialing_timer(void); @@ -310,8 +317,10 @@ class EndpointAppPBX : public EndpointApp /* facility function */ void pick_join(char *extension); - int join_join(void); - int join_3pty(void); + int join_join_dss1(void); + int join_join_fxs(void); + int join_3pty_dss1(void); + int join_3pty_fxs(void); int split_3pty(void); void encrypt_shared(void); void encrypt_keyex(void); diff --git a/fxs.cpp b/fxs.cpp new file mode 100644 index 0000000..1928814 --- /dev/null +++ b/fxs.cpp @@ -0,0 +1,835 @@ +/*****************************************************************************\ +** ** +** LCR ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg ** +** ** +** mISDN fxs ** +** ** +\*****************************************************************************/ + +/* Procedures: + + +*****TBD: THIS TEXT IS OLD ****** + +off-hook indication: + no port instance: + Create port/endpoint instance and send SETUP message to endpoint. + port instance active: + Send CONNECT message to endpoint. + port instance inactive: + Put inactive port instance active. Send RETRIEVE message to endpoint. +on-hook indication: + Release active port instance. Send RELEASE message to endpoint if exists. + inactive port instance: + Send ring request. Use caller ID on incomming call or connected ID on outgoing call. +hookflash indication: + active port instance not connected: + Release active port instance. Send RELEASE message to endpoint if exists. + active port instance connected: + Put active port instance inactive. Send HOLD MESSAGE to endpoint. + inactive port instance: + Put inactive port instance active. Send RETRIEVE message to endpoint. + no inactive port instance: + Create port/endpoint instance and send SETUP message to endpoint. +keypulse indication: + active port instance in incomming overlap state: + Send INFORMATION message to endpoint. + active port instance in other state: + Send KEYPAD message to endpoint, if exists. +SETUP message: + no instance: + Create port instance and send ALERTING message to endpoint. + Send ring request. Use caller ID. + only one instance active: + Create port instance and send ALERTING message to endpoint. + Send knock sound. Send ALERTING message to endpoint. + one instance on hold: + Send RELEASE message (cause = BUSY) to endpoint. +PROCEEDING / ALERTING / CONNECT message: + (change state only) +DISCONNECT message: + is inactive port instance: + Release port instance. Send RELEASE message to endpoint. +RELEASE message: + is active port instance: + Create hangup tone (release tone) + is inactive port instance: + Release port instance. +*/ + +#include "main.h" +#include "myisdn.h" +// socket mISDN +//#include +extern "C" { +} + +#ifdef ISDN_P_FXS_POTS + +static int fxs_age = 0; + +static int delete_event(struct lcr_work *work, void *instance, int index); + +static int dtmf_timeout(struct lcr_timer *timer, void *instance, int index) +{ + class Pfxs *pfxs = (class Pfxs *)instance; + + /* allow DTMF dialing now */ + PDEBUG(DEBUG_ISDN, "%s: allow DTMF now\n", pfxs->p_name); + pfxs->p_m_fxs_allow_dtmf = 1; + + return 0; +} + +/* + * constructor + */ +Pfxs::Pfxs(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, struct interface *interface, int mode) : PmISDN(type, mISDNport, portname, settings, interface, 0, 0, mode) +{ + p_callerinfo.itype = (mISDNport->ifport->interface->extension)?INFO_ITYPE_ISDN_EXTENSION:INFO_ITYPE_ISDN; + + memset(&p_m_fxs_delete, 0, sizeof(p_m_fxs_delete)); + add_work(&p_m_fxs_delete, delete_event, this, 0); + p_m_fxs_allow_dtmf = 0; + memset(&p_m_fxs_dtmf_timer, 0, sizeof(p_m_fxs_dtmf_timer)); + add_timer(&p_m_fxs_dtmf_timer, dtmf_timeout, this, 0); + p_m_fxs_age = fxs_age++; + p_m_fxs_knocking = 0; + + PDEBUG(DEBUG_ISDN, "Created new FXSPort(%s). Currently %d objects use, FXS port #%d\n", portname, mISDNport->use, p_m_portnum); +} + + +/* + * destructor + */ +Pfxs::~Pfxs() +{ + del_timer(&p_m_fxs_dtmf_timer); + del_work(&p_m_fxs_delete); +} + +/* deletes only if l3id is release, otherwhise it will be triggered then */ +static int delete_event(struct lcr_work *work, void *instance, int index) +{ + class Pfxs *pots = (class Pfxs *)instance; + + delete pots; + + return 0; +} + + +int Pfxs::hunt_bchannel(void) +{ + if (p_m_mISDNport->b_num < 1) + return -47; + if (p_m_mISDNport->b_port[0]) + return -17; + return 1; +} + +int Pfxs::ph_control_pots(unsigned int cont, unsigned char *data, int len) +{ + unsigned char buffer[MISDN_HEADER_LEN+sizeof(int)+len]; + struct mISDNhead *ctrl = (struct mISDNhead *)buffer; + unsigned int *d = (unsigned int *)(buffer+MISDN_HEADER_LEN); + int ret; + + ctrl->prim = PH_CONTROL_REQ; + ctrl->id = 0; + *d++ = cont; + if (len) + memcpy(d, data, len); + ret = sendto(p_m_mISDNport->pots_sock.fd, buffer, MISDN_HEADER_LEN+sizeof(int)+len, 0, NULL, 0); + if (ret <= 0) + PERROR("Failed to send to socket %d\n", p_m_mISDNport->pots_sock.fd); + + return ret; +} + +void Pfxs::pickup_ind(unsigned int cont) +{ + struct interface *interface = p_m_mISDNport->ifport->interface; + class Endpoint *epoint; + struct lcr_msg *message; + int ret, channel; + + p_m_fxs_age = fxs_age++; + + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } + + chan_trace_header(p_m_mISDNport, this, "PICKUP", DIRECTION_NONE); + + if (interface->ifmsn && interface->ifmsn->msn[0]) { + SCPY(p_callerinfo.id, interface->ifmsn->msn); + add_trace("caller", "ID", "%s", p_callerinfo.id); + } + p_callerinfo.present = INFO_PRESENT_ALLOWED; + p_callerinfo.isdn_port = p_m_portnum; + SCPY(p_callerinfo.interface, p_m_mISDNport->ifport->interface->name); + p_capainfo.source_mode = B_MODE_TRANSPARENT; + p_capainfo.bearer_capa = INFO_BC_AUDIO; + p_capainfo.bearer_info1 = 0x80 + ((options.law=='a')?3:2); + p_capainfo.bearer_mode = INFO_BMODE_CIRCUIT; + + if ((cont & (~POTS_KP_MASK)) == POTS_KP_VAL) { + p_dialinginfo.ntype = INFO_NTYPE_UNKNOWN; + p_dialinginfo.id[0] = cont & POTS_KP_MASK; + } + + if (!p_m_b_channel) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) alloc bchannel\n", p_name); + /* hunt bchannel */ + ret = channel = hunt_bchannel(); + if (ret < 0) + goto no_channel; + + /* open channel */ + ret = seize_bchannel(channel, 1); + if (ret < 0) { +no_channel: + /* + * NOTE: we send MT_RELEASE_COMPLETE to "REJECT" the channel + * in response to the setup + */ + add_trace("error", NULL, "no b-channel"); + end_trace(); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); + return; + } + end_trace(); + bchannel_event(p_m_mISDNport, p_m_b_index, B_EVENT_USE); + } else { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) bchannel is already active\n", p_name); + } + + /* create endpoint */ + if (p_epointlist) + FATAL("Incoming call but already got an endpoint.\n"); + if (!(epoint = new Endpoint(p_serial, 0))) + FATAL("No memory for Endpoint instance\n"); + epoint->ep_app = new_endpointapp(epoint, 0, p_m_mISDNport->ifport->interface->app); //incoming + epointlist_new(epoint->ep_serial); + + /* indicate flash control */ + if (cont == POTS_HOOK_FLASH || cont == POTS_EARTH_KEY) + p_dialinginfo.flash = 1; + + /* send setup message to endpoit */ + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_SETUP); + message->param.setup.isdn_port = p_m_portnum; + message->param.setup.port_type = p_type; +// message->param.setup.dtmf = !p_m_mISDNport->ifport->nodtmf; + memcpy(&message->param.setup.callerinfo, &p_callerinfo, sizeof(struct caller_info)); + memcpy(&message->param.setup.dialinginfo, &p_dialinginfo, sizeof(struct dialing_info)); + memcpy(&message->param.setup.capainfo, &p_capainfo, sizeof(struct capa_info)); + message_put(message); + + new_state(PORT_STATE_IN_OVERLAP); + + schedule_timer(&p_m_fxs_dtmf_timer, 0, 500000); +} + +void Pfxs::hangup_ind(unsigned int cont) +{ + struct lcr_msg *message; + + /* deactivate bchannel */ + chan_trace_header(p_m_mISDNport, this, "HANGUP", DIRECTION_NONE); + end_trace(); + drop_bchannel(); + PDEBUG(DEBUG_ISDN, "Pfxs(%s) drop bchannel\n", p_name); + + /* send release message, if not already */ + if (p_epointlist) { + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 16; + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + } + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); +} + +void Pfxs::answer_ind(unsigned int cont) +{ + struct lcr_msg *message; + int ret, channel; + + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } + + chan_trace_header(p_m_mISDNport, this, "ANSWER", DIRECTION_NONE); + if (!p_m_b_channel) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) alloc bchannel\n", p_name); + /* hunt bchannel */ + ret = channel = hunt_bchannel(); + if (ret < 0) + goto no_channel; + + /* open channel */ + ret = seize_bchannel(channel, 1); + if (ret < 0) { +no_channel: + /* + * NOTE: we send MT_RELEASE_COMPLETE to "REJECT" the channel + * in response to the setup + */ + add_trace("error", NULL, "no b-channel"); + end_trace(); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); + return; + } + end_trace(); + bchannel_event(p_m_mISDNport, p_m_b_index, B_EVENT_USE); + } else { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) bchannel is already active\n", p_name); + } + + if (p_m_hold) { + p_m_hold = 0; + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_NOTIFY); + message->param.notifyinfo.notify = INFO_NOTIFY_REMOTE_RETRIEVAL; + message->param.notifyinfo.local = 1; /* call is held by supplementary service */ + message_put(message); + } else { + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT); + message_put(message); + } + + new_state(PORT_STATE_CONNECT); +} + +void Pfxs::hold_ind(unsigned int cont) +{ + struct lcr_msg *message; + + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_NOTIFY); + message->param.notifyinfo.notify = INFO_NOTIFY_REMOTE_HOLD; + message->param.notifyinfo.local = 1; /* call is held by supplementary service */ + message_put(message); + + /* deactivate bchannel */ + chan_trace_header(p_m_mISDNport, this, "HOLD", DIRECTION_NONE); + end_trace(); + drop_bchannel(); + PDEBUG(DEBUG_ISDN, "Pfxs(%s) drop bchannel\n", p_name); + + p_m_hold = 1; +} + +void Pfxs::retrieve_ind(unsigned int cont) +{ + struct lcr_msg *message; + int ret, channel; + + p_m_fxs_age = fxs_age++; + + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } + + if (cont == POTS_ON_HOOK) { + const char *callerid; + + if (p_state == PORT_STATE_CONNECT) { + new_state(PORT_STATE_OUT_ALERTING); +#if 0 + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_ALERTING); + message_put(message); +#endif + chan_trace_header(p_m_mISDNport, this, "RING (retrieve)", DIRECTION_NONE); + } else + chan_trace_header(p_m_mISDNport, this, "RING (after knocking)", DIRECTION_NONE); + if (p_type == PORT_TYPE_POTS_FXS_IN) { + if (p_connectinfo.id[0]) { + callerid = numberrize_callerinfo(p_connectinfo.id, p_connectinfo.ntype, options.national, options.international); + add_trace("connect", "number", callerid); + } else { + callerid = p_dialinginfo.id; + add_trace("dialing", "number", callerid); + } + } else { + callerid = numberrize_callerinfo(p_callerinfo.id, p_callerinfo.ntype, options.national, options.international); + add_trace("caller", "id", callerid); + } + ph_control_pots(POTS_RING_ON, (unsigned char *)callerid, strlen(callerid)); + end_trace(); + return; + } + + chan_trace_header(p_m_mISDNport, this, "RETRIEVE", DIRECTION_NONE); + if (!p_m_b_channel) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) alloc bchannel\n", p_name); + /* hunt bchannel */ + ret = channel = hunt_bchannel(); + if (ret < 0) + goto no_channel; + + /* open channel */ + ret = seize_bchannel(channel, 1); + if (ret < 0) { +no_channel: + /* + * NOTE: we send MT_RELEASE_COMPLETE to "REJECT" the channel + * in response to the setup + */ + add_trace("error", NULL, "no b-channel"); + end_trace(); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); + return; + } + end_trace(); + bchannel_event(p_m_mISDNport, p_m_b_index, B_EVENT_USE); + } else { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) bchannel is already active\n", p_name); + } + + p_m_hold = 0; + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_NOTIFY); + message->param.notifyinfo.notify = INFO_NOTIFY_REMOTE_RETRIEVAL; + message->param.notifyinfo.local = 1; /* call is held by supplementary service */ + message_put(message); +} + +void Pfxs::keypulse_ind(unsigned int cont) +{ + struct lcr_msg *message; + + chan_trace_header(p_m_mISDNport, this, "PULSE", DIRECTION_NONE); + add_trace("KP", NULL, "%c", cont & DTMF_TONE_MASK); + end_trace(); + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_INFORMATION); + message->param.information.id[0] = cont & POTS_KP_MASK; + PDEBUG(DEBUG_ISDN, "Pfxs(%s) PH_CONTROL INDICATION DTMF digit '%c'\n", p_name, message->param.dtmf); + message_put(message); +} + +void Pfxs::reject_ind(unsigned int cont) +{ + struct lcr_msg *message; + + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } + + /* deactivate bchannel */ + chan_trace_header(p_m_mISDNport, this, "REJECT", DIRECTION_NONE); + end_trace(); + drop_bchannel(); + PDEBUG(DEBUG_ISDN, "Pfxs(%s) drop bchannel\n", p_name); + + /* send release message, if not already */ + if (p_epointlist) { + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 16; + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + } + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); +} + + +void Pfxs::message_setup(unsigned int epoint_id, int message_id, union parameter *param) +{ + struct lcr_msg *message; + class Port *port; + class Pfxs *pots; + struct epoint_list *epointlist; + const char *callerid; + int any_call = 0; + + memcpy(&p_callerinfo, ¶m->setup.callerinfo, sizeof(p_callerinfo)); + memcpy(&p_redirinfo, ¶m->setup.redirinfo, sizeof(p_redirinfo)); + + message = message_create(p_serial, epoint_id, PORT_TO_EPOINT, MESSAGE_ALERTING); + message_put(message); + + new_state(PORT_STATE_OUT_ALERTING); + + /* attach only if not already */ + epointlist = p_epointlist; + while(epointlist) { + if (epointlist->epoint_id == epoint_id) + break; + epointlist = epointlist->next; + } + if (!epointlist) + epointlist_new(epoint_id); + + /* find port in connected active state */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == p_m_mISDNport) { + if (pots != this) + any_call = 1; + if (pots->p_state == PORT_STATE_CONNECT && !pots->p_m_hold) + break; // found + } + } + port = port->next; + } + + if (port) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) knock because there is an ongoing active call\n", p_name); + chan_trace_header(p_m_mISDNport, this, "KNOCK", DIRECTION_NONE); + callerid = numberrize_callerinfo(p_callerinfo.id, p_callerinfo.ntype, options.national, options.international); + add_trace("caller", "id", callerid); + end_trace(); + ph_control_pots(POTS_CW_ON, (unsigned char *)callerid, strlen(callerid)); + p_m_fxs_knocking = 1; + return; + } + if (any_call) { + /* reject call, because we have a call, but we are not connected */ + PDEBUG(DEBUG_ISDN, "Pfxs(%s) reject because there is an ongoing and incomplete call\n", p_name); + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 17; // busy + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); + return; + } + PDEBUG(DEBUG_ISDN, "Pfxs(%s) ring because there is not calll\n", p_name); + chan_trace_header(p_m_mISDNport, this, "RING", DIRECTION_NONE); + callerid = numberrize_callerinfo(p_callerinfo.id, p_callerinfo.ntype, options.national, options.international); + add_trace("caller", "id", callerid); + end_trace(); + ph_control_pots(POTS_RING_ON, (unsigned char *)callerid, strlen(callerid)); +} + +void Pfxs::message_proceeding(unsigned int epoint_id, int message_id, union parameter *param) +{ + new_state(PORT_STATE_IN_PROCEEDING); +} + +void Pfxs::message_alerting(unsigned int epoint_id, int message_id, union parameter *param) +{ + new_state(PORT_STATE_IN_ALERTING); +} + +void Pfxs::message_connect(unsigned int epoint_id, int message_id, union parameter *param) +{ + new_state(PORT_STATE_CONNECT); + + memcpy(&p_connectinfo, ¶m->connectinfo, sizeof(struct connect_info)); +} + +void Pfxs::message_disconnect(unsigned int epoint_id, int message_id, union parameter *param) +{ + if (p_state == PORT_STATE_OUT_ALERTING) { + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } else { + ph_control_pots(POTS_RING_OFF, NULL, 0); + } + if (p_epointlist) { + struct lcr_msg *message; + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE); + message->param.disconnectinfo.cause = 16; + message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL; + message_put(message); + } + free_epointid(epoint_id); + new_state(PORT_STATE_RELEASE); + trigger_work(&p_m_fxs_delete); + return; + } + + new_state(PORT_STATE_OUT_DISCONNECT); +} + +void Pfxs::message_release(unsigned int epoint_id, int message_id, union parameter *param) +{ + chan_trace_header(p_m_mISDNport, this, "CLEAR", DIRECTION_NONE); + end_trace(); + + if (p_state == PORT_STATE_OUT_ALERTING) { + if (p_m_fxs_knocking) { + ph_control_pots(POTS_CW_OFF, NULL, 0); + p_m_fxs_knocking = 0; + } else { + ph_control_pots(POTS_RING_OFF, NULL, 0); + } + trigger_work(&p_m_fxs_delete); + } + if (p_state == PORT_STATE_CONNECT) { + if (!p_m_hold) + set_tone("", "release"); + else + trigger_work(&p_m_fxs_delete); + } + + new_state(PORT_STATE_RELEASE); + + free_epointid(epoint_id); +} + +/* + * endpoint sends messages to the port + */ +int Pfxs::message_epoint(unsigned int epoint_id, int message_id, union parameter *param) +{ + if (PmISDN::message_epoint(epoint_id, message_id, param)) + return(1); + + switch(message_id) { + case MESSAGE_SETUP: /* dial-out command received from epoint */ + message_setup(epoint_id, message_id, param); + break; + + case MESSAGE_PROCEEDING: /* call of endpoint is proceeding */ + message_proceeding(epoint_id, message_id, param); + break; + + case MESSAGE_ALERTING: /* call of endpoint is ringing */ + message_alerting(epoint_id, message_id, param); + break; + + case MESSAGE_CONNECT: /* call of endpoint is connected */ + message_connect(epoint_id, message_id, param); + break; + + case MESSAGE_DISCONNECT: /* call has been disconnected */ + message_disconnect(epoint_id, message_id, param); + break; + + case MESSAGE_RELEASE: /* release isdn port */ + if (p_state==PORT_STATE_RELEASE) { + break; + } + message_release(epoint_id, message_id, param); + break; + } + + return(1); +} + +/* + * data from isdn-stack (layer-1) to pbx (port class) + */ +int stack2manager_fxs(struct mISDNport *mISDNport, unsigned int cont) +{ + class Port *port; + class Pfxs *pots, *latest_pots = NULL, *alerting_pots = NULL; + int latest = -1; + char name[32]; + + PDEBUG(DEBUG_ISDN, "cont(0x%x)\n", cont); + + if ((cont & (~POTS_KP_MASK)) == POTS_KP_VAL) { + /* find port in dialing state */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport + && pots->p_state == PORT_STATE_IN_OVERLAP) + break; // found + } + port = port->next; + } + if (port) { + pots->keypulse_ind(cont); + return 0; + } + goto flash; + } + + switch (cont) { + case POTS_OFF_HOOK: + /* find ringing */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport + && pots->p_state == PORT_STATE_OUT_ALERTING) + break; // found + } + port = port->next; + } + if (port) { + pots->answer_ind(cont); + break; + } + +setup: + /* creating port object */ + SPRINT(name, "%s-%d-in", mISDNport->ifport->interface->name, mISDNport->portnum); + pots = new Pfxs(PORT_TYPE_POTS_FXS_IN, mISDNport, name, NULL, mISDNport->ifport->interface, B_MODE_TRANSPARENT); + if (!pots) + FATAL("Failed to create Port instance\n"); + pots->pickup_ind(cont); + break; + + case POTS_ON_HOOK: + if (mISDNport->ifport->pots_transfer) { + struct lcr_msg *message; + class Pfxs *pots1 = NULL, *pots2 = NULL; + int count = 0; + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport) { + if (pots->p_state == PORT_STATE_CONNECT + || pots->p_state == PORT_STATE_IN_PROCEEDING + || pots->p_state == PORT_STATE_IN_ALERTING) { + if (count == 0) + pots1 = pots; + if (count == 1) + pots2 = pots; + count++; + } + } + } + port = port->next; + } + + if (count == 2) { + if (pots1->p_state == PORT_STATE_CONNECT) { + message = message_create(pots1->p_serial, ACTIVE_EPOINT(pots1->p_epointlist), PORT_TO_EPOINT, MESSAGE_TRANSFER); + message_put(message); + } + else if (pots2->p_state == PORT_STATE_CONNECT) { + message = message_create(pots2->p_serial, ACTIVE_EPOINT(pots2->p_epointlist), PORT_TO_EPOINT, MESSAGE_TRANSFER); + message_put(message); + } + pots1->hangup_ind(cont); + pots2->hangup_ind(cont); + break; + } + } + if (mISDNport->ifport->pots_ring) { + /* release all except calls on hold, let the latest call ring */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport) { + if (pots->p_state == PORT_STATE_CONNECT && pots->p_m_hold) { + if (pots->p_m_fxs_age > latest) { + latest = pots->p_m_fxs_age; + latest_pots = pots; + } + } + if (pots->p_state == PORT_STATE_OUT_ALERTING) + alerting_pots = pots; + } + } + port = port->next; + } + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport) { + if ((pots->p_state != PORT_STATE_CONNECT || !pots->p_m_hold) && pots != alerting_pots) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) release because pots-ring-after-hangup set and call not on hold / alerting\n", pots->p_name); + pots->hangup_ind(cont); + } + } + } + port = port->next; + } + if (alerting_pots) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) answer because pots-ring-after-hangup set and call is alerting (knocking)\n", alerting_pots->p_name); + alerting_pots->retrieve_ind(cont); + break; + } + if (latest_pots) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) retrieve because pots-ring-after-hangup set and call is latest on hold\n", latest_pots->p_name); + latest_pots->retrieve_ind(cont); + break; + } + } else { + /* release all pots */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) release because pots-ring-after-hangup not set\n", pots->p_name); + pots->hangup_ind(cont); + } + } + port = port->next; + } + } + break; + case POTS_HOOK_FLASH: + case POTS_EARTH_KEY: +flash: + if (!mISDNport->ifport->pots_flash) { + PDEBUG(DEBUG_ISDN, "Pfxs flash key is disabled\n"); + break; + } + /* hold active pots / release not active pots */ + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + pots = (class Pfxs *)port; + if (pots->p_m_mISDNport == mISDNport) { + if (pots->p_state == PORT_STATE_CONNECT) { + if (pots->p_m_hold) { + if (pots->p_m_fxs_age > latest) { + latest = pots->p_m_fxs_age; + latest_pots = pots; + } + } else { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) hold, because flash on active call\n", pots->p_name); + pots->hold_ind(cont); + } + } else if (pots->p_state == PORT_STATE_OUT_ALERTING) { + alerting_pots = pots; + } else { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) hangup, because flash on incomplete/released call\n", pots->p_name); + pots->hangup_ind(cont); + } + } + } + port = port->next; + } +#if 0 + /* now we have our bchannel available, so we can look alerting port to answer */ + if (alerting_pots) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) answer because call is alerting (knocking)\n", alerting_pots->p_name); + alerting_pots->answer_ind(cont); + break; + } + if (latest_pots) { + PDEBUG(DEBUG_ISDN, "Pfxs(%s) retrieve because call is latest on hold\n", latest_pots->p_name); + latest_pots->retrieve_ind(cont); + break; + } +#endif + goto setup; + + default: + PERROR("unhandled message: xontrol(0x%x)\n", cont); + return(-EINVAL); + } + return(0); +} + +#endif /* ISDN_P_FXS_POTS */ diff --git a/fxs.h b/fxs.h new file mode 100644 index 0000000..e9b7b3f --- /dev/null +++ b/fxs.h @@ -0,0 +1,48 @@ +/*****************************************************************************\ +** ** +** PBX4Linux ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg ** +** ** +** fxs-port header file ** +** ** +\*****************************************************************************/ + +/* FXS port classes */ +class Pfxs : public PmISDN +{ + public: + Pfxs(int type, struct mISDNport *mISDNport, char *portname, struct port_settings *settings, struct interface *interface, int mode); + ~Pfxs(); + + struct lcr_work p_m_fxs_delete; + struct lcr_timer p_m_fxs_dtmf_timer; + int p_m_fxs_allow_dtmf; + int p_m_fxs_age; + int p_m_fxs_knocking; + + int ph_control_pots(unsigned int cont, unsigned char *data, int len); + int hunt_bchannel(void); + + void pickup_ind(unsigned int cont); + void hangup_ind(unsigned int cont); + void answer_ind(unsigned int cont); + void hold_ind(unsigned int cont); + void retrieve_ind(unsigned int cont); + void keypulse_ind(unsigned int cont); + void flash_ind(unsigned int cont); + void reject_ind(unsigned int cont); + + void message_setup(unsigned int epoint_id, int message_id, union parameter *param); + void message_information(unsigned int epoint_id, int message_id, union parameter *param); + void message_release(unsigned int epoint_id, int message_id, union parameter *param); + void message_proceeding(unsigned int epoint_id, int message_id, union parameter *param); + void message_alerting(unsigned int epoint_id, int message_id, union parameter *param); + void message_connect(unsigned int epoint_id, int message_id, union parameter *param); + void message_disconnect(unsigned int epoint_id, int message_id, union parameter *param); + int message_epoint(unsigned int epoint_id, int message, union parameter *param); +}; + +int stack2manager_fxs(struct mISDNport *mISDNport, unsigned int cont); + diff --git a/interface.c b/interface.c index 587fc62..b9dd275 100644 --- a/interface.c +++ b/interface.c @@ -1110,6 +1110,57 @@ static int inter_context(struct interface *interface, char *filename, int line, return(0); } +static int inter_pots_flash(struct interface *interface, char *filename, int line, char *parameter, char *value) +{ + struct interface_port *ifport; + + /* port in chain ? */ + if (!interface->ifport) { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' expects previous 'port' definition.\n", filename, line, parameter); + return(-1); + } + /* goto end of chain */ + ifport = interface->ifport; + while(ifport->next) + ifport = ifport->next; + + ifport->pots_flash = 1; + return(0); +} +static int inter_pots_ring(struct interface *interface, char *filename, int line, char *parameter, char *value) +{ + struct interface_port *ifport; + + /* port in chain ? */ + if (!interface->ifport) { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' expects previous 'port' definition.\n", filename, line, parameter); + return(-1); + } + /* goto end of chain */ + ifport = interface->ifport; + while(ifport->next) + ifport = ifport->next; + + ifport->pots_ring = 1; + return(0); +} +static int inter_pots_transfer(struct interface *interface, char *filename, int line, char *parameter, char *value) +{ + struct interface_port *ifport; + + /* port in chain ? */ + if (!interface->ifport) { + SPRINT(interface_error, "Error in %s (line %d): parameter '%s' expects previous 'port' definition.\n", filename, line, parameter); + return(-1); + } + /* goto end of chain */ + ifport = interface->ifport; + while(ifport->next) + ifport = ifport->next; + + ifport->pots_transfer = 1; + return(0); +} static int inter_shutdown(struct interface *interface, char *filename, int line, char *parameter, char *value) { interface->shutdown = 1; @@ -1309,6 +1360,21 @@ struct interface_param interface_param[] = { {"context", &inter_context, "", "Give context for calls to application."}, + {"pots-flash", &inter_pots_flash, "", + "Allow flash button to hold an active call and setup a new call.\n" + "Ihis parameter only appies to POTS type of interfaces\n" + "This parameter must follow a 'port' parameter.\n"}, + {"pots-ring-after-hangup", &inter_pots_ring, "", + "Allow ringing of last hold call after hangup. Other calls on hold will not be\n" + "released.\n" + "Ihis parameter only appies to POTS type of interfaces\n" + "This parameter must follow a 'port' parameter.\n"}, + {"pots-transfer-after-hangup", &inter_pots_transfer, "", + "If two calls on hold, both are connected after hangup.\n" + "If one call is on hold and another one alerting, call on hold is tranfered.\n" + "Ihis parameter only appies to POTS type of interfaces\n" + "This parameter must follow a 'port' parameter.\n"}, + {"shutdown", &inter_shutdown, "", "Interface will not be loaded when processing interface.conf"}, diff --git a/interface.h b/interface.h index 40c89f6..e38987c 100644 --- a/interface.h +++ b/interface.h @@ -67,6 +67,9 @@ struct interface_port { int dialmax; /* maximum number of digits to dial */ char tones_dir[128]; int nonotify; /* blocks outgoing notify messages */ + int pots_flash; /* allow flash button / keypulse to hold active call */ + int pots_ring; /* after hangup let calls on hold ring the phone */ + int pots_transfer; /* after hangup, two calls are transfered */ }; struct interface_msn { diff --git a/mISDN.cpp b/mISDN.cpp index dd61890..cdc24ef 100644 --- a/mISDN.cpp +++ b/mISDN.cpp @@ -1057,10 +1057,23 @@ void PmISDN::bchannel_receive(struct mISDNhead *hh, unsigned char *data, int len end_trace(); if (!p_m_dtmf) return; - message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DTMF); - message->param.dtmf = cont & DTMF_TONE_MASK; - PDEBUG(DEBUG_PORT, "PmISDN(%s) PH_CONTROL INDICATION DTMF digit '%c'\n", p_name, message->param.dtmf); - message_put(message); + if (p_type == PORT_TYPE_POTS_FXS_IN && p_state == PORT_STATE_IN_OVERLAP) { + class Pfxs *pfxs = (class Pfxs *)this; + if (!pfxs->p_m_fxs_allow_dtmf) { + PDEBUG(DEBUG_PORT, "PmISDN(%s) DTMF for FXS not yet allowed\n", p_name); + return; + } + SCCAT(p_dialinginfo.id, cont & DTMF_TONE_MASK); + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_INFORMATION); + message->param.information.id[0] = cont & DTMF_TONE_MASK; + PDEBUG(DEBUG_PORT, "PmISDN(%s) PH_CONTROL INDICATION INFORMATION digit '%s'\n", p_name, message->param.information.id); + message_put(message); + } else { + message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_DTMF); + message->param.dtmf = cont & DTMF_TONE_MASK; + PDEBUG(DEBUG_PORT, "PmISDN(%s) PH_CONTROL INDICATION DTMF digit '%c'\n", p_name, message->param.dtmf); + message_put(message); + } return; } switch(cont) { @@ -1777,6 +1790,43 @@ int mISDN_getportbyname(int sock, int cnt, char *portname) return (port); } +/* handle frames from pots */ +static int pots_sock_callback(struct lcr_fd *fd, unsigned int what, void *instance, int i) +{ + struct mISDNport *mISDNport = (struct mISDNport *)instance; + unsigned char buffer[2048+MISDN_HEADER_LEN]; + struct mISDNhead *hh = (struct mISDNhead *)buffer; + unsigned int cont; + int ret; + + ret = recv(fd->fd, buffer, sizeof(buffer), 0); + if (ret < 0) { + PERROR("read error frame, errno %d\n", errno); + return 0; + } + if (ret < (int)MISDN_HEADER_LEN) { + PERROR("read short frame, got %d, expected %d\n", ret, (int)MISDN_HEADER_LEN); + return 0; + } + switch(hh->prim) { + case PH_CONTROL_IND: + cont = *((unsigned int *)(buffer + MISDN_HEADER_LEN)); + /* l1-control is sent to LCR */ + if (mISDNport->ntmode) + stack2manager_fxs(mISDNport, cont); + else + PERROR("FXO not supported!\n"); + break; + case PH_ACTIVATE_REQ: + break; + + default: + PERROR("child message not handled: prim(0x%x) socket(%d) msg->len(%d)\n", hh->prim, fd->fd, ret-MISDN_HEADER_LEN); + } + + return 0; +} + /* * global function to add a new card (port) */ @@ -1845,32 +1895,29 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) pri = 1; nt = 1; } -#ifdef ISDN_P_FXS - if (devinfo.Dprotocols & (1 << ISDN_P_FXS)) { +#ifdef ISDN_P_FXS_POTS + if (devinfo.Dprotocols & (1 << ISDN_P_FXO_POTS)) { pots = 1; te = 1; } -#endif -#ifdef ISDN_P_FXO - if (devinfo.Dprotocols & (1 << ISDN_P_FXO)) { + if (devinfo.Dprotocols & (1 << ISDN_P_FXS_POTS)) { pots = 1; nt = 1; } #endif if (force_nt && !nt) { - PERROR_RUNTIME("Port %d does not support NT-mode\n", port); + if (!pots) + PERROR_RUNTIME("Port %d does not support NT-mode\n", port); + else + PERROR_RUNTIME("Port %d does not support FXS-mode\n", port); return(NULL); } if (bri && pri) { PERROR_RUNTIME("Port %d supports BRI and PRI?? What kind of controller is that?. (Can't use this!)\n", port); return(NULL); } - if (pots && !bri && !pri) { - PERROR_RUNTIME("Port %d supports POTS, LCR does not!\n", port); - return(NULL); - } - if (!bri && !pri) { - PERROR_RUNTIME("Port %d does not support BRI nor PRI!\n", port); + if (!bri && !pri && !pots) { + PERROR_RUNTIME("Port %d does not support BRI nor PRI nor POTS!\n", port); return(NULL); } if (!nt && !te) { @@ -1883,6 +1930,10 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) /* if TE an NT is supported (and not forced to NT), turn off NT */ if (te && nt) nt = 0; + if (pots && te) { + PERROR_RUNTIME("Port %d uses FXO-mode, but not supported by LCR!\n", port); + return(NULL); + } /* check for double use of port */ if (nt) { @@ -1963,40 +2014,85 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) } /* allocate ressources of port */ - protocol = (nt)?L3_PROTOCOL_DSS1_NET:L3_PROTOCOL_DSS1_USER; - prop = (1 << MISDN_FLG_L2_CLEAN); - if (ptp) // ptp forced - prop |= (1 << MISDN_FLG_PTP); - if (nt) // supports hold/retrieve on nt-mode - prop |= (1 << MISDN_FLG_NET_HOLD); - if (l1hold) // supports layer 1 hold - prop |= (1 << MISDN_FLG_L1_HOLD); - if (l2hold) // supports layer 2 hold - prop |= (1 << MISDN_FLG_L2_HOLD); - /* open layer 3 and init upqueue */ - /* queue must be initializes, because l3-thread may send messages during open_layer3() */ - mqueue_init(&mISDNport->upqueue); - mISDNport->ml3 = open_layer3(port, protocol, prop , do_layer3, mISDNport); - if (!mISDNport->ml3) { - mqueue_purge(&mISDNport->upqueue); - PERROR_RUNTIME("open_layer3() failed for port %d\n", port); - start_trace(port, - ifport->interface, - NULL, - NULL, - DIRECTION_NONE, - CATEGORY_CH, - 0, - "PORT (open failed)"); - end_trace(); - mISDNport_close(mISDNport); - return(NULL); + if (!pots) { + /* ISDN */ + protocol = (nt)?L3_PROTOCOL_DSS1_NET:L3_PROTOCOL_DSS1_USER; + prop = (1 << MISDN_FLG_L2_CLEAN); + if (ptp) // ptp forced + prop |= (1 << MISDN_FLG_PTP); + if (nt) // supports hold/retrieve on nt-mode + prop |= (1 << MISDN_FLG_NET_HOLD); + if (l1hold) // supports layer 1 hold + prop |= (1 << MISDN_FLG_L1_HOLD); + if (l2hold) // supports layer 2 hold + prop |= (1 << MISDN_FLG_L2_HOLD); + /* open layer 3 and init upqueue */ + /* queue must be initializes, because l3-thread may send messages during open_layer3() */ + mqueue_init(&mISDNport->upqueue); + mISDNport->ml3 = open_layer3(port, protocol, prop , do_layer3, mISDNport); + if (!mISDNport->ml3) { + mqueue_purge(&mISDNport->upqueue); + PERROR_RUNTIME("open_layer3() failed for port %d\n", port); + start_trace(port, + ifport->interface, + NULL, + NULL, + DIRECTION_NONE, + CATEGORY_CH, + 0, + "PORT (open failed)"); + end_trace(); + mISDNport_close(mISDNport); + return(NULL); + } + } else { +#ifdef ISDN_P_FXS_POTS + /* POTS */ + int sock, ret; + struct sockaddr_mISDN addr; + struct mISDNhead act; + + /* open socket */ + /* queue must be initializes, because even pots interfaces are checked at mISDN_upqueue loop */ + mqueue_init(&mISDNport->upqueue); + sock = socket(PF_ISDN, SOCK_DGRAM, (nt) ? ISDN_P_FXS_POTS : ISDN_P_FXO_POTS); + if (sock < 0) { + PERROR_RUNTIME("Cannot open mISDN due to '%s'.\n", strerror(errno)); + return NULL; + } + /* bind socket to dchannel */ + addr.family = AF_ISDN; + addr.dev = port; + addr.channel = 0; + ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + PERROR_RUNTIME("Error: Failed to bind pots control channel\n"); + start_trace(port, + ifport->interface, + NULL, + NULL, + DIRECTION_NONE, + CATEGORY_CH, + 0, + "PORT (open failed)"); + end_trace(); + return(NULL); + } + act.prim = PH_ACTIVATE_REQ; + act.id = 0; + ret = sendto(sock, &act, MISDN_HEADER_LEN, 0, NULL, 0); + if (ret <= 0) + PERROR("Failed to send to socket %d\n", sock); + mISDNport->pots_sock.fd = sock; + register_fd(&mISDNport->pots_sock, LCR_FD_READ, pots_sock_callback, mISDNport, i); +#endif } SCPY(mISDNport->name, devinfo.name); mISDNport->b_num = devinfo.nrbchan; mISDNport->portnum = port; mISDNport->ntmode = nt; + mISDNport->pots = pots; mISDNport->tespecial = ifport->tespecial; mISDNport->pri = pri; mISDNport->ptp = ptp; @@ -2012,7 +2108,7 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) } /* if ptp, pull up the link */ - if (mISDNport->l2hold && (mISDNport->ptp || !mISDNport->ntmode)) { + if (!pots && mISDNport->l2hold && (mISDNport->ptp || !mISDNport->ntmode)) { mISDNport->ml3->to_layer3(mISDNport->ml3, MT_L2ESTABLISH, 0, NULL); l1l2l3_trace_header(mISDNport, NULL, L2_ESTABLISH_REQ, DIRECTION_OUT); add_trace("tei", NULL, "%d", 0); @@ -2020,8 +2116,8 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) schedule_timer(&mISDNport->l2establish, 5, 0); /* 5 seconds */ } - /* for nt-mode ptmp the link is always up */ - if (mISDNport->ntmode && !mISDNport->ptp) + /* for POTS or nt-mode ptmp the link is always up */ + if (pots || (mISDNport->ntmode && !mISDNport->ptp)) mISDNport->l2link = 1; PDEBUG(DEBUG_BCHANNEL, "using 'mISDN_dsp.o' module\n"); @@ -2034,7 +2130,10 @@ struct mISDNport *mISDNport_open(struct interface_port *ifport) CATEGORY_CH, 0, "PORT (open)"); - add_trace("mode", NULL, (mISDNport->ntmode)?"network":"terminal"); + if (!pots) + add_trace("mode", NULL, (mISDNport->ntmode)?"network":"terminal"); + else + add_trace("mode", NULL, (mISDNport->ntmode)?"FXS":"FXO"); add_trace("channels", NULL, "%d", mISDNport->b_num); if (mISDNport->ss5) add_trace("ccitt#5", NULL, "enabled"); @@ -2129,6 +2228,12 @@ void mISDNport_close(struct mISDNport *mISDNport) close_layer3(mISDNport->ml3); } + /* close layer 1, if open */ + if (mISDNport->pots_sock.fd) { + unregister_fd(&mISDNport->pots_sock); + close(mISDNport->pots_sock.fd); + } + /* purge upqueue */ mqueue_purge(&mISDNport->upqueue); diff --git a/mISDN.h b/mISDN.h index 32136d1..d8c504b 100644 --- a/mISDN.h +++ b/mISDN.h @@ -45,6 +45,8 @@ struct mISDNport { int ntmode; /* is TRUE if port is NT mode */ int tespecial; /* is TRUE if port uses special TE mode */ int pri; /* is TRUE if port is a primary rate interface */ + int pots; /* is TRUE if port is of type POTS */ + struct lcr_fd pots_sock; /* socket for L1 */ int tones; /* TRUE if tones are sent outside connect state */ int earlyb; /* TRUE if tones are received outside connect state */ int b_num; /* number of bchannels */ diff --git a/main.h b/main.h index b16012d..d571c4b 100644 --- a/main.h +++ b/main.h @@ -150,6 +150,7 @@ void debug(const char *file, const char *function, int line, const char *prefix, #ifdef WITH_MISDN #include "mISDN.h" #include "dss1.h" +#include "fxs.h" #endif #if defined WITH_GSM_BS || defined WITH_GSM_MS #include "gsm.h" diff --git a/message.h b/message.h index 76adfdb..68abd5d 100644 --- a/message.h +++ b/message.h @@ -185,6 +185,7 @@ struct dialing_info { char display[84]; /* display information */ char keypad[33]; /* send keypad facility */ char context[32]; /* asterisk context */ + int flash; /* flash key caused setup of call */ }; /* call-info structure CONNECT */ @@ -433,6 +434,7 @@ enum { /* messages between entities */ MESSAGE_BRIDGE, /* control port bridge */ MESSAGE_TRAFFIC, /* exchange bchannel traffic */ MESSAGE_3PTY, /* 3PTY call invoke */ + MESSAGE_TRANSFER, /* call transfer invoke */ MESSAGE_DISABLE_DEJITTER/* tell (mISDN) port not to dejitter */ }; @@ -471,6 +473,7 @@ enum { /* messages between entities */ "MESSAGE_BRIDGE", \ "MESSAGE_TRAFFIC", \ "MESSAGE_3PTY", \ + "MESSAGE_TRANSFER", \ "MESSAGE_DISABLE_DEJITTER", \ }; diff --git a/port.h b/port.h index 061d94c..6cfc6b5 100644 --- a/port.h +++ b/port.h @@ -18,6 +18,9 @@ #define PORT_CLASS_DSS1 0x1100 #define PORT_CLASS_DSS1_NT 0x1110 #define PORT_CLASS_DSS1_TE 0x1120 +#define PORT_CLASS_POTS 0x1200 +#define PORT_CLASS_POTS_FXS 0x1210 +#define PORT_CLASS_POTS_FXO 0x1220 #define PORT_CLASS_SS5 0x1300 #define PORT_CLASS_SIP 0x2000 #define PORT_CLASS_GSM 0x3000 @@ -27,6 +30,7 @@ #define PORT_CLASS_MASK 0xf000 #define PORT_CLASS_mISDN_MASK 0xff00 #define PORT_CLASS_DSS1_MASK 0xfff0 +#define PORT_CLASS_POTS_MASK 0xfff0 #define PORT_CLASS_GSM_MASK 0xff00 #define PORT_CLASS_DIR_MASK 0x000f #define PORT_CLASS_DIR_IN 0x0001 @@ -37,6 +41,12 @@ /* te-mode */ #define PORT_TYPE_DSS1_TE_IN 0x1121 #define PORT_TYPE_DSS1_TE_OUT 0x1122 + /* FXS-mode */ +#define PORT_TYPE_POTS_FXS_IN 0x1211 +#define PORT_TYPE_POTS_FXS_OUT 0x1212 + /* FXO-mode */ +#define PORT_TYPE_POTS_FXO_IN 0x1221 +#define PORT_TYPE_POTS_FXO_OUT 0x1222 /* gsm */ #define PORT_TYPE_GSM_BS_IN 0x3101 #define PORT_TYPE_GSM_BS_OUT 0x3102 diff --git a/route.c b/route.c index 7216842..7954b6f 100644 --- a/route.c +++ b/route.c @@ -94,6 +94,14 @@ struct cond_defs cond_defs[] = { "remote=","Matches if remote application is running."}, { "notremote", MATCH_NOTREMOTE,COND_TYPE_STRING, "notremote=","Matches if remote application is not running."}, + { "pots-flash", MATCH_POTS_FLASH,COND_TYPE_NULL, + "pots-flash","When using POTS: Matches if call was invoked by flash/earth button."}, + { "pots-cw", MATCH_POTS_CW, COND_TYPE_NULL, + "pots-cw","When using POTS: Matches if a call is waiting."}, + { "pots-calls", MATCH_POTS_CALLS,COND_TYPE_INTEGER, + "pots-calls=","When using POTS: Matches if given number of calls are held."}, + { "pots-last", MATCH_POTS_LAST,COND_TYPE_INTEGER, + "pots-last=","When using POTS: Matches if given call number (1=oldest) was the last active call."}, { NULL, 0, 0, NULL} }; @@ -245,6 +253,9 @@ struct param_defs param_defs[] = { { PARAM_KEYPAD, "keypad", PARAM_TYPE_NULL, "keypad", "Use 'keypad facility' for dialing, instead of 'called number'."}, + { PARAM_POTS_CALL, + "pots-call", PARAM_TYPE_INTEGER, + "pots-call=", "Select call number. The oldest call is number 1."}, { 0, NULL, 0, NULL, NULL} }; @@ -380,6 +391,30 @@ struct action_defs action_defs[] = { "efi", &EndpointAppPBX::action_init_efi, NULL, NULL, PARAM_PROCEEDING | PARAM_ALERTING | PARAM_CONNECT, "Elektronische Fernsprecher Identifikation - announces caller ID."}, + { ACTION_POTS_RETRIEVE, + "pots-retrieve", &EndpointAppPBX::action_init_pots_retrieve, NULL, NULL, + PARAM_POTS_CALL, + "When using POTS: Select call on hold to retrieve."}, + { ACTION_POTS_RELEASE, + "pots-release", &EndpointAppPBX::action_init_pots_release, NULL, NULL, + PARAM_POTS_CALL, + "When using POTS: Select call on hold to release."}, + { ACTION_POTS_REJECT, + "pots-reject", &EndpointAppPBX::action_init_pots_reject, NULL, NULL, + 0, + "When using POTS: Reject incomming waiting call."}, + { ACTION_POTS_ANSWER, + "pots-answer", &EndpointAppPBX::action_init_pots_answer, NULL, NULL, + 0, + "When using POTS: Answer incomming waiting call."}, + { ACTION_POTS_3PTY, + "pots-3pty", &EndpointAppPBX::action_init_pots_3pty, NULL, NULL, + 0, + "When using POTS: Invoke 3PTY call of two calls on hold"}, + { ACTION_POTS_TRANSFER, + "pots-transfer", &EndpointAppPBX::action_init_pots_transfer, NULL, NULL, + 0, + "When using POTS: Interconnect two calls on hold"}, { -1, NULL, NULL, NULL, NULL, 0, NULL} }; @@ -983,7 +1018,7 @@ struct route_ruleset *ruleset_parse(void) while(*p!=':' && *p!='\0') { /* read item text */ i = 0; - while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9')) { + while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9') || *p == '-') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ @@ -1427,11 +1462,15 @@ struct route_ruleset *ruleset_parse(void) while(*p != 0) { /* read param text */ i = 0; - while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9')) { + while((*p>='a' && *p<='z') || (*p>='A' && *p<='Z') || (*p>='0' && *p<='9') || *p == '-') { if (*p>='A' && *p<='Z') *p = *p-'A'+'a'; /* lower case */ key[i++] = *p++; if (i == sizeof(key)) i--; /* limit */ } + if (*p == ':') { + p++; + goto nextaction; + } key[i] = 0; if (key[0] == '\0') { SPRINT(failure, "Expecting parameter name."); @@ -1919,6 +1958,10 @@ struct route_action *EndpointAppPBX::route(struct route_ruleset *ruleset) int avail, any; int jj; + class Port *port; + class Pfxs *ourfxs, *fxs; + int fxs_count; + int fxs_age; #endif struct admin_list *admin; time_t now; @@ -2237,6 +2280,86 @@ struct route_action *EndpointAppPBX::route(struct route_ruleset *ruleset) istrue = 1; break; +#ifdef WITH_MISDN + case MATCH_POTS_FLASH: + if (e_dialinginfo.flash) + istrue = 1; + break; + + case MATCH_POTS_CW: + port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!port) + break; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) + break; + ourfxs = (class Pfxs *)port; + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (fxs->p_state == PORT_STATE_OUT_ALERTING) { + istrue = 1; + break; + } + } + } + port = port->next; + } + break; + + case MATCH_POTS_CALLS: + port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!port) + break; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) + break; + ourfxs = (class Pfxs *)port; + integer = 0; + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (fxs->p_state == PORT_STATE_CONNECT) { + integer++; + } + } + } + port = port->next; + } + goto match_integer; + + case MATCH_POTS_LAST: + port = find_port_id(ea_endpoint->ep_portlist->port_id); + if (!port) + break; + if ((port->p_type & PORT_CLASS_POTS_MASK) != PORT_CLASS_POTS_FXS) + break; + ourfxs = (class Pfxs *)port; + /* integer gets the call number that has been the least active call on hold */ + fxs_age = -1; + fxs_count = 0; + integer = 0; + port = port_first; + while(port) { + if ((port->p_type & PORT_CLASS_POTS_MASK) == PORT_CLASS_POTS_FXS) { + fxs = (class Pfxs *)port; + if (fxs->p_m_mISDNport == ourfxs->p_m_mISDNport && fxs != ourfxs) { + if (fxs->p_state == PORT_STATE_CONNECT) { + fxs_count++; + if (fxs->p_m_fxs_age > fxs_age) { + fxs_age = fxs->p_m_fxs_age; + integer = fxs_count; + } + } + } + } + port = port->next; + } + goto match_integer; +#endif + default: PERROR("Software error: MATCH_* %d not parsed in function '%s'", cond->match, __FUNCTION__); break; diff --git a/route.h b/route.h index c6ca347..ba49f49 100644 --- a/route.h +++ b/route.h @@ -76,6 +76,10 @@ enum { /* what to check during runtime */ MATCH_IDLE, MATCH_REMOTE, MATCH_NOTREMOTE, + MATCH_POTS_FLASH, + MATCH_POTS_CW, + MATCH_POTS_CALLS, + MATCH_POTS_LAST, }; enum { /* how to parse text file during startup */ @@ -149,6 +153,7 @@ enum { /* defines when a statement should be executed */ #define PARAM_EXTEN (1LL<<46) #define PARAM_ON (1LL<<47) #define PARAM_KEYPAD (1LL<<48) +#define PARAM_POTS_CALL (1LL<<49) /* action index * NOTE: The given index is the actual entry number of action_defs[], so add/remove both lists!!! @@ -185,6 +190,12 @@ enum { /* defines when a statement should be executed */ #define ACTION_PASSWORD_WRITE 29 #define ACTION_NOTHING 30 #define ACTION_EFI 31 +#define ACTION_POTS_RETRIEVE 32 +#define ACTION_POTS_RELEASE 33 +#define ACTION_POTS_REJECT 34 +#define ACTION_POTS_ANSWER 35 +#define ACTION_POTS_3PTY 36 +#define ACTION_POTS_TRANSFER 37 struct route_cond { /* an item */ struct route_cond *next; /* next entry */ -- 2.13.6