Простейшее голосовое меню на Cisco VoiceXML

    Неподготовленному человеку, который захочет написать самое простецкое голосовое меню с использованием языка 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 &lt;= VAR_Hour &amp;&amp; VAR_Hour &lt; 14 &amp;&amp; 0 &lt; VAR_Day &amp;&amp; VAR_Day &lt; 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:/test/test.vxml
    gw(config-app)#service test flash0:/test/test.vxml
    Вот и всё! Надеюсь, с помощью этого базового скрипта даже у новичка получится написать простое голосовое меню. Полагаю, следующим этапом можно было бы прикрутить выбор одной из веток меню, путём нажатия одной из цифр на телефоне. Но мне это было пока не нужно, поэтому оставим за скобками.

    В заключение приведу несколько ссылок, которые могут помочь в разработке IVR:
    • www.cisco.com — официальный документ от Cisco — VoiceXML Programmer's Guide
    • www.vxml.org — есть множество туториалов, правда это реализация не Cisco, а компании Voxeo
    • www.w3.org — спецификация языка VoiceXML 3.0
    • +5
    • 18,2k
    • 5
    Поделиться публикацией

    Комментарии 5

      +1
      Ну вроде кошерненько, похоже на фрисвитч немного.
        0
        Я недавно стал заниматься телефонией, поэтому мой вопрос совсем для новичков. Вы написали, что телефонный звонок приходит от провайдера по обычной телефонной линии, а как настроить входящие звонки, если телефон приходит от шлюза?
          0
          Если я всё правильно понял, то вам нужно всего лишь указать не dial-peer voice 100 pots, а dial-peer voice 100 voip
            +1
            Увы, несколько сложнее :).

            Конфигурация для случая с 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.

            Можно почитать текст и посмотреть видео про вариант VXML здесь: alakinalexandr.blogspot.com
          +1
          Я просто оставлю это здесь: VXML.ru
          Мы постарались собрать все про VXML на русском в одном месте.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое