Asterisk and Skinny Phones

One look on eBay and it's very obvious that there are a lot of Cisco 7940 and 7960 phones out there! Though they are missing some of the features of some of the newer phones, they aren't terrible phones. Their SIP implementation is somewhat lacking and not especially convenient for doing busy lamp fields and paging, but the Skinny version of the code seems to be better at that. If only there were some way to use the Skinny protocol (aka SCCP) with Asterisk. But, there is, and we're going to go through that entire process, building everything from scratch on top of a CentOS 6.5 minimal install. There are a number of steps, but the end result will be worth it!

We're going to use the latest version of Asterisk 1.8. The 1.8 branch is more mature and stable, and hopefully will result in a more robust final product. Let's start by getting CentOS installed. We are going to be using a virtual machine, but virtual or physical, the basic steps will be the same.

Install CentOS 6.5

CentOS can basically be considered a re-badged version of Red Hat Enterprise Linux. That means that it is quite stable and reliable, though some of the packages are no where near the most current releases.

CentOS is available both in 32- and 64-bit versions. At this point, I firmly recommend the 32-bit version. Sure, it is completely possible to build everything with the 64-bit version, but there are a lot more manual steps and no real advantage right now for going 64-bit.

Visit www.centos.org and download the minimal ISO. Make sure it is a 32-bit version, which can be identified by having i386 in it's file name, such as CentOS-6.5-i386-minimal.iso. Burn this file to a DVD. This will result in an install that is command line only; if you wish to have X Windows and other graphical utilities, you'll want to grab a different ISO and install appropriately.

Your server will need at least 20GB of disk space, and 1024MB of RAM for decent performance and a graphical install (which will allow you to alter the disk layout). Boot off the DVD just burnt and start the install process.

When asked, assign a hostname that means something to you. PBX is a good name. At this point, you'll also want to click on Configure Network and set your network settings appropriately. You'll definitely want to enable your network card, and I recommend assigning a static IP address.

By default, CentOS will install logical volumes. Personally, I feel they only complicate things, so I don't use them. I keep things simple and create these three partitions, in this order:

  1. 500MB for /boot formatted ext4
  2. 2GB formatted swap
  3. the remainder for / formatted ext4

Once the disk is laid out, it will automatically proceed to do the minimal install.

Updating and Installing packages

Once CentOS is completely installed, you'll be given a login prompt. Use root as the userid, and when prompted, enter the root password that you provided during the CentOS install. You will now be at a Linux command prompt. Don't worry, it's still easy!

The first thing we want to do is make sure that our base code is up to date, and then we'll make sure a couple of package groups are installed, and then we'll install some individual packages. CentOS will automatically check for and include dependent packages, so this is a really easy step!


# yum update
# yum groupinstall core
# yum groupinstall base
# yum install gcc gcc-c++ wget bison mysql-devel mysql-server php php-mysql php-process php-pear php-mbstring tftp-server httpd make ncurses-devel libtermcap-devel sox newt-devel libxml2-devel libtiff-devel php-gd audiofile-devel gtk2-devel subversion kernel-devel selinux-policy sqlite-devel openssl-devel libtool-ltdl-devel unixODBC unixODBC-devel mysql-connector-odbc logwatch dhcp

Pear DB Package

We will install FreePBX later to help simplify management, and FreePBX needs the Pear DB package. There will be an warning/error that can be ignored.


# pear install db

Firewall

These instructions assume that your CentOS machine is going to be behind some firewall, and thus we are keeping the firewalling very, very simple. Make wise choices regarding firewall construction for your environment.

We want iptables to be running, but we want to simplify it so that we can readily connect. We are going to get rid of the firewall rules, and the next time the machine is restarted, the firewall will accept all connections, but have functionality to allow fail2ban to work. For a simple, but more secure firewall example using iptables, check this entry.


# rm -f /etc/sysconfig/iptables

Selinux

Again, to keep things simple, we are going to disable selinux. Edit the /etc/selinux/config file so that the SELINUX= line says SELINUX=disabled. The complete file will look similar to this.


# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these two values:
#     targeted - Targeted processes are protected,
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

php.ini Settings

The file /etc/php.ini contains a lot of settings, and we need to massage a couple of them. The easiest thing to do is open the file in vi and search for the variable name and set the values as shown below. If a line is commented out (prefixed with a semicolon), you will need to uncomment that line.


date.timezone=America/New_York   ; set appropriate for your location
upload_max_filesize = 100M
post_max_size = 105M

Adjustment to Apache

By default, the Apache web server will run as user apache. We need to create a new user, asterisk, and then update Apache so that it will run as our asterisk user. Execute the following.


# adduser asterisk -M -c "Asterisk User"

And then update the following lines in /etc/httpd/conf/httpd.conf, similarly to how php.ini was updated.


User asterisk
Group asterisk

tftp Server

The Cisco 7940 and 7960 phones get a lot of their information via tftp, so we're going to need to have a server running. We will need to update the /etc/xinetd.d/tftp file such that disable = no, as shown here:


service tftp
{
        socket_type             = dgram
        protocol                = udp
        wait                    = yes
        user                    = root
        server                  = /usr/sbin/in.tftpd
        server_args             = -s /var/lib/tftpboot
        disable                 = no 
        per_source              = 11
        cps                     = 100 2
        flags                   = IPv4
}

We also need to fix a symbolic link. Some of our software will be expecting the directory for tftp to be /tftpboot, but CentOS puts it at /var/lib/tftpboot. Here's how we fix that:


# ln -s /var/lib/tftpboot /tftpboot

Setting Services to Start

We have added and configured quite a bit already. Now, we are ready to reboot, but let's make sure everything is set to start like we want. Follow these steps. The last step will cause the system to reboot.


# chkconfig httpd on
# chkconfig mysqld on
#
# shutdown -r now

Firmware Files for Cisco Phones

