Device States I(Asterisk)

It is often useful to be able to determine the state of the devices that are attached to a telephone system. For example, a receptionist might require the ability to see the statuses of everyone in the office in order to determine whether somebody can take a phone call. Asterisk itself needs this same information. As another example, if you were building a call queue, Asterisk needs to know when an agent is available so that another call can be delivered. This topic discusses device state concepts in Asterisk, as well as how devices and applications use and access this information.

Device States

There are two types of devices that device states refer to: real devices and virtual devices. Real devices are telephony endpoints that can make or receive calls, such as SIP phones. Virtual devices include things that are inside Asterisk but provide useful state information. Table -1 lists the available virtual devices in Asterisk.

1a1

1a2

1a3

A device state is a simple one-to-one mapping to a device. Figure -1 shows this mapping.1a4

Figure -1. Device state mappings

Checking Device States

The DEVICE_STATE() dialplan function can be used to read the current state of a device. Here is a simple example of it being used in the dialplan:
exten => 7012,1,Answer()
; *** This line should not have any line breaks
same => n,Verbose(3,The state of SIP/0004F2060EB4 is
${DEVICE_STATE(SIP/0004F2060EB4)})
same => n,Hangup()
If we call extension 7012 from the same device that we are checking the state of, the following verbose message comes up on the Asterisk console:
— The state of SIP/0004F2060EB4 is INUSE

The following list includes the possible values that will come back from the DEVICE_STATE() function:
• UNKNOWN
• NOT_INUSE
• INUSE
• BUSY
• INVALID
• UNAVAILABLE
• RINGING
• RINGINUSE
• ONHOLD

Extension States

Extension states are another important concept in Asterisk. Extension states are what SIP devices subscribe to for presence information. The state of an extension is determined by checking the state of one or more devices. The list of devices that map to extension states is defined in the Asterisk dialplan, /etc/asterisk/extensions.conf, using a special hint directive. Figure -2 shows the mapping between devices, device states, and extension states.

1a5

Figure -2. Extension state mappings

Hints

To define an extension state hint in the dialplan, the keyword hint is used in place of a priority. Here is a simple example dialplan that relates to Figure -2:

[default]
exten => 1234,hint,SIP/phoneA&SIP/phoneB&SIP/phoneC
exten => 5555,hint,DAHDI/1
exten => 31337,hint,MeetMe:31337

Typically, hints are simply defined along with the rest of the extension. This next example adds simple extension entries for what would happen if each of these extensions were called: [default]
exten => 1234,hint,SIP/phoneA&SIP/phoneB&SIP/phoneC
exten => 1234,1,Dial(SIP/phoneA&SIP/phoneB&SIP/phoneC)
exten => 5555,hint,DAHDI/1
exten => 5555,1,Dial(DAHDI/1)

           exten => 31337,hint,MeetMe:31337
exten => 31337,1,MeetMe(31337,dM)

Checking Extension States

The easiest way to check the current state of an extension is at the Asterisk CLI. The core show hints command will show you all currently configured hints. Consider the following hint definition:

[phones]
exten => 7001,hint,SIP/0004F2060EB4

When core show hints is executed at the Asterisk CLI, the following output is presented when the device is currently in use:

*CLI> core show hints
-= Registered Asterisk Dial Plan Hints =-

             7001@phones                : SIP/0004F2060EB4            State:InUse         Watchers         0

—————– 1 hints registered

In addition to showing you the state of the extension, the output of core show hints also provides a count of watchers. A watcher is something in Asterisk that has subscribed to receive updates on the state of this extension. If a SIP phone subscribes to the state of
an extension, the watcher count will be increased.

Extension state can also be retrieved with a dialplan function, EXTENSION_STATE() . This function operates much like the DEVICE_STATE() function described in the preceding section. The following example shows an extension that will print the current state of another extension to the Asterisk console:

         exten => 7013,1,Answer()
same => n,Verbose(3,The state of 7001@phones is
${EXTENSION_STATE(7001@phones)})
same => n,Hangup()

When this extension is called, this is the verbose message that shows up on the Asterisk console:
— The state of 7001@phones is INUSE

The following list includes the possible values that may be returned from the EXTEN
SION_STATE() function:
• UNKNOWN
• NOT_INUSE
• INUSE
• BUSY
• UNAVAILABLE
• RINGING
• RINGINUSE
• HOLDINUSE
• ONHOLD

SIP Presence

Asterisk gives devices the capability to subscribe to extension state using the SIP protocol. This functionality is often referred to as BLF (Busy Lamp Field).

Asterisk Configuration

To get this working, hints must be defined in /etc/asterisk/extensions.conf. Additionally, some important options must be set in the configuration file for the SIP channel driver, which is /etc/asterisk/sip.conf. The following list discusses these options:callcounter

Enables/disables call counters. This must be enabled for Asterisk to be able to provide state information for SIP devices. This option may be set either in the [general] section or in peer-specific sections of sip.conf.

busylevel
Sets the number of calls that must be in progress for Asterisk to report that a device is busy. This option may only be set in peer-specific sections of sip.conf. By default, this option is not set. This means that Asterisk will report that a device is in use, but never busy.

call-limit
This option has been deprecated in favor of using the GROUP() and GROUP_COUNT() functions in the Asterisk dialplan. You may find older documentation that suggests that this option is required for SIP presence to work. That used to be the case, but this option has been replaced by the callcounter option for that purpose.

allowsubscribe
Allows you to disable support for subscriptions. If this option has not been set, subscriptions will be enabled. To disable subscription support completely, set allow subscribe to no in the [general] section of sip.conf.

subscribecontext
Allows you to set a specific context for subscriptions. Without this set, the context defined by the context option will be used. This option may be set either in the [general] section or in peer-specific sections of sip.conf.

notifyringing
Controls whether or not a notification will be sent when an extension goes into a ringing state. This option is set to yes by default. It only has an effect on subscriptions that use the dialog-info event package. This option can only be set globally in the [general] section of sip.conf.

notifyhold
Allows chan_sip to set SIP devices’ states to ONHOLD . This is set to yes by default. This option can only be set globally in the [general] section of sip.conf.

notifycid
Enables/disables sending of an inbound call’s caller ID information to an extension. This option applies to devices that subscribe to dialog-info+xml -based extension state notifications, such as Snom phones. Displaying caller ID information can be useful to help an agent decide whether to execute a pickup on an incoming call. This option is set to no by default.

Using Custom Device States

Asterisk provides the ability to create custom device states, which lends itself to the development of some interesting custom applications. We’ll start by showing the basic syntax for controlling custom device states, and then we’ll build an example that uses
them.

Custom device states all start with a prefix of Custom: . The text that comes after theprefix can be anything you want. To set or read the value of a custom device state, use the DEVICE_STATE() dialplan function. For example, to set a custom device state:
exten => example,1,Set(DEVICE_STATE(Custom:example)=BUSY)
Similarly, to read the current value of a custom device state:
exten => Verbose(1,The state of Custom:example is
${DEVICE_STATE(Custom:example)})

Custom device states can be used as a way to directly control the state shown on a device that has subscribed to the state of an extension. Just map an extension to a custom device state using a hint in the dialplan:
exten => example,hint,Custom:example

An Example

There are a number of interesting use cases for custom device states. In this section we will build an example that implements a custom “Do not disturb” (DND) button on a SIP phone. This same approach could be applied to many other things that you might like to be able to toggle at the touch of a button. For example, this approach could be used to let members know if they are currently logged into a queue or not. The first piece of the example is the hint in the dialplan. This is required so BLF can be configured on a SIP phone to subscribe to this extension. In this case, the phone must be configured to subscribe to the state of DND_7015 :
exten => DND_7015,hint,Custom:DND_7015

Next, we will create an extension that will be called when the user presses the key associated with the custom DND feature. It is interesting to note that this extension does nothing with audio. In fact, the user of the phone most likely will not even know that a call is placed when he presses the button. As far as the user is concerned, pressing that key simply turns on or off the light next to the button that reflects whether or not DND is enabled. The extension should look like this:
exten => DND_7015,1,Answer()
same => n,GotoIf($[“${DEVICE_STATE(Custom:DND_7015)}”=”BUSY”]?
turn_off:turn_on)
same => n(turn_off),Set(DEVICE_STATE(Custom:DND_7015)=NOT_INUSE)
same => n,Hangup()
same => n(turn_on),Set(DEVICE_STATE(Custom:DND_7015)=BUSY)
same => n,Hangup()
The final part of this example shows how the DND state is used in the dialplan. If DND is enabled, a message is played to the caller saying that the agent is unavailable. If it is disabled, a call will be made to a SIP device:

exten =>7015,1,GotoIf($[“${DEVICE_STATE(Custom:DND_7015)}”=”BUSY”]?busy:available)
same => n(available),Verbose(3,DND is currently off for 7015.)
same => n,Dial(SIP/exampledevice)
same => n,Hangup()

same => n(busy),Verbose(3,DND is on for 7015.)
same => n,Playback(vm-theperson)
same => n,Playback(digits/7&digits/0&digits/1&digits/5)
same => n,Playback(vm-isunavail)
same => n,Playback(vm-goodbye)
same => n,Hangup()

Example -1 shows the full set of extensions as they would appear in /etc/asterisk/ extensions.conf.

Example -1. Custom “Do not disturb” functionality using custom device states
;
; A hint so a phone can use BLF to signal the DND state.
;
exten => DND_7015,hint,Custom:DND_7015

;
; An extension to dial when the user presses the custom DND
; key on his phone. This will toggle the state and will result
; in the light on the phone turning on or off.
;
exten => DND_7015,1,Answer()
same => n,GotoIf($[“${DEVICE_STATE(Custom:DND_7015)}”=”BUSY”]?turn_off:turn_on)
same => n(turn_off),Set(DEVICE_STATE(Custom:DND_7015)=NOT_INUSE)

same => n,Hangup()
same => n(turn_on),Set(DEVICE_STATE(Custom:DND_7015)=BUSY)
same => n,Hangup()
;
; Example usage of the DND state.
;
exten => 7015,1,GotoIf($[“${DEVICE_STATE(Custom:DND_7015)}”=”BUSY”]?busy:available)
same => n(available),Verbose(3,DND is currently off for 7015.)
same => n,Dial(SIP/exampledevice)
same => n,Hangup()

same => n(busy),Verbose(3,DND is on for 7015.)
same => n,Playback(vm-theperson)
same => n,Playback(digits/7&digits/0&digits/1&digits/5)
same => n,Playback(vm-isunavail)
same => n,Playback(vm-goodbye)
same => n,Hangup()

Distributed Device States

Asterisk is primarily designed to run on a single system. However, as requirements for scalability increase, it is common for deployments to require multiple Asterisk servers. Since that has become increasingly common, some features have been added to make it easier to coordinate multiple Asterisk servers. One of those features is distributed device state support.

This means that if a device is on a call on one Asterisk server, the state of that device on all servers reflects that. To be more specific, the way this works is that every server knows the state of each device from the perspective of each server. Using this collection of states, each server will calculate what the overall device-state value is to report to the rest of Asterisk.

To accomplish distributed device state, some sort of messaging mechanism must be used for the servers to communicate with each other. Two such mechanisms are supported as of Asterisk 11: Corosync and XMPP.

Using Corosync

Corosync provides a set of APIs useful for clustered applications. Asterisk uses Corosync’s group communication API to distribute events among a cluster of Asterisk servers. Corosync is built in such a way that nodes must be located on the same high-speed, low- latency LAN. If your deployment is geographically distributed, you should use the XMPP-based distributed device state support.

Installation

Next, install Corosync. The easiest way to install it is from the distribution’s package management system. On RHEL:
$ sudo yum install corosync corosynclib corosynclib-devel
On Ubuntu:
$ sudo apt-get install corosync corosync-dev

If you installed Asterisk prior to installing Corosync, you will need to recompile and reinstall Asterisk to get Corosync support. Start by running the Asterisk configure script. The configure script is responsible for inspecting the system to find out which optional dependencies can be found so that the build system knows which modules can be built:
$ cd /path/to/asterisk
$ ./configure

After running the configure script, run the menuselect tool to ensure that Asterisk has been told to build the res_corosync module (this module can be found in the Resource Modules section of menuselect):
$ make menuselect
Finally, compile and install Asterisk:
$ make
$ sudo make install

Corosync configuration

Now that Corosync has been installed, it needs to be configured. There is a configuration file for Corosync that must be put in place. Check to see if /etc/corosync/corosync.conf exists. If not, there should be some samples in /etc/corosync to get you started.

While writing this section, the following configuration was used in corosync.conf for a simple setup between two virtual machines. This setup uses the udpu transport, which means that it’s doing unicast between the two nodes instead of multicast, which is the traditional Corosync transport:
# Please read the corosync.conf.5 manual page
compatibility: whitetank

totem {
version: 2
secauth: off
interface {
member {
memberaddr: 192.168.122.250
}
member {
memberaddr: 192.168.122.94
}
ringnumber: 0
bindnetaddr: 192.168.122.0
mcastport: 5405
ttl: 1
}
transport: udpu

                   }
logging {
fileline: off
to_logfile: yes
to_syslog: yes
debug: on
logfile: /var/log/corosync/corosync.log
debug: off
timestamp: on
logger_subsys {
subsys: AMF
debug: off
}
}

For detailed documentation on the options in this configuration file, see the associated manpage:

$ man corosync.conf

There is one other file that must be created for Corosync. Corosync must be told that applications running as the asteriskpbx user are allowed to connect to it. Place the following contents in a new file called /etc/corosync/uidgid.d/asterisk:

uidgid {
uid: asteriskpbx
gid: asteriskpbx
}

Once configuration has been completed, start the Corosync service:

       $ sudo service corosync start

Asterisk configuration

The res_corosync module for Asterisk has a single configuration file, /etc/asterisk/ res_corosync.conf. One short section is required in this file to enable distributed device state in a Corosync cluster. Place the following contents in the /etc/asterisk/res_coro sync.conf file:

            [general]
publish_event = device_state
subscribe_event = device_state

An Asterisk CLI command can be used to ensure that this configuration has been loaded properly:
*CLI> corosync show config
=============================================================
=== res_corosync config =====================================
=============================================================
===
=== ==> Publishing Event Type: device_state
=== ==> Subscribing to Event Type: device_state
=== ==> Publishing Event Type: ping
=== ==> Subscribing to Event Type: ping
===
=============================================================
Another useful Asterisk CLI command provided by the res_corosync module is used
to list the members of the Corosync cluster:
*CLI> corosync show members
=============================================================
=== Cluster members =========================================
=============================================================
===
=== Node 1
=== –> Group: asterisk
=== –> Address 1: 192.168.122.94
=== Node 2
=== –> Group: asterisk
=== –> Address 1: 192.168.122.250
===
=============================================================

Testing device state changes

Now that you’ve set up and configured distributed device state using Corosync, there are some simple tests that can be done using custom device states to ensure that device states are being communicated among the servers. Start by creating a test hint in the Asterisk dialplan, /etc/asterisk/extensions.conf:
[devstate_test]
exten => foo,hint,Custom:abc

Now, you can adjust the custom device state from the Asterisk CLI using the dialplan set global CLI command and then check the state on each server using the core show hints command. For example, we can use this command to set the state on one server:

            pbx1*CLI> dialplan set global DEVICE_STATE(Custom:abc) INUSE
— Global variable ‘DEVICE_STATE(Custom:abc)’ set to ‘INUSE’
and then, check the state on another server using this command:
*CLI> core show hints
-= Registered Asterisk Dial Plan Hints =-
foo@devstatetest :          Custom:abc             State:InUse              Watchers           0
If you would like to dive deeper into the processing of distributed device state changes, some useful debug messages can be enabled. First, enable debug on the Asterisk console in /etc/asterisk/logger.conf. Then, enable debugging at the Asterisk CLI:
*CLI> core set debug 1

