النجمة - fi ، إنها أخلاق سيئة
مرحبا أعزائي القراء من هذا المورد الرائع. حسب التقاليد ، أنا قارئ هابر منذ فترة طويلة ، لكنني قررت الآن فقط إنشاء مشاركة. ما الذي دفعك في الواقع إلى الكتابة؟ بصراحة ، أنا لا أعرف نفسي. إما المقالات المرسومة حول أداء FreeSWITCH / Yate / 3CX / إلخ مقارنةً بالنجمة ، أو المشكلات الحقيقية والحقيقية لمعمارية الأخير ، أو ربما الرغبة في القيام بشيء فريد.
والمثير للدهشة ، في الحالة الأولى ، كقاعدة عامة ، أنهم يقارنون بين الناعمة والدافئة ، إذا جاز التعبير ، FreeSWITCH / Yate / etc و FreePBX. نعم ، FreePBX. هذا ليس خطأ مطبعي. ومن المثير للاهتمام أنه في جميع المقارنات غالبًا ما توجد علامة نجمية واحدة في التكوين الافتراضي. حسنًا ، كما تعلم ، يتم تحميل هذا التكوين بجميع الوحدات النمطية المتاحة ، ومنحنى مخطط الطلب (مساهمات نوع FreePBX) ومجموعة من التحيزات الأخرى. أما بالنسبة لقرحات النجمة العامة - نعم ، موضوعيا ، نقلهم وعربة صغيرة.
ماذا تفعل بكل هذا؟ كسر القوالب النمطية وإصلاح صدمة الولادة. هذا ما سنفعله.
نحن نعبر القنفذ بثعبان
يشعر العديد من المبتدئين بعدم الارتياح عند النظر إلى بناء الجملة لوصف مخطط الطلب في النجمة ، ويبرر البعض بجدية اختيار خادم هاتف آخر على وجه التحديد من خلال الحاجة إلى كتابة مخطط الطلب بالشكل الافتراضي. مثل ، تجريف XML متعدد الأسطر هو قمة الراحة. نعم ، من الممكن استخدام LUA / AEL ، وهو أمر جيد. لكنني شخصياً سأصنف هذا على أنه عيب ، وخاصة فيما يتعلق بـ pbx_lua
كما ذكرنا سابقًا ، فإن القدرة على وصف مخطط الطلب بلغة برمجة كاملة أمر جيد. تكمن المشكلة في أن عمر البرنامج النصي وبيئته يساوي عمر القناة. لكل قناة جديدة ، يتم تشغيل مثيل البرنامج النصي الخاص بها ، وبالتالي ، الوداع ، المتغيرات المشتركة بين القنوات ، تنزيل واحد لوحدات الطرف الثالث ، اتصال واحد مشترك بقاعدة البيانات ، إلخ ، إلخ. بالمعنى الدقيق للكلمة ، هذا ليس ضروريًا من لغة وصف البرنامج النصي المضمنة ، لكنني أريد ذلك حقًا. وإذا أردت ، فعليك أن تفعل.
لذلك ، سوف نأخذ مبادئ pbx_lua من النجمة الكلاسيكية ، وسوف نأخذ نموذج التوجيه من Yate ، ولن نأخذ أي شيء من FreeSWITCH ، لأنه ليس هناك حاجة إلى "overhead". حسنًا ، لقد قررنا ما نحتاجه للولادة. ماذا سنستخدم للتجارب الجينية:
- Asterisk, . ARI , , 12- . , - 1.8/1.6, 1.4, .
- Lua — , . , , .
- Lunapark — github', voip-.
Lunapark . , AMI- FastAGI, . , ARI AGI AMI .
: ? Asterisk REST Interface, ! . , ARI : , , , "" , WebSockets , , , XML/JSON — . , , , . — . — - , .
? FastAGI-, , pbx_lua . Asterisk’ , FastAGI- AMI-. , FastAGI-, , , NewChannel. ARI, , stasis' ARI .
Lunapark , . "shared data". , . — , , - .
?
— ? , , . , . .
:
[test]
exten => _XXX/102,1,Hangup()
exten => _XXX,1,Dial(SIP/${EXTEN})
, 102. , , extended CallerID . , , CallerIDName , , regexp, . , , , :
[test]
exten => _XXX/102,1,Hangup()
; CallerIDName
exten => _XXX,1,ExecIf($[ "${CALLERID(name)}" == "Vasya" ]?Hangup())
;
exten => _XXX,n,ExecIf($[ "${CHANNEL(state)}" != "Ring" ]?Hangup())
;
exten => _XXX,n,ExecIf($[ "${CUT(CUT(CHANNEL,-,1),/,2)}" == "333" ]?Hangup())
exten => _XXX,n,Dial(SIP/${EXTEN})
, , Hangup', extensions.conf Goto, GoSub, Macro , , Local.
— .
:
${Exten}:match('%d%d%d')
and
(
${CallerIDNum}:match('201') or
${CallerIDName}:match('Vasya') or
${State}:lower() ~= 'ring' or
${Channel}:match('^[^/]+/([^%-]+)') == '333'
) => Hangup();
${Exten}:match('%d%d%d') => Dial {callee = ('SIP/%s'):format(${Exten})};
, , . , regexp' , , , .
, .
Lunapark pbx_lua. . ${...}
, ('...')
. .
, :
-- Exten = 123
-- Sate = Ring
-- CallerIDNum = 100
-- CallerIDName = Test
-- Channel = SIP/100-00000012c
if ('123'):match('%d%d%d') and
(
('100'):match('201') or
('Test'):match('Vasya') or
('Ring'):lower() ~= 'ring' or
('SIP/100-00000012c'):match('^[^/]+/([^%-]+)') == '333'
) then
Hangup()
end
if ('123'):match('%d%d%d') then
Dial {callee = ('SIP/%s'):format(('123'))}
end
fmt syntax :
local fmt = function(str, tab)
return (str:gsub('(%${[^}{]+})', function(w)
local mark = w:sub(3, -2)
return (mark:gsub('(.+)',function(v)
local out = tab[v] or v
return ("('%s')"):format(out)
end))
end))
end
local syntax = function(str)
return (str:gsub('([^;]+)=>([^;]+)',function(p,r)
return ([[
if %s then
%s
end
]]):format(p,r)
end))
end
, . — , . routes.
local routes = function(...)
local conf, content = ...
local f, err = io.open(conf, "r")
if io.type(f) ~= 'file' then
log.warn(err) -- LOG Lunapark'
return ""
else
content = f:read('*all')
end
f:close() return content
end
: Lunapark . — Lunapark handler'. , FastAGI- AMI .
, AMI — , AMI-, AMI . , extensions.conf.
[default]
exten => _[hit],1,NoOp()
exten => _.,n,Wait(5)
exten => _.,1,AGI(agi://127.0.0.1/${EXTEN}${IF($[ "X${PRMS}" != "X" ]?"?${PRMS}")})
Wait(5) FastAGI-, , Redirect default ${EXTEN}.
, Lunapark', FastAGI-.
-- rules
local rules = routes('routes.conf')
-- ,
-- HUP/QUIT
ami.removeEvents('*')
--
ami.addEvents {
['newchannel'] = function(e)
-- , users
if (e['Context'] and e['Context']:match('users')) and e['Exten'] then
-- , , FastAGI
local step
-- FatsAGI
local count = 0
--
local code, err = loadstring(syntax(fmt(rules,e)))
-- ,
if type(code) == 'function' then
-- FastAGI
setfenv(code,setmetatable({indexes = {}},{__index = function(t,k)
--
return coroutine.wrap(
function(...)
local prms = {} -- FastAGI
local owner = t --
local event = e -- event
local thread = coroutine.running() -- ID
-- URI
for p,v in pairs({...}) do
if type(v) == 'table' then
for key, val in pairs(v) do
table.insert(prms,("%s=%s"):format(key,val))
end
else
table.insert(prms,("%s=%s"):format(p,v))
end
end
-- FastAGI
if step then
--
local last = ("%s"):format(step)
-- UserEvent .
-- indexes( )
--
table.insert(owner['indexes'],ami.addEvent('UserEvent',function(evt)
-- AGIStatus
-- ,
if (evt['Channel'] and evt['Channel'] == event['Channel'])
and
(evt['UserEvent'] and evt['UserEvent']:match('AGIStatus'))
and
(evt['Script'] and evt['Script'] == last)
then
--
--
--
if owner['indexes'][count] == thread then
if coroutine.status(thread) ~= 'dead' then
coroutine.resume(thread)
end
end
end
end,thread))
-- FastAGI
step = k
--
coroutine.yield()
else -- FastAGI
local index -- Hangup
-- FastAGI
step = k
-- Hangup
--
index = ami.addEvent('Hangup',function(evt)
if evt['Channel'] and evt['Channel'] == event['Channel'] then
-- Hangup
ami.removeEvent('Hangup',index)
--
for _,v in pairs(owner['indexes']) do
ami.removeEvent('UserEvent',v)
end
--
owner = nil
end
end,thread)
end
-- AMI
ami.setvar{
Value = table.concat(prms,'&'),
Channel = event['Channel'],
Variable = 'PRMS'
}
-- AGI- default
ami.redirect{
Exten = k,
Priority = 1,
Channel = event['Channel'],
Context = 'default'
}
--
count = count + 1
end)
end}))()
else
-- -
log.warn(err)
end
end
end
}
, , , . , . , . , , , ..
, . — , redirect . , , FastAGI-. Lunapark UserEvent FastAGI- — . default , , PRMS.
, redirect' handler, AGI . Hangup() Dial(). .
function Hangup(...)
local app, channel = ... -- pbx_lua
app.verbose(('The Channel %s does not match by routing rules'):format(channel.get('CHANNEL')))
app.hangup()
end
function Dial(...)
local app, channel = ...
local leg = app.agi.params['callee'] or ''
app.verbose(('Trying to make a call from %s to %s'):format(
channel.get('CALLERID(num)'),
leg:match('^[^/]+/([^%-]+)'))
)
app.dial(leg)
end
, —
, . ?
- , ;
- VoIP-. Queue, , asterisk';
- , VoIP-, asterisk' Mediahub, VoIP- ;
- القدرة على استخدام لغة برمجة بسيطة إلى حد ما وقابلة للتوسيع ومرنة للغاية لإنشاء تطبيقات VoIP ؛
- وسعت إمكانيات التكامل مع الأنظمة الخارجية من تطبيقات VoIP.
كأي شخص ، لكني ما زلت أحب كل شيء.
local fmt = function(str, tab)
return (str:gsub('(%${[^}{]+})', function(w)
local mark = w:sub(3, -2)
return (mark:gsub('(.+)',function(v)
local out = tab[v] or v
return ("('%s')"):format(out)
end))
end))
end
local syntax = function(str)
return (str:gsub('([^;]+)=>([^;]+)',function(p,r)
return ([[
if %s then
%s
end
]]):format(p,r)
end))
end
local routes = function(...)
local conf, content = ...
local f, err = io.open(conf, "r")
if io.type(f) ~= 'file' then
log.warn(err) -- LOG Lunapark'
return ""
else
content = f:read('*all')
end
f:close() return content
end
-- rules
local rules = routes('routes.conf')
-- ,
-- HUP/QUIT
ami.removeEvents('*')
--
ami.addEvents {
['newchannel'] = function(e)
-- , users
if (e['Context'] and e['Context']:match('users')) and e['Exten'] then
local step -- , , FastAGI
local count = 0 -- FatsAGI
--
local code, err = loadstring(syntax(fmt(rules,e)))
-- ,
if type(code) == 'function' then
-- FastAGI
setfenv(code,setmetatable({indexes = {}},{__index = function(t,k)
--
return coroutine.wrap(
function(...)
local prms = {} -- FastAGI
local owner = t --
local event = e -- event
local thread = coroutine.running() -- ID
-- URI
for p,v in pairs({...}) do
if type(v) == 'table' then
for key, val in pairs(v) do
table.insert(prms,("%s=%s"):format(key,val))
end
else
table.insert(prms,("%s=%s"):format(p,v))
end
end
-- FastAGI
if step then
--
local last = ("%s"):format(step)
-- UserEvent .
-- indexes( )
--
table.insert(owner['indexes'],ami.addEvent('UserEvent',function(evt)
-- AGIStatus
-- ,
if (evt['Channel'] and evt['Channel'] == event['Channel'])
and
(evt['UserEvent'] and evt['UserEvent']:match('AGIStatus'))
and
(evt['Script'] and evt['Script'] == last)
then
--
--
--
if owner['indexes'][count] == thread then
if coroutine.status(thread) ~= 'dead' then
coroutine.resume(thread)
end
end
end
end,thread))
-- FastAGI
step = k
--
coroutine.yield()
else -- FastAGI
local index -- Hangup
-- FastAGI
step = k
-- Hangup
--
index = ami.addEvent('Hangup',function(evt)
if evt['Channel'] and evt['Channel'] == event['Channel'] then
-- Hangup
ami.removeEvent('Hangup',index)
--
for _,v in pairs(owner['indexes']) do
ami.removeEvent('UserEvent',v)
end
--
owner = nil
end
end,thread)
end
-- AMI
ami.setvar{
Value = table.concat(prms,'&'),
Channel = event['Channel'],
Variable = 'PRMS'
}
-- AGI- default
ami.redirect{
Exten = k,
Priority = 1,
Channel = event['Channel'],
Context = 'default'
}
--
count = count + 1
end)
end}))()
else
-- -
log.warn(err)
end
end
end
}
function Hangup(...)
local app, channel = ... -- pbx_lua
app.verbose(('The Channel %s does not match by routing rules'):format(channel.get('CHANNEL')))
app.hangup()
end
function Dial(...)
local app, channel = ...
local leg = app.agi.params['callee'] or ''
app.verbose(('Trying to make a call from %s to %s'):format(
channel.get('CALLERID(num)'),
leg:match('^[^/]+/([^%-]+)'))
)
app.dial(leg)
end