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:
parent
827a44a029
commit
874ec1fe3b
|
@ -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
|
|
@ -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 =
|
||||
|
|
|
@ -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: */
|
|
@ -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: */
|
||||
|
|
|
@ -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@
|
||||
|
|
|
@ -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: */
|
Loading…
Reference in New Issue