Обзор Asterisk REST Interface (ARI)

В начале времен единственным «поставщиком» функционала Asterisk были модули, многие из которых расширяли арсенал приложений и функций плана набора.


Тогда, в начале времен, все эти команды и функции далеко опережали свое время, и благодаря им Asterisk «уделывал» по функционалу многие коммерческие продукты.


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


Но разработку модуля Астериск на языке С сложно назвать тревиальной задачей. Это весьма тернистый путь, к тому же весьма рискованный, ведь критическая ошибка в своем модуле запросто приводила к полному падению Asterisk в core.


Нужны были более «мягкие» и простые способы для расширения функций и интеграции с другими системами.


Так появились интерфейсы AGI и AMI.



Asterisk Gateway Interface (AGI) — это синхронный интерфейс выполнения диалплана, архитектурно «слизанный» с CGI. Команда диалплана AGI запускала процесс, и использовала стандартный ввод и вывод для получения команд и передачи результатов. При помощи AGI можно решать задачи интеграции с внешними системами, например, можно отправиться в корпоративную базу данных и найти имя звонящего клиента по его номеру.


По сути, AGI предоставлял способ написать план набора Asterisk не в формате extensions.conf, а на своем языке программирования, используя поставляемые модулями команды и функции, вокруг которых строится своя бизнес-логика.


Asterisk Manager Interface (AMI) — это асинхронный (событийный) интерфейс, позволяющий контролировать внутреннее состояние объектов в Asterisk, и получать информацию о происходящих событиях. Если AGI архитектурно напоминает CGI интерфейс, то AMI сессия похожа на телнет-сессию, в рамках которой стороннее приложение подключается по TCP/IP к AMI порту Asterisk, и может отправлять свои команды, ответ на которые приходит через некоторое время в виде события-ответа. Помимо ответов на команды в AMI соединение «валятся» всевозможные события, происходящие в Asterisk, и дело клиента определить, относятся они к нему или их можно просто игнорировать.


Про AGI можно сказать, что это call execution механизм, а про AMI — что это call control механизм. Чаще всего для построения своего телекоммуникационного приложения необходимо использовать сразу AGI и AMI вместе. Происходит «размазывание» бизнес логики по разным приложениям, что затрудняет его понимание и дальнейшее сопровождение и развитие.


Помимо этого, существует еще несколько ограничений:


  • AGI: блокирует поток, обслуживающий канал.
  • AGI: реакция на события (DTMF, изменение состояния) невозможна или затруднена только с AGI.
  • Фундаментальные операции ограничены тем, что выполняется на канале. Но есть и другие примитивы: мосты, устройства, состояния, индикации сообщений и медиа на каналах, недоступные в AGI/AMI.
  • AMI & AGI — морально устарели. REST, XML/JSON RPC более привычны и удобны в сегодняшнем мире.

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


Так было до появления Asterisk REST Interface.


Основные концепции ARI:


  • ARI позволяет как управлять состоянием звонка (call control), так и выполнять логику (call execution).
  • ARI асинхронен.
  • ARI «выставляет» «сырые» примитивы — каналы, мосты, устройства и т.п. через REST интерфейс.
  • Состояния объектов доступны через JSON события поверх WebSocket.
  • ARI — не для того, чтобы «зарулить» звонок в приложение VoiceMail, а для того, чтобы создать свое собственное приложение VoiceMail!

«Три кита» ARI:


  • RESTful интерфейс.
  • WebSocket подключение, по которому передаются события о контролируемых ресурсах в JSON формате.
  • Приложение диалплана — Stasis, передающее управление каналом в ARI приложение.

Пример диалплана, передающего управление в Stais:


exten => _X.,1,Stasis(myapp,arg1,arg2)
exten => _X.,n,NoOp(Left Stasis)

ARI имеет некоторые ограничения


  • ARI не имеет доступа к любым объектам, а только к тем, которые контролирует. Это значит, что нельзя сделать answer на канале, которые не зарулен в Stasis приложение. Однако, channel list вернет все активные каналы, а не только те, что зарулены в Stasis
  • Доступны только те операции, которые определены на стороне Asterisk (что понятно, ведь это Asterisk определяет все REST операции).
  • Stasis приложение доступно только при установленном клиентском соединении. Если нет соединения на WebSocket с именем данного приложения, Stasis выдаст ошибку и пойдет дальше по диалплану.

