Auswahlmenü (IVR-Menü) für Asterisk als AGI-Skript in Python

16. Februar 2023

Ein Auswahlmenü, auch IVR-Menü (Interactive Voice Response) genannt, ist eine automatisierte Telefonanwendung, die es dem Anrufer ermöglicht, durch Drücken von Tasten auf seiner Telefontastatur eine gewünschte Option auszuwählen. Es bietet dem Anrufer eine einfache Möglichkeit, auf eine bestimmte Abteilung oder Dienstleistung zuzugreifen, indem er eine bestimmte Tastenkombination drückt, die mit der entsprechenden Funktion oder Person verbunden ist. Es wird oft in Unternehmen und Organisationen eingesetzt, um den Anrufer direkt an die richtige Stelle zu leiten und Wartezeiten und Übertragungen zu reduzieren.

Auswahlmenüs für Asterisk-Telefonsysteme lassen sich direkt mit den Wahlregeln in der extensions.conf definieren. Ich finde es jedoch eleganter sie mit AGI-Skripten in Python zu programmieren. Wenn man einmal das Prinzip verstanden hat, ist es sogar einfacher. Die extensions.conf gestaltet sich zudem kompakter.

Hier ein Beispiel, das als Vorlage dienen kann. Das Skript ist in der auswahlmenue.py untergebracht. Alles weitere steht im Kommentar:

#!/usr/bin/python

# auswahlmenue.py läuft auf Python 2.7 und sicherlich auf 3.0

######### Nachfolgendes erspart den Einsatz von pyst #######################

import sys
import re
import time

# Read and ignore AGI environment (read until blank line)

env = {}
tests = 0;

while 1:
   line = sys.stdin.readline().strip()

   if line == '':
      break
   key,data = line.split(':')
   if key[:4] != 'agi_':
      #skip input that doesn't begin with agi_
      sys.stderr.write("Did not work!\n");
      sys.stderr.flush()
      continue
   key = key.strip()
   data = data.strip()
   if key != '':
      env[key] = data

sys.stderr.write("AGI Environment Dump:\n");
sys.stderr.flush()
for key in env.keys():
   sys.stderr.write(" -- %s = %s\n" % (key, env[key]))
   sys.stderr.flush()

def checkresult (params):
   params = params.rstrip()
   if re.search('^200',params):
      result = re.search('result=(\d+)',params)
      if (not result):
         sys.stderr.write("FAIL ('%s')\n" % params)
         sys.stderr.flush()
         return -1
      else:
         result = result.group(1)
         #debug("Result:%s Params:%s" % (result, params))
         sys.stderr.write("PASS (%s)\n" % result)
         sys.stderr.flush()
         return result
   else:
      sys.stderr.write("FAIL (unexpected result '%s')\n" % params)
      sys.stderr.flush()
      return -2

def sayit (params):
   sys.stderr.write("STREAM FILE %s \"\"\n" % str(params))
   sys.stderr.flush()
   sys.stdout.write("STREAM FILE %s \"\"\n" % str(params))
   sys.stdout.flush()
   result = sys.stdin.readline().strip()
   checkresult(result)

def saynumber (params):
   sys.stderr.write("SAY NUMBER %s \"\"\n" % params)
   sys.stderr.flush()
   sys.stdout.write("SAY NUMBER %s \"\"\n" % params)
   sys.stdout.flush()
   result = sys.stdin.readline().strip()
   checkresult(result)

def getnumber (prompt, timelimit, digcount):
   sys.stderr.write("GET DATA %s %d %d\n" % (prompt, timelimit, digcount))
   sys.stderr.flush()
   sys.stdout.write("GET DATA %s %d %d\n" % (prompt, timelimit, digcount))
   sys.stdout.flush()
   result = sys.stdin.readline().strip()
   result = checkresult(result)
   sys.stderr.write("digits are %s\n" % result)
   sys.stderr.flush()
   if result:
      return result
   else:
      result = -1

################### Ab hier beginnt das eigentliche Skript #####################

limit=5         # nicht definiert
digitcount=1    # Der Nutzer kann nur eine Ziffer eingeben und danach beginnt die Vermittlung
ttanswer=5000   # Der Nutzer hat 5000 ms ( 5 Sekunden ) die Ziffer oder Ziffern einzugeben


time_duration = 1           # Ein Sekunde Pause eingebaut, damit der Nutzer den Anfang
time.sleep(time_duration)   # der Ansage mitbekommt.

# Nachfolgend erfolgt das Abspielen eines WAV- oder GSM-Soundfiles, dass den Nutzer
# informiert welche Ziffer er zu wählen hat. Der Nutzer kann während des Abspielens
# der Sounddatei die gewünschte Ziffer wählen. Sogleich wird er weiterverbunden.
# Die Sounddateien lassen sich z.B. mit Text to Speech auf https://ttsmp3.com/ als MP3 erstellen
# und anschließend auf https://g711.org/ mit der Standardeinstellung in das g.711-Format
# als Wav-Datei umwandeln, die für Asterisk  geeignet ist.

number = getnumber("/usr/share/asterisk/sounds/eigene/auswahl_menue_ansage",ttanswer,digitcount);

# number ist die Ziffer oder die Zahl, die vom Nutzer in die Tastatur des Telefons eingetippt
# wurde.

