You are on page 1of 22

SEMS application plugin

development tutorial
writing SEMS applications
[ Version 0.1 / 12.10.2006 ]

iptego GmbH
Am Borsigturm 40
D-13507 Berlin
Germany
0700 - IPTEGODE
This document is copyright of iptego GmbH. 
Distribution is only allowed with written 
permission of iptego GmbH.
http://www.iptego.de

Filename: semsng­app_module_tutorial.odt
Author: Stefan Sayer
Version: 0.1

 
Page 1/22

Table of Contents
1 Introduction.......................................................................................................................................................3
2 Writing a C++ application plugin........................................................................................................................4
2.1 Overview...................................................................................................................................................4
2.2 MyApp – the empty template.....................................................................................................................4
2.3 MyConfigurableApp – adding configurability..............................................................................................7
2.4 MyAnnounceApp – an announcement.......................................................................................................8
2.5 MyJukebox - reacting to user input .........................................................................................................9
2.6 Calling Card application in B2BUA mode................................................................................................11
3 Writing an IVR application...............................................................................................................................18
3.1 Ivr Overview............................................................................................................................................18
3.2 Simple announcement script...................................................................................................................18
3.3 RFC 4240 announcement service...........................................................................................................19

This document is copyright of iptego GmbH. 
Distribution is only allowed with written 
permission of iptego GmbH.
http://www.iptego.de

Filename: semsng­app_module_tutorial.odt
Author: Stefan Sayer
Version: 0.1

 
Page 2/22

de Filename: semsng­app_module_tutorial. As a next step we react on the caller's keypresses. suggestions. First. this documentation is for SEMS-ng (SEMS v0. .0). Nevertheless. and then.  Distribution is only allowed with written  permission of iptego GmbH.iptel. This document is copyright of iptego GmbH. for simplicity from now on SEMS-ng is referred to in this document as SEMS.org) or directly at mailto:stefan.iptego. SEMS-ng is a high performance.12 and 0.odt Author: Stefan Sayer Version: 0. extensible media server for SIP (RFC3261) based VoIP services. So even if the reader only wants to use the IVR. or signaling logic. carrier grade.. one basically does the same things in both C++ plugins: On session start one sets input and output.0 please refer to the documentation provided with the respective source packages.12. the second part explains how to write an application in the IVR as python script. file formats. The IVR tutorial has a minimal announcement application and announcement service following rfc4240: Basic Media Services with SIP. and corrections.1   Page 3/22 . for example with additional codecs. SEMS v0. Application modules that implement applications logic can use the core SEMS-ng API on various levels of abstraction. modify and adapt them. The reader is encouraged to try the plugins while reading this document.).de.9. SEMS-ng can be extended through loadable modules. and then one reacts to various events (BYE. and subsequently adds functionality. For each step there is a separate archive with all the source available. For further information about SEMS versioning please refer to the SEMS-versions document. This tutorial has two parts: The first one describes how to write application modules in C++ using the SEMS framework API. While a part of this guide also applies to older versions of SEMS (SEMS v0. so called plug-ins. Other modules extend SEMS' functionality. Then a calling card application will describe how to use component plugins and SEMS' B2BUA capabilities. The author welcomes comments. the plugin is made configurable.0).1 Introduction This is the application development tutorial for SEMS-ng. For further information about SEMS version 0. The C++ tutorial starts from an empty application plugin template.sayer@iptego.9.. therefore flexibility in the development of services is provided.10. the plugin will play an announcement. DTMF. the first part gives some probably useful insight. while the code is explained. http://www. While the API and the use of the framework is different or sometimes has differing names.8. and can best be reached via email at the sems or semsdev mailing lists (see http://lists.8.

1 Overview Application plugins written in C++ can use the SEMS API on various levels of abstraction.which in most cases should be sufficient .or take the freedom to control precisely call flow or media processing on a low level. Both on signaling and on media side the application can either use some high level functions . which will build the module for us. further uses of the SEMS API can be found in the application modules that come with SEMS. In this folder there is a directory for each plugin.iptego.odt Author: Stefan Sayer Version: 0.tgz empty template for c++ plugin An application plugin's source resides in the SEMS source tree under the /apps folder./core include $(COREPATH)/plug-in/Makefile. 2. The empty. Now lets move on to the Header file: #include "AmSession. but functional template contains the following files: myapp/MyApp.h Header file myapp/MyApp. while also complex applications can build on the SEMS core. http://www. This tutorial-style guide will present only high level functions..cpp C++ source file myapp/Makefile The Makefile myapp/Readme Documentation Lets start with the Makefile: plug_in_name = myapp Here we define the plugin-name for the build system module_ldflags = module_cflags = we do not need additional compiler flags COREPATH ?=.  Distribution is only allowed with written  permission of iptego GmbH.1   Page 4/22 .app_module This includes the Makefile for application modules from the core path. It should be sufficient though to get the reader started to programming her or his own applications and provide a basis for understanding of the other applications.2 Writing a C++ application plugin 2. This provides the flexibility that both simple application modules can be written with a few lines of code../. It is therefore not possible to define a closed set of SEMS API functions – the SEMS core should rather be seen and used as a framework for developing VoIP applications.de Filename: semsng­app_module_tutorial.h" This document is copyright of iptego GmbH.2 MyApp – the empty template ● sems-tutorial-appmyapp.

This object is the one that will handle the call itself. which we'll look at later.odt Author: Stefan Sayer Version: 0. WARN and ERR macros) #define MOD_NAME "myapp" EXPORT_SESSION_FACTORY(MyAppFactory. There are of course more event handlers.  Distribution is only allowed with written  permission of iptego GmbH. and return an AmSession object.1   Page 5/22 . This is the session factory class for our application. void onBye(const AmSipRequest& req).From AmSession.iptego. http://www. Whenever an INVITE comes for this application. This is a macro so that SEMS finds the exports while loading the module MyAppFactory::MyAppFactory(const string& _app_name) : AmSessionFactory(_app_name) { This document is copyright of iptego GmbH. which will be called when the session starts with the INVITE requests that started the session. AmSession* onInvite(const AmSipRequest& req).h" #include "log. }. which is called if a BYE is received in the session. and onBye. int onLoad(). ~MyAppDialog(). As parameter we get the SIP request. As we see there is two event handlers: OnSessionStart. Now to the implementation. Lets have a look at the class we derive from AmSession that will handle our call: class MyAppDialog : public AmSession { public: MyAppDialog().MOD_NAME).h" we need this to display debug messages on SEMS' log (DEBUG. It will be instantiated on startup. which is almost empty yet: #include "MyApp. so we can act accordingly. it is requested to react on this. }. and at this point onLoad() is called. void onSessionStart(const AmSipRequest& req).de Filename: semsng­app_module_tutorial.h we get most of the Session related stuff class MyAppFactory: public AmSessionFactory { public: MyAppFactory(const string& _app_name).

This will stop the session thread and eventually the Session will be removed from the SessionContainer.tgz) ● rebuild (make all) This document is copyright of iptego GmbH. http://www.  Distribution is only allowed with written  permission of iptego GmbH..odt Author: Stefan Sayer Version: 0. } Here we create a new Session object and return it. How to start that example: ● chdir to sems/ ● unpack myapp.tgz (tar xzvf myapp. MyAppDialog::MyAppDialog() { } MyAppDialog::~MyAppDialog() { } void MyAppDialog::onSessionStart(const AmSipRequest& req) { DBG("MyAppDialog::onSessionStart: Hello World!\n").iptego.only if we receive a BYE we have to stop the Session. void MyAppDialog::onBye(const AmSipRequest& req) { DBG("onBye: stopSession\n"). } AmSession* MyAppFactory::onInvite(const AmSipRequest& req) { return new MyAppDialog().. } Until now we don't want to do anything with the call. setStopped(). } ..de Filename: semsng­app_module_tutorial.} int MyAppFactory::onLoad() { return 0.1   Page 6/22 ..

you will see something like this in the log: (7419) DEBUG: onBye (MyApp.1   Page 7/22 . If you hangup the phone.cfg to sems using t_write_unix("/tmp/your_sems_socket". an then SEMS complained that it does not have something to send or record.so .cpp:484): missing audio input and/or ouput.iptego. We call this application myconfigurableapp. the myapp got the call.odt Author: Stefan Sayer Version: 0.cpp:35): MyAppDialog::onSessionStart: Hello World! (7419) ERROR: onSipRequest (AmSession. In the MyConfigurableAppFactory."myapp") you should see in the logfile something like: (7419) DEBUG: onSessionStart (MyApp.conf) . in this case have a look at your sems. or you did not place the module in the correct path where SEMS searches for plugins. So we have to rename the source files. we add a static member to remember the configuration: static string AnnouncementFile.3 MyConfigurableApp – adding configurability sems-tutorial-appmyconfigurableapp.cpp:327): application 'myapp' loaded. which is perfectly ok because so far the app is empty. and each module has its own configuration file.. which will be a simple announcement application. which will read the config file for us: #include "AmConfigReader. (7419) DEBUG: loadAppPlugIn (AmPlugIn. We use the ConfigReader from AmConfigReader.tgz ● application module with configurability The next step is to add configurability.h.de Filename: semsng­app_module_tutorial. so we can set the file which will be played in the following chapter.conf”. the module name in the Makefile and MOD_NAME in the source. If you then route a call from ser's ser. which usually has the name of the module plus “. we configure it in the onLoad function: This document is copyright of iptego GmbH. http://www. In SEMS has one main configuration file (sems.h" When the module is loaded.conf configuration.cpp:40): onBye: stopSession If you do not get these log entries.cpp:115): loading lib/myapp.● restart sems In the SEMS log you should see a line like: (7419) DEBUG: load (AmPlugIn. 2.  Distribution is only allowed with written  permission of iptego GmbH.. As we can see. there is probably something wrong with your ser configuration.

we open the file and set it as output: void MyAnnounceAppDialog::onSessionStart(const AmSipRequest& req) This document is copyright of iptego GmbH. return -1."/tmp/default."myconfigurableapp") 2. If we return error (-1) here. the second is the default value which will be used if the there is no configuration for announcement_file.de Filename: semsng­app_module_tutorial. When the session starts.cfg to execute the new application: t_write_unix("/tmp/your_sems_socket". We add a member wav_file to the session class as MyAnnounceAppDialog::wav_file. looks for the appropriate file format and codec.1   Page 8/22 . Read the parameter. http://www.4 MyAnnounceApp – an announcement sems-tutorial-appmyannounceapp. if(!file_exists(AnnouncementFile)){ ERROR("announcenment file for configurableApp module does not exist ('%s').iptego. SEMS will not startup on failure. now the time has come to let it do something: it will play the file to the caller indicated in the configuration.\n". } We check whether the file exists with the file_exists function form AmUtils. AnnouncementFile. return 0. } Return OK.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ". which already does all the work for us: it opens the file. we must not forget to change the line in the ser. we use AmAudioFile from AmAudio.getParameter("announcement_file". if(cfg.int MyConfigurableAppFactory::onLoad() { AmConfigReader cfg.odt Author: Stefan Sayer Version: 0.conf"))) return -1. Now that this application is called myconfigurableapp and not myapp any more. AnnouncementFile = cfg.wav").c_str()).tgz ● play an announcement from a (configurable) file While the plugin didn't do much with the caller until now. To read a file.  Distribution is only allowed with written  permission of iptego GmbH.

Additionally.c_str()). http://www.iptego. if(wav_file.5 MyJukebox . If the user presses a key while in the call. each AmSession has an input and an output.1   Page 9/22 .g. the corresponding file is added to the back of the playlist.AmAudioFile::Read)) throw string("MyAnnounceAppDialog::onSessionStart: Cannot open file\n").file is '%s'\n". MyAnnounceAppFactory::AnnouncementFile. Additionally the playlist (AmPlaylist) is introduced.  Distribution is only allowed with written  permission of iptego GmbH.odt Author: Stefan Sayer Version: 0. the audio engine sends an event to the session (an AmAudio event with the type AmAudio::cleared).{ DBG("MyAnnounceAppDialog::onSessionStart . which will send a bye. and received audio is written to the input. for putting filters beore a conference) For a more detailed description and examples have a look at the developersguide section ➢ insert reference 2. AmConferenceChannel Connects to a conference (a mixer of several streams) AmAudioBridge Connects in and out (write and read) AmAudioDelay Connects in and out with a delay AmAudioQueue Holds several items. Audio is read from the output and sent via RTP.reacting to user input sems-tutorial-appmyjukebox.tgz ● react to key input ● use playlist This application shows how to react on user input via DTMF. the Audio is written/read through all of them (e. and the default behaviour in this case is to stop the session. If an error has occured. the return value is != 0 setOutput(&wav_file). In the core there is several useful things already implemented: AmAudioFile Write or read from or to file AmPlaylist Has several items in the playlist that are played sequentially.open(MyAnnounceAppFactory::AnnouncementFile. when all the file is played. The MyJukeboxDialog gets an AmPlaylist as member: This document is copyright of iptego GmbH. } As we maybe remember from the developersguide. It is sufficient for the Session to set the output – the SEMS core does the rest. We can set anything derived from AmAudio as input or output.de Filename: semsng­app_module_tutorial.

. but we'll still set it). &playlist). and enable DTMF detection: void MyJukeboxDialog::onSessionStart(const AmSipRequest& req) { DBG("MyJukeboxDialog::onSessionStart ..wav". delete wav_file. public: . This document is copyright of iptego GmbH.de Filename: semsng­app_module_tutorial. duration).. } By default DTMF detection is disabled as the signal processing involved use quite some processing power.1   Page  10/22 .iptego. AmAudioFile* wav_file = new AmAudioFile(). MyJukeboxDialog::MyJukeboxDialog() : playlist(this) { } When the session starts.class MyJukeboxDialog : public AmSession { AmPlaylist playlist. return. got event %d. When the caller presses a key. the onDtmf event handler is executed. MyJukeboxFactory::JukeboxDir. when the playlist is empty we will get an event from the playlist.AmAudioFile::Read)) { ERROR("MyJukeboxDialog::onSessionStart: Cannot open file\n"). } AmPlaylistItem* item = new AmPlaylistItem(wav_file. if(wav_file->open(MyJukeboxFactory::JukeboxDir + int2str(event) + ". we don't need input actually. setInOut(&playlist. we set input and output to the playlist (well.jukedir is '%s'\n". The playlist is initialized with the dialog object as event receiver. event.c_str()). duration %d.  Distribution is only allowed with written  permission of iptego GmbH. where we just open the file and add it to the playlist: void MyJukeboxDialog::onDtmf(int event. NULL).. int duration) { DBG("MyJukeboxDialog::onDtmf. http://www.\n".odt Author: Stefan Sayer Version: 0. setDtmfDetectionEnabled(true).

AmAudioEvent* audio_ev = dynamic_cast<AmAudioEvent*>(ev).wav. 2.iptego.conf which contains for example the following: #CFGOPTION_SEMS_MYJUKEBOX_JUKEBOXDIR jukebox_dir=/tmp/jukebox/ #ENDCFGOPTION and we create a directory /tmp/jukebox where we put some sound files. http://www. using the following line we can get the numbers as wav files from the CVS of the 0.1   Page  11/22 . mv answer_machine/wav /tmp/jukebox.  Distribution is only allowed with written  permission of iptego GmbH. } AmSession::process(ev). and so on. we create a config file myjukebox. while not forgetting to send the rest of the events to AmAudio::process for correct handling: void MyJukeboxDialog::process(AmEvent* ev) { DBG("MyJukeboxDialog::process\n"). f++)). for example for a This document is copyright of iptego GmbH.de:/cvsroot/sems co answer_machine/wav/$f. } To try this application. named 0. f < 10.playlist.wav. 1.tgz ● create component module ● use DI API sems-tutorial-appmycc. As this is an AmAudio event. return. we have to filter this on the process routine before the AmAudio base class processes the events. } We also get an event when the playlist is empty. do cvs -d :pserver:anonymous@cvs.wav.odt Author: Stefan Sayer Version: 0. done .wav.tar ● B2BUA application As next example we will use SEMS' B2BUA capabilities to create a calling card application.de Filename: semsng­app_module_tutorial.tgz ● use user_timer ccard_wav.9 version of SEMS: for ((f = 0. if(audio_ev && (audio_ev->event_id == AmAudioEvent::noAudio)){ DBG("MyJukeboxDialog::process: Playlist is empty!\n").addToPlaylist(item). rm -rf answer_machine 2.6 Calling Card application in B2BUA mode sems-tutorial-appcc_acc.berlios. Even though this not that much fun like real music files.

On termination. map<string. his PIN number is collected and verified. which has seconds granularity.h" /** * accounting class for calling card. When a user dials in. he is asked for a number to dial. which is used by the other one. This shows how to provide and use a di-interface (di stands for dynamic invocation). /** returns remaining credit */ int subtractCredit(string pin. called cc_acc provides the accounting.h). * this illustrates the DI interface * and component modules */ class CCAcc : public AmDynInvoke { /** returns credit for pin. we simply use a singleton CCAcc. mycc. http://www.de Filename: semsng­app_module_tutorial.odt Author: Stefan Sayer Version: 0. // it must be protected by a mutex AmMutex credits_mut. To test this application there is a set of wave files available with the messages to be played back to the caller. and if his credit is sufficient. ret): #include "AmApi. With SEMS acting as Back-to-Back user agent with this number then a session is established (the callee leg). -1 if pin wrong */ int getCredit(string pin). We want to build the application in a modular manner: On module.iptego. As every class that provides a di-interface. This document is copyright of iptego GmbH. The application also uses the user_timer. args. the class must implement AmDynInvoke (from AmApi. -1 if pin wrong */ int getCredit(string pin). the users' credit is reduced by the amount of time the session was established. unsigned int> credits. which consists of the function invoke(method. which is the implementation of the call logic. The implementation of these functions is straightforward.tar. which should be enough for a calling card application. // as this is used from various sessions. They were created with the flite speech synthesizer and are of course only for testing purposes. in which we have a map to hold the credits.1   Page  12/22 . /** returns remaining credit */ int subtractCredit(string pin. As an example we add a credit for “12345” on startup.  Distribution is only allowed with written  permission of iptego GmbH. int amount). it should provide only two functions: /** returns credit for pin. Lets start with the accounting module cc_acc. and how to create component modules. The session is terminated when the user is out of credit. a timer from the session_timer plugin. int amount). as ccard_wav.PSTN gateway.

The AmArgArray type is a dynamic array that can hold entries of types int. the caller gets the DI Interface and calls invoke with the function name as the first parameter. which is the factory class for instances of the DI-interface. const AmArgArray& args. AmArgArray& ret) { if(method == "getCredit"){ ret. To use the API . double or c string. so that the plugin-loader knows that we export a DI-API from the plugin. we must export a class that implements AmDynInvokeFactory.static CCAcc* _instance. the arguments as the second. In our case we have the implementation of the accounting as singleton as well so we just give out the same CCAcc::instance every time: class CCAccFactory : public AmDynInvokeFactory { public: CCAccFactory(const string& name) : AmDynInvokeFactory(name) {} AmDynInvoke* getInstance(){ return CCAcc::instance().1   Page  13/22 . } else if(method == "subtractCredit"){ ret. } else throw AmDynInvoke::NotImplemented(method).get(0).asInt())).push(subtractCredit(args.de Filename: semsng­app_module_tutorial. public: CCAcc().asCStr(). http://www.odt Author: Stefan Sayer Version: 0.asCStr())). void invoke(const string& method.iptego. This document is copyright of iptego GmbH.push(getCredit(args.  Distribution is only allowed with written  permission of iptego GmbH. and an empty array for return values as the third. ~CCAcc().get(0). args. static CCAcc* instance(). } To export the interface. }. AmArgArray& ret).get(1). In the implementation of the invoke function we check the function name and call the appropriate function: void CCAcc::invoke(const string& method. const AmArgArray& args.

1   Page  14/22 .. if(!user_timer){ ERROR("could not get a cc acc reference\n"). return 0. throw AmSession::Exception(500.iptego. we use EXPORT_PLUGIN_CLASS_FACTORY(CCAccFactory.} int onLoad(){ DBG("CCAcc calling card accounting loaded.. user_timer)."cc_acc").. AmDynInvoke* cc_acc = cc_acc_fact->getInstance().odt Author: Stefan Sayer Version: 0. cc_acc_fact = AmPlugIn::instance()->getFactory4Di("cc_acc"). which we pass to the CCDialog class (the session class): AmSession* MyCCFactory::onInvite(const AmSipRequest& req) { . To export the CCAccFactory for the plugin loader. } }. The second parameter is the interface factory name. We will use the user_timer..  Distribution is only allowed with written  permission of iptego GmbH. if(!cc_acc_fact){ ERROR("could not load cc_acc accounting. it also has a function onLoad(). in int MyCCFactory::onLoad() { .\n").de Filename: semsng­app_module_tutorial. } ."could not get a cc acc reference"). AmDynInvokeFactory* cc_acc_fact. to limit the call time to the maximum This document is copyright of iptego GmbH.cpp and apps/MyCC.h). please provide a module\n"). http://www. Lets now see how we use the DI API.. } We get a user_timer DI interface as well and pass it to the CCDialog class. which is a timer with seconds granularity provided by the session_timer plugin. In the SessionFactory we get a Factory for the Interface. which is used by the user of the DI-API. return -1. is later used to get a DI interface. This Factory. } return new MyCCDialog(cc_acc. in the application mycc (apps/MyCC.. As every plugin factory (AmDynInvokeFactory is a AmPluginFactory).

This document is copyright of iptego GmbH. if we sense this we can start the accounting when the callee is connected: void MyCCDialog::onOtherReply(const AmSipReply& reply) { DBG("OnOtherReply \n"). set the call timer from the session timer. di_args. code %d\n". as here in MyCCDialog::onDtmf: AmArgArray di_args.get(0).push(dlg. To use the DI interface we fill the args with the arguments. we derive the Call control class from AmB2BCallerSession. the MyCCDialog. Now to the call control class.local_tag. NULL).. As we want to get into B2BUA mode later. http://www. di_args.c_str()). which will set sip_relay_only to true.push(credit). } We can get the call back and start again with collecting the number if the callee could not be reached: } else { DBG("Callee final error with code %d\n". A AmB2BCallerSession behaves like a normal AmSession. such that all events (SIP requests and replys) coming from the caller will be relayed to the other leg to the callee leg. call the function with the appropriate name.code < 200) { DBG("Callee is trying. and read the result from the return value array. di_args..push(TIMERID_CREDIT_TIMEOUT). this will post us an event on timeout: // set the call timer AmArgArray di_args.push(pin. cc_acc->invoke("getCredit".available credit.de Filename: semsng­app_module_tutorial. if (state == CC_Dialing) { if (reply. ret). set our input and output to NULL such that we don't send RTP to the caller: setInOut(NULL. start the accounting: startAccounting().ret.odt Author: Stefan Sayer Version: 0.iptego. addToPlaylist(MyCCFactory::DialFailed). A AmB2BCallerSession also has two more callbacks: onOtherReply and onOtherBye.reply. with the function connectCallee. credit = ret.asInt(). the so-called callee leg.ret.1   Page  15/22 .code). ret). We create another leg of the call. onOtherReply is called when there is a reply in the callee leg. // in seconds di_args.code < 300){ if (getCalleeStatus() == Connected) { state = CC_Connected. di_args.  Distribution is only allowed with written  permission of iptego GmbH. user_timer->invoke("setTimer". di_args. } else if(reply.c_str()). if sip_relay_only == false. reply.code).

. Collecting_PIN.number = "". duration). if (state == CC_Connected) { stopAccounting(). void MyCCDialog::onDtmf(int event.c_str()). int duration) { DBG("MyCCDialog::onDtmf. // will stop the session } We also stop the accounting when we receive a BYE from the caller leg: void MyCCDialog::onBye(const AmSipRequest& req) { DBG("onBye: stopSession\n"). got event %d. switch (state) { .odt Author: Stefan Sayer Version: 0. http://www. AmB2BCallerSession::onOtherBye(req). Depending on this state we add the number to the PIN or the Number in onDtmf.1   Page  16/22 .iptego. stopAccounting(). // as it tears down the call if callee could // not be reached } By sensing onOtherBye we can stop the accounting when the other side hangs up: void MyCCDialog::onOtherBye(const AmSipRequest& req) { DBG("onOtherBye\n"). case CC_Collecting_Number: if(event <10) { number +=int2str(event). state = CC_Collecting_Number. event.de Filename: semsng­app_module_tutorial. number. setStopped(). duration %d. } terminateOtherLeg(). Collecting_ Number. } } // we dont call // AmB2BCallerSession::onOtherReply(reply). This document is copyright of iptego GmbH. or we do connectCallee. CC_Dialing and CC_Connected.\n". } Our implementation of the calling card service logic uses a state machine with four states.  Distribution is only allowed with written  permission of iptego GmbH. DBG("number is now '%s'\n"..

. if (timer_id == TIMERID_CREDIT_TIMEOUT) { DBG("timer timeout: no credit. terminateOtherLeg().asInt(). addToPlaylist(MyCCFactory::Dialing). and tear down the call: void MyCCDialog::process(AmEvent* ev) { ..} else { if (getCalleeStatus() == None) { state = CC_Dialing. we get an event in our event queue. and has name==”timer_timeout” . This event is of the type AmPluginEvent. http://www.1   Page  17/22 . terminateLeg().de Filename: semsng­app_module_tutorial. } } break. if(plugin_event && plugin_event->name == "timer_timeout") { int timer_id = plugin_event->data. return.odt Author: Stefan Sayer Version: 0. ev->processed = true. connectCallee(number+MyCCFactory::ConnectSuffix.  Distribution is only allowed with written  permission of iptego GmbH. stopAccounting(). } This document is copyright of iptego GmbH..iptego.get(0). "sip:"+number+MyCCFactory::ConnectSuffix)..\n"). So in MyCCDialog::process we filter out this event. } } AmB2BCallerSession::process(ev). AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(ev). If there is a timeout of the timer we set before using the user_timer API.

and its onSessionStart will be executed. They will log using SEMS' logging facility. The IvrDialogBase also has the user_timer integrated. the function enqueue(AmAudio) can be used to put an entry in the playlist. The IvrDialogBase class has as attribute the SEMS' representation of the SIP dialog class. the call can be ended or a bye sent. the onDtmf event handler is executed. named IvrDialogBase::dialog. and terminateOtherLeg terminates it. flush empties the playlist. which holds the configuration for the application. From the python script. as the processing takes some CPU power. and registers the applications with the core. and the onTimer callback. http://www. and debug. If for example the script “mailbox” is loaded.odt Author: Stefan Sayer Version: 0. clearTimers.  Distribution is only allowed with written  permission of iptego GmbH. for example an IvrAudioFile. and the second one CalleeLeg. IvrDialogBase also has other event handlers: onDtmf. There is a dictionary config. the application will be executed. 3. connectCallee connects another leg. from etc. With the methods stopSession and bye. onEmptyQueue.3 Writing an IVR application 3. If a keypress is received. the ivr registers the application name “mailbox” to this script. applications can be written as a Python script which will be executed in a python interpreter embedded in the ivr module. a configuration parameter of the ivr plugin. it loads all scripts that are in the path script_path.2 Simple announcement script sems-tutorial-appivr_announce. IvrDialogBase already has a playlist integrated. An instance of this class will be created if a call is received for the application. If key presses should be received. disableDTMFDetection should be called if key presses are ignored. Every application script has to define one class derived from IvrDialogBase. In the log library the logging functions known from SEMS are defined: error. setRelayonly controls whether SEMS should only relay SIP events or react to them itself.1 Ivr Overview With the ivr plugin.de Filename: semsng­app_module_tutorial. warn.iptego. In B2BUA mode the first leg of the call is called CallerLeg. which contains to. which is already implemented in the ivr library. The ivr can be used in B2BUA mode. On SEMS startup. enableDTMFDetection should be called. when the ivr is loading. The script itself is executed on startup of the script. while terminateLeg tears down the caller leg itself (the script is running in the “CallerLeg”). onBye. it is used with functions setTimer.tgz ● use playlist for announcement ● configurable announcement file A basic ivr script which plays back a file configured as “announcement” configuration parameter is a twelve liner: from log import * from ivr import * This document is copyright of iptego GmbH. clearTimer.1   Page  18/22 . and if an INVITE is passed to SEMS with “mailbox” as application name. while mute/unmute control whether the session is muted.

announcement. The file to be played back is encoded in the request uri of the INVITE as parameter “play” (e. The script uses urlparse and urllib libraries to parse the parameter and get the file.AUDIO_READ) self. INVITE sip:annc@mediaserver. hdrs): debug("onSessionStart of ivr announcement app") self.bye() self.enqueue(self.class IvrDialog(IvrDialogBase): announcement = None def onSessionStart(self. repeat.net/example.tgz ● process request uri ● react on empty playlist ● use user_timers This script implements announcement service following rfc4240. duration. and then we have to end the call ourselves.announcement = IvrAudioFile() self. 3. None) def onEmptyQueue(self): self. # Simple implementation of (a part) of RFC4240 # announcement service. http://www. # # supported parameters: # play.iptego. which must be >0.odt Author: Stefan Sayer Version: 0. Every timer has an ID.announcement.net:5060. and on timeout the onTimer method is called with the corresponding timer ID. Additional optional parameters are the maximum play time.1   Page  19/22 . It can be a remote (http uri) or a local file.open(config['announcement']. setting the timer or rewinding and enqueuing the file once more.play=http://mediaserver.3 RFC 4240 announcement service sems-tutorial-appannc_service.de Filename: semsng­app_module_tutorial. This timer id is used when setting the timer with the setTimer method. The timers have seconds granularity so the second parameter to the setTimer function is the timeout in seconds. the repeat count and delay between two repeats.  Distribution is only allowed with written  permission of iptego GmbH. by hanging up.wav). ivr.stopSession() onEmptyQueue is called every time that the playlist is empty. The script uses two timers from user_timer (session_timer plugin) to control this. We react to onEmptyQueue (empty playlist) and onTimer events.g. delay from log import * This document is copyright of iptego GmbH.

repeat=param[7:len(param)] elif (param.delay=int(param[6:len(param)]) elif (param.local_uri). urlsplit from urllib import urlretrieve from os import unlink TIMEOUT_TIMER_ID = 1 DELAY_TIMER_ID = 2 class IvrDialog(IvrDialogBase): announcement=None filename = "" repeat="1" delay=0 duration=-1 play="" delete_onbye = False repeat_left = 0 def onSessionStart(self. http://www.startswith("delay=")): self.de Filename: semsng­app_module_tutorial.hdrs): debug("configuration: %s" % repr(config)) debug("local_uri = " + self.duration=int(param[9:len(param)]) This document is copyright of iptego GmbH.startswith("duration=")): self.play=param[5:len(param)] elif (param.local_uri)[2].dialog.startswith("play=")): self.startswith("repeat=")): self. # we use urlsplit as urlparse only returns the # parameters of the last path params = urlsplit(self.") debug("parameters are " + str(params)) for param in params[0:len(params)]: if (param.  Distribution is only allowed with written  permission of iptego GmbH.iptego.1   Page  20/22 .odt Author: Stefan Sayer Version: 0.split(".from ivr import * from urlparse import urlparse.dialog.

play) if (resource[0] == "http"): self.stopSession() self.announcement = IvrAudioFile() self.announcement.repeat_left>0): if (int(self.announcement.repeat+" delay:"+ str(self.enqueue(self.duration) > 0): self.  Distribution is only allowed with written  permission of iptego GmbH.cleanup() This document is copyright of iptego GmbH.repeat!="forever"): self.duration)) self. None) def onBye(self): self.filename = urlretrieve(self.delay) > 0): self.cleanup() def onEmptyQueue(self): if (self.repeat)-1 else: self.setTimer(TIMEOUT_TIMER_ID.repeat_left=500 # maximum if (int(self.delay)/1000) else: self.play+" repeat: "+self.resource = urlparse(self.1   Page  21/22 .announcement.rewind() self.odt Author: Stefan Sayer Version: 0.setTimer(DELAY_TIMER_ID.delay)+" duration: "+str(self.delete_onbye = True self.open(self. http://www.stopSession() self.play)[0] debug("play: "+self. int(self.repeat_left-=1 self. None) else: self. self.de Filename: semsng­app_module_tutorial.AUDIO_READ) if (self.filename.repeat_left=int(self.announcement.bye() self.enqueue(self.iptego.duration/1000) self.

enqueue(self. None) def cleanup(self): if (self.cleanup() elif (timer_id == DELAY_TIMER_ID): self.repeat_left-=1 self.rewind() self.removeTimers() References This document is copyright of iptego GmbH.delete_onbye): unlink(self.de Filename: semsng­app_module_tutorial. timer_id): if (timer_id == TIMEOUT_TIMER_ID): self.filename + " deleted.iptego.filename) debug("cleanup.odt Author: Stefan Sayer Version: 0.bye() self.duration): pass def onTimer(self.1   Page  22/22 .announcement.stopSession() self.key.. http://www.") self.announcement..  Distribution is only allowed with written  permission of iptego GmbH.def onDtmf(self." + self.