Files
BR_YKC/4G/源代码/lib/sms.lua

714 lines
24 KiB
Lua
Raw Normal View History

2026-03-31 15:46:04 +08:00
--- 模块功能:短信功能
-- @module sms
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.25
require "sys"
require "ril"
require "common"
require "utils"
module(..., package.seeall)
local publish = sys.publish
local req = ril.request
--ready底层短信功能是否准备就绪
local smsReady, isn, tlongsms, netReady, cpinReady,smsUrcReady = false, 255, {}
local ssub, slen, sformat, smatch = string.sub, string.len, string.format, string.match
local tsend = {}
--[[
numtobcdnum
ASCII字符串 BCD编码格式字符串+"+8618126324567" -> 91688121364265f7 10x9120x68......
num
]]
local function numtobcdnum(num)
local len, numfix, convnum = slen(num), "81", ""
if ssub(num, 1, 1) == "+" then
numfix = "91"
len = len - 1
num = ssub(num, 2, -1)
end
if len % 2 ~= 0 then --奇数位
for i = 1, (len - (len % 2)) / 2 do
convnum = convnum .. ssub(num, i * 2, i * 2) .. ssub(num, i * 2 - 1, i * 2 - 1)
end
convnum = convnum .. "F" .. ssub(num, len, len)
else --偶数位
for i = 1, (len - (len % 2)) / 2 do
convnum = convnum .. ssub(num, i * 2, i * 2) .. ssub(num, i * 2 - 1, i * 2 - 1)
end
end
return numfix .. convnum
end
--[[
bcdnumtonum
BCD编码格式字符串 ASCII字符串+91688121364265f7 10x9120x68...... -> "+8618126324567"
num
]]
local function bcdnumtonum(num)
local len, numfix, convnum = slen(num), "", ""
if len % 2 ~= 0 then
print("your bcdnum is err " .. num)
return
end
if ssub(num, 1, 2) == "91" then
numfix = "+"
end
len, num = len - 2, ssub(num, 3, -1)
for i = 1, (len - (len % 2)) / 2 do
convnum = convnum .. ssub(num, i * 2, i * 2) .. ssub(num, i * 2 - 1, i * 2 - 1)
end
if ssub(convnum, len, len) == "f" or ssub(convnum, len, len) == "F" then
convnum = ssub(convnum, 1, -2)
end
return numfix .. convnum
end
--[[
_send
()
num,
data:
istp:
truefalse发送失败
]]
local function _send(num, data,istp)
if istp == "TEXT" or istp == "text" then
req("AT+CSCS=\"IRA\"")
-- req("AT+CSMP?")
-- req("AT+CSCS?")
req("AT+CMGF=1")
table.insert(tsend, {sval = 1, rval = 0, flg = true})--sval发送的包数rval收到的包数
req(string.format("%s%s", "AT+CMGS=",num), string.format("%s",data))
req('AT+CSCS="UCS2"')
req("AT+CMGF=0")
return
end
local numlen, datalen, pducnt, pdu, pdulen, udhi = sformat("%02X", slen(num)), slen(data) / 2, 1, "", "", ""
if not smsReady or not netReady then return false end
--如果发送的数据大于140字节则为长短信
if datalen > 140 then
--计算出长短信拆分后的总条数长短信的每包的数据实际只有134个实际要发送的短信内容数据的前6字节为协议头
pducnt = sformat("%d", (datalen + 133) / 134)
pducnt = tonumber(pducnt)
--分配一个序列号范围为0-255
isn = isn == 255 and 0 or isn + 1
end
table.insert(tsend, {sval = pducnt, rval = 0, flg = true})--sval发送的包数rval收到的包数
if ssub(num, 1, 1) == "+" then
numlen = sformat("%02X", slen(num) - 1)
end
for i = 1, pducnt do
--如果是长短信
if pducnt > 1 then
local len_mul
len_mul = (i == pducnt and sformat("%02X", datalen - (pducnt - 1) * 134 + 6) or "8C")
--udhi6位协议头格式
udhi = "050003" .. sformat("%02X", isn) .. sformat("%02X", pducnt) .. sformat("%02X", i)
log.info(datalen, udhi)
pdu = "005110" .. numlen .. numtobcdnum(num) .. "000800" .. len_mul .. udhi .. ssub(data, (i - 1) * 134 * 2 + 1, i * 134 * 2)
--发送短短信
else
datalen = sformat("%02X", datalen)
pdu = "001110" .. numlen .. numtobcdnum(num) .. "000800" .. datalen .. data
end
pdulen = slen(pdu) / 2 - 1
req(sformat("%s%s", "AT+CMGS=", pdulen), pdu)
end
return true
end
--[[
read
pos短信位置
truefalse读失败
]]
function read(pos)
if not smsReady or pos == nil or pos == 0 then return false end
req("AT+CMGR=" .. pos)
return true
end
--[[
delete
pos短信位置
truefalse删除失败
]]
function delete(pos)
if not smsReady or pos == nil or pos == 0 then return false end
req("AT+CMGD=" .. pos)
return true
end
Charmap = {[0] = 0x40, 0xa3, 0x24, 0xa5, 0xe8, 0xE9, 0xF9, 0xEC, 0xF2, 0xC7, 0x0A, 0xD8, 0xF8, 0x0D, 0xC5, 0xE5
, 0x0394, 0x5F, 0x03A6, 0x0393, 0x039B, 0x03A9, 0x03A0, 0x03A8, 0x03A3, 0x0398, 0x039E, 0x1B, 0xC6, 0xE5, 0xDF, 0xA9
, 0x20, 0x21, 0x22, 0x23, 0xA4, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F
, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
, 0xA1, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F
, 0X50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xC4, 0xD6, 0xD1, 0xDC, 0xA7
, 0xBF, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F
, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xE4, 0xF6, 0xF1, 0xFC, 0xE0}
Charmapctl = {[10] = 0x0C, [20] = 0x5E, [40] = 0x7B, [41] = 0x7D, [47] = 0x5C, [60] = 0x5B, [61] = 0x7E
, [62] = 0x5D, [64] = 0x7C, [101] = 0xA4}
--[[
gsm7bitdecode
7, PDU模式中使7160
data
longsms
]]
function gsm7bitdecode(data, longsms)
local ucsdata, lpcnt, tmpdata, resdata, nbyte, nleft, ucslen, olddat = "", slen(data) / 2, 0, 0, 0, 0, 0
if longsms then
tmpdata = tonumber("0x" .. ssub(data, 1, 2))
resdata = bit.rshift(tmpdata, 1)
if olddat == 27 then
if Charmapctl[resdata] then --特殊字符
olddat, resdata = resdata, Charmapctl[resdata]
ucsdata = ssub(ucsdata, 1, -5)
else
olddat, resdata = resdata, Charmap[resdata]
end
else
olddat, resdata = resdata, Charmap[resdata]
end
ucsdata = ucsdata .. sformat("%04X", resdata)
else
tmpdata = tonumber("0x" .. ssub(data, 1, 2))
resdata = bit.band(bit.bor(bit.lshift(tmpdata, nbyte), nleft), 0x7f)
if olddat == 27 then
if Charmapctl[resdata] then --特殊字符
olddat, resdata = resdata, Charmapctl[resdata]
ucsdata = ssub(ucsdata, 1, -5)
else
olddat, resdata = resdata, Charmap[resdata]
end
else
olddat, resdata = resdata, Charmap[resdata]
end
ucsdata = ucsdata .. sformat("%04X", resdata)
nleft = bit.rshift(tmpdata, 7 - nbyte)
nbyte = nbyte + 1
ucslen = ucslen + 1
end
for i = 2, lpcnt do
tmpdata = tonumber("0x" .. ssub(data, (i - 1) * 2 + 1, i * 2))
if tmpdata == nil then break end
resdata = bit.band(bit.bor(bit.lshift(tmpdata, nbyte), nleft), 0x7f)
if olddat == 27 then
if Charmapctl[resdata] then --特殊字符
olddat, resdata = resdata, Charmapctl[resdata]
ucsdata = ssub(ucsdata, 1, -5)
else
olddat, resdata = resdata, Charmap[resdata]
end
else
olddat, resdata = resdata, Charmap[resdata]
end
ucsdata = ucsdata .. sformat("%04X", resdata)
nleft = bit.rshift(tmpdata, 7 - nbyte)
nbyte = nbyte + 1
ucslen = ucslen + 1
if nbyte == 7 then
if olddat == 27 then
if Charmapctl[nleft] then --特殊字符
olddat, nleft = nleft, Charmapctl[nleft]
ucsdata = ssub(ucsdata, 1, -5)
else
olddat, nleft = nleft, Charmap[nleft]
end
else
olddat, nleft = nleft, Charmap[nleft]
end
ucsdata = ucsdata .. sformat("%04X", nleft)
nbyte, nleft = 0, 0
ucslen = ucslen + 1
end
end
return ucsdata, ucslen
end
--[[
gsm8bitdecode
8
data
longsms
]]
function gsm8bitdecode(data)
local ucsdata, lpcnt = "", slen(data) / 2
for i = 1, lpcnt do
ucsdata = ucsdata .. "00" .. ssub(data, (i - 1) * 2 + 1, i * 2)
end
return ucsdata, lpcnt
end
--[[
rsp
AT应答
cmd,success,response,intermediate
]]
local function rsp(cmd, success, response, intermediate)
local prefix = smatch(cmd, "AT(%+%u+)")
log.info("lib_sms rsp", prefix, cmd, success, response, intermediate)
--读短信成功
if prefix == "+CMGR" then
if not success then publish("SMS_READ_CNF") return end
local convnum, t, stat, alpha, len, pdu, data, longsms, total, isn, idx = "", ""
if intermediate then
stat, alpha, len, pdu = smatch(intermediate, "+CMGR:%s*(%d),(.*),%s*(%d+)\r\n(%x+)")
len = tonumber(len)--PDU数据长度不包括短信息中心号码
end
--收到的PDU包不为空则解析PDU包
if pdu and pdu ~= "" then
local offset, addlen, addnum, flag, dcs, tz, txtlen, fo = 5
pdu = ssub(pdu, (slen(pdu) / 2 - len) * 2 + 1, -1)--PDU数据不包括短信息中心号码
fo = tonumber("0x" .. ssub(pdu, 1, 1))--PDU短信首字节的高4位,第6位为数据报头标志位
if bit.band(fo, 0x4) ~= 0 then
longsms = true
end
addlen = tonumber(sformat("%d", "0x" .. ssub(pdu, 3, 4)))--回复地址数字个数
addlen = addlen % 2 == 0 and addlen + 2 or addlen + 3 --加上号码类型2位56or 加上号码类型2位56和1位F
offset = offset + addlen
addnum = ssub(pdu, 5, 5 + addlen - 1)
convnum = bcdnumtonum(addnum)
flag = tonumber(sformat("%d", "0x" .. ssub(pdu, offset, offset + 1)))--协议标识 (TP-PID)
offset = offset + 2
dcs = tonumber(sformat("%d", "0x" .. ssub(pdu, offset, offset + 1)))--用户信息编码方式 Dcs=8表示短信存放的格式为UCS2编码
offset = offset + 2
tz = ssub(pdu, offset, offset + 13)--时区7个字节
offset = offset + 14
txtlen = tonumber(sformat("%d", "0x" .. ssub(pdu, offset, offset + 1)))--短信文本长度
offset = offset + 2
data = ssub(pdu, offset, offset + txtlen * 2 - 1)--短信文本
if longsms then
if tonumber("0x" .. ssub(data, 5, 6)) == 3 then
isn, total, idx = tonumber("0x" .. ssub(data, 7, 8)), tonumber("0x" .. ssub(data, 9, 10)), tonumber("0x" .. ssub(data, 11, 12))
data = ssub(data, 13, -1)--去掉报头6个字节
elseif tonumber("0x" .. ssub(data, 5, 6)) == 4 then
isn, total, idx = tonumber("0x" .. ssub(data, 7, 10)), tonumber("0x" .. ssub(data, 11, 12)), tonumber("0x" .. ssub(data, 13, 14))
data = ssub(data, 15, -1)--去掉报头7个字节
end
end
log.info("TP-PID : ", flag, "dcs: ", dcs, "tz: ", tz, "data: ", data, "txtlen", txtlen)
if dcs == 0x00 then --7bit encode
local newlen
data, newlen = gsm7bitdecode(data, longsms)
if newlen > txtlen then
data = ssub(data, 1, txtlen * 4)
end
log.info("7bit to ucs2 data: ", data, "txtlen", txtlen, "newlen", newlen)
elseif dcs == 0x04 then --8bit encode
data, txtlen = gsm8bitdecode(data)
log.info("8bit to ucs2 data: ", data, "txtlen", txtlen)
end
for i = 1, 7 do
t = t .. ssub(tz, i * 2, i * 2) .. ssub(tz, i * 2 - 1, i * 2 - 1)
if i <= 3 then
t = i < 3 and (t .. "/") or (t .. ",")
elseif i <= 6 then
t = i < 6 and (t .. ":") or (t .. "+")
end
end
end
local pos = smatch(cmd, "AT%+CMGR=(%d+)")
data = data or ""
alpha = alpha or ""
publish("SMS_READ_CNF", success, convnum, data, pos, t, alpha, total, idx, isn)
elseif prefix == "+CMGD" then
publish("SMS_DELETE_CNF", success)
elseif prefix == "+CMGS" then
--如果是短短信,直接发送短信确认消息
if tsend[1].sval == 1 then --{sval=pducnt,rval=0,flg=true}
table.remove(tsend, 1)
publish("SMS_SEND_CNF", success)
--如果是长短信所有cmgs之后才抛出SMS_SEND_CNF,所有cmgs都成功才true其余都是false
else
tsend[1].rval = tsend[1].rval + 1
--只要其中有发送失败的短信,则整个长短信将标记为发送失败
if not success then tsend[1].flg = false end
if tsend[1].sval == tsend[1].rval then
publish("SMS_SEND_CNF", tsend[1].flg)
table.remove(tsend, 1)
end
end
end
end
local function init()
if cpinReady and smsUrcReady and netReady then
--使用PDU模式发送
req("AT+CMGF=0")
--设置短信TEXT 模式参数
-- req("AT+CSMP=17,167,0,8")
--设置AT命令的字符编码是UCS2
req('AT+CSCS="UCS2"')
--设置存储区为SM
req('AT+CPMS="SM"')
-- 上报消息CMTI
req("AT+CNMI=2,1,0,0,0")
--分发短信准备好消息
publish("SMS_READY")
--清空已读短信
req("AT+CMGD=1,3")
end
end
--[[
urc
data,prefix
]]
local function urc(data, prefix)
--短信准备好
if data == "SMS READY" then
smsUrcReady = true
init()
-- 存储短信
elseif prefix == "+CMTI" then
--分发收到新短信消息
publish("SMS_NEW_MSG_IND", smatch(data, "(%d+)", slen(prefix) + 1))
end
end
--[[
getsmsstate
true准备好
]]
function getsmsstate()
return smsReady
end
--[[
mergelongsms
tag(ISN和总条数的拼接字符串)
]]
local function mergelongsms(tag)
local data = ""
--按表中的顺序,一次拼接短消息内容
for i = 1, tlongsms[tag]["total"] do
data = data .. (tlongsms[tag]["dat"][i] or "")
end
--分发长短信合并确认消息
publish("LONG_SMS_MERGR_CNF", true, tlongsms[tag]["num"], data, tlongsms[tag]["t"], tlongsms[tag]["nam"])
log.info("mergelongsms", "num:", tlongsms[tag]["num"], "data", data)
tlongsms[tag]["dat"], tlongsms[tag] = nil
end
--[[
longsmsind
id,num, data,datetime,name,total,idx,isn
]]
local function longsmsind(num, data, datetime, name, total, idx, isn)
log.info("longsmsind", "isn", isn, "total:", total, "idx:", idx, "data", data)
if tlongsms[isn .. total] then
if not tlongsms[isn .. total]["dat"] then tlongsms[isn .. total]["dat"] = {} end
tlongsms[isn .. total]["dat"][idx] = data
else
tlongsms[isn .. total] = {}
tlongsms[isn .. total]["total"], tlongsms[isn .. total]["num"], tlongsms[isn .. total]["t"], tlongsms[isn .. total]["nam"] = total, num, datetime, name
tlongsms[isn .. total]["dat"] = {}
tlongsms[isn .. total]["dat"][idx] = data
end
local totalrcv = 0
for i = 1, tlongsms[isn .. total]["total"] do
if tlongsms[isn .. total]["dat"][i] then totalrcv = totalrcv + 1 end
end
--长短信接收完整
if tlongsms[isn .. total]["total"] == totalrcv then
sys.timerStop(mergelongsms, isn .. total)
mergelongsms(isn .. total)
else
--如果2分钟后长短信还没收完整2分钟后将自动合并已收到的长短信
sys.timerStart(mergelongsms, 120000, isn .. total)
end
end
--注册长短信合并处理函数
sys.subscribe("LONG_SMS_MERGE", longsmsind)
ril.regUrc("SMS READY", urc)
ril.regUrc("+CMT", urc)
ril.regUrc("+CMTI", urc)
ril.regRsp("+CMGR", rsp)
ril.regRsp("+CMGD", rsp)
ril.regRsp("+CMGS", rsp)
--短信发送缓冲表最大个数
local SMS_SEND_BUF_MAX_CNT = 10
--短信发送间隔,单位毫秒
local SMS_SEND_INTERVAL = 3000
--短信发送缓冲表
local tsmsnd = {}
--[[
sndnxt
]]
local function sndnxt()
if #tsmsnd > 0 then
_send(tsmsnd[1].num, tsmsnd[1].data)
end
end
--[[
sendcnf
SMS_SEND_CNF消息的处理函数
resulttrue为成功false或者nil为失败
]]
local function sendcnf(result)
log.info("sendcnf", result)
local num, data, cb = tsmsnd[1].num, tsmsnd[1].data, tsmsnd[1].cb
--从短信发送缓冲表中移除当前短信
table.remove(tsmsnd, 1)
--如果有发送回调函数,执行回调
if cb then cb(result, num, data) end
--如果短信发送缓冲表中还有短信则SMS_SEND_INTERVAL毫秒后继续发送下条短信
if #tsmsnd > 0 then sys.timerStart(sndnxt, SMS_SEND_INTERVAL) end
end
--- 发送短信
-- @string num 短信接收方号码ASCII码字符串格式
-- @string data 短信内容GB2312编码的字符串
-- 如果短信内容中只有ascii可见字符则超过160个字符时会被拆分为几条长级联短信进行发送
-- 如果短信内容中包含除ascii可见字符外的其他字符例如包含汉字一个汉字算作一个字符一个ascii可见字符也算作一个字符超过70个字符时会被拆分为几条长级联短信进行发送
-- @function[opt=nil] cbFnc 短信发送结果异步返回时的用户回调函数,回调函数的调用形式为:
-- cbFnc(result,num,data)
-- num短信接收方的号码ASCII码字符串格式
-- data短信内容unicode大端编码的HEX字符串
-- @number[opt=nil] idx 插入短信发送缓冲表的位置,默认是插入末尾
-- @string[opt=nil] istp 表示发送方式选择 "TEXT" or "text" 表示 text文本方式发送nil或其它参数表示pdu方式发送补充pdu方式采用 USC2 编码方式发送
-- @return resulttrue表示调用接口成功并不是短信发送成功短信发送结果通过sendcnf返回如果有cbFnc会通知cbFnc函数返回false表示调用接口失败
-- @usage sms.send("10086","test",cbFncistp)
function send(num, data, cbFnc, idx,istp)
--号码或者内容非法
if not num or num == "" or not data or data == "" then if cbFnc then cbFnc(false, num, data) end return end
--短信发送缓冲表已满
if #tsmsnd >= SMS_SEND_BUF_MAX_CNT then if cbFnc then cbFnc(false, num, data) end return end
if istp == "TEXT" or istp == "text" then
--如果指定了插入位置
if idx then
table.insert(tsmsnd, idx, {num = num, data = data, cb = cbFnc})
--没有指定插入位置,插入到末尾
else
table.insert(tsmsnd, {num = num, data = data, cb = cbFnc})
end
--如果短信发送缓冲表中只有一条短信,立即触发短信发送动作
if #tsmsnd == 1 then _send(num, data,istp) return true end
return
end
local dat = string.toHex(common.gb2312ToUcs2be(data))
--如果指定了插入位置
if idx then
table.insert(tsmsnd, idx, {num = num, data = dat, cb = cbFnc})
--没有指定插入位置,插入到末尾
else
table.insert(tsmsnd, {num = num, data = dat, cb = cbFnc})
end
--如果短信发送缓冲表中只有一条短信,立即触发短信发送动作
if #tsmsnd == 1 then _send(num, dat) return true end
end
--短信接收位置表
local tnewsms = {}
--[[
readsms
]]
local function readsms()
if #tnewsms ~= 0 then
read(tnewsms[1])
end
end
--[[
newsms
SMS_NEW_MSG_IND
pos
]]
local function newsms(pos)
--存储位置插入到短信接收位置表中
table.insert(tnewsms, pos)
--如果只有一条短信,则立即读取
if #tnewsms == 1 then
readsms()
end
end
--新短信的用户处理函数
local newsmscb
--- 设置新短信的用户处理函数
-- @function cbFnc 新短信的用户处理函数
-- @return nil
-- @usage sms.setNewSmsCb(cbFnc)
function setNewSmsCb(cbFnc)
newsmscb = cbFnc
end
--[[
readcnf
SMS_READ_CNF消息的处理函数
resulttrue为成功false或者nil为失败
numASCII码字符串格式
dataUCS2大端格式的16进制字符串
pos
datetimeASCII码字符串格式
name
]]
local function readcnf(result, num, data, pos, datetime, name, total, idx, isn)
if result then
--过滤号码中的86和+86
local d1, d2 = string.find(num, "^([%+]*86)")
if d1 and d2 then
num = string.sub(num, d2 + 1, -1)
end
--删除短信
delete(tnewsms[1])
--从短信接收位置表中删除此短信的位置
table.remove(tnewsms, 1)
if total and total > 1 then
publish("LONG_SMS_MERGE", num, data, datetime, name, total, idx, isn)
readsms()--读取下一条新短信
return
end
if data then
--短信内容转换为GB2312字符串格式
data = common.ucs2beToGb2312(data:fromHex())
--用户应用程序处理短信
if newsmscb then newsmscb(num, data, datetime) end
end
--继续读取下一条短信
readsms()
else
--删除短信
delete(tnewsms[1])
--从短信接收位置表中删除此短信的位置
table.remove(tnewsms, 1)
--继续读取下一条短信
readsms()
end
end
local function longsmsmergecnf(res, num, data, datetime)
--log.info("longsmsmergecnf",num,data,datetime)
if data then
--短信内容转换为GB2312字符串格式
data = common.ucs2beToGb2312(data:fromHex())
--用户应用程序处理短信
if newsmscb then newsmscb(num, data, datetime) end
end
end
sys.subscribe("SMS_NEW_MSG_IND", newsms)
sys.subscribe("SMS_READ_CNF", readcnf)
sys.subscribe("SMS_SEND_CNF", sendcnf)
sys.subscribe("SMS_READY",
function()
if not smsReady then
smsReady = true
if netReady then sndnxt() end
end
end
)
sys.subscribe("NET_STATE_REGISTERED",
function()
if not netReady then
netReady = true
init()
if smsReady then sndnxt() end
end
end
)
sys.subscribe("SIM_IND",
function(para)
if para=="RDY" and not cpinReady then
cpinReady = true
init()
end
end
)
sys.subscribe("LONG_SMS_MERGR_CNF", longsmsmergecnf)
-- 此处为临时AT+CNMI补丁
sys.timerStart(req, 30000, "AT+CNMI=2,1,0,0,0")