Anrufmonitor in LIMBAS


Verknüpfung eines Anrufs mit der LIMBAS-Datenbank

Limbas ist ein Open-Source PHP-Framework, das die Umsetzung von individuellen Datenbank-Anwendungen ermöglicht. Durch den modularen Aufbau, die SOAP-Schnittstelle und die Möglichkeit, es mit eigenen Skripten zu erweitern, lassen sich neue Features leicht in Limbas integrieren. Ein praktisches Beispiel ist die Anbindung der AVM FRITZ!Box an Limbas, um den Namen oder die Nummer eines Anrufers in Echtzeit im Browser anzuzeigen, Anrufe zu loggen oder diese automatisch mit einem bestehenden Datensatz zu verknüpfen. In diesem Artikel soll gezeigt werden, wie diese Funktionalität implementiert und an Limbas angebunden werden kann.


Um die gesamte Funktionalität umzusetzen, werden insgesamt fünf Dateien an unterschiedlichen Stellen hinzugefügt: Die Dateien callmonitor.php, callserver.php und CallList.class.php befinden sich auf einem Server, der sich sowohl mit der FRITZ!Box, als auch mit dem Limbas-Server verbinden kann. Man kann die Dateien aber auch direkt auf dem Limbas-Server platzieren. Auf diesem werden des Weiteren die Dateien ext_multiframe.inc und ext_main.js im Limbas-Extension-Verzeichnis (<LimbasVerzeichnis>/dependent/EXTENSIONS/) oder einem beliebigen Unterordner erstellt. Der gesamte Beispielcode kann im LIMBAS-Wiki eingesehen werden, Limbas selbst kann kostenlos heruntergeladen oder auch online getestet werden.

Der FRITZ!Box Callmonitor
Der Callmonitor einer FRITZ!Box ist ein Dienst, der Informationen über ein- und ausgehende Anrufe zur Verfügung stellt. Damit er genutzt werden kann, muss einmalig auf einem verbundenen Telefon „#96*5*“ gewählt werden, bzw. „#96*4*“ um ihn wieder zu deaktivieren. Wurde der Dienst aktiviert, werden auf TCP Port 1012 Informationen zu aktuellen Anrufen bereitgestellt, wobei jeder Anruf in mehrere Ereignisse aufgeteilt wird: Klingelt beispielsweise bei einem eingehenden Anruf das Telefon, wird das Ereignis RING ausgelöst. Im Gegensatz dazu wird ein ausgehender Anruf CALL genannt. CONNECT wird nur ausgelöst, wenn danach der angerufene Teilnehmer abhebt und dadurch die Verbindung zustande kommt. Wird das Telefonat im Anschluss beendet, wird ein DISCONNECT gesendet.
Für jedes dieser Ereignisse sendet die FRITZ!Box eine Zeichenkette (Abb. 1) mit zusätzlichen Informationen, wie Datum und Uhrzeit, anrufende/angerufene Nummer bzw. Nebenstelle, Dauer des Gesprächs in Sekunden (nur bei DISCONNECT) oder Connection-ID. Letztere wird dabei verwendet, um einen Anruf über mehrere Ereignisse hinweg identifizieren zu können.


Abb. 1: Output der FRITZ!Box, passend zu Ereignissen in Abb. 2


Auswertung des Callmonitors
Um diese Funktionalität der FRITZ!Box zu nutzen, wird ein Dienst benötigt, der dauerhaft ausgeführt wird, die bereitgestellten Daten auswertet, sich vom Limbas-Server zusätzliche Informationen holt und diese schließlich an alle verbundenen Limbas-Clients weitergibt. Diese Aufgabe ist in der Datei CallMonitor.php (Listing 1) implementiert.


Listing 1

Datei: callmonitor.php

...

# connect to fritzbox
$fritzboxSocket = fsockopen($fritzHost, $fritzPort);

# store last 10 calls
$callList = new CallList(10);

# wait for new lines
while(true) {
    $newLine = fgets($fritzboxSocket);
    if($newLine != null) {
        echo $newLine;
        handleFritzboxEvent($newLine);

    } else {
        sleep(1);
    }

}

function handleFritzboxEvent($line) {
    ...
    # split into parts
    $parts = split(';', $line);

    # extract data
    $dateTime = $parts[0];
    $type = $parts[1];
    $connectionId = $parts[2];

    # differ between event types
    if($type == 'RING') { # incoming ring
        $callerNr = $parts[3];

        # store phone nr
        $callList->onRing($connectionId, $callerNr);
    }
    ...

    limbasSocketNotification($connectionId, $type);

}

function limbasSocketNotification($cid, $type) {

    ...

    # Get data from limbas via soap given call data
    $data = null;
    $lmpar[0]['extension']["OnPhone{$type}"] = json_encode($callList->get($cid));
    $cdata = call_client($lmpar);
    if(count($cdata) > 0 && $cdata[0]){
        $data = $cdata[0];
    }

    ...

    # Notify socket server
    sendDataToWebsocketServer($wsHost, $wsPort, json_encode($data));
    ...

}

Über den jetzt offenen Port 1012 wird zunächst in PHP eine Socket-Verbindung mit der FRITZ!Box hergestellt. Die erhaltenen Anruf-Informationen werden dann mithilfe der Klasse CallList verwaltet. Die CallList übernimmt die Aufgabe, mehrere Ereignisse durch die Connection-ID zu einem Anruf zuzuordnen und die Daten der letzten zehn Anrufe zu speichern. Die Anzahl der gespeicherten Anrufe lässt sich jedoch leicht im Code ändern. Für jedes erhaltene Ereignis wird über die SOAP-Schnittstelle eine Anfrage an den Limbas-Server gesendet, welche die aktuell gesammelten Daten des Telefonats, wie z.B. Telefonnummer oder Gesprächsdauer enthält. Als Rückgabewert der Anfrage wird ein HTML-String erwartet. Was dieser beinhaltet, kann der Server-Administrator durch die Limbas-Extensions selbst entscheiden und implementieren. Eine mögliche Implementierung wird in Abschnitt „Beispiel-Implementierung der SOAP-Schnittstelle” beschrieben. Zu jedem Anruf gibt es dadurch eine HTML-Beschreibung, die auch in der CallList gespeichert wird. Die Beschreibungen der letzten Anrufe müssen nun nur noch an alle verbundenen Limbas-Clients gesendet werden. Da der CallMonitor dauerhaft auf eintreffende Ereignisse der FRITZ!Box wartet und somit nicht in der Lage ist mehrere Clients zu verwalten, wird ein weiterer Daemon benötigt.


Verteilung der Informationen an mehrere Clients Der CallServer (callserver.php) verwaltet in PHP mehrere WebSocket-Verbindungen von Limbas-Clients sowie die Verbindung mit dem CallMonitor. Erhält er von diesem eine Nachricht mit den HTML-Beschreibungen der letzten Anrufe, leitet er diese Nachricht an alle verbundenen Clients weiter. Damit neue Verbindungen sofort Anruf-Informationen erhalten und nicht auf das Senden eines neuen Ereignisses warten müssen, wird die letzte Nachricht des CallMonitors zusätzlich im CallServer zwischengespeichert.


Integration in das Limbas-Frontend Um das Feature zu vervollständigen, wird letztendlich noch eine Instanz benötigt, welche die Verbindung zum CallServer herstellt und dessen Informationen in Limbas darstellt. Limbas kann durch verschiedene Skripte erweitert werden. Welche das sind, ist im Wiki dokumentiert. In Erweiterungsdateien mit dem Namen ext_multiframe.inc (Listing 2) können neue Elemente zum Multiframe, dem Menü an der rechten Seite, hinzugefügt werden. Das erstellte Element wird mit einem Telefon-Icon dargestellt und kann auf- bzw. zugeklappt werden (Abb. 2), wobei jedes Mal eine selbst definierte JavaScript-Funktion ausgeführt wird. Diese wird in ext_main.js implementiert, stellt zunächst die Verbindung mit dem CallServer her und zeigt bei eintreffenden Nachrichten die mitgesendeten Beschreibungen der Anrufe im erstellten Multiframe-Element an. Um dem Nutzer Einsicht in den Status der Verbindung zum CallServer zu geben, ändert sich die Farbe des Telefon-Icons: Ist das Multiframe-Element zugeklappt, wird keine Verbindung aufgebaut (grau). Wenn das Element aufgeklappt ist, steht rot für eine fehlgeschlagene Verbindung, gelb für einen Verbindungsversuch und grün für eine bestehende Verbindung. Bei einer neuen Nachricht, also einer Aktualisierung der Anrufe, wird das Icon kurzzeitig blau.


Listing 2

Datei ext_multiframe.inc

$melements["id"] = "Phone";
$melements["name"] = "Telefon";
$melements["link"] = "";
$melements["target"] = "main";
$melements["preview"] = "";
$melements["event"] = "limbas_callmonitor_toggle";
$melements["params"] = "";
$melements["gicon"] = "lmb-phone";
$melements["autorefresh"] = 1;
$menu[0][] = $melements;

Beispiel-Implementierung der SOAP-Schnittstelle Wie bereits beschrieben, sendet der CallMonitor eine SOAP-Anfrage an den Limbas-Server. Die Schnittstelle kann dabei vom Administrator in der Extension-Datei ext_soap.inc (Listing 3) implementiert werden. Es werden vier Funktionen erwartet: extSoapOnPhoneRing, extSoapOnPhoneCall, extSoapOnPhoneConnect und extSoapOnPhoneDisconnect. Je nach ausgelöstem Ereignis wird dabei die passende Funktion aufgerufen.


Listing 3


Datei: ext_soap.inc

# display name and 'outgoing'-icon
function extSoapOnPhoneCall($param_string=null, $param_array=null, $lmb=null) {
    return getCredentials($param_string, 'lmb-sign-out');
}

# display name and 'ingoing'-icon (bold)
function extSoapOnPhoneRing($param_string=null, $param_array=null, $lmb=null) {
    return '<b>' . getCredentials($param_string, 'lmb-sign-in') . '</b>';
}

...

function getCredentials($param_string, $imgClass) {
    # decode input
    $decoded = json_decode($param_string, 1);

    # get phone nr
    $phoneNr = $decoded['phoneNr'];

    # ensure data is stored in session
    if(!$_SESSION['callmonitor']) { $_SESSION['callmonitor'] = array(); }
    if(!$_SESSION['callmonitor']['nr'.strval($phoneNr)]) {

        # store customer data in session
        $_SESSION['callmonitor']['nr'.strval($phoneNr)] = getCustomerDataByPhoneNr($phoneNr);
    }

    # get date
    if($decoded['startDate'] != null) {

        # extract start time
        $dateObj = DateTime::createFromFormat('d.m.y H:i:s', $decoded['startDate']);
        $toolTip = $dateObj->format('H:i:s');

        # add end time on disconnect
        if($decoded['lengthSec'] != null) {
            $dateObj->modify("+{$decoded['lengthSec']} seconds");
            $toolTip .= ' - ' . $dateObj->format('H:i:s');
        }

    } else {
        $toolTip = 'Anruf';
    }     

    # return customer data to callmonitor
    $customerData = $_SESSION['callmonitor']['nr'.strval($phoneNr)];
    if($customerData) {
        ob_start();
        pop_menu2("{$customerData['contactName']} ($phoneNr)", "$toolTip", null, $imgClass, null, "parent.main.location.href='main.php?action=gtab_change&gtabid=4&ID={$customerData['customerId']}';");
        return ob_get_clean();

    } else {
        ob_start();
        pop_menu2("$phoneNr", "$toolTip", null, $imgClass, null, null);
        return ob_get_clean();
    }

}


In der Demodatenbank kann diese Funktionalität beispielsweise genutzt werden, um direkt den Namen und den Link zum Datensatz des anrufenden Kunden/Kontakts anzuzeigen, sowie Zeitpunkt und Dauer des Gesprächs in einer eigenen Tabelle zu loggen und direkt mit dem Kunden zu verknüpfen. In der Limbas Demo-Datenbank gibt es eine Tabelle Kunden mit Feldern wie ID, Firmenname oder Telefonnummer. Diese ist über eine 1:n-Verknüpfung mit der Tabelle "Kontakte" verknüpft, in welcher beispielsweise Name des Kontakts oder dessen Telefonnummer gespeichert sind. Um den passenden Kunden zur anrufenden Nummer zu finden, wird zunächst die Tabelle "Kontakte" durchsucht. Wurde eine Kontaktperson gefunden, kann über die Verknüpfung direkt der zugehörige Kunde herausgefunden werden. War die Suche jedoch erfolglos, wird zusätzlich noch die Kunden-Tabelle durchsucht. Wurde ein Kunde gefunden, kann ein Link generiert werden, der den Nutzer direkt zum Detail-Datensatz des Kunden führt. Zusätzlich wird ein Icon angezeigt, das kennzeichnet, ob der Anruf ein- oder ausgehend ist. In der Funktion extSoapOnPhoneDisconnect ist zusätzlich das Speichern des Anrufs sowie das Verknüpfen mit dem Kunden-Datensatz implementiert. In Abbildung 2 sieht man auf der rechten Seite im Telefonelement, dass zuerst eine unbekannte Nummer angerufen hat (unterste Zeile). Beim zweiten Telefonat (mittlere Zeile) wurde zur angerufenen Nummer der Kontakt „Alfred Unterberger“ gefunden. Durch das veränderte Icon lässt sich erkennen, dass es sich diesmal um einen ausgehenden Anruf handelt. Als neustes Ereignis (oberste Zeile) wird ein eingehender Anruf desselben Kontakts angezeigt. Da in diesem Szenario noch nicht abgehoben wurde, sondern das Telefon noch klingelt, wird der Text fett dargestellt. Klickt man auf einen Eintrag des Elements, wird im Limbas-Hauptfenster der zugehörige Detail-Datensatz des Kunden (hier „Alfreds Futterkiste“) angezeigt. In diesem ist auch der vorherige Anruf des Kontakts zu sehen.


Abb. 2: LIMBAS im Browser


Fazit

Durch die Erweiterbarkeit des Limbas-Frameworks konnte der Callmonitor an Limbas angebunden werden. Die Funktionalität wurde mithilfe der FRITZ!Box gezeigt, es ist aber auch möglich, jede andere Telefonanlage in das System zu integrieren, sofern sie eine geeignete Schnittstelle bereitstellt. Es wurde die Funktionsweise der Anbindung, sowie eine mögliche Implementierung der Schnittstelle beschrieben. Welche Informationen letztendlich bei einem Anruf dargestellt werden sollen, kann jeder Limbas-Entwickler selbst entscheiden. Somit ist die Callmonitor-Funktionalität – wie auch Limbas – nicht nur auf ein Szenario begrenzt, sondern auf viele Wünsche anpassbar.


Artikel aus: PHP Magazin - 5.17 - Seite 30-33 (Autor: Peter Greth, LIMBAS GmbH)