Files

248 lines
7.1 KiB
Lua
Raw Permalink Normal View History

2026-03-31 15:46:04 +08:00
--- 模块功能:通话管理
-- @module cc
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.11.2
module(..., package.seeall)
require"ril"
require"pm"
--- 通话中
CONNECTED = 0
--- 通话保持中
HOLD = 1
--- 正在呼出
DIALING = 2
ALERTING = 3
--- 正在呼入
INCOMING = 4
WAITING = 5
--- 正在挂断通话
DISCONNECTING = 98
--- 通话已挂断
DISCONNECTED = 99
local req = ril.request
local publish = sys.publish
--底层通话模块是否准备就绪true就绪false或者nil未就绪
local ccready = false
--通话列表
local call_list = {n= 0}
--通话断开原因
local discReason
--- 是否存在通话
-- @return bool result 存在通话返回true否则返回false
-- @usage result = cc.anyCallExist()
function anyCallExist()
return call_list.n ~= 0
end
--- 查询某个号码的通话状态
-- @string num 查询号码
-- @return number state 通话状态状态值参考本模块Fields定义
-- @usage state = cc.getState('10086')
function getState(num)
return call_list[num] or DISCONNECTED
end
--- 呼出电话
-- @string num 呼出号码
-- @number[opt=0] delay 延时delay毫秒后才发起呼叫
-- @return bool resulttrue表示允许发送at命令拨号并且发送atfalse表示不允许at命令拨号
-- @usage cc.dial('10086')
function dial(num, delay)
if num == "" or num == nil then return false end
pm.wake("cc")
req(string.format("%s%s;", "ATD", num), nil, nil, delay)
call_list[num] = DIALING
return true
end
--- 挂断通话
-- @string num 号码,若指定号码通话状态不对,则直接退出,不会执行挂断,若挂断时会挂断所有电话
-- @return nil
-- @usage cc.hangUp('10086')
function hangUp(num)
if call_list[num] == DISCONNECTING or call_list[num] == DISCONNECTED then return end
if audio and type(audio.stop)=="function" then audio.stop() end
req("AT+CHUP")
call_list[num] = DISCONNECTING
end
--- 接听电话
-- @string num 号码,若指定号码通话状态不对,则直接退出,不会接通
-- @return nil
-- @usage cc.accept('10086')
function accept(num)
if call_list[num] ~= INCOMING then return end
if audio and type(audio.stop)=="function" then audio.stop() end
req("ATA")
call_list[num] = CONNECTING
end
--- 通话中发送声音到对端,必须是12.2K AMR格式
-- @string data 12.2KAMR格式的数据
-- @bool[opt=nil] loop 是否循环发送true为循环其余为不循环
-- @bool[opt=nil] downLinkPlay 声音是否在本端播放true为播放其余为不播放
-- @return bool result true为成功false为失败
-- @usage
-- cc.transVoice("#!AMR\010\060*********")
-- cc.transVoice("#!AMR\010\060*********",true)
-- cc.transVoice("#!AMR\010\060*********",true,true)
function transVoice(data, loop, downLinkPlay)
local f = io.open("/RecDir/rec000", "wb")
if f == nil then
log.error("transVoice:open file error")
return false
end
-- 有文件头并且是12.2K帧
if string.sub(data, 1, 7) == "#!AMR\010\060" then
-- 无文件头且是12.2K帧
elseif string.byte(data, 1) == 0x3C then
f:write("#!AMR\010")
else
log.error('cc.transVoice', 'must be 12.2K AMR')
return false
end
f:write(data)
f:close()
req(string.format("AT+AUDREC=%d,%d,2,0,50000", downLinkPlay == true and 1 or 0, loop == true and 1 or 0))
return true
end
--- 设置dtmf检测是否使能以及灵敏度
-- @bool[opt=nil] enable true使能false或者nil为不使能
-- @number[opt=3] sens 灵敏度最灵敏为1
-- @return nil
-- @usage cc.dtmfDetect(true)
function dtmfDetect(enable, sens)
if enable == true then
if sens then
req("AT+DTMFDET=2,1," .. sens)
else
req("AT+DTMFDET=2,1,3")
end
end
req("AT+DTMFDET=" .. (enable and 1 or 0))
end
--- 发送dtmf到对端
-- @string str dtmf字符串仅支持数字、ABCD*#
-- @number[opt=100] playtime 每个dtmf播放时间单位毫秒
-- @number[opt=100] intvl 两个dtmf间隔单位毫秒
-- @return nil
-- @usage cc.sendDtmf("123")
function sendDtmf(str, playtime, intvl)
if string.match(str, "([%dABCD%*#]+)") ~= str then
log.error("sendDtmf: illegal string " .. str)
return false
end
playtime = playtime and playtime or 100
intvl = intvl and intvl or 100
--req("AT+SENDSOUND=" .. string.format("\"%s\",%d,%d", str, playtime, intvl))
req("AT+VTS=".. str)
end
local dtmfnum = { [71] = "Hz1000", [69] = "Hz1400", [70] = "Hz2300" }
local function parsedtmfnum(data)
local n = tonumber(string.match(data, "(%d+)"))
local dtmf
if (n >= 48 and n <= 57) or (n >= 65 and n <= 68) or n == 42 or n == 35 then
dtmf = string.char(n)
else
dtmf = dtmfnum[n]
end
if dtmf then
publish("CALL_DTMF_DETECT", dtmf) -- 通话中dtmf解码会产生消息AUDIO_DTMF_DETECT消息数据为DTMF字符
end
end
local function ccurc(data, prefix)
if data == "CALL READY" then --底层通话模块准备就绪
ccready = true
publish("CALL_READY")
req("AT+CCWA=1")
elseif prefix == "+DTMFDET" then
parsedtmfnum(data)
else
if data=="NO CARRIER" or data=="NO ANSWER" or data=="BUSY" then
discReason = data
end
req('AT+CLCC')
if data == "CONNECT" and audio and type(audio.stop)=="function" then audio.stop() end --先停止音频播放
end
end
local function ccrsp(cmd, success, response, intermediate)
if cmd=="AT+CHUP" then
discReason = "CHUP"
end
req('AT+CLCC')
end
--注册以下通知的处理函数
ril.regUrc("CALL READY", ccurc)
ril.regUrc("CONNECT", ccurc)
ril.regUrc("NO CARRIER", ccurc)
ril.regUrc("NO ANSWER", ccurc)
ril.regUrc("BUSY", ccurc)
ril.regUrc("+CLIP", ccurc)
ril.regUrc("+CCWA", ccurc)
ril.regUrc("+DTMFDET", ccurc)
--注册以下AT命令的应答处理函数
ril.regRsp("D", ccrsp)
ril.regRsp("A", ccrsp)
ril.regRsp("+CHUP", ccrsp)
ril.regRsp("+CHLD", ccrsp)
ril.regRsp("+CLCC", function(cmd, success, response, intermediate)
if success then
local new = {n = 0 }
if intermediate and intermediate:len() > 0 then
for id, dir, stat, num in intermediate:gmatch('%+CLCC:%s*(%d+),(%d),(%d),%d,%d,"([^"]*)".-\r\n') do
stat = tonumber(stat)
if stat == WAITING then
req('AT+CHLD=1' .. id)
return
end
if call_list[num] ~= stat then
if stat == INCOMING or stat == CONNECTED then
pm.wake('cc')
publish(stat == INCOMING and 'CALL_INCOMING' or 'CALL_CONNECTED', num)
end
end
new[num] = stat
new.n = new.n + 1
end
end
call_list = new
if new.n == 0 then
publish('CALL_DISCONNECTED',discReason)
discReason = nil
pm.sleep('cc')
end
end
end)
--开启拨号音,忙音检测
req("ATX4")
--开启来电urc上报
req("AT+CLIP=1")
req("ATS7=60")
req("AT+SETVOLTE=1")