第八章 Lua FreeSWITCH 脚本

        执行脚本来处理呼叫,这是实现复杂FreeSWITCH应用的一种常见方式。当你感觉在XML拨号方案中处理条件控制逻辑太耗脑力时,是时候开始编写脚本了。

       要点是你在拨号方案中创建一个extension,而这个extension只有一行代码:执行脚本!然后脚本就把剩下的事全做了。好像这么一句台词:我们已经尽力了,其它就靠脚本的意志力了

执行脚本的主要优势是使用了恰当的编程语言,它具有轻松管理任何复杂性所需的编程结构(这正是编程语言的用途)。

        在这一章里,我们将首先了解广泛的FreeSWITCH脚本功能,然后深入研究Lua脚本,这些概念和构造同样有助于理解FreeSWITCH所支持的其它脚本语言。

 

支持的脚本语言

        FreeSWITCH支持多种脚本语言,可以从拨号方案调用脚本,也可以从命令行调用。对于每种语言,都有一个具体的FreeSWITCH模块实现拨号方案和命令行接口。

        最好的一点是,所有这些支持都来源于同一个代码库:我们使用SWIG(Simplified Wrapper and Interface Generatorhttp://www.swig.org/)令FreeSWITCH内核可访问脚本。

        使用SWIG的最大好处是:在每种生成脚本语言中,FreeSWITCH对象、方法和函数看起来都是一样的,具体相同的参数表和返回值。事实上,所有脚本语言调用的都是FreeSWITCH内核库(和FreeSWITCH可执行文件使用的库是同一个)提供的同一套API。SWIG确保对所有脚本语言以相同的方式调用库接口,而不是重复工作,避免混淆调用API的不同方法。

        因此,对于如何编写FreeSWITCH脚本,基本上只需要一份文档,然后每个具体脚本语言再添加它自己的语法。正因如此,把一个应用程序从一种脚本语言转换成另一种脚本语言是一件很简单的事。

        Freeswitch通过SWIG支持的最流行的语言如下:

  • Lua
  • Perl
  • JavaScript
  • Python
  • 所有.NET,Mono和托管语言(C#等)
  • Java

 

        无论如何,在初始化特定语言对象之后,你终将调用FreeSWITCH API(与FreeSWITCH命令行上相同),执行拨号方案APP,或与FreeSWITCH事件交互。

 

为什么是Lua?

        那么,哪种脚本语言更适合于FreeSWITCH呢?

        这里根本没什么科学可言,但又处处是科学。是的Lua标榜的是更容易嵌入,运行更快,但这又能怎样?

        FreeSWITCH里的脚本有一个精准的角色定位:为复杂应用程序扩充拨号方案。也就是说,用拨号方案进行高CPS调度。你也可以在FreeSWITCH中自定义C语言模块来实现。我敢打赌,在任何真实场景中,使用脚本都是最正确的选择,不同脚本语言间并没有显著的性能差距。内在延时、数据库查询、用户的响应时间,所有这些开销都远远大于脚本初始化/执行的时间。换句话说,你只需要在两方面间平衡你的语言选择:你对它的熟悉程度(这可以让你马上见到成效);和它在FreeSWITCH中的流行程度(越流行,碰到问题时,越容易在社区论坛、IRC、HipChat或邮件列表中找到答案)。

        在FreeSWITCH社区中,最流行的语言就是Lua。显然,选择Lua有许多好处:至少有很多现成的简单示例。如果你在脚本语言的 选择上犹豫不决,那么我们推荐Lua。其它常用的选择还有Perl、JavaScript,还有Python。

        即使你选择其它语言,这章的内容也会对你有所帮助:你将不得不修改语法和结构以适配你的语言,但所有概念依然适用,而且示例代码翻译成其它语言也不困难。

 

基础语法

        Lua的语法无论读还是写都很简单。以下是一个简单的脚本实例:

-- 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

 

      把它存储为/usr/local/freeswitch/scripts/test2.lua,然后在FreeSWITCH控制台上调用它,命令是lua test2.lua ciao cucu:

 

Lua 与 FreeSWITCH

        在FreeSWITCH调用Lua脚本会自动实例化一个对象:"freeswitch"对象(从中可以创建其它FreeSWITCH相关的基础对象)。

        在FreeSWITCH控制台,有两种方式可以执行一个Lua脚本:lua和luarun。如果你用lua命令执行脚本,控制台会一直堵塞到lua脚本返回为止,就像是控制台线程本身在执行脚本似的。只有在脚本退出之后,你才能看到控制台消息。如果用luarun命令替代,会创建一个完全独立于控制台的新线程来执行脚本(在这种情况下,脚本将无法访问流对象)。

        如果在拨号方案中调用Lua脚本(呼叫匹配到某个extension,而它有一个action是执行lua),那么系统会自动实例化另一个可用对象:session。Session对象描述call leg,这样你就能与之互动(应答,播放媒体,捕获DTMF,挂机,等等)。让我们先玩一会Lua中的session对象。首先,创建一个新的extension,让它在用户拨打9910时执行Lua脚本。编辑文件/usr/local/freeswitch/conf/dialplan/default/01_lua_example.xml,修改为:

<extension name="Simple Lua Test">

<condition field="destination_number" expression="^(9910)$">

<action application="lua" data="test3.lua"/>

</condition>

</extension>

        保存文件,启动fs_cli,执行reloadxml,现在我们的拨号方案已经准备好调用名为test3.lua的脚本。万事俱备,只欠东风,在freeswitch/scripts/目录创建一个test3.lua脚本,并添加以下代码:

-- 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")

 

      保存脚本。这时不需要reloadxml或其它任何动作:如果脚本被修改了,会自动从文件系统中重载。用一部注册到FreeSWITCH平台的话机,拨打9910。你会听到播放的提示音,最后FreeSWITCH挂断通话。

        在呼叫之前,可以把日志等级调到6或7,去研究一下FreeSWITCH到底发生了什么(LOG等级7是DEBUG级别,会完整输出内部日志信息,我们这里为了截图,用的是6级,INFO级别):


现在,让我们研究一下FreeSWITCH Lua脚本中,你可以看到或创建的各种对象。

 

freeswitch

        它是其中的主对象,你可以用它提供的方法创建其它FreeSWITCH相关的对象(后叙)。此外,它还提供了大量的非创建对象的方法,以下是最重要的几个:

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

freeswitch.Session 与会话

        这个对象描述一个call leg。创建外呼call leg时,向freeswitch. Session传递一个拨号串作为参数:

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

 

        让我们测试一个示例脚本,它首先呼叫1011的注册用户,然后发起另一路通话,呼叫1010。最后把两个call leg桥接在一起,让两个用户直接通话。我们把脚本另存为/usr/local/freeswitch/scripts/test4.lua,然后在FreeSWITCH控制台调用它,先执行lua test4.lua,再执行luarun test4.lua(luarun不会堵塞控制台)。下面是脚本代码:

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

         当我们从拨号方案(而不是从控制台)执行FreeSWITCH Lua脚本时,你就已经拥有一个名为session的对象,它描述A-leg信息。让我们重写前面的示例脚本,改为拨号方案版本,并把脚本另存为/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

接下来,在新建一个extension,编辑/usr/local/freeswitch/conf/dialplan/default/02_lua_example.xml文件,添加以下内容:

<extension name="Another Lua Test">

<condition field="destination_number" expression="^(9911)$">

<action application="lua" data="test5.lua"/>

</condition>

</extension>

        在控制台上输入reloadxml激活新的extension。这回,我们拨打9911,脚本会应答我们的呼叫,然后向1010发起呼叫,1010摘机后,两路通话被桥接在一起。

         执行这些脚本过程中,你得到另一个重要提示:当两条腿成功桥接之后,脚本会在"bridge"那一行挂起,直到某一方挂机(也就是桥接结束)。当桥接建立时,我们的脚本只是简单地等待桥接结束。如果你希望桥接后立即继续执行后续代码,你需要在主脚本中另起一个线程来执行桥接,并且在新线程中需要查找每个条call leg的uuid,然后调用bridge(这是一个非常高级的主题,我们这里不深入探讨)。Session对象结合API对象,是FreeSWITCH脚本中使用频率最高的。它们提供了许多方法,对应于处理call leg的各种操作和交互。这些方法很多都是平行/类似于拨号方案的APP。

        下面是相关方法的简表,你可以在https://freeswitch.org/confluence搜索Lua API相关文档,找到具体的解释的用法说明。在本章的后续内容中,我们会用到其中一些方法(Lua实现XML演示中的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

      这是FreeSWITCH Lua脚本的第二主力。API对象允许你向FreeSWITCH发送API命令,就像你在控制台一样。

       API命令由mod_commands模块和其它许多模块共同提供(事实上,几乎所有模块都会提供附加的API命令)。通过FreeSWITCH的控制台,你可以查看平台提供的所有API命令(help命令也可以):

show api

        让我们看看freeswitch.API对象的工作原理,编写脚本文件/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")

        这个脚本首先创建API object,然后通过它与FreeSWITCH服务器互动,就像你在使用控制台一样(它发送version、status、sofia status这几条信息查询命令,并把API命令的返回信息输出到控制台)。在执行几条信息查询命令之后,脚本更进一步,向注册的1011用户发起呼叫。然后执行二十次show channels命令并回显返回的信息,最后暂停1秒并挂机。

        你可以在控制台上通过lua test6.lua命令执行脚本,这会挂起一会,直到脚本退出才刷新信息。你可以通过luarun test6.lua命令调用脚本,这时你就能实时看到脚本的执行过程:


        此外,你可以在控制台外使用“调用Lua脚本”的技术。你可以在shell终端或cron脚本上执行以下命令之一:

 

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

 

        显然,这时你看不到像控制台那样的日志输出。如果你需要回显这些信息,你可以编辑脚本,把类似这样的代码删掉:

 

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

.."\n")

替换成以下格式:

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

 

        替换后,如果通过luarun执行脚本,这时日志机制不工作(luarun 无法访问 stream 对象)。因此,你需要在脚本退出处收集打印输出信息。

freeswitch.Dbh

        Dbh对象是FreeSWITCH本身支持的各种数据库的句柄,即:SQLite(一种基于文件的数据库,适用于温和的,没有大量并发访问的场景);PostgreSQL(重型、企业级、运营商级的,支持高并发的数据库);和ODBC(ODBC是一种中间件,它能够访问所有支持ODBC驱动的数据库。是的,MySQL是其中的一种。ODBC是FreeSWITCH连接MySQL和MariaDB的方式。顺便说一下,当初我将ODBC移植到Unix的时候,还是始祖鸟在空中游荡的时期)。

       处理数据库的另一种办法是使用LuaSQL相关内容请查询https://freeswitch.org/confluence

        Dbh对象提供了一种来自FreeSWITCH数据库池机制的句柄。这些是FreeSwitch管理的数据库连接,因此它们总是随时可用。使用它,在每次需要数据连接(在每次执行的脚本中)时,不会增加打开数据库连接的开销。相反,你连接的是FreeSWITCH,它会维护自己的连接池。也就是说,连接已经打开在那里,由FreeSWITCH管理。你只需要一个句柄。FreeSwitch将在后台根据需要创建连接或增加池中的实际数据库连接数。

        你可以通过Dbh句柄执行查询和其它常用的数据库操作。我们还是通过实例来了解吧,编辑/usr/local/freeswitch/scripts/test7.lua文件,添加以下代码:

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

 

你可以在控制台通过lua test7.lua 命令执行这个脚本:


        脚本的第一部分和任意的数据库交互,它可以是远程PostgreSQL或通过ODBC连接的本地MySQL。为了简单起见,脚本实际上通过连接缺省存放FreeSWITCH  SQLite数据库目录(/usr/local/freeswitch/db)的SQLite数据库my_db来创建Dbh对象。然后它调用test_reactive方法,这个方法执行一次查询,并在出错时执行两条SQL操作:本例是,一个是"drop table",另一个是"create table"。这个方法可以用于测试数据库、表或者结构是否存在,并在需要时创建它们。需要注意的是,如果你在FreeSWITCH运行时删除表,那么需要重启FreeSWITCH,才能保证test_reactive方法工作正常。然后通过query方法与数据库交互(在http://freeswitch.org/confluence上探索"Lua API Reference",以获取打印查询结果的内容细节,也就是怎么对每个返回行设置回调函数),最终释放数据库句柄。

      脚本的第二部分连接FreeSWITCH内部的内核数据库,它是FreeSWITCH保存自己内部数据的地方。这次,我们查询interface表,当你在控制台上执行"show interfaces"命令时,也会查询这张表。

      是的,你可以在FreeSWITCH内部数据库和表中读、写,甚至删除,FreeSWITCH保留其勇气和灵魂。显然,这样做有可能损坏FreeSWITCH。能力越大,责任越大。如果损坏已经产生,请先停止FreeSWITCH,删除/usr/local/freeswitch/db/目录下的所有数据库,或PGSQL|ODBC数据库里的所有表,然后重启FreeSWITCH。启动过程中会重建这些表。

 

freeswitch.Event

        这个对象允许你的脚本向FreeSWITCH注入事件,让它成为FreeSWITCH事件系统的正式成员:

--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();

      以这种方式发送事件可以与FreeSWITCH内核本身、其它模块、自定义模块或其它应用交互。请参考第11章,ESL,通过事件控制FreeSWITCH

      下一节,我们将用一个完整的实例来说明。

 

freeswitch.EventConsumer

这个对象为FreeSWITCH事件系统中的特定事件创建一个侦听者/消费者:

-- 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

 

让我们看看事件的响应过程,把上面代码存为文件: /usr/local/freeswitch/scripts/test8.lua:



        在FreeSWITCH控制台上执行命令:lua test8.lua。请注意:绑定的消息在stream:write()输出内容之后才打印到控制台,换句话说,脚本退出后才打印。

        如果把脚本分为两个将会很有趣,个监听事件,一个发送,并用两个CLI终端上分别执行它(两个以CLI连接同一台FreeSWITCH)。

 

freeswitch.IVRMenu

        这个对象让你可以动态创建XML IVR菜单,以及所有XML  IVR标记,比如greet_long、greet_short,等等。具体请参考上一章内容。

 

Lua 脚本实现 IVR 应用

        自助导航或IVR的概念就是:自动应答来电,然后向呼叫方提供一些选项,用户可以通过DTMF按键选择,在等待用户输入时循环播放菜单,捕获用户输入时执行用户的选择,或者告诉他们输入是错误的,然后继续循环,直到超时或者错误次数超限。操作选项可以要多复杂就多复杂:调用任何外部交互工具,包括数据库、web服务或其它服务。其中有一种选项可以从用户那边收集一长串的DTMF,比如会员卡号。此外,用户可以通过适当选择,从一个菜单跳转到另一个菜单……嘿,又有什么鬼主意了…….

playAndGetDigits

FreeSWITCH脚本中的主唱非她莫属---- Session对象的playAndGetDigits方法(驼峰表示法,第一个字母"p"是小写)。其它一切都围绕着她展开,就像1700年法兰西的"Le Roi Soleil"那样。

 

语法

digits = session:playAndGetDigits (

min_digits, max_digits, max_attempts, timeout, terminators, prompt_audio_files, input_error_audio_files,

digit_regex, variable_name, digit_timeout, transfer_on_failure)

 

参数

 

min_digits

要求输入的最小位数

max_digits

允许输入的最大位数

max_attempts

输入不符合要求时的最大尝试次数,其间会播放 prompt_audio_file 提示音

timeout

等待输入的时间 (单位毫秒)

terminators

触发这个功能结束的字串列表

prompt_audio_file

需要播放的初始语音文件 。一旦收到DTMF就停止播放。每次超时都会重播,最多播放 max_attempts 指定的次数。

input_error_audio_file

如果捕获的内容与digit_regex不匹配,播放这个文件。在这个文件播放期间,收到的DTMF会被丢弃。如果不用这个特性,把它置空

 

digit_regex

用于验证输入的正则表达式

variable_name

(可选的)用于存储接由内容的通道变量

digit_timeout

(可选)两位输入之间的等待间隔(单位毫秒)。如果设置.,在输入每个数字后重置超时时钟,从而使机动性有限的用户能够在不引起超时的情况下缓慢输入数字。如果没有指定,则将digit_timeout设置为timeout。

transfer_on_failure

(可选) 处理失败时,这个函数将把会话转移到拨号方案中的指定 extension。具体语法是 "extension- name [dialplan-id [context]]".

 

行为

  • 当超时并且尝试次数超限时这个方法返回一个空串
  • 一旦输入位数达到允许最大位数,函数立刻返回,即使没有输入结束符也返回
  • 如果用户忘记输入结束符,这时如果输入与某个条目匹配,函数会在下次超时时返回
  • 会话必须先应答才能调用。如果没有应答,声音文件依然会播放,但收集不到用户的输入

Lua实现演示配置中的XML IVR

        下面代码用Lua重写演示配置中的XML  IVR实例,就是那个接入号5000的用例。把它另存为/usr/local/freeswitch/scripts/test9.lua file:

conference_call_session = NIL first_time_main_menu = false first_time_submenu = false



api = freeswitch.API() is_a_demo_user_extension = "false"

-----------------------------------------------------------------------

session:answer()

session:execute("playback", "silence_stream://1000")



if(not session:ready())then goto END end

digits = session:playAndGetDigits (1,4,1,10000, '#','phrase:demo_ivr_main_menu', 'ivr/ivr-that_was_an_invalid_entry.wav', '^\\d$|^10[01][0-9]$','digits',2000)

first_time_main_menu = true goto MAIN_MENU_OPTIONS

-----------------------------------------------------------------------

::MAIN_MENU_SECOND_TIME::

if(not session:ready())then goto END end if(conference_call_session) then goto END end digits = session:playAndGetDigits (1,4,2,10000,

'#','phrase:demo_ivr_main_menu_short', 'ivr/ivr-that_was_an_invalid_entry.wav', '^\\d$|^10[01][0-9]$','digits',2000)

first_time_main_menu = false

goto MAIN_MENU_OPTIONS

-----------------------------------------------------------------------

::MAIN_MENU_OPTIONS::

if(not session:ready())then goto END end if(conference_call_session) then goto END end if(digits == "") then

freeswitch.consoleLog("WARNING", "No digits!\n") elseif(digits == "1") then

conference_call_session = freeswitch.Session( "sofia/internal/888@conference.freeswitch.org")



if(conference_call_session:ready()) then conference_call_session:execute("playback",

"silence_stream://1000")

if(not session:ready())then goto END end

if(not conference_call_session:ready())then goto END end freeswitch.bridge(session, conference_call_session)

end

elseif(digits == "2") then

session:execute("transfer", "9196 XML default") elseif(digits == "3") then

session:execute("transfer", "9664 XML default") elseif(digits == "4") then

session:execute("transfer", "9191 XML default") elseif(digits == "5") then

session:execute("transfer", "1234*256 enum") elseif(digits == "6") then

goto SUBMENU



else

is_a_demo_user_extension = api:executeString( "regex " .. digits .. "|^(10[01][0-9])$")



if(is_a_demo_user_extension == "true") then session:execute("transfer", digits .. " XML features")

end

end

if(first_time_main_menu) then goto MAIN_MENU_SECOND_TIME end goto END

-----------------------------------------------------------------------

::SUBMENU::

if(not session:ready())then goto END end digits = session:playAndGetDigits (1,1,1,5000,

'#','phrase:demo_ivr_sub_menu',

'ivr/ivr-that_was_an_invalid_entry.wav', '\\*','digits',2000)

first_time_submenu = true goto SUBMENU_OPTIONS

-----------------------------------------------------------------------



::SUBMENU_SECOND_TIME::

if(not session:ready())then goto END end digits = session:playAndGetDigits (1,1,2,5000,

'#','phrase:demo_ivr_sub_menu_short', 'ivr/ivr-that_was_an_invalid_entry.wav', '\\*','digits',2000)

first_time_submenu = false goto SUBMENU_OPTIONS

-----------------------------------------------------------------------

::SUBMENU_OPTIONS::

if(not session:ready())then goto END end if(digits == "") then

freeswitch.consoleLog("WARNING", "No digits in submenu!\n") elseif(digits == "*") then

goto MAIN_MENU_SECOND_TIME

end

if(first_time_submenu) then goto SUBMENU_SECOND_TIME end goto END

-----------------------------------------------------------------------

::END::

if(session:ready())then

session:execute("playback", "voicemail/vm-goodbye.wav")

end

freeswitch.consoleLog("WARNING", "END!\n")

接下来,在/usr/local/freeswitch/conf/dialplan/default/02_lua_example.xml创建拨号方案的 extension:

<extension name="XML IVR in Lua">

<condition field="destination_number" expression="^(9912)$">

<action application="lua" data="test9.lua"/>

</condition>

</extension>

       在控制台执行reloadxml激活新建的extension,然后呼叫9912。

        如你所见,我们需要一种方式来模拟XML IVR的方法,才能在初次读取菜单或连续处理传递时得到不同的提示音。我们通过执行playAndGetDigits()实现这个目的。代码里第一次调用时的参数表示播放长提示音,并只重复一次;而在后续的传递处理中playAndGetDigits()的参数表示播放提示音并重试两次(最多播放三次,和XML IVR的行为一样)。

        主菜单中,playAndGetDigits的digit_regex参数是'^\\d$|^10[01][0-9]$',它表示一个单位的数字输入或1000到1019这20个号码(正好与演示配置中的本地号码匹配)。如果输入其它任何内容都认为是非法的。我们同时设置最小位数为1,最大位数为4,inter-digits的超时时间是两秒。

        当用户输入一个完整的分机号时(1000...1019),我们执行一个完全没必要的进一步检查,它只是向你展示如何通过API对象使用FreeSWITCH的Perl兼容正则表达式,而不是使用Lua自身那种奇怪的格式(至少我看来是的)。注意is_a_demo_user_extension变量值是一个字符串,不是布尔值。换句话说,它的取值是字符串”true”或” false”,是你发送regex API命令时FreeSWITCH返回的内容。

        在子菜单中,playAndGetDigits只接受星号输入,最小和最大位数都设置为1。

        此外,你还会注意到里面有大量的GOTO与标签,因此,我们可以安全地从一个菜单跳到另一个菜单,并有一个完全模块化的脚本,将来我们可以根据需要插入其他菜单或选项。

 

总是检查状态:session:ready()

        在这个脚本中,有一个几乎强制的状态检查----调用Session对象的ready()方法,它检查通话是否已经摘机,是否被挂机,总之一句话:还有人在吗。

        检查session:ready()非常重要,不仅要知道呼叫者离开应用程序的时间,还要避免陷入循环、等待数据库查询的返回,或者(更糟的是)等待呼叫者给出输入。

      你的脚本可能被卡死而告终,这时FreeSWITCH将无法释放相关资源(脚本和呼叫数据结构等等)。千万慎重!

 

总结

 

       这一章,们研究了Freeswitch中脚本编写的基本原理(任何复杂的事情都是XML拨号方案的补充),以及Freeswitch支持的多种脚本语言。

       然后我们选用Lua作为示例脚本语言,因为它在FreeSWITCH社区中最流行。

       我们研究了FreeSWITCH对Lua脚本提供的各种对象及它们的用法,然后用Lua重写演示IVR逻辑,把它作为一个实际的可扩展应用实例。

       下一章,我们将研究如何使用XML 拨号方案进入下一个层次,深入研究我们之前几乎没有涉及到的概念。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值