Эта статья подводит промежуточный итог в разработке моих приложений для asterisk'а. Все началось в новогодние праздники, когда мне захотелось сделать быстрый голосовой набор на asterisk'е. Затем был реализован поиск направления по номеру звонящего (полезная штука для входящих на номера 8-800). Затем была пара заказных проектов. А недавно еще и LCR для asterisk. Все эти приложения разработаны с использованием библиотеки ding-dong, которая позволяет работать с AGI (Asterisk Gateway Interface) в node.js приложении.
Далее я хотел бы показать, что используя node.js и ding-dong, можно быстро разрабатывать полезные приложения для астериска.
За эти полгода ding-dong (форк проекта node-agi) стал поддерживать promise style (спасибо моему коллеге Денису), а также полный набор функций AGI.
Но прежде написания приложения хочу немного сначала поговорить о другом. Далее немного в формате разговора «сам с собой».
В чем проблема, бро?
Я много настраиваю астериски. Часто клиенты требуют интересных фишек, которые вполне себе реально воплотить в жизнь, используя астериск. Более того зачастую можно реализовать требуемый функционал разными способами. Но нередко добавление этого функционала усложняет диалплан астериска настолько, что поддерживать все фишки в рабочем состоянии становится трудно.
Более того если один человек каким-то образом сконфигурировал и настроил, то впору писать мануал, как другому там разобраться и что-то подправить или перенастроить.
Вот пример большого рецепта для единичного воспроизведения. А хочется придумать фишку, разработать, потестировать, а уж затем поставить на рабочую систему клиента, да еще и быстро и с минимальными правками. А всем остальным клиентам ставить одним мизинцем.
Многие пользуются продуктами типа FreePBX или Elastix, где типовые вещи выполняются типовым образом. Но если что-то нестандартное, то начинается «скриптование» в файлах типа "*_custom.conf"
И что ты предлагаешь?
Не «серебрянная пуля», конечно. Грубо говоря, диалплан астериска можно разделить на две части: собственно маршрутизация звонка и сопутствующие вспомогательные вычисления, от результата которых зависит дальнейший маршрут звонка.
Так вот эти вычисления мы и должны вынести за пределы диалплана астериска.
Этот подход очень хорошо иллюстрируют приложения agi-number-archer и lcr-finder, т.е. на вход даем номер телефона, на выходе получаем имя или код.
lcr-finder: как результат выдает имя оператора с наименьшей стоимостью звонка для вызываемого номера.
agi-number-archer: как результат выдает регион по номеру звонящего.
Диалплан астериска, который использует такое приложение абсолютно не знает как устроен этот сервис, работает он на node.js или twisted, используется БД MySQL или MongoDB. Более того диалплан должен быть так написан, чтобы иметь вариант прохождения звонка при отсутствии данных от сервиса.
Например, в lcr-finder'е появилась возможность отключать оператора из поиска наименьшей стоимости, а с точки зрения диалплана астериска ничего не поменялось, приложение обновил, а в работающем астериске даже 'dialplan reload' делать не пришлось.
Еще как пример. Первая версия voicer'а отправляла команду на звонок в астериск по найденному номеру. Текущая версия voicer'а просто возвращает найденный номер, предоставляя возможность диалплану проанализировать результат, сделать вызов, обработать статусы совершенного вызова.
Ок, AGI — это круто. А почему сервер, а не скрипт?
Потому что это полноценно работающий сервис, который готов в любой момент ответить на AGI-запрос, который ведет логи о своей работе.
Также у меня в одной сети работает несколько астерисков, поднимать для каждого из них свой сервер для определения направления как-то избыточно, обновлять скрипт также на всех машинах было более трудоемкой задачей нежели на одной.
Более того наличие системы мониторинга и process manager'а также помогают. Система мониторинга проследит за работающим сервером, подробные логи помогут разобраться в деталях, а process manager (типа pm2) перезапустит в случае падения в следствие ошибок при обработке запросов.
* Надо отметить на весь этот подход оказал влияние манифест 12 факторного приложения (перевод).
Хорошо, сервер. А почему node.js?
М-м, надо было брать php? Если серьезно, то тоже самое можно сделать и на php. Но мне понравилось использование npm для быстрой разработки и распространения приложения. Изначально напрягали callback-функции, но сейчас ding-dong поддерживает promise стиль, поэтому можно писать аккуратные сценарии, в том числе и циклические (смотрите код voicer'а).
Может быть что-то более производительное, чем node.js? У меня нет миллионов запросов в секунду, все работает ок. Возможно, на более загруженных системах надо использовать что-то иное (хотя и астериск тоже, вероятно, придется сменить).
А есть альтернативы ding-dong?
Конечно, например, недавно появился отличный вариант для agi-сервера на js. В нем есть mapper запросов к AGI, т.е. можно описать несколько сценарием и повешать их на разные uri_string на одном порту, для php есть phpagi, fast-pagi. В общем-то, дело не в языке программирования, а в подходе.
Может уже напишем helloWorld, используя AGI?
Итак. Посмотрим как может ding-dong помочь нам быстро разработать приложение.
Главная фишка использования AGI — это передача работы управления диалпланом стороннему приложению, т.е. нашему серверу. Напишем такой простой диалплан.
Звонок вашего абонента по контексту default будет контролироваться AGI-сервером. Напишем простой AGI-сервер.
Теперь сохранив и перегрузив диалплан, запустив AGI-сервер, делаем вызов. Мы должны услышать «бип», а в консоли астериска в verbose должен отобразиться helloWorld.
Нашему AGI-серверу, который запущен на 3000 порту, мы должны дать функцию handler, которая будет вести звонок. Для взаимодействия с каналом есть context. Когда звонок приходит на AGI-сервер, происходит событие variables. При звонке также передается набор данных vars. Там содержится время, набираемый номер, peer вызывающий и многое другое.
Также далее через context мы можем отправлять команды в канал звонка, здесь в примере context проигрывает звук beep командой streamFile.
В конце мы делаем context.end(), который закрывает сеанс. Диалплан астериска проходит дальше.
В примере также есть context.setVariable() — функция, которая устанавливает переменную диалплана. Т.е. мы можем сохранить свой результат в переменную диалплана, а дальше уже сам диалплан определится, что ему с ней делать. В этой переменной может быть имя пира, куда звонить, или номер заказа, или что-то еще.
Также используя context мы можем отправлять команды на получение DTMF, запись в файл, произношение цифр и другие функции астериска.
И, конечно, в handler'е мы должны проделать всю ту полезную работу, ради которой мы направили управление звонком на AGI, будь-то поиск данных в БД, распознавание речи, что-то еще.
Этот пример на гитхабе: github.com/antirek/ding-dong-sample
Недостатки?
В целом, достаточно удобно. Пожалуй, самый неудобное с чем пришлось столкнуться, так это невозможность передать по AGI файлы. Т.е. если бы астериск умел записать файл, а потом его передать по AGI, то voicer был бы немного проще в конфигурировании.
Что еще можно реализовать при таком подходе?
К примеру, полагаю, что часть задач (раз, два, три, четыре) можно было бы решить, написав небольшой AGI-сервер и отдав ему поиск решения, а не писать скрипты на bash. И это было бы более удобно в использовании.
Также, например, можно реализовать оповещение о статусе заказа при вводе DTMF'ом номера заказа, сбор информации о выполненых работах по заявкам, прослушивание файлов. Т.е. для написания небольших, простых и функциональных сценариев вполне достаточно.
И в завершение?
Конечно, AGI существует не первый день. И уже есть множество скриптов и приложений, которые используют этот способ взаимодействия с астериском, разделяя с ним выполняемые задачи. Надеюсь, что указанные приложения, а также информация, представленная в заметке, будут полезны.
ding-dong, voicer, agi-number-archer, lcr-finder
функции AGI на сайте Asterisk'а
Буду рад комментариям к статье, предложениям по разработке, конструктивной критике.
Далее я хотел бы показать, что используя node.js и ding-dong, можно быстро разрабатывать полезные приложения для астериска.
За эти полгода ding-dong (форк проекта node-agi) стал поддерживать promise style (спасибо моему коллеге Денису), а также полный набор функций AGI.
Но прежде написания приложения хочу немного сначала поговорить о другом. Далее немного в формате разговора «сам с собой».
В чем проблема, бро?
Я много настраиваю астериски. Часто клиенты требуют интересных фишек, которые вполне себе реально воплотить в жизнь, используя астериск. Более того зачастую можно реализовать требуемый функционал разными способами. Но нередко добавление этого функционала усложняет диалплан астериска настолько, что поддерживать все фишки в рабочем состоянии становится трудно.
Более того если один человек каким-то образом сконфигурировал и настроил, то впору писать мануал, как другому там разобраться и что-то подправить или перенастроить.
Вот пример большого рецепта для единичного воспроизведения. А хочется придумать фишку, разработать, потестировать, а уж затем поставить на рабочую систему клиента, да еще и быстро и с минимальными правками. А всем остальным клиентам ставить одним мизинцем.
Многие пользуются продуктами типа FreePBX или Elastix, где типовые вещи выполняются типовым образом. Но если что-то нестандартное, то начинается «скриптование» в файлах типа "*_custom.conf"
И что ты предлагаешь?
Не «серебрянная пуля», конечно. Грубо говоря, диалплан астериска можно разделить на две части: собственно маршрутизация звонка и сопутствующие вспомогательные вычисления, от результата которых зависит дальнейший маршрут звонка.
Так вот эти вычисления мы и должны вынести за пределы диалплана астериска.
Этот подход очень хорошо иллюстрируют приложения agi-number-archer и lcr-finder, т.е. на вход даем номер телефона, на выходе получаем имя или код.
lcr-finder: как результат выдает имя оператора с наименьшей стоимостью звонка для вызываемого номера.
agi-number-archer: как результат выдает регион по номеру звонящего.
Диалплан астериска, который использует такое приложение абсолютно не знает как устроен этот сервис, работает он на node.js или twisted, используется БД MySQL или MongoDB. Более того диалплан должен быть так написан, чтобы иметь вариант прохождения звонка при отсутствии данных от сервиса.
Например, в lcr-finder'е появилась возможность отключать оператора из поиска наименьшей стоимости, а с точки зрения диалплана астериска ничего не поменялось, приложение обновил, а в работающем астериске даже 'dialplan reload' делать не пришлось.
Еще как пример. Первая версия voicer'а отправляла команду на звонок в астериск по найденному номеру. Текущая версия voicer'а просто возвращает найденный номер, предоставляя возможность диалплану проанализировать результат, сделать вызов, обработать статусы совершенного вызова.
Ок, AGI — это круто. А почему сервер, а не скрипт?
Потому что это полноценно работающий сервис, который готов в любой момент ответить на AGI-запрос, который ведет логи о своей работе.
Также у меня в одной сети работает несколько астерисков, поднимать для каждого из них свой сервер для определения направления как-то избыточно, обновлять скрипт также на всех машинах было более трудоемкой задачей нежели на одной.
Более того наличие системы мониторинга и process manager'а также помогают. Система мониторинга проследит за работающим сервером, подробные логи помогут разобраться в деталях, а process manager (типа pm2) перезапустит в случае падения в следствие ошибок при обработке запросов.
* Надо отметить на весь этот подход оказал влияние манифест 12 факторного приложения (перевод).
Хорошо, сервер. А почему node.js?
М-м, надо было брать php? Если серьезно, то тоже самое можно сделать и на php. Но мне понравилось использование npm для быстрой разработки и распространения приложения. Изначально напрягали callback-функции, но сейчас ding-dong поддерживает promise стиль, поэтому можно писать аккуратные сценарии, в том числе и циклические (смотрите код voicer'а).
Может быть что-то более производительное, чем node.js? У меня нет миллионов запросов в секунду, все работает ок. Возможно, на более загруженных системах надо использовать что-то иное (хотя и астериск тоже, вероятно, придется сменить).
А есть альтернативы ding-dong?
Конечно, например, недавно появился отличный вариант для agi-сервера на js. В нем есть mapper запросов к AGI, т.е. можно описать несколько сценарием и повешать их на разные uri_string на одном порту, для php есть phpagi, fast-pagi. В общем-то, дело не в языке программирования, а в подходе.
Может уже напишем helloWorld, используя AGI?
Итак. Посмотрим как может ding-dong помочь нам быстро разработать приложение.
Главная фишка использования AGI — это передача работы управления диалпланом стороннему приложению, т.е. нашему серверу. Напишем такой простой диалплан.
extensions.conf
[default]
exten => _X.,1,AGI(agi://localhost:3000)
exten => _X.,n,Verbose(DIALPLAN_VARIABLE)
Звонок вашего абонента по контексту default будет контролироваться AGI-сервером. Напишем простой AGI-сервер.
var AGIServer = require('ding-dong');
var handler = function (context) {
context.onEvent('variables')
.then(function (vars) {
return context.answer();
})
.then(function () {
return context.streamFile('beep');
})
.then(function (result) {
return context.setVariable('DIALPLAN_VARIABLE', 'helloWorld');
})
.then(function (result) {
return context.end();
});
};
var agi = new AGIServer(handler);
agi.start(3000);
Теперь сохранив и перегрузив диалплан, запустив AGI-сервер, делаем вызов. Мы должны услышать «бип», а в консоли астериска в verbose должен отобразиться helloWorld.
Нашему AGI-серверу, который запущен на 3000 порту, мы должны дать функцию handler, которая будет вести звонок. Для взаимодействия с каналом есть context. Когда звонок приходит на AGI-сервер, происходит событие variables. При звонке также передается набор данных vars. Там содержится время, набираемый номер, peer вызывающий и многое другое.
Также далее через context мы можем отправлять команды в канал звонка, здесь в примере context проигрывает звук beep командой streamFile.
В конце мы делаем context.end(), который закрывает сеанс. Диалплан астериска проходит дальше.
В примере также есть context.setVariable() — функция, которая устанавливает переменную диалплана. Т.е. мы можем сохранить свой результат в переменную диалплана, а дальше уже сам диалплан определится, что ему с ней делать. В этой переменной может быть имя пира, куда звонить, или номер заказа, или что-то еще.
Также используя context мы можем отправлять команды на получение DTMF, запись в файл, произношение цифр и другие функции астериска.
И, конечно, в handler'е мы должны проделать всю ту полезную работу, ради которой мы направили управление звонком на AGI, будь-то поиск данных в БД, распознавание речи, что-то еще.
Этот пример на гитхабе: github.com/antirek/ding-dong-sample
Недостатки?
В целом, достаточно удобно. Пожалуй, самый неудобное с чем пришлось столкнуться, так это невозможность передать по AGI файлы. Т.е. если бы астериск умел записать файл, а потом его передать по AGI, то voicer был бы немного проще в конфигурировании.
Что еще можно реализовать при таком подходе?
К примеру, полагаю, что часть задач (раз, два, три, четыре) можно было бы решить, написав небольшой AGI-сервер и отдав ему поиск решения, а не писать скрипты на bash. И это было бы более удобно в использовании.
Также, например, можно реализовать оповещение о статусе заказа при вводе DTMF'ом номера заказа, сбор информации о выполненых работах по заявкам, прослушивание файлов. Т.е. для написания небольших, простых и функциональных сценариев вполне достаточно.
И в завершение?
Конечно, AGI существует не первый день. И уже есть множество скриптов и приложений, которые используют этот способ взаимодействия с астериском, разделяя с ним выполняемые задачи. Надеюсь, что указанные приложения, а также информация, представленная в заметке, будут полезны.
ding-dong, voicer, agi-number-archer, lcr-finder
функции AGI на сайте Asterisk'а
Буду рад комментариям к статье, предложениям по разработке, конструктивной критике.