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
Установка "ESL.so" для сообщения LUA с FREESWITCH
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
Возможности
IVR на Lua
Перехват событий (event hooks) FS в Lua
Вы можете определить скрипт Lua, который будет выполняться каждый раз, когда происходит конкретное событие:
Mod_lua#Event_Hooks
Serve configs
Lua может обслуживать конфигурацию FS, генерирую XML в режиме реалтайм не требуя при этом обращений к веб серверу, т.е. заменяя собой mod_xml_curl.
Freeswitch: Конфигурация при помощи Lua
Вызывайте API FS из кода Lua
mod_lua очень лёгкий
- rwxr-xr-x 1 freeswitch freeswitch 659K Nov 7 11:59 mod_lua.so
CLI Usage: lua and luarun
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
Event Hooks
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")
For IVR use
Nothing should be needed here.
For making API calls
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.
To call another Lua script
api = freeswitch.API(); reply = api:executeString("luarun another.lua");
For serving configuration
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.
Lua scripts at startup
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
In-line expansion
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)
Regex API Example
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.
Native Lua Pattern Matching
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
Run a shell command
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
Why is mod_lua not unloadable?
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.
Where is my debug information?
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.
How can I make it use the "system lua"
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!
How can I get Lua to see my own libraries using "require"
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
How can I find useful undocumented Functions?
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 book pages 149-151