====== Пишем голосовое 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/]]