Tuesday, September 2, 2008

Sending DTMF tones inband part 2.

Well, I finished my first TCL IVR script. As mentioned earlier, I have a problem where I am required to create "short-cut" dial-string. This will enable users to dial a short number e.g. 5551 that will then make a call to the PSTN and send a string of digits via DTMF.

As there seems to be many occasions when this may be handy I decided to try and make what ever script I wrote as generic as possible so that I could easily change the number that was dialed, and the DTMF strings that would be sent.

As this is my first TCL IVR script, if anyone sees any obvious mistakes or short-cuts please let me know.

There really does not seem to be to much information out there regarding the development or explanation of TCL IVR scripts for IOS, so started by reading the Cisco TCL IVR 2.0 Programming guide, It is a good starting point, though I did find things still very murky. This is where I turned to the two other helpful resources... Google, and the TCL wiki page.

The other thing I found quite frustrating, is the whole development process debugging and testing involved many steps:
  • Script would edited/corrected on my laptop
  • Script would then copied to the router
  • then application would have to be reloaded
  • finally I would dail into the script, observer any errors then start the process again...
After 50 or 60 times this becomes quite laborious.

The following paragraphs go through the script I have written, and hopefully covers some of the things that I learned. Once again, if there any obvious mistakes then please let me know, also If you would like a copy of the script, and prompts let me know.

First up a bit about the FSM

TCL IVR scripts make use of a FSM (finite state machine) depending what you want your script to do, you define a set of actions (procedures) that are run when the state of the FSM changes. These procedure may change the state of the FSM directly , or alternatively the reponse to a procedure may cause an event that can be acted upon if the FSM has state defined that matches the action.. Confused hmm you arent the only one.

An example:

the FSM is in a "CONNECTED" state a procedure has been actioned that plays a media prompt. once the prompt has played out, an event is fired "ev_media_done" if the FSM has an entry defined "CONNECTED" and "ev_media_done" then the procedure that is a associated with this state and event is called.

It does take some getting you head around, especially if you have not have not had much to do with FSM's. The otehr thing to note, is that when a procedure is called, the code in the procedure is run one after the other, there is no waiting for a function to complete before the next step is taken.. eg. If a procedure is called that plays a prompt then sends some DTMF tones, if you do not make use of the FSM to manage the steps, you willl start playing the prompt, and then the tones will be immediatley played over the top of the prompt.

Anyway back to my script.

The first thing to do is to initalise a pile of variables, some of these are read from the router configuration, others are internal to the script.

proc init { } {
global param
global callInfo
global legConnected
global instructionsPrompt
global pleaseWaitPrompt
global invalidOptionByePrompt
global divertSetPrompt
global DTMFStrings
global pattern
global validKeys
global PSTNDivertNumber
global currentDigit
set currentDigit 0
set param(interruptPrompt) true
set param(abortKey) *
set param(terminationKey) #
set instructionsPrompt "flash:en_welcome_prompt.au"
set pleaseWaitPrompt "flash:en_dialing_prompt.au"
set invalidOptionByePrompt "flash:en_invalid_option_prompt.au"
set divertSetPrompt "flash:en_divert_set_prompt.au"
set legConnected false #check that the PSTNDivertNumber has been configured
if [infotag get cfg_avpair_exists PSTN-Divert-Number] {
set PSTNDivertNumber [string trim [infotag get cfg_avpair PSTN-Divert-Number]]
} else {
set PSTNDivertNumber "NONE" puts -nonewline "TCL EMG: -- ERROR: Mandatory parameter PSTN-Divert-Number does not exist --"
set configFail 1
call close
}
set DTMFStrings(0) 0
set pattern(0) 0
set validKeys {{0}}
for {set var 1} { $var <= $numberOfOptions } {incr var} {
if [infotag get cfg_avpair_exists DTMFString$var] {
set DTMFStrings($var) [string trim [infotag get cfg_avpair DTMFString$var]]
lappend validKeys $var
set pattern($var) $var
}
}
}

The next thing that is required is to set the FSM this defines what actions/functions are called based on the "state" of the script.

set fsm(any_state,ev_disconnected)   "act_Cleanup same_state"
set fsm(CALL_INIT,ev_setup_indication) "act_Setup   GETDEST"
set fsm(GETDEST,ev_collectdigits_done) "act_GotDest   CONNECT"
set fsm(CONNECT,ev_media_done)  "act_OutboundCall   CONNECTED"
set fsm(CONNECTED,ev_setup_done)  "act_SendDigit   same_state"
set fsm(CONNECTED,ev_media_done)  "act_SendDigit   same_state"
set fsm(FINISHED,ev_media_done) "act_Cleanup    same_state" 
fsm define fsm CALL_INIT

The first thing that happens when a call is received, is that we receive a ev_setup_indication message.  From the state table, we can see that when this message is received, the act_Setup function is called and the "state" is set to GETDEST.

proc act_Setup { } {
    global param
    global dest
    global instructionsPrompt
    global pattern
    global currentDigit
    set currentDigit 0
    set param(maxDigits) 1
    leg setupack leg_incoming
    leg proceeding leg_incoming
    leg connect leg_incoming
    media play leg_incoming $instructionsPrompt
    leg collectdigits leg_incoming param pattern
}

From this function we can see that once the call leg has been set up, a prompt is played, then we collect the digit that is input by the end user.  Once the digit is receive an "ev_collectdigits_done" is received,  from the FSM, we can see that when we receive this message and we are in the GETDEST state, the script should run the act_GotDest function and set the state to CONNECT.

proc act_GotDest { } {
    global digit
    global validKeys
    global pleaseWaitPrompt
    global invalidOptionByePrompt
    set digit [infotag get evt_dcdigits]
    puts "\nDIGIT DIALED: $digit"
    #check that digit is valid:
    set x [lsearch $validKeys $digit]
    if {$x >= 1} {
            media play leg_incoming $pleaseWaitPrompt
        } else {
            puts "\n WRONG NUMBER!!!"
            media play leg_incoming $invalidOptionByePrompt
            fsm setstate FINISHED
        }
}

This function does a basic check, that will terminate the call if an incorrect digit was received.  If a valid digit was received the user is told to wait while the outbound call is made and DTMF string is sent.

proc act_OutboundCall {} {
    global PSTNServiceNumber
    leg setup $PSTNServiceNumber callInfo leg_outgoing
}

On completion of the call setup, a ev_setup_done message is received, all that is done now is to send the digits:

proc act_SendDigit { } {
    global digit
    global DTMFStrings
    global currentDigit
    global stringLength
    #global silencePrompt
    global pleaseWaitPrompt
    global divertSetPrompt
    #global shortSilencePrompt
    set stringLength [string length $DTMFStrings($digit)]
    set d [string index $DTMFStrings($digit) $currentDigit]
    if {$currentDigit < $stringLength}  {
        switch $d  {
           "," {
                media play leg_outgoing %s2000
                set currentDigit [expr {$currentDigit + 1}]
                } 
        default {
                leg senddigit leg_outgoing $d -t 200    
                set currentDigit [expr {$currentDigit + 1}]
                media play leg_outgoing %s400
                }
        }
    } else {
        media play leg_incoming $divertSetPrompt
        fsm setstate FINISHED
        }
}

This function is responsible for sending the digits.  As we can only send one digit at a time, we have to loop through the DTMF string.   I have added the ability to add pauses by using a comma.  As the digits need to be separated by silence, we use the "media play" function to introduce this silence, it is also used as the trigger to send the next digit.

Once all this is completed, the service needs to be configured on the rtr.

application
service sendDTMF flash:send-dtmf-1.0.0.10.tcl
param PSTN-Service-Number 95556789 #the PSTN number that is dialed
param number-Of-Options 5 #number of options
param DTMFString1 ,,045433333,*,5555*,,*2,045433333

Once this is done, the last thing that is required is for a dial-peer to be set up.

Well thats it, if you have any questions, please contact me.

Friday, August 15, 2008

Sending DTMF tones inband part 1.

This is a problem that has me a bit stumped, I have come up with one working solution, unfortunately it does not quite meet my requirement.

The problem is this, Using CME, how does one send a string of DTMF tones inband via a PRI connection to the PSTN. Why would we want to do this? There are many occasions when interaction is required across the PSTN using just DTMF tones e.g. Logging in to vmail, logging into a providers call center, setting call diverts etc.

In the case that I was looking at I have been asked to provide a quickdial solution that would call a PSTN number, then when the call was connected, log into the service, and enter mobile phone number. This would in effect set the destination for the companies 1800 number.

The sequence of numbers and information sent to me was as follows (numbers etc changed for obvious reasons):

dial pstn number 555-5678
when asked enter pin "12345" then *
after listening to the instructions enter the destination number that the 1800 number will be diverted to 555-8888, then #
Wait for the digits to be read back, then enter 1 to accept.

The plan was that depending who was on call each week would have the 1800 number diverted to their phone. The client wanted users to be able to dial 150, 151, 152... etc to move the on call number between phones without having to manually doing the process.

At first look it seems like a pretty straight forward request, and something that I would imagine is pretty common. I am sure it would be very easy using IPCC, or CUE AA. Unfortunately the client is running straight CME, without CUE.

My next thought was TCL, so I had a quick search, hmmm, not much luck there. Then after having a discussion with a close friend we came up with a fiendish plan to use num-exp.

So here is what I did.

Firstly I wrote the DTMF sequence as it would be entered by the end user, using commas for pauses:
95555678,,,12345*,,,5558888#,,,,,1

The real trick of this solution comes from using "forward-digits extra inband" in the dial-peer configuration. What this does is uses the number that appears in the destination-pattern in the ISDN call setup, then forwards any extra digits "inband" (ie as DTMF tones).

Using these two things we can then configure a working solution:

! First Diversion
num-exp 150 5555678,,,12345*,,,5558888#,,1
! Second Diversion
num-exp 151 5555678,,,12345*,,,5558889#,,1
! Third Diversion
num-exp 152 5555678,,,12345*,,,5558890#,,1

dial-peer voice 10 pots
destination-pattern 5555678
forward-digits extra inband
port 0/2/0:23

That's it. Simple!

Now the problem is. It appears that dial-peers can only forward 32 digits, so though this does work for the solution above, it will not work if we even wanted to add one more comma/pause. It appears to me (unless someone has a better idea) that the only way to send DTMF tones will be to create a TCL script.

Time to learn a bit about TCL I guess. Look out for part 2 sometime in there near future where I do just that.

Monday, August 4, 2008

Redundant CME Configuration

Recently I was required to configure a redundant CME solution.

The scenario is as follows:
Two routers each with a PRI connected, the Telco had
no restrictions as to which PRI could be used for voice calls into the site. Outbound calls would prefer the active CME router, with overflow being sent to the non-active CME router. I have tried to show the requirements in the following diagram.


If we break the configuration down, between the Telephony service, and H323 routing components, the configuration to meet the requirements is pretty straight forward.

Telephony service.
Pre version 4.0 CME redundancy was achieved by using HSRP between the two CME routers, however we can now configure the standby router address directly, this will then assign two "callmanager" addresses to the IP phones (this is similar to a CallManager group if we were using CUCM). In the device menu on the phone the addresses will be listed, with an indication showing which address is the current active address.

To do this the following command is entered on the router that will be the primary CME (as per the address listed as DHCP option 150 :

ip source 192.168.1.1 port 2000 secondary 192.168.1.2

on the secondary router, you can get by with listing only one address:

ip source 192.168.1.2 port 2000

At this point both routers will now be running the Telephony Service, and will accept phone registrations. The rest of the Telephony service will now be identical between the two routers. All call routing will be done via the Dial-peer configurations.

Outbound calls:
For simplification only one dial-peer will be configured.

The following dial-peer will send all traffic destined for 9T (using 9 as the PSTN access number) to the local PSTN voice-port. This dial-peer will be configured exactly the same on on both routers

dial-peer voice 9 pots
destination-pattern 9.T
port 0/0/0:23

If this port is unavailable for some reason due to either a fault, or no available channels, all outbound traffic should be sent to the standby router. Being a VOIP dial-peer all matched digits will be forwarded. The call will then match the PSTN dial-peer that has been configured on the standby router, as mentioned above.

dial-peer voice 91 voip
destination-pattern 9.T
preference 2
session target ipv4:192.168.1.2

Inbound Calls:
Inbound calls require a slightly trickier configuration, as we have to make sure that calls that are received on the standby router are forwarded to the active router, even though the standby router has the same ephone-dn's configured on it as the active router.

There a several ways this can be achieved, the first is to create a dial-peer for each DN and give this dial-peer as better preference than that of the ephone-dn's. Though this will work, it involves quite a bit of extra configuration should new ephones be added.

The method I decided on was to create two dial-peers that matched the digits received from the telco, the most preferred dial-peer will forward the call to the active router, without any translation. The second less preferred dial-peer will match the digits received from the telco, translate the received digits to those of the ephones, and then have itself configured as the dial-peer. This way inbound calls will only be received by the standby router destined for the ephone-dn's them selves if the active router is unavailable.

Hopefully the following configuration will "clarify" things.

First as this solution means that we cannot use the dial-plan configuration command, an inbound translation rule will need to be defined. For our example, the full e164 number is 450-2300 - 450-2399 mapping to extns 1300-1399

voice translation-rule 1
rule 1 /\^45023\(..$\)/ /1\1/

voice translation-profile pstnIN
translate called 1

This translation will need to be configured on both routers.
On the active router, the translation profile is applied to the default inbound pots dial-peer, and the default inbound voip dial-peer (this is required as the standby router is going to forward all digits)

dial-peer voice 1 pots
direct-inward-dial
incoming called-number .
translation-profile incoming pstnIN
port 0/0/0:23

dial-peer voice 1 voip
incoming called-number .
translation-profile incoming pstnIN
codec g711ulaw
no vad

On the standby router, similar dial-peers are configures, however this time the translation profile is not applied to the default pots dial-peer. Tow additional dial-peers are added to forward the inbound PSTN traffic to the active router as first preference, then back to itself as second preference, the number is then translated at this point to match the numbers that have been assigned to the ephones.

dial-peer voice 1 pots
direct-inward-dial
incoming called-number .
port 0/0/0:23

dial-peer voice 1 voip
incoming called-number .
translation-profile incoming pstnIN
codec g711ulaw
no vad

dial-peer voice 91 voip
destination-pattern 45023..
session target ipv4:192.168.1.1

dial-peer voice 91 voip
destination-pattern 45023...
preference 2
translation-profile outgoing pstnIN
session target ipv4:192.168.1.2

That is pretty much it.

Wednesday, July 23, 2008

UCM 6.1 DHCP Server

One of the first problems I came across when playing with CallManager 6.1  was what to do in regards to the DHCP server.   Luckily Cisco have provided a "simple" integrated DHCP server.  Simple? yes it is somewhat lacking in flexibility and worse still would be  impossible to use in any sort of enterprise environment.  There are no tools to see assigned addresses, no way to configure static assignments and debugging errors are a bit of a nightmare.

So how does the DHCP service on CUCM 6.1 fit together?

The DHCP service is configured via the Cisco Unified CM Administration Web page.  There are two sub menu options:

The first configures the DHCP server itself



The second configures the DHCP subnet (this is the equivalent to a scope in the Microsoft world)

As can be seen a number of fields appear on both the server wide  configuration page, or they can be overwritten on a per subnet level.  Most of the fields are fairly self explanatory, the last four fields are concerned with DHCP timeouts:

ARP Cache Timeout:

IP Address Lease Timeout:

Renewal(T1) Time: 
T1 is the time at which the client enters the RENEWING state and attempts to contact the server.
 
Rebinding(T2) Time: 
T2 is the time at which the client enters the REBINDING state and attempts to contact any server.

T1 MUST be earlier than T2, which MUST be earlier than the time at which the client's lease will expire, it should also be noted that according to the dhcpd-option man page. the Renewal and Rebinding timings are not user settable, so I am unsure as to what there actual impact is - if you do set a value here it is not reflected in the generated dhcpd.conf file.

Once entered the fields are inserted into the CCM database into two tables, DhcpServer, and DhcpSubnet.  

in this example a DHCP server has been configured on a CCM with an ip address of 192.168.28.100

select * from DhcpServer;

pkid   fkprocessnode    domainnameserver1 domainnameserver2 tftpserver1    tftpserver2 nextserver domainname     arpcachetimeout ipaddrleasetime t1 t2 tftpservername 
====   =============    ================= ================= ============== =========== ========== ============== =============== =============== == == ============== 
xxxx   xxxx-xxxxxxxx                                        192.168.28.100                        ciscolab.local 0               0               0  0    

select * from DhcpSubnet;
             
pkid   fkdhcpserver  subnet       ipaddrfrom1   ipaddrfrom2 ipaddrto1     ipaddrto2 router1      router2 subnetmask    domainname     arpcachetimeout domainnameserver1 domainnameserver2 tftpserver1 tftpserver2 ipaddrleasetime nextserver t1   t2  tftpservername 
====   ============  ============ ============= =========== ============= ========= ============ ======= ============= ============== =============== ================= ================= =========== =========== ============    ========== ==== === ============== 
xxxx xxxx-xxxxxxxxxx 192.168.29.0 192.168.29.10             192.168.29.30           192.168.29.1         255.255.255.0 ciscolab.local 0                 

At this point the DHCP Monitor service will be triggered, the DHCP service itself is the standard dhcp daemon (v3.01)  that is included with  Redhat.  The DHCP Monitor service will read the tables form the database write the dhcpd.conf file located at /etc/dhcpd.conf, and reset  dhcpd.  

The dhcpd.conf file written is shown below.

#more /etc/dhcpd.conf
ddns-update-style ad-hoc;
option tftp-server-address code 150 = array of ip-address;
option domain-name "ciscolab.local";
option tftp-server-address 192.168.28.100;
next-server 0.0.0.0;

subnet 192.168.28.0 netmask 255.255.255.0 {
}
subnet 192.168.29.0 netmask 255.255.255.0 {
    range 192.168.29.10 192.168.29.30;
    option routers 192.168.29.1;
    option domain-name "ciscolab.local";
    option arp-cache-timeout 800;
    default-lease-time 36400;
}

As mentioned earlier, there are no simple ways to see leased addresses.  This does make it difficult to fault find and use.  Debugging log information is available via RTMT, however they do take some working through to see what is happening.