Advanced Queues

In this section we’ll take a look at some of the finer-grained queue controls, such as options for controlling announcements and when callers should be placed into (or removed from) the queue. We’ll also look at penalties and priorities, exploring how we can control the agents in our queue by giving preference to a pool of agents to answer the call and increase that pool dynamically based on the wait times in the queue. Finally, we’ll look at using Local channels as queue members, which gives us the ability to perform dialplan functionality prior to connecting the caller to an agent.

Priority Queue (Queue Weighting)

Sometimes you need to add people to a queue at a higher priority than that given to other callers. Perhaps the caller has already spent time waiting in a queue, and an agent has taken some information but realized the caller needed to be transferred to another queue. In this case, to minimize the caller’s overall wait time, it might be desirable to transfer the call to a priority queue that has a higher weight (and thus a higher preference), so it will be answered quickly.

Setting a higher priority on a queue is done with the weight option. If you have two queues with differing weights (e.g., support and support-priority), agents assigned to both queues will be passed calls from the higher-priority queue in preference to calls from the lower-priority queue. Those agents will not take any calls from the lower-priority queue until the higher-priority queue is cleared. (Normally, there will be some agents who are assigned only to the lower-priority queue, to ensure that those calls are dealt with in a timely manner.) For example, if we place queue member James Shaw into both the support and support-priority queues, callers in the support-priority queue will have a preferred standing with James over callers in the support queue.

Let’s take a look at how we could make this work. First, we need to create two queues that are identical except for the weight option. We can use a template for this to ensure that the two queues remain identical if anything should need to change in the future:

[support_template](!)
musicclass=default
strategy=rrmemory
joinempty=no
leavewhenempty=yes
ringinuse=no

[support](support_template)
weight=0

[support-priority](support_template)
weight=10

With our queues configured (and subsequently reloaded using module reload app_queue.so from the Asterisk console), we can now create two extensions to transfer callers to. This can be done wherever you would normally place your dialplan logic to perform transfers. We’re going to use the LocalSets context, which we’ve previously enabled as the starting context for our devices:

[LocalSets]
include => Queue  ; allow direct transfer of calls to queues

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Queue(support)             ; standard support queue available
                                        ; at extension 7000
   same => n,VoiceMail(7000@queues,u)   ; if there are no members in the queue, 
                                        ; we exit and send the caller to voicemail
   same => n,Hangup()

exten => 8000,1,Verbose(2,Entering the priority support queue)
   same => n,Queue(support-priority)    ; priority queue available at 
                                        ; extension 8000
   same => n,VoiceMail(7000@queues,u)   ; if there are no members in the queue, 
                                        ; we exit and send the caller to voicemail
   same => n,Hangup()

There you have it: two queues defined with different weights. We’ve configured our standard queues to start at extension 7000, and our priority queues to start at 8000. We can mirror this for several queues by simply matching between the 7XXX and 8XXX ranges. So, for example, if we have our sales queue at extension 7004, our priority-sales queue (for returning customers, perhaps?) could be placed in the mirrored queue at 8004, which has a higher weight.

The only other configuration left to do is to make sure some or all of your queue members are placed in both queues. If you have more callers in your 7XXXX queues, you may want to have more queue members logged into that queue, with a percentage of your queue members logged into both queues. Exactly how you wish to configure your queues will depend on your local policy and circumstances.

Queue Member Priority

Within a queue, we can penalize members in order to lower their preference for being called when there are people waiting in a particular queue. For example, we may penalize queue members when we want them to be a member of a queue, but to be used only when the queue gets full enough that all our preferred agents are unavailable. This means we can have three queues (say, support, sales, and billing), each containing the same three queue members: James Shaw, Kay Madsen, and Danielle Roberts.

Suppose, however, that we want James Shaw to be the preferred contact in the support queue, Kay Madsen preferred in sales, and Danielle Roberts preferred in billing. By penalizing Kay Madsen and Danielle Roberts in support, we ensure that James Shaw will be the preferred queue member called. Similarly, we can penalize James Shaw and Danielle Roberts in the sales queue so Kay Madsen is preferred, and penalize James Shaw and Kay Madsen in the billing queue so Danielle Roberts is preferred.

Penalizing queue members can be done either in the queues.conf file, if you’re specifying queue members statically, or through the AddQueueMember() dialplan application. Let’s look at how our queues would be set up with static members in queues.conf. We’ll be using the StandardQueue template we defined earlier in this chapter:

[support](StandardQueue)
member => SIP/0000FFFF0001,0,James Shaw         ; preferred
member => SIP/0000FFFF0002,10,Kay Madsen        ; second preferred
member => SIP/0000FFFF0003,20,Danielle Roberts  ; least preferred

[sales](StandardQueue)
member => SIP/0000FFFF0002,0,Kay Madsen
member => SIP/0000FFFF0003,10,Danielle Roberts
member => SIP/0000FFFF0001,20,James Shaw

[billing](StandardQueue)
member => SIP/0000FFFF0003,0,Danielle Roberts
member => SIP/0000FFFF0001,10,James Shaw
member => SIP/0000FFFF0002,20,Kay Madsen

By defining different penalties for each member of the queue, we can help control the preference for where callers are delivered, but still ensure that other queue members will be available to answer calls if the preferred member is unavailable. Penalties can also be defined using AddQueueMember(), as the following example demonstrates:

exten => *54,1,Verbose(2,Logging In Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})

; *CLI> database put queue support/0000FFFF0001/penalty 0
   same => n,Set(QueuePenalty=${DB(queue/support/${CHANNEL(peername)}/penalty)})

; *CLI> database put queue support/0000FFFF0001/membername "James Shaw"
   same => n,Set(MemberName=${DB(queue/support/${CHANNEL(peername)}/membername)})

; AddQueueMember(queuename[,interface[,penalty[,options[,membername
; [,stateinterface]]]]])
   same => n,AddQueueMember(support,${MemberChannel},${QueuePenalty},,${MemberName})

Using AddQueueMember(), we’ve shown how you could retrieve the penalty associated with a given member name for a particular queue and assign that value to the member when she logs into the queue. Some additional abstraction would need to be done to make this work for multiple queues; for more information see the section called “Automatically Logging Into and Out of Multiple Queues”.

Changing Penalties Dynamically (queuerules.conf)

Using the queuerules.conf file, it is possible to specify rules to change the values of the QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY channel variables. The QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY channel variables are used to control which members of a queue are to be used for servicing callers. Let’s say we have a queue called support, and we have five queue members with various penalties ranging from 1 through 5. If prior to a caller entering the queue the QUEUE_MIN_PENALTY channel variable is set to a value of 2 and the QUEUE_MAX_PENALTY is set to a value of 4, only queue members whose penalties are set to values ranging from 2 through 4 will be considered available to answer that call:

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Set(QUEUE_MIN_PENALTY=2)   ; set minimum queue member penalty to be used
   same => n,Set(QUEUE_MAX_PENALTY=4)   ; set maximum queue member penalty we'll use
   same => n,Queue(support)             ; entering the queue with minimum and maximum 
                                        ; member penalties to be used

What’s more, during the caller’s stay in the queue, we can dynamically change the values of QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY for that caller. This allows either more or a different set of queue members to be used, depending on how long the caller waits in the queue. For instance, in the previous example, we could modify the minimum penalty to 1 and the maximum penalty to 5 if the caller has to wait more than 60 seconds in the queue.

The rules are defined using the queuerules.conf file. Multiple rules can be created in order to facilitate different penalty changes throughout the call. Let’s take a look at how we’d define the changes described in the previous paragraph:

[more_members]
penaltychange => 60,5,1

Note

If you make changes to the queuerules.conf file and reload app_queue.so, the new rules will affect only new callers in the queue, not existing callers.

We’ve defined the rule more_members in queuerules.conf and passed the following values to penaltychange: 60 is the number of seconds to wait before changing the penalty values, 5 is the new QUEUE_MAX_PENALTY, and 1 is the new QUEUE_MIN_PENALTY. With our new rule defined, we must reload app_queue.so to make it available to us for use:

*CLI> module reload app_queue.so
    -- Reloading module 'app_queue.so' (True Call Queueing)
  == Parsing '/etc/asterisk/queuerules.conf':   == Found

We can also verify our rules at the console with queue show rules:

*CLI> queue show rules
Rule: more_members
   After 60 seconds, adjust QUEUE_MAX_PENALTY to 5 and adjust QUEUE_MIN_PENALTY to 1

With our rule now loaded into memory, we can modify our dialplan to make use of it. Just modify the Queue() line to include the new rule, like so:

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Set(QUEUE_MIN_PENALTY=2)   ; set minimum queue member penalty
   same => n,Set(QUEUE_MAX_PENALTY=4)   ; set maximum queue member penalty

; Queue(queuename[,options[,URL[,announceoverride[,timeout[,AGI[,macro
; [,gosub[,rule[,position]]]]]]]]])
   same => n,Queue(support,,,,,,,,more_members)  ; entering queue with minimum and 
                                                 ; maximum member penalties

The queuerules.conf file is quite flexible. We can define our rule using relative instead of absolute penalty values, and we can define multiple rules:

[more_members]
penaltychange => 30,+1
penaltychange => 45,,-1
penaltychange => 60,+1
penaltychange => 120,+2

Here, we’ve modified our more_members rule to use relative values. After 30 seconds, we increase the maximum penalty by 1 (which would take us to 5 using our sample dialplan). After 45 seconds, we decrease the minimum penalty by 1, and so on. We can verify our new rule changes after a module reload app_queue.so at the Asterisk console:

*CLI> queue show rules
Rule: more_members
  After 30 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 45 seconds, adjust QUEUE_MAX_PENALTY by 0 and adjust QUEUE_MIN_PENALTY by -1
  After 60 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 120 seconds, adjust QUEUE_MAX_PENALTY by 2 and adjust QUEUE_MIN_PENALTY by 0

Announcement Control

Asterisk has the ability to play several announcements to callers waiting in the queue. For example, you might want to announce the caller’s position in the queue, the average wait time, or make periodic announcements thanking your callers for waiting (or whatever your audio files say). It’s important to tune the values that control when these announcements are played to the callers, because announcing their position, thanking them for waiting, and telling them the average hold time too often may annoy them, causing them to either hang up or take it out on your agents.

There are several options in the queues.conf file that you can use to fine-tune what and when announcements are played to your callers. The full list of queue options is available in the section called “The queues.conf File”, but we’ll review the relevant ones here.

Table 13.5, “Options related to prompt control timing within a queue” lists the options you can use to control when announcements are played to the caller.

Table 13.5. Options related to prompt control timing within a queue

OptionsAvailable valuesDescription
announce-frequencyValue in secondsDefines how often we should announce the caller’s position and/or estimated hold time in the queue. Set this value to zero to disable.
min-announce- frequencyValue in secondsIndicates the minimum amount of time that must pass before we announce the caller’s position in the queue again. This is used when the caller’s position may change frequently, to prevent the caller hearing multiple updates in a short period of time.
periodic - announce - frequencyValue in secondsSpecifies how often we should make periodic announcements to the caller.
random-periodic-announceyes, noIf set to yes, will play the defined periodic announcements in a random order. See periodic-announce.
relative-periodic-announceyes, noIf set to yes, the periodic-announce-frequency timer will start from when the end of the file being played back is reached, instead of from the beginning. Defaults to no.
announce-holdtimeyes, no, onceDefines whether the estimated hold time should be played along with the periodic announcements. Can be set to yes, no, or only once.
announce-positionyes, no, limit, moreDefines whether the caller’s position in the queue should be announced to her. If set to no, the position will never be announced. If set to yes, the caller’s position will always be announced. If the value is set to limit, the caller will hear her position in the queue only if it is within the limit defined by announce-position-limit. If the value is set to more, the caller will hear her position only if it is beyond the number defined by announce-position-limit.
announce-position-limitNumber of zero or greaterUsed if you’ve defined announce-position as either limit or more.
announce-round-secondsValue in secondsIf this value is nonzero, we’ll announce the number of seconds as well, and round them to the value defined.

Table 13.6, “Options for controlling the playback of prompts within a queue” shows what files will be used when announcements are played to the caller.

Table 13.6. Options for controlling the playback of prompts within a queue

OptionsAvailable valuesDescription
musicclassMusic class as defined by musiconhold.confSets the music class to be used by a particular queue. You can also override this value with the CHANNEL(musicclass) channel variable.
queue-thankyouFilename of prompt to playIf not defined, will play the default value (“Thank you for your patience”). If set to an empty value, the prompt will not be played at all.
queue-youarenextFilename of prompt to playIf not defined, will play the default value (“You are now first in line”). If set to an empty value, the prompt will not be played at all.
queue-thereareFilename of prompt to playIf not defined, will play the default value (“There are”). If set to an empty value, the prompt will not be played at all.
queue - calls waitingFilename of prompt to playIf not defined, will play the default value (“calls waiting”). If set to an empty value, the prompt will not be played at all.
queue-holdtimeFilename of prompt to playIf not defined, will play the default value (“The current estimated hold time is”). If set to an empty value, the prompt will not be played at all.
queue-minutesFilename of prompt to playIf not defined, will play the default value (“minutes”). If set to an empty value, the prompt will not be played at all.
queue-secondsFilename of prompt to playIf not defined, will play the default value (“seconds”). If set to an empty value, the prompt will not be played at all.
queue-reportholdFilename of prompt to playIf not defined, will play the default value (“Hold time”). If set to an empty value, the prompt will not be played at all.
periodic-announceA set of periodic announcements to be played, separated by commasPrompts are played in the order they are defined. Defaults to queue-periodic-announce (“All representatives are currently busy assisting other callers. Please wait for the next available representative”).

If the number of options devoted to playing announcements to callers is any indication of their importance, it’s probably in our best interest to use them to their fullest potential. The options in Table 13.5, “Options related to prompt control timing within a queue” help us define when we’ll play announcements to callers, and the options in Table 13.6, “Options for controlling the playback of prompts within a queue” help us control what we play to our callers. With those tables in hand, let’s take a look at an example queue where we’ve defined some values. We’ll use our basic queue template as a starting point:

[general]
autofill=yes             ; distribute all waiting callers to available members
shared_lastcall=yes      ; respect the wrapup time for members logged into more 
                         ; than one queue

[StandardQueue](!)       ; template to provide common features
musicclass=default       ; play [default] music
strategy=rrmemory        ; use the Round Robin Memory strategy
joinempty=yes            ; do not join the queue when no members available
leavewhenempty=no        ; leave the queue when no members available
ringinuse=no             ; don't ring members when already InUse (prevents 
                         ; multiple calls to an agent)

[sales](StandardQueue)   ; create the sales queue using the parameters in the
                         ; StandardQueue template

[support](StandardQueue) ; create the support queue using the parameters in the
                         ; StandardQueue template

We’ll now modify the StardardQueue template to control our announcements:

[StandardQueue](!)       ; template to provide common features
musicclass=default       ; play [default] music
strategy=rrmemory        ; use the Round Robin Memory strategy
joinempty=yes            ; do not join the queue when no members available
leavewhenempty=no        ; leave the queue when no members available
ringinuse=no             ; don't ring members when already InUse (prevents 
                         ; multiple calls to an agent)

; -------- Announcement Control --------
announce-frequency=30           ; announces caller's hold time and position every 30
                                ; seconds
min-announce-frequency=30       ; minimum amount of time that must pass before the
                                ; caller's position is announced 
periodic-announce-frequency=45  ; defines how often to play a periodic announcement to
                                ; caller
random-periodic-announce=no     ; defines whether to play periodic announcements in 
                                ; a random order, or serially
relative-periodic-announce=yes  ; defines whether the timer starts at the end of
                                ; file playback (yes) or the beginning (no)
announce-holdtime=once          ; defines whether the estimated hold time should be 
                                ; played along with the periodic announcement
announce-position=limit         ; defines if we should announce the caller's position
                                ; in the queue
announce-position-limit=10      ; defines the limit value where we announce the
                                ; caller's position (when announce-position is set to 
                                ; limit or more)
announce-round-seconds=30       ; rounds the hold time announcement to the nearest 
                                ; 30-second value

Let’s describe what we’ve just set in our StandardQueue template.

We’ll announce the caller’s hold time and position every 30 seconds (announce-frequency),[129] and make sure the minimum amount of time that passes before we announce it again is at least 30 seconds (min-announce-frequency). We do this to limit how often our announcements are played to the callers, in order to avoid the updates becoming annoying. Periodically, we’ll play an announcement to the callers that thanks them for holding and assures them that an agent will be with them shortly. (The announcement is defined by the periodic-announcement setting. We’re using the default announcement, but you can define one or more announcements yourself using periodic-announce.)

