cc.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. --- 模块功能:通话管理
  2. -- @module cc
  3. -- @author openLuat
  4. -- @license MIT
  5. -- @copyright openLuat
  6. -- @release 2017.11.2
  7. module(..., package.seeall)
  8. require"ril"
  9. require"pm"
  10. --- 通话中
  11. CONNECTED = 0
  12. --- 通话保持中
  13. HOLD = 1
  14. --- 正在呼出
  15. DIALING = 2
  16. ALERTING = 3
  17. --- 正在呼入
  18. INCOMING = 4
  19. WAITING = 5
  20. --- 正在挂断通话
  21. DISCONNECTING = 98
  22. --- 通话已挂断
  23. DISCONNECTED = 99
  24. local req = ril.request
  25. local publish = sys.publish
  26. --底层通话模块是否准备就绪,true就绪,false或者nil未就绪
  27. local ccready = false
  28. --通话列表
  29. local call_list = {n= 0}
  30. --通话断开原因
  31. local discReason
  32. --- 是否存在通话
  33. -- @return bool result 存在通话返回true,否则返回false
  34. -- @usage result = cc.anyCallExist()
  35. function anyCallExist()
  36. return call_list.n ~= 0
  37. end
  38. --- 查询某个号码的通话状态
  39. -- @string num 查询号码
  40. -- @return number state 通话状态,状态值参考本模块Fields定义
  41. -- @usage state = cc.getState('10086')
  42. function getState(num)
  43. return call_list[num] or DISCONNECTED
  44. end
  45. --- 呼出电话
  46. -- @string num 呼出号码
  47. -- @number[opt=0] delay 延时delay毫秒后,才发起呼叫
  48. -- @return bool result,true表示允许发送at命令拨号并且发送at,false表示不允许at命令拨号
  49. -- @usage cc.dial('10086')
  50. function dial(num, delay)
  51. if num == "" or num == nil then return false end
  52. pm.wake("cc")
  53. req(string.format("%s%s;", "ATD", num), nil, nil, delay)
  54. call_list[num] = DIALING
  55. return true
  56. end
  57. --- 挂断通话
  58. -- @string num 号码,若指定号码通话状态不对,则直接退出,不会执行挂断,若挂断时会挂断所有电话
  59. -- @return nil
  60. -- @usage cc.hangUp('10086')
  61. function hangUp(num)
  62. if call_list[num] == DISCONNECTING or call_list[num] == DISCONNECTED then return end
  63. if audio and type(audio.stop)=="function" then audio.stop() end
  64. req("AT+CHUP")
  65. call_list[num] = DISCONNECTING
  66. end
  67. --- 接听电话
  68. -- @string num 号码,若指定号码通话状态不对,则直接退出,不会接通
  69. -- @return nil
  70. -- @usage cc.accept('10086')
  71. function accept(num)
  72. if call_list[num] ~= INCOMING then return end
  73. if audio and type(audio.stop)=="function" then audio.stop() end
  74. req("ATA")
  75. call_list[num] = CONNECTING
  76. end
  77. --- 通话中发送声音到对端,必须是12.2K AMR格式
  78. -- @string data 12.2K,AMR格式的数据
  79. -- @bool[opt=nil] loop 是否循环发送,true为循环,其余为不循环
  80. -- @bool[opt=nil] downLinkPlay 声音是否在本端播放,true为播放,其余为不播放
  81. -- @return bool result true为成功,false为失败
  82. -- @usage
  83. -- cc.transVoice("#!AMR\010\060*********")
  84. -- cc.transVoice("#!AMR\010\060*********",true)
  85. -- cc.transVoice("#!AMR\010\060*********",true,true)
  86. function transVoice(data, loop, downLinkPlay)
  87. local f = io.open("/RecDir/rec000", "wb")
  88. if f == nil then
  89. log.error("transVoice:open file error")
  90. return false
  91. end
  92. -- 有文件头并且是12.2K帧
  93. if string.sub(data, 1, 7) == "#!AMR\010\060" then
  94. -- 无文件头且是12.2K帧
  95. elseif string.byte(data, 1) == 0x3C then
  96. f:write("#!AMR\010")
  97. else
  98. log.error('cc.transVoice', 'must be 12.2K AMR')
  99. return false
  100. end
  101. f:write(data)
  102. f:close()
  103. req(string.format("AT+AUDREC=%d,%d,2,0,50000", downLinkPlay == true and 1 or 0, loop == true and 1 or 0))
  104. return true
  105. end
  106. --- 设置dtmf检测是否使能以及灵敏度
  107. -- @bool[opt=nil] enable true使能,false或者nil为不使能
  108. -- @number[opt=3] sens 灵敏度,最灵敏为1
  109. -- @return nil
  110. -- @usage cc.dtmfDetect(true)
  111. function dtmfDetect(enable, sens)
  112. if enable == true then
  113. if sens then
  114. req("AT+DTMFDET=2,1," .. sens)
  115. else
  116. req("AT+DTMFDET=2,1,3")
  117. end
  118. end
  119. req("AT+DTMFDET=" .. (enable and 1 or 0))
  120. end
  121. --- 发送dtmf到对端
  122. -- @string str dtmf字符串,仅支持数字、ABCD*#
  123. -- @number[opt=100] playtime 每个dtmf播放时间,单位毫秒
  124. -- @number[opt=100] intvl 两个dtmf间隔,单位毫秒
  125. -- @return nil
  126. -- @usage cc.sendDtmf("123")
  127. function sendDtmf(str, playtime, intvl)
  128. if string.match(str, "([%dABCD%*#]+)") ~= str then
  129. log.error("sendDtmf: illegal string " .. str)
  130. return false
  131. end
  132. playtime = playtime and playtime or 100
  133. intvl = intvl and intvl or 100
  134. --req("AT+SENDSOUND=" .. string.format("\"%s\",%d,%d", str, playtime, intvl))
  135. req("AT+VTS=".. str)
  136. end
  137. local dtmfnum = { [71] = "Hz1000", [69] = "Hz1400", [70] = "Hz2300" }
  138. local function parsedtmfnum(data)
  139. local n = tonumber(string.match(data, "(%d+)"))
  140. local dtmf
  141. if (n >= 48 and n <= 57) or (n >= 65 and n <= 68) or n == 42 or n == 35 then
  142. dtmf = string.char(n)
  143. else
  144. dtmf = dtmfnum[n]
  145. end
  146. if dtmf then
  147. publish("CALL_DTMF_DETECT", dtmf) -- 通话中dtmf解码会产生消息AUDIO_DTMF_DETECT,消息数据为DTMF字符
  148. end
  149. end
  150. local function ccurc(data, prefix)
  151. if data == "CALL READY" then --底层通话模块准备就绪
  152. ccready = true
  153. publish("CALL_READY")
  154. req("AT+CCWA=1")
  155. elseif prefix == "+DTMFDET" then
  156. parsedtmfnum(data)
  157. else
  158. if data=="NO CARRIER" or data=="NO ANSWER" or data=="BUSY" then
  159. discReason = data
  160. end
  161. req('AT+CLCC')
  162. if data == "CONNECT" and audio and type(audio.stop)=="function" then audio.stop() end --先停止音频播放
  163. end
  164. end
  165. local function ccrsp(cmd, success, response, intermediate)
  166. if cmd=="AT+CHUP" then
  167. discReason = "CHUP"
  168. end
  169. req('AT+CLCC')
  170. end
  171. --注册以下通知的处理函数
  172. ril.regUrc("CALL READY", ccurc)
  173. ril.regUrc("CONNECT", ccurc)
  174. ril.regUrc("NO CARRIER", ccurc)
  175. ril.regUrc("NO ANSWER", ccurc)
  176. ril.regUrc("BUSY", ccurc)
  177. ril.regUrc("+CLIP", ccurc)
  178. ril.regUrc("+CCWA", ccurc)
  179. ril.regUrc("+DTMFDET", ccurc)
  180. --注册以下AT命令的应答处理函数
  181. ril.regRsp("D", ccrsp)
  182. ril.regRsp("A", ccrsp)
  183. ril.regRsp("+CHUP", ccrsp)
  184. ril.regRsp("+CHLD", ccrsp)
  185. ril.regRsp("+CLCC", function(cmd, success, response, intermediate)
  186. if success then
  187. local new = {n = 0 }
  188. if intermediate and intermediate:len() > 0 then
  189. for id, dir, stat, num in intermediate:gmatch('%+CLCC:%s*(%d+),(%d),(%d),%d,%d,"([^"]*)".-\r\n') do
  190. stat = tonumber(stat)
  191. if stat == WAITING then
  192. req('AT+CHLD=1' .. id)
  193. return
  194. end
  195. if call_list[num] ~= stat then
  196. if stat == INCOMING or stat == CONNECTED then
  197. pm.wake('cc')
  198. publish(stat == INCOMING and 'CALL_INCOMING' or 'CALL_CONNECTED', num)
  199. end
  200. end
  201. new[num] = stat
  202. new.n = new.n + 1
  203. end
  204. end
  205. call_list = new
  206. if new.n == 0 then
  207. publish('CALL_DISCONNECTED',discReason)
  208. discReason =
  209. pm.sleep('cc')
  210. end
  211. end
  212. end)
  213. --开启拨号音,忙音检测
  214. req("ATX4")
  215. --开启来电urc上报
  216. req("AT+CLIP=1")
  217. req("ATS7=60")
  218. req("AT+SETVOLTE=1")