Like all ASTERISKers, I have repeatedly encountered the problem that there are several trunks on the PBX that are used for outgoing communication. And like many, my customers also have a part of these trunks as the main ones, and the rest play the role of backup ones in case of a fall / employment / something else first.
A standard mechanism for solving such a problem is the following example:
')
exten => _ <Che then there>, 1, Dial (SIP / trunk / <Che then there>)
exten => _ <Che then there>, n, GotoIf ($ ["$ {DIALSTATUS}"! = "ANSWER"]? Dial_Another_Prov: Hangup)
exten => _ <Che then there>, n (Dial_Another_Prov), Dial (SIP / trunk2 / <Che then there>)
exten => _ <Che then there>, n (hangup), Hangup ()
Well, or here's an example, which, incidentally, lies in the open spaces of the network
[macro-safedial]
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, I started to turn up such decisions, based on considerations of their loudness and an increase in the number of backup channels from one of the customers, who had a question
to get through to the client no matter what . It is quite understandable in general: telephony should always remain telephony, and work. That's why it is PBX - to automate work and get rid of headaches.
In the meantime, transferring all of their wards from the usual dialplan to lua, it was decided to wake up.
Well. We have an excellent working tool at hand - the whole LANGUAGE of programming. Which, like many of his brothers, can work with network interfaces. This means that we can use this property for our own benefit. Why not look at the state of trunks and then call available? All you need is:
1. Connect to AMI
2. Get trunk names
3. Get their statuses
So. First of all, we hook the socket library:
local socket = require("socket")
For the analysis of trunks, I will use AMI (as everyone probably already guessed from the name). Since AMI works on the tcp stack, I will describe it:
tcp = socket.tcp() tcp:settimeout(100)
Further I describe a context from which trunks will be caused and I hang on it the function necessary to us. Let's say ...
outgoing_calls_external_dst In essence, this function is the essence of the context. That is an analogue of the context in extensions.conf (I will not write this code. Everything is on wiki.asterisk.org)
Here I, when receiving a call, will connect to my asterisk's AMI interface:
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()
Further, in general, the fun begins. We request all peers from ASTERISK. “Why is everything?” The reader will ask. "After all, there is
SIPshowregistry !". Yes. There is. But firstly, it 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 this will be a good point. therefore, trunks need to be prepared.
In sip / users / <Where are you still who adds your trunks> for each trunk I:
1. Enable qualify
2. Prescribed parameter description = line
That is, in other words - all that is described as line is the trunk. Why is it important? because SIPpeers will give us this description for each feast. Moreover, it 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, then parsing all that is from the peers on the server, we thus perfectly separate the wheat from the chaff and put the grain 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,": ")
So now we have an array / table 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,": ")
Well, do not forget to close the door behind him))
tcp:send("Action: Logoff\r\n\r\n") while result~="Response: Goodbye" do result=tcp:receive() end tcp:close()
This is, in general, the simplest example of how AMI can be used directly in the dialplan itself. Also, nothing prevents to recognize the employment of channels. It will only be necessary to parse the output of the sip show inuse command. Screws here and mysql connectors and redis, and anything, if necessary. Without crutches.
PS For the lazy there is a whole library ami-lua. Only here with the documentation there ... no way.