Быстрые помощники для вашего Asterisk'а

    Эта статья подводит промежуточный итог в разработке моих приложений для 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 — это передача работы управления диалпланом стороннему приложению, т.е. нашему серверу. Напишем такой простой диалплан.

    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'а

    Буду рад комментариям к статье, предложениям по разработке, конструктивной критике.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 22
    • +1
      Добрый день, спасибо за статью.
      мне понравилось использование npm для быстрой разработки и распространения приложения
      на мой взгляд весьма сомнительное преимущество, особенно если не имели опыта работы с Node.js. в своих проектах использую библиотеку pyst для работы с AGI и AMI на python.
      Т.е. если бы астериск умел записать файл, а потом его передать по AGI
      вообщем-то файл записать можно, да и передать файл тоже можно, правда не силами AGI(собственно для этого протокол и не предназначен), а например через sftp(не знаю, имеет ли node.js библиотеки для работы с sftp) скопировать записанный файл на удаленный сервер.
      • 0
        Я имел опыт с Node.js, поэтому пишу о своих мироощущениях. В целом, согласен, что кто чем умеет, тем и рисует.

        По поводу передачи файла: в voicer'е у меня было также разделение по машинам — одна с астериском, вторая с приложением, обошлись пока просто монтированием sshfs. там пришлось правда сделать отдельные настройки директории с файлами для астериска и для приложения.

      • +1
        Почему все забили на lua? Это же намного быстрее чем AGI. Ну в 1.8 я еще понимаю, на в 12-13 версиях AGI уже как костыль смотрится честно говоря…
        • +1
          Я AEL использую, вполне удобно и читабельно.
          • +1
            lua это lua, а agi хочешь perl,python,php,c,java,lua…
            • 0
              AGI тяжелее за счет того что это внешний интерфейс… Гораздо тяжелее. И не важно в каком исполнении он будет — сетевом или файловом.
              • 0
                Меня интересовала минимальное взаимодействие астериска и дополнительного сервиса. Поэтому FastAGI. Плюс то, что я знаю и понимаю, поэтому node.js. К тому же, активно у меня используемый сервис — это для определения входящих по регионам на 8800, там несколько сотен звонков в час, что для AGI вполне по силам.

                Диалплан на lua? Может быть. Вот в недавней статье приводится инфо, что работает все очень хорошо и быстро. Я писал, что можно поделить диалплан на маршрутизацию звонка и допвычисления. Вот для маршрутизации может быть стоит использовать.

                А еще вопрос: можно ли часть диалплана использовать из lua, а часть нативного диалплана, который может вызывать куски диалплана на lua? Типа, макросы написать на lua, а затем при необходимости их использовать в диалплане.
                • +1
                  Да. Можно. Смешивается все отлично. Читаются переменные и тд. Я вообще всю работу с базади данных (mysql, redis) вынес в lua. быстрые и удобные коннекторы + работа с функциями диалплана сразу из ядра- очень достойное решение.
                  • 0
                    Спасибо, вот теперь я точно хочу это попробовать сделать.
                    • 0
                      в продолжение темы пара вопросов:
                      как вы организуете extensions.lua? Т.е. не все же лежит в одном файле? как-то делится на модули?
                      не понял тему Long Running Operations (Autoservice). После 10-го астериска надо заморачиваться?
                      каким lua модулем вы цепляетесь к mysql, mongo?
                      пробовали делать какое-то динамическое добавление extensions? типа в веб-интерфейсе добавли абонента: добавили параметры в sip.conf, а затем прописали ему внутренний номер в extensions.

                      простейший диалплан я набросал, работает: )
                      забавно было в консоли астериска дебажить lua скрипт
                      • +1
                        Ну у меня на данный момент инклюдов файлов нет. Но вообще реорганизация предстоит естественно.

                        Long Running Operations (Autoservice) как раз таки после 10 астериска -заморачиваться не надо, а до 10 — как раз нужно.

                        По поводу конфигурирования екстеншнов — я не пишу диалпаны для каждого екстеншна. Я как правило пишу набор функций и храню екстеншны и соответсвие функций (или контекстов, когда как) в базе. Универсальней получается. Не люблю я эти все деревянные хардкодинговые решения.

                        Mongo не пользую. Пользую в основном redis
                        моудль redis-lua
                        и mysql
                        модуль LuaSQL

                        вообще набор библиотек у lua очень широк. Делать можно очень многое.
                        • 0
                          >>Я как правило пишу набор функций и храню екстеншны и соответсвие функций (или контекстов, когда как) в базе.
                          А можно пример кода этого момента?

                          >>вообще набор библиотек у lua очень широк.
                          да, я уже заценил luarocks, на стандартные нужды хватает: )

                          а как делаете отладку? Ну, т.е. постоянно app.noop() используете? или можно как-то более развернуто посмотреть значения таблиц lua?

                          а не в курсе как происходит работа с диалпланом lua в астериске? Т.е. астериск при загрузке pbx_lua.so парсит extensions.lua в диалплан и далее его держит в памяти, проводя каждый звонок по диалплану? Меня интересует, можно ли держать в памяти какие-то данные, а какие-то данные можно выставлять для каждого звонка?
                          • +1
                            А можно пример кода этого момента?

                            Коммерческая тайна. Не могу разглашать. как нить статью напишу по этому поводу)) чтобы без кода проектного. обобщить.

                            по отладке когда как — если это не диалплан как таковой то сначала скрип стандартным способом тестирую а потом заливаю его как часть диалплана. в dialplan да. Через NoOp

                            Астериск не парсит lua. он его проверяет изначально на ошибки обычным интерпритатором и потом при вхождении звонка — запускает скрипт его обработки, коим является extensions.lua
                            Все что можно держать в памяти обычным диалпланом — то можно держать в памяти и с помощью lua.
                            Ну либо я не совсем понял ваш вопрос)))

                            • 0
                              >>как нить статью напишу по этому поводу)) чтобы без кода проектного. обобщить.
                              киньте, пожалуйста, потом ссылку

                              по отладке, в wiki астериска нашел пример для замены стандартного print — print через консоль астериска: )

                              extensions.lua частично таки парсится, т.к. таблица extensions добавляется в диалплан, видно dialplan show

                              Память еще потестирую, но в целом, наверное для этого лучше использовать redis.
                              • 0
                                А еще вот особенность заметил: чуть что-то в extensions.lua не так, то при перезагрузке module reload pbx_lua.so астериск вылетает тут же. У вас так же? Это мне кажется как-то не очень нормальное поведение. Астериск 11.18, lua 5.2
                                • +1
                                  В память заносится только табличка с еxtensions
                                  Нет. У меня не вылетает. Но я использую 5.1 и астериск 13.
                                  Возможно дело в кривой омпиляции модуля на 11 астериске. Помню при подключении сторонних модулей lua в 12 была проблема что не видел астериск этих модулей. Приходилось в ручную переписывать кусок кода chan_sip по-моему. Точнее в ручную выставлять значение флага одного.
                                  В 13 это уже было решено. Так что вполне возможно проблема связана как то с этим.
                                  • 0
                                    А у тебя точно работают контексты из extensions.conf c контекстами в extensions.lua?
                                    Я пробую сделать
                                    extensions = {
                                    		["local"] = {
                                    			include = {'inner', 'outbound'}
                                    		};
                                    		["inner"] =inner(conf);
                                    	};
                                    

                                    где outbound описан в extensions.conf, а подключить его хочу чтобы во вне звонили по направлениям из outbound. У абонента назначен local, на inner внутренние номера уходят.
                                    • 0
                                      да. Точно. Главное чтобы они не дублировались.
                                      Попробуй include прописать для каждого контекста, а не как таблицу его формировать
                                      И возможно у тебя регулярки для екстеншнов в inner подходят для звонка в outbound И тогда естественно звонок останется на inner
              • 0
                fastAGI который сетевой совсем ничего. или у Вас были реальные а не теоретические проблеммы с перформансом fastAGI?
                • +1
                  Я бы сказал так — в любом случае на lua нагрузка будет меньше потому что функции работают напрямую с ядром. Не через какие либо интерфейсы. Я не говорю что fastAGI плох. Просто отпала необходимость в подобных интефейсах когда появилась возможность прямого кодинга. + lua сам по себе очень быстрый язык и у него очень много плюшек вплоть до впиливания функций напрямую на C.

                  P. S. Я как бы не против чего либо другого ине настаиваю. Прсото факт констатирую что это очень удобный интерфейс.
                  • +1
                    Подходы разные нужны. Я против 'Ну в 1.8 я еще понимаю, на в 12-13 версиях AGI уже как костыль смотрится честно говоря'.
                    — Мешать все в кучу не красиво. IMHO.
                    — Есть конфиг телефонии — отдельно, есть бизнес логика — отдельно.
                    — Разработчик и админ-телефонист в одном лице встречаются реже чем по отдельности.
                    — Если что то тяжелое то через fastAGI замасштабировать легче.
                    • 0
                      Согласен по всем пунктам.

                      По пункту 3 — вот поэтому, наверное, так мало пользуются lua для диалпланов.

                      Также какие-то проблемы, то с lua ты разбираешься сам, а с традиционным диалпланом — можно пойти на форуме спросить.

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