This currently has support for
Hot Desking
Agent Voicemail
Dynamic Agent Voicemail Mapping to Phone for MWI support
Agents stay logged in through asterisk reload or restart
Here is what I’ve come up with and is working for me. I’m sure there are improvements that can/will be made and I hope to update this as I make them.
I plan to add realtime and make this post more pretty. But for now, it’s here for what it is worth.
I got started with comments in the script but it was taking too long. Maybe later.
I’ve attached this whole post as a file because of ael formatting. So open the attached file for easier reading.
I started from the example given in the doc dir of the source code and worked from there. It didn’t support hot desking.
I used parts fro the other agents without channels wiki pages.
A problem I found is that regular expression extraction using Set(from_channel=$[ “${CHANNEL}” : “+(.+)\[-\]\[+-\]+$” ]) is broken if done in AEL.
So I used the parser to generate the flat extensions.conf.aeldump file using aelparse -w -d -q.
Note that I put the extensions.ael in /etc/asterisk/ael/ so I can cd to that dir, and run aelparse -w -d -q after each change to the ael file.
I then #included the dump file in extensions.conf
Important parts from extension.conf
[globals]
COMPANY_CONTEXT=mycompany ; !!! This value needs to be manually changed/set in sip.conf for mailboxes, voicemail.conf the context for mailboxes, and switch => Realtime statements in this file. Grep in other configs for the old value!
queue_by_exten_6010=Queue_ONE
queue_by_exten_6020=Queue_TWO
Queue_ONE_exten=6010
Queue_TWO_exten=6020
DOLLAR=$
#include ael/extensions.conf.aeldump
[internal_devices] ; non agent phones enter here
include => internal
include => queue_dial_plan ; _60XX & _!Queue extensions for queues, _6XXX for agents
include => match_outbound
[macro-edit_audio_file]
exten => s,1,Set(File_Record=$ARG1)
exten => s,n,Background(vm-intro)
exten => s,n,Record(${File_Record}_tmp.ulaw)
exten => s,n,Background(recorded)
exten => s,n,Background(${File_Record}_tmp)
exten => s,n,Background(save-announce-press)
exten => s,n,Background(digits/1)
exten => s,n,Background(to-rerecord-announce)
exten => s,n,Background(digits/2)
exten => s,n,Background(press-3)
exten => s,n,Background(to-cancel-this-msg)
exten => s,n,WaitExten(10,m)
exten => s,n,Goto(3,1)
exten => 1,1,System(/bin/rm /var/lib/asterisk/sounds/${File_Record}.*)
exten => 1,n,System(/bin/mv /var/lib/asterisk/sounds/${File_Record}_tmp.ulaw /var/lib/asterisk/sounds/${File_Record}.ulaw)
exten => 1,n,Return()
exten => 2,1,Goto(s,1)
exten => 3,1,System(/bin/rm /var/lib/asterisk/sounds/${File_Record}_tmp.ulaw)
exten => 3,n,Return()
exten => h,1,System(/bin/rm /var/lib/asterisk/sounds/${File_Record}_tmp.ulaw)
sip.conf example entries
[AP_1001]
type=friend
host=dynamic
secret=1234
context=agent_devices
mailbox=1001@mycompany_agent_phones
callerid=Agent Phone <1001>
[dsk_5555]
type=friend
host=dynamic
secret=1234
context=internal_devices
mailbox=5555@mycompany
callerid=My Desk <5555>
voicemail.conf example parts
[mycompany] ; this should match the global var COMPANY_CONTEXT is set to in extensions
; put all users voicemail defs in this context.
5555 => 1234,My Name,[email protected],,
[mycompany_agents]
6101 => 1234,Agent Name,[email protected],,
[mycompany_agent_phones]
; these are just placeholders for mapping agent voicemails to phones. /var/spool/asterisk/voicemail/mycompany_agent_phones must be created manually
1001 => 892143432423
ael/extensions.ael
//============= The Contexts referenced from the queues.conf file
// context name same as queue name defined in queues.conf
context Queue_one {
// while in the queue, a customer can press 9 to leave a VM for the Queue mailbo
9 => Voicemail(${EVAL(${DOLLAR}{${CONTEXT}_exten})}@${COMPANY_CONTEXT}_agents,b) ;
99999 => {
//(re)record queue greeting
&edit_audio_file(custom/${CONTEXT}_greeting) ;
goto s,1 ;
}
s => {
Background(custom/${CONTEXT}_greeting);
WaitExten(2);
// check if a queue name has already been prefixed on the caller id name,
// assumes there is never a : in original CID name
Set(orig_cid_name=${CUT(CALLERID(name),:,2)}) ;
if ( "x${orig_cid_name}" = "x" )
{
// use ${CALLERID(name) since ${orig_cid_name} is empty
Set(CALLERID(name)=${CONTEXT:7}:${CALLERID(name):5});
} else {
// use ${orig_cid_name} since it already has the orig CID name.
Set(CALLERID(name)=${CONTEXT:7}:${orig_cid_name});
}
// if you want to do Queue escalation, set QUEUE_MAX_PENALTY
// Set(QUEUE_MAX_PENALTY=10)
// enter queue allowing the destination agent to transfer the call.
//NOTE: I don't think the "t" this has any effect since the agent is
// dialed through Local then Dial command witch will determine transfer capabilities.
// not to mention the phones built in capabilities to transfer.
Queue(${CONTEXT}|t);
// Queue escalation example continued
// Set(QUEUE_MAX_PENALTY=20)
// Queue(${CONTEXT}|t);
// Set so all agents are tried
// Set(QUEUE_MAX_PENALTY=0)
// Queue(${CONTEXT}|t);
// not sure what effect this has since the call is about to be hung up,
// other than to see it in the logs. maybe the cdr?..
Set(CALLERID(name)=Empty_${CONTEXT});
}
}
context Queue_two {
9 => Voicemail(${EVAL(${DOLLAR}{${CONTEXT}_exten})}@${COMPANY_CONTEXT}_agents,b) ;
99999 => {
&edit_audio_file(custom/${CONTEXT}_greeting) ;
goto s,1 ;
}
s => {
Background(custom/Queue_two_greeting);
WaitExten(2);
Set(orig_cid_name=${CUT(CALLERID(name),:,2)}) ;
if ( "x${orig_cid_name}" = "x" )
{
Set(CALLERID(name)=:${CALLERID(name):5});
} else {
Set(CALLERID(name)=two:${orig_cid_name});
}
Queue(${CONTEXT}|t);
Set(CALLERID(name)=Empty_${CONTEXT});
}
}
//================= Agents Making calls
context agent_devices
{
_!. => {
Set(from_channel=$[ "${CHANNEL}" : "+(.+)\[-\]\[+-\]+$" ]);
if( ${DB_EXISTS(AGENT/channelHasAgent/${from_channel})} )
Set(from_agent=${DB(AGENT/channelHasAgent/${from_channel})});
else
goto queues-loginout,${EXTEN},1 ;
Set(GROUP(agents)=${from_agent});
NoOp(mygroupcount: ${GROUP_COUNT(${from_agent}@agents)}) ;
Set(CALLERID(num)=${from_agent});
Set(CALLERID(name)=${DB(AGENT/${MACRO_EXTEN}/name)});
if ( ${LEN(${CALLERID(name)})} = 0 )
Set(CALLERID(name)=Call Center Agent) ;
goto agent_call_plan,${EXTEN},1 ;
}
}
context agent_call_plan
{
includes {
queues-loginout;
queue_dial_plan;
internal;
}
}
//================= Agents Log In and Out
context queues-loginout
{
*98 => {
Answer();
Set(TIMEOUT(response)=3);
if ( "x${from_agent}" != "x" )
{
//Playback(ask_logout_agent) ;
SayDigits(${from_agent}) ;
//Read(logout) ;
Set(queue-announce-success=1);
goto queues-manip,O${from_agent},1 ;
} else {
Read(from_agent,jedi-extension-trick);
VMAuthenticate(${from_agent}@${COMPANY_CONTEXT}_agents,s);
Set(queue-announce-success=1);
goto queues-manip,I${from_agent},1;
}
}
*95 => { // Pause
if ( "x${from_agent}" = "x" )
goto *98,1 ;
Answer();
Wait(1);
&queue-addremove(P,${from_agent},"","");
}
*96 => { // UnPause
if ( "x${from_agent}" = "x" )
goto *98,1 ;
Answer();
Wait(1);
&queue-addremove(U,${from_agent},,);
}
*97 => { // check voicemail
if ( "x${from_agent}" = "x" )
goto *98,1 ;
Answer();
Wait(1);
VoicemailMain(${from_agent}@${COMPANY_CONTEXT}_agents,s);
}
i => {
if ( "x${from_agent}" = "x" )
goto *98,1 ;
else
Playback(pbx-invalid) ;
}
}
context queues-manip {
// My Name
_[IO]6101 => &queue-addremove(${EXTEN:0:1},${EXTEN:1},My Caller ID name,Queue_one+10:Queue_two+10);
}
macro queue-success(IO_action)
{
if( ${queue-announce-success} > 0 )
{
switch(${IO_action})
{
case I:
Playback(agent-loginok);
Hangup();
break;
case O:
Playback(agent-loggedoff);
Hangup();
break;
}
}
}
//The queue-addremove macro is defined in this manner:
macro queue-addremove(action,agent,agent_name,queues)
{
switch(${action})
{
pattern [IO]: // Login
if ( "${action}" = "I" )
{
Set(DB(AGENT/channelHasAgent/${from_channel})=${agent}) ;
Set(DB(AGENT/${agent}/onChannel)=${from_channel}) ;
Set(DB(AGENT/${agent}/name)=${agent_name}) ;
Set(dev_mailbox=${SIPPEER($[ "${CHANNEL}" : "\[+/\]+/(.+)\[-\]\[+-\]+" ]:mailbox)}) ;
System(/bin/ln -s /var/spool/asterisk/voicemail/${COMPANY_CONTEXT}_agents/${agent} /var/spool/asterisk/voicemail/${CUT(dev_mailbox,@,2)}/${CUT(dev_mailbox,@,1)}) ;
} else {
Set(agentchan=${DB(AGENT/${agent}/onChannel)});
Set(oldval=${DB_DELETE(AGENT/channelHasAgent/${agentchan})});
DBdeltree(AGENT/${from_agent});
Set(dev_mailbox=${SIPPEER($[ "${agentchan}" : "\[+/\]+/(.+)" ]:mailbox)}) ;
System(/bin/rm /var/spool/asterisk/voicemail/${CUT(dev_mailbox,@,2)}/${CUT(dev_mailbox,@,1)}) ;
}
Set(i=1) ;
while ( 1 ) {
Set(queue=${CUT(queues,:,${i})}) ;
Set(queuename=${CUT(queue,+,1)}) ;
Set(penalty=${CUT(queue,+,2)}) ;
Set(penalty=$[ ${LEN(${penalty})} = 0 ]:0:${penalty}) ;
if ( ${LEN(${queuename})} = 0 )
break;
if ( "${action}" = "I" )
AddQueueMember(${queuename},Local/${agent}@queue_calling_agents,${penalty});
else
RemoveQueueMember(${queuename},Local/${agent}@queue_calling_agents);
Set(i=$[ ${i} + 1 ]);
}
&queue-success(${action});
break;
case P: // Pause
PauseQueueMember(${queuename},Local/${agent}@queue_calling_agents);
break;
case U: // Unpause
UnpauseQueueMember(${queuename},Local/${agent}@queue_calling_agents);
break;
default: // Invalid
Playback(invalid);
break;
}
}
//=======================================================
//| Controlling The Way Queues Call the Agents |
//=======================================================
context queue_calling_agents
{
_! => &callagent(no_vm);
}
context queue_dial_plan
{
// Goto queue by extension number
_60XX => Goto(${EVAL(${DOLLAR}{queue_by_exten_${EXTEN}})},s,1) ;
6010 => goto Queue_one,1 ;
_Queue_. => goto ${EXTEN},s,1 ;
_6XXX => &callagent(w_vm);
}
macro callagent(use_vm)
{
Set(device=${DB(AGENT/${MACRO_EXTEN}/onChannel)}) ;
if ( "${use_vm}" = "no_vm")
{
NoOp(GC agnt:${GROUP_COUNT(${MACRO_EXTEN}@agents)} - ${GROUP_COUNT(${MACRO_EXTEN}@agents_vmail)}) ;
if ( ${GROUP_COUNT(${MACRO_EXTEN}@agents)} - ${GROUP_COUNT(${MACRO_EXTEN}@agents_vmail)} = 0 )
{
Set(GROUP(agents)=${MACRO_EXTEN});
Dial(${device}|300|t);
switch(${DIALSTATUS})
{
case BUSY:
Busy();
break;
case NOANSWER:
Set(queue-announce-success=0);
goto queues-manip|O${MACRO_EXTEN}|1;
default:
Hangup();
break;
}
}
else
{
Busy();
}
} else {
NoOp(${GROUP_COUNT(${MACRO_EXTEN}@agents)} - ${GROUP_COUNT(${MACRO_EXTEN}@agents_vmail)}) ;
if ( ${GROUP_COUNT(${MACRO_EXTEN}@agents)} - ${GROUP_COUNT(${MACRO_EXTEN}@agents_vmail)} = 0 )
{
NoOp(device (${device}) has / ?: $[ "x${device}" != "x" ]) ;
if ( "x${device}" != "x" )
{
Set(GROUP(agents)=${MACRO_EXTEN});
Dial(${device}|20|t);
NoOp(Dialstatus:${DIALSTATUS}) ;
Set(GROUP(agents_vmail)=${MACRO_EXTEN}) ;
if ( "${DIALSTATUS}" == "BUSY" )
{
Voicemail(${MACRO_EXTEN}@${COMPANY_CONTEXT}_agents,b);
} else {
Voicemail(${MACRO_EXTEN}@${COMPANY_CONTEXT}_agents,u);
}
} else {
Voicemail(${MACRO_EXTEN}@${COMPANY_CONTEXT}_agents,u);
}
} else {
Voicemail(${MACRO_EXTEN}@${COMPANY_CONTEXT}_agents,b);
}
}
}