Asterisk
Asterisk Asterisk
Contents

Asterisk - The Open Source VoIP PBX

Previous Page Next Page
 
Asterisk: The Future of Telephony
Table of Contents
Copyright
Foreword
Preface
Audience
Organization
Software
Conventions Used in This Book
Using Code Examples
Safari® Enabled
How to Contact Us
Acknowledgments
Chapter 1.  A Telephony Revolution
Section 1.1.  VoIP: Bridging the Gap Between Traditional Telephony and Network Telephony
Section 1.2.  Massive Change Requires Flexible Technology
Section 1.3.  Asterisk: The Hacker's PBX
Section 1.4.  Asterisk: The Professional's PBX
Section 1.5.  The Asterisk Community
Section 1.6.  The Business Case
Section 1.7.  This Book
Chapter 2.  Preparing a System for Asterisk
Section 2.1.  Server Hardware Selection
Section 2.2.  Environment
Section 2.3.  Telephony Hardware
Section 2.4.  Types of Phone
Section 2.5.  Linux Considerations
Section 2.6.  Conclusion
Chapter 3.  Installing Asterisk
Section 3.1.  What Packages Do I Need?
Section 3.2.  Obtaining the Source Code
Section 3.3.  Compiling Zaptel
Section 3.4.  Compiling libpri
Section 3.5.  Compiling Asterisk
Section 3.6.  Installing Additional Prompts
Section 3.7.  Updating Your Source Code
Section 3.8.  Common Compiling Issues
Section 3.9.  Loading Zaptel Modules
Section 3.10.  Loading libpri
Section 3.11.  Loading Asterisk
Section 3.12.  Directories Used by Asterisk
Section 3.13.  Conclusion
Chapter 4.  Initial Configuration of Asterisk
Section 4.1.  What Do I Really Need?
Section 4.2.  Working with Interface Configuration Files
Section 4.3.  FXO and FXS Channels
Section 4.4.  Configuring an FXO Channel
Section 4.5.  Configuring an FXS Channel
Section 4.6.  Configuring SIP
Section 4.7.  Configuring Inbound IAX Connections
Section 4.8.  Configuring Outbound IAX Connections
Section 4.9.  Debugging
Section 4.10.  Conclusion
Chapter 5.  Dialplan Basics
Section 5.1.  Dialplan Syntax
Section 5.2.  A Simple Dialplan
Section 5.3.  Adding Logic to the Dialplan
Section 5.4.  Conclusion
Chapter 6.  More Dialplan Concepts
Section 6.1.  Expressions and Variable Manipulation
Section 6.2.  Dialplan Functions
Section 6.3.  Conditional Branching
Section 6.4.  Voicemail
Section 6.5.  Macros
Section 6.6.  Using the Asterisk Database (AstDB)
Section 6.7.  Handy Asterisk Features
Section 6.8.  Conclusion
Chapter 7.  Understanding Telephony
Section 7.1.  Analog Telephony
Section 7.2.  Digital Telephony
Section 7.3.  The Digital Circuit-Switched Telephone Network
Section 7.4.  Packet-Switched Networks
Section 7.5.  Conclusion
Chapter 8.  Protocols for VoIP
Section 8.1.  The Need for VoIP Protocols
Section 8.2.  VoIP Protocols
Section 8.3.  Codecs
Section 8.4.  Quality of Service
Section 8.5.  Echo
Section 8.6.  Asterisk and VoIP
Section 8.7.  Conclusion
Chapter 9.  The Asterisk Gateway Interface (AGI)
Section 9.1.  Fundamentals of AGI Communication
Section 9.2.  Writing AGI Scripts in Perl
Section 9.3.  Creating AGI Scripts in PHP
Section 9.4.  Writing AGI Scripts in Python
Section 9.5.  Debugging in AGI
Section 9.6.  Conclusion
Chapter 10.  Asterisk for the Über-Geek
Section 10.1.  Festival
Section 10.2.  Call Detail Recording
Section 10.3.  Customizing System Prompts
Section 10.4.  Manager
Section 10.5.  Call Files
Section 10.6.  DUNDi
Section 10.7.  Conclusion
Chapter 11.  Asterisk: The Future of Telephony
Section 11.1.  The Problems with Traditional Telephony
Section 11.2.  Paradigm Shift
Section 11.3.  The Promise of Open Source Telephony
Section 11.4.  The Future of Asterisk
Appendix A.  VoIP Channels
Section A.1.  IAX
Section A.2.  SIP
Appendix B.  Application Reference
AbsoluteTimeout( )
AddQueueMember( )
ADSIProg( )
AgentCallbackLogin( )
AgentLogin( )
AgentMonitorOutgoing( )
AGI( )
AlarmReceiver( )
Answer( )
AppendCDRUserField( )
Authenticate( )
Background( )
BackgroundDetect( )
Busy( )
CallingPres( )
ChangeMonitor( )
ChanIsAvail( )
CheckGroup( )
Congestion( )
ControlPlayback( )
Curl( )
Cut( )
DateTime( )
DBdel( )
DBdeltree( )
DBget( )
DBput( )
DeadAGI( )
Dial( )
DigitTimeout( )
Directory( )
DISA( )
DumpChan( )
DUNDiLookup( )
EAGI( )
Echo( )
EndWhile( )
ENUMLookup( )
Eval( )
Exec( )
ExecIf( )
FastAGI( )
Festival( )
Flash( )
ForkCDR( )
GetCPEID( )
GetGroupCount( )
GetGroupMatchCount( )
Goto( )
GotoIf( )
GotoIfTime( )
Hangup( )
HasNewVoicemail( )
HasVoicemail( )
IAX2Provision( )
ImportVar( )
LookupBlacklist( )
LookupCIDName( )
Macro( )
MailboxExists( )
Math( )
MeetMe( )
MeetMeAdmin( )
MeetMeCount( )
Milliwatt( )
Monitor( )
MP3Player( )
MusicOnHold( )
NBScat( )
NoCDR( )
NoOp( )
Park( )
ParkAndAnnounce( )
ParkedCall( )
PauseQueueMember( )
Playback( )
Playtones( )
Prefix( )
PrivacyManager( )
Progress( )
Queue( )
Random( )
Read( )
RealTime
RealTimeUpdate( )
Record( )
RemoveQueueMember( )
ResetCDR( )
ResponseTimeout( )
RetryDial( )
Ringing( )
SayAlpha( )
SayDigits( )
SayNumber( )
SayPhonetic( )
SayUnixTime( )
SendDTMF( )
SendImage( )
SendText( )
SendURL( )
Set( )
SetAccount( )
SetAMAFlags( )
SetCallerID( )
SetCallerPres( )
SetCDRUserField( )
SetCIDName( )
SetCIDNum( )
SetGlobalVar( )
SetGroup( )
SetLanguage( )
SetMusicOnHold( )
SetRDNIS( )
SetVar( )
SIPAddHeader( )
SIPDtmfMode( )
SIPGetHeader( )
SoftHangup( )
StopMonitor( )
StopPlaytones( )
StripLSD( )
StripMSD( )
SubString( )
Suffix( )
System( )
Transfer( )
TrySystem( )
TXTCIDName( )
UnpauseQueueMember( )
UserEvent( )
Verbose( )
VMAuthenticate( )
VoiceMail( )
VoiceMailMain( )
Wait( )
WaitExten( )
WaitForRing( )
WaitForSilence( )
WaitMusicOnHold( )
While( )
Zapateller( )
ZapBarge( )
ZapRAS( )
ZapScan( )
Appendix C.  AGI Reference
ANSWER
CHANNEL STATUS
DATABASE DEL
DATABASE DELTREE
DATABASE GET
DATABASE PUT
EXEC
GET DATA
GET FULL VARIABLE
GET OPTION
GET VARIABLE
HANGUP
NOOP
RECEIVE CHAR
RECORD FILE
SAY ALPHA
SAY DATE
SAY DATETIME
SAY DIGITS
SAY NUMBER
SAY PHONETIC
SAY TIME
SEND IMAGE
SEND TEXT
SET AUTOHANGUP
SET CALLERID
SET CONTEXT
SET EXTENSION
SET MUSIC ON
SET PRIORITY
SET VARIABLE
STREAM FILE
TDD MODE
VERBOSE
WAIT FOR DIGIT
Appendix D.  Configuration Files
Section D.1.  modules.conf
Section D.2.  adsi.conf
Section D.3.  adtranvofr.conf
Section D.4.  agents.conf
Section D.5.  alarmreceiver.conf
Section D.6.  alsa.conf
Section D.7.  asterisk.conf
Section D.8.  cdr.conf
Section D.9.  cdr_manager.conf
Section D.10.  cdr_odbc.conf
Section D.11.  cdr_pgsql.conf
Section D.12.  cdr_tds.conf
Section D.13.  codecs.conf
Section D.14.  dnsmgr.conf
Section D.15.  dundi.conf
Section D.16.  enum.conf
Section D.17.  extconfig.conf
Section D.18.  extensions.conf
Section D.19.  features.conf
Section D.20.  festival.conf
Section D.21.  iax.conf
Section D.22.  iaxprov.conf
Section D.23.  indications.conf
Section D.24.  logger.conf
Section D.25.  manager.conf
Section D.26.  meetme.conf
Section D.27.  mgcp.conf
Section D.28.  modem.conf
Section D.29.  musiconhold.conf
Section D.30.  osp.conf
Section D.31.  oss.conf
Section D.32.  phone.conf
Section D.33.  privacy.conf
Section D.34.  queues.conf
Section D.35.  res_odbc.conf
Section D.36.  rpt.conf
Section D.37.  rtp.conf
Section D.38.  sip.conf
Section D.39.  sip_notify.conf
Section D.40.  skinny.conf
Section D.41.  voicemail.conf
Section D.42.  vpb.conf
Section D.43.  zapata.conf
Section D.44.  zaptel.conf
Appendix E.  Asterisk Command-Line Interface Reference
!
abort halt
Section E.1.  add
Section E.2.  agi
Section E.3.  database
Section E.4.  iax2
Section E.5.  indication
Section E.6.  logger
Section E.7.  meetme
Section E.8.  pri
Section E.9.  remove
Section E.10.  restart
Section E.11.  set
Section E.12.  show
Section E.13.  sip
Section E.14.  stop
Section E.15.  zap
Colophon
About the Authors
Colophon
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
Previous Page
Next Page

