mirror of https://github.com/sipwise/sems.git
git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@294 8eb893ce-cfd4-0310-b710-fb5ebe64c474sayer/1.4-spce2.6
parent
067c0571ca
commit
2ff1e991de
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
import compileall
|
||||
compileall.main()
|
||||
@ -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<string, unsigned int>::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<string, unsigned int>::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;
|
||||
}
|
||||
|
||||
@ -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<string, unsigned int> 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);
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = cc_acc
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1 @@
|
||||
announcement=/usr/local/lib/sems/audio/default.wav
|
||||
@ -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()
|
||||
@ -0,0 +1,2 @@
|
||||
import compileall
|
||||
compileall.main()
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = myannounceapp
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
#ifndef _MYANNOUNCEAPP_H_
|
||||
#define _MYANNOUNCEAPP_H_
|
||||
|
||||
#include "AmSession.h"
|
||||
#include "AmAudio.h"
|
||||
|
||||
#include <string>
|
||||
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
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
|
||||
MyAnnouceapp
|
||||
|
||||
This app plays a file back to the
|
||||
caller. The file is configured
|
||||
with the annoucement_file config
|
||||
option.
|
||||
@ -0,0 +1,3 @@
|
||||
#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE
|
||||
announcement_file=/tmp/test.wav
|
||||
#ENDCFGOPTION
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = myapp
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
|
||||
MyApp
|
||||
|
||||
This is an empty template for a SEMS 0.10.0
|
||||
application plug-in written in C++.
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = mycc
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -0,0 +1,252 @@
|
||||
|
||||
#include "MyCC.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "AmConfigReader.h"
|
||||
#include "AmUtils.h"
|
||||
#include "AmAudio.h"
|
||||
#include "AmPlugIn.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#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<AmAudioEvent*>(ev);
|
||||
if(audio_ev && (audio_ev->event_id == AmAudioEvent::noAudio)){
|
||||
DBG("MyCCDialog::process: Playlist is empty!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
#ifndef _MYJUKEBOX_H_
|
||||
#define _MYJUKEBOX_H_
|
||||
|
||||
#include "AmB2BSession.h"
|
||||
#include "AmPlaylist.h"
|
||||
|
||||
#include <string>
|
||||
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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = myconfigurableapp
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
#ifndef _MYCONFIGURABLEAPP_H_
|
||||
#define _MYCONFIGURABLEAPP_H_
|
||||
|
||||
#include "AmSession.h"
|
||||
|
||||
#include <string>
|
||||
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
|
||||
|
||||
@ -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.
|
||||
@ -0,0 +1,3 @@
|
||||
#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE
|
||||
announcement_file=/tmp/test.wav
|
||||
#ENDCFGOPTION
|
||||
@ -0,0 +1,7 @@
|
||||
plug_in_name = myjukebox
|
||||
|
||||
module_ldflags =
|
||||
module_cflags =
|
||||
|
||||
COREPATH ?=../../../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -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<AmAudioEvent*>(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();
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
#ifndef _MYJUKEBOX_H_
|
||||
#define _MYJUKEBOX_H_
|
||||
|
||||
#include "AmSession.h"
|
||||
#include "AmPlaylist.h"
|
||||
|
||||
#include <string>
|
||||
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
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
#CFGOPTION_SEMS_MYCONFIGURABLEAPP_ANNFILE
|
||||
jukebox_dir=/tmp/jukebox/
|
||||
#ENDCFGOPTION
|
||||
Loading…
Reference in new issue