5. Call Processing Language


5.1. Einleitung

Zur Behandlung eingehender Anrufe wird eine Skriptsprache benötigt, die folgenden Kriterien genügt:

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.


5.2. Prozedurale vs. ereignisorientierte Sprache

Günstig scheint somit eine Sprache, die beides unterstützt: Prozedurale Programmierung für überwiegend sequentiell ablaufende Anwendungen und ereignisorientierte Programmierung für Anwendungen, bei denen viele gleiche Events unter verschiedenen Umständen, d.h. Zuständen auftreten können.

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.


5.3. Vergleiche mit anderen Sprachen

5.3.1. Modemsprachen

Bei der Wahl einer geeigneten Sprache liegt es aufgrund der ähnlichen Einsatzgebiete (Behandlung von Anrufen) nahe, einen Blick auf sog. Modemsprachen zu werfen. Dies sind Sprachen, die meist ein Verbindung mit Terminal-Emulationsprogrammen angeboten werden und die Aufgabe haben, den Verbindungsaufbau zu anderen Rechnern über serielle Leitungen zu automatisieren. Folgende Sprachen habe ich mir näher angeschaut:

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

Modem control languages

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.

5.3.2. Ereignisorientierte Sprachen

Die Suche nach ereignisorientierten Sprachen hatte die folgenden Sprachen zum Ergebnis:

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.


5.4. Call Processing Language (CPL)

Die gemachten Beobachtungen brachten mich dazu, mir über eine neue Sprache bzw. über eine Erweiterung der PhoneScript-Idee nachzudenken. Der Einsatz von Tcl erfüllt immerhin die ersten der beiden eingangs gestellten Forderungen nach einer möglichst einfachen, bereits bekannten bzw. leicht portierbaren Sprache. Weiterhin sind bei Integration eines Tcl-Interpreters auch in Zukunft neue Features automatisch verfügbar, der Call-Server muß lediglich mit der neuesten Version der Tcl-Library gelinkt werden.

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.

5.4.1. Finite-State-Machine

Da Tcl selbst keine Sprachkonstrukte für die Behandlung asynchroner Ereignisse besitzt, liegt es nahe, neben der Registrierung der neuen Anweisungen für das Call-Handling, aus einer Art Finite-State-Machine-Beschreibungssprache heraus einen Tcl-Interpreter zur Ausführung der eigentlichen Anweisungen aufzurufen. Diese Beschreibungssprache definiert alle Zustände der State-Machine in einer Datei (Skript) in beliebiger Reihenfolge. Anfangszustand ist dabei derjenige, der am Beginn des Skripts steht. Im folgenden wird exemplarisch ein Zustand in der Notation dieser Sprache, die ich Call Processing Language kurz CPL nennen will, dargestellt:

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.

5.4.2. ISDN-Timing

CPL-Skripte werden vom Call-Server ausgeführt, welcher nebenbei das an einigen Punkten zeitkritische ISDN-Protokoll abwickeln muß. Beim Ausführen von Tcl-Skripten mit Tcl_Eval() hat der Server keine Kontrolle über die Ausführungszeit der Tcl-Skripte und so kann es vorkommen, daß in der ISDN-Schicht Timer ablaufen, ohne rechtzeitg behandelt werden zu können. Im schlimmsten Fall kann eine Endlosschleife oder eine sleep Anweisung den gesamten Server blockieren. Auch innerhalb der Skriptsprache definierte Timer müssen jederzeit die Ausführung eines Anweisungsblockes unterbrechen können. Alternativ könnte man natürlich das Tcl-Skript parsen und jedes Statement einzeln mit Tcl_Eval() ausführen, in der Hoffnung, daß keine Anweisung eine bestimmte Zeit überschreitet. Hierfür benötigt man jedoch einen vollständigen Tcl-Parser und das wäre schon der halbe Implementierungsaufwand für eine neue Sprache.

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.


5.5. Sprachbeschreibung

Die möglichen Zustände, die bei der Behandlung eines Anrufes auftreten, können in CPL in Form einer Finite-State-Machine abgebildet werden. Es bleibt dem Anwender überlassen, ob er hiervon Gebrauch macht, oder die gesamte Anwendung in einem einzigen Zustand beschreibt. Ein CPL-Programm besteht somit aus mindestens einem Zustand. Für jeden Zustand werden die möglichen zu behandelnden Events definiert. Eintretende Events, für die keine Definition existiert, werden mit Ausnahme des Disconnect-Events ignoriert. Events können prinzipiell sein:

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).

5.5.1. Datentypen

Wie in Tcl so ist auch in CPL String der einzig unterstützte Datentyp. Für numerische Berechnungen, etwa mit der -Anweisung, erfolgt intern eine automatische Typumwandlung, ebenso bei Vergleichen zweier Variablen mit numerischem Inhalt.

5.5.2. Pattern-Matching

Mittels können beliebige Folgen von DTMF Tönen im ankommenden Audiostrom einer ISDN-Verbindung erkannt werden. Die Erkennung einer Folge löst das entsprechende Event aus, deren weitere Behandlung analog den anderen Events erfolgt. Die erkannte Zeichenfolge steht unmittelbar nach Auslösung des Events in der Systemvariablen sysDtmf zur Verfügung. Die zu erkennenden Folgen werden in der Event-Definition durch einen Matchstring definiert. Neben den eigentlichen DTMF-Zeichen 0123456789ABCD*# kann der Matchstring die in Tabelle [pm-meta] aufgelisteten Metazeichen enthalten.

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

Pattern matching meta characters

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

Beispiele für Matchstrings zur DTMF-Erkennung

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 "???" {
  ....
}

5.5.3. Event-Handling

Der erste im Skript definierte Zustand stellt zugleich den Anfangszustand dar. Bei Eingang eines Anrufes beginnt die Ausführung in dem Anweisungsblock der Event-Definition. Daher wird diese Event-Definition im Anfangszustand erzwungen, in allen anderen Zuständen hingegen nicht, d.h. beim Eintritt in einen solchen Zustand werden keine Aktionen ausgeführt.

Ü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.

5.5.4. CPL-Anweisungen

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.

Die redirect-Anweisung

Die -Anweisung dient der Umleitung des eingehenden Anrufes, also des Anrufes, der die Skript-Ausführung initiiert hat. Das Umleitungsziel wird in Form eines Uniform Resource Locators URL RFC 1738 [RFC1738] angegeben. Für Umleitungsziele ins Telefonnetz wird hierbei auf einen Vorschlag aus [Zig96:URL] zurückgegriffen. Hiernach wird ein Telefonanschluß wie folgt als URL dargestellt:

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 invite-Anweisung

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

Belegung der Felder im SIP-INVITE-Request

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.

5.5.5. Ausführung externer Programme

Beliebige Programme/Shell-Skripte können mit dem -Kommando ausgeführt werden. Da die Ausführungszeit der gestarteten Programme variabel bzw. dem CPL-Interpreter nicht bekannt ist, wird für das ausführende Kommando einen Prozeß erzeugt, der über Pipes mit dem Server-Prozeß verbunden ist, Abbildung [pic-cur-arch] veranschaulicht dies am Beispiel der Ausführung des Programms iAuCat, welches den Inhalt einer Audiodatei einliest und nach stdout schreibt.

Process communication during execution of external commands

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

Externe Programme

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
}
...

5.5.6. Variablen

CPL unterstützt derzeit nur globale Variablen, welche mit der -Anweisung erzeugt und initialisiert werden. Variablennamen können maximal 256 Zeichen lang sein, müssen mit einem Buchstaben beginnen und können Buchstaben und Ziffern enthalten. Zustands- oder prozedurlokale Variablen sind derzeit nicht vorgesehen. Anhang [app-sysvars] enthält eine Liste aller in CPL vordefinierten Variablen. Die mit "read only" gekennzeichneten Variablen können nicht mittels Kommando verändert werden; etwaige Zuweisungen führen zu einer Fehlermeldung (ERR117) und zum Abbruch des Skriptes. Bis auf sysCalls und sysConnections sind alle Variablen vollständig implementiert.

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.

5.5.7. Timer

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

In Abhängigkeit vom Geltungsbereich werden lokal und globale Timer unterschieden. Ein lokaler Timer wird mit timer timername local timeout und ein globaler mit timer timername global timeout gestartet. Dabei ist timername ein beliebiger Name und timeout die Zeit, nach deren Ablauf das Timer-Event ausgelöst wird. Letzteres kann mittels aufgefangen und behandelt werden.

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.


5.6. CPL Fehlermeldungen

Anhang [app-errors] enthält eine Liste aller Fehlermeldungen, die während der Skriptausführung auftreten können. Meldungen, die mit 1 beginnen, werden vom Skript-Compiler, Meldungen, die mit 2 beginnen von Skript-Interpreter generiert. Alle Meldungen werden nach stderr geschrieben und führen zum sofortigen Abbruch des Skriptes (nicht des Call-Servers). Bei der Abarbeitung eines CPL-Skriptes wird ein Logfile erstellt, in dem alle mit der -Anweisung erzeugten Meldungen abgelegt werden. Das Logfile trägt den Namen des Skriptes plus angehängtem .log.
File was created Wed Feb 26 18:31:47 1997 by tex2html