The AGI script we’ll be writing in Python, called “The Subtraction Game,” was inspired by a Perl program written by Ed Guy and discussed by him at the 2004 AstriCon conference. Ed described his enthusiasm for the power and simplicity of Asterisk when he found he could write a quick Perl script to help his young daughter improve her math skills.
Since we’ve already written a Perl program using AGI, and Ed has already written the math program in Perl, we figured we’d take a stab at it in Python!
Let’s go through our Python script:
#!/usr/bin/python
This line tells the system to run this script in the Python
interpreter. For small scripts, you may consider adding the -u
option to this line, which will run Python in unbuffered mode. This
is not recommended, however, for larger or frequently used AGI scripts, as
it can affect system performance.
import sys import re import time import random
Here, we import several libraries that we’ll be using in our AGI script.
# 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()
This section of code reads in the variables that are passed to our
script from Asterisk, and saves them into a dictionary named env
. These values are then written to STDERR
for debugging purposes.
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
The checkresult
function is
almost identical in purpose to the checkresult
subroutine in the sample Perl AGI
script we covered earlier in the chapter. It reads in the result of an
Asterisk command, parses the answer, and reports whether or not the
command was successful.
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)
The sayit
function is a simple
wrapper around the STREAM FILE
command.
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)
The saynumber
function is a
simple wrapper around the SAY NUMBER
command.
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
The getnumber
function calls the
GET DATA
command to get DTMF input from the caller. It is used in our
program to get the caller’s answers to the subtraction problems.
limit=20 digitcount=2 score=0 count=0 ttanswer=5000
Here, we initialize a few variables that we’ll be using in our program.
starttime = time.time() t = time.time() - starttime
In these lines we set the starttime
variable to the current time and
initialize t
to 0. We’ll use the
t
variable to keep track of the number
of seconds that have elapsed since the AGI script was started.
sayit("subtraction-game-welcome")
Next, we welcome the caller to the subtraction game.
while ( t < 180 ): big = random.randint(0,limit+1) big += 10 subt= random.randint(0,big) ans = big - subt count += 1 #give problem: sayit("subtraction-game-next"); saynumber(big); sayit("minus"); saynumber(subt); res = getnumber("equals",ttanswer,digitcount); if (int(res) == ans) : score+=1 sayit("subtraction-game-good"); else : sayit("subtraction-game-wrong"); saynumber(ans); t = time.time() - starttime
This is the heart of the AGI script. We loop through this section of code and give subtraction problems to the caller until 180 seconds have elapsed. Near the top of the loop, we calculate two random numbers and their difference. We then present the problem to the caller, and read in the caller’s response. If the caller answers incorrectly, we give the correct answer.
pct = float(score)/float(count)*100; sys.stderr.write("Percentage correct is %d\n" % pct) sys.stderr.flush() sayit("subtraction-game-timesup") saynumber(score) sayit("subtraction-game-right") saynumber(count) sayit("subtraction-game-pct") saynumber(pct)
After the user is done answering the subtraction problems, she is given her score.
As you have seen, the basics you should remember when writing AGI scripts in Python are:
Flush the output buffer after every write. This will ensure that your AGI program won’t hang while Asterisk is waiting for the buffer to fill and Python is waiting for the response from Asterisk.
Read data from Asterisk with the sys.stdin.readline
command.
Write commands to Asterisk with the sys.stdout.write
command. Don’t forget to
call sys.stdout.flush
after
writing.
If you are planning on writing lot of Python AGI code, you may want to check out Karl Putland’s Python module, Pyst. You can find it at http://www.sourceforge.net/projects/pyst/.