# In der if-elif-else Anweisung wird sie in eine Ganzzahl umgewandelt. Diese If-Aweisung
# bestimmt, welche Nummern (Extensions) gewählt werden, wenn man
# eine Ziffer oder Zahl eintippt.

# Tippt man die z.B. die 2 ein, dann wird mit Print ein String ausgedruckt,
# der die Variable EXTNEW auf 675 setzt. Diese wird an Asterisk 
# übergeben, wie nachfolgend noch erklärt wird.

if int(number) == 1:
    print("SET VARIABLE EXTNEW " + "3898")
elif int(number) == 2:
    print("SET VARIABLE EXTNEW " + "675")
elif int(number) == 3:
    print("SET VARIABLE EXTNEW " + "656778")
elif int(number) == 4:
    print("SET VARIABLE EXTNEW " + "878")
elif int(number) == 5:
    print("SET VARIABLE EXTNEW " + "487")
elif int(number) == 6:
    print("SET VARIABLE EXTNEW " + "67856")    
else: 
    sayit("/usr/share/asterisk/sounds/en_US_f_Allison/beep")
    time_duration = 2
    time.sleep(time_duration) 

# Wenn der Nutzer die 5 Sekunden verstreichen lässt oder eine
# nicht definierte Zahl eintippt, springt das Skript zu
# else, es wird eine Sounddatei abgespielt die Piep macht
# und dann geht es wieder von vorne los.

#
# exampel in extensions.conf
#
#exten => 500,1,Answer() # Hier im Demo die 500 wählen, um ins Menue zu kommen
#  same => n,AGI(vermittlung.py) # eventuell absolute Pfadangabe notwendig
#  same => n,NoOP(from script vermittlung.py dialing ${EXTNEW})
#  same => n,Goto(telefone,${EXTNEW},1) # Hier wird die Nummer gewählt
#  same => n,Hangup()

Man muss also im Prinzip nur die If-Schleife und ein Soundfile anpassen. Der Rest kann bleiben.

Selbstverständlich ließen sich zur Schaffung Untermenüs die IF-Anweisungen verschachteln, so dass sich If-Anweisungen innerhalb von If-Anweisungen befinden. Übersichtlicher ist es aber weitere AGI-Skripte zu schreiben, die über Extensions (“interne Astersik-Nummern”) aufgerufen werden können.

Aufruf des AGI-Skripts in der Extensions.conf:

[telefone] ; Im Beispiel sollte die Wahlregel im Context telefone stehen

exten => 500,1,Answer()
#  same => n,AGI(auswahlmenue.py)
#  same => n,NoOP(from script auswahlmenue.py dialing ${EXTNEW})
#  same => n,Goto(telefone,${EXTNEW},1)
#  same => n,Hangup()

Was grundsätzlich bei AGI-Skripten zu beachten ist: Besonders wenn man sie in Windows editiert, ist einiges zu beachten.

Der Programmier-Code ist fehlerfrei. Trotzdem läuft das AGI-Skript nicht. Warum verflixt noch einmal?
AGI-Skripte für Asterisk unter Windows editieren – Was ist zu beachten? – 9. März 2023: Windows ist weit verbreitet. Deshalb schreiben nicht wenige ihre AGI-Skripte auf Windows, um sie dann in ihren Raspberry Pi zu kopieren. Wer einige Dinge nicht beachtet, wird die Skripte nicht zum Laufen bekommen. Hier sind die Fallstricke beschrieben, die mich viel Zeit gekostet haben. Die Beispiele beziehen sich auf Python. Sie haben für andere Programmiersprachen die entsprechende Gültigkeit. – weiter

Sound-Dateien mit Text to Speech erstellen:

Online lassen sich Audio-Dateien in Formate umwandeln, die für Asterisk geeignet sind. Aber es geht noch viel mehr.
Erzeugen von Telefon-Ansagen in verschiedenen Sprachen – 3.11.2022: Für einen Anrufbeantworter oder meinen Asterisk-Server benötige ich Sprach-Ansagen in verschiedenen Sprachen. Die weiblichen oder männlichen Stimmen sollten professionell und deutlich klingen. Wie mache ich das für private Zwecke ohne einen Cent dafür zu zahlen? Und alles soll möglichst online erstellt werden können. – weiter

Auf einem Raspberry Pi lässt sich ein kompletter Telefonserver betreiben, der seinen Teilnehmern über Landesgrenzen hinweg kostenloses Telefonieren von unterwegs oder daheim ermöglicht.
Asterisk-Telefonserver auf einem Raspberry Pi – Installation, Konfiguration, Programmierung, SIP, IAX2, AGI-Skripte, Sicherheit und Tipps zum praktischen Betrieb – 2.11.2022: Diese Seite richtet sich an jene, welche einen Asterisk-Telefon-Server auf einem Raspberry Pi betreiben möchten und später ein kleines Netzwerk aus Asterisk-Servern planen, um ein eigenständiges Telefonnetz aufzubauen. Los geht es mit der Installation von Raspbian und Asterisk auf einem Raspberry Pi und dann nach Lust und Laune immer tiefer in die Programmierung von Asterisk. Die Themen werden laufend erweitert.

Selbstverständlich muss es nicht unbedingt ein Raspberry Pi sein. Andere Linux-Rechner gehen auch. – weiter