Tutorial: writing an incoming script

In this section, I'll show you how to code your own incoming script step by step. We begin with simply accepting every incoming call, playing a beep. The last example is a very simple but useful answering machine with fax recognition and receiving.

Basics and a really dumb answering machine.

Let's start with a very simple case: accept all incoming calls, beep and record something so we have an audio file to play with later. First of all, create a new directory somewhere which must be writable to root. We also need some test audio file for sending it. Let's take the beep which is distributed with CapiSuite.

mkdir capisuite-examples
chmod 777 capisuite-examples # make it world-writeable
cd capisuite-examples
cp /usr/local/share/capisuite/beep.la .

Perhaps you must change the path in the last line to reflect your installation.

Now copy and paste the example shown here to a file called example.py in this directory. Don't forget to change the my_path-setting.

Example 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

Let's walk through the script line by line:

1

Import the capisuite module which holds all CapiSuite specific functions. All CapiSuite objects (functions, constants) in this module can be referenced by capisuite.objectname now. You could also do a "from capisuite import *", which will insert all objects in the current namespace - but this isn't recommended as they may collide with other global objects.

Note

The imported module capisuite isn't available as extra module, so you can't do this in an interactive Python session. It's included in the CapiSuite binary and only available in scripts interpreted by CapiSuite.

2

Please change this to the real path you use for running these examples.

3

Define the necessary function as explained in the section called “The incoming script”

4

That's the first CapiSuite function we use: it accepts the pending call. The first parameter tells CapiSuite which call you mean. This parameter is necessary for nearly all CapiSuite functions. Ok, we only have one call now - but please think about an incoming script which also wants to place an outgoing call at the same time (for example to transfer a call). In this case CapiSuite wouldn't know which call you mean - so you must pass the reference you got to all connection related functions.

You can also tell CapiSuite to wait for an arbitrary time before accepting a call - that's what the second parameter is used for. So this script will wait 10 seconds before connecting with the caller. Don't think this parameter is useless and you could call a Python function (like time.sleep()) to wait instead. This won't work for any delay longer than 4 (or 8, depending on your ISDN setup) seconds as the call will timeout if an ISDN device doesn't "pre-accept" it by telling your network provider that it's ringing. CapiSuite will do so if necessary - so please just use this parameter.

5

This call should be fairly self-explainig. Send the audio file stored in beep.la.

6

Record an audio file for maximal 20 seconds - stopping earlier if more than 3 seconds of silence are recognized.

7

Last, but not least - disconnect. Hang up. Finish. It's over.

CapiSuite configuration must be changed to use the just created script. Do this by editing your capisuite.conf and replacing the incoming_script value by the path to the file you just created (see the section called “Configuration of CapiSuite”) and restart CapiSuite.

Now test it: call any number which ends up at your ISDN card - if you have connected it to your ISDN interface, than any number (MSN) will do - if it's connected to a PBX, then you must call a number which was configured for the card in your PBX.

You should hear a beep and then you can speak something into this primitive answering machine. Please don't hangup before the script does as this case isn't handled yet. Just wait 3 seconds after saying something - it should disconnect after this period of silence.

If it doesn't work, you perhaps made an error when copying the script. In this case, please have a look at the CapiSuite log and error log, which you'll find in /var/log/capisuite or /usr/local/var/log/capisuite if you haven't changed the path.

A good trick to check for syntax errors is also to run your script through the normal Python interpreter. Do this by calling python /path/to/your/example.py. Naturally, it will complain about the import capisuite as this is no standard Python module. But before it does this, it will check the syntax of your script - so if you get any other error, please fix it and try again. If you only get

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

then your script has a correct syntax.

I hope you got your script working by now - if not, don't hesitate to ask on the CapiSuite mailing lists if you have read a Python tutorial before.

In the next section we want to use an announcement, so please record some words with this simple script and move the created file recorded.la to announce.la.

Improving it to a useful (?) state

Well, it's really not nice that the caller mustn't hangup - and it's even worse that we do accept all incoming calls - perhaps by taking away your mothers important calls?

Let's quickly improve this.

Example 2.2. example.py, improved

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 will tell the script that the other party has disconnected by raising an exception named CallGoneError. So you should always put your code in a try statement and catch the raised exception at the end of your script (or perhaps earlier if needed). This exception can be raised by call to a CapiSuite command.

2

Have a look at the called number (please replace 123 with the number CapiSuite should accept)...

3

Play the announcement we recorded in the last section. If you don't like it, simply record a new one and move the recorded.la again to announce.la.

4

Ignore the call. The second parameter tells the exact reason for the reject - you can ignore a call (any other ISDN device or phone will still be ringing for that number) by using 1, actively disconnect by using 2 or any error condition which is available in the ISDN specification (see the section called “ISDN error codes” for available codes).

5

You always have to call disconnect at the end of your script, as this will wait for the end of the call, while reject only initiates the call reject. Otherwise you'll get a warning in the error log.

6

This is the exception handler for CallGoneError - the exception CapiSuite raises when the call is disconnected by the other party. You should also call disconnect here to wait until the call is completely disconnected.

Save this to example.py again and test it. It's not necessary to restart CapiSuite as all scripts will be read at each time they're executed. Now you're allowed to hang up, too ;-).

Using sensible file names

We always used the same name to save the recorded message to which clearly isn't reasonable. We should really choose a new name for every new call. This isn't as simple as it may sound - you must assure that the used algorithm will also work for multiple calls arriving at the same time. Fortunately, the helpful programmer of CapiSuite had the same problem and so we can use the code he (hmmm... I?) has written.

The Python module cs_helpers.py contains some useful functions which are needed by the default scripts provided with CapiSuite but may be also helpful for the use in your own scripts. It contains the function uniqueName which does exactly what we need here. The syntax is:

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

The function will find a new unique filename in the given directory. The created filename will be "prefix-XXX.suffix" where XXX is the next free number started at 0. The next free number is remembered in a file prefix-nextnr and the created name is returned.

We can simply add this call to our script:

Example 2.3. using unique filenames

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)

If you're interested in other functions which cs_helpers.py defines, just have a look at the reference at the section called “cs_helpers.py”.

Automatic fax recognition and receiving

As last step, I want to show you how fax recognition and receiving works and how to switch from voice to fax mode.

Here's the last and most complicated example of this section. It'll introduce four new CapiSuite functions and shows how to split up the functionality in another function which is used by callIncoming. There are much changes which are described below - but most of them should be nearly self-explanatory. So I don't think this last step is too big. And you don't want to read 10 more steps here, do you? ;-)

Example 2.4. Adding fax functions

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 this example, we need a normal Python module for the first time. The os module holds functions for all kinds of operation system services and is needed for deleting a file here.

2

DTMF is the abbreviation for Dual Tone Multi Frequency. These are the tones which are generated when you press the digits on your phone and are usually used for dialling. They're also sent by modern fax machines before the transmission starts. Therefore, the same functions can be used for recognizing pressed digits and fax machines.

Before any DTMF is recognized by CapiSuite, the according function must be enabled by enable_DTMF.

3

All audio send and receive functions support abortion when a DTMF tone is recognized. This is enabled by passing "1" as last parameter. It will also prevent the function from starting if a DTMF char was recognized before but not yet read by the script.

4

CapiSuite stores all received DTMF signals in a buffer from where they can be read at any time. Reading is done by read_DTMF which also clears the buffer. It will return all received characters in a string, so if the caller presses "3","5","*", you'll get "35*".

The 0 tells CapiSuite not to wait for DTMF signals - if none are available, it will simply return an empty string. It's also possible to specify that it should wait for a certain amount of time or until a certain number of signals have been received.

Note

Please note that it's not necessary to check for received DTMF after each audio send or receive function. Simply enable the DTMF abortion in all commands in a block and check for received tones after the whole block.

5

Fax machines send a special tone which is represented as "X" by the CAPI. So if you receive the string "X", a fax machine is calling and we should start our fax handling routines.

6

Possibly, the announcement was so short that the recording has started already before the fax is recognized. We won't save a file containing only the fax beep and so we test if it was created (os.access checks for the existence of a file) and delete it if needed by calling os.unlink.

7

Fax handling was realized in a separate function which is called here.

8

So far, this connection is in voice mode (which was set by using connect_voice). If we want to receive a fax now, the mode must be changed to fax. This is done by switch_to_faxG3. As the fax protocol needs some additional parameters, they must be given here. The first string is the so called fax station ID which is sent to the calling fax and shown in it's protocol, while the second one is a fax headline. This headline is mainly used for sending faxes. To be honest, I personally don't know if it has any sense to specify this if you only want to receive a fax. But it surely won't harm ;-). If someone knows this for sure, please tell me.

Note

If you want to use an own number solely for fax purposes, you should not use switch_to_faxG3. Use connect_faxG3 instead.

9

After the connection has been set to fax mode succesfully, we can receive the fax document finally. The used function fax_receive gets a new name which is again created by calling cs_helpers.uniqueName as above.

Congrats. You've finished my small tutorial. Now it's up to you - you can play with the created script and try to make it more complete. There's still much to do - sending received calls to a user via e-mail, log connections, ... If you want to complete this script, the section called “CapiSuite command reference” will be helpful. You can also read on here to have a short look on the idle scripts, followed by a quick overview of the structure of the default scripts shipped with CapiSuite.