These periodic announcements will be played every 45 seconds (periodic-announce-frequency), in the order they were defined (random-period-announce). To determine when the periodic-announce-frequency timer should start, we use relative-periodic-announce. The yes setting means the timer will start after the announcement has finished playing, rather than when it starts to play. The problem you could run into if you set this to no is that if your periodic announcement runs for any significant length of time (lets say 30 seconds), it will appear as if it is being played every 15 seconds, rather than every 45 seconds as may be intended.

How many times we announce the hold time to the caller is controlled via the announce-holdtime option, which we’ve set to once. Setting the value to yes will announce it every time, and setting to no will disable it.

We configure how and when we announce the caller’s estimated remaining hold time via announce-position, which we’ve set to limit. Using the value of limit for announce-position lets us announce the caller’s position only if it is within the limit defined by announce-position-limit. So, in this case we’re only announcing the callers’ positions if they are in the first 10 positions of the queue. We could also use yes to announce the position every time the periodic announcement is played, set it to no to never announce it, or use the value more if we want to announce the position only when it is greater than the value set for announce-position-limit.

Our last option, announce-round-seconds, controls the value to round to when we announce the caller’s hold time. In this case, instead of saying “1 minute and 23 seconds,” the value would be rounded to the nearest 30-second value, which would result in a prompt of “1 minute and 30 seconds.”

Overflow

Overflowing out of the queue is done either with a timeout value, or when no queue members are available (as defined by joinempty or leavewhenempty). In this section we’ll discuss how to control when overflow happens.

Controlling timeouts

The Queue() application supports two kinds of timeout: one is for the maximum period of time a caller stays in the queue, and the other is how long to ring a device when attempting to connect a caller to a queue member. We’ll be talking about the maximum period of time a caller stays in the queue before the call overflows to another location, such as VoiceMail(). Once the call has fallen out of the queue, it can go anywhere that a call could normally go when controlled by the dialplan.

The timeouts are specified in two locations. The timeout that indicates how long to ring queue members for is specified in the queues.conf file. The absolute timeout (how long the caller stays in the queue) is controlled via the Queue() application. To set a maximum amount of time for callers to stay in a queue, simply specify it after the queue name in the Queue() application:

[Queues]
exten => 7000,1,Verbose(2,Joining the support queue for a maximum of 2 minutes)
   same => n,Queue(support,120)
   same => n,VoiceMail(support@queues,u)
   same => n,Hangup()

Of course, we could define a different destination, but the VoiceMail() application is as good as any. Just make sure that if you’re going to send callers to voicemail someone checks it regularly and calls your customers back.

Now let’s say we have the scenario where we have set our absolute timeout to 10 seconds, our timeout value for ringing queue members to 5 seconds, and our retry timeout value to 4 seconds. In this scenario, we would ring the queue member for 5 seconds, then wait 4 seconds before attempting another queue member. That brings us up to 9 seconds of our absolute timeout of 10 seconds. At this point, should we ring the second queue member for 1 second and then exit the queue, or should we ring this member for the full 5 seconds before exiting?

We control which timeout value has priority with the timeoutpriority option in queues.conf. The available values are app and conf. If we want the application timeout (the absolute timeout) to take priority, which would cause our caller to be kicked out after exactly 10 seconds, we should set the timeoutpriority value to app. If we want the configuration file timeout to take priority and finish ringing the queue member, which will cause the caller to stay in the queue a little longer, we should set timeoutpriority to conf. The default value is app (which is the default behavior in previous versions of Asterisk).

Controlling when to join and leave a queue

Asterisk provides two options that control when callers can join and are forced to leave queues, based on the statuses of the queue members. The first option, joinempty, is used to control whether callers can enter a queue. The leavewhenempty option is used to control when callers already in a queue should be removed from that queue (i.e., if all of the queue members become unavailable). Both options take a comma-separated list of values that control this behavior. The factors are listed in Table 13.7, “Options that can be set for joinempty or leavewhenempty”.

Table 13.7. Options that can be set for joinempty or leavewhenempty

ValueDescription
pausedMembers are considered unavailable if they are paused.
penaltyMembers are considered unavailable if their penalties are less than QUEUE_MAX_PENALTY.
inuseMembers are considered unavailable if their device status is In Use.
ringingMembers are considered unavailable if their device status is Ringing.
unavailableApplies primarily to agent channels; if the agent is not logged in but is a member of the queue it is considered unavailable.
invalidMembers are considered unavailable if their device status is Invalid. This is typically an error condition.
unknownMembers are considered unavailable if device status is unknown.
wrapupMembers are considered unavailable if they are currently in the wrapup time after the completion of a call.

