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:
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:
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:
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:
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:
Well, do not forget to close the door behind you))
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.
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.