Hello there!
I figure that if I’m going to start using this blog to post the wanderings and
wonderings of a mid-level engineer at a dot-com company (I work at
dealnews to be specific, and I guess I should include the
standard disclosure that my employer does not endose or support anything that I
say/do here), perhaps I should give some substance to my first post. So, I
figure I would write a post on something I have plenty of experience with:
PHP.
But what to write about? Surely, there must be ten million PHP tutorials on the
‘net and I don’t need to add to the noise already out there as to what
are/aren’t the best practices using PHP, so I thought about using PHP in some
lesser known areas. And here is one lesser known, but very cool area: you can
use PHP to route phone calls!
At a previous employer, I worked with Asterisk as a
software development consultant. My primary role was to build web interfaces to
Asterisk (and other telecom hardware) backends, though while working as a
consultant I learned quite a bit about extending Asterisk to do crazy cool
things.
“It’s Just Software!”
Asterisk is an open-source software PBX that
was created by Mark Spencer (an Auburn grad and now
CEO at digium). It is quickly becoming a
challenger in the PBX market (fact: we use it at dealnews), and an entire
industry has sprung up around Asterisk and open-source IP telephony.
For the purposes of this tutorial, I’m going to assume that you already have
Asterisk installed and configured to your liking, and are now wishing to extend
it beyond what it is capable of doing with the builtin dialplan applications. If
this is not a good assumption in your case, may I highly suggest the Asterisk
Tutorial
at voip-info.org, or even better, the O’Reilly Asterisk
book, which is a little dated but
still quite relevant to most beginner-level stuff.
Meet AGI, CGI’s hard-working cousin:
AGI, or the Asterisk Gateway Interface, is the key to extending Asterisk beyond
what it is capable of doing on its own. AGI gives Asterisk the ability to run
and interact with scripts and programs outside of Asterisk. AGIs can be written
in any language that can be executed on a Linux system (and there have been AGIs
written in PHP, Python, Perl, C, Bash and just about every other language out
there). Since PHP is my language of choice, that is what I’m going to
concentrate on in this tutorial.
Asterisk AGIs are actually incredibly simple creatures. When run from within the
Asterisk dialplan, they simply send commands to Asterisk using standard output
and read the results on standard input. Its what happens between those that is
really, really cool.
Enough Talk! Code or GTFO!
So, let’s get started!
First, you need to set up your script environment. I recommend doing this in an
include-able file so that you can reuse it in future AGIs. There are a few
commands you need to know about:
<?php
// This turns on implicit flushing, meaning PHP will flush the buffer after
// every output call. This is necessary to make sure that AGI scripts get their
// instructions to Asterisk as soon as possible, rather than buffering until
// script termination.
ob_implicit_flush(true);
// This sets the maximum execution time for the AGI script. I usually like to
// keep this set low (6 seconds), because the script should complete pretty
// quickly and the last thing we want it to do is hang a call because the script
// is churning.
set_time_limit(6);
//This sets a custom error handler function. We'll get back to this later.
set_error_handler("error");
//This creates a standard in that can be used by our script.
$in = fopen("php://stdin","r");
//This creates an access to standard error, for debugging.
$stdlog = fopen("php://stderr", "w");
?>
Okay, that’s not too bad!
Now, we’re going to do a little more advanced stuff. Every time an AGI script executes, Asterisk passes a number (about 20) values to the script. These AGI headers take the form of “key: value”, one per line separated with a line feed (\n), concluding with a blank line. Before we can do this, we need to write a few functions to read from AGI input, write to Asterisk, Execute commands, and write to the Asterisk CLI. These are the functions I use:
<?php
function read() {
global $in, $debug, $stdlog;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug){
fputs($stdlog, "read: $input\n");
}
return $input;
}
?>
So what are we doing here? Well, the first line, we strip out the line feed in each chunk we get from stdin. Then, we check to see if $debug is set and, if so, echo what we read to standard error. Finally, we return the line we just read. Pretty simple, right? Well, this little funtion will save you lots of time. Next, we need a way to write data:
<?php
function write($line) {
global $debug, $stdlog;
if ($debug) {
fputs($stdlog, "write: $line\n");
}
echo $line."\n";
}
?>
This function is even more simple: it just writes out to standard error if $debug is on, and outputs whatever was sent to it with an additional new line. This next function, however, is more complex.
<?php
function execute($command) {
global $in, $out, $debug, $stdlog;
write($command);
$data = fgets($in, 4096);
if (preg_match("/^([0-9]{1,3}) (.*)/", $data, $matches)) {
if (preg_match('/^result=([0-9a-zA-Z]*)( ?\((.*)\))?$/', $matches[2], $match)) {
$arr['code'] = $matches[1];
$arr['result'] = $match[1];
if (isset($match[3]) && $match[3]) {
$arr['data'] = $match[3];
}
if($debug) {
fputs($stdlog, "CODE: " . $arr['code'] . " \n");
fputs($stdlog, "result: " . $arr['result'] . " \n");
fputs($stdlog, "result: " . $arr['data'] . " \n");
fflush($stdlog);
}
return $arr;
} else return 0;
} else return -1;
}
?>
Woah, complex! Well, not really. execute() is the swiss army knife of AGI programming: it allows you to do interactive stuff inside this AGI script.
First, as you can see, it calls the write() function we just wrote, writing an AGI command to Asterisk. Then it looks for a response on standard in. A response from Asterisk takes the form of “result=<result> <data>”. So, we use preg_match to get this out for us and put it into something usable. We do the debug output again, then return the array or 0 or -1 in the event of failures.
Just two more functions to go:
<?php
function verbose($str,$level=0) {
$str=addslashes($str);
execute("VERBOSE \"$str\" $level");
}
function error($errno,$errst,$errfile,$errline) {
verbose("AGI ERROR: $errfile, on line $errline: $errst");
}
?>
As you can see, these two functions are very simple. One gives verbose output to the Asterisk CLI, and the other is the error function we declared using set_error_handler above.
Back to reading in variables. Now that we have the ability to read in, let’s read in the default variables that are passed to the script by Asterisk. We do this using the following code chunk:
<?php
while ($env=read()) {
$s = split(": ",$env);
$key = str_replace("agi_","",$s[0]);
$value = trim($s[1]);
$_AGI[$key] = $value;
if($debug) {
verbose("Registered AGI variable $key as $value.");
}
if (($env == "") || ($env == "\n")) {
break;
}
}
?>
This creates an $_AGI associative array (in the spirit of $_POST, $_GET, etc) for you to use containing all the items Asterisk passed in. For each read() line, in the first line we split it to get the key and value (this could probably have been done better with a regular expression, but I got a copy of some AGI code from a friend and modified it many moons ago before I began using regular expressions). Then, we strip out the “agi_” that Asterisk adds to the key because it is superfluous, and trim out the spaces and other garbage from the value, adding them to an array.
Putting It All Together:
Congratuations! You now have all the tools necessary to write an AGI! I suggest (as above) putting those in an include so you can reuse as necessary.
So what next? Now, you write an AGI script!
Let’s start with a simple example:
#!/usr/bin/php
<?php
include "agi.php";
execute("SAY DATETIME #");
?>
That simple! Of course, all this AGI does is read the date and time to the
caller, then exit, but it just shows that AGIs can do really powerful things,
really simply.
“Calling” you AGI:
So now you have this AGI written and you want to use it, but you don’t know how.
Well this is pretty easy too!
AGIs should be placed in whatever directory you define for “astagidir” in your
asterisk.conf file. Unless you changed it, this will be
/var/lib/asterisk/agi-bin. Next, be sure that the file is executed by setting
the executable bit “chmod +x ". You may also have to fiddle with the
permissions: the asterisk user or group need the ability to read and execute the
script.
Then, you just call it from your dialplan, like so:
exten => 1000,1,AGI(<filename>)`
Now, after you “extensions reload” of course, you should be able to dial 1000, and watch your AGI spring into action!
A more complex example:
This is an AGI I wrote at dealnews when someone in the office requested the ability to custom set names to caller IDs and have it work on all phones. Keep in mind that this is only half of the solution (the other half is a web interface).
#!/usr/bin/php
<?php
include "agi.php" ;
$db=mysql_connect('redacted', 'redacted', 'redacted');
if(!$db) {
verbose("Could not connect to DB!");
}
if(!mysql_select_db('redacted', $db)) {
verbose("Could not use DB!");
}
$res=mysql_query(sprintf("select substitution_name from cid_substitution where from_number='%s'",$_AGI['callerid']));
$result=mysql_fetch_row($res);
if(sizeof($result)) {
execute(sprintf("SET CALLERID \"%s <%s>\"",$result[0],$_AGI['callerid']));
}
mysql_close($db);
?>
This demonstrates one of the main advantages to using AGIs, and PHP in
particular: the ability to easily interact with databases. In this program, I’m
using the caller ID supplied by the carrier to fetch a corresponding name from a
database and send it back along with the call.
Routing calls is accomplished by calling the EXEC
function with DIAL, giving you the
ability, with a little work, to route calls based on the database. Pretty neat
for a language thought of only as web coding. Indeed, there is a large list of
commands that AGIs can use, and variables passed into them, available
here.
Help! It doesn’t work!
Relax! Problems happen from time to time.
One of the most common faults is forgetting to set the +x bit on the file to make it executable. Permissions problems are also relatively common.
For More Information:
voip-info.org - a.k.a. “the wiki,” is the
major information repository for Asterisk knowledge specifically, and IP
telephony in general.
Read More