FreeSWITCH用Lua脚本实现IVR功能

什么是IVR

在我们的日常生活中,我们拨打10000或者10086后经常会出现一段提示音,该提示音告诉我们按0会进入什么功能,按1会进入什么功能,这就是IVR,在下面的示例中我们使用FreeSWITCH的Lua脚本实现这个IVR功能。

示例

我们接下来实现一个充值业务,流程如下图所示。

录音

我们录制了几个声音文件,用于在lua脚本中提示用户时使用

Lua脚本如下

lua 复制代码
error_prompt = "/root/ivr_audio/input_error.wav" -- 用户按键错误提示
account = ""  -- 账号
password = ""  -- 密码
digits = ""   -- 按键数字
balance = 100  -- 余额
charge = 100 -- 充值卡上的金额
card = ""  -- 卡号
checkPress1_chargePress2 = "/root/ivr_audio/checkPress1_chargePress2.wav"  -- 查询请按1,充值请按2语音文件
input_account = "/root/ivr_audio/inputAccount.wav"  -- 请输入账号,以井号结束
input_password = "/root/ivr_audio/inputPassword.wav" -- 请输入密码,以井号结束
input_charge_cardnumber = "/root/ivr_audio/inputChargeCardNumber.wav"  -- 请输入卡号, 以井号结束
confirmPress1_returnPress2 = "/root/ivr_audio/confirmPress1_returnPress2.wav"  -- 确认请按1,返回请按2

-- 充值操作
function do_charge(account, charge) 
  balance = balance + charge -- 将充值的金额加到了余额上
  return balance
end

-- 主菜单
function main_menu()
  -- 在进入主菜单前判断下当前通话是否还存在
  if not session:ready() then return end

  -- playAndGetDigits参数解析
  -- 第一个1表示最少输入1位数字
  -- 第二个1表示最多输入1位数字
  -- 第三个参数3表示最多可以尝试输入3次
  -- 5000表示收集所有号的超市时间
  -- # 表示输入结束提示符,当我们输入一位数字以后按#结束
  -- checkPress1_chargePress2 为提示用户的提示音
  -- error_prompt 为当用户输入错误以后的提示音
  -- "^1|2$" 为正则匹配用户的输入按键,匹配下是否符合我们的正则表达式
  digits = session:playAndGetDigits(1, 1, 3, 5000, "#", checkPress1_chargePress2, error_prompt, "^1|2$")

  session:execute("log", "INFO main_menu:" .. digits)  -- 打印日志

  if not (digits == "") then
    ask_account(digits)  -- 用户输入按键了
  else
    goodbye()  -- 没有输入按键,再见
  end
end

-- 查询账户
function ask_account(service_type)
  if not session:ready() then return end -- 检查通话是否还是存在的

  -- 获取用户的账户
  digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_account, error_prompt, "\\d{4}$")

  session:execute("log", "INFO account:"  .. digits);

  if not (digits == "") then
    account = digits
    if (service_type == "1") then  -- 查询业务
      ask_account_password()
    else
      ask_card()  -- 充值业务
    end
  else
    goodbye()
  end
end

function ask_account_password()
  if not session:ready() then return end
  
  -- 获取密码
  digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_password, error_prompt, "\\d{4}$")

  session:execute("log", "INFO account password: " .. digits)

  if not (digits == "") then  -- 用户输入了密码,检查下密码是否正确
    password = digits
    check_account_password()
  else
    goodbye()
  end
end

-- 充值业务
function ask_card()
  if not session:ready() then return end

  -- 获取卡号
  digits = session:playAndGetDigits(4, 5, 3, 5000, "#", input_charge_cardnumber, error_prompt, "\\d{4}$")

  session:execute("log", "INFO card number: " .. digits)

  if not (digits == "") then  -- 用户输入了卡号
    card = digits
    check_account_card()
  else
    goodbye()
  end
end

-- 查询余额
function check_account_password()
  if not session:ready() then return end

  if (account == "1111" and password == "1111") then  -- 检查用户的账户和密码是否正确,正确的查询出现在的余额告诉用户
    session:speak("The balance is" .. balance)
    session:sleep(500)  -- sleep 500ms后返回主菜单
    main_menu()
  else
    session:speak("Input error, please re-enter")  -- 输入错误,返回主菜单
    main_menu()
  end
end

-- 充值

function check_account_card()
  if not session:ready() then return end

  if (account == "1111" and card == "2222") then 
    session:speak("You want to charge" .. charge)
    
    -- 获取用户是否充值的按键, 确认充值按1,返回按2
    digits = session:playAndGetDigits(1, 1, 3, 10000, "#", confirmPress1_returnPress2, error_prompt, "^[12]$")

    if digits == "1" then
      balance = do_charge(account, charge)
      session:speak("charge successful, charge is" ..  charge .. ", balance is" .. balance ); 
      main_menu()  -- 充值成功,返回主菜单
    else
      if digits == "2" then
        session:sleep(500)
        main_menu()  -- 用户按2取消充值, 返回主菜单
      else 
        goodbye()
      end
    end

  else
    session:speak("Input error, please re-enter")  -- 输入错误,返回充值业务
    ask_account(2)
  end
end

-- 挂机函数
function goodbye()
  if not session:ready() then return end

  session:speak("Goodbye")
  session:hangup()  -- 挂机
end

-- 设置TTS语音参数
session:set_tts_params("cepstral", "David")

session:answer()  -- 应答
session:speak("welcome")

main_menu()

加载TTS模块

在测试之前我们先要加载TTS模块,具体参考FreeSWITCH配置TTS模块,这一步也可省略,使用session:streamFile播放声音也可以。

测试

添加一个extension,该分机匹配6666号码, 并且修改Dialplan

xml 复制代码
<extension name="ivr-test">
  <condition field="destination_number" expression="^6666$">
     <action application="lua" data="/usr/local/freeswitch/test/ivr.lua"/>
  </condition>
</extension>

重新扫描配置文件

sh 复制代码
sofia profile internal rescan 

拨打6666号码测试

可以看到呼叫走我们写的Lua脚本了

输入1,我们查询下账号余额

sh 复制代码
freeswitch@debianh61> 2025-04-06 17:34:57.564651 99.37% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:01.664651 99.40% [INFO] switch_channel.c:528 RECV DTMF 1:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO main_menu:1)
2025-04-06 17:35:01.664651 99.40% [INFO] mod_dptools.c:1865 main_menu:1
2025-04-06 17:35:01.664651 99.40% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:07.384652 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:07.724620 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:08.144621 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:08.444653 99.30% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:09.624621 99.30% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO account:1111)
2025-04-06 17:35:09.624621 99.30% [INFO] mod_dptools.c:1865 account:1111
2025-04-06 17:35:09.624621 99.30% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:35:14.464653 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:14.764651 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:15.064653 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:15.444621 99.23% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:35:16.264651 99.23% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO account password: 1111)
2025-04-06 17:35:16.264651 99.23% [INFO] mod_dptools.c:1865 account password: 1111

输入2,我们充值

sh 复制代码
freeswitch@debianh61> 2025-04-06 17:36:51.964621 99.40% [INFO] switch_channel.c:528 RECV DTMF 2:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO main_menu:2)
2025-04-06 17:36:51.964621 99.40% [INFO] mod_dptools.c:1865 main_menu:2
2025-04-06 17:36:51.964621 99.40% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:36:56.344627 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:56.744626 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:57.204626 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:57.624651 99.37% [INFO] switch_channel.c:528 RECV DTMF 1:960
2025-04-06 17:36:58.364612 99.37% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO account:1111)
2025-04-06 17:36:58.364612 99.37% [INFO] mod_dptools.c:1865 account:1111
2025-04-06 17:36:58.364612 99.37% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:37:03.604651 99.37% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:03.984651 99.37% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:04.324652 99.33% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:04.664627 99.33% [INFO] switch_channel.c:528 RECV DTMF 2:960
2025-04-06 17:37:05.444621 99.73% [INFO] switch_channel.c:528 RECV DTMF #:960
EXECUTE [depth=0] sofia/internal/1001@192.168.0.38 log(INFO card number: 2222)
2025-04-06 17:37:05.444621 99.73% [INFO] mod_dptools.c:1865 card number: 2222
2025-04-06 17:37:16.084640 99.27% [WARNING] switch_core_file.c:463 File has 2 channels, muxing to 1 channel will occur.
2025-04-06 17:37:22.624621 99.20% [INFO] switch_channel.c:528 RECV DTMF 1:960

参考资料

相关推荐
爱勇宝2 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
IT_陈寒6 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码8 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo8 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志8 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..9 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects9 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰9 小时前
一些基础和问题解决
后端
陈随易9 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN9 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp