Add FXS support
authorAndreas Eversberg <jolly@eversberg.eu>
Sun, 16 Dec 2012 08:31:36 +0000 (09:31 +0100)
committerAndreas Eversberg <jolly@eversberg.eu>
Sun, 16 Dec 2012 09:12:44 +0000 (10:12 +0100)
This requires FXS support to mISDN too.

16 files changed:
Makefile.am
action.cpp
appbridge.cpp
apppbx.cpp
apppbx.h
fxs.cpp [new file with mode: 0644]
fxs.h [new file with mode: 0644]
interface.c
interface.h
mISDN.cpp
mISDN.h
main.h
message.h
port.h
route.c
route.h

index 8da7b3f..e802edc 100644 (file)
@@ -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
index 7b60126..3fc6449 100644 (file)
@@ -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.
index 1202fc9..bc93a84 100644 (file)
@@ -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);
index 2cf4826..8df43f6 100644 (file)
@@ -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)
index e42651f..1d372b1 100644 (file)
--- 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 (file)
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 <sys/socket.h>
+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, &param->setup.callerinfo, sizeof(p_callerinfo));
+       memcpy(&p_redirinfo, &param->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, &param->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 (file)
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);
+
index 587fc62..b9dd275 100644 (file)
@@ -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, "<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"},
 
index 40c89f6..e38987c 100644 (file)
@@ -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 {
index dd61890..cdc24ef 100644 (file)
--- 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 (file)
--- 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 (file)
--- 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"
index 76adfdb..68abd5d 100644 (file)
--- 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 (file)
--- 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
        /* 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 (file)
--- a/route.c
+++ b/route.c
@@ -94,6 +94,14 @@ struct cond_defs cond_defs[] = {
          "remote=<application name>","Matches if remote application is running."},
        { "notremote",  MATCH_NOTREMOTE,COND_TYPE_STRING,
          "notremote=<application name>","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=<total number>","When using POTS: Matches if given number of calls are held."},
+       { "pots-last",  MATCH_POTS_LAST,COND_TYPE_INTEGER,
+         "pots-last=<call number>","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=<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 (file)
--- 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 */