AEL в asterisk

    В последнее время на хабре появилось довольно много статей, посвященных астериску и во всех статьях авторы для описания dial plan'а используют стандартный extensions.conf. Я не буду описывать здесь формат записи в extensions.conf, а лишь постараюсь кратко описать его различия с конфигурационными файлами в формате AEL(extensions.ael), которых на самом деле совсем немного, а вот удобств ael добавляет значительно. В дальнейшем, для удобства, dial plan, описанный в extensions.conf буду называть «обычным» форматом, ну а ael — соответственно ael. Давайте рассмотрим пример простейшего dial plan'a в обычном формате:
    ;
    [internal]
    exten => s,1,Answer                                                             
    exten => s,n,Background(someivr)
    exten => s,n,Read(intgroup,,3)
    exten => s,n,Goto(${intgroup},1)
    exten => XXX,1,Dial(SIP/${EXTEN})
    exten => XXX,n,HangUp


    А вот так этот же контекст будет выглядеть в синтаксисе ael:

    context internal {
    	s=> {
    		Answer;
    		Background(someivr);
    		Read(intgroup,,3);
    		Goto(${intgroup},1);
    	}
    	XXX => {
    		Dial(SIP/${EXTEN});
    		HangUp;
    	}
    }


    Итак, различия налицо. Для меня одним из главных плюсов ael является удобство чтения конфига: даже на этом маленьком абстрактном примере видно, что читать и понимать конфиг проще. Хотя, допускаю, что это субъективно. Второе что сразу бросается в глаза — это отсутствие приоритета (s,n в коде выше, правда, как заметили в комментариях, приоритет и там уже не обязателен) в описании dialplan'a.
    Почему их нет? Вернее, почему они есть в описании обычного dialplan'a?
    Если вы не имели до этого дела с астериском и с его обычным dialplan'ом, то вы будете удивлены, узнав что в нем нет ни циклов, ни условий. Есть только лишь условный переход (gotoif), с помощью которого, в принципе, можно организовать даже циклы, но удобство этого подхода весьма сомнительно. Давайте представим себе простейший ael код, который будет выводить в консоль астериска true или false:
    if(${var}=foo) {
    	NoOp(true);
    }
    else {
    	NoOp(false);
    }
    


    В обычном виде это условие будет выглядеть так:
    exten=>1,1,GotoIf($[${var}=foo]?label1:label2) 
    exten=>1,n(label1),NoOp(true)
    exten=>1,n,Goto(end_of_if)
    exten=>1,n(label2),NoOp(false)
    exten=>1,n(end_of_if),...
    


    Мало того, что мы потеряли «читаемость», так еще и вынуждены либо статически прописывать везде приоритеты (и получить абсолютно нерасширяемый код), либо придумывать метки на каждый чих. Да, в ael тоже есть метки, только надобность в них практически пропадает, так как синтаксис позволяет уйти от goto как такового.
    s=> {
    	Answer;
    	somelabel:
    		HangUp;
    }
    


    Кроме непосредственно условий (if/else) в ael вы можете использовать и оператор switch с привычным синтаксисом:
     _777X => { 
             switch (${EXTEN}) { 
                  pattern N11: 
                       NoOp(You called a N11 number-- ${EXTEN}); 
                       break; 
                  case 7771: 
                       NoOp(You called 7771!); 
                       break; 
                  case 7772: 
                       NoOp(You called 7772!); 
                       break; 
                  case 7773: 
                       NoOp(You called 7773!); 
                       // fall thru- 
                  default: 
                       NoOp(In the default clause!); 
             }; 
    

    А теперь попробуйте описать эту же конфигурацию, но в обычном формате? То то же.

    А теперь представьте себе, что у вас более-менее сложный dialplan, который со временем будет расти и расширяться, будет добавляться новый функционал, будет много условий, очередей и так далее — здесь без ael все будет довольно печально. По работе периодически приходится настраивать астериск и запросы у клиентов бывают довольно экзотичными. Решать эти задачи без ael было бы гораздо сложнее.

    Справедливости ради следует сказать, что астериск преобразует конфигурацию ael в «обычный» формат и работает уже с ним (можно проверить через asterisk -rx «dialplan show»), так что его синтаксис тоже знать обязательно. Но ael может значительно облегчить жизнь, тем более, как вы видите, синтаксис у него довольно очевиден, а если есть опыт написания обычных dial plan'ов, то перейти на ael будет легко.
    Поделиться публикацией

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

      +1
      В последнее время на хабре стало больше публикаций о Voip телефонии, это радует. +

      Кто бы осветил другие решения, на Freeswitch, или Yate? Мне кажется что для програмиста со стажем намного больше подходит Freeswitch.
        0
        FreeSWITCH очень специфичное решение. Это еще более телефонный движок, чем Asterisk. Not user friendly. Хотя более надежное и архитектурно правильное решение.
          0
          >Not user friendly

          Да ну? Что это там такого не friendly?
          Тот же диалплан гораздо понятнее чем на *.
            0
            Вы робот? :-)
            С каких пор XML конфиги юзабильнее конфига в формате [section] / [option]?
        +1
        AEL радует только в процессе написания диалплана. Желательно простого и наглядного.

        Как-то раз мы делали виртуальную АТС, где вся бизнес-логика была в mySQL базе. Пиры через RealTime, а вот диалплан вначале сделали на AEL. Логика обработки звонка в AEL.

        Функция MySQL query, которая напрямую из диалплана лезет в базу, смотрит всякие там установки юзера типа call forward, dial timeout, etc. Так вот, получился такой не слабый диалплан на сотни строк (завтра прийду на работу, поищу).

        Отладка такого диалплана — кошмар! Так как этот красивый код превращается во внутренний extension.conf! В общем, это надо видеть.
          0
          Последнее мое увлечение — pbx_lua, но это было год назад, и я не успел на нем хоть что-то реализовать :-)
            +1
            Пример диалплана на LUA — pastebin.com/vzWLMewH
            0
            а оно тоже в обычный формат компилируется?
              0
              Нет, Lua исполняется прямо в ядре. В консоли Asterisk вы увидите просто исполняемые команды. А в dialplan show будет пусто.
                0
                а дебужится как?
                  0
                  Ну как в обычном языке программирования :-)
                  app.dial
                  print «This is after dial»
                  app.hangup
            0
            Было бы очень интересно посмотреть. Пиры у меня тоже realtime, а вот логика вся в ael. Несколько раз в голову приходила идея закинуть всю логику в mysql и нарисовать веб-морду к этому делу, чтобы менеджеры могли сами клиентские запросы решать, но со временем пришло понимание что запросы у клиентов часто специфические, и далеко не всегда эти вопросы можно будет решить без меня и смысл в затее теряется…
              0
              Найду завтра на работе и покажу свой ael. Попробуйте pbx_lua, мне интересно, что у Вас получится ;-)
                0
                а как в рилтайм AEL засунуть?
                  +1
                  Мне кажется, только через res_config, при помощи которого любой .conf файл помещается в базу.
                    0
                    ну, не любой, а в «обычном», построчном формате.
                    тоесть уже оттранслированный AEL.
                    слабо представляю как это можно сделать.
              0
              Мы делали IVR для сдачи показаний счетчиков, где нужно было в цикле делать SOAP запрос для каждого счетчика в квартире и в зависимости от результатов по-разному действовать и воспроизводить различные звуки…

              \me рвет волосы на разных частях тела

              Почему мы не знали тогда про ael! 50 строчек, а, кажется, проще было бы * переписать.
                0
                И ведь самое главное — синтаксис практически тот же, только нагляднее и с новыми вкусными плюшками :).
                  +1
                  А уж в случае с дерганием SOAP тем более надо было выносить обработку логики из extensions. В AGI например :-)
                    0
                    Это точно, особенно учитывая то, что agi можно писать на любом удобном языке — не думаю что у кого-либо возникнут проблемы с освоением.
                      0
                      И это правильно. Просто скрипт разрастался постепенно в соответствии с ростом аппетитов заказчика. А с госзаказчиком в плане ТЗ спорить тяжело. Когда мы осознали что в диалплане больше кода чем в скриптах, которые он дергает, было уже поздно.
                    0
                    exten=>1,1,ExecIf($[${var}=foo]?Noop(true):Noop(false)) — не то?
                      0
                      Для одной команды в условии — то, только в реальной жизни команд обычно больше.
                        0
                        В реальной жизни достают именно GotoIf из за единственной команды :) Если их больше — GotoIf уже выглядит приемлемо. А ExecIf + GoSub вообще все решает.

                        Подтолкнуло взяться за статью «dialplan tricks».

                          0
                          Может это действительно субъективно, но мне кажется читаемость никакая. Посмотрите пример на LUA, который скинул litnimax: там уже вообще все походит на нормальный высокоуровневый ЯП.
                            0
                            Согласен, чтобы читать надо иметь определенный skill. Но у AEL все же есть проблемы с дебагом. Если уж хочется ЯП в телефонии — вооружайтесь FastAGI, прям на php или python можно писать.
                              0
                              Мне кажется AGI больше подходит для внешнего функционала — письма там отправлять или еще что подобное… У ael нет проблем с дебагом: дебажить все равно dialplan. Не будем спорить — как написано в топе, удобство и читаемость субъективны, и мое мнение, что ael удобнее, я знал что не для всех.
                                0
                                Я бы воздержался от использования AGI и не использовал бы его для обработки _всех_ звонков на астериске — проблемы со стабильностью у него были и, судя по всему, до сих пор есть — вот буквально только что словил с AGI дедлок на 1.6.0.26 (не припомню, когда с этой версией вообще проблемы были).

                                  0
                                  Относительно стабильности AGI согласен. Я рекомендовал FastAGI, т.к. по моему опыту его можно использовать на массовых, промышленных объемах.
                              0
                              Если окажется полезным при написании статьи про триксы — вот пример нашего скрипта. По всей видимости, сплошной антипаттерн. Специально ничего не приукрашивал — как есть.

                              Буду благодарен, если ляпы укажете. Все вызываемые скрипты — коротулечки на Ruby+savon которые дергают большой сервер по SOAP.

                              [test_meters]
                              exten => 333330,1,Background(14)
                              exten => 333330,2,WaitExten(1)
                              exten => *,1,GotoIf($["${phase2}" = "1"]?test_meters,333330,say_next_number:test_meters,333330,4)
                              exten => 333330,3,Set(numTries=0)
                              exten => 333330,4,Playback(15)
                              exten => 333330,5(auth),Read(account,,10,,1,60)
                              exten => 333330,n,System(/var/lib/asterisk/saracen/needed_digits.rb ${account} >> /tmp/needed.log 2>&1)
                              exten => 333330,n,Set(needed_digits=${SHELL(/var/lib/asterisk/saracen/needed_digits.rb ${account})})
                              exten => 333330,n,GotoIf($["${needed_digits}" = ""]?bad_try)

                              exten => 333330,n(next_meter),Set(next_meter_number=${SHELL(/var/lib/asterisk/saracen/next_line.rb ${account})})
                              exten => 333330,n,Set(next_number=${SHELL(/var/lib/asterisk/saracen/next_counter.rb ${account})})
                              exten => 333330,n,GotoIf($["${next_number}" = "all"]?all)
                              exten => 333330,n,GotoIf($["${next_number}" = "night"]?night)
                              exten => 333330,n,GotoIf($["${next_number}" = "day"]?day)
                              exten => 333330,n(normal),Playback(18)
                              exten => 333330,n(say_next_number),SayDigits(${next_number})
                              exten => 333330,n,Goto(input_data)

                              exten => 333330,n(night),Playback(25)
                              exten => 333330,n,Set(next_number=${SHELL(/var/lib/asterisk/saracen/next_counter.rb ${account})})
                              exten => 333330,n,Goto(say_next_number)

                              exten => 333330,n(day),Playback(24)
                              exten => 333330,n,Set(next_number=${SHELL(/var/lib/asterisk/saracen/next_counter.rb ${account})})
                              exten => 333330,n,Goto(say_next_number)

                              exten => 333330,n(input_data),Read(device_data,,5,,1,60)
                              exten => 333330,n,Playback(20)
                              exten => 333330,n,SayDigits(${device_data})
                              exten => 333330,n,Set(phase2=1)
                              exten => 333330,n,Background(19)
                              exten => 333330,n,WaitExten(4)
                              exten => #,1,Goto(test_meters,333330,submit_data)

                              ;exten => 333330,30(submit_data),System(/var/lib/asterisk/saracen/submit_data.rb ${account} "${next_meter_number}" ${device_data})
                              exten => 333330,30(submit_data),Set(submit_result=${SHELL(/var/lib/asterisk/saracen/submit_data.rb ${account} "${next_meter_number}" ${device_data})})
                              exten => 333330,n,GotoIf($["${submit_result}" = "ok"]?submit_success:submit_failure)

                              exten => 333330,n(submit_success),Playback(22)
                              exten => 333330,n,Goto(next_meter)

                              exten => 333330,n(submit_failure), Playback(23)
                              exten => 333330,n,Goto(hangup)
                          0
                          ух ты, в тему. спасибо
                            0
                            Существует ли конвертилка из обычного в AEL?

                            А то вот у меня более-менее сложный dialplan, который со временем разросся и расширился, добавился новый функционал, много условий, очередей и так далее.
                            И всё это на старом добром.

                            Ещё вопрос — как всю эту красоты отлаживать?
                            Меня в своё время смутило именно это.

                              0
                              >>Существует ли конвертилка из обычного в AEL?
                              Насколько знаю, нет.

                              Повторюсь, что ael все равно преобразуется в обычный формат, функции и команды в dial plan'е и ael те же — поэтому процесс отлаживания такой же, отличается лишь процесс написания.
                                0
                                процесс отлаживания хоть и такойже, только отлаживается то уже оттранслированный AEL, в котором все прелести наглядности херятся нафик.
                                  0
                                  Все прелести наглядности — это осмысленные метки? При использовании ael вы с легкостью откажетесь от меток и отлаживать эту часть не понадобится.
                              +1
                              А выводить в лог консоли всёже уместнее Verbose(0, блабла) :)
                                0
                                Большое спасибо за статью — действительно полезная информация. Удобочитаемость в разы лучше с AEL.
                                  0
                                  > А теперь представьте себе, что у вас более-менее сложный dialplan, который со временем будет расти и расширяться, будет добавляться новый функционал.
                                  Вы прям как в воду глядите!

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

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