Caiviar Manual

line
Summary:  Caiviar is a library for simplifying the creation of "Interactive Voice" (IVR) Applications, like Voiceboxes, Unified Messaging Services, Authentication services etc. This document is a description of the steps necessary to install Caiviar, and a tutorial on it's use. Chapter 1 will give you a description of the installation process, while Chapter 2 will let you build up a simple IVR System step by step.
line

1.Installing Caiviar

The source code of Caiviar is distributed as .tar.gz file. You have to extract it, using e.g. WinZip or the gzip and tar utils that come with Linux. E.g.

tar -xzvf caiviar-0.3.3.tar.gz
will extract Version 0.3.3 of Caiviar into a subdirectory named caiviar-0.3.3.

1.1  Compiling Caiviar 

The following assumes you already have a working Capi 2.0 library. It's installed by default under Windows along with the ISDN drivers, under Linux it's part of the isdn4kutils package available from http://www.isdn4linux.de, and, depending on the card, may require a new kernel or even some proprietary drivers.

The compiling of Caiviar depends on the Operating system and the compiler to be used.

1.1.1  Windows + Cygwin gcc compiler 

Under Windows, this is the preferred solution. Cygwin gcc is available from http://sources.redhat.com/cygwin. Running

./configure && make
in the main directory of caiviar performs the compilation. (If you want to build the Java class files, be sure that a javac is in the PATH before calling configure) You can specifiy several options to the ./configure script related to TTS support. Look into the TTS-Caiviar-HOWTO for more information.

1.1.2  Windows + Visual C compiler 

Rename config.h.win32 into config.h, and copy src/Makefile.vc to src/Makefile, server/Makefile.vc to server/Makefile. Then, run nmake in src/ and server/. If you don't have the MS Speech SDK, undefine HAVE_MSSAPI in config.h, and remove all occurences of "TTS/sapi.lib" in server/Makefile. If you want to use Realspeak instead of MS Speech SDK, follow the instructions in the "Realspeak" section of the TTS-Caiviar-HOWTO.

1.1.3  Linux 

Like with Cygwin, doing

./configure && make
compiles the package. You can specifiy several options to the ./configure script related to TTS support. Look into the TTS-Caiviar-HOWTO for more information.

1.1.4  Cross compilation Linux -> Windows using MinGW  

This has yet only been tested using the Debian MinGW packages. (mingw32, mingw32-runtime, mingw32-binutils). Also, some parts of the configure script assume that your mingw environment is in /usr/i586-mingw32msvc/. (Default with Debian)

Doing

CXX=i586-mingw32msvc-g++ CC=i586-mingw32msvc-gcc ./configure --host=i586-mingw32msvc && make
compiles a Win32 executable.

Installation procedure is the same as for VC compiled executables.

1.2  Installing Caiviar  

1.2.1  Windows  

The .exe generated by the compilation process is named capiserver.exe and is located in the server/ subdirectory. Copying this to the Windows Autostart group will start the Server at each boot time. You also have to copy the ivr.properties file to the same directory. You should now edit this file, especially change the line
local.msisdn=33
to your own msisdn, and
local.controller=1-2
to the number of controllers on your ISDN card. (E.g. an AVM C4 card has 4 controllers) Other options are described in Appendix A. If the Cygwin DLL is not in your normal Windows PATH, also copy the /bin/cygwin1.dll (aka C:\cygwin\bin\cygwin1.dll) into the directory where you install the capiserver.exe.

1.2.2  Linux  

make install
installs the libraries, the capiserver binary and the header files. You should now edit the ivr.properties file in /etc. (See above)

2.Using Caiviar

All of the following assumes you are using the Java interface. There are some comments at the end of tutorial describing the differences between the Java and the C++ Interface.

2.1.1  A Simple Caiviar Application: Making a call  

Code listing 2.1

class CallDemo1
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi capi = new SimpleCapi("127.0.0.1", 7347);
	capi.call("1234", 60);
    }
}

The class Demo1, when started, will call the number "089123456789". The "60" means it will wait 60 seconds for the called person to answer the call, then give up. Notice we didn't check the return code of the call() method- which would have been 0 if we made a connection, and an error code otherwise. (E.g. SimpleCapi.CAPI_REASON_BUSY - the line was busy)

By the way, the two parameters in the constructor mean "connect to the capiserver on this host on port 7347". If you are running the application on a different host than the one this server resides on, you have to change the "127.0.0.1" to the server's IP-Address. The "7347" comes from the ivr.properties file, and may be set to any port number.

2.1.2  Making a call and listening  

In case of the other end actually picking up the line, the previous example returned from the main() method just as the call was ready (resulting in the fact that the SimpleCapi destructor is called, which then terminates the connection again). As a next example let's try to actually do something with the open connection.

Code listing 2.2

class CallDemo2
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi capi = new SimpleCapi("127.0.0.1", 7347);
	if(capi.call("1234", 60) == 0) {
	    while(true) {
		ISDNEvent event = capi.listen(3);
		if(event.type == SimpleCapi.TIMEOUT)
		    continue;
		
		System.out.println(capi.eventToString(event));

		if(event.type == SimpleCapi.CAPI_EVENT_HANGUP)
		    break;
	    }
	} else {
	    System.out.println("Couldn't connect!\n");
	}
    }
}

Now we at least keep the connection open until the other end hangs up.

Let's go though this example line by line. The first difference to example 2 is that we now do check the return code of the call() function- only taking action if it's zero, i.e. successful.

What we do then is "listen on the phone line". We wait for events to come up. One such event is TIMEOUT, when we waited a three full seconds without anything happening. (The 3 is the parameter given to the listen() function- listen(-1) means listen forever (not recommended), otherwise listen(n) returns a TIMEOUT event after n seconds.) In this example, we only take action in the case of the HANGUP event (CAPI_EVENT_HANGUP) (which means the other end hung up the receiver) and let our application return Another interesting event is the DIALTONE event (CAPI_EVENT_DIALTONE), which means the user pressed a specific button.

An example output of the above application would be


    DIALTONE tone=3 msisdn="1234"
    DIALTONE tone=0 msisdn="1234"
    DIALTONE tone=3 msisdn="1234"
    DIALTONE tone=# msisdn="1234"
    HANGUP reason=0 msisdn="1234"

meaning the called person typed "303#" on his keypad.

2.1.3  Playing sound files  

The following example assumes you have a sound file, test.wav, in your local directory. It shouldn't be too short, 4 or more seconds should be fine.

You can now play this soundfile to someone over phone. First, we have to call him, as described above, and then we play the soundfile over the phone with:

capi.play("test.wav");

Now, the soundfile is playing. We now have regularily call the listen() function to check when the soundfile ends, which returns a DONE event (CAPI_EVENT_DONE). Here's the complete program:

Code listing 2.3

class CallDemo3
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi capi = new SimpleCapi("127.0.0.1", 7347);
	if(capi.call("1234", 60) == 0) {
	    capi.play("test.wav");
	    while(true) {
		ISDNEvent event = capi.listen(3);
		if(event.type == SimpleCapi.TIMEOUT)
		    continue;
		
		System.out.println(capi.eventToString(event));

		if(event.type == SimpleCapi.CAPI_EVENT_HANGUP)
		    break;
		if(event.type == SimpleCapi.CAPI_EVENT_DONE) { // soundfile done playing - hang up
		    capi.hangup();
		    break;
		}
	    }
	} else {
	    System.out.println("Couldn't connect!\n");
	}
    }
}

Notice: An already playing soundfile can be stopped by calling stop() with the number that play() returned. This is useful for "barge in", where the text message stops as soon as the user starts to press some buttons.

2.1.4  Receiving calls  

Using the last event remaining, INCOMING (CAPI_EVENT_INCOMING), is useful for an application which waits for calls, instead of calling out. Look at the following example:

Code listing 2.4

class AnswerDemo1
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi capi = new SimpleCapi("127.0.0.1", 7347);
	capi.acceptCalls();
	    
	ISDNEvent event;
	while(true) {
	    event = capi.listen(3);
	    if(event.type == SimpleCapi.TIMEOUT)
		continue;
	    
	    System.out.println(capi.eventToString(event));

	    if(event.type == SimpleCapi.CAPI_EVENT_INCOMING) {
		System.out.println("incoming call from " + event.msisdn);
		break;
	    }
	}
	if(capi.answer(event)) {
	    System.out.println("Connected!");
	    capi.hangup();
	}
    }
}

This example first calls the function acceptCalls, which means it wants to be notified of incoming calls. It then waits for one and tries to answer it. If it has been successful (i.e. answer() returns "true"), it hangs up again. You may wonder why answer() can return false. This simply happens if we don't make it in time, e.g. if the caller has hung up again before we were able to call answer(), or some other device or application has taken the call before us.

Exercise: Extend the above example to create "Callback" application which returns all calls it receives!

Notice: The telephone number on which the device listens for calls (remember that there isn't a one-to-one dependency between B-Channels and phonenumbers!) is usually set in ivr.properties, but can be changed runtime by calling capi.setMSISDN(number). (E.g. if you want different threads to listen on different numbers)

2.1.5  Using more than one phone line  

The typical ISDN card has support for 2 BChannels. You can use both of them to simultaneously wait for connections. There's only one thing to be careful about: Each SimpleCapi object resembles exactly one channel, or none at all- this means every channel needs to have it's own SimpleCapi object.

A typical 2-threaded 2-channel application might look as follows:

Code listing 2.5

class TwoLineDemo extends Thread
{
    public SimpleCapi capi;
    String number;

    public void run()
    {
	try {
	    if(capi.call(number, 60) == 0)
		System.out.println(number + "answered.");
	    capi.beep("3231211");
	    capi.listen(60);
	    capi.hangup();
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }

    public static void main(String args[]) throws Exception
    {
	TwoLineDemo thread1 = new TwoLineDemo();
	thread1.capi = new SimpleCapi("127.0.0.1", 7347);
	thread1.number = "1234";
	TwoLineDemo thread2 = new TwoLineDemo();
	thread2.capi = new SimpleCapi("127.0.0.1", 7347);
	thread2.number = "5678";

	thread1.start();
	thread2.start();
    }
}

This application simultaneously calls the numbers "1234" and "5678". You may extend this application to more than just 2 numbers, if you have the hardware available (you now need some more sophisticated ISDN-Cards, or simply more of them).

The run() method just plays some sounds to the other end (using the beep() function which emulates touchpad tones), and then stays connected up to 60 seconds. Notice that we don't do anything with the ISDNEvent that is returned by capi.listen(). (Which is either TIMEOUT or CAPI_EVENT_DONE)

2.1.6  Using FAX  

Caiviar allows you to send Fax Pages. This is only avaiable when running the Server on Linux. (The application itself can run on any OS) The following example does fax a .html to the number "1357":

Code listing 2.6

class FaxDemo
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi capi = new SimpleCapi("127.0.0.1", 7347);
	capi.put("/tmp/test.html", "test.html");
	capi.fax("1357", "test.html");
	capi.del("test.html");
    }
}

capi.put() transfers the file from your local computer to the Capiserver. (Which is especially useful if those two are different. If they are not, it at least stores the file into the right directory) The first parameter is the name of the local file (A preceding path is allowed), the second parameter is the name of the remote file. (A preceding path is not allowed, for security reasons, among others (*)) capi.fax() sends the file to the number.

The call to capi.del() simply removes the remote file again, to avoid clogging the server.

(*) Notice that whoever has access to the capiserver could also call 0190 numbers on your behalf, so the server should not be accessible to everybody anyway.

2.1.7  Using connect()  

Starting with version 0.3.0, Caiviar has the ability to connect the audio channels of two different connections, so that two callees on two different lines can talk directly to each other. (This is similar to three party conference, except that you don't have to pay a 3pty charge to your telephone provider for initiating it) The following demo program calls two numbers, 123456 and 789012, and connects both once both have answered the phone:

Code listing 2.7

class ConnectDemo extends Thread
{
    public static void main(String args[]) throws Exception
    {
	SimpleCapi[] capi = new SimpleCapi[2];
	capi[0] = new SimpleCapi("127.0.0.1", 7347);
	capi[1] = new SimpleCapi("127.0.0.1", 7347);

	capi[0].call_nonblock("123456");
	capi[1].call_nonblock("789012");

	boolean[] done = new boolean[2];
	done[0] = done[1] = false;
	int nr=0;

	while(!done[0] || !done[1])
	{
	    if(!done[nr]) {
		ISDNEvent event;
		event = capi[nr].listen(1);
		if(event.type == SimpleCapi.CAPI_EVENT_CONNECTED)
		    break;
		if(event.type == SimpleCapi.CAPI_EVENT_HANGUP) {
		    /* rejected, busy, ... */
		    capi[0].close();
		    capi[1].close();
		    return;
		}
	    }
	    nr^=1;
	}

	capi[0].connect(capi[0],capi[1]);

	while(true) {
	    ISDNEvent event0 = capi[0].listen(1);
	    if (event0.type == SimpleCapi.CAPI_EVENT_HANGUP)
		break;
	    ISDNEvent event1 = capi[1].listen(1);
	    if (event0.type == SimpleCapi.CAPI_EVENT_HANGUP)
		break;
	}

	capi[0].close();
	capi[1].close();
    }
}

Notice that we didn't use call() here, but call_nonblock(). Remember that call() was "blocking" until either a specified time was elapsed (we used 60 seconds in the demos above), or the caller answered or turned out to be busy etc. call_nonblock(), on the contrary, returns immediately, and we get the result of the operation later, in form of an event. This enables us to wait simultaneously for two lines (non-threaded), as in this example. Another possiblity would have been to make the calls in seperate Threads (as in TwoLineDemo above), and then let one of the threads do the connect().

The previous sections discussed the Java interface. The following chapters will now take a short look at interfaces in other languages.

2.2  The Perl Interface  

Since caiviar 0.3.0, there's also a Perl Interface. It's very similar to the Java Interface, so it should be easy to transform the examples given in the previous section to Perl. For the sake of completeness, here is a short example, which makes a call and plays a .wav-file to the callee:

Code listing 2.8

require SimpleCapi;

$capi = SimpleCapi::new("127.0.0.1",7347);
$capi->call("1234567890", 60);
$playjob = $capi->play("welcome.wav");
while(1)
{
    $event = $capi->listen(1);
    next if($event->{type} == $capi->{CAPI_EVENT_TIMEOUT});
    last if($event->{type} == $capi->{CAPI_EVENT_DONE});
    last if($event->{type} == $capi->{CAPI_EVENT_HANGUP});
}
$capi->close();

2.3  The C++ Interface  

The C++ Interface is located in the src/ directory of the caiviar package and consists of the two files SimpleCapi.a and SimpleCapi.h. Under Linux make install also installs a shared library and the corresponding headers.

The one major difference between the Java and the C++ interface is that while in Java, you need one SimpleCapi object for each line, in C++ you need just one for all lines, but have to pass the line used to all functions in SimpleCapi. (You have to open() a line first, which returns a line number- It's also necessary to close() them again)

In the caiviar package, there's a subdirectory demo/ which contains several small C++ demo applications.

Notice: The C++ links directly to the Caiviar libraries, instead of talking to the "capiserver" executable like the Java and Perl interfaces. Therefore, you should only use the C++ Interface if you need "low level" access to the SimpleCapi library, and don't require capiserver specific enhancements, like TTS. In the future, there will probably be a second C++ Interface, which behaves like the Perl and Java ones and therefore supports also TTS.

One thing which didn't fit elsewhere: Some people with special performance requirements also use the C++ Interface, as you don't need the socket communication with the server here. You can get additional performance when precomputing the wav->alaw conversion:


sox file.wav -r 8000 -c 1 file.la

Now file.la hasn't to be converted and thus plays with less delay.

3. APPENDIX - Options in ivr.properties

3.1  Logging Properties  

Property Name Default Description
file.name "ivr.log" The name of the logfile to be used (If not present, no logfile will be created)
file.loglevel 2 Allows to select the severity of messages to store in the logfile. (The different Logevels are explained below)
server.name "" For remote logging: The name of the server to copy logging datas to
server.port 0 For remote logging: The port on the logging server
server.loglevel -1 Allows to select the severity of messages to store in the logfile. (The different Logevels are explained below)
screen.loglevel 1 Allows to select messages of which kind are displayed on the screen.

3.2  Loglevels  

server.loglevel, screen.loglevel and file.loglevel are given a number as parameter, selecting the kind of messages to be stored/shown. The numbers have the following meaning:

Number Meaning
-1 no output
0 Only fatal errors
1 Only fatal errors and errors
2 Only warnings and errors
3 Also normal messages (Notice)
4 Also less interesting messages (Verbose)
5 Debugmessages and capi packets (Debug)
6 Disassembly of capi packets (Trace)

3.3  Server Properties  

Property Name Default Description
local.msisdn (none) The number the server should listen for calls on. Notice: The server will ignore all incoming calls which don't go to this number. Leave this entry blank (or set local.allcalls to 1) to not ignore any calls. This parameter also affects the number that is tranmitted to the called person. (I.e. the number to be shown on the displays of the telephone)
local.allcalls 0 [=off] If this is set to 1, the server will not block any calls if they don't go to the right number. (which is set by local.msisdn)
ivr.port 7347 The port on which the IVRServer listens for connections.

3.4  Text-To-Speech Properties  

Property Name Default Description
realspeak.dictionary (none) Realspeak allows settings of User Dictionaries to allow special pronounciations to be passed to the TTS Engine. This parameter tells the IVRServer to pass the dictionary name to RealSpeak.