aboutsummaryrefslogtreecommitdiffstats
path: root/doc/building_queues.txt
blob: a5da7a2f8320073001feeab8626ff5aff2c8c41d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
=================
 Building Queues
=================

Written by: Leif Madsen
Initial version: 2010-01-14

In this article, we'll look at setting up a pair of queues in Asterisk called
'sales' and 'support'. These queues can be logged into by queue members, and
those members will also have the ability to pause and unpause themselves.

All configuration will be done in flat files on the system in order to maintain
simplicity in configuration.

Note that this documentation is based on Asterisk 1.6.2, and this is just one
approach to creating queues and the dialplan logic. You may create a better way,
and in that case, I would encourage you to submit it to the Asterisk issue
tracker at http://issues.asterisk.org for inclusion in Asterisk.

-------------------------------------
| Adding SIP Devices to Your Server |
-------------------------------------

The first thing we want to do is register a couple of SIP devices to our server.
These devices will be our agents that can login and out of the queues we'll
create later. Our naming convention will be to use MAC addresses as we want to
abstract the concepts of user (agent), device, and extension from each other.

In sip.conf, we add the following to the bottom of our file:

sip.conf
--------

[std-device](!)
type=peer
context=devices
host=dynamic
secret=s3CuR#p@s5
dtmfmode=rfc2833
disallow=all
allow=ulaw

[0004f2040001](std-device)

[0004f2040002](std-device)



What we're doing here is creating a [std-device] template and applying it to
a pair of peers that we'll register as 0004f2040001 and 0004f2040002; our
devices.

Then our devices can register to Asterisk. In my case I have a hard phone and
a soft phone registered. I can verify their connectivity by running 'sip show
peers'.

*CLI> sip show peers
Name/username              Host            Dyn Nat ACL Port     Status     
0004f2040001/0004f2040001  192.168.128.145  D          5060     Unmonitored 
0004f2040002/0004f2040002  192.168.128.126  D          5060     Unmonitored 
2 sip peers [Monitored: 0 online, 0 offline Unmonitored: 2 online, 0 offline]



----------------------------
| Configuring Device State |
----------------------------

Next, we need to configure our system to track the state of the devices. We do
this by defining a 'hint' in the dialplan which creates the ability for a device
subscription to be retained in memory. By default we can see there are no hints
registered in our system by running the 'core show hints' command.

*CLI> core show hints
There are no registered dialplan hint


We need to add the devices we're going to track to the extensions.conf file
under the [default] context which is the default configuration in sip.conf,
however we can change this to any context we want with the 'subscribecontext'
option.

Add the following lines to extensions.conf:

[default]
exten => 0004f2040001,hint,SIP/0004f2040001
exten => 0004f2040002,hint,SIP/0004f2040002

Then perform a 'dialplan reload' in order to reload the dialplan.

After reloading our dialplan, you can see the status of the devices with 'core
show hints' again.


*CLI> core show hints

    -= Registered Asterisk Dial Plan Hints =-
           0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
           0004f2040001@default             : SIP/0004f2040001      State:Idle            Watchers  0
----------------
- 2 hints registered


At this point, create an extension that you can dial that will play a prompt
that is long enough for you to go back to the Asterisk console to check the
state of your device while it is in use.

To do this, add the 555 extension to the [devices] context and make it playback
the tt-monkeys file.


extensions.conf
---------------

[devices]
exten => 555,1,Playback(tt-monkeys)


Dial that extension and then check the state of your device on the console.

*CLI>   == Using SIP RTP CoS mark 5
    -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000001", "tt-monkeys") in new stack
    -- <SIP/0004f2040001-00000001> Playing 'tt-monkeys.slin' (language 'en')

*CLI> core show hints

    -= Registered Asterisk Dial Plan Hints =-
           0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
           0004f2040001@default             : SIP/0004f2040001      State:Idle            Watchers  0
----------------
- 2 hints registered

Aha, we're not getting the device state correctly. There must be something else
we need to configure.

In sip.conf, we need to enable 'callcounter' in order to activate the ability
for Asterisk to monitor whether the device is in use or not. In versions prior
to 1.6.0 we needed to use 'call-limit' for this functionality, but call-limit
is now deprecated and is no longer necessary.

So, in sip.conf, in our [std-device] template, we need to add the callcounter
option.

sip.conf
--------

[std-device](!)
type=peer
context=devices
host=dynamic
secret=s3CuR#p@s5
dtmfmode=rfc2833
disallow=all
allow=ulaw
callcounter=yes     ; <-- add this


Then reload chan_sip with 'sip reload' and perform our 555 test again. Dial 555
and then check the device state with 'core show hints'.

*CLI>   == Using SIP RTP CoS mark 5
    -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000002", "tt-monkeys") in new stack
    -- <SIP/0004f2040001-00000002> Playing 'tt-monkeys.slin' (language 'en')

*CLI> core show hints

    -= Registered Asterisk Dial Plan Hints =-
           0004f2040002@default             : SIP/0004f2040002      State:Idle            Watchers  0
           0004f2040001@default             : SIP/0004f2040001      State:InUse           Watchers  0
----------------
- 2 hints registered


Note that now we have the correct device state when extension 555 is dialed,
showing that our device is InUse after dialing extension 555. This is important
when creating queues, otherwise our queue members would get multiple calls from
the queues.

-----------------------------
| Adding Queues to Asterisk |
-----------------------------

The next step is to add a couple of queues to Asterisk that we can assign queue
members into. For now we'll work with two queues; sales and support. Lets create
those queues now in queues.conf.

We'll leave the default settings that are shipped with queues.conf.sample in the
[general] section of queues.conf. See the queues.conf.sample file for more
information about each of the available options.

queues.conf
-----------

[general]
persistantmembers=yes
autofill=yes
monitor-type=MixMonitor
shared_lastcall=no


We can then define a [queue_template] that we'll assign to each of the queues
we create. These definitions can be overridden by each queue individually if you
reassign them under the [sales] or [support] headers. So under the [general]
section of your queues.conf file, add the following.


queues.conf
----------

[queue_template](!)
musicclass=default      ; play [default] music
strategy=rrmemory       ; use the Round Robin Memory strategy
joinempty=yes           ; join the queue when no members available
leavewhenempty=no       ; don't leave the queue no members available
ringinuse=no            ; don't ring members when already InUse

[sales](queue_template)
; Sales queue

[support](queue_template)
; Support queue



After defining our queues, lets reload our app_queue.so module.


*CLI> module reload app_queue.so
    -- Reloading module 'app_queue.so' (True Call Queueing)

  == Parsing '/etc/asterisk/queues.conf':   == Found


Then verify our queues loaded with 'queue show'.


*CLI> queue show
support      has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   No Members
   No Callers

sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   No Members
   No Callers



------------------------
| Adding Queue Members |
------------------------

You'll notice that we have no queue members available to take calls from the
queues. We can add queue members from the Asterisk CLI with the 'queue add
member' command.

This is the format of the 'queue add member' command:

Usage: queue add member <channel> to <queue> [[[penalty <penalty>] as <membername>] state_interface <interface>]
       Add a channel to a queue with optionally:  a penalty, membername and a state_interface

The penalty, membername, and state_interface are all optional values. Special
attention should be brought to the 'state_interface' option for a member though.
The reason for state_interface is that if you're using a channel that does not
have device state itself (for example, if you were using the Local channel to
deliver a call to an end point) then you could assign the device state of a SIP
device to the pseudo channel. This allows the state of a SIP device to be
applied to the Local channel for correct device state information.

Lets add our device located at SIP/0004f2040001

*CLI> queue add member SIP/0004f2040001 to sales
Added interface 'SIP/0004f2040001' to queue 'sales'

Then lets verify our member was indeed added.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
   No Callers

Now, if we dial our 555 extension, we should see that our member becomes InUse
within the queue.

*CLI>   == Using SIP RTP CoS mark 5
    -- Executing [555@devices:1] Playback("SIP/0004f2040001-00000001", "tt-monkeys") in new stack
    -- <SIP/0004f2040001-00000001> Playing 'tt-monkeys.slin' (language 'en')


*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (In use) has taken no calls yet
   No Callers

We can also remove our members from the queue using the 'queue remove' CLI
command.

*CLI> queue remove member SIP/0004f2040001 from sales 
Removed interface 'SIP/0004f2040001' from queue 'sales'

Because we don't want to have to add queue members manually from the CLI, we
should create a method that allows queue members to login and out from their
devices. We'll do that in the next section.

But first, lets add an extension to our dialplan in order to permit people to
dial into our queues so calls can be delivered to our queue members.

extensions.conf
---------------

[devices]
exten => 555,1,Playback(tt-monkeys)

exten => 100,1,Queue(sales)

exten => 101,1,Queue(support)


Then reload the dialplan, and try calling extension 100 from SIP/0004f2040002,
which is the device we have not logged into the queue.

*CLI> dialplan reload

And now we call the queue at extension 100 which will ring our device at
SIP/0004f2040001.

*CLI>   == Using SIP RTP CoS mark 5
    -- Executing [100@devices:1] Queue("SIP/0004f2040002-00000005", "sales") in new stack
    -- Started music on hold, class 'default', on SIP/0004f2040002-00000005
  == Using SIP RTP CoS mark 5
    -- SIP/0004f2040001-00000006 is ringing


We can see the device state has changed to Ringing while the device is ringing.

*CLI> queue show sales
sales        has 1 calls (max unlimited) in 'rrmemory' strategy (2s holdtime, 3s talktime), W:0, C:1, A:1, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (Ringing) has taken 1 calls (last was 14 secs ago)
   Callers: 
      1. SIP/0004f2040002-00000005 (wait: 0:03, prio: 0)


Our queue member then answers the phone.

*CLI>     -- SIP/0004f2040001-00000006 answered SIP/0004f2040002-00000005
    -- Stopped music on hold on SIP/0004f2040002-00000005
    -- Native bridging SIP/0004f2040002-00000005 and SIP/0004f2040001-00000006


And we can see the queue member is now in use.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 3s talktime), W:0, C:1, A:1, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (In use) has taken 1 calls (last was 22 secs ago)
   No Callers


Then the call is hung up.

*CLI>   == Spawn extension (devices, 100, 1) exited non-zero on 'SIP/0004f2040002-00000005'


And we see that our queue member is available to take another call.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (Not in use) has taken 2 calls (last was 6 secs ago)
   No Callers

--------------------------------
| Logging In and Out of Queues |
--------------------------------

In this section we'll show how to use the AddQueueMember() and 
RemoveQueueMember() dialplan applications to login and out of queues. For more
information about the available options to AddQueueMember() and 
RemoveQueueMember() use the 'core show application <app>' command from the CLI.

The following bit of dialplan is a bit long, but stick with it, and you'll see
that it isn't really all that bad. The gist of the dialplan is that it will
check to see if the active user (the device that is dialing the extension) is
currently logged into the queue extension that has been requested, and if logged
in, then will log them out; if not logged in, then they will be logged into the
queue.

We've updated the two lines we added in the previous section that allowed us to
dial the sales and support queues. We've abstracted this out a bit in order to
make it easier to add new queues in the future. This is done by adding the queue
names to a global variable, then utilizing the extension number dialed to look
up the queue name.

So we replace extension 100 and 101 with the following dialplan.

; Call any of the queues we've defined in the [globals] section.
exten => _1XX,1,Verbose(2,Call queue as configured in the QUEUE_${EXTEN} global variable)
exten => _1XX,n,Set(thisQueue=${GLOBAL(QUEUE_${EXTEN})})
exten => _1XX,n,GotoIf($["${thisQueue}" = ""]?invalid_queue,1)
exten => _1XX,n,Verbose(2, --> Entering the ${thisQueue} queue)
exten => _1XX,n,Queue(${thisQueue})
exten => _1XX,n,Hangup()

exten => invalid_queue,1,Verbose(2,Attempted to enter invalid queue)
exten => invalid_queue,n,Playback(silence/1&invalid)
exten => invalid_queue,n,Hangup()

The [globals] section contains the following two global variables.

[globals]
QUEUE_100=sales
QUEUE_101=support

So when we dial extension 100, it matches our pattern _1XX. The number we dialed
(100) is then retrievable via ${EXTEN} and we can get the name of queue 100
(sales) from the global variable QUEUE_100. We then assign it to the channel
variable thisQueue so it is easier to work with in our dialplan.

exten => _1XX,n,Set(thisQueue=${GLOBAL(QUEUE_${EXTEN})})

We then check to see if we've gotten a value back from the global variable which
would indicate whether the queue was valid or not.

exten => _1XX,n,GotoIf($["${thisQueue}" = ""]?invalid_queue,1)

If ${thisQueue} returns nothing, then we Goto the invalid_queue extension and
playback the 'invalid' file.

We could alternatively limit our pattern match to only extension 100 and 101
with the _10[0-1] pattern instead.

Lets move into the nitty-gritty section and show how we can login and logout our
devices to the pair of queues we've created.

First, we create a pattern match that takes star (*) plus the queue number 
that we want to login or logout of. So to login/out of the sales queue (100) we 
would dial *100. We use the same extension for logging in and out.

; Extension *100 or *101 will login/logout a queue member from sales or support queues respectively.
exten => _*10[0-1],1,Set(xtn=${EXTEN:1})                        ; save ${EXTEN} with * chopped off to ${xtn}
exten => _*10[0-1],n,Goto(queueLoginLogout,member_check,1)      ; check if already logged into a queue

We save the value of ${EXTEN:1} to the 'xtn' channel variable so we don't need
to keep typing the complicated pattern match.

Now we move into the meat of our login/out dialplan inside the 
[queueLoginLogout] context.

The first section is initializing some variables that we need throughout the
member_check extension such as the name of the queue, the members currently
logged into the queue, and the current device peer name (i.e. SIP/0004f2040001).



; ### Login or Logout a Queue Member
[queueLoginLogout]
exten => member_check,1,Verbose(2,Logging queue member in or out of the request queue)
exten => member_check,n,Set(thisQueue=${GLOBAL(QUEUE_${xtn})})                  ; assign queue name to a variable
exten => member_check,n,Set(queueMembers=${QUEUE_MEMBER_LIST(${thisQueue})})    ; assign list of logged in members of thisQueue to
                                                                                ; a variable (comma separated)
exten => member_check,n,Set(thisActiveMember=SIP/${CHANNEL(peername)})          ; initialize 'thisActiveMember' as current device

exten => member_check,n,GotoIf($["${queueMembers}" = ""]?q_login,1)             ; short circuit to logging in if we don't have
                                                                                ; any members logged into this queue



At this point if there are no members currently logged into our sales queue,
we then short-circuit our dialplan to go to the 'q_login' extension since there
is no point in wasting cycles searching to see if we're already logged in.

The next step is to finish initializing some values we need within the While()
loop that we'll use to check if we're already logged into the queue. We set
our ${field} variable to 1, which will be used as the field number offset in
the CUT() function.


; Initialize some values we'll use in the While() loop
exten => member_check,n,Set(field=1)                                            ; start our field counter at one
exten => member_check,n,Set(logged_in=0)                                        ; initialize 'logged_in' to "not logged in"
exten => member_check,n,Set(thisQueueMember=${CUT(queueMembers,\,,${field})})   ; initialize 'thisQueueMember' with the value in the
                                                                                ;   first field of the comma-separated list


Now we get to enter our While() loop to determine if we're already logged in.


; Enter our loop to check if our member is already logged into this queue
exten => member_check,n,While($[${EXISTS(${thisQueueMember})}])                                 ; while we have a queue member...


This is where we check to see if the member at this position of the list is the
same as the device we're calling from. If it doesn't match, then we go to the
'check_next' priority label (where we increase our ${field} counter variable).
If it does match, then we continue on in the dialplan.

exten => member_check,n,GotoIf($["${thisQueueMember}" != "${thisActiveMember}"]?check_next)     ; if 'thisQueueMember' is not the
                                                                                                ;   same as our active peer, then
                                                                                                ;   check the next in the list of
                                                                                                ;   logged in queue members

If we continued on in the dialplan, then we set the ${logged_in} channel
variable to '1' which represents we're already logged into this queue. We then
exit the While() loop with the ExitWhile() dialplan application.

exten => member_check,n,Set(logged_in=1)                                                        ; if we got here, set as logged in
exten => member_check,n,ExitWhile()                                                             ;  then exit our loop



If we didn't match this peer name in the list, then we increase our ${field}
counter variable by one, update the ${thisQueueMember} channel variable and then
move back to the top of the loop for another round of checks.

exten => member_check,n(check_next),Set(field=$[${field} + 1])                                  ; if we got here, increase counter
exten => member_check,n,Set(thisQueueMember=${CUT(queueMembers,\,,${field})})                   ; get next member in the list
exten => member_check,n,EndWhile()                                                              ; ...end of our loop


And once we exit our loop, we determine whether we need to log our device in
or out of the queue.

; if not logged in, then login to this queue, otherwise, logout
exten => member_check,n,GotoIf($[${logged_in} = 0]?q_login,1:q_logout,1)        ; if not logged in, then login, otherwise, logout



The following two extensions are used to either log the device in or out of the
queue. We use the AddQueueMember() and RemovQueueMember() applications to login
or logout the device from the queue.

The first two arguments for AddQueueMember() and RemoveQueueMember() are 'queue'
and 'device'. There are additional arguments we can pass, and you can check
those out with 'core show application AddQueueMember' and 'core show
application RemoveQueueMember()'.

; ### Login queue member ###
exten => q_login,1,Verbose(2,Logging ${thisActiveMember} into the ${thisQueue} queue)
exten => q_login,n,AddQueueMember(${thisQueue},${thisActiveMember})                     ; login our active device to the queue 
                                                                                        ; requested
exten => q_login,n,Playback(silence/1)  ; answer the channel by playing one second of silence

; If the member was added to the queue successfully, then playback "Agent logged in", otherwise, state an error occurred
exten => q_login,n,ExecIf($["${AQMSTATUS}" = "ADDED"]?Playback(agent-loginok):Playback(an-error-has-occurred))
exten => q_login,n,Hangup()


; ### Logout queue member ###
exten => q_logout,1,Verbose(2,Logging ${thisActiveMember} out of ${thisQueue} queue)
exten => q_logout,n,RemoveQueueMember(${thisQueue},${thisActiveMember})
exten => q_logout,n,Playback(silence/1)
exten => q_logout,n,ExecIf($["${RQMSTATUS}" = "REMOVED"]?Playback(agent-loggedoff):Playback(an-error-has-occurred))
exten => q_logout,n,Hangup()


And that's it! Give it a shot and you should see console output similar to the
following which will login and logout your queue members to the queues you've
configured.

You can see there are already a couple of queue members logged into the sales
queue.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
   Members: 
      SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
      SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
   No Callers


Then we dial *100 to logout the active device from the sales queue.

*CLI>   == Using SIP RTP CoS mark 5
    -- Executing [*100@devices:1] Set("SIP/0004f2040001-00000012", "xtn=100") in new stack
    -- Executing [*100@devices:2] Goto("SIP/0004f2040001-00000012", "queueLoginLogout,member_check,1") in new stack
    -- Goto (queueLoginLogout,member_check,1)
    -- Executing [member_check@queueLoginLogout:1] Verbose("SIP/0004f2040001-00000012", "2,Logging queue member in or out of the request queue") in new stack
  == Logging queue member in or out of the request queue
    -- Executing [member_check@queueLoginLogout:2] Set("SIP/0004f2040001-00000012", "thisQueue=sales") in new stack
    -- Executing [member_check@queueLoginLogout:3] Set("SIP/0004f2040001-00000012", "queueMembers=SIP/0004f2040001,SIP/0004f2040002") in new stack
    -- Executing [member_check@queueLoginLogout:4] Set("SIP/0004f2040001-00000012", "thisActiveMember=SIP/0004f2040001") in new stack
    -- Executing [member_check@queueLoginLogout:5] GotoIf("SIP/0004f2040001-00000012", "0?q_login,1") in new stack
    -- Executing [member_check@queueLoginLogout:6] Set("SIP/0004f2040001-00000012", "field=1") in new stack
    -- Executing [member_check@queueLoginLogout:7] Set("SIP/0004f2040001-00000012", "logged_in=0") in new stack
    -- Executing [member_check@queueLoginLogout:8] Set("SIP/0004f2040001-00000012", "thisQueueMember=SIP/0004f2040001") in new stack
    -- Executing [member_check@queueLoginLogout:9] While("SIP/0004f2040001-00000012", "1") in new stack
    -- Executing [member_check@queueLoginLogout:10] GotoIf("SIP/0004f2040001-00000012", "0?check_next") in new stack
    -- Executing [member_check@queueLoginLogout:11] Set("SIP/0004f2040001-00000012", "logged_in=1") in new stack
    -- Executing [member_check@queueLoginLogout:12] ExitWhile("SIP/0004f2040001-00000012", "") in new stack
    -- Jumping to priority 15
    -- Executing [member_check@queueLoginLogout:16] GotoIf("SIP/0004f2040001-00000012", "0?q_login,1:q_logout,1") in new stack
    -- Goto (queueLoginLogout,q_logout,1)
    -- Executing [q_logout@queueLoginLogout:1] Verbose("SIP/0004f2040001-00000012", "2,Logging SIP/0004f2040001 out of sales queue") in new stack
  == Logging SIP/0004f2040001 out of sales queue
    -- Executing [q_logout@queueLoginLogout:2] RemoveQueueMember("SIP/0004f2040001-00000012", "sales,SIP/0004f2040001") in new stack
[Nov 12 12:08:51] NOTICE[11582]: app_queue.c:4842 rqm_exec: Removed interface 'SIP/0004f2040001' from queue 'sales'
    -- Executing [q_logout@queueLoginLogout:3] Playback("SIP/0004f2040001-00000012", "silence/1") in new stack
    -- <SIP/0004f2040001-00000012> Playing 'silence/1.slin' (language 'en')
    -- Executing [q_logout@queueLoginLogout:4] ExecIf("SIP/0004f2040001-00000012", "1?Playback(agent-loggedoff):Playback(an-error-has-occurred)") in new stack
    -- <SIP/0004f2040001-00000012> Playing 'agent-loggedoff.slin' (language 'en')
    -- Executing [q_logout@queueLoginLogout:5] Hangup("SIP/0004f2040001-00000012", "") in new stack
  == Spawn extension (queueLoginLogout, q_logout, 5) exited non-zero on 'SIP/0004f2040001-00000012'


And we can see that the device we loggd out by running 'queue show sales'.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (3s holdtime, 4s talktime), W:0, C:2, A:1, SL:0.0% within 0s
   Members: 
      SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
   No Callers


-------------------------------------------
| Pausing and Unpausing Members of Queues |
-------------------------------------------

Once we have our queue members logged in, it is inevitable that they will want
to pause themselves during breaks, and other short periods of inactivity. To do
this we can utilize the 'queue pause' and 'queue unpause' CLI commands.

We have two devices logged into the sales queue as we can see with the 'queue
show sales' CLI command.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
      SIP/0004f2040001 (dynamic) (Not in use) has taken no calls yet
   No Callers


We can then pause our devices with 'queue pause' which has the following format.

Usage: queue {pause|unpause} member <member> [queue <queue> [reason <reason>]]
	Pause or unpause a queue member. Not specifying a particular queue
	will pause or unpause a member across all queues to which the member
	belongs.

Lets pause device 0004f2040001 in the sales queue by executing the following.

*CLI> queue pause member SIP/0004f2040001 queue sales
paused interface 'SIP/0004f2040001' in queue 'sales' for reason 'lunch'


And we can see they are paused with 'queue show sales'.

*CLI> queue show sales
sales        has 0 calls (max unlimited) in 'rrmemory' strategy (0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0004f2040002 (dynamic) (Not in use) has taken no calls yet
      SIP/0004f2040001 (dynamic) (paused) (Not in use) has taken no calls yet
   No Callers

At this point the queue member will no longer receive calls from the system. We
can unpause them with the CLI command 'queue unpause member'.

*CLI> queue unpause member SIP/0004f2040001 queue sales 
unpaused interface 'SIP/0004f2040001' in queue 'sales'

And if you don't specify a queue, it will pause or unpause from all queues.

*CLI> queue pause member SIP/0004f2040001
paused interface 'SIP/0004f2040001'


Of course we want to allow the agents to pause and unpause themselves from their
devices, so we need to create an extension and some dialplan logic for that to
happen.

Below we've created the pattern patch _*0[01]! which will match on *00 and *01,
and will *also* match with zero or more digits following it, such as the queue
extension number.

So if we want to pause ourselves in all queues, we can dial *00; unpausing can
be done with *01. But if our agents just need to pause or unpause themselves
from a single queue, then we will also accept *00100 to pause in queue 100
(sales), or we can unpause ourselves from sales with *01100.


extensions.conf
---------------

; Allow queue members to pause and unpause themselves from all queues, or an individual queue.
;
; _*0[01]! pattern match will match on *00 and *01 plus 0 or more digits.
exten => _*0[01]!,1,Verbose(2,Pausing or unpausing queue member from one or more queues)
exten => _*0[01]!,n,Set(xtn=${EXTEN:3})                                                         ; save the queue extension to 'xtn'
exten => _*0[01]!,n,Set(thisQueue=${GLOBAL(QUEUE_${xtn})})                                      ; get the queue name if available
exten => _*0[01]!,n,GotoIf($[${ISNULL(${thisQueue})} & ${EXISTS(${xtn})}]?invalid_queue,1)  ; if 'thisQueue' is blank and the
                                                                                                ;  the agent dialed a queue exten,
                                                                                                ;  we will tell them it's invalid

The following line will determine if we're trying to pause or unpause. This is
done by taking the value dialed (e.g. *00100) and chopping off the first 2 
digits which leaves us with 0100, and then the :1 will return the next digit,
which in this case is '0' that we're using to signify that the queue member 
wants to be paused (in queue 100).

So we're doing the following with our EXTEN variable.

      ${EXTEN:2:1}
offset        ^ ^  length


Which causes the following.

 *00100
 ^^      offset these characters

 *00100
   ^     then return a digit length of one, which is digit 0


exten => _*0[01]!,n,GotoIf($[${EXTEN:2:1} = 0]?pause,1:unpause,1)                       ; determine if they wanted to pause
                                                                                        ;  or to unpause.


The following two extensions, pause & unpause, are used for pausing and
unpausing our extension from the queue(s). We use the PauseQueueMember() and
UnpauseQueueMember() dialplan applications which accept the queue name
(optional) and the queue member name. If the queue name is not provided, then it
is assumed we want to pause or unpause from all logged in queues.

; Unpause ourselves from one or more queues
exten => unpause,1,NoOp()
exten => unpause,n,UnpauseQueueMember(${thisQueue},SIP/${CHANNEL(peername)})            ; if 'thisQueue' is populated we'll pause in
                                                                                        ;   that queue, otherwise, we'll unpause in
                                                                                        ;   in all queues


Once we've unpaused ourselves, we use GoSub() to perform some common dialplan
logic that is used for pausing and unpausing. We pass three arguments to the
subroutine: 

 * variable name that contains the result of our operation
 * the value we're expecting to get back if successful
 * the filename to play

exten => unpause,n,GoSub(changePauseStatus,start,1(UPQMSTATUS,UNPAUSED,available))      ; use the changePauseStatus subroutine and
                                                                                        ;   pass the values for: variable to check,
                                                                                        ;   value to check for, and file to play
exten => unpause,n,Hangup()


And the same method is done for pausing.

; Pause ourselves in one or more queues
exten => pause,1,NoOp()
exten => pause,n,PauseQueueMember(${thisQueue},SIP/${CHANNEL(peername)})
exten => pause,n,GoSub(changePauseStatus,start,1(PQMSTATUS,PAUSED,unavailable))
exten => pause,n,Hangup()


Lets explore what happens in the subroutine we're using for pausing and
unpausing.


; ### Subroutine we use to check pausing and unpausing status ###
[changePauseStatus]
; ARG1:  variable name to check, such as PQMSTATUS and UPQMSTATUS (PauseQueueMemberStatus / UnpauseQueueMemberStatus)
; ARG2:  value to check for, such as PAUSED or UNPAUSED
; ARG3:  file to play back if our variable value matched the value to check for
;
exten => start,1,NoOp()
exten => start,n,Playback(silence/1)                                                     ; answer line with silence

The following line is probably the most complex. We're using the IF() function 
inside the Playback() application which determines which file to playback
to the user.

Those three values we passed in from the pause and unpause extensions could have
been something like:

 * ARG1 -- PQMSTATUS
 * ARG2 -- PAUSED
 * ARG3 -- unavailable

So when expanded, we'd end up with the following inside the IF() function.

  $["${PQMSTATUS}" = "PAUSED"]?unavailable:not-yet-connected

${PQMSTATUS} would then be expanded further to contain the status of our
PauseQueueMember() dialplan application, which could either be PAUSED or 
NOTFOUND. So if ${PQMSTATUS} returned PAUSED, then it would match what we're
looking to match on, and we'd then return 'unavailable' to Playback() that would
tell the user they are now unavailable. 

Otherwise, we'd get back a message saying "not yet connected" to indicate they
are likely not logged into the queue they are attempting to change status in.


; Please note that ${ARG1} is wrapped in ${  } in order to expand the value of ${ARG1} into
;   the variable we want to retrieve the value from, i.e. ${${ARG1}} turns into ${PQMSTATUS}
exten => start,n,Playback(${IF($["${${ARG1}}" = "${ARG2}"]?${ARG3}:not-yet-connected)})         ; check if value of variable
                                                                                                ;  matches the value we're looking
                                                                                                ;  for and playback the file we want
                                                                                                ;  to play if it does

If ${xtn} is null, then we just go to the end of the subroutine, but if it isn't
then we will play back "in the queue" followed by the queue extension number
indicating which queue they were (un)paused from.

exten => start,n,GotoIf($[${ISNULL(${xtn})}]?end)       ; if ${xtn} is null, then just Return()
exten => start,n,Playback(in-the-queue)                 ;   if not null, then playback "in the queue"
exten => start,n,SayNumber(${xtn})                      ;   and the queue number that we (un)paused from
exten => start,n(end),Return()                          ; return from were we came

--------------
| Conclusion |
--------------

You should now have a simple system that permits you to login and out of queues
you create in queues.conf, and to allow queue members to pause themselves within
one or more queues. There are a lot of dialplan concepts utilized in this
article, so you are encouraged to seek out additional documentation if any of
these concepts are a bit fuzzy for you. 

A good start is the doc/ subdirectory of the Asterisk sources, or the various
configuration samples files located in the configs/ subdirectory of your
Asterisk source code.