Dialog Asterisk Manager Interface

    Like all ASTERISKers, I have repeatedly encountered the problem that there are several trunks on PBX that are used for outgoing communication. And like many, for my customers, some of these trunks are also the main ones, while the rest play the role of backup ones in case of a fall / employment / anything else first.



    The following example is considered a standard mechanism for solving this problem:

    exten => _ <Something>, 1, Dial (SIP / trunk / <Something>)
    exten => _ <Something>, n, GotoIf ($ [" $ {DIALSTATUS} "! =" ANSWER "]? Dial_Another_Prov: Hangup)
    exten => _ <Something>, n (Dial_Another_Prov), Dial (SIP / trunk2 / <Something>)
    exten => _ <Something there>, n (hangup), Hangup ()

    Well, or here is an example,


    exten => s, 1, Set (DIALSTART = $ {EPOCH})
    exten => s, n, Dial ($ {ARG1}, $ {ARG2}, $ {ARG3}, $ {ARG4})
    exten => s, n, Goto (s - $ {DIALSTATUS}, 1)

    exten => s-NOANSWER, 1, GotoIf ($ ["$ {DTIME}" = "0"]? here)
    exten => s-NOANSWER, n, Hangup
    exten => s-NOANSWER, n (here), Verbose (1, Need failover for "$ {ARG1}")
    exten => s-BUSY, 1, Busy
    exten => s-CHANUNAVAIL, 1, Verbose (1, Need failover for "$ {ARG1}")
    exten => s-CONGESTION, 1, Congestion
    exten => _s -., 1, Congestion
    exten => s-, 1, Congestion

    After some time, such decisions started to me for reasons of their volume and the increase in the number of backup channels for one of the customers,who had a question by all means get through to the client. It is generally understandable: telephony should always remain telephony, and work. That's why it and PBX - to automate the work and get rid of headaches.

    In between, transferring all of their wards from the usual dialplan to lua, it was decided to fight.

    Well. We have a great working tool at hand - a whole LANGUAGE of programming. Which, like many of his brothers, knows how to work with network interfaces. And this means that we can use this property for our own benefit. Why not look at the state of the trunks and then call the available one? All you need is:

    1. Connect to AMI
    2. Get the names of trunks
    3. Get their statuses

    And so. First, we hook the socket library:

    local socket = require("socket")
    


    For the analysis of trunks I will use AMI (as everyone probably guessed from the name). Since AMI works on the tcp stack, I will describe it:

    tcp = socket.tcp()
    tcp:settimeout(100)
    


    Next, I describe the context from which trunks will be called and attach the function we need to it. Say ... outgoing_calls_external_dst Essentially this function is the essence of the context. That is, an analog of the context in extensions.conf (I will not paint this with code. Everything is on wiki.asterisk.org)

    Here, when I receive a call, I will connect to the AMI interface of my asterisk:

    tcp:connect("127.0.0.1", 5038)
    result = tcp:receive()
    tcp:send("Action: Login\r\n")
    tcp:send("Username: pr\r\n")
    tcp:send("Secret: 1\r\n\r\n")
    LoginIsOk = 0	
    while LoginIsOk == 0	do
    	result=tcp:receive()  -- перебираем входящие сообщения пока не встретим сообщение о удачном соединении.
    	if string.find(result,"Authentication accepted")~=nil then
    		LoginIsOk = 1
    	end
    	if string.find(result,"Response: Error")~=nil then
    		LoginIsOk = 2
    	end
    end
    


    Further, in general, the fun begins. We ask ASTERISK for all the feasts. “Why all?” - the reader will ask. “After all, there is SIPshowregistry !” Yes. There is. But firstly, he will show us only trunks with registration, and secondly, if the provider has become unavailable, and the registration time has not yet expired, then information about the state of the trunk will still be invalid. “But SIPpeers will show customers too!” - and that will be the right remark. so you need to prepare trunks.
    In sip / users / <Where are you still putting your trunks out there> for each trunk I:

    1. Enable qualify
    2. I registered parameter description = line

    That is, in other words, everything that is described as line is a trunk. Why is it important? because SIPpeers will return us such a description for each feast. Moreover, he will return it to you in the order in which they are written in your mysql file / table

    Channeltype: SIP
    ObjectName: mysupertrunk
    ChanObjectType: peer
    IPaddress: -none-
    IPport: 0
    Dynamic: yes
    AutoForcerport: no
    Forcerport: yes
    AutoComedia: no
    Comedia: yes
    VideoSupport: no
    TextSupport: no
    ACL: no
    Status: UNKNOWN
    RealtimeDevice: no
    Description: line

    In general, having parsed everything that is from peers on the server, we will thus perfectly separate the grains from the chaff and put the grains in one basket called trunks:

    
    tcp:send("Action: SIPpeers\r\n\r\n")
    while result ~= "EventList: start" do
    	result = tcp:receive()
    end
    trunks = {}
    i = 1
    while result ~= "Event: PeerlistComplete" do
    	 result = tcp:receive()
    	if string.find(result,"ObjectName")~=nil then
    			ObjectName = splitted_value(result,": ")    --splitted_value - это самописная функция, которая разделяет строку на подстроки и возращает результат
    	end	
    	if string.find(result,"Description")~=nil then
    			Description = splitted_value(result,": ")
    	end	
    	if Description == "line" then
    			trunks[i] = ObjectName
    			i = i + 1
    			Description=nil  -- обязательно обнуляем переменную. Иначе попадем в бесконечный цикл.
    	end	
    end		         
    


    In general, now we have an array / plate of all trunks on our ASTERISK.
    it remains only to find out which one is available and call through it. This can be done through SIPpeerstatus:

    for key,val in pairs(trunks) do
    		tcp:send("Action: SIPpeerstatus\r\n")  
    		tcp:send("Peer: "..val.."\r\n\r\n")
    		while result~="Event: SIPpeerstatusComplete" do
    		         result=tcp:receive()
    			 if string.find(result,"PeerStatus:")~=nil then
    				  status=split(result,": ")     --split еще одна самописная функция, которая делит подстроку и возвращает таблицу. Предыдущая функция включает в себя эту 
    				  if status[2]=="Reachable" then
    							app.Dial("SIP/"..val.."/"..extension)
    						end
    					end	
    				end
    		end
    


    Well, do not forget to close the door behind you))

    tcp:send("Action: Logoff\r\n\r\n")
    while result~="Response: Goodbye" do
    	  result=tcp:receive()
    end
    tcp:close()
    


    This is generally the simplest example of how AMI can be used directly in the dialplan itself. Also, busy channels do not interfere with recognizing anything. You only need to parse the output of the sip show inuse command. Both mysql connectors and redis are screwed here, and anything else if necessary. Without crutches.

    PS For the lazy, there is a whole ami-lua library. Only here with the documentation there ... no way.

    Also popular now: