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


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

</file>

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

Результатом выполнения данной процедуры будет подключение входящей линии к Cisco за счет команд //**leg setupack, leg proceeding, leg connect,**//  и проигрыш музыкальных файлов по очереди во входящую линию за счет команды //**media play leg_incoming**// .\\
Тут же запускается процесс сбора нажатых клавиш **//leg collectdigit//**  и таймер командой //**timer start**// .\\
\\
И проверяется рабочее сейчас время или нет, вызывая функцию //**GetDate**// :
<code>''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 .


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

<code>''####################################################### # 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 ''

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

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