Fs: mod_lua

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

Документация Lua API представлена здесь Lua API Reference

Начиная с версии FreeSWITCH 1.4, требуется версия Lua5.2 (http://www.lua.org/manual/5.2/).

Если вы хотите использовать Lua5.1, для обратной совместимости доступен старый mod_lua в директории freeswitch/src/mod/legacy/languages/mod_lua/.

Установка

apt install lua5.2 
apt install liblua5.2-dev
 
 apt-get install lua5.2
 git clone https://github.com/signalwire/freeswitch.git -bv1.10 /usr/src/freeswitch.git
 cd /usr/src/freeswitch.git
 ./bootstrap.sh
 ./configure
 make -j
 cd /usr/src/freeswitch.git/libs/esl
 make luamod
 mkdir -p /usr/local/lib/lua/5.2
 cp lua/ESL.so /usr/local/lib/lua/5.2/ESL.so

Возможности

Hello Lua

Вы можете определить скрипт Lua, который будет выполняться каждый раз, когда происходит конкретное событие:
Mod_lua#Event_Hooks

Lua может обслуживать конфигурацию FS, генерирую XML в режиме реалтайм не требуя при этом обращений к веб серверу, т.е. заменяя собой mod_xml_curl.

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

non-session_api

  1. rwxr-xr-x 1 freeswitch freeswitch 659K Nov 7 11:59 mod_lua.so

You can issue a «luarun /path/to/script.lua» and launch a thread which your lua script will run in. The «lua» command is for inline lua like from the dialplan i.e. ${lua(codehere)}. «luarun» will spawn a thread while «lua» will block until the code is complete. A «~» in front of the argument will run a single line lua command. Note that lua scripts executed with luarun cannot write to the console through stream:write API as there is no stream.

Passing Arguments

Arguments are passed as space-separated values:

luarun arg1 arg2 arg3

Arguments are accessed with «argv» like this:

my_first_var = argv[1];
my_next_var = argv[2];

And so on…

freeswitch@DVORAK> lua ~print(string.find("1234#5678", "(%d+)#(%d+)"))
1       9       1234    5678
freeswitch@DVORAK> luarun ~print(string.find("1234#5678", "(%d+)#(%d+)"))
+OK
1       9       1234    5678
freeswitch@DVORAK> luarun ~stream:write("1234#5678")
+OK
2011-06-20 13:35:35.274782 [ERR] mod_lua.cpp:191 [string "line"]:1: attempt to index global 'stream' (a nil value)
stack traceback:
       [string "line"]:1: in main chunk
freeswitch@DVORAK> lua ~stream:write("1234#5678")
1234#5678

Configuring

The following configuration shows how to have the tone_event.lua script executed each time the DETECTED_TONE event is fired.

<configuration name="lua.conf" description="LUA Configuration">
  <settings>
     <param name="script-directory" value="$${base_dir}/scripts/?.lua"/>
     <hook event="DETECTED_TONE" script="tone_event.lua"/>
     <hook event="CUSTOM" subclass="conference::maintenance" script="conference.lua"/>
  </settings>
</configuration>

Event Hook Script

This is an example event hook script. getHeader() can be called on the event to get information about the event.

local uuid = event:getHeader("Unique-ID")
local tone = event:getHeader("Detected-Tone")
freeswitch.consoleLog("info", uuid .. " detected tone: " .. tone .. "\n")

Nothing should be needed here.

api = freeswitch.API();

digits = api:execute("regex", "testing1234|/(\\d+)/|$1");
-- The returned output of the API call is stored in the variable if you need it.
freeswitch.consoleLog("info", "Extracted digits: " .. digits .. "\n")

Note: Please take care to escape the arguments that you pass, as has been done to the regex string above.

api = freeswitch.API();
reply = api:executeString("luarun another.lua");

mod_lua allows you to replace request for configuration data from a lookup in the static XML to your script.

See Mod_lua/Serving_Configuration for more information.

Here is a minimum configuration file:

<configuration name="lua.conf" description="LUA Configuration">

  <settings>
    <!--
    The following options identifies a lua script that is launched
    at startup and may live forever in the background.
    You can define multiple lines, one for each script you 
    need to run.
    -->
    <!--<param name="startup-script" value="startup_script_1.lua"/>-->
    <!--<param name="startup-script" value="startup_script_2.lua"/>-->
  </settings>
</configuration>

The start-up script values represent lua scripts (located inside the scripts/ directory) that are launched when FreeSWITCH is started. The scripts live in their own thread. You can use them to run simple tasks (and then let them finish) or looping forever, watching (for example) for events, generating calls or whatever you like.

Sample Dialplan

<action application="lua" data="helloworld.lua arg1 arg2"/>

NOTE: arguments can be accessed by using argv[1] argv[2] in your script

NOTE: for looking up the location of the helloworld.lua file, it looks in prefix/scripts by default

 

e.g.1: <action application="set" data="MY_VAR=${lua(helloworld.lua arv1 arg2)}" />
e.g.2: <action application="set" data="MY_VAR=${lua(helloworld.lua $1)}" />
e.g.3: <action application="set" data="MY_VAR=${lua(helloworld.lua $1 ${caller_id_number})}" />
e.g.4: <action application="set" data="MY_VAR=${lua(helloworld.lua ${caller_id_number})}" inline="true" />
 
e.g.5: <condition field="${lua(helloworld.lua arv1 arg2)}" expression="^Hello World$" >

Sample IVR's

This is a basic IVR example, in which we answer the call, wait 1 second and play an wav audio file.

-- answer the call
session:answer();

-- sleep a second
session:sleep(1000);

-- play a file
session:streamFile("/path/to/blah.wav");

-- hangup
session:hangup();

Pattern matching (regular expressions)

Using this method, you can execute regex conditions from inside your lua scripts.

session:execute("set", "some_chan_variable=${regex(" .. destination .. "|^([0-9]{10})$)}")

If destination is a lua variable with your destination number in it, and your regex was ^([0-9]{10})$

the result will be put in «some_chan_variable» that you can get thorough a session:getVariable

You can also do:

session:execute("set", "some_chan_variable=${regex(" .. destination .. "|^(0\\|61)([2,3,7,8][0-9]{8})$|$2)}")

To match and return parts of your regex.

Lua supports a simple but powerful pattern matching syntax. It is less powerful than PCRE but it handles the majority of pattern matching cases will ever need in a simple script.

The following is a simple script that can be run with «luarun» from fs_cli and demonstrates the capturing of two values from a data string:

-- pattern.lua
data = "1234#5678";
_,_,var1,var2 = string.find(data,"(%d+)#(%d+)");
freeswitch.consoleLog("INFO","\ndata: " .. data .. "\nvar1: " .. var1 .. "\nvar2: " .. var2 .. "\n");

Output:
freeswitch@internal> luarun pattern.lua
+OK

2011-04-18 08:28:49.242080 [INFO] switch_cpp.cpp:1197 
data: 1234#5678
var1: 1234
var2: 5678

Misc. Samples

When using session:execute() and api:execute() to execute a shell command (ie: bash script) it will only return the error code integer.

To return the output of a shell command use io.popen(). The following example Lua function is from http://lua-users.org/wiki/ShellAccess.

 function shell(c)
   local o, h
   h = assert(io.popen(c,"r"))
   o = h:read("*all")
   h:close()
   return o
 end

FAQ

freeswitch@baremetal> reload mod_lua
+OK Reloading XML
-ERR unloading module [Module is not unloadable]
-ERR loading module [Module already loaded]
2018-03-14 17:53:22.492225 [CRIT] switch_loadable_module.c:1643 Module is not unloadable.
2018-03-14 17:53:22.492225 [WARNING] switch_loadable_module.c:1592 Module mod_lua Already Loaded!

Some threads explaining why the decision was made http://freeswitch-users.2379917.n2.nabble.com/Reload-mod-lua-td7586568.html

mod_lua was reloadable  but when I found that long-running scripts cannot be  easily stopped the developers changed it to un-reloadable.
There's also a blog page in Chinese explaining this: http://www.freeswitch.org.cn/2010/03/15/zai-freeswitchzhong-zhi-xing-chang-qi-yun-xing-de-qian-ru-shi-jiao-ben-luayu-yan-li-zi.html
 
(...truncated thread...)
 
The reason being if the module unloads while scripts are still running you're going to be almost guaranteed a segmentation fault - a well meaning admin running a simple command could crash the entire server dropping all calls.

Q: When I use static XML or xml_curl, I see the commands executed in the fs_cli and in the «application log» section of the xml_cdr file. However, when I run commands via my lua script, I don't see that information anywhere?

A: When you have an actual call session, you can use session:execute(«$application»,«$data») just like in the static XML - it will then show up in the fs_cli and the xml_cdr application log. If you don't have a call session - e.g. if you are running lua as a background application or from the CLI, then you have to use other commands which may not be as easily logged.

Q: I have Lua installed, but mod_lua seems to ignore the Lua binary.

A: Lua is so small that the whole ball of wax is statically linked into the module!

Q: Can I use the require mechanism for including libraries with the Lua in FreeSWITCH?

A: You may need to alter the LUA_PATH variable to instruct the embedded Lua inside FreeSWITCH to find your libraries. A simple startup script to do this is:

#!/bin/bash
export LUA_PATH=/usr/local/freeswitch/scripts/?.lua\;\;
ulimit -s 240
screen /usr/local/freeswitch/bin/freeswitch

The default path is something like /usr/local/share/lua/5.1/

Another option for common code similar to the «include» directive in many languages is to use dofile

eg. dofile(«/home/fred/scripts/fredLuaFunctions.lua»)

Note that this will just execute the code contained in the file as if it were inline - this is not the same as creating a lua module

There may be times when a function gets added, but not documented. This simple Lua script may help.

-- This function simply tells us what function are available in Session
--   It just prints a list of all functions.  We may be able to find functions
--   that have not yet been documented but are useful.  I did :)
function printSessionFunctions( session )
   metatbl = getmetatable(session)
   if not metatbl then return nil end
   local f=metatbl['.fn'] -- gets the functions table
   if not f then return nil end
   for k,v in pairs(f) do stream:write(k.."\n") end
   stream:write("\nEND\n")
end
new_session = freeswitch.Session() -- create a blank session
stream:write("\n***Session Functions***\n")
printSessionFunctions(new_session)
new_event = freeswitch.Event("message_waiting");
stream:write("\n***Event Functions***\n")
printSessionFunctions(new_event)
new_api = freeswitch.API();
stream:write("\n***API Functions***\n")
printSessionFunctions(new_api)

At freeswitch version FreeSWITCH Version 1.6.17-34-0fc0946~64bit (-34-0fc0946 64bit) this is the output of the script:

***Session Functions***
playAndGetDigits   
speak
setDTMFCallback
answer
mediaReady
__disown
getVariable
collectDigits
streamFile
setEventData
sleep
flushDigits
setLUA
setHangupHook
setInputCallback
unsetInputCallback
preAnswer
consoleLog2
consoleLog
run_dtmf_callback
say
process_callback_result
execute
get_cb_args
waitForAnswer
getXMLCDR
flushEvents
set_tts_parms
end_allow_threads
sendEvent
getState
begin_allow_threads
get_uuid
setAutoHangup
getDigits
getPrivate
bridged
recordFile
destroy
set_tts_params
ready
transfer
hangupCause
insertFile
originate
sayPhrase
answered
read
hangupState
hangup
check_hangup_hook
setPrivate
setVariable
END
***Event Functions***
setPriority
chat_send
serialize
getHeader
getBody
fire
delHeader
__disown
getType
addBody
chat_execute
addHeader
END
***API Functions***
getTime
execute
__disown
executeString
END

Jester Lua Toolkit

Jester is a scripting lua toolkit for FreeSWITCH :Wiki Jester

See Also

  • freeswitch/mod/mod_lua.txt
  • Последние изменения: 2020/03/21