Now let's begin our first IVR application. As we mentioned before, an IVR application needs working with two fundamental interfaces: TAPI and Wave Driver for call control and media processing respectivelly.
Using TAPI:
1. Initialize the TAPI environment: lineInitializeEx
eg.
nRet = lineInitializeEx(
&gb_hLineApp,
AfxGetInstanceHandle(),
NULL,
THEAPP_NAME,
&dwChannelCnt,
&gb_dwgTAPIVersion,
lineParams );
2. Enum TAPI lines: lineGetDevCaps
eg.
lv_lstLines.RemoveAll();
for( DWORD i = 0;i < dwChannelCnt; i++ )
{
LINEEXTENSIONID lineExten;
ZeroMemory(&lineExten, sizeof(lineExten));
DWORD dwApiVer=0;
nRet = lineNegotiateAPIVersion(gb_hLineApp, i, 0x00010003, 0x00030000, &dwApiVer, &lineExten);
outputError(nRet);
if( nRet == 0 )
{
LPLINEDEVCAPS pDev = new LINEDEVCAPS;
pDev->dwTotalSize = sizeof(LINEDEVCAPS);
nRet = lineGetDevCaps(gb_hLineApp, i, dwApiVer, 0, pDev);
outputError(nRet);
if( pDev->dwNeededSize > pDev->dwTotalSize )
{
dwSize = pDev->dwNeededSize;
delete pDev;
pDev = (LPLINEDEVCAPS)new BYTE[dwSize];
pDev->dwTotalSize = dwSize;
nRet = lineGetDevCaps(gb_hLineApp, i, dwApiVer, 0, pDev);
}
if( nRet == 0 )
{
sLineName = (LPCTSTR)((LPBYTE)pDev + pDev->dwLineNameOffset);
WriteTrace(TraceDebug, "Debug - Enum get TAPI line: %d - %s, media mode: 0x%08X", i, sLineName, pDev->dwMediaModes);
// Only accept Cisco CTI Ports
/// Line Device
if( (pDev->dwMediaModes & (LINEMEDIAMODE_AUTOMATEDVOICE | LINEMEDIAMODE_INTERACTIVEVOICE)) == (LINEMEDIAMODE_AUTOMATEDVOICE | LINEMEDIAMODE_INTERACTIVEVOICE) )
{
sUpperLineName = sLineName;
sUpperLineName.MakeUpper();
if( sUpperLineName.Find("CISCO LINE") >= 0 )
{
/// Sort by DN
lv_nPos = sUpperLineName.Find("(");
lv_nPos2 = sUpperLineName.Find(")");
if( lv_nPos > 0 && lv_nPos2 > lv_nPos )
{
sTemp = sUpperLineName.Mid(lv_nPos+1, lv_nPos2-lv_nPos-1);
sUpperLineName.Format("DN%s-%s:%d", sTemp, sLineName, i);
}
else
sUpperLineName.Format("%s:%d", sLineName, i);
// Sort the List
lv_pos = lv_lstLines.GetHeadPosition();
while( lv_pos != NULL )
{
lv_posTemp = lv_pos;
sTemp = lv_lstLines.GetNext(lv_pos);
if( sTemp > sUpperLineName )
{
lv_lstLines.InsertBefore(lv_posTemp, sUpperLineName);
lv_pos = lv_posTemp;
break;
}
else if( sTemp == sUpperLineName )
{
lv_pos = lv_posTemp;
break;
}
}
if( lv_pos == NULL )
lv_lstLines.AddTail(sUpperLineName);
}
}
}
}
delete pDev;
}
}
3. Open TAPI line and set events filter: lineOpen, lineSetStatusMessages
eg.
ret = lineOpen(gb_hLineApp,
m_ID,
&m_hLine,
m_dwApiVersion,
0, 0, LINECALLPRIVILEGE_OWNER,
m_mediaMode,
NULL);
ret = lineSetStatusMessages(
m_hLine,
m_pLineDevCaps->dwLineStates,
0);
// Pickup after ? rings
lineSetNumRings(m_hLine, 0, m_intRingCnt);
4. Basic Call control APIs: lineAccept, lineAnswer, lineDrop, lineMakeCall...
eg:
if( m_hCall )
ret = lineAnswer(m_hCall, NULL, 0);
5. TAPI events processing: IVR should start a thread to track and process TAPI events
eg.
//*******************************************************************
//* A thread to receive TAPI line event to this thread
//*******************************************************************
UINT CTAPIManager::TapiEventThread(LPVOID pParam)
{
CTAPIManager* pCM = (CTAPIManager*) pParam;
MSG msg;
CString sz;
WriteTrace(TraceInfo, _T("SysInfo - TapiEventThread Started."));
while( !s_threadTerminate )
{
switch( WaitForSingleObject(gb_hLineEvent, 10) )
{
// Have we got any events?
case WAIT_OBJECT_0:
// I did not catch errors here
try
{
msg.message = WM_TAPI_LINEEVENT;
pCM->ProcessMessage(msg);
}
catch( CTapiObj::TEx e )
{
sz.Format(_T("TapiEventThread Err: [code:%d],")
_T("[date:%s],")
_T("[time:%s],")
_T("[reason:%s]\n"), e.code, e.date, e.time, e.result);
WriteTrace(TraceError, sz);
}
catch(...)
{
WriteTrace(TraceError, _T("TapiEventThread Error!!!"));
}
break;
// Sure we have nothing to process now
case WAIT_TIMEOUT:
break;
// Go on waiting and processing
default: continue;
}
}
WriteTrace(TraceInfo, _T("SysInfo - TapiEventThread Stopped."));
return 0;
}
6. When a call reaches a CTI RoutePoint, CTI RoutePoint should find an available IVR channel and redirect the call to that channel.
eg.
if( lv_hCall )
{
ret = lineRedirect(lv_hCall, lpszDestAddress, 0);
}
7. When get a call-offered event, usually the IVR channel (TAPI line) should accept the call, gether ANI/DNIS and enable DTMF collecting.
eg.
ret = lineGetCallInfo(hCall, lpCallInfo);
ret = lineMonitorDigits(lv_hCall, LINEDIGITMODE_DTMF);
Using Cisco Wave Driver to play and record:
1. Get Wave Handlers (In/Out) while opening TAPI line
eg.
long CTapiLine::GetWaveID()
{
VARSTRING *pVs;
LONG ret=0;
DWORD dwWaveDev;
DWORD dwSize;
CString strDevClass;
int patience;
//---------------------------------------------------------------------------
for( int lv_loop = 0; lv_loop < 2; lv_loop++ )
{
// allocate memory for structure
pVs = (VARSTRING *) calloc (1, 1024);
memset(pVs, 0x00, 1024);
// set structure size
pVs->dwTotalSize = 1024; //sizeof(VARSTRING);
if( lv_loop == 0 )
strDevClass = _T("wave/out");
else
strDevClass = _T("wave/in");
patience = 0;
do
{
// We have not more patience, stop the loop
if( ++patience > 100)
{
WriteTrace(TraceError, _T("TapiObj.cpp, IVR Channel: %d Line:%d GetWaveID(), Recovered from unlimited loop"), GetDeviceID(), m_ID);
return -1;
}
// Get wave/out ID
ret = lineGetID(
m_hLine,
0L,
0L,
LINECALLSELECT_LINE, // LINECALLSELECT_CALL, LINECALLSELECT_LINE
pVs,
(LPCSTR)strDevClass);
// check errors
if( ret )
{
free( pVs );
WriteTrace(TraceError, _T("TapiObj.cpp, IVR Channel: %d Line:%d lineGetID error: 0x%x"), GetDeviceID(), m_ID, ret);
return -1;
}
// reallocate and try again
if( pVs->dwTotalSize < pVs->dwNeededSize )
{
dwSize = pVs->dwNeededSize;
free( pVs );
pVs = (VARSTRING *) calloc(1, dwSize);
pVs->dwTotalSize = dwSize;
continue;
} // end if (need more space)
break; // success
} while (TRUE);
// copy wave id
dwWaveDev = (DWORD) *((DWORD *)((LPSTR)pVs + pVs->dwStringOffset));
free (pVs);
if( lv_loop == 0 )
m_dwWaveOutID = dwWaveDev;
else
{
m_dwWaveInID = dwWaveDev;
CCtrlWaveIn::SetMinDiskBuffer(0);
CCtrlWaveIn::SetMaxDiskBuffer(640);
CCtrlWaveIn::SetMaxSilenceTime(5);
CCtrlWaveIn::SetActiveThreshold(500);
}
}
return 0;
}
2.Open Wave In/Out devices
eg,
if( m_dwWaveInID == 0 )
{
m_mmr = waveInOpen(0, WAVE_MAPPER, &m_pcmWaveFormat, 0, 0, WAVE_FORMAT_QUERY);
if( m_mmr )
{
WriteTrace(TraceError, "IVR Channel: %d - CWaveInDevice(%d)::OpenDev0: waveInOpen error.", m_lngIVRChannelID, m_dwWaveInID);
return FALSE;
}
m_mmr = waveInOpen(&m_hIn, WAVE_MAPPER, &m_pcmWaveFormat, m_dwAudioDevId, s_dwInstance, CALLBACK_THREAD);
}
else
{
m_mmr = waveInOpen(&m_hIn, m_dwWaveInID, &m_pcmWaveFormat, m_dwAudioDevId, s_dwInstance, CALLBACK_THREAD | WAVE_MAPPED);
}
if( m_mmr )
{
WriteTrace(TraceError, "IVR Channel: %d - CWaveInDevice(%d)::OpenDev: waveInOpen error.", m_lngIVRChannelID, m_dwWaveInID);
return FALSE;
}
if( m_dwWaveOutID == 0 )
{
m_mmr = waveOutOpen(0, WAVE_MAPPER, &m_pcmWaveFormat, 0, 0, WAVE_FORMAT_QUERY);
if( m_mmr )
{
WriteTrace(TraceError, "IVR Channel: %d - CWaveOutDevice::OpenDev0: waveOutOpen error 0x%x.", m_lngIVRChannelID, m_mmr);
return FALSE;
}
m_mmr = waveOutOpen(&m_hOut, WAVE_MAPPER, &m_pcmWaveFormat, m_dwAudioDevId, s_dwInstance, CALLBACK_THREAD);
}
else
{
m_mmr = waveOutOpen(&m_hOut, m_dwWaveOutID, &m_pcmWaveFormat, m_dwAudioDevId, s_dwInstance, CALLBACK_THREAD | WAVE_MAPPED);
}
3. Start wave working threads
4. Play / Record
eg.
m_mmr = waveOutPrepareHeader(m_hOut, pwh, sizeof(WAVEHDR));
m_mmr = waveOutWrite(m_hOut, pwh, sizeof(WAVEHDR));
5. Close Wave Devices and stop threads
eg.
m_mmr = waveOutReset(m_hOut);
m_mmr = waveOutClose(m_hOut);
Call Transfer:
Following the above steps, we can implement an Automaic Response System (ARS). But in most cases, callers need talk to the Customer Service Representative (CSR). IVR should be able to transfer call to an agent or a skillset. Generally, there're two methods to accomplish call transfering:
1. SingleStepTransfer (or BlindTransfer)
It means IVR transfers calls regardless of the result of operation. This method is offen used in Callcenters with ACD component.
eg.
ret = lineBlindTransfer(m_hConsultCall, lpszDigits, 0);
2. Consultant & Transfer/Conference
It means IVR should make sure the transfer destination receives the call or even answer the call before finishing the operation. So this method is a two-step operation: Consultant and Transfer/Conference. If the destination doesn't receive or answer the call, IVR may retrieval the call for further treatment.
eg.
Step1:
ret = lineSetupConference(m_hCall, NULL, &m_hConferenceCall, &m_hConsultCall, 3, &lv_Param);
if( ret > 0 )
{
ret = lineDial(m_hConsultCall, lpszDigits, 0);
}
Step2:
ret = lineCompleteTransfer(m_hCall, m_hConsultCall, NULL, LINETRANSFERMODE_TRANSFER);
or
ret = lineAddToConference(m_hConferenceCall, m_hConsultCall);
Call Flow designer:
Most of IVR products provide GUI for call flow design, users can customerize call flow by drag-and-drop. VXML is a wide accepted standard in this scenario. The following diagram is the structure of a typical IVR system named PowerVoice.
In my 10 more years career in CTI field, I've obtained lots of help from my coworkers, venders and online resources. Now I relocated from China to Toronto and got some spare time. I'd like to share my experience in return, and practice writing in English at the same time.
Popular Posts
-
Now let's begin our first IVR application. As we mentioned before, an IVR application needs working with two fundamental interfaces: TAP...
-
To implement an IVR application, we should open two paths: call control path and media processing path. In the CTI port case, we use TAPI a...
-
To Develop an IP-IVR application on CISCO CallManager or CUCM platform, we usually have two options: 1. Based on CTI port / CTI routepoint ...
Tuesday, August 16, 2011
Develop IP-IVR on Cisco CallManager platform: 2
To implement an IVR application, we should open two paths: call control path and media processing path.
In the CTI port case, we use TAPI and Cisco Wave Driver for each of them.
Basic Features of call control includes:
1. Call distribution among a group of IVR channels;
2. Call Answer, Clear/Block, Hold, Retrive, Make call;
3. Transfer, Conference.
4. Collect DTMF input, ANI, DNIS.
Basic Features of media processing includes: Play (File, Buffer), Record, Beep and Tone detect.
First of all, let's setup development and testing environment, which involes the following steps:
CUCM Settings:
1. Add CTI Ports and CTI RoutePoints in CUCM
In the CTI port case, we use TAPI and Cisco Wave Driver for each of them.
Basic Features of call control includes:
1. Call distribution among a group of IVR channels;
2. Call Answer, Clear/Block, Hold, Retrive, Make call;
3. Transfer, Conference.
4. Collect DTMF input, ANI, DNIS.
Basic Features of media processing includes: Play (File, Buffer), Record, Beep and Tone detect.
First of all, let's setup development and testing environment, which involes the following steps:
CUCM Settings:
1. Add CTI Ports and CTI RoutePoints in CUCM
2. Add User (End User or Application User), and associates to CTI ports and RoutePoints.
IVR Server Settings (I recommend use virtual machine for IVR development and testing):
1. Download CiscoTSP (CCMAdmin->Application->Plugins)
2. Install and configure CiscoTSP on IVR server
3. Install Cisco Wave Driver
4. Test TSP environment on IVR server: usually use 'dialer.exe' coming with Windows.
Open a CTI port and make a call to an extension, if it works well, our developing environment is OK. If you can't find any CTI port in the line list, check the TSP log for detail.
So far we can start coding our IVR application.
(To be continued...)
Develop IP-IVR on Cisco CallManager platform: 1
To Develop an IP-IVR application on CISCO CallManager or CUCM platform, we usually have two options:
1. Based on CTI port / CTI routepoint resources, using TAPI as call control interface and Cisco wave driver as media processing interface.
2. Based on SIP protocol, IVR channels act as SIP phones or SIP trunks (only avaliable after CUCM5.0).
In terms of CTI tech, Option 1 is the third-part CTI while option 2 is the first-part CTI.
For option 2, we can rely on many dev-kits and open source codes, such as Dialogic HMP, Asterisk, sipX, oSIP, OPAL etc, and lead to a general IP-IVR application which is able to apply on any SIP compatible platform.
For option 1, we can only develop a dedicated IP-IVR for CUCM. However, we can get some outstanding advantages accordingly:
a> CTI port is free (no license fee so far), which means we need not pay extra charge on IP-PBX side when deploy IP-IVR system. (Think about it, if you are biding a callcenter with 100-200 ports IVR, you will definitely beat your competitors considering the cost)
b> Easy to implement and maintain. Not only because TAIP and wave programming are simple, but the structure of your application is flat.
c> No risk or potential risk of intellectual property rights
d> Supports almost all features of CISCO device, such as CTI monitoring.
The following posts are going to share my experience about CTI port and wave driver programming.
(To be continued...)
1. Based on CTI port / CTI routepoint resources, using TAPI as call control interface and Cisco wave driver as media processing interface.
2. Based on SIP protocol, IVR channels act as SIP phones or SIP trunks (only avaliable after CUCM5.0).
In terms of CTI tech, Option 1 is the third-part CTI while option 2 is the first-part CTI.
For option 2, we can rely on many dev-kits and open source codes, such as Dialogic HMP, Asterisk, sipX, oSIP, OPAL etc, and lead to a general IP-IVR application which is able to apply on any SIP compatible platform.
For option 1, we can only develop a dedicated IP-IVR for CUCM. However, we can get some outstanding advantages accordingly:
a> CTI port is free (no license fee so far), which means we need not pay extra charge on IP-PBX side when deploy IP-IVR system. (Think about it, if you are biding a callcenter with 100-200 ports IVR, you will definitely beat your competitors considering the cost)
b> Easy to implement and maintain. Not only because TAIP and wave programming are simple, but the structure of your application is flat.
c> No risk or potential risk of intellectual property rights
d> Supports almost all features of CISCO device, such as CTI monitoring.
The following posts are going to share my experience about CTI port and wave driver programming.
(To be continued...)
Subscribe to:
Posts (Atom)