Introduzione
Asterisk, il noto PBX , espone grandi potenzialità di integrazione. Nello specifico, a noi interessa AGI (Asterisk Gateway Inteface), una sorta di API per interagire con il motore. Oggi introdurremo l'interfaccia java di AGI: asterisk-java. Utilizzeremo questa semplice libreria per costruire appunto un motore di scripting. Il linguaggio scelto è BeanShell, ma è possibile sostituirlo con un qualsiasi linguaggio supportato dal jdk Sun (python,ruby,groovy, javascript etc.). Asterisk-Java e AGI - Un pò di codice
La libreria asterisk-java è di utilizzo immediato; il più semplice uso è mediante il protocollo fastagi, e sebbene esponga anche una Manager API, fastagi sarà sufficiente per i nostri scopi attuali. Per costruire il nostro motore di scripting dobbiamo solo "subclassare" la classe "BaseAgiScript" e reimplementare il metodo "service". Dall'oggetto "AgiRequest" otterremo il parametro ai quali siamo interessati, "script", che non è nient'altro che il nome dello script beanshell che vogliamo eseguire. Passeremo poi una istanza della nostra classe, gli oggetti AgiRequest e AgiChannel all'interprete beanshell, lanceremo uno script globale che definisca alcune funzioni di utilità (vedere sotto) e alla fine lanceremo lo script richiesto. L'approccio è piuttosto flessibile, il nostro motore di scripting non necessita di risiedere sulla stessa macchina del pbx, e possiamo aggiungere/modificare script al volo senza ricompilazioni o ripartenze del motore. 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(); } } }
Un file "fastagi-mapping.properties" deve esistere nel "classpath" del motore di scripting e deve contenere qualcosa del tipo: server.agi = BAgiServer
dove "server.agi" è il nome dello script chiamato da asterisk, da mapparsi a "BagiServer", il nome della nostra classe. Abbiamo anche bisogno di aggiungere una chiamata al nostro motore dal "dial plan" di asterisk, per cui in "extensions.conf" aggiungeremo: exten => 1200,1,Agi(agi://localhost/server.agi?script=scriptname)
dove "1200" è il numero di interno scelto, "localhost" è il server sul quale risiede il motore di scripting, "server.agi" il nome che abbiamo mappato per il nostro motore e "scriptname" il nome dello script beanshell da lanciare(senza estensione .bsh). Quando qualcuno tenterà di chiamare l'estensione "1200", lo script specificato verrà lanciato. Nel nostro codice sorgente sono indicate 2 directory relative al "classpath" di base di BAgiServer: - "scriptlib", che contiene gli script "default"(chiamato se nessuno script viene indicato come parametro), e "bagi_import" ( dove mettiamo funzioni generiche utilizzabili da tutti gli script).
- "scripts" , contenente tutti gli script definiti dall'utente.
Ovviamente questi percorsi sono arbitrari e possiamo sceglierli differentemente. Ora diamo un'occhiata ai nostri script beanshell.
bagi_import.bsh
Questo è uno script d'utilità automaticamente incluso ogni volta che il motore viene chiamato, quindi è qui che andranno tutte le funzioni d'utilizzo generale. Ecco come si presenta:
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); }
Importiamo alcune classi necessarie, istanziamo BAgiServer, AgiRequest e AgiChannel, e definiamo alcune funzioni. Queste fanno esattamente ciò che il loro nome indica; da notare come il più delle volte siano solo involucri(wrapper) di metodi di AgiChannel("answer" e "hangup" ad es.). "getDigits", fa uso del metodo "waitForDigit" di AgiChannel per aspettare l'inserimento da parte dell'utente di una quantità di cifre numerica indicata dal parametro della funzione e restituisce l'intero numero inserito come stringa. "say", è usato per il text-to -speech(conversione da testo ad audio) . Invece di usare Festival o Flite, quì ci serviamo di un approccio più leggero. Mediante il metodo "exec" di AgiChannel lanciamo "text2wave" sul pbx(quindi text2wave deve essere installato sul pbx,) e ne salviamo il risultato in un file audio in una directory tmp con un nome casuale. Poi, mediante il metodo "streamFile" inviamo l'audio all'utente e infine cancelliamo il file audio temporaneo dalla directory tmp.
default.bsh Questo viene chiamato quando non venga indicato un script da eseguire. answer(); wait(1000); say("No script defined"); wait(1000); hangup();
Come potete osservare, utilizza le funzioni definite in bagi_import.bsh. Risponde alla chiamata(telefonica), attende 1 secondo, scandisce una frase, attende un altro secondo e riattacca.
test.bsh Ora un esempio di script definito dall'utente. Lo script deve risiedere nella directory "scripts", e la chiamata agi da asterisk deve essere qualcosa del tipo:
exten => 1200,1,Agi(agi://localhost/server.agi?script=test) Ecco il codice: 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();
Sono certo che possiate apprezzare quanto sia a questo punto semplice scrivere uno script. In questo caso lo script attende un secondo, chiede vocalmente l'inserimento di un numero di interno, legge ad alta voce il numero inserito e tenta di redirigervi la chiamata. Poi riattacca. Un paio di linee guida per chi voglia sperimentare: - La struttura delle directory deve essere::
basedir scriptlib bagi_import.bsh default.bsh scripts your_scripts_go_here.bsh lib asteriskjava.jar (il nome dipende dalla versione) beanshell.jar (il nome dipende dalla versione) bagiserver.jar (è la sola classe org.beanizer.bagiserver.BAgiServer.class in un file jar) fastagi-mapping.properties - da "basedir", includere nel classpath la directory "lib" e tutti i jar che contiene, e lanciare la classe 'org.asteriskjava.fastagi.DefaultAgiServer'.
Conclusioni Quello proposto è solo lo scheletro di ciò che potrebbe diventare un completo motore di scripting agi per sviluppatori java (o ruby,python,javascript,groovy) alle prese con progetti di integrazione di Asterisk con piattaforme differenti. E' piuttosto semplice estendere il motore aggiungendo metodi alla classe java o, meglio, aggiungendo funzioni allo script bagi_import.bsh Hasta la proxima. |