Articles

The Asterisk Gateway Protocol: A practical introduction and tutorial to agi applications


Introduction

The Asterisk Gateway Protocol (AGI from now on) is the protocol used by the Asterisk server as its interface for telephony applications.

AGI is just a way that allows you (as a software developer) to easily make telephony applications that asterisk will run someway along the dialplan. Through AGI, you can read input from the user, play sound files, control the call and its flow, and pretty much everything needed to make a successfull IVR (Interactive Voice Response) in virtually any programming language.

You might also be interested in PAGI. An AGI client/framework that allows you to quickly develop agi applications in an oop fashion.

Asterisk configuration for AGI applications

To start your agi application you will use the AGI() dialplan application from you own dialplan. For example, in extensions.conf:


exten => 1,1,AGI(myApplication.php)


This will tell asterisk to start an agi application when a call is made to the '1' extension. That's it ;)

Overview of the AGI Protocol

AGI is a very simple protocol. Asterisk communicate with the applications through their standard input (stdin) and standard output (stdout). So you can send commands to asterisk via your stdout and receive asterisk responses through your stdin. This makes AGI very easy to deal with from any programming language.

When a call is received by asterisk, it will start to execute the dial plan in the configured context. This context will depend on various reasons that escape the scope of this article, so I wont get into any details.
When the AGI() application is called from the dialplan, asterisk will fork() a new process and execute that application, communicating (as stated above) with it through its standard input and standard output (normally file descriptors 0 -zero- and 1 respectively).

Before forking and executing the new agi process, asterisk will set the following environment variables:

  • AST_CONFIG_DIR: astetcdir key from asterisk.conf.
  • AST_CONFIG_FILE: absolute path to the asterisk.conf file used.
  • AST_MODULE_DIR: astmoddir key from asterisk.conf.
  • AST_SPOOL_DIR: astspooldir key from asterisk.conf.
  • AST_MONITOR_DIR: usually AST_SPOOL_DIR/monitor
  • AST_VAR_DIR: astvarlibdir key from asterisk.conf.
  • AST_DATA_DIR: astdbdir key from asterisk.conf.
  • AST_LOG_DIR: astlogdir key from asterisk.conf.
  • AST_AGI_DIR: astagidir key from asterisk.conf.
  • AST_KEY_DIR: astkeydir key from asterisk.conf.
  • AST_RUN_DIR: astrundir key from asterisk.conf.

As soon as your application is fork()ed from asterisk, it starts to receive the channel variables via its standard input, which are:

  • agi_request: The filename of your script.
  • agi_channel: The originating channel.
  • agi_language: The language code.
  • agi_type: The originating channel type.
  • agi_uniqueid: A unique ID for the call.
  • agi_version: The version of Asterisk (since Asterisk 1.6).
  • agi_calleridv: The caller ID number (or "unknown").
  • agi_calleridname: The caller ID name (or "unknown").
  • agi_callingpres: The presentation for the callerid.
  • agi_callingani2: PRI Channels ani2 variable.
  • agi_callington: The type of number used in PRI Channels.
  • agi_callingtns: An optional 4 digit number (Transit Network Selector).
  • agi_dnid: The dialed number id (or "unknown").
  • agi_rdnis: The referring DNIS number (or "unknown").
  • agi_context: Origin context in extensions.conf.
  • agi_extension: The called number (dnis).
  • 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, 0.0 otherwise.
  • agi_accountcode: Account code of the origin channel.
  • agi_threadid: Thread ID of the AGI script.

The variables come in the following format:
<variable_name>:<space><variable_value>
Example:
agi_context: default

Quick shell example

Let's try a very basic example to see in action what we've covered so far:

Configure asterisk to run an example

Let's configure a demo application, adding 3 (optional, of course) arguments to it:


exten => 1,1,AGI(/tmp/agi.sh,arg1,arg2,arg3)


Create the agi application

#!/bin/bash

filedump=/tmp/dump.txt

function log() {
    echo ${@} >> ${filedump}
}

function dumpvar() {
    log "channel variable: ${1} = ${2}"
}

log "------------ call start ------------"
dumpvar "config dir" ${AST_CONFIG_DIR}
dumpvar "configfile" ${AST_CONFIG_FILE}
dumpvar "module dir" ${AST_MODULE_DIR}
dumpvar "spool dir" ${AST_SPOOL_DIR}
dumpvar "monitor dir" ${AST_MONITOR_DIR}
dumpvar "var dir" ${AST_VAR_DIR}
dumpvar "data dir" ${AST_DATA_DIR}
dumpvar "log dir" ${AST_LOG_DIR}
dumpvar "agi dir" ${AST_AGI_DIR}
dumpvar "key dir" ${AST_KEY_DIR}
dumpvar "run dir" ${AST_RUN_DIR}

line='init'
while [ "${#line}" -gt "2" ]; do
        read line
        log ${line}
done
log "------------ call done ------------"
exit 0

Run the application

Make a call to the '1' extension. Your agi application should run and create the file /tmp/dump.txt with the output from the channel variables. This application does nothing at all. It will end when asterisk sends an empty line, which marks the end of the channel variables block.

Example of the file /tmp/dump.txt generated:
------------ call start ------------
channel variable: config dir = /export/users/marcelog/config/asterisk
channel variable: configfile = /export/users/marcelog/config/asterisk/asterisk.conf
channel variable: module dir = /usr/lib/asterisk/modules
channel variable: spool dir = /tmp/marcelog/asterisk/spool
channel variable: monitor dir = /tmp/marcelog/asterisk/spool/monitor
channel variable: var dir = /tmp/marcelog/asterisk
channel variable: data dir = /tmp/marcelog/asterisk
channel variable: log dir = /tmp/marcelog/asterisk/logs
channel variable: agi dir = /tmp/marcelog/asterisk/agi-bin
channel variable: key dir = /tmp/marcelog/asterisk/keys
channel variable: run dir = /tmp/marcelog/asterisk
agi_request: /tmp/agi.sh
agi_channel: SIP/marcelog-e00d2760
agi_language: ar
agi_type: SIP
agi_uniqueid: 1297542965.8
agi_version: 1.6.0.9
agi_callerid: marcelog
agi_calleridname: marcelog@mg
agi_callingpres: 0
agi_callingani2: 0
agi_callington: 0
agi_callingtns: 0
agi_dnid: 667
agi_rdnis: unknown
agi_context: default
agi_extension: 667
agi_priority: 2
agi_enhanced: 0.0
agi_accountcode:
agi_threadid: 1104922960
agi_arg_1: arg1
agi_arg_2: arg2
agi_arg_3: arg3
------------ call done ------------

Notice how asterisk passes the variables from the dialplan to your application (i.e: the "special" channel variables agi_arg_x where x is the argument index).

Sending AGI commands and receiving responses

An example agi command that logs to the console is:
VERBOSE "message" 3

In this case, the command is "verbose", which accepts 2 arguments: the message to log, and the log level needed in order to log the message. Asterisk can respond with something like:
200 result=1

So asterisk responses have a format. The format is:
<error_code><space>result=<result_data><space>[additional_data]

Meaning:

  • error_code: An integer number that indicates the result of the operation (see below).
  • result_data: The result value for the command executed.
  • additional_data: Optional additional data returned along with the result_data.

The error code can be one of:

  • 200: Operation was completed successfully.
  • 510: Invalid or unknown command.
  • 511: The command cant be executed on a dead (closed, terminated, hung up) channel.
  • 520: End of proper usage, when the command returns its syntax.

As an AGI script, you should always set the AGI variable AGISTATUS to one of:

  • SUCCESS
  • FAILURE
  • HANGUP

That's about it, you can find the complete list of commands here.

Quick shell example: Part 2

Now let's modify the example, like this:

#!/bin/bash

filedump=/tmp/dump.txt

function log() {
    echo ${@} >> ${filedump}
}

function dumpvar() {
    log "channel variable: ${1} = ${2}"
}

function send() {
    log Sending: ${@}
    echo ${@}
    read line
    log "Got: ${line}"
}

function answer() {
    send "ANSWER"
}

function agilog() {
    send "VERBOSE" \"${@}\"
}

function play() {
    send STREAM FILE ${1} "#"
}

function setvariable() {
    send SET VARIABLE ${1} ${2}
}

function hangup() {
    setvariable AGISTATUS SUCCESS
    send "HANGUP 16"
}

callStatus=1
function callEnded() {
    if [ "${callStatus}" -eq "0" ]; then
        return
    fi
    callStatus=0
    agilog "Call ended abruptly"
    hangup
    exit 0
}
# Asterisk will send a SIGHUP signal when the user hangups the channel. In this
# example you can try it by waiting for the welcome audio message to finish or
# hangup before it finishes playing.
trap callEnded SIGHUP
log "------------ call start ------------"
dumpvar "config dir" ${AST_CONFIG_DIR}
dumpvar "configfile" ${AST_CONFIG_FILE}
dumpvar "module dir" ${AST_MODULE_DIR}
dumpvar "spool dir" ${AST_SPOOL_DIR}
dumpvar "monitor dir" ${AST_MONITOR_DIR}
dumpvar "var dir" ${AST_VAR_DIR}
dumpvar "data dir" ${AST_DATA_DIR}
dumpvar "log dir" ${AST_LOG_DIR}
dumpvar "agi dir" ${AST_AGI_DIR}
dumpvar "key dir" ${AST_KEY_DIR}
dumpvar "run dir" ${AST_RUN_DIR}

line='init'
while [ "${#line}" -gt "2" ]; do
        read line
        log ${line}
done
answer
agilog Hello There
play welcome
hangup
log "------------ call done ------------"
exit 0

Example of the file /tmp/dump.txt generated:
------------ call start ------------
channel variable: config dir = /export/users/marcelog/config/asterisk
channel variable: configfile = /export/users/marcelog/config/asterisk/asterisk.conf
channel variable: module dir = /usr/lib/asterisk/modules
channel variable: spool dir = /tmp/marcelog/asterisk/spool
channel variable: monitor dir = /tmp/marcelog/asterisk/spool/monitor
channel variable: var dir = /tmp/marcelog/asterisk
channel variable: data dir = /tmp/marcelog/asterisk
channel variable: log dir = /tmp/marcelog/asterisk/logs
channel variable: agi dir = /tmp/marcelog/asterisk/agi-bin
channel variable: key dir = /tmp/marcelog/asterisk/keys
channel variable: run dir = /tmp/marcelog/asterisk
agi_request: /tmp/agi.sh
agi_channel: SIP/marcelog-e00d2760
agi_language: ar
agi_type: SIP
agi_uniqueid: 1297542965.8
agi_version: 1.6.0.9
agi_callerid: marcelog
agi_calleridname: marcelog@mg
agi_callingpres: 0
agi_callingani2: 0
agi_callington: 0
agi_callingtns: 0
agi_dnid: 667
agi_rdnis: unknown
agi_context: default
agi_extension: 667
agi_priority: 2
agi_enhanced: 0.0
agi_accountcode:
agi_threadid: 1104922960
agi_arg_1: arg1
agi_arg_2: arg2
agi_arg_3: arg3

Sending: ANSWER
Got: 200 result=0
Sending: VERBOSE "Hello There"
Got: 200 result=1
Sending: STREAM FILE welcome #
Sending: VERBOSE "Call ended abruptly"
Got: 00 result=-1 endpos=3840
Sending: SET VARIABLE AGISTATUS SUCCESS
Got: 200 result=1
Sending: HANGUP 16
Got: 200 result=1
------------ call done ------------

Conclusions

As you can see, its fairly easy to implement almost *any* ivr application very quickly and easy via agi. One of the most important keys to the success of asterisk as a telephony software and product. I hope this tutorial helps you out in making agi applications in your language of choice.


Marcelo Gornstein <marcelog@gmail.com>