Developers Club geek daily blog

1 year, 6 months ago
As well as all ASTERISK'ERY I faced problem of more than once that on PBX there are some trunks which are used for outgoing communication. And as at many, at my customers too the part of these trunks is the main, and the others play role reserve, on falling/employment case / chego-libo still the first.



The standard mechanism of solution of such problem is considered the following example:

exten => _<Че то там>, 1, Dial (SIP/trunk/<Че то там>)
exten => _<Че то там>, n, GotoIf ($ [" $ {DIALSTATUS}"! = "ANSWER"]? Dial_Another_Prov:Hangup)
exten => _<Че то там>, n (Dial_Another_Prov), Dial (SIP/trunk2/<Че то там>)
exten => _<Че то там>, n (hangup), Hangup ()

Well or here such example which however lies on network open spaces

[macro-safedial]
exten => s, 1, Set (DIALSTART= of $ {EPOCH})
exten => s, n, Dial ($ {ARG1}, $ {ARG2}, $ {ARG3}, $ {ARG4})
exten => s, n, Goto (s-of $ {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 of "$ {ARG1}")
exten => s-BUSY, 1, Busy
exten => s-CHANUNAVAIL, 1, Verbose (1, Need failover for of "$ {ARG1}")
exten => s-CONGESTION, 1, Congestion
exten => _s-., 1, Congestion
exten =>, - 1, Congestion

Through some time to me have started sickening such solutions, proceeding from reasons of their gromozkost and increase in number of reserve channels at one of customers who had question by all means to phone to the client. It in general and clearly: the telephony has to remain always telephony, and work. On that it and PBX — to automate work and to relieve of headaches.

Between times transferring all the wards from normal dialplan on lua the decision — to model has been made.

That. We have at hand excellent working tool — the whole Programming language. Which, as well as his many colleagues, is able to work with network interfaces. And it means that we can use this property for the benefits. At that not to look at statuses of trunks and already then to cause the available? It is necessary only:

1. To be connected to AMI
2. To receive names of trunks
3. To receive their statuses

And so. First of all we cling library of sockets:

local socket = require("socket")


For the analysis of trunks I will use AMI (as already probably all have guessed proceeding from the name). As AMI works on tcp to stack, I also will describe it:

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


Further I describe context from which trunks will be caused and I hang on it function necessary to us. Let's tell … outgoing_calls_external_dst In fact this function - is entity of context. That is analog of context in extensions.conf (I will not paint it with code. Everything is on wiki.asterisk.org)

Here I, when receiving call will be connected to the AMI interface of the 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 most interesting begins. We request all feasts from ASTERISK. "Why all?" — the reader will ask. "After all there is SIPshowregistry!". Yes. Is. But first it will show us only trunks with registration, and secondly if the provider became unavailable, and registration time has not expired yet, information on trunk status all the same will be non-valid. "But SIPpeers will show also clients too!" — and it will be the correct note. therefore it is necessary to prepare trunks.
In sip/users/<Куда вы там еще кто складывает свои транки>for each trunk I:

1. Has included qualify
2. Has prescribed the description parameter = to line

That is in other words — all that is described as line and there is trunk. Why it is important? because SIPpeers will return us here such description for each feast. Moreover — it will return you it in that order in which they are stated at you in the file/table mysql

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 rasparsiv all that is from feasts on the server we thus perfectly will separate grains from ryegrass and we will put grains in one basket under the name 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 array/plate of all trunks on our ASTERISK.
it was necessary only to find out what of them it is available and to call through it. It is possible to make it 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 also we do not forget to close behind ourselves door))

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


It in general the simplest example of how AMI can use directly in the dialplan. Also prevents to learn nothing also employment of channels. It will be necessary only rasparsit output of the sip show inuse team. mysql connectors and redis, and anything if necessary is fastened here both. Without crutches.

P.S. For lazy there is the whole library ami-lua. Only with documentation there … in any way.

This article is a translation of the original post at habrahabr.ru/post/264819/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus