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, its 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 with out 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,myemail@example.com,, [mycompany_agents] 6101 => 1234,Agent Name,agentemail@example.com,, [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); } } }^