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 ...
Subscribe to:
Post Comments (Atom)
I have UCCX installed. How can I call to my IPIVR script as there is no ICM.
ReplyDelete