Freeswitch: Конфигурация при помощи Lua

serving configuration with Lua

Created by Belaid Areski, last modified by John Boteler Переведено и дополнено примерами - Олег Звездочкин

Описание

mod_lua позволяет заменить статические XML файлы на Lua скрипты. Это предоставляет дополнительные возможности для расширения функциональности диалплана, динамического предоставления данных, создания интерфейсов или приложений. Например, FusionPBX построен на этом принципе.

Lua скрипты вызываются из XML инструкций, (как URL в модуле mod_xml_curl).

Когда FreeeSWITCH обнаруживает вызов Lua в XML реестре, он выполняет его. Скрипт может обращаться к базе данных или выполнять другие действия на ваше усмотрение и возвращать данные в виде XML строки или переменных диалплана.

Конфигурация mod_lua находится в файле ../autoload_configs/lua.conf.xml.

Если вы редактируете lua.conf.xml, команды reloadxml недостаточно для применения изменений, требуется перезагрузить FreeSWITCH, чтобы распознать xml-handler-script.

Если Lua скрипт вызывается из диалплана или подключается в sip_profile, достаточно выполнить reloadxml или sofia profile <name> restart rescan reloadxml соответственно.

Далее приведен базовый пример подключения Lua скрипта для обслуживания диалплана в файле lua.conf.xml.

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
    <param name="xml-handler-script" value="dp.lua"/>
    <param name="xml-handler-bindings" value="dialplan"/>
  </settings>
</configuration>
В этом случае FS будет считать, что весь диалплан находится в dp.lua. Для перехода в XML можно воспользоваться командой transfer.

Рассмотрим несколько примеров для параметра xml-handler-bindings в его возможных значениях: dialplan , directory и configuration, имена которых, говорят сами за себя.

Скрипт принимает объект - XML_REQUEST который содержит

  • section - секция
  • tag_name - имя тега
  • key_name - имя ключа
  • key_value - значение ключа

В FS возвращаются данные помещенные в объект - XML_STRING.

Также данные могут возвращаться в таблице ACTIONS (рассматривается в описании секции dialplan) или выполняются прямо в скрипте методом session:execute (не рассматривается в этом материале). Используемые методы выбираются в зависимости от точки подключения скрипта.

Доступ к объекту XML_REQUEST в Lua скрипте получается при помощи переменных XML_REQUEST["section"], XML_REQUEST["tag_name"], XML_REQUEST["key_name"], и XML_REQUEST["key_value"]

Пример:

freeswitch.consoleLog("notice", "SECTION " .. XML_REQUEST["section"] .. "\n")

В консоль будет выведено значение section, например dialplan.

Также для configuration, directory и dialplan запросов, набор событий объекта передается в скрипт при помощи переменной params.

Под «событиями объекта» понимаются все переменные и данные получаемые при инициации вызова или вызове модуля.

Основным поставщиком данных для params, являются sip заголовки и модули FS.

Доступ к определенным данным params в скрипте можно при помощи переменной params:getHeader("name") или сериализировать весь массив - params:serialize("xml").

В описании секции directory приведены подробные списки params для некоторых важных событий, например таких как INVITE или REGISTER и др.

Конфигурация модулей при помощи Lua

Если mod_lua загружен при старте, тогда другие модули, загружаемые после, могут получать данные из связанных скриптов по запросу, каждый раз когда это требуется.

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
    <param name="xml-handler-script" value="configuration.lua"/>
    <param name="xml-handler-bindings" value="configuration"/>
  </settings>
</configuration>

Теперь когда модуль запущен, XML_REQUEST объект в Lua скрипте получит следующие данные:

key_value = 'iax.conf'|'event_socket.conf'|'sofia.conf'|...
key_name = 'name'
section = 'configuration'
tag_name = 'configuration'
'params' event object
acl.conf no N/A
event_socket.conf no N/A
post_load_switch.conf no N/A
sofia.conf yes Event-Name: REQUEST_PARAMS
Core-UUID: 0f8afb73-2183-a1e2-2316-71053c746130
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.12
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A38
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A38%20GMT
Event-Date-Timestamp: 1281117878629975
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 2637
switch.conf no N/A
syslog.conf no N/A

Example XML_STRING distributor.conf rom DB

Пример: Генерим XML конфиг для mod_distributor на основании данных из БД PostgreSQL.

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

fs => select *  from distributor;
 id | list  | node | weight 
----+-------+------+--------
 10 | test1 | tr2  | 1
 11 | test2 | tr3  | 1
  8 | test1 | tr5  | 1
  9 | test1 | tr5  | 1
(4 rows)
freeswitch.consoleLog("notice", "[xml_handler] Key Value: " .. XML_REQUEST["key_value"] .. "\n");
 
local dbh = freeswitch.Dbh("pgsql://host=127.0.0.1 dbname=DATABASE user=USER password='PASS' options='-c client_min_messages=NOTICE' application_name='freeswitch'")
 
if dbh:connected() == false then
  freeswitch.consoleLog("notice", "distributor.conf.lua cannot connect to database" .. dsn .. "\n")
  return
end
 
local list_query = "select distinct(list) from distributor"
 
    --start the xml array
      local xml = {}
      table.insert(xml, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
      table.insert(xml, [[<document type="freeswitch/xml">]]);
      table.insert(xml, [[  <section name="configuration">]]);
      table.insert(xml, [[    <configuration name="distributor.conf" description="Distributor Configuration">]]);
      table.insert(xml, [[      <lists>]]);
 
assert (dbh:query(list_query, function(u)
              table.insert(xml, [[        <list name="]]..u.list..[[">]]);
      assert (dbh:query("select node, weight from distributor where list='".. u.list.."'", function(n)
 
              table.insert(xml, [[          <node name="]]..n.node..[[" weight="]] ..n.weight.. [["/>]]);
        end))      
 
              table.insert(xml, [[        </list>]]);
end))
 
 
      table.insert(xml, [[      </lists>]]);
      table.insert(xml, [[    </configuration>]]);
      table.insert(xml, [[  </section>]]);
      table.insert(xml, [[</document>]]);
dbh:release();
XML_STRING = table.concat(xml, "\n")
 
    freeswitch.consoleLog("notice", "Debug from distributor.conf.lua, generated XML:\n" .. XML_STRING .. "\n")

fs_cli> reload mod_distributor

Example XML_STRING sofia.conf from DB

Sip профили и транки (gateways) в базе данных PostgreSQL.

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

                               TABLE "public.gateways"
 COLUMN  |       TYPE        |                       Modifiers                       
---------+-------------------+-------------------------------------------------------
 id      | INTEGER           | NOT NULL DEFAULT NEXTVAL('gateways_id_seq'::regclass)
 profile | CHARACTER VARYING | 
 gw_name | CHARACTER VARYING | 
 TYPE    | CHARACTER VARYING | 
 name    | CHARACTER VARYING | 
 VALUE   | CHARACTER VARYING | 
 enable  | BOOLEAN           | NOT NULL DEFAULT FALSE
Indexes:
    "gateways_pkey" PRIMARY KEY, btree (id)
fsdir=> select * from gateways;
 id | profile  | gw_name | type  |       name        |       value        | enable 
----+----------+---------+-------+-------------------+--------------------+--------
  1 | internal | fs0     | param | proxy             | 192.168.0.231:5070 | t
  2 | internal | fs0     | param | register          | false              | t
  3 | internal | fs0     | param | transport         | udp                | t
  4 | internal | fs0     | param | context           | default            | t
  5 | internal | fs0     | param | caller-id-in-from | true               | t
(5 rows)
fsdir=> \d sip_profiles
                               TABLE "public.sip_profiles"
 COLUMN  |       TYPE        |                         Modifiers                         
---------+-------------------+-----------------------------------------------------------
 id      | INTEGER           | NOT NULL DEFAULT NEXTVAL('sip_profiles_id_seq'::regclass)
 profile | CHARACTER VARYING | 
 TYPE    | CHARACTER VARYING | 
 name    | CHARACTER VARYING | 
 VALUE   | CHARACTER VARYING | 
 enable  | BOOLEAN           | NOT NULL DEFAULT FALSE
Indexes:
    "sip_profiles_pkey" PRIMARY KEY, btree (id)
fsdir=> select * from sip_profiles
fsdir-> ;
 id | profile  |   type   |        name        |        value        | enable 
----+----------+----------+--------------------+---------------------+--------
  6 | internal | settings | apply-inbound-acl  | 192.168.0.0/16      | t
  7 | internal | settings | apply-register-acl | 192.168.0.0/16      | t
  8 | internal | settings | auth-calls         | false               | t
  9 | internal | settings | debug              | 0                   | t
 10 | internal | settings | log-level          | 0                   | t
 11 | internal | settings | sip-trace          | no                  | t
 12 | internal | settings | dialplan           | XML                 | t
 13 | internal | settings | context            | default             | t
 14 | internal | settings | codec-prefs        | PCMA,PCMU,G722,OPUS | t
 15 | internal | settings | rtp-ip             | 192.168.210.231     | t
 16 | internal | settings | sip-ip             | 192.168.210.231     | t
 19 | internal | settings | sip-port           | 5070                | t
 20 | internal | settings | rtp-autofix-timing | true                | t
 21 | office   | settings | apply-inbound-acl  | 192.168.0.0/16      | t
 22 | office   | settings | apply-register-acl | 192.168.0.0/16      | t
 23 | office   | settings | auth-calls         | false               | t
 24 | office   | settings | debug              | 0                   | t
 25 | office   | settings | log-level          | 0                   | t
 26 | office   | settings | sip-trace          | no                  | t
 27 | office   | settings | dialplan           | XML                 | t
 28 | office   | settings | context            | default             | t
 29 | office   | settings | codec-prefs        | PCMA,PCMU,G722,OPUS | t
 30 | office   | settings | rtp-ip             | 192.168.210.231     | t
 31 | office   | settings | sip-ip             | 192.168.210.231     | t
 34 | office   | settings | sip-port           | 5077                | t
 35 | office   | settings | rtp-autofix-timing | true                | t
 17 | internal | settings | ext-rtp-ip         | 192.168.210.231     | t
 32 | office   | settings | ext-rtp-ip         | 192.168.210.231     | t
 18 | internal | settings | ext-sip-ip         | 192.168.210.231     | t
 33 | office   | settings | ext-sip-ip         | 192.168.210.231     | t
(30 rows)
local dbh = freeswitch.Dbh("pgsql://host=127.0.0.1 dbname=DB user=USER password='PASS' options='-c client_min_messages=NOTICE' application_name='freeswitch'")
 
    local xml = {}
    table.insert(xml, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
    table.insert(xml, [[<document type="freeswitch/xml">]]);
    table.insert(xml, [[          	<section name="configuration">]]);
    table.insert(xml, [[          		<configuration name="sofia.conf" description="sofia Endpoint">]]);
    table.insert(xml, [[          			<global_settings>]]);
    table.insert(xml, [[          				<param name="log-level" value="0"/>]]);
    table.insert(xml, [[          				<param name="auto-restart" value="false"/>]]);
    table.insert(xml, [[          				<param name="debug-presence" value="0"/>]]);
    table.insert(xml, [[          				<param name="capture-server" value="udp:homer.domain.com:5060"/>]]);
    table.insert(xml, [[          			</global_settings>]]);
    table.insert(xml, [[          			<profiles>]]);
 
      assert (dbh:query("select distinct(profile) from sip_profiles", function(u)
                table.insert(xml, [[                    	<profile name="]]..u.profile..[[">]]);
                table.insert(xml, [[                    	<gateways>]]);
                     assert (dbh:query("select distinct(gw_name) from gateways where profile = '" ..u.profile.."'", function(g)
                               table.insert(xml, [[                    	<gateway name="]]..g.gw_name..[[">]]);
                                  assert (dbh:query("select * from gateways where profile = '"..u.profile.."' and gw_name='".. g.gw_name.."' and type = 'param' and enable = TRUE", function(m)
 
                                         table.insert(xml, [[                    		<param name="]]..m.name..[[" value="]] ..m.value.. [["/>]]);
                                   end))          
                            table.insert(xml, [[                    	</gateway>]]);
                 end))
                table.insert(xml, [[                    	</gateways>]]);             
                table.insert(xml, [[                    	   <settings>]]);
                     assert (dbh:query("select * from sip_profiles where profile = '".. u.profile.."' and type = 'settings' and enable = TRUE", function(n)
                               table.insert(xml, [[                    		<param name="]]..n.name..[[" value="]] ..n.value.. [["/>]]);
                 end))          
                table.insert(xml, [[                    	   </settings>]]);
                table.insert(xml, [[                    	</profile>]]);
     end))
    table.insert(xml, [[                 </profiles>]]);
    table.insert(xml, [[             </configuration>]]);
    table.insert(xml, [[         </section>]]);
    table.insert(xml, [[ </document>]]);
 
dbh:release();
XML_STRING = table.concat(xml, "\n")

Первоначальный запуск

Во время первоначального запуска/чтения, из directory считываются данные для gateways и связанных доменов.

Когда модуль запущен, XML_REQUEST объект в Lua скрипте должен иметь значения:

  • key_value = ' '
  • key_name = ' '
  • section = 'directory'
  • tag_name = ' '

А также params принадлежащие данному объекту.

directory читаются один раз для каждого профиля в конфигурации sofia.

Переменные без значений '' являются пустыми строками, не nil.

Params event headers

External profile

External profile REQUEST_PARAMS

External profile REQUEST_PARAMS

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A40
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A40%20GMT
Event-Date-Timestamp: 1281117880813532
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 3481
purpose: gateways
profile: external

Полезная нагрузка:

purpose: gateways
profile: external
...
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
Internal profile

Internal profile REQUEST_PARAMS

Internal profile REQUEST_PARAMS

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A41
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A41%20GMT
Event-Date-Timestamp: 1281117881174514
Event-Calling-File: sofia.c
Event-Calling-Function: config_sofia
Event-Calling-Line-Number: 3481
purpose: gateways
profile: internal

Полезные данные:

purpose: gateways
profile: internal
...
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
Network lists

Также во время первоначальной загрузки список сетей (network lists) может быть прочитан из directory. В этой точке XML_REQUEST имеет вид:

  • key_value = <name-of-domain> (e.g. 192.168.1.11)
  • key_name = 'name'
  • section = 'directory'
  • tag_name = 'domain'

Network list GENERAL

Network list GENERAL

Event-Name: GENERAL
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A46
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A46%20GMT
Event-Date-Timestamp: 1281117886025842
Event-Calling-File: switch_core.c
Event-Calling-Function: switch_load_network_lists
Event-Calling-Line-Number: 1040
domain: 192.168.1.11
purpose: network-list

Полезные данные:

domain: 192.168.1.11
purpose: network-list
...
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11

Когда регистрируется или вызывается user, FS ищет пользователя в конкретном домене.

XML_REQUEST имеет вид:

  • key_value = '<name-of-domain>'
  • key_name = 'name'
  • section = 'directory'
  • tag_name = 'domain'

И params которые имеются в событиях объекта:

При регистрации (REGISTER)

При регистрации (REGISTER)

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2014%3A04%3A43
Event-Date-GMT: Fri,%2006%20Aug%202010%2018%3A04%3A43%20GMT
Event-Date-Timestamp: 1281117883173795
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_parse_auth
Event-Calling-Line-Number: 1797
action: sip_auth
sip_profile: internal
sip_user_agent: IP-Phone-V3.2.49T5.13%20-%20G729
sip_auth_username: 1000
sip_auth_realm: 192.168.1.11
sip_auth_nonce: <auth_nonce_uuid>
sip_auth_uri: sip%3A192.168.1.11
sip_contact_user: 1000
sip_contact_host: 192.168.88.202
sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1000
sip_from_host: 192.168.1.11
sip_from_port: 5060
sip_request_host: 192.168.1.11
sip_auth_qop: auth
sip_auth_cnonce: 829326
sip_auth_nc: 00000001
sip_auth_response: <auth_response - md5sum?>
sip_auth_method: REGISTER
key: id
user: 1000
domain: 192.168.1.11
ip: 192.168.88.202

Полезные данные:

key: id
user: 1000
domain: 192.168.1.11
...
action: sip_auth
sip_profile: internal
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
ip: 192.168.88.202

Когда запрашивается соединение (INVITE)

Когда запрашивается соединение (INVITE)

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2016%3A28%3A08
Event-Date-GMT: Fri,%2006%20Aug%202010%2020%3A28%3A08%20GMT
Event-Date-Timestamp: 1281126488274011
Event-Calling-File: sofia_reg.c
Event-Calling-Function: sofia_reg_parse_auth
Event-Calling-Line-Number: 1797
action: sip_auth
sip_profile: internal
sip_user_agent: IP-Phone-V3.2.49T5.13%20-%20G729
sip_auth_username: 1001
sip_auth_realm: 192.168.1.11
sip_auth_nonce: 1fe3d1fa-a199-11df-b392-b105e374638e
sip_auth_uri: sip%3A1000%40192.168.1.11
sip_contact_user: 1001
sip_contact_host: 192.168.88.99
sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1001
sip_from_host: 192.168.1.11
sip_from_port: 5060
sip_request_user: 1000
sip_request_host: 192.168.1.11
sip_auth_qop: auth
sip_auth_cnonce: 10560d0
sip_auth_nc: 00000001
sip_auth_response: b99d1213022480a2b6c4e14432661821
sip_auth_method: INVITE
key: id
user: 1001
domain: 192.168.1.11
ip: 192.168.88.99

1001 вызывает 1000.

Полезные данные:

sip_to_user: 1000
sip_to_host: 192.168.1.11
sip_to_port: 5060
sip_from_user: 1001
sip_from_host: 192.168.1.11
sip_from_port: 5060
...
key: id
user: 1001
domain: 192.168.1.11
...
ip: 192.168.88.99
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
action: sip_auth
sip_profile: internal

Когда вызывается (другим extension)

Когда вызывается (другим extension)

Event-Name: REQUEST_PARAMS
Core-UUID: <uuid>
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11
FreeSWITCH-IPv6: %3A%3A1
Event-Date-Local: 2010-08-06%2016%3A28%3A09
Event-Date-GMT: Fri,%2006%20Aug%202010%2020%3A28%3A09%20GMT
Event-Date-Timestamp: 1281126489462522
Event-Calling-File: mod_dptools.c
Event-Calling-Function: user_outgoing_channel
Event-Calling-Line-Number: 2662
as_channel: true
action: user_call
key: id
user: 1000
domain: 192.168.1.11

Полезные данные:

key: id
user: 1000
domain: 192.168.1.11
...
FreeSWITCH-Hostname: hostname
FreeSWITCH-IPv4: 192.168.1.11

as_channel: true
Event-Calling-Function: user_outgoing_channel

Example XML_STRING Directory from DB

Храним аккаунты пользователей в PostgreSQL:

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

fsdir=> \d directory;
                    TABLE "public.directory"
           COLUMN           |          TYPE          | Modifiers 
----------------------------+------------------------+-----------
 DOMAIN                     | CHARACTER VARYING(32)  | 
 id                         | CHARACTER VARYING(32)  | 
 number-alias               | CHARACTER VARYING(32)  | 
 mailbox                    | CHARACTER VARYING(64)  | 
 cidr                       | CHARACTER VARYING(64)  | 
 password                   | CHARACTER VARYING(64)  | 
 toll_allow                 | CHARACTER VARYING(32)  | 
 user_context               | CHARACTER VARYING(32)  | 
 default_gateway            | CHARACTER VARYING(32)  | 
 effective_caller_id_name   | CHARACTER VARYING(512) | 
 effective_caller_id_number | CHARACTER VARYING(64)  | 
 outbound_caller_id_name    | CHARACTER VARYING(512) | 
 outbound_caller_id_number  | CHARACTER VARYING(64)  | 
 callgroup                  | CHARACTER VARYING(64)  | 
 codec                      | CHARACTER VARYING(64)  | 
 tls                        | CHARACTER VARYING(32)  | 
 uservar1                   | CHARACTER VARYING(64)  | 
 uservar2                   | CHARACTER VARYING(64)  | 
 uservar3                   | CHARACTER VARYING(64)  | 
 uservar4                   | CHARACTER VARYING(64)  | 
 uservar5                   | CHARACTER VARYING(64)  | 
 uservar6                   | CHARACTER VARYING(64)  | 
 uservar7                   | CHARACTER VARYING(64)  |
if params  ~= nil then
  local req_domain = params:getHeader("domain")
  local req_key    = params:getHeader("key")
  local req_user   = params:getHeader("user")
end  
 
local dbh = freeswitch.Dbh("pgsql://host=127.0.0.1 dbname=DB user=USER password='PASS' options='-c client_min_messages=NOTICE' application_name='freeswitch'")
 
  function decodeURI(s)
    if(s) then
      s = string.gsub(s, '%%(%x%x)', 
        function (hex) return string.char(tonumber(hex,16)) end )
    end
    return s
  end
 
function listUsers(q, dbh)
 
 local xml = {}
-- assert (dbh:query("select domain, id, toll_allow, effective_caller_id_name  from directory ", function(l)
 
    if dbh:connected() == false then
       freeswitch.consoleLog("notice", "directory_xml.lua cannot connect to database" .. dsn .. "\n")
      return
      end
 
        table.insert(xml, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
        table.insert(xml, [[<document type="freeswitch/xml">]]);
        table.insert(xml,   [[<section name="directory">]]);
 
        assert (dbh:query(q, function(u)
 
               table.insert(xml,     [[<domain name="]] .. u.domain .. [[">]]);
               table.insert(xml,       [[<users>]]);
               table.insert(xml,        [[<user id="]] .. u.id .. [[" mailbox="]] .. u.mailbox .. [[" cidr="]] .. u.cidr .. [[" number-alias="]] .. u["number-alias"] .. [[">]]);
               table.insert(xml,         [[<params>]]);
               table.insert(xml,             [[<param name="a1-hash" value="]] .. u.password .. [["/>]]);
               table.insert(xml,             [[<param name="dial-string" value="{rtp_secure_media=${regex(${sofia_contact(${dialed_user}@${dialed_domain})}|transport=tls)},presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}" />]]);
               table.insert(xml,             [[<param name="jsonrpc-allowed-methods" value="verto"/>]]);
               table.insert(xml,             [[<param name="jsonrpc-allowed-event-channels" value="demo,conference"/>]]);
               table.insert(xml,         [[</params>]]);
               table.insert(xml,         [[<variables>]]);
               table.insert(xml,             [[<variable name="toll_allow" value="]] .. u.toll_allow .. [["/>]]);
               table.insert(xml,             [[<variable name="user_context" value="]] .. u.user_context .. [["/>]]);
               table.insert(xml,             [[<variable name="default_gateway" value="]] .. u.default_gateway .. [["/>]]);
               table.insert(xml,             [[<variable name="effective_caller_id_name" value="]] .. decodeURI(u.effective_caller_id_name) .. [["/>]]);
               table.insert(xml,             [[<variable name="effective_caller_id_number" value="]] .. u.effective_caller_id_number .. [["/>]]);
               table.insert(xml,             [[<variable name="outbound_caller_id_name" value="]] .. u.outbound_caller_id_name .. [["/>]]);
               table.insert(xml,             [[<variable name="outbound_caller_id_number" value="]] .. decodeURI(u.outbound_caller_id_number) .. [["/>]]);
               table.insert(xml,             [[<variable name="callgroup" value="]] .. u.callgroup .. [["/>]]);
               table.insert(xml,             [[<variable name="codec" value="]] .. u.codec .. [["/>]]);
               table.insert(xml,             [[<variable name="tls" value="]] .. u.tls .. [["/>]]);
               table.insert(xml,             [[<variable name="uservar1" value="]] .. u.uservar1 .. [["/>]]);
               table.insert(xml,             [[<variable name="uservar2" value="]] .. u.uservar2 .. [["/>]]);
               table.insert(xml,             [[<variable name="uservar3" value="]] .. u.uservar3 .. [["/>]]);
               table.insert(xml,             [[<variable name="uservar4" value="]] .. u.uservar4 .. [["/>]]);
               table.insert(xml,             [[<variable name="uservar5" value="]] .. u.uservar5 .. [["/>]]);
               table.insert(xml,         [[</variables>]]);
               table.insert(xml,        [[</user>]]);
               table.insert(xml,       [[</users>]]);
               table.insert(xml,     [[</domain>]]);
        end))
        table.insert(xml,   [[</section>]]);
        table.insert(xml, [[</document>]]);
 
dbh:release();
 
   return xml
end
 
if req_domain == nil then
 
local query = "select * from directory"
 
local users = listUsers(query, dbh)
 
XML_STRING = table.concat(users, "\n")
 
 -- freeswitch.consoleLog("info","\n".. XML_STRING .."\n")
 
else
 
local query = string.format("select * from directory where domain = '%s' and \"%s\"='%s'", req_domain, req_key, req_user)  
 
local user = listUsers(query, dbh)
 
XML_STRING = table.concat(user, "\n")
 
end

XML

Приведенный ниже пример диалплана dp.lua генерирует XML_STRING и возвращает в FS, когда скрипт обработан.

Такая форма скрипта может быть использована, когда Lua диалплан подключен, через lua.conf.xml.
-- params is the event passed into us we can use params:getHeader to grab things we want.
io.write("TEST\n" .. params:serialize("xml") .. "\n");  

mydialplan = [[
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
  <section name="dialplan" description="RE Dial Plan For FreeSwitch">
    <context name="default">
      <extension name="freeswitch_public_conf_via_sip">
        <condition field="destination_number" expression="^9(888|1616)$">
          <action application="bridge" data="sofia/${use_profile}/$1@conference.freeswitch.org"/>
        </condition>
      </extension>
    </context>
  </section>
</document>
]]

XML_STRING = mydialplan
-- comment the following line for production:
freeswitch.consoleLog("notice", "Debug XML:\n" .. XML_STRING .. "\n")

Example XML_STRING Dialplan from DB

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

fsdir=> \d dialplan
                            TABLE "public.dialplan"
   COLUMN    |  TYPE   |                       Modifiers                       
-------------+---------+-------------------------------------------------------
 id          | INTEGER | NOT NULL DEFAULT NEXTVAL('dialplan_id_seq'::regclass)
 dp_domain   | text    | 
 dp_context  | text    | 
 dp_name     | text    | 
 dp_continue | text    | 
 dp_number   | text    | 
 dp_xml      | text    | 
 enable      | BOOLEAN | 
Indexes:
    "dialplan_pkey" PRIMARY KEY, btree (id)
fsdir=> SELECT dp_xml FROM dialplan WHERE id = 5;
                                        dp_xml                                         
---------------------------------------------------------------------------------------
 <extension name="user_map" continue="true">                                          +
         <condition FIELD="destination_number" expression="^([12345]\\d{3}|7\\d{4})$">+
             <action application="set" DATA="dst=${destination_number}" />            +
             <action application="set" DATA="dmn=192.168.210.231" />                  +
             <action inline="true" application="lua" DATA="user_map.lua $1" />        +
         </condition>                                                                 +
     </extension>
(1 ROW)
local dbh = freeswitch.Dbh("pgsql://host=127.0.0.1 dbname=DB user=USER password='PASS' options='-c client_min_messages=NOTICE' application_name='freeswitch'")
 
local xml = {}
 
        table.insert(xml, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
        table.insert(xml, [[<document type="freeswitch/xml">]]);
        table.insert(xml, [[  <section name="dialplan" description="Regex/XML Dialplan">]]);
                table.insert(xml, [[    <context name="default">]]);
             assert(dbh:query("select * from dialplan where enable = TRUE", function(u)
             	table.insert(xml, [[       ]]..u.dp_xml..[[]]);
             	end))            
                table.insert(xml, [[    </context>]]);
        table.insert(xml, [[  </section>]]);
        table.insert(xml, [[</document>]]);
 
XML_STRING = table.concat(xml, "\n")

Lua

Другой вариант использования: в sip профиле, вместо XML, можно указать Lua скрипт для генерации диалплана:

<profile name="phones">
  <!-- ... -->
  <settings>
    <param name="dialplan" value="LUA"/>
    <param name="context" value="dialplan-from-phones.lua"/>
    <!-- ... -->
  </settings>
</profile>

После этого создайте скрипт диалплана ../scripts/dialplan-from-phones.lua вроде этого:

-- dialplan-from-phones.lua
 
ACTIONS = {}
freeswitch.consoleLog("notice", "from your script")

table.insert(ACTIONS, "answer")
table.insert(ACTIONS, {"log", "NOTICE after your script"})

Все сохраненные в table (Array) данные, будут выполнены (EXECUTE), после того как скрипт остановится.

Это работает также, как и XML диалплан, когда сначала генерируется лист действий (<action application…/>), который затем выполняется, когда все данные собраны и обработаны.

Таким образом можно не активировать медиа потоки непосредственно во время выполнения скрипта.
(Обратный пример session:answer() в реализации IVR.)
Преимущество этого метода, над ответом на вызов непосредственно во время выполнения скрипта, в том что во время вызова скрипт уже завершил работу и все инструкции уже переданы ядру FS. Если ваше IVR приложение обрабатывает тысячи вызовов одновременно, это снизит нагрузку на интерпретатор Lua.

Также Lua скрипт может быть вызван непосредственно из XML диалплана для получения переменных, чтобы затем использовать их при последовательном вызове приложений диалплана прямо в скрипте или передав в XML.

Или же можно генерировать XML блок, как было показано в первом примере.

И конечно можно передавать управление из одной формы диалплана в другую.:

Из Lua в XML:

table.insert(ACTIONS, {"transfer", "123 XML some-context"})

Из XML в Lua:

<action application="transfer" data="123 LUA some-dialplan.lua"/>

Not found

Если Lua приложение получает запрос, но не находит соответствующий диалплан нужно вернуть результат «not found»

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
  <section name="result">
    <result status="not found" />
  </section>
</document>

Если возвращается пустой ответ, вместо not found, будет вызвано следующее сообщение о ошибке:

[ERR] switch_xml.c:1534 switch_xml_locate() Error[[error near line 1]: root tag missing]

Original Page

original pdf

Example Plain text: Sip_profiles & Gateway

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

     freeswitch.consoleLog("notice", "[xml_handler] Key Value: " .. XML_REQUEST["key_value"] .. "\n");
-- sofia.conf
    local xml = {}
    table.insert(xml, [[<?xml version="1.0" encoding="UTF-8" standalone="no"?>]]);
    table.insert(xml, [[<document type="freeswitch/xml">]]);
    table.insert(xml, [[	<section name="configuration">]]);
    table.insert(xml, [[		<configuration name="sofia.conf" description="sofia Endpoint">]]);
    table.insert(xml, [[			<global_settings>]]);
    table.insert(xml, [[				<param name="log-level" value="0"/>]]);
    table.insert(xml, [[				<param name="auto-restart" value="false"/>]]);
    table.insert(xml, [[				<param name="debug-presence" value="0"/>]]);
    table.insert(xml, [[				<param name="capture-server" value="udp:homer.domain.com:5060"/>]]);
    table.insert(xml, [[			</global_settings>]]);
    table.insert(xml, [[			<profiles>]]);
    table.insert(xml, [[                <profile name="internal">]]);
    table.insert(xml, [[                    <gateways>]]);
    table.insert(xml, [[                         <gateway name="fs0">]]);
    table.insert(xml, [[                             <param name="proxy" value="192.168.0.231:5070" />]]);
    table.insert(xml, [[                             <param name="register" value="false" />]]);
    table.insert(xml, [[                             <param name="transport" value="udp" />]]);
    table.insert(xml, [[                             <param name="context" value="default" />]]);
    table.insert(xml, [[                             <param name="caller-id-in-from" value="true" />]]);
    table.insert(xml, [[                         </gateway>]]);
    table.insert(xml, [[                     </gateways>]]);
    table.insert(xml, [[                     <settings>]]);
    table.insert(xml, [[                         <param name="apply-inbound-acl" value="192.168.0.0/16"/>]]);
    table.insert(xml, [[                         <param name="apply-register-acl" value="192.168.0.0/16"/>]]);
    table.insert(xml, [[                         <param name="auth-calls" value="false"/>]]);
    table.insert(xml, [[                         <param name="apply-nat-acl" value="auto.nat"/>]]);                         
    table.insert(xml, [[                         <param name="debug" value="0"/>]]);
    table.insert(xml, [[                         <param name="log-level" value="0"/>]]);
    table.insert(xml, [[                         <param name="sip-trace" value="no"/>]]);
    table.insert(xml, [[                         <param name="dialplan" value="XML"/>]]);
    table.insert(xml, [[                         <param name="context" value="default"/>]]);
    table.insert(xml, [[                         <param name="codec-prefs" value="PCMA,PCMU,G722,opus"/>]]);
    table.insert(xml, [[                         <param name="rtp-ip" value="$${local_ip_v4}"/>]]);
    table.insert(xml, [[                         <param name="sip-ip" value="$${local_ip_v4}"/>]]);
    table.insert(xml, [[                         <param name="ext-rtp-ip" value="auto-nat"/>]]);
    table.insert(xml, [[                         <param name="ext-sip-ip" value="auto-nat"/>]]);
    table.insert(xml, [[                         <param name="sip-port" value="5070"/>]]);
    table.insert(xml, [[                         <param name="rtp-autofix-timing" value="true"/>]]);
    table.insert(xml, [[                     </settings>]]);
    table.insert(xml, [[                 </profile>]]);
    table.insert(xml, [[             </profiles>]]);
    table.insert(xml, [[         </configuration>]]);
    table.insert(xml, [[     </section>]]);
    table.insert(xml, [[ </document>]]);
 
dbh:release();
XML_STRING = table.concat(xml, "\n")
    --freeswitch.consoleLog("notice", "Debug from sofia.conf.lua, generated XML:\n" .. XML_STRING .. "\n")
Serhio, 2021/11/10

За скрипты спасибо, но как в этой куче можно что то понять, людям которые в этом не разбираются, а только хоят начать

Только авторизованные участники могут оставлять комментарии.
  • freeswitch/db/fs_lua_serv_conf.txt
  • Последние изменения: 2019/03/19