With the debug output enabled, you will see some messages that show how Asterisk is processing each state change. When the state of a device changes on one server, Asterisk checks the state information it has for that device on all servers and determines the overall device state. The following examples illustrate:

*CLI> dialplan set global DEVICE_STATE(Custom:abc) NOT_INUSE

            — Global variable ‘DEVICE_STATE(Custom:abc)’ set to ‘NOT_INUSE’

[Nov 13 13:27:12] DEBUG[14801]: devicestate.c:652
handle_devstate_change: Processing device state change for ‘Custom:abc’
[Nov 13 13:27:12] DEBUG[14801]: devicestate.c:602
process_collection: Adding per-server state of ‘Not in use’ for ‘Custom:abc’
[Nov 13 13:27:12] DEBUG[14801]: devicestate.c:602
process_collection: Adding per-server state of ‘Not in use’ for ‘Custom:abc’
[Nov 13 13:27:12] DEBUG[14801]: devicestate.c:609
process_collection: Aggregate devstate result is ‘Not in use’ for ‘Custom:abc’
[Nov 13 13:27:12] DEBUG[14801]: devicestate.c:631
process_collection: Aggregate state for device ‘Custom:abc’ has changed to
‘Not in use’

*CLI> dialplan set global DEVICE_STATE(Custom:abc) INUSE

                   — Global variable ‘DEVICE_STATE(Custom:abc)’ set to ‘INUSE’

[Nov 13 13:29:30] DEBUG[14801]: devicestate.c:652 handle_devstate_change:
Processing device state change for ‘Custom:abc’
[Nov 13 13:29:30] DEBUG[14801]: devicestate.c:602 process_collection:
Adding per-server state of ‘Not in use’ for ‘Custom:abc’
[Nov 13 13:29:30] DEBUG[14801]: devicestate.c:602 process_collection:
Adding per-server state of ‘In use’ for ‘Custom:abc’
[Nov 13 13:29:30] DEBUG[14801]: devicestate.c:609 process_collection:
Aggregate devstate result is ‘In use’ for ‘Custom:abc’
[Nov 13 13:29:30] DEBUG[14801]: devicestate.c:631 process_collection:
Aggregate state for device ‘Custom:abc’ has changed to ‘In use’

Using XMPP

The eXtensible Messaging and Presence Protocol (XMPP), formerly (and still commonly) known as Jabber, is a communications protocol standardized by the Internet Engineering Task Force (IETF). It is most commonly known as an IM protocol, but it can be used for a number of other interesting applications as well. The XMPP Standards Foundation (XSF) works to standardize extensions to the XMPP protocol. One such extension, referred to as PubSub, provides a publish/subscribe mechanism. Asterisk has the ability to use XMPP PubSub to distribute device state information. One of the nice things about using XMPP this way is that it works very well for geographically distributed Asterisk servers.

Installation

To distribute device states using XMPP, you will need an XMPP server that supports PubSub. One such server that has been successfully tested against Asterisk is Tigase. The Tigase website has instructions for installing and configuring the Tigase server. We suggest that you follow those instructions (or the instructions provided for whatever other server you may choose to use) and come back to this book when you’re ready to work on the Asterisk-specific parts.

On the Asterisk side of things, you will need to ensure that you have installed the res_jabber module. You can check to see if it is already loaded at the Asterisk CLI:
*CLI> module show like jabber

Module                                           Description                                         user count
res_jabber.so                                   AJI – Asterisk Jabber Interface                 0
1 modules loaded

If you are using a custom /etc/asterisk/modules.conf file that lists only specific modules to be loaded, you can also check the filesystem to see if the module was compiled and installed:

$ cd /usr/lib/asterisk/modules
$ ls -l res_jabber.so

-rwxr-xr-x 1 root root 837436
2010-11-12 15:33 res_jabber.so

If you do not yet have res_jabber installed, you will need to install the iksemel andOpenSSL libraries. Then, you will need to recompile and reinstall Asterisk. Start by running the Asterisk configure script, which is responsible for inspecting the system and locating optional dependencies, so that the build system knows which modules can be built:
$ cd /path/to/asterisk
$ ./configure

After running the configure script, run the menuselect tool to ensure that Asterisk has been told to build the res_jabber module. This module can be found in the Resource Modules section of menuselect:
$ make menuselect
Finally, compile and install Asterisk:
$ make
$ sudo make install

Creating XMPP accounts

Unfortunately, Asterisk is currently not able to register new accounts on an XMPP server. You will have to create an account for each server via some other mechanism. The method we used while testing was to complete account registration via an XMPP client such as Pidgin. After account registration is complete, the XMPP client is no longer needed. For the rest of the examples, we will use the following two buddies, both of which are on the server jabber.shifteight.org:
• server1@jabber.shifteight.org/astvoip1
• server2@jabber.shifteight.org/astvoip2

Asterisk configuration

The /etc/asterisk/jabber.conf file will need to be configured on each server. We will show the configuration for a two-server setup here, but the configuration can easily be expanded to more servers as needed. Example -2 shows the contents of the configuration file for server 1, and Example -3 shows the contents of the configuration file for server 2. For additional information on the jabber.conf options associated with distributed device states, see the configs/jabber.conf.sample file that is included in the Asterisk source tree.

Example 14-2. jabber.conf for server1

[general]
autoregister = yes

[asterisk]
type = client
serverhost = jabber.shifteight.org
pubsub_node = pubsub.jabber.shifteight.org
username = server1@jabber.shifteight.org/astvoip1
secret = mypassword
distribute_events = yes
status = available
usetls = no
usesasl = yes
buddy = server2@jabber.shifteight.org/astvoip2

Example -3. jabber.conf for server2

[general]
autoregister = yes
[asterisk]
type = client
serverhost = jabber.shifteight.org
pubsub_node = pubsub.jabber.shifteight.org
username = server2@jabber.shifteight.org/astvoip2
secret = mypassword
distribute_events = yes
status = available
usetls = no
usesasl = yes
buddy = server1@jabber.shifteight.org/astvoip1

Testing

To ensure that everything is working properly, start by doing some verification of the jabber.conf settings on each server. A couple of relevant Asterisk CLI commands can be used here. The first is the jabber show connected command, which will verify that Asterisk has successfully logged in with an account on the jabber server. The output of this command on the first server shows:

*CLI> jabber show connected

Jabber Users and their status:
User: server1@jabber.shifteight.org/astvoip1  -Connected
—-
Number of users: 1

Meanwhile, if jabber show connected is executed on the second server, it shows:

*CLI> jabber show connected
Jabber Users and their status:
User: server2@jabber.shifteight.org/astvoip2
—-
Number of users: 1                                 – Connected

The next useful command for verifying the setup is jabber show buddies. This command allows you to verify that the other server is correctly listed on your buddy list. It also lets you see if the other server is seen as currently connected. If you were to run this command on the first server without Asterisk currently running on the second server, the output would look like this:

*CLI> jabber show buddies
Jabber buddy lists
Client: server1@jabber.shifteight.org/astvoip1
Buddy: server2@jabber.shifteight.org
Resource: None
Buddy: server2@jabber.shifteight.org/astvoip2
Resource: None

Next, start Asterisk on the second server and run jabber show buddies on that server. The output will contain more information, since the second server will see the first server online:

*CLI> jabber show buddies
Jabber buddy lists
Client: server2@jabber.shifteight.org/astvoip2
Buddy: server1@jabber.shifteight.org
Resource: astvoip1
node: http://www.asterisk.org/xmpp/client/caps
version: asterisk-xmpp
Jingle capable: yes
Status: 1
Priority: 0
Buddy: server1@jabber.shifteight.org/astvoip1
Resource: None

At this point, you should be ready to test out the distribution of device states.