Asterisk AGI php

Business PBX Solutions
Provider Solution Details
3CX Software PBX for Windows
  • Windows Software Solution
  • Easy to Install and Manage
  • Auto Configures Phones & Trunks
  • Android, iOS, Windows & Mac clients
Details
Bicom VoIP Become an ITSP Now!
  • Become a serious competitor in VoIP Immediately
  • FULL Consultancy, Installation, Training & Support
  • Sell Hosted IP PBXs, Biz Lines, Call Centre
  • Turnkey Provisioning at your data center
Details
4PSA's VoipNow Cloud Communications Platform
  • Enjoy your custom licensing plan - Pay-As-You-Grow!
  • Your fastest go-to-market solution - from deployment to billing.
  • Professional support, training and knowledge base to help you grow your business
  • On your infrastructure or cloud-based, it's up to you.
Details
Using PHP with the Asterisk Gateway Interface (AGI)

PHP Tips and Examples

Script Location

Asterisk expects to find scripts in the /var/lib/asterisk/agi-bin/ directory for AGI execution.

File Permissions

Remember to make your files executable with chmod 755 /var/lib/asterisk/agi-bin/*.php .

The Shebang

The first line in your script will of course determine which executable runs it. Be aware of the different versions of PHP, such as /usr/bin/php and /usr/bin/php-cgi. In most cases life will be easier if you use the CLI version — typically at /usr/bin/php. (Use /usr/bin/php -v to confirm that you are using the CLI version.) This will make $argv and $argc available, disable buffering, disable HTTP header output, and pipe error messages to STDERR instead of STDOUT. All these are desirable for working with Asterisk. If you use the CGI executable, you will have to use some command line options and edit your scripts to ensure proper behaviour.
The shebang should be followed immediately by the <?php script opener, with only a newline between them. Any whitespace could get sent to Asterisk, causing problems.

Read AGI Inputs

All communication with the AGI occurs over STDIN and STDOUT; these IO streams are predefined in PHP and do not need to be opened or closed. When your program starts, Asterisk will stream a number of variables to it, which it's in your interest to capture.

 $agivars = array();
 while (!feof(STDIN)) {
     $agivar = trim(fgets(STDIN));
     if ($agivar === '') {
         break;
     }
     $agivar = explode(':', $agivar);
     $agivars[$agivar[0]] = trim($agivar[1]);
 }
 extract($agivars);
 echo $agi_callerid;


The info passed by Asterisk is:
  • agi_request - The filename of your script
  • agi_channel - The originating channel (your phone)
  • agi_language - The language code (e.g. "en")
  • agi_type - The originating channel type (e.g. "SIP" or "ZAP")
  • agi_uniqueid - A unique ID for the call
  • agi_callerid - The caller ID number (or "unknown")
  • agi_calleridname - The caller ID number (or "unknown")
  • agi_callingpres - The presentation for the callerid in a ZAP channel
  • agi_callingani2 - The number which is defined in ANI2 see Asterisk Detailed Variable List (only for PRI Channels)
  • agi_callington - The type of number used in PRI Channels see Asterisk Detailed Variable List
  • agi_callingtns - An optional 4 digit number (Transit Network Selector) used in PRI Channels see Asterisk Detailed Variable List
  • agi_dnid - The dialed number id
  • agi_rdnis - The referring DNIS number
  • agi_context - Origin context in extensions.conf
  • agi_extension - The called number
  • agi_priority - The priority it was executed as in the dial plan
  • agi_enhanced - The flag value is 1.0 if started as an EAGI script
  • agi_accountcode - Account code of the origin channel


Sending commands and receiving responses

Sending Commands

To send commands to Asterisk, use fwrite (or its alias fputs) to send AGI commands to standard ouput.

  fwrite(STDOUT, "SAY NUMBER 1234567 '79#'\n");
  fflush($stdout);

Notes

  • Use fflush after an output, just to be safe. If output buffering is somehow enabled, the command may not be sent properly, leaving you to wait for your script to timeout.
  • Most AGI command options are required, even if empty. Some options must be quoted, and some must not be quoted. Check the list of AGI commands to be sure.
  • Follow each command with a carriage return (\n).

Receiving Responses

To receive responses from Asterisk, simply read standard input with fgets

  $msg  = fgets(STDIN);

The response is in the form <code> result=<result> [extra] where code is a numeric HTTP-like response code (200 for success, 5xx for errors) result is the result of the command and extra is data which is sometimes appended to the results. If a command times out, extra will contain the value (timeout).

Sample Function


 function execute_agi($command) {
     fwrite(STDOUT, "$command\n");
     fflush(STDOUT);
     $result = fgets(STDIN);
     $ret = array('code'=> -1, 'result'=> -1, 'timeout'=> false, 'data'=> '');
     if (preg_match("/^([0-9]{1,3}) (.*)/", $result, $matches)) {
         $ret['code'] = $matches[1];
         $ret['result'] = 0;
         if (preg_match('/^result=([0-9a-zA-Z]*)(?:\s?\((.*?)\))?$/', $matches[2], $match))  {
             $ret['result'] = $match[1];
             $ret['timeout'] = ($match[2] === 'timeout') ? true : false;
             $ret['data'] = $match[2];
         }
     }
     return $ret;
 }
 $result = agi_execute('WAIT FOR DIGIT 5000');
 print_r($result);
 /*
 If # is pressed, output will be:
 Array
 (
     [code] => 200
     [result] => 35
     [timeout] => false
     [data] =>
 )
 */


