Den ersten beiden Forderungen würde die Verwendung von Tcl [Oust94:Tcl] gerecht werden. Zudem bietet Tcl einen ausreichend großen Sprachumfang um auch komplexe Anwendungen zu erstellen. Da Tcl keine Möglichkeiten besitzt, asynchronen Ereignisse aufzufangen und zu behandeln, müßten hierfür sowie für die telefoniespezifischen Funktionen entsprechende Erweiterungen vorgenommen werden.
Dieser Weg wird in einer Arbeit von Uhler [Uhle9301:PhoneStation] beschritten. Dort bildet ein um einige Befehle erweiterter Tcl-Interpreter die Skriptsprache PhoneScript, mit der Telefonieanwendungen erstellt werden können. Ein (analoges) Telefon und der Computer werden über eine spezielle Hardware miteinander verbunden. Mit Befehlen wie und können Anrufe initiiert und Audiofiles abgespielt werden. Den speziellen ISDN-Merkmalen wie mehrere Mediendatenströme/Kanäle sowie dem Zugang zu Multimedia-Konferenzen wird allerdings nicht Rechnung getragen.
Da bei einem Anruf verschiedene Ereignisse asynchron auftreten können, liegt der Gedanke nahe, eine Telefonieanwendung als Finite-State-Machine zu betrachten und als solche zu programmieren. In PhoneScript ist Tcl zwar um ein Eventhandling erweitert worden, jedoch gibt es keine Zustände bzw. nur einen einzigen, in dem die definierten Events gelten. Beispielsweise bei der Realisierung verschachtelter Auswahlmenüs, die der Anrufer mittels Telefontastatur durchlaufen kann, wäre es mit PhoneScript recht umständlich, die verschiedenen Menüebenen (Zustände) zu unterscheiden, um eine gedrückte Taste zu behandeln. Entsprechende Verschachtelungen von if-then-else oder switch-Anweisungen würden die Lesbarkeit der Programme erschweren.
Andererseits ist es unnötig, sequentiell ablaufende Anwendungen wie einen einfachen Ansagedienst oder gar einen Anrufbeantworter in verschiedene Zustände zu zerlegen, da die einzigen Events, die auftreten können (Auflegen durch den Anrufer oder Ablauf der Sprechzeit) zum Ende der Anwendung führen. Eine Unterscheidung mehrerer Zustände würde hier also zu unnötig aufgeblähten Programmen führen.
Am Beispiel eines einfachen Anrufbeantworters sollen die angesprochenen Probleme kurz verdeutlicht werden. Eine rein sequentielle Darstellung zeigt folgendes Beispiel in einer fiktiven Sprache (sysCalls ist eine Variable, die die Anzahl der eingehenden Anrufe zählt).
while (1) {
play "ogm/welcome.au" # Play a welcome message
getkey $in # Get user selection
switch $in {
case "1":
play "icm/*.au" # Play all recorded messages
continue
case "2":
record "icm/m$sysCalls.au" # Record a new message
continue
case "#":
play "ogm/bye.au"
hangup
exit # terminate script
default:
play "invalid input"
continue
}
}
Der Anrufer kann hier mit drei Tasten wählen, ob er eingegangene Nachrichten abhören will (durch Drücken der 1) oder eine neue Nachricht aufnehmen will (durch Drücken der 2). Mit der \#-Taste kann er die Anwendung beenden, was natürlich auch durch Auflegen geht. Für den Fall, in dem bereits Nachrichten eingegangen sind, stellt sich die Frage, wie das Abspielen ohne Auflegen vorzeitig beendet werden kann. Die \#-Taste hat hier natürlich keine Wirkung, da das Skript sequentiell abgearbeitet wird. Eine zudem um einige Zeilen kürzere Lösung zeigt folgende ereignisorientierte Variante:
state AnsweringMachine {
on start
play "ogm/welcome.au" # Play a welcome message
on key "1"
play "icm/*.au" # Play all recorded messages
enter welcome
on key "2"
record "icm/m$sysCalls.au" # Record a new message
enter welcome
on key "#"
play "ogm/bye.au" # say "good bye"
hangup # disconnect and
exit # terminate script
}
Eine explizite Eingabeanweisung wie im ersten Beispiel entfällt hier, da die Tastendrücke als Ereignisse definiert sind. Ihre Erkennung löst automatisch die Ausführung der Anweisungen hinter der jeweiligen -Klausel aus. Wird während des Abspielens der Nachrichten die \#-Taste gedrückt, wird die Ausführung der -Anweisung unterbrochen und hinter dem # fortgesetzt. So kann hier außerdem ein Anrufer, der die Funktionsweise der Anwendung bereits kennt, die Begrüßungsnachricht durch vorzeitiges Drücken einer der drei Tasten überspringen.
Tabelle [tab-languages] stellt die wesentlichen Eigenschaften der Sprachen gegenüber.
Merkmal | Microsoft | Trumpet | RCL | CCL |
supported data types | int, string, bool | int, string | int, string | string |
supported loop statements | while | while, repeat | while | none |
conditional branch statements | if-then | if-then | if-then | iftries |
incoming call handling supported? | no | no | no | no |
support for outgoing calls | yes | yes | yes | yes |
supports procedures/functions? | yes | no | yes | no |
event handling supported? | no | no | no | no |
Wie man sieht, besitzen die Sprachen stark ähnliche Merkmale, was angesichts ihres Einsatzgebietes kaum wundert. Die Sprachen von Microsoft, Trumpet und InterCon dienen zum Modem-Setup bzw. Setup des lokalen Netzwerk-Stacks bei SLIP oder PPP-Verbindungen. Die fehlende Fähigkeit, eingehende Anrufe zu beantworten ist scheinbar darauf zurückzuführen.
Den weitaus größten Sprachumfang besitzt die Sprache von WRQ, die inzwischen von einem Visual Basic ähnlichen Reflection-Basic ersetzt worden ist, mit dem man (unter MacOS und Windows) auch Anwendungen mit grafischer Oberfläche erstellen kann. Jedoch ist auch hier das Haupteinsatzgebiet das automatisierte Login auf fremden Rechnern. Anrufe im Sinne von Voice-Calls werden von keiner der Sprachen unterstützt.
LOTOS ist eine Formale Sprache zur Beschreibung verteilter Systeme, die im ISO-Standard IS 8807 beschrieben ist. SDL (CCITT Specification and Description Language) ist eine in der ITU-T Norm Z.100 [ITU-Z100] definierte Sprache zur Beschreibung von Telekommunikationssystemen. Die Sprachen verfügen u.a. über Möglichkeiten zur Modellierung von Daten und Prozessen sowie der Kommunikation zwischen Prozessen. SDL definiert neben einer textuellen auch eine grafische Notation. Da die Sprachen Systeme auf eine sehr abstrakte Art beschreiben, sind die mit ihnen erstellten Beschreibungen keineswegs als kompakt und leicht lesbar zu bezeichnen. Jeder benutzte Datentyp muß in aufwendigen Definitionen erst einmal erzeugt werden. Allein aufgrund ihrer Komplexität und des großen Lernaufwandes scheinen mir diese Sprachen oder Varianten davon ungeeignet für den Einsatz im Call-Server.
Tcl-expect ist eine in Tcl geschriebene Anwendung, mit der sich andere Applikationen über Skripte steuern lassen. Wie mit den oben beschriebenen Modemsprachen kann z.B. die Anmeldeprozedur auf einem entfernten Rechner automatisiert werden. Hierfür wird mit einem Skriptbefehl spawn z.B. ein rlogin-Kommando abgesetzt. Das Skript kann nun unter Einsatz von Pattern-Matching auf bestimmte Ausgaben des Rechners wie "Password:" warten und mit einem send-Befehl eine entsprechende Eingabe zum entfernten Rechner schicken.
Da das Erkennen von Strings sozusagen die einzige Art ist, Events auszulösen, ist auch expect für den Einsatz im Call-Server ungeeignet.
Es bietet sich also die Erweiterung von Tcl um die benötigten Anrufbehandlungs-Routinen an. Diese sollen es ermöglichen, eine Finite-State-Machine abzubilden. Darüberhinaus soll die Möglichkeit der prozeduralen Programmierung erhalten bleiben. Vor allem aber soll die Behandlung eingehender Anrufe mit dem Ziel der Teilnahme an Internet-Konferenzen unterstützt werden.
state statename {
on <eventtype> { # local event definition
<tcl-script> }
}
on <eventtype> { # global event definition
<tcl-script>
}
proc <procname> <args> { # creation of a new Tcl procedure
<procbody>
}
Ein Zustand (state) wird durch die Klausel beschrieben. Die Events, die innerhalb des Zustandes behandelt werden sollen, werden mit definiert. Die bei Eintritt des Events auszuführenden Anweisungen werden in dem Anweisungsblock angegeben. Mit der als Tcl-Erweiterung implementierten -Anweisung, kann aus einem Anweisungsblock heraus in einen anderen Zustand gewechselt werden. Der Endzustand ist erreicht, wenn das Skript mittels verlassen wird. Um gleiche Events, die in mehreren oder allen Zuständen auftreten können, nicht in jedem Zustand erneut definieren zu müssen, können diese global definiert werden. Das Disconnect-Event ist ein solches Beispiel. Da der Anrufer jederzeit, also in jedem Zustand der State-Machine, die Verbindung beenden kann, und die Reaktion des Call-Servers darauf sehr wahrscheinlich immer die Gleiche sein wird, bietet sich die globale Definition dieses Events an.
Globale Event-Definition sind außerhalb eines -Blocks, der Übersichtlichkeit wegen möglichst zentral an einer Stelle im Skript anzuordnen.
Mit der Library-Funktion Tcl_Eval() kann ein C-Programm einzelne Tcl-Anweisungen oder Anweisungsblöcke (sog. Tcl-Skripte) ausführen, so daß der Ausführung der Anweisungsblöcke der State-Machine mit Tcl_Eval() scheinbar nichts im Wege steht. Durch die von mir gewählte Architektur des Call-Servers entsteht jedoch ein Problem, das im folgenden Abschnitt behandelt wird.
Eine bessere Alternative scheint mir in der Abarbeitung der Anweisungsblöcke bzw Tcl-Skripte durch eigene Prozesse bzw. Threads zu bestehen. Leider hat sich diese Erkenntnis erst relativ spät im Verlauf der Arbeit durchgesetzt, so daß die gegenwärtige Implementierung auf den Einsatz von Tcl_Eval() verzichtet. In [scheduling] wird hierauf genauer eingegangen.
Der von mir eingeschlagene Weg besteht nun darin, die Anweisungsblöcke in den Event-Definitionen nicht durch einen Tcl-Interpreter sondern, einen eigenen Interpreter abarbeiten zu lassen. Um den Aufwand in Grenzen zu halten, habe ich nur einen Teil des Tcl-Sprachumfangs implementiert. Um den Einsatz eines Tcl-Interpreters zu einem späteren Zeitpunkt zu unterstützten, habe ich darauf geachtet, daß innerhalb der CPL-Anweisungsblöcke die Syntax mit Tcl identisch ist.
Eine CPL Applikation besteht aus Zustandsbeschreibungen und Prozeduren. Letztere entsprechen den procs in Tcl. In den Zustandsbeschreibungen werden die möglichen Ereignisse und die entsprechenden Reaktionen in Form prozeduraler Anweisungsblöcke definiert. Innerhalb einer Zustandsdefinition vorgenommene Event-Definition gelten lokal, also nur innerhalb dieses Zustandes. Events, die außerhalb von Zuständen vereinbart sind, gelten global, wenn sie nicht durch eine gleiche Definition innerhalb des aktuellen Zustandes überdeckt werden.
Anhang [app-grammar] zeigt den vollständigen Sprachumfang von CPL. Alle Anweisungen sind case-sensitiv. Kommentare können durch # an beliebigen Stellen im Sourcecode eingeleitet werden und gelten immer bis zum Zeilenende (wie in Tcl).
char | meaning |
d | one digit (0..9) |
D | one or more digits |
a | one DTMF alpha character (A, B, C, D) |
A | one or more alpha character |
one or more DTMF character | |
? | one DTMF character |
Um Metazeichen von den DTMF-Zeichen zu unterscheiden, müssen letztere in einfache Hochkommata eingeschlossen werden. Einige Beispiele zeigt Tabelle [tab-pmex]
Anwendungsgebiet | pattern string |
Eingabe einer IP-Adresse in dotted decimal notation (Terminierung durch \#). | D'*'D'*'D'*'D'#' |
Eingabe einer vierstelligen PIN | dddd |
Eingabe einer Faxnummer (Terminierung durch \#) | D'#' |
Eingabe einer amex Kartennummer | '37'ddddddddddddd |
Um bei Eingaben variabler Länge die Erkennung abzuschließen, muß im Matchstring ein Schlußzeichen angegeben werden, z.B. das "#". Nachteilig ist hierbei, daß dieses Zeichen ebenfalls in sysDtmf enthalten ist, obwohl es u.U. nicht Bestandteil der gewünschten Zeichenfolge ist. Um solche unerwünschten Zeichen herauszufiltern, stellt CPL die Anweisung zur Verfügung.
Die Erkennung einer IP-Adresse wie in Tabelle [tab-pmex] ist ein solches Beispiel. In Ermangelung einer .-Taste auf der Telefontastatur soll der Benutzer hier die *-Taste benutzen. Da die Zahlenblöcke zwischen den Punkten ein, zwei oder drei Ziffern lang sein können, wird im Matchstring als Metazeichen das 'D' benutzt. Abgesehen davon, daß hierdurch auch vier und mehr Ziffern zugelassen werden, müssen noch das abschließende \# entfernt und die Sterne durch Punkte ersetzt werden, um eine gültige IP-Adresse zu erhalten. Dies sowie die Überprüfung auf eine korrekte IP-Adresse geschieht durch die -Anweisung.
Um die Eingabe von Buchstaben per Telefontastatur nach ITU-T E.161 zu unterstützten, dient die -Variante der -Definition. Da hierbei erkannte DTMF-Zeichen anders interpretiert werden, können und nicht innerhalb desselben Zustands verwendet werden. Auch hier steht eine erkannte Zeichenfolge anschließend in sysDtmf. Im Gegensatz zur numerischen Tastenerkennung sind im Matchstring nur die Zeichen ? und * verfügbar. Soll also ein Unix-Username erkannt werden, so geschieht dies mit folgender Definition:
on ascii dtmf "*'#'" {
....
}
Beachte, daß auch hier die Eingabe mit einer bestimmten Taste (hier \#) abgeschlossen werden muß, wenn man sich nicht auf eine feste Anzahl von Zeichen festlegen will. Folgende Variante kommt ohne eine solche Taste aus und löst nach genau dem dritten erkannten Buchstaben das Event aus:
on ascii dtmf "???" {
....
}
Übergänge in einen anderen Zustand erfolgen durch die Anweisung . Beim Auflegen durch den Anrufer (bei Anrufen aus dem ISDN) wird das Disconnect-Event erzeugt. Fehlt eine entsprechende Definition im aktuellen Zustand, so wird Skript sofort beendet. Wird das Disconnect-Event mittels abgefangen, so kann das Skript beispielsweise Aufräumarbeiten erledigen etc. Der Aufruf von Programmen, die Audiodaten zum ISDN schicken, bleibt natürlich wirkungslos.
Die Reihenfolge der -Anweisungen innerhalb einer Zustandsdefinition ist nur für das Erkennen von DTMF-Sequenzen relevant. Alle innerhalb eines Zustandes erkannten DTMF-Zeichen werden gesammelt und mit allen bzw. Definition des Zustandes verglichen. Die Reihenfolge der Vergleiche erfolgt in der Reihenfolge der Definitionen. So würde in folgendem Beispiel die "10" nie erkannt werden, weil zuvor bereits die Erkennung der "1" ein Event auslöst.
on dtmf "1" {
....
}
on dtmf "10" {
....
}
Die Reihenfolge von Event-Definitionen ist also nur für die Erkennung der Matchstrings von Bedeutung.
Die Befehlsausführung in Folge eines Events terminiert ein evtl. noch laufendes Kommando. Wird beispielsweise ein Befehl mittels ausgeführt (z.B. Abspielen einer Ansage), so wird dieser unterbrochen, wenn z.B. eine DTMF-Sequenz erkannt wurde. Beachte, daß der alleinige Eintritt des Events nicht ausreicht; erst die Ausführung des ersten Befehls im Anweisungsblock einer Eventdefinition beendet einen laufenden Befehl. Wird beispielsweise ein Timer gestartet, das Timeout-Event aber nicht mit abgefangen, so führt dies nicht zum Abbruch laufender Befehle, etwa eines !
Beachte, daß beim Abfangen des Disconnect-Events mit das Skript für seine Beendigung selbst verantwortlich ist, d.h. bei fehlender -Anweisung läuft das Skript weiter.
Anhang [app-statements] enthält eine Liste aller CPL-Anweisungen, die in einem Anweisungsblock verwendet werden können. Die Liste ist unterteilt in Tcl-Anweisungen und CPL-Anweisungen. Bei der Verwendung eines Tcl-Interpreters zur Abarbeitung der Anweisungsblöcke müßten also die CPL-Anweisungen als neue Tcl-Befehle beim Tcl-Interpreter registriert werden. Nach Ausführung eines Tcl-Befehls enthält die Variable sysStatus den Wert 0, wenn die Ausführung des Kommandos erfolgreich war. Werte größer 0 zeigen einen Fehler an. In der Datei iSrvCpl.h sind die möglichen Fehlerwerte als Macros definiert. Der Befehl liefert in sysStatus den Exitstatus des ausgeführten Programms. Die -Anweisung liefert hingegen den SIP-Response-Code.
phone://[+<country-code>-]<phone-number>
Die Umleitung eines bereits mit angenommenen Anrufes zum Anschluß 4711 würde also durch die Anweisung
redirect "phone://4711"
erfolgen. Kommt der eingehende Anruf bereits aus dem ISDN, so wird für die Rufumleitung ein zweiter B-Kanal verwendet. Steht kein freier Kanal zur Verfügung, so terminiert und sysStatus enthält den Wert ERR_CALL_FAILED (definiert in iSrvCpl.h). Die Rufumleitung ins ISDN hat zur Folge, daß es beim Angerufenen klingelt. Die Rufumleitung ins Internet mittels sieht keine Rufsignalisierung vor. Vielmehr wird der eingehende Audiostrom mit dem angegebenen RTP-Audiostrom verbunden. Um die Parametrisierung der -Anweisung einheitlich zu gestalten, wurde hierfür ebenfalls eine URL-Darstellung gewählt. Für das Verbinden mit einem RTP-Strom wird ein neuer URL-Typ benutzt, der rtp://-URL, dessen Syntax wie folgt lautet:
rtp://<host>:<port>
Die Art der Angabe von Hostadresse und Portnummer entspricht RFC 1738 [RFC1738]. Die Anweisung
redirect "rtp://224.2.58.17:23711"
verbindet den Anrufer also mit einem entsprechenden Audiostrom. Bei dieser Darstellung fehlt derzeit noch die Angabe des TTL, in der Implementierung wird hierfür mit einem Default-Wert gearbeitet.
Soll bei einer Rufumleitung ins Internet ein Anrufer gezielt eingeladen werden, so geschieht dies mit Hilfe der -Anweisung, welche im folgenden Abschnitt beschrieben wird.
Die -Anweisung dient wie in [fwd-into-inet] beschrieben, der Erzeugung eines SIP-INVITE-Requests, also zur Einladung eines Netzwerkbenutzers. Der gegenwärtige Stand[Die Implementierung dieses Befehls ist noch nicht abgeschlossen] sieht folgende Syntax vor:
invitecmd ::= "invite" <to> <info> <media>
to ::= "<to-user>@<to-host>"
info ::= stringexpr
media ::= "<media-addr>:<media-port>"
Die Felder "To:", "i=", "c=" und "m="des SIP-Requests werden durch die Parameter der -Anweisung versorgt. Am folgenden Beispiel eines INVITE-Requests soll die Zuordnung der Feldinhalte gezeigt werden.
INVITE <request-id> SIP/2.0
From: <from-user>@<from-host>
To: <to-user>@<to-host>
v=0
o=<o-user> <session-id> <version> IN IP4 <my-ip>
s= MBONE audio
i= <info>
c= IN IP4 <media-addr>/<ttl>
t= 0 0
m= audio <media-port> RTP 0
Alle anderen Felder wie "Path:" oder "e= " werden nicht erzeugt. Die in spitzen Klammern geschriebenen Feldinhalte werden gemäß Tabelle [sip-data] belegt. Die restlichen werden durch den Call-Server fest belegt.
request-id | wird vom Call-Server erzeugt |
from-user | User-Id, unter der das aktuelle Script ausgeführt wird |
from-host | Rechnername, auf dem der Call-Server läuft |
to-user | Teil des ersten Parameters der -Anweisung |
to-host | Teil des ersten Parameters der -Anweisung |
o-user | wie from-user |
session-id | wird vom Call-Server erzeugt |
version | wird vom Call-Server erzeugt |
my-ip | Adresse des Rechners, auf dem der Call-Server läuft |
info | zweiter Parameter der -Anweisung |
media-addr | Teil des dritten Parameters der -Anweisung |
ttl | Teil des dritten Parameters der -Anweisung |
media-port | Teil des dritten Parameters der -Anweisung |
Folgender CPL-Code verschickt einen SIP-INVITE-Request, die benutzten Variablen müssen zuvor vom Skript entsprechend belegt werden.
invite "$user@$hostname" "Call from $sysCalledParty" "$ip:$port"
if {$sysStatus == 200} then {
redirect "rtp://$ip:$port"
...
}
Es ist vorgesehen, daß die Ausführung der -Anweisung bis zum Eintreffen des Response-Codes bzw. bis zum Ablauf eines Timeouts blockiert. Als Returncode wird entweder der dreistellige Response-Code oder aber beim Timeout ein vierstelliger Wert zurückgeliefert. Das Blockieren des Skriptes scheint mir allerdings ungünstig, wenn dem Anrufer während des Wartens auf die Antwort z.B. eine Ansage wie "Bitte warten Sie..."vorgespielt werden soll. Günstiger wäre den Eingang des Response-Codes als Event zu realisieren.
Durch dieses Verfahren werden die in [ISDN-timing] beschriebenen Timing-Probleme umgangen. Um das Prozeß-Handling und die damit verbundenen Probleme nur einmal zu bewältigen, habe ich CPL von allen Kommandos befreit, die ebenfalls eine variable bzw. unbekannte Ausführungszeit benötigen. So waren ursprünglich eigene CPL-Kommandos für das Abspielen von Audiofiles und das Generieren von Tönen vorgesehen. Diese Kommandos sind nun als externe Kommandos realisiert (Tabelle [ext-prgs]).
Programm | Bedeutung |
iAuCat | Liest den Inhalt einer Audiodatei, und schreibt diesen nach stdout |
iRTP | Verbindet den Anrufer mit einem RTP-Strom |
iTone | Erzeugt einen Ton der angegebenen Länge und Frequenz |
Bei der Wahl der mit zu startenden Programme ist zu beachten, daß stdout und stdin jeweils mit dem ISDN-B-Kanal verbunden sind. Ausgaben nach stderr werden ins Logfile des Skriptes geschrieben. Die benutzten Programme müssen also je nach Land einen 8kHz A-law oder µ-law Audiostrom erzeugen. Da dies leider die wenigsten Programme tun, habe ich die in Tabelle [ext-prgs] aufgezählten geschrieben.
Wie bereits erwähnt, werden mit gestartete Prozesse beim Eintreffen von Events abgebrochen. Wird beispielsweise ein Audiofile abgespielt, so wird der Prozeß unterbrochen sobald eine mittels bzw. definierte Tastenfolge erkannt wurde. Da die Ausführung der -Anweisung die Skriptausführung blockiert, gibt es keine andere Abbruchmöglichkeit, als durch Events. So kann die Ausführung eines Kommandos beispielsweise mit Hilfe eines Timers zeitlich begrenzt werden. Das durch den Timeout ausgelöste Event beendet die Befehlsausführung.
Als Returncode liefet den Exitstatus des gestarteten Programms, wie folgendes Beispiel zeigt:
...
exec "iAuCat welcom.au" # play greeting message
if { $sysStatus != 0 } { # error occured?
exec "iAuCat error.au" # error message
exit # terminate script
}
...
Bei der späteren Verwendung von Tcl_Eval() ist zu beachten, daß die Anweisungen im globalen Kontext des Tcl-Interpreters ausgeführt werden. In den dann als normale Tcl-Prozeduren realisierten procs stehen, wie in Tcl üblich, lokale Variablen zur Verfügung.
Mit der -Anweisung können Timer beliebig gestartet und angehalten werden. Die Syntax der Anweisung lautet:
timercmd ::= "timer" timername { timerstart | "stop" }
timerstart ::= { "local" | "global" } timeout
timeout ::= hours":"minutes":"seconds
Im folgenden Beispiel eines Auswahlmenüs wird der lokale Timer t1 auf 8 Sekunden gesetzt. Nach seinem Ablauf wird eine Meldung ausgegeben und der Timer durch Wiedereintritt in den selben Zustand () erneut gestartet.
state Menu {
on enter {
exec "iAuCat sounds/Menu.au" # tell user the menu
timer t1 local 00:00:08 # allow 8 secs for input
}
on dtmf "1" { enter GetIPaddr }
on dtmf "2" { enter RecordMessage }
on dtmf "#" { enter Bye }
on timer t1 {
exec "iAuCat sounds/InvalidOrNoInput.au"
enter Menu
}
}
Ein lokaler Timer gilt immer nur innerhalb des Zustandes, in dem er gestartet wurde. Beim Verlassen dieses Zustandes werden automatisch werden alle lokalen Timer beendet, ohne daß ein Timer-Event generiert wird. Aus diesem Grunde ist in obigen Beispiel das Stoppen des Timers t1 nicht erforderlich, da nach dem Drücken einer der Tasten 1, 2 oder \# der aktuelle Zustand verlassen wird.
Globale Timer gelten innerhalb des gesamten Skriptes, werden also beim Verlassen eines Zustandes nicht automatisch beendet. So kann z.B. durch Starten eines globalen Timers die Gesamtdauer einer Verbindung limitiert werden, auch dann wenn das Skript aus mehreren Zuständen besteht. Globale Timer dürfen nicht durch einen lokalen Timer gleichen Namens überdeckt werden.
Im folgenden Beispiel werden zwei Timer parallel gestartet, der globale connectTime mit 30 Minuten und der lokale Timer t1 mit 4 Sekunden.
state s1 {
on enter {
timer connectTime global 00:30:00
timer t1 local 00:00:04
accept
}
on dtmf "#" {
enter s2
}
on timer t1 {
log "t1 timed out!"
}
}
state s2 {
on timer connectTime { # local event definition
log "global timer 'connectTime' timed out!"
enter s1
}
}
on timer connectTime { # global event definition
log "timeout fetched by global definition"
exit
}
Sofern in s1 nicht innerhalb von 4 Sekunden die \#-Taste erkannt wurde, läuft t1 ab und es wird die Meldung "t1 timed out!"ausgegeben. Wird nicht innerhalb der restlichen 3 Sekunden von connectTime die Taste \# erkannt, so läuft auch connectTime ab und durch die globale Event-Definition am Ende des Skriptes, wird "timeout fetched by global definition"ausgegeben und das Skript beendet.
Wird in s1 die \#-Taste innerhalb von 4 Sekunden gedrückt, so wird in den Zustand s2 verzweigt. Da dort keine anderen Events definiert sind, führt lediglich der Ablauf des globalen Timers connectTime zur Ausgabe von "global timer 'connectTime' timed out!". Die globale Event-Definition am Skript-Ende ist hier also durch eine lokale Definition überlagert worden.
Ein gestarteter Timer kann jederzeit mit gelöscht werden. Wird der Name eines nicht existierenden oder schon abgelaufenen Timers angegeben, so wird der Befehl ignoriert. Ein laufender Timer kann zurückgesetzt werden, indem er erneut gestartet wird.