Phpconf 2013 - Agile Telephony Applications with PAMI and PAGI


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
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Good evening! Thank you all for coming in today, considering that we're the last 3 talks of the day, and also, considering that the beer has already been delivered! So I guess this topic should be of interest to you, and I hope that the talk is good enough to encourage that interest even more.
  • So a bit about me, first. My name is Marcelo Gornstein, I'm the author of several open source projects (two of them are the reason for this talk, : PAGI and PAMI). I've been working in the IT field for a bit more than 15 years, but I do have a few more years as a hobbist. Currently, I'm working at Inaka, as an erlang developer, which is awesome, because this gives me the chance to use a languange and an environment that I love, on a daily basis. At Inaka we mostly do social applications, that handle millions of users per day. And we do everything, from the server (which is almost always written in erlang) up to the applications for the final users, most of the time, Android and IOS applications. On the personal side and besides my current work, I had to deal with a lot of different scenarios, from parking and virtual hosting systems to telephony, and other backend stuff. And in the last few years I have dedicated myself a bit more to the telephony world, specially voip. For those who are interested, those are my github account and my personal website, with other open source projects and some articles that I like to write from time to time.
  • Now about the talk for today. I thought about splitting the talk in 5 big topics, as some kind of roadmap that can take us from the big overview, up to the very details, as some kind of zoom in into the subject. We're going to start by defining some telephony concepts, as a way to state a common language between us, with the ones that are not familiarized with the subject, but also with the ones that already have some experience in the field. Afterwards, there's a small introduction to the asterisk world, but mostly to the concepts that are more important to us, as developers, like the interfaces that asterisk provides so we can actually do something useful with it. And the final part of the talk is more technical, focused in how to actually use those interfaces with the frameworks pagi and pami, to create our cool applications.
  • So let's get started. When we first start working with asterisk, there are a couple of words that tend to appear more often, so let's define them, to get a common language between us.
  • An IVR is just a system (an interface actually) that allow us to interact with a computer, using our voice or the DTMF tones. The DTMF tones are generated each time we press a button on our numeric keypad, in our telephones. Lots of IVRs application can be found out there, like surveys, customer support systems, voicemails, etc. This kind of stuff is the window that we can use to enter this new world!
  • Now, every time we speak about an IVR, we can also speak about call flows. A call flow is actually just a flow chart, where we have “nodes” (let's remember that word, “nodes” for the last part of the talk), and those nodes are connected to each other with arrows. There are nodes that represent actions (like telling when a sound needs to be played), but also nodes where decisions are made and so and so. The flow itself describes how the ivr is navigated, where the user is asked for input, how that input is validated, what happens when the user presses 1, or 2, or the asterisk in the menu, etc.
  • Another word that we probably will hear about is PBX. Let's say for now that a pbx is just a small telephone exchange. It will serve a small number of “extensions” (let's say extensions, and “subscribers” when speaking about telephone exchanges). A PBX is small, meaning that it will be serving an office, or a specific floor of a building, or maybe the building itself, although that would even be a bit big. A lot of pbx nowadays will offer some IVRs, like voicemails, very similar to what our mobile or landline telephone company can offer.
  • So what is a telephone exchange? For what we care, a telephone exchange is a system that is part of the public switched telephone network, and it has all the needed components to actually switch the needed circuits to let two points of the network talk between each other, either two subscribers or maybe even other telephone exchanges. Let's see a brief historic summary, of how do we go from these definitions up to the point of why asterisk exists, what can we do with it, and what is exactly used for.
  • Telephone exchanges are the magic thing behind the scenes. Without them, we wouldn't be able to do much, actually. Telephone exchanges (let's call them switches, from now on) are what allow us to route calls from one subscriber to another, and the actual connections between them are the core of the public switched telephone network.
  • The first switches used humans, that literally connected the right plugs to close the needed circuits to establish a connection between two subscribers.
  • A few years later, these crazy machines were invented, these were mechanical switches, that had very complex mechanical components, like relays, motors, and other stuff. These where too big, compared to the number of subscribers that they could serve, so.. not much to say about them.
  • Some time later, these digital switches appeared, with modern electronic components (transistors), and the software started to enter the scene, these switches could be programmed, and also the very first IVRs were born
  • And nowadays we have these “softswitches”, where the software has completely taken over the situation, even in the name (the word “soft” is actually referring to software). They are standard computers, with standard hardware, but also with standard operating systems, like linux, solaris, etc. They are rackable computers, much smaller than their ancestors, and cheaper. Since they have standard operating systems, everyone can actually create software for them.
  • This means that we can even use open source platforms, like asterisk
  • Or freeswitch, or any of the available alternatives
  • Currently, the public switched telephone network is a mixed environment, where we can find modern softswitches, along with old digital switches that haven't been replaced yet, and also along new softswitches using software like asterisk and freeswitch. And this is really great for us, because we can download and install the very same software that's being used right now, in the telephony world. In the past, the only way to access such systems was to work for a company, pay a lot of money, even wait for a lot of time for simple support requests. Today we can just download the software, invest a weekend playing around with it, and we're ready to start creating our own applications, even submit new features to these projects, or change the source code to match our needs. We now have a complete new market to work for, where we can create applications, get new clients, have fun, and learn a lot of new stuff!
  • We can now create our own applications, like voicemails, for companies or even home subscribers...
  • Or regular ivrs, like the “official hour of the day”...
  • … or customer support systems, autoprovisioning, or a ticket system, to let users report problems..
  • .. also some very specific platforms, like platforms for prepaid calling cards, that allows anyone to buy a calling card and use it in our system, to make calls, transfer funds, etc...
  • ...and we can also create telephone campaign systems, to do surveys, or advertising, just like those times when a famous politician or actor call us before the elections, the kind of applications that we like so much!
  • So as a summary. We now know that asterisk is software, and that it can be run in a wide set of unix-like operating systems. We also know that it's more a pbx than anything else, although it can be used as a softswitch in a small-medium scale, since it wasn't really designed as a softswitch in the first place. It's also very easy to use, which is great for us as developers, we can get it up and running in a short time. We can also connect different kind of endpoints to it, like SIP, R2, SS7, Jabber, etc. R2 and SS7, for those who haven't heard about them, are the standard protocols for the public switched telephone network. R2 is analog, it uses dtmf tones for signalling, and SS7 is pretty much what is out there now, a complete stack of digital protocols, that can also send and receive digital data, etc. Asterisk also has a huge community, with thousands of available propietary and free or open source applications where to choose from. And also, it has two interfaces for us, developers, to actually work with it and create our own applications.
  • So this is a good time to ask some questions before moving on to the gory details of asterisk. We have now taken a ride through the telephony world, what's out there, what's asterisk, how can we use it, and where we, developers, stand in all this new world.
  • Ok, let's start looking at what is most important for us, as developers, to know about asterisk. First of all, asterisk has a dialplan. The dialplan allow us to route calls, that means getting the call to where we want to, based on different criteria, like destination number, or other stuff. On the other hand, the dialplan is where we can sequentially program what is going to happen to that call, when it's going to be answered, what messages are going to be played to it, or if we want to accept input from the caller, etc. It is effectively the roadmap for the call, line by line. This takes me to the second point, which is the dialplan applications. Asterisk has a lot of them by default, and they let us do our thing, they are used to answer a call, play audio, accept input, play tones, get information like the caller id, etc. We actually use these from the dialplan to execute stuff for that call. Also, there are lot of propietary and open source applications out there, that we can download, compile, and use to extend the features of our dialplan. And last but not least, the third important point, are the AGI and AMI interfaces that asterisk offers, so we can write applications without depending on the dialplan.
  • Here's a small dialplan example. The dialplan is nothing more than a mere text file, that asterisk reads as part of its configuration, it's actually called extensions.conf. In this small example, there are two “contexts”. Let's say that a dialplan can be divided into contexts, and in this case there are two of them, named “default”, and “say_hi”. For the sake of the example, let's say that calls start (because of system configuration), into the context default. So in this case, we're saying that we want to handle the calls with destination number equal to “113”. What we're going to do with those calls is to answer them, and then immediately jump to the context “say_hi”, where we are going to play a welcome message and read up to 4 digits from the user. We then read that back to the user, and execute an agi application. Note that we are only using dialplan functions, like Answer, Goto, Playback, etc, including the AGI dialplan function. This tell asterisk to actually fork a new process and execute the command /usr/ivr/run.php, the call will get handlded by that agi application aftewards. So without paying too much attention to the details, the idea for this slide is to actually show what's the first tool that we can use to create an ivr application, which is the dialplan, and also, to show how limited this tool is, and why we need interfaces like AMI and AGI to do more powerful things (it's not that awesome if we need to redeploy asterisk configuration files for a change in our application, right?)
  • So let's talk a bit more about those interfaces. These are called AMI and AGI (Asterisk Manager Interface, and Asterisk Gateway Interface). It's really important that the difference between them is clear. With AMI, we use a TCP socket to connect to asterisk, and we can execute commands that affect the whole box and all the ongoing calls, like reloading configuration, restarting modules, terminating channels, originating new calls, etc. Also, we can listen for events in the whole system. On the other hand, with AGI, we can create IVR applications, everything we execute will only affect the current call. With AMI, we can react to realtime events, writing to a db (like for a crm, like sugarcrm, etc), and we can also execute stuff, create console operators, and things like that. With AGI, we can write systems that do surveys, or voicemails, etc.
  • Let's now take a look at what's behind the scenes of AGI and AMI, which (suprisingly) are the AGI and AMI protocols.
  • AMI uses TCP as the transport protocol for the messages, which are plain text, and separated by a standard dos-like newline, \r\n, similar to how other protocols, like http, and smtp, work. Each message contains lines which are also separated by \r\n. When using AMI, there are three concepts that we wiil work with. Events, actions, and responses. Events are sent asynchronously by asterisk, that means that we don't know in advance, when a specific event or set of events will arrive, but asterisk will send them as interesting things happen in the system, like the start of a new call, a channel in the system changing state, etc. On the other hand, actions are sent by us to execute commands in the box, we can edit configuration, reload modules, terminate channels, etc. And each one of those actions have a response, that will tell us what happened to that specific command. Sometimes, a response will have associated events with it, that complete it, so those events wont be related to things happening in the system, but as part of a response to an action.
  • Now let's see the AGI protocol. Just like AMI, this protocol is plain text, but it's always synchronous, we send a command, and we get a response. All commands will only affect the current call. With this command we can create our IVR applications. Also, the lines are separated by \r\n. AGI was originally designed to use stdin and stdout as transport for the messages. If you think about it, it kind of makes sense to use stdin and stdout because the original idea is that we're going to tell asterisk to fork a process and execute a file, by using the AGI dialplan function, like we mentioned before. So that makes sense, to use stdin and stdout as a way of doing IPC between asterisk and the new process. But, eventually, they understood that this wasn't scalable, because we're forced to run all the calls in just one server. So they came up with fastagi, which is basically the same, but using tcp as the transport for the AGI protocol. With AGI we can play sounds, get input, send and receive faxes, etc.
  • As you can see these are very simple protocols, and writing a client would only involve a couple of function calls, like fopen, fread, fwrite, and the like, plus some text parsing. This is part of the success of asterisk, because we can write clients in any language, even shell scripts, and it's fun. But, they are a bit primitive to be working with them directly, we actually have very specific needs as developers, we want to write code that's easily readable and maintainable, and also we want to know as few implementation details as possible when dealing with 3 rd party products like asterisk. We just want to focus on our code, on the business logic of our application, and mainly do what we want to do with as few distractions as possible. That's why we need a proper layer of abstraction over the AGI and AMI protocols and interfaces.
  • So let's talk a bit about PAMI and PAGI, which are the php clients for the AGI and AMI interfaces. There are other frameworks out there, but PAGI and PAMI were born due to a not-so-great personal experience while using other frameworks. I think that the best thing about PAGI and PAMI is that they offer a proper layer of abstraction, comfortable enough for us, devs, to work with. They are closer to what we would expect of them in every sense, they help us write code and hide lot of asterisk implementation details. They let us focus on the important parts of the application, the business logic. Also, they are unit tested, there's a jenkins server that automatically runs the tests and generates api documentation, and the coverage is almost always around 100%, which is cool, not a bulletproof thing, but a very safe bet. They are object oriented, and they are easy to use and extend. They use log4php, too. For those who don't know about it, it's a logger, and an apache project. It offers similar features to what log4j offers in the java world, giving us the option to route different log classes to different appenders, it has NDC and MDC which are really helpful when tracing our application behavior at runtime, and other cool features. So with PAMI, we can write monitoring applications, write accounting logs, send and receive sms or calls, queue or park calls, write console operators, and a long list of etceteras. With PAGI, we can write our IVR applications.
  • Let's now enter the final part of the talk, with lots of code ahead, the fun part begins. Let's see how to write applications using PAMI and PAGI to interact with asterisk.
  • So a very basic example of using PAMI. First we instantiate the client, passing in some options, like the credentials, and host and port of the asterisk box. We can also pass the log4php configuration file, but this can also be done by ourselves in our application, and pami will use the already configured log4php instance. So once we have the client, we just call open() to connect to asterisk, and close() when we're done to close the connection. What we see in the middle is the main loop. This is actually needed because we don't have threads in php, or any other way to read the data that asterisk is sending in parallel to our code. So what we see here is actually the skeleton of a cliché pami application, where we connect, do our thing (sending actions and/or listening for events), and then close the connection. This might very well be a daemon that runs in the background, or a process that does some specific things like sending sms or originating calls.
  • To execute actions we just need to instantiate the appropiate class and use the method send to send it. The actions in pami are represented by classes, and we can find all the available actions in the api documentation and also in the asterisk wiki. Different actions will have different arguments for the constructor, and also different methods to manipulate it. Once we send the action, we get a response, and we can also look into that response to find any specific information we're interested in. Sending an action is a synchronous process, that means that the method send will return only when a response has arrived, and not before, including any associated events.
  • We can also listen for events, by registering an event listener, calling the registerEventListener() method. This allow us to listen for events coming in from asterisk, and we can register an anonymous function, but we can also register a specific method of an already instantiated object. The execution of these listeners is completely asynchronous, we don't know when they will be executed or if they will be executed at all, because it will depend on when asterisk decides to send us the events. Once we get an event, we can manipulate it, get it's properties, name, etc. We can register as many event listeners as needed.
  • We also have the change to filter the incoming events before they reach the listeners. For this, we use the 2 nd argument of the registerEventListener() method, which is closure, called “predicate”. Predicates will return true or false, and true means that we want to let that event pass. In the example, we're filtering for events of type “dial”, but only those that indicate a new call (subevent begin). The exact details of what events are sent or needed to do our job is mostly in the asterisk wiki.
  • Now let's see a more complete example, coming directly from the real world. This is mostly used in telephone campaigns, where we want to dispatch a lot of calls and then trace what happened with those calls. Were they answered? Were they busy? Etc. In this case, we instantiate the Originate action, passing as argument the channel to dial. It doesn't actually matter what is a channel for this talk, let's just say that a channel can mean a real phone number, a sip device, etc. In this case, is the target we're trying to reach. So we're telling asterisk that for that call, more specifically, when that call IS ANSWERED, we want to execute an agi application, which is contained in the file myagi.php, and we're also telling asterisk that we want to originate the call in an asynchronous fashion. This means that the call will get spooled, send() will return the control immediately and asterisk will tell us what happened with that call later, asynchronously, by sending us a specific event we should listen for. This is the exact opposite of originating a call synchronously, where the send method will only return when that call is over, being answered, busy, or with error. Originating calls asynchronously has the advantage that we can generate a lot of calls without having to process them one by one. We then send the action, and afterwards, execute the now already known main loop of our application.
  • So we originated a bunch of calls, and now we have to listen for the events that correspond to those calls, that contain the needed information to know how those calls ended. For that, we register an event listener that will handle only OriginateResponse events. So we send an Originate action, and wait for the OriginateResponse events, right? We filter the events that get to that event listener by only letting through the right type of events. Then, in our event listener, we get the result of that call, and we can then do different things based on that. So this code is pretty simple, and it's actually the skeleton for telephone campaign applications, generating calls and then tracing what happened with those calls, maybe retrying some numbers after a while, writing into a crm, etc.
  • PAMI is available via composer, and also via a pear channel. Jenkins will also generate api documentation and the phar and tarball files for those who want to use it or install it manually. There's also more information about pami, the ami protocol, and general pami usage in the first two links.
  • So let's enter the final part of the talk. We're going to dive into PAGI now, and learn how to create IVR applications.
  • Just like PAMI, PAGI has a client (an AGI client in this case) that we need to instantiate before using it. And we pass in some options, too, like the log4php configuration (again, we could configure log4php in our own application instead of letting pagi do it). Once we get the client, we can start working with it, for example answering or hanging up a call...
  • We also have access to the asterisk logger. Asterisk has a logger that we can configure to send different logs to different files, or even to the asterisk console, so we can use it and log there. We can also generate calls, for example in our pami example we were originating calls and running an agi application when those calls were answered. This might very well be that agi application that once the call is connected, it will issue another call to some other place, call centers to this a lot to reach potential customers and then forwarding the call to the real seller. Of course we can also know how that call ended, and do specific things with that information.
  • We can also read input from the user, and know what the user pressed, or even if nothing was input at all. We can manipulate the caller id used to generate calls, or read the caller id information of the incoming calls, we can also start music on hold for the current call
  • There's also the chance to manipulate some of the fields of the CDR. The CDR is the call detail record, one CDR entry is saved for every incoming/outgoing call, so we can write some custom stuff in them. We can also play audio for signalling call states, like dial tone, busy tone, etc. But besides the audio, we can also send specific signalling info, like call in progress, or busy, congestion, etc. This is one of the most important features of a softswitch, for example.
  • And of course we can do a lot more! Like manipulating channel variables, adding or removing sip headers, send and receive faxes, start automatic machine detection, record calls, say dates in a specific format, or digits, or whole numbers, we can also spool calls. Asterisk can be configured to poll in a directory for new files, so when a file is created with a specific format, it will read it and originate calls with the contained information, we can write those files using pagi. And we can also execute custom agi commands, for example when we want to use an extension not natively supported by pagi, so we are not limited in that sense. And there's more, I invite you all to play with it!
  • At one side of the pagi client, there's the pagi application. Its use is completely optional, you're not required to use it, although its very useful and handy. It gives you a nice skeleton to define your application lifecycle (it has an init method, a run method, and a shutdown method), and it also has some boiler plate code embedded, like signal and error handling, which are very handy, since in php our ivr applications are all separate process, so we should be able to handle those as well. To use a pagiapplication, you only need to extend it and implement the abstract methods shown, then we instantiate it passing in a pagi client, then call init, run, and shutdown. This is not a minor detail, because it allow us to do unit testing real easy, because the pagi application will use “a pagi client” to do its job, but not necessarily the real one, but a mock. Suprisingly enough, this mocked client comes with pagi, and it's the one we're going to use to write unit tests for our ivr applications in a minute.
  • Before moving on to the unit testing, let's see a small application example, a very small one, suitable to be shown in just one slide. What this application does is to answer a call, and try to read the caller id information of the caller. If it can't find the needed information, it will play a specific sound, but if it can get the phone number of the caller, it will play another sound and play the digits of that number. In both cases, we play a sound after hanging up.
  • And this is how a unit test looks like for our ivr applications, this skeleton can be used in almost all pagi applications, and if you look at this closely, you will notice that we're doing the same things I've shown in the last slides. We instantiate a client, pass it to a pagi application as a constructor argument, and then call init, run, and shutdown. The only thing we do differently is that we're instantiating a MockedClient, instead of the regular pagi client. But everything else is the same, so our application will be run in the same way, either for tests or for production environments.
  • So what can we do with this mocked client? On one hand, we can assert our application behavior. We can assert that some specific client methods will be called in a specific order, like in this case, we're asserting that the call will be answered, and that the caller id information will be read from a specific variable, and also that the correct audio files and digits will be played, and that we will hangup the call when done.
  • We can also specify exact return values for when those client methods are called, like returning true when answering the call, or the custom phone number when asking for the caller id variable, or even the result of the sound played, including any digits the user input while playing it, so that's pretty cool, we can forge the user behavior and test our application against that!
  • So... nodes! So we can say that the client allow us to work with the agi protocol from a high level perspective, and we can say more or less the same about nodes. The nodes in pagi are a facade to the client, so we can write applications using an even higher abstraction layer, that feels more natural than using the client, just like using the client feels more natural than using the dialplan directly (or the agi protocol, for that case). A node is intended to represent either one or more of the nodes that we find in a call flow. It will help us write cleaner code, in a declarative way. They implement a fluent interface, so the code ends up being more verbose, it's DI compatible and can be tested. A node can keep state, and that state can be accessed from inside or outside of the node, for example when we ask for user input and we want to access that input from some other node of our application. We can play sounds, read input, and validate that input. Also, the nodes have real life features, like letting the user interrupt the sounds, or play different messages in different situations, like last input attempt, or on invalid inputs, etc. We can also choose if we consider as input what the user presses during a sound, etc. Also, we can have custom callbacks before or after the node execution, or when a validation fails. And after the node is executed, we can check how it ended, if it was completed, or cancelled, if it had input or exceeded the maximum attempts, etc.
  • Let's go back to the callflow I showed at the very first slides, and let's take a closer look at it. In this case, this call flow describes a small application that allow users to register complains by entering their telephone number. A user calls, a welcome message is played, and then the user is asked to input its telephone number, so the system can validate that number and save that to a database, so this effectively is a user complaining about some malfunctioning in his telephone. So after a user enters its telephone number, the system will validate it, save it, and if it's not valid will ask the user again and again, until a valid telephone number is entered and the application says “goodbye” and then the call ends. Let's see how we could code this application using the pagi nodes.
  • So let's start by playing the welcome message, and then we're going to create a node that will represent the rest of the application. We're creating a node named 'get-number', that will play the sound 'enter-number' as a prompt.
  • Let's do a pause here, to show you some real world features of pagi. Let's configure the node with some stuff not present in the callflow, but very probably part of a real world application. Let's have a maximum number of attempts to input a valid telephone number (instead of looping infinitely), let's say we have 3 attempts to enter a valid number. Also, we're going to play a specific message when those maximum attempts have been exceeded, and also, let's say that a valid phone number has between 10 to 12 digits, and that the user has 3 seconds in total to input all of the digits. If this time is exceeded, we're going to play another message, and also, another thing that wasn't in the flow, and something completely optional: we're going to configure that the user can “cancel” the node, by pressing star, this could be used later to jump to another node (like the previous one). And let's configure the node to have a special digit to finish the input, like the hash, so when a user enters the digit 10 or 11, he/she can press hash, and the system wont wait for more digits.
  • Ok, let's now add a validator. We can add as many as needed, in this case we're going to add just one. So the first argument is the name of the validator, this is useful for debugging the application and later traceability. The second argument is an anonymous function that is the validator itself, it should return true or false, where true means that the input is valid. The last (3 rd ) argument is the name of the sound we want to play every time this validation fails.
  • So if the input is valid, we're going to play a sound
  • And on the other hand, when the input is not valid, we're going to do something that wasn't in the original call flow, but I'd like to show it anyway. We are going to reproduce a sound telling the user to try again, but only if we're not in the last attempt. This is so, because if we are on the last attempt and it failed, then we can't tell the user to try again, because there wont be another chance to enter a valid input.
  • When the node is done, hangup the call
  • And... run the node!
  • This is the complete source code for the application. As you can see, it's actuallly a small piece of code, if we take into account that it is a complete ivr application. Also, there are no ifs, fors, whiles, switch-case, break-continue, etc, which is nice! Nothing weird at sight, we just have a nice piece of declarative code, with our business logic in very specific points, without anything else to worry about.
  • After the node ends, we can check how it ended. Did it have any input? Was it cancelled? Was the maximum number of attempts exceeded? Etc. After gathering the needed information, we can just run another node, or do other stuff. But what happens when we have lots of nodes? Our application starts to have lots of ifs lying around there, uglyfing our code. Let's see another way to do this, using the NodeController.
  • So the NodeController takes its name for what it does. It's a way of controlling and defining the behavior of our application, in a more integral way. How do we go from one node to the other, and what happens in between. So in this example, we're defining four very simple nodes. An initial node, that will play a welcome message, a menu, a node that will be used for when the menu reaches the maximum input attempts, and another node that will call a human operator, for example when pressing '0' in the menu, or whatever.
  • So we have defined our nodes, and let's now define the behavior for those nodes. We're going to tell the NodeController that when the initial node is done, it should run the menu. And when the menu finishes due to exceeding the maximum number of attempts, it should jump to the node we created for that particular case. Also, when that node ends, it should hangup the call.
  • When the menu ends with input '1', it should jump to a node (that we haven't defined, but it serves our purpose) called 'sales', and when it ends with input '2', it should run the node named 'support'. In any other case, we're going to jump to the 'operator' node. I wanted to implement this behavior using the jumpAfterEval() method, just to show that it exists. When using this method, we pass in a closure, that will return a string. This string will be evaluated by the NodeController as the name of the node where it should jump afterwards. This is a nice place to put complex and custom logic to manage the navigation.
  • After we're done, we just jump to the node where we want to start the execution.
  • Applications that use nodes can also be unit tested, in a very similar way as the ones that only use the client. We have a mocked node, and we can obtain a MockedNode from it. And we can define that we want to run that node with some specific input, and that input will be consumed by the node, when it needs to. We can also do some asserts, to be sure there's some specific behavior we want to do, like playing dates, or digits, etc. After that, we just run the pagi application as usual.
  • Just like PAMI, PAGI is available via composer and pear, and also you can download the phar or tarball file if you're interested in doing a manual installation. The jenkins server will also autogenerate the api documentation and the coverage reports, and you can also find more about the ami protocol and pami in the first two links.
  • So before going to the questions, I'd like to thank you all for coming in today, and also to the organizers that did a wonderful job, creating a conference that trascends the fact that it's called phpconf, having a very nice set of interesting topics. Also, on the personal side, to Mariano Iglesias that gave me the opportunity to be here with you today, and of course my awesome wife, and “the kids”, Qwerty and Dvorak.
  • And that's pretty much it... Questions?
  • 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 ( ● Variety of projects and scenarios (sysadmin, parking systems, virtual hosting, telephony, etc). ● Last years: VoIP ● ●
    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:
    6. 6.
    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:
    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:
    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 ( ● 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 ● ●
    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) ●
    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 ( ● 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( '' => '', 'host' => '', '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 – – ● How to install – – – – –
    40. 40. IVRs!
    41. 41. PAGI Client: Basic use // Get a client $options = array( '' => '' ); 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 – – ● How to install – – – – –
    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? ● ● ●