Неподготовленному человеку, который захочет написать самое простецкое голосовое меню с использованием языка VoiceXML на голосовом шлюзе от компании Cisco, предстоит наступить на множество граблей. Некоторые из них я отмечу в этой статье. Вполне возможно, что какой-нибудь Cisco-специалист фыркнет и скажет, что это всё элементарщина, но тем не менее, когда передо мной встала эта задача, я не знал с чего начать. Google не давал сколько-нибудь толковых примеров готовых IVR. Единственной более-менее хорошей исходной точкой послужила эта статья. Мой хабравопрос также не дал особых результатов. Но отбросим лирику и перейдем к делу. Предположим, что у нас есть голосовой шлюз Cisco, поддерживающий выполнение скриптов на языке VoiceXML (например, Cisco 3925). Будем разрабатывать голосовое меню, показанное на картинке. В рабочие часы будет воспроизводиться приветствие и звонок будет переводиться на заданный внутренний номер, в нерабочее и выходные — специальное объявление, что, мол, звоните по будням.
Выделим основные «строительные блоки» и этапы построения нашего голосового меню.
1. Маршрутизация входящего звонка на наш vxml-скрипт.
2. Временно́е условие
3. Воспроизведение звукового файла
4. Перевод звонка на внутренний номер
5. Загрузка и активация vxml-скрипта
1. Маршрутизация входящего звонка
Предположим, что номер у нас приходит от провайдера по обычной телефонной линии (pots) в виде 7XXXYYYZZZZ. Нужно создать новый диалпир и указать, что маршрутизировать входящий звонок следует на сервис «test»:
gw#conf t
gw(config)#dial-peer voice 100 pots
gw(config-dial-peer)#description IVR-Test
gw(config-dial-peer)#incoming called-number 7XXXYYYZZZZ
gw(config-dial-peer)#service test
Отлично, звонок входит, но пока у нас нет собственно приложения «test», сейчас займёмся его написанием — по частям.
2. Временно́е условие
Пишем первый XML-блок. Сперва инициализируем переменные, затем составляем нужное условие, не забывая о том, что мы работаем над XML-документом, а значит, все символы вроде "<" и "&" следует заэкранировать:
<!-- Time is in UTC -->
<!-- Day of week: 0 - sunday, 1 - monday ... 6 - sunday -->
<var name="VAR_Hour" expr="new Date().getHours()"/>
<var name="VAR_Day" expr="new Date().getDay()"/>
<!-- Time condition -->
<form id="IVR_TimeCondition">
<block>
<if cond="5 <= VAR_Hour && VAR_Hour < 14 && 0 < VAR_Day && VAR_Day < 6">
<goto next="#IVR_Business" />
<else />
<goto next="#IVR_NonBusiness" />
</if>
</block>
</form>
Здесь мы переходим в блок под названием IVR_Business в рабочее время, и в IVR_NonBusiness в нерабочее время и выходные. О них чуть позже.
3. Звуковые файлы и их воспроизведение
Звукозаписи я храню во флеш-памяти шлюза в формате G.711 μ-law. В Windows их можно записать с помощью стандартной программы Звукозапись, а в Linux я пользуюсь программой sox. Следующая команда конвертирует звуковой файл формата PCM в файл формата μ-law:
sox input.wav -e u-law -V4 -r 8k -c 1 output.wav
Закачиваем необходимые файлы в память, к примеру, с HTTP-сервера 192.168.0.10:
gw#mkdir test
gw#copy http://192.168.0.10/ivr1.wav flash0:/test/ivr1.wav
gw#copy http://192.168.0.10/ivr2.wav flash0:/test/ivr2.wav
XML-блок, который отвечает за объявление по выходным, выглядит так:
<!-- Non business hours -->
<form id="IVR_NonBusiness">
<block>
<prompt><audio src="flash0:/test/ivr2.wav"/></prompt>
</block>
</form>
4. Маршрутизация на внутренний номер
Тут у нас по схеме также небольшое приветствие (ivr1) и перевод звонка на заданный внутренний номер. Этим номером может быть как номер телефона, так и, например, групповой номер CUCM (Hunt Pilot). В моём случае перевод звонка осуществлялся с помощью адреса phone://EXT, хотя в интернете я видел вариации вида tel://EXT или sip://EXT. Поэтому у вас может быть немного по-другому.
<!-- Business hours -->
<form id="IVR_Business">
<block>
<prompt><audio src="flash0:/test/ivr1.wav"/></prompt>
</block>
<transfer name="mycall" transferaudio="flash0:/test/music.wav" bridge="false" dest="phone://123">
<filled>
<log>TRANSFER RETURNED: <value expr="mycall"/></log>
</filled>
</transfer>
</form>
При необходимости мы сможем отлавливать код результата звонка в переменной mycall, если включить режим отладки debug voip application vxml puts. Еще обратите внимание на параметр transferaudio — это звуковой файл, который будет играть, пока номер 123 не возьмёт трубку.
5. Загрузка и активация vxml-скрипта
Итак, теперь мы имеем окончательный скрипт test.vxml, и можно заливать его в шлюз. Аналогично, с помощью HTTP-сервера:
gw#copy http://192.168.0.10/test.vxml flash0:/test/test.vxml
Наконец, активируем скрипт «test»:
gw#conf t
gw(config)#application
gw(config-app)#no service test flash0:/vxml/test.vxml
gw(config-app)#service test flash0:/vxml/test.vxml
Вот и всё! Надеюсь, с помощью этого базового скрипта даже у новичка получится написать простое голосовое меню. Полагаю, следующим этапом можно было бы прикрутить выбор одной из веток меню, путём нажатия одной из цифр на телефоне. Но мне это было пока не нужно, поэтому оставим за скобками.
В заключение приведу несколько ссылок, которые могут помочь в разработке IVR:
- www.cisco.com — официальный документ от Cisco — VoiceXML Programmer's Guide
- www.vxml.org — есть множество туториалов, правда это реализация не Cisco, а компании Voxeo
- www.w3.org — спецификация языка VoiceXML 3.0
Автор: oioki