7.3. extensions.conf <-> extensions.ael

Wir wollen hier übersichtlich darstellen, wie AEL im Vergleich zur herkömmlichen extensions.conf aussieht. Das kann man am besten an Beispielen veranschaulichen. Es wird vorausgesetzt, dass Sie bereits mit der Dialplan-Programmierung vertraut sind.

Zeilenende/Befehlsende

Befehle müssen in AEL immer mit ; (Semikolon) abgeschlossen werden, da theoretisch auch mehrere Befehle in einer Zeile stehen könnten (was jedoch unüblich ist).

Kontexte, Extensions, Prioritäten

Der Unterschied in der Schreibweise von Contexten, Extensions und Prioritäten bestimmt das ganze Erscheinungsbild. In AEL werden geschweifte Klammern ({ ... }) verwendet. Die Angabe von Prioritäten (1, n) ist nicht mehr erforderlich. In der extensions.ael hat man also endlich nicht mehr den zweifelhaften Charme früher BASIC-Programme, in denen man noch die Zeilennummern angeben musste, und man erspart sich auch das überflüssige mehrfache Tippen der gleichen Extension für mehrere Zeilen bzw. Befehle. Diese Stärke kommt vor allem bei längeren Dialplänen zum Tragen, weshalb die kurzen Beispiele hier manchmal etwas unfair gegen AEL sind.
extensions.confextensions.ael
[interne-benutzer]

exten => 21,1,Dial(SIP/anna)
exten => 21,n,VoiceMail(anna)

exten => 22,1,Dial(SIP/lisa)
exten => 22,n,VoiceMail(lisa)

exten => _3X,1,Dial(SIP/${EXTEN})
context interne-benutzer {
  21 => {
    Dial(SIP/anna);
    VoiceMail(anna);
  }
  22 => {
    Dial(SIP/lisa);
    VoiceMail(lisa);
  }
  _3X => {
    Dial(SIP/${EXTEN});
  }
}
Bei einer Extension, in der nur ein Befehl ausgeführt wird, könnte man in AEL die geschweiften Klammern übrigens auch weglassen und nur
 
context default {
  23 => Playback(hello-world);
}
schreiben. Gewöhnen Sie sich das bitte aber erst gar nicht an, und verwenden Sie immer die volle Schreibweise
 
context default {
  23 => {
    Playback(hello-world);
  }
}
denn man hat ja sowieso bei den meisten Extensions mehrere Befehle und erreicht so ein einheitliches Format. Nur in wenigen Fällen (jump, siehe „Labels, goto und jump“) kann die kurze Schreibweise sinnvoll sein.

Wichtig

Die öffnende geschweifte Klammer { eines Blocks muss immer auf der gleichen Zeile stehen, nicht auf einer eigenen!

Kommentare

Kommentare werden in AEL durch // (zwei Schrägstriche) eingeleitet.

Wichtig

Bitte verwenden Sie für Kommentare nicht den C-Stil (/* ... */). Für mehrzeilige Kommentare leiten Sie bitte jede Zeile separat mit // ein.
extensions.confextensions.ael
; ein Kommentar
exten => 10,1,Dial(SIP/anna) ; Dial
// ein Kommentar
10 => {
  Dial(SIP/anna);  // Dial
}

Includes – Andere Contexte einbinden

Wie aus Abschnitt 3.4, „Includes im Dialplan“ und „Includes zeitgesteuert“ bekannt ist, können Sie in Contexte andere Contexte einbinden.
extensions.confextensions.ael
[verkauf]
exten => 2001,1,Dial(SIP/anna)
exten => 2002,1,Dial(SIP/hans)

[lager]
exten => 3001,1,Dial(SIP/lisa)

[tag]
include => verkauf
include => lager

[nacht]
exten => _.,1,VoiceMail(${EXTEN},u)

[von-extern]
include => tag|09:00-17:00|mon-fri|*|*

include => tag|09:00-14:00|sat|*|*
include => nacht
context verkauf {
  2001 => {
    Dial(SIP/anna);
  }
  2002 => {
    Dial(SIP/hans);
  }
}

context lager {
  3001 => {
    Dial(SIP/lisa);
  }
}

context tag {
  includes {
    verkauf;
    lager;
  }
}

context nacht {
  _. => {
    VoiceMail(${EXTEN},u);
  }
}

context von-extern {
  includes {
    tag|09:00-17:00|mon-fri|*|*;
    tag|09:00-14:00|sat|*|*;
    nacht;
  }
}

Wichtig

Bitte beachten Sie in AEL das s am Ende von includes.

Globale Variablen

Globale Variablen (siehe „Variablen“) können in AEL im speziellen Block globals gesetzt werden.
extensions.confextensions.ael
[globals]
KUCHEN=Marmorkuchen
KLINGELZEIT=60
globals {
  KUCHEN=Marmorkuchen;
  KLINGELZEIT=60;
}

Ausdrücke und Zuweisungen

In AEL werden intern automatisch die Ausdrücke (expressions) in Kontrollstrukturen wie if(), while(), der (mittleren) Abbruchbedingung in for() sowie der rechten Seite von Zuweisungen (assignments) so behandelt, als stünden sie in einem $[...]-Ausdruck (siehe Expression).
Das klingt zuerst kompliziert, entspicht aber dem ganz normalen Verhalten, wie man es von anderen Programmiersprachen kennt. Bei Zuweisungen ist dieses Verhalten allerdings untypisch für Asterisk und kann leicht zu merkwürdigen Fehlern führen. Denken Sie daran, dass auch AEL eben keine richtige Programmiersprache ist und z. B. Strings nie als solche mit Anführungszeichen gekennzeichnet werden. Wir empfehlen daher, Zuweisungen nicht so zu schreiben:
 
context test {
  123 => {
    ergebnis=10/2;
    NoOp(ergebnis ist ${ergebnis});
  }
}
sondern nach wie vor die Applikation Set() (siehe Abschnitt C.149, „Set()) zu verwenden:
 
context test {
  123 => {
    Set(ergebnis=$[ 10 / 2 ]);
    NoOp(ergebnis ist ${ergebnis});
  }
}
Für Sprachkonstrukte wie if(), while() usw. ist dieses Verhalten allerdings gut, da es die unübersichtlichen Klammern $[ ... ] einspart:
extensions.confextensions.ael
exten => 50,1,Set(a=test)
exten => 50,n,ExecIf($["${a}" = "101"],SayDigits,123)
50 => {
  Set(a=test);
  if ("${a}" = "test") {
    SayDigits(123);
  }
}

Labels, goto und jump

Als hartgesottener extensions.conf-Programmierer ist man (zwangsweise) daran gewöhnt, mit Goto(), GotoIf(), Gosub() und GosubIf() zu Prioritäten oder Labels (Markern) zu springen. (Eigentlich ist das aber nur eine Behelfslösung, weil es in der extensions.conf keine sauberen Kontrollstrukturen für den Programmablauf gibt.)
Labels befinden sich immer innerhalb einer Extension und werden in AEL auf einer eigenen Zeile geschrieben. Bitte beachten Sie den Doppelpunkt (:) am Zeilenende:
extensions.confextensions.ael
[beispiel]

; zu einem Label in der
; gleichen Extension gehen:
;
exten => 10,1(anfang),NoOp()
exten => 10,n,Wait(1)
exten => 10,n,SayNumber(1)
exten => 10,n,NoOp(Endlosschleife)
exten => 10,n,Goto(anfang)

; zu einem Label in einer
; anderen Extension im
; gleichen Kontext gehen:
;
exten => 20,1,SayNumber(20)
exten => 20,n,Goto(10,anfang)

; zu einem Label in einem
; anderen Kontext gehen:
;
exten => 30,1,SayNumber(30)
exten => 30,n,Goto(cntxt2,40,vierzig)

[cntxt2]

exten => 40,1(vierzig),NoOp()
exten => 40,n,SayNumber(40)

exten => 50,1,Goto(40,1)
exten => 60,1,Goto(beispiel,10,1)
context beispiel {
  
  // zu einem Label in der
  // gleichen Extension gehen:
  //
  10 => {
   anfang:
    Wait(1);
    SayNumber(10);
    NoOp(Endlosschleife);
    goto anfang;
  }
  
  // zu einem Label in einer
  // anderen Extension im
  // gleichen Kontext gehen:
  //
  20 => {
    SayNumber(20);
    goto 10|anfang;
  }
  
  // zu einem Label in einem
  // anderen Kontext gehen:
  //
  30 => {
    SayNumber(30);
    goto cntxt2|40|vierzig;
  }
}

context cntxt2 {
  
  40 => {
   vierzig:
    SayNumber(40);
  }
  50 => jump 40;
  60 => jump 10@beispiel;
}
In dem obigen Beispiel sehen wir auch eine andere Syntax für die Sprünge. Während man in der extensions.conf auf die Applikation Goto() (siehe Abschnitt C.65, „Goto()) angewiesen ist, sollte man diese in AEL nicht mehr verwenden (man kann es aber problemlos tun). Dafür gibt es jetzt das neue Sprachkonstrukt goto.
Ein Vergleich der Syntax von Goto() und goto zeigt, dass es hier keinen großen Erklärungsbedarf gibt:
.conf
Goto([[context,]extension,]label)
.ael
goto [[context|]extension|]label
Dass man in AEL natürlich nicht auf Idee kommen sollte, zu einer Priorität anhand ihrer Nummer zu springen, versteht sich auch von selbst, denn wie der AEL-Compiler die Befehlszeilen in Prioritäten umsetzt, sollte beim Schreiben von AEL nicht interessieren. Weil es aber vorkommt, dass man zu einer anderen Extension (im gleichen oder in einem anderen Context) springen will, gibt es in AEL zusätzlich zu goto auch noch die Anweisung jump.
.conf
Goto([context,]extension,1)
.ael (schlecht!)
goto [context|]extension|1
.ael (gut)
jump extension[@context]
Im Folgenden („Bedingte Anweisungen (conditionals)“ und „Schleifen (loops)“) werden wir aber lernen, dass man in AEL nicht mehr darauf angewiesen ist, den Kontrollfluss (control flow) des Programms durch goto-Sprünge zu definieren, da es echte Kontrollstrukturen gibt.

Bedingte Anweisungen (conditionals)

Bedingte Anweisungen[31] (conditionals[32]) gehören zu den Kontrollstrukturen (control structures) eines Programmablaufs.

Anmerkung

Für Informationen über Ausdrücke (expressions) siehe auch Expression.
In AEL gibt es sowohl if- als auch switch-Blöcke. Das ist ein riesiger Vorteil, denn es erleichtert die Lesbarkeit ganz entscheidend, und je umfangreicher die Programmlogik wird, desto mehr kommt dieser Vorteil zum Tragen. Vergleichen Sie selbst:

if

extensions.confextensions.ael
exten => 90,1,Dial(SIP/anna)
exten => 90,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?b:n)

