Aus Linux-Magazin 02/2008

Workshop: Die eigene Asterisk-Anlage - Teil 2

© hulibu, photocase.com

Im zweiten Teil des großen Telefonanlagen-Workshops erfahren Sie, wie Sie komplexe Komfortfunktionen programmieren, beispielsweise interaktive Sprachmenüs, und welche Mechanismen Asterisk im Einzelnen dafür in seinem Ärmel hat.

Im ersten Teil der Artikelserie [1] haben Sie gelernt Asterisk [2] zu installieren und so zu konfigurieren, dass Sie intern und extern über einen SIP-Provider telefonieren können. In dieser Folge bekommen Sie das Handwerkszeug, um dieser noch recht einfachen Telefonanlage Komplexität und Intelligenz einzuhauchen. Los geht\’s mit dem Interactive Voice Response System.

Das IVR stellt Automaten bereit, die den Anrufer anhand von Sprachmenüs führen und weiterverbinden – also ungleich mehr als ein Anrufbeantworter (Abbildung 3). Um eine IVR aufzubauen, müssen Sie natürlich zuvor Sprachdateien aufnehmen. In Asterisks Köcher steckt dafür die Applikation »Record()«. Im Gegensatz zu »Playback()« verlangt »Record()«, dass die Endung des übergebenen Dateinamens zu dem gewünschten Codec passt.

Mit dem Dialplan in Listing 1 können Sie durch Anrufen der 9900 bis 9999 unkompliziert 100 verschiedene Sprachdateien – auch Sprach- oder Voiceprompts genannt – aufnehmen. Sie beenden jede Aufnahme durch Auflegen, die [#]-Taste oder durch Warten. Eine lange Pause am Ende eines Sprachprompts macht sich jedoch schlecht beim Aneinanderketten von mehreren Prompts.

Listing 1:
»extensions.conf« (1)

01 exten => _99XX,1,Answer()
02 exten => _99XX,2,Wait(1)
03 exten => _99XX,3,Record(/tmp/sprachprompt${EXTEN:2}.wav)
04 exten => _99XX,4,Wait(1)
05 exten => _99XX,5,Playback(/tmp/sprachprompt${EXTEN:2})
06 exten => _99XX,6,Hangup()

Um mit den Sprachprompts ein IVR zu realisieren, benutzen Sie die Applikation »Background(Dateinamen)«. Sie spielt den angegebenen Voiceprompt vor und gibt DTMF-Eingaben während des Abspielens an den Dialplan weiter. Der verarbeitet die Touchtones wie ganz normale Anrufe. Wenn Sie während eines Background die [8][8] eingeben, dann sucht Asterisk im aktuellen Context nach der Extension 88 und arbeitet ihn von der Priorität 1 beginnend ab.

Eine einfache Übung: Besprechen Sie die folgenden Voiceprompts und speichern Sie sie danach im Verzeichnis »/var/lib/asterisk/sounds/« ab:

  • »eingabe.wav«: “Bitte drücken Sie auf
    Ihrer Telefontastatur eine Zahl.”
  • »gerade.wav«: “Diese Zahl ist
    gerade.”
  • »ungerade.wav«: “Diese Zahl ist
    ungerade.”

Mit dem Dialplan aus Listing 2 probieren Sie die IVR-Funktion aus. Achtung: Die Eingabe muss während der Abarbeitung von »Background()« passieren! Wollen Sie dem User eine Möglichkeit geben, weitere Eingaben zu machen, benutzen Sie die vorgefertigten Asterisk-Prompts »silence/1« bis »silence/9«:

exten => 30,1,Answer()
exten => 30,n,Background(eingabe)
exten => 30,n,Background(silence/5)
exten => 30,n,Hangup()

Die Zahlen stehen für die Anzahl der Sekunden, in der Asterisk Stille abspielt.

Listing 2:
»extensions.conf« (2)

01 exten => 30,1,Answer()
02 exten => 30,n,Background(eingabe)
03 exten => 30,n,Hangup()
04 
05 exten => _[13579],1,Playback(ungerade)
06 exten => _[13579],n,Hangup()
07 
08 exten => _[2468],1,Playback(gerade)
09 exten => _[2468],n,Hangup()

Mehrstufige IVRs

Das Problem des gezeigten IVR-Konzepts ist, dass es immer innerhalb eines Context arbeitet und deshalb mehrstufige IVRs nur über mehrstellige Ziffern abbilden kann (Abbildung 1 und passendes Listing 3). Das ist natürlich unbefriedigend, aber ein mit der Applikation »Goto()« lösbares Problem. Goto springt wie im guten alten Basic an eine beliebige Stelle des Programms.

Listing 3:
»extensions.conf« (3)

01 [ivr]
02 exten => 50,1,Answer()
03 exten => 50,n,Background(beispielmenue)
04 exten => 50,n,Background(silence/5)
05 exten => 50,n,Hangup()
06 
07 ; Da in einem Context eine Extension nur
08 ; einmal vorhanden sein kann, realisiert
09 ; das Beispiel die Menueebenen durch
10 ; fortlaufende (auch zweistellige) Zahlen
11 ;
12 exten => 1,1,Background(dummy1)
13 exten => 1,n,Background(silcence/5)
14 exten => 1,n,Hangup()
15 
16 exten => 2,1,Playback(dummy2)
17 exten => 2,n,Hangup()
18 
19 exten => 3,1,Playback(dummy3)
20 exten => 3,n,Hangup()
21 
22 exten => 4,1,Playback(dummy4)
23 exten => 4,n,Hangup()

In Asterisk hüpft ein »Goto(10)« zur Priorität 10 in der aktuellen Extension. »Goto(555,1)« geht zur Extension 555 und da zur Priorität 1. Spannend ist »Goto(produktion,20,5)«, das im Context »produktion« die Extension 20 und Priorität 5 anspringt. Mit diesem Trick können Sie bei der Eingabe einer einstelligen Zahl auf der Menü-Ebene 1 auf eine zweistellige Extension in einem anderen Context verzweigen und haben dort wieder die einstelligen Zahlen von 0 bis 9 als Eingabeoption zur Verfügung (Abbildung 2 und zugehöriges Listing 4).

Listing 4:
»extensions.conf« (4)

01 [ebene0]
02 exten => 50,1,Answer()
03 exten => 50,n,Background(beispielmenue)
04 exten => 50,n,Background(silence/5)
05 exten => 50,n,Hangup()
06 
07 ; Um in der naechsten Ebene die einstelligen
08 ; Zahlen fuer Eingaben frei zu halten, sollte
09 ; man an eine beliebige mehrstellige Extension
10 ; springen.
11 ;
12 exten => 1,1,Goto(ebene1,99,1)
13 
14 exten => 2,1,Playback(dummy2)
15 exten => 2,n,Hangup()
16 
17 [ebene1]
18 exten => 99,1,Background(dummy1)
19 exten => 99,n,Background(silcence/5)
20 exten => 99,n,Hangup()
21 
22 exten => 1,1,Playback(dummy3)
23 exten => 1,n,Hangup()
24 
25 exten => 2,1,Playback(dummy4)
26 exten => 2,n,Hangup()

Wer die »n«-Priorität benutzt, muss für Goto Sprungadressen-Labels definieren. Das Beispiel

exten => 40,1,Answer()
exten => 40,n(anfang),Playback(hello-world)
exten => 40,n,Wait(0.5)
exten => 40,n,Goto(anfang)

zeigt, dass Asterisk die Labelnamen-Definition in Klammern nach dem »n« in der Extension erwartet.

Jedes IVR sollte Falscheingaben abfangen und den User wieder auf ein Grundmenü bringen. Eine nicht definierte Eingabe fangen Sie mit der »i«-Extension ab. Im Gerade/ungerade-Beispiel behandelt die Zeile

exten => i,1,Goto(30,2)

die Eingabe der Null und bringt den User wieder ins Grundmenü.

Abbildung 1: Asterisks IVR-Konzept arbeitet nur innerhalb eines Context (siehe Listing 3).

Abbildung 1: Asterisks IVR-Konzept arbeitet nur innerhalb eines Context (siehe Listing 3).

Abbildung 2: Dank Goto verzweigt Listing 4 bei der Eingabe einer Ziffer auf der Menü-Ebene 1 auf eine zweistellige Extension in einem anderen Context.

Abbildung 2: Dank Goto verzweigt Listing 4 bei der Eingabe einer Ziffer auf der Menü-Ebene 1 auf eine zweistellige Extension in einem anderen Context.

Verzweigungen im Dialplan

Als essenzielles Programmier-Element fehlt noch eine Möglichkeit, in Dialplänen bedingungsgesteuert zu verzweigen. Dies geht mit der Applikation »GotoIf()«. Die zugehörige Bedingung erwartet Asterisk als Expression in eckigen Klammern mit einem vorgestellten Dollarzeichen. Die Asterisk-Community bemüht als Beispiel für eine »GotoIf()«-Anwendung gern das Exfreundin-Szenario:

exten => 1234,1,GotoIf($[${CALLERID(num)} = 03012345678]?10:20)
exten => 1234,10,VoiceMail(1234,u)
exten => 1234,20,Dial(SIP/1234)

Wenn der Anruf von der Rufnummer 03012345678 kommt, verbindet ihn das System direkt mit der Voicemailbox 1234. Ansonsten stellt es das Gespräch ganz normal an das Telefon 1234 durch.

Eine Auflistung aller Applikationen bekommen Sie im CLI mit »core show applications«. Hilfe zu einer bestimmten Applikation gibt\’s mit »core show application Application-Name« (siehe Auszug in Listing 5). Eine deutschsprachige Beschreibung aller Applikationen finden Sie online unter [3].

Listing 5: »*CLI> core
show application VoiceMail«

01 -= Info about application 'VoiceMail' =-
02 
03 [Synopsis]
04 Leave a Voicemail message
05 
06 [Description] VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This application allows


the calling party to leave a message for the specified list of mailboxes. When multiple mailboxes are


specified, the greeting will be taken from the first mailbox specified. Dialplan execution will stop


if the specified mailbox does not exist.
07 
08 The Voicemail application will exit if any of the following DTMF digits are received:
09     0 - Jump to the 'o' extension in the current dialplan context.
10     * - Jump to the 'a' extension in the current dialplan context.
11 [...]

Die am häufigsten verwendete Variable in Dialplänen ist »${EXTEN}«. Sie gibt die gewählte Extension, also das Ziel des Anrufs aus. Da Asterisk weder über einen Debugger noch über sonstige Programmiertools verfügt, muss zum Anzeigen von Variablen die Applikation »NoOp()« herhalten. Diese No Operation listet den Inhalt der Klammer ab einem Verbose-Level von 3 (einstellbar mit »core set verbose 3«) im CLI auf. Zum Beispiel:

exten => _XXX,1,Answer()
exten => _XXX,n,NoOp(Anruf fuer ${EXTEN})
exten => _XXX,n,Playback(hello-world)
exten => _XXX,n,Hangup()

Systemvariablen wie »${EXTEN}« lassen sich nur lesen, selbst definierte Variablen lesen und schreiben. Fürs Schreiben zeichnet die Funktion Set() verantwortlich, deren Syntax lautet »Set(VARIABLENNAME=Wert[,Option])«.

Asterisk kennt zwei Arten von Variablen: Channel und globale. Channel-Variablen gelten immer nur im aktuellen Gespräch und sind außerhalb des Gesprächs nicht für andere Gespräche (Channels) nutzbar. Hingegen dürfen der gesamte Dialplan und alle Channel globale Variablen übergreifend setzen und lesen. Eine globale Variable lässt sich mit der Option »,g« in der »Set()«-Funktion bearbeiten. Ohne »,g« geht Asterisk immer von Channel-Variablen aus. Das Beispiel

[globals]
ANZAHL=0
[meine-telefone]
exten => 1234,1,Set(ANZAHL=$[${ANZAHL} + 1])
exten => 1234,n,Answer()
exten => 1234,n,SayNumber(${ANZAHL})
exten => 1234,n,Hangup()

zählt die Anzahl der Gespräch seit Systemstart und spielt sie dem Anrufer mit der Applikation »SayNumber()« vor. Sie setzen eine globale Variable normalerweise beim Starten von Asterisk und auch bei jedem Reload neu. Das kann in der »extensions.conf« im Bereich »[globals]« erfolgen wie oben im Beispiel.

Abbildung 3: Der sprechende Urvater, das Alibiphon der Firma Willy Müller aus dem Jahr 1957, spielte dem Anrufer einen aufgezeichneten Text vor, beispielsweise die Geschäftsöffnungszeiten. Mit Asterisk und einem Linux-PC kann heute jedermann ein Interactive Voice Response System konfigurieren.

Abbildung 3: Der sprechende Urvater, das Alibiphon der Firma Willy Müller aus dem Jahr 1957, spielte dem Anrufer einen aufgezeichneten Text vor, beispielsweise die Geschäftsöffnungszeiten. Mit Asterisk und einem Linux-PC kann heute jedermann ein Interactive Voice Response System konfigurieren.

Rechnen lernen

Asterisk ist kein Taschenrechner und hantiert nur mit ganzzahligen, maximal 18-ziffrigen Integern. Für normale Operationen innerhalb eines Dialplans reicht das. Rechenoperationen müssen stets innerhalb einer Expression stehen, die »$[« einleitet und »]« beendet. Im gegen Ende des vorigen Abschnitts abgedruckten Code finden Sie eine solche Rechenoperation als Addition bei der Priorität 1.

Asterisk kann addieren (»+«), subtrahieren (»-«), multiplizieren (»*«) und dividieren (»/«). Achten Sie innerhalb einer Expression darauf, dass Sie die Variablen und die Rechenfunktionen mit Leerzeichen voneinander trennen: Für Asterisk ist »$[10+20]« der String mit dem Inhalt »10+20«, erst »$[10 + 20]« ergibt 30.

Strings trennen und fügen

Wenn Sie aufgefordert sind, Strings zusammenzufügen, schreiben Sie sie einfach nacheinander. Eine Telefonnummer gilt auch als String. Wollen Sie allen Calls die Phantasievorwahl 0123456 voranstellen, realisieren Sie das wie folgt:

exten => _X.,1,Dial(SIP/0123456${EXTEN})

Auf diese Weise können Sie sehr leicht mehrere Call-by-Call-Provider ansteuern. Mit einem Doppelpunkt trennen Sie den Anfang eines Strings ab. Soll Asterisk die führende Null bei Gesprächen ins Festnetz nicht mitwählen, unterbinden Sie dies mit:

exten => _0X.,1,Dial(SIP/${EXTEN:1})

Die Zeile wertet nur den Teil des Strings ab der ersten Stelle aus (die Zählung beginnt bei Null). Mit einem zweiten Doppelpunkt können Sie noch die gewünschte Länge angeben. Wer warum auch immer die Vorwahl bei Gesprächen nach Berlin in der Variablen »VAR1« speichern will, benutzt einen Ausdruck wie den folgenden:

exten => _0030X.,1,Set(VAR1=${EXTEN:1:3})

Auch gut: Mit einer negativen Zahl kann man sogar am Ende des Strings anfangen zu zählen.

Gleichwohl ist Asterisks String-Behandlung stiefmütterlich zu nennen. So belegt »Set(VAR1=Das ist ein Haus.)« die Variable »VAR1« nur mit dem Wert »Das«. Wer erwartungsgemäß den ganzen Satz auf »VAR1« abbilden will, muss »Set(VAR1=”Das ist ein Haus.”)« schreiben. Das ist nicht weiter schlimm. Dummerweise ergeben aber »Set(VAR1=”foo”)« und »Set(VAR2=foo)« nicht Variablen gleichen Inhalts. Denn zu »VAR1« gehören die Anführungszeichen und zu »VAR2« nicht. Würde man mit »Set(VAR3=${VAR1}${VAR2})« beide Werte zusammenführen, kommt für »VAR3« »”foo”foo« heraus.

Funktionen

In der Hauptsache unterscheiden sich Funktionen und Applikation darin, dass der Programmierer eine Funktion innerhalb einer Applikation aufrufen darf, aber nicht umgekehrt. So kann innerhalb der Applikation »NoOp()« die Funktion »CALLERID(num)« stehen und die Nummer des Anrufers im CLI ausgeben. Andersrum ergibt ein Aufruf keinen Sinn und ist auch nicht erlaubt.

Mit »CALLERID()« kennen Sie schon die wichtigste Funktion. Als Parameter akzeptiert sie »name« (also den Namen des Anrufers), »num« (dessen Nummer) oder »all« (beides kombiniert). »CALLERID()« kann die Caller-ID sowohl lesen als auch schreiben. Letzteres ist zum Beispiel nötig, wenn Sie intern eine führende Null zum Rauswählen und zusätzlich die eingebauten Rückruflisten in den Telefonen benutzen.

Wenn der Anrufer mit der Nummer 03012345678 anruft, dann wird in der Rückrufliste des Telefons genau diese Nummer auftauchen. Da ihr aber die führende Null für das virtuelle Amt fehlt, würde der Rückruf auf der 3012345678 landen und damit im Telefonanlagen-Nirvana. Die Funktion »CALLERID()« erweitert daher eingehende Gespräche um die führende Null:

exten => _X.,1,Set(CALLERID(num)=0${CALLERID(num)})

Analog zu den Applikationen liefert im CLI der Befehl »core show functions« die Liste aller Funktionen.

Local Channels

Angenommen für Ihre betriebliche Organisation wäre es wünschenswert, dass bei einem Anruf auf die Nummer 1000 zuerst das SIP-Telefon 2000 und zehn Sekunden später auch das Telefon 2001 klingeln soll. Das kann zum Beispiel beim Betrieb einer Hotline sinnvoll sein, die regulär mit einem Mitarbeiter am Apparat 2000 besetzt ist. Wenn der mal für kleine Techniker muss, könnte ein ebenfalls sachkundiger Kollege, vielleicht aus der Entwicklungsabteilung, mit der Nummer 2001 einspringen.

Asterisk hält zum Abbilden solcher Situationen Pseudochannels vor, um andere Extensions so aufzurufen, als wäre der Anruf von extern gekommen. Das Hotline-Beispiel ist nur die einfachste Form eines solchen Local Channel. Das verzögerte Weiterleiten erreicht:

exten => 1000,1,Dial(Local/2000&Local/2001)
exten => 2000,1,Dial(SIP/2000)
exten => 2001,1,Wait(10)
exten => 2001,2,Dial(SIP/2001)

Das Ampersand »&« in der ersten Codezeile teilt der »Dial()«-Applikation mit, dass sie gleichzeitig bei beiden angegebenen Telefonen anrufen möge.

Asterisks Datenbank

Immer wenn Werte auch über einen Reboot des Servers oder Asterisks zu bewahren sind, reichen Variablen nicht aus, Sie brauchen eine Datenbank. Wegen einfacher Dinge, zum Beispiel einer programmierbaren Rufumleitung, gleich eine externe Datenbank einzubinden, schösse aber mit Kanonen auf Spatzen. Denn Asterisk hat eine kleine Berkley-DB bereits eingebaut, die Sie mit der Funktion »DB(Gattung/Schlüssel)« beschreiben und lesen können.

Dabei dürfen Sie auch mehrere Keys mit »DB(Gattung/Schlüssel_1/Schlüssel_2/Schlüssel_n)« hintereinanderschachteln und so mehrere Ebenen darstellen. Einen Wert in die Datenbank schreiben, gelingt mit »Set(DB(obst/apfel)=20)«, den gleichen Datenbankeintrag wieder auslesen und in der Variablen »VAR1« speichern mit »Set(VAR1=${DB(obst/apfel)})«. Die Funktion »DB_DELETE(Gattung/Schlüssel)« löscht Datenbankeinträge. Da Sie eine Funktion nicht einfach so aufrufen können, gehen Sie dabei den Umweg über die »NoOp()«-Applikation.

Um den Eintrag »obst/apfel« aus der Datenbank zu löschen, starten Sie deshalb »NoOp(${DB_DELETE(obst/apfel)})«. Zwar wären auch andere Applikationen möglich, aber »NoOp()« eignet sich meist am besten, weil es keine Seiteneffekte produziert. Ist ein Eintrag gelöscht, findet sich der Wert immer in der Channel-Variablen »${DB_RESULT}«.

Wollen Sie nur prüfen, ob ein Eintrag in der Datenbank vorhanden ist, nehmen Sie die Funktion »DB_EXISTS(Gattung/Schlüssel)«. Sie gibt 1 bei einem vorhandenen und 0 bei einem nicht vorhandenen Eintrag zurück. Den Eintrag selber nimmt wieder die Channel-Variable »${DB_RESULT}« auf.

Ein Beispiel: Der folgende Dialplan realisiert eine programmierbare Rufumleitung (Call Forwarding) für interne Benutzer. Diese rufen die Servicenummer *5 an und geben danach das Ziel der gewünschten Rufumleitung ein, beispielsweise [*][5][1][2][3] für eine Umleitung auf die 123. Wählt ein Benutzer [*][5] ohne Rufnummer, löscht er die eingestellte Umleitung.

Dabei ist zu berücksichtigen, dass der Besitzer des Zieltelefons vielleicht seinerseits eine Umleitung eingerichtet hat. Hier kommt der Local Channel ins Spiel. Mit dem können Sie innerhalb des Dialplans die gewünschte Zielnummer normal anrufen. Ein vertrackter Loop aus Rufumleitungen bräuchte dann noch mal eine Sonderhandlung. Den kompletten Dialplan mit ausführlichen Kommentaren finden Sie in Listing 6.

Listing 6:
»extensions.conf« (5)

01 ; Die Datenbank speichert eine neue Rufumleitung auf dem Feld (CF/Rufnummer des Ziel der Rufumleitung Teilnehmers).
02 ; SayDigits() liest das dem Anrufer vor.
03 ; 
04 exten => _*5X.,1,Answer()
05 exten => _*5X.,n,Set(DB(CF/${CALLERID(num)})=${EXTEN:2})
06 exten => _*5X.,n,Playback(call-forwarding)
07 exten => _*5X.,n,SayDigits(${EXTEN:2})
08 exten => _*5X.,n,Hangup()
09 
10 ; Loescht einen evt. vorhandenen Eintrag in der Datenbank.
11 ;
12 exten => *5,1,Answer()
13 exten => *5,n,NoOp(${DB_DELETE(CF/${CALLERID(num)})})
14 exten => *5,n,Playback(call-fwd-cancelled)
15 exten => *5,n,Hangup()
16 
17 ; Prueft bei Anruf, ob fuer die Zielrufnummer ein Eintrag in der Datenbank vorhanden ist. 
18 ; Falls ja, wird ueber einen Local Channel an diesen Eintrag weitervermittelt.
19 ; Dadurch lassen sich auch mehrere Rufumleitungen in Reihe schalten.
20 ; Falls keine Rufumleitung gesetzt ist, startet normal ein Dial().
21 ; Ist beim Teilnehmer besetzt oder geht er nach 120 Sekunden nicht ran, greift der Voicemail-Aufruf.
22 ;
23 exten => _X.,1,GotoIf($[${DB_EXISTS(CF/${EXTEN})} = 1]?CF:normal)
24 exten => _X.,n(CF),Dial(local/${DB_RESULT}@meine-telefone)
25 exten => _X.,n(normal),Dial(SIP/${EXTEN},120)
26 exten => _X.,n,VoiceMail(${EXTEN},u)

Teile des Dialplans outsourcen

Wenn Sie mal mit den Programmiermöglichkeiten im Dialplan an Grenzen stoßen, stehen immer noch AGI-Skripte bereit. Das Asterisk Gateway Interface ist das Gleiche wie ein CGI für Apache. Ein AGI-Skript rufen Sie im Dialplan mit der Applikation »AGI()« auf, es übernimmt ab diesem Zeitpunkt die Kontrolle in der aktuellen Extension. Skript und Asterisk kommunizieren über Stdin und Stdout.

Die Liste aller AGI-Steueranweisungen, (»agi show« im CLI erzeugt eine) ist kürzer als die Liste der Asterisk-Applikationen und -Funktionen. Denn die reine Programmierlogik übernimmt ja das AGI-Skript und damit die benutzte Programmiersprache. Wenn nicht anders konfiguriert, gehören AGI-Skripte ins Verzeichnis »/var/lib/asterisk/agi-bin/«.

Als Beispiel für ein AGI-Skript dient »lotto.php« aus Listing 7, ein PHP-Programm, das zufällige Lottozahlen ausgibt. Fügen Sie den Code

exten => 1234,1,Answer()
exten => 1234,2,AGI(lotto.php)
exten => 1234,3,Hangup()

in Ihren Dialplan ein, um das Programm in der Extension »1234« auszuführen.

Listing 7:
»lotto.php«

01 #!/usr/bin/php -q
02 <?php
03 # Sicherheitseinstellung: Das Skript laeuft 
04 # nicht laenger als 8 Sekunden.
05 #############################################
06 set_time_limit(8);
07 
08 # Output Buffer deaktivieren. Alternativ 
09 # koennte man nach jeder Ausgabe 
10 # fflush(STDOUT); aufrufen. 
11 #############################################
12 ob_implicit_flush();
13 
14 # PHP Error Reporting deaktivieren.
15 #############################################
16 error_reporting(0);
17 
18 # Die Kommunikation mit Asterisk benoetigt 
19 # STDIN- und STDOUT-Filehandles.
20 #############################################
21 if (!defined('STDIN'))
22   define('STDIN' , fopen('php://stdin' , 'r'));
23 if (!defined('STDOUT'))
24   define('STDOUT', fopen('php://stdout', 'w'));
25 if (!defined('STDERR'))
26   define('STDERR', fopen('php://stderr', 'w'));
27 
28 # Die von Asterisk uebergebenen Variablen und 
29 # Werte auslesen und im Array $agi speichert.
30 #############################################
31 
32 $agi = array();
33 
34 while (!feof(STDIN))
35 {
36   $tmp = trim(fgets(STDIN,4096));
37   if (($tmp == '') || ($tmp == "n"))
38     break;
39   $var1 = split(':',$tmp);
40   $name = str_replace('agi_','',$var1[0]);
41   $agi[$name] = trim($var1[1]);
42 }
43 
44 # Ein Array mit 6 zufaelligen und nicht dop-
45 # pelten Zahlen von 1 bis 49 generieren.
46 #############################################
47 
48 $Lottozahlen = array();
49 do {
50   $Zahl = rand(1,49);
51   if (array_search($Zahl, $Lottozahlen) == FALSE) {
52     $Lottozahlen[] = $Zahl;
53   }
54 } while (count($Lottozahlen) < 6);
55 
56 # Vor der ersten Ansage eine Sekunde warten.
57 #############################################
58 fwrite(STDOUT,"EXEC Wait 1 ""n");
59 fflush(STDOUT);
60 
61 # Die Zahlen nacheinander vorlesen. Zwischen
62 # den einzelnen Zahlen gibt es immer eine
63 # Pause von einer Sekunde.
64 #############################################
65 foreach ($Lottozahlen as $value) {
66   fwrite(STDOUT,"SAY NUMBER $value ""n");
67   fflush(STDOUT);
68   fwrite(STDOUT,"EXEC Wait 1 ""n");
69   fflush(STDOUT);
70 }
71 ?>

Asterisk fernsteuern

Ein ungepatchtes Asterisk kennt im Grundsatz drei Möglichkeiten, um sich von außen fernsteuern zu lassen:

  • Per »asterisk -rx “Befehl”«
  • Mit Callfiles
  • Über das Asterisk Manager Interface

Die »-rx«-Option ist eine praktische Schnittstelle, um in Bash-Skripten Variablen und Datenbankwerte aus Asterisk zu zapfen und auch dorthin zu schicken. So listet ein simples »asterisk -rx “sip show peers”« – auf der Linux-Konsole eingetippt oder in einem Shellskript verewigt – eine Liste der SIP-Peers.

Callfiles sind Miniskripte ohne nennenswerte Eigenintelligenz, die Asterisk automatisch abarbeitet, sobald sie im Verzeichnis »/var/spool/asterisk/outgoing/« liegen. Wer das Datum eines Callfile in die Zukunft schreibt, kann auch zeitgesteuerte Events wie Weckrufe initiieren. Ein einfaches Callfile kann aus den folgenden Zeilen bestehen:

Channel: SIP/500
Context: from-intern
Extension: 501

Sobald sich Asterisk dieses Callfile annimmt, wird es das SIP-Telefon mit dem Benutzer 500 anrufen und warten, dass er abhebt. Dann startet Asterisk die Extension 501 im Context »from-intern«, die wiederum ruft das Telefon 501 an und vermittelt somit eine Verbindung zwischen den Teilnehmern 500 und 501. Callfiles sehen anfänglich sehr einfach aus, sind aber für viele Asterisk-Profis das Mittel der Wahl, wenn sie von außen komplizierte Abläufe in Anlagen realisieren und steuern möchten.

Das Asterisk Manager Interface (AMI) ist theoretisch das mächtigste aller Werkzeuge – und zugleich das komplexeste. Erschwerend kommt hinzu, dass die AMI-Abarbeitung in großen Telefonanlagen immer wieder unter Engpässen leidet. Asterisks AMI-Dienst lässt sich per Telnet ansprechen, was auch anderen Diensten den Zugriff möglich macht. Wenn Sie sich mehr damit beschäftigen möchten, weiß [3] Näheres.

Vermitteln und
parken

Alle Admins, die mit »Dial(SIP/1234)« ein Gespräch zum SIP-Telefon 1234 aufbauen, haben sich die Chance auf ein Vermitteln des Gesprächs bereits verbaut. Bei Asterisk muss man die Funktionalität nämlich bereits bei der »Dial()«-Applikation mit einem Parameter aktivieren. »T« gibt dem Anrufer die Möglichkeit, das Gespräch zu vermitteln, »t« lässt dem Angerufenen die gleiche Wahl. Ein »Dial(SIP/1234,,tT)« schafft die eingangs gewünschten Verbindungsmöglichkeiten.

Wie die Teilnehmer ein Gespräch verbinden, definiert wiederum die Datei »/etc/asterisk/features.conf«. Dort bestimmt beispielsweise »blindxfer => #1« die Tastenkombination, die im Asterisk-Jargon den “Blind Transfer” auslöst. Um ein laufendes Gespräch an das interne Telefon 345 zu vermitteln, wählen Sie in dieser Konfiguration [#][1][3][4][5] und drücken danach [Wählen] oder warten auf den Timeout.

Ein paar Zeilen weiter in der »features.conf« legt »atxfer => *2« den Attended Transfer fest. Die Funktion informiert vor dem Übergeben des Gesprächs das Ziel 345 darüber. Mit Asterisk in der Version 1.4 weichen übrigens die Unterschiede zwischen Attended und Blind Transfer auf.

Für die meisten Benutzer reicht der Attended Transfer aus, da er nun auch die Blind-Transfer-Funktionalität beherrscht und der Anrufer einfach auflegen kann. Je nach verwendetem Telefon lässt sich die Transfer-Funktion auch auf eine entsprechende Taste legen.

Kampf um den Parkplatz

Wer laufende Gespräche parken will (Abbildung 4), muss auch hierfür einen Blick in die »features.conf« werfen: »parkext => 700« bestimmt die Extension, an die man ein Gespräch zum Parken weitervermitteln muss – hier mit [#][1][7][0][0]. Asterisk wird Ihnen die Nummer der Parkposition vorsprechen. Den Parkraum dürfen Sie mit »parkpos => 701-720« definieren. Um die Parkfunktion zu aktivieren, fügen Sie den entsprechenden vordefinierten Context mit »include => parkedcalls« ein.

Abbildung 4: Wer seinen Benutzer einräumen will, ein Gespräch temporär zu parken, setzt in der »features.conf«-Datei »parkext => Park-Extension«.

Abbildung 4: Wer seinen Benutzer einräumen will, ein Gespräch temporär zu parken, setzt in der »features.conf«-Datei »parkext => Park-Extension«.

Mit »parkingtime => 45« setzen Sie die Standard-Parkzeit in Sekunden fest. Danach verbindet Asterisk das geparkte Gespräch automatisch wieder zum ursprüngliche gewählten Telefon zurück. Wollen Sie aber das Gespräch von einem anderen Telefon aus wieder entparken, so wählen Sie dort einfach die Nummer der Parkposition. Deshalb müssen Sie den Park-Context in Ihrem Dialplan einfügen – andernfalls könnte Asterisk ja mit der Eingabe der Parkposition nichts anfangen.

Dialpläne übersichtlich gestalten

Selbst wenn Sie die Struktur in Dialplänen mit Regular Expressions vereinfachen, ufern ab mittelgroßen Installationen Dialpläne aus und geraten unübersichtlich. Glücklicherweise dürfen Sie mit »#include Dateiname« ganze Dateien in den Dialplan hineinziehen. Im Dialplan selber fügt »include Contextnamen« andere Contexts in den aktuellen ein.

Das lässt sich gut an dem Beispiel in Listing 8 erklären. In ihm telefonieren alle aus dem Context »verkauf« sowohl intern als auch mit vorgewählter Null extern. Die Apparate aus dem Context »produktion« müssen sich mit internen Gesprächen begnügen. Beide Contexts dürfen die Notrufnummern 110 und 112 anrufen. In den Zeilen 19 und 20 ist zu beachten, das die Variable »${VORWAHL}« mit der eigenen Vorwahl belegt ist. Außerdem sollten Sie bei Ihrem SIP-Provider nachfragen, ob er diese Nummern richtig routet.

Listing 8:
»extensions.conf« (6)

01 [verkauf]
02 include => notruf
03 include => intern
04 include => extern
05 
06 [produktion]
07 include => notruf
08 include => intern
09 
10 [intern]
11 exten => _XX,1,Dial(SIP/${EXTEN},90)
12 exten => _XX,n,Voicemail(${EXTEN},u)
13 exten => 99,1,VoiceMailMain(${CALLERID(num),s)
14 
15 [extern]
16 exten => _0X.,1,Dial(SIP/${EXTEN:1}@SIP-Provider)
17 
18 [notruf]
19 exten => 110,1,Dial(SIP/${VORWAHL}110@SIP-Provider)
20 exten => 112,1,Dial(SIP/${VORWAHL}112@SIP-Provider)

Zeitsteuerung mit Includes

Mit Includes strukturieren Sie Ihren Dialplan nicht nur einfacher und übersichtlicher, Sie können auch Includes für verschiedene Uhrzeiten aufrufen. Die Syntax lehnt sich an die der Crontab an und lautet »include => Context|Zeiten|Wochentage|Monatstage|Monate«. Mehrere Includes arbeitet Asterisk stets von oben nach unten ab. Ein typisches Beispiel für eine Firma zeigt Listing 9.

Listing 9:
»extensions.conf« (7)

01 ; Feiertage
02 include => feiertag|*|*|24|dec
03 include => feiertag|*|*|01|jan
04 
05 ; Tag
06 include => tagschaltung|09:00-17:00|mon-fri|*|*
07 include => tagschaltung|09:00-14:00|sat|*|*
08 
09 ; Wenn die nicht matchen, dann muss es Nacht sein.
10 include => nachtschaltung

Nach den ersten beiden Teilen des Workshops besitzen Sie bereits das Handwerkszeug, um Ihre neue Telefonanlage selbst für komplexere betriebliche Workflows passend zu programmieren – logisch, zeitgesteuert und interaktiv mit Voicedateien. Im weiteren Verlauf des Workshops erfahren Sie, wie Sie ISDN und analoge Telefone – auch gemischt – mit Asterisk und VoIP-Telefonie verbinden. Der nächste Artikel geht zudem auf Fax- und Analogtelefon-Adapter ein. Einen anderen Aspekt bildet die Signalisierung, zum Beispiel bei “besetzt”. (jk)

Infos

[1] Stefan Wintermeyer, “Die eigene Asterisk-Anlage – Teil 1”: Linux-Magazin 01/08, S. 84

[2] Asterisk-Projekt: [http://www.asterisk.org]

[3] Deutsches Asterisk-Buch: [http://www.das-asterisk-buch.de], kommende Version: [http://www.das-asterisk-buch.de/unstable/]

Der Autor

Stefan Wintermeyer ist Autor des bei Addison-Wesley erschienenen Asterisk-Buches und erster deutscher DCAP (Digium Certified Asterisk Professional). Bei der Amooma GmbH [http://www.amooma.de] bietet er Consulting, Support und Training zu Asterisk an.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben