Monday, October 27, 2014

Asterisk IVR application tips

Finding good documentation on asterisk can be a chore, there are a lot of examples using old versions, and examples using simple dial plans for an office phone, but not much for advanced IVR applications.  Here are a few of the tips and tricks we have found over the last year using asterisk as an IVR application platform.


1. Use hangup handler

Your application will usually require some processing when a call ends.  Don't put h extensions in every context.  You can have issues if you need to update, or if you are in a macro/sub when the hangup happens.  Instead asterisk has a nice feature:
Set(CHANNEL(hangup_handler_push)=CALLDONECONTEXT,s,1)
this hangup handler makes sure your code runs in all cases.  Just make sure you call that line at every entry point into the asterisk dialplan

2. Use AGI

AGI, similar to what CGI was for web applications, allows you to use development resources in other programming languages to run advanced code.  PHP is used for command line scripts for our applications.  This allows us to use the same PHP classes and code from asterisk and from our website.
AGI(SCRIPT.php,${VAR1},${VAR2})
AGI scripts can get variables directly from asterisk; however, testing from the command line then becomes impossible.
Something to keep in mind asterisk is unavailable while the script is running, so keep the scripts simple, keep reading for examples where scripts are expected to run lone (credit card processing).

3. Use GoSub and Macro

Don't duplicate dialplan code, learn to use gosub and macro functions

4. Inject audio into a channel

There are numerous reasons to play audio into a channel, whispers for time remaining, etc.  The dialplan code for this is very simple:
[YOURCONTEXT]
exten => s,1,Answer()
 same => n,Wait(0.3)
 same => n,Playback(${AUDIO})
 same => n,Hangup()

exten => do_chanspy,1,Answer()
 same => n,Chanspy(${CHANNEL},qW)
 same => n,Hangup()
the script code is what originates the call and sets the variable with the audio to play, this uses the phpagi project, though we modified the send_request function to a new version that allows multiple variables.
if ($AsteriskAMI->connect($SERVER . ':5038','AMIUSERNAME','AMIPASSWORD')) {
 $asm_data[0]['name']='Channel';
 $asm_data[0]['value']="Local/s@YOURCONTEXT";
 $asm_data[1]['name']='Context';
 $asm_data[1]['value']='YOURCONTEXT';
 $asm_data[2]['name']='Exten';
 $asm_data[2]['value']='do_chanspy';
 $asm_data[3]['name']='Priority';
 $asm_data[3]['value']=1;
 $asm_data[4]['name']='Variable';
 $asm_data[4]['value']='CHANNEL=' . $channel;
 $asm_data[5]['name']='Variable';
 $asm_data[5]['value']='AUDIO=' . $audio;
 $call = send_request2('Originate',$asm_data);
}
This code originates a call to the asterisk system, uses the chanspy app to connect to the channel you want to play the audio on, plays the audio, then hangs up.

5. Redirect a channel to another context

This is useful in a number of cases when you need another channel to be moved based on the action of another, e.g. one caller is waiting on the system to call another then move both into a conference
ChannelRedirect(${CHANNEL},CONTEXT,s,1)

6. Don't allow endless loops

Not sure why, but you will get people or scripts that just sit on the system, so make sure all options that request input from the user.
[macro-Tries]
;arg1=current count
;arg2=max count
;arg3=context to goto if at max count
;arg4=exten
;arg5=priority
exten => s,1,GotoIf($[${ARG1} >= ${ARG2}]?${ARG3},${ARG4},${ARG5})

[CONTEXT]
exten => s,1,Set(Tries=0)
 same =>   n(start),Set(Tries=$[${Tries} + 1])
 same =>   n,Macro(Tries,${Tries},10,BadTries,s,1)
 same =>   n,GoSub(Press_Or_Say,start,1(AUDIOFILE,1,129*,DTMF))
 same =>   n,GoToIf($[${RETURN_CODE}=1]?opt${ENTRY},1)
 same =>   n,Goto(s,start)
this is the framework of an example that will only allow a subroutine, that gets the users menu input, and if the expected results aren't received will retry for 10 times, then forward to the BadTries context where you can give an audio message or just hangup.

7. Long running AGI

As mentioned above AGI holds up asterisk processing, you don't want seconds of dead air while a customer is on the phone.  Here is one example on running a script in the background, looping audio while waiting for a response.
GoSub(BackgroundProcess,s,1(PREAUDIO,PHPSCRIPT.php,VARIABLE,LOOPAUDIO,20,"","",${VAR1}
\"${VAR2}\"))



[TTMBackgroundProcess]
;ARG1 audio that plays
;ARG2 script to call - script has to accept channel as first parameter
;ARG3 variable to check - script must set this variable on the channel given using AMI
;ARG4 audio while processing
;ARG5 number of times to play audio recording before returning failure
;ARG6 return without playing the arg1 audio if script is done quickly
;ARG7 redirect channel immediatly on completed processing - script has to accept this as second parameter
;ARG8 arguments to pass to script, space separated
exten => s,1,Set(i=0)
 same =>   n,AGI(BackgroundProcess.php,${CDR(channel)},${ARG7},${ARG2},${ARG8})
 same =>   n,ExecIF($[${ARG6}=1 && "${${ARG3}}"!=""]?Return())
 same =>   n,Playback(${ARG1})
 same =>   n(waitingloop),ExecIF($["${${ARG3}}"!=""]?Goto(success,1))
 same =>   n,ExecIF($[${ARG5}<${i}]?Goto(failure,1))
 same =>   n,Playback(${ARG4})
 same =>   n,Set(i=$[${i} + 1])
 same =>   n,Goto(waitingloop)

exten => success,1,Set(BACKGROUNDPROCESS=TRUE)
 same =>   n,Return()

exten => failure,1,Set(BACKGROUNDPROCESS=FALSE)
 same =>   n,Return()
And the PHP script
#!/usr/bin/php
/* BackgroundProcess
 * runs a script in the background so AGI can return to asterisk
 * script should take channel and redirect as first to parameters
 * script should set variable that the dialplan expects, or redirects the user to the redirect context,channel,priority
 * 
 */
try {
if (!empty($argv[1]) && is_numeric($argv[1])) {
 $script_override_theater_id = $argv[1];
}

//this is an internal function that sets up the arg variables
$data = asterisk_arguments($argv, array(
 'channel' => 'string', //users channel inside asterisk
 'redirect' => 'string', //context,extension,priority to redirect to or empty if no redirect
 'script_name' => 'string', //php script to run, assumes in the agi-bin/TL directory of the project
 'parameters' => 'string' //parameters to pass to the script, should be space and quoted already
));

$result = array(
 // NO RESPONSE
);

if (!empty($data)) {
 //$AsteriskAGI->verbose(str_replace("\n","",print_r($data,true)));
 $cmd_start = '/var/lib/asterisk/agi-bin/TL/' . $data['script_name'];
 $cmd_end = ' > /dev/null &';
 
 $cmd = $cmd_start . ' "' . $data['channel'] . '" ' .      
  '"' . $data['redirect'] . '" ' .
  $data['parameters'] . 
  $cmd_end;
 $AsteriskAGI->verbose(str_replace('"','|',$cmd));
 exec($cmd);
}

} catch (exception $e) {

}

2 comments:

  1. An IVR application gives pre-recorded voice responses to fitting circumstances, keypad signal rationale, access to important information and, possibly, the capacity to record voice data for later taking care of. Utilizing PC telephony coordination (CTI), IVR applications can hand off a call to an individual who can see information identified with the guest at a showcase.

    IVR phone system

    ReplyDelete
  2. Very cool article, it's just a virtual magic of some kind))
    Cool, I found your article very interesting and informative, thanks to the good examples of the script concept.
    Richard Brown virtual data room

    ReplyDelete