Mathe-Trainingsprogramm in Python mit AGI für Asterisk-Server

4.11.2022

Dieses kleine Mathe-Trainingsprogramm dient zum Üben der Subtraktion von Zahlen. Es wurde auf Python geschrieben und läuft in Verbindung mit AGI auf Asterisk-Telefon-Servern.

Vorgeschichte: Ursprünglich wurde dieses Programm von Ed Guy auf Perl geschrieben und 2004 auf der AstriCon-Konferenz vorgestellt. Ed Guy wollte seiner Tochter ein Programm anbieten, mit dem sie das Subtrahieren von Zahlen üben kann.

Später wurde es auf Python umgeschrieben und auf http://www.asteriskdocs.org/en/2nd_Edition/asterisk-book-html-chunk/asterisk-CHP-9-SECT-4.html vorgestellt. Allerdings ist der Code veraltet und läuft nicht mehr auf Python 3. Zudem fehlen die Sound-Dateien für die Anweisungen, die ich erstellt habe.

Das neue Python-Skript für Python 3: Nachfolgend das angepasste Python-Programm für Python 3 und mit den entsprechenden Verknüpfungen zu den Audio-Dateien. Getestet wurde es auf Asterisk 16.2.1, Python 3.7 und Python 2.7. Beide Python-Versionen sind bereits auf dem Raspbian für den Raspberry vorinstalliert. Das Skript habe ich eine Textdatei kopiert und ihr  den Namen subtraction_game.py vergeben:

#!/usr/bin/python
# adapted for Python 3 from http://www.asteriskdocs.org/en/2nd_Edition/asterisk-book-html-chunk/asterisk-CHP-9-SECT-4.html

import sys
import re
import time
import random

# 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

limit=20
digitcount=2
score=0
count=0
ttanswer=5000

starttime = time.time()
t = time.time() - starttime

sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-welcome")

while ( t < 180 ):

   big = random.randint(0,limit+1)
   big += 10
   subt= random.randint(0,big)
   ans = big - subt
   count += 1

   #give problem:
   sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-next");
   saynumber(big);
   sayit("/usr/share/asterisk/sounds/subtraction-game/minus");
   saynumber(subt);
   res = getnumber("/usr/share/asterisk/sounds/subtraction-game/equals",ttanswer,digitcount);

   if (int(res) == ans) :
      score+=1
      sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-good");
   else :
      sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-wrong");
      saynumber(ans);

   t = time.time() - starttime

pct = float(score)/float(count)*100;
sys.stderr.write("Percentage correct is %d\n" % pct)
sys.stderr.flush()

sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-timesup")
saynumber(score)
sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-right")
saynumber(count)
sayit("/usr/share/asterisk/sounds/subtraction-game/subtraction-game-pct")
saynumber(pct)

Aufrufen aus der extensions.conf: Der Teilnehmer wählt in diesem Beispiel die Nummer 345 und los geht es mit dem Mathe-Training.

exten => 345,1,Answer()   
 same => n,AGI(subtraction_game.py)
 same => n,Hangup()

Bedienung: Das Spiel wird demnach durch die Wahl der Nummer 345 aufgerufen. Es werden der Reihe nach zwei Zahlen genannt, die zu subtrahieren sind. Die richtige Lösung ist mit der Telefon-Tastatur einzutippen. Dafür hat man ein paar Sekunden Zeit und erfährt, ob man falsch oder richtig liegt. Nach ein paar Minuten ist das Spiel vorbei und der Nutzer erfährt seinen Punktestand.

Was ist noch alles zu beachten, damit die AGI-Skripte funktionieren? Was grundsätzlich beim AGI-Skripten zu beachten ist, ist nachfolgend anfängerfreundlich erklärt. Dateien, die auf Windows erzeugt wurden, müssen umgewandelt werden, da sonst die Skripte nicht funktionieren. Dort steht auch in welches Verzeichnis die AGI-Skripte zu kopieren sind:

Rotes schwedisches Ericsson Dialog. Die Ziffern sind versetzt angeordnet. Ein kleines Skript für Asterisk löst das Problem.
Schwedische Wählscheibe: AGI-PHP-Skript für Asterisk wandelt die gewählten Nummern um – 1.11.2022: Schwedische Wählscheiben besitzen gegenüber den meisten Wählscheiben auf der Welt eine andere Bedruckung. Die Ziffern sind um eine Stelle entgegen des Uhrzeigersinns verrutscht dargestellt. Wer sich an den aufgedruckten Ziffern eines schwedischen Wählscheibentelefons orientiert, wählt falsch, wenn das Telefon z.B. an einer üblichen Telefonanlage, an einem Asterisk-Server oder an einer FritzBox angeschlossen ist. – weiter

Die Audio-Dateien: Die notwendigen Sounddateien sind auf Englisch und  nachfolgend in einer ZIP-Datei abgelegt: subtraction-game

Wie sie erzeugt werden können, ist nachfolgend beschrieben:

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

Debugging von Python mit der IDE Thonny: Thonny ist wie Python 2.7 und 3.0 bereits auf dem Raspbian Buster mitgeliefert. Allerdings läuft Thonny erst ab Python 3.0 und die Python-Bibliotheken pyst2 laufen nicht auf Python 3.

Debugging mit der IDE Thonny für Python 3.

Auf jeden Fall ist der Raspberry eine geeignete Plattform um Python zu erlernen. Mit Python und AGI erweitern sich die Möglichkeiten, die Asterisk bieten kann, erheblich.

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

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