Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Phpconf 2013 - Agile Telephony Applications with PAMI and PAGI

15,792 views

Published on

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 :)

Published in: Technology, Business

Phpconf 2013 - Agile Telephony Applications with PAMI and PAGI

  1. 1. Agile Telephony Applications
  2. 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. 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. 4. Asterisk What can we know upfront ● Telephony related ● IVR? PBX? Switch? ● VoIP
  5. 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. 6. http://www.ivrsworld.com/advanced-ivrs/using-ivrs-in-complaint-managment-system/
  7. 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. 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. 9. Without switches: not that fun
  10. 10. First switch Hardware: humans
  11. 11. Mechanical switches Hardware: relays, motors, strowger switch
  12. 12. Digital Switches Hardware: modern electronic components, software starts to make its appearance. First IVRs..
  13. 13. SoftSwitches Software pretty much does everything. Any regular computer can be a switch.
  14. 14. SoftSwitches Yes, even Open Source :)
  15. 15. SoftSwitches Yes, even Open Source :)
  16. 16. This is great for us! Internet / PSTN
  17. 17. VoiceMail This is great for us! Internet / PSTN
  18. 18. VoiceMail Time of day This is great for us! Internet / PSTN
  19. 19. VoiceMail Customer SupportTime of day This is great for us! Internet / PSTN
  20. 20. VoiceMail Customer Support PrePaid Calling Cards Time of day This is great for us! Internet / PSTN
  21. 21. VoiceMail Customer Support PrePaid Calling Cards Telephone Campaigns Time of day This is great for us! Internet / PSTN
  22. 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. 23. Let's get a bit more serious
  24. 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. 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. 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. 27. AMI and AGI: The protocols
  28. 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. 29. AGI ● Text Protocol, uses stdin/stdout (although FastAGI allows tcp) ● Reply example: error_code<space>result=<...>[<space>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. 30. Easy Protocols ● fopen / fread / fwrite / fclose ● Any language (even shell scripts!) ● Lack of real use without a proper abstraction
  31. 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. 32. Lot of code ahead of us now!
  33. 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. 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. 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. 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. 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. 38. Example: Originating a call $client->registerEventListener( function (OriginateResponseEvent $event) { $reason = $event->getReason(); switch ($reason) { ... } }, function (EventMessage $event) { return instanceof OriginateResponseEvent; } );
  39. 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. 40. IVRs!
  41. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 52. A Simple IVR Application
  53. 53. $pagiClient->streamFile('welcome'); $node = $pagiClient ->createNode('get-number') ->saySound('enter-number') Playing Welcome and Prompt
  54. 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. 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. 56.   ->executeOnValidInput(function (Node $node) {     $pagiClient = $node->getClient();     $pagiClient->streamFile('thank-you');   }) On valid input...
  57. 57.   ->executeOnInputFailed(function (Node $node) {       if($node->getTotalInputAttemptsUsed() <= 2) {       $node->addPrePromptMessage("please-try-again");      }     }) On failed validation...
  58. 58. When done, hangup!   ->executeAfterRun(function (Node $node) {     $pagiClient->hangup();    })
  59. 59.   ->run(); That's it :)
  60. 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() <= 2) {       $node->addPrePromptMessage("please-try-again");      }     })->executeAfterRun(function (Node $node) {     $pagiClient->hangup();    })->run();
  61. 61. Checking the result if($node->maxInputsReached()) {     $node->getClient()->hangup();   } else if($node->isComplete()) {     $input = $node->getInput();   $otherNode->run(); } else if($node->wasCancelled()) {   ... } Nice, but too many nodes = too many ifs
  62. 62. The NodeController       // 1. Create the node controller     $controller = $client       ->createNodeController('customerSupport');     // 2. Create a few nodes     $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. 63. General Flow Behavior     // 3. Specify behavior     $controller->registerResult('salutation')       ->onComplete()->jumpTo('menu');          $controller->registerResult('menu')       ->onMaxAttemptsReached()->jumpTo('maxAttempts');     $controller->registerResult('maxAttempts')       ->onComplete()->hangup(16);
  64. 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. 65.     // 4. Run!     $controller->jumpTo('salutation'); The NodeController
  66. 66. Unit testing (Node apps) // For nodes, we can specify the input, and // the rest is just like testing “client-only” apps. $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. 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. 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. 69. Questions? ● marcelog@gmail.com ● https://github.com/marcelog ● http://marcelog.github.io/

×