====== Пишем голосовое 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, которые мне довелось получить можно скачать [[https://cloud.mail.ru/public/FFBy/ZEujVorKp|здесь]].
==== FSM ====
\\
Первое это **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, который вы можете скачать [[https://cloud.mail.ru/public/FFBy/ZEujVorKp|здесь]], я поподробнее остановлюсь только на тех, которые буду использовать непосредственно в скрипте
===== 1) Инициализация скрипта =====
\\
Начало любого 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**. С инициализацией закончим, дальше будет понятнее, что происходит (я надеюсь).
===== 2) Приветствие =====
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//** .
===== 3) Проверка введенного номера =====
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
}
===== 4) Проверка номера звонящего абонента =====
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 абонента происходит подключение линии к требуемому номеру.
===== 5) Подключение номера =====
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**// наступает после подключения звонящего к требуемой линии.
===== 6) Повторный запрос номера =====
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"
===== 7) Разрыв соединения =====
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"
===== 8) Подключение скрипта =====
\\
Подключение на маршрутизаторе 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//** .
===== 9) Полная версия скрипта =====
\\
Выше были рассмотрены только основные функции скрипта, далее полный текст, имейте в виду, это практически самый простой вариант:
#######################################################
# 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/|https://habr.com/ru/articles/265453/]]