Asterisk. Ненормальный перевод

    В Asterisk 2 типа трансферов:

    — слепой: после набора нужного сотрудника переводящий сразу отваливается.
    — расширенный: возможность поговорить с тем, кому перевод предназначен, принять callback.

    Мне понадобилось совместить простоту первого и функционал второго. Без AMI/ARI/AGI. Без костылей.

    Велосипед под катом.

    Сразу оговорюсь почему не использовал интерфейсы, которые уже есть: это не очень удобная штука, когда речь заходит о стеке Asterisk, например (да и вообще не люблю я их). Но это так. Лирическое отступление.

    Поехали далее. Указатели (расшифровки, или как там еще можно сказать. Для простоты короче):

    • Переводящий — тот кто переводит
    • Принимающий — тот кто принимает перевод

    Сценарий:

    • проверить, а доступен ли вообще оператор которому должен прийти перевод звонка. Если недоступен — проиграть переводящему сообщение об этом и вернуть назад звонок
    • если оператор доступен, положить трубку переводящему и отправить звонок на принимающего
    • если принимающий занят (разговаривает например), предложить пользователю подождать или вернуться к переводящему
    • если принимающий не отвечает, вернуться к переводящему*

    Инструменты Asterisk, которые мне понадобились: features.conf.

    С него и начну. Так как у меня появляется собственное событие, которое должно быть произведено по нажатию на определенную клавишу (DTMF) во время разговора, то тут самое место использовать features.conf с его возможностью создавать свои события и определять на них свои действия

    Мое событие называется customTransfer и выглядит так (описание того как создаются кастомные события есть в features.conf и на wiki.asterisk.org. Не буду расписывать):

    customTransfer => #,self,GoSub(customTransfer,#,1),default

    То есть по нажатию на # вызываем GoSub и уходим в контекст диалплана.

    Тут оговорюсь что использую lua, поэтому далее буду писать функции которые вызываю
    Для тех кто не знает, то в lua контекст определяется в таблице extenions и может указывать на функцию которую надо выполнять:


    extensons={
            ["customTransfer"]={
                       ["#"]=customTransfer --название функции
            }
    }

    Функция выглядит так ( Комменты описывают, что к чему и рекомендованы к прочтению для понимания)

    
    function customTransfer(context,extension)
            
            --[[ считываю номер, на который делается перевод ]]
     	app.Read("TRANSF",nil,10,nil,1,5)		
    	
    	if channel.READSTATUS:get() == "OK" then
    
                    -- [[ Проверяю доступен ли абонент, на которого я делаю перевод. я использую другой сервер регистрации, но при использовании именно Asterisk как сервера регистрации наличие пользователя можно проверить так.]]
                    app.chanIsAvail('SIP/'..chan.TRANSF:get())
                    if channel.AVAILCHAN:get()~='' then            
    		
                         --[[ запоминаю кто сделал перевод (переменную канала CALLED_NUMBER я создал когда принимал входящий звонок (здесь ее нет). В ней лежит номер, на который поступил звонок (то есть перевод работает с тем кто принял звонок, а не с тем кто его инициировал)). Кладу во внутренюю БД Asterisk (можно redis, можно вообще все что угодно). Делаю это потому, что  ChannelRedirect создаст новый канал, и уже не увидит переменных, созданных в этом канале.  ]]
                          channel.DB('transferedBy/'..channel.BRIDGEPEER:get()):set(channel.CALLED_NUMBER:get())
    		
                         -- [[перевожу на новый канал, который уже в свою очередь обработает соединение и отправит в нужный контекст, который в свою очередь вызовет необходимую точку. То есть, по сути я имитирую сценарий создаваемый функцией трансфер (кэп тут)]]
                         app.ChannelRedirect(channel.BRIDGEPEER:get(),'redirectSetup',channel.TRANSF:get(),1)
                         --[[ закрываю канал того, кто переводил, так как он мне уже не нужен (автоматически кладу трубку)]]
    		        app.Hangup()
    	       else
                         --[[ если считать номер не удалось, информирую об этом ПЕРЕВОДЯЩЕГО и соединяю переводящего и ожидающего пользователя. Переводящий может попробовать перевести еще раз]]
    		     app.NoOp("user unavailible")
    		     app.Playback(channel.UNAVAILIBLEONTRANSFER:get())
    	       end
    
           else
                   --[[ если пользователь недоступен, проигрываю сообщение ПЕРЕВОДЯЩЕМУ и соединяю переводящего и ожидающего пользователя. Переводящий может попробовать перевести еще раз]]
                   app.NoOp("user unavailible")
    	       app.Playback(channel.UNAVAILIBLEONTRANSFER:get())
           end
    
    end	
    

    После того как канал был брошен в transfer и была выполнена проверка, его необходимо направить на принимающего пользователя.

    В контексте 'redirectSetup' настраивается вход в данную функцию (аналогично предыдущему)

    Сама функция выглядит следующим образом

    function redirectSetup(context,extension)
    	app.NoOp("trying to redirect to "..extension)
    	
            --[[ Эта переменная канала мне понадобится чтобы отправить звонок обратно, если принимающий пользователь не возьмет трубку, например или будет занят. Я храню ее в переменной канала так как в моем случае она путешествует по функциям диалплана и в общем то переменные канала это удобный способ хранить глобальные для канала переменные. В общем то ничто не мешает сохранить ее в переменной lua]]
    	channel.__TRANSFEREDBY:set(channel.DB('transferedBy/'..channel.CHANNEL:get()):get())
    	
            --[[ В базе данных эта переменная больше не понадобится, поэтому мы можно ее удалить ]]
    	channel.DB_DELETE('transferedBy/'..channel.CHANNEL:get()):get()
    
            --[[ Далее я отправляю вызов в основную функцию моего диалплана для обработки соединения, вместо нее вполне может быть просто  app.Dial на необходимый extension ]]
    	main(context,extension)
    end

    последний шаг — это организация возврата звонка при неответе/занятости абонента или по какой либо еще причине.

    Я думаю уже многие поняли что делать это нужно с помощью переменной TRANSFEREDBY

    Свой диалплан полностью выкладывать не буду. Приведу пример маленькой функции, чтобы не вводить в заблуждение — назову ее main.

    function main(context,extension)
          app.Dial("SIP/"..extension)
          if channel.DIALSTATUS:get()~="ANSWER" then
               app.Playback("olala")
               app.Dial("SIP/"..channel.TRANSFEREDBY:get())
          end 
    end

    Касательно возможностей данной организации: у меня диалплан в купе с данной функцией организован таким образом, что проверяет не только доступность локального номера, но и наличие мобильного номера, закрепленного за абонентом.

    Если абонент занят, предлагает ему оставаться на линии дожидаясь ответа или выйти из режима ожидания и перезвонить обратно тому, кто перевел (некий микс очереди и IVR), предлагает абоненту вернуться к тому кто перевел, если звонок на сотовый отправил на голосовую почту (у многих операторов данная услуга ничем не отличается от поднятой трубки. Они просто шлют 200 ответ и потом несут ересь...). последнее организовано тоже через customFeature.

    В общем-то все это достигается путем линейного программирования и включения головы.

    Вроде все.
    С наступающим тоагисчи
    Адекватных клиентов, и хороших исполнителей вам.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 2
    • 0

      Спасибо, тема нужная. Но придётся переписать под классический диаплан, ввиду почти никакого распространения lua )

      • 0
        Это вот вы зря так про lua. Куча всего написано на нем.

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