Our Cisco phones will need to be running the SCCP code load, and not SIP. The best place to find the firmware files is from cisco.com, but there are other sources available on the Internet (e.g. ftp://ftp.itl.ua/pub/cisco/ip-7900ser/ ). The most recent version is typically what is recommended, but we have had good success with the 8.0.6 SCCP code, which is what we will use in our example. Extract the files if required and you should have the following:

  • P00308000600.bin
  • P00308000600.loads
  • P00308000600.sb2
  • P00308000600.sbn

Place these files in your /tftpboot directory. Create the following as XMLDefault.cnf.xml and place it in the /tftpboot directory. Note that the <loadInformation> needs to reflect the code files you just placed. No extension, just the base filename.


<Default>
<callManagerGroup>
<members>
<member priority=”0″>
<callManager>
<ports>
<ethernetPhonePort>2000</ethernetPhonePort>
<mgcpPorts>
<listen>2427</listen>
<keepAlive>2428</keepAlive>
</mgcpPorts>
</ports>
<processNodeName></processNodeName>
</callManager>
</member>
</members>
</callManagerGroup>
<loadInformation>P00308000600</loadInformation>
<authenticationURL></authenticationURL>
<directoryURL></directoryURL>
<idleURL></idleURL>
<informationURL></informationURL>
<messagesURL></messagesURL>
<servicesURL></servicesURL>
</Default>

Ring Tones

While we're in the neighborhood, we might as well provide some additional ring tones. Simply download the files specified here and place them in your /tftpboot directory. wget is a good utility for downloading these files. Once you have downloaded all of the files, create RINGLIST.XML as follows in the /tftpboot folder.


<CiscoIPPhoneRingList>
<Ring>
  <DisplayName>Are You There</DisplayName> 
  <FileName>AreYouThere.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Beep Beep</DisplayName> 
  <FileName>beepbeep.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Caramba</DisplayName> 
  <FileName>caramba.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Clock Shop</DisplayName> 
  <FileName>ClockShop.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Cylon</DisplayName> 
  <FileName>cylon.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Flint Phone</DisplayName> 
  <FileName>FlintPhone.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Piano1</DisplayName> 
  <FileName>Piano1.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Piano2</DisplayName> 
  <FileName>Piano2.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>Ringer1</DisplayName> 
  <FileName>ringer1.pcm</FileName> 
</Ring>
<Ring>
  <DisplayName>Ringer2</DisplayName> 
  <FileName>ringer2.pcm</FileName> 
</Ring>
<Ring>
  <DisplayName>Ringer4</DisplayName> 
  <FileName>ringer4.pcm</FileName> 
</Ring>
<Ring>
  <DisplayName>Ringer6</DisplayName> 
  <FileName>ringer6.pcm</FileName> 
</Ring>
<Ring>
  <DisplayName>Sax1</DisplayName> 
  <FileName>Sax1.raw</FileName> 
</Ring>
<Ring>
  <DisplayName>TV 24</DisplayName> 
  <FileName>tv24.raw.wav</FileName> 
</Ring>
</CiscoIPPhoneRingList>

SIPDefault.cnf

If our Cisco phones currently have a SIP code load on them, they are going to look for the SIPDefault.cnf file when they boot. This is the file that we will use to tell the phone to update its firmware to the SCCP version. Create SIPDefault.cnf in /tftpboot with these simple contents. Note how image_version matches the SCCP firmware that we have stored in /tftpboot.


image_version: P00308000600

dialplan.xml

There is one more important file that we need to put in our /tftpboot directory, the dialplan.xml file. This file provides patterns that when the phone recognizes that these patterns have been entered via the keypad, will cause the phone to automatically dial. Of course, the dial soft key can always be used to force the dial. *97 and *98 are voicemail access number for FreePBX.


<DIALTEMPLATE>
  <TEMPLATE MATCH="2.." TIMEOUT="1" />
  <TEMPLATE MATCH="......." TIMEOUT="1" />
  <TEMPLATE MATCH=".........." TIMEOUT="1" />
  <TEMPLATE MATCH="1.........." TIMEOUT="0" />
  <TEMPLATE MATCH="*98" TIMEOUT="0" />
  <TEMPLATE MATCH="*97" TIMEOUT="0" />
</DIALTEMPLATE>

Let's make sure that the Asterisk User is the one who owns all the files in the tftpboot folder. If files get changed or added in the future, it is always a good idea to make sure the Asterisk User is the owner of both the directory and all the files.


# cd /var/lib/tftpboot
# chown asterisk:asterisk . *

DHCP Server

In our test scenario, we are going to use a DHCP server to hand out addresses to our Cisco phones. Following is the /etc/dhcp/dhcpd.conf file that we are using. You, of course, will need to adjust the values to your own scenario. Not the least of importance is the address associated with tftp-server-name, which tells the phone where to look for all the tftp files that we just placed.


#
# DHCP Server Configuration file.
#   see /usr/share/doc/dhcp*/dhcpd.conf.sample
#   see 'man 5 dhcpd.conf'
#
allow unknown-clients;
allow booting;
allow bootp;
ddns-update-style none;

subnet 192.168.86.0 netmask 255.255.255.0 {
  class "cisco-7940g" {
    match if option vendor-class-identifier="Cisco Systems, Inc. IP Phone CP-7940G\x00";
    option tftp-server-name "192.168.86.10";
  }

  range 192.168.86.32 192.168.86.223;
  option subnet-mask 255.255.255.0;
  option domain-name "tridata.us";
  option domain-name-servers 192.168.86.254;
  option routers 192.168.86.254;
  option ntp-servers 192.168.86.254;
  default-lease-time 86400;
  max-lease-time 86400;
  authoritative;
}

Of course, we now need to start our DHCP server and make sure it will automatically start the next time our server reboots. Make sure this server won't conflict with another DHCP server on your network.


# /etc/init.d/dhcpd start
# chkconfig dhcpd on

Get and Install DAHDI

Though we may not use DAHDI, we need to install it as a couple of pieces of Asterisk like for it to be around. It's pretty simple, though.


# cd ~
# wget http://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-current.tar.gz
# cd /usr/src
# tar xzvf ~/dahdi*
# cd dahdi*
# make
# make install
# make config
# /etc/init.d/dahdi start

Get and Install Asterisk

In this example, we will only be making Internet phone connection, so we will not be compiling libpri or dahdi. If your scenario needs them, get them and compile them. Your card(s) may have specific versions required, so check your documentation.

Visit www.asterisk.org to find the version of Asterisk to download. I am choosing the latest version of Asterisk 1.8.


# cd /usr/src
# wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-1.8-current.tar.gz
# tar xzvf asterisk-1.8-current.tar.gz
# cd asterisk-1.8.28.2
# ./configure
# ./contrib/scripts/get_mp3_source.sh
# make menuselect

On the make menuselect, ensure that the following packages are selected.

  • Applications
    • app_zapateller
  • Core Sound Packages
    • CORE-SOUNDS-EN-ULAW
  • Music On Hold File Packages
    • MOH-OPSOUND-ULAW
  • Extras Sound Packages
    • EXTRA-SOUNDS-EN-ULAW

Make sure to select "Save & Exit" to exit make menuselect. Continue the building of Asterisk with these commands.


make install
make config

Music on Hold

Asterisk and FreePBX put their music on hold files in slightly different directories. We will make a symbolic link so that everything ends up in the same directory.


# ln -s /var/lib/asterisk/moh /var/lib/asterisk/mohmp3

Chan SCCP B

This is the magic piece of code that will allow us to use Cisco phones with the Skinny protocol with Asterisk! We want the 4.2 branch of code, version 5517 or better, which at the current time, is only available via the trunk as there has not yet been a release of the 4.2 code. I recommend visiting the chan_sccp_b homepage to find if this is still the case. We will use svn to check out a copy of the trunk, because the released versions don't have some features we want.


# cd /usr/src
# 
# svn checkout svn://svn.code.sf.net/p/chan-sccp-b/code/trunk chan-sccp-b-code
# cd chan-sccp-b-code

We want to make a couple of changes to the source code before we proceed. These changes have to do with style of ring, and we will take advantage of these changes later to have distinctive ringing. We need to open the file src/sccp_pbx.c and modify a single line in the procedure sccp_pbx_call(). We will be changing


        sccp_channel_display_callInfo(c);

        if (!c->ringermode) {
                c->ringermode = SKINNY_RINGTYPE_OUTSIDE;
        }
        boolean_t isRinging = FALSE;
        boolean_t hasDNDParticipant = FALSE;

to


        sccp_channel_display_callInfo(c);

        if (!c->ringermode) {
                c->ringermode = SKINNY_RINGTYPE_INSIDE;  /* vlm */
        }
        boolean_t isRinging = FALSE;
        boolean_t hasDNDParticipant = FALSE;

I like to put my initials on any line of source code that I change, so that I can locate it more easily later.

Similarly, we will want to make a change to src/pbx_impl/ast/ast108.c in the procedure sccp_wrapper_asterisk18_request() from


        if (alert_info && !sccp_strlen_zero(alert_info)) {
                sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: Found ALERT_INFO=%s\n", alert_info);
                if (strcasecmp(alert_info, "inside") == 0) {   
                        ringermode = SKINNY_RINGTYPE_INSIDE;
                } else if (strcasecmp(alert_info, "feature") == 0) {
                        ringermode = SKINNY_RINGTYPE_FEATURE;
                } else if (strcasecmp(alert_info, "silent") == 0) {
                        ringermode = SKINNY_RINGTYPE_SILENT;
                } else if (strcasecmp(alert_info, "urgent") == 0) {
                        ringermode = SKINNY_RINGTYPE_URGENT;
                }
        }

to


        if (alert_info && !sccp_strlen_zero(alert_info)) {
                sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: Found ALERT_INFO=%s\n", alert_info);
                if (strcasecmp(alert_info, "outside") == 0) {          /* vlm */
                        ringermode = SKINNY_RINGTYPE_OUTSIDE;          /* vlm */
                } else if (strcasecmp(alert_info, "inside") == 0) {    /* vlm */
                        ringermode = SKINNY_RINGTYPE_INSIDE;
                } else if (strcasecmp(alert_info, "feature") == 0) {
                        ringermode = SKINNY_RINGTYPE_FEATURE;
                } else if (strcasecmp(alert_info, "silent") == 0) {
                        ringermode = SKINNY_RINGTYPE_SILENT;
                } else if (strcasecmp(alert_info, "urgent") == 0) {
                        ringermode = SKINNY_RINGTYPE_URGENT;
                }
        }

Moving down a few lines in the same file, we are going to change from


                                } else if (!strncasecmp(optv[opti], "aa=", 3)) {
                                        optv[opti] += 3;
                                        pbx_log(LOG_NOTICE, "parsing aa\n");
                                        if (!strncasecmp(optv[opti], "1w", 2)) {
                                                autoanswer_type = SCCP_AUTOANSWER_1W;
                                                optv[opti] += 2;
                                        } else if (!strncasecmp(optv[opti], "2w", 2)) {
                                                autoanswer_type = SCCP_AUTOANSWER_2W;
                                                pbx_log(LOG_NOTICE, "set aa to 2w\n");
                                                optv[opti] += 2;
                                        }
                                }

to


                                } else if (!strncasecmp(optv[opti], "aa=", 3)) {
                                        optv[opti] += 3;
                                        pbx_log(LOG_NOTICE, "parsing aa\n");
                                        if (!strncasecmp(optv[opti], "1w", 2)) {
                                                autoanswer_type = SCCP_AUTOANSWER_1W;
                                                ringermode = SKINNY_RINGTYPE_SILENT; /* vlm */
                                                optv[opti] += 2;
                                        } else if (!strncasecmp(optv[opti], "2w", 2)) {
                                                autoanswer_type = SCCP_AUTOANSWER_2W;
                                                pbx_log(LOG_NOTICE, "set aa to 2w\n");
                                                optv[opti] += 2;
                                        }
                                }

Now we are ready to build the SCCP channel driver!


# ./configure
# make
# make install

The SCCP channel driver needs some of its own tables in MySQL, so we need to get those added.


# mysql -u root asterisk <conf/mysql-v5.sql

modules.conf Adjustments

By default when installing Asterisk, modules will get autoloader, which keeps things simple. Make sure your /etc/asterisk/modules.conf has the following lines in it.


preload => res_odbc.so
preload => res_config_odbc.so

noload => chan_skinny.so  

FreePBX Install

Downloading and installing FreePBX is different from most packages, but follow the steps and you'll do fine.


# cd /usr/src
# export VER_FREEPBX=2.11
# svn co http://www.freepbx.org/v2/svn/freepbx/branches/${VER_FREEPBX} freepbx
# cd freepbx
# mysqladmin -u root create asterisk
# mysql -u root asterisk <SQL/newinstall.sql
# mysqladmin -u root create asteriskcdrdb
# mysql -u root asteriskcdrdb <SQL/cdr_mysql_table.sql

the following is the default password, but you can make it anything you like

# export ASTERISK_DB_PW=amp109
# mysql -u root -e "GRANT ALL PRIVILEGES ON asterisk.* TO asteriskuser@localhost IDENTIFIED BY '${ASTERISK_DB_PW}';"
# mysql -u root -e "GRANT ALL PRIVILEGES ON asteriskcdrdb.* TO asteriskuser@localhost IDENTIFIED BY '${ASTERISK_DB_PW}';"
# mysql -u root -e "flush privileges;"
# chown asterisk. /var/run/asterisk
# chown -R asterisk. /etc/asterisk /var/{lib,log,spool}/asterisk /usr/lib/asterisk /var/www
# ./start_asterisk start

the following will ask several question.  Take the default if you are unsure.

# ./install_amp

It's good to have good records, so we'll install a piece which will allow us to put our CDR, call detail records, into a database. This requires the creation of file /etc/odbc.ini and it will look as follows.


[MySQL-asteriskcdrdb]
Description     = MySQL ODBC Driver
Driver          = MySQL
Socket          = /var/lib/mysql/mysql.sock
Server          = localhost
Database        = asteriskcdrdb
Option          = 3

[MySQL-asterisk]
Description     = MySQL ODBC Driver
Driver          = MySQL
Socket          = /var/lib/mysql/mysql.sock
Server          = localhost
Database        = asterisk
Option          = 3

Once installed, and with MySQL running, this can be tested with odbcinst -s -q and isql -v MySQL-asteriskcdrdb DBUSERNAME DBPASSWORD.


# odbcinst -s -q
[MySQL-asteriskcdrdb]
# isql -v MySQL-asteriskcdrdb asteriskuser amp109
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL> quit
#

Now we need to create a file which will tell Asterisk to put CDR records into our MySQL database. The file will be created as /etc/asterisk/cdr_adaptive_odbc.conf and will look like what follows. If you look at CDR Reports in FreePBX and there are no calls, this is very likely the culprit.


[first]
connection=asteriskcdrdb
table=cdr
alias start => calldate

Following is a fairly minimal set of modules to get things up and running. These modules can be downloaded with amportal admin modadmin download <module name> and subsequently installed with amportal admin modadmin install <module name>. Alternately, you can do amportal admin modadmin install installall or use a modern brower and surf your admin site at http://pbx.machine.ip.address/admin and install the proper modules via Admin/Module Admin. There is an order to modules, make sure framework and core are among the first. A trick I like to use is to create a file with a single module per line and then cat the file and pipe it to xargs -n 1 amportal admin modadmin download to download the modules and subsequently repeat the procedure, changing the download parameter to install, to install the modules.


framework
core
announcement
asteriskinfo
backup
blacklist
builtin
callforward
callrecording
callwaiting
cdr
conferences
customappsreg
dashboard
daynight
donotdisturb
fax
featurecodeadmin
findmefollow
fw_ari
iaxsettings
infoservices
ivr
logfiles
music
outroutemsg
paging
parking
recordings
sipsettings
userpaneltab
voicemail
queues

Queue Module Patch

Once the modules are all installed, we want to make a change to the source code of the queues module so that it will generate ring hints for our SCCP phones. We won't get into ring hints here, we'll just be using them. Let start by patching /var/www/html/admin/modules/queues/functions.inc/dialplan.php around line 411 from


                                // generate with #exec if we are using dynamic hints
                                //
                                if ($amp_conf['DYNAMICHINTS']) {
                                        $ext->addExec($c,$amp_conf['AMPBIN'].'/generate_queue_hints.php '.$que_code);
                                } else {
                                        foreach ($device_list as $device) {
                                                if ($device['tech'] == 'sip' || $device['tech'] == 'iax2') {
                                                        $ext->add($c, $que_code . '*' . $device['id'], '', new ext_goto('start','s','app-all-queue-toggle'));
                                                        if ($device['user'] != '' &&  isset($qc[$device['user']])) {
                                                                $hlist = 'Custom:QUEUE' . $device['id'] . '*' . implode('&Custom:QUEUE' . $device['id'] . '*', $qc[$device['user']]);
                                                                $ext->addHint($c, $que_code . '*' . $device['id'], $hlist);
                                                        }
                                                }
                                        }
                                }

to


                                // generate with #exec if we are using dynamic hints
                                //
                                if ($amp_conf['DYNAMICHINTS']) {
                                        $ext->addExec($c,$amp_conf['AMPBIN'].'/generate_queue_hints.php '.$que_code);
                                } else {
                                        foreach ($device_list as $device) {
                                                if ($device['tech'] == 'sip' || $device['tech'] == 'iax2' || $device['tech'] == 'custom' /* vlm */) {
                                                        $ext->add($c, $que_code . '*' . $device['id'], '', new ext_goto('start','s','app-all-queue-toggle'));
                                                        if ($device['user'] != '' &&  isset($qc[$device['user']])) {
                                                                $hlist = 'Custom:QUEUE' . $device['id'] . '*' . implode('&Custom:QUEUE' . $device['id'] . '*', $qc[$device['user']]);
                                                                $ext->addHint($c, $que_code . '*' . $device['id'], $hlist);
                                                        }
                                                }
                                        }
                                }

SCCP Manager for FreePBX

Managing SCCP phones can be a bit tedious, so we'll use a manager to make it easier for us. This module isn't readily available like other FreePBX modules, so we have to jump through a couple of hoops to get it installed.


# cd ~
# svn co https://github.com/FreePBX-ContributedModules/sccp_manager/trunk sccp_manager
# tar czvf sccp_manager-1.0.tgz sccp_manager/*
# cd /var/www/html/admin/modules
# tar xzvf ~/sccp_manager-1.0.tgz
# amportal admin modadmin install sccp_manager

There is a file in /var/www/html/admin/modules/sccp_manager called SEPXML.txt that will become the basis for the config of our Skinny phones. We need to make adjustments so that it fits our needs better. First, ensure that loadInformation represents your firmware revision(s). Then, you'll want to change IP addresses to reflect your server's address. Here is the one that I use. The most crucial entries are <idleURL> which specifies the image to place on the screen of the phone when the phone is idle, and <directoryURL> which specifies a contact directory.


<device>
<loadInformation model="IP Phone 7940">P00308000600</loadInformation>
<deviceProtocol>SCCP</deviceProtocol>
<sshUserId>root</sshUserId>
<sshPassword>root</sshPassword>
<devicePool>
<dateTimeSetting>
<dateTemplate>M-D-Y</dateTemplate>
<timeZone>Eastern Standard/Daylight Time</timeZone>
</dateTimeSetting>
<name>Default</name>
<callManagerGroup>
<members>
<member priority="0">
<callManager>
<ports>
<ethernetPhonePort>2000</ethernetPhonePort>
<mgcpPorts>
<listen>2427</listen>
<keepAlive>2428</keepAlive>
</mgcpPorts>
</ports>
<processNodeName>{$cm_ip}</processNodeName>
</callManager>
</member>
</members>
</callManagerGroup>
<srstInfo>
<name>Disable</name>
<srstOption>Disable</srstOption>
<userModifiable>false</userModifiable>
<ipAddr1></ipAddr1>
<port1>2000</port1>
<ipAddr2></ipAddr2>
<port2>2000</port2>
<ipAddr3></ipAddr3>
<port3>2000</port3>
<isSecure>false</isSecure>
</srstInfo>
</devicePool>
<userLocale>
<name>United_States</name>
<uid>1</uid>
<winCharSet>iso-8859-1</winCharSet>
<langCode>en</langCode>
</userLocale>
<networkLocale>United_States</networkLocale>
<commonProfile>
<phonePassword></phonePassword>
<backgroundImageAccess>true</backgroundImageAccess>
<callLogBlfEnabled>2</callLogBlfEnabled>
</commonProfile>
<vendorConfig>
<disableSpeaker>false</disableSpeaker>
<disableSpeakerAndHeadset>false</disableSpeakerAndHeadset>
<pcPort>0</pcPort>
<settingsAccess>1</settingsAccess>
<garp>0</garp>
<voiceVlanAccess>0</voiceVlanAccess>
<videoCapability>0</videoCapability>
<autoSelectLineEnable>0</autoSelectLineEnable>
<daysDisplayNotActive>1,7</daysDisplayNotActive>
<displayOnTime>08:20</displayOnTime>
<displayOnDuration>00:01</displayOnDuration>
<displayIdleTimeout>00:01</displayIdleTimeout>
<webAccess>1</webAccess>
<spanToPCPort>1</spanToPCPort>
<loggingDisplay>1</loggingDisplay>
<loadServer></loadServer>
</vendorConfig>
<deviceSecurityMode>0</deviceSecurityMode>
<authenticationURL></authenticationURL>
<directoryURL>http://192.168.86.10/directory.xml</directoryURL>
<idleURL>http://192.168.86.10/logo.php</idleURL>
<idleTimeout>300</idleTimeout>
<informationURL></informationURL>
<messagesURL></messagesURL>
<proxyServerURL></proxyServerURL>
<servicesURL></servicesURL>
<dscpForSCCPPhoneConfig>96</dscpForSCCPPhoneConfig>
<dscpForSCCPPhoneServices>0</dscpForSCCPPhoneServices>
<dscpForCm2Dvce>96</dscpForCm2Dvce>
<transportLayerProtocol>4</transportLayerProtocol>
<capfAuthMode>0</capfAuthMode>
<capfList>
	<capf>
		<phonePort>3804</phonePort>
	</capf>
</capfList>
<certHash></certHash>
<encrConfig>false</encrConfig>
</device>

We want to change some code in the sccp_manager to better allow us to set up some speeddials later. These changes are in /var/www/html/admin/modules/sccp_manager/functions.inc.php. The first change is in the function get_speeddial_extension() where we will change


function get_speeddial_extension($device, $instance){
        global $db;

        $sql = "SELECT name
                FROM buttonconfig
                WHERE device='$device' AND instance='$instance' ";

        $row = $db->getRow($sql, DB_FETCHMODE_ASSOC);
        if(DB::IsError($row)) {
                die_freepbx($row->getMessage()."<br><br>Error selecting row from buttonconfig");
        }

        list ($description, $extension, $context) = explode(",",$row['name']);
        return $extension;  

}

to


function get_speeddial_extension($device, $instance){
        global $db;

        $sql = "SELECT name
                FROM buttonconfig
                WHERE device='$device' AND instance='$instance' ";

        $row = $db->getRow($sql, DB_FETCHMODE_ASSOC);
        if(DB::IsError($row)) {
                die_freepbx($row->getMessage()."<br><br>Error selecting row from buttonconfig");
        }

        list ($description, $extension, $context) = explode(",",$row['name']);
        // return $extension;   /* vlm */
        return($row['name']);   /* vlm */

}

Further down in the same file, we will update a section of function sccp_add_device() from


                                case ('speeddial'):
                                case ('blf'):

                                        if (!empty($buttonData['button'.$numButton])) {

                                        $sdString = make_speeddial_string($buttonData['button'.$numButton], $buttonData['type'.$numButton]);  
                                        $buttonData['type'.$numButton]="speeddial";
                                        } else {
                                                $buttonData['type'.$numButton]="empty";
                                                $sdString = "";
                                        }

to


                                case ('speeddial'):
                                case ('blf'):

                                        if (!empty($buttonData['button'.$numButton])) {

                                        // $sdString = make_speeddial_string($buttonData['button'.$numButton], $buttonData['type'.$numButton]);  /* vlm */
                                        $sdString = $buttonData['button'.$numButton];  /* vlm */
                                        $buttonData['type'.$numButton]="speeddial";
                                        } else {
                                                $buttonData['type'.$numButton]="empty";
                                                $sdString = "";
                                        }

sccp.conf

There is a configuration file for the SCCP channel driver, /etc/asterisk/sccp.conf. Pretty much, just use the following file, but update the permit= line to be accurate for your network. The hotline feature is cool. If an SCCP phone comes up on the network, and isn't configured into the system, it becomes a hotline phone, which is a phone which automatically calls the hotline_extension when the handset is picked up. Think of an unattended front desk!


;=========================================================================================
;
; general definitions
;
;=========================================================================================
[general]
servername = FreePbxSCCP
keepalive = 60
debug = 1
context = from-internal-xfer
dateformat = M-D-Y
bindaddr = 0.0.0.0
port = 2000
disallow=all
allow=alaw
allow=ulaw
allow=g729
firstdigittimeout = 16
digittimeout = 8
autoanswer_ring_time = 1
autoanswer_tone = 0x32
remotehangup_tone = 0x32
transfer_tone = 0
callwaiting_tone = 0x2d
musicclass=default
language=en
deny=0.0.0.0/0.0.0.0
permit=192.168.86.0/255.255.255.0
dnd = on
sccp_tos = 0x68
sccp_cos = 4
audio_tos = 0xB8
audio_cos = 6
video_tos = 0x88
video_cos = 5
echocancel = on
silencesuppression = off
private = on
callanswerorder=oldestfirst
hotline_enabled=yes					; can devices without configuration register
hotline_context=from-internal 				; context for hotline
hotline_extension=300					; extension will be dialed on offHook
devicetable=sccpdevice 
linetable=sccpline 

;=========================================================================================
;
; include files
;
;=========================================================================================
; #include sccp_hardware.conf
; #include sccp_extensions.conf

extconfig.conf

If you noticed in the sccp.conf file, there were two references for database tables. We need to do a little more to make those work. We are going to create a new file, /etc/asterisk/extconfig.conf with some additional information so that we can get to the right tables in our database. If you haven't been following along exactly, the format of your file might be different.


[settings]
sccpdevice => odbc,asterisk,sccpdeviceconfig     ; make use of the sccpdeviceconfig view instead of sccpdevice
sccpline => odbc,asterisk,sccpline

res_odbc_custom.conf

The "asterisk" reference in the extconfig.conf file above is in reference to some database connectivity. We need to supply what "asterisk" actually means, and we'll do that in the /etc/asterisk/res_odbc_custom.conf file as follows. If that dsn value looks familiar, it's because it is from the /etc/odbc.ini file we created earlier. If you assigned a different username or password to access the asterisk database in MySQL, you'll need to update those values here.


[asterisk]
enabled=>yes
dsn=>MySQL-asterisk 
pooling=>no
limit=>1
pre-connect=>yes
username=>asteriskuser
password=>amp109

And restart the asterisk/FreePBX system with the following.


# amportal restart

Idle Screen Image

If you recall, we specified an idleURL in the /var/www/html/admin/modules/sccp_manager/SEPXML.txt file. Now we need to create that file. I used the handy, dandy CIP Idle Java app to create the idle screen image that we used, and then put it into a php file. On our system that file is /var/www/html/logo.php.


<?php
header("Content-type: text/xml");
header("Expires: -1");
header("Connection: Keep-Alive");
header("Cache-Control: private");

