Asterisk-Java and AGI – some code
The library asterisk-java is of immediate use; its simplest use is based on fastagi protocol, and though it also exposes a Manager API, fastagi will be enough for this article purposes . To build our scripting engine we just need to subclass “BaseAgiScript” and override its “service” method. From the AgiRequest object we’ll grab the parameter we’re interested in, “script”, which is just the name of a beanshell script we want to execute. We’ll then pass a reference of our class, the AgiRequest and the AgiChannel object to the beanshell interpreter, launch a global script that defines some utility functions (see below) and finally launch the requested script. The approach is quite flexible, our script engine doesn’t need to be on the same computer the pbx is on, and we can add/modify our scripts on the fly without need for compilation or engine restart.
package org.beanizer.bagiserver;
import bsh.EvalError;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.asteriskjava.fastagi.AgiChannel;
import org.asteriskjava.fastagi.AgiException;
import org.asteriskjava.fastagi.AgiRequest;
import org.asteriskjava.fastagi.BaseAgiScript;
import bsh.Interpreter;
public class BAgiServer extends BaseAgiScript{
public BAgiServer() {
}
public void service(AgiRequest request, AgiChannel channel)
throws AgiException {
Interpreter interp=new Interpreter();
try {
String script=((request.getParameter("script")!=null) && !( request.getParameter("script").equals("") ) )
? request.getParameter("script") : "../scriptlib/default";
interp.source("scriptlib/bagi_import.bsh");
interp.set("bagi",this);
interp.set("request",request);
interp.set("channel",channel);
interp.source("scripts/"+ script +".bsh");
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} catch (EvalError ex) {
ex.printStackTrace();
}
}
}
A “fastagi-mapping.properties” file must exists on the classpath of the scripting engine and should contain something like this:
server.agi = BAgiServer
where “server.agi” is the agi script name called from asterisk, to be mapped to “BagiServer”, our class name.
We also need to add a call to our engine from asterisk’s dialplan, so in “extensions.conf” we’ll add something like:
exten => 1200,1,Agi(agi://localhost/server.agi?script=scriptname)
where “1200” can be an extension number of your choice, “localhost” is the server your scripting engine is hosted on, “server.agi” is the name we’ve mapped for our scripting engine and “scriptname” is the name of the beanshell script we want to invoke(without .bsh extension).
When someone will try to call extension “1200”, the specified agi script will be executed.
In our source code you can see we’re using two directories relative to the base classpath of BAgiServer:
- “scriptlib”, wich contains scripts “default”(called if no script name is passed as parameter), and “bagi_import” ( where we put generic functions we can use from other scripts).
- “scripts” , containing all the user defined scripts.
Obviously these paths are arbitrary and you can choose differently. Now let’s have a look at our beanshell scripts.
import org.asteriskjava.fastagi.AgiChannel;
import org.asteriskjava.fastagi.AgiException;
import org.asteriskjava.fastagi.AgiRequest;
import org.asteriskjava.fastagi.BaseAgiScript;
import org.beanizer.bagiserver.BAgiServer;
BAgiServer bagi;
AgiRequest request;
AgiChannel channel;
say(String string){
java.rmi.server.UID uid=new java.rmi.server.UID();
String command="echo '" + string + "' | text2wave -scale 8 -o "+ "/tmp/"+uid.toString() +".ulaw -otype ulaw -";
channel.exec("System",command );
channel.streamFile("/tmp/" +uid.toString());
channel.exec("System","rm /tmp/"+ uid.toString() +".ulaw" );
}
answer(){
channel.answer();
}
hangup(){
channel.hangup();
}
getDigits(int number){
StringBuffer sb=new StringBuffer();
for(int t=0;t<number;t++){
sb.append(channel.waitForDigit(10000));
}
return sb.toString();
}
wait(int millis){
(new Thread()).sleep(millis);
}
Here we basically import some useful classes, instantiate BAgiServer,AgiRequest and AgiChannel, and then define some functions. These just do what their names say, note how most of the time we just wrap AgiChannel’s methods (“answer” and “hangup” i.e.).
“getDigits”, makes use of the method “waitForDigit” of AgiChannel to wait for the user to input a number of digits specified by the function parameter and returns the entire number as a string.
“say”, is used to achieve text-to-speech. Instead of using festival or flite, here we use a lighter approach. Using the “exec” method of AgiChannel we launch “text2wave” on the pbx host (so text2wave must be installed there), and save the resulting sound file in a tmp directory with a random name.
We then use “streamFile” to stream the sound to the user and finally delete the sound file from the tmp directory.
answer(); wait(1000); say("No script defined"); wait(1000); hangup();
exten => 1200,1,Agi(agi://localhost/server.agi?script=test)
And here is the code:
import org.asteriskjava.fastagi.command.*; answer(); wait(1000); say("Please, enter extension code"); String code = getDigits(3); say("Trying to call extension:"); channel.sayDigits(code); channel.exec("ChannelRedirect", channel.getName() + "|from-internal|" + code + "|1"); hangup();
I’m sure you can appreciate how easy it is now to write a script. In this case the script answers and waits for a while, then asks for an extension number to be dialed, reads aloud the dialed code and then tries to redirect the call to the requested extension, using th “ChannelRedirect” asterisk command. Then it hangs up.
If you want to test this approach, here are a couple of guidelines:
- The directory structure should be like this:
basedir
scriptlib
bagi_import.bsh
default.bsh
scripts
your_scripts_go_here.bsh
lib
asteriskjava.jar (the name depends on the version)
beanshell.jar (the name depends on the version)
bagiserver.jar (it’s just org.beanizer.bagiserver.BAgiServer.class jarred)
fastagi-mapping.properties
- from “basedir”, start the engine including in your classpath the “lib” dir and all the jars it contains, and launching the class org.asteriskjava.fastagi.DefaultAgiServer
It’s quite easy to extend the engine by adding methods in the java class or, better, by adding functions to bagi_import.bsh