Страницы

Cisco IOS IVR с помощью VXML

Давайте напишем VXML-скрипт, который будет работать на голосовом шлюзе Cisco в качестве IVR по приведенной схеме.
Алгоритм работы: при звонке на 2100 проиграть файл (Нажмите 1, 2 или 3). Если звонящий ничего не нажимает или нажимает, но не то – проигрываем приглашение снова и терпеливо ждем адекватных действий со стороны звонящего.
Если нажали 1, то переводим звонок на номер 9991, если 2, то предлагаем набрать внутренний номер абонента (четыре цифры, начинающиеся с 2 или 3), ну а если 3, то разрываем соединение, не забыв предварительно попрощаться.

Начало скрипта простое до неприличия:
<?xml version="1.0" encoding="UTF-8"?>
<vxml version = "2.1" >

<var name="DestNumber"/> <!-- Variable used to carry the destination number -->
Рассказали о себе (скрипте) и объявили переменную DestNumber, которую будем использовать для перевода звонков на внутренний номер.

В качестве эксперимента создал отдельную форму (form), которая используется только для проигрывания приветствия, которое нельзя "перебивать" вводом DTMF и переходом к следующей форме (id="Main").
<form id="Start">
 <block>
  <prompt bargein="false"><!-- You cannot interrupt this prompt -->
   <audio src="tftp://192.168.2.213/quickstart/audio/welcome.au"/>
  </prompt>
  <goto next="#Main"/><!-- Goto оно и в Африке Goto -->
 </block>
</form>
id тега <form> используется для переходов с помощью Goto. Параметр bargein="false" тега <prompt> запрещаете (false) или разрешает (true) прерывать проигрывание аудио-файлов вводом DTMF.
В качестве места хранения голосовых сообщений использовал TFTP, чтобы не копировать все файлы на лабораторный маршрутизатор. Следует учитывать задержку на копирование по TFTP при первом проигрывании, а также то, что роутер кэширует файлик у себя (в RAM?) и при замене файла на стороне TFTP-сервера, роутер просто так перекачивать файлик не будет (если знаете как сбросить такой кэш для того, чтобы роутер перезалил файл – напишите, пожалуйста в комментариях).

Очень полезно дебажить скрипт командой
debug voip application vxml error

Ниже приведен вывод дебага, когда я использовал тег <prompt> сразу после тега <form>, методом наблюдения за другими скриптами оказалось, что в этом случаем <prompt> нужно "обернуть" с помощью <block> (пример выше).
Lab_BB1_CME(config-app-param)#do deb voip app vxml erro
vxml software & call error debugging are on

Lab_BB1_CME(config-app-param)#
Feb  1 10:53:38.966: //0/77A7A02681C2/VXML:/vxml_start_element_handler:  
   CALL_ERROR; tftp://192.168.2.213/hello.vxml
   at line 8: Element <prompt> is not used according to DTD
Feb  1 10:53:38.970: //-1//VXML:/vxml_create:  
   CALL_ERROR; code=ERROR vapp=VAPP_SUCCESS vxml=
Lab_BB1_CME(config-app-param)#

Итак, повторим корректную структуру для проигрывания голосового приветствия:
<form>
 <block>
  <prompt>
   <audio/>
  </prompt>
 </block>
</form>

Теперь посмотрим на структуру VXML-скрипта для интерактивного диалога с пользователем:
<form>
 <field>
  <grammar></grammar><!-- Здесь будут описываться RegExp'ы -->

  <noinput>
   <!-- Пользователь ничего не ввёл -->
  </noinput>

  <nomatch>
   <!-- Пользователь брутфорсит -->
  </nomatch>

  <prompt>
   <!-- Расскажем пользователю что делать -->
   <audio/>
  </prompt>
  
  <filled>
   <!-- Обрабатываем ввод пользователя -->
  </filled>
 </field>
</form>

Короткий пример того, как при вводе цифры 1, 2 или 3 происходит присвоение значения переменной и переход к другой форме (TransferToDestNumber):
<form id="Main">
 <field name="getdigit" type="digits?length=1"><!-- Expect one digit to be entered -->
  <grammar type="application/grammar+regex">[123]</grammar><!-- Expect 1, 2, or 3 as user input -->
  <filled> <!-- Right digits were caught -->
   <assign name="DestNumber" expr="'phone://9991'"/>
   <goto next="#TransferToDestNumber"/>
  </filled>
 </field>
</form>

Осталось посмотреть на форму, которая используется для перевода звонка на внутренний номер:
<form id="TransferToDestNumber">
 <transfer connecttimeout="20s" name="mycall"  destexpr="DestNumber" bridge="false"> </transfer>
 <block>
  <prompt bargein="true">
   <audio src="tftp://192.168.2.213/quickstart/audio/busy.au" caching="fast"/>
   <audio src="tftp://192.168.2.213/quickstart/audio/goodbye.au" caching="fast"/>
  </prompt>
  <disconnect/>
 </block>
</form>

И тут (барабанная дробь) на сцене появляется завершающая часть скрипта :)
</vxml>

Скрипт целиком

Нажав на кнопку вы сможете просмотреть мой лабораторный скрипт целиком:


А теперь закономерный вопрос: что же делать с этим скриптом?

Ответ: нужно скопировать скрипт и все прилагающиеся аудио-файлы на flash голосового шлюза и "привязать" скрипт к dial-peer.
Есть много примеров того, как привязать VXML-скрипт к dial-peer типа POTS, но вот как быть, если в моём распоряжении вообще нет аналоговых портов? Оказалось, что можно привязать и к dial-peer типа VOIP.

Конфигурация для случая с dial-peer типа VOIP:
!
application
 service NAME_OF_SERVICE flash:/vxml_script.vxml
!
dial-peer voice 1 voip
 service NAME_OF_SERVICE out-bound
 destination-patter ^2100$
 session target ipv4:1.1.1.1
Session target можно указать любой, он нужен только для того, чтобы этот outbound dial-peer пришел в состояние UP.

Немного ссылок:

Cisco IOS Tcl IVR and VoiceXML Application Guide - 12.3(14)T and later (Outbound Voice Applications)
Cisco IOS VoiceXML Quick Start Guide
Voice Gateway API (VGAPI) Developer Center (можно скачать Sample Scripts для QSG)
Cisco IOS Voice Troubleshooting and Monitoring -- Cisco VoiceXML Troubleshooting
VoiceXML Development Guide
Cisco VoiceXML Programmer's Guide

Простейшее голосовое меню на Cisco VoiceXML
VXML IVR на IOS гейтвее
Простой IVR или автоответчик на маршрутизаторе

P.S.

Получилось немного сумбурно и не последовательно, но если будет время, то я постараюсь реорганизовать материал для более удобного усвоения.

Всем успехов! :)

А напоследок небольшое видео (4,2 МБ) с попыткой объяснением алгоритма работы скрипта (удачно или нет, решать Вам, уважаемые читатели):

36 комментариев:

  1. Крайне полезная информация. Спасибо, Саня!

    ОтветитьУдалить
  2. Александр, спасибо за статью! Все получилось очень неплохо.

    Согласен с Сашей Левичевым - информация очень полезная, да еще и наглядно показано, как написать скрипт. Думаю, что она будет полезна всем, кто хочет без особых денежных затрат создать автосекретарь на базе циско-шлюза.

    Так держать! :) Молодец!

    ОтветитьУдалить
  3. Добрый день Александр, спасибо за скрипт и доступное описание.
    Информация которая может помочь у кого cisco 2801, скрипт не работал пока не поменял кодировку.
    Звуковые промты не обязательно хранить на tftp сервере, их можно хранить и во флеш. Для того, чтобы это работало необходимо изменить encoding с UTF8 на iso-8859-1 пример без скобок <>, так как не позволяет и писать:
    ?xml version="1.0" encoding="iso-8859-1"?
    vxml version="2.0",
    а в переменной промт источник аудио файла указать следующий параметр flash:имя звукового файла
    пример ниже:
    prompt
    audio src="flash:no_input.au"/
    /prompt
    еще полезная команда sh call application voice имя сервиса VXML
    с помощью данной команды можно посмотреть статистику : сколько было входящих вызовов на сркипт,сколько раз проигрывался промт, сколько записанных сообщений, а также что за скрипт в данный момент времени используется.

    к примеру:
    application
    service VXML flash:vxml_script.vxml
    в нашем случае комманда просмотра будет следующая
    sh call application voice VXML
    Вопрос, а как сделать переменную, которая бы собирала счетчик сесии, к примеру когда человек не вводит никаких цифр скажем 3 раза после проигрывания звукового файла меню ему проигрывался промт досвидание и происходил разрыв линии. В выше приведенном примере скрипта vxml_script.vxml, если абонент не набирает никаких цифр ему постоянно проигрываются промты bip и меню, тем самым абонент дозвонившийся на скрипт занимает линию и не уходит, так сказать некая dos атака, если таких абонентов будет несколько.
    Спасибо.

    ОтветитьУдалить
    Ответы
    1. Доброго времени суток!
      Насчет хранения на флешке: насколько я помню у меня на 2811 не нужно было менять кодировку, работало и для flash. и для tftp. Просто с tftp мне было удобнее скрипт подгонять до нужной кондиции :). Но всё равно спасибо за информацию: как появится время – добавлю Ваши комментарии в статью.
      Насчет счетчика проигрываний: посмотрите скрипт начинающийся на странице 3-12 этого документа от Cisco (catch event ="noinput nomatch error" count="3").

      Удалить
  4. Во время дозвона включается аудио файл) кнопки 1 2 становятся активными только после прослушивания аудио записи сразу нельзя! как можно поправить чтобы когда идёт аудио сразу нажать допутим 1

    ОтветитьУдалить
    Ответы
    1. Проверьте параметр bargein:
      Параметр bargein тега prompt запрещаете (false) или разрешает (true) прерывать проигрывание аудио-файлов вводом DTMF.

      Удалить
    2. ага) уже настроил пасиб)

      Удалить
  5. А как насчёт проиграть файл ВЫЗВАННОМУ абоненту? типа при звонке с определённого номера "С вами будет говорить ХХХХ" или при звонке с внешней линии "Вам звонок из филиала NNNN"

    ОтветитьУдалить
  6. После загрузка новой редакции - сброс старой и загрузка новой делается вот так:

    application
    no service бла-бла
    service бла-бла

    ОтветитьУдалить
  7. мне руководитель поставил в задачу чтобы случайным образом или по очереди проигрывались два разных приветствия. ума не приложу как это реализовать. буду признателен за подсказку.

    ОтветитьУдалить
    Ответы
    1. Доброго времени суток!

      Я бы копал в сторону Math.random и prompt cond (вот ссылка на ресурс, где нашел этот пример):
      <_var name="r" expr="Math.random()"/>
      <_prompt cond="r < .50">
      <_audio src="flash:prompt_01.au"/>

      <_prompt cond="r >= .50">
      <_audio src="flash:prompt_02.au"/>

      Удалить
    2. Посъедались теги, ещё раз...

      <_var name="r" expr="Math.random()"/>

      <_prompt cond="r < .50">
          <_audio src="flash:prompt_01.au"/>
      <_/prompt>

      <_prompt cond="r >= .50">
          <_audio src="flash:prompt_02.au"/>
      <_/prompt>

      Удалить
    3. спасибо за помощь, но решил уже вопрос самостоятельно.

      создал переменную, которая получает миллисекунды времени звонка

      <_var name="time_ms" expr="new Date().getUTCMilliseconds()"/>

      если значение больше 500 то один файл, если меньше то другой

      Удалить
  8. Доброго времени суток.
    Возможно вопрос не в тему. Если есть возможность натолкните на мысль.
    Требуется отображение фото и данных с AD звонящего.
    Нарисовал скрипт который запрашивает данные и генерит png файл в виде визитки.
    Каким образом сделать запрос к скрипту с Cisco ума не дам. Имеет ли решение рыть в сторону VXML?

    ОтветитьУдалить
  9. День добрый!
    такая проблема скрипт работает, если все лежит на flash
    если на tftp то сам скрипт с tftp подгружается а prompt нет... хотя в ручную с этого адреса промт выкачивается...
    есть также скрипт, похожий, который запускается на этом же роутере с tftp без проблем...

    ОтветитьУдалить
    Ответы
    1. Лучше поздно, чем никогда :)
      Я бы подебажил как на стороне Cisco, так и на стороне TFTP-сервера (есть ли обращения на TFTP от Cisco? приходят ли запросы на TFTP? в общем с помощью debug и Wireshark)

      Удалить
  10. Добрый день!
    Делал все по инструкции на CISCO 881 voice.
    При попытке звонка на пир, сервер отвечает 500 internal server error.
    Подскажите, в чем может быть проблема?

    ОтветитьУдалить
  11. Здравствуйте!
    Спасибо за статью.
    Подскажите пожалуйста, какая у вас модель маршрутизатора и ИОС, на котором работает IVR ?

    ОтветитьУдалить
    Ответы
    1. Д.д. Маршрутизатор был 2811, а вот IOS.. Скорее всего это был c2800nm-adventerprisek9-mz.151-3.T3.bin (отыскал в старых логах).

      Удалить
  12. Александр, не подскажете, как реализовать продолжение диалплана после того, как вторая сторона положила трубку?

    ОтветитьУдалить
    Ответы
    1. Попробуйте вот этот вариант: http://www.w3.org/TR/voicexml20/#dml1.5.4

      Удалить
  13. Большое спасибо за статью. Сам только второй день, как начал ковырять vxml. Помогло.)
    По поводу Вашего вопроса о сбросе кэша с аудиозаписью в оперативке.
    У меня запись хранится на флеше и так же столкнулся с описанной проблемой при ее замене.
    Для моего случая получилось так:
    1. Заливаю новую запись на флешку
    2. Включаю стриминг с флеша добавлением в конфиг команды: ivr prompt streamed flash
    3. Отключаю стриминг: ivr prompt streamed none
    И при следующем звонке играет новая запись.

    Надеюсь, поможет)

    ОтветитьУдалить
    Ответы
    1. Дополнение. Между пунктами 2 и 3 нужно позвонить на этот ivr.

      Удалить
  14. Этот комментарий был удален автором.

    ОтветитьУдалить
  15. Этот комментарий был удален автором.

    ОтветитьУдалить
  16. Добрый день,
    Недавно столкнулся с такой проблемой - как проигрывание IVR скрипта на H323 и SIP транках, на cisco.com в основном описываются решения, когда клиенты звонят на cisco с POTS,FXO,FXS или cisco phones зарегистрированных на этом же CME, где находится IVR скрипт. Но как быть когда клиенты звонят на cisco через SIP или H323 транк? Я потратил на решение этой проблемы 3 недели, пытался поднимать транскодинг, кодировать файлы в другой кодек, так как при входящем вызове на dial-peer voip в h323 согласовывался кодек g729r8 скрипт отрабатывал, но ничего не было слышно, при звонках через sip транк кодек согласовывался g711ulaw, но в ответ так же тишина. В итоге, после долгих тестирований и поисков ответа в интернете решение все таки было найдено, на cisco.com в форуме я его описал подробней - ссылка на форум https://supportforums.cisco.com/ru/discussion/11841966. Здесь же я оставлю краткое решение на всякий случай.
    Итак для корректной работы скрипта IVR на SIP и H323 транках необходимо использовать только dial-peer voip как входящий интерфейс по аналогии dial-peer pots.
    т.е. рабочий dial-peer должен выглядеть следующим образом:
    dial-peer voice 8892 voip
    incoming called-number 8892
    session target ipv4:192.168.100.121
    destination-pattern 8892
    service test
    В этом пире главное условие применение сервиса service test , а не service test outbound, и обязательное условие указать ,что этот пир является входящим incoming called-number.
    На мысль сделать voip как pots подтолкнула вот эта статья http://www.cisco.com/c/en/us/td/docs/routers/access/as5350/software/feature/guide/pull_ivr.pdf Configuring Interactive Voice Response for Cisco Access Platforms и вот этот абзац:
    Dial-Peer Application Field
    Use the application field in the inbound dial peer to associate an application with an incoming call.

    P.S. После того, как я дошел до этого, нашел вот эту статью https://supportforums.cisco.com/ru/discussion/11858086 - обидно ответ был, но я его не видел.

    ОтветитьУдалить
  17. Александр здравствуйте!
    подскажите каким xml редактором вы на видео пользовались?

    ОтветитьУдалить
  18. По поводу загрузки новых версий скрипта и аудио: всё очевидно и описано на сайте циски :)
    call application voice load
    audio-prompt load

    ОтветитьУдалить
  19. Друзья, а ограничение VMXL когда нельзя повторно (после попадания вызова к секретарю, например) переадресовать вызов - ещё актуально?

    ОтветитьУдалить
  20. Александр, здравствуйте!
    Прошу Вашей помощи вот с какой хитрой проблемой со входящим DTMF.
    Буду безмерно благодарен за хоть какие-то мысли как побороть проблему. Своей головы уже совершенно не хватает...

    Дано:
    Cisco 2900, CME, IVR на входящем dial-peer.
    SIP транк от провайдера с вышестоящей HuaweiSoftX3000
    DTMF Payload type 97 (a=fmtp:97 0-15)

    Исходящие DTMF победил с помощью
    "rtp payload-type nte 97" и, самое важное: "voice-class sip dtmf-relay force rtp-nte" - без этой команды DTMF ходить не хотел, хоть и payload type согласованный.

    А вот со входящим DTMF беда - как только не крутил, но не проходит, хоть ты тресни. Скрипт рабочий - проверял звонком не на внешний номер, а на внутренний, на котором висит IVR. Пересмотрел уже кучу debug'ов, по-разному крутил настройки - безрезультатно.

    Единственно, что смущает, если пристально смотреть SDP, то от провайдера приходит, "a=fmtp:97 0-15", а Cisco отдаёт "a=fmtp:97 0-16".
    Насколько я понял, это лишь говорит о том, что Cisco поддерживает hook flash и никак влиять не должно. По крайней мере исходящие DTMF работают без проблем.

    Озадачил уже провайдера этим вопросом, но пока всё традиционно: "у нас всё хорошо". Может есть ещё какая скрытая команда типа "voice-class sip dtmf-relay force rtp-nte" которая поможет с входящим DTMF при нестандартном payload type?

    ОтветитьУдалить
    Ответы
    1. Добрый день, если ещё актуально, то напишите мне на a@alakin.org

      Удалить