exten => 90,10(b),Answer()
exten => 90,11,Playback(hello-world)
exten => 90,12,Voicemail(anna,b)
exten => 90,13,Goto(ende)

exten => 90,20(n),Dial(SIP/lisa)
exten => 90,21,Playback(beeperr)
exten => 90,22,Goto(ende)
exten => 90,30(ende),NoOp(Fertig)
90 => {
  Dial(SIP/anna);
  if ("${DIALSTATUS}" = "BUSY") {
    Answer();
    Playback(hello-world)
    Voicemail(anna,b);
  }
  else {
    Dial(SIP/lisa);
    Playback(beeperr);
  }
  NoOp(Fertig);
}
Wer an AEL gewöhnt ist, empfindet das unübersichtliche Herumspringen mit GotoIf() zu Recht als umständlich, mal ganz zu schweigen davon, dass man bei einem Befehl wie GotoIf($["${DIALSTATUS}" = "BUSY"]?b:n) nicht auf den ersten Blick sieht, ob all die Klammern richtig sind.

Wichtig

Auch hier gilt, dass die öffnende geschweifte Klammer { eines Blocks auf der gleichen Zeile stehen muss, nicht auf einer eigenen!

switch

extensions.confextensions.ael
exten => 70,1,Dial(SIP/anna)
exten => 70,n,Goto(70-${DIALSTATUS},10)
exten => 70,n(ende),NoOp(Fertig)

exten => 70-BUSY,10,NoOp(besetzt)
exten => 70-BUSY,11,Goto(ende)

exten => 70-NOANSWER,10,NoOp(hebt nicht ab)
exten => 70-NOANSWER,11,Goto(ende)

exten => _70-.,10,NoOp(was anderes)
exten => _70-.,11,Goto(ende)
70 => {
  Dial(SIP/anna);
  switch ("${DIALSTATUS}") {
    case "BUSY":
      NoOp(besetzt);
      break;
    case "NOANSWER":
      NoOp(hebt nicht ab);
      break;
    default:
      NoOp(was anderes);
  }
  
  NoOp(Fertig);
}

Wichtig

Bitte denken Sie bei den case-Sprungpunkten im switch-Block in AEL immer an die break-Anweisungen! Ohne diese Begrenzung läuft die Programmausführung einfach nach unten weiter, also zum nächsten case oder default – eben zur nächsten Zeile.
Dass das Äquivalent einer einfachen switch-Anweisung in der traditionellen extensions.conf eine mittlere Katastrophe ist, braucht wohl nicht groß erwähnt zu werden. Verschachtelte if- oder switch-Blöcke wären vollkommen unübersichtlich und unwartbar.
Übrigens gibt es – falls man das wirklich mal brauchen sollte – in switch-Blöcken nicht nur case-Vergleiche, sondern auch pattern:
extensions.confextensions.ael
exten => _70,1,NoOp(Gewaehlt: ${EXTEN})
exten => _70,n,Goto(70-${EXTEN},10)

exten => 70-703,10,NoOp(703)
exten => 70-703,11,Goto(ende)

exten => 70-704,10,NoOp(704)
exten => 70-704,11,Goto(ende)

exten => _70-70[5-8],10,NoOp(70[5-8]);
exten => _70-70[5-8],11,Goto(ende)

exten => _70-.,10,NoOp(was anderes)
exten => _70-.,11,Goto(ende)

exten => 70,n(ende),NoOp(Fertig) 
_70. => {
  NoOp(Gewaehlt: ${EXTEN});
  switch (${EXTEN}) {
    case 703:
      NoOp(703);
      break;
    case 704:
      NoOp(704);
      break;
    pattern 70[5-8]:
      NoOp(70[5-8]);
      break;
    default:
      NoOp(was anderes);
  }
  
  NoOp(Fertig);
}

ifTime

Es sei noch erwähnt, dass es in AEL auch eine Entsprechung für GotoIfTime() (siehe Abschnitt C.67, „GotoIfTime()) gibt, nämlich das Sprachkonstrukt ifTime.
 
20 => {
  ifTime (08:00-18:00|mon-fri|*|*) {
    Dial(SIP/20);
  } else {
    Playback(ansage-geschlossen);
    Voicemail(20,s);
  }
}
Die Syntax der Zeitangabe entspricht der von GotoIfTime(). ifTime ist eigentlich überflüssig, denn mit einem normalen if und der Funktion IFTIME() (Abschnitt D.51, „IFTIME()) kann man das Gleiche erreichen. Der Programmcode ist nur etwas länger:
 
20 => {
  if (${IFTIME(08:00-18:00|mon-fri|*|*?1:0)}) {
    Dial(SIP/20);
  } else {
    Playback(ansage-geschlossen);
    Voicemail(20,s);
  }
}

random

random(){...} ist ein Sprachkonstrukt, bei dem man in Klammern einen ganzzahligen Prozentwert von 1 bis 99 angibt, der bestimmt, mit welcher Wahrscheinlichkeit der Code-Block ausgeführt wird.
 
20 => {
  random (42) {
    NoOp(42 % Chance);
  } else {
    NoOp(58 % Chance);
  }
}
Das Sprachkonstrukt random ist eigentlich überflüssig, denn mit einem normalen if und der Funktion RAND() (Abschnitt D.80, „RAND()) kann man das Gleiche erreichen. Der Code ist nur etwas länger:
 
20 => {
  if (${RAND(0,100)} < 42) {
    NoOp(42 % Chance);
  } else {
    NoOp(58 % Chance);
  }
}

Schleifen (loops)

Schleifen (loops) gehören – wie bedingte Anweisungen – zu den Kontrollstrukturen (control structures) eines Programmablaufs.
In AEL gibt es for- und while-Schleifen, wie sie aus anderen Programmiersprachen bekannt sind.

while

Ein while-Block in AEL entspricht in etwa der Verwendung von While() (Abschnitt C.195, „While()) und EndWhile() (Abschnitt C.47, „EndWhile()).
Auch die aus anderen Sprachen bekannten Befehle break und continue lassen sich in Schleifen nutzen. break springt zum Ende des Schleifenblocks, continue zum Anfang. Das ist also die Entsprechung zu ExitWhile() (Abschnitt C.53, „ExitWhile()) bzw. ContinueWhile() (Abschnitt C.24, „ContinueWhile()).
extensions.confextensions.ael
exten => 30,1,Set(x=0)
exten => 30,n,While($[${x} <= 9])
exten => 30,n,NoOp(x ist ${x})
exten => 30,n,ExecIf($[${x} > 5],ExitWhile)
exten => 30,n,Playback(beep)
exten => 30,n,Set(x=$[${x} + 1])
exten => 30,n,EndWhile()
exten => 30,n,NoOp(Fertig)
30 => {
  x=0;
  while (${x} <= 9) {
    NoOp(x ist ${x});
    if (${x} > 5) {
      break;
    }
    Playback(beep);
    x=${x} + 1;
  }
  NoOp(Fertig);
}

Anmerkung

In AEL brauchen wir hier nur deshalb mehr Zeilen, weil wir darauf verzichten, in dem if-Block vor dem break noch einen zweiten Befehl wie NoOp() auszuführen. Dann nämlich reicht im .conf-Format nicht mehr ein ExecIf(), sondern man braucht ein kompliziertes mehrzeiliges Konstrukt mit GotoIf().

for

Zusätzlich zu while gibt es in AEL auch for-Schleifen. Diese habe keine Entsprechung im .conf-Format. (Allerdings lässt sich jede for-Schleife als while-Schleife schreiben.)
extensions.confextensions.ael
exten => 40,1,Set(x=0)
exten => 40,n,While($[${x} <= 5])
exten => 40,n,NoOp(x ist ${x})
exten => 40,n,Playback(beep)
exten => 40,n,Set(x=$[${x} + 1])
exten => 40,n,EndWhile()
exten => 40,n,NoOp(Fertig)
40 => {
  for (x=0; ${x}<=5; x=${x}+1) {
    NoOp(x ist ${x});
    Playback(beep);
  }
  NoOp(Fertig);
}

Makros (macros)

In AEL muss man sich keine Gedanken darüber machen, ob man die nicht mehr empfohlene Applikation Macro() (Abschnitt C.81, „Macro()) oder Gosub() (Abschnitt C.63, „Gosub()) verwenden soll, denn Makros gibt es in AEL als Sprachkonstrukt macro.
extensions.confextensions.ael
[macro-countdown]
exten => s,1,Set(c=${ARG1})
exten => s,n,While($[ ${c} > 0])
exten => s,n,SayNumber(${c})
exten => s,n,Set(c=$[ ${c} - 1 ])
exten => s,n,EndWhile()

[default]
exten => 123,1,Macro(countdown,3)
exten => 124,1,Macro(countdown,5)
macro countdown( count ) {
  for (c=${count}; ${c}>0; c=${c}-1) {
    SayNumber(${c});
  }
}

context default {
  123 => {
    &countdown(3);
  }
  124 => &countdown(5);
}
Intern macht der AEL-Compiler aus macro automatisch eine Gosub()-Subroutine, was uns aber glücklicherweise nicht interessieren muss.

Hints

Wie sogenannte Hints in AEL geschrieben werden, erklären wir ausführlich in Kapitel 23, BLF, Hints, Pickup. Hier nur ein einfaches Beispiel:
extensions.confextensions.ael
[interne-benutzer]
exten => 21,hint,SIP/anna
exten => 21,1,Dial(SIP/anna)

exten => 22,hint,SIP/lisa
exten => 22,1,Dial(SIP/lisa)
context interne-benutzer {
  hint(SIP/anna) 21 => {
    Dial(SIP/anna);
  }
  hint(SIP/lisa) 22 => {
    Dial(SIP/lisa);
  }
}
... und eines mit Pattern:
extensions.confextensions.ael
[interne-benutzer]
exten => 21,hint,SIP/21
exten => 22,hint,SIP/22

exten => _2X,1,Dial(SIP/${EXTEN})
context interne-benutzer {
  hint(SIP/21) 21 => {}
  hint(SIP/22) 22 => {}
  
  _2X => {
    Dial(SIP/${EXTEN});
  }
}

Filtern nach Anrufernummer

Man braucht es zwar in der Praxis eher selten, aber die Syntax für eine (in der Asterisk-Community viel zitierte) Ex-Girlfriend Extension sähe so aus:
extensions.confextensions.ael
exten => 10/55555,1,NoOp(Ex-Freundin)
exten => 10/55555,n,Busy()

exten => 10,1,Dial(SIP/karl)
exten => 10,n,Voicemail(karl)
10/55555 => {
  NoOp(Ex-Freundin);
  Busy();
}
10 => {
  Dial(SIP/karl);
  Voicemail(karl);
}
Wenn also die Ex-Freundin von der Nummer (Caller-ID) 55555 anruft, würde sie auf Busy() geleitet, alle anderen Anrufer jedoch nicht.
Übrigens sind hier auch Pattern erlaubt. Mit /_0123. könnte man also direkt einen ganzen Vorwahl-Bereich matchen.