Redial: Interactive Telephony : Week 4
Programming Asterisk: PHP 101 and AGI Scripting
Asterisk Conference Rooms
Asterisk is now 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/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/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/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/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/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/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
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 useragi directory: /var/lib/asterisk/agi-bin/useragi/ 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 and the third is the extension in that context. The second and third arguments are optional and will default to our class context and the "s" extension. The first argument is required.
To execute this script from the command line, you do the following:
/var/lib/asterisk/agi-bin/useragi/gencallfile.php 17188096659
or
/var/lib/asterisk/agi-bin/useragi/gencallfile.php 17188096659 somecontext 10
To use this from Asterisk itself we can use our System command:
[sve204_php]
exten => s,1,TrySystem(/var/lib/asterisk/agi-bin/useragi/gencallfile.php 17188096659);
exten => s,1,Goto(redial_sve204,s,1);
Here is the code (for your reference):
#!/usr/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
// Get Arguments
// First argument Number to Call
$numbertocall = "";
if ($argc > 1)
{
$numbertocall = $argv[1];
}
else
{
echo "No Number Entered\n";
exit(1);
}
// Context
$context = "2127960961";
if ($argc > 2)
{
$context = $argv[2];
}
// Extension
$extension = "s";
if ($argc > 3)
{
$extension = $argv[3];
}
$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\nRetryTime: 60\nWaitTime: 30\n");
fwrite($cfile,"Context: " . $context . "\n");
fwrite($cfile,"Extension: " . $extension . "\n");
fclose($cfile);
chmod($startcallfile,0777);
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(useragi/sve_agi_test.php);
By default Asterisk looks for the AGI script in the agi-bin (/var/lib/asterisk/agi-bin). There is a "useragi" directory inside the agi-bin which is where we will put our scripts. The "asterisk_agi" symlink in your home directory points to the agi-bin 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
First of all, the normal PHP application on social is set to run in what is called "safe mode". This is a strict setting for enforcing safe practices in web development. Since we are not doing web development we can relax those rules a bit but to do so requires us to use a different "php.ini" file (where the settings for PHP are located). To use this new ini file in our applications we have to pass another flag to the PHP command in our scripts.
#!/usr/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
<?PHP
if (ini_get('safe_mode'))
{
echo("PHP IS running in safe mode!\n");
}
else
{
echo("PHP is NOT running in safe mode!\n");
}
?>
AGI Scripting the Hard Way
#!/usr/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
<?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/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
<?PHP
require('/var/lib/asterisk/agi-bin/useragi/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 */
?>
PHPAGI PHP Library Docs
PHPAGI Site
More Fun
#!/usr/bin/php -qc /var/lib/asterisk/agi-bin/useragi/php_agi.ini
<?PHP
require('/var/lib/asterisk/agi-bin/useragi/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;
}
}
?>