Пишем голосовое IVR меню на языке TCL, с использованием Cisco IVR API

Сегодня речь о голосовом меню (IVR) для маршрутизаторов Cisco, которое мы будем писать на языке TCL, и подключать на Cisco 3845.

Итак, для начала давайте разберемся в азах


Cisco начиная с версии IOS 12 поддерживает как VXML так и TCL скрипты для работы с голосовым меню. Однако, в отличии от VXML, скрипты на TCL имеют гораздо больше возможностей взаимодействия с Cisco IVR API. Так же существует возможность подключать гибридные IVR скрипты, со встроенными кусками VXML кода внутри TCL скрипта.

Все документы, связанные с IVR от Cisco, которые мне довелось получить можно скачать здесь.


Первое это FSM переходы.
Finite-State Machines — абстрактный автомат, число возможных внутренних состояний которого конечно.
Выглядит это примерно так:

set ivr_fsm(CALLCOMES,ev_setup_indication) "act_Setup same_state"

Переходов таких может быть сколько угодно, и расположены они в конце TCL скрипта.

Давайте разберемся, что это вообще такое.
Общий синтаксис этой команды таков:

set array(CURRSTATE, curr_event) “act_proc NEXTSTATE”

где:\ array – это имя FSM массива.
CURRSTATE – имя текущего состояния, при котором получено событие curr_event .
act_proc – имя функции, которую необходимо выполнить при поступлении события curr_event .
NEXTSTATE – имя состояния, которое установится после выполнения act_proc .

Другими словами, FSM это маркер, по которому Cisco сравнивает полученное от API событие с curr_event и текущий статус с CURRSTATE, если в каком либо FSM переходе они описаны, вызывается процедура act_proc и состояние изменяется на NEXTSTATE.

Самое главное в этом — это то, что текущее событие и состояние сравниваются со всеми описанными FSM переходами одновременно. Т.е. для Cisco не имеет значения порядок, в котором расположены FSM переходы, все они обрабатываются асинхронно.


Второй момент, это сами функции, которые должны быть описаны до инициализации скрипта.

Назначение всех команд и состояний подробно описано в файле tcl_ivr_2.0_programming_guide, который вы можете скачать здесь, я поподробнее остановлюсь только на тех, которые буду использовать непосредственно в скрипте


Начало любого TCL IVR скрипта содержит процедуру init , в моем примере эта функция выглядит так:

proc init { } {
    puts "\n proc Init start"
    global param
}

Здесь по сути выполняется вывод на экран командой puts «…» и определение глобальной переменной param

Инициализация скрипта происходит после описания всех функций, и начинается с запуска функции init . На этом простые вещи закончились, дальше все гораздо интереснее.

Последней исполняемой строкой скрипта должна быть строка определения стартового FSM перехода и стартового состояния. В нашем случае это:

fsm define ivr_fsm CALLCOMES

Это значит, что имя FSM массива задано как ivr_fsm, и стартовое состояние CALLCOMES. С инициализацией закончим, дальше будет понятнее, что происходит (я надеюсь).

proc Play_Welcome { } {
    puts "\n\n IVR - proc Play_Welcome start \n\n"
    global playng_files
    global param
    global pattern
    global numbers
    global workingtime

    #Вызываем процедуру, где описаны все переменные
    init_perCallVars

    #Получаем время
    GetDate

    #В зависимости рабочее сейчас время или нет, устанавливаем приветствие
    if {$workingtime} {
    set after_welcome $playng_files(takenumber)
    } else {
    set after_welcome $playng_files(noworking)
    }

    #Устанавливаем параметры подключения входящего вызова
    set param(interruptPrompt) true
    set param(abortKey) *
    set param(terminationKey) #

    #Подключаем входящий вызов
    leg setupack leg_incoming
    leg proceeding leg_incoming
    leg connect leg_incoming

    #Запускаем процедуру сбора нажатых цифр со стороны звонящего
    leg collectdigits leg_incoming param pattern

    #Запускаем проигрыш файлов звонящему абоненту, после их окончания
    #начнет действовать таймер param(interDigitTimeout), по истечении которого
    #Произойдет событие ev_collectdigits_done
    media play leg_incoming %s500 $playng_files(welcome) $after_welcome $playng_files(onhold)

    #Запускаем таймер, по истечении которого произойдет событие ev_named_timer
    timer start named_timer $numbers(waiting_time) t1
}

Здесь довольно подробно все описано.

Результатом выполнения данной процедуры будет подключение входящей линии к Cisco за счет команд leg setupack, leg proceeding, leg connect, и проигрыш музыкальных файлов по очереди во входящую линию за счет команды media play leg_incoming . Тут же запускается процесс сбора нажатых клавиш leg collectdigit и таймер командой timer start .

И проверяется рабочее сейчас время или нет, вызывая функцию GetDate :

proc GetDate { } {
    global workingtime

    #Час
    set houris [clock format [clock seconds] -format %H]
    #День недели
    set dayis [clock format [clock seconds] -format %A]
    #Проверяем рабочее время
    if {$houris> 17 || $houris <8 || $dayis=="Sunday" || $dayis=="Saturday"} {
    set workingtime 0
    } else {
    set workingtime 1
    }
}

В зависимости от того рабочее время или нет мы меняем музыкальный файл, который будет проигран звонящему абоненту.

Так как стартовое состояние задано в нашем случае как fsm define ivr_fsm CALLCOMES , в него попадают сразу 3 FSM:

set ivr_fsm(CALLCOMES,ev_setup_indication)    "Play_Welcome,   same_state"
set ivr_fsm(CALLCOMES,ev_collectdigits_done)  "CheckDestanation,  same_state"
set ivr_fsm(CALLCOMES,ev_named_timer)          "GoToReception,      same_state"

Событие ev_setup_indication произойдет при поступлении звонка, и будет запущена процедура Play_Welcome , в которой описан старт процесса сбора нажатых цифр и старт таймера.

После окончания проигрывания музыки абоненту, начнется обратный отчет таймера, который задается параметром param(initialDigitTimeout) (который можно было задать чуть выше строкой set param(initialDigitTimeout) 15 и установить значение 15 секунд), т.к. он у нас не указан, его стандартное значение 10 секунд, после чего скрипт получит событие ev_collectdigits_done , при наступлении которого, как мы описали в FSM переходе, будет выполнена функция CheckDestanation .

Таймер, запущенный в Play_Welcome командой:

#Тип таймера named_timer, длительность, взята из переменной numbers(waiting_time), имя таймера t1
timer start named_timer $numbers(waiting_time) t1

После своего окончания сгенерирует событие ev_named_timer , которое будет обработано следующим FSM переходом:

set ivr_fsm(CALLCOMES,ev_named_timer)   "GoToReception,      same_state"

и вызовется процедура GoToReception .

proc CheckDestanation { } {
    puts "\n\n IVR - proc CheckDestanation start \n\n"
    global playng_files
    global numbers
    global digit
    #Останавливаем проигрыш медиа
    media stop leg_incoming

    #Определяем значение переменным
    set status [infotag get evt_status]
    set digit [infotag get evt_dcdigits]

    #Сравниваем полученные цифры и статусы
    #Если введенная цифра соответствует той, что задана в $numbers(fast_reception),
    #изменяем  digit  на номер ресепшн и передаем $digit в функцию CheckCallersAndConnect,
    # предварительно изменив статус на CALLCONNECTED,
    # благодаря которому, при наступлении события ev_setup_done (подключение к номеру секретаря)
    # будет отработана процедура CallIsConnect
    if {$digit == $numbers(fast_reception)} {
   puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next reception \n\n"
        fsm setstate CALLCONNECTED
        set digit $numbers(reception)
        #Передаем $digit в функцию CheckCallersAndConnect
        CheckCallersAndConnect $digit
    #Если введенная цифра соответствует той, что задана в $numbers(fast_ckp), подключаем на ЦКП
    #через CheckCallersAndConnect
    } elseif {$digit == $numbers(fast_ckp)} {
        puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next CKP \n\n"
        fsm setstate CALLCONNECTED
        set digit $numbers(ckp)
        #Передаем $digit в функцию CheckCallersAndConnect
        CheckCallersAndConnect $digit
    #Если введенная цифра соответствует той, что задана в $numbers(fast_fax), подключаем на факс
    #через CheckCallersAndConnect
    } elseif {$digit == $numbers(fast_fax)} {
        puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next fax \n\n"
        fsm setstate CALLCONNECTED
        set digit $numbers(fax)
        #Передаем $digit в функцию CheckCallersAndConnect
        CheckCallersAndConnect $digit
    #Если статус = cd_004 (введены корректные цифры номера) - подключаем к нужному номеру
    #через CheckCallersAndConnect
    } elseif {$status == "cd_004"} {
        puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
        fsm setstate CALLCONNECTED
        #Передаем $digit в функцию CheckCallersAndConnect
        CheckCallersAndConnect $digit
    #Если статус = cd_005 (совпадение с dial plan) - подключаем к нужному номеру
    #через CheckCallersAndConnect
    } elseif {$status == "cd_005"} {
        puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
        fsm setstate CALLCONNECTED
        #Передаем $digit в функцию CheckCallersAndConnect
        CheckCallersAndConnect $digit
    #Если статус = cd_006 (набран не существующий номер) - играем в линию $playng_files(noexist)
    # и изменяем статус на TORECEPTION, при действии которого и наступлении события
    #ev_media_done (конец проигрывания звукового файла) вызовется процедура Play_TakeNumber
    } elseif {$status == "cd_006"} {
        puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
        fsm setstate TRYAGAIN
        media play leg_incoming $playng_files(noexist)
    #Во всех остальных случаях изменяем статус на TORECEPTION, при действии которого
    #и наступлении события ev_media_done (конец проигрывания звукового файла) вызовется
    #процедура GoToReception
    } else {
        #Проигрываем "Ваш вызов переадресовывается на секретаря"
        fsm setstate TORECEPTION
        media play leg_incoming $playng_files(toreception)
        puts "\n\n IVR - proc CheckDestanation status = $status \n\n"
    }
}

