Nudge am 06.02.2009

Asterisk: CDR im hangup-Kontext

in Linux, PHP | Tags: Asterisk, Call Data Record, CDR, hangup, Kontext, PHP, Zeitstempel

Sowie ich heute recherchiert habe, ist es bis heute bei allen Asterisk-Versionen gleich geblieben: Man kann innerhalb des h-Kontextes (“after hangup”) noch nicht auf die Felder des Call Data Records (CDR) zugreifen. Das bedeutet ganz konkret, dass sich innerhalb der Abarbeitung eigener Anrufskripte in /etc/asterisk/extensions.conf kaum zur Laufzeit eines Anrufs sinnvoll abrechnen lässt. Doch es gibt einen kleinen Trick, dies auszuhebeln… 🙂

Asterisk und der Call Data Record

Normalerweise geht man als Benutzer einer Telefonsoftware davon aus, dass nach dem Auflegen gewisse Daten eines Anrufs verfĂĽgbar sind: Woher der Anrufer kam, wer den Anruf entgegennahm und wie lange der Anruf dauerte. Vor allem die Anrufdauer ab dem Antworten ist besonders interessant, da es die abzurechnende Telefonzeit ist und oft mit Kosten verbunden.

Asterisk schreibt den CDR, wenn richtig konfiguriert, in eine Datenbank. Das ist sehr schön, und man kann die Daten auch sehr gut am Ende des Tages oder Monats abrechnen. Möchte man allerdings die Daten bereits am Ende eines Gesprächs benutzen, so sitzt man in der Tinte: Erst nach kompletter Abarbeitung aller Kontexte erstellt Asterisk die CDR-Daten wie Dauer ab klingen in CDR(duration), Dauer des Telefonats CDR(billsec) et cetera.

Es gab Versuche, den internen Asterisk-Code fĂĽr diese Abrechnungszwecke umzuschachteln, also den CDR nach Abbau der Bridge zu schreiben, und dann erst in den h-Kontext (die Abarbeitung “after hangup”) zu wechseln. Dazu gab es bereits die Option

1
endbeforehexten=yes
endbeforehexten=yes

in der Datei /etc/asterisk/cdr.conf. Allerdings fĂĽhrte das zum kompletten Crash der Software, also wurde dies wieder verworfen.

So gehts: CDR(duration) im hangup-Kontext selbst gemacht

Mit ein bisschen Bastelei lassen sich aber die benötigten Daten weitgehend selbst erstellen. Dazu muss man wissen, dass zwar CDR(billsec) und CDR(duration) stets auf 0 stehen, aber der derzeitige Zeitstempel immer per ${EPOCH} zur Verfügung steht. Ebenso steht die Abnehm-Zeit in CDR(answer) zur Verfügung. Ob CDR(start) da ist, kann ich gerade nicht genau beantworten, aber dies lässt sich ebenso ableiten. Man nutze einfach lokale Variablen:

1
2
3
4
5
6
[Mein-Kontext]
exten => s,1,Set(Start=${EPOCH})
exten => s,n,Dial(SIP/100,30,rtT)
exten => h,1,Set(Ende=${EPOCH})
exten => h,n,NoOp(Dauer: $[${Ende} - ${Start}])
exten => h,n,Hangup()
[Mein-Kontext]
exten => s,1,Set(Start=${EPOCH})
exten => s,n,Dial(SIP/100,30,rtT)
exten => h,1,Set(Ende=${EPOCH})
exten => h,n,NoOp(Dauer: $[${Ende} - ${Start}])
exten => h,n,Hangup()

Wer die Syntax von Asterisk nicht kennt, wird jetzt wahrscheinlich die Hände ĂĽber den Kopf zusammenschlagen, aber so ist Asterisk nun einmal: Friss oder Stirb 🙂 Also bevorgen die Herrschaften folgendermaĂźen zu speisen: Vor dem Aufbau der SIP-Verbindung per  Dial() -Funktion wird der aktuelle Zeitstempel in die Variable Start gegossen. Und nach dem Auflegen wird als erstes der aktuelle Zeitstempel in die Variable Ende gelegt. Mithilfe der NoOp()-Funktion wird eine Ausgabe auf der Asterisk-Console durchgefĂĽhrt, in der eine Berechnung $[…] stattfindet, worin Ende – Start, also die Dauer ab dem Klingeln ausgegeben wird: Fertig ist CDR(duration), verfĂĽgbar im hangup-Kontext.

So gehts: CDR(billsec) im hangup-Kontext selbst gemacht

Auf gleiche Weise lösen wir nun das Problem mit der fehlenden Gesprächsdauer. Dazu muss man wissen, dass der Zeitpunkt des Abhebens schon zur Laufzeit in CDR(answer) anliegt. Allerdings nicht als Unix-Timestamp (ein Ganzzahliger Sekundenwert seit dem 01.01.1970) wie oben in ${EPOCH}, sondern als Zeitangabe im Stil “Jahr-Monat-Tag Stunde:Minute:Sekunde”, also fĂĽr Kenner von date()-Funktionen die Syntax “Y-m-d H:i:s”. Sehr schön zu lesen, leider innerhalb von Asterisk nicht so schön brauchbar. Allerdings lässt sich immerhin mit Hilfe der Asterisk-Funktion LEN() sagen, ob ĂĽberhaupt abgenommen wurde oder nicht:

1
2
3
4
5
exten => h,1,GotoIf($[${LEN(0${CDR(answer)})} > 1]?Angenommen,Nichtangenommen)
exten => h,n(Angenommen),NoOp(Juhu, Angenommen!)
exten => h,n,Hangup()
exten => h,n(Nichtangenommen),NoOp(Noe, nicht angenommen!)
exten => h,n,Hangup()
exten => h,1,GotoIf($[${LEN(0${CDR(answer)})} > 1]?Angenommen,Nichtangenommen)
exten => h,n(Angenommen),NoOp(Juhu, Angenommen!)
exten => h,n,Hangup()
exten => h,n(Nichtangenommen),NoOp(Noe, nicht angenommen!)
exten => h,n,Hangup()

Diese Syntax schon wieder! Hier muss man folgendes bemerken: Ein Vergleich in $[…] benutzt einen variablen Wert ${…}, der aus der Funktion CDR(answer) kommt, allerdings schreiben wir noch eine 0 davor. Dies hat folgende Bewandnis: Sollte nicht abgenommen worden sein, so ist CDR(answer) nicht gesetzt. Asterisk hat dann das Problem, eine ungesetzte Variable zu benutzen und meckert herum. Um dieses Hinderniss zu umschiffen, nehmen wir einfach vorn eine Null hinzu und schauen, ob die Variablenlänge größer als 1 ist.

Die Extension-Einträge 2 und 4 benutzen Labels, die per GotoIf() angesprungen werden können: Nach der Bedingung kommt ein Fragezeichen, dann das Label, falls alles geklappt hat, nach einem Komma dann der else-Zweig. Der else-Zweig ist hier nach dem If-Zweig notiert und würde eigentlich beim Zutreffen der Bedingung mit ausgeführt. Hier wirkt die Hangup()-Funktion also wie eine Art break-Anweisung innerhalb einer switch-Verzweigung.

Wie kommen wir jetzt an die Dauer? Nun, wir mĂĽssen den Wert CDR(answer) in einen Zeitstempel umrechnen. Aber das ĂĽberlassen wir einfach einem externen Abrechnungstool. Denn intern brauchen wir es eigentlich nicht mehr:

1
2
3
4
5
6
7
exten => h,1,GotoIf($[${LEN(${0CDR(answer)})} > 1]?Angenommen,Nichtangenommen)
exten => h,n(Angenommen),NoOp(Juhu, Angenommen!)
exten => h,n,System('php -f /mein/pfad/cdr_abrechnung.php \
                        ${CallerID(num)} ${EXTEN} "${CDR(answer)}" ${EPOCH}');
exten => h,n,Hangup()
exten => h,n(Nichtangenommen),NoOp(Noe, nicht angenommen!)
exten => h,n,Hangup()
exten => h,1,GotoIf($[${LEN(${0CDR(answer)})} > 1]?Angenommen,Nichtangenommen)
exten => h,n(Angenommen),NoOp(Juhu, Angenommen!)
exten => h,n,System('php -f /mein/pfad/cdr_abrechnung.php \
                        ${CallerID(num)} ${EXTEN} "${CDR(answer)}" ${EPOCH}');
exten => h,n,Hangup()
exten => h,n(Nichtangenommen),NoOp(Noe, nicht angenommen!)
exten => h,n,Hangup()

Damit rufen wir das Skript zum Beispiel mit folgenden Variablen auf:

1
2
3
4
5
6
7
$argv = array (
   [0] ...
   [1] => 0123456789
   [2] => 100
   [3] => 2009-02-06 20:07:00
   [4] => 12392827161
);
$argv = array (
   [0] ...
   [1] => 0123456789
   [2] => 100
   [3] => 2009-02-06 20:07:00
   [4] => 12392827161
);

mit strtotime() lässt sich $argv[3] direkt in einen Zeitstempel umrechnen. Von $argv[4] subtrahiert, ergibt sich CDR(billsec). Geschafft – Das Logging-Skript hat nun alle Daten, die es braucht!


Das mark ich mir: Alltagz Mr Wong Yigg Del.icio.us Yahoo MyWeb Blinklist Google folkd
 

3 responses to “Asterisk: CDR im hangup-Kontext”

  1. marcom says:

    Hi quark1980, wie habt ihr das mit ResetCDR(w) gelöst ??

    Danke und Gruss

  2. Nudge says:

    Geht das bereits bei Asterisk 1.2? Ich werde es morgen gleich mal probieren, danke auf jeden Fall fĂĽr den Tip!

  3. quark1980 says:

    Wir hatten bei einem Kunden ein ähnliches Problem.

    Warum führst du nicht ResetCDR(w) aus bevor du nach h-context switcht? Das ist doch um einiges eleganter. Aber OK, wir mussten dafür auch ne weile recherchieren 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *