Как и все А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. Получить их статусы
И так. Первым делом цепляем библиотеку сокетов:
Для анализа транков я буду использовать AMI (как уже наверное все догадались исходя из названия). Так как AMI работает по tcp стеку, то его я и опишу:
Далее я описываю контекст из которого будут вызываться транки и навешиваю на него нужную нам функцию. Скажем… outgoing_calls_external_dst По сути эта функция- есть сущность контекста. То есть аналог контекста в extensions.conf (Это я расписывать кодом не буду. Все есть на wiki.asterisk.org)
Здесь я, при получении звонка подключусь к AMI интерфейсу своего asterisk:
Дальше в общем-то начинается самое интересное. Запрашиваем у 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:
В общем то теперь у нас есть массив/табличка всех транков на нашем ASTERISK.
осталось только выяснить какой из них доступен и позвонить через него. Сделать это можно через SIPpeerstatus:
Ну и не забываем закрыть за собой дверь))
Это в общем-то самый простой пример того, как можно использовать AMI непосредственно в самом диалплане. Так же ничего не мешает узнавать и занятость каналов. Необходимо будет только распарcить вывод команды sip show inuse. Прикручивается сюда и mysql коннекторы и redis, и все что угодно при необходимости. Без костылей.
P.S. Для ленивых есть целая библиотека ami-lua. Только вот с документацией там… никак.
Стандартным механизмом решения такой проблемы считается следующий пример:
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. Только вот с документацией там… никак.