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/[email protected] 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/[email protected] 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/[email protected] 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/[email protected] 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/[email protected] 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/[email protected] 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

参考资料

相关推荐
程序员清风6 分钟前
Redis Pipeline 和 MGET,如果报错了,他们的异常机制是什么样的?
java·后端·面试
审计侠32 分钟前
Go语言-初学者日记(四):包管理
开发语言·后端·golang
Aska_Lv39 分钟前
Linux---jstat命令的作用
后端
嘻嘻哈哈开森1 小时前
从零开始学习模型蒸馏
人工智能·后端
DataFunTalk1 小时前
大模型时代数据科学岗位的未来思考
前端·后端·算法
阮瑭雅2 小时前
Java语言的Web安全
开发语言·后端·golang
编程乐趣2 小时前
UnitOfWork:一个支持多数据库,工作单元模式、支持分布式事务以及支持 MySQL 多数据库/表分片的开源项目
后端
东方雴翾2 小时前
Dart语言的3D可视化
开发语言·后端·golang
嘉然今天吃粑粑柑2 小时前
实时通讯压缩调研
后端
雷渊2 小时前
深入分析学习 Arthas 在项目中的应用
java·后端·面试