Это старая версия документа!


Пишем голосовое 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"
    }
}
''


</file>

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

<code>''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)
    }
}
''


</file>

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


''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)
    }
}
''


</file>

Данная функция проверяет какой раз ошибается звонящий, и если значение меньше чем **//$numbers(max_try)//**  просит ввести номер еще раз.\
\
Данная функция вызывается следующими **FSM**:
<code>''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
''


</file>

**Второе**, подключить //**service**//  к **//dial-peer//** :
<code>''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.1720599328.txt.gz
  • Последнее изменение: 2024/07/10 11:15
  • rolland