Mod lua
From FreeSWITCH Wiki
Features
Write IVR scripts in Lua
It has a very easy to use syntax, see the Hello Lua script.
Serve configs (the same way mod_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.
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...
Configuring
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")
To call another Lua script
api = freeswitch.API();
reply = api:executeString("luarun another.lua");
For serving configuration
For serving configuration with mod_lua:
- You can bind a script to the XML req, like you do with URL in mod_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.
- Note: When you edit lua.conf.xml, giving the reloadxml command from the FreeSWITCH console is not sufficient to recognize the "xml-handler-script" parameters. You have to restart FreeSWITCH.
- Some examples for the "xml-handler-bindings" parameter: "dialplan" "directory" "dialplan|directory"
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
For dialplan
In for example a sip profile you can - instead of XML dialplan - use LUA directly to generate a dialplan:
<profile name="phones">
<!-- ... -->
<settings>
<param name="dialplan" value="LUA"/>
<param name="context" value="dialplan-from-phones.lua"/>
<!-- ... -->
</settings>
</profile>
Then create a dialplan in scripts/dialplan-from-phones.lua like this:
-- dialplan-from-phones.lua
ACTIONS = {}
freeswitch.consoleLog("notice", "from your script")
table.insert(ACTIONS, "answer")
table.insert(ACTIONS, {"log", "NOTICE after your script"})
The entire table (array) of actions will be executed after the LUA script has stopped.
This works the same as when the XML dialplan generates a list of actions that is run after it has reached the end.
Note that you must not use media directly from your script (eg session:answer()) like you could if it were called as an IVR script.
Advantage of this method over answering from an IVR script is that during the call itself there will be no LUA script (per session) running - which could add up if you have a few thousand sessions going.
Alternative is to call a LUA script from the XML dialplan that only sets some channel variables that will be picked up by consecutive applications - or you can generate an XML stub as is described in the previous section.
Of course it is also possible to transfer from one dialplan to another.
From LUA to XML:
table.insert(ACTIONS, {"transfer", "123 XML some-context"})
From XML to LUA:
<action application="transfer" data="123 LUA some-dialplan.lua"/>
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
Sample IVR's
Hello Lua
-- answer the call
session:answer();
-- sleep a second
session:sleep(1000);
-- play a file
session:streamFile("/path/to/blah.wav");
-- hangup
session:hangup();
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..
More Samples
- See scripts/lua directory on the file system
- Example IVRs
- Lua MythTV alert example
- Fakecall responder
- Fun Lua Examples
- Call retry based on hangup cause
- Bridging two calls with retry
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.
NOTE: Assuming you have <param name="module-directory" value="$${base_dir}/scripts/?.so"/> in lua.conf.xml, in the scripts, require statements in the form of:
require "luasql.mysql"
This will look for the shared object ${base_dir}/scripts/luasql/mysql.so
NOTE: The native MySQL driver for Lua has a very bad memory leak. Do not use it.
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 will 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
Can I access a database via ODBC?
Yes, see luasql (LuaSQL 2.1.1)
WARNING: On x64_86 you need to change in config a few things
http://luaforge.net/frs/download.php/2686/luasql-2.1.1.tar.gz tar xfvz luasql-2.1.1.tar.gz cd luasql-2.1.1/
Example: to use Microsoft SQL Server:
sed -i 's/#T= odbc/T= odbc/g' config sed -i 's/T=sqlite3/#T=sqlite3/g' config DRIVER_LIBS= -L/usr/lib64 -lodbc DRIVER_INCS= -DUNIXODBC -I/usr/include
Set proper values for LUA_LIBDIR, LUA_DIR , LUA_INC , DRIVER_LIBS, DRIVER_INCS
To allow compile properly:
sed -i 's/WARN= /WARN= -fPIC /g' config
Compile.
make make install
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:I found you also need to set some environment variables for scripts to run properly
echo export LUA_PATH=/usr/local/freeswitch/scripts/?.lua >> /etc/bashrc echo export LUA_CPATH=/usr/local/freeswitch/scripts/?.so >> /etc/bashrc echo export PATH=$PATH:/usr/local/freeswitch/bin >> ~/.bashrc
NOTE: you need to symlink the shared object (ie: mysql.so) to /usr/local/lib/lua/5.1/luasql/mysql.so
WARNING: This change breaks load of external lua modules http://fisheye.freeswitch.org:8081/changelog/FreeSWITCH?cs=9605
NOTE: Fixed http://fisheye.freeswitch.org:8081/changelog/FreeSWITCH?cs=10306
How can I find useful undocumented Session 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
print("\n***Session Functions***\n")
for k,v in pairs(f) do print(k,v) end
print("\n\n")
end
new_session = freeswitch.Session() -- create a blank session
printSessionFunctions(new_session)
At freeswitch revision 16256 this is the output of the script:
process_callback_result function: 0x79b380 flushDigits function: 0x798db0 execute function: 0x798ed0 setAutoHangup function: 0x798de0 collectDigits function: 0x798c30 mediaReady function: 0x7993d0 speak function: 0x79b2d0 setHangupHook function: 0x799510 getDigits function: 0x798c60 check_hangup_hook function: 0x799380 playAndGetDigits function: 0x798cf0 getState function: 0x79b1e0 hangupState function: 0x79b4e0 say function: 0x79b150 recordFile function: 0x79b210 waitForAnswer function: 0x798ea0 sleep function: 0x798d50 setEventData function: 0x798f30 setPrivate function: 0x79b540 setLUA function: 0x7995d0 begin_allow_threads function: 0x799320 setInputCallback function: 0x7994e0 unsetInputCallback function: 0x799470 run_dtmf_callback function: 0x799400 get_cb_args function: 0x799020 read function: 0x798cc0 getPrivate function: 0x79b570 answer function: 0x79b450 setVariable function: 0x79b510 getXMLCDR function: 0x798f60 originate function: 0x799570 destroy function: 0x7992f0 answered function: 0x798e70 set_tts_parms function: 0x79b300 sendEvent function: 0x798f00 ready function: 0x799540 preAnswer function: 0x79b4b0 streamFile function: 0x798d20 hangupCause function: 0x79b1b0 setDTMFCallback function: 0x79b2a0 getVariable function: 0x79b350 transfer function: 0x798c90 get_uuid function: 0x798ff0 hangup function: 0x79b480 end_allow_threads function: 0x799350 flushEvents function: 0x798d80 sayPhrase function: 0x79b180
API
Events
These methods apply to generating events.
event:addBody
--Create Custom event
custom_msg = "dial_record_id: " .. dial_record_id .. "\n" ..
"call_disposition: " .. Disposition .. "\n" ..
"campaign_number: " .. Campaign .. "\n" ..
"called_number: " .. dial_num .."\n" ;
local e = freeswitch.Event("custom", "dial::dial-result");
e:addBody(custom_msg);
e:fire();
You can add as much data to the body as you like, in this case 4 items are to be sent.
The result will be
[Content-Length] => 555
[Content-Type] => text/event-plain
[Body] => Array
(
[Event-Subclass] => dial::dial-result
[Event-Name] => CUSTOM
[Core-UUID] => 2dc7cc50-b157-4868-ae16-04e5f4b95dae
[FreeSWITCH-Hostname] => pp6.noble.co.uk
[FreeSWITCH-IPv4] => 192.168.0.106
[FreeSWITCH-IPv6] => ::1
[Event-Date-Local] => 2009-02-17 23:15:49
[Event-Date-GMT] => Tue, 17 Feb 2009 23:15:49 GMT
[Event-Date-Timestamp] => 1234912549610060
[Event-Calling-File] => switch_cpp.cpp
[Event-Calling-Function] => fire
[Event-Calling-Line-Number] => 297
[Content-Length] => 85
[Body] => Array
(
[dial_record_id] => 1234
[call_disposition] => AA
[campaign_number] => 20
[called_number] => 7777777
)
)
)
event:addHeader
event:delHeader
event:fire
bkw gave the example of such...
local event = freeswitch.Event("message_waiting");
event:addHeader("MWI-Messages-Waiting", "no");
event:addHeader("MWI-Message-Account", "sip:1000@10.0.1.100");
event:addHeader("Sofia-Profile", "internal");
event:fire();
event:getBody
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:getType
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:setPriority
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();
Sessions
The following methods can be applied to existing sessions.
session:answer
Answer the session:
session:answer();
session:answered
session:bridged
Check to see if this session's channel is bridged to another channel.
if (session:bridged() == true) do
-- Do something
end
session:check_hangup_hook
session:collectDigits
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:flushDigits
session:flushEvents
session:get_uuid
session:getDigits
Get digits:
- getDigits has three arguments: max_digits, terminators, timeout
- max_digits: maximum number of DTMF tones that will be collected
- terminators: buffer of characters that will terminate the digit collection
- timeout: maximum time in milliseconds allowed for digit collection
- return: buffer containing collected digits
- The method blocks until one of the exit criteria is met.
digits = session:getDigits(5, "#", 3000);
freeswitch.consoleLog("info", "Got dtmf: ".. digits .."\n");
session:getState
Get the call state, i.e. "CS_EXECUTE". The call states are described in "switch_types.h".
state=session:getState();
session:getVariable
To get system variables such as ${hold_music}
local moh = session:getVariable("hold_music")
--[[ events obtained from "switch_channel.c"
regards Monroy from Mexico
]]
session:getVariable("context");
session:getVariable("destination_number");
session:getVariable("caller_id_name");
session:getVariable("caller_id_number");
session:getVariable("network_addr");
session:getVariable("ani");
session:getVariable("aniii");
session:getVariable("rdnis");
session:getVariable("source");
session:getVariable("chan_name");
session:getVariable("uuid");
session:hangup
You can hang up a session and provide an optional Hangup causes.
session:hangup("USER_BUSY");
or
session:hangup(); -- default normal_clearing
session:hangupCause
You can find the hangup cause of an answered call and/or the reason an originated call did not complete. See Hangup causes.
-- Initiate an outbound call
obSession = freeswitch.Session("sofia/192.168.0.4/1002")
-- Check to see if the call was answered
if obSession:ready() then
-- Do something good here
else -- This means the call was not answered ... Check for the reason
local obCause = obSession:hangupCause()
freeswitch.consoleLog("info", "obSession:hangupCause() = " .. obCause )
if ( obCause == "USER_BUSY" ) then -- SIP 486
-- For BUSY you may reschedule the call for later
elseif ( obCause == "NO_ANSWER" ) then
-- Call them back in an hour
elseif ( obCause == "ORIGINATOR_CANCEL" ) then -- SIP 487
-- May need to check for network congestion or problems
else
-- Log these issues
end
end
session:hangupState
session:mediaReady
session:originate
session:playAndGetDigits
Play a file and get digits:
- The call has to be answered before any DTMF will be read. If you do not answer the call you will still hear the file being played, but no functionality will be available.
- No DTMF is read while bad_input_audio_file is being played. DTMF is only read when playing audio_files (using variable names as show in the example below)
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_file, digits_regex
Note: If you need to match the * key in the regexp, you will have to quote it twice, as in:
digits = session:playAndGetDigits(2, 5, 3, 3000, "#", "/sr8k.wav", "", "\\d+|\\*");
session:preAnswer
Pre answer the session:
session:preAnswer();
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:ready
See #session:hangupCause for more detail on if NOT ready.
while (session:ready() == true) do -- do something here end
session:recordFile
syntax is session:recordFile(file_name, max_len, silence_threshold, silence_secs)
silence_secs is the amount of silence to tolerate before ending the recording.
Example:
session:recordFile("/tmp/blah.wav", 30000, 10, 10); -- use inputcallback to end recording.
session:streamFile("/tmp/blah.wav");
'''NOTE:'''
if you want the old behaviour of pressing "#" to stop the recording use somthing like the following input callback (the input callback returns "break"):
function onInput(s, type, obj)
if (type == "dtmf" and obj['digit'] == '#') then
return "break";
end
end
session:answer();
session:setInputCallback("onInput", "");
session:recordFile("/tmp/blah.wav", 30000, 10, 10); -- pressing # ends the recording
session:streamFile("/tmp/blah.wav");
session:hangup();
--Another one
function onInputEntradaAudio(session, tipoEntrada, contenedor, argumentos)
local tonosDeParo = {};
tonosDeParo = Utils:commaText(argumentos[1]);
if (tipoEntrada == "dtmf") then
for k, v in pairs(tonosDeParo) do
if (contenedor['digit'] == v) then return "break"; end
end
end
end
table.insert(argumentosX, "*, #");
session:setInputCallback("onInputEntradaAudio", "argumentosX");
session:recordFile(path, segundosMax, umbralSilencio, segundosInterrupcion);
session:sayPhrase
Play a speech phrase macro.
session.sayPhrase(macro_name [,macro_data] [,language]);
- macro_name - (string) The name of the say macro to speak.
- macro_data - (string) Optional. Data to pass to the say macro.
- language - (string) Optional. Language to speak macro in (ie. "en" or "fr"). Defaults to "en".
To capture events or DTMF, use it in combination with session:setInputCallback
Example:
function key_press(session, input_type, data, args)
if input_type == "dtmf" then
freeswitch.consoleLog("info", "Key pressed: " .. data["digit"])
return "break"
end
end
if session:ready() then
session:answer()
session:execute("sleep", "1000")
session:setInputCallback("key_press", "")
session:sayPhrase("voicemail_menu", "1:2:3:#", "en")
end
When used with setInputCallback, the return values and meanings are as follows:
- true or "true" - Causes prompts to continue speaking.
- Any other string value interrupts the prompt.
session:sendEvent
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: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: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:setVariable
Set a variable on a session:
session:setVariable("varname", "varval");
session:sleep
session:sleep(3000);
- This will allow callbacks to DTMF to occur and session:execute("sleep", "5000"), won't.
session:speak
session:set_tts_parms("flite", "kal");
session:speak("Please say the name of the person you're trying to contact");
session:say
Plays pre-recorded sound files for things like numbers, dates, currency, etc. Refer to Misc. Dialplan Tools say for info about the say application.
Arguments: <data><lang><say_type><say_method>
Example:
session:say("12345", "en", "number", "pronounced");
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");
execution of your lua script will immediately stop, make sure you set session:setAutoHangup(false) if you don't want your call to disconnect
session:unsetInputCallback
session:unsetInputCallback()
session:waitForAnswer
Non-Session API
These methods are generic in that they do not apply to a session or an event. For example, printing data to the FreeSWITCH console is neither event- nor session-specific.
freeswitch.API
api = freeswitch.API();
freeswitch.bridge
session1 = freeswitch.Session("sofia/internal/1001%192.168.1.1");
session2 = freeswitch.Session("sofia/internal/1002%192.168.1.1");
freeswitch.bridge(session1, session2);
freeswitch.consoleCleanLog
freeswitch.consoleCleanLog("This Rocks!!!\n");
freeswitch.consoleLog
Log something to the freeswitch logger. Arguments are loglevel, message.
freeswitch.consoleLog("info", "lua rocks\n");
freeswitch.consoleLog("notice", "lua rocks\n");
freeswitch.consoleLog("err", "lua rocks\n");
freeswitch.consoleLog("debug", "lua rocks\n");
freeswitch.consoleLog("warning","lua rocks\n");
freeswitch.Event
This is firing a custom event my::event.
local event = freeswitch.Event("custom", "my::event");
event:addHeader("My-Header", "test");
event:fire();
--Another one
function FSMan:fire(nombreHeader, header, body)--Manda un evento MESSAGE a algun receptor
local miEvento = freeswitch.Event("MESSAGE");
nombreHeader = Utils:trim(nombreHeader); header = Utils:trim(header); body = Utils:trim(body);
if (nombreHeader == false ) then nombreHeader="Nombre_Header_Generico" end
if (header == false) then header="Header_Generico" end
if (body == false) then body="Body_Generico" end
miEvento:addHeader(nombreHeader, header);
miEvento:addBody(body);
miEvento:fire();
end
freeswitch.EventConsumer
Consumes events from FreeSWITCH.
Usage:
con = freeswitch.EventConsumer("all");
-- pop() returns an event or nil if no events
con:pop();
-- pop(1) blocks until there is an event
con:pop(1);
Example:
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.IVRMenu
hash = {
["main"] = undef,
["name"] = "top",
["greet_long"] = "phrase:demo_ivr_main_menu",
["greet_short"] = "phrase:demo_ivr_main_menu_short",
["invalid_sound"] = "ivr/ivr-that_was_an_invalid_entry.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");
When using SAY, 3 additional variables have to be set or you will get the following error:
> [ERR] mod_lua.cpp:182 Error in IVRMenu expected 16..16 args, got 13 stack traceback:
> [C]: in function 'IVRMenu'
> /usr/local/freeswitch/scripts/ivr.lua:19: in main chunk
These variables are:
["tts_engine"] = "flite",
["tts_voice"] = "rms",
["max_timeouts"] = "2"
freeswitch.msleep
Tells script to sleep for a specified number of milliseconds.
NOTE: Do not use this on a session-based script or bad things will happen.
-- Sleep for 500 milliseconds freeswitch.msleep(500);
freeswitch.Session
Create a new session.
local session = freeswitch.Session("sofia/10.0.1.100/1001");
session:transfer("3000", "XML", "default");
Create a new session with execute_on_answer variable set.
local session = freeswitch.Session("[execute_on_answer=info notice]sofia/10.0.1.100/1001");
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");
Special Case: env object
When lua is called as the hangup hook there will be a special env object that contains all the channel variables from the channel that just disconnected.
Add an extension to test this feature:
<extension name="lua-env-hangup-hook-test">
<condition field="destination_number" expression="^(1234)$">
<action application="answer"/>
<action application="set" data="api_hangup_hook=lua hook-test.lua"/>
<action application="set" data="my_custom_var=foobar"/>
<action application="sleep" data="10000"/>
<action application="hangup"/>
</condition>
</extension>
Then add freeswitch/scripts/hook-test.lua:
-- hook-test.lua
-- demonstrates using env to look at channel variables in hangup hook script
-- See everything
dat = env:serialize()
freeswitch.consoleLog("INFO","Here's everything:\n" .. dat ..\nn")
-- Grab a specific channel variable
dat = env:getHeader("uuid")
freeswitch.consoleLog("INFO","Inside hangup hook, uuid is: " .. dat .. "\n")
-- If you created a custom variable you can get it also...
dat = env:getHeader("my_custom_var")
freeswitch.consoleLog("INFO","my_custom_var is '" .. dat .. "'\n")
Watch the FS console and dial 1234 and then hangup. You'll see all your channel variables!