В процедуре CheckDestanation, которая будет вызвана после набора номера звонящим абонентом, мы сравниваем полученные при наборе цифры с настройками и переводим скрипт в соответствующее состояние командой fsm setstate .\ \ Все состояния, попавшие в функцию, попадают под следующие FSM переходы:

set ivr_fsm(CALLCONNECTED,ev_setup_done)   "CallIsConnect,  same_state"
set ivr_fsm(TORECEPTION,ev_media_done)   "GoToReception,  same_state"
set ivr_fsm(TRYAGAIN,ev_media_done)           "Play_TakeNumber,  TRYING"
set ivr_fsm(TRYING,ev_collectdigits_done)  "CheckDestanation,  same_state"
set ivr_fsm(TRYING,ev_named_timer)   "GoToReception,  same_state"

Давайте по порядку.\

1) Итак, изначально функция CheckDestanation вызывается после окончания процедуры сбора нажатия клавиш.
2) Информацию о нажатых клавишах мы записываем в переменную digit с помощью команды set digit [infotag get evt_dcdigits]
Аналогично записываем состояние линии в переменную status
3) Затем сравниваем полученные результаты с заданными переменными и изменяем состояние скрипта при совпадении:

if {$digit == $numbers(fast_reception)} {
 puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next reception \n\n"
 fsm setstate CALLCONNECTED
 leg setup $numbers(reception) callinfo leg_incoming
}
proc CheckCallersAndConnect {digit} {
 puts "\n\n IVR - proc CheckCallersAndConnect start \n\n"

 set callernumber [infotag get leg_ani]

 switch $callernumber {
  "9120000000" {set callInfo(displayInfo) "Director(mobile)"}
  "9130000000" {set callInfo(displayInfo) "Buhgalter(mobile)"}
  default {}
 }
 puts "\n\n IVR - caller is $callernumber connect with $digit\n\n"

 leg setup $digit callInfo leg_incoming
}

Данная функция позволяет изменить поле, отвечающее за написание имени звонящего. Просто ради эстетики, будет приятнее, когда на телефоне будет написан не только номер но и ID абонента. После изменения ID абонента происходит подключение линии к требуемому номеру.

proc CallIsConnect { } {
    puts "\n\n IVR - proc CallIsConnect start \n\n"
    global playng_files

    #Определяем чему равен status
    set status [infotag get evt_status]

    #Если статус равен ls_000 (успешное соединение с требуемым номером), изменяем состояние на CALLACTIVE
    if {$status == "ls_000"} {
    fsm setstate CALLACTIVE

    #Если статус равен ls_002 (никто не ответил на звонок), запускаем процедуру запроса номера
    } elseif {$status == "ls_002"} {
   fsm setstate TRYAGAIN
        media play leg_incoming $playng_files(noanswer)
    #Если статус - неверный номер, запускаем процедуру запроса номера
    } elseif {$status == "ls_004" || $status == "ls_005" || $status == "ls_006"} {
        fsm setstate TRYAGAIN
        media play leg_incoming $playng_files(noexist)
    #Если статус равен ls_007 (абонент занят), запускаем процедуру запроса номера
    } elseif {$status == "ls_007"} {
        fsm setstate TRYAGAIN
        media play leg_incoming $playng_files(busy)
    }
}

Данная функция вызывается следующим FSM переходом:

set ivr_fsm(CALLCONNECTED,ev_setup_done)   "CallIsConnect,  same_state"

Событие ev_setup_done наступает после подключения звонящего к требуемой линии.

proc Play_TakeNumber { } {
    puts "\n\n IVR - proc Play_TakeNumber start \n\n"
    global playng_files
    global numbers
    global param
    global pattern

    #Проверяем какой раз абонент пытается набрать номер
    if {$numbers(cur_try) <= $numbers(max_try)} {
    puts "\n\n IVR - proc Play_TakeNumber current try is: $numbers(cur_try) \n\n"
    incr numbers(cur_try)

    #Запускаем процедуру сбора нажатых цифр со стороны звонящего
    leg collectdigits leg_incoming param pattern

    #Запускаем проигрыш файлов
    media play leg_incoming $playng_files(takenumber)

    #Запускаем таймер, по истечении которого произойдет событие ev_named_timer
    timer start named_timer $numbers(waiting_time) t1

    #Если попытка больше чем $numbers(max_try) - разъединяем
    } else {
   fsm setstate CALLDISCONNECTED
        media play leg_incoming $playng_files(callafter)
    }
}

Данная функция проверяет какой раз ошибается звонящий, и если значение меньше чем $numbers(max_try) просит ввести номер еще раз.\ \ Данная функция вызывается следующими FSM:

set ivr_fsm(TRYAGAIN,ev_media_done)   "Play_TakeNumber,     TRYING"
set ivr_fsm(TRYING,ev_collectdigits_done)            "CheckDestanation,     same_state"
set ivr_fsm(TRYING,ev_named_timer)                "GoToReception,             same_state"
proc AbortCall { } {
 puts "\n\n IVR - proc AbortCall start \n\n"
 call close
}

Вызывается следующими FSM:

set ivr_fsm(any_state,ev_disconnected)   "AbortCall,   same_state"
set ivr_fsm(CALLACTIVE,ev_disconnected)     "AbortCall,  CALLDISCONNECTED"
set ivr_fsm(CALLDISCONNECTED,ev_disconnected)   "AbortCall,  same_state"
set ivr_fsm(CALLDISCONNECTED,ev_media_done)    "AbortCall,  same_state"
set ivr_fsm(CALLDISCONNECTED,ev_disconnect_done)  "AbortCall,  same_state"


Подключение на маршрутизаторе Cisco проходит в 2 этапа.
Первое, что нужно сделать это определить application:

application
 service voicemunu flash:voicemenu.tcl
  param allowed_pattern 5[5-7]..
  param fastto_reception 1
  param reception_number 5501
  param fastto_ckp 2
  param ckp_number 5604
  param fastto_fax 3
  param fax_number 5555
  param waiting_time 20
  param max_try 3
  param file_noanswer flash:en_noanswer.au
  param file_after flash:en_after.au
  param file_noexist flash:en_noexist.au
  param file_busy flash:en_busy.au
  param file_welcome flash:en_welcome.au
  param file_onhold flash:music-on-hold.au
  param file_noworking flash:en_takenumber2.au
  param file_takenumber flash:en_takenumber2.au

Второе, подключить service к dial-peer :

dial-peer voice 200 pots
 description -= ISP Beeline - INcoming call to number 3300100 =-
 service voicemunu
 incoming called-number 3300100

Таким образом, при поступлении звонка на номер 3300100, произойдет вызов нашего голосового меню voicemunu .


Выше были рассмотрены только основные функции скрипта, далее полный текст, имейте в виду, это практически самый простой вариант:

#######################################################
# Cisco IVR TCL script by Konovalov D.A. v.2
#######################################################
#
#	Для дебага скрипта
#		debug voip application script
#	Более полный дебага (не рекомендуется, может привести к перегрузке)
# 		debug voip ivr
# 
# Скрипт должен быть запущен со следующими параметрами:
# 	param allowed_pattern 5[5-7]..
# 	param fastto_reception 1
# 	param reception_number 5501
#	param fastto_ckp 2
# 	param ckp_number 5604
# 	param fastto_fax 3
# 	param fax_number 5555
# 	param waiting_time 20
# 	param max_try 3
# 	param file_welcome flash:en_welcome.au
# 	param file_takenumber flash:en_takenumber.au
# 	param file_after flash:en_after.au
# 	param file_busy flash:en_busy.au
# 	param file_noexist flash:en_noexist.au
# 	param file_noanswer flash:en_noanswer.au
# 	param file_onhold flash:music-on-hold.au
#	param file_noworking flash:music-on-hold.au

#Процедура инициализации скрипта
proc init { } {
    puts "\n proc Init start"
    global param
}

#Процедура с переменными
proc init_perCallVars { } {
	global pattern
	global numbers
	global playng_files
	
	#####Допустимая нумерация
	#Если в параметрах скрипта не указана допустимая нумерация, будет установлено значение .... - 4 любых цифры
	if {[infotag get cfg_avpair_exists allowed_pattern]} {
		set pattern(1) [string trim [infotag get cfg_avpair allowed_pattern]]
		puts "\n\n IVR - Allowed pattern set as: $pattern(1) \n\n"
		} else {
			set pattern(1) ....
			puts "\n\n IVR - Allowed pattern set as DEFAULT: $pattern(1) \n\n"
			}	
	#####Номера
	#Секретарь. Если в параметрах скрипта не указан номер секретаря, номер будет установлен в 0000
	if {[infotag get cfg_avpair_exists reception_number]} {
		set numbers(reception) [string trim [infotag get cfg_avpair reception_number]]
		puts "\n\n IVR - reception number set as: $numbers(reception) \n\n"
		} else {			
			set numbers(reception) 0000
			puts "\n\n IVR - reception number set as DEFAULT: $numbers(reception) \n\n"
			}	
	#ЦКП 
	if {[infotag get cfg_avpair_exists ckp_number]} {
		set numbers(ckp) [string trim [infotag get cfg_avpair ckp_number]]
		puts "\n\n IVR - ckp number set as: $numbers(ckp) \n\n"
		} else {			
			set numbers(ckp) 0000
			puts "\n\n IVR - ckp number set as DEFAULT: $numbers(ckp) \n\n"
			}
	#Факс 
	if {[infotag get cfg_avpair_exists fax_number]} {
		set numbers(fax) [string trim [infotag get cfg_avpair fax_number]]
		puts "\n\n IVR - fax number set as: $numbers(fax) \n\n"
		} else {			
			set numbers(fax) 0000
			puts "\n\n IVR - fax number set as DEFAULT: $numbers(fax) \n\n"
			}			
	#Быстрый перевод на Ресепшн 
	if {[infotag get cfg_avpair_exists fastto_reception]} {
		set numbers(fast_reception) [string trim [infotag get cfg_avpair fastto_reception]]
		puts "\n\n IVR - fast to reception set as: $numbers(fast_reception) \n\n"
		} else {			
			set numbers(fast_reception) 1
			puts "\n\n IVR - fast to reception set as DEFAULT: $numbers(fast_reception) \n\n"
			}
	#Быстрый перевод на ЦКП 
	if {[infotag get cfg_avpair_exists fastto_ckp]} {
		set numbers(fast_ckp) [string trim [infotag get cfg_avpair fastto_ckp]]
		puts "\n\n IVR - fast to ckp set as: $numbers(fast_ckp) \n\n"
		} else {			
			set numbers(fast_ckp) 2
			puts "\n\n IVR - fast to ckp set as DEFAULT: $numbers(fast_ckp) \n\n"
			}
	#Быстрый перевод на факс 
	if {[infotag get cfg_avpair_exists fastto_fax]} {
		set numbers(fast_fax) [string trim [infotag get cfg_avpair fastto_fax]]
		puts "\n\n IVR - fast to fax set as: $numbers(fast_fax) \n\n"
		} else {			
			set numbers(fast_fax) 3
			puts "\n\n IVR - fast to fax set as DEFAULT: $numbers(fast_fax) \n\n"
			}
	#Время ожидания введения номера (должно быть больше времени проигрыша всех файлов приветствия)
	if {[infotag get cfg_avpair_exists waiting_time]} {
		set numbers(waiting_time) [string trim [infotag get cfg_avpair waiting_time]]
		puts "\n\n IVR - wait number set as: $numbers(waiting_time) \n\n"
		} else {			
			set numbers(waiting_time) 10
			puts "\n\n IVR - wait number set as DEFAULT: $numbers(waiting_time) \n\n"
			}
	#Количество попыток ввести правильный номер, прежде чем звонок будет переведен на секретаря
	if {[infotag get cfg_avpair_exists max_try]} {
		set numbers(max_try) [string trim [infotag get cfg_avpair max_try]]
		puts "\n\n IVR - max try set as: $numbers(max_try) \n\n"
		set numbers(cur_try) 0
		} else {			
			set numbers(max_try) 5
			puts "\n\n IVR - max try set as DEFAULT: $numbers(max_try) \n\n"
			set numbers(cur_try) 0
			}
	#####Музыкальные файлы, которые будут проигрываться		
	#Файл приветствия
	if {[infotag get cfg_avpair_exists file_welcome]} {
		set playng_files(welcome) [string trim [infotag get cfg_avpair file_welcome]]
		puts "\n\n IVR - file_welcome set as: $playng_files(welcome) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(welcome) %s1
			puts "\n\n IVR - file_welcome set as DEFAULT: $playng_files(welcome) \n\n"
			}
	#Файл запроса ввести требуемый номер
	if {[infotag get cfg_avpair_exists file_takenumber]} {
		set playng_files(takenumber) [string trim [infotag get cfg_avpair file_takenumber]]
		puts "\n\n IVR - file_takenumber set as: $playng_files(takenumber) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(takenumber) %s1
			puts "\n\n IVR - file_takenumber set as DEFAULT: $playng_files(takenumber) \n\n"
			}
	#Файл "Пожалуйста перезвоните позднее"
	if {[infotag get cfg_avpair_exists file_after]} {
		set playng_files(callafter) [string trim [infotag get cfg_avpair file_after]]
		puts "\n\n IVR - file_after set as: $playng_files(callafter) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(callafter) %s1
			puts "\n\n IVR - file_after set as DEFAULT: $playng_files(callafter) \n\n"
			}
	#Файл "Номер занят"
	if {[infotag get cfg_avpair_exists file_busy]} {
		set playng_files(busy) [string trim [infotag get cfg_avpair file_busy]]
		puts "\n\n IVR - file_busy set as: $playng_files(busy) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(busy) %s1
			puts "\n\n IVR - file_busy set as DEFAULT: $playng_files(busy) \n\n"
			}
	#Файл "Номер не существует"
	if {[infotag get cfg_avpair_exists file_noexist]} {
		set playng_files(noexist) [string trim [infotag get cfg_avpair file_noexist]]
		puts "\n\n IVR - file_noexist set as: $playng_files(noexist) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(noexist) %s1
			puts "\n\n IVR - file_noexist set as DEFAULT: $playng_files(noexist) \n\n"
			}
	#Файл "Соеденяю с секретарем/оператором"
	if {[infotag get cfg_avpair_exists file_toreception]} {
		set playng_files(toreception) [string trim [infotag get cfg_avpair file_toreception]]
		puts "\n\n IVR - file_toreception set as: $playng_files(toreception) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(toreception) %s1
			puts "\n\n IVR - file_toreception set as DEFAULT: $playng_files(toreception) \n\n"
			}
	#Файл "Номер не отвечает, перезвоните позднее"
	if {[infotag get cfg_avpair_exists file_noanswer]} {
		set playng_files(noanswer) [string trim [infotag get cfg_avpair file_noanswer]]
		puts "\n\n IVR - file_noanswer set as: $playng_files(noanswer) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(noanswer) %s1
			puts "\n\n IVR - file_noanswer set as DEFAULT: $playng_files(noanswer) \n\n"
			}
	#Файл музыки, которая будет проигрываться при ожидании
	if {[infotag get cfg_avpair_exists file_onhold]} {
		set playng_files(onhold) [string trim [infotag get cfg_avpair file_onhold]]
		puts "\n\n IVR - file_onhold set as: $playng_files(onhold) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(onhold) %s1
			puts "\n\n IVR - file_onhold set as DEFAULT: $playng_files(onhold) \n\n"
			}
	#Файл музыки, которая будет проигрываться В нерабочее время
	if {[infotag get cfg_avpair_exists file_noworking]} {
		set playng_files(noworking) [string trim [infotag get cfg_avpair file_noworking]]
		puts "\n\n IVR - file_noworking set as: $playng_files(noworking) \n\n"
		} else {
			#Если файл не найден, он будет заменен на тишину в 1мс
			set playng_files(noworking) %s1
			puts "\n\n IVR - file_noworking set as DEFAULT: $playng_files(noworking) \n\n"
			}
}

proc GetDate { } {
	global workingtime
	
	#Час
	set houris [clock format [clock seconds] -format %H]
	#День недели
	set dayis [clock format [clock seconds] -format %A]
	#Проверяем рабочее время
	if {$houris > 17 || $houris < 8 || $dayis=="Sunday" || $dayis=="Saturday"} {
	set workingtime 0
	} else {
	set workingtime 1
	}
}

#Процедура проигрыша приветствия
proc Play_Welcome { } {
    puts "\n\n IVR - proc Play_Welcome start \n\n"
	global playng_files
	global param
	global pattern
	global numbers
	global workingtime
	
	#Вызываем процедуру, где описаны все переменные
	init_perCallVars
	
	#Получаем время
	GetDate
	#В зависимости рабочее сейчас время или нет, устанавливаем приветствие 
	if {$workingtime} {
	set after_welcome $playng_files(takenumber)	
	} else {
	set after_welcome $playng_files(noworking)
	}
	
	#Устанавливаем параметры подключения входящего вызова
	set param(interruptPrompt) true
	set param(abortKey) *
	set param(terminationKey) #	
	
	#Подключаем входящий вызов
    leg setupack leg_incoming
    leg proceeding leg_incoming
    leg connect leg_incoming
	
	#Запускаем процедуру сбора нажатых цифр со стороны звонящего
	leg collectdigits leg_incoming param pattern
	#Запускаем проигрыш файлов звонящему абоненту, после их окончания начнет 
	#действовать таймер param(interDigitTimeout), по истечении которого 
	#будет событие ev_collectdigits_done
    media play leg_incoming %s500 $playng_files(welcome) $after_welcome $playng_files(onhold)
	#Запускаем таймер, по истечении которого произойдет событие ev_named_timer
	timer start named_timer $numbers(waiting_time) t1
}

#Процедура запроса ввести номер
proc Play_TakeNumber { } {
    puts "\n\n IVR - proc Play_TakeNumber start \n\n"
	global playng_files
	global numbers
	global param
	global pattern
	
	#Проверяем какой раз абонент пытается набрать номер
	if {$numbers(cur_try) <= $numbers(max_try)} {
	puts "\n\n IVR - proc Play_TakeNumber current try is: $numbers(cur_try) \n\n"
	incr numbers(cur_try)
	#Запускаем процедуру сбора нажатых цифр со стороны звонящего
	leg collectdigits leg_incoming param pattern
	#Запускаем проигрыш файлов
	media play leg_incoming $playng_files(takenumber)
	#Запускаем таймер, по истечении которого произойдет событие ev_named_timer
	timer start named_timer $numbers(waiting_time) t1
	
	#Если попытка больше чем $numbers(max_try) - разъединяем
	} else { 
		fsm setstate CALLDISCONNECTED
		media play leg_incoming $playng_files(callafter)	
	}
}

#Процедура перевода звонка на секретаря
proc GoToReception { } {
	puts "\n\n IVR - proc GoToReception start \n\n"
	global numbers
	#Останавливаем проигрыш медиа
	media stop leg_incoming
	#Меняем состояние
	fsm setstate CALLCONNECTED
	
	set digit $numbers(reception)
	
	#Передаем $digit в функцию CheckCallersAndConnect
	CheckCallersAndConnect $digit
}

#Здесь проверяем введенные или не введенные звонящим цифры 
proc CheckDestanation { } {
    puts "\n\n IVR - proc CheckDestanation start \n\n"
	global playng_files
	global numbers
	global digit
	#Останавливаем проигрыш медиа
	media stop leg_incoming
	
	#Определяем значение переменным
    set status [infotag get evt_status]
	set digit [infotag get evt_dcdigits]
	
	#Сравниваем полученные цифры и статусы
	#Если введенная цифра соответствует той, что задана в $numbers(fast_reception), 
	#изменяем  digit  на номер ресепшн и передаем $digit в функцию CheckCallersAndConnect,
	# предварительно изменив статус на CALLCONNECTED,
	# благодаря которому, при наступлении события ev_setup_done (подключение к номеру секретаря)
	# будет отработана процедура CallIsConnect
	if {$digit == $numbers(fast_reception)} {
		puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next reception \n\n"
		fsm setstate CALLCONNECTED
		set digit $numbers(reception)
		#Передаем $digit в функцию CheckCallersAndConnect
		CheckCallersAndConnect $digit
	#Если введенная цифра соответствует той, что задана в $numbers(fast_ckp), подключаем на ЦКП 
	#через CheckCallersAndConnect
	} elseif {$digit == $numbers(fast_ckp)} {
		puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next CKP \n\n"
		fsm setstate CALLCONNECTED
		set digit $numbers(ckp)
		#Передаем $digit в функцию CheckCallersAndConnect
		CheckCallersAndConnect $digit
	#Если введенная цифра соответствует той, что задана в $numbers(fast_fax), подключаем на факс 
	#через CheckCallersAndConnect
	} elseif {$digit == $numbers(fast_fax)} {
		puts "\n\n IVR - proc CheckDestanation digit = $digit\nGoing to next fax \n\n"
		fsm setstate CALLCONNECTED
		set digit $numbers(fax)
		#Передаем $digit в функцию CheckCallersAndConnect
		CheckCallersAndConnect $digit
	#Если статус = cd_004 (введены корректные цифры номера) - подключаем к нужному номеру 
	#через CheckCallersAndConnect
	} elseif {$status == "cd_004"} {
		puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
		fsm setstate CALLCONNECTED
		#Передаем $digit в функцию CheckCallersAndConnect
		CheckCallersAndConnect $digit
	#Если статус = cd_005 (совпадение с dial plan) - подключаем к нужному номеру 
	#через CheckCallersAndConnect	
	} elseif {$status == "cd_005"} {
		puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
		fsm setstate CALLCONNECTED	
		#Передаем $digit в функцию CheckCallersAndConnect
		CheckCallersAndConnect $digit	
	#Если статус = cd_006 (набран не существующий номер) - играем в линию $playng_files(noexist) 
	#и изменяем статус на TRYAGAIN, при действии которого и наступлении события ev_media_done 
	#(конец проигрывания звукового файла) вызовется процедура Play_TakeNumber
	} elseif {$status == "cd_006"} {
		puts "\n\n IVR - proc CheckDestanation status = $status digit = $digit \n\n"
		fsm setstate TRYAGAIN
		media play leg_incoming $playng_files(noexist)
	#Во всех остальных случаях изменяем статус на TORECEPTION, при действии которого и 
	#наступлении события ev_media_done (конец проигрывания звукового файла) вызовется процедура GoToReception
	} else {
		#Проигрываем "Ваш вызов переадресовывается на секретаря"
		fsm setstate TORECEPTION
		media play leg_incoming $playng_files(toreception)		
		puts "\n\n IVR - proc CheckDestanation status = $status \n\n"
	}	
}

#Проверяем звонящего, если совпадает, будем менять отображаемое имя
proc CheckCallersAndConnect {digit} {
	puts "\n\n IVR - proc CheckCallersAndConnect start \n\n"
	
	set callernumber [infotag get leg_ani]
	
	 switch $callernumber {
	 "9120000000" {set callInfo(displayInfo) "Director(mobile)"}
	 "9130000000" {set callInfo(displayInfo) "Buhgalter(mobile)"}  
	 default {} 	
	}
	
	leg setup $digit callInfo leg_incoming
}

#Процедура проверки состоянии линии после подключения звонящего к требуемому номеру
proc CallIsConnect { } {
	puts "\n\n IVR - proc CallIsConnect start \n\n"
	global playng_files	

	#Определяем чему равен status
	set status [infotag get evt_status]
	
	#Если статус равен ls_000 (успешное соединение с требуемым номером), изменяем состояние на CALLACTIVE
	if {$status == "ls_000"} {
	fsm setstate CALLACTIVE	
	
	#Если статус равен ls_002 (никто не ответил на звонок), запускаем процедуру запроса номера
	} elseif {$status == "ls_002"} {
		fsm setstate TRYAGAIN
		media play leg_incoming $playng_files(noanswer)
	#Если статус - неверный номер, запускаем процедуру запроса номера
	} elseif {$status == "ls_004" || $status == "ls_005" || $status == "ls_006"} {
		fsm setstate TRYAGAIN
		media play leg_incoming $playng_files(noexist)
	#Если статус равен ls_007 (абонент занят), запускаем процедуру запроса номера
	} elseif {$status == "ls_007"} {
		fsm setstate TRYAGAIN
		media play leg_incoming $playng_files(busy)
	}
}

#Процедура прерывания звонка
proc AbortCall { } {
	puts "\n\n IVR - proc AbortCall start \n\n"
	call close
}

#Исполнение скрипта
init
#init_perCallVars

#Это набор состояний и возникающих при данных состояних событий
#По сути именно это и описывает работу скрипта
	
	#Если в любом состоянии возникнет событие отключения ev_disconnected, вызвать AbortCall
	set ivr_fsm(any_state,ev_disconnected)    			"AbortCall, 		same_state"
	
	#Если в состоянии CALLCOMES возникнет событие ev_setup_indication (входящий вызов)
	#запускается Play_Welcome, и состояние меняется на same_state (т.е. остается прежним)
	set ivr_fsm(CALLCOMES,ev_setup_indication)  		"Play_Welcome, 		same_state"
	
	#Если в состоянии CALLCOMES возникнет событие ev_collectdigits_done (закончен ввод цифр) 
	#запускается CheckDestanation, и состояние остается прежним
	set ivr_fsm(CALLCOMES,ev_collectdigits_done)		"CheckDestanation, 	same_state"
	
	#Если в состоянии CALLCOMES возникнет событие ev_named_timer (закончился таймер ожидания ввода цифр) 
	#запускается GoToReception, и состояние остается прежним
	set ivr_fsm(CALLCOMES,ev_named_timer)				"GoToReception, 	same_state"
	
	#Если в состоянии TORECEPTION возникнет событие ev_media_done (закончился проигрыш файла) 
	#запускается GoToReception, и состояние остается прежним
	set ivr_fsm(TORECEPTION,ev_media_done)         		"GoToReception, 	same_state"
	
	#Данные настройки описывают поведение скрипта при ошибке в номере
	set ivr_fsm(TRYAGAIN,ev_media_done)         		"Play_TakeNumber, 	TRYING"
	set ivr_fsm(TRYING,ev_collectdigits_done)			"CheckDestanation, 	same_state"
	set ivr_fsm(TRYING,ev_named_timer)					"GoToReception, 	same_state"
	
	#Если в состоянии CALLCONNECTED возникнет событие ev_setup_done 
	#(установлено/неустановлено соединение с требуемым номером) запускается CallIsConnect, и состояние остается прежним
	set ivr_fsm(CALLCONNECTED,ev_setup_done) 			"CallIsConnect,		same_state"
	
	#Эти события отрабатывают отключение линии
	set ivr_fsm(CALLACTIVE,ev_disconnected)   			"AbortCall,			CALLDISCONNECTED"
	set ivr_fsm(CALLDISCONNECTED,ev_disconnected) 		"AbortCall,			same_state"
	set ivr_fsm(CALLDISCONNECTED,ev_media_done)  		"AbortCall,			same_state"
	set ivr_fsm(CALLDISCONNECTED,ev_disconnect_done) 	"AbortCall,			same_state"

fsm define ivr_fsm CALLCOMES

Источник https://habr.com/ru/articles/265453/

  • work/telephony/cisco-ivr-tcl.txt
  • Последнее изменение: 2024/07/10 11:36
  • rolland