For joinempty, prior to placing a caller into the queue, all the members are checked for availability using the factors you list as criteria. If all members are deemed to be unavailable, the caller will not be permitted to enter the queue, and dialplan execution will continue at the next priority.[130] For the leavewhempty option, the members’ statuses are checked periodically against the listed conditions; if it is determined that no members are available to take calls, the caller is removed from the queue, with dialplan execution continuing at the next priority.

An example use of joinempty could be:

joinempty=paused,inuse,invalid

With this configuration, prior to a caller entering the queue the statuses of all queue members will be checked, and the caller will not be permitted to enter the queue unless at least one queue member is found to have a status that is not paused, inuse, or invalid.

The leavewhenempty example could be something like:

leavewhenempty=inuse,ringing

In this case, the queue members’ statuses will be checked periodically, and callers will be removed from the queue if no queue members can be found who do not have a status of either inuse or ringing.

Previous versions of Asterisk used the values yes, no, strict, and loose as the available values to be assigned. The mapping of those values is shown in Table 13.8, “Mapping between old and new values for controlling when callers join and leave queues”.

Table 13.8. Mapping between old and new values for controlling when callers join and leave queues

ValueMapping (joinempty)Mapping (leavewhenempty)
yes(empty)penalty,paused,invalid
nopenalty,paused,invalid(empty)
strictpenalty,paused,invalid,unavailablepenalty,paused,invalid,unavailable
loosepenalty,invalidpenalty,invalid

Using Local Channels

The use of Local channels as queue members is a popular way of executing parts of the dialplan and performing checks prior to dialing the actual agent’s device. For example, it allows us to do things like start recording the call, set up channel variables, write to a log file, set a limit on the call length (e.g., if it is a paid service), or do any of the other things we might need to do once we know which location we’re going to call.

When using Local channels for queues, they are added just like any other channels. In the queues.conf file, adding a Local channel would look like this:

; queues.conf
[support](StandardQueue)
member => Local/SIP-0000FFFF0001@MemberConnector  ; pass the technology to dial over
                                                  ; and the device identifier,
                                                  ; separated by a hyphen. We'll
                                                  ; break it apart inside the 
                                                  ; MemberConnector context.

Tip

Notice how we passed the type of technology we want to call along with the device identifier to the MemberConnector context. We’ve simply used a hyphen (although we could have used nearly anything as a separator argument) as the field marker. We’ll use the CUT() function inside the MemberConnector context and assign the first field (SIP) to one channel variable and the second field (0000FFFF0001) to another channel variable, which will then be used to call the endpoint.

Passing information to be later “exploded” in the context used by the Local channel is a common and useful technique (kind of like the explode() function in PHP).

Of course, we’ll need the MemberConnector context to actually connect the caller to the agent:

[MemberConnector]
exten => _[A-Za-z0-9].,1,Verbose(2,Connecting ${CALLERID(all)} to Agent at ${EXTEN})

   ; filter out any bad characters, allowing alphanumeric characters and the hyphen
   same => n,Set(QueueMember=${FILTER(A-Za-z0-9\-,${EXTEN})

   ; assign the first field of QueueMember to Technology using the hyphen separator
   same => n,Set(Technology=${CUT(QueueMember,-,1)})

   ; assign the second field of QueueMember to Device using the hyphen separator
   same => n,Set(Device=${CUT(QueueMember,-,2)})

   ; dial the agent
   same => n,Dial(${Technology}/${Device})
   same => n,Hangup()

So, now we’ve passed our queue member to the context, and we can dial the device. However, because we’re using the Local channel as the queue member, the Queue() won’t necessarily know the state the call is in, especially when the Local channel is optimized out of the path (see https://wiki.asterisk.org/wiki/display/AST/Local+Channel+Modifiers for information about the /n modifier, which causes the Local channel to not be optimized out of the path). The queue will be monitoring the state of the Local channel, and not that of the device we really want to monitor.

Luckily, we can give the Queue() the actual device to monitor and associate that with the Local channel, so that the Local channel’s state is always that of the device we’ll end up calling. Our queue member would be modified in the queues.conf file like so:

; queues.conf
[support](StandardQueue)
member => Local/SIP-0000FFFF0001@MemberConnector,,,SIP/0000FFFF0001

Warning

Only SIP channels are capable of sending back reliable device state information, so it is highly recommended that you use only these channels when using Local channels as queue members.

You can also use the AddQueueMember() and RemoveQueueMember() applications to add members to and remove members from a queue, just like with any other channel. AddQueueMember() also has the ability to set the state interface, which we defined statically in the queues.conf file. An example of how you might do this follows:

[QueueMemberLogin]
exten => 500,1,Verbose(2,Logging in device ${CHANNEL(peername)} into the support queue)

   ; Save the device's technology to the MemberTech channel variable
   same => n,Set(MemberTech=${CHANNEL(channeltype)})
 
   ; Save the device's identifier to the MemberIdent channel variable
   same => n,Set(MemberIdent=${CHANNEL(peername)})

   ; Build up the interface name and assign it to the Interface channel variable
   same => n,Set(Interface=${MemberTech}/${MemberIdent})

   ; Add the member to the support queue using a Local channel. We're using the same
   ; format as before, separating the technology and the device indentifier with
   ; a hyphen and passing that information to the MemberConnector context. We then
   ; use the IF() function to determine if the member's technology is SIP and, if so,
   ; to pass back the contents of the Interface channel variable as the value to the
   ; state interface field of the AddQueueMember() application.
   ;
   ; *** This line should not have any line breaks
   same => n,AddQueueMember(support,Local/${MemberTech}-${MemberIdent}
@MemberConnector,,,${IF($[${MemberTech} = SIP]?${Interface})})
   same => n,Playback(silence/1)

   ; Play back either the agent-loginok or agent-incorrect file, depending on what
   ; the AQMSTATUS variable is set to.
   same => n,Playback(${IF($[${AQMSTATUS} = ADDED]?agent-loginok:agent-incorrect)})
   same => n,Hangup()

Now that we can add devices to the queue using Local channels, let’s look at how we might control the number of calls to either non-SIP channels or devices with more than one line on them. We can make use of the GROUP() and GROUP_COUNT() functions to track call counts to an endpoint. We’ll modify our MemberConnector context to take this into account:

[MemberConnector]
exten => _[A-Za-z0-9].,1,Verbose(2,Connecting ${CALLERID(all)} to Agent at ${EXTEN})

   ; filter out any bad characters, allowing alphanumeric characters and the hyphen
   same => n,Set(QueueMember=${FILTER(A-Za-z0-9\-,${EXTEN})

   ; assign the first field of QueueMember to Technology using the hyphen separator
   same => n,Set(Technology=${CUT(QueueMember,-,1)})

   ; assign the second field of QueueMember to Device using the hyphen separator
   same => n,Set(Device=${CUT(QueueMember,-,2)})

   ; Increase the value of the group inside the queue_members category by one
   same => n,Set(GROUP(queue_members)=${Technology}-${Device})

   ; Check if the group@category is greater than 1, and, if so, return Congestion()
   ; (too many channels)
   ;
   ; *** This line should not have any line breaks
   same => n,ExecIf($[${GROUP_COUNT(${Technology}-${Device}@queue_members)} > 1]
?Congestion())

   ; dial the agent
   same => n,Dial(${Technology}/${Device})
   same => n,Hangup()

The passing back of Congestion() will cause the caller to be returned to the queue (while this is happening, the caller gets no indication that anything is amiss and keeps hearing music until we actually connect to the device). While this is not an ideal situation because the queue will keep trying the member over and over again (or at least include it in the cycle of agents, depending on how many members you have and their current statuses), it is better than an agent getting multiple calls at the same time.

We’ve also used this same method to create a type of reservation process. If you want to call an agent directly (for example, if the caller needs to follow up with a particular agent), you could reserve that agent by using the GROUP() and GROUP_COUNT() functions to essentially pause the agent in the queue until the caller can be connected. This is particularly useful in situations where you need to play some announcements to the caller prior to connecting her with the agent, but you don’t want the agent to get connected to another caller while the announcements are being played.



[129] Callers’ positions and hold times are only announced if more than one person is holding in the queue.

[130] If the priority n+1 from where the Queue() application was called is not defined, the call will be hung up.