Tutorial: Ein Incoming-Skript schreiben

In diesem Abschnitt werde ich Ihnen Schritt für Schritt zeigen, wie Sie Ihr eigenes Incoming-Skript schreiben können. Wir fangen an, indem wir einfach jeden eingehenden Anruf annehmen und einen Piep abspielen. Das letzte Beispiel ist ein sehr einfacher, aber nützlicher Anrufbeantworter mit Fax-Erkennung und -Empfang.

Grundlagen und ein wirklich dummer Anrufbeantworter

Lassen Sie uns mit einem sehr einfachen Fall anfangen: wir nehmen alle eingehenden Anrufe an, piepen und nehmen etwas auf, sodass wir eine Audio-Datei haben, mir der wir später ein bisschen rumspielen können. Legen Sie zuerst irgendwo ein neues Verzeichnis an, für das root Schreibrechte haben muss. Wir brauchen auch eine Test-Audio-Datei, um sie zu verschicken. Lassen Sie uns den Piep nehmen, der mit CapiSuite geliefert wird.

mkdir capisuite-examples
chmod 777 capisuite-examples # schreibbar für alle
cd capisuite-examples
cp /usr/local/share/capisuite/beep.la .

Vielleicht müssen Sie den Pfad in der letzten Zeile anpassen, damit er zu Ihrer Installation passt.

Kopieren Sie das hier gezeigte Beispiel jetzt in eine Datei namens example.py in diesem Verzeichnis. Vergessen Sie nicht, den Pfad my_path zu ändern.

Beispiel 2.1. example.py

import capisuite1

my_path="/path/to/the/just/created/capisuite-examples/"2

def callIncoming(call,service,call_from,call_to):3
	capisuite.connect_voice(call,10)4
	capisuite.audio_send(call,my_path+"beep.la")5
	capisuite.audio_receive(call,my_path+"recorded.la",20,3)6
	capisuite.disconnect(call)7

Lassen Sie uns das Skript Zeile für Zeile durchgehen:

1

Das capisuite-Modul, das alle CapiSuite-spezifischen Funktionen enthält, wird importiert. Alle CapiSuite-Objekte (Funktionen, Konstanten) in diesem Modul können nun über capisuite.objectname angesprochen werden. Sie können auch ein "from capisuite import *" machen, das alle Objekte in den aktuellen Namespace einfügt - aber dies wird nicht empfohlen, da sie mit anderen globalen Objekten kollidieren können.

Anmerkung

Das importierte Modul capisuite ist nicht als extra Modul verfügbar, sodass Sie dies nicht in einer interaktiven Python-Session tun können. Es wird in das CapiSuite-Binary eingebunden und ist nur in Skripten, die von CapiSuite interpretiert werden, verfügbar.

2

ändern Sie dies bitte auf den echten Pfad, aus dem Sie diese Beispiele aufrufen.

3

Defininieren Sie die erforderliche Funktion wie im „Das Incoming-Skript“ beschrieben.

4

Dies ist die erste CapiSuite-Funktion, die wir verwenden: Sie nimmt den wartenden Anruf an. Der erste Parameter teilt CapiSuite mit, welchen Anruf Sie meinen. Dieser Parameter ist für fast alle CapiSuite-Funktionen erforderlich. Ok, wir haben jetzt nur einen Anruf - aber stellen Sie sich ein Incoming-Skript vor, das zur selben Zeit auch noch einen ausgehenden Anruf tätigen will (zum Beispiel, um einen Anruf weiterzuleiten). In diesem Fall wüßte CapiSuite, welchen Anruf Sie meinen - deshalb müssen Sie die übergebene Referenz an alle Funktionen weiterreichen, die mit der Verbindung zu tun haben.