9.3. Creating AGI Scripts in PHP

We promised we'd cover several languages, so let's go ahead and see what an AGI script in PHP looks like. The fundamentals of AGI programming still apply; only the programming language has changed. In this example, we'll write an AGI script to download a weather report from the Internet and deliver the temperature, wind direction, and wind speed back to the caller.

    #!/usr/bin/php -q
    <?php

The first line tells the system to use the PHP interpreter to run this script. The -q option turns off HTML error messages. You should ensure that there aren't any extra lines between the first line and the opening PHP tag, as they'll confuse Asterisk.

    # change this to match the code of your particular city
    # for a complete list of US cities, go to
    # http://www.nws.noaa.gov/data/current_obs/
    $weatherURL="http://www.nws.noaa.gov/data/current_obs/KMDQ.xml";

This tells our AGI script where to go to get the current weather conditions. In this example, we're getting the weather for Huntsville, Alabama. Feel free to visit the web site listed above for a complete list of stations throughout the United States of America.[*]

[*] We apologize to our readers outside of the United States for using a weather service that only works for U.S. cities. If you can find a good international weather service that provides its data in XML, it shouldn't be too hard to change this AGI script to work with that particular service. Once we find one, we'll update this script for future editions.

    # don't let this script run for more than 60 seconds
    set_time_limit(60);

Here, we tell PHP not to let this program run for more than 60 seconds. This is a safety net, which will end the script if for some reason it takes more than 60 seconds to run.

    # turn off output buffering
    ob_implicit_flush(false);

This command turns off output buffering, meaning that all data will be sent immediately to the AGI interface and will not be buffered.

    # turn off error reporting, as it will most likely interfere with
    # the AGI interface
    error_reporting(0);

This command turns off all error reporting, as it can interfere with the AGI interface. (You might find it helpful to comment out this line during testing.)

    # create file handles if needed
    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'));
    }

This section of code ensures that we have open file handles for STDIN, STDOUT, and STDERR, which will handle all communication between Asterisk and our script.

    # retrieve all AGI variables from Asterisk
    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]);
    }

Next, we'll read in all of the AGI variables passed to us by Asterisk. Using the fgets command in PHP to read the data from STDIN, we'll save each variable in the hash called $agi. Remember that we could use these variables in the logic of our AGI script, although we won't in this example.

    # print all AGI variables for debugging purposes
    foreach($agi as $key=>$value)
    {
        fwrite(STDERR,"-- $key = $value\n");
        fflush(STDERR);
    }

Here, we print the variables back out to STDERR for debugging purposes.

    #retrieve this web page
    $weatherPage=file_get_contents($weatherURL);

This line of code retrieves the XML file from the National Weather Service and puts the contents into the variable called $weatherPage. This variable will be used later on to extract out the pieces of the weather report that we want.

    #grab temperature in Fahrenheit
    if (preg_match("/<temp_f>([0-9]+)<\/temp_f>/i",$weatherPage,$matches))
    {
        $currentTemp=$matches[1];
    }

This section of code extracts the temperature (in Fahrenheit) from the weather report, using the preg_match command. This command uses Perl-compatible regular expressions[*] to extract out the needed data.

[*] The ultimate guide to regular expressions is O'Reilly's Mastering Regular Expressions, by Jeffrey Friedl.

    #grab wind direction
    if (preg_match("/<wind_dir>North<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='northerly';
    }
    elseif (preg_match("/<wind_dir>South<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='southerly';
    }
    elseif (preg_match("/<wind_dir>East<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='easterly';
    }
    elseif (preg_match("/<wind_dir>West<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='westerly';
    }
    elseif (preg_match("/<wind_dir>Northwest<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='northwesterly';
    }
    elseif (preg_match("/<wind_dir>Northeast<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='northeasterly';
    }
    elseif (preg_match("/<wind_dir>Southwest<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='southwesterly';
    }
    elseif (preg_match("/<wind_dir>Southeast<\/wind_dir>/i",$weatherPage))
    {
        $currentWindDirection='southeasterly';
    }

The wind direction is found through the use of preg_match (located in the wind_dir tags) and is assigned to the variable $currentWindDirection.

    #grab wind speed
    if (preg_match("/<wind_mph>([0-9.]+)<\/wind_mph>/i",$weatherPage,$matches))
    {
        $currentWindSpeed = $matches[1];
    }

Finally, we'll grab the current wind speed and assign it to the $currentWindSpeed variable.

    # tell the caller the current conditions
    if ($currentTemp)
    {
        fwrite(STDOUT,"STREAM FILE temperature \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE is \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"SAY NUMBER $currentTemp \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE degrees \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE fahrenheit \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
    }

    if ($currentWindDirection && $currentWindSpeed)
    {
        fwrite(STDOUT,"STREAM FILE with \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE $currentWindDirection \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE wx/winds \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"STREAM FILE at \"\"\n";)
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite(STDOUT,"SAY NUMBER $currentWindSpeed \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
        fwrite($STDOUT,"STREAM FILE miles-per-hour \"\"\n");
        fflush(STDOUT);
        $result = trim(fgets(STDIN,4096));
        checkresult($result);
    }

Now that we've collected our data, we can send AGI commands to Asterisk (checking the results as we go) that will deliver the current weather conditions to the caller. This will be achieved through the use of the STREAM FILE and SAY NUMBER AGI commands.

We've said it before, and we'll say it again: when calling AGI commands, you must pass in all of the required arguments. In this case, both STREAM FILE and SAY NUMBER commands require a second argument; we'll pass empty quotes escaped by the backslash character.

You should also notice that we call the fflush command each time we write to STDOUT. While this is arguably redundant, there's no harm in ensuring that the AGI command is not buffered and is sent immediately to Asterisk.

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

The checkresult function is identical in purpose to the checkresult subroutine we saw in our Perl example. As its name suggests, it checks the result that Asterisk returns whenever we call an AGI command.

    ?>

At the end of the file, we have our closing PHP tag. Don't place any whitespace after the closing PHP tag, as it can confuse the AGI interface.

We've now covered two different languages, in order to demonstrate the similarities and differences of programming an AGI script in PHP as opposed to Perl. The following things should be remembered when writing an AGI script in PHP:

  • Invoke PHP with the -q switch; it turns off HTML in error messages.

  • Turn off the time limit, or set it to a reasonable value (newer versions of PHP automatically disable the time limit when PHP is invoked from the command line).

  • Turn off output buffering with the ob_implicit_flush(false) command.

  • Open file handles to STDIN, STDOUT, and STDERR (newer versions of PHP may have one or more of these file handles already openedsee the code above for a slick way of making this work across most versions of PHP).

  • Read variables from STDIN using the fgets function.

  • Use the fwrite function to write to STDOUT and STDERR.

  • Always call the fflush function after writing to either STDOUT or STDERR.

9.3.1. The PHP AGI Library

For advanced AGI programming in PHP, you may want to check out the PHPAGI project at http://phpagi.sourceforge.net. It was originally written by Matthew Asham and is being developed by several other members of the Asterisk community.


Previous Page
Next Page
Asterisk