diff --git a/apps/examples/tutorial/Readme.tutorial b/apps/examples/tutorial/Readme.tutorial new file mode 100644 index 00000000..d00c2b16 --- /dev/null +++ b/apps/examples/tutorial/Readme.tutorial @@ -0,0 +1,22 @@ +SEMS application development tutorial examples + +These are the examples from the application development tutorial +which is available from the SEMS website: +http://www.iptel.org/sems/sems_application_development_tutorial + +The examples here compile fine with 'make' in their respective +directories. Depending on your python version, you may want to use +e.g. 'make PYTHON_VERSION=2.4' for the python examples. + +The tutorial consists of the PDF document +(http://www.iptel.org/files/semsng-app_module_tutorial.pdf) and +the examples, and there are installation instructions on the +website. + +A sample configuration set is available at +http://www.iptel.org/files/sems-tutorial-config.tgz + +For the calling card example, you may want to download some sample +wav files from http://www.iptel.org/files/ccard_wav.tgz + + diff --git a/apps/examples/tutorial/annc_service/Makefile b/apps/examples/tutorial/annc_service/Makefile new file mode 100644 index 00000000..5fd6f5bf --- /dev/null +++ b/apps/examples/tutorial/annc_service/Makefile @@ -0,0 +1,14 @@ + +NAME=annc_service +VERSION=0.0.1 + +PYTHON_VERSION=2.3 + +LIBDIR= + +COREPATH=../../../../core +IVRPATH=../../../ivr + +# general python app Makefile +include ../../../ivr/Makefile.ivr_application + diff --git a/apps/examples/tutorial/annc_service/annc_service.py b/apps/examples/tutorial/annc_service/annc_service.py new file mode 100644 index 00000000..bcbd123c --- /dev/null +++ b/apps/examples/tutorial/annc_service/annc_service.py @@ -0,0 +1,104 @@ +# +# Simple implementation of (a part) of RFC4240 +# announcement service. +# +# supported parameters: +# play, repeat, duration, delay + + +from log import * +from ivr import * + +from urlparse import urlparse, 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,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)]) + + 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() + + 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() + + diff --git a/apps/examples/tutorial/annc_service/py_comp b/apps/examples/tutorial/annc_service/py_comp new file mode 100644 index 00000000..911ce58f --- /dev/null +++ b/apps/examples/tutorial/annc_service/py_comp @@ -0,0 +1,2 @@ +import compileall +compileall.main() diff --git a/apps/examples/tutorial/cc_acc/CCAcc.cpp b/apps/examples/tutorial/cc_acc/CCAcc.cpp new file mode 100644 index 00000000..82865da6 --- /dev/null +++ b/apps/examples/tutorial/cc_acc/CCAcc.cpp @@ -0,0 +1,84 @@ +#include "AmPlugIn.h" +#include "log.h" + +#include "CCAcc.h" + +class CCAccFactory : public AmDynInvokeFactory +{ +public: + CCAccFactory(const string& name) + : AmDynInvokeFactory(name) {} + + AmDynInvoke* getInstance(){ + return CCAcc::instance(); + } + + int onLoad(){ + DBG("CCAcc calling card accounting loaded.\n"); + return 0; + } +}; + +EXPORT_PLUGIN_CLASS_FACTORY(CCAccFactory,"cc_acc"); + +CCAcc* CCAcc::_instance=0; + +CCAcc* CCAcc::instance() +{ + if(!_instance) + _instance = new CCAcc(); + return _instance; +} + +CCAcc::CCAcc() { + // add some sample credits + credits["12345"] = 100; +} + +CCAcc::~CCAcc() { } + +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); +} + +/* accounting functions... */ +int CCAcc::getCredit(string pin) { + credits_mut.lock(); + map::iterator it = credits.find(pin); + if (it == credits.end()) { + DBG("PIN '%s' dies not exist.", pin.c_str()); + credits_mut.unlock(); + return -1; + } + unsigned int res = it->second; + credits_mut.unlock(); + return res; +} + +int CCAcc::subtractCredit(string pin, int amount) { + credits_mut.lock(); + map::iterator it = credits.find(pin); + if (it == credits.end()) { + ERROR("PIN '%s' dies not exist.", pin.c_str()); + credits_mut.unlock(); + return -1; + } + if (it->second - amount < 0) { + credits[pin] = 0; + } else { + credits[pin] = it->second - amount; + } + unsigned int res = credits[pin]; + credits_mut.unlock(); + return res; +} + diff --git a/apps/examples/tutorial/cc_acc/CCAcc.h b/apps/examples/tutorial/cc_acc/CCAcc.h new file mode 100644 index 00000000..289ef187 --- /dev/null +++ b/apps/examples/tutorial/cc_acc/CCAcc.h @@ -0,0 +1,27 @@ +#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; + + static CCAcc* _instance; + public: + CCAcc(); + ~CCAcc(); + static CCAcc* instance(); + void invoke(const string& method, const AmArgArray& args, AmArgArray& ret); +}; diff --git a/apps/examples/tutorial/cc_acc/Makefile b/apps/examples/tutorial/cc_acc/Makefile new file mode 100644 index 00000000..028c7acc --- /dev/null +++ b/apps/examples/tutorial/cc_acc/Makefile @@ -0,0 +1,7 @@ +plug_in_name = cc_acc + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/ivr_announce/Makefile b/apps/examples/tutorial/ivr_announce/Makefile new file mode 100644 index 00000000..ee148cbc --- /dev/null +++ b/apps/examples/tutorial/ivr_announce/Makefile @@ -0,0 +1,14 @@ + +NAME=ivr_announce +VERSION=0.0.1 + +PYTHON_VERSION=2.4 + +LIBDIR= + +COREPATH=../../../../core +IVRPATH=../../../ivr + +# general python app Makefile +include $(IVRPATH)/Makefile.ivr_application + diff --git a/apps/examples/tutorial/ivr_announce/etc/ivr_announce.conf b/apps/examples/tutorial/ivr_announce/etc/ivr_announce.conf new file mode 100644 index 00000000..ded5e168 --- /dev/null +++ b/apps/examples/tutorial/ivr_announce/etc/ivr_announce.conf @@ -0,0 +1 @@ +announcement=/usr/local/lib/sems/audio/default.wav diff --git a/apps/examples/tutorial/ivr_announce/ivr_announce.py b/apps/examples/tutorial/ivr_announce/ivr_announce.py new file mode 100644 index 00000000..a055ce15 --- /dev/null +++ b/apps/examples/tutorial/ivr_announce/ivr_announce.py @@ -0,0 +1,16 @@ + +from log import * +from ivr import * + +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() diff --git a/apps/examples/tutorial/ivr_announce/py_comp b/apps/examples/tutorial/ivr_announce/py_comp new file mode 100644 index 00000000..911ce58f --- /dev/null +++ b/apps/examples/tutorial/ivr_announce/py_comp @@ -0,0 +1,2 @@ +import compileall +compileall.main() diff --git a/apps/examples/tutorial/myannounceapp/Makefile b/apps/examples/tutorial/myannounceapp/Makefile new file mode 100644 index 00000000..a0b3fda6 --- /dev/null +++ b/apps/examples/tutorial/myannounceapp/Makefile @@ -0,0 +1,7 @@ +plug_in_name = myannounceapp + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/myannounceapp/MyAnnounceApp.cpp b/apps/examples/tutorial/myannounceapp/MyAnnounceApp.cpp new file mode 100644 index 00000000..1d6ccefc --- /dev/null +++ b/apps/examples/tutorial/myannounceapp/MyAnnounceApp.cpp @@ -0,0 +1,66 @@ + +#include "MyAnnounceApp.h" + +#include "log.h" +#include "AmConfigReader.h" +#include "AmUtils.h" + +#define MOD_NAME "myannounceapp" + +EXPORT_SESSION_FACTORY(MyAnnounceAppFactory,MOD_NAME); + +string MyAnnounceAppFactory::AnnouncementFile; + +MyAnnounceAppFactory::MyAnnounceAppFactory(const string& _app_name) + : AmSessionFactory(_app_name) +{ +} + +int MyAnnounceAppFactory::onLoad() +{ + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) + return -1; + + AnnouncementFile = cfg.getParameter("announcement_file","/tmp/default.wav"); + + if(!file_exists(AnnouncementFile)){ + ERROR("announcement file for configurableApp module does not exist ('%s').\n", + AnnouncementFile.c_str()); + return -1; + } + + return 0; +} + +AmSession* MyAnnounceAppFactory::onInvite(const AmSipRequest& req) +{ + return new MyAnnounceAppDialog(); +} + +MyAnnounceAppDialog::MyAnnounceAppDialog() +{ +} + +MyAnnounceAppDialog::~MyAnnounceAppDialog() +{ +} + +void MyAnnounceAppDialog::onSessionStart(const AmSipRequest& req) +{ + 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"); + + setOutput(&wav_file); + +} + +void MyAnnounceAppDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye: stopSession\n"); + setStopped(); +} + diff --git a/apps/examples/tutorial/myannounceapp/MyAnnounceApp.h b/apps/examples/tutorial/myannounceapp/MyAnnounceApp.h new file mode 100644 index 00000000..8323be8a --- /dev/null +++ b/apps/examples/tutorial/myannounceapp/MyAnnounceApp.h @@ -0,0 +1,35 @@ +#ifndef _MYANNOUNCEAPP_H_ +#define _MYANNOUNCEAPP_H_ + +#include "AmSession.h" +#include "AmAudio.h" + +#include +using std::string; + +class MyAnnounceAppFactory: public AmSessionFactory +{ +public: + static string AnnouncementFile; + + MyAnnounceAppFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class MyAnnounceAppDialog : public AmSession +{ + + AmAudioFile wav_file; + + public: + MyAnnounceAppDialog(); + ~MyAnnounceAppDialog(); + + void onSessionStart(const AmSipRequest& req); + void onBye(const AmSipRequest& req); +}; + +#endif + diff --git a/apps/examples/tutorial/myannounceapp/Readme b/apps/examples/tutorial/myannounceapp/Readme new file mode 100644 index 00000000..3268df38 --- /dev/null +++ b/apps/examples/tutorial/myannounceapp/Readme @@ -0,0 +1,7 @@ + +MyAnnouceapp + +This app plays a file back to the +caller. The file is configured +with the annoucement_file config +option. diff --git a/apps/examples/tutorial/myannounceapp/etc/myannounceapp.conf b/apps/examples/tutorial/myannounceapp/etc/myannounceapp.conf new file mode 100644 index 00000000..1d5d45fc --- /dev/null +++ b/apps/examples/tutorial/myannounceapp/etc/myannounceapp.conf @@ -0,0 +1,3 @@ +#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE +announcement_file=/tmp/test.wav +#ENDCFGOPTION diff --git a/apps/examples/tutorial/myapp/Makefile b/apps/examples/tutorial/myapp/Makefile new file mode 100644 index 00000000..8450ca13 --- /dev/null +++ b/apps/examples/tutorial/myapp/Makefile @@ -0,0 +1,7 @@ +plug_in_name = myapp + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/myapp/MyApp.cpp b/apps/examples/tutorial/myapp/MyApp.cpp new file mode 100644 index 00000000..c7b8a0c2 --- /dev/null +++ b/apps/examples/tutorial/myapp/MyApp.cpp @@ -0,0 +1,41 @@ +#include "MyApp.h" +#include "log.h" + +#define MOD_NAME "myapp" + +EXPORT_SESSION_FACTORY(MyAppFactory,MOD_NAME); + +MyAppFactory::MyAppFactory(const string& _app_name) + : AmSessionFactory(_app_name) +{ +} + +int MyAppFactory::onLoad() +{ + return 0; +} + +AmSession* MyAppFactory::onInvite(const AmSipRequest& req) +{ + return new MyAppDialog(); +} + +MyAppDialog::MyAppDialog() +{ +} + +MyAppDialog::~MyAppDialog() +{ +} + +void MyAppDialog::onSessionStart(const AmSipRequest& req) +{ + DBG("MyAppDialog::onSessionStart: Hello World!\n"); +} + +void MyAppDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye: stopSession\n"); + setStopped(); +} + diff --git a/apps/examples/tutorial/myapp/MyApp.h b/apps/examples/tutorial/myapp/MyApp.h new file mode 100644 index 00000000..2dcb02b0 --- /dev/null +++ b/apps/examples/tutorial/myapp/MyApp.h @@ -0,0 +1,27 @@ +#ifndef _MYAPP_H_ +#define _MYAPP_H_ + +#include "AmSession.h" + +class MyAppFactory: public AmSessionFactory +{ +public: + MyAppFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class MyAppDialog : public AmSession +{ + + public: + MyAppDialog(); + ~MyAppDialog(); + + void onSessionStart(const AmSipRequest& req); + void onBye(const AmSipRequest& req); +}; + +#endif + diff --git a/apps/examples/tutorial/myapp/Readme b/apps/examples/tutorial/myapp/Readme new file mode 100644 index 00000000..e85f4d05 --- /dev/null +++ b/apps/examples/tutorial/myapp/Readme @@ -0,0 +1,6 @@ + +MyApp + +This is an empty template for a SEMS 0.10.0 +application plug-in written in C++. + diff --git a/apps/examples/tutorial/mycc/Makefile b/apps/examples/tutorial/mycc/Makefile new file mode 100644 index 00000000..b2874042 --- /dev/null +++ b/apps/examples/tutorial/mycc/Makefile @@ -0,0 +1,7 @@ +plug_in_name = mycc + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/mycc/MyCC.cpp b/apps/examples/tutorial/mycc/MyCC.cpp new file mode 100644 index 00000000..15c7a874 --- /dev/null +++ b/apps/examples/tutorial/mycc/MyCC.cpp @@ -0,0 +1,252 @@ + +#include "MyCC.h" + +#include "log.h" +#include "AmConfigReader.h" +#include "AmUtils.h" +#include "AmAudio.h" +#include "AmPlugIn.h" + +#include +#include + +#define MOD_NAME "mycc" + +#define TIMERID_CREDIT_TIMEOUT 1 + +EXPORT_SESSION_FACTORY(MyCCFactory,MOD_NAME); + + string MyCCFactory::InitialAnnouncement; + string MyCCFactory::IncorrectPIN; + string MyCCFactory::OutOfCredit; + string MyCCFactory::Dialing; + string MyCCFactory::DialFailed; + string MyCCFactory::EnterNumber; + string MyCCFactory::ConnectSuffix; + +MyCCFactory::MyCCFactory(const string& _app_name) + : AmSessionFactory(_app_name), user_timer_fact(NULL) +{ +} + +int MyCCFactory::onLoad() +{ + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) + return -1; + + InitialAnnouncement = cfg.getParameter("initial_announcement", "/tmp/hello.wav"); + IncorrectPIN = cfg.getParameter("incorrect_pin", "/tmp/incorrect_pin.wav"); + OutOfCredit = cfg.getParameter("out_of_credit", "/tmp/out_of_credit.wav"); + Dialing = cfg.getParameter("dialing", "/tmp/dialing.wav"); + DialFailed = cfg.getParameter("dial_failed", "/tmp/dial_failed.wav"); + EnterNumber = cfg.getParameter("enter_number", "/tmp/enter_number.wav"); + ConnectSuffix = cfg.getParameter("connect_suffix", "@127.0.0.1"); + + user_timer_fact = AmPlugIn::instance()->getFactory4Di("user_timer"); + if(!user_timer_fact){ + ERROR("could not load user_timer from session_timer plug-in\n"); + return -1; + } + 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; + } + + return 0; +} + +AmSession* MyCCFactory::onInvite(const AmSipRequest& req) +{ + AmDynInvoke* user_timer = user_timer_fact->getInstance(); + if(!user_timer){ + ERROR("could not get a user timer reference\n"); + throw AmSession::Exception(500,"could not get a user timer reference"); + } + + 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); +} + + +MyCCDialog::MyCCDialog(AmDynInvoke* cc_acc, AmDynInvoke* user_timer) + : playlist(this), + state(CC_Collecting_PIN), + cc_acc(cc_acc), user_timer(user_timer), + AmB2BCallerSession() + +{ + sip_relay_only = false; + memset(&acc_start, 0, sizeof(struct timeval)); +} + +MyCCDialog::~MyCCDialog() +{ +} + + +void MyCCDialog::addToPlaylist(string fname) { + AmAudioFile* wav_file = new AmAudioFile(); + if(wav_file->open(fname,AmAudioFile::Read)) { + ERROR("MyCCDialog::onSessionStart: Cannot open file\n"); + delete wav_file; + } else { + AmPlaylistItem* item = new AmPlaylistItem(wav_file, NULL); + playlist.addToPlaylist(item); + } +} + +void MyCCDialog::onSessionStart(const AmSipRequest& req) +{ + DBG("MyCCDialog::onSessionStart"); + + AmB2BCallerSession::onSessionStart(req); + + setInOut(&playlist, &playlist); + addToPlaylist(MyCCFactory::InitialAnnouncement); + + setDtmfDetectionEnabled(true); +} + +void MyCCDialog::onDtmf(int event, int duration) { + DBG("MyCCDialog::onDtmf, got event %d, duration %d.\n", event, duration); + + switch (state) { + case CC_Collecting_PIN: + if(event <10) { + pin +=int2str(event); + DBG("pin is now '%s'\n", pin.c_str()); + } else { + AmArgArray di_args,ret; + di_args.push(pin.c_str()); + cc_acc->invoke("getCredit", di_args, ret); + credit = ret.get(0).asInt(); + if (credit < 0) { + addToPlaylist(MyCCFactory::IncorrectPIN); + pin = ""; + } else if (credit == 0) { + addToPlaylist(MyCCFactory::OutOfCredit); + pin = ""; + } else { + number = ""; + state = CC_Collecting_Number; + addToPlaylist(MyCCFactory::EnterNumber); + } + } break; + case CC_Collecting_Number: + if(event <10) { + number +=int2str(event); + DBG("number is now '%s'\n", number.c_str()); + } else { + if (getCalleeStatus() == None) { + state = CC_Dialing; + connectCallee(number+MyCCFactory::ConnectSuffix, + "sip:"+number+MyCCFactory::ConnectSuffix); + addToPlaylist(MyCCFactory::Dialing); + } + } break; + case CC_Dialing: + case CC_Connected: + default: break; + }; +} + +void MyCCDialog::process(AmEvent* ev) +{ + DBG("MyCCDialog::process\n"); + + AmAudioEvent* audio_ev = dynamic_cast(ev); + if(audio_ev && (audio_ev->event_id == AmAudioEvent::noAudio)){ + DBG("MyCCDialog::process: Playlist is empty!\n"); + return; + } + + 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); +} + +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; + startAccounting(); + setInOut(NULL, NULL); + // 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); + } + } else { + DBG("Callee final error with code %d\n",reply.code); + addToPlaylist(MyCCFactory::DialFailed); + number = ""; + state = CC_Collecting_Number; + } + } + // we dont call + // AmB2BCallerSession::onOtherReply(reply); + // as it tears down the call if callee could + // not be reached +} + +void MyCCDialog::onOtherBye(const AmSipRequest& req) { + DBG("onOtherBye\n"); + stopAccounting(); + AmB2BCallerSession::onOtherBye(req); // will stop the session +} + +void MyCCDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye: stopSession\n"); + if (state == CC_Connected) { + stopAccounting(); + } + terminateOtherLeg(); + setStopped(); +} + +void MyCCDialog::startAccounting() { + gettimeofday(&acc_start,NULL); + DBG("start accounting at %ld\n", acc_start.tv_sec); +} + +void MyCCDialog::stopAccounting() { + if ((acc_start.tv_sec != 0) || (acc_start.tv_usec != 0)) { + struct timeval now; + gettimeofday(&now,NULL); + DBG("stop accounting at %ld\n", now.tv_sec); + timersub(&now,&acc_start,&now); + if (now.tv_usec>500000) now.tv_sec++; + DBG("Call lasted %ld seconds\n", now.tv_sec); + + AmArgArray di_args,ret; + di_args.push(pin.c_str()); + di_args.push((int)now.tv_sec); + cc_acc->invoke("subtractCredit", di_args, ret); + } +} diff --git a/apps/examples/tutorial/mycc/MyCC.h b/apps/examples/tutorial/mycc/MyCC.h new file mode 100644 index 00000000..327f165f --- /dev/null +++ b/apps/examples/tutorial/mycc/MyCC.h @@ -0,0 +1,69 @@ +#ifndef _MYJUKEBOX_H_ +#define _MYJUKEBOX_H_ + +#include "AmB2BSession.h" +#include "AmPlaylist.h" + +#include +using std::string; + +class MyCCFactory: public AmSessionFactory +{ + + AmDynInvokeFactory* user_timer_fact; + AmDynInvokeFactory* cc_acc_fact; + +public: + static string InitialAnnouncement; + static string IncorrectPIN; + static string OutOfCredit; + static string EnterNumber; + static string Dialing; + static string DialFailed; + static string ConnectSuffix; + + MyCCFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class MyCCDialog : public AmB2BCallerSession +{ + enum {CC_Collecting_PIN = 0, + CC_Collecting_Number, + CC_Dialing, + CC_Connected + } CallerState; + + AmPlaylist playlist; + + int state; + + string pin; + string number; + int credit; + void addToPlaylist(string fname); + + void startAccounting(); + void stopAccounting(); + struct timeval acc_start; + + AmDynInvoke* user_timer; + AmDynInvoke* cc_acc; + + public: + MyCCDialog(AmDynInvoke* cc_acc, AmDynInvoke* user_timer); + ~MyCCDialog(); + + void onSessionStart(const AmSipRequest& req); + void onDtmf(int event, int duration); + void process(AmEvent* ev); + void onBye(const AmSipRequest& req); + protected: + void onOtherReply(const AmSipReply& reply); + void onOtherBye(const AmSipRequest& req); +}; + +#endif + diff --git a/apps/examples/tutorial/mycc/Readme b/apps/examples/tutorial/mycc/Readme new file mode 100644 index 00000000..ead4744c --- /dev/null +++ b/apps/examples/tutorial/mycc/Readme @@ -0,0 +1,11 @@ + +MyCC + +This is a calling card application. +Caller is asked for a PIN which +is verified against an accounting +function provided by a cc_acc interface. +If PIN is correct, the caller +may enter a number to call, and is +connected to it in b2bua mode. + diff --git a/apps/examples/tutorial/mycc/etc/mycc.conf b/apps/examples/tutorial/mycc/etc/mycc.conf new file mode 100644 index 00000000..90d22a58 --- /dev/null +++ b/apps/examples/tutorial/mycc/etc/mycc.conf @@ -0,0 +1,8 @@ +initial_announcement =/usr/local/lib/sems/audio/ccard_wav/hello.wav +incorrect_pin =/usr/local/lib/sems/audio/ccard_wav/incorrect_pin.wav +out_of_credit =/usr/local/lib/sems/audio/ccard_wav/out_of_credit.wav +dialing =/usr/local/lib/sems/audio/ccard_wav/dialing.wav +dial_failed =/usr/local/lib/sems/audio/ccard_wav/dial_failed.wav +enter_number =/usr/local/lib/sems/audio/ccard_wav/enter_number.wav + +connect_suffix=@127.0.0.1 diff --git a/apps/examples/tutorial/myconfigurableapp/Makefile b/apps/examples/tutorial/myconfigurableapp/Makefile new file mode 100644 index 00000000..cd7fd618 --- /dev/null +++ b/apps/examples/tutorial/myconfigurableapp/Makefile @@ -0,0 +1,7 @@ +plug_in_name = myconfigurableapp + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.cpp b/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.cpp new file mode 100644 index 00000000..7bde13c4 --- /dev/null +++ b/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.cpp @@ -0,0 +1,60 @@ + +#include "MyConfigurableApp.h" + +#include "log.h" +#include "AmConfigReader.h" +#include "AmUtils.h" + +#define MOD_NAME "myconfigurableapp" + +EXPORT_SESSION_FACTORY(MyConfigurableAppFactory,MOD_NAME); + +string MyConfigurableAppFactory::AnnouncementFile; + +MyConfigurableAppFactory::MyConfigurableAppFactory(const string& _app_name) + : AmSessionFactory(_app_name) +{ +} + +int MyConfigurableAppFactory::onLoad() +{ + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) + return -1; + + AnnouncementFile = cfg.getParameter("announcement_file","/tmp/default.wav"); + + if(!file_exists(AnnouncementFile)){ + ERROR("announcement file for configurableApp module does not exist ('%s').\n", + AnnouncementFile.c_str()); + return -1; + } + + return 0; +} + +AmSession* MyConfigurableAppFactory::onInvite(const AmSipRequest& req) +{ + return new MyConfigurableAppDialog(); +} + +MyConfigurableAppDialog::MyConfigurableAppDialog() +{ +} + +MyConfigurableAppDialog::~MyConfigurableAppDialog() +{ +} + +void MyConfigurableAppDialog::onSessionStart(const AmSipRequest& req) +{ + DBG("MyConfigurableAppDialog::onSessionStart - file is '%s'\n", + MyConfigurableAppFactory::AnnouncementFile.c_str()); +} + +void MyConfigurableAppDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye: stopSession\n"); + setStopped(); +} + diff --git a/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.h b/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.h new file mode 100644 index 00000000..8c5cb355 --- /dev/null +++ b/apps/examples/tutorial/myconfigurableapp/MyConfigurableApp.h @@ -0,0 +1,32 @@ +#ifndef _MYCONFIGURABLEAPP_H_ +#define _MYCONFIGURABLEAPP_H_ + +#include "AmSession.h" + +#include +using std::string; + +class MyConfigurableAppFactory: public AmSessionFactory +{ +public: + static string AnnouncementFile; + + MyConfigurableAppFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class MyConfigurableAppDialog : public AmSession +{ + + public: + MyConfigurableAppDialog(); + ~MyConfigurableAppDialog(); + + void onSessionStart(const AmSipRequest& req); + void onBye(const AmSipRequest& req); +}; + +#endif + diff --git a/apps/examples/tutorial/myconfigurableapp/Readme b/apps/examples/tutorial/myconfigurableapp/Readme new file mode 100644 index 00000000..fed31ed7 --- /dev/null +++ b/apps/examples/tutorial/myconfigurableapp/Readme @@ -0,0 +1,7 @@ + +MyConfigurableApp + +This is a template for a SEMS 0.10.0 +application plug-in written in C++. +If can be configured with its own +configuration file. diff --git a/apps/examples/tutorial/myconfigurableapp/etc/myconfigurableapp.conf b/apps/examples/tutorial/myconfigurableapp/etc/myconfigurableapp.conf new file mode 100644 index 00000000..1d5d45fc --- /dev/null +++ b/apps/examples/tutorial/myconfigurableapp/etc/myconfigurableapp.conf @@ -0,0 +1,3 @@ +#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE +announcement_file=/tmp/test.wav +#ENDCFGOPTION diff --git a/apps/examples/tutorial/myjukebox/Makefile b/apps/examples/tutorial/myjukebox/Makefile new file mode 100644 index 00000000..2564b28a --- /dev/null +++ b/apps/examples/tutorial/myjukebox/Makefile @@ -0,0 +1,7 @@ +plug_in_name = myjukebox + +module_ldflags = +module_cflags = + +COREPATH ?=../../../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/examples/tutorial/myjukebox/MyJukebox.cpp b/apps/examples/tutorial/myjukebox/MyJukebox.cpp new file mode 100644 index 00000000..ebf6e0f9 --- /dev/null +++ b/apps/examples/tutorial/myjukebox/MyJukebox.cpp @@ -0,0 +1,88 @@ + +#include "MyJukebox.h" + +#include "log.h" +#include "AmConfigReader.h" +#include "AmUtils.h" +#include "AmAudio.h" + +#define MOD_NAME "myjukebox" + +EXPORT_SESSION_FACTORY(MyJukeboxFactory,MOD_NAME); + +string MyJukeboxFactory::JukeboxDir; + +MyJukeboxFactory::MyJukeboxFactory(const string& _app_name) + : AmSessionFactory(_app_name) +{ +} + +int MyJukeboxFactory::onLoad() +{ + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) + return -1; + + JukeboxDir = cfg.getParameter("jukebox_dir","/tmp/"); + if( !JukeboxDir.empty() + && JukeboxDir[JukeboxDir.length()-1] != '/' ) + JukeboxDir += "/"; + + return 0; +} + +AmSession* MyJukeboxFactory::onInvite(const AmSipRequest& req) +{ + return new MyJukeboxDialog(); +} + +MyJukeboxDialog::MyJukeboxDialog() + : playlist(this) +{ +} + +MyJukeboxDialog::~MyJukeboxDialog() +{ +} + +void MyJukeboxDialog::onSessionStart(const AmSipRequest& req) +{ + DBG("MyJukeboxDialog::onSessionStart - jukedir is '%s'\n", + MyJukeboxFactory::JukeboxDir.c_str()); + + setInOut(&playlist, &playlist); + setDtmfDetectionEnabled(true); +} + +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); + playlist.addToPlaylist(item); +} + +void MyJukeboxDialog::process(AmEvent* ev) +{ + DBG("AmSession::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); +} + +void MyJukeboxDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye: stopSession\n"); + setStopped(); +} + diff --git a/apps/examples/tutorial/myjukebox/MyJukebox.h b/apps/examples/tutorial/myjukebox/MyJukebox.h new file mode 100644 index 00000000..0c233e14 --- /dev/null +++ b/apps/examples/tutorial/myjukebox/MyJukebox.h @@ -0,0 +1,36 @@ +#ifndef _MYJUKEBOX_H_ +#define _MYJUKEBOX_H_ + +#include "AmSession.h" +#include "AmPlaylist.h" + +#include +using std::string; + +class MyJukeboxFactory: public AmSessionFactory +{ +public: + static string JukeboxDir; + + MyJukeboxFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); +}; + +class MyJukeboxDialog : public AmSession +{ + AmPlaylist playlist; + + public: + MyJukeboxDialog(); + ~MyJukeboxDialog(); + + void onSessionStart(const AmSipRequest& req); + void onDtmf(int event, int duration); + void process(AmEvent* ev); + void onBye(const AmSipRequest& req); +}; + +#endif + diff --git a/apps/examples/tutorial/myjukebox/etc/myjukebox.conf b/apps/examples/tutorial/myjukebox/etc/myjukebox.conf new file mode 100644 index 00000000..43e8a784 --- /dev/null +++ b/apps/examples/tutorial/myjukebox/etc/myjukebox.conf @@ -0,0 +1,3 @@ +#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE +jukebox_dir=/tmp/jukebox/ +#ENDCFGOPTION