النجمة هي سيارة فورمولا 1 وليست حافلة عادية

النجمة - 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



All Articles