Archive for the ‘’ Category

pseudo-realtime reporting of agent status in asterisk

Monday, October 23rd, 2006

Once you have a few agents on your asterisk box, and especially if they are distributed throughout the building, you quickly find yourself in need of a way of easily seeing who is or isn’t logged in. This helps with agent tracking, but it’s also useful for those times that you’re debugging your dialing stategies. So we’re going to write a Python script that will run constantly and update a database with all of the agents’ names and their current status. I’ve personally been running this for months with no issues as far as CPU load or instability in the Asterisk system.

First of all, you need to enable the manager interface. Look in /etc/asterisk/manager.conf. You’ll need something to the effect of…

[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0

[joe]
secret = schmoe
read = system,call,log,verbose,command,agent,user
write = system,call,log,verbose,command,agent,user

So Asterisk will be listening on the localhost address, on port 5038. Our user ‘joe’ is set up with the password ‘schmoe’. Now we need to connect to the server using Python. Here’s the script:
[python]
import socket, time, MySQLdb

def main():
db = MySQLdb.connect(host=”dbaddress”, user=”dbuser”, passwd=”dbpass”, db=”dbname”)
sql = db.cursor()
agents = {}
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((“localhost”, 5038))
while 1:
try:
s.send(“Action: Login\r\n”+”UserName: joe\r\n”+”Secret: schmoe\r\n\r\n”+”Action: Agents\r\n\r\n”)
time.sleep(1)
data = s.recv(30000)
for line in data.split(“\r\n”):
if line[:5] == ‘Event’:
if line[7:] == ‘AgentsComplete’:
break
if line[:5] == ‘Agent’:
agent = line[7:]
if line[:4] == ‘Name’:
name = line[6:]
if line[:6] == ‘Status’:
if vars().has_key(‘agent’) and vars().has_key(‘name’):
if not agents.has_key(agent):
agents[agent] = “”
if line[8:] <> agents[agent]:
agents[agent] = line[8:]
print name.ljust(15), agent.ljust(10), line[8:].ljust(20), time.ctime()
sql.execute(‘replace into ACD (agent, status, name) values (“‘+agent+'”,”‘+line[8:]+'”,”‘+name+'”)’)
s.send(“Action: Login\r\n”+”UserName: manager\r\n”+”Secret: manager\r\n\r\n”+”Action: Queues\r\n\r\n”)
time.sleep(1)
data = s.recv(30000)
for line in data.split(“\r\n”):
if line[:16] == “4 has”:
calls = int(line[line.find(“has “)+4:line.find(” calls”)])
if calls > 0:
print calls
sql.execute(‘replace into ACD (agent, status, name) values (“CALLS”,”‘+str(calls)+'”,”CALLS”)’)
except:
s.connect((“localhost”, 5038))

s.close()
[/python]
As you can see, I’m using sockets to connect to the manager interface. You can actually open up the interactive interface to Python and type these commands individually if you’d like to see the output that you’ll receive directly on the socket. Suffice it to say, the FOR statement starting on line 14 checks each line, looking for specific attributes that give us various info. In my case, I wanted to get the Agent ID, their Name, and their status. Once I have that information, I just insert it into a table that I created called ACD.

Something else that I should note is the FOR statement on line 33. This chunk writes a line to the same MySQL table with the total number of unanswered calls in the queue. This number allows me to know if customers are waiting unnecessarily long to get to an agent. You could obviously extend something like this to include timestamps, etc… allowing you to know average wait times and whatnot.

That’s basically it! At this point, you should install the script on your asterisk box as a daemon. Otherwise, you’ll need to keep a live SSH connection open specifically for this. Keep in mind, any time the Asterisk server dies for any reason, so does the manager interface, and your script. You’ll need to either restart it manually, or let your daemon restart it.

If you can think of ways to improve this script… I’m sure there are many ways… please comment below!

use macros in asterisk

Monday, September 4th, 2006

I was just watching an episode of Systm the other day with a friend… Episode 5, the one where they describe how to build a basic Asterisk server. First, I’ve gotta say, I love that Kevin Rose and the other guys over there have created Systm… it’s a very cool idea, and something that honestly just sounds like a blast to do.

Anyway, while watching it, I remembered back when I was learning asterisk that many would-be mentors simply showed how to create a static definition in extensions.conf for every extension. While that works for a small system, it quickly becomes needlessly complicated when you need to change the way things flow for every extension after that. Just imagine changing something as small as the voicemail context for 50 extensions. It just doesn’t make sense. That’s where macros come in. They make it possible to cover most of the extensions with one script. The issue, I’ve found, is that most example macros just aren’t useful enough. :)

For my users, I want to give them the ability to call their own number in order to go straight to voicemail. That, and the Polycom 501 actually defaults to calling itself when you hit the ‘messages’ button. So we need to create a macro that will perform the normal functions, but will also know when you’re calling yourself.

First, in the main context you’ll need a line like this:

exten => _10XX,1,Macro(sipphone,${EXTEN})

This assumes extensions between 1000 and 1099. Change it to suit your needs. The point of this line is that when someone dials a ‘1000’ extension, it will send the call to the ‘sipphone’ macro, sending the actual extension dialed along with it.

Then, somewhere else in the file, perhaps just above your main context, add:

[macro-sipphone]

exten => s,1,GotoIf($[“${ARG1}” = “${CALLERIDNUM}”]?5:2)
exten => s,2,Dial(SIP/${ARG1},20)
exten => s,3,Voicemail(su${ARG1})
exten => s,4,Hangup
exten => s,5,VoicemailMain(${CALLERIDNUM})
exten => s,6,Hangup

So when a call is sent to this macro, ${ARG} would equal the extension dialed, and ${CALLERIDNUM} would be the extension dialing. On the first line of the macro, we’re simply saying “if the caller id and dialed number are equal, send the call to line 5, or voicemail. Otherwise, we follow the normal flow of a macro and ring the extension for 20 seconds, and then send the call to voicemail. Also, notice that if you call yourself, we use the VoicemailMain command instead of the Voicemail command… that sends you in to listen rather than record.

a successful Polycom IP 501 deployment model

Thursday, August 31st, 2006

Those who have embraced asterisk or other VoIP systems have a large choice of phones to deploy on the desks throughout their organization. I’ve personally ordered quite a few phones, and I’ve seen some that are very very bad from a user’s perspective. One particular model was horrible, to the extent of needing to be reset regularly. But one of the models I tested was actually recommended by a user on the asterisk IRC channel… the Polycom IP 501. It’s a multi-line SIP phone with all of the features you would expect to find in a solid desktop phone meant for business. They have a large screen, a very good full-duplex speakerphone, and a built-in switch… so you don’t have to run extra cabling.

Polycom IP 501One place they do fall short, however, is in the configuration. Many people actually shun Polycom because they refuse to provide reliable support unless you are an actual dealer. The configuration files aren’t well documented, and the firmware is fairly hard to find. That, my friend, is the reason I’m writing this post. I’m going to detail everything you need in order to get a quick deployment model setup in your company. By the end of this, you should be able to add a new phone to your network within about 15 minutes.

By the way, some of those who read this may question why I’m not even glancing in the direction of configuring the phones via the built-in webserver. I’ve taken this route for a few reasons… 1) the built-in webserver is hideously slow, 2) when you change the configuration enough, you have to wait for the phone to reboot (which takes a short eternity) before you can continue configuration, 3) it doesn’t make sense to do all of your configuration manually on every phone, especially if you have more than 2-3 users.

So now that we’ve made our decision, let’s start with the firmware. You can find the latest firmware and bootrom files right here. I suggest you get:

  • SoundPointIP_BootROM_2_6_2.zip
  • SoundPoint_IP_SIP_1_6_2.zip

Just unzip those files onto your local drive, and try to keep from editing anything in that directory… just so you have clean files to reference.

Now is the time to install an FTP server. Unfortunately, as much as I hate these words, the details are beyond the scope of this article. The main point is that you’ll need a working FTP server that is accessible from your local network. Whether you decide to install it on a local Linux server following my instructions here, or you just install Filezilla Server on a Windows machine, you should be fine. On your FTP server, create a new user by the name of ‘PlcmSpIp’, case IS sensitive. One suggestion, when setting your password you should remember that you’ll be entering the password several times on a telephone keypad, so sticking to a fairly short numeric password might be a good idea. ;)

At this point, revisit the directory of files you unzipped. You’re going to need to copy the following files to your FTP user’s home directory:

  • bootrom.ld
  • bootrom.ver
  • sip.cfg
  • sip.ld
  • sip.ver
  • SoundPointIPWelcome.wav
  • The entire SoundPointIPLocalization directory

Also, you’ll need to create a directory called ‘logs’ in the user’s home directory. We’ll configure the phones to upload their log files to this location so that things are fairly tidy.

Take a look at the files you unzipped on your harddrive. You’ll find 000000000000.cfg, 000000000000-phone.cfg, and 000000000000-sip.cfg. These are the primary configuration files that will control every aspect of the way your phone behaves. We’re going to make these into template files, specifically for a program that we write to parse and create proper config files out of. Neat, eh? If you’d like to just use my template files, you can download them, or you can just use them for reference:

You’ll notice in each file I inserted various placemarks using the format “{ATTRIBUTE}”. These are in the 3 config files, and match up with the columns in the MySQL table. We’re going to create a Python script that will read in the data and create custom configs for each phone in the table. So let’s say we have the following information:

id server fname lname extension mac ringtone linekeys acdline acdlabel chirp
1 192.168.1.3 Joe Smith 1000 0a1b2c3d4e5f 4 2 2000 IT silence
2 192.168.1.3 Jane Doe 1001 0a1b2c3e4f5g 4 3 silence

So we’ve got the main server, the users’ names, their extensions and phone MAC addresses. Notice we also have the ‘linekeys’ column. The 501 has the ability to assign a line number to one or multiple lines on the phone. Since the 501 only has 3 physical line keys, I don’t want to mess with any more than that. As you can see, though, Joe is in the IT group. The idea is that all of your agents who are in queues will only want one line dedicated to that queue, that way Asterisk isn’t going to ring their phone if they’re already on a queue call. So by setting ‘linekeys’ to 2, and assigning an ‘acdline’ number and ‘acdlabel’, we set up the third line to be a separate extension number. Naturally, you’ll need to configure this properly in your Asterisk box. As far as Jane Doe goes, she just gets the same extension for all three lines, since she’s not a member of any queues.

Ok, so now we have our database set up with the user data, we’ve got template config files, and we have an FTP server set up. Only a few more steps to go. On the next page, we’re going to build the Python script that will tie it all together. I’ll see you there!

So to recap, we need to bring the FTP server, the configuration template files, and the phone user database all together into a fairly easy to use system. The way I did it was with a Python script. Is there anything Python can’t do? :)

Here’s the script:

[python]
import MySQLdb, os, shutil
print ” Connecting to Database”
db=MySQLdb.connect(host=”host”, user=”user”, passwd=”pass”, db=””)
c = db.cursor()

print ” Creating Company Directory”
output = open(“c:\\phones\\000000000000-directory.xml”,”w”)
output.write(‘\n\n\n’)
c.execute(‘select fname,extension,ringtone from PBX.phones’)
for rec in c.fetchall():
fname,extension,ringtone = rec
output.write(‘\n\t\n\t‘+fname+’\n\t‘+str(extension)+’\n\t\n\t\n\t\n\t0\n\t0\n\t0\n\t0\n\n’)
output.write(‘
\n
\n’)
output.close()

print ” Creating CFG Files”
c.execute(‘select server,fname,lname,extension,mac,ringtone,linekeys,acdline,acdlabel,chirp from PBX.phones’)
for rec in c.fetchall():
server,fname,lname,extension,mac,ringtone,lines,acdline,acdlabel,chirp = rec
print ” ” + fname + ” ” + lname
password = “ae”+extension[::-1]
password2 = “ae”+acdline[::-1]
template = open(“c:\\phones\\000000000000.cfg”,”r”)
output = open(“Q:\\PlcmSpip\\”+mac+”.cfg”,”w”)
for line in template.readlines():
line = line.replace(“{SERVER}”,server)
line = line.replace(“{FNAME}”,fname)
line = line.replace(“{LNAME}”,lname)
line = line.replace(“{EXTENSION}”,str(extension))
line = line.replace(“{MAC}”,mac)
line = line.replace(“{LINES}”,str(lines))
line = line.replace(“{RINGTONE}”,str(ringtone))
line = line.replace(“{PASSWORD}”,password)
line = line.replace(“{PASSWORD2}”,password2)
line = line.replace(“{ACDLINE}”,str(acdline))
line = line.replace(“{ACDLABEL}”,str(acdlabel))
output.write(line)
template.close()
output.close()
template = open(“c:\\phones\\000000000000-phone.cfg”,”r”)
output = open(“Q:\\PlcmSpip\\”+mac+”-phone.cfg”,”w”)
for line in template.readlines():
line = line.replace(“{SERVER}”,server)
line = line.replace(“{FNAME}”,fname)
line = line.replace(“{LNAME}”,lname)
line = line.replace(“{EXTENSION}”,str(extension))
line = line.replace(“{MAC}”,mac)
line = line.replace(“{LINES}”,str(lines))
line = line.replace(“{RINGTONE}”,str(ringtone))
line = line.replace(“{PASSWORD}”,password)
line = line.replace(“{PASSWORD2}”,password2)
line = line.replace(“{ACDLINE}”,str(acdline))
line = line.replace(“{ACDLABEL}”,str(acdlabel))
output.write(line)
template.close()
output.close()
template = open(“c:\\phones\\000000000000-sip.cfg”,”r”)
output = open(“Q:\\PlcmSpip\\”+mac+”-sip.cfg”,”w”)
for line in template.readlines():
line = line.replace(“{CHIRP}”,str(chirp))
output.write(line)
template.close()
output.close()
try:
os.unlink(“Q:\\PlcmSpip\\”+mac+”-directory.xml”)
except:
pass
shutil.copyfile(“c:\\phones\\000000000000-directory.xml”,”Q:\\PlcmSpip\\”+mac+”-directory.xml”)

[/python]

Remember, any time you use code from my site, you’ll need to remove the line numbers.

One thing you’re going to need to do is either have Samba installed on your local FTP server, or use something like NetDrive to map a remote FTP directory. Both options work great, though NetDrive would certainly be the slowest of the two. In any event, as you can see, I mapped my FTP directory to my Q: drive in Windows.

So the basic flow of this program is to first read in all of the users from the PBX.phones table and build a “directory” XML file. This is the file that the phone uses for the speed dials. If you’d like to leave it out, feel free. Then the script reads in the data again and goes through each template file, replacing the relevant placemarks with good data. The files are then saved to the FTP server using the MAC provided in the table.

At this point, we’re done. In all, it takes about 10-15 minutes to setup a brand new phone out of the box… and that includes the time the phone spends downloading the latest firmware. It’s an insanely easy process at this point to setup an entire office fairly quickly.

I hope this information is useful to someone out there. If so, please let me know!