additional checking related to issue 17186
[asterisk/asterisk.git] / doc / building_queues.txt
1 =================
2  Building Queues
3 =================
4
5 Written by: Leif Madsen
6 Initial version: 2010-01-14
7
8 In this article, we'll look at setting up a pair of queues in Asterisk called
9 'sales' and 'support'. These queues can be logged into by queue members, and
10 those members will also have the ability to pause and unpause themselves.
11
12 All configuration will be done in flat files on the system in order to maintain
13 simplicity in configuration.
14
15 Note that this documentation is based on Asterisk 1.6.2, and this is just one
16 approach to creating queues and the dialplan logic. You may create a better way,
17 and in that case, I would encourage you to submit it to the Asterisk issue
18 tracker at http://issues.asterisk.org for inclusion in Asterisk.
19
20 -------------------------------------
21 | Adding SIP Devices to Your Server |
22 -------------------------------------
23
24 The first thing we want to do is register a couple of SIP devices to our server.
25 These devices will be our agents that can login and out of the queues we'll
26 create later. Our naming convention will be to use MAC addresses as we want to
27 abstract the concepts of user (agent), device, and extension from each other.
28
29 In sip.conf, we add the following to the bottom of our file:
30
31 sip.conf
32 --------
33
34 [std-device](!)
35 type=peer
36 context=devices
37 host=dynamic
38 secret=s3CuR#p@s5
39 dtmfmode=rfc2833
40 disallow=all
41 allow=ulaw
42
43 [0004f2040001](std-device)
44
45 [0004f2040002](std-device)
46
47
48
49 What we're doing here is creating a [std-device] template and applying it to
50 a pair of peers that we'll register as 0004f2040001 and 0004f2040002; our
51 devices.
52
53 Then our devices can register to Asterisk. In my case I have a hard phone and
54 a soft phone registered. I can verify their connectivity by running 'sip show
55 peers'.
56
57 *CLI> sip show peers
58 Name/username              Host            Dyn Nat ACL Port     Status     
59 0004f2040001/0004f2040001  192.168.128.145  D          5060     Unmonitored 
60 0004f2040002/0004f2040002  192.168.128.126  D          5060     Unmonitored 
61 2 sip peers [Monitored: 0 online, 0 offline Unmonitored: 2 online, 0 offline]
62
63
64
65 ----------------------------
66 | Configuring Device State |
67 ----------------------------
68
69 Next, we need to configure our system to track the state of the devices. We do
70 this by defining a 'hint' in the dialplan which creates the ability for a device
71 subscription to be retained in memory. By default we can see there are no hints
72 registered in our system by running the 'core show hints' command.
73
74 *CLI> core show hints
75 There are no registered dialplan hint
76
77
78 We need to add the devices we're going to track to the extensions.conf file
79 under the [default] context which is the default configuration in sip.conf,
80 however we can change this to any context we want with the 'subscribecontext'
81 option.
82
83 Add the following lines to extensions.conf:
84
85 [default]
86 exten => 0004f2040001,hint,SIP/0004f2040001
87 exten => 0004f2040002,hint,SIP/0004f2040002
88
89 Then perform a 'dialplan reload' in order to reload the dialplan.
90
91 After reloading our dialplan, you can see the status of the devices with 'core
92 show hints' again.
93
94
95 *CLI> core show hints
96
97     -= Registered Asterisk Dial Plan Hints =-
98            0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
99            0004f2040001@default             : SIP/0004f2040001      State:Idle            Watchers  0
100 ----------------
101 - 2 hints registered
102
103
104 At this point, create an extension that you can dial that will play a prompt
105 that is long enough for you to go back to the Asterisk console to check the
106 state of your device while it is in use.
107
108 To do this, add the 555 extension to the [devices] context and make it playback
109 the tt-monkeys file.
110
111
112 extensions.conf
113 ---------------
114
115 [devices]
116 exten => 555,1,Playback(tt-monkeys)
117
118
119 Dial that extension and then check the state of your device on the console.
120
121 *CLI>   == Using SIP RTP CoS mark 5
122     -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000001", "tt-monkeys") in new stack
123     -- <SIP/0004f2040001-00000001> Playing 'tt-monkeys.slin' (language 'en')
124
125 *CLI> core show hints
126
127     -= Registered Asterisk Dial Plan Hints =-
128            0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
129            0004f2040001@default             : SIP/0004f2040001      State:Idle            Watchers  0
130 ----------------
131 - 2 hints registered
132
133 Aha, we're not getting the device state correctly. There must be something else
134 we need to configure.
135
136 In sip.conf, we need to enable 'callcounter' in order to activate the ability
137 for Asterisk to monitor whether the device is in use or not. In versions prior
138 to 1.6.0 we needed to use 'call-limit' for this functionality, but call-limit
139 is now deprecated and is no longer necessary.
140
141 So, in sip.conf, in our [std-device] template, we need to add the callcounter
142 option.
143
144 sip.conf
145 --------
146
147 [std-device](!)
148 type=peer
149 context=devices
150 host=dynamic
151 secret=s3CuR#p@s5
152 dtmfmode=rfc2833
153 disallow=all
154 allow=ulaw
155 callcounter=yes     ; <-- add this
156
157
158 Then reload chan_sip with 'sip reload' and perform our 555 test again. Dial 555
159 and then check the device state with 'core show hints'.
160
161 *CLI>   == Using SIP RTP CoS mark 5
162     -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000002", "tt-monkeys") in new stack
163     -- <SIP/0004f2040001-00000002> Playing 'tt-monkeys.slin' (language 'en')
164
165 *CLI> core show hints
166
167     -= Registered Asterisk Dial Plan Hints =-
168            0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
169            0004f2040001@default             : SIP/0004f2040001      State:InUse           Watchers  0
170 ----------------
171 - 2 hints registered
172
173
174 Note that now we have the correct device state when extension 555 is dialed,
175 showing that our device is InUse after dialing extension 555. This is important
176 when creating queues, otherwise our queue members would get multiple calls from
177 the queues.
178
179 -----------------------------
180 | Adding Queues to Asterisk |
181 -----------------------------
182
183 The next step is to add a couple of queues to Asterisk that we can assign queue
184 members into. For now we'll work with two queues; sales and support. Lets create
185 those queues now in queues.conf.
186
187 We'll leave the default settings that are shipped with queues.conf.sample in the
188 [general] section of queues.conf. See the queues.conf.sample file for more
189 information about each of the available options.
190
191 queues.conf
192 -----------
193
194 [general]
195 persistantmembers=yes
196 autofill=yes
197 monitor-type=MixMonitor
198 shared_lastcall=no
199
200
201 We can then define a [queue_template] that we'll assign to each of the queues
202 we create. These definitions can be overridden by each queue individually if you
203 reassign them under the [sales] or [support] headers. So under the [general]
204 section of your queues.conf file, add the following.
205
206
207 queues.conf
208 ----------
209
210 [queue_template](!)
211 musicclass=default      ; play [default] music
212 strategy=rrmemory       ; use the Round Robin Memory strategy
213 joinempty=yes           ; join the queue when no members available
214 leavewhenempty=no       ; don't leave the queue no members available
215 ringinuse=no            ; don't ring members when already InUse
216
217 [sales](queue_template)
218 ; Sales queue
219
220 [support](queue_template)
221 ; Support queue
222
223
224
225 After defining our queues, lets reload our app_queue.so module.
226
227
228 *CLI> module reload app_queue.so
229     -- Reloading module 'app_queue.so' (True Call Queueing)
230
231   == Parsing '/etc/asterisk/queues.conf':   == Found
232
233
234 Then verify our queues loaded with 'queue show'.
235
236
237 *CLI> queue show
238 support      has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
239    No Members
240    No Callers
241
242 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
243    No Members
244    No Callers
245
246
247
248 ------------------------
249 | Adding Queue Members |
250 ------------------------
251
252 You'll notice that we have no queue members available to take calls from the
253 queues. We can add queue members from the Asterisk CLI with the 'queue add
254 member' command.
255
256 This is the format of the 'queue add member' command:
257
258 Usage: queue add member <channel> to <queue> [[[penalty <penalty>] as <membername>] state_interface <interface>]
259        Add a channel to a queue with optionally:  a penalty, membername and a state_interface
260
261 The penalty, membername, and state_interface are all optional values. Special
262 attention should be brought to the 'state_interface' option for a member though.
263 The reason for state_interface is that if you're using a channel that does not
264 have device state itself (for example, if you were using the Local channel to
265 deliver a call to an end point) then you could assign the device state of a SIP
266 device to the pseudo channel. This allows the state of a SIP device to be
267 applied to the Local channel for correct device state information.
268
269 Lets add our device located at SIP/0004f2040001
270
271 *CLI> queue add member SIP/0004f2040001 to sales
272 Added interface 'SIP/0004f2040001' to queue 'sales'
273
274 Then lets verify our member was indeed added.
275
276 *CLI> queue show sales
277 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
278    Members: 
279       SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
280    No Callers
281
282 Now, if we dial our 555 extension, we should see that our member becomes InUse
283 within the queue.
284
285 *CLI>   == Using SIP RTP CoS mark 5
286     -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000001", "tt-monkeys") in new stack
287     -- <SIP/0004f2040001-00000001> Playing 'tt-monkeys.slin' (language 'en')
288
289
290 *CLI> queue show sales
291 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
292    Members: 
293       SIP/0004f2040001 (dynamic) (In use) has taken no calls yet
294    No Callers
295
296 We can also remove our members from the queue using the 'queue remove' CLI
297 command.
298
299 *CLI> queue remove member SIP/0004f2040001 from sales 
300 Removed interface 'SIP/0004f2040001' from queue 'sales'
301
302 Because we don't want to have to add queue members manually from the CLI, we
303 should create a method that allows queue members to login and out from their
304 devices. We'll do that in the next section.
305
306 But first, lets add an extension to our dialplan in order to permit people to
307 dial into our queues so calls can be delivered to our queue members.
308
309 extensions.conf
310 ---------------
311
312 [devices]
313 exten => 555,1,Playback(tt-monkeys)
314
315 exten => 100,1,Queue(sales)
316
317 exten => 101,1,Queue(support)
318
319
320 Then reload the dialplan, and try calling extension 100 from SIP/0004f2040002,
321 which is the device we have not logged into the queue.
322
323 *CLI> dialplan reload
324
325 And now we call the queue at extension 100 which will ring our device at
326 SIP/0004f2040001.
327
328 *CLI>   == Using SIP RTP CoS mark 5
329     -- Executing [100@devices:1] Queue("SIP/0004f2040002-00000005", "sales") in new stack
330     -- Started music on hold, class 'default', on SIP/0004f2040002-00000005
331   == Using SIP RTP CoS mark 5
332     -- SIP/0004f2040001-00000006 is ringing
333
334
335 We can see the device state has changed to Ringing while the device is ringing.
336
337 *CLI> queue show sales
338 sales        has 1 calls (max unlimited) in 'rrmemory' strategy (2s holdtime, 3s talktime), W:0, C:1, A:1, SL:0.0% within 0s
339    Members: 
340       SIP/0004f2040001 (dynamic) (Ringing) has taken 1 calls (last was 14 secs ago)
341    Callers: 
342       1. SIP/0004f2040002-00000005 (wait: 0:03, prio: 0)
343
344
345 Our queue member then answers the phone.
346
347 *CLI>     -- SIP/0004f2040001-00000006 answered SIP/0004f2040002-00000005
348     -- Stopped music on hold on SIP/0004f2040002-00000005
349     -- Native bridging SIP/0004f2040002-00000005 and SIP/0004f2040001-00000006
350
351
352 And we can see the queue member is now in use.
353
354 *CLI> queue show sales
355 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 3s talktime), W:0, C:1, A:1, SL:0.0% within 0s
356    Members: 
357       SIP/0004f2040001 (dynamic) (In use) has taken 1 calls (last was 22 secs ago)
358    No Callers
359
360
361 Then the call is hung up.
362
363 *CLI>   == Spawn extension (devices, 100, 1) exited non-zero on 'SIP/0004f2040002-00000005'
364
365
366 And we see that our queue member is available to take another call.
367
368 *CLI> queue show sales
369 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
370    Members: 
371       SIP/0004f2040001 (dynamic) (Not in use) has taken 2 calls (last was 6 secs ago)
372    No Callers
373
374 --------------------------------
375 | Logging In and Out of Queues |
376 --------------------------------
377
378 In this section we'll show how to use the AddQueueMember() and 
379 RemoveQueueMember() dialplan applications to login and out of queues. For more
380 information about the available options to AddQueueMember() and 
381 RemoveQueueMember() use the 'core show application <app>' command from the CLI.
382
383 The following bit of dialplan is a bit long, but stick with it, and you'll see
384 that it isn't really all that bad. The gist of the dialplan is that it will
385 check to see if the active user (the device that is dialing the extension) is
386 currently logged into the queue extension that has been requested, and if logged
387 in, then will log them out; if not logged in, then they will be logged into the
388 queue.
389
390 We've updated the two lines we added in the previous section that allowed us to
391 dial the sales and support queues. We've abstracted this out a bit in order to
392 make it easier to add new queues in the future. This is done by adding the queue
393 names to a global variable, then utilizing the extension number dialed to look
394 up the queue name.
395
396 So we replace extension 100 and 101 with the following dialplan.
397
398 ; Call any of the queues we've defined in the [globals] section.
399 exten => _1XX,1,Verbose(2,Call queue as configured in the QUEUE_${EXTEN} global variable)
400 exten => _1XX,n,Set(thisQueue=${GLOBAL(QUEUE_${EXTEN})})
401 exten => _1XX,n,GotoIf($["${thisQueue}" = ""]?invalid_queue,1)
402 exten => _1XX,n,Verbose(2, --> Entering the ${thisQueue} queue)
403 exten => _1XX,n,Queue(${thisQueue})
404 exten => _1XX,n,Hangup()
405
406 exten => invalid_queue,1,Verbose(2,Attempted to enter invalid queue)
407 exten => invalid_queue,n,Playback(silence/1&invalid)
408 exten => invalid_queue,n,Hangup()
409
410 The [globals] section contains the following two global variables.
411
412 [globals]
413 QUEUE_100=sales
414 QUEUE_101=support
415
416 So when we dial extension 100, it matches our pattern _1XX. The number we dialed
417 (100) is then retrievable via ${EXTEN} and we can get the name of queue 100
418 (sales) from the global variable QUEUE_100. We then assign it to the channel
419 variable thisQueue so it is easier to work with in our dialplan.
420
421 exten => _1XX,n,Set(thisQueue=${GLOBAL(QUEUE_${EXTEN})})
422
423 We then check to see if we've gotten a value back from the global variable which
424 would indicate whether the queue was valid or not.
425
426 exten => _1XX,n,GotoIf($["${thisQueue}" = ""]?invalid_queue,1)
427
428 If ${thisQueue} returns nothing, then we Goto the invalid_queue extension and
429 playback the 'invalid' file.
430
431 We could alternatively limit our pattern match to only extension 100 and 101
432 with the _10[0-1] pattern instead.
433
434 Lets move into the nitty-gritty section and show how we can login and logout our
435 devices to the pair of queues we've created.
436
437 First, we create a pattern match that takes star (*) plus the queue number 
438 that we want to login or logout of. So to login/out of the sales queue (100) we 
439 would dial *100. We use the same extension for logging in and out.
440
441 ; Extension *100 or *101 will login/logout a queue member from sales or support queues respectively.
442 exten => _*10[0-1],1,Set(xtn=${EXTEN:1})                        ; save ${EXTEN} with * chopped off to ${xtn}
443 exten => _*10[0-1],n,Goto(queueLoginLogout,member_check,1)      ; check if already logged into a queue
444
445 We save the value of ${EXTEN:1} to the 'xtn' channel variable so we don't need
446 to keep typing the complicated pattern match.
447
448 Now we move into the meat of our login/out dialplan inside the 
449 [queueLoginLogout] context.
450
451 The first section is initializing some variables that we need throughout the
452 member_check extension such as the name of the queue, the members currently
453 logged into the queue, and the current device peer name (i.e. SIP/0004f2040001).
454
455
456
457 ; ### Login or Logout a Queue Member
458 [queueLoginLogout]
459 exten => member_check,1,Verbose(2,Logging queue member in or out of the request queue)
460 exten => member_check,n,Set(thisQueue=${GLOBAL(QUEUE_${xtn})})                  ; assign queue name to a variable
461 exten => member_check,n,Set(queueMembers=${QUEUE_MEMBER_LIST(${thisQueue})})    ; assign list of logged in members of thisQueue to
462                                                                                 ; a variable (comma separated)
463 exten => member_check,n,Set(thisActiveMember=SIP/${CHANNEL(peername)})          ; initialize 'thisActiveMember' as current device
464
465 exten => member_check,n,GotoIf($["${queueMembers}" = ""]?q_login,1)             ; short circuit to logging in if we don't have
466                                                                                 ; any members logged into this queue
467
468
469
470 At this point if there are no members currently logged into our sales queue,
471 we then short-circuit our dialplan to go to the 'q_login' extension since there
472 is no point in wasting cycles searching to see if we're already logged in.
473
474 The next step is to finish initializing some values we need within the While()
475 loop that we'll use to check if we're already logged into the queue. We set
476 our ${field} variable to 1, which will be used as the field number offset in
477 the CUT() function.
478
479
480 ; Initialize some values we'll use in the While() loop
481 exten => member_check,n,Set(field=1)                                            ; start our field counter at one
482 exten => member_check,n,Set(logged_in=0)                                        ; initialize 'logged_in' to "not logged in"
483 exten => member_check,n,Set(thisQueueMember=${CUT(queueMembers,\,,${field})})   ; initialize 'thisQueueMember' with the value in the
484                                                                                 ;   first field of the comma-separated list
485
486
487 Now we get to enter our While() loop to determine if we're already logged in.
488
489
490 ; Enter our loop to check if our member is already logged into this queue
491 exten => member_check,n,While($[${EXISTS(${thisQueueMember})}])                                 ; while we have a queue member...
492
493
494 This is where we check to see if the member at this position of the list is the
495 same as the device we're calling from. If it doesn't match, then we go to the
496 'check_next' priority label (where we increase our ${field} counter variable).
497 If it does match, then we continue on in the dialplan.
498
499 exten => member_check,n,GotoIf($["${thisQueueMember}" != "${thisActiveMember}"]?check_next)     ; if 'thisQueueMember' is not the
500                                                                                                 ;   same as our active peer, then
501                                                                                                 ;   check the next in the list of
502                                                                                                 ;   logged in queue members
503
504 If we continued on in the dialplan, then we set the ${logged_in} channel
505 variable to '1' which represents we're already logged into this queue. We then
506 exit the While() loop with the ExitWhile() dialplan application.
507
508 exten => member_check,n,Set(logged_in=1)                                                        ; if we got here, set as logged in
509 exten => member_check,n,ExitWhile()                                                             ;  then exit our loop
510
511
512
513 If we didn't match this peer name in the list, then we increase our ${field}
514 counter variable by one, update the ${thisQueueMember} channel variable and then
515 move back to the top of the loop for another round of checks.
516
517 exten => member_check,n(check_next),Set(field=$[${field} + 1])                                  ; if we got here, increase counter
518 exten => member_check,n,Set(thisQueueMember=${CUT(queueMembers,\,,${field})})                   ; get next member in the list
519 exten => member_check,n,EndWhile()                                                              ; ...end of our loop
520
521
522 And once we exit our loop, we determine whether we need to log our device in
523 or out of the queue.
524
525 ; if not logged in, then login to this queue, otherwise, logout
526 exten => member_check,n,GotoIf($[${logged_in} = 0]?q_login,1:q_logout,1)        ; if not logged in, then login, otherwise, logout
527
528
529
530 The following two extensions are used to either log the device in or out of the
531 queue. We use the AddQueueMember() and RemovQueueMember() applications to login
532 or logout the device from the queue.
533
534 The first two arguments for AddQueueMember() and RemoveQueueMember() are 'queue'
535 and 'device'. There are additional arguments we can pass, and you can check
536 those out with 'core show application AddQueueMember' and 'core show
537 application RemoveQueueMember()'.
538
539 ; ### Login queue member ###
540 exten => q_login,1,Verbose(2,Logging ${thisActiveMember} into the ${thisQueue} queue)
541 exten => q_login,n,AddQueueMember(${thisQueue},${thisActiveMember})                     ; login our active device to the queue 
542                                                                                         ; requested
543 exten => q_login,n,Playback(silence/1)  ; answer the channel by playing one second of silence
544
545 ; If the member was added to the queue successfully, then playback "Agent logged in", otherwise, state an error occurred
546 exten => q_login,n,ExecIf($["${AQMSTATUS}" = "ADDED"]?Playback(agent-loginok):Playback(an-error-has-occurred))
547 exten => q_login,n,Hangup()
548
549
550 ; ### Logout queue member ###
551 exten => q_logout,1,Verbose(2,Logging ${thisActiveMember} out of ${thisQueue} queue)
552 exten => q_logout,n,RemoveQueueMember(${thisQueue},${thisActiveMember})
553 exten => q_logout,n,Playback(silence/1)
554 exten => q_logout,n,ExecIf($["${RQMSTATUS}" = "REMOVED"]?Playback(agent-loggedoff):Playback(an-error-has-occurred))
555 exten => q_logout,n,Hangup()
556
557
558 And that's it! Give it a shot and you should see console output similar to the
559 following which will login and logout your queue members to the queues you've
560 configured.
561
562 You can see there are already a couple of queue members logged into the sales
563 queue.
564
565 *CLI> queue show sales
566 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
567    Members: 
568       SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
569       SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
570    No Callers
571
572
573 Then we dial *100 to logout the active device from the sales queue.
574
575 *CLI>   == Using SIP RTP CoS mark 5
576     -- Executing [*100@devices:1] Set("SIP/0004f2040001-00000012", "xtn=100") in new stack
577     -- Executing [*100@devices:2] Goto("SIP/0004f2040001-00000012", "queueLoginLogout,member_check,1") in new stack
578     -- Goto (queueLoginLogout,member_check,1)
579     -- Executing [member_check@queueLoginLogout:1] Verbose("SIP/0004f2040001-00000012", "2,Logging queue member in or out of the request queue") in new stack
580   == Logging queue member in or out of the request queue
581     -- Executing [member_check@queueLoginLogout:2] Set("SIP/0004f2040001-00000012", "thisQueue=sales") in new stack
582     -- Executing [member_check@queueLoginLogout:3] Set("SIP/0004f2040001-00000012", "queueMembers=SIP/0004f2040001,SIP/0004f2040002") in new stack
583     -- Executing [member_check@queueLoginLogout:4] Set("SIP/0004f2040001-00000012", "thisActiveMember=SIP/0004f2040001") in new stack
584     -- Executing [member_check@queueLoginLogout:5] GotoIf("SIP/0004f2040001-00000012", "0?q_login,1") in new stack
585     -- Executing [member_check@queueLoginLogout:6] Set("SIP/0004f2040001-00000012", "field=1") in new stack
586     -- Executing [member_check@queueLoginLogout:7] Set("SIP/0004f2040001-00000012", "logged_in=0") in new stack
587     -- Executing [member_check@queueLoginLogout:8] Set("SIP/0004f2040001-00000012", "thisQueueMember=SIP/0004f2040001") in new stack
588     -- Executing [member_check@queueLoginLogout:9] While("SIP/0004f2040001-00000012", "1") in new stack
589     -- Executing [member_check@queueLoginLogout:10] GotoIf("SIP/0004f2040001-00000012", "0?check_next") in new stack
590     -- Executing [member_check@queueLoginLogout:11] Set("SIP/0004f2040001-00000012", "logged_in=1") in new stack
591     -- Executing [member_check@queueLoginLogout:12] ExitWhile("SIP/0004f2040001-00000012", "") in new stack
592     -- Jumping to priority 15
593     -- Executing [member_check@queueLoginLogout:16] GotoIf("SIP/0004f2040001-00000012", "0?q_login,1:q_logout,1") in new stack
594     -- Goto (queueLoginLogout,q_logout,1)
595     -- Executing [q_logout@queueLoginLogout:1] Verbose("SIP/0004f2040001-00000012", "2,Logging SIP/0004f2040001 out of sales queue") in new stack
596   == Logging SIP/0004f2040001 out of sales queue
597     -- Executing [q_logout@queueLoginLogout:2] RemoveQueueMember("SIP/0004f2040001-00000012", "sales,SIP/0004f2040001") in new stack
598 [Nov 12 12:08:51] NOTICE[11582]: app_queue.c:4842 rqm_exec: Removed interface 'SIP/0004f2040001' from queue 'sales'
599     -- Executing [q_logout@queueLoginLogout:3] Playback("SIP/0004f2040001-00000012", "silence/1") in new stack
600     -- <SIP/0004f2040001-00000012> Playing 'silence/1.slin' (language 'en')
601     -- Executing [q_logout@queueLoginLogout:4] ExecIf("SIP/0004f2040001-00000012", "1?Playback(agent-loggedoff):Playback(an-error-has-occurred)") in new stack
602     -- <SIP/0004f2040001-00000012> Playing 'agent-loggedoff.slin' (language 'en')
603     -- Executing [q_logout@queueLoginLogout:5] Hangup("SIP/0004f2040001-00000012", "") in new stack
604   == Spawn extension (queueLoginLogout, q_logout, 5) exited non-zero on 'SIP/0004f2040001-00000012'
605
606
607 And we can see that the device we loggd out by running 'queue show sales'.
608
609 *CLI> queue show sales
610 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
611    Members: 
612       SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
613    No Callers
614
615
616 -------------------------------------------
617 | Pausing and Unpausing Members of Queues |
618 -------------------------------------------
619
620 Once we have our queue members logged in, it is inevitable that they will want
621 to pause themselves during breaks, and other short periods of inactivity. To do
622 this we can utilize the 'queue pause' and 'queue unpause' CLI commands.
623
624 We have two devices logged into the sales queue as we can see with the 'queue
625 show sales' CLI command.
626
627 *CLI> queue show sales
628 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
629    Members: 
630       SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
631       SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
632    No Callers
633
634
635 We can then pause our devices with 'queue pause' which has the following format.
636
637 Usage: queue {pause|unpause} member <member> [queue <queue> [reason <reason>]]
638         Pause or unpause a queue member. Not specifying a particular queue
639         will pause or unpause a member across all queues to which the member
640         belongs.
641
642 Lets pause device 0004f2040001 in the sales queue by executing the following.
643
644 *CLI> queue pause member SIP/0004f2040001 queue sales
645 paused interface 'SIP/0004f2040001' in queue 'sales' for reason 'lunch'
646
647
648 And we can see they are paused with 'queue show sales'.
649
650 *CLI> queue show sales
651 sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
652    Members: 
653       SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
654       SIP/0004f2040001 (dynamic) (paused) (Not in use) has taken no calls yet
655    No Callers
656
657 At this point the queue member will no longer receive calls from the system. We
658 can unpause them with the CLI command 'queue unpause member'.
659
660 *CLI> queue unpause member SIP/0004f2040001 queue sales 
661 unpaused interface 'SIP/0004f2040001' in queue 'sales'
662
663 And if you don't specify a queue, it will pause or unpause from all queues.
664
665 *CLI> queue pause member SIP/0004f2040001
666 paused interface 'SIP/0004f2040001'
667
668
669 Of course we want to allow the agents to pause and unpause themselves from their
670 devices, so we need to create an extension and some dialplan logic for that to
671 happen.
672
673 Below we've created the pattern patch _*0[01]! which will match on *00 and *01,
674 and will *also* match with zero or more digits following it, such as the queue
675 extension number.
676
677 So if we want to pause ourselves in all queues, we can dial *00; unpausing can
678 be done with *01. But if our agents just need to pause or unpause themselves
679 from a single queue, then we will also accept *00100 to pause in queue 100
680 (sales), or we can unpause ourselves from sales with *01100.
681
682
683 extensions.conf
684 ---------------
685
686 ; Allow queue members to pause and unpause themselves from all queues, or an individual queue.
687 ;
688 ; _*0[01]! pattern match will match on *00 and *01 plus 0 or more digits.
689 exten => _*0[01]!,1,Verbose(2,Pausing or unpausing queue member from one or more queues)
690 exten => _*0[01]!,n,Set(xtn=${EXTEN:3})                                                         ; save the queue extension to 'xtn'
691 exten => _*0[01]!,n,Set(thisQueue=${GLOBAL(QUEUE_${xtn})})                                      ; get the queue name if available
692 exten => _*0[01]!,n,GotoIf($[${ISNULL(${thisQueue})} & ${EXISTS(${xtn})}]?invalid_queue,1)  ; if 'thisQueue' is blank and the
693                                                                                                 ;  the agent dialed a queue exten,
694                                                                                                 ;  we will tell them it's invalid
695
696 The following line will determine if we're trying to pause or unpause. This is
697 done by taking the value dialed (e.g. *00100) and chopping off the first 2 
698 digits which leaves us with 0100, and then the :1 will return the next digit,
699 which in this case is '0' that we're using to signify that the queue member 
700 wants to be paused (in queue 100).
701
702 So we're doing the following with our EXTEN variable.
703
704       ${EXTEN:2:1}
705 offset        ^ ^  length
706
707
708 Which causes the following.
709
710  *00100
711  ^^      offset these characters
712
713  *00100
714    ^     then return a digit length of one, which is digit 0
715
716
717 exten => _*0[01]!,n,GotoIf($[${EXTEN:2:1} = 0]?pause,1:unpause,1)                       ; determine if they wanted to pause
718                                                                                         ;  or to unpause.
719
720
721 The following two extensions, pause & unpause, are used for pausing and
722 unpausing our extension from the queue(s). We use the PauseQueueMember() and
723 UnpauseQueueMember() dialplan applications which accept the queue name
724 (optional) and the queue member name. If the queue name is not provided, then it
725 is assumed we want to pause or unpause from all logged in queues.
726
727 ; Unpause ourselves from one or more queues
728 exten => unpause,1,NoOp()
729 exten => unpause,n,UnpauseQueueMember(${thisQueue},SIP/${CHANNEL(peername)})            ; if 'thisQueue' is populated we'll pause in
730                                                                                         ;   that queue, otherwise, we'll unpause in
731                                                                                         ;   in all queues
732
733
734 Once we've unpaused ourselves, we use GoSub() to perform some common dialplan
735 logic that is used for pausing and unpausing. We pass three arguments to the
736 subroutine: 
737
738  * variable name that contains the result of our operation
739  * the value we're expecting to get back if successful
740  * the filename to play
741
742 exten => unpause,n,GoSub(changePauseStatus,start,1(UPQMSTATUS,UNPAUSED,available))      ; use the changePauseStatus subroutine and
743                                                                                         ;   pass the values for: variable to check,
744                                                                                         ;   value to check for, and file to play
745 exten => unpause,n,Hangup()
746
747
748 And the same method is done for pausing.
749
750 ; Pause ourselves in one or more queues
751 exten => pause,1,NoOp()
752 exten => pause,n,PauseQueueMember(${thisQueue},SIP/${CHANNEL(peername)})
753 exten => pause,n,GoSub(changePauseStatus,start,1(PQMSTATUS,PAUSED,unavailable))
754 exten => pause,n,Hangup()
755
756
757 Lets explore what happens in the subroutine we're using for pausing and
758 unpausing.
759
760
761 ; ### Subroutine we use to check pausing and unpausing status ###
762 [changePauseStatus]
763 ; ARG1:  variable name to check, such as PQMSTATUS and UPQMSTATUS (PauseQueueMemberStatus / UnpauseQueueMemberStatus)
764 ; ARG2:  value to check for, such as PAUSED or UNPAUSED
765 ; ARG3:  file to play back if our variable value matched the value to check for
766 ;
767 exten => start,1,NoOp()
768 exten => start,n,Playback(silence/1)                                                     ; answer line with silence
769
770 The following line is probably the most complex. We're using the IF() function 
771 inside the Playback() application which determines which file to playback
772 to the user.
773
774 Those three values we passed in from the pause and unpause extensions could have
775 been something like:
776
777  * ARG1 -- PQMSTATUS
778  * ARG2 -- PAUSED
779  * ARG3 -- unavailable
780
781 So when expanded, we'd end up with the following inside the IF() function.
782
783   $["${PQMSTATUS}" = "PAUSED"]?unavailable:not-yet-connected
784
785 ${PQMSTATUS} would then be expanded further to contain the status of our
786 PauseQueueMember() dialplan application, which could either be PAUSED or 
787 NOTFOUND. So if ${PQMSTATUS} returned PAUSED, then it would match what we're
788 looking to match on, and we'd then return 'unavailable' to Playback() that would
789 tell the user they are now unavailable. 
790
791 Otherwise, we'd get back a message saying "not yet connected" to indicate they
792 are likely not logged into the queue they are attempting to change status in.
793
794
795 ; Please note that ${ARG1} is wrapped in ${  } in order to expand the value of ${ARG1} into
796 ;   the variable we want to retrieve the value from, i.e. ${${ARG1}} turns into ${PQMSTATUS}
797 exten => start,n,Playback(${IF($["${${ARG1}}" = "${ARG2}"]?${ARG3}:not-yet-connected)})         ; check if value of variable
798                                                                                                 ;  matches the value we're looking
799                                                                                                 ;  for and playback the file we want
800                                                                                                 ;  to play if it does
801
802 If ${xtn} is null, then we just go to the end of the subroutine, but if it isn't
803 then we will play back "in the queue" followed by the queue extension number
804 indicating which queue they were (un)paused from.
805
806 exten => start,n,GotoIf($[${ISNULL(${xtn})}]?end)       ; if ${xtn} is null, then just Return()
807 exten => start,n,Playback(in-the-queue)                 ;   if not null, then playback "in the queue"
808 exten => start,n,SayNumber(${xtn})                      ;   and the queue number that we (un)paused from
809 exten => start,n(end),Return()                          ; return from were we came
810
811 --------------
812 | Conclusion |
813 --------------
814
815 You should now have a simple system that permits you to login and out of queues
816 you create in queues.conf, and to allow queue members to pause themselves within
817 one or more queues. There are a lot of dialplan concepts utilized in this
818 article, so you are encouraged to seek out additional documentation if any of
819 these concepts are a bit fuzzy for you. 
820
821 A good start is the doc/ subdirectory of the Asterisk sources, or the various
822 configuration samples files located in the configs/ subdirectory of your
823 Asterisk source code.