DTMF Events not available on Event Socket (Outbound)?
I've been trying to implement a basic IVR that interacts with our database using PHP. With some help on IRC, I've created an Event Socket interface for PHP. It is rudimentary at this point, and not ready for prime-time, but it is working -- FreeSwitch connects to my process when a call comes in, sends the basic context info, and I can issue calls. After a call to "event plain ALL", I start receiving events. Mainly HEARTBEAT events, with a few others thrown in. What I'm NOT seeing is any DTMF events. Now, I know that the DTMF codes are recognized by other parts of FreeSwitch, such as the IVR, and I've ensure that my SIP client is sending them in-audio, SIP INFO and RFC2833. However, I'm not getting anything when I connect and press buttons. I have some other issues with the Event Socket, that I'd love to get through, but without the DTMF, most of the applications we're working on won't go very far. Is there anything I have to do to be able to get these Events to show up? Thanks Bill PS: I've attached the EventSocketListener PHP class. You can run it from the command line and it will listen on port 9090 -- obviously, it's not clean nor done, but it is working #!/usr/local/bin/php <?php /** * Based loosely on the NET_Server code in PEAR. * This is only an example - considerable additional work is needed * Specifically, the code in the handleConnection method should be * handled in a subclass * * */ class Message { var $properties = array(); var $content = null; } class EventSocketListener { var $host; var $port; var $sock; var $is_parent = true; var $clientInfo; var $clientFD; var $connectionContext = array(); function &create($port, $host = "localhost") { $esl = new EventSocketListener; $esl->port = $port; $esl->host = $host; if (!function_exists('socket_create')) { return PEAR::raiseError('Sockets extension not available.'); } return $esl; } function start() { if (($this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n"; } if (!socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1)) { echo 'Unable to set option on socket: '. socket_strerror(socket_last_error()) . PHP_EOL; } if (socket_bind($this->sock, $this->host, $this->port) === false) { echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n"; } if (socket_listen($this->sock, 5) === false) { echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n"; } // Dear children, please do not become zombies pcntl_signal(SIGCHLD, SIG_IGN); // wait for incmoning connections while (true) { // new connection if(($fd = socket_accept($this->sock))) { $pid = pcntl_fork(); if($pid == -1) { return PEAR::raiseError('Could not fork child process.'); } // This is the child => handle the request elseif($pid == 0) { // this is not the parent $this->_isParent = false; // store the new file descriptor $this->clientFD = $fd; $peer_host = ""; $peer_port = ""; socket_getpeername($this->clientFD, $peer_host, $peer_port); $this->clientInfo = array( "host" => $peer_host, "port" => $peer_port, "connectOn" => time() ); $this->handleConnection(); socket_shutdown($this->clientFD, 2); socket_close($this->clientFD); } else /* Parent does nothing */ { } } } } function handleConnection() { $fd = $this->clientFD; //first, read headers & setup a state for this connection $line = ""; socket_write($fd, "CONNECT\n\n"); do { $line = socket_read($fd, 2048, PHP_NORMAL_READ); if (trim($line) == "") break; //we got a header, we need to add it to the list($key, $value) = explode(":", $line); $key = trim($key); $value = trim(urldecode($value)); $this->connectionContext[$key] = $value; } while ($line != ""); // print_r($this->connectionContext); $this->callConnected(); exit(); } function processMessages($returnOnReply = false) { $fd = $this->clientFD; $result = new Message(); $props = array(); while (true) { do { $line = @socket_read($fd, 2048, PHP_NORMAL_READ); if (socket_last_error($fd) == 104) return null; if ($line == null || $line == FALSE || trim($line) == "") break; //we got a header, we need to add it to the message list($key, $value) = explode(":", $line); $key = trim($key); $value = trim(urldecode($value)); $props[$key] = $value; } while ($line != ""); $result->properties = $props; if (isset($props['Content-Length'])) { $length = $props['Content-Length']; print("Reading content - $length\n"); $data = socket_read($fd, $length); $result->content = $data; } if (isset($props['Content-Type'])) { $type = $props['Content-Type']; if ($returnOnReply && ($type == "command/reply" || $type == "api/response")) { return $result; } else if ($type == "text/event-plain") //only plain events for now { $this->handleEvent($result); } } else { print("UNKNOWN MESSAGE: \n"); print_r($result); } } } function invokeCommand($command) { //Send the command print("Invoking: $command\n"); $this->sendCommand($command); // Wait for the response $result = $this->processMessages(true); return $result; } function sendCommand($command) { $fd = $this->clientFD; socket_write($fd, trim($command) . "\n\n"); } /*-----------------------------------------------------*/ /* Abstract Methods - should move to subclass*/ function callConnected() { print_r($this->connectionContext); print("----------------\n"); $result = $this->invokeCommand("log DEBUG"); print_r($result); $result = $this->invokeCommand("event plain ALL"); print_r($result); $this->processMessages(false); print("DONE PROCESSING MESSAGES"); print_r($this->connectionContext); } function handleCommandResponse($response) { print("Recieved Unhandled Response:\n"); print_r($response); } function handleEvent($event) { print("Recieved Unhandled Event:\n"); print_r($event); } } // create a server that forks new processes $server = &EventSocketListener::create(9090); // start the server $server->start(); ?>
Bill, When I connect and do a DTMF digit when events all is on: event "events DTMF" gets these: Content-Length: 3690 Content-Type: text/event-plain Channel-State: CS_EXECUTE Channel-State-Number: 4 Channel-Name: sofia/default/1008%4010.0.1.250%3A5060 Unique-ID: f3b34b4b-a1bb-4caf-b49d-cd7f277a9ae7Call-Direction: inboundAnswer-State: answeredChannel-Read-Codec-Name: PCMU Channel-Read-Codec-Rate: 8000 Channel-Write-Codec-Name: PCMU Channel-Write-Codec-Rate: 8000 Caller-Username: 1008 Caller-Dialplan: XML Caller-Caller-ID-Name: Brian%20West Caller-Caller-ID-Number: 1008 Caller-Network-Addr: 10.0.1.10 Caller-Destination-Number: 9999 Caller-Unique-ID: f3b34b4b-a1bb-4caf-b49d-cd7f277a9ae7 Caller-Source: mod_sofia Caller-Context: default Caller-Channel-Name: sofia/default/1008%4010.0.1.250%3A5060 Caller-Channel-Created-Time: 1199497969261353 Caller-Channel-Answered-Time: 1199497969345822 Caller-Channel-Hangup-Time: 0 Caller-Channel-Transfer-Time: 0 Caller-Screen-Bit: yes Caller-Privacy-Hide-Name: no Caller-Privacy-Hide-Number: no variable_sip_authorized: true variable_sip_mailbox: 1008 variable_sip_auth_username: 1008 variable_sip_auth_realm: 10.0.1.250 variable_mailbox: 1008 variable_record_stereo: true variable_accountcode: 1008 variable_user_context: default variable_effective_caller_id_name: Extension%201008 variable_effective_caller_id_number: 1008 variable_sip_from_user: 1008 variable_sip_from_port: 5060 variable_sip_from_uri: 1008%4010.0.1.250%3A5060 variable_sip_from_uri: 1008%4010.0.1.250%3A5060 variable_sip_from_host: 10.0.1.250 variable_sip_from_user_stripped: 1008 variable_sofia_profile_name: default variable_sip_req_user: 9999 variable_sip_req_port: 5060 variable_sip_req_uri: 9999%4010.0.1.250%3A5060 variable_sip_req_host: 10.0.1.250 variable_sip_to_user: 9999 variable_sip_to_port: 5060 variable_sip_to_uri: 9999%4010.0.1.250%3A5060 variable_sip_to_host: 10.0.1.250 variable_sip_contact_user: 1008 variable_sip_contact_port: 5060 variable_sip_contact_uri: 1008%4010.0.1.10%3A5060 variable_sip_contact_host: 10.0.1.10 variable_channel_name: sofia/default/1008%4010.0.1.250%3A5060 variable_sip_call_id: 5898854f-9bcbf32d%4010.0.1.10 variable_sip_user_agent: Linksys/SPA962-5.1.7 variable_sip_via_host: 10.0.1.10 variable_sip_via_port: 5060 variable_sip_via_rport: 5060 variable_max_forwards: 70 variable_presence_id: 1008%4010.0.1.250 variable_switch_r_sdp: v%3D0%0D%0Ao%3D-%201678769%201678769%20IN %20IP4%2010.0.1.10%0D%0As%3D-%0D%0Ac%3DIN%20IP4%2010.0.1.10%0D%0At %3D0%200%0D%0Am%3Daudio%2016414%20RTP/AVP %200%202%204%208%2018%2096%2097%2098%20101%0D%0Aa%3Drtpmap%3A0%20PCMU/ 8000%0D%0Aa%3Drtpmap%3A2%20G726-32/8000%0D%0Aa%3Drtpmap %3A4%20G723/8000%0D%0Aa%3Drtpmap%3A8%20PCMA/8000%0D%0Aa%3Drtpmap %3A18%20G729a/8000%0D%0Aa%3Drtpmap%3A96%20G726-40/8000%0D%0Aa%3Drtpmap %3A97%20G726-24/8000%0D%0Aa%3Drtpmap%3A98%20G726-16/8000%0D%0Aa %3Drtpmap%3A101%20telephone-event/8000%0D%0Aa%3Dfmtp%3A101%200-15%0D %0Aa%3Dptime%3A30%0D%0A variable_remote_media_ip: 10.0.1.10 variable_remote_media_port: 16414 variable_read_codec: PCMU variable_read_rate: 8000 variable_write_codec: PCMU variable_write_rate: 8000 variable_use_profile: nat variable_numbering_plan: US variable_default_gateway: asterlink.com variable_default_area_code: 918 variable_user_name: default variable_domain_name: 10.0.1.250 variable_local_media_ip: 10.0.1.250 variable_local_media_port: 16414 variable_endpoint_disposition: ANSWER DTMF-Digit: 5 DTMF-Duration: 720 Event-Name: DTMF Core-UUID: 0b75d60f-25cb-4be0-9f3c-e70bf91eaaa9 FreeSWITCH-Hostname: localhost.localdomain FreeSWITCH-IPv4: 10.0.1.250 FreeSWITCH-IPv6: 127.0.0.1 Event-Date-Local: 2008-01-04%2019%3A52%3A54 Event-Date-GMT: Sat,%2005%20Jan%202008%2001%3A52%3A54%20GMT Event-Date-timestamp: 1199497974959896 Event-Calling-File: switch_channel.c Event-Calling-Function: switch_channel_dequeue_dtmf Event-Calling-Line-Number: 275 Thats the DTMF event :) /b On Jan 4, 2008, at 7:04 PM, Bill Binko wrote:
I've been trying to implement a basic IVR that interacts with our database using PHP. With some help on IRC, I've created an Event Socket interface for PHP. It is rudimentary at this point, and not ready for prime-time, but it is working -- FreeSwitch connects to my process when a call comes in, sends the basic context info, and I can issue calls.
After a call to "event plain ALL", I start receiving events. Mainly HEARTBEAT events, with a few others thrown in.
What I'm NOT seeing is any DTMF events. Now, I know that the DTMF codes are recognized by other parts of FreeSwitch, such as the IVR, and I've ensure that my SIP client is sending them in-audio, SIP INFO and RFC2833. However, I'm not getting anything when I connect and press buttons.
I have some other issues with the Event Socket, that I'd love to get through, but without the DTMF, most of the applications we're working on won't go very far.
Is there anything I have to do to be able to get these Events to show up?
Thanks Bill
PS: I've attached the EventSocketListener PHP class. You can run it from the command line and it will listen on port 9090 -- obviously, it's not clean nor done, but it is working
#!/usr/local/bin/php <?php
/** * Based loosely on the NET_Server code in PEAR. * This is only an example - considerable additional work is needed * Specifically, the code in the handleConnection method should be * handled in a subclass * * */
class Message { var $properties = array(); var $content = null; }
class EventSocketListener {
var $host; var $port; var $sock; var $is_parent = true; var $clientInfo; var $clientFD;
var $connectionContext = array();
function &create($port, $host = "localhost") { $esl = new EventSocketListener; $esl->port = $port; $esl->host = $host; if (!function_exists('socket_create')) { return PEAR::raiseError('Sockets extension not available.'); } return $esl; }
function start() { if (($this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n"; }
if (!socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1)) { echo 'Unable to set option on socket: '. socket_strerror(socket_last_error()) . PHP_EOL; }
if (socket_bind($this->sock, $this->host, $this->port) === false) { echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n"; }
if (socket_listen($this->sock, 5) === false) { echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($this->sock)) . "\n"; }
// Dear children, please do not become zombies pcntl_signal(SIGCHLD, SIG_IGN);
// wait for incmoning connections while (true) { // new connection if(($fd = socket_accept($this->sock))) { $pid = pcntl_fork(); if($pid == -1) { return PEAR::raiseError('Could not fork child process.'); } // This is the child => handle the request elseif($pid == 0) { // this is not the parent $this->_isParent = false; // store the new file descriptor $this->clientFD = $fd;
$peer_host = ""; $peer_port = ""; socket_getpeername($this->clientFD, $peer_host, $peer_port); $this->clientInfo = array( "host" => $peer_host, "port" => $peer_port, "connectOn" => time() ); $this->handleConnection(); socket_shutdown($this->clientFD, 2); socket_close($this->clientFD); } else /* Parent does nothing */ { } } } }
function handleConnection() { $fd = $this->clientFD; //first, read headers & setup a state for this connection $line = ""; socket_write($fd, "CONNECT\n\n"); do { $line = socket_read($fd, 2048, PHP_NORMAL_READ); if (trim($line) == "") break; //we got a header, we need to add it to the list($key, $value) = explode(":", $line); $key = trim($key); $value = trim(urldecode($value)); $this->connectionContext[$key] = $value; } while ($line != "");
// print_r($this->connectionContext); $this->callConnected();
exit();
}
function processMessages($returnOnReply = false) { $fd = $this->clientFD; $result = new Message(); $props = array(); while (true) { do { $line = @socket_read($fd, 2048, PHP_NORMAL_READ); if (socket_last_error($fd) == 104) return null; if ($line == null || $line == FALSE || trim($line) == "") break; //we got a header, we need to add it to the message list($key, $value) = explode(":", $line); $key = trim($key); $value = trim(urldecode($value)); $props[$key] = $value; } while ($line != ""); $result->properties = $props;
if (isset($props['Content-Length'])) { $length = $props['Content-Length']; print("Reading content - $length\n"); $data = socket_read($fd, $length); $result->content = $data; } if (isset($props['Content-Type'])) { $type = $props['Content-Type']; if ($returnOnReply && ($type == "command/reply" || $type == "api/response")) { return $result; } else if ($type == "text/event-plain") //only plain events for now { $this->handleEvent($result); } } else { print("UNKNOWN MESSAGE: \n"); print_r($result); } } }
function invokeCommand($command) { //Send the command print("Invoking: $command\n"); $this->sendCommand($command); // Wait for the response $result = $this->processMessages(true); return $result; }
function sendCommand($command) { $fd = $this->clientFD; socket_write($fd, trim($command) . "\n\n"); }
/*-----------------------------------------------------*/ /* Abstract Methods - should move to subclass*/
function callConnected() { print_r($this->connectionContext); print("----------------\n"); $result = $this->invokeCommand("log DEBUG"); print_r($result); $result = $this->invokeCommand("event plain ALL"); print_r($result);
$this->processMessages(false); print("DONE PROCESSING MESSAGES"); print_r($this->connectionContext); }
function handleCommandResponse($response) { print("Recieved Unhandled Response:\n"); print_r($response); }
function handleEvent($event) { print("Recieved Unhandled Event:\n"); print_r($event); } }
// create a server that forks new processes $server = &EventSocketListener::create(9090);
// start the server $server->start(); ?>
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
Brian, I don't doubt that it should work that way... however, when running my script, here's what I get. Array ( ALL CONFIG VARIABLES PRINT HERE - I CAN SEND OFFLINE IF NEEDED ) ---------------- Invoking: log DEBUG Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK log level DEBUG [7] ) [content] => ) Invoking: events DTMF Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK event listener enabled plain ) [content] => ) DONE PROCESSING MESSAGES I get no Events at all with "events DTMF" When I use "event plain ALL" like in the example, I do get HEARTBEAT and other background events, but no DTMF events at all. I am thinking that I need to send some kind of command to get it "unstuck" or "out of park" or something so that that it starts listening for those tones. I get the same results with the 3CX softphone and the HT 488 SIP ATA. Bill
Hi Bill, I think you've hit the nail on the head below - it's just working out which one..! Do you have anything to answer the call - I can't see that in your script. Is the DTMF being sent inband - if so, you need to enable inband tone detection for it to be detected. For what it's worth, my event socket listener does the following when a call comes in: connect - get call information (caller ID, dialler number, etc.) myevents - send me my events answer - answer the call and it then gets DTMF events just fine. Hope this helps -- Dave
Brian, I don't doubt that it should work that way... however, when running my script, here's what I get.
Array ( ALL CONFIG VARIABLES PRINT HERE - I CAN SEND OFFLINE IF NEEDED ) ---------------- Invoking: log DEBUG Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK log level DEBUG [7] )
[content] => ) Invoking: events DTMF Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK event listener enabled plain )
[content] => ) DONE PROCESSING MESSAGES
I get no Events at all with "events DTMF" When I use "event plain ALL" like in the example, I do get HEARTBEAT and other background events, but no DTMF events at all.
I am thinking that I need to send some kind of command to get it "unstuck" or "out of park" or something so that that it starts listening for those tones.
I get the same results with the 3CX softphone and the HT 488 SIP ATA.
Bill
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
-- David Knell, Director, 3C Limited T: 020 8114 8901 F: 020 8692 0677 M: 07773 800623 http://www.3c.co.uk
David (et al), Here's how I invoke my event socket app through the XML dialplan: <extension name="socketTest"> <condition field="destination_number" expression="^444$"> <action application="socket" data="127.0.0.1:9090 full"/> </condition> </extension> When the call is connected, I send an auth (really, anything works), then read the response, which has all of the call parameters, and then I send the 'event plain ALL' command. What is the actual set of commands you're sending? I should be getting DTMF through all three standard methods (in-audio, SIP INFO, and the RFC version). Thanks Bill David Knell wrote:
Hi Bill,
I think you've hit the nail on the head below - it's just working out which one..! Do you have anything to answer the call - I can't see that in your script. Is the DTMF being sent inband - if so, you need to enable inband tone detection for it to be detected.
For what it's worth, my event socket listener does the following when a call comes in: connect - get call information (caller ID, dialler number, etc.) myevents - send me my events answer - answer the call and it then gets DTMF events just fine.
Hope this helps --
Dave
Brian, I don't doubt that it should work that way... however, when running my script, here's what I get.
Array ( ALL CONFIG VARIABLES PRINT HERE - I CAN SEND OFFLINE IF NEEDED ) ---------------- Invoking: log DEBUG Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK log level DEBUG [7] )
[content] => ) Invoking: events DTMF Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK event listener enabled plain )
[content] => ) DONE PROCESSING MESSAGES
I get no Events at all with "events DTMF" When I use "event plain ALL" like in the example, I do get HEARTBEAT and other background events, but no DTMF events at all.
I am thinking that I need to send some kind of command to get it "unstuck" or "out of park" or something so that that it starts listening for those tones.
I get the same results with the 3CX softphone and the HT 488 SIP ATA.
Bill
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
Ok, the issue is that I had removed the "async" on my <action> tag. I don't know if this is intentional or not, but in sync mode *some* events make it through (HEARTBEAT, EXECUTE), etc. , but not DTMF. With "async full" I am getting the events... I will march ever onward ;-) Thanks for everyone's help. Bill Bill Binko wrote:
David (et al),
Here's how I invoke my event socket app through the XML dialplan:
<extension name="socketTest"> <condition field="destination_number" expression="^444$"> <action application="socket" data="127.0.0.1:9090 full"/> </condition> </extension>
When the call is connected, I send an auth (really, anything works), then read the response, which has all of the call parameters, and then I send the 'event plain ALL' command.
What is the actual set of commands you're sending? I should be getting DTMF through all three standard methods (in-audio, SIP INFO, and the RFC version).
Thanks Bill
David Knell wrote:
Hi Bill,
I think you've hit the nail on the head below - it's just working out which one..! Do you have anything to answer the call - I can't see that in your script. Is the DTMF being sent inband - if so, you need to enable inband tone detection for it to be detected.
For what it's worth, my event socket listener does the following when a call comes in: connect - get call information (caller ID, dialler number, etc.) myevents - send me my events answer - answer the call and it then gets DTMF events just fine.
Hope this helps --
Dave
Brian, I don't doubt that it should work that way... however, when running my script, here's what I get.
Array ( ALL CONFIG VARIABLES PRINT HERE - I CAN SEND OFFLINE IF NEEDED ) ---------------- Invoking: log DEBUG Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK log level DEBUG [7] )
[content] => ) Invoking: events DTMF Message Object ( [properties] => Array ( [Content-Type] => command/reply [Reply-Text] => OK event listener enabled plain )
[content] => ) DONE PROCESSING MESSAGES
I get no Events at all with "events DTMF" When I use "event plain ALL" like in the example, I do get HEARTBEAT and other background events, but no DTMF events at all.
I am thinking that I need to send some kind of command to get it "unstuck" or "out of park" or something so that that it starts listening for those tones.
I get the same results with the 3CX softphone and the HT 488 SIP ATA.
Bill
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
_______________________________________________ Freeswitch-users mailing list Freeswitch-users@lists.freeswitch.org http://lists.freeswitch.org/mailman/listinfo/freeswitch-users UNSUBSCRIBE:http://lists.freeswitch.org/mailman/options/freeswitch-users http://www.freeswitch.org
participants (3)
-
Bill Binko
-
Brian West
-
David Knell