Redial: Interactive Telephony : Week 4
Programming Asterisk: PHP 101 and AGI Scripting
Asterisk Conference Rooms
Asterisk is setup to work with the MeetMe conference room application. This application allows us to have multiple callers bridged into one virtual "room" where they can talk.
In order to record a MeetMe conference, we need to set the MEETME_RECORDINGFILE variable with the name of the file to record to (as you can see in the example below).
To send a caller into the MeetMe application, you can simply issue the MeetMe() command in your dialplan. There are a variety of options that can be used as well. The first would be the room number followed by various options. To record the conference use the "r" option. For more please check the online documentation or the documentation in the Asterisk book.
exten => s,1,SetVar(MEETME_RECORDINGFILE=/home/sve204/conferencerooms/conference_recording-${CONFNO}-${UNIQUEID});
exten => s,n,NoOp(${MEETME_RECORDINGFILE}); Echo out the name of the recording file
exten => s,n,NoOp(${MEETME_RECORDINGFORMAT}); Echo out the format of the recording file should default to WAV
exten => s,n,MeetMe(101,ri); Room 101, record and announce
exten => s,n,Goto(redial_sve204,s,1);
To configure your own MeetMe conference room simply create a file (similar to all of our other configuration files) in the asterisk_conf directory with your netid_meetme.conf. Mine is called sve204_meetme.conf.
In the file specify 1 room per line such as follows:
; Rooms should be unique, use your extension + more digits if you want more rooms
conf => 10,1,2 ; room 10, pin 1, admin pin 2 ; shawn's main room
conf => 101,1,2 ; room 101, shawn's other room
Use room numbers that match your extension plus additional digits if you want more than one. This way we are each guarunteed to have unique room numbers. I am using rooms such as 10, 101, 102, 100002 and so on. (You will want to use rooms like 19, 190, 1901 if your extension is 19.)
More Information:
Asterisk cmd MeetMe
PHP 101
PHP is a very useful language for doing many types of development. Primarily it is used for web development (hence the name, Hypertext Preprocessor) but it is certainly not limited. For our purposes we will be using PHP as both a web development tool and a command line scripting tool.
Creating a PHP file for use on the command line
PHP can be developed in any text editor and doesn't require any compilation. To start writing a PHP script for use on the command line we need to start our file with the "shebang" and the path to the PHP executable. When using PHP as a web development tool this is not nessecary.
#!/usr/local/bin/php
By default, our version of php outputs HTTP headers so we need to turn that off by passing the "-q" flag to the php executable.
#!/usr/local/bin/php -q
Following the "shebang" line, we need to use <?PHP to tell PHP that we want to it to start interpreting code (PHP is meant to be mixed with HTML and therefore it will not interpret any code that is outside of <?PHP and ?>)
#!/usr/local/bin/php -q
<?PHP
// Put Your Code Here
?>
As is evident in the above example, "//" is used to make single line comments and "/* ... */" is used to make multiple line comments.
Let's make a PHP file that outputs "Hello World!";
#!/usr/local/bin/php -q
<?PHP
echo("Hello World!\n");
?>
As you can see, PHP is very similar to many other languages. There are a bunch of built-in functions (such as echo) and arguments are passed into functions through paranthesis. Strings are put into single or double quotes and the newline char is the \n. Lines of code are ended with semi-colons.
To execute this application, save it as something.php and upload it to a server (social is probably a good place).
You can execute it by SSHing onto social and issuing the following command:
php php101_1.php
You can skip the beginning "php" if you make the file executable:
chmod 755 php101_1.php
./php101_1.php
More Information:
PHP: Basic Syntax - Manual
Variables
Variables in PHP are not strictly typed, meaning that you do not have to differentiate between strings, integers and so on. They start with a "$".
<?PHP
$myString = "hello world\n";
echo($myString);
$myInteger = 1003;
echo($myInteger . "\n");
$somethingelse = false;
echo($somethingelse . "\n");
?>
More Information:
PHP: Types - Manual
PHP: Variables - Manual
PHP: Constants - Manual
Mathematical Operators
<?PHP
$aValue = 0;
$aValue++;
echo("aValue now = " . $aValue . "\n");
$aValue = $aValue + $aValue;
echo("aValue now = " . $aValue . "\n");
// % + - * / ++ -- and so on, same as in Processing/Java
?>
More Information:
PHP: Expressions - Manual
PHP: Operators - Manual
Control Structures, Logical Operators and Loops
<?PHP
// If Statement
$aValue = 0;
if ($aValue == 0)
{
echo("aValue is 0");
}
else if ($aValue == 1)
{
echo("aValue is 1");
}
else if ($aValue >= 2)
{
echo("aValue is greater than or equal to 2");
}
else
{
echo("aValue something else");
}
echo("\n");
// Other Logical Operators ==, >, <, >=, <=, ||, &&
// For loop
for ($i = 0; $i < 10; $i++)
{
echo("\$i = $i\n");
}
// Also While
?>
More Information:
PHP: Control Structures - Manual
Arrays and Loops
<?PHP
// Pretty normal array
$anArray = array();
$anArray[0] = "Something";
$anArray[1] = "Something Else";
for ($i = 0; $i < sizeof($anArray); $i++)
{
echo($anArray[$i] . "\n");
}
// Key Value Map
$anotherA = array("somekey" => "somevalue", "someotherkey" => "someothervalue");
$keys = array_keys($anotherA);
$values = array_values($anotherA);
for ($i = 0; $i < sizeof($keys); $i++)
{
echo($keys[$i] . " = " . $values[$i] . "\n");
}
?>
More Information:
PHP: Arrays - Manual
Functions
<?PHP
function myFunction($somearg)
{
// You would do something here
return "You passed in $somearg";
}
$passing_in = "Hello World";
$return_value = myFunction($passing_in);
echo($return_value);
echo("\n");
?>
More Information:
PHP: Functions - Manual
Classes and Objects
<?PHP
class MyClass
{
var $myClassVar;
function set_var($new_var)
{
$this->myClassVar = $new_var;
}
function get_var()
{
return $this->myClassVar;
}
}
$aClass = new MyClass;
$aClass->set_var("something");
echo("Var: " . $aClass->get_var() . "\n");
?>
Classes and Objects in PHP are very similar to Java/Processing. Syntactically though they don't use the "." operator, instead using "->". Also, in the class definition you need to use the "this" keyword.
More Information:
PHP: Classes and Objects - Manual
Some Interesting Functions
isset()
<?PHP
$somevar = NULL;
if (isset($somevar))
{
echo("somevar is set");
}
else
{
echo("somevar is NOT set");
}
?>
Command line arguments
$argc is the number of arguments passed into the script.
$argv is an array of those arguments.
$argv[0] will always be the name of the script that is being executed.
$argv[1] would be the first argument.
Example:
#!/usr/local/bin/php -q
<?
echo($argc . " command line arguments\n");
for ($i = 0; $i < $argc; $i++)
{
echo("Argument " . $i . ":" . $argv[$i] . "\n");
}
?>
[sve204@social ~]$ ./commandline.php test two blah 2 1
6 command line arguments
Argument 0:./commandline.php
Argument 1:test
Argument 2:two
Argument 3:blah
Argument 4:2
Argument 5:1
File writing
$cfile = fopen($startcallfile,"w");
fwrite($cfile,"Channel: SIP/itp_jnctn/" . $numbertocall . "\n");
fwrite($cfile,"MaxRetries: 1\nRetryTime: 60\nWaitTime: 30\n");
fwrite($cfile,"Context: " . $context . "\n");
fwrite($cfile,"Extension: " . $extension . "\n");
fclose($cfile);
More Information:
Tons More Here: PHP: Function Reference - Manual
Generating Call Files
I created a quick PHP application which you are all free to use in the AGI directory: /var/lib/asterisk/agi-bin/ called gencallfile.php.
This is a basic command line PHP script that takes in a couple of arguments:
The first argument is the number to call, the second the context to put the call in, the third is the extension in that context, the fourth is any variables you would like set and the fifth is the time you would like the call to occur in this this format: HH-MM-SS-MM-DD-YYYY. The second remaining arguments are optional and will default to our class context and the "s" extension without any variables and immediately making the call. The first argument is required.
To execute this script from the command line, you do the following:
/var/lib/asterisk/agi-bin/gencallfile.php 17188096659
or
/var/lib/asterisk/agi-bin/gencallfile.php 17188096659 somecontext s somevar=somevalue 02-10-00-09-27-2007
To use this from Asterisk itself we can use our System command:
[sve204_php]
exten => s,1,TrySystem(/var/lib/asterisk/agi-bin/gencallfile.php 17188096659);
exten => s,1,Goto(redial_sve204,s,1);
Here is the code (for your reference):
#!/usr/local/bin/php -q
<?
error_reporting(E_ALL);
// Get Arguments
// First argument Number to Call
$numbertocall = "";
if ($argc > 1)
{
$numbertocall = $argv[1];
}
else
{
echo "No Number Entered\n";
exit(1);
}
// Context
$context = "2127960729";
if ($argc > 2)
{
$context = $argv[2];
}
// Extension
$extension = "s";
if ($argc > 3)
{
$extension = $argv[3];
}
$vars = "";
if ($argc > 4)
{
$vars = $argv[4];
}
$touchtime = "";
if ($argc > 5)
{
$touchtime = $argv[5];
}
$time = time();
$temp_dir = "/tmp/";
$callfile = "call_" . $time . ".call";
$startcallfile = $temp_dir . $callfile;
$end_dir = "/var/spool/asterisk/outgoing/";
$endcallfile = $end_dir . $callfile;
$cfile = fopen($startcallfile,"w");
fwrite($cfile,"Channel: SIP/itp_jnctn/" . $numbertocall . "\n");
fwrite($cfile,"MaxRetries: 1\n");
fwrite($cfile,"RetryTime: 60\n");
fwrite($cfile,"WaitTime: 30\n");
fwrite($cfile,"Context: " . $context . "\n");
fwrite($cfile,"Extension: " . $extension . "\n");
if ($vars != "")
{
fwrite($cfile,"Set: " . $vars . "\n");
}
fclose($cfile);
chmod($startcallfile,0777);
if ($touchtime != "")
{
list($hour,$minute,$second,$month,$day,$year) = split('-',$touchtime);
//system("touch -t " . $year . $month . $day . $hour . $minute . "." . $second . " " . $startcallfile);
$ctime = mktime($hour,$minute,$second,$month,$day,$year);
//echo $ctime;
touch($startcallfile,$ctime,$ctime);
}
rename($startcallfile,$endcallfile);
?>
AGI Scripting
AGI stands for Asterisk Gateway Interface. It is very similar to CGI (common gateway interface) which is one of the first forms of web development.
Calling AGI Scripts
To call an AGI script from the Asterisk dialplan you use the AGI command
exten => s,1,Answer();
exten => s,n,AGI(/home/sve204/asterisk_agi/sve_agi_test.php);
By default Asterisk looks for the AGI script in the agi-bin (/var/lib/asterisk/agi-bin). You can place them anywhere but it is probably a good idea to put them in your asterisk_agi directory.
Debugging
In the Asterisk administration console you can type: agi debug to enable AGI debugging. Type agi no debug when you get sick of it.
Basic AGI Scripting in PHP
AGI Scripting the Hard Way
#!/usr/local/bin/php -q
<?PHP
set_time_limit(60); // Don't let script run forever, 60 seconds should do
ob_implicit_flush(false); // Don't buffer output
error_reporting(0); // Turn off error reporting, can mess asterisk up..
// Setup our standard in, out and error constants
if (!defined('STDIN'))
{
define('STDIN', fopen('php://stdin', 'r'));
}
if (!defined('STDOUT'))
{
define('STDOUT', fopen('php://stdout', 'w'));
}
if (!defined('STDERR'))
{
define('STDERR', fopen('php://stderr', 'w'));
}
// Setup our array of AGI variables
$agi = array();
while (!feof(STDIN))
{
$temp = trim(fgets(STDIN,4096));
if (($temp == "") || ($temp == "\n"))
{
break;
}
$s = split(":",$temp);
$name = str_replace("agi_","",$s[0]);
$agi[$name] = trim($s[1]);
}
// Output our array of AGI variables to the Asterisk console
foreach($agi as $key=>$value)
{
fwrite(STDERR,"-- $key = $value\n");
fflush(STDERR);
}
/* Start out AGI Scripting Here */
// write to our standard output the STREAM FILE command which plays an audio file
fwrite(STDOUT, "STREAM FILE vm-extension \"\"\n"); // Two arguments, hence empty quotes
fflush(STDOUT);
// Get the result from Asterisk by reading from standard in
$result = fgets(STDIN,4096);
$result = trim($result);
// Check the result, will write to Asterisk console FAIL or PASS
checkresult($result);
// The above 5 lines are pretty much every thing you need to do to interact with Asterisk...
// Appendix C of Asterisk: TOFT book lists all of the commands you can use
// Get input from the caller
fwrite(STDOUT, "WAIT FOR DIGIT 10000\n"); // Command and timeout of 10 seconds
fflush(STDOUT);
$result = fgets(STDIN,4096);
$result = trim($result);
$result = checkresult($result);
if ($result > 0)
{
$ascii = chr($result);
fwrite(STDOUT, "SAY NUMBER " . $ascii . " \"\"\n");
fflush(STDOUT);
$result = fgets(STDIN,4096);
$result = trim($result);
checkresult($result);
}
/* End our AGI Scripting Here */
// Function to check the result of an AGI command
function checkresult($res)
{
trim($res);
if (preg_match('/^200/',$res))
{
if (! preg_match('/result=(-?\d+)/',$res,$matches))
{
fwrite(STDERR,"FAIL ($res)\n");
fflush(STDERR);
return 0;
}
else
{
fwrite(STDERR,"PASS (".$matches[1].")\n");
fflush(STDERR);
return $matches[1];
}
}
else
{
fwrite(STDERR,"FAIL (unexpected result '$res')\n");
fflush(STDERR);
return -1;
}
}
?>
AGI Scripting the Easy Way
#!/usr/local/bin/php -q
<?PHP
require('/var/lib/asterisk/agi-bin/phpagi.php');
$agi = new AGI();
/* Start out AGI Scripting */
$agi->stream_file("vm-extension");
$return = $agi->wait_for_digit(10000);
if ($return['result'] > 0)
{
$ascii = chr($return['result']);
$agi->say_number($ascii);
}
/* End AGI Scripting */
?>
Upload, Test and Run
Save the above script to a file, let's call it "easy.php" for now. Using Fetch or another SFTP application upload the script to the "asterisk_agi" in your home directory. Log into the server using SSH and navigate to the directory containing the PHP (cd asterisk_agi) and execute the command required to make the file executable (chmod 755 easy.php).
To test that the file runs without error, type the following:
./easy.php
This should start the execution of the file, if it doesn't there is an error that you need to fix. The application should wait indefinitely for input (which it is expecting from Asterisk through STDIN). You can simulate this by hitting return a couple of times. You should see some semblance of the commands that are sent to Asterisk via STDOUT.
PHPAGI PHP Library Docs
PHPAGI Site
More Fun
#!/usr/local/bin/php -q
<?PHP
require('/var/lib/asterisk/agi-bin/phpagi.php');
$agi = new AGI();
// Predefined Variables
/*
$agi->request["agi_request"]
$agi->request["agi_channel"]
$agi->request["agi_language"]
$agi->request["agi_uniqueid"]
$agi->request["agi_allerid"]
$agi->request["agi_dnid"]
$agi->request["agi_rdnis"]
$agi->request["agi_context"]
$agi->request["agi_extension"]
$agi->request["agi_priority"]
$agi->request["agi_enhanced"]
$agi->request["agi_accountcode"]
$agi->request["agi_network"]
$agi->request["agi_network_script"]
*/
/*
// Exec any dialplan application/function
$return = $agi->exec("Application","Options");
if ($return['result'] == 1)
{
$agi->say_number(1);
}
*/
# for a complete list of US cities, go to
# http://www.nws.noaa.gov/data/current_obs/
$weatherURL = array();
$weatherURL[0]="http://www.nws.noaa.gov/data/current_obs/KJFK.xml";
$weatherURL[1]="http://www.nws.noaa.gov/data/current_obs/KALB.xml";
$weatherURL[2]="http://www.nws.noaa.gov/data/current_obs/KART.xml";
$weatherURL[3]="http://www.nws.noaa.gov/data/current_obs/KBGM.xml";
$weatherURL[4]="http://www.nws.noaa.gov/data/current_obs/KBUF.xml";
$weatherURL[5]="http://www.nws.noaa.gov/data/current_obs/KDKK.xml";
$weatherURL[6]="http://www.nws.noaa.gov/data/current_obs/KDSV.xml";
$weatherURL[7]="http://www.nws.noaa.gov/data/current_obs/KELM.xml";
$weatherURL[8]="http://www.nws.noaa.gov/data/current_obs/KHPN.xml";
$weatherURL[9]="http://www.nws.noaa.gov/data/current_obs/KFRG.xml";
$continue = true;
while($continue)
{
$agi->stream_file("vm-extension");
$return = $agi->wait_for_digit(10000);
if ($return['result'] > 0)
{
$ascii = chr($return['result']);
}
else
{
$continue = false;
}
$weatherPage=file_get_contents($weatherURL[$ascii]);
$currentTemp = -100;
if (preg_match("/<temp_f>([0-9]+)<\/temp_f>/i",$weatherPage,$matches))
{
$currentTemp=$matches[1];
}
if ($currentTemp > -100)
{
$agi->say_number($currentTemp);
//$agi->say_digits($ascii);
}
else
{
$continue = false;
}
}
?>