Debugging with VERBOSE

VERBOSE is your friend if you'd like to display debugging information in the Asterisk CLI. But make sure to also read its return as otherwise you will screw up your script badly! Using the above execute_agi example it's simple to build a logging function:

 function log_agi($entry, $level = 1) {
     if (!is_numeric($level)) {
         $level = 1;
     }
     $result = execute_agi("VERBOSE \"$entry\" $level");
 }
 log_agi('Log message');


Debugging with PHP

To view PHP's error messages, you need to view STDERR. Fortunately, STDERR from an AGI script is output to the Asterisk console. Unfortunately, it is only output to the original instance of Asterisk. Connecting to an already-running instance with asterisk -r will not work; you'll need to connect to the original Asterisk instance or stop Asterisk and start your own instance. On most systems, Asterisk will have a console running on TTY9; connect to that console and you'll see the messages from STDERR. If that doesn't work, you can always stop the running Asterisk and start your own with (for example) asterisk -fgc -U asterisk -G asterisk . Obviously this is not an option on a live server! Note that no verbosity is required, output from STDERR is always displayed in the original Asterisk console.

An other way to see errors of your scripts live is to edit php.ini and set error_log = /var/log/mylogfile
Make sure that display_errors is set to "Off" as well. This will prevent PHP printing out notices and error messages as output of your script.
After that tail -f /var/log/mylogfile will show the errors of your script.

Exiting cleanly

On hangup from the caller, Asterisk will kill your script with SIGHUP; you can use pcntl_signal to run a user-defined function at this time.

 pcntl_signal(SIGHUP,  "agi_hangup_handler");
 function agi_hangup_handler($signo) {
     //this function is run when Asterisk kills your script ($signo is always 1)
     //close file handles, write database records, etc.
 }

If you call HANGUP from within your script, you must then terminate your script with exit or the script will not stop, since Asterisk 1.6. Prior versions terminated the script after a hangup regardless of its origin.

Kill zombie AGI scripts while debugging

If your script failed for some reason, the process could be hung; remember to kill your script process with killproc my_script.php before testing it again.

Putting it all together


#!/usr/bin/php
<?php
/*
This script answers the call and prompts for an extension, provided your caller ID is approved.
When it receives 4 digits it will read them back to you and hang up.
*/
$debug_mode = false; //debug mode writes extra data to the log file below whenever an AGI command is executed
$log_file = '/tmp/agitest.log'; //log file to use in debug mode
$allowed_ext = array('1234', '0'); //who's allowed to access this script

//get the AGI variables; we will check caller id
$agivars = array();
while (!feof(STDIN)) {
	$agivar = trim(fgets(STDIN));
	if ($agivar === '') {
		break;
	}
	else {
		$agivar = explode(':', $agivar);
		$agivars[$agivar[0]] = trim($agivar[1]);
	}
}
foreach($agivars as $k=>$v) {
	log_agi("Got $k=$v");
}
extract($agivars);

//hangup if it's not an allowed extension
if (!empty($allowed_ext)) {
	if (!in_array($agi_callerid, $allowed_ext)) {
		log_agi("Call rejected from $agi_calleridname <$agi_callerid>");
		execute_agi('STREAM FILE goodbye ""');
		execute_agi('HANGUP');
		exit;
	}
}

//ask for an extension
$ext = '';
$result = execute_agi('STREAM FILE please-enter-the "0123456789"');
if ($result['result'] == 0) {
	$result = execute_agi('STREAM FILE users "0123456789"');
	if ($result['result'] == 0) {
		//they haven't entered anything yet
		$result = execute_agi("GET DATA extension 5000 4");
		if ($result['result'] > 0) {
			$ext = $result['result'];
		}
	}
	else {
		$ext = chr($result['result']);
	}
}
else {
	$ext = chr($result['result']);
}

//we haven't got 4 digits yet
if (strlen($ext) < 4) {
	//still no input after GET DATA timeout
	if (empty($ext)) {
		execute_agi('STREAM FILE please-enter-the ""');
		execute_agi('STREAM FILE users ""');
		execute_agi('STREAM FILE extension ""');
	}
	//we got a single digit during playback of 'please enter the' or 'users'
	while (strlen($ext) < 4) {
		$result = execute_agi('WAIT FOR DIGIT -1');
		//look for digits only
		if ($result['result'] >= 48 && $result['result'] <= 57) {
			$ext .= chr($result['result']);
		}
		//ignore * or # (or a, b, c, d)
		else {
			continue;
		}
	}
}
log_agi("Got extension $ext");

//say back the extension; at this point you could certainly do something more productive but that is left to you
execute_agi('STREAM FILE you-entered ""');
execute_agi("SAY DIGITS $ext \"\"");
execute_agi('STREAM FILE goodbye ""');
execute_agi('HANGUP');
exit;

function execute_agi($command) {
	global $debug_mode, $log_file;

	fwrite(STDOUT, "$command\n");
	fflush(STDOUT);
	$result = trim(fgets(STDIN));
	$ret = array('code'=> -1, 'result'=> -1, 'timeout'=> false, 'data'=> '');
	if (preg_match("/^([0-9]{1,3}) (.*)/", $result, $matches)) {
		$ret['code'] = $matches[1];
		$ret['result'] = 0;
		if (preg_match('/^result=([0-9a-zA-Z]*)\s?(?:\(?(.*?)\)?)?$/', $matches[2], $match))  {
			$ret['result'] = $match[1];
			$ret['timeout'] = ($match[2] === 'timeout') ? true : false;
			$ret['data'] = $match[2];
		}
	}
	if ($debug_mode && !empty($logfile)) {
		$fh = fopen($logfile, 'a');
		if ($fh !== false) {
			$res = $ret['result'] . (empty($ret['data']) ? '' : " / $ret[data]");
			fwrite($fh, "-------\n>> $command\n<< $result\n<<     parsed $res\n");
			fclose($fh);
		}
	}
	return $ret;
}

function log_agi($entry, $level = 1) {
	if (!is_numeric($level)) {
		$level = 1;
	}
	$result = execute_agi("VERBOSE \"$entry\" $level");
}
?>


Old Samples

Note the following samples are all 4-5 years old at this point, and are written for earlier versions of PHP and Asterisk. They should be approached with caution.


12. sample.php


If you copy and paste this sample, be sure to remove the leading spaces before the first two lines or you will get an error message talking about an "Exec format error". You may also get this message if your text file is not encoded in ANSI format. Also check your php version in the first line. You may need "#!/usr/bin/php5 -q" or simply "#!/usr/bin/php -q".


  1. !/usr/bin/php4 -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
$in = fopen("php://stdin","r");
$stdlog = fopen("/var/log/asterisk/my_agi.log", "w");

// toggle debugging output (more verbose)
$debug = false;

// Do function definitions before we start the main loop
function read() {
global $in, $debug, $stdlog;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug) fputs($stdlog, "read: $input\n");
return $input;
}

function errlog($line) {
global $err;
echo "VERBOSE \"$line\"\n";
}

function write($line) {
global $debug, $stdlog;
if ($debug) fputs($stdlog, "write: $line\n");
echo $line."\n";
}

// parse agi headers into array
while ($env=read()) {
$s = split(": ",$env);
$agi[str_replace("agi_","",$s[0])] = trim($s[1]);
if (($env == "") || ($env == "\n")) {
break;
}
}

// main program
echo "VERBOSE \"Here we go!\" 2\n";
read();
errlog("Call from ".$agi['channel']." - Calling phone");
read();
write("SAY DIGITS 22 X"); // X is the escape digit. since X is not DTMF, no exit is possible
read();
write("SAY NUMBER 2233 X"); // X is the escape digit. since X is not DTMF, no exit is possible
read();

// clean up file handlers etc.
fclose($in);
fclose($stdlog);

exit;
?>






13. another sample, ANI


Scenario - did callers call the Asterisks box and land on the context did, Asterisks answers the call and then execute the script ani.agi. This script will connect to mysql and see whether originators phone number(caller ID) is already in the database. If it exists, then agi.script will pass the control back to dial plan. Dial plan now will progress the call. If the caller ID does not exists, ani.agi will pass the control to the dial plan as un authenticated call.


ani.agi
  1. !/usr/local/bin/php -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
$in = fopen("php://stdin","r");

$stdlog = fopen("/var/log/asterisk/my_agi.log", "w");


// Do function definitions before we start the main loop
function read() {
global $in, $debug;
$input = str_replace("\n", "", fgets($in, 4096));
return $input;
}

function errlog($line) {
global $err;
echo "VERBOSE \"$line\"\n";
}

function write($line) {
global $debug;
echo $line."\n";
}

// parse agi headers into array

while ($env=read()) {
$env = str_replace("\"","",$env);
$s = split(": ",$env);
$agi[str_replace("agi_","",$s[0])] = trim($s[1]);
if (($env == "") || ($env == "\n")) {
break;
}
}


function connect_db() {
$db_connection = mysql_connect ('localhost', 'admin', 'admincdr') or die (mysql_error());
$db_select = mysql_select_db('cdrdb') or die (mysql_error());
}

// main program
$cli = $agi[[callerid];
$exten= $agi[extension];
//errlog("Call from ".$agi[callerid]." - Calling phone");

connect_db();

$query1 = "SELECT pin_no FROM accounts WHERE clid = '$cli' ";
$query_result1 = @mysql_query($query1);
$row_count = mysql_num_rows($query_result1);
$row1 = @mysql_fetch_array ($query_result1);

If ($row_count !=0 ) { // caller is authenticated based on ANI
$pin = $row1[pin_no];
write ("SET CONTEXT did");
write ("EXEC SETACCOUNT $pin");
write ("EXEC GoTO s|2"); // ask for the number to call, no authentication
}

Else { // clid does not exist so ask for PIN
write ("SET CONTEXT did");
write ("EXEC GoTO 866XXXXXXX|7");
}

// clean up file handlers etc.
fclose($in);
fclose($stdlog);

exit;
?>


Extensions.conf

[did]
;for did callers

exten => 866XXXXXXX,1,Ringing
exten => 866XXXXXXX,2,Wait,4
exten => 866XXXXXXX,3,Answer
exten => 866XXXXXXX,4,SetCIDname()
exten => 866XXXXXXX,5,agi,ani.agi
exten => 866XXXXXXX,6,Authenticate(/etc/asterisk/authenticate.txt|a)
exten => 866XXXXXXX,7,GoTO(s|2)
exten => s,2,BackGround(pls-entr-num-uwish2-call)
exten => s,3,DigitTimeout,5
exten => s,4,ResponseTimeout,10
exten => _011XXXXXXXX.,1,Playback(pls-wait-connect-call)
exten => _011XXXXXXXX.,2,AbsoluteTimeout(3600)
exten => _011XXXXXXXX.,3,ResetCDR(w)
exten => _011XXXXXXXX.,4,Dial(H323/${EXTEN}@a.b.c.d,90)
exten => _011XXXXXXXX.,5,NoCDR() ; if no answer
exten => _011XXXXXXXX.,6,Busy
exten => _011XXXXXXXX.,105,NoCDR() ; if line is busy
exten => _011XXXXXXXX.,106,Busy

exten => t,1,Playback(vm-goodbye) ; if timeout
exten => t,2,NoCDR()
exten => t,3,Hangup
exten => i,1,Playback,invalid ; if any number other than 011.....
exten => i,2,Goto,s|3
;exten => h,1,Hangup

mysql database
database name - cdrdb ( user - admin, login - admincdr )
table name - accounts ( two fields, pin_no and clid)



14. yet another sample, read()


Purpose - Get info received from READ command


call_start.agi
  1. !/usr/local/bin/php -q
<?php
ob_implicit_flush(true);
set_time_limit(6);
error_reporting(0); //added by user: democritus
$in = fopen("php://stdin","r");
$stdlog = fopen("/var/log/asterisk/my_agi.log", "w");

// toggle debugging output (more verbose)
$debug = true;

// Do function definitions before we start the main loop
function read() {
global $in, $debug, $stdlog;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug) fputs($stdlog, "read: $input\n");
return $input;
}

function write($line) {
global $debug, $stdlog;
if ($debug) fputs($stdlog, "write: $line\n");
echo $line."\n";
}

// parse agi headers into array

while ($env=read()) {
$s = split(": ",$env);
$agi[str_replace("agi_","",$s[0])] = trim($s[1]);
if (($env == "") || ($env == "\n")) {
break;
}
}


write("EXEC READ dialed|IVR/en_enter_destination|0 X"); // Custom Sound File (So make your own!)
read();
write("GET VARIABLE dialed X");
$a = read();
$num = substr($a,14);
$num = substr($num,0,-1);
echo "VERBOSE \"$num\" \n";
read();


// clean up file handlers etc.
fclose($in);
fclose($stdlog);
exit;

?>

By Mike Roberts (manipura) Nov 30 '04

15. Simple example for GET VARIABLE


function __read__() {
global $in, $debug;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug) echo "VERBOSE \"read: $input\"\n";
return $input;
}

function __write__($line) {
global $debug;
if ($debug) echo "VERBOSE \"write: $line\"\n";
print $line."\n";
}

//get the variable and strip off all the extra stuff around it
__write__("GET VARIABLE MYDIALPLANVAR");
$res = substr(strrchr(__read__(),"("),1,-1);
__write__("EXEC NOOP '======??? MYDIALPLANVAR: ".$res." ???======'\n");
__read__();


By hkirrc.patrick 2003-11-11, some changes in source code by Florian Overkamp and WipeOut and Philipp von Klitzing and democritus


See also



Go back to Asterisk

Created by: oej, Last modification: Sat 10 of Nov, 2012 (02:23 UTC) by danoman


Please update this page with new information, just login and click on the "Edit" or "Discussion" tab. Get a free login here: Register Thanks! - Find us on Google+

Page Changes | Comments

 

Featured -

Search: