Scripting FreeSWITCH Lua

In this article, Giovanni Maruzzelli and Anthony Minessale II, the author of the book FreeSWITCH 1.8, we execute scripts to answer incoming calls is a common way to implement complex FreeSWITCH applications. When you feel you are putting too much of your brain power into constructing complex conditional XML dialplan extensions, it's time to start scripting.

(For more resources related to this topic, see here.)

The gist is that you create an extension in the dialplan, and this extension will only be one line: execute the script. Then the script will do it all. As they say: You do your best, script do the rest.

The main advantage of executing a script is that you are using a proper programming language, with all the usual programming constructs required to manage whatever complexity with ease (that's what programming languages are made for).

Many scripting languages

FreeSWITCH supports many scripting languages, both from the dialplan and command line. For each language, a specific FreeSWITCH module implements the dialplan and command line interface.

The best thing is that support for all of them comes from the same source code base: we use the Simplified Wrapper and Interface Generator (SWIG, http://www.swig.org/) to make the core FreeSWITCH library accessible to scripting.

The main advantage of using SWIG is that, in each resulting scripting language, the FreeSWITCH objects, methods, and functions all look the same, with the same arguments and return values. In fact, all the scripting languages access the same APIs from the FreeSWITCH core library (the same library used by the FreeSWITCH executable itself). SWIG ensures that this library interfacing is done in the same way for all scripting languages, instead of duplicating efforts, avoiding confusing different ways to call into the APIs.

So, there is basically only one documentation effort on how to script FreeSWITCH, and then each specific scripting language will add its own syntactic sugar. This even makes it very easy to rewrite an application from one scripting language to another, should the need arise.

The most popular languages supported by FreeSWITCH via SWIG are as follows:

  • Lua
  • Perl
  • JavaScript
  • Python
  • All .NET, mono, and managed languages (C# and so on)
  • Java

Anyway, after initializing language-specific objects, you will end up calling FreeSWITCH APIs (the same as you use from the FreeSWITCH console command line), executing dialplan applications, or interacting with FreeSWITCH Events.

Why Lua?

So, which language is better for FreeSWITCH scripting?

There is no science at all, here – all here is broscience. Yes, Lua is definitely more embeddable and faster than other languages; so what?

Scripting in FreeSWITCH has a precise role: to complement the dialplan for complex applications. That is, you use the dialplan for high-cps call dispatching. Or you use a custom C FreeSWITCH module.I would bet that, in any real-world scenario where scripting is used in its correct role, there is no meaningful performance gap between different scripting languages. Network delays, database queries, and caller reaction times will always bury whatever script initialization/execution time.

That said, you balance your language choice between two sides: the language(s) you already know (this will make you immediately productive), and the popularity of its use with FreeSWITCH (so you will have immediate answers when you ask the community in IRS, HipChat, ora mailing list).

The most popular scripting language in the FreeSWITCH community, by a large margin, is Lua. Obviously, this has many benefits, such as a lot of easily accessible examples, and so on. If you are in any doubt about which language to use, use Lua. Other popular choices are Perl, JavaScript, and Python.

Basic Lua syntax

Lua has a simple syntax that is easy to both learn and read. The following is a simple script:

-- This is a sample Lua script
-- Single line comments begin with two dashes
--[[
This is a multi-line comment.
Everything between the double square brackets
is part of the comment block.
]]
-- Lua is loosely typed
var = 1         -- This is a comment
var ="alpha"   -- Another comment
var ="A1"      -- You get the idea...
-- Lua makes extensive use of tables
-- Tables are a hybrid of arrays and associative arrays
val1 = 1
val2 = 2
my_table = {
key1 = val1,
key2 = val2,
"index 1",
"index 2"
}
--[[
When the Lua script is called from FreeSWITCH
you have a few magic objects. The main one is
the 'freeswitch' object:
freeswitch.consoleLog("INFO","This is a log line\n")

If script is executed from dialplan (eg: there is 
an incoming call to manage) you have the  'session' 
object which lets you manipulate the call:
session:answer()
session:hangup() 
]]
freeswitch.consoleLog("INFO","my_table key1 is '" .. my_table["key1"] .."'\n")
freeswitch.consoleLog("INFO","my_table index 1 is '" .. my_table[1] .."'\n")
-- Access arguments passed in
arg1 = argv[1]      -- First argument
arg2 = argv[2]      -- Second argument
-- Simple if/then
if ( var =="A1" ) then
freeswitch.consoleLog("INFO","var is 'A1'\n")
end
--  Simple if/then/else
if ( arg1 =="ciao" ) then
freeswitch.consoleLog("INFO","arg1 is 'ciao'\n")
else
freeswitch.consoleLog("INFO","arg1 is not 'ciao'!\n")
end
-- String concatenation uses ..
var ="This" .." and " .. "that"
freeswitch.consoleLog("INFO","var contains '" .. var .."'\n")
freeswitch.consoleCleanLog("This Rocks!!!\n");
-- The end

Save this script as /usr/local/freeswitch/scripts/test2.lua, and then call it from the FreeSWITCH console, as lua test2.lua ciao cucu:

FreeSWITCH 1.8

Lua and FreeSWITCH

Calling a Lua script from FreeSWITCH makes one objectavailable: the "freeswitch" object (from which you can create other basic FreeSWITCH-related objects; see later).

You can execute a Lua script from FreeSWITCH console in two ways: lua and luarun. If you execute the script with lua, it will block your console until the script returns, as though it was the console thread itself to execute the script. Only after the script has exited will you see console messages. If instead you use luarun, a new thread will be created that will run the script (in this case the script will have no access to the stream object; see later) completely independently from the console.

If the Lua script has been called from dialplan (an incoming call matches an extension where a lua action is executed), then an additional object is automatically already available: session. The object session represents the call leg and lets you interact with it (answer, play media, get DTMFs, hangup, and so on).

Let's play a little with the session Lua object. First, create a new dialplan extension that will execute the Lua script when a user dials 9910. Edit the file /usr/local/freeswitch/conf/dialplan/default/01_lua_example.xml to be:

<extension name="Simple Lua Test">
<condition field="destination_number" expression="^(9910)$">
<action application="lua" data="test3.lua"/>
</condition>
</extension>

Save the file, launch fs_cli, and issue reloadxml. Our dialplan is now ready to call the Lua script named test3.lua. Then create the test3.lua script in the freeswitch/scripts/ directory, and add the following code lines:

-- test3.lua
-- Answer call, play a prompt, hang up
-- Set the path separator
pathsep = '/'
-- Windows users do this instead:
-- pathsep = '\'
-- Answer the call
freeswitch.consoleLog("WARNING","Not yet answered\n")
session:answer()
freeswitch.consoleLog("WARNING","Already answered\n")
-- Create a string with path and filename of a sound file
prompt ="ivr" ..pathsep .."ivr-welcome_to_freeswitch.wav"
-- Play the prompt
freeswitch.consoleLog("WARNING","About to play '" .. prompt .."'\n")
session:streamFile(prompt)
freeswitch.consoleLog("WARNING","After playing '" .. prompt .."'\n")
-- Hangup
session:hangup()
freeswitch.consoleLog("WARNING","Afterhangup\n")

Save the script. There is no need to reloadxml or anything else: scripts are automatically reloaded from filesystem is they have been modified. Using a phone that is registered on your FreeSWITCH server, dial 9910. You will hear the sound prompt being played, and then FreeSWITCH will hangup the call.

Before placing the call, you set the loglevel to 6 or 7, to see what's happening inside FreeSWITCH (level 7 is DEBUG, and will show you the complete internals. Here we use level 6, INFO, for the sake of the screenshot):

FreeSWITCH 1.8

Now, let's have a look at the various objects you'll find or create in a Lua FreeSWITCH script.

freeswitch

This is the main object, and you use its methods to create all the other FreeSWITCH-related objects (see following).Also, it has a number of non-object-creating methods, the most important of which are as follows:

  • freeswitch.consoleLog("warning","lua rocks\n")
  • freeswitch.bridge(session1, session2)
  • freeswitch.msleep(500)
  • my_globalvar = freeswitch.getGlobalVariable("varname")

freeswitch.Session and session

This object represents a call leg. You create an outbound call leg, giving freeswitch. Session a dialing string as an argument:

new_leg = freeswitch.Session("user/1011")

Let's test an example script that originates a call to a phone registered as 1011, then originates another leg to another phone (registered as 1010), and bridge the two legs so they can talk. Save this script as /usr/local/freeswitch/scripts/test4.lua, and then call it from the FreeSWITCH console, typing lua test4.lua, then run it again typing luarun test4.lua (hint: with luarun the console will not block):

freeswitch.consoleLog("WARNING","Before first call\n")
first_session = freeswitch.Session("user/1011")
if (first_session:ready()) then  
freeswitch.consoleLog("WARNING","first leg answered\n")
second_session = freeswitch.Session("user/1010")  
if (second_session:ready()) then
freeswitch.consoleLog("WARNING","second leg answered\n")  
freeswitch.bridge(first_session, second_session)
freeswitch.consoleLog("WARNING","After bridge\n")  
else
freeswitch.consoleLog("WARNING","second leg failed\n")  
end
else  
freeswitch.consoleLog("WARNING","first leg failed\n")
end

When the FreeSWITCH Lua script has been executed from dialplan (as opposed to executing the script from the console), you have a session object named session already existing, representing the inbound A-leg. Let's rewrite the previous example to be called from dialplan, edit the file /usr/local/freeswitch/scripts/test5.lua:

freeswitch.consoleLog("WARNING","before first leg answered\n")
session:answer()
if (session:ready()) then
freeswitch.consoleLog("WARNING","first leg answered\n")
second_session = freeswitch.Session("user/1010")
if (second_session:ready()) then
freeswitch.consoleLog("WARNING","second leg answered\n")
freeswitch.bridge(session, second_session)
freeswitch.consoleLog("WARNING","After bridge\n")
else
freeswitch.consoleLog("WARNING","second leg failed\n")
end
else
freeswitch.consoleLog("WARNING","first leg failed\n")
end

Then, create the dialplan extension, in the /usr/local/freeswitch/conf/dialplan/default/02_lua_example.xml file:

<extension name="Another Lua Test">
<condition field="destination_number" expression="^(9911)$">
<action application="lua" data="test5.lua"/>
</condition>
</extension>

Type reloadxml from the console to activate the new extension. This time we'll call 9911 from a registered phone. The script will answer our call, then originate another call to the phone registered as 1010, and will bridge the two legs together so we can talk:

FreeSWITCH 1.8

Another important hint you get from the execution of those scripts is that, when two legs are bridged, the script will not continue to run until one of the two legs hangs up (so, the bridge ends). While the bridge is up, our script simply waits for the bridge to end. If you want the script to continue immediately after bridging, you need to spawn another thread, find the call legs from it, and bridge from it (this is a very advanced topic; we will not further elaborate here).Session objects, perhaps together with API objects, are the ones you will use most often in your FreeSWITCH scripting. They have a lot of methods, corresponding to the various manipulations and interactions you can have with a call leg. Many of them are parallel/similar to dialplan applications. Following is a list of them; you'll find them commented and explained when you search for Lua API Reference in our docs at https://freeswitch.org/confluence/display/FREESWITCH/FreeSWITCH+Explained. We'll see many of them in action later, in "Lua Example IVR":

  • session:answer
  • session:answered
  • session:bridged
  • session:check_hangup_hook
  • session:collectDigits
  • session:consoleLog
  • session:destroy
  • session:execute
  • session:flushDigits
  • session:flushEvents
  • session:get_uuid
  • session:getDigits
  • session:getState
  • session:getVariable
  • session:hangup
  • session:hangupCause
  • session:hangupState
  • session:insertFile
  • session:mediaReady
  • session:originate
  • session:playAndGetDigits
  • session:preAnswer
  • session:read
  • session:ready
  • session:recordFile
  • session:sayPhrase
  • session:sendEvent
  • session:setAutoHangup
  • session:setHangupHook
  • session:setInputCallback
  • session:setVariable
  • session:sleep
  • session:speak
  • session:say
  • session:streamFile
  • session:transfer
  • session:unsetInputCallback
  • session:waitForAnswer

freeswitch.API

This is the second workhorse of FreeSWITCH Lua scripting. The API object allows you to send API commands to FreeSWITCH exactly as if you were at the console.

API commands are provided by mod_commands (look it up in https://freeswitch.org/confluence/display/FREESWITCH/FreeSWITCH+Explained) and by many other modules (in fact, almost all modules provide additional API commands). You can see a list of all API commands available in your FreeSWITCH server by issuing the following command from the FreeSWITCH console (help will do the same):

show api

Let's look at the freeswitch.API object doing its things: edit the file /usr/local/freeswitch/scripts/test6.lua:

freeswitch.consoleLog("WARNING","before creating the API object\n")  
api = freeswitch.API()  
freeswitch.consoleLog("WARNING","after creating the API object\n")  
reply = api:executeString("version")
freeswitch.consoleLog("WARNING","reply is: " .. reply .."\n")
reply = api:executeString("status")
freeswitch.consoleLog("WARNING","reply is: " .. reply .."\n")
reply = api:executeString("sofia status")
freeswitch.consoleLog("WARNING","reply is: " .. reply .."\n")
reply = api:executeString("bgapi originate user/1011 5000")
-- reply = api:executeString("originate user/1011 5000")
freeswitch.consoleLog("WARNING","reply is: " .. reply .."\n")
counter = 0
while(counter < 20) do
reply = api:executeString("show channels")
freeswitch.consoleLog("WARNING","reply #" .. counter .. " is: " .. reply .."\n")
counter = counter + 1
api:executeString("msleep 1000")
end
freeswitch.consoleLog("WARNING","about to hangup all calls in the server\n")
reply = api:executeString("hupall")
freeswitch.consoleLog("WARNING","reply is: " .. reply .."\n")
freeswitch.consoleLog("WARNING","GOODBYE (world)\n")

This script will first create the API object, then use it to interact with the FreeSWITCH server as if it were you at the console (it issues the commands version, status, and sofia status, then print back to the console what the server returned after each API command). After those informative actions, our script takes a more decisive route, and originates an outbound call leg toward a phone registered as 1011 to the server. Then it shows the result of the show channels command twenty times, pausing for one second, and eventually hangs up all the calls on the FreeSWITCH server.

You can execute it from the console by issuing lua test6.lua; it will block your console until the script exits. Or you can execute it issuing luarun test6.lua and you'll see the entire process in real time:

FreeSWITCH 1.8

Also, note that you can use this same execute a Lua script technique from outside the console, that is, from a shell or a cron script, in the following two forms:

fs_cli -x "lua test6.lua"
fs_cli -x "luarun test6.lua"

You will have no console printing of logs, obviously. To get your printouts back you can substitute the following line:

freeswitch.consoleLog("WARNING","reply #" .. counter .. " is: " .. reply .."\n")

You can substitute it as follows:

stream:write("reply #" .. counter .. " is: " .. reply .."\n")

This will not work if you invoke the script with luarun (luarun has no access to the stream object). So, you're stuck collecting your printouts on the script's exit.

freeswitch.Dbh

The Dbh object is a handle to all kinds of databases supported natively by FreeSWITCH, namely: SQLite (a filesystem-based database, suitable for moderate, non-heavy parallel-concurrent access), PostgreSQL (a heavy duty, Enterprise- and Carrier-ready, massively parallel concurrent-access database), and ODBC (ODBC is a middleware that gives you access to all databases that have an ODBC-compatible driver. Let's say that all databases have an ODBC driver. Yes, MySQLhasone, and ODBC is the way you connect to MySQL and MariaDB from FreeSWITCH. By the way, I originally ported iODBC to Unix back when archaeopteryxes were roaming the skies).

An alternative way to deal with databases is to use LuaSQL. Search https://freeswitch.org/confluence/display/FREESWITCH/FreeSWITCH+Explained about it.

The Dbh object gives you a handle from the FreeSWITCH server's database-pooling mechanism. These are database connections that FreeSWITCH manages, so they're always available and ready. This will not incur the overhead of opening a direct database connection each time you need it (in a script that would be per execution). Instead, you connect to the FreeSWITCH server, which will multiplex you in its own pool of database connections. That is to say, the connection is already there, managed by FreeSWITCH. You only need a handle for it. FreeSWITCH will create the connection or grow the number of actual database connections in the pool as needed, in the background.

You can then use the Dbh object to execute queries and all the usual database operations. Let's look at Dbh in action: edit the /usr/local/freeswitch/scripts/test7.lua file as follows:

dbh = freeswitch.Dbh("sqlite://my_db") -- sqlite database in subdirectory "db"
assert(dbh:connected()) -- exits the script if we didn't connect properly
dbh:test_reactive("SELECT * FROM my_table",
"DROP TABLE my_table",
"CREATE TABLE my_table (id INTEGER(8), name VARCHAR(255))")
dbh:query("INSERT INTO my_table VALUES(1, 'foo')") -- populate the table
dbh:query("INSERT INTO my_table VALUES(2, 'bar')") -- with some test data
dbh:query("SELECT id, name FROM my_table", function(row)
stream:write(string.format("%5s : %s\n", row.id, row.name))
end)
dbh:query("UPDATE my_table SET name = 'changed'")
stream:write("Affected rows: " .. dbh:affected_rows() .. "\n")
dbh:release() -- optional
------
------
dbh2 = freeswitch.Dbh("sqlite://core") -- sqlite database in subdirectory "db"
assert(dbh2:connected()) -- exits the script if we didn't connect properly
dbh2:query("select name,filename from interfaces where name='luarun'", function(row)  
stream:write("name is ".. row.name .. " while module is " ..row.filename)
end)
dbh2:release() -- optional

You can run this script from console issuing lua test7.lua

FreeSWITCH 1.8

The first part of this script interacts with an arbitrary database, it could be a remote PostgreSQL or a local MySQL reached via ODBC. For the sake of simplicity, the script actually creates the Dbh object by connecting to the my_db SQLite database in the default FreeSWITCH directory for SQLite databases (/usr/local/freeswitch/db).Then it use the test_reactive method, which runs a query, and in case of error performs two SQL operations: in this case a drop table, and a create table. This method is useful to check if a database, table, or schema exists, and create it if needed. Be aware: if you delete the table while FreeSWITCH is running, you will need to restart FreeSWITCH for thetest_reactive method to work properly. Then it uses the query method to interact with the database (search for Lua API Reference at http://freeswitch.org/confluence for details on how to print the query results, for example, the callback function that will run for each returned row), and eventually release the database handle.

The second part of the script connects to the FreeSWITCH internal core database, where FreeSWITCH keeps its own internal data. In this case we query the interface table, the same table that is queried when you issue show interfaces from the console.

So, yes, you can read, write, and delete from FreeSWITCH internal databases, where FreeSWITCH keeps its guts and soul. Obviously, you can damage FreeSWITCH by doing that. With great power comes great responsibility. If damage you done, stop FreeSWITCH, delete all databases into the /usr/local/freeswitch/db/ directory, or all tables in the PGSQL|ODBC databases, and restart FreeSWITCH. They will be re-created at startup.

freeswitch.Event

This object allows your script to inject an event in FreeSWITCH, which will then be a full member of the FreeSWITCH Event System:

--Create Custom event
event = freeswitch.Event("custom", "dial::dial-result")     
event:addBody("dial_record_id: " .. dial_record_id .. "\n" ..
"call_disposition: " .. Disposition .. "\n" ..
"campaign_number: "  .. Campaign .. "\n" ..
"called_number: "    ..dial_num .."\n")
event:fire();

Events sent this way can interact with FreeSWITCH core itself, or with modules, or be consumed by custom modules or applications.

freeswitch.EventConsumer

This object creates a listener/consumer for a certain kind of event in FreeSWITCH Event System:

-- You may subscribe to specific events if you want to, and even subclasses
-- con1 = freeswitch.EventConsumer("CUSTOM")
-- or
-- con2 = freeswitch.EventConsumer("CUSTOM","conference::maintenance")

--listen for an event specific subclass
con3 = freeswitch.EventConsumer("CUSTOM", "ping::running")

-- create an event in that specific subclass
event = freeswitch.Event("CUSTOM", "ping::running")
event:addHeader("hi", "there")
-- fire the event
event:fire()

-- look in the listened events stack for events to "pop" out, block there for max 20 seconds
received_event = con3:pop(1, 20000)
if(received_event) then
stream:write("event received\n")
stream:write(received_event:serialize("text"))
else  
stream:write("no event\n")
end

Let's look at events in action: edit the /usr/local/freeswitch/scripts/test8.lua file:

FreeSWITCH 1.8

You execute this script issuing lua test8.lua from the FreeSWITCH console. Note that the system messages that tell us the script binded to listening for events are printed after the output of stream: write, so, after our script has exited.

It would be interesting to split the script into two, one that listens and one that sends, and execute them using two different console instances on the same FreeSWITCH server.

freeswitch.IVRMenu

This object allows you to create XML IVR menus on the fly, with all the XML IVR trappings such as greet_long, greet_short, and so on.

Summary

In this article, we looked at the extensive FreeSWITCH scripting capabilities, and then we delved into Lua FreeSWITCH scripting, examining concepts and constructs that will also be useful for the other scripting languages supported by FreeSWITCH. 

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

FreeSWITCH 1.8

Explore Title