Added the CallAssist class and an example module pbxassist.

git-svn-id: http://voip.null.ro/svn/yate@829 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2006-06-01 20:29:14 +00:00
parent 827a44a029
commit 874ec1fe3b
6 changed files with 531 additions and 3 deletions

View File

@ -0,0 +1,52 @@
[general]
; Common settings
; enabled: bool: Enable the module
;enabled=yes
; priority: int: Priority to install message handlers into engine
;priority=15
; minlen: int: Minimum length of command sequences
;minlen=2
; maxlen: int: Maximum length of command sequences
;maxlen=20
; timeout: int: Inter-digit timeout in milliseconds
;timeout=30000
; retake: string: Exact sequence to exit DTMF pass-through mode
;retake=###
; Every other section defines an operation to trigger
;[operation]
; The name of the section is used as the default operation name
; trigger: regexp: Trigger key sequence
;trigger=
; operation: string: Operation name, will use section name if missing
;operation=
; message: string: Message to emit for this operation
;message=chan.operation
; remember: bool: Remember peer's ID just before performing the operation
;remember=yes
; All other parameters are copied in the generated message after having
; the \0 .. \9 placeholders replaced with matches from the trigger
; Example: enter DTMF pass-through mode with 3 successive asterisks
;[passthrough]
;trigger=\*\*\*
; Example: enter conference named conf/dyn-N with key sequence #N# where N=0..9
;[conference]
;trigger=#\([0-9]\)#$
;message=call.conference
;room=conf/dyn-\1

View File

@ -13,7 +13,7 @@ INCFILES := @top_srcdir@/yateclass.h @srcdir@/yatepbx.h
PROGS=
LIBS = libyatepbx.a
OBJS = multiroute.o
OBJS = multiroute.o assist.o
LOCALFLAGS =
LOCALLIBS =

141
contrib/ypbx/assist.cpp Normal file
View File

@ -0,0 +1,141 @@
/**
* assist.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Common C++ base classes for PBX related plugins
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatepbx.h>
using namespace TelEngine;
bool ChanAssistList::received(Message& msg, int id)
{
String* chanId = msg.getParam("id");
if (!chanId || chanId->null())
return (id < Private) && Module::received(msg,id);
lock();
RefPointer <ChanAssist> ca = find(*chanId);
unlock();
switch (id) {
case Startup:
if (ca) {
Debug(this,DebugMild,"Channel '%s' already assisted!",chanId->c_str());
return false;
}
ca = create(msg,*chanId);
if (ca) {
m_calls.append(ca);
// only notify the constructed and added to list assistant
ca->msgStartup(msg);
}
return false;
case Hangup:
if (ca) {
ca->msgHangup(msg);
// remove and deref
m_calls.remove(ca,true);
}
return false;
case Execute:
if (ca)
return false;
// for incoming channels the decision can be in routing
ca = create(msg,*chanId);
if (ca) {
m_calls.append(ca);
// only notify the constructed and added to list assistant
ca->msgStartup(msg);
}
return false;
case Disconnected:
return ca && ca->msgDisconnect(msg,msg.getValue("reason"));
default:
if (ca)
return received(msg,id,ca);
return (id < Private) && Module::received(msg,id);
}
}
void ChanAssistList::removeAssist(ChanAssist* assist)
{
lock();
m_calls.remove(assist,false);
unlock();
}
void ChanAssistList::initialize()
{
Module::initialize();
if (m_first) {
m_first = false;
init();
}
}
bool ChanAssistList::received(Message& msg, int id, ChanAssist* assist)
{
return false;
}
void ChanAssistList::init(int priority)
{
installRelay(Execute,priority);
Engine::install(new MessageRelay("chan.startup",this,Startup,priority));
Engine::install(new MessageRelay("chan.hangup",this,Hangup,priority));
Engine::install(new MessageRelay("chan.disconnected",this,Disconnected,priority));
}
ChanAssist::~ChanAssist()
{
Debug(list(),DebugAll,"Assistant for '%s' deleted",id().c_str());
// remove form the list if we happen to be still there
if (list())
list()->removeAssist(this);
}
void ChanAssist::msgStartup(Message& msg)
{
Debug(list(),DebugInfo,"Assistant for '%s' startup",id().c_str());
}
void ChanAssist::msgHangup(Message& msg)
{
Debug(list(),DebugInfo,"Assistant for '%s' hangup",id().c_str());
}
bool ChanAssist::msgDisconnect(Message& msg, const String& reason)
{
Debug(list(),DebugInfo,"Assistant for '%s' disconnected, reason '%s'",
id().c_str(),reason.c_str());
return false;
}
RefPointer<CallEndpoint> ChanAssist::locate(const String& id)
{
if (id.null())
return 0;
Message m("chan.locate");
m.addParam("id",id);
return static_cast<CallEndpoint*>(Engine::dispatch(m) ? m.userData() : 0);
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -126,6 +126,61 @@ private:
MessageRelay* m_relDisconnected;
};
}
class ChanAssistList;
class YPBX_API ChanAssist : public RefObject
{
public:
virtual ~ChanAssist();
virtual const String& toString() const
{ return m_chanId; }
virtual void msgStartup(Message& msg);
virtual void msgHangup(Message& msg);
virtual bool msgDisconnect(Message& msg, const String& reason);
inline ChanAssistList* list() const
{ return m_list; }
inline const String& id() const
{ return m_chanId; }
static RefPointer<CallEndpoint> locate(const String& id);
inline RefPointer<CallEndpoint> locate() const
{ return locate(m_chanId); }
protected:
inline ChanAssist(ChanAssistList* list, const String& id)
: m_list(list), m_chanId(id)
{ }
private:
ChanAssistList* m_list;
String m_chanId;
};
class YPBX_API ChanAssistList : public Module
{
friend class ChanAssist;
public:
enum {
Startup = Private,
Hangup,
Disconnected,
AssistPrivate
};
virtual ~ChanAssistList()
{ }
virtual bool received(Message& msg, int id);
virtual bool received(Message& msg, int id, ChanAssist* assist);
virtual void initialize();
virtual ChanAssist* create(Message& msg, const String& id)=0;
virtual void init(int priority = 15);
inline ChanAssist* find(const String& id) const
{ return static_cast<ChanAssist*>(m_calls[id]); }
protected:
inline ChanAssistList(const char* name)
: Module(name, "misc"), m_first(true)
{ }
void removeAssist(ChanAssist* assist);
private:
HashList m_calls;
bool m_first;
};
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -24,7 +24,7 @@ PROGS := cdrbuild.yate cdrfile.yate \
regexroute.yate regfile.yate accfile.yate register.yate \
tonegen.yate wavefile.yate conference.yate moh.yate \
callgen.yate analyzer.yate rmanager.yate msgsniff.yate \
pbx.yate dbpbx.yate dumbchan.yate callfork.yate \
pbx.yate dbpbx.yate pbxassist.yate dumbchan.yate callfork.yate \
extmodule.yate yradius.yate \
ysipchan.yate yrtpchan.yate
LIBS :=
@ -190,6 +190,10 @@ dbpbx.yate: ../contrib/ypbx/libyatepbx.a
dbpbx.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ypbx
dbpbx.yate: LOCALLIBS = ../contrib/ypbx/libyatepbx.a
pbxassist.yate: ../contrib/ypbx/libyatepbx.a
pbxassist.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ypbx
pbxassist.yate: LOCALLIBS = ../contrib/ypbx/libyatepbx.a
ilbccodec.yate: ../contrib/ilbc/libilbc.a
ilbccodec.yate: LOCALLIBS = ../contrib/ilbc/libilbc.a
ilbccodec.yate: LOCALFLAGS = @ILBC_INC@

276
modules/pbxassist.cpp Normal file
View File

@ -0,0 +1,276 @@
/**
* pbxassist.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* PBX assist module
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatepbx.h>
using namespace TelEngine;
namespace { //anonymous
class YPBX_API PBXAssist : public ChanAssist
{
public:
inline PBXAssist(ChanAssistList* list, const String& id)
: ChanAssist(list,id), m_last(0), m_pass(false)
{ }
virtual void msgHangup(Message& msg);
virtual bool msgDisconnect(Message& msg, const String& reason);
virtual bool msgTone(Message& msg);
virtual bool msgOperation(Message& msg, const String& operation);
protected:
bool rememberPeer(const String& peer);
u_int64_t m_last;
bool m_pass;
String m_tones;
String m_peer1;
String m_peer2;
};
class YPBX_API PBXList : public ChanAssistList
{
public:
enum {
Operation = AssistPrivate
};
inline PBXList()
: ChanAssistList("pbxassist")
{ }
virtual ChanAssist* create(Message& msg, const String& id);
virtual void initialize();
virtual void init(int priority);
virtual bool received(Message& msg, int id, ChanAssist* assist);
};
// Inter-tone timeout in usec
static unsigned int s_timeout = 30000000;
// Minimum sequence length
static int s_minlen = 2;
// Maximum sequence length
static int s_maxlen = 20;
// Take back control command
static String s_retake;
// On Hold (music)
static String s_onhold;
// The entire module configuration
static Configuration s_cfg;
ChanAssist* PBXList::create(Message& msg, const String& id)
{
if (msg.userObject("Channel"))
return new PBXAssist(this,id);
return 0;
}
void PBXList::init(int priority)
{
priority = s_cfg.getIntValue("general","priority",priority);
ChanAssistList::init(priority);
installRelay(Tone,priority);
Engine::install(new MessageRelay("chan.operation",this,Operation,priority));
}
void PBXList::initialize()
{
lock();
s_cfg = Engine::configFile(name());
s_cfg.load();
s_minlen = s_cfg.getIntValue("general","minlen",2);
if (s_minlen < 1)
s_minlen = 1;
s_maxlen = s_cfg.getIntValue("general","maxlen",20);
if (s_maxlen < s_minlen)
s_maxlen = s_minlen;
int tout = s_cfg.getIntValue("general","timeout",30000);
if (tout < 1000)
tout = 1000;
if (tout > 1800000)
tout = 1800000;
s_timeout = (unsigned int)tout * 1000;
s_retake = s_cfg.getValue("general","retake","###");
s_onhold = s_cfg.getValue("general","onhold","moh/default");
unlock();
if (s_cfg.getBoolValue("general","enabled",true))
ChanAssistList::initialize();
}
bool PBXList::received(Message& msg, int id, ChanAssist* assist)
{
switch (id) {
case Tone:
return static_cast<PBXAssist*>(assist)->msgTone(msg);
case Operation:
return static_cast<PBXAssist*>(assist)->msgOperation(msg,msg.getValue("operation"));
default:
return false;
}
}
bool PBXAssist::msgDisconnect(Message& msg, const String& reason)
{
if ((reason == "hold") || (reason == "park")) {
if (s_onhold) {
msg = "call.execute";
msg.setParam("callto",s_onhold);
}
return false;
}
return ChanAssist::msgDisconnect(msg,reason);
}
bool PBXAssist::msgTone(Message& msg)
{
const char* tone = msg.getValue("text");
if (null(tone))
return false;
if (m_last && m_tones && ((m_last + s_timeout) < msg.msgTime())) {
Debug(list(),DebugNote,"Chan '%s' collect timeout, clearing tones '%s'",id().c_str(),m_tones.c_str());
m_tones.clear();
}
m_last = msg.msgTime();
m_tones += tone;
// truncate collected number to some decent length
if ((int)m_tones.length() > s_maxlen)
m_tones = m_tones.substr(-s_maxlen);
Debug(list(),DebugCall,"Chan '%s' got tone '%s' collected '%s'",id().c_str(),tone,m_tones.c_str());
if ((int)m_tones.length() < s_minlen)
return false;
if (m_pass) {
// we are in pass-through mode, only look for takeback comand
if (m_tones.endsWith(s_retake)) {
Debug(list(),DebugCall,"Chan '%s' back in tone collect mode",id().c_str());
m_pass = false;
m_tones.clear();
return true;
}
return false;
}
Lock lock(list());
int n = s_cfg.sections();
for (int i = 0; i < n; i++) {
NamedList* sect = s_cfg.getSection(i);
if (!sect)
continue;
const char* tmp = sect->getValue("trigger");
if (!tmp)
continue;
Regexp r(tmp);
if (!m_tones.matches(r))
continue;
// good! we matched the trigger sequence
tmp = sect->getValue("operation",*sect);
if (tmp) {
Debug(list(),DebugInfo,"Chan '%s' triggered operation '%s'",id().c_str(),tmp);
if (sect->getBoolValue("remember",true))
rememberPeer(msg.getValue("peerid"));
// now masquerade the message
Message* m = new Message("chan.masquerade");
m->addParam("id",id());
m->addParam("message",sect->getValue("message","chan.operation"));
m->addParam("operation",tmp);
unsigned int len = sect->length();
for (unsigned int idx = 0; idx < len; idx++) {
const NamedString* s = sect->getParam(idx);
if ((s->name() == "trigger") ||
(s->name() == "operation") ||
(s->name() == "remember") ||
(s->name() == "message"))
continue;
m->addParam(s->name(),m_tones.replaceMatches(*s));
}
Engine::enqueue(m);
}
m_tones.clear();
return true;
}
return false;
}
bool PBXAssist::msgOperation(Message& msg, const String& operation)
{
if (operation == "passthrough") {
if (s_retake.null()) {
Debug(list(),DebugWarn,"Chan '%s' refusing pass-through, retake string is not set!",id().c_str());
return true;
}
Debug(list(),DebugCall,"Chan '%s' entering tone pass-through mode",id().c_str());
m_pass = true;
m_tones.clear();
return true;
}
else if (operation == "conference") {
RefPointer<CallEndpoint> c = locate();
if (!c)
return false;
String peer = c->getPeerId();
rememberPeer(peer);
Message* m = new Message("call.conference");
m->addParam("id",id());
m->addParam("callto",s_onhold);
Engine::enqueue(m);
return true;
}
return false;
}
void PBXAssist::msgHangup(Message& msg)
{
RefPointer<CallEndpoint> c1 = locate(m_peer1);
if (c1) {
RefPointer<CallEndpoint> c2 = locate(m_peer2);
if (c2) {
// we hung up having two peers on hold - join them
Debug(list(),DebugCall,"Chan '%s' doing autotransfer '%s' <-> '%s'",
id().c_str(),m_peer1.c_str(),m_peer2.c_str());
c1->connect(c2);
}
}
}
bool PBXAssist::rememberPeer(const String& peer)
{
if (peer.null() || (peer == id()))
return false;
if (peer == m_peer1)
return true;
if (m_peer1.null()) {
m_peer1 = peer;
return true;
}
if (peer == m_peer2)
return true;
if (m_peer2.null()) {
m_peer2 = peer;
return true;
}
Debug(list(),DebugMild,"Channel '%s' can not remember '%s', both slots full",
id().c_str(),peer.c_str());
return false;
}
INIT_PLUGIN(PBXList);
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */