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;
    					}
    			}
            ?>