Files

642 lines
19 KiB
Lua
Raw Permalink Normal View History

2026-03-31 15:46:04 +08:00
--- 模块功能esp8266 wifi模块AT命令交互管理
-- @module wifiRil
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.02.13
-- require "uart"
-- require "rtos"
require "sys"
require "log"
module(..., package.seeall)
local UART_ID = 1
--加载常用的全局函数至本地
local vwrite = uart.write
local vread = uart.read
--是否为透传模式true为透传模式false或者nil为非透传模式
--默认非透传模式
local transparentmode
--透传模式下,虚拟串口数据接收的处理函数
local rcvfunc
local uartswitch
--cipsend执行完毕的flag
local cipsendflag
--执行AT命令后1分钟无反馈判定at命令执行失败则重启软件
local TIMEOUT,DATA_TIMEOUT = 120000,120000
--AT命令的应答类型
--NORESULT收到的应答数据当做urc通知处理如果发送的AT命令不处理应答或者没有设置类型默认为此类型
--NUMBERIC纯数字类型例如发送AT+CGSN命令应答的内容为862991527986589\r\nOK此类型指的是862991527986589这一部分为纯数字类型
--SLINE有前缀的单行字符串类型例如发送AT+CSQ命令应答的内容为+CSQ: 23,99\r\nOK此类型指的是+CSQ: 23,99这一部分为单行字符串类型
--MLINE有前缀的多行字符串类型例如发送AT+CMGR=5命令应答的内容为+CMGR: 0,,84\r\n0891683108200105F76409A001560889F800087120315123842342050003590404590D003A59\r\nOK此类型指的是OK之前为多行字符串类型
--STRING无前缀的字符串类型例如发送AT+ATWMFT=99命令应答的内容为SUCC\r\nOK此类型指的是SUCC
--SPECIAL特殊类型需要针对AT命令做特殊处理例如CIPSEND、CIPCLOSE、CIFSR
local NORESULT, NUMBERIC, SLINE, MLINE, STRING, SPECIAL = 0, 1, 2, 3, 4, 10
--AT命令的应答类型表预置了如下几项
local RILCMD = {
["+CSQ"] = 2,
["+MUID"] = 2,
["+CGSN"] = 1,
["+WISN"] = 4,
["+CIMI"] = 1,
["+CCID"] = 1,
["+CGATT"] = 2,
["+CCLK"] = 2,
["+ATWMFT"] = 4,
["+CMGR"] = 3,
["+CMGS"] = 2,
["+CPBF"] = 3,
["+CPBR"] = 3,
['+CLCC'] = 3,
['+CNUM'] = 3,
["+CIPSEND"] = 10,
["+CIPCLOSE"] = 10,
["+SSLINIT"] = 10,
["+SSLCERT"] = 10,
["+SSLCREATE"] = 10,
["+SSLCONNECT"] = 10,
["+SSLSEND"] = 10,
["+SSLDESTROY"] = 10,
["+SSLTERM"] = 10,
["+CIFSR"] = 10,
["+CTFSGETID"] = 2,
["+CTFSDECRYPT"] = 2,
["+CTFSAUTH"] = 2,
["+ALIPAYOPEN"] = 2,
["+ALIPAYREP"] = 2,
["+ALIPAYPINFO"] = 2,
["+ALIPAYACT"] = 2,
["+ALIPAYDID"] = 2,
["+ALIPAYSIGN"] = 2
}
--radioreadyAT命令通道是否准备就绪
--delaying执行完某些AT命令前需要延时一段时间才允许执行这些AT命令此标志表示是否在延时状态
local radioready, delaying = false
--AT命令队列
local cmdqueue = {
"ATE0",
-- "AT+SYSLOG=0",
'AT+CIPSNTPCFG=1,8,"cn.ntp.org.cn","ntp.sjtu.edu.cn"',
}
--当前正在执行的AT命令,参数,反馈回调,延迟执行时间,命令头,类型,反馈格式
local currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt, cmdRspParam
--反馈结果,中间信息,结果信息
local result, interdata, respdata
local sslCreating
--ril会出现三种情况:
--发送AT命令收到应答
--发送AT命令命令超时没有应答
--底层软件主动上报的通知下文我们简称为urc
--[[
atimeout
AT命令
]]
local function atimeout()
--重启软件
sys.restart("wifiRil.atimeout_" .. (currcmd or ""))
end
--[[
defrsp
AT命令的默认应答处理AT的应答处理函数
cmdAT命令
successAT命令执行结果true或者false
responseAT命令的应答中的执行结果字符串
intermediateAT命令的应答中的中间信息
]]
local function defrsp(cmd, success, response, intermediate)
log.info("wifiRil.defrsp", cmd, success, response, intermediate)
end
--AT命令的应答处理表
local rsptable = {}
setmetatable(rsptable, {
__index = function()
return defrsp
end
})
--自定义的AT命令应答格式表当AT命令应答为STRING格式时用户可以进一步定义这里面的格式
local formtab = {}
---注册某个AT命令应答的处理函数
-- @param head 此应答对应的AT命令头去掉了最前面的AT两个字符
-- @param fnc AT命令应答的处理函数
-- @param typ AT命令的应答类型取值范围NORESULT,NUMBERIC,SLINE,MLINE,STRING,SPECIAL
-- @param formt typ为STRING时进一步定义STRING中的详细格式
-- @return bool ,成功返回true失败false
-- @usage wifiRil.regRsp("+CSQ", rsp)
function regRsp(head, fnc, typ, formt)
--没有定义应答类型
if typ == nil then
rsptable[head] = fnc
return true
end
--定义了合法应答类型
if typ == 0 or typ == 1 or typ == 2 or typ == 3 or typ == 4 or typ == 10 then
--如果AT命令的应答类型已存在并且与新设置的不一致
if RILCMD[head] and RILCMD[head] ~= typ then
return false
end
--保存
RILCMD[head] = typ
rsptable[head] = fnc
formtab[head] = formt
return true
else
return false
end
end
--[[
rsp
AT命令的应答处理
]]
local function rsp()
--停止应答超时定时器
sys.timerStopAll(atimeout)
--如果发送AT命令时已经同步指定了应答处理函数
if currsp then
currsp(currcmd, result, respdata, interdata)
--用户注册的应答处理函数表中找到处理函数
else
rsptable[cmdhead](currcmd, result, respdata, interdata, cmdRspParam)
end
--重置全局变量
if cmdhead == "+CIPSEND" and cipsendflag then
return
end
currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt = nil
result, interdata, respdata = nil
end
--[[
defurc
urc的默认处理urc的应答处理函数
dataurc内容
]]
local function defurc(data)
log.info("wifiRil.defurc", data)
end
--urc的处理表
local urctable = {}
setmetatable(urctable, {
__index = function()
return defurc
end
})
--- 注册某个urc的处理函数
-- @param prefix urc前缀最前面的连续字符串包含+、大写字符、数字的组合
-- @param handler urc的处理函数
-- @return 无
-- @usage wifiRil.regUrc("+CREG", neturc)
function regUrc(prefix, handler)
urctable[prefix] = handler
end
--- 解注册某个urc的处理函数
-- @param prefix urc前缀最前面的连续字符串包含+、大写字符、数字的组合
-- @return 无
-- @usage deRegUrc("+CREG")
function deRegUrc(prefix)
urctable[prefix] = nil
end
--“数据过滤器”,虚拟串口收到的数据时,首先需要调用此函数过滤处理一下
local urcfilter
--[[
urc
urc处理
dataurc数据
]]
local function urc(data, cmd)
--AT通道准备就绪
if data == "ready" then
radioready = true
else
local prefix = string.match(data, "([%+%*]*[%a%d& ]+)")
-- 执行prefix的urc处理函数返回数据过滤器
urcfilter = urctable[prefix](data, prefix, cmd)
end
end
--[[
procatc
data
]]
local function procatc(data)
log.info("wifiRil.proatc", data)
--如果命令的应答是多行字符串格式
if interdata and cmdtype == MLINE then
--不出现OK\r\n则认为应答还未结束
if data ~= "OK\r\n" then
--去掉最后的\r\n
if string.find(data, "\r\n", -2) then
data = string.sub(data, 1, -3)
end
--拼接到中间数据
interdata = interdata .. "\r\n" .. data
return
end
end
--如果存在“数据过滤器”
if urcfilter then
data, urcfilter = urcfilter(data)
end
--去掉最后的\r\n
if not data:find("+CIPRECVDATA") then
if string.find(data, "\r\n", -2) then
data = string.sub(data, 1, -3)
end
else
local len, tmp = data:match("%+CIPRECVDATA:(%d+),(.+)")
if len + 2 == #tmp and string.find(data, "\r\n", -2) then
data = string.sub(data, 1, -3)
end
end
--数据为空
if data == "" then
return
end
--当前无命令在执行则判定为urc
if currcmd == nil then
urc(data)
return
end
local isurc = false
--一些特殊的错误信息转化为ERROR统一处理
if string.find(data, "^%+CMS ERROR:") or string.find(data, "^%+CME ERROR:") or (data == "CONNECT FAIL" and currcmd and string.match(currcmd, "CIPSTART")) then
data = "ERROR"
end
if sslCreating and data=="+PDP: DEACT" and tonumber(string.match(rtos.get_version(),"Luat_V(%d+)_"))<31 then
sys.publish("SSL_DNS_PARSE_PDP_DEACT")
end
if cmdhead == "+CIPSEND" and data == "SEND OK" then
result = true
respdata = data
cipsendflag = false
elseif cmdhead == "+CIPSEND" and data == "OK" then
result = true
respdata = data
cipsendflag = true
--执行成功的应答
elseif data == "OK" or data == "SHUT OK" then
result = true
respdata = data
--执行失败的应答
elseif data == "ERROR" or data == "NO ANSWER" or data == "NO DIALTONE" then
result = false
respdata = data
--需要继续输入参数的AT命令应答
elseif data == ">" then
--发送短信
if cmdhead == "+CMGS" then
log.info("wifiRil.procatc.send", currarg)
vwrite(UART_ID, currarg, "\026")
--发送数据
elseif cmdhead == "+CIPSEND" or cmdhead == "+SSLSEND" or cmdhead == "+SSLCERT" then
log.info("wifiRil.procatc.send", "first 200 bytes", currarg:sub(1,200))
vwrite(UART_ID, currarg)
elseif cmdhead == "+SYSFLASH" then
log.info("wifiRil.sysflash.send", "first 200 bytes", currarg:sub(1,200))
vwrite(UART_ID, currarg)
else
log.error("error promot cmd:", currcmd)
end
else
--无类型
if cmdtype == NORESULT then
isurc = true
--全数字类型
elseif cmdtype == NUMBERIC then
local numstr = string.match(data, "(%x+)")
if numstr == data then
interdata = data
else
isurc = true
end
--字符串类型
elseif cmdtype == STRING then
--进一步检查格式
if string.match(data, rspformt or "^.+$") then
interdata = data
else
isurc = true
end
elseif cmdtype == SLINE or cmdtype == MLINE then
if interdata == nil and string.find(data, cmdhead) == 1 then
interdata = data
else
isurc = true
end
--特殊处理
elseif cmdhead == "+CIFSR" then
local s = string.match(data, "%d+%.%d+%.%d+%.%d+")
if s ~= nil then
interdata = s
result = true
else
isurc = true
end
--特殊处理
elseif cmdhead == "+CIPSEND" or cmdhead == "+CIPCLOSE" then
local keystr = cmdhead == "+CIPSEND" and "SEND" or "CLOSE"
local lid, res = string.match(data, "(%d), *([%u%d :]+)")
if data:match("^%d, *CLOSED$") then
isurc = true
elseif lid and res then
if (string.find(res, keystr) == 1 or string.find(res, "TCP ERROR") == 1 or string.find(res, "UDP ERROR") == 1 or string.find(data, "DATA ACCEPT")) and (lid == string.match(currcmd, "=(%d)")) then
result = data:match("ERROR") == nil
respdata = data
else
isurc = true
end
elseif data == "+PDP: DEACT" then
result = true
respdata = data
elseif data:match("^Recv %d+ bytes$") then
result = true
respdata = data
else
isurc = true
end
elseif cmdhead == "+SSLINIT" or cmdhead == "+SSLCERT" or cmdhead == "+SSLCREATE" or cmdhead == "+SSLCONNECT" or cmdhead == "+SSLSEND" or cmdhead == "+SSLDESTROY" or cmdhead == "+SSLTERM" then
if string.match(data, "^SSL&%d, *CLOSED") or string.match(data, "^SSL&%d, *ERROR") or string.match(data, "SSL&%d,CONNECT ERROR") then
isurc = true
elseif string.match(data, "^SSL&%d,") then
respdata = data
if string.match(data, "ERROR") then
result = false
else
result = true
end
if cmdhead == "+SSLCREATE" then
sslCreating = false
end
else
isurc = true
end
else
isurc = true
end
end
-- urc处理
if isurc then
urc(data, currcmd)
--应答处理
elseif result ~= nil then
rsp()
end
end
--是否在读取虚拟串口数据
local readat = false
--[[
getcmd
AT命令
itemAT命令
AT命令的内容
]]
local function getcmd(item)
local cmd, arg, rsp, delay, rspParam
--命令是string类型
if type(item) == "string" then
--命令内容
cmd = item
--命令是table类型
elseif type(item) == "table" then
--命令内容
cmd = item.cmd
--命令参数
arg = item.arg
--命令应答处理函数
rsp = item.rsp
--命令延时执行时间
delay = item.delay
--命令携带的参数,执行回调时传入此参数
rspParam = item.rspParam
else
log.info("wifiRil.getcmd", "getpack unknown item")
return
end
-- 命令前缀
local head = string.match(cmd, "AT([%+%*]*%u+)")
if head == nil then
log.error("wifiRil.getcmd", "request error cmd:", cmd)
return
end
--这两个命令必须有参数
if head == "+CMGS" or head == "+CIPSEND" then -- 必须有参数
if arg == nil or arg == "" then
log.error("wifiRil.getcmd", "request error no arg", head)
return
end
end
--赋值全局变量
currcmd = cmd
currarg = arg
currsp = rsp
curdelay = delay
cmdhead = head
cmdRspParam = rspParam
cmdtype = RILCMD[head] or NORESULT
rspformt = formtab[head]
return currcmd
end
--[[
sendat
AT命令
]]
local function sendat()
-- AT通道未准备就绪、正在读取虚拟串口数据、有AT命令在执行或者队列无命令、正延时发送某条AT
if not radioready or readat or currcmd ~= nil or delaying then
return
end
local item
while true do
--队列无AT命令
if #cmdqueue == 0 then
return
end
--读取第一条命令
item = table.remove(cmdqueue, 1)
--解析命令
getcmd(item)
--需要延迟发送
if curdelay then
--启动延迟发送定时器
sys.timerStart(delayfunc, curdelay)
--清除全局变量
currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt, cmdRspParam = nil
item.delay = nil
--设置延迟发送标志
delaying = true
--把命令重新插入命令队列的队首
table.insert(cmdqueue, 1, item)
return
end
if currcmd ~= nil then
break
end
end
--启动AT命令应答超时定时器
if currcmd:match("^AT%+CIPSTART") or currcmd:match("^AT%+CIPSEND") or currcmd:match("^AT%+SSLCREATE") or currcmd:match("^AT%+SSLCONNECT") or currcmd:match("^AT%+SSLSEND") then
sys.timerStart(atimeout,DATA_TIMEOUT)
else
sys.timerStart(atimeout, TIMEOUT)
end
if currcmd:match("^AT%+SSLCREATE") then
sslCreating = true
end
log.info("wifiRil.sendat", currcmd)
--向虚拟串口中发送AT命令
vwrite(UART_ID, currcmd .. "\r\n")
end
-- 延时执行某条AT命令的定时器回调
-- @return 无
-- @usage wifiRil.delayfunc()
function delayfunc()
--清除延时标志
delaying = nil
--执行AT命令发送
sendat()
end
--[[
atcreader
AT命令的虚拟串口数据接收消息
]]
local function atcreader()
local s
if not transparentmode then
readat = true
end
-- 循环读取虚拟串口收到的数据
while true do
--每次读取一行
s = vread(UART_ID, "*l", 0)
if string.len(s) ~= 0 then
if transparentmode then
--透传模式下直接转发数据
rcvfunc(s)
else
--非透传模式下处理收到的数据
procatc(s)
end
else
break
end
end
if not transparentmode then
readat = false
--数据处理完以后继续执行AT命令发送
sendat()
end
end
--- 发送AT命令到底层软件
-- @param cmd AT命令内容
-- @param arg AT命令参数例如AT+CMGS=12命令执行后接下来会发送此参数AT+CIPSEND=14命令执行后接下来会发送此参数
-- @param onrsp AT命令应答的处理函数只是当前发送的AT命令应答有效处理之后就失效了
-- @param delay 延时delay毫秒后才发送此AT命令
-- @return 无
-- @usage wifiRil.request("AT+CENG=1,1")
-- @usage wifiRil.request("AT+CRSM=214,28539,0,0,12,\"64f01064f03064f002fffff\"", nil, crsmResponse)
function request(cmd, arg, onrsp, delay, param)
if transparentmode then
return
end
--插入缓冲队列
if arg or onrsp or delay or formt or param then
table.insert(cmdqueue, {
cmd = cmd,
arg = arg,
rsp = onrsp,
delay = delay,
rspParam = param
})
else
table.insert(cmdqueue, cmd)
end
--执行AT命令发送
sendat()
end
--[[
setransparentmode
AT命令通道设置为透传模式
fnc
]]
function setransparentmode(fnc)
transparentmode, rcvfunc = true, fnc
end
--[[
sendtransparentdata
data
truenil
]]
function sendtransparentdata(data)
if not transparentmode then
return
end
vwrite(UART_ID, data)
return true
end
function setDataTimeout(tm)
DATA_TIMEOUT = (tm<120000 and 120000 or tm)
end
uart.setup(UART_ID, 115200, 8, uart.PAR_NONE, uart.STOP_1)
--注册“AT命令的虚拟串口数据接收消息”的处理函数
uart.on(UART_ID, "receive", atcreader)