login | register
Sat 06 of Sep, 2008 [17:28 UTC]

voip-info.org

Discuss [5] History

Asterisk AGI php

Created by: oej,Last modification on Mon 13 of Aug, 2007 [12:06 UTC] by swatchy

How to setup PHP to work with the Asterisk Gateway Interface (AGI)


1. The PHP Environment


First order of business is to be aware of your php environment. I'm NOT saying you should change anything but you should be aware of it as it may spare you some frustration along the way especially when you are in the thick of things. On Debian you'll probably need to get "php4-cli" first since the typical PHP4 installation is intended for web use only.

CLI vs. CGI

I've finally managed to find the problem with my PHP AGI script! Apparently, my machine has 2 versions of PHP installed. One for CGI and the other for CLI. Without knowing, to hand test my script I was calling the CLI version (/usr/local/bin/php), whereas Asterisk was calling the CGI version (/usr/bin/php). It turns out that my script wouldn't run on the CGI version of PHP... Interesting, isn't it?

Yes, be aware that depending on your settings Apache might read both php.ini and override your web php.ini
Also: Using the wrong PHP version of PHP can cause troubles when trying to read channel/dialplan variables in your PHP script, so check if you need e.g. "#!/usr/bin/php-cgi -q" or "#!/usr/bin/php -q" in the first line.

Look in your php config file (in /etc/php.ini or /usr/local/etc/php.ini) for the following:

  ob_implicit_flush(false);
  set_time_limit(5);
  ; error_log = filename
  error_reporting(0);  //added by user: democritus

The first row, ob_implicit_flush, shows whether php should buffer output; in the case of Asterisk agi, if you buffer your output, the Asterisk PBX will not get your instruction for a long time unless you flush the buffer manually (see below)

The second item, set_time_limit, is the maximum allowable time to run your php script. Most Asterisk agi scripts will run within a reasonable time but if you have a very lengthy script that is producing strange errors, it is possible that your script was terminated prematurely.

The third item is error_log; great for debug but a killer for production system. It might have been turned off by default or you may have turn it off on purpose and don't remember.

The fourth item, error_reporting(0), tells PHP to not report any errors during script runtime. This is important because any errors (and some warnings) that your script creats will be sent to STDOUT, the same buffer all AGI commands are sent to. Therefore, any errors in your script will be sent to AGI and Asterisk will try to read them as an AGI command. In my experience, this behavior will make all successive AGI commands, after a script error (unknown to you), return '510 invalid command', although commands like 'EXEC Playback' will actually still play back a sound file. For important AGI commands like 'GET DATA my_file' and 'GET DIGIT my_file', the Asterisk CLI will report "Playing File my_file" but the sound file will not be heard on the channel and no DTMF input can be received by the caller. There is also no way for the script to tell what DTMF keys were pressed since the command returns a "510 invalid' string. An alternative to using error_reporting(0) at the beginning of your script to help you debug would be to prepend your suspected error-generating commands in your script with @, such as

 $rst = @mysql_query($strSQL);
 --OR--
 $fp = @fopen("some non-existant file", "r");

The @ will suppress any errors or warning generated by the statement.

2. Script directory

Put your scripts in the /var/lib/asterisk/agi-bin/ directory and get it working first before you do anything fancy


3. Execution permission on the script files

Remember to chmod ALL your script to 755 as follows:

 chmod 755 *.php

4. Interfacing to the command shell

The very first 2 lines in your script should be as follows (assuming that your php bindery is in /usr/bin; double check now):

 #!/usr/bin/php -q
 <?php
  • Notice: there are no gaps and no white space char (except a single \n) between lines 1 and 2. otherwise strange things will be sent to stdout and ruin your day!
  • Notice: If you have trouble reading dialplan variables you might need to use "#!/usr/bin/php-cgi -q" instead

  • The -q flag is REQUIRED for properly running AGI scripts. This tells PHP to suppress HTML headers and other garbage (used only in conjunction with a web server) that will seriously mess up your script! The headers are interpreted by Asterisk AGI as commands since they appear on the STDOUT buffer, and will make all successive AGI commands return "510 Invalid Command", making AGI commands like GET DATA useless since the caller's DTMF digits typed can not be read from the return string.

5. Open input and output channels

Next, you should use fopen() to create all your needed handles. I know different versions of php have varying features to deal with stdio streams but fopen() will work with most new and old versions making your scripts more portable and you don't have to fidget with the php.ini file. besides, fopen() does not pose any inconvenience for use with * agi, so use it.

 $stdin = fopen('php://stdin', 'r');
 $stdout = fopen('php://stdout', 'w');
 $stdlog = fopen('my_agi.log', 'w');

Note:
STDOUT is already open within php of 4.3.0 and above so there just do
  fwrite(STDOUT,"blah");

6. Handle the Asterisk environment input

Asterisk always sends a bunch of info each time agi is called as follows:

   agi_request: test.php
   agi_channel: Zap/1-1
   agi_language: en
   agi_type: Zap
   agi_callerid:
   agi_dnid:
   agi_context: default
   agi_extension: 1000
   agi_priority: 1
                .
Save the info with this function (or the example below):




while (!feof($stdin)) {
  $temp = fgets($stdin);
  $temp = str_replace("\n","",$temp);
  $s = explode(":",$temp);
  $agivar[$s[0]] = trim($s[1]);
  if $temp == "")  {
     break;
     }
  }



This will leave you will an array variable called $agivar. The available options are..

  • agi_request - The agi filename
  • agi_channel - The originating channel (Your phone)
  • agi_language - Typically "en"
  • agi_type - The originateing channel type eg "sip" or "zap"
  • agi_uniqueid - A unique ID for the call
  • agi_callerid - The caller ID eg Joe Soap <1234>
  • agi_context - Origin context
  • agi_extension - The called number
  • agi_priority - The priority it was executed as in the dial plan
  • agi_accountcode - Account code of the origin channel eg joesoap1

To use simply call the variable and the key.. eg If you want the called number simply use the variable $agivar[agi_extension] in your PHP code..


Other AGI headers thrown by Asterisk (environment inputs)


  • agi_calleridname - The caller Name eg Joe Soap
  • 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_enhanced - The flag value is 1.0 if started as an EAGI script

Note: if no caller id number was set to sip.conf agi_callerid will have the same value as agi_calleridname

- Lambert Antonio Sep 01 '05

7. Start using the AGI channel

This is the point where you can start talking with Asterisk. use fputs to send * agi commands. You may also use echo the echo command.

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

  • Note: Use fflush() regardless of php.ini setting just to be safe. If you don't fflush() (auto or manual), Asterisk will not receive the command and your app will be stuck there until timeout.

8. On the use of quotes

  • agi command options are not optional, i.e. they must appear on the command string
  • some options MUST be enclosed in quotes, e.g. <escape digits> of SAY NUMBER and SAY DIGITS
  • some options MUST NOT be enclosed in quotes, e.g. <digit string> of SAY NUMBER and SAY DIGITS
  • some options can go either way.
  • you can use single quote where quote is needed e.g. as above
  • as a reminder, escape char such as \n in a strings within single quotes will not be resolved !!!!

Passing variables as a value of another variable

Variables such as Channel variables set as a value for another variable should be parsed/replaced by actual value for it will be read as a string.

This example wont work if issued to Dial command
    SET VARIABLE MY_DIALCOMMAND "ZaP/g1/${EXTEN}"

The ${EXTEN} variable should be replaced by the actual dialed number. eg 12345678
    SET VARIABLE MY_DIALCOMMAND "ZaP/g1/12345678"

in extensions.conf
    exten => s,1,Dial(${MY_DIALCOMMAND})

- Lambert Antonio Sep 01 '05

9. Asterisk responses

Next, you'll have to pick up responses from * which is pretty straight forward:

    $msg  = fgets($stdin,1024);
    fputs($stdlog,$msg . "\n");

Catching SIGHUP from asterisk, clean exit


   declare(ticks = 1);
   if (function_exists('pcntl_signal')) {
       pcntl_signal(SIGHUP,  "agi_hangup_handler");
   }
   function agi_hangup_handler($signo)//asterisk always sends SIGHUP=1
   {
        //from my experience you cannot verbose anything from here into the cli
        //write custom CDR here
        //close database connections and file handlers
   }


Sample function for executing an asterisk command and retrieving it's result into an array


function execute($command) {
   GLOBAL $in, $out;
   fputs($out, $command . " \n");
   fflush($out);
   $data = fgets($in, 4096);
   if (preg_match("�^([0-9]{1,3}) (.*)�", $data, $matches)) {
       if (preg_match('�^result=([0-9a-zA-Z]*)( ?\((.*)\))?$�', $matches[2], $match)) {
           $arr['code'] = $matches[1];
           $arr['result'] = $match[1];
           if (isset($match[3]) && $match[3])
               $arr['data'] = $match[3];
           return $arr;
       } else return 0;
   } else return -1;
}

example:
$result = execute("GET VARIABLE testing");
// Now $result['data'] holds the value of testing

$result = execute("GET DATA insertid 3000 5");
// $result['result'] is the entered digits




10. Kill zombie AGI scripts while debugging

If your script failed for some reason (especially if you forget to fflush()), the process is actually hung in limbo; REMEMBER to killproc your script process before you test again:

    killproc my_script.php

11. 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!

12. sample.php



 #!/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 == "")  {
     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
#!/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 == "")  {
     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 ".$agicallerid." - 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 recieved from READ command


call_start.agi
#!/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 == "")  { 
    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 of 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


Comments

Comments Filter
222

333register_globals

by bstocker, Thursday 13 of September, 2007 [10:56:53 UTC]
register_globals is turned "off" in most PHP Distributions (see php.ini). Without register_globals set to "on", the argv array cannot be accessed directly. The following syntax will always work: $x = $_SERVER('argv')(1); (use square brackets to make the example working)
222

333passing and recieving parameters in to your agi script

by miked, Thursday 11 of January, 2007 [14:46:05 UTC]
The parameters end up in the $argv array in your AGI script.

If you call you AGI script and pass a parameter in your dialplan like this :

<code>exten => 400,n,AGI(my-agi-script.php|12345)

In the my-agi-script.php AGI script the value 12345 will be exposed in the first $argv array element (I'm having trouble getting square brackets the hold values to display here, so $argv<leftsquarebracket>1<rightsquarebracket>)

As a side note, the 0th element in $argv contains the full path including filename to the script

I've never run any sort of php scripting from the command line (but have used it for the first time to debug my agi script) so I wasn't familiar with the presence of the $argv and $argc arrays and got hornswaggled for more than a few hours with trying to figure out where the parameters were inside my AGI script.


222

333hint about how to call this script

by , Sunday 07 of December, 2003 [21:55:45 UTC]
You're forgetting to answer the line first.

Mark

On Fri, 21 Nov 2003, WipeOut wrote:

> I am trying to use the SAY NUMBER command from an AGI script but it does
> not seem to be working..
>
> If I use "EXEC SayNumber 2" and execute the asterisk command from the
> AGI it works and I hear the 2 said on the phone..
>
> If I use "SAY NUMBER 2" I see "-- Playing 'digits/2' (language 'en')" on
> the console but I don't hear the number said on the phone..
>
> I would prefer to use the AGI commands from within the AGI script.. :)
>
> Is this a bug or am I doing something wrong??
>
> Thanks..

222

333confusion

by , Sunday 07 of December, 2003 [20:59:17 UTC]
sort out the array naming:

$argv[]
$agivar[]
$agi[]

222

333Error in sample (11.)

by , Sunday 07 of December, 2003 [20:56:30 UTC]
watch out for:

read() and read()
write() and write()
errlog() and errlog()