Sie können CapiSuite auch sagen, dass es eine bestimmte Zeit warten soll, bevor es einen Anruf annehmen soll - dafür wird der zweite Parameter benutzt. Diese Skript wartet also 10 Sekunden bevor die Verbindung zum Anrufer hergestellt wird. Glauben Sie nicht, dass dieser Paramter überflüssig ist und dass Sie stattdessen eine Python-Funktion (z.B. time.sleep()) aufrufen können, um zu warten. Dies wird für Verzögerungen länger als 4 (oder 8, hängt von Ihrem ISDN-Setup ab) Sekunden nicht funktionieren, da der Anruf abgewiesen wird, wenn er nicht von einem ISDN-Device "vor-angenommen" wird, indem Ihrem Netz-Provider mitgeteilt wird, dass es klingelt. CapiSuite macht das, wenn nötig - nutzen Sie also bitte diesen Parameter.

5

Dieser Aufruf sollte ziemlich selbsterklärend sein. Er sendet die Audio-Datei, die unter beep.la gespeichert ist.

6

Nimmt eine Audio-Datei von maximal 20 Sekunden auf - es wird eher aufgehört, wenn mehr als 3 Sekunden Stille erkannt werden.

7

Last, but not least - wird die Verbindung beendet. Auflegen. Fertig. Es ist vorbei.

Die CapiSuite-Konfiguration muss geändert werden, um das gerade erstellte Skript nutzen zu können. Editieren Sie dazu Ihre capisuite.conf und ersetzen Sie den incoming_script-Wert durch den Pfad zur Datei, die Sie gerade erstellt haben (siehe „Konfiguration von CapiSuite“) und starten Sie CapiSuite neu.

Probieren Sie's mal aus: Rufen Sie irgendeine Nummer Ihrer ISDN-Karte an - wenn sie am ISDN-Anschluss hängt, geht jede Nummer (MSN) - wenn sie an einer Telefonanlage hängt, müssen Sie eine Nummer wählen, die in der Anlage für die Karte konfiguriert ist.

Sie sollten einen Piep hören und dann können Sie etwas auf diesen primitiven Anrufbeantworter sprechen. Legen Sie bitte nicht auf, bevor das Skript dies macht, da dieser Fall noch nicht behandelt wird. Warten Sie einfach 3 Sekunden, nachdem Sie etwas gesagt haben - es sollte die Verbindung nach dieser Stille-Periode trennen.

Wenn das nicht funktioniert, haben Sie vielleicht einen Fehler beim Kopieren des Skripts gemacht. In diesem Fall können Sie sich das Log und Error-Log von CapiSuite anschauen. Sie finden dies unter /var/log/capisuite oder /usr/local/var/log/capisuite, wenn Sie den Pfad nicht geändert haben.

Ein guter Trick, um auf Syntaxfehler zu prüfen, ist, Ihre Skripte durch den normalen Python-Interpreter laufen zu lassen. Rufen Sie dazu python /path/to/your/example.py auf. Natürlich wird er sich über das import capisuite beschweren, da dies kein Standard-Python-Modul ist. Aber bevor er dies macht, prüft er die Syntax Ihres Skripts - wenn Sie also irgendeinen anderen Fehler bekommen, beheben Sie ihn und probieren Sie es nochmal. Wenn Sie nur

Traceback (most recent call last):
  File "../scripts/incoming.py", line 16, in ?
    import capisuite,cs_helpers
ImportError: No module named capisuite

bekommen, hat Ihr Skript die korrekte Syntax.

Ich hoffe, Sie haben Ihr Skript jetzt zum Laufen bekommen - wenn nicht, zögern Sie nicht, auf der CapiSuite-Mailingliste zu fragen, wenn Sie zuvor ein Python-Tutorial gelesen haben.

Im nächsten Abschnitt werden wir eine Ansage verwenden. Nehmen Sie also einge Worte mit diesem einfachen Skript auf und verschieben Sie die erzeugte Datei recorded.la nach announce.la.

Verbesserungen in eine brauchbaren (?) Zustand

Gut, es ist wirklich nicht so schön, dass der Anrufer nicht auflegen darf - und es ist schlecht, dass wir alle eingehenden Anrufe annehmen - dadurch nehmen wir vielleicht der Mutter ihre wichtigen Anrufe weg.

Lassen Sie uns das schnell verbessern.

Beispiel 2.2. example.py, verbessert

import capisuite

my_path="/path/to/the/just/created/capisuite-examples/"

def callIncoming(call,service,call_from,call_to):
	try:1
		if (call_to=="123"):2
			capisuite.connect_voice(call,10)
			capisuite.audio_send(call,my_path+"announce.la")3
			capisuite.audio_send(call,my_path+"beep.la")
			capisuite.audio_receive(call,my_path+"recorded.la",20,3)
			capisuite.disconnect(call)
		else:
			capisuite.reject(call,1)4
			capisuite.disconnect(call)5
	except capisuite.CallGoneError:
		capisuite.disconnect(call)6
1

CapiSuite sagt dem Skript, dass die Gegenstelle aufgelegt hat, indem die Exception namens CallGoneError ausgelöst wird. Sie sollten deshalb Ihren Code immer in ein try-Statement einfassen und die ausgelöste Exception am Ende Ihres Skripts (oder früher, wenn nötig) behandeln. Diese Exception kann durch ein CapiSuite-Kommando ausgelöst werden.

2

Wir schauen uns die angerufene Nummer an (ersetzen Sie bitte 123 durch die Nummer, die CapiSuite akzeptieren soll)...

3

Die Ansage, die wir im letzten Abschnitt aufgenommen haben, wird abgespielt. Wenn Sie das nicht möchten, nehmen Sie einfach eine neue auf und verschieben Sie die Datei recorded.la wieder nach announce.la.

4

Der Anruf wird ignoriert. Der zweite Parameter teilt den genauen Grund für die Ablehnung mit - Sie können den Anruf ignorieren (jedes andere ISDN-Device oder Telefon klingelt für diese Nummer weiter), indem Sie 1 angeben, aktiv die Verbindung beenden, indem Sie 2 angeben, oder irgend einen Fehler-Code, der in der ISDN-Spezifikation enthalten ist (siehe „ISDN-Fehler-Codes“ für die verfügbaren Codes).

5

Sie müssen immer am Ende Ihres Skripts disconnect aufrufen, da dieses immer auf das Ende des Anrufs wartet, während reject nur die Ablehnung des Anrufs auslöst. Andernfalls erhalten Sie eine Warning im Error-Log.

6

Dies ist der Exception-Handler für CallGoneError - die Exception, die CapiSuite auslöst, wenn der Anruf vom anderen Gespächspartner beendet wird. Sie sollten hier auch disconnect aufrufen, um zu warten bis der Anruf vollständig beendet wurde.

Speichern Sie dies wieder als example.py und probieren Sie es aus. Es ist nicht erforderlich, CapiSuite neu zu starten, da alle Skripte jedesmal, wenn sie ausgeführt werden, neu gelesen werden. Jetzt dürfen Sie auch auflegen ;-).

Verwendung vernünftiger Dateinamen

Wir haben immer den selben Namen verwendet, um die aufgenommene Nachricht zu speichen, was natürlich nicht sinnvoll ist. Wir sollten wirklich für jeden neuen Anruf einen neuen Namen wählen. Dies ist nicht so einfach, wie es sich anhört - sie müssen sicherstellen, dass der verwendete Algorithmus auch noch mit mehreren Anrufen, die zur selben Zeit ankommen, funktioniert. Glücklicherweise hatte der hilfsbereite Programmierer von CapiSuite das selbe Problem, sodass wir seinen (hmmm... meinen?) Code verwenden könenn.

Das Python-Modul cs_helpers.py enthält einige hilfreiche Funktionen, die von den Standard-Skripten, die mit CapiSuite geliefert werden, benötigt werden, die aber auch in Ihren eigenen Skripten ganz nützlich sein können. Es enthält die Funktion uniqueName, die genau das macht, was wir hier brauchen. Die Syntax lautet:

filename=cs_helpers.uniqueName(directory,prefix,sufix)

Die Funktion sucht einen neuen eindeutigen Dateinamen im angegebenen directory. Der erzeugte Dateiname ist "prefix-XXX.suffix", wobei XXX die nächste freie Nummer, angefangen bei 0, ist. Die nächste freie Nummer wird in der Datei prefix-nextnr vermerkt und der erzeugte Name wird zurück gegeben.