Рассмотрим категории операций, доступных в ARI:


  • Asterisk
  • Мосты (bridges)
  • Каналы (channels)
  • Устройства (endpoints)
  • Состояния устройств (device states)
  • События (events)
  • Почтовые ящики (mailboxes)
  • Воспроизведения (playbacks)
  • Записи (recordings)
  • Звуки (sounds)

И остановимся на каждой категории подробнее.


Asterisk


  • Динамическая конфигурация (sorcery, pjsip)
  • Информация о сборке
  • Управление модулями (список, загрузка, выгрузка)
  • Управление логированием и ротацией логов
  • Глобальные переменные (чтение и установка)

Мосты


  • Получение, создание, удаление мостов
  • Добавление / удаление каналов
  • Проигрывание музыки на ожидании
  • Включение записи

Каналы


  • Список активных каналов и подробные данные канала.
  • Создание канала (originate) и удаление (hangup) канала.
  • Выход в диалплан
  • Редирект канала
  • Answer, Ring, DTMF, Mute, Hold, MoH, Silence, Play, Record, Variable, Snoop

Каналы


  • Список активных каналов и подробные данные канала.
  • Создание канала (originate) и удаление (hangup) канала.
  • Выход в диалплан
  • Редирект канала
  • Answer, Ring, DTMF, Mute, Hold, MoH, Silence, Play, Record, Variable, Snoop

Устройства


  • Список всех устройств
  • Отправка сообщения на устройство (SIP, PJSIP, XMPP)

Состояние устройств


  • Список статусов контролируемых устройств
  • Установка статуса (NOT_INUSE, INUSE, BUSY, INVALID, UNAVAILABLE, RINGING, RINGINUSE, ONHOLD)

Полный список возможных операций смотрите на wiki asterisk — https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+ARI


События


Приведу частичный список событий, которые доступны на веб-сокете подключенного приложения:


  • StasisStart / StasisEnd — посылается в сокет сразу при попадании звонка в Stasis, и последним при выходе звонка из Стасиса.
  • ChannelCreated / ChannelDestroyed — при создании и разрушении канала.
  • BridgeCreated / BridgeDestroyed — при создании и разрушении моста.
  • ChannelDtmfReceived — при получении DTMF.
  • ChannelStateChange — изменилось состояние канала.
  • ChannelUserevent — пользовательское событие. Очень удобная штука, которая позволяет надстраиваться над событийной моделью ARI.
  • DeviceStateChanged — изменилось состояние устройства (NOT_INUSE, INUSE, BUSY, INVALID, UNAVAILABLE, RINGING, RINGINUSE, ONHOLD).
  • EndpointStateChange — изменилось состояние конечной точки.
  • PlaybackStarted / PlaybackFinished — началось и закончилось проигрывание файла.
  • TextMessageReceived — получено сообщение.
  • и другие (https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+REST+Data+Models)

Что нового в Asterisk 14 ARI


  • Получение записей
  • Проигрывание медиа из HTTP источников.
  • Медиа-плейлист (асинхронность требовала ожидания окончания одного звука для запуска следующего).

Пример


Ну и в заключение приведу пример оригинации вызова при помощи Python ARI библиотеки.


В этом примере делается оригинация по указанному пиру, и возвращается cause code:


import ari
import gevent
from gevent.event import Event
from gevent.monkey import patch_all; patch_all()

ari_client = ari.connect(
                conf['pbx_ari_url'],
                conf['pbx_ari_user'],
                conf['pbx_ari_password']
)

def originate_to_ari(endpoint='', number='', app='', variables={},
                     callerid='', timeout=60):

        evt = Event()  # Wait flag for origination
        result = {}
        channel = ari_client.channels.originate(
            endpoint=endpoint,
            app='barrier',
            appArgs=app,
            callerId=callerid,
            timeout=timeout,
            variables={'variables': variables},
        )

        def destroyed(channel, event):
            result['status'] = 'success'
            result['message'] = '%s (%s)' % (
                                    event.get('cause_txt'),
                                    event.get('cause'))
            evt.set()

        channel.on_event('ChannelDestroyed', destroyed)
        # Wait until we get origination result
        evt.wait()
        return result

P.S. Написано по материалам выступления автора на Asterconf 2016.

Комментарии (0)

© Habrahabr.ru