Asterisk Manager Interface в диалплане

от автора

Как и все АSTERISK’еры я не раз сталкивался с проблемой того, что на PBX существует несколько транков, которые используются для исходящей связи. И как у многих, у моих заказчиков тоже часть этих транков является основными, а остальные играют роль резервных, на случай падения/занятости/чего-либо еще первых.

Стандартным механизмом решения такой проблемы считается следующий пример:

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()

Ну или вот такой пример, который впрочем лежит на просторах сети

[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

Через какое-то время мне начали претить такие решения, исходя из соображений их громозкости и увеличения количества резервных каналов у одного из заказчиков, у которого стоял вопрос во что бы то ни стало дозвониться до клиента. Оно в общем то и понятно: телефония должна всегда оставаться телефонией, и работать. На то оно и PBX — чтобы автоматизировать работу и избавить от головных болей.

Между делом переводя всех своих подопечных с обычного диалплана на lua было принято решение — воять.

Что ж. У нас под руками отличный рабочий инструмент — целый ЯЗЫК программирования. Который, как и многие его собратья, умеет работать с сетевыми интерфейсами. А это значит что мы можем использовать это свойство на свои блага. Чего бы не посмотреть на состояния транков и уже затем вызвать доступный? Нужно всего то:

1. Подключиться к AMI
2. Получить имена транков
3. Получить их статусы

И так. Первым делом цепляем библиотеку сокетов:

local socket = require("socket") 

Для анализа транков я буду использовать AMI (как уже наверное все догадались исходя из названия). Так как AMI работает по tcp стеку, то его я и опишу:

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

Далее я описываю контекст из которого будут вызываться транки и навешиваю на него нужную нам функцию. Скажем… outgoing_calls_external_dst По сути эта функция- есть сущность контекста. То есть аналог контекста в extensions.conf (Это я расписывать кодом не буду. Все есть на wiki.asterisk.org)

Здесь я, при получении звонка подключусь к AMI интерфейсу своего 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 

Дальше в общем-то начинается самое интересное. Запрашиваем у ASTERISK все пиры. «Зачем все?» — спросит читатель. «Ведь есть же SIPshowregistry!». Да. Есть. Но во-первых он покажет нам только транки с регистрацией, а во-вторых, если провайдер стал недоступен, а время регистрации еще не истекло, то информация о состоянии транка все равно будет невалидной. «Но SIPpeers покажет и клиентов тоже!» — и это будет правильным замечанием. поэтому нужно подготовить транки.
В sip/users/<Куда вы там еще кто складывает свои транки> для каждого транка я:

1. Включил qualify
2. Прописал параметр description = line

То есть иными словами — все что описано как line и есть транк. Почему это важно? потому что SIPpeers вернет нам вот такое описание для каждого пира. Более того — он вернет вам это в том порядке, в котором они прописаны у вас в файле/таблице 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

В общем то распарсив все что есть из пиров на сервере мы таким образом отлично отделим зерна от плевел и сложим зерна в одну корзину под названием 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		            

В общем то теперь у нас есть массив/табличка всех транков на нашем ASTERISK.
осталось только выяснить какой из них доступен и позвонить через него. Сделать это можно через 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 			 		 

Ну и не забываем закрыть за собой дверь))

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

Это в общем-то самый простой пример того, как можно использовать AMI непосредственно в самом диалплане. Так же ничего не мешает узнавать и занятость каналов. Необходимо будет только распарcить вывод команды sip show inuse. Прикручивается сюда и mysql коннекторы и redis, и все что угодно при необходимости. Без костылей.

P.S. Для ленивых есть целая библиотека ami-lua. Только вот с документацией там… никак.

ссылка на оригинал статьи http://habrahabr.ru/post/264819/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *