March 10, 2008

Debugging Asterisk AGI with PHPAGI and Festival

Author: Colin Beckingham

Programming the Asterisk open source PBX via the Asterisk Gateway Interface (AGI) is a fun but exasperating exercise for the telephony programmer. It is fun since it can make a telephone dance, but frustrating because errors and debugging information can be difficult to catch since status information arrives on multiple channels: audible, Asterisk console, and STDERR. You can make the process of debugging a bit easier with the assistance of PHPAGI and Festival.

PHPAGI is an open source PHP class library that gives convenient access to the AGI. Festival is a remarkable free voice synthesis application developed by the University of Edinburgh, which you can use to "speak" debug information over the phone line. An alternative open source synthesizer is Java-based FreeTTS; I use Festival because it ties in nicely with PHPAGI with the call to text2wav().

Let's take a specific example of a useful AGI application. Sometimes your customer may be reluctant to try Linux as a base for the telephone system, so as part of the package you offer a callback test: the customer can call the PBX, which gathers some information and calls the customer back to reassure him that it is working correctly. Here's how this application might be implemented with some audible debugging feedback. We'll build an example script for a callback test.

For testing I am using a free Zoiper softphone. Zoiper is capable of SIP (Session Initiation Protocol) or IAX2 (Inter-Asterisk Exchange) communication protocols; I will assume our accounts are set up as IAX2.

Some possible development errors to watch for:

  • Group 1 - due to reference or syntax errors, processing stops before the voice engine is useful
    • Incorrect path to the PHP binary in the first line
    • PHPAGI class library not found
    • PHP Syntax error
  • Group 2 - voice feedback is possible and useful
    • Missing variables (e.g. scope, etc.)
    • Poor logic construction
    • Connection to MySQL is not available
    • Badly formed SQL statements
    • SQL returns an empty set unexpectedly
    • DTMF tones not understood
  • Group 3 - voice feedback is possible but errors are not immediately detected
    • Asterisk-specific errors -- e.g. call to extension in wrong context

Group 1 errors

Here is a basic template for this project:

#!/usr/local/bin/php -q
<?php
// callback routine
set_time_limit(30);
require('phpagi.php');
// die("Syntax ok\n"); // line A: uncomment for syntax test
$agi = new AGI();
$agi->answer();
//
// begin script body
//
// $agi->text2wav("Hello world"); // line B: uncomment for readback through the phone
//
// end script body and tidy up
//
$agi->hangup();
?>

Any group 1 error will never make it to the phone but will dump visual information. You can save this file as, for example, /var/lib/asterisk/agi-bin/agitest.php and run it from the system command line independently of Asterisk and the AGI.

You can save this template with line A (marked above in red) uncommented as a .php file in /var/lib/asterisk/agi-bin/, and change file permissions with chmod to make it executable. Running it produces only the output "Syntax ok." If you fail to uncomment line A then the script will hang waiting for further instructions.

The commenting and uncommenting of lines A and B should only be necessary at the beginning of the process to identify and eliminate group 1 errors. A good editor that helps highlight syntax errors is also useful.

If you find that you need to edit the file frequently or the process is not rigorous enough, you can use the $_SERVER['argc'] and $_SERVER['argv'] predefined variables to control execution. See the PHP manual, Chapter 10: using PHP from the command line, or the online manual, example #8, for some good examples of this technique.

Group 2 errors

With confidence in our syntax checking system, we can now look at group 2 problems, which we will hope to resolve entirely on the phone. With line A commented out and line B uncommented, output from the script will become audible.

Asterisk performs actions based largely on a dialplan, which is a collection of real or imaginary telephone extensions grouped into contexts so that each is uniquely defined. You can create an Asterisk extension in /etc/asterisk/extensions.conf using syntax such as:

extension => 22,1,AGI(your_script.php)
extension => 22,2,Hangup()

The above code makes the link between extension 22 and the PHP script that the AGI will run. When you dial extension 22, it should now synthesize the words "Hello World" through Festival and hang up.

As you add complexity to your script, group 1 errors might creep back in. Syntax errors usually are self-evident on the phone -- dialing the test extension results in a very abrupt, immediate hangup at the PBX end.

If you want to expand the code to do something more interesting than just say "Hello world," you can replace the "begin script body" ... "end script body" section with:

$agi->text2wav("Script begins");
$numlen = 3;
$prompt = 'Enter the eye aye ecks number you wish me to cawl.';
$agi->text2wav($prompt);
$result = $agi->get_data("beep",10000,$numlen);
$num = $result['result'];
$agi->text2wav('You have requested an eye aye ecks cawl to ');
$agi->say_digits($num);
// more statements to come here
$agi->text2wav("Script ends");

This script asks for the number to call, plays a beep, waits 10 seconds for three Dual-Tone Multi-Frequency (DTMF) tones (or uses as many as it received when the time expires), and repeats the number back to the user. When a hardware option is not set correctly, a phone may not send DTMF tones in a format intelligible to Asterisk, so it is important to get that confirmation. Yes, there is some bizarre spelling in the prompt, but Festival sometimes needs help in voicing syllables correctly.

Including the "script ends" instruction lets you know that the script was processed right to the end. If you don't hear that line then the script ended abruptly for some reason that needs investigation. Since PHP is interpreted, it will progress through the logic line by line and can fail before it reaches the end.

We have reached the stage where it is profitable to have the Asterisk console open and giving us visual feedback. The command asterisk -rvvv reconnects to an existing running Asterisk process and gives verbosity at level 3. In addition, since we are testing AGI we need to run:

*CLI> agi debug

from the Asterisk command line interface, which will give extra information related to AGI instructions. However, the goal here is not to have to look too much at the monitor for visual information, which can be complex and cryptic, but to have enough information coming back via Festival to make sure all is well.

Now we can verify from the caller that Asterisk should go ahead and place the call:

$agi->text2wav('Press 1 to confirm or any other number to cancel');
$result = $agi->get_data("beep",10000,1);
$conf = $result['result'];
$agi->text2wav("User selected $conf");
if ($conf == 1) {
$mess = phon_out("IAX2/",$num);
$agi->text2wav($mess);
} else {
$agi->text2wav('User cancelled');
}

and our function phon_out() is

function phon_out($atyp,$phn) {
$mess = "Channel: $atyp$phn
CallerID: Callback service
MaxRetries: 1
RetryTime: 60
WaitTime: 30
Context: default
Extension: 51
Priority: 1
";
$astfil = "test$phn.call";
$callfil = "/var/spool/asterisk/outcalls/$astfil";
$destfil = "/var/spool/asterisk/outgoing/$astfil";
if (!file_exists($destfil)) {
if (file_put_contents($callfil,$mess)) {
touch($callfil,time());
if (rename($callfil,$destfil)) {
$msg .= "Success. Cawl queued";
} else {
$msg .= "Error. Cawl file rename failed";
}
} else {
$msg .= "Error. Cannot write file";
}
} else {
$msg .= "Error. Cawl file already exists";
}
return $msg;
}

This function relies on a pre-existing holding area such as "outcalls" directory, and extension 51, which contains something like:

extension => 51,1,Playback(hello-world)

which when called simply plays back a pre-recorded "Hello world." This routine now takes a call, notes a number to call back, asks for a confirmation, places the call, and makes the announcement. Since we are using the Zoiper softphone, which supports multiple lines, we can make the call and enter our own extension number from line 1, allowing the callback to come in on line 2.

Group 2 errors emerge when we do not hear "Script ends," but if our audible feedback is detailed, then we have a rough idea where it stopped. Additional information comes from the Asterisk console.

Group 3 errors

Group 3 errors are a different class. The script will appear to run correctly, but in fact something expected does not happen, such as an incoming call that does not arrive. Calls to numbers that do not exist, or in the wrong Asterisk dialplan context, will still be recorded as failures in the console. The script could be extended to prompt the caller to indicate that the call did come in, and, if not, take further action.

Points to remember

The text2wav() function in Festival is a valuable tool for simple feedback. It handles short, definable messages such as "User pressed $my_variable" or "No link to my ess cue ell data base" well, but it is of no use in more complex situations when there is a problem with syntax in an SQL statement. In that situation it is better to have Festival say "there is a problem in an ess cue ell statement" and have the script email the SQL string together with the contents of mysql_error() in a mail() statement.

PHPAGI and Festival can work together to provide audible clues that make debugging AGI less painful than it might be. Once fully debugged, much of the reporting can be removed or diverted to the mail system for the final production version.

Categories:

  • Telecomm
  • Programming
Click Here!