1. 功能环境说明
某市区送水公司安装点星PBX呼叫中心,基本的电话功能有:客户呼入请求送水服务,坐席呼出回访,微信公众号使客户可以关注后在线购买桶装水套餐。除了上述功能外,点星PBX还支持客户电话呼入,并通过IVR语音导航调用PHP程序查询客户套餐余额或剩余桶装水数量信息,并通过TTS引擎将查询结果转wav语音文件,再播报给来电客户。
本案例假设信息如下:
送水公司 已经安装点星PBX呼叫中心系统,且系统的对外服务热线号码为: 77778888
版本要求: 需要点星PBX v3.7或以上版本,点星PBX v3.7下载地址:
http://www.dotasterisk.cn/thread-2240-1-1.html
2. 由于我们只是给出实现上述功能的原理,所以先给出简单流程图,如下。
本文主要讨论上面绿色流程环节的实现原理和设置。
3. 呼叫中心设置
1)在呼叫中心后台菜单【PBX呼叫设置】——【可编程配置】——【可编程目的地】添加一个"自助查询套餐余额"的AGI脚本目的地,如下图。
请注意此处的 "dial分机字符串"字符串要按照规矩提示填写,该字符串必须符合点星PBX的拨号分机规则。后续操作需要自己编写基于该拨号字符串的拨号规则脚本,开发者需要略微懂一点asterisk的拨号规则。
2)新建IVR导航,指定按键3动作的下一步流程为"可编程目的地",如下图。
3)在点星PBX的拨号规则脚本中编写AGI脚本,实现相关功能。此处先编写拨号规则,需要自定义拨号规则的文件如下。
/opt/app/ast/asterisk/exten_program_custom.conf
加入内容如下:
[root@da36:~]#cat /opt/app/ast/asterisk/exten_program_custom.conf
[agi-program]
exten => **90000,1,NoOp(-----DEBUG: 来电手机号: ${CALLERID(num)})
exten => **90000,n,AGI(queryRestMoney.php)
exten => h,1,Hangup()
[root@da36:~]#
4)准备一张 测试数据表client_water,表的内容很简单: 三个主要的field为"客户手机号"、"剩余水费"、“剩余桶”。表结构如下。
创建sql表数据如下:
CREATE TABLE `client_water` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`telephone` varchar(16) NOT NULL COMMENT '客户电话',
`rest_money` int(11) DEFAULT '0' COMMENT '剩余金额(元)',
`rest_number` int(11) DEFAULT '0' COMMENT '剩余桶装水数量',
PRIMARY KEY (`id`,`telephone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
预先录入3条数据,以备后续测试,如下图。
上述呼叫流程框架基本设置完成,剩下的主要是php的AGI脚本 queryRestMoney.php 代码编写逻辑。
4. 编写AGI脚本 queryRestMoney.php 。
1)首先需要安装点星PBX适配的TTS文本转语言引擎,下载地址和安装步骤如下。
下载地址: http://www.dotasterisk.cn/iso/tts-rpms.zip
2) 将下载文件上传到点星PBX,用unzip命令解压,并按照如下步骤安装,如下:
先安装依赖rpm包: rpm -ivh rpm-deps/*.rpm
再安装TTS引擎: rpm -ivh TTS-2.0.0-2.x86_64.rpm
如下图。
3). 有两种方式编写AGI脚本。
可以从 https://packagist.org/ 下载 phpagi,然后引入,如下图:
也可以用点星PBX自带的AGI类库进行AGI类对象的初始化,点星自带的AGI类库文件路径为: /opt/app/crm/Common/Libs/Asterisk/AGI.class.php ,原理一样,编码方法大同小异。
下面以 下载的 "d4rkstar/phpagi" 为例子进行代码编写。
首先请将phpagi库里面的 phpagi.php ,phpagi-asmanager.php 两个文件上传到点星PBX的 /opt/app/ast/agi-bin/目录,然后编写AGI脚本 TTS.conf.php 和 queryRestMoney.php,如下。【注意】php脚本务必给执行权限,简单点就是给777权限即可。
先看测试结果,在文章的最后会给出 TTS.conf.php 和 queryRestMoney.php 的源代码。
5 测试结果如下。
有php代码调用tts引擎生成的text文本文件和wav文件在目录 /dev/shm/tts/ 下面。
6. 最后给出大家最期待的源码,代码比较简单,仅仅只是说明原理,仅供测试参考。
TTS.conf.php
<?php
//按键相关配置
define("TIMEOUT",6000);//输入超时时间,毫秒
define("TRY_TIMES",3);//输入提示重试次数
define("DELETE_TMP_TXT_FILE",FALSE);//是否删除脚本生成的txt文件
define("DELETE_TMP_WAV_FILE",TRUE);//是否删除TTS生成的音频文件
//TTS引擎配置
$TTS_cfg_dir = "/opt/SinoVoice/jTTS-6.0/bin";
$TTS_bin_file = "/opt/SinoVoice/jTTS-6.0/Example/test/PlayToFile.exe";
$TTS_tmp_dir = "/dev/shm/tts" ;//本目录权限最好为777,保证asterisk有读写权限
if( !file_exists($TTS_tmp_dir) ) mkdir($TTS_tmp_dir, 0777);
//mysql链接信息配置
$globalConf = parse_ini_file("/opt/app/pbx_linux.conf", TRUE);
define ( 'MY_DB_HOST', $globalConf['crm']['hostname'] );
define ( 'MY_DB_NAME', $globalConf['crm']['dbname'] );
define ( 'MY_DB_USER', $globalConf['crm']['user'] );
define ( 'MY_DB_PWD', $globalConf['crm']['password'] );
$PDO = getPDO();
//todo; 待做功能,可以更好的完善整个流程,asterisk拨号计划相关功能码配置
define("MAIN_MENU_WELCOME","**80000"); //todo: 接通后主菜单Welcome欢迎语
define("HELP","**80001"); //todo: 人工帮助,将呼叫流程转入人工服务(坐席队列)
//MySQL的PDO链接
function getPDO() {
$dsn = 'mysql:host='. MY_DB_HOST .';port=3306;dbname='. MY_DB_NAME;
return new PDO($dsn, MY_DB_USER, MY_DB_PWD);
}
//调用TTS生成WAV音频文件。参数$tts_tmp_text是php脚本临时生成的文本文件
function generateWAV($tts_tmp_text,$tts_tmp_wav){
global $TTS_tmp_dir,$TTS_bin_file,$TTS_cfg_dir;
$cmd = "cd /tmp/; $TTS_bin_file WangJing_e 65001 $tts_tmp_text $tts_tmp_wav";
//$cmd = "cd /tmp/; $TTS_bin_file WangJing_e 65001 $tts_tmp_text $tts_tmp_wav 2>&1"; //调试
//file_put_contents('/dev/shm/tts/cmd.log', __FILE__.':'. $cmd.PHP_EOL, FILE_APPEND); //调试
$retStr = $retCode = NULL;
exec($cmd,$retStr,$retCode);
//file_put_contents('/dev/shm/tts/cmd.log', __FILE__.':'. var_export($retStr, true).PHP_EOL, FILE_APPEND); //调试
if(DELETE_TMP_TXT_FILE){
unlink($tts_tmp_text);//成功转换成WAV音频文件后,就删掉原来的临时 txt文件
}
if($retCode == 0){
return TRUE;
}else{
return FALSE;
}
}
//repeat.wav 重新收听请按9,返回上一层请按星号键,返回主菜单请按井号键,如需帮助请按0
//$lastMenu是上级菜单的功能码,$mainMenu是主菜单的功能码
function playWavFile($tts_tmp,$cidnum,$playContent,$lastMenu=''){
global $agi;
$wav_file = $tts_tmp .".wav";
$agi->stream_file($tts_tmp,'#');
$dtmf = NULL; $try = 0;
do{
//repeat.wav 音频文件内容为: 重新收听请按9 ,返回上一层菜单请按*号键
$dtmf = $agi->get_data("custom/repeat",TIMEOUT,1);
if($dtmf['result'] == '9'){ //重新收听
$try = 0;
$agi->stream_file($tts_tmp,'#');
}elseif($dtmf['result'] == '*'){//返回上一层菜单
if(empty($lastMenu)){
$agi->verbose("WARNING: lastMenu has no value,do nothing");
$try++;//如果没有具体提供上级菜单,就什么也不做
}else{
if(DELETE_TMP_WAV_FILE){ //如果定义了删除临时tts生成的文件,那么就在这里删除
unlink($wav_file);
}
$agi->verbose("debug--jump to: $lastMenu");
$agi->goto_dest('from-internal',$lastMenu,1);
exit; //只要是goto语句,必须在下一行用exit;退出脚本,否则脚本继续执行
}
}elseif($dtmf['result'] == '' && $dtmf['data'] == ''){//返回主菜单(AGI的get_data函数只能用这种方法捕获井号键,别无他法)
if(DELETE_TMP_WAV_FILE){ //如果定义了删除临时tts生成的文件,那么就在这里删除
unlink($wav_file);
}
$agi->goto_dest('from-internal',MAIN_MENU_WELCOME,1);
exit; //只要是goto语句,必须在下一行用exit;退出脚本,否则脚本继续执行
}else{
$try ++;
}
if($try == 3 && DELETE_TMP_WAV_FILE ){
unlink($wav_file);
}
}while($dtmf['result'] == '9' || $try < 3);
}
queryRestMoney.php
#!/opt/php/bin/php -q
<?php
//AGI测试
require_once("TTS.conf.php");
require_once("phpagi.php");
$agi = new AGI();
$cidnum = $agi->request['agi_callerid']; //获得来电号码
//查询数据库
$result = $PDO->query("SELECT * FROM client_water WHERE telephone='{$cidnum}' LIMIT 1");
$row = $result->fetch();
//假设可以查询到数据
$tts_tmp = $TTS_tmp_dir ."/" .$cidnum ."_" .time();
$tts_tmp_text = $tts_tmp .'.txt';
$tts_tmp_wav = $tts_tmp .'.wav';
$playContent = '您的账号余额为' .$row['rest_money']. '元,剩余桶装水数量为' .$row['rest_number']. '桶。';
file_put_contents($tts_tmp_text,$playContent);
if( generateWAV($tts_tmp_text, $tts_tmp_wav) ){
playWavFile($tts_tmp, $cidnum, $playContent, MAIN_MENU_WELCOME); //传参数必须是$tts_tmp,而不是$tts_tmp_wav,因为播放录音时不需要指出文件后缀.wav
}