Phpconf 2013 - Agile Telephony Applications with PAMI and PAGI

Embed Size (px)

DESCRIPTION

This is the talk about PAMI and PAGI, and general telephony applications with PHP and Asterisk for the php conference argentina (phpconfar). The **complete** talk is available in the slides (in english), just download it (see above), and check out the slide notes for the complete text for each slide. Looking forward for your feedback. Enjoy :)

Citation preview

  • 1. Agile Telephony Applications

2. Who the f...? Marcelo Gornstein Author of some open source projects, in Erlang, C, PHP, Js (NodeJS), Ruby. A bit more of 15 years working in the industry (sysadmin, developer, lead developer, architect, product owner). A few more years, as a hobbyist. Code monkey: Currently an Erlang developer at Inaka ( http://www.inaka.net/). Variety of projects and scenarios (sysadmin, parking systems, virtual hosting, telephony, etc). Last years: VoIP https://github.com/marcelog http://marcelog.github.io/ 3. Agenda Introduction to some telephony concepts IVR Call Flow PBX Switch What is Asterisk, how does it fit Brief Introduction Dialplan Applications Interacting with Asterisk: Interfaces AMI Protocol AGI Protocol PAMI Basic use Actions / Responses Events / Filtering PAGI Basic use The PAGIApplication Nodes / NodeController Unit Testing 4. Asterisk What can we know upfront Telephony related IVR? PBX? Switch? VoIP 5. Interactive Voice Response Interactive voice response (IVR) is a technology that allows a computer to interact with humans through the use of voice and DTMF tones input via keypad. Wikipedia: http://en.wikipedia.org/wiki/Interactive_voice_response 6. http://www.ivrsworld.com/advanced-ivrs/using-ivrs-in-complaint-managment-system/ 7. PBX A private branch exchange (PBX) is a telephone exchange that serves a particular business or office, as opposed to one that a common carrier or telephone company operates for many businesses or for the general public Wikipedia: http://en.wikipedia.org/wiki/Private_branch_exchange 8. Telephone Exchange A telephone exchange is a telecommunications system used in the public switched telephone network or in large enterprises. An exchange consists of electronic components and in older systems also human operators that interconnect (switch) telephone subscriber lines or virtual circuits of digital systems to establish telephone calls between subscribers. Wikipedia: http://en.wikipedia.org/wiki/Telephone_exchange 9. Without switches: not that fun 10. First switch Hardware: humans 11. Mechanical switches Hardware: relays, motors, strowger switch 12. Digital Switches Hardware: modern electronic components, software starts to make its appearance. First IVRs.. 13. SoftSwitches Software pretty much does everything. Any regular computer can be a switch. 14. SoftSwitches Yes, even Open Source :) 15. SoftSwitches Yes, even Open Source :) 16. This is great for us! Internet / PSTN 17. VoiceMail This is great for us! Internet / PSTN 18. VoiceMail Time of day This is great for us! Internet / PSTN 19. VoiceMail Customer SupportTime of day This is great for us! Internet / PSTN 20. VoiceMail Customer Support PrePaid Calling Cards Time of day This is great for us! Internet / PSTN 21. VoiceMail Customer Support PrePaid Calling Cards Telephone Campaigns Time of day This is great for us! Internet / PSTN 22. Asterisk: What we know now It's software. Can be run in a variety of unix-like operating systems (http://www.asterisk.org/downloads) More a PBX than anything else Poor man's switch (scale might be difficult, some API inconsistencies, not really designed as a softswitch) Easy to use (although your mileage can vary) Supports SIP, R2, SS7 Huge community Thousands of propietary and open source applications Useful interfaces to interact with it and to create IVRs 23. Let's get a bit more serious 24. What's important for us, devs Dialplan (extensions.conf) Routing (Context / Priority / Extension) Patterns: _X. Roadmap for the call Applications / Functions Answer / Busy / Hangup / Ringing SetCallerID AGI Playback / Playtones / SayDigits Interfaces (AGI / AMI) 25. Dialplan [default] 113, 1, Answer 113, 2, Goto(say_hi,${EXTEN},1) [say_hi] _X.,1,Playback(welcome) _X.,n,Read(NUMBER,,4) _X.,n,SayNumber(${NUMBER}) _X.,n,AGI(/usr/ivr/run.php) _X.,n,Hangup (can you imagine how a complex application would look like?) 26. Interfaces Asterisk Manager Interface (AMI) TCP/TLS Can listen for events in the whole box Can execute commands (actions) Privileges are configurable per user Asterisk Gateway Interface (AGI) stdin/stdout Serves a specific call Can execute some commands, only for the current call. 27. AMI and AGI: The protocols 28. AMI Message Description Example Events Sent in an async way by Asterisk without notice (sometimes as part of a response) Event: FullyBooted Privilege: system,all Status: Fully Booted Variable: foo=bar Actions Sent by us, to execute commands Action: Ping ActionId: 1378159694 Responses Sent by asterisk as a reply to an executed action Response: Success ActionID: 1378159694 Ping: Pong Timestamp: 1378159694 Text Protocol, TCP as transport Lines end with rn, Messages by rn https://wiki.asterisk.org/wiki/display/AST/AMI+Actions https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+AMI+Events 29. AGI Text Protocol, uses stdin/stdout (although FastAGI allows tcp) Reply example: error_coderesult=[additional_data]rn ANSWER 200 result=0 GET DATA /tmp/file.wav 5000 5 200 result=2 (timeout) https://wiki.asterisk.org/wiki/display/AST/AGI+Commands 30. Easy Protocols fopen / fread / fwrite / fclose Any language (even shell scripts!) Lack of real use without a proper abstraction 31. PAMI and PAGI There are other frameworks available: phpagi, shift8. Features from the real world, for devs and products. Unit tested: coverage ~100% (most of the time..) Object Oriented: Easy to use and extend Use log4php (http://logging.apache.org/log4php/) PAMI (Php-AMI): AMI Client Monitoring, Call Tracing (CDR, Billing) Can send/receive calls, SMS, etc Queue/Park calls Operator consoles, realtime events, CRMs Can provide IVR's via AsyncAGI + PAGI PAGI (Php-AGI): AGI Client IVR's (Surveys, Customer support, VoiceMail, Fax, etc) 32. Lot of code ahead of us now! 33. PAMI: Basic use $options = array( 'log4php.properties' => 'log4php.properties', 'host' => 'mypbx.mycompany.com', 'scheme' => 'tcp://', // Or tls:// 'port' => 5038, 'username' => 'mrwolf', 'secret' => 'badass' ); // Create the client use PAMIClientImplClientImpl as PamiClient; $client = new PamiClient($options); $client->open(); // Needed to process and deliver events and responses while($running) { $client->process(); usleep(1000); } $client->close(); 34. Executing Actions use PAMIMessageActionReloadAction; $response = $pamiClient->send(new ReloadAction()); if ($response->isSuccess()) { ... } else { ... } Execution is synchronous, send() will only return when the response is ready and complete (including any related events) $response->isSuccess() to know if eveything went well $response->getEvents() returns associated events $response->getKeys() All response keys $response->getVariable()/getVariables() To get response variables 35. Listening for events Closures Pre existant objects $client->registerEventListener( function (EventMessage $event) { ... } ); $client->registerEventListener( array($object, 'method') ); Execution is asynchronous $response->getKeys() All response keys $response->getVariable()/getVariables() To get response variables $event->getName() Event name (Bridge, Unlink, Relad, Dial, etc) 36. Filtering events with predicates use PAMIMessageEventDialEvent; // The 2nd argument allows the addition of predicates, // that will return true (let the event pass) // or false (drop it) $client->registerEventListener( function (EventMessage $event) { ... }, function (EventMessage $event) { return $event instanceof DialEvent && $event->getSubEvent() == 'Begin'; } ); 37. Example: Originating a call $call = new OriginateAction('sip/marcelog'); $call->setApplication(AGI); $call->setData(myagi.php); $call->setAsync(true); $response = $this->pami->send($call); while($running) { $client->process(); usleep(1000); } 38. Example: Originating a call $client->registerEventListener( function (OriginateResponseEvent $event) { $reason = $event->getReason(); switch ($reason) { ... } }, function (EventMessage $event) { return instanceof OriginateResponseEvent; } ); 39. PAMI Read more http://marcelog.github.io/PAMI http://marcelog.github.io/articles/articles.html How to install http://marcelog.github.io/PAMI/install.html https://github.com/marcelog/PAMI https://packagist.org/packages/marcelog/pami http://pear.marcelog.name/ http://ci.marcelog.name/job/PAMI 40. IVRs! 41. PAGI Client: Basic use // Get a client $options = array( 'log4php.properties' => 'log4php.properties' ); use PAGIClientImplClientImpl as PagiClient; $client = PagiClient::getInstance($options); // Handling the call $client->answer(); $client->hangup(); 42. // Logging through the asterisk logger (logger.conf) $asteriskLogger = $client->getAsteriskLogger(); $asteriskLogger->notice("A NOTICE priority message"); $asteriskLogger->error("An ERROR priority message"); $asteriskLogger->warn("A WARNING priority message"); // Originating a call $result = $client->dial( "SIP/marcelog", array(60, 'rh') ); if ($result->isAnswer()) { $asteriskLogger->debug( $result->getAnsweredTime() ); } PAGI Client: Basic use 43. // Play a file and get input // timeout in 3s, up to 8 digits $result = $client->getData($aSoundFile, 3000, 8); if ($result->isTimeout()) { // No input } else { // Get what the user pressed... $digits = $result->getDigits(); } // Accessing the Caller ID info $clid = $client->getCallerId(); $clid->setNumber('123123'); // Music On Hold $client->setMusic(true, 'myMOHClass'); PAGI Client: Basic use 44. // Playing different tones, sending indications $client->playDialTone(); $client->playBusyTone($seconds); // Sending indications $client->indicateProgress(); $client->indicateBusy(); PAGI Client: Basic use // Changing CDR records $cdr = $pagiClient->getCDR(); $cdr->setUserfield("my own content here"); $cdr->setAccountCode("blah"); $cdr->setCustom("myOwnField", "withMyOwnValue"); $asteriskLogger->debug($cdr->getAnswerLength()); 45. More Get / Set channel variables Add / Remove SIP headers Send / Receive FAXes Automatic Machine Detection Record audio Say a datetime, digits, complete numbers Spool calls Execute custom commands (exec) And some more :) 46. Optional: The PAGIApplication class MyPagiApplication extends PAGIApplication { public function run() { } public function init() { } public function signalHandler($signo) { } public function errorHandler($type, $message, $file, $line){} public function shutdown() {} } $pagiAppOptions = array('pagiClient' => $pagiClient); $pagiApp = new MyPagiApplication($pagiAppOptions); $pagiApp->init(); $pagiApp->run(); Lots of boiler plate code included: signal handling, error handler, includes a logger, etc 47. Unit testing (Client) $client->answer(); $number = $client->getCallerId()->getNumber(); if ($number == 'anonymous') { $client->streamFile("i-cant-find-your-number"); } else { $client->streamFile("you-are-calling-from"); $client->sayDigits($number); } $client->streamFile("bye") 48. Unit testing: General skeleton /** * @test */ public function can_read_caller_id() { $mock = new MockedClientImpl($options); // In the next slides we'll see what to // do with the mock... $app = new App(array('pagiClient' => $mock)); $app->init(); $app->run(); $app->shutdown(); } 49. $mock // We want to be sure these are called with // these arguments... ->assert('answer') ->assert('getFullVariable', array('CALLERID(num)')) ->assert('streamFile', array('you-are-calling-from')) ->assert('sayDigits', array('5555555')) ->assert('streamFile', array('bye')) ->assert('hangup') Unit testing: Assert behavior 50. // and on calls to these functions, // return the given values ->onAnswer(true) ->onGetFullVariable(true, '5555555') ->onStreamFile(false, '#') ->onSayDigits(true, '#') ->onStreamFile(false, '#') ->onHangup(true) ; Unit testing: Mock asterisk responses 51. Nodes Client Wrapper Declarative programming Fluent interface DI compatible Testable Can keep state Prompt, input, validations Real life features: (un)interruptable prompts, messages for different situations (invalid input, last attempt, etc), accept/discard input on pre prompt messages, etc Callbacks: executeOnValidInput(), executeOnInputFailed(), executeBeforeRun(), executeAfterRun(), executeAfterFailedValidation() Execution result COMPLETE CANCEL TIMEOUT MAX_INPUT_REACHED 52. A Simple IVR Application 53. $pagiClient->streamFile('welcome'); $node = $pagiClient ->createNode('get-number') ->saySound('enter-number') Playing Welcome and Prompt 54. ->maxAttemptsForInput(3) ->playOnMaxValidInputAttempts( 'too-many-attempts' )->expectAtLeast(10) ->expectAtMost(12) ->maxTotalTimeForInput(3000) ->playOnNoInput('no-input') ->cancelWith(Node::DTMF_STAR) ->endInputWith(Node::DTMF_HASH) Some mundane details... 55. ->validateInputWith( 'numberIsValid', function(Node$node)use($db){ $input=$node->getInput(); $db->saveNumber($input); return$db->numberIsValid($input); }, 'number-is-not-valid' ) Adding validators 56. ->executeOnValidInput(function(Node$node){ $pagiClient=$node->getClient(); $pagiClient->streamFile('thank-you'); }) On valid input... 57. ->executeOnInputFailed(function(Node$node){ if($node->getTotalInputAttemptsUsed()addPrePromptMessage("please-try-again"); } }) On failed validation... 58. When done, hangup! ->executeAfterRun(function(Node$node){ $pagiClient->hangup(); }) 59. ->run(); That's it :) 60. $pagiClient->streamFile('welcome'); $pagiClient ->createNode('menu') ->saySound('enter-number') ->maxAttemptsForInput(3) ->playOnMaxValidInputAttempts( 'too-many-attempts' )->expectAtLeast(10) ->expectAtMost(12) ->maxTotalTimeForInput(3000) ->playOnNoInput('no-input') ->cancelWith(Node::DTMF_STAR) ->endInputWith(Node::DTMF_HASH) ->validateInputWith( 'numberIsValid', function(Node$node)use($db){ $input=$node->getInput(); $db->saveNumber($input); return$db->numberIsValid($input); }, 'number-is-not-valid' )->executeOnValidInput(function(Node$node){ $pagiClient=$node->getClient(); $pagiClient->streamFile('thank-you'); })->executeOnInputFailed(function(Node$node){ if($node->getTotalInputAttemptsUsed()addPrePromptMessage("please-try-again"); } })->executeAfterRun(function(Node$node){ $pagiClient->hangup(); })->run(); 61. Checking the result if($node->maxInputsReached()){ $node->getClient()->hangup(); }elseif($node->isComplete()){ $input=$node->getInput(); $otherNode->run(); }elseif($node->wasCancelled()){ ... } Nice, but too many nodes = too many ifs 62. The NodeController //1.Createthenodecontroller $controller=$client ->createNodeController('customerSupport'); //2.Createafewnodes $controller->register('salutation') ->saySound('salutation'); $controller->register('menu') ->saySound('main-menu') ->maxAttemptsForInput(3) ->expectExactly(1) ->validateInputWith(...); $controller->register('maxAttempts') ->saySound('too-many-attempts'); $controller->register('operator') ->dial('SIP/operator',array(60,'rh'); 63. General Flow Behavior //3.Specifybehavior $controller->registerResult('salutation') ->onComplete()->jumpTo('menu'); $controller->registerResult('menu') ->onMaxAttemptsReached()->jumpTo('maxAttempts'); $controller->registerResult('maxAttempts') ->onComplete()->hangup(16); 64. $controller->registerResult('menu') ->onComplete()->withInput('1')->jumpTo('sales'); $controller->registerResult('menu') ->onComplete()->withInput('2')->jumpTo('support'); $controller->registerResult('menu')->onComplete()-> jumpAfterEval(function(Node$node){ return'operator'; }); Menu Behavior 65. //4.Run! $controller->jumpTo('salutation'); The NodeController 66. Unit testing (Node apps) //Fornodes,wecanspecifytheinput,and //therestisjustliketestingclient-onlyapps. $mockedMenu=$mockedPagiClient ->onCreateNode('mainMenu') $mockedMenu->runWithInput('123') ->assertSaySound('some-audio',1) ->assertSayDigits(123,1) ->assertSayNumber(321,1) ->assertSayDateTime(1,'dmY',1) $pagiApp->init(); $pagiApp->run(); 67. PAGI Read more http://marcelog.github.io/PAGI http://marcelog.github.io/articles/articles.html How to install http://marcelog.github.io/PAGI/install.html https://github.com/marcelog/PAGI https://packagist.org/packages/marcelog/pagi http://pear.marcelog.name/ http://ci.marcelog.name/job/PAGI 68. Thank you :) @all, for coming today All the organizers Mariano Iglesias @mgiglesias Mi so-cool-and-beautiful wife for her support Dvorak and Qwerty, of course 69. Questions? [email protected] https://github.com/marcelog http://marcelog.github.io/