Mod lua
From FreeSWITCH Wiki
mod_lua is supported as of RC4
Features
Write IVR scripts in lua
It has a very easy to use syntax, see the Hello Lua script.
Serve configs (the same way xml_curl does it)
Make API calls directly from Lua code
Lightweight
Stripped mod_lua.so is 272k
Highly Embeddable
As far as embeddability goes - python ranks a 2, perl ranks a 4, js is a 5, and lua is a 10!
luarun at the CLI
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 ie ${lua(codehere)}. "luarun" will spawn a thread while "lua" will block till the code is complete.
Configuring
For IVR use
Nothing should be needed here.
For making API calls
api = freeswitch.API();
r = api:execute("regex", "testing1234|/(\\d+)/|$1");
io.write(r .. "\n");
For serving configuration
For serving configuration with mod_lua:
- You can bind a script to the xml req, like you do with url in xml_curl
- When something looks up sections in the xml registry, it calls your script
- Your script does any db lookups or whatever it needs, and it returns the XML string.
The file conf/autoload_configs/lua.conf.xml is used in the default FreeSWITCH™ setup.
Here is a minimum configuration file, it will fetch a dialplan from LUA script.
<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>
This is a sample dp.lua, whatever is in XML_STRING will be returned to freeswitch once the script is finished
-- 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
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 startup-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"/>
NOTE: for looking up the location of the helloworld.lua file, it looks in prefix/scripts by default
Sample IVR's
Hello Lua
-- answer the call
session:answer();
-- play a file
session:streamFile("/path/to/blah.wav");
-- hangup
session:hangup();
More Samples
See scripts/lua directory on the filessytem, and Example IVRs
LUA Regex Example
using this method, you can execute regex conditions from inside your lua scripts. ( thanks bkw_ )
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..
FAQ
Where do I put 3rd part lua scripts/modules?
Q: Where do I need to stick my lua classes for FS to see them. I am running a lua script from within the /scripts folder, but it includes (requires) another lua file. FS.
A: (short answer) /usr/local/share/lua/5.1/
Alternatively, you can install a system lua (think apt-get or yum), and the lua embedded in freeswitch will look in the add-ons directory.
To do this in the mod_lua.conf file (currently not possible), the following development would be needed
* install them to a nonstandard * push a nonstandard place into the path of where it looks
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!
Can I access a database via ODBC?
Yes, see luasql
First you need to download and install Lua, and download LuaSQL, and compile and install whichever database modules you want to use. Next you can do something like:
#!/usr/local/bin/lua
require "luasql.mysql"
env = assert (luasql.mysql())
con = assert (env:connect("database","username","password","localhost"))
cur = assert (con:execute"SELECT * FROM table")
row = cur:fetch ({}, "a")
session:setVariable("varname", tostring(row.column));
cur:close()
con:close()
env:close()
Note: you need to symlink the shared object (ie: mysql.so) to /usr/local/lib/lua/5.1/luasql/mysql.so
API
event:getHeader
This is a generic API call
event:getHeader("Caller-Caller-ID-Name")
or, This can be used inside of a dialplan.lua to get certain information
params:getHeader("variable_sip_req_uri")
event:setPriority
event:fire
event:serialize
Use this to dump all available Headers to the console
io.write(params:serialize());
Or this to display them as an info message
freeswitch.consoleLog("info",params:serialize())
event:addHeader
event:delHeader
event:addBody
event:getBody
event:getType
session:answer
Answer the session:
session:answer();
session:preAnswer
Pre answer the session:
session:preAnswer();
session:streamFile
Stream a file to the session
session:streamFile("/tmp/blah.wav");
session:transfer
Transfer the current session. The arguments are extensions, dialplan and context.
session:transfer("3000", "XML", "default");
session:sleep
session:sleep(3000);
-- This will allow callbacks to DTMF to occur and session:execute("sleep", "5000"), won't..
session:read
Play a file and get digits.
digits = session:read(5, 10, "/sr8k.wav", 3000, "#");
freeswitch.consoleLog("info", "Got dtmf: ".. digits .."\n");
session:read has 5 arguments: <min digits> <max digits> <file to play> <inter-digit timeout> <terminators>
session:playAndGetDigits
Play a file and get digits:
digits = session:playAndGetDigits(2, 5, 3, 3000, "#", "/sr8k.wav", "", "\\d+");
freeswitch.consoleLog("info", "Got dtmf: ".. digits .."\n");
This has 8 arguments: min_digits, max_digits, max_tries, timeout, terminators, audio_files, bad_input_audio_files, digits_regex
session:getDigits
digits = session:getDigits(5, "#", 3000);
freeswitch.consoleLog("info", "Got dtmf: ".. digits .."\n");
session:setVariable
Set a variable on a session:
session:setVariable("varname", "varval");
session:getVariable
To get system variables such as ${hold_music}
local moh = session:getVariable("hold_music")
session:recordFile
syntax is session:recordFile(file_name, max_len, silence_threshold, silence_secs)
Example:
session:recordFile("/tmp/blah.wav", 30000, 10, 10); -- pressing # ends the recording
session:streamFile("/tmp/blah.wav");
session:setCallerData
session:ready
while (session:ready() == true) do -- do something here end
session:setHangupHook
In your lua code, you can use setHangupHook to define the function to call when the session hangs up.
function myHangupHook(s, status, arg)
freeswitch.consoleLog("NOTICE", "myHangupHook: " .. status .. "\n")
-- close db_conn and terminate
db_conn:close()
error()
end
blah="w00t";
session:setHangupHook("myHangupHook", "blah")
session:execute
session:execute(app, data)
local mySound = "/usr/local/freeswitch/sounds/music/16000/partita-no-3-in-e-major-bwv-1006-1-preludio.wav"
session:execute("playback", mySound)
Callbacks (DTMF and friends) CAN NOT EXECUTE during an execute.
session:setAutoHangup
By default, lua script hangs up when it is done executing. If you need to run the next action in your dialplan after the lua script, you will need to setAutoHangup to false. The default value is true.
session:setAutoHangup(false)
session:flushEvents
session:flushDigits
session:sendEvent
session:speak
session:set_tts_parms("flite", "kal");
session:speak("Please say the name of the person you're trying to contact");
session:setInputCallback
function my_cb(s, type, obj, arg)
if (arg) then
io.write("type: " .. type .. "\n" .. "arg: " .. arg .. "\n");
else
io.write("type: " .. type .. "\n");
end
if (type == "dtmf") then
io.write("digit: [" .. obj['digit'] .. "]\nduration: [" .. obj['duration'] .. "]\n");
else
io.write(obj:serialize("xml"));
e = freeswitch.Event("message");
e:add_body("you said " .. obj:get_body());
session:sendEvent(e);
end
end
blah="w00t";
session:answer();
session:setInputCallback("my_cb", "blah");
session:streamFile("/tmp/swimp.raw");
session:hangup
You can hang up a session and provide an optional Hangup causes.
session:hangup("USER_BUSY");
or
session:hangup(); -- default normal_clearing
freeswitch.Session
local session = freeswitch.Session("sofia/10.0.1.100/1001");
session:transfer("3000", "XML", "default");
freeswitch.consoleLog
Log something to the freeswitch logger. Arguments are loglevel, message.
freeswitch.consoleLog("info","lua rocks\n");
freeswitch.consoleCleanLog
freeswitch.consoleCleanLog("This Rocks!!!\n");
freeswitch.EventConsumer
con = freeswitch.EventConsumer("all");
session = freeswitch.Session("sofia/default/dest@host.com");
while session:ready() do
session:execute("sleep", "1000");
for e in (function() return con:pop() end) do
print("event\n" .. e:serialize("xml"));
end
end
freeswitch.Event
This is firing a custom event my::event.
local event = freeswitch.Event("custom", "my::event");
event:addHeader("My-Header", "test");
event:fire();
freeswitch.IVRMenu
hash = {
["main"] = undef,
["name"] = "top",
["greet_long"] = "phrase:demo_ivr_main_menu",
["greet_short"] = "phrase:demo_ivr_main_menu_short",
["invalid_sound"] = "/tmp/wtf.wav",
["exit_sound"] = "voicemail/vm-goodbye.wav",
["confirm_macro"] = "undef",
["confirm_key"] = "undef",
["confirm_attempts"] = "3",
["inter_digit_timeout"] = "2000",
["digit_len"] = "1",
["timeout"] = "10000",
["max_failures"] = "3"
}
top = freeswitch.IVRMenu(hash["main"],
hash["name"],
hash["greet_long"],
hash["greet_short"],
hash["invalid_sound"],
hash["exit_sound"],
hash["confirm_macro"],
hash["confirm_key"],
hash["confirm_attempts"],
hash["inter_digit_timeout"],
hash["digit_len"],
hash["timeout"],
hash["max_failures"]);
top:bindAction("menu-exec-app", "playback /tmp/swimp.raw", "2");
top:execute(session, "top");
stream:write
stream:write("Content-Type: text/html\n\n");
stream:write("<title>FreeSWITCH Command Portal</title>");
stream:write("<h2>FreeSWITCH Command Portal</h2>");
stream:write("<form method=post><input name=command size=40> ");
stream:write("<input type=submit value=\"Execute\">");
stream:write("</form><hr noshade size=1><br>");
command = env:getHeader("command");
if (command) then
api = freeswitch.API();
reply = api:executeString(command);
if (reply) then
stream:write("<br><B>Command Result</b><br>" .. reply .. "\n");
end
end
env:addHeader("cool", "true");
stream:write(env:serialize() .. "\n\n");
Sending an Event
Using luarun to execute this code you can toggle the MWI on a registered phone on and off.
local event = freeswitch.Event("message_waiting");
event:addHeader("MWI-Messages-Waiting", "no");
event:addHeader("MWI-Message-Account", "sip:1002@10.0.1.100");
event:fire();