Wir können diesen Aufruf einfach in unser Skript einbauen:

Beispiel 2.3. Verwendung eindeutiger Dateinamen

import capisuite,cs_helpers

my_path="/path/to/the/just/created/capisuite-examples/"

def callIncoming(call,service,call_from,call_to):
	try:
		if (call_to=="123"):
			filename=cs_helpers.uniqueName(my_path,"voice","la")
			capisuite.connect_voice(call,10)
			capisuite.audio_send(call,my_path+"announce.la")
			capisuite.audio_send(call,my_path+"beep.la")
			capisuite.audio_receive(call,filename,20,3)
			capisuite.disconnect(call)
		else:
			capisuite.reject(call,1)
	except capisuite.CallGoneError:
		capisuite.disconnect(call)

Wenn es Sie interessiert, welche anderen Funktionen in cs_helpers.py definiert sind, schauen Sie sich die Referenz unter dem „cs_helpers.py“ an.

Automatische Fax-Erkennung und -Empfang

Als letzten Schritt möchte ich Ihnen zeigen, wie Fax-Erkennung und -Empfang funktionieren und wie man vom Sprach- in den Fax-Modus umschaltet.

Hier ist das letzte und komplizierteste Beispiel dieses Abschnitts. Es führt vier neue CapiSuite-Funktionen ein und zeigt, wie die Funktionalität in eine andere Funktion aufgeteilt werden werden kann, die von callIncoming genutzt wird. Es gibt viele änderungen, die weiter unten beschrieben sind - aber die meisten davon sollten nahezu selbsterklärend sein. Ich glaube daher nicht, dass dieser letzte Schritt zu umfangreich ist. Und Sie wollen ja nicht noch 10 weitere Schritte lesen, stimmt's? ;-)

Beispiel 2.4. Hinzufügen der Fax-Funktionen

import capisuite,cs_helpers,os1

my_path="/path/to/the/just/created/capisuite-examples/"

def callIncoming(call,service,call_from,call_to):
	try:
		if (call_to=="123"):
			filename=cs_helpers.uniqueName(my_path,"voice","la")
			capisuite.connect_voice(call,10)
			capisuite.enable_DTMF(call)2
			capisuite.audio_send(call,my_path+"announce.la",1)3
			capisuite.audio_send(call,my_path+"beep.la",1)
			capisuite.audio_receive(call,filename,20,3,1)
			dtmf=capisuite.read_DTMF(call,0)4
			if (dtmf=="X"):5
				if (os.access(filename,os.R_OK)):6
					os.unlink(filename)
				faxIncoming(call)7
			capisuite.disconnect(call)
		else:
			capisuite.reject(call,1)
	except capisuite.CallGoneError:
		capisuite.disconnect(call)

def faxIncoming(call):
	capisuite.switch_to_faxG3(call,"+49 123 45678","Test headline")8
	filename=cs_helpers.uniqueName(my_path,"fax","sff")
	capisuite.fax_receive(call,filename)9
1

In diesem Beispiel brauchen wir zum ersten Mal ein normales Python-Modul. Das os-Modul enthält Funktionen für alle Arten von Betriebssystem-Diensten und wird hier benötigt, um eine Datei zu löschen.

2

DTMF ist die Abkürzung für Dual Tone Multi Frequency. Dies sind die Töne, die generiert werden, wenn Sie die Ziffern auf Ihrem Telefon drücken, und werden normalerweise genutzt, um zu wählen. Sie werden auch von modernen Fax-Geräten gesendet, bevor die übertragung beginnt. Deshalb können die selben Funktionen zur Erkennung von gedrückten Tasten und von Fax-Geräten verwendet werden.

Bevor irgendein DTMF von CapiSuite erkannt wird, muss die entsprechende Funktion mit enable_DTMF aktiviert werden.

3

Alle Audio-Sende- und -Empfangsfunktionen unterstützen das Abbrechen, wenn ein DTMF-Ton erkannt wird. Dies wird aktiviert, indem eine "1" als letzter Parameter übergeben wird. Es verhindert auch, dass die Funktion startet, wenn ein DTMF-Zeichen zuvor erkannt, aber noch nicht vom Skript gelesen wurde.

