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