semsng-app module tutorial

Upload: adnan-mahmud

Post on 01-Jun-2018

303 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/9/2019 Semsng-App Module Tutorial

    1/22

    SEMS application plugindevelopment tutorial

    writing SEMS applications[ Version 0.1 / 12.10.2006 ]

    iptego GmbHAm Borsigturm 40D-13507 BerlinGermany0700 - IPTEGODE

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 1/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    2/22

    Table of Contents

    1 Introduction.......................................................................................................................................................32 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:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 2/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    3/22

    1 Introduction

    This is the application development tutorial for SEMS-ng. SEMS-ng is a high performance, carrier grade,extensible media server for SIP (RFC3261) based VoIP services.

    SEMS-ng can be extended through loadable modules, so called plug-ins. Application modules that implementapplications logic can use the core SEMS-ng API on various levels of abstraction, therefore flexibility in thedevelopment of services is provided. Other modules extend SEMS' functionality, for example with additionalcodecs, file formats, or signaling logic.

    While a part of this guide also applies to older versions of SEMS (SEMS v0.8.12, SEMS v0.9.0), thisdocumentation is for SEMS-ng (SEMS v0.10.0). For further information about SEMS version 0.8.12 and 0.9.0please refer to the documentation provided with the respective source packages. Nevertheless, for simplicityfrom now on SEMS-ng is referred to in this document as SEMS . 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, the second part explains how to write an application in the IVR as python script. While the APIand the use of the framework is different or sometimes has differing names, one basically does the same thingsin both C++ plugins: On session start one sets input and output, and then one reacts to various events (BYE,DTMF, ...). So even if the reader only wants to use the IVR, the first part gives some probably useful insight.

    The C++ tutorial starts from an empty application plugin template, and subsequently adds functionality, while thecode is explained. First, the plugin is made configurable, and then, the plugin will play an announcement. As anext step we react on the caller's keypresses. Then a calling card application will describe how to usecomponent plugins and SEMS' B2BUA capabilities.

    The IVR tutorial has a minimal announcement application and announcement service following rfc4240: BasicMedia Services with SIP.

    The reader is encouraged to try the plugins while reading this document, modify and adapt them. For each step

    there is a separate archive with all the source available.The author welcomes comments, suggestions, and corrections, and can best be reached via email at the semsor semsdev mailing lists (see http://lists.iptel.org) or directly at mailto:[email protected].

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 3/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    4/22

    2 Writing a C++ application plugin

    2.1 Overview

    Application plugins written in C++ can use the SEMS API on various levels of abstraction. Both on signaling andon media side the application can either use some high level functions - which in most cases should besufficient - or take the freedom to control precisely call flow or media processing on a low level. This providesthe flexibility that both simple application modules can be written with a few lines of code, while also complexapplications can build on the SEMS core.

    It is therefore not possible to define a closed set of SEMS API functions – the SEMS core should rather be seenand used as a framework for developing VoIP applications.

    This tutorial-style guide will present only high level functions, further uses of the SEMS API can be found in theapplication modules that come with SEMS. It should be sufficient though to get the reader started toprogramming her or his own applications and provide a basis for understanding of the other applications.

    2.2 MyApp – the empty template

    sems-tutorial-app-myapp.tgz

    ● empty template for c++ plugin

    An application plugin's source resides in the SEMS source tree under the /apps folder. In this folder there is adirectory for each plugin. The empty, but functional template contains the following files:

    myapp/MyApp.h Header file

    myapp/MyApp.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 ?=../../core

    include $(COREPATH)/plug-in/Makefile.app_module

    This includes the Makefile for application modules from the core path, which will build the module for us.

    Now lets move on to the Header file:

    #include "AmSession.h"

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 4/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    5/22

    From AmSession.h we get most of the Session related stuff

    class MyAppFactory: public AmSessionFactory

    {public:

      MyAppFactory(const string& _app_name);

      int onLoad();

      AmSession* onInvite(const AmSipRequest& req);

    };

    This is the session factory class for our application. It will be instantiated on startup, and at this point onLoad() iscalled. Whenever an INVITE comes for this application, it is requested to react on this, and return anAmSession object. This object is the one that will handle the call itself. As parameter we get the SIP request, so

    we can act accordingly.

    Lets have a look at the class we derive from AmSession that will handle our call:

    class MyAppDialog : public AmSession

    {

    public:

      MyAppDialog();

      ~MyAppDialog();

      void onSessionStart(const AmSipRequest& req);

      void onBye(const AmSipRequest& req);

    };

    As we see there is two event handlers: OnSessionStart, which will be called when the session starts with theINVITE requests that started the session, and onBye, which is called if a BYE is received in the session. Thereare of course more event handlers, which we'll look at later.

    Now to the implementation, which is almost empty yet:

    #include "MyApp.h"

    #include "log.h"

    we need this to display debug messages on SEMS' log (DEBUG, WARN and ERR macros)#define MOD_NAME "myapp"

    EXPORT_SESSION_FACTORY(MyAppFactory,MOD_NAME);

    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.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 5/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    6/22

    }

    int MyAppFactory::onLoad(){

      return 0;

    }

    AmSession* MyAppFactory::onInvite(const AmSipRequest& req)

    {

      return new MyAppDialog();

    }

    Here we create a new Session object and return it.MyAppDialog::MyAppDialog()

    {

    }

    MyAppDialog::~MyAppDialog()

    {

    }

    void MyAppDialog::onSessionStart(const AmSipRequest& req)

    {

      DBG("MyAppDialog::onSessionStart: Hello World!\n");

    }

    Until now we don't want to do anything with the call...

    void MyAppDialog::onBye(const AmSipRequest& req)

    {

      DBG("onBye: stopSession\n");

      setStopped();

    }

    ...only if we receive a BYE we have to stop the Session. This will stop the session thread and eventually theSession will be removed from the SessionContainer.

    How to start that example:

    ● chdir to sems/ 

    ● unpack myapp.tgz (tar xzvf myapp.tgz)

    ● rebuild (make all)

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 6/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    7/22

    ● restart sems

    In the SEMS log you should see a line like:(7419) DEBUG: load (AmPlugIn.cpp:115): loading lib/myapp.so ...

    (7419) DEBUG: loadAppPlugIn (AmPlugIn.cpp:327): application 'myapp' loaded.

    If you then route a call from ser's ser.cfg to sems using

    t_write_unix("/tmp/your_sems_socket","myapp")

    you should see in the logfile something like:

    (7419) DEBUG: onSessionStart (MyApp.cpp:35): MyAppDialog::onSessionStart: Hello

    World!

    (7419) ERROR: onSipRequest (AmSession.cpp:484): missing audio input and/or ouput.

    As we can see, the myapp got the call, an then SEMS complained that it does not have something to send orrecord, which is perfectly ok because so far the app is empty.

    If you hangup the phone, you will see something like this in the log:

    (7419) DEBUG: onBye (MyApp.cpp:40): onBye: stopSession

    If you do not get these log entries, there is probably something wrong with your ser configuration, or you did notplace the module in the correct path where SEMS searches for plugins, in this case have a look at yoursems.conf configuration.

    2.3 MyConfigurableApp – adding configurability

    sems-tutorial-app-myconfigurableapp.tgz

    ● application module with configurability

    The next step is to add configurability, so we can set the file which will be played in the following chapter, whichwill be a simple announcement application.

    We call this application myconfigurableapp. So we have to rename the source files, the module name in the

    Makefile and MOD_NAME in the source. In

    SEMS has one main configuration file (sems.conf ) , and each module has its own configuration file, which

    usually has the name of the module plus “.conf”.

    In the MyConfigurableAppFactory, we add a static member to remember the configuration:

      static string AnnouncementFile;

    We use the ConfigReader from AmConfigReader.h, which will read the config file for us:

    #include "AmConfigReader.h"

    When the module is loaded, we configure it in the onLoad function:

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 7/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    8/22

    int MyConfigurableAppFactory::onLoad()

    {

      AmConfigReader cfg;  if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))

    return -1;

      AnnouncementFile = cfg.getParameter("announcement_file","/tmp/default.wav");

    Read the parameter, the second is the default value which will be used if the there is no configuration forannouncement_file.

      if(!file_exists(AnnouncementFile)){

    ERROR("announcenment file for configurableApp module does not exist

    ('%s').\n",

      AnnouncementFile.c_str());

    return -1;

      }

    We check whether the file exists with the file_exists function form AmUtils. If we return error (-1) here, SEMSwill not startup on failure.

      return 0;

    }

    Return OK.

    Now that this application is called myconfigurableapp  and not myapp  any more, we must not forget to change

    the line in the ser.cfg to execute the new application:t_write_unix("/tmp/your_sems_socket","myconfigurableapp")

    2.4 MyAnnounceApp – an announcement

    sems-tutorial-app-myannounceapp.tgz

    ● play an announcement from a (configurable) file

    While the plugin didn't do much with the caller until now, now the time has come to let it do something: it will playthe file to the caller indicated in the configuration.

    To read a file, we use AmAudioFile from AmAudio, which already does all the work for us: it opens the file, looksfor the appropriate file format and codec. We add a member wav_file to the session class as

    MyAnnounceAppDialog::wav_file ;

    When the session starts, we open the file and set it as output:

    void MyAnnounceAppDialog::onSessionStart(const AmSipRequest& req)

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 8/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    9/22

    {

      DBG("MyAnnounceAppDialog::onSessionStart - file is '%s'\n",

    MyAnnounceAppFactory::AnnouncementFile.c_str());

      if(wav_file.open(MyAnnounceAppFactory::AnnouncementFile,AmAudioFile::Read))

    throw string("MyAnnounceAppDialog::onSessionStart: Cannot open file\n");

    If an error has occured, the return value is != 0

     

    setOutput(&wav_file);

    }

    As we maybe remember from the developersguide, each AmSession has an input  and an output . Audio is readfrom the output  and sent via RTP, and received audio is written to the input . It is sufficient for the Session to set

    the output – the SEMS core does the rest.

    Additionally, when all the file is played, the audio engine sends an event to the session (an AmAudio event withthe type AmAudio::cleared), and the default behaviour in this case is to stop the session, which will send a bye.

    We can set anything derived from AmAudio as input or output. In the core there is several useful things alreadyimplemented:

    AmAudioFile Write or read from or to file

    AmPlaylist Has several items in the playlist that are played sequentially.

    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, the Audio is written/read through all of them (e.g. for putting filters beorea conference)

    For a more detailed description and examples have a look at the developersguide section

    ➢ insert reference

    2.5 MyJukebox - reacting to user input

    sems-tutorial-app-myjukebox.tgz

    ● react to key input

    ● use playlist

    This application shows how to react on user input via DTMF. Additionally the playlist (AmPlaylist) is introduced.If the user presses a key while in the call, the corresponding file is added to the back of the playlist.

    The MyJukeboxDialog gets an AmPlaylist as member:

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page 9/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    10/22

    class MyJukeboxDialog : public AmSession

    {

      AmPlaylist playlist;

     public:

    ...

    The playlist is initialized with the dialog object as event receiver; when the playlist is empty we will get an eventfrom the playlist.

    MyJukeboxDialog::MyJukeboxDialog()

      : playlist(this)

    {

    }

    When the session starts, we set input and output to the playlist (well... we don't need input actually, but we'll stillset it), and enable DTMF detection:

    void MyJukeboxDialog::onSessionStart(const AmSipRequest& req)

    {

      DBG("MyJukeboxDialog::onSessionStart - jukedir is '%s'\n",

    MyJukeboxFactory::JukeboxDir.c_str());

     

    setInOut(&playlist, &playlist);

      setDtmfDetectionEnabled(true);

    }

    By default DTMF detection is disabled as the signal processing involved use quite some processing power.

    When the caller presses a key, the onDtmf event handler is executed, where we just open the file and add it tothe playlist:

    void MyJukeboxDialog::onDtmf(int event, int duration) {

      DBG("MyJukeboxDialog::onDtmf, got event %d, duration %d.\n", event, duration);

      AmAudioFile* wav_file = new AmAudioFile();

      if(wav_file->open(MyJukeboxFactory::JukeboxDir + int2str(event) +

    ".wav",AmAudioFile::Read)) {

      ERROR("MyJukeboxDialog::onSessionStart: Cannot open file\n");

      delete wav_file;

      return;

      }

      AmPlaylistItem* item = new AmPlaylistItem(wav_file, NULL);

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    10/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    11/22

      playlist.addToPlaylist(item);

    }

    We also get an event when the playlist is empty. As this is an AmAudio event, we have to filter this on theprocess routine before the AmAudio base class processes the events, 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");

      AmAudioEvent* audio_ev = dynamic_cast(ev);

      if(audio_ev && (audio_ev->event_id == AmAudioEvent::noAudio)){

      DBG("MyJukeboxDialog::process: Playlist is empty!\n");

      return;

      }

      AmSession::process(ev);

    }

    To try this application, we create a config file myjukebox.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, named 0.wav, 1.wav, 2.wav, and so on.

    Even though this not that much fun like real music files, using the following line we can get the numbers as wavfiles from the CVS of the 0.9 version of SEMS:

    for ((f = 0; f < 10; f++)); do cvs -d

    :pserver:[email protected]:/cvsroot/sems co answer_machine/wav/$f.wav; done

    ; mv answer_machine/wav /tmp/jukebox; rm -rf answer_machine

    2.6 Calling Card application in B2BUA mode

    sems-tutorial-app-cc_acc.tgz

    sems-tutorial-app-mycc.tgz

    ccard_wav.tar

    ● create component module

    ● use DI API

    ● use user_timer  

    ● B2BUA application

    As next example we will use SEMS' B2BUA capabilities to create a calling card application, for example for a

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    11/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    12/22

    PSTN gateway. When a user dials in, his PIN number is collected and verified, and if his credit is sufficient, heis asked for a number to dial. With SEMS acting as Back-to-Back user agent with this number then a session isestablished (the callee leg). The session is terminated when the user is out of credit. On termination, the users'

    credit is reduced by the amount of time the session was established.To test this application there is a set of wave files available with the messages to be played back to the caller, asccard_wav.tar. They were created with the flite speech synthesizer and are of course only for testing purposes.

    We want to build the application in a modular manner: On module, called cc_acc provides the accounting,

    which is used by the other one, mycc , which is the implementation of the call logic. This shows how to provide

    and use a di-interface (di  stands for dynamic invocation ), and how to create component modules. The

    application also uses the user_timer, a timer from the session_timer plugin, which has seconds granularity,which should be enough for a calling card application.

    Lets start with the accounting module cc_acc , it should provide only two functions:

      /** returns credit for pin, -1 if pin wrong */

      int getCredit(string pin);  /** returns remaining credit */

      int subtractCredit(string pin, int amount);

    The implementation of these functions is straightforward, we simply use a singleton CCAcc, in which we have amap to hold the credits. As an example we add a credit for “12345” on startup.

    As every class that provides a di-interface, the class must implement AmDynInvoke  (from AmApi.h), which

    consists of the function invoke(method, args, ret):

    #include "AmApi.h"

    /**

     * accounting class for calling card.

     * this illustrates the DI interface

     * and component modules

     */

    class CCAcc : public AmDynInvoke

    {

      /** returns credit for pin, -1 if pin wrong */

      int getCredit(string pin);

      /** returns remaining credit */

      int subtractCredit(string pin, int amount);

     

    map credits;

      // as this is used from various sessions,

      // it must be protected by a mutex

      AmMutex credits_mut;

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    12/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    13/22

      static CCAcc* _instance;

     public:  CCAcc();

      ~CCAcc();

      static CCAcc* instance();

      void invoke(const string& method, const AmArgArray& args, AmArgArray& ret);

    };

    To use the API , the caller gets the DI Interface and calls invoke with the function name as the first parameter,the arguments as the second, and an empty array for return values as the third. The AmArgArray type is adynamic array that can hold entries of types int, double or c string.

    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, AmArgArray& ret)

    {

      if(method == "getCredit"){

      ret.push(getCredit(args.get(0).asCStr()));

      }

      else if(method == "subtractCredit"){

      ret.push(subtractCredit(args.get(0).asCStr(),

      args.get(1).asInt()));

      }

      else

    throw AmDynInvoke::NotImplemented(method);

    }

    To export the interface, so that the plugin-loader knows that we export a DI-API from the plugin, we must exporta class that implements AmDynInvokeFactory , which is the factory class for instances of the DI-interface. In our

    case we have the implementation of the accounting as singleton as well so we just give out the sameCCAcc::instance every time:

    class CCAccFactory : public AmDynInvokeFactory{

    public:

      CCAccFactory(const string& name)

    : AmDynInvokeFactory(name) {}

      AmDynInvoke* getInstance(){

    return CCAcc::instance();

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    13/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    14/22

      }

      int onLoad(){  DBG("CCAcc calling card accounting loaded.\n");

      return 0;

      }

    };

    As every plugin factory (AmDynInvokeFactory is a AmPluginFactory ), it also has a function onLoad().

    To export the CCAccFactory for the plugin loader, we use

    EXPORT_PLUGIN_CLASS_FACTORY(CCAccFactory,"cc_acc");

    The second parameter is the interface factory name, which is used by the user of the DI-API.

    Lets now see how we use the DI API, in the application mycc (apps/MyCC.cpp and apps/MyCC.h). In theSessionFactory we get a Factory for the Interface, in

    int MyCCFactory::onLoad() {

     ...

     cc_acc_fact = AmPlugIn::instance()->getFactory4Di("cc_acc");

      if(!cc_acc_fact){

      ERROR("could not load cc_acc accounting, please provide a module\n");

      return -1;

     }

     ...

    This Factory, AmDynInvokeFactory* cc_acc_fact , is later used to get a DI interface, which we pass to the

    CCDialog class (the session class):

    AmSession* MyCCFactory::onInvite(const AmSipRequest& req)

    {

    ...

      AmDynInvoke* cc_acc = cc_acc_fact->getInstance();

      if(!user_timer){ERROR("could not get a cc acc reference\n");

    throw AmSession::Exception(500,"could not get a cc acc reference");

      }

      return new MyCCDialog(cc_acc, user_timer);

    }

    We get a user_timer   DI interface as well and pass it to the CCDialog class. We will use the user_timer , which

    is a timer with seconds granularity provided by the session_timer plugin, to limit the call time to the maximum

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    14/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    15/22

    available credit.

    To use the DI interface we fill the args with the arguments, call the function with the appropriate name, and readthe result from the return value array, as here in MyCCDialog::onDtmf :

      AmArgArray di_args,ret;

    di_args.push(pin.c_str());

    cc_acc->invoke("getCredit", di_args, ret);

    credit = ret.get(0).asInt();

    Now to the call control class, the MyCCDialog. As we want to get into B2BUA mode later, we derive the Callcontrol class from AmB2BCallerSession . A AmB2BCallerSession behaves like a normal AmSession, if

    sip_relay_only == false. We create another leg of the call, the so-called callee leg, with the functionconnectCallee , which will set sip_relay_only to true, such that all events (SIP requests and replys) coming from

    the caller will be relayed to the other leg to the callee leg.

    A AmB2BCallerSession also has two more callbacks: onOtherReply and onOtherBye. onOtherReply iscalled when there is a reply in the callee leg; if we sense this we can start the accounting when the callee isconnected:

    void MyCCDialog::onOtherReply(const AmSipReply& reply) {

      DBG("OnOtherReply \n");

      if (state == CC_Dialing) {

      if (reply.code < 200) {

      DBG("Callee is trying... code %d\n", reply.code);

      } else if(reply.code < 300){

      if (getCalleeStatus() == Connected) {

    state = CC_Connected;

    start the accounting:

    startAccounting();

    set our input and output to NULL such that we don't send RTP to the caller:

    setInOut(NULL, NULL);

    set the call timer from the session timer, this will post us an event on timeout:

    // set the call timer

    AmArgArray di_args,ret;

    di_args.push(TIMERID_CREDIT_TIMEOUT);

    di_args.push(credit); // in seconds

    di_args.push(dlg.local_tag.c_str());

    user_timer->invoke("setTimer", di_args, ret);

      }

    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",reply.code);

      addToPlaylist(MyCCFactory::DialFailed);

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    15/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    16/22

      number = "";

      state = CC_Collecting_Number;

      }  }

    // we dont call

      // AmB2BCallerSession::onOtherReply(reply);

      // 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");

      stopAccounting();

      AmB2BCallerSession::onOtherBye(req); // 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");

      if (state == CC_Connected) {

      stopAccounting();

      }

      terminateOtherLeg();

      setStopped();

    }

    Our implementation of the calling card service logic uses a state machine with four states, Collecting_PIN,Collecting_ Number, CC_Dialing and CC_Connected. Depending on this state we add the number to the PIN orthe Number in onDtmf, or we do connectCallee.

    void MyCCDialog::onDtmf(int event, int duration) {

      DBG("MyCCDialog::onDtmf, got event %d, duration %d.\n", event, duration);

      switch (state) {

    ...

     case CC_Collecting_Number:

    if(event

  • 8/9/2019 Semsng-App Module Tutorial

    17/22

      } else {

      if (getCalleeStatus() == None) {

    state = CC_Dialing;connectCallee(number+MyCCFactory::ConnectSuffix,

    "sip:"+number+MyCCFactory::ConnectSuffix);

    addToPlaylist(MyCCFactory::Dialing);

      }

      } break;

    If there is a timeout of the timer we set before using the user_timer API, we get an event in our event queue.This event is of the type AmPluginEvent, and has name==”timer_timeout” . So in MyCCDialog::process we filterout this event, and tear down the call:

    void MyCCDialog::process(AmEvent* ev)

    {

    ...

    AmPluginEvent* plugin_event = dynamic_cast(ev);

      if(plugin_event && plugin_event->name == "timer_timeout") {

      int timer_id = plugin_event->data.get(0).asInt();

      if (timer_id == TIMERID_CREDIT_TIMEOUT) {

    DBG("timer timeout: no credit...\n");

    stopAccounting();

    terminateOtherLeg();

    terminateLeg();

    ev->processed = true;

    return;

      }

      }

      AmB2BCallerSession::process(ev);

    }

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    17/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    18/22

    3 Writing an IVR application

    3.1 Ivr Overview

    With the ivr plugin, applications can be written as a Python script which will be executed in a python interpreterembedded in the ivr module. On SEMS startup, when the ivr is loading, it loads all scripts that are in the pathscript_path, a configuration parameter of the ivr plugin, and registers the applications with the core. If forexample the script “mailbox” is loaded, the ivr registers the application name “mailbox” to this script, and if anINVITE is passed to SEMS with “mailbox” as application name, the application will be executed.

    The script itself is executed on startup of the script. Every application script has to define one class derived fromIvrDialogBase . An instance of this class will be created if a call is received for the application, and its

    onSessionStart will be executed. IvrDialogBase also has other event handlers: onDtmf , onBye ,

    onEmptyQueue .

    The IvrDialogBase class has as attribute the SEMS' representation of the SIP dialog class, named

    IvrDialogBase::dialog, which contains to, from etc.

    There is a dictionary config, which holds the configuration for the application.

    With the methods stopSession and bye, the call can be ended or a bye sent.

    IvrDialogBase already has a playlist integrated. From the python script, the function enqueue(AmAudio) can beused to put an entry in the playlist, for example an IvrAudioFile, which is already implemented in the ivr library.

    flush empties the playlist, while mute  / unmute  control whether the session is muted.

    If key presses should be received, enableDTMFDetection  should be called. If a keypress is received, the

    onDtmf event handler is executed. disableDTMFDetection should be called if key presses are ignored, as the

    processing takes some CPU power.

    The IvrDialogBase also has the user_timer integrated; it is used with functions setTimer, clearTimer,

    clearTimers, and the onTimer callback.

    The ivr can be used in B2BUA mode. In B2BUA mode the first leg of the call is called CallerLeg, and the secondone CalleeLeg. connectCallee connects another leg, and terminateOtherLeg terminates it, while terminateLeg

    tears down the caller leg itself (the script is running in the “CallerLeg”). setRelayonly controls whether SEMS

    should only relay SIP events or react to them itself.

    In the log library the logging functions known from SEMS are defined: error , warn , and debug. They will log

    using SEMS' logging facility.

    3.2 Simple announcement script

    sems-tutorial-app-ivr_announce.tgz

    ● use playlist for announcement

    ● configurable announcement file

    A basic ivr script which plays back a file configured as “announcement” configuration parameter is a twelveliner:

    from log import *

    from ivr import *

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    18/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    19/22

    class IvrDialog(IvrDialogBase):

    announcement = None

    def onSessionStart(self, hdrs):

    debug("onSessionStart of ivr announcement app")

    self.announcement = IvrAudioFile()

    self.announcement.open(config['announcement'], ivr.AUDIO_READ)

    self.enqueue(self.announcement, None)

    def onEmptyQueue(self):

    self.bye()

    self.stopSession()

    onEmptyQueue is called every time that the playlist is empty, and then we have to end the call ourselves.

    3.3 RFC 4240 announcement service

    sems-tutorial-app-annc_service.tgz

    ● process request uri

    ● react on empty playlist

    ● use user_timers

    This script implements announcement service following rfc4240. The file to be played back is encoded in therequest uri of the INVITE as parameter “play” (e.g. INVITEsip:[email protected]:5060;play=http://mediaserver.net/example.wav). It can be a remote (http uri) or alocal file. The script uses urlparse and urllib libraries to parse the parameter and get the file.

    Additional optional parameters are the maximum play time, the repeat count and delay between two repeats.The script uses two timers from user_timer (session_timer plugin) to control this. Every timer has an ID, whichmust be >0. This timer id is used when setting the timer with the setTimer method, and on timeout the onTimermethod is called with the corresponding timer ID. The timers have seconds granularity so the second parameterto the setTimer function is the timeout in seconds.

    We react to onEmptyQueue (empty playlist) and onTimer events, by hanging up, setting the timer or rewindingand enqueuing the file once more.

    # Simple implementation of (a part) of RFC4240

    # announcement service.

    #

    # supported parameters:

    # play, repeat, duration, delay

    from log import *

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    19/22

    mailto:[email protected]://www.iptego.de/mailto:[email protected]://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    20/22

    from ivr import *

    from urlparse import urlparse, urlsplitfrom 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,hdrs):

    debug("configuration: %s" % repr(config))

    debug("local_uri = " + self.dialog.local_uri);

    # we use urlsplit as urlparse only returns the

    # parameters of the last path

    params = urlsplit(self.dialog.local_uri)[2].split(";")

    debug("parameters are " + str(params))

    for param in params[0:len(params)]:

    if (param.startswith("play=")):

    self.play=param[5:len(param)]

    elif (param.startswith("repeat=")):

    self.repeat=param[7:len(param)]

    elif (param.startswith("delay=")):

    self.delay=int(param[6:len(param)])

    elif (param.startswith("duration=")):

    self.duration=int(param[9:len(param)])

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    20/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    21/22

    resource = urlparse(self.play)

    if (resource[0] == "http"):self.delete_onbye = True

    self.filename = urlretrieve(self.play)[0]

    debug("play: "+self.play+" repeat: "+self.repeat+" delay:"+

    str(self.delay)+" duration: "+str(self.duration))

    self.announcement = IvrAudioFile()

    self.announcement.open(self.filename,AUDIO_READ)

    if (self.repeat!="forever"):

    self.repeat_left=int(self.repeat)-1

    else:

    self.repeat_left=500 # maximum

    if (int(self.duration) > 0):

    self.setTimer(TIMEOUT_TIMER_ID, self.duration/1000)

    self.enqueue(self.announcement, None)

    def onBye(self):

    self.stopSession()

    self.cleanup()

    def onEmptyQueue(self):

      if (self.repeat_left>0):

    if (int(self.delay) > 0):

    self.setTimer(DELAY_TIMER_ID, int(self.delay)/1000)

    else:

    self.repeat_left-=1

    self.announcement.rewind()

    self.enqueue(self.announcement, None)

    else:

    self.bye()

    self.stopSession()

    self.cleanup()

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    http://www.iptego.de

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    21/22

    http://www.iptego.de/http://www.iptego.de/

  • 8/9/2019 Semsng-App Module Tutorial

    22/22

    def onDtmf(self,key,duration):

    pass

    def onTimer(self, timer_id):

    if (timer_id == TIMEOUT_TIMER_ID):

    self.bye()

    self.stopSession()

    self.cleanup()

    elif (timer_id == DELAY_TIMER_ID):

    self.repeat_left-=1

    self.announcement.rewind()

    self.enqueue(self.announcement, None)

    def cleanup(self):

    if (self.delete_onbye):

    unlink(self.filename)

    debug("cleanup..." + self.filename + " deleted.")

    self.removeTimers()

    References

    This document is copyright of iptego GmbH.

    Distribution is only allowed with written

    permission of iptego GmbH.

    Filename:

    Author:

    Version:

    semsng-app_module_tutorial.odt

    Stefan Sayer

    0.1

     

    Page

    /

    http://www.iptego.de/