4

CapiSuite speichert alle empfangenen DTMF-Signale in einem Puffer, von wo sie jederzeit wieder gelesen werden können. Gelesen werden sie von read_DTMF, das auch den Puffer löscht. Sie gibt alle empfangenen Zeichen in einem String zurück. Wenn der Anrufer "3","5","*" drückt, bekommen Sie "35*".

Die 0 weist CapiSuite an, nicht auf DTMF-Signale zu warten - wenn keine vorhanden sind, wird einfach ein leerer String zurück gegeben. Es ist auch möglich zu sagen, dass eine bestimmte Zeit gewartet werden soll oder bis eine bestimmte Anzahl von Signalen empfangen wurden.

Anmerkung

Bitte beachten Sie, dass es nicht erforderlich ist, nach jeder Audio-Sende- oder -Empfangsfunktion auf empfangene DTMF zu prüfen. Aktivieren Sie einfach den DTMF-Abbruch in allen Befehlen in einem Block und prüfen Sie auf empfangene Töne nach dem ganzen Block.

5

Fax-Geräte senden einen speziellen Ton, der vom CAPI als "X" repräsentiert wird. Wenn Sie also den String "X" empfangen, ruft ein Fax-Gerät an und wir sollten unsere Fax-Routinen starten.

6

Möglicherweise war die Ansage so kurz, dass die Aufnahme schon begonnen hat, bevor das Fax erkannt wurde. Wir wollen keine Datei speichern, die nur das Fax-Piepen enthält. Deshalb testen wir, ob sie erzeugt wurde (os.access prüft die Existenz einer Datei) und löschen sie wenn nötig, indem wir os.unlink aufrufen.

7

Fax-Behandlung wurde in einer separaten Funktion realisiert, die hier aufgerufen wird.

8

Bis jetzt ist diese Verbindung im Sprach-Modus (der von connect_voice gesetzt wurde). Wenn wir nun ein Fax empfangen wollen, müssen wir den Modus auf Fax umstellen. Dies wird mit switch_to_faxG3 gemacht. Da das Fax-Protokoll einige zusätzliche Parameter benötigt, müssen sie hier angegeben werden. Der erste String ist die sog. Fax-Gerätekennung, die an das anrufende Fax geschickt und die im Protokoll angezeigt wird, während der zweite eine Fax-Titelzeile ist. Diese Titelzeile wird hauptsächlich beim Versenden von Faxen verwendet. Um ehrlich zu sein, ich persönlich weiss nicht, ob es irgendeinen Sinn macht diese anzugeben, wenn Sie nur Faxe empfangen möchten. Aber es schadet sicher auch nicht ;-). Wenn das jemand sicher weiss, sage er's mir bitte.

Anmerkung

Wenn Sie eine eigene Nummer nur für Fax-Zwecke nutzen möchten, sollten Sie nicht switch_to_faxG3 benutzen. Benutzen Sie stattdessen connect_faxG3.

9

Nachdem die Verbindung erfolgreich in den Fax-Modus umgeschaltet wurde, können wir schließlich das Fax-Dokument empfangen. Die benutzte Funktion fax_receive bekommt einen neuen Namen, der wieder von cs_helpers.uniqueNameerzeugt wird wie oben.

Glückwunsch. Sie haben mein kleines Tutorial beendet. Jetzt sind Sie dran - Sie können ein bisschen mit dem erstellten Skript herumspielen und versuchen, es noch weiter zu vervollständigen. Es gibt noch viel zu tun - empfangene Anrufe per E-Mail an den Benutzer schicken, Verbindungen protokollieren, ... Wenn Sie dieses Skript vervollständigen wollen, ist der „CapiSuite-Befehlsreferenz“ hilfreich. Sie können auch hier weiter lesen, um einen kurzen Blick auf das Idle-Skript zu werfen, gefolgt von einem schnellen überblick über die Struktur der Standard-Skripte, die mit CapiSuite geliefert werden.