?><CiscoIPPhoneImage>
    <LocationX>-1</LocationX>
    <LocationY>-1</LocationY>
    <Width>90</Width>
    <Height>56</Height>
    <Depth>2</Depth>
    <Data>00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A0AAAAAAAAAA2A00AAAAAAAAAA2A000000000000000000AAAAAAAAAAAA0A80AAAAAAAAAA020000000000000000A0AAAAAAAAAAAA02A0AAAAAAAA2A000000000000000000AAAAAAAAAAAAAA00A8AAAAAAAA020000000000000000A00A00000000000000AAAAAAAA2A000000000000000000AA0000000000000080AAAAAAAA020000000000000000A00A00000000000000A0AAAAAA2A000000000000000000AA0000000000000000A8AAAAAA020000000000000000A00A80AAAA2A00000000AAAAAA2A000000000000000000AA00A8AAAA02000000A0AAAAAA020000000000000000A00A80AAAAAA00008000A8AAAA2A000000000000000000AA00A8AAAA2A00002800AAAAAA020000000000000000A00A80AAAAAA0A00800A80AAAA2A000000000000000000AA00A8AAAAAA0200A802A0AAAA020000000000000000A00A80AAAAAA2A0080AA00A8AA2A000000000000000000AA00A8AAAAAA0A00A82A00AAAA020000000000000000A00A80AAAAAAAA0280AA0A80AA2A000000000000000000AA00A8AAAAAAAA00A8AA02A0AA020000000000000000A00A80AAAAAAAA2A80AA2A00A82A000000000000000000AA00A8AAAAAAAA02A8AA0A00AA020000000000000000A00A80AAAAAAAAAA80AAAA02802A000000000000000000AA00A8AAAAAAAA2AA8AAAA00A0020000000000000000A00A80AAAAAAAAAA8AAAAA2A002A000000000000000000AA00A8AAAAAAAAAAAAAAAA0280020000000000000000A00A80AAAAAAAAAAAAAAAA2A0020000000000000000000AA00A8AAAAAAAAAAAAAAAA0200000000000000000000A00A80AAAAAAAAAAAAAAAA2A0000000000000000000000AA00A8AAAAAAAAAAAAAAAA0208000000000000000000A00A80AAAAAAAAAAAAAAAA2A8002000000000000000000AA00A8AAAAA2AAAAAAAAAA02A8000000000000000000A00A80AAAA2AA8AAAAAAAA2A802A000000000000000000AA00A8AAAA02AAAAAAAAAA02A8020000000000000000A00A80AAAA2A80AAAAAAAA2A802A000000000000000000AA00A8AAAA02A8AAAAAAAA02A8020000000000000000A00A80AAAA2A00AAAAAAAA2A802A000000000000000000AA00A8AAAA0280AAAAAAAA02A8020000000000000000A00A80AAAA2A00A0AAAAAA2A802A000000000000000000AA00A8AAAA0200A8AAAAAA02A8020000000000000000A00A80AAAA2A0080AAAAAA2A802A000000000000000000AA00A8AAAA0200A0AAAAAA02A8020000000000000000A00A80AAAA2A0000A8AAAA2A802A000000000000000000AA00A8AAAA020000AAAAAA02A8020000000000000000A00A80AAAA2A000080AAAA2A802A000000000000000000AA00A8AAAA020000A8AAAA02A8020000000000000000A00A00000000000000000000802A000000000000000000AA0000000000000000000000A8020000000000000000A00A00000000000000000000802A000000000000000000AA0000000000000000000000A8020000000000000000A0AAAAAAAAAAAAAAAAAAAAAAAA2A000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAA020000000000000000A0AAAAAAAAAAAAAAAAAAAAAAAA2A000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAA0200000000</Data>
    <Title>NorthEast</Title>
    <Prompt></Prompt>
</CiscoIPPhoneImage>

Directory

It's really convenient to have a directory in the phone, so that everyone doesn't have to tape a list of extensions to their phone. It's even better to have a directory that is automatically updated when extensions are changed, added, or removed! These first of these files was specified in /var/www/html/admin/modules/sccp_manager/SEPXML.txt and is actually located at /var/www/html/directory.xml


<CiscoIPPhoneMenu>
<Title>NorthEast</Title>
<Prompt>Select name:</Prompt>
<MenuItem>
     <Name>NorthEast</Name>
     <URL>http://192.168.86.10/ncc.php</URL>
</MenuItem>
</CiscoIPPhoneMenu>

You'll note that another URL is specified in this file. This URL ends up being the file /var/www/html/ncc.php. The directory is populated by using the Description field for the SCCP phone as the name, and the number assigned to Line 1 as the phone number. Pretty simple.


<?php

header('Content-type: text/xml');

echo "<CiscoIPPhoneDirectory>\n";
echo "<Title>NorthEast</Title>\n";
echo "<Prompt>Select</Prompt>\n";

$mysql = new mysqli("localhost", "root", null, "asterisk");

$result = $mysql->query("select sccpdevice.description as name, buttonconfig.name as ext from sccpdevice, buttonconfig where buttonconfig.device=sccpdevice.name and buttonconfig.instance=1 and buttonconfig.type='line'");

while($row = $result->fetch_assoc())
{
  echo "<DirectoryEntry>\n";
  echo "  <Name>" . $row['name'] . "</Name>\n";
  echo "  <Telephone>" . $row['ext'] . "</Telephone>\n";
  echo "</DirectoryEntry>\n";
}

echo "</CiscoIPPhoneDirectory>\n";

?>

FreePBX Admin Suite

Phew! It's been a lot of work, but we are finally ready to get into the admin suite and add some phones! Point your browser to http://<Asterisk IP Address>/admin. You will be prompted to create the administrator credentials. These are what you want them to be. Once submitted, click on the FreePBX Administration link and enter the admin credentials that you just specified. You are now in! If the red Apply Config is in the header row, go ahead and click it. It's not a bad idea to go into Admin/Module Admin and finish installing any modules that are listed as "Not Installed (locally available)."

Before we add our first extension, let's enable Custom Devices States so that our hints will work. This is done by going to Settings/Advanced Settings and scrolling down until you find Enable Custom Device States, set it's value to True and then click the check mark to save it.

Now, let's add our first extension by clicking on Applications/Extensions so that we can add our first extension. Select the Device to be Other (Custom) Device and click Submit. We are setting the following information:

  • User Extension: 200
  • Display Name: Herbert
  • Under Device options, dial: SCCP/200
  • Under Voicemail, Status: Enabled
  • Under Voicemail, Voicemail Password: 1111

And click Submit. Now find the 7940 phone that you want to assign to this extension. Flip it over and find the label where the phone's MAC address is written. Most of the time, it is the label in the middle and will always be a 12-digit hexadecimal number. Now in FreePBX, click on Other/SCCP Phones. We are setting the following information for our phone:

  • MAC: 001c58f0d268
  • Type: 794X
  • Description (this is the name that ends up in the Directory!): Herbert
  • Button 1 (this is the number that ends up in the Directory!): Line: 200
    • We are leaving the rest unchanged for now. Click on Submit. Now click on Other/SCCP Extensions and notice the list of extensions on the right of the page. Click on the extension just added, 200 in our case. We are simply going to change the Label value from Extension 200 and make it be 200. Click Submit and then click the red Apply Config in the header bar.

      Now we can plug the phone in, but we want to reset it to defaults, so we're going to hold down the # key until the phone indicates "Reset Key Sequence Detected," and then enter 123456789*0#, and when prompted, 2. The phone is now reset and should pick up it's new software and config from our new server!

      Let's repeat this process with another phone. Here are our extension properties:

      • User Extension: 201
      • Display Name: Gerty
      • Under Device options, dial: SCCP/201
      • Under Voicemail, Status: Enabled
      • Under Voicemail, Voicemail Password: 1111

      And here are our SCCP Phone properties (your MAC will obviously be different)

      • MAC: 001b0c18da90
      • Type: 794X
      • Description (this is the name that ends up in the Directory!): Gerty
      • Button 1 (this is the number that ends up in the Directory!): Line: 201
        • Don't forget to go into Other/SCCP Extensions and change the Label value from Extension 201 to simply 201, click Submit, and then click on the red Apply Config in the header bar. Plug the phone in, do the reset, and wait for it to grab its new code. Once it is up, see if you can call between phones! If you can, you deserve a pat on the back!! If you press the Directory button and can see your dynamic directory, you probably deserve some DQ!

          Queues

          Let's get a little fancy and define a queue. Go to Applications/Queues and fill in the following:

          • Queue Number 300
          • Queue Name: Front Desk
          • Generate Device Hints: checked
          • Alert Info: outside
          • Use the Extension Quick Pick below Dynamic Members and pick both Herbert and Gerty
          • Music on Hold Class: inherit, Agent Ringing
          • Max Wait Time: 30 seconds
          • Agent Timeout: 30 seconds
          • Join Empty: No
          • Leave Empty: Yes
          • At the button under Fail Over Destination, select Voicemail and then select Herbert's unavailable

          Click Submit and then go to Other/SCCP Phones. Select Herbert's phone and select his Button 2 to be a SpeedDial and enter the following in space:

          
          Frontdesk,*45*200,*45*200@ext-queues
          
          

          And then click Submit. Now select Gerty's phone, do the same for her Button 2, except her content will need to reflect her extension, as follows.

          
          Frontdesk,*45*201,*45*201@ext-queues
          
          

          Click Submit, and then click the red Apply Config in the header bar. The phones now have some Speed Dials on them that will also behave as a busy lamp field. These Speed Dials allow the users to toggle their log in/out status in the Front Desk queue. When they are in, they will receive calls sent to the Front Desk queue, and when they are out, they will not. When logged in, the words "External Call" will be next to the Speed Dial and the little phone icon will change to 2 handsets. These are your indicators that you are logged into the queue. Use the button couple of times to see the difference.

          Now, let's add a third phone, but we won't configure it in our FreePBX system. We will still hold the # key down and do the reset procedure when we plug it in, but we want it to go into hotline mode. If you recall, we set the hotline extension in /etc/asterisk/sccp.conf to 300, so when someone picks up the hotline extension, it will ring the phones that are logged into the Front Desk queue that we set up. Try it! Use the Speed Dial to log in and out and see how it behaves when the hotline is used. See how it will drop to Herbert's voicemail if no one is logged in or if no one answers! Also, note that hotline calls have a double ring, which is because of the Alert Info we set when we defined the queue.

          Paging

          One of the issues with the SIP implementation on the 7940-like phones is their poor ability to do paging. Fortunately, this is resolved with the SCCP implementation! Let's create the file /etc/asterisk/extensions_custom.conf with the following content:

          
          [from-internal-custom]
          exten => 320,1,Page(SCCP/200/aa=1wb&SCCP/201/aa=1wb)
          exten => 320,2,Hangup()
          
          

          This creates an extension 320 that is our page extension, and it will page both Herbert's and Gerty's phones. If the 200 and 201 look familiar, they should! It's a little manual to have to update a file like this to include the extensions we want in our page group, but it gets the job done. After editing the dialplan, we need to reload the dialplan in Asterisk.

          
          # asterisk -rx "dialplan reload"
          
          

          Probably more annoying than useful, but you could set the hotline extension in /etc/asterisk/sccp.conf to be 320, restart asterisk, and then whenever someone picks up the hotline extension, it pages the other desk sets!

          logrotate

          If a system is going to be running long term, it's good to do some extra configs to make sure it continues to run as smoothly as possible. Create the following file as /etc/logrotate.d/asterisk. Logrotate keeps log files from growing infinitely by rotating them to new file names periodically, and getting rid of old log files.

          
          /var/log/asterisk/messages /var/log/asterisk/*log /var/log/asterisk/full {
             missingok
             notifempty
             sharedscripts
             create 0640 asterisk asterisk
             postrotate
             /usr/sbin/asterisk -rx 'logger reload' > /dev/null 2> /dev/null
             endscript
          }
          
          

          Similarly, create the following file as /etc/logrotate.d/freepbx.

          
          /var/log/asterisk/freepbx_dbug /var/log/asterisk/freepbx_debug {
             missingok
             notifempty
             sharedscripts
             create 0640 asterisk asterisk
             postrotate
             /usr/sbin/asterisk -rx 'logger reload' > /dev/null 2> /dev/null
             endscript
          }