工程提交

This commit is contained in:
2026-03-31 15:46:04 +08:00
parent 75f512a5b4
commit da4e944bca
2841 changed files with 4822938 additions and 1 deletions

Binary file not shown.

14
4G/code/core/cmd.lua Normal file
View File

@@ -0,0 +1,14 @@
-- CMD
require "linksocket"
require "socket"
require "sys"
function handleCmd01(subCmd, payload)
--sendToSocket(subCmd, payload) --向socket发送数据
sendToSocket(subCmd, payload)
log.info("CMD 0x01 received, sub:", subCmd, "data len:", #payload)
end
function handleCmd02(subCmd, payload)
sys.restart()
end

View File

@@ -0,0 +1,83 @@
require "socket"
require "log"
-- 目标服务器IP和端口
local ip, port = "121.43.69.62", "8767"
-- 连接数量
local clientCount = 1
-- 存储所有客户端
local clients = {}
-- 初始化多个socket连接
for i = 1, clientCount do
local client = {
id = i,
socket = nil,
connected = false
}
-- 为每个socket创建独立的连接任务
sys.taskInit(function()
while true do
while not socket.isReady() do sys.wait(1000) end
client.socket = socket.tcp()
log.debug("Client " .. client.id .. ": Connecting to " .. ip .. ":" .. port)
while not client.socket:connect(ip, port) do
log.warn("Client " .. client.id .. ": Connection failed, retrying...")
sys.wait(2000)
end
client.connected = true
log.debug("Client " .. client.id .. ": Connected successfully")
-- 发送连接成功信息到串口
uart.write(1, "Socket " .. client.id .. " connected\r\n")
-- 保持连接并处理数据
while client.socket:asyncSelect(60, "ping") do end
client.connected = false
client.socket:close()
log.error("Client " .. client.id .. ": Disconnected")
-- 发送断开连接信息到串口
end
end)
-- 存储客户端
clients[i] = client
end
local function toHexcode(str)
local hexcode = ""
for i = 1, #str do
hexcode = hexcode .. string.format("%02X", str:byte(i))
end
return hexcode
end
-- 测试代码,异步回调接收数据
sys.subscribe("SOCKET_RECV", function(id)
-- 遍历所有客户端,找到对应的连接
for i, client in ipairs(clients) do
if client.socket and client.socket.id == id then
-- 转发到串口添加socket标识并清理数据
local data = client.socket:asyncRecv()
log.info("Client " .. client.id .. ": Received data: " .. toHexcode(data))
-- 转发到串口添加socket标识
local frame = string.char(0x55) .. string.char(0xAA) .. string.char(0x01) .. string.char(i) .. string.char(#data) .. data .. string.char(0xAA) .. string.char(0x55)
uart.write(1, frame)
break
end
end
end)
-- 发送数据到指定socket
function sendToSocket(id_byte, msg)
if clients[id_byte] and clients[id_byte].socket and clients[id_byte].connected then
clients[id_byte].socket:asyncSend(msg)
else
log.error("Socket " .. id_byte .. " not connected or does not exist")
end
end

65
4G/code/core/main.lua Normal file
View File

@@ -0,0 +1,65 @@
--- testSocket
-- @module asyncSocket
-- @author AIRM2M
-- @license MIT
-- @copyright openLuat.com
-- @release 2018.10.27
PROJECT = "4G_NETWORK"
VERSION = "1.0.0"
require "sys"
require "net"
require "log"
require "cmd"
require "uart"
require "linksocket"
-- 初始化 UART1
uart.setup(1, 115200, 8, uart.PAR_NONE, uart.STOP_1)
-- 启动网络指示灯功能模块
require "netLed"
pmd.ldoset(2,pmd.LDO_VLCD)
netLed.setup(true,pio.P0_1,pio.P0_4)
local uartID = 1
local function toHexcode(str)
local hexcode = ""
for i = 1, #str do
hexcode = hexcode .. string.format("%02X", str:byte(i))
end
return hexcode
end
uart.on(uartID, "receive", function()
local data = uart.read(uartID, 300)
if data and type(data) == "string" and #data > 0 then
log.info("UART received:", toHexcode(data))
if data:byte(1) == 0x55 and data:byte(2) == 0xAA and data:byte(-2) == 0xAA and data:byte(-1) == 0x55 then
local Main_Cmd = data:byte(3)
local Sub_Cmd = data:byte(4)
local payload = data:sub(5, -3)
if Main_Cmd ==0x01 then
handleCmd01(Sub_Cmd, payload)
end
if Main_Cmd == 0x02 then
handleCmd02(Sub_Cmd, payload)
end
end
else
log.warn("UART receive callback triggered but no valid data")
end
end)
net.startQueryAll(8 * 1000, 60 * 1000)
ril.request("AT+RNDISCALL=0,1")
sys.init(0, 0)
sys.run()

535
4G/code/lib/aLiYun.lua Normal file
View File

@@ -0,0 +1,535 @@
--- 模块功能:阿里云物联网套件客户端功能.
-- 目前的产品节点类型仅支持“设备”,设备认证方式支持“一机一密和“一型一密”
-- @module aLiYun
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.04.16
require"log"
require"http"
require"mqtt"
module(..., package.seeall)
local rrpc_state = false
local sProductKey,sProductSecret,sGetDeviceNameFnc,sGetDeviceSecretFnc,sSetDeviceSecretFnc
local sRegion = "cn-shanghai"
--实例ID根据此ID来判断是否需要一型一密免预注册认证
local sInstanceId
--连接方式
local sConnectMode,sConnectHost,sConnectPort,sGetClientIdFnc,sGetUserNameFnc,sGetPasswordFnc
local sKeepAlive,sCleanSession,sWill
local isSleep = false--休眠,不去重连服务器
local isAuthSleep =false --休眠,不去重连鉴权
local sErrHandleCo,sErrHandleCb,sErrHandleTmout
local outQueue =
{
SUBSCRIBE = {},
PUBLISH = {},
}
local evtCb = {}
local function insert(type,topic,qos,payload,cbFnc,cbPara)
table.insert(outQueue[type],{t=topic,q=qos,p=payload,cb=cbFnc,para=cbPara})
end
local function remove(type)
if #outQueue[type]>0 then return table.remove(outQueue[type],1) end
end
local function procSubscribe(client)
local i
for i=1,#outQueue["SUBSCRIBE"] do
if not client:subscribe(outQueue["SUBSCRIBE"][i].t,outQueue["SUBSCRIBE"][i].q) then
outQueue["SUBSCRIBE"] = {}
return false,"procSubscribe"
end
end
outQueue["SUBSCRIBE"] = {}
return true
end
local function procReceive(client)
local r,data,s
while true do
r,data,s = client:receive(60000,"aliyun_publish_ind")
--接收到数据
if r and data~="timeout" then
log.info("aLiYun.procReceive",data.topic,string.toHex(data.payload))
--OTA消息
if data.topic=="/ota/device/upgrade/"..sProductKey.."/"..sGetDeviceNameFnc() then
if aLiYunOta and aLiYunOta.upgrade then
aLiYunOta.upgrade(data.payload)
end
--其他消息
else
if evtCb["receive"] then evtCb["receive"](data.topic,data.qos,data.payload) end
end
--如果有等待发送的数据,则立即退出本循环
if #outQueue["PUBLISH"]>0 then
return true,"procReceive"
else
if sErrHandleCo then coroutine.resume(sErrHandleCo,"feed") end
end
elseif data == "aliyun_publish_ind" and s:find("disconnect") then--主动断开
client:disconnect()
return false,"procReceive"
elseif data == "aliyun_publish_ind" and s:find("send") then--来数据要发了
log.info("aliyun aliyun_publish_ind")
return true,"procReceive"
else
break
end
end
return data=="timeout" or r,"procReceive"
end
local function procSend(client)
if not procSubscribe(client) then
return false,"procSubscribe"
end
while #outQueue["PUBLISH"]>0 do
local item = table.remove(outQueue["PUBLISH"],1)
local result = client:publish(item.t,item.p,item.q)
if item.cb then item.cb(result,item.para) end
if not result then
return false,"procSend"
else
if sErrHandleCo then coroutine.resume(sErrHandleCo,"feed") end
end
end
return true,"procSend"
end
--- 断开阿里云物联网套件的连接,并且不再重连
-- @return nil
-- @usage
-- aLiYun.sleep()
function sleep()
isSleep = true
log.info("aLiYun.sleep","open sleep, stop try reconnect")
sys.publish("aliyun_publish_ind","disconnect")
end
--- 重新打开阿里云物联网套件的连接
-- @return nil
-- @usage
-- aLiYun.wakeup()
function wakeup()
isSleep = false
sys.publish("ALITUN_WAKEUP")
log.info("aLiYun.wakeup","exit sleep")
end
--- 查看打开阿里云物联网套件的是否允许连接状态
-- @return bool 是否允许连接阿里云
-- @usage
-- local ar = aLiYun.sleepStatus()
function sleepStatus()
return isSleep
end
--- 断开阿里云物联网套件的鉴权连接,并且不再重连
-- @return nil
-- @usage
-- aLiYun.Authsleep()
function Authsleep()
isAuthSleep = true
log.info("aLiYun.Authsleep","open sleep, stop try reconnect")
sys.publish("aliyun_publish_ind","disconnect")
end
--- 重新打开阿里云物联网套件的鉴权连接
-- @return nil
-- @usage
-- aLiYun.Authwakeup()
function Authwakeup()
isAuthSleep = false
sys.publish("ALITUN_Auth_WAKEUP")
log.info("aLiYun.auth_wakeup","exit sleep")
end
--- 查看打开阿里云物联网套件的是否允许鉴权状态
-- @return bool 是否允许连接阿里云
-- @usage
-- local ar = aLiYun.AuthSleepStatus()
function AuthSleepStatus()
return isAuthSleep
end
function clientDataTask(host,tPorts,clientId,user,password)
local retryConnectCnt = 0
local portIdx = 0
while true do
if isSleep then sys.waitUntil("ALITUN_WAKEUP") end
if not socket.isReady() then
retryConnectCnt = 0
--等待网络环境准备就绪超时时间是5分钟
sys.waitUntil("IP_READY_IND",300000)
end
if socket.isReady() then
local mqttClient = mqtt.client(clientId,sKeepAlive or 240,user,password,sCleanSession,sWill)
portIdx = portIdx%(#tPorts)+1
if mqttClient:connect(host,tonumber(tPorts[portIdx]),sConnectMode=="direct" and "tcp" or "tcp_ssl") then
retryConnectCnt = 0
if aLiYunOta and aLiYunOta.connectCb then aLiYunOta.connectCb(true,sProductKey,sGetDeviceNameFnc()) end
if evtCb["connect"] then evtCb["connect"](true) end
local result,prompt = procSubscribe(mqttClient)
if result then
local procs,k,v = {procSend,procReceive}
while true do
for k,v in pairs(procs) do
result,prompt = v(mqttClient)
if not result then log.warn("aLiYun.clientDataTask."..prompt.." error") break end
end
if not result then break end
if sErrHandleCo then coroutine.resume(sErrHandleCo,"feed") end
end
else
log.warn("aLiYun.clientDataTask."..prompt.." error")
end
while #outQueue["PUBLISH"]>0 do
local item = table.remove(outQueue["PUBLISH"],1)
if item.cb then item.cb(false,item.para) end
end
if aLiYunOta and aLiYunOta.connectCb then aLiYunOta.connectCb(false,sProductKey,sGetDeviceNameFnc()) end
if evtCb["connect"] then evtCb["connect"](false) end
else
retryConnectCnt = retryConnectCnt+1
if evtCb["reconnect"] then evtCb["reconnect"]() end
end
mqttClient:disconnect()
if retryConnectCnt>=5 then link.shut() retryConnectCnt=0 end
sys.wait(2000)
else
--进入飞行模式20秒之后退出飞行模式
net.switchFly(true)
sys.wait(20000)
net.switchFly(false)
end
end
end
local function getDeviceSecretCb(result,prompt,head,body)
log.info("aLiYun.getDeviceSecretCb",result,prompt)
if result and body then
local tJsonDecode = json.decode(body)
if tJsonDecode and tJsonDecode["data"] and tJsonDecode["data"]["deviceSecret"] and tJsonDecode["data"]["deviceSecret"]~="" then
sSetDeviceSecretFnc(tJsonDecode["data"]["deviceSecret"])
end
end
sys.publish("GetDeviceSecretEnd")
end
local function authCbFnc(result,statusCode,head,body)
log.info("aLiYun.authCbFnc",result,statusCode,body)
sys.publish("ALIYUN_AUTH_IND",result,statusCode,body)
end
local function getBody(tag)
if tag=="auth" then
local data = "clientId"..sGetDeviceNameFnc().."deviceName"..sGetDeviceNameFnc().."productKey"..sProductKey
local signKey= sGetDeviceSecretFnc()
local sign = crypto.hmac_md5(data,data:len(),signKey,signKey:len())
return "productKey="..sProductKey.."&sign="..sign.."&clientId="..sGetDeviceNameFnc().."&deviceName="..sGetDeviceNameFnc()
elseif tag=="register" then
local random=rtos.tick()
local data = "deviceName"..sGetDeviceNameFnc().."productKey"..sProductKey.."random"..random
local sign = crypto.hmac_md5(data,data:len(),sProductSecret,sProductSecret:len())
return "productKey="..sProductKey.."&deviceName="..sGetDeviceNameFnc().."&random="..random.."&sign="..sign.."&signMethod=HmacMD5"
end
end
function clientAuthTask()
while not socket.isReady() do sys.waitUntil("IP_READY_IND") end
while true do
if isAuthSleep then sys.waitUntil("ALITUN_Auth_WAKEUP") end
local retryCnt,authBody = 0,getBody("auth")
while true do
http.request("POST",
"https://iot-auth."..sRegion..".aliyuncs.com/auth/devicename",
nil,{["Content-Type"]="application/x-www-form-urlencoded"},authBody,20000,authCbFnc)
local _,result,statusCode,body = sys.waitUntil("ALIYUN_AUTH_IND")
--log.info("aLiYun.clientAuthTask1",result and statusCode=="200",body)
local invalidSign
if result and statusCode=="200" then
local tJsonDecode,result = json.decode(body)
--log.info("aLiYun.clientAuthTask2",result,tJsonDecode["message"],tJsonDecode["data"])
if result and tJsonDecode["message"]=="success" and tJsonDecode["data"] and type(tJsonDecode["data"])=="table" then
--log.info("aLiYun.clientAuthTask3",tJsonDecode["data"]["iotId"],tJsonDecode["data"]["iotToken"])
if tJsonDecode["data"]["iotId"] and tJsonDecode["data"]["iotId"]~="" and tJsonDecode["data"]["iotToken"] and tJsonDecode["data"]["iotToken"]~="" then
if evtCb["auth"] then evtCb["auth"](true) end
local ports,host,returnMqtt = {}
if tJsonDecode["data"]["resources"] and type(tJsonDecode["data"]["resources"])=="table" then
if tJsonDecode["data"]["resources"]["mqtt"] then
returnMqtt,host = true,tJsonDecode["data"]["resources"]["mqtt"]["host"]
table.insert(ports,tJsonDecode["data"]["resources"]["mqtt"]["port"])
end
end
sys.taskInit(clientDataTask,returnMqtt and host or sProductKey..".iot-as-mqtt."..sRegion..".aliyuncs.com",#ports~=0 and ports or {1883},sGetDeviceNameFnc(),tJsonDecode["data"]["iotId"],tJsonDecode["data"]["iotToken"])
return
end
end
if body and body:match("invalid sign") then
invalidSign = true
end
end
if sProductSecret and invalidSign then
http.request("POST","https://iot-auth."..sRegion..".aliyuncs.com/auth/register/device",nil,
{['Content-Type']="application/x-www-form-urlencoded"},
getBody("register"),30000,getDeviceSecretCb)
sys.waitUntil("GetDeviceSecretEnd")
sys.wait(1000)
authBody = getBody("auth")
end
retryCnt = retryCnt+1
if retryCnt==3 then
break
end
end
if evtCb["auth"] then evtCb["auth"](false) end
sys.wait(5000)
end
end
local function directProc()
log.info("rrpc",rrpc_state)
local clientId
clientId = (sGetClientIdFnc and sGetClientIdFnc() or sGetDeviceNameFnc()).."|securemode=3,timestamp=2524608000000,signmethod=hmacsha1"..(rrpc_state and ",ext=1" or "").."|"
log.info("clientId",clientId)
local userName = sGetUserNameFnc and sGetUserNameFnc() or (sGetDeviceNameFnc().."&"..sProductKey)
local password
if sGetPasswordFnc then
password = sGetPasswordFnc()
else
local content = "clientId"..(sGetClientIdFnc and sGetClientIdFnc() or sGetDeviceNameFnc()).."deviceName"..sGetDeviceNameFnc().."productKey"..sProductKey.."timestamp2524608000000"
local signKey= sGetDeviceSecretFnc()
password = crypto.hmac_sha1(content,content:len(),signKey,signKey:len())
end
log.info("aLiYun.directProc",clientId,userName,password)
sys.taskInit(clientDataTask,sConnectHost or (sProductKey..".iot-as-mqtt."..sRegion..".aliyuncs.com"),{sConnectPort},clientId,userName,password)
end
local function clientDirectTask()
while not socket.isReady() do sys.waitUntil("IP_READY_IND") end
local tm=os.time()
--一机一密
if sProductSecret==nil then
directProc()
--一型一密
else
local clientId
--预注册
if sInstanceId==nil then
clientId = (sGetClientIdFnc and sGetClientIdFnc() or sGetDeviceNameFnc()).."|securemode=2,authType=register,random="..tm..",signmethod=hmacsha1"..(rrpc_state and ",ext=1" or "").."|"
--免预注册
else
clientId = (sGetClientIdFnc and sGetClientIdFnc() or sGetDeviceNameFnc()).."|securemode=-2,authType=regnwl,random="..tm..",signmethod=hmacsha1,instanceId="..sInstanceId.."|"
end
local userName = sGetUserNameFnc and sGetUserNameFnc() or (sGetDeviceNameFnc().."&"..sProductKey)
local content = "deviceName"..sGetDeviceNameFnc().."productKey"..sProductKey.."random"..tm
local signKey= sProductSecret
local password = crypto.hmac_sha1(content,content:len(),signKey,signKey:len())
while true do
if isAuthSleep then sys.waitUntil("ALITUN_Auth_WAKEUP") end
local mqttClient = mqtt.client(clientId,sKeepAlive or 240,userName,password)
local r,ack = mqttClient:connect(sConnectHost,sConnectPort,"tcp_ssl")
if r then
local result,data = mqttClient:receive(60000)
--接收到数据
if result then
log.info("aLiYun.clientDirectTask register rsp",data.topic,data.payload)
local tJsonDecode,res = json.decode(data.payload)
if res and tJsonDecode["deviceName"] and tJsonDecode["deviceSecret"] then
sSetDeviceSecretFnc(tJsonDecode["deviceSecret"])
sys.wait(1000)
mqttClient:disconnect()
directProc()
break
end
end
end
mqttClient:disconnect()
if ack==4 then
directProc()
break
else
sys.wait(5000)
end
end
end
end
--- 配置阿里云物联网套件的产品信息和设备信息
-- @string productKey 产品标识
-- @string[opt=nil] productSecret 产品密钥
-- 一机一密认证方案时此参数传入nil
-- 一型一密认证方案时,此参数传入真实的产品密钥
-- @function getDeviceNameFnc 获取设备名称的函数
-- @function getDeviceSecretFnc 获取设备密钥的函数
-- @function[opt=nil] setDeviceSecretFnc 设置设备密钥的函数,一型一密认证方案才需要此参数
-- @return nil
-- @usage
-- aLiYun.setup("b0FMK1Ga5cp",nil,getDeviceNameFnc,getDeviceSecretFnc)
-- aLiYun.setup("a1AoVqkCIbG","7eCdPyR6fYPntFcM",getDeviceNameFnc,getDeviceSecretFnc,setDeviceSecretFnc)
function setup(productKey,productSecret,getDeviceNameFnc,getDeviceSecretFnc,setDeviceSecretFnc)
sProductKey,sProductSecret,sGetDeviceNameFnc,sGetDeviceSecretFnc,sSetDeviceSecretFnc = productKey,productSecret,getDeviceNameFnc,getDeviceSecretFnc,setDeviceSecretFnc
if sConnectMode=="direct" then
sys.taskInit(clientDirectTask)
else
sys.taskInit(clientAuthTask)
end
end
--- 设置MQTT数据通道的参数
-- @number[opt=1] cleanSession 1/0
-- @table[opt=nil] will 遗嘱参数,格式为{qos=,retain=,topic=,payload=}
-- @number[opt=240] keepAlive 单位秒
-- @return nil
-- @usage
-- aLiYun.setMqtt(0)
-- aLiYun.setMqtt(1,{qos=0,retain=1,topic="/willTopic",payload="will payload"})
-- aLiYun.setMqtt(1,{qos=0,retain=1,topic="/willTopic",payload="will payload"},120)
function setMqtt(cleanSession,will,keepAlive)
sCleanSession,sWill,sKeepAlive = cleanSession,will,keepAlive
end
--- 设置地域region id
-- @string region 地域id字符串参考https://help.aliyun.com/document_detail/40654.html?spm=a2c4g.11186623.2.16.c0a63f82Z7qCtA#concept-h4v-j5k-xdb
-- @return nil
-- @usage
-- 设置华北1aLiYun.setRegion("cn-qingdao")
-- 设置华东1aLiYun.setRegion("cn-hangzhou")
-- 设置华南1aLiYun.setRegion("cn-shenzhen")
function setRegion(region)
sRegion = region
end
-- 设置企业版实例id
-- @string id企业版实例id
-- @return nil
-- @usage
-- aLiYun.setInstanceId(iot-060a1234")
function setInstanceId(id)
sInstanceId = id
end
--- 设置连接方式
-- @string mode 连接方式,支持如下几种方式:
-- "direct"表示MQTT-TCP直连
-- @string host 服务器地址
-- @number port 服务器端口
-- @function getClientIdFnc 获取mqttclientid的函数
-- @function getUserNameFnc 获取mqttclientuserName的函数
-- @function getPasswordFnc 获取mqttclientpassword的函数
-- @return nil
-- @usage
-- 设置为MQTT-TCP直连aLiYun.setConnectMode("direct")
function setConnectMode(mode,host,port,getClientIdFnc,getUserNameFnc,getPasswordFnc)
sConnectMode = mode
sConnectHost = host
sConnectPort = port or 1883
sGetClientIdFnc = getClientIdFnc
sGetUserNameFnc = getUserNameFnc
sGetPasswordFnc = getPasswordFnc
end
--- 订阅主题
-- @param topic string或者table类型一个主题时为string类型多个主题时为table类型主题内容为UTF8编码
-- @param qos number或者niltopic为一个主题时qos为number类型(0/1默认0)topic为多个主题时qos为nil
-- @return nil
-- @usage
-- aLiYun.subscribe("/b0FMK1Ga5cp/862991234567890/get", 0)
-- aLiYun.subscribe({["/b0FMK1Ga5cp/862991234567890/get"] = 0, ["/b0FMK1Ga5cp/862991234567890/get"] = 1})
function subscribe(topic,qos)
insert("SUBSCRIBE",topic,qos)
sys.publish("aliyun_publish_ind","send")
end
--- 发布一条消息
-- @string topic UTF8编码的主题
-- @string payload 负载
-- @number[opt=0] qos 质量等级0/1默认0
-- @function[opt=nil] cbFnc 消息发布结果的回调函数
-- 回调函数的调用形式为cbFnc(result,cbPara)。result为true表示发布成功false或者nil表示订阅失败cbPara为本接口中的第5个参数
-- @param[opt=nil] cbPara 消息发布结果回调函数的回调参数
-- @return nil
-- @usage
-- aLiYun.publish("/b0FMK1Ga5cp/862991234567890/update","test",0)
-- aLiYun.publish("/b0FMK1Ga5cp/862991234567890/update","test",1,cbFnc,"cbFncPara")
function publish(topic,payload,qos,cbFnc,cbPara)
insert("PUBLISH",topic,qos,payload,cbFnc,cbPara)
sys.publish("aliyun_publish_ind","send")
log.info("aliyun aliyun_publish_ind","publish")
end
--- 注册事件的处理函数
-- @string evt 事件
-- "auth"表示鉴权服务器认证结果事件
-- "connect"表示接入服务器连接结果事件
-- "reconnect"表示重连事件
-- "receive"表示接收到接入服务器的消息事件
-- @function cbFnc 事件的处理函数
-- 当evt为"auth"时cbFnc的调用形式为cbFnc(result)result为true表示认证成功false或者nil表示认证失败
-- 当evt为"connect"时cbFnc的调用形式为cbFnc(result)result为true表示连接成功false或者nil表示连接失败
-- 当evt为"receive"时cbFnc的调用形式为cbFnc(topic,qos,payload)topic为UTF8编码的主题(string类型)qos为质量等级(number类型)payload为原始编码的负载(string类型)
-- 当evt为"reconnect"时cbFnc的调用形式为cbFnc()表示lib中在自动重连阿里云服务器
-- @return nil
-- @usage
-- aLiYun.on("connect",cbFnc)
function on(evt,cbFnc)
evtCb[evt] = cbFnc
end
--- 设置阿里云task连续一段时间工作异常的处理程序
-- @function cbFnc 异常处理函数cbFnc的调用形式为cbFnc()
-- @number[opt=150] tmout 连续工作异常的时间当连续异常到达这个时间之后会调用cbFnc()
-- @return nil
-- @usage
-- aLiYun.setErrHandle(function() sys.restart("ALIYUN_TASK_INACTIVE") end, 300)
function setErrHandle(cbFnc,tmout)
sErrHandleCb = cbFnc
sErrHandleTmout = tmout or 150
if not sErrHandleCo then
sErrHandleCo = sys.taskInit(function()
while true do
if sys.wait(sErrHandleTmout*1000) == nil then
if not isSleep then
sErrHandleCb()
end
end
end
end)
end
end
function rrpcUseCustomTopic(state)
rrpc_state = state
end

255
4G/code/lib/aLiYunOta.lua Normal file
View File

@@ -0,0 +1,255 @@
--- 模块功能阿里云物联网套件客户端OTA功能.
-- 目前固件签名算法仅支持MD5
-- @module aLiYunOta
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.04.16
require"log"
require"http"
module(..., package.seeall)
--gVersion固件版本号字符串如果用户没有调用本文件的setVer接口设置则默认为_G.PROJECT.."_".._G.VERSION.."_"..sys.getcorever()
--gName阿里云iot网站上配置的新固件文件下载后在模块中的保存路径如果用户没有调用本文件的setName接口设置则默认为/luazip/update.bin
--gCb新固件下载成功后要执行的回调函数
local gVersion,gName,gCb = _G.PROJECT.."_".._G.VERSION.."_"..rtos.get_version()
local gFilePath,gFileSize
local processed = 0
local flowMd5
--productKey产品标识
--deviceName设备名称
local productKey,deviceName
--verRpted版本号是否已经上报
local verRpted,sConnected
--lastStep最后一次上报的下载新固件的进度
local lastStep
--下载中标志
local downloading
local function otaCb(result,filePath,md5,size)
log.info("aLiYunOta.otaCb",gCb,result,filePath,size,(type(gName) =="string") and io.fileSize(filePath) or "function")
downloading = false
--校验MD5
if result then
if type(gName) == "string" then
local calMD5 = crypto.md5(filePath,"file")
result = (string.upper(calMD5) == string.upper(md5))
log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
else
local calMD5 = flowMd5:hexdigest()
result = (string.upper(calMD5) == string.upper(md5))
log.info("aLiYunOta.otaCb cmp md5",result,calMD5,md5)
end
end
rtos.fota_end()
if not result then
rtos.fota_start()
rtos.fota_end()
end
if gCb then
gCb(result,filePath)
else
if result then
sys.restart("ALIYUN_OTA")
end
end
end
--[[
函数名upgradeStepRpt
功能 :新固件文件下载进度上报
参数
step1到100代表下载进度比-2代表下载失败
desc描述信息可为空或者nil
返回值:无
]]
local function upgradeStepRpt(step,desc)
log.info("aLiYunOta.upgradeStepRpt",step,desc,sConnected)
if sConnected then
if step<=0 or step==100 then sys.timerStop(getPercent) end
lastStep = step
aLiYun.publish("/ota/device/progress/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"step\":\""..step.."\",\"desc\":\""..(desc or "").."\"}}")
end
end
function getPercent()
local step
if type(gName) == "string" then
step = io.fileSize(gName)*100/gFileSize
else
step = processed*100/gFileSize
end
log.info("aLiYunOta.getPercent",step)
if step~=0 and step~=lastStep then
upgradeStepRpt(step)
end
sys.timerStart(getPercent,5000)
end
local function downloadCbFnc(result,prompt,head,filePath)
log.info("aLiYunOta.downloadCbFnc",result,prompt,filePath)
sys.publish("ALIYUN_OTA_DOWNLOAD_IND",result)
end
local function saveUpdata(pdata,binlen,statusCode)
log.info("saveUpdata",binlen,statusCode)
if statusCode == "206" and pdata and binlen then
if rtos.fota_process(pdata,binlen) ~=0 then --返回-94表示没有进行初始化需要调用rtos.fota_start()进行更新初始化
log.info("updata.fota_process","fail!!")
return
else
flowMd5:update(pdata)
--打印此升级包的长度跟总包长度
processed = processed + pdata:len()
log.info("updata.fota_process",processed,binlen)
end
end
end
local function downloadTask(url,size,md5,ver)
log.info("aLiYunOta.downloadTask1",downloading,url,size,md5)
if not downloading then
downloading = true
gFileSize = size
local rangeBegin,retryCnt = 0,0
sys.timerStart(getPercent,5000)
sys.publish("LIB_ALIYUN_OTA_DOWNLOAD_BEGIN",ver)
while true do
if not gName then
gName = saveUpdata
end
http.request("GET",url,nil,{["Range"]="bytes="..rangeBegin.."-"},"",20000,downloadCbFnc,gName)
--if rangeBegin==0 then os.remove(gName) end
local _,result = sys.waitUntil("ALIYUN_OTA_DOWNLOAD_IND")
log.info("aLiYunOta.downloadTask2",result)
if result then
upgradeStepRpt(100,0)
sys.timerStart(otaCb,5000,true,gName,md5,size)
break
else
retryCnt = retryCnt+1
if retryCnt>=30 then
upgradeStepRpt(-2,"timeout")
otaCb(false,gName)
break
end
end
if type(gName) == "string" then
rangeBegin = io.fileSize(gName)
else
rangeBegin = processed
end
end
end
end
--[[
函数名upgrade
功能 :收到云端固件升级通知消息时的回调函数
参数
payload消息负载原始编码收到的payload是什么内容就是什么内容没有做任何编码转换
返回值:无
]]
function upgrade(payload)
local result
local jsonData, result = json.decode(payload)
log.info("aLiYunOta.upgrade", result, payload)
if result and jsonData.data and jsonData.data.url then
if rtos.fota_start() ~= 0 then
log.info("fota_start fail")
sys.timerStart(verRpt, 10000)
return
end
flowMd5 = crypto.flow_md5()
sys.taskInit(downloadTask, jsonData.data.url, jsonData.data.size, jsonData.data.md5, jsonData.data.version)
end
end
--[[
函数名verRptCb
功能 上报固件版本号给云端后收到PUBACK时的回调函数
参数
resulttrue表示上报成功false或者nil表示失败
返回值:无
]]
local function verRptCb(result)
log.info("aLiYunOta.verRptCb",result)
verRpted = result
if not result then sys.timerStart(verRpt,20000) end
end
--[[
函数名verRpt
功能 :上报固件版本号给云端
参数 :无
返回值:无
]]
function verRpt()
log.info("aLiYunOta.verRpt",sConnected,gVersion)
if sConnected then
aLiYun.publish("/ota/device/inform/"..productKey.."/"..deviceName,"{\"id\":1,\"params\":{\"version\":\""..gVersion.."\"}}",1,verRptCb)
end
end
function connectCb(result,key,name)
sConnected = result
if result then
log.info("aLiYunOta.connectCb",verRpted)
productKey,deviceName = key,name
--订阅主题
aLiYun.subscribe({["/ota/device/upgrade/"..key.."/"..name]=0, ["/ota/device/upgrade/"..key.."/"..name]=1})
if not verRpted then
--上报固件版本号给云端
verRpt()
end
else
sys.timerStop(verRpt)
end
end
function isDownloading()
return downloading
end
--- 设置当前的固件版本号
-- @string version 当前固件版本号
-- @return nil
-- @usage
-- aLiYunOta.setVer("MCU_VERSION_1.0.0")
function setVer(version)
local oldVer = gVersion
gVersion = version
if verRpted and version~=oldVer then
verRpted = false
verRpt()
end
end
--- 设置新固件保存的文件名
-- @string name 新固件下载后保存的文件名注意此文件名并不是保存的完整路径完整路径通过setCb设置的回调函数去获取
-- @return nil
-- @usage
-- aLiYunOta.setName("MCU_FIRMWARE.bin")
function setName(name)
gName = name
end
--- 设置新固件下载后的回调函数
-- @function cbFnc 新固件下载后的回调函数
-- 回调函数的调用形式为cbFnc(result,filePath)result为下载结果true表示成功false或者nil表示失败filePath为新固件文件保存的完整路径
-- @return nil
-- @usage
-- aLiYunOta.setCb(cbFnc)
function setCb(cbFnc)
gCb = cbFnc
end

232
4G/code/lib/agps.lua Normal file
View File

@@ -0,0 +1,232 @@
--- 模块功能GPS辅助定位以及星历更新服务.
-- 本功能模块只能配合Air800或者Air530使用
-- require"agps"后,会自动开启本功能模块的任务;
-- 开机后仅获取一次基站对应的经纬度位置和当前时间把经纬度位置和时间写到GPS芯片中可以加速GPS定位
-- 会定期更新GPS星历星历更新算法如下
-- 从最后一次GPS定位成功的时间算起每隔4小时连接星历服务器下载一次星历数据大概4K字节写入GPS芯片。
-- 例如01:00分开机后更新了一次星历文件截止到05:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在05:00就会下载星历数据然后写入GPS芯片
-- 05:00更新星历数据后在06:00打开了GPS并且GPS定位成功然后在07:00关闭了GPS关闭前GPS仍然处于定位成功状态
-- 截止到11:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在11:00就会下载星历数据然后写入GPS芯片
-- @module agps
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.26
require"http"
require"lbsLoc"
require"net"
require"gps"
module(..., package.seeall)
local EPH_TIME_FILE = "/ephTime.txt"
local EPH_DATA_FILE = "/ephData.bin"
local writeEphIdx,sEphData,writeEphSta = 0
local EPH_UPDATE_INTERVAL = 4*3600
local lastLbsLng,lastLbsLat = "",""
local fneed_xingli=false
local function runTimer()
sys.timerStart(updateEph,EPH_UPDATE_INTERVAL*1000)
end
local function writeEphEnd()
log.info("agps.writeEphEnd")
local cmd,sum = (("AAF00E00950000"):fromHex())..pack.pack("<i",gps.uartBaudrate),0
for i=3,cmd:len() do
sum = bit.bxor(sum,cmd:byte(i))
end
gps.writeCmd(cmd..string.char(sum).."\r\n")
sys.timerStart(gps.close,2000,gps.TIMER,{tag="lib.agps.lua.eph"})
writeEphIdx,sEphData,writeEphSta = 0
end
local function writeEph()
log.info("agps.writeEph",writeEphSta)
if writeEphSta=="IDLE" then
gps.writeCmd("$PGKC149,1,"..gps.uartBaudrate.."*")
writeEphSta = "WAIT_BINARY_CMD_ACK"
elseif writeEphSta=="WAIT_BINARY_CMD_ACK" or writeEphSta=="WAIT_WRITE_EPH_CMD_ACK" then
if sEphData and sEphData:len()>0 then
local hexStr = sEphData:sub(1,1024)
if hexStr:len()<1024 then hexStr = hexStr..("F"):rep(1024-hexStr:len()) end
hexStr = "AAF00B026602"..string.format("%02X",writeEphIdx):upper().."00"..hexStr
local checkSum = 0
local binStr = hexStr:fromHex()
for i=3,binStr:len() do
checkSum = bit.bxor(checkSum,binStr:byte(i))
end
string.format("%02X",checkSum):upper()
hexStr = hexStr..(string.format("%02X",checkSum):upper()).."0D0A"
gps.writeCmd(hexStr:fromHex(),true)
sEphData = sEphData:sub(1025,-1)
writeEphIdx = writeEphIdx+1
writeEphSta = "WAIT_WRITE_EPH_CMD_ACK"
else
gps.writeCmd(("AAF00B006602FFFF6F0D0A"):fromHex(),true)
writeEphSta = "WAIT_WRITE_EPH_END_CMD_ACK"
end
elseif writeEphSta=="WAIT_WRITE_EPH_END_CMD_ACK" then
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
writeEphEnd()
end
end
local function writeEphBegin()
writeEphSta = "IDLE"
writeEph()
end
local function downloadEphCb(result,prompt,head,body)
log.info("agps.downloadEphCb",result,prompt)
runTimer()
if result and prompt=="200" and body then
io.writeFile(EPH_DATA_FILE,body)
if gps.isFix() then
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
else
fneed_xingli=false
sEphData = body:toHex()
gps.open(gps.TIMER,{tag="lib.agps.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEphBegin,2000)
return
end
end
end
--连接服务器下载星历
function updateEph()
if gps.isFix() then runTimer() return end
http.request("GET","download.openluat.com/9501-xingli/brdcGPD.dat_rda",nil,nil,nil,20000,downloadEphCb)
end
--JWL在星历的时间范围内如果有星历文件重启模块就直接把本地的星历文件写入GPS芯片
--在AGPS_LOCATED 的订阅事件中,调用此接口。
function upd_xingli()
if not gps.isFix() then
local lstm = io.readFile(EPH_TIME_FILE)
if not lstm or lstm=="" then return end
log.info("agps.upd_xingli lstm=",lstm)
if os.time()-tonumber(lstm) < EPH_UPDATE_INTERVAL then
local body = io.readFile(EPH_DATA_FILE)
if body and #body >0 then
log.info("agps.upd_xingli length=",#body)
fneed_xingli=false
sEphData = body:toHex()
gps.open(gps.TIMER,{tag="lib.agps.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEphBegin,2000)
else
log.info("agps.upd_xingli wanto update xingli data")
updateEph()
end
end
end
end
sys.subscribe("AGPS_LOCATED", function()
sys.timerStart(function()
log.info("agps.want to upd_xingli",fneed_xingli)
if fneed_xingli then
upd_xingli()
end
end,5000)
end)
--检查是否需要更新星历
local function checkEph()
local result
if not gps.isFix() then
lastTm = io.readFile(EPH_TIME_FILE)
if not lastTm or lastTm=="" then return true end
log.info("agps.checkEph",os.time(),tonumber(lastTm)," DELTA=",os.time()-tonumber(lastTm))
result = (os.time()-tonumber(lastTm) >= EPH_UPDATE_INTERVAL)
end
if not result then runTimer() end
return result
end
local function setFastFix(lng,lat,tm)
gps.setFastFix(lat,lng,tm)
if checkEph() then updateEph() end
end
local getloc = 0
local lbsLocRequesting
--获取到基站对应的经纬度写到GPS芯片中
local function getLocCb(result,lat,lng,addr,time)
fneed_xingli=true
log.info("agps.getLocCb",result,lat,lng,time and time:len() or 0)
lbsLocRequesting = false
if result==0 then
lastLbsLng,lastLbsLat = lng,lat
sys.publish("AGPS_LOCATED", lng,lat)
if not gps.isFix() then
local tm = {year=0,month=0,day=0,hour=0,min=0,sec=0}
if time:len()==6 then
tm = {year=time:byte(1)+2000,month=time:byte(2),day=time:byte(3),hour=time:byte(4),min=time:byte(5),sec=time:byte(6)}
misc.setClock(tm)
tm = common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0)
end
gps.open(gps.TIMERORSUC,{tag="lib.agps.lua.fastFix",val=4})
sys.timerStart(setFastFix,2000,lng,lat,tm)
getloc = 1
end
end
if result~=0 or gps.isFix() then
if checkEph() then updateEph() end
end
end
--是否获取到基站对应的经纬度
function isgetloc()
return getloc
end
local function ipReady()
if gps.isFix() then
runTimer()
else
if not lbsLocRequesting then
lbsLocRequesting = true
lbsLoc.request(getLocCb,nil,30000,"0","bs.openluat.com","12411",true)
end
end
end
local function gpsState(evt,para)
log.info("agps.GPS_STATE",evt,para)
if evt=="LOCATION_SUCCESS" or (evt=="CLOSE" and para==true) then
runTimer()
elseif evt=="BINARY_CMD_ACK" or evt=="WRITE_EPH_ACK" or evt=="WRITE_EPH_END_ACK" then
writeEph()
elseif evt=="OPEN" then
local lng,lat = gps.getLastLocation()
if lng=="" or lat=="" then
lng,lat = lastLbsLng,lastLbsLat
end
if lng~="" and lat~="" then
gps.open(gps.TIMERORSUC,{tag="lib.agps.lua.fastFix",val=4})
local tm = os.date("*t")
sys.timerStart(gps.setFastFix,2000,lat,lng,common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0))
end
end
end
function init()
sys.subscribe("GPS_STATE",gpsState)
sys.subscribe("IP_READY_IND",ipReady)
log.info("agps.init")
end
function unInit()
sys.unsubscribe("GPS_STATE",gpsState)
sys.unsubscribe("IP_READY_IND",ipReady)
log.info("agps.unInit")
end
init()

292
4G/code/lib/agps9701.lua Normal file
View File

@@ -0,0 +1,292 @@
--- 模块功能GPS辅助定位以及星历更新服务.
-- 本功能模块只能配合Air512G、Air551G、Air552G使用
-- require"agps"后,会自动开启本功能模块的任务;
-- 开机后仅获取一次基站对应的经纬度位置和当前时间把经纬度位置和时间写到GPS芯片中可以加速GPS定位
-- 会定期更新GPS星历星历更新算法如下
-- 从最后一次GPS定位成功的时间算起每隔4小时连接星历服务器下载一次星历数据大概4K字节写入GPS芯片。
-- 例如01:00分开机后更新了一次星历文件截止到05:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在05:00就会下载星历数据然后写入GPS芯片
-- 05:00更新星历数据后在06:00打开了GPS并且GPS定位成功然后在07:00关闭了GPS关闭前GPS仍然处于定位成功状态
-- 截止到11:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在11:00就会下载星历数据然后写入GPS芯片
-- @module agps9701
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.26
require"http"
require"lbsLoc"
require"net"
local gps = require"gps9701"
module(..., package.seeall)
local EPH_TIME_FILE = "/ephTime.txt"
local EPH_DATA_FILE = "/ephData.bin"
local writeEphIdx,sEphData,writeEphSta = 0
local EPH_UPDATE_INTERVAL = 4*3600
local lastLbsLng,lastLbsLat = "",""
local fneed_xingli=false
local cachepack = ""
local gpsresult = nil
local agpsend = nil
local tasklock = true
--重置写入agps星历包
local function agpsreag()
tasklock = false
sys.taskInit(function()
while true do
if agpsend then
log.info("agpsend")
tasklock = true
agpsend = false
break
end
sys.wait(1000)
if gps.checkdatapack then
while true do
if agpsend then
break
end
gpsresult = sys.waitUntil("NOEMAL_RECE",1000)
if (not gpsresult) and (not agpsend) then
gps.writeCmd(cachepack:fromHex(),true)
end
end
end
end
end)
end
local function runTimer()
sys.timerStart(updateEph,EPH_UPDATE_INTERVAL*1000)
end
local function writeEphEnd()
agpsend = true
log.info("agps.writeEphEnd",pack.pack("<i",gps.uartBaudrate):toHex())
local cmd,sum = (("AAF00F00950000"):fromHex())..pack.pack("<i",gps.uartBaudrate),0
for i=3,cmd:len() do
sum = bit.bxor(sum,cmd:byte(i))
end
local validate = "00AA0F"
gps.writeCmd(cmd..string.char(sum)..validate:fromHex(),true)
sys.timerStart(gps.close,2000,gps.TIMER,{tag="lib.agps.lua.eph"})
writeEphIdx,sEphData,writeEphSta = 0
end
local function writeEph()
log.info("agps.writeEph",writeEphSta)
if writeEphSta=="IDLE" then
if tasklock then
agpsreag()
end
gps.writeCmd("$PGKC149,1,"..gps.uartBaudrate.."*")
writeEphSta = "WAIT_BINARY_CMD_ACK"
elseif writeEphSta=="WAIT_BINARY_CMD_ACK" or writeEphSta=="WAIT_WRITE_EPH_CMD_ACK" then
if sEphData and sEphData:len()>0 then
local hexStr = sEphData:sub(1,1024)
if hexStr:len()<1024 then hexStr = hexStr..("0"):rep(1024-hexStr:len()) end
hexStr = "AAF00C026602"..string.format("%02X",writeEphIdx):upper().."00"..hexStr
local checkSum = 0
local binStr = hexStr:fromHex()
for i=3,binStr:len() do
checkSum = bit.bxor(checkSum,binStr:byte(i))
end
string.format("%02X",checkSum):upper()
hexStr = hexStr..(string.format("%02X",checkSum):upper()).."00".."AA0F"
cachepack = hexStr
gps.writeCmd(hexStr:fromHex(),true)
sEphData = sEphData:sub(1025,-1)
writeEphIdx = writeEphIdx+1
writeEphSta = "WAIT_WRITE_EPH_CMD_ACK"
else
gps.writeCmd(("AAF00C006602FFFF6800AA0F"):fromHex(),true)
writeEphSta = "WAIT_WRITE_EPH_END_CMD_ACK"
end
elseif writeEphSta=="WAIT_WRITE_EPH_END_CMD_ACK" then
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
writeEphEnd()
end
end
-- sys.taskInit(function()
-- sys.waitUntil()
-- while true do
-- if agpsend then
-- log.info("agpsend")
-- break
-- end
-- sys.wait(1000)
-- if gps.checkdatapack then
-- while true do
-- if agpsend then
-- break
-- end
-- gpsresult = sys.waitUntil("NOEMAL_RECE",1000)
-- if (not gpsresult) and (not agpsend) then
-- gps.writeCmd(cachepack:fromHex(),true)
-- end
-- end
-- end
-- end
-- end)
local function writeEphBegin()
writeEphSta = "IDLE"
writeEph()
end
local function downloadEphCb(result,prompt,head,body)
log.info("agps.downloadEphCb",result,prompt,body)
runTimer()
if result and prompt=="200" and body then
io.writeFile(EPH_DATA_FILE,body)
if gps.isFix() then
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
else
fneed_xingli=false
sEphData = body:toHex()
gps.open(gps.TIMER,{tag="lib.agps.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEphBegin,2000)
return
end
end
end
--连接服务器下载星历
function updateEph()
if gps.isFix() then runTimer() return end
http.request("GET","download.openluat.com/9501-xingli/brdcGPD.dat_rda",nil,nil,nil,20000,downloadEphCb)
end
--JWL在星历的时间范围内如果有星历文件重启模块就直接把本地的星历文件写入GPS芯片
--在AGPS_LOCATED 的订阅事件中,调用此接口。
function upd_xingli()
if not gps.isFix() then
local lstm = io.readFile(EPH_TIME_FILE)
if not lstm or lstm=="" then return end
log.info("agps.upd_xingli lstm=",lstm)
if os.time()-tonumber(lstm) < EPH_UPDATE_INTERVAL then
local body = io.readFile(EPH_DATA_FILE)
if body and #body >0 then
log.info("agps.upd_xingli length=",#body)
fneed_xingli=false
sEphData = body:toHex()
gps.open(gps.TIMER,{tag="lib.agps.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEphBegin,2000)
else
log.info("agps.upd_xingli wanto update xingli data")
updateEph()
end
end
end
end
sys.subscribe("AGPS_LOCATED", function()
sys.timerStart(function()
log.info("agps.want to upd_xingli",fneed_xingli)
if fneed_xingli then
upd_xingli()
end
end,7000)
end)
--检查是否需要更新星历
local function checkEph()
local result
if not gps.isFix() then
lastTm = io.readFile(EPH_TIME_FILE)
if not lastTm or lastTm=="" then return true end
log.info("agps.checkEph",os.time(),tonumber(lastTm)," DELTA=",os.time()-tonumber(lastTm))
result = (os.time()-tonumber(lastTm) >= EPH_UPDATE_INTERVAL)
end
if not result then runTimer() end
return result
end
local function setFastFix(lng,lat,tm)
gps.setFastFix(lat,lng,tm)
if checkEph() then
updateEph()
end
end
local getloc = 0
local lbsLocRequesting
--获取到基站对应的经纬度写到GPS芯片中
local function getLocCb(result,lat,lng,addr,time)
fneed_xingli=true
log.info("agps.getLocCb",result,lat,lng,time and time:len() or 0)
lbsLocRequesting = false
if result==0 then
lastLbsLng,lastLbsLat = lng,lat
sys.publish("AGPS_LOCATED", lng,lat)
if not gps.isFix() then
local tm = {year=0,month=0,day=0,hour=0,min=0,sec=0}
if time:len()==6 then
tm = {year=time:byte(1)+2000,month=time:byte(2),day=time:byte(3),hour=time:byte(4),min=time:byte(5),sec=time:byte(6)}
misc.setClock(tm)
tm = common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0)
end
gps.open(gps.TIMERORSUC,{tag="lib.agps.lua.fastFix",val=4})
sys.timerStart(setFastFix,2000,lng,lat,tm)
getloc = 1
end
end
if result~=0 or gps.isFix() then
if checkEph() then updateEph() end
end
end
--是否获取到基站对应的经纬度
function isgetloc()
return getloc
end
local function ipReady()
if gps.isFix() then
runTimer()
else
if not lbsLocRequesting then
lbsLocRequesting = true
lbsLoc.request(getLocCb,nil,30000,"0","bs.openluat.com","12411",true)
end
end
end
local function gpsState(evt,para)
log.info("agps.GPS_STATE",evt,para)
if evt=="LOCATION_SUCCESS" or (evt=="CLOSE" and para==true) then
runTimer()
elseif evt=="BINARY_CMD_ACK" or evt=="WRITE_EPH_ACK" or evt=="WRITE_EPH_END_ACK" then
writeEph()
elseif evt=="OPEN" then
local lng,lat = gps.getLastLocation()
if lng=="" or lat=="" then
lng,lat = lastLbsLng,lastLbsLat
end
if lng~="" and lat~="" then
gps.open(gps.TIMERORSUC,{tag="lib.agps.lua.fastFix",val=4})
local tm = os.date("*t")
sys.timerStart(gps.setFastFix,2000,lat,lng,common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0))
end
end
end
function init()
sys.subscribe("GPS_STATE",gpsState)
sys.subscribe("IP_READY_IND",ipReady)
log.info("agps.init")
end
function unInit()
sys.unsubscribe("GPS_STATE",gpsState)
sys.unsubscribe("IP_READY_IND",ipReady)
log.info("agps.unInit")
end
init()

194
4G/code/lib/agpsHxxt.lua Normal file
View File

@@ -0,0 +1,194 @@
--- 模块功能GPS辅助定位以及星历更新服务.
-- 本功能模块只能配合Air820UX系列的模块以及Air530H模块和芯星通GPS芯片使用
-- require"agpsHxxt"后,会自动开启本功能模块的任务;
-- 会定期更新GPS星历星历更新算法如下
-- 从最后一次GPS定位成功的时间算起每隔4小时连接星历服务器下载一次星历数据大概4K字节写入GPS芯片。
-- 例如01:00分开机后更新了一次星历文件截止到05:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在05:00就会下载星历数据然后写入GPS芯片
-- 05:00更新星历数据后在06:00打开了GPS并且GPS定位成功然后在07:00关闭了GPS关闭前GPS仍然处于定位成功状态
-- 截止到11:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在11:00就会下载星历数据然后写入GPS芯片
-- @module agpsHxxt
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2020.10.28
require"http"
require"lbsLoc"
require"net"
local gps = require"gpsHxxt"
module(..., package.seeall)
local EPH_TIME_FILE = "/ephTime.txt"
local EPH_DATA_FILE = "/ephData.bin"
local sEphData
local EPH_UPDATE_INTERVAL = 3*3600
local lastLbsLng,lastLbsLat = "",""
local function runTimer()
sys.timerStart(updateEph,EPH_UPDATE_INTERVAL*1000)
end
local function writeEphEnd()
log.info("agpsHxxt.writeEphEnd")
sys.timerStart(gps.close,3000,gps.TIMER,{tag="lib.agpsHxxt.lua.eph"})
sEphData = nil
end
local function writeEph()
log.info("agpsHxxt.writeEph")
gps.writeData(sEphData)
--gps.writeData(io.readFile("/lua/aid_data.bin"))
--uart.setup(2, 9600, 8, uart.PAR_NONE, uart.STOP_1)
--uart.write(2,io.readFile("/lua/aid_data.bin"))
writeEphEnd()
end
local function downloadEphCb(result,prompt,head,body)
log.info("agpsHxxt.downloadEphCb",result,prompt)
runTimer()
if result and prompt=="200" and body then
io.writeFile(EPH_DATA_FILE,body)
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
if gps.isFix() then
-- io.writeFile(EPH_TIME_FILE,tostring(os.time()))
else
sEphData = body
gps.open(gps.TIMER,{tag="lib.agpsHxxt.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEph,2000)
return
end
end
end
--连接服务器下载星历
function updateEph()
if gps.isFix() then runTimer() return end
http.request("GET","download.openluat.com:80/9501-xingli/HXXT_GPS_BDS_AGNSS_DATA.dat",nil,nil,nil,20000,downloadEphCb)
end
--JWL在星历的时间范围内如果有星历文件重启模块就直接把本地的星历文件写入GPS芯片
--在AGPS_LOCATED 的订阅事件中,调用此接口。
function upd_xingli()
if not gps.isFix() then
local lstm = io.readFile(EPH_TIME_FILE)
if not lstm or lstm=="" then return end
log.info("agpsHxxt.upd_xingli lstm=",lstm)
if os.time()-tonumber(lstm) < EPH_UPDATE_INTERVAL then
local body = io.readFile(EPH_DATA_FILE)
if body and #body >0 then
log.info("agpsHxxt.upd_xingli length=",#body)
sEphData = body
gps.open(gps.TIMER,{tag="lib.agpsHxxt.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEph,2000)
else
log.info("agpsHxxt.upd_xingli wanto update xingli data")
updateEph()
end
end
end
end
--检查是否需要更新星历
local function checkEph()
local result
if not gps.isFix() then
lastTm = io.readFile(EPH_TIME_FILE)
if not lastTm or lastTm=="" then return true end
log.info("agpsHxxt.checkEph",os.time(),tonumber(lastTm)," DELTA=",os.time()-tonumber(lastTm))
result = (os.time()-tonumber(lastTm) >= EPH_UPDATE_INTERVAL)
end
if not result then runTimer() end
return result
end
local function setFastFix(lng,lat,tm)
gps.setFastFix(lat,lng,tm)
if checkEph() then updateEph()end
end
local getloc = 0
local lbsLocRequesting
--获取到基站对应的经纬度写到GPS芯片中
local function getLocCb(result,lat,lng,addr,time)
log.info("agpsHxxt.getLocCb",result,lat,lng,time and time:len() or 0)
lbsLocRequesting = false
if result==0 then
lastLbsLng,lastLbsLat = lng,lat
if not gps.isFix() then
local tm = {year=0,month=0,day=0,hour=0,min=0,sec=0}
if time:len()==6 then
tm = {year=time:byte(1)+2000,month=time:byte(2),day=time:byte(3),hour=time:byte(4),min=time:byte(5),sec=time:byte(6)}
misc.setClock(tm)
tm = common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0)
end
gps.open(gps.TIMERORSUC,{tag="lib.agpsHxxt.lua.fastFix",val=4})
sys.timerStart(setFastFix,2000,lng,lat,tm)
getloc = 1
end
end
if result~=0 or gps.isFix() then
if checkEph() then updateEph() end
end
end
--是否获取到基站对应的经纬度
function isgetloc()
return getloc
end
local function ipReady()
if gps.isFix() then
runTimer()
else
if not lbsLocRequesting then
lbsLocRequesting = true
lbsLoc.request(getLocCb,nil,30000,"0","bs.openluat.com","12411",true)
end
log.info("agpsHxxt.ipready to updateEph")
if checkEph() then
updateEph()
else
sys.timerStart(upd_xingli,3000)
end
end
end
local function gpsState(evt,para)
log.info("agpsHxxt.GPS_STATE",evt,para)
if evt=="LOCATION_SUCCESS" or (evt=="CLOSE" and para==true) then
runTimer()
elseif evt=="OPEN" then
local lng,lat = gps.getLastLocation()
if lng=="" or lat=="" then
lng,lat = lastLbsLng,lastLbsLat
end
if lng~="" and lat~="" then
gps.open(gps.TIMERORSUC,{tag="lib.agpsHxxt.lua.fastFix",val=4})
local tm = os.date("*t")
sys.timerStart(gps.setFastFix,2000,lat,lng,common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0))
end
end
end
function init()
sys.subscribe("GPS_STATE",gpsState)
sys.subscribe("IP_READY_IND",ipReady)
log.info("agpsHxxt.init")
end
function unInit()
sys.unsubscribe("GPS_STATE",gpsState)
sys.unsubscribe("IP_READY_IND",ipReady)
log.info("agpsHxxt.unInit")
end
init()

193
4G/code/lib/agpsZkw.lua Normal file
View File

@@ -0,0 +1,193 @@
--- 模块功能GPS辅助定位以及星历更新服务.
-- 本功能模块只能配合Air820UX系列的模块以及Air530Z模块中科微GPS芯片使用
-- require"agpsZkw"后,会自动开启本功能模块的任务;
-- 会定期更新GPS星历星历更新算法如下
-- 从最后一次GPS定位成功的时间算起每隔4小时连接星历服务器下载一次星历数据大概4K字节写入GPS芯片。
-- 例如01:00分开机后更新了一次星历文件截止到05:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在05:00就会下载星历数据然后写入GPS芯片
-- 05:00更新星历数据后在06:00打开了GPS并且GPS定位成功然后在07:00关闭了GPS关闭前GPS仍然处于定位成功状态
-- 截止到11:00“一直没有开启过GPS”或者“开启过GPS但是GPS从来没有定位成功”在11:00就会下载星历数据然后写入GPS芯片
-- @module agpsZkw
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2020.10.28
require"http"
require"lbsLoc"
require"net"
local gps = require"gpsZkw"
module(..., package.seeall)
local EPH_TIME_FILE = "/ephTime.txt"
local EPH_DATA_FILE = "/ephData.bin"
local sEphData
local EPH_UPDATE_INTERVAL = 4*3600
local lastLbsLng,lastLbsLat = "",""
local function runTimer()
sys.timerStart(updateEph,EPH_UPDATE_INTERVAL*1000)
end
local function writeEphEnd()
log.info("agpsZkw.writeEphEnd")
sys.timerStart(gps.close,3000,gps.TIMER,{tag="lib.agpsZkw.lua.eph"})
sEphData = nil
end
local function writeEph()
log.info("agpsZkw.writeEph")
gps.writeData(sEphData)
writeEphEnd()
end
local function downloadEphCb(result,prompt,head,body)
log.info("agpsZkw.downloadEphCb",result,prompt)
runTimer()
if result and prompt=="200" and body then
io.writeFile(EPH_DATA_FILE,body)
io.writeFile(EPH_TIME_FILE,tostring(os.time()))
if gps.isFix() then
else
sEphData = body
gps.open(gps.TIMER,{tag="lib.agpsZkw.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEph,2000)
return
end
end
end
--连接服务器下载星历
function updateEph()
if gps.isFix() then runTimer() return end
http.request("GET","http://download.openluat.com/9501-xingli/CASIC_data.dat",nil,nil,nil,20000,downloadEphCb)
end
--JWL在星历的时间范围内如果有星历文件重启模块就直接把本地的星历文件写入GPS芯片
--在AGPS_LOCATED 的订阅事件中,调用此接口。
function upd_xingli()
if not gps.isFix() then
local lstm = io.readFile(EPH_TIME_FILE)
if not lstm or lstm=="" then return end
log.info("agpsZkw.upd_xingli lstm=",lstm)
if os.time()-tonumber(lstm) < EPH_UPDATE_INTERVAL then
local body = io.readFile(EPH_DATA_FILE)
if body and #body >0 then
log.info("agpsZkw.upd_xingli length=",#body)
sEphData = body
gps.open(gps.TIMER,{tag="lib.agpsZkw.lua.eph",val=10,cb=writeEphEnd})
sys.timerStart(writeEph,2000)
else
log.info("agpsZkw.upd_xingli wanto update xingli data")
updateEph()
end
end
end
end
--检查是否需要更新星历
local function checkEph()
local result
if not gps.isFix() then
lastTm = io.readFile(EPH_TIME_FILE)
if not lastTm or lastTm=="" then return true end
log.info("agpsZkw.checkEph",os.time(),tonumber(lastTm)," DELTA=",os.time()-tonumber(lastTm))
result = (os.time()-tonumber(lastTm) >= EPH_UPDATE_INTERVAL)
end
if not result then runTimer() end
return result
end
local function setFastFix(lng,lat,tm)
gps.setFastFix(lat,lng,tm)
if checkEph() then updateEph() end
end
local getloc = 0
local lbsLocRequesting
--获取到基站对应的经纬度写到GPS芯片中
local function getLocCb(result,lat,lng,addr,time)
log.info("agpsZkw.getLocCb",result,lat,lng,time and time:len() or 0)
lbsLocRequesting = false
if result==0 then
lastLbsLng,lastLbsLat = lng,lat
if not gps.isFix() then
local tm = {year=0,month=0,day=0,hour=0,min=0,sec=0}
if time:len()==6 then
tm = {year=time:byte(1)+2000,month=time:byte(2),day=time:byte(3),hour=time:byte(4),min=time:byte(5),sec=time:byte(6)}
misc.setClock(tm)
tm = common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0)
end
gps.open(gps.TIMERORSUC,{tag="lib.agpsZkw.lua.fastFix",val=4})
sys.timerStart(gps.setFastFix,2000,lng,lat,tm)
getloc = 1
end
end
if result~=0 or gps.isFix() then
if checkEph() then updateEph() end
end
end
--是否获取到基站对应的经纬度
function isgetloc()
return getloc
end
local function ipReady()
if gps.isFix() then
runTimer()
else
if not lbsLocRequesting then
lbsLocRequesting = true
lbsLoc.request(getLocCb,nil,30000,"0","bs.openluat.com","12411",true)
end
log.info("agpsZkw.ipready to updateEph")
if checkEph() then
updateEph()
else
sys.timerStart(upd_xingli,3000)
end
end
end
local function gpsState(evt,para)
log.info("agpsZkw.GPS_STATE",evt,para)
if evt=="LOCATION_SUCCESS" or (evt=="CLOSE" and para==true) then
runTimer()
elseif evt=="OPEN" then
local lng,lat = gps.getLastLocation()
if lng=="" or lat=="" then
lng,lat = lastLbsLng,lastLbsLat
end
if lng~="" and lat~="" then
gps.open(gps.TIMERORSUC,{tag="lib.agpsZkw.lua.fastFix",val=4})
local tm = os.date("*t")
sys.timerStart(gps.setFastFix,2000,lat,lng,common.timeZoneConvert(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec,8,0))
end
end
end
function init()
sys.subscribe("GPS_STATE",gpsState)
sys.subscribe("IP_READY_IND",ipReady)
log.info("agpsZkw.unInit")
end
function unInit()
sys.unsubscribe("GPS_STATE",gpsState)
sys.unsubscribe("IP_READY_IND",ipReady)
log.info("agpsZkw.unInit")
end
init()

483
4G/code/lib/audio.lua Normal file
View File

@@ -0,0 +1,483 @@
--- 模块功能:音频播放.
-- 支持MP3、amr文件播放
-- 支持本地TTS播放、通话中TTS播放到对端需要使用支持TTS功能的core软件
-- @module audio
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.3.19
require "common"
require "misc"
require "utils"
module(..., package.seeall)
local req = ril.request
local stopCbFnc
--tts速度默认50
local ttsSpeed = 50
--喇叭音量和mic音量等级
local sVolume,sMicVolume = 4,1
local sCallVolume = 4
--音频播放的协程ID
local taskID
--播放和停止请求队列用于存储通过调用audio.play和audio.stop接口允许播放和停止播放的请求项
--每个播放请求项为table类型数据结构如下参考本文件中的play接口注释
--priority播放优先级
--type播放类型
--path播放音频内容
--vol播放音量
--cbFnc播放结束后的回调函数
--dup是否重复播放
--dupInterval重复播放的间隔单位毫秒
--每个停止请求项为table类型数据结构如下参考本文件中的stop接口注释
--type固定为"STOP"
--cbFnc停止播放后的回调函数
local audioQueue = {}
--sStrategy优先级相同时的播放策略0(表示继续播放正在播放的音频,忽略请求播放的新音频)1(表示停止正在播放的音频,播放请求播放的新音频)
local sStrategy
local function isTtsStopResultValid()
local version = string.match(rtos.get_version(),"(%d+)_RDA")
if version then
return tonumber(version)>=8
else
return false
end
end
local function handleCb(item,result)
log.info("audio.handleCb",item.cbFnc,result)
if item.cbFnc then item.cbFnc(result) end
table.remove(audioQueue,1)
end
local function handlePlayInd(item,key,value)
log.info("audio.handlePlayInd",key,value)
--播放结束
if key=="RESULT" then
--播放成功
if value then
if item.dup then
if item.dupInterval>0 then
log.info("audio.handlePlayInd",item.type,"dup wait LIB_AUDIO_PLAY_IND or timeout",item.dupInterval)
local result,reason = sys.waitUntil("LIB_AUDIO_PLAY_IND",item.dupInterval)
log.info("audio.handlePlayInd",item.type,"dup wait",reason or "timeout")
if result then
log.warn("audio.handlePlayInd",item.type,"dup wait error",reason)
handleCb(item,reason=="NEW" and 4 or 5)
end
end
else
handleCb(item,0)
end
--播放失败
else
log.warn("audio.handlePlayInd",item.type,"play cnf error")
handleCb(item,1)
end
--新的优先级更高的播放请求
elseif key=="NEW" then
log.warn("audio.handlePlayInd",item.type,"priority error")
handleCb(item,4)
--主动调用audio.stop
elseif key=="STOP" then
log.warn("audio.handlePlayInd",item.type,"stop error",result)
handleCb(item,5)
end
end
local ttsEngineInited
local audioTaskWaitPlayEntry
local function audioTask()
while true do
if #audioQueue==0 then
log.info("audioTask","wait LIB_AUDIO_PLAY_ENTRY")
audioTaskWaitPlayEntry = true
sys.waitUntil("LIB_AUDIO_PLAY_ENTRY")
audioTaskWaitPlayEntry = false
end
local item = audioQueue[1]
log.info("audioTask",item.type,"#audioQueue",#audioQueue)
if item.type=="FILE" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
local result
if type(item.path)=="table" then
if (item.path[1]):match("%.amr$") or (item.path[1]):match("%.AMR$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,(io.readFile(item.path[i])):sub(i==1 and 1 or 7,-1))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.AMR)
elseif (item.path[1]):match("%.pcm$") or (item.path[1]):match("%.PCM$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.PCM)
elseif (item.path[1]):match("%.mp3$") or (item.path[1]):match("%.MP3$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.MP3)
elseif (item.path[1]):match("%.wav$") or (item.path[1]):match("%.WAV$") then
local dataBuf = {}
for i=1,#item.path do
table.insert(dataBuf,io.readFile(item.path[i]))
end
result = audiocore.playdata(table.concat(dataBuf),audiocore.WAV)
else
result = false
end
--result = audiocore.play(unpack(item.path))
else
if item.path:match("%.wav$") or item.path:match("%.WAV$") then
result = audiocore.playdata(io.readFile(item.path),audiocore.WAV)
else
result =audiocore.play(item.path)
end
end
if result then
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
audiocore.stop()
handlePlayInd(item,key,value)
else
log.warn("audioTask",item.type,"audiocore.play error")
audiocore.stop()
handleCb(item,1)
end
end
elseif item.type=="TTS" or item.type=="TTSCC" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
if item.type=="TTS" then
if not ttsEngineInited then
ttsply.initEngine()
ttsEngineInited = true
end
ttsply.setParm(0,ttsSpeed)
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low1")
if isTtsStopResultValid() then
if ttsply.stop() then
sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
end
else
ttsply.stop()
sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
end
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
ttsply.play(common.utf8ToGb2312(item.path))
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
if item.type=="TTS" then
if isTtsStopResultValid() then
--log.info("tts 1")
if ttsply.stop() then
--log.info("tts 2")
sys.waitUntil("LIB_AUDIO_PLAY_IND",2000)
end
--log.info("tts 3")
else
ttsply.stop()
sys.waitUntil("LIB_AUDIO_PLAY_IND",500)
end
else
end
handlePlayInd(item,key,value)
end
else
end
end
elseif item.type=="RECORD" then
--队列中有优先级高的请求等待处理
if #audioQueue>1 then
log.warn("audioTask",item.type,"priority low")
local behind = audioQueue[2]
handleCb(item,behind.type=="STOP" and 5 or 4)
else
setVolume(item.vol)
f,d=record.getSize()
req("AT+AUDREC=1,0,2,"..item.path..","..d*1000)
--等待三种消息播放结束、主动调用audio.stop、新的优先级更高的播放请求
log.info("audioTask",item.type,"wait LIB_AUDIO_PLAY_IND")
local _,key,value = sys.waitUntil("LIB_AUDIO_PLAY_IND")
log.info("audioTask",item.type,"recv LIB_AUDIO_PLAY_IND",key,value)
req("AT+AUDREC=1,0,3,"..item.path..","..d*1000)
sys.waitUntil("LIB_AUDIO_RECORD_STOP_RESULT")
handlePlayInd(item,key,value)
end
elseif item.type=="STOP" then
if item.cbFnc then item.cbFnc(0) end
table.remove(audioQueue,1)
end
end
end
--- 播放音频
-- @number priority 音频优先级,数值越大,优先级越高
-- 优先级高的播放请求会终止优先级低的播放
-- 相同优先级的播放请求播放策略参考audio.setStrategy接口
-- @string type 音频类型,目前仅支持"FILE"、"TTS"
-- @string path 音频文件路径跟typ有关
-- typ为"FILE"时:表示音频文件路径
-- typ为"TTS"时表示要播放的UTF8编码格式的数据
-- @number[opt=4] vol 播放音量取值范围0到70为静音
-- @function[opt=nil] cbFnc 音频播放结束时的回调函数,回调函数的调用形式如下:
-- cbFnc(result)
-- result表示播放结果
-- 0-播放成功结束;
-- 1-播放出错
-- 2-播放优先级不够,没有播放
-- 3-传入的参数出错,没有播放
-- 4-被新的播放请求中止
-- 5-调用audio.stop接口主动停止
-- @bool[opt=nil] dup 是否循环播放true循环false或者nil不循环
-- @number[opt=0] dupInterval 循环播放间隔(单位毫秒)dup为true时此值才有意义
-- @return resultbool或者nil类型同步调用成功返回true否则返回false
-- @usage audio.play(0,"FILE","/lua/call.mp3")
-- @usage audio.play(0,"FILE","/lua/call.mp3",7)
-- @usage audio.play(0,"FILE","/lua/call.mp3",7,cbFnc)
-- @usage 更多用法参考demo/audio/testAudio.lua
function play(priority,type,path,vol,cbFnc,dup,dupInterval)
log.info("audio.play",priority,type,path,vol,cbFnc,dup,dupInterval)
if not taskID then
taskID = sys.taskInit(audioTask)
end
local item = {priority=priority,type=type,path=path,vol=vol or 4,cbFnc=cbFnc,dup=dup,dupInterval=dupInterval or 0}
if #audioQueue==0 then
table.insert(audioQueue,item)
sys.publish("LIB_AUDIO_PLAY_ENTRY")
else
local front = audioQueue[#audioQueue]
if front.type=="STOP" then
table.insert(audioQueue,item)
else
if priority>front.priority or (priority==front.priority and sStrategy==1) then
table.insert(audioQueue,item)
if not audioTaskWaitPlayEntry then
sys.publish("LIB_AUDIO_PLAY_IND","NEW")
end
else
log.warn("audio.play","priority error")
if cbFnc then cbFnc(2) end
end
end
end
return true
end
--- 停止音频播放
-- @function[opt=nil] cbFnc 停止音频播放的回调函数(停止结果通过此函数通知用户),回调函数的调用形式为:
-- cbFnc(result)
-- resultnumber类型
-- 0表示停止成功
-- @return nil
-- @usage audio.stop()
function stop(cbFnc)
log.info("audio.stop",cbFnc)
if #audioQueue==0 then
if cbFnc then cbFnc(0) end
else
table.insert(audioQueue,{type="STOP",cbFnc=cbFnc})
sys.publish("LIB_AUDIO_PLAY_IND","STOP")
end
end
local function audioMsg(msg)
--log.info("audio.MSG_AUDIO",msg.play_end_ind,msg.play_error_ind)
sys.publish("LIB_AUDIO_PLAY_IND","RESULT",msg.play_end_ind)
end
--注册core上报的rtos.MSG_AUDIO消息的处理函数
rtos.on(rtos.MSG_AUDIO,audioMsg)
rtos.on(rtos.MSG_TTSPLY_STATUS, function() log.info("rtos.MSG_TTSPLY_STATUS") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",true) end)
rtos.on(rtos.MSG_TTSPLY_ERROR, function() log.info("rtos.MSG_TTSPLY_ERROR") sys.publish("LIB_AUDIO_PLAY_IND","RESULT",false) end)
--- 设置喇叭音量等级
-- @number vol 音量值为0-70为静音
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setVolume(7)
function setVolume(vol)
local result = audiocore.setvol(vol)
if result == 1 then
result = true
elseif result == 0 then
result = false
end
if result then sVolume = vol end
return result
end
--- 设置通话音量等级
-- @number vol 音量值为0-70为静音
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setCallVolume(7)
function setCallVolume(vol)
--local result = audiocore.setsphvol(vol)
--if result then sCallVolume = vol end
--return result
audiocore.setsphvol(vol)
sCallVolume = vol
return true
end
-- 设置麦克音量等级
-- @number vol音量值为0-150为静音
-- @return bool result设置成功返回true,失败返回false
-- @usage audio.setMicVolume(14)
function setMicVolume(vol)
ril.request("AT+CMIC="..audiocore.LOUDSPEAKER..","..vol)
return true
end
ril.regRsp("+CMIC",function(cmd,success)
if success then
sMicVolume = tonumber(cmd:match("CMIC=%d+,(%d+)"))
end
end)
--- 设置mic增益等级
-- 通话时mic增益在通话建立成功之后设置才有效
-- 录音mic增益设置后实时生效
-- @string mode 增益类型
-- "call"表示通话中mic增益
-- "record"表示录音mic增益
-- @number level 增益等级取值为0-7
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setMicGain("record",7)设置录音时mic增益为7级
function setMicGain(mode, level)
if (mode ~= "call" and mode ~= "record") or (level > 7 and level < 0) then
return false
else
local gainHex
if level == 7 then
gainHex = string.format("%02X%02X%02X%02X", 7, 0, 15, 0)
else
gainHex = string.format("%02X%02X%02X%02X", level, 0, level * 2, 0)
end
if mode == "call" then
ril.request("AT+CACCP=5,1,0," .. gainHex)
ril.request("AT+CACCP=0,1,0," .. gainHex)
elseif mode == "record" then
ril.request("AT+CACCP=2,1,6," .. gainHex)
end
return true
end
end
--- 获取喇叭音量等级
-- @return number vol喇叭音量等级
-- @usage audio.getVolume()
function getVolume()
return sVolume
end
--- 获取通话音量等级
-- @return number vol通话音量等级
-- @usage audio.getCallVolume()
function getCallVolume()
return sCallVolume
end
-- 获取麦克音量等级
-- @return number vol麦克音量等级
-- @usage audio.getMicVolume()
function getMicVolume()
return sMicVolume
end
--- 设置优先级相同时的播放策略
-- @number strategy 优先级相同时的播放策略;
-- 0表示继续播放正在播放的音频忽略请求播放的新音频
-- 1表示停止正在播放的音频播放请求播放的新音频
-- @return nil
-- @usage audio.setStrategy(0)
-- @usage audio.setStrategy(1)
function setStrategy(strategy)
sStrategy=strategy
end
--- 设置TTS朗读速度
-- @number speed 速度范围为0-100默认50
-- @return bool result设置成功返回true失败返回false
-- @usage audio.setTTSSpeed(70)
function setTTSSpeed(speed)
if type(speed) == "number" and speed >= 0 and speed <= 100 then
ttsSpeed = speed
return true
end
end
--- 设置音频输入、输出通道
-- 设置后实时生效
-- @number[opt=2] output 0earphone听筒1headphone耳机2speaker喇叭
-- @number[opt=0] input 0主mic3耳机mic
-- @return nil
-- @usage
-- 设置为听筒输出audio.setChannel(0)
-- 设置为耳机输出audio.setChannel(1)
-- 设置为喇叭输出audio.setChannel(2)
-- 设置为喇叭输出、耳机mic输入audio.setChannel(2,3)
function setChannel(output, input)
local version = string.match(rtos.get_version(), "(%d+)_RDA")
if not version or tonumber(version) >= 9 then --匹配不到,兼容其它版本 或者大于版本9
audiocore.setchannel(output or 2, input or 0)
else
ril.request("AT+AUDCH="..(output==1 and 1 or 2))
end
end
--默认音频通道设置为LOUDSPEAKER因为目前的模块只支持LOUDSPEAKER通道
audiocore.setchannel(audiocore.LOUDSPEAKER)
--默认音量等级设置为4级4级是中间等级最低为0级最高为7级
setVolume(sVolume)
setCallVolume(sCallVolume)
--默认MIC音量等级设置为1级最低为0级最高为15级
setMicVolume(sMicVolume)

247
4G/code/lib/cc.lua Normal file
View File

@@ -0,0 +1,247 @@
--- 模块功能:通话管理
-- @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")

38
4G/code/lib/clib.lua Normal file
View File

@@ -0,0 +1,38 @@
--- 模块功能完善luat的c库接口
-- @module clib
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.20
local uartReceiveCallbacks = {}
local uartSentCallbacks = {}
--- 注册串口事件的处理函数
-- @number id 串口ID1表示串口12表示串口2uart.ATC表示虚拟AT口
-- @string event 串口事件:
-- "recieve"表示串口收到数据注意使用uart.setup配置串口时第6个参数设置为nil或者0收到数据时才会产生"receive"事件
-- "sent"表示串口数据发送完成注意使用uart.setup配置串口时第7个参数设置为1调用uart.write接口发送数据之后才会产生"sent"事件
-- @function[opt=nil] callback 串口事件的处理函数
-- @return nil
-- @usage
-- uart.on(1,"receive",rcvFnc)
-- uart.on(1,"sent",sentFnc)
uart.on = function(id, event, callback)
if event == "receive" then
uartReceiveCallbacks[id] = callback
elseif event == "sent" then
uartSentCallbacks[id] = callback
end
end
rtos.on(rtos.MSG_UART_RXDATA, function(id, length)
if uartReceiveCallbacks[id] then
uartReceiveCallbacks[id](id, length)
end
end)
rtos.on(rtos.MSG_UART_TX_DONE, function(id)
if uartSentCallbacks[id] then
uartSentCallbacks[id](id)
end
end)

308
4G/code/lib/common.lua Normal file
View File

@@ -0,0 +1,308 @@
---模块功能:通用库函数、编码格式转换、时区时间转换
-- @module common
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.02.20
--定义模块,导入依赖库
module(..., package.seeall)
--加载常用的全局函数至本地
local tinsert, ssub, sbyte, schar, sformat, slen = table.insert, string.sub, string.byte, string.char, string.format, string.len
--- ascii字符串的unicode编码的16进制字符串 转化为 ascii字符串
-- @string inNum 待转换字符串
-- @return string data转换后的字符串
-- @usage
-- local data = common.ucs2ToAscii("0031003200330034")
-- data is "1234"
function ucs2ToAscii(inNum)
local tonum = {}
for i = 1, slen(inNum), 4 do
tinsert(tonum, tonumber(ssub(inNum, i, i + 3), 16) % 256)
end
return schar(unpack(tonum))
end
--- ascii字符串 转化为 ascii字符串的unicode编码的16进制字符串(仅支持数字和+)
-- @string inNum 待转换字符串
-- @return string data,转换后的字符串
-- @usage
-- local data = common.nstrToUcs2Hex("+1234")
-- data is "002B0031003200330034"
function nstrToUcs2Hex(inNum)
local hexs = ""
local elem = ""
for i = 1, slen(inNum) do
elem = ssub(inNum, i, i)
if elem == "+" then
hexs = hexs .. "002B"
else
hexs = hexs .. "003" .. elem
end
end
return hexs
end
--- ASCII字符串 转化为 BCD编码格式字符串(仅支持数字)
-- @string inStr 待转换字符串
-- @number destLen 转换后的字符串期望长度如果实际不足则填充F
-- @return string data,转换后的字符串
-- @usage
-- local data = common.numToBcdNum("8618126324567")
-- data is "688121364265f7" 表示第1个字节是0x68第2个字节为0x81......
function numToBcdNum(inStr,destLen)
local l,t,num = string.len(inStr or ""),{}
destLen = destLen or (inStr:len()+1)/2
for i=1,l,2 do
num = tonumber(inStr:sub(i,i+1),16)
if i==l then
num = 0xf0+num
else
num = (num%0x10)*0x10 + (num-(num%0x10))/0x10
end
table.insert(t,num)
end
local s = string.char(unpack(t))
l = slen(s)
if l < destLen then
s = s .. string.rep("\255",destLen-l)
elseif l > destLen then
s = ssub(s,1,destLen)
end
return s
end
--- BCD编码格式字符串 转化为 号码ASCII字符串(仅支持数字)
-- @string num 待转换字符串
-- @return string data,转换后的字符串
-- @usage
-- local data = common.bcdNumToNum(common.fromHex("688121364265f7")) --表示第1个字节是0x68第2个字节为0x81......
-- data is "8618126324567"
function bcdNumToNum(num)
local byte,v1,v2
local t = {}
for i=1,num:len() do
byte = num:byte(i)
v1,v2 = bit.band(byte,0x0f),bit.band(bit.rshift(byte,4),0x0f)
if v1 == 0x0f then break end
table.insert(t,v1)
if v2 == 0x0f then break end
table.insert(t,v2)
end
return table.concat(t)
end
--- unicode小端编码 转化为 gb2312编码
-- @string ucs2s unicode小端编码数据
-- @return string data,gb2312编码数据
-- @usage local data = common.ucs2ToGb2312(ucs2s)
function ucs2ToGb2312(ucs2s)
local cd = iconv.open("gb2312", "ucs2")
return cd:iconv(ucs2s)
end
--- gb2312编码 转化为 unicode小端编码
-- @string gb2312s gb2312编码数据
-- @return string data,unicode小端编码数据
-- @usage local data = common.gb2312ToUcs2(gb2312s)
function gb2312ToUcs2(gb2312s)
local cd = iconv.open("ucs2", "gb2312")
return cd:iconv(gb2312s)
end
--- unicode大端编码 转化为 gb2312编码
-- @string ucs2s unicode大端编码数据
-- @return string data,gb2312编码数据
-- @usage data = common.ucs2beToGb2312(ucs2s)
function ucs2beToGb2312(ucs2s)
local cd = iconv.open("gb2312", "ucs2be")
return cd:iconv(ucs2s)
end
--- gb2312编码 转化为 unicode大端编码
-- @string gb2312s gb2312编码数据
-- @return string data,unicode大端编码数据
-- @usage local data = common.gb2312ToUcs2be(gb2312s)
function gb2312ToUcs2be(gb2312s)
local cd = iconv.open("ucs2be", "gb2312")
return cd:iconv(gb2312s)
end
--- unicode小端编码 转化为 utf8编码
-- @string ucs2s unicode小端编码数据
-- @return string data,utf8编码数据
-- @usage data = common.ucs2ToUtf8(ucs2s)
function ucs2ToUtf8(ucs2s)
local cd = iconv.open("utf8", "ucs2")
return cd:iconv(ucs2s)
end
--- utf8编码 转化为 unicode小端编码
-- @string utf8s utf8编码数据
-- @return string data,unicode小端编码数据
-- @usage local data = common.utf8ToUcs2(utf8s)
function utf8ToUcs2(utf8s)
local cd = iconv.open("ucs2", "utf8")
return cd:iconv(utf8s)
end
--- unicode大端编码 转化为 utf8编码
-- @string ucs2s unicode大端编码数据
-- @return string data,utf8编码数据
-- @usage data = common.ucs2beToUtf8(ucs2s)
function ucs2beToUtf8(ucs2s)
local cd = iconv.open("utf8", "ucs2be")
return cd:iconv(ucs2s)
end
--- utf8编码 转化为 unicode大端编码
-- @string utf8s utf8编码数据
-- @return string data,unicode大端编码数据
-- @usage local data = common.utf8ToUcs2be(utf8s)
function utf8ToUcs2be(utf8s)
local cd = iconv.open("ucs2be", "utf8")
return cd:iconv(utf8s)
end
--- utf8编码 转化为 gb2312编码
-- @string utf8s utf8编码数据
-- @return string data,gb2312编码数据
-- @usage local data = common.utf8ToGb2312(utf8s)
function utf8ToGb2312(utf8s)
local cd = iconv.open("ucs2", "utf8")
local ucs2s = cd:iconv(utf8s)
cd = iconv.open("gb2312", "ucs2")
return cd:iconv(ucs2s)
end
--- gb2312编码 转化为 utf8编码
-- @string gb2312s gb2312编码数据
-- @return string data,utf8编码数据
-- @usage local data = common.gb2312ToUtf8(gb2312s)
function gb2312ToUtf8(gb2312s)
local cd = iconv.open("ucs2", "gb2312")
local ucs2s = cd:iconv(gb2312s)
cd = iconv.open("utf8", "ucs2")
return cd:iconv(ucs2s)
end
local function timeAddzone(y, m, d, hh, mm, ss, zone)
if not y or not m or not d or not hh or not mm or not ss then
return
end
hh = hh + zone
if hh >= 24 then
hh = hh - 24
d = d + 1
if m == 4 or m == 6 or m == 9 or m == 11 then
if d > 30 then
d = 1
m = m + 1
end
elseif m == 1 or m == 3 or m == 5 or m == 7 or m == 8 or m == 10 then
if d > 31 then
d = 1
m = m + 1
end
elseif m == 12 then
if d > 31 then
d = 1
m = 1
y = y + 1
end
elseif m == 2 then
if (((y + 2000) % 400) == 0) or (((y + 2000) % 4 == 0) and ((y + 2000) % 100 ~= 0)) then
if d > 29 then
d = 1
m = 3
end
else
if d > 28 then
d = 1
m = 3
end
end
end
end
local t = {}
t.year, t.month, t.day, t.hour, t.min, t.sec = y, m, d, hh, mm, ss
return t
end
local function timeSubZone(y, m, d, hh, mm, ss, zone)
if not y or not m or not d or not hh or not mm or not ss then
return
end
hh = hh + zone
if hh < 0 then
hh = hh + 24
d = d - 1
if m == 2 or m == 4 or m == 6 or m == 8 or m == 9 or m == 11 then
if d < 1 then
d = 31
m = m - 1
end
elseif m == 5 or m == 7 or m == 10 or m == 12 then
if d < 1 then
d = 30
m = m - 1
end
elseif m == 1 then
if d < 1 then
d = 31
m = 12
y = y - 1
end
elseif m == 3 then
if (((y + 2000) % 400) == 0) or (((y + 2000) % 4 == 0) and ((y + 2000) % 100 ~= 0)) then
if d < 1 then
d = 29
m = 2
end
else
if d < 1 then
d = 28
m = 2
end
end
end
end
local t = {}
t.year, t.month, t.day, t.hour, t.min, t.sec = y, m, d, hh, mm, ss
return t
end
--- 时区时间转换
-- @number y 源时区年份
-- @number m 源时区月份
-- @number d 源时区天
-- @number hh 源时区小时
-- @number mm 源时区分
-- @number ss 源时区秒
-- @number srcTimeZone 源时区
-- @number dstTimeZone 目的时区
-- @return table dstZoneTime,返回目的时区对应的时间,{year,month,day,hour,min,sec}
-- @usage
-- local dstZoneTime = common.timeZoneConvert(2018,1,1,18,00,00,0,8)
-- dstZoneTime为{year=2018,month=1,day=2,hour=2,min=0,sec=0}
function timeZoneConvert(y, m, d, hh, mm, ss, srcTimeZone, dstTimeZone)
local t = {}
local zone = dstTimeZone-srcTimeZone
if zone >= 0 and zone < 23 then
t = timeAddzone(y, m, d, hh, mm, ss, zone)
elseif zone < 0 and zone >= -24 then
t = timeSubZone(y, m, d, hh, mm, ss, zone)
end
return t
end

137
4G/code/lib/console.lua Normal file
View File

@@ -0,0 +1,137 @@
--- 模块功能Luat控制台.
-- 使用说明参考demo/console下的《console功能使用说明.docx》
-- @module console
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.15
require"ril"
module(..., package.seeall)
local uart_id
local console_task
local function read_line()
while true do
local s = uart.read(uart_id, "*l")
if s ~= "" then
return s
end
coroutine.yield()
end
end
local function write(s)
uart.write(uart_id, s)
end
local function on_wait_event_timeout()
coroutine.resume(console_task, "TIMEOUT")
end
local function wait_event(event, timeout)
if timeout then
sys.timerStart(on_wait_event_timeout, timeout)
end
while true do
local receive_event = coroutine.yield()
if receive_event == event then
sys.timerStop(on_wait_event_timeout)
return
elseif receive_event == "TIMEOUT" then
write("WAIT EVENT " .. event .. "TIMEOUT\r\n")
return
end
end
end
local function main_loop()
local cache_data = ""
local wait_event_flag
-- 定义执行环境命令行下输入的脚本的print重写到命令行的write
local execute_env = {
print = function(...)
local arg = { ... }
for i, v in ipairs(arg) do
arg[i] = type(v) == "nil" and "nil" or tostring(v)
end
write(table.concat(arg, "\t"))
write("\r\n")
end,
sendat = function(cmd, data)
ril.request(cmd, data, function(cmd, success, response, intermediate)
if intermediate then
write("\r\n" .. intermediate .. "\r\n")
end
if response then
write("\r\n" .. response .. "\r\n")
end
coroutine.resume(console_task, "WAIT_AT_RESPONSE")
end, nil)
wait_event_flag = "WAIT_AT_RESPONSE"
end,
}
setmetatable(execute_env, { __index = _G })
-- 输出提示语
write("\r\nWelcome to Luat Console\r\n")
write("\r\n> ")
while true do
-- 读取输入
local new_data = read_line("*l")
-- 输出回显
write(new_data)
-- 拼接之前未成行的剩余数据
cache_data = cache_data .. new_data
-- 去掉回车换行
local line = string.match(cache_data, "(.-)\r?\n")
if line then
-- 收到一整行的数据 清除缓冲数据
cache_data = ""
-- 输出新行
write("\n")
-- 用xpcall执行用户输入的脚本可以捕捉脚本的错误
xpcall(function()
-- 执行用户输入的脚本
local f = assert(loadstring(line.." "))
setfenv(f, execute_env)
f()
end,
function(err) -- 错误输出
write(err .. '\r\n')
write(debug.traceback())
end)
if wait_event_flag then
wait_event(wait_event_flag, 3000)
wait_event_flag = nil
end
-- 输出输入提示符
write("\r\n> ")
end
end
end
--- 配置控制台使用的串口参数,创建控制台协程
-- @number id 控制台使用的串口ID1表示串口12表示串口2
-- @number[opt=115200] baudrate 控制台使用的串口波特率
-- 支持1200,2400,4800,9600,1040014400,19200,28800,38400,57600,76800,115200,230400,460800,576000,921600,1152000,4000000
-- @return nil
-- @usage console.setup(1, 115200)
function setup(id, baudrate)
-- 默认串口1
uart_id = id or 1
-- 默认波特率115200
baudrate = baudrate or 115200
-- 创建console处理的协程
console_task = coroutine.create(main_loop)
-- 初始化串口
uart.setup(uart_id, baudrate, 8, uart.PAR_NONE, uart.STOP_1)
-- 串口收到数据时唤醒console协程
uart.on(uart_id, "receive", function()
coroutine.resume(console_task)
end)
coroutine.resume(console_task)
end

347
4G/code/lib/errDump.lua Normal file
View File

@@ -0,0 +1,347 @@
--- 模块功能:系统错误日志管理(强烈建议用户开启此模块的“错误日志上报调试服务器”功能).
-- 错误日志包括四种:
-- 1、系统主任务运行时的错误日志
-- 此类错误会导致软件重启,错误日志保存在/luaerrinfo.txt文件中
-- 2、调用sys.taskInit创建的协程运行过程中的错误日志
-- 此类错误会终止当前协程的运行,但是不会导致软件重启,错误日志保存在/lib_err.txt中
-- 3、调用errDump.appendErr或者sys.restart接口保存的错误日志
-- 此类错误日志保存在/lib_err.txt中
-- 4、调用errDump.setNetworkLog接口打开网络异常日志功能后会自动保存最近几种网络异常日志
-- 错误日志保存在/lib_network_err.txt中
-- 5、底层固件的死机信息
--
-- 其中2和3保存的错误日志最多支持5K字节
-- 每次上报错误日志给调试服务器之后,会清空已保存的日志
-- @module errDump
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.09.26
require"socket"
require"misc"
module(..., package.seeall)
--错误信息文件以及错误信息内容
local LIB_ERR_FILE,libErr,LIB_ERR_MAX_LEN = "/lib_err.txt","",5*1024
local LUA_ERR_FILE,luaErr = "/luaerrinfo.txt",""
local sReporting,sProtocol,switch
local LIB_NETWORK_ERR_FILE,sNetworkLog,stNetworkLog,sNetworkLogFlag = "/lib_network_err.txt","",{}
local firmwareAssertErr = ""
-- 初始化LIB_ERR_FILE文件中的错误信息(读取到内存中,并且打印出来)
-- @return nil
-- @usage readTxt.initErr()
local function initErr()
libErr = io.readFile(LIB_ERR_FILE) or ""
if libErr~="" then
log.error("errDump.libErr", libErr)
end
luaErr = io.readFile(LUA_ERR_FILE) or ""
if luaErr~="" then
log.error("errDump.luaErr", luaErr)
end
sNetworkLog = io.readFile(LIB_NETWORK_ERR_FILE) or ""
if sNetworkLog~="" then
log.error("errDump.libNetErr", sNetworkLog)
end
if type(rtos.get_fatal_info)=="function" then
firmwareAssertErr = rtos.get_fatal_info() or ""
if firmwareAssertErr~="" then
log.error("errDump.firmwareAssertErr", firmwareAssertErr)
end
end
end
--- 追加错误信息到LIB_ERR_FILE文件中文件最多允许存储5K字节的数据
-- @string s 用户自定义的错误信息errDump功能模块会对此错误信息做如下处理
-- 1、重启后会通过Luat下载调试工具输出在trace中搜索errDump.libErr可以搜索到错误信息
-- 2、如果用户调用errDump.request接口设置了错误信息要上报的调试服务器地址和端口则每次重启会自动上报错误信息到调试服务器
-- 3、如果用户调用errDump.request接口设置了定时上报则定时上报时会上报错误信息到调试服务器
-- 其中第2和第3种情况上报成功后会自动清除错误信息
-- @return bool resulttrue表示成功false或者nil表示失败
-- @usage errDump.appendErr("net working timeout!")
function appendErr(s)
if s then
s=s.."\r\n"
log.error("errDump.appendErr",s)
if (s:len()+libErr:len())<=LIB_ERR_MAX_LEN then
libErr = libErr..s
return io.writeFile(LIB_ERR_FILE, libErr)
end
end
end
local function reportData()
local s = _G.PROJECT.."_"..rtos.get_version()..",".._G.VERSION..","..misc.getImei()..","..misc.getSn()..","
s = s.."\r\npoweron reason:"..rtos.poweron_reason().."\r\n"..luaErr..(luaErr:len()>0 and "\r\n" or "")..libErr..(libErr:len()>0 and "\r\n" or "")..sNetworkLog
s = s..(firmwareAssertErr:len()>0 and "\r\n" or "")..firmwareAssertErr
return s
end
local function httpPostCbFnc(result,statusCode)
log.info("errDump.httpPostCbFnc",result,statusCode)
sys.publish("ERRDUMP_HTTP_POST",result,statusCode)
end
local function checkSwitch(addr)
local first = true
while true do
if not socket.isReady() then sys.waitUntil("IP_READY_IND") end
--log.info("errDump.clientTask","err",luaErr~="" or libErr~="")
local host,port = addr:match("://(.+):(%d+)$")
if not host then log.error("errDump.request invalid host port") return end
local result, data, time
while true do
local sck = socket.udp()
data = string.char(0, 0) .. misc.getImei()
if sck:connect(host, port) then
if sck:send(data) then
result, data = sck:recv(5000)
if result then
data, result = json.decode(data)
if result then
if data.r == 1 then
switch = true
time = tonumber(data.expire_at)
end
end
end
else
switch = false
end
else
switch = false
end
sck:close()
if time then
local clk = time - os.time()
if clk < 7200 then
sys.timerStart(function() switch = false end, clk * 1000)
end
end
break
end
if first then
sys.publish("GET_SWITCH")
first = nil
end
sys.wait(7200000)
end
end
function clientTask(protocol,addr,period,flag)
sReporting = true
if flag then
sys.taskInit(checkSwitch, addr)
sys.waitUntil("GET_SWITCH")
end
while true do
if not socket.isReady() then sys.waitUntil("IP_READY_IND") end
--log.info("errDump.clientTask","err",luaErr~="" or libErr~="")
if luaErr~="" or libErr~="" or sNetworkLog~="" or firmwareAssertErr~="" then
local retryCnt,result,data = 0
while true do
if protocol=="http" or protocol=="https" then
http.request("POST",addr,nil,nil,reportData(),20000,httpPostCbFnc)
_,result = sys.waitUntil("ERRDUMP_HTTP_POST")
else
if flag and not switch then
break
end
local host,port = addr:match("://(.+):(%d+)$")
if not host then log.error("errDump.request invalid host port") return end
local sck = protocol=="udp" and socket.udp() or socket.tcp()
if sck:connect(host,port) then
result = sck:send(reportData())
if result and protocol=="udp" then
result,data = sck:recv(20000)
if result then
if not flag then
result = data=="OK"
else
data, result = json.decode(data)
if result then
result = data.r == 1 and true or false
end
end
end
end
end
sck:close()
end
if result then
libErr = ""
os.remove(LIB_ERR_FILE)
luaErr = ""
os.remove(LUA_ERR_FILE)
sNetworkLog = ""
stNetworkLog = {}
os.remove(LIB_NETWORK_ERR_FILE)
firmwareAssertErr = ""
if type(rtos.remove_fatal_info)=="function" then rtos.remove_fatal_info() end
break
else
if flag then
break
end
retryCnt = retryCnt+1
if retryCnt==3 then
break
end
sys.wait(5000)
end
end
end
if period then
--log.info("errDump.clientTask","wait",period)
sys.wait(period)
else
break
end
end
sReporting = false
end
function updateNetworkLog()
if sNetworkLogFlag then
sNetworkLog = ""
for k,v in pairs(stNetworkLog) do
if v and v~="" then
sNetworkLog = sNetworkLog.."\r\n"..k.."@"..v
end
end
if sNetworkLog~="" then
io.writeFile(LIB_NETWORK_ERR_FILE,sNetworkLog)
end
end
end
local onceGsmRegistered,onceGprsAttached
--- 配置网络错误日志开关
-- @bool[opt=nil] flag 是否打开网络错误日志开关true为打开false或者nil为关闭
-- @usage
-- errDump.setNetworkLog(true)
function setNetworkLog(flag)
sNetworkLogFlag = flag
local procer = flag and sys.subscribe or sys.unsubscribe
if not flag then
sNetworkLog,stNetworkLog = "",{}
end
local function getTimeStr()
local clk = os.date("*t")
return string.format("%02d_%02d:%02d:%02d",clk.day,clk.hour,clk.min,clk.sec)
end
procer("FLYMODE",function(value)
if value then
stNetworkLog["FLYMODE"] = getTimeStr()
updateNetworkLog()
end
end)
procer("SIM_IND",function(value)
if value~="RDY" then
stNetworkLog["SIM_IND"] = getTimeStr()..":"..value
updateNetworkLog()
end
end)
procer("NET_STATE_UNREGISTER",function()
if onceGsmRegistered then
stNetworkLog["NET_STATE_UNREGISTER"] = getTimeStr()
updateNetworkLog()
end
end)
procer("NET_STATE_REGISTERED",function() onceGsmRegistered=true end)
procer("GPRS_ATTACH",function(value)
if value then
onceGprsAttached = true
elseif onceGprsAttached then
stNetworkLog["GPRS_ATTACH"] = getTimeStr()..":0"
updateNetworkLog()
end
end)
procer("LIB_SOCKET_CONNECT_FAIL_IND",function(ssl,prot,addr,port)
stNetworkLog[(ssl and "ssl" or prot).."://"..addr..":"..port] = getTimeStr()..":connect fail"
updateNetworkLog()
end)
procer("LIB_SOCKET_SEND_FAIL_IND",function(ssl,prot,addr,port)
stNetworkLog[(ssl and "ssl" or prot).."://"..addr..":"..port] = getTimeStr()..":send fail"
updateNetworkLog()
end)
procer("LIB_SOCKET_CLOSE_IND",function(ssl,prot,addr,port)
stNetworkLog[(ssl and "ssl" or prot).."://"..addr..":"..port.." closed"] = getTimeStr()
updateNetworkLog()
end)
procer("PDP_DEACT_IND",function()
stNetworkLog["PDP_DEACT_IND"] = getTimeStr()
updateNetworkLog()
end)
procer("IP_SHUT_IND",function()
stNetworkLog["IP_SHUT_IND"] = getTimeStr()
updateNetworkLog()
end)
end
--- 配置调试服务器地址,启动错误信息上报给调试服务器的功能,上报成功后,会清除错误信息
-- @string addr 调试服务器地址信息支持httpudptcp
-- 1、如果调试服务器使用http协议终端将采用POST命令把错误信息上报到addr指定的URL中addr的格式如下
-- (除protocol和hostname外其余字段可选目前的实现不支持hash)
-- |------------------------------------------------------------------------------|
-- | protocol ||| auth | host | path | hash |
-- |----------|||-----------|-----------------|---------------------------|-------|
-- | ||| | hostname | port | pathname | search | |
-- | ||| |----------|------|----------|----------------| |
-- " http(s) :// user:pass @ host.com : 8080 /p/a/t/h ? query=string # hash "
-- | ||| | | | | | |
-- |------------------------------------------------------------------------------|
-- 2、如果调试服务器使用udp协议终端将错误信息直接上报给调试服务器调试服务器收到信息后要回复大写的OKaddr格式如下
-- |----------|||----------|------|
-- | protocol ||| hostname | port |
-- | |||----------|------|
-- " udp :// host.com : 8081 |
-- | ||| | |
-- |------------------------------|
-- 3、如果调试服务器使用tcp协议终端将错误信息直接上报给调试服务器addr格式如下
-- |----------|||----------|------|
-- | protocol ||| hostname | port |
-- | |||----------|------|
-- " tcp :// host.com : 8082 |
-- | ||| | |
-- |------------------------------|
-- @number[opt=600000] period 单位毫秒,定时检查错误信息并上报的间隔
-- @bool flag 当使用合宙调试服务器时此参数填为true使用自定义服务器时此参数可省略
-- @return bool result成功返回true失败返回nil
-- @usage
-- errDump.request("http://www.user_server.com/errdump")
-- errDump.request("udp://www.user_server.com:8081")
-- errDump.request("tcp://www.user_server.com:8082")
-- errDump.request("tcp://www.user_server.com:8082",6*3600*1000)
-- errDump.request("udp://www.hezhou_server.com:8083",6*3600*1000,true)
function request(addr,period,flag)
local protocol = addr:match("(%a+)://")
if protocol~="http" and protocol~="https" and protocol~="udp" and protocol~="tcp" then
log.error("errDump.request invalid protocol",protocol)
return
end
if flag and protocol ~= "udp" then
log.error("errDump.request invalid protocol",protocol)
return
end
if not sReporting then
sys.taskInit(clientTask,protocol,addr,period or 600000, flag)
end
return true
end
initErr()

344
4G/code/lib/ftp.lua Normal file
View File

@@ -0,0 +1,344 @@
--- 模块功能FTP客户端
-- @module ftp
-- @author Dozingfiretruck
-- @license MIT
-- @copyright OpenLuat.com
-- @release 2020.12.08
require "socket"
require "utils"
module(..., package.seeall)
local ftp_client -- ftp命令连接socket对象
local ftp_data_client -- ftp数据连接socket对象
local data_client_ip, data_client_port -- ftp数据连接地址
--- FTP客户端关闭
function close()
ftp_client:send("QUIT\r\n")
ftp_client:close()
log.info("ftp", "ftp close")
end
--- FTP客户端命令
-- @string command 命令,例如"PWD","HELP","SYST"
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function command(command, timeout)
if not ftp_client:send(command .. "\r\n") then
close()
return '426', 'SOCKET_SEND_ERROR'
end
local r, n = ftp_client:recv(timeout)
if r == true and n:sub(1, 3) == "230" then
r, n = ftp_client:recv(timeout)
end
log.info("ftp_command", n)
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
else
return n:sub(1, 3), n:sub(4, #n)
end
end
--- 连接到PASV接口
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
local function pasv_connect(timeout)
---被动模式
if not ftp_client:send("PASV\r\n") then
close()
return '426', 'SOCKET_SEND_ERROR'
end
local r, n = ftp_client:recv(timeout)
if r == true and n:sub(1, 3) == "230" then
r, n = ftp_client:recv(timeout)
end
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
end
local h1, h2, h3, h4, p1, p2 =
n:match("(%d+),(%d+),(%d+),(%d+),(%d+),(%d+)")
data_client_ip, data_client_port =
h1 .. '.' .. h2 .. '.' .. h3 .. '.' .. h4,
string.format("%d", (p1 * 256 + p2))
log.info("ftp ip", data_client_ip, "port", data_client_port)
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
end
---创建ftp数据连接
ftp_data_client = socket.tcp()
log.info("ftp ip", data_client_ip, "port", data_client_port)
if not ftp_data_client:connect(data_client_ip, data_client_port) then
close()
ftp_data_client:close()
return '502', 'SOCKET_CONN_ERROR'
end
return '200', 'ftp pasv success'
end
--- FTP客户端登录
-- @string ftp_mode FTP模式"PASV"or"PORT",默认PASV:被动模式,PORT:主动模式(暂时仅支持被动模式)
-- @string host ip地址
-- @string port 端口,默认21
-- @string username 用户名
-- @string password 密码
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @bool ssl 可选参数默认为nilssl是否为ssl连接true表示是其余表示否
-- @table cert 可选参数默认为nilcertssl连接需要的证书配置只有ssl参数为true时才参数才有意义cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @return string,string,返回 response_code, response_message
function login(ftp_mode, host, port, username, password, timeout, ssl, cert)
if ftp_mode ~= "PASV" then
log.error("暂不支持主动模式 ")
return '-1', 'ftp ftp_mode error'
end
while not socket.isReady() do sys.wait(1000) end
ftp_client = socket.tcp(ssl, cert)
if not ftp_client:connect(host, port or 21) then
close()
return '502', 'SOCKET_CONN_ERROR'
end
local r, n = ftp_client:recv(timeout)
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
end
if not ftp_client:send("USER " .. username .. "\r\n") then
close()
return '426', 'SOCKET_SEND_ERROR'
end
r = ftp_client:recv(timeout)
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
end
-- 密码
if not ftp_client:send("PASS " .. password .. "\r\n") then
close()
return '426', 'SOCKET_SEND_ERROR'
end
r, n = ftp_client:recv(timeout)
if not r then
close()
return '503', 'SOCKET_RECV_TIMOUT'
end
if n:sub(1, 3) == '230' then
log.info("ftp", n)
elseif n:sub(1, 3) == '530' then
log.error("ftp Password error ", n)
close()
return '530', n
end
log.info("ftp login success")
return '200', 'ftp login success'
end
--- FTP客户端文件上传
-- @string remote_file 远程文件名
-- @string local_file 本地文件名
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function upload(remote_file, local_file, timeout)
local com, msg = pasv_connect()
if com ~= "200" then return com, msg end
-- 文件上传
if not ftp_client:send("APPE " .. remote_file .. "\r\n") then
close()
ftp_data_client:close()
return '426', 'SOCKET_SEND_ERROR'
end
local r, n = ftp_client:recv(timeout)
log.info("ftp upload", n)
if not r then
close()
ftp_data_client:close()
return '503', 'SOCKET_RECV_TIMOUT'
end
if n:sub(1, 3) == '553' then
log.error("ftp STOR error ", n)
close()
ftp_data_client:close()
return '553', n
end
local file = io.open(local_file, "r")
if file then
local file_size = io.fileSize(local_file)
local file_cnt = 0
log.info("ftp", "local_file_size", file_size)
repeat
log.info("ftp", "file_cnt", file_cnt, "file_size - file_cnt",
file_size - file_cnt)
local temp_data = io.readStream(local_file, file_cnt, 11200)
if not ftp_data_client:send(temp_data) then
close()
ftp_data_client:close()
return '426', 'SOCKET_SEND_ERROR'
end
file_cnt = file_cnt + 11200
until (file_size - file_cnt < 11200)
local temp_data = io.readStream(local_file, file_cnt,
file_size - file_cnt)
if not ftp_data_client:send(temp_data) then
close()
ftp_data_client:close()
return '426', 'SOCKET_SEND_ERROR'
end
file:close()
ftp_data_client:close()
return '200', 'ftp success'
else
log.info("文件打开失败")
ftp_data_client:close()
return '503', 'file open error'
end
end
--- FTP客户端文件下载
-- @string remote_file 远程文件名
-- @string local_file 本地文件名
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function download(remote_file, local_file, timeout)
local com, msg = pasv_connect()
if com ~= "200" then return com, msg end
-- 文件大小
if not ftp_client:send("SIZE " .. remote_file .. "\r\n") then
close()
ftp_data_client:close()
return '426', 'SOCKET_SEND_ERROR'
end
local r, n = ftp_client:recv(timeout)
if not r then
close()
ftp_data_client:close()
return '503', 'SOCKET_RECV_TIMOUT'
end
if n:sub(1, 3) == '213' then
log.info("ftp filename size", n:sub(5, #n))
filename_size = tonumber(n:sub(5, #n))
elseif n:sub(1, 3) == '550' then
log.error("ftp filename error ", n)
return
end
-- 文件下载
if not ftp_client:send("RETR " .. remote_file .. "\r\n") then
close()
ftp_data_client:close()
return '426', 'SOCKET_SEND_ERROR'
end
local r, n = ftp_client:recv(timeout)
log.info("ftp download", n)
if not r then
close()
ftp_data_client:close()
return '503', 'SOCKET_RECV_TIMOUT'
end
if io.exists(local_file) then
os.remove(local_file)
log.info("删除文件", local_file)
end
local file = io.open(local_file, "ab")
if file then
while true do
local r, file_data = ftp_data_client:recv(timeout)
if r then
if file_data:find("226 Successfully transferred ") == nil and
file_data:find("226 Transfer OK") == nil then
file:write(file_data)
end
else
break
end
end
file:close()
ftp_data_client:close()
return '200', 'ftp success'
else
log.info("文件打开失败")
ftp_data_client:close()
return '503', 'file open error'
end
end
--- 设置FTP传输类型 A:ascii I:Binary
-- @string mode A:ascii I:Binary
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function checktype(mode, timeout) return command("TYPE " .. mode, timeout) end
--- 显示当前工作目录
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function pwd(timeout) return command("PWD ", timeout) end
--- 更改工作目录
-- @string path 工作目录
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function cwd(path, timeout) return command("CWD " .. path, timeout) end
--- 回到上级目录
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function cdup(timeout) return command("CDUP", timeout) end
--- 创建目录
-- @string path 目录
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function mkd(path, timeout) return command("MKD " .. path, timeout) end
--- 列出目录列表或文件信息
-- @string file_irectory 目录或文件
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function list(file_irectory, timeout)
local com, msg = pasv_connect()
if com ~= "200" then return com, msg end
local r, n = command("LIST " .. file_irectory, timeout)
if r == "503" then return r, n end
r, n = ftp_client:recv(timeout)
log.info("ftp", r, n)
if not r then
close()
ftp_data_client:close()
return '503', 'SOCKET_RECV_TIMOUT'
end
local data = ""
while true do
local r, n = ftp_data_client:recv(timeout)
if r then
data = data .. n
else
break
end
end
ftp_data_client:close()
return r, data
end
--- 删除目录
-- @string file_irectory 路径目录
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function deletefolder(file_irectory, timeout)
return command("RMD " .. file_irectory, timeout)
end
--- 删除文件
-- @string file_irectory 路径文件(相对/绝对)
-- @number[opt=0] timeout 接收超时时间,单位毫秒
-- @return string,string,返回 response_code, response_message
function deletefile(file_irectory, timeout)
return command("DELE " .. file_irectory, timeout)
end

901
4G/code/lib/gps.lua Normal file
View File

@@ -0,0 +1,901 @@
--- 模块功能GPS模块管理
-- 注意:此功能模块中的功能接口可以分为四大类:
-- 1、GPS开启
-- 2、GPS关闭
-- 3、GPS定位数据读取
-- 4、GPS参数和功能设置
-- 1、2、3是通用功能除了支持合宙的Air530模块理论上也支持其他厂家的串口GPS模块
-- 4是专用功能仅支持合宙的Air530模块
-- @module gps
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.23
require"pm"
require"utils"
module(..., package.seeall)
local smatch,sfind,slen,ssub,sbyte,sformat,srep = string.match,string.find,string.len,string.sub,string.byte,string.format,string.rep
--GPS开启标志true表示开启状态false或者nil表示关闭状态
local openFlag
--GPS定位标志"2D"表示2D定位"3D"表示3D定位其余表示未定位
--GPS定位标志true表示其余表示未定位
local fixFlag,fixOnece=nil,nil
--GPS定位成功后过滤掉前filterSeconds秒的经纬度信息
--是否已经过滤完成
local filterSeconds,filteredFlag = 0
--从定位成功切换到定位失败,连续定位失败的次数
local fixFailCnt = 0
--经纬度类型和数据
local latitudeType,latitude,longitudeType,longitude = "N","","E",""
--海拔,速度,方向角
local altitude,speed,course = "0","0","0"
--参与定位的卫星个数,所有可见卫星的最大信号值,所有可见卫星的最大信号值中间缓存值
local usedSateCnt,maxSignalStrength,maxSignalStrengthVar = "0",0,0
--可见卫星个数
local viewedGpsSateCnt,viewedBdSateCnt = "0","0"
--可用卫星号UTC时间信噪比
local SateSn,UtcTime,Gsv
--大地高,度分经度,度分纬度
local Sep,Ggalng,Ggalat
--是否需要解析项
local psUtcTime,psGsv,psSn
--GPS供电设置函数
local powerCbFnc
--串口配置
uartBaudrate = 115200
local uartID,uartDatabits,uartParity,uartStopbits = 2,8,uart.PAR_NONE,uart.STOP_1
--搜星模式命令字符串,"$PGKC115," .. gps .. "," .. glonass .. "," .. beidou .. "," .. galieo .. "*"
local aerialModeStr,aerialModeSetted = ""
--运行模式命令字符串,"$PGKC105," .. mode .. "," .. rt .. "," .. st .. "*"
local runModeStr,runModeSetted = ""
--正常运行模式下NMEA数据上报间隔命令字符串"$PGKC101," .. interval .. "*"
local nmeaReportStr,nmeaReportSetted = ""
--每种NEMA数据的输出频率命令字符串
local nmeaReportFreqStr,nmeaReportFreqSetted = ""
--NMEA数据处理模式0表示仅gps.lua内部处理1表示仅用户自己处理2表示gps.lua和用户同时处理
--用户处理一条NMEA数据的回调函数
local nmeaMode,nmeaCbFnc = 0
--NMEA数据输出间隔
local nmeaInterval = 1000
--运行模式
--0正常运行模式
--1周期超低功耗跟踪模式
--2周期低功耗模式
--4直接进入超低功耗跟踪模式
--8自动低功耗模式可以通过串口唤醒
--9, 自动超低功耗跟踪模式需要force on来唤醒
local runMode = 0
--gps 的串口线程是否在工作;
local taskFlag=false
--runMode为1或者2时GPS运行状态和休眠状态的时长
local runTime,sleepTime
--检测gps是否工作正常的定时器ID
local workAbnormalTimerId
--[[
函数名getstrength
功能 解析GSV数据
参数
sgNEMA中的一行GSV数据
返回值:无
]]
local function getstrength(sg)
sg = ssub(sg, 4, #sg)
local d1,d2,curnum,lineno,total,sgv_str = sfind(sg,"GSV,(%d),(%d),(%d+),(.*)%*.*")
if not curnum or not lineno or not total or not sgv_str then
return
end
if lineno == nil then
maxSignalStrengthVar = 0
maxSignalStrength = 0
elseif tonumber(lineno) == 1 then
maxSignalStrength = maxSignalStrengthVar
maxSignalStrengthVar = 0
end
local tmpstr,i = sgv_str
for i=1,4 do
local d1,d2,id,elevation,azimuth,strength = sfind(tmpstr,"(%d+),([%-]*%d*),(%d*),(%d*)")
if id == nil then return end
if strength == "" or not strength then
strength = "00"
end
strength = tonumber(strength)
if strength > maxSignalStrengthVar then
maxSignalStrengthVar = strength
end
local idx,cur,fnd,tmpid = 0,id..","..elevation..","..azimuth..","..strength..",",false
for tmpid in string.gmatch(Gsv,"(%d+),%d*,%d*,%d*,") do
idx = idx + 1
if tmpid == id then fnd = true break end
end
if fnd then
local pattern,i = ""
for i=1,idx do
pattern = pattern.."%d+,%d*,%d*,%d*,"
end
local m1,m2 = sfind(Gsv,"^"..pattern)
if m1 and m2 then
local front = ssub(Gsv,1,m2)
local n1,n2 = sfind(front,"%d+,%d*,%d*,%d*,$")
if n1 and n2 then
Gsv = ssub(Gsv,1,n1-1)..cur..ssub(Gsv,n2+1,-1)
end
end
else
Gsv = Gsv..cur
end
tmpstr = ssub(tmpstr,d2+1,-1)
end
end
local function filterTimerFnc()
log.info("gps.filterTimerFnc end")
filteredFlag = true
end
local function stopWorkAbnormalTimer()
if workAbnormalTimerId then
sys.timerStop(workAbnormalTimerId)
workAbnormalTimerId = nil
end
end
local function parseNmea(s)
if not s or s=="" then return end
local lat,lng,spd,cog,gpsFind,gpsTime,gpsDate,locSateCnt,hdp,latTyp,lngTyp,altd
local hexStr = s:toHex()
if "AAF00C0001009500039B0D0A"==hexStr then
sys.publish("GPS_STATE","BINARY_CMD_ACK")
stopWorkAbnormalTimer()
return
elseif smatch(hexStr,"^AAF00C000300") then
sys.publish("GPS_STATE",smatch(hexStr,"^AAF00C000300FFFF") and "WRITE_EPH_END_ACK" or "WRITE_EPH_ACK")
stopWorkAbnormalTimer()
return
end
local fixed,workAbnormal
if smatch(s,"GGA") then
lat,latTyp,lng,lngTyp,gpsFind,locSateCnt,hdp,altd,sep = smatch(s,"GGA,%d+%.%d+,(%d+%.%d+),([NS]),(%d+%.%d+),([EW]),(%d),(%d+),([%d%.]*),(.*),M,(.*),M")
if (gpsFind=="1" or gpsFind=="2" or gpsFind=="4") and altd then
--fixed = true
altitude = altd
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
usedSateCnt = locSateCnt
Ggalng,Ggalat = (lngTyp=="W" and "-" or "")..lng,(latTyp=="S" and "-" or "")..lat
Sep = sep
else
fixed = false
end
elseif smatch(s,"RMC") then
gpsTime,gpsFind,lat,latTyp,lng,lngTyp,spd,cog,gpsDate = smatch(s,"RMC,(%d%d%d%d%d%d)%.%d+,(%w),(%d*%.*%d*),([NS]*),(%d*%.*%d*),([EW]*),(.-),(.-),(%d%d%d%d%d%d),")
if gpsFind=="A" and cog then
fixed = true
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
speed = spd
course = cog
else
fixed = false
end
if psUtcTime and gpsFind == "A" and gpsTime and gpsDate and gpsTime ~= "" and gpsDate ~= "" then
local yy,mm,dd,h,m,s = tonumber(ssub(gpsDate,5,6)),tonumber(ssub(gpsDate,3,4)),tonumber(ssub(gpsDate,1,2)),tonumber(ssub(gpsTime,1,2)),tonumber(ssub(gpsTime,3,4)),tonumber(ssub(gpsTime,5,6))
UtcTime = {year=2000+yy,month=mm,day=dd,hour=h,min=m,sec=s}
end
elseif smatch(s,"GPGSV") then
viewedGpsSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"BDGSV") then
viewedBdSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"GSA") then
if psSn then
local satesn = smatch(s,"GSA,%w*,%d*,(%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,)") or ""
if slen(satesn) > 0 and smatch(satesn,"%d+,") then
SateSn = satesn
end
end
else
workAbnormal = true
end
if not workAbnormal then
stopWorkAbnormalTimer()
end
if filterSeconds>0 and fixed and not fixFlag and not filteredFlag then
if not sys.timerIsActive(filterTimerFnc) then
log.info("gps.filterTimerFnc begin")
sys.publish("GPS_STATE","LOCATION_FILTER")
sys.timerStart(filterTimerFnc,filterSeconds*1000)
end
return
end
--定位成功
if fixed then
if not fixFlag then
fixFlag,filteredFlag = true,true
fixOnece=true
fixFailCnt = 0
sys.publish("GPS_STATE","LOCATION_SUCCESS")
end
elseif fixed==false then
if fixFlag then
fixFailCnt = fixFailCnt+1
if fixFailCnt>=20 then
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
sys.publish("GPS_STATE","LOCATION_FAIL")
end
end
end
end
local function taskRead()
local cacheData = ""
local co = coroutine.running()
while true do
local s =""
if openFlag then
s= uart.read(uartID, "*l")
end
if s == "" then
uart.on(uartID,"receive",function() coroutine.resume(co) end)
coroutine.yield()
uart.on(uartID,"receive")
else
cacheData = cacheData..s
local d1,d2,nemaStr = sfind(cacheData,"\r\n")
while d1 do
writePendingCmds()
nemaStr = ssub(cacheData,1,d2)
cacheData = ssub(cacheData,d2+1,-1)
if nmeaMode==0 or nmeaMode==2 then
--解析一行NEMA数据
parseNmea(nemaStr)
end
if (nmeaMode==1 or nmeaMode==2) and nmeaCbFnc then
nmeaCbFnc(nemaStr)
end
d1,d2 = sfind(cacheData,"\r\n")
end
end
end
end
-- GPS串口写命令操作
-- @string cmdGPS指令(cmd格式"$PGKC149,1,115200*"或者"$PGKC149,1,115200*XX\r\n")
-- @bool isFullcmd是否为完整的指令格式包括校验和以及\r\ntrue表示完整false或者nil为不完整
-- @return nil
-- @usage gps.writeCmd(cmd)
function writeCmd(cmd,isFull)
local tmp = cmd
if not isFull then
tmp = 0
for i=2,cmd:len()-1 do
tmp = bit.bxor(tmp,cmd:byte(i))
end
tmp = cmd..(string.format("%02X",tmp)):upper().."\r\n"
end
uart.write(uartID,tmp)
log.info("gps.writecmd",tmp)
log.info("gps.writecmd:toHex",tmp:toHex())
end
function writePendingCmds()
if not aerialModeSetted and aerialModeStr~="" then writeCmd(aerialModeStr) aerialModeSetted=true end
if not runModeSetted and runModeStr~="" then writeCmd(runModeStr) runModeSetted=true end
if not nmeaReportSetted and nmeaReportStr~="" then writeCmd(nmeaReportStr) nmeaReportSetted=true end
if not nmeaReportFreqSetted and nmeaReportFreqStr~="" then writeCmd(nmeaReportFreqStr) nmeaReportFreqSetted=true end
end
local function _open()
if openFlag then return end
pm.wake("gps.lua")
uart.setup(uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits)
if not taskFlag then
taskFlag =true
sys.taskInit(taskRead)
end
if powerCbFnc then
powerCbFnc(true)
else
pmd.ldoset(15, pmd.LDO_VMMC)
rtos.sys32k_clk_out(1)
end
openFlag = true
workAbnormalTimerId = sys.timerStart(sys.publish,8000,"GPS_WORK_ABNORMAL_IND")
sys.publish("GPS_STATE","OPEN")
fixFlag,filteredFlag = false
Ggalng,Ggalat,Gsv,Sep = "","",""
log.info("gps._open")
end
local function _close()
if not openFlag then return end
if powerCbFnc then
powerCbFnc(false)
else
pmd.ldoset(0,pmd.LDO_VMMC)
rtos.sys32k_clk_out(0)
end
uart.close(uartID)
pm.sleep("gps.lua")
openFlag = false
sys.publish("GPS_STATE","CLOSE",fixFlag)
stopWorkAbnormalTimer()
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
Ggalng,Ggalat,Gsv,Sep = "","",""
aerialModeSetted,runModeSetted,nmeaReportSetted,nmeaReportFreqSetted = nil
log.info("gps._close")
end
--- GPS应用模式1.
--
-- 打开GPS后GPS定位成功时如果有回调函数会调用回调函数
--
-- 使用此应用模式调用gps.open打开的“GPS应用”必须主动调用gps.close或者gps.closeAll才能关闭此“GPS应用”,主动关闭时,即使有回调函数,也不会调用回调函数
DEFAULT = 1
--- GPS应用模式2.
--
-- 打开GPS后如果在GPS开启最大时长到达时没有定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后如果在GPS开启最大时长内定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gps.close或者gps.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMERORSUC = 2
--- GPS应用模式3.
--
-- 打开GPS后在GPS开启最大时长时间到达时无论是否定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gps.close或者gps.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMER = 3
--“GPS应用”表
local tList = {}
--[[
函数名delItem
功能 从“GPS应用”表中删除一项“GPS应用”并不是真正的删除只是设置一个无效标志
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function delItem(mode,para)
for i=1,#tList do
--标志有效 并且 GPS应用模式相同 并且 “GPS应用”标记相同
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then
--设置无效标志
tList[i].flag,tList[i].delay = false
break
end
end
end
--[[
函数名addItem
功能 新增一项“GPS应用”到“GPS应用”表
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function addItem(mode,para)
--删除相同的“GPS应用”
delItem(mode,para)
local item,i,fnd = {flag=true, mode=mode, para=para}
--如果是TIMERORSUC或者TIMER模式初始化GPS工作剩余时间
if mode==TIMERORSUC or mode==TIMER then item.para.remain = para.val end
for i=1,#tList do
--如果存在无效的“GPS应用”项直接使用此位置
if not tList[i].flag then
tList[i] = item
fnd = true
break
end
end
--新增一项
if not fnd then table.insert(tList,item) end
end
local function existTimerItem()
for i=1,#tList do
if tList[i].flag and (tList[i].mode==TIMERORSUC or tList[i].mode==TIMER or tList[i].para.delay) then return true end
end
end
local function timerFnc()
for i=1,#tList do
if tList[i].flag then
log.info("gps.timerFnc@"..i,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay)
local rmn,dly,md,cb = tList[i].para.remain,tList[i].para.delay,tList[i].mode,tList[i].para.cb
if rmn and rmn>0 then
tList[i].para.remain = rmn-1
end
if dly and dly>0 then
tList[i].para.delay = dly-1
end
rmn = tList[i].para.remain
if isFix() and md==TIMER and rmn==0 and not tList[i].para.delay then
tList[i].para.delay = 1
end
dly = tList[i].para.delay
if isFix() then
if dly and dly==0 then
if cb then cb(tList[i].para.tag) end
if md == DEFAULT then
tList[i].para.delay = nil
else
close(md,tList[i].para)
end
end
else
if rmn and rmn == 0 then
if cb then cb(tList[i].para.tag) end
close(md,tList[i].para)
end
end
end
end
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
--[[
函数名statInd
功能 处理GPS定位成功的消息
参数
evtGPS消息类型
返回值:无
]]
local function statInd(evt)
--定位成功的消息
if evt == "LOCATION_SUCCESS" then
for i=1,#tList do
log.info("gps.statInd@"..i,tList[i].flag,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay,tList[i].para.cb)
if tList[i].flag then
if tList[i].mode ~= TIMER then
tList[i].para.delay = 1
if tList[i].mode == DEFAULT then
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
end
end
end
end
end
--- 打开一个“GPS应用”
-- “GPS应用”指的是使用GPS功能的一个应用
-- 例如假设有如下3种需求要打开GPS则一共有3个“GPS应用”
-- “GPS应用1”每隔1分钟打开一次GPS
-- “GPS应用2”设备发生震动时打开GPS
-- “GPS应用3”收到一条特殊短信时打开GPS
-- 只有所有“GPS应用”都关闭了才会去真正关闭GPS
-- 每个“GPS应用”打开或者关闭GPS时最多有4个参数其中 GPS应用模式和GPS应用标记 共同决定了一个唯一的“GPS应用”
-- 1、GPS应用模式(必选)
-- 2、GPS应用标记(必选)
-- 3、GPS开启最大时长[可选]
-- 4、回调函数[可选]
-- 例如gps.open(gps.TIMERORSUC,{tag="TEST",val=120,cb=testGpsCb})
-- gps.TIMERORSUC为GPS应用模式"TEST"为GPS应用标记120秒为GPS开启最大时长testGpsCb为回调函数
-- @number mode GPS应用模式支持gps.DEFAULTgps.TIMERORSUCgps.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps.TIMERORSUC或者gps.TIMER时此值才有意义
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)
-- @return nil
-- @usage gps.open(gps.DEFAULT,{tag="TEST1",cb=test1Cb})
-- @usage gps.open(gps.TIMERORSUC,{tag="TEST2",val=60,cb=test2Cb})
-- @usage gps.open(gps.TIMER,{tag="TEST3",val=120,cb=test3Cb})
-- @see DEFAULT,TIMERORSUC,TIMER
function open(mode,para)
assert((para and type(para) == "table" and para.tag and type(para.tag) == "string"),"gps.open para invalid")
log.info("gps.open",mode,para.tag,para.val,para.cb)
--如果GPS定位成功
if isFix() then
if mode~=TIMER then
--执行回调函数
if para.cb then para.cb(para.tag) end
if mode==TIMERORSUC then return end
end
end
addItem(mode,para)
--真正去打开GPS
_open()
--启动1秒的定时器
if existTimerItem() and not sys.timerIsActive(timerFnc) then
sys.timerStart(timerFnc,1000)
end
end
--- 关闭一个“GPS应用”
-- 只是从逻辑上关闭一个GPS应用并不一定真正关闭GPS是有所有的GPS应用都处于关闭状态才会去真正关闭GPS
-- @number mode GPS应用模式支持gps.DEFAULTgps.TIMERORSUCgps.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps.TIMERORSUC或者gps.TIMER时此值才有意义使用close接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用close接口时不需要传入此参数
-- @return nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口关闭时mode和para.tag要和gps.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gps.close(gps.DEFAULT,{tag="TEST1"})
-- @usage gps.close(gps.TIMERORSUC,{tag="TEST2"})
-- @usage gps.close(gps.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function close(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.close para invalid")
log.info("gps.close",mode,para.tag,para.val,para.cb)
--删除此“GPS应用”
delItem(mode,para)
local valid,i
for i=1,#tList do
if tList[i].flag then
valid = true
end
end
--如果没有一个“GPS应用”有效则关闭GPS
if not valid then _close() end
end
--- 关闭所有“GPS应用”
-- @return nil
-- @usage gps.closeAll()
-- @see open,DEFAULT,TIMERORSUC,TIMER
function closeAll()
for i=1,#tList do
if tList[i].flag and tList[i].para.cb then tList[i].para.cb(tList[i].para.tag) end
close(tList[i].mode,tList[i].para)
end
end
--- 判断一个“GPS应用”是否处于激活状态
-- @number mode GPS应用模式支持gps.DEFAULTgps.TIMERORSUCgps.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps.TIMERORSUC或者gps.TIMER时此值才有意义使用isActive接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用isActive接口时不需要传入此参数
-- @return bool result处于激活状态返回true否则返回nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口查询状态时mode和para.tag要和gps.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gps.isActive(gps.DEFAULT,{tag="TEST1"})
-- @usage gps.isActive(gps.TIMERORSUC,{tag="TEST2"})
-- @usage gps.isActive(gps.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function isActive(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.isActive para invalid")
for i=1,#tList do
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then return true end
end
end
--- 设置GPS模块供电控制的回调函数
-- 如果使用的是Air800或者供电控制使用的是LDO_VCAM则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gps.open前使用此接口传入自定义的供电控制函数cbFncGPS开启时gps.lua自动执行cbFnc(true)GPS关闭时gps.lua自动执行cbFnc(false)
-- @param cbFnc function类型用户自定义的GPS供电控制函数
-- @return nil
-- @usage gps.setPowerCbFnc(cbFnc)
function setPowerCbFnc(cbFnc)
powerCbFnc = cbFnc
end
--- 设置GPS模块和GSM模块之间数据通信的串口参数
-- 如果使用的是Air800或者使用的UART2(波特率115200数据位8无检验位停止位1)则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gps.open前使用此接口传入UART参数
-- @number id UART ID支持1和21表示UART12表示UART2
-- @number baudrate 波特率支持1200,2400,4800,9600,10400,14400,19200,28800,38400,57600,76800,115200,230400,460800,576000,921600,1152000,4000000
-- @number databits 数据位支持8
-- @number parity 校验位支持uart.PAR_NONE,uart.PAR_EVEN,uart.PAR_ODD
-- @number stopbits 停止位支持uart.STOP_1,uart.STOP_2
-- @return nil
-- @usage gps.setUart(2,115200,8,uart.PAR_NONE,uart.STOP_1)
function setUart(id,baudrate,databits,parity,stopbits)
uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits = id,baudrate,databits,parity,stopbits
end
--- 设置GPS模块搜星模式.
-- 如果使用的是Air800或者Air530不调用此接口配置则默认同时开启GPS和北斗定位
-- @number gps GPS定位系统1是打开0是关闭
-- @number beidou 中国北斗定位系统1是打开0是关闭
-- @number glonass 俄罗斯Glonass定位系统1是打开0是关闭
-- @number galieo 欧盟伽利略定位系统1是打开0是关闭
-- @return nil
-- @usage gps.setAeriaMode(1,1,0,0)
function setAerialMode(gps,beidou,glonass,galieo)
local gps = gps or 0
local glonass = glonass or 0
local beidou = beidou or 0
local galieo = galieo or 0
if gps+glonass+beidou+galieo == 0 then gps=1 beidou=1 end
local tmpStr = "$PGKC115,"..gps..","..glonass..","..beidou..","..galieo.."*"
if tmpStr~=aerialModeStr then
aerialModeStr,aerialModeSetted = tmpStr
end
end
--- 设置NMEA数据处理模式.
-- 如果不调用此接口配置则默认仅gps.lua内部处理NMEA数据
-- @number mode NMEA数据处理模式0表示仅gps.lua内部处理1表示仅用户自己处理2表示gps.lua和用户同时处理
-- @param cbFnc function类型用户处理一条NMEA数据的回调函数mode为1和2时此值才有意义
-- @return nil
-- @usage gps.setNmeaMode(0)
-- @usage gps.setNmeaMode(1,cbFnc)
-- @usage gps.setNmeaMode(2,cbFnc)
function setNmeaMode(mode,cbFnc)
nmeaMode,nmeaCbFnc = mode,cbFnc
end
-- 设置GPS模块的运行模式.
-- 如果不调用此接口配置,则默认为正常运行模式
-- @number mode 运行模式
-- 0正常运行模式
-- 1周期超低功耗跟踪模式
-- 2周期低功耗模式
-- 4直接进入超低功耗跟踪模式
-- 8自动低功耗模式可以通过串口唤醒
-- 9自动超低功耗跟踪模式需要force on来唤醒
-- @number runTm 单位毫秒mode为0时表示NEMA数据的上报间隔mode为1或者2时表示运行时长其余mode时此值无意义
-- @number sleepTm 单位毫秒mode为1或者2时表示运行时长其余mode时此值无意义
-- @return nil
-- @usage gps.setRunMode(0,1000)
-- @usage gps.setRunMode(1,5000,2000)
function setRunMode(mode,runTm,sleepTm)
local rt,st = runTm or "",sleepTm or ""
if mode==0 and rt then
if rt>10000 then rt=10000 end
if rt<200 then rt=200 end
nmeaReportStr = "$PGKC101,"..rt.."*"
end
local tmpStr = "$PGKC105,"..mode..((mode==1 or mode==2) and (","..rt..","..st) or "").."*"
if tmpStr~=runModeStr then
runModeStr,runModeSetted = tmpStr
end
end
--- 设置NEMA语句的输出频率.
-- @number[opt=1] rmc 单位秒RMC语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gga 单位秒GGA语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gsa 单位秒GSA语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gsv 单位秒GSV语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] vtg 单位秒VTG语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=0] gll 单位秒GLL语句输出频率取值范围0到10之间的整数0表示不输出
-- @return nil
-- @usage gps.setNemaReportFreq(5,0,0,0,0,0)
function setNemaReportFreq(rmc,gga,gsa,gsv,vtg,gll)
local tmpStr = "$PGKC242,"..(gll or 0)..","..(rmc or 1)..","..(vtg or 1)..","..(gga or 1)..","..(gsa or 1)..","..(gsv or 1)..",0,0,0,0,0,0,0,0,0,0,0,0,0".."*"
if tmpStr~=nmeaReportFreqStr then
nmeaReportFreqStr,nmeaReportFreqSetted = tmpStr
end
end
--- 设置GPS定位成功后经纬度的过滤时间.
-- @number[opt=0] seconds 单位秒GPS定位成功后丢弃前seconds秒的位置信息
-- @return nil
-- @usage gps.setLocationFilter(2)
function setLocationFilter(seconds)
filterSeconds = seconds or 0
end
function setFastFix(lat,lng,tm)
local t = tm.year..","..tm.month..","..tm.day..","..tm.hour..","..tm.min..","..tm.sec.."*"
log.info("gps.setFastFix",lat,lng,t)
writeCmd("$PGKC634,"..t)
writeCmd("$PGKC635,"..lat..","..lng..",0,"..t)
end
--- 获取GPS模块是否处于开启状态
-- @return bool resulttrue表示开启状态false或者nil表示关闭状态
-- @usage gps.isOpen()
function isOpen()
return openFlag
end
--- 获取GPS模块是否定位成功
-- @return bool resulttrue表示定位成功false或者nil表示定位失败
-- @usage gps.isFix()
function isFix()
return fixFlag
end
--- 获取GPS模块是否首次定位成功过
-- @return bool resulttrue表示曾经定位成功
-- @usage gps.isOnece()
function isOnece()
return fixOnece
end
-- 度分格式转换为度格式
-- @string inStr 度分格式的位置
-- @return string度格式的位置
-- @usage degreeMinuteToDegree("3114.50931")--->"31.2418218"31度14.50931分转换为31.2418218度
-- @usage degreeMinuteToDegree("12128.44954")--->"121.4741590"121度28.44954分转换为121.4741590度
local function degreeMinuteToDegree(inStr)
local integer,fraction = smatch(inStr,"(%d+)%.(%d+)")
if integer and fraction then
local intLen = slen(integer)
if intLen~=4 and intLen~=5 then log.error("gps.degreeMinuteToDegree integer error",inStr) return "" end
if slen(fraction)<5 then fraction = fraction..srep("0",5-slen(fraction)) end
fraction = ssub(fraction,1,5)
local temp = tonumber(ssub(integer,intLen-1,intLen)..fraction)*10
fraction = tostring((temp-(temp%6))/6)
local fracLen = slen(fraction)
if fracLen>7 then
fraction = ssub(fraction,1,7)
elseif fracLen<7 then
fraction = srep("0",7-fracLen)..fraction
end
return ssub(integer,1,intLen-2).."."..fraction
end
return ""
end
--- 获取度格式的经纬度信息
-- @string[opt=nil] typ 返回的经纬度格式typ为"DEGREE_MINUTE"时表示返回度分格式,其余表示返回度格式
-- @return table location
-- 例如typ为"DEGREE_MINUTE"时返回{lngType="E",lng="12128.44954",latType="N",lat="3114.50931"}
-- 例如typ不是"DEGREE_MINUTE"时返回{lngType="E",lng="121.123456",latType="N",lat="31.123456"}
-- lngTypestring类型表示经度类型取值"E""W"
-- lngstring类型表示度格式的经度值无效时为""
-- latTypestring类型表示纬度类型取值"N""S"
-- latstring类型表示度格式的纬度值无效时为""
-- @usage gps.getLocation()
function getLocation(typ)
return {
lngType=longitudeType,
lng=isFix() and (typ=="DEGREE_MINUTE" and longitude or degreeMinuteToDegree(longitude)) or "",
latType=latitudeType,
lat=isFix() and (typ=="DEGREE_MINUTE" and latitude or degreeMinuteToDegree(latitude)) or ""
}
end
function getLastLocation(typ)
if typ=="DEGREE_MINUTE" then
return {
lngType=longitudeType,
lng=longitude,
latType=latitudeType,
lat=latitude
}
else
return (longitude and longitude~="") and degreeMinuteToDegree(longitude) or "", (latitude and latitude~="") and degreeMinuteToDegree(latitude) or ""
end
end
--- 获取海拔
-- @return number altitude海拔单位米
-- @usage gps.getAltitude()
function getAltitude()
return tonumber(smatch(altitude,"(%d+)") or "0")
end
--- 获取速度
-- @return number kmSpeed第一个返回值为公里每小时的速度
-- @return number nmSpeed第二个返回值为海里每小时的速度
-- @usage gps.getSpeed()
function getSpeed()
local integer = tonumber(smatch(speed,"(%d+)") or "0")
return (integer*1852 - (integer*1852 %1000))/1000,integer
end
--- 获取原始速度,字符串带浮点
-- @return number speed 海里每小时的速度
-- @usage gps.getOrgSpeed()
function getOrgSpeed()
return speed
end
--- 获取方向角
-- @return number course方向角
-- @usage gps.getCourse()
function getCourse()
return tonumber(smatch(course,"(%d+)") or "0")
end
-- 获取所有可见卫星的最大信号强度
-- @return number strength最大信号强度
-- @usage gps.getMaxSignalStrength()
function getMaxSignalStrength()
return maxSignalStrength
end
--- 获取可见卫星的个数
-- @return number count可见卫星的个数
-- @usage gps.getViewedSateCnt()
function getViewedSateCnt()
return tonumber(viewedGpsSateCnt)+tonumber(viewedBdSateCnt)
end
--- 获取定位使用的卫星个数
-- @return number count定位使用的卫星个数
-- @usage gps.getUsedSateCnt()
function getUsedSateCnt()
return tonumber(usedSateCnt)
end
--- 获取GGA语句中度分格式的经纬度信息
-- @return string lng度分格式的经度值(dddmm.mmmm),西经会添加一个-前缀,无效时为"";例如"12112.3456"表示东经121度12.3456分,"-12112.3456"表示西经121度12.3456分
-- @return string lat度分格式的纬度值(ddmm.mmmm),南纬会添加一个-前缀,无效时为"";例如"3112.3456"表示北纬31度12.3456分,"-3112.3456"表示南纬31度12.3456分
-- @usage gps.getGgaloc()
function getGgaloc()
return Ggalng or "",Ggalat or ""
end
--- 获取RMC语句中的UTC时间
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第一个参数设置为true
-- @return table utcTimeUTC时间nil表示无效例如{year=2018,month=4,day=24,hour=11,min=52,sec=10}
-- @usage gps.getUtcTime()
function getUtcTime()
return UtcTime
end
--- 获取定位使用的大地高
-- @return number sep大地高
-- @usage gps.getSep()
function getSep()
return tonumber(Sep or "0")
end
--- 获取GSA语句中的可见卫星号
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第三个参数设置为true
-- @return string viewedSateId可用卫星号""表示无效
-- @usage gps.getSateSn()
function getSateSn()
return SateSn or ""
end
--- 获取GSV语句中的可见卫星的信噪比
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第二个参数设置为true
-- @return string gsv信噪比
-- @usage gps.getGsv()
function getGsv()
return Gsv or ""
end
--- 设置是否需要解析的字段
-- @bool[opt=nil] utcTime 是否解析RMC语句中的UTC时间true表示解析false或者nil不解析
-- @bool[opt=nil] gsv 是否解析GSV语句true表示解析false或者nil不解析
-- @bool[opt=nil] gsaId 是否解析GSA语句中的卫星IDtrue表示解析false或者nil不解析
-- @usage gps.setParseItem(true,true,true)
function setParseItem(utcTime,gsv,gsaId)
psUtcTime,psGsv,psSn = utcTime,gsv,gsaId
end
function init()
sys.subscribe("GPS_STATE",statInd)
end
function unInit()
sys.unsubscribe("GPS_STATE",statInd)
closeAll()
end
init()

933
4G/code/lib/gps9701.lua Normal file
View File

@@ -0,0 +1,933 @@
--- 模块功能GPS模块管理
-- 注意:此功能模块中的功能接口可以分为四大类:
-- 1、GPS开启
-- 2、GPS关闭
-- 3、GPS定位数据读取
-- 4、GPS参数和功能设置
-- 1、2、3是通用功能除了支持合宙的Air512G、Air551G、Air552G模块理论上也支持其他厂家的串口GPS模块
-- 4是专用功能仅支持合宙的Air512G、Air551G、Air552G模块
-- @module gps9701
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.23
require"pm"
require"utils"
module(..., package.seeall)
local smatch,sfind,slen,ssub,sbyte,sformat,srep = string.match,string.find,string.len,string.sub,string.byte,string.format,string.rep
--GPS开启标志true表示开启状态false或者nil表示关闭状态
local openFlag
--GPS定位标志"2D"表示2D定位"3D"表示3D定位其余表示未定位
--GPS定位标志true表示其余表示未定位
local fixFlag,fixOnece=nil,nil
--GPS定位成功后过滤掉前filterSeconds秒的经纬度信息
--是否已经过滤完成
local filterSeconds,filteredFlag = 0
--从定位成功切换到定位失败,连续定位失败的次数
local fixFailCnt = 0
--经纬度类型和数据
local latitudeType,latitude,longitudeType,longitude = "N","","E",""
--海拔,速度,方向角
local altitude,speed,course = "0","0","0"
--参与定位的卫星个数,所有可见卫星的最大信号值,所有可见卫星的最大信号值中间缓存值
local usedSateCnt,maxSignalStrength,maxSignalStrengthVar = "0",0,0
--可见卫星个数
local viewedGpsSateCnt,viewedBdSateCnt = "0","0"
--可用卫星号UTC时间信噪比
local SateSn,UtcTime,Gsv
--大地高,度分经度,度分纬度
local Sep,Ggalng,Ggalat
--是否需要解析项
local psUtcTime,psGsv,psSn
--GPS供电设置函数
local powerCbFnc
--串口配置
uartBaudrate = 115200
local uartID,uartDatabits,uartParity,uartStopbits = 2,8,uart.PAR_NONE,uart.STOP_1
--搜星模式命令字符串,"$PGKC115," .. gps .. "," .. glonass .. "," .. beidou .. "," .. galieo .. "*"
local aerialModeStr,aerialModeSetted = ""
--运行模式命令字符串,"$PGKC105," .. mode .. "," .. rt .. "," .. st .. "*"
local runModeStr,runModeSetted = ""
--正常运行模式下NMEA数据上报间隔命令字符串"$PGKC101," .. interval .. "*"
local nmeaReportStr,nmeaReportSetted = ""
--每种NEMA数据的输出频率命令字符串
local nmeaReportFreqStr,nmeaReportFreqSetted = ""
--NMEA数据处理模式0表示仅gps.lua内部处理1表示仅用户自己处理2表示gps.lua和用户同时处理
--用户处理一条NMEA数据的回调函数
local nmeaMode,nmeaCbFnc = 0
--NMEA数据输出间隔
local nmeaInterval = 1000
--运行模式
--0正常运行模式
--1周期超低功耗跟踪模式
--2周期低功耗模式
--4直接进入超低功耗跟踪模式
--8自动低功耗模式可以通过串口唤醒
--9, 自动超低功耗跟踪模式需要force on来唤醒
local runMode = 0
--gps 的串口线程是否在工作;
local taskFlag=false
--runMode为1或者2时GPS运行状态和休眠状态的时长
local runTime,sleepTime
--检测gps是否工作正常的定时器ID
local workAbnormalTimerId
local agpsver = nil
checkdatapack = false
local testcoro = nil
local i = 0
--[[
函数名getstrength
功能 解析GSV数据
参数
sgNEMA中的一行GSV数据
返回值:无
]]
local function getstrength(sg)
sg = ssub(sg, 4, #sg)
local d1,d2,curnum,lineno,total,sgv_str = sfind(sg,"GSV,(%d),(%d),(%d+),(.*)%*.*")
if not curnum or not lineno or not total or not sgv_str then
return
end
if lineno == nil then
maxSignalStrengthVar = 0
maxSignalStrength = 0
elseif tonumber(lineno) == 1 then
maxSignalStrength = maxSignalStrengthVar
maxSignalStrengthVar = 0
end
local tmpstr,i = sgv_str
for i=1,4 do
local d1,d2,id,elevation,azimuth,strength = sfind(tmpstr,"(%d+),([%-]*%d*),(%d*),(%d*)")
if id == nil then return end
if strength == "" or not strength then
strength = "00"
end
strength = tonumber(strength)
if strength > maxSignalStrengthVar then
maxSignalStrengthVar = strength
end
local idx,cur,fnd,tmpid = 0,id..","..elevation..","..azimuth..","..strength..",",false
for tmpid in string.gmatch(Gsv,"(%d+),%d*,%d*,%d*,") do
idx = idx + 1
if tmpid == id then fnd = true break end
end
if fnd then
local pattern,i = ""
for i=1,idx do
pattern = pattern.."%d+,%d*,%d*,%d*,"
end
local m1,m2 = sfind(Gsv,"^"..pattern)
if m1 and m2 then
local front = ssub(Gsv,1,m2)
local n1,n2 = sfind(front,"%d+,%d*,%d*,%d*,$")
if n1 and n2 then
Gsv = ssub(Gsv,1,n1-1)..cur..ssub(Gsv,n2+1,-1)
end
end
else
Gsv = Gsv..cur
end
tmpstr = ssub(tmpstr,d2+1,-1)
end
end
local function filterTimerFnc()
log.info("gps.filterTimerFnc end")
filteredFlag = true
end
local function stopWorkAbnormalTimer()
if workAbnormalTimerId then
sys.timerStop(workAbnormalTimerId)
workAbnormalTimerId = nil
end
end
local function parseNmea(s)
if not s or s=="" then return end
local lat,lng,spd,cog,gpsFind,gpsTime,gpsDate,locSateCnt,hdp,latTyp,lngTyp,altd
local hexStr = s:toHex()
if smatch(hexStr,"AAF00D0001009500039A00AA0F") then
sys.publish("GPS_STATE","BINARY_CMD_ACK")
agpsver = true
stopWorkAbnormalTimer()
i= i+1
return
elseif smatch(hexStr,"AAF00D000300") then
sys.publish("GPS_STATE",smatch(hexStr,"^AAF00D000300FFFF") and "WRITE_EPH_END_ACK" or "WRITE_EPH_ACK")
stopWorkAbnormalTimer()
return
end
local fixed,workAbnormal
if smatch(s,"GGA") then
lat,latTyp,lng,lngTyp,gpsFind,locSateCnt,hdp,altd,sep = smatch(s,"GGA,%d+%.%d+,(%d+%.%d+),([NS]),(%d+%.%d+),([EW]),(%d),(%d+),([%d%.]*),(.*),M,(.*),M")
if (gpsFind=="1" or gpsFind=="2" or gpsFind=="4") and altd then
--fixed = true
altitude = altd
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
usedSateCnt = locSateCnt
Ggalng,Ggalat = (lngTyp=="W" and "-" or "")..lng,(latTyp=="S" and "-" or "")..lat
Sep = sep
else
fixed = false
end
elseif smatch(s,"RMC") then
gpsTime,gpsFind,lat,latTyp,lng,lngTyp,spd,cog,gpsDate = smatch(s,"RMC,(%d%d%d%d%d%d)%.%d+,(%w),(%d*%.*%d*),([NS]*),(%d*%.*%d*),([EW]*),(.-),(.-),(%d%d%d%d%d%d),")
if gpsFind=="A" and cog then
fixed = true
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
speed = spd
course = cog
else
fixed = false
end
if psUtcTime and gpsFind == "A" and gpsTime and gpsDate and gpsTime ~= "" and gpsDate ~= "" then
local yy,mm,dd,h,m,s = tonumber(ssub(gpsDate,5,6)),tonumber(ssub(gpsDate,3,4)),tonumber(ssub(gpsDate,1,2)),tonumber(ssub(gpsTime,1,2)),tonumber(ssub(gpsTime,3,4)),tonumber(ssub(gpsTime,5,6))
UtcTime = {year=2000+yy,month=mm,day=dd,hour=h,min=m,sec=s}
end
elseif smatch(s,"GPGSV") then
viewedGpsSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"BDGSV") then
viewedBdSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"GSA") then
if psSn then
local satesn = smatch(s,"GSA,%w*,%d*,(%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,)") or ""
if slen(satesn) > 0 and smatch(satesn,"%d+,") then
SateSn = satesn
end
end
else
workAbnormal = true
end
if not workAbnormal then
stopWorkAbnormalTimer()
end
if filterSeconds>0 and fixed and not fixFlag and not filteredFlag then
if not sys.timerIsActive(filterTimerFnc) then
log.info("gps.filterTimerFnc begin")
sys.publish("GPS_STATE","LOCATION_FILTER")
sys.timerStart(filterTimerFnc,filterSeconds*1000)
end
return
end
--定位成功
if fixed then
if not fixFlag then
fixFlag,filteredFlag = true,true
fixOnece=true
fixFailCnt = 0
sys.publish("GPS_STATE","LOCATION_SUCCESS")
end
elseif fixed==false then
if fixFlag then
fixFailCnt = fixFailCnt+1
if fixFailCnt>=20 then
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
sys.publish("GPS_STATE","LOCATION_FAIL")
end
end
end
end
local function taskRead()
local cacheData = ""
local co = coroutine.running()
while true do
local s =""
if openFlag then
s= uart.read(uartID, "*l")
end
if i==2 then
agpsver =false
i = 0
end
if agpsver then
if s=="" then
checkdatapack = true
else
sys.publish("NOEMAL_RECE")
end
end
if s == "" then
uart.on(uartID,"receive",function() coroutine.resume(co) end)
coroutine.yield()
uart.on(uartID,"receive")
else
cacheData = cacheData..s
if cacheData:toHex()=="AAF00D0001009500039A00AA0F" or smatch(cacheData:toHex(),"AAF00D000300") then
cacheData = cacheData.."\r\n"
end
local d1,d2,nemaStr = sfind(cacheData,"\r\n")--截取串口收到的数据
while d1 do
writePendingCmds()
nemaStr = ssub(cacheData,1,d2)
cacheData = ssub(cacheData,d2+1,-1)
if nmeaMode==0 or nmeaMode==2 then
--解析一行NEMA数据
parseNmea(nemaStr)
end
if (nmeaMode==1 or nmeaMode==2) and nmeaCbFnc then
nmeaCbFnc(nemaStr)
end
d1,d2 = sfind(cacheData,"\r\n")
end
end
end
end
-- GPS串口写命令操作
-- @string cmdGPS指令(cmd格式"$PGKC149,1,115200*"或者"$PGKC149,1,115200*XX\r\n")
-- @bool isFullcmd是否为完整的指令格式包括校验和以及\r\ntrue表示完整false或者nil为不完整
-- @return nil
-- @usage gps.writeCmd(cmd)
function writeCmd(cmd,isFull)
local tmp = cmd
-- log.info("星历数据5",tmp:toHex())
if not isFull then
tmp = 0
for i=2,cmd:len()-1 do
tmp = bit.bxor(tmp,cmd:byte(i))
end
tmp = cmd..(string.format("%02X",tmp)):upper().."\r\n"
end
-- sys.timerStart(function() uart.write(uartID,tmp) end, 100)
uart.write(uartID,tmp)
-- log.info("gps.writecmd",tmp)
log.info("gps.writecmd:toHex",uartID, tmp:toHex())
end
function writePendingCmds()
if not aerialModeSetted and aerialModeStr~="" then writeCmd(aerialModeStr) aerialModeSetted=true end
if not runModeSetted and runModeStr~="" then writeCmd(runModeStr) runModeSetted=true end
if not nmeaReportSetted and nmeaReportStr~="" then writeCmd(nmeaReportStr) nmeaReportSetted=true end
if not nmeaReportFreqSetted and nmeaReportFreqStr~="" then writeCmd(nmeaReportFreqStr) nmeaReportFreqSetted=true end
end
local function _open()
if openFlag then return end--gps开启状态
pm.wake("gps.lua")
uart.setup(uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits)
if not taskFlag then --串口线程
taskFlag =true
sys.taskInit(taskRead)
end
if powerCbFnc then
powerCbFnc(true)
else
pmd.ldoset(15, pmd.LDO_VMMC)
rtos.sys32k_clk_out(1)
end
openFlag = true
workAbnormalTimerId = sys.timerStart(sys.publish,8000,"GPS_WORK_ABNORMAL_IND")
sys.publish("GPS_STATE","OPEN")
fixFlag,filteredFlag = false
Ggalng,Ggalat,Gsv,Sep = "","",""
log.info("gps._open")
end
local function _close()
if not openFlag then return end
if powerCbFnc then
powerCbFnc(false)
else
pmd.ldoset(0,pmd.LDO_VMMC)
rtos.sys32k_clk_out(0)
end
uart.close(uartID)
pm.sleep("gps.lua")
openFlag = false
sys.publish("GPS_STATE","CLOSE",fixFlag)
stopWorkAbnormalTimer()
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
Ggalng,Ggalat,Gsv,Sep = "","",""
aerialModeSetted,runModeSetted,nmeaReportSetted,nmeaReportFreqSetted = nil
log.info("gps._close")
end
--- GPS应用模式1.
--
-- 打开GPS后GPS定位成功时如果有回调函数会调用回调函数
--
-- 使用此应用模式调用gps9701.open打开的“GPS应用”必须主动调用gps9701.close或者gps9701.closeAll才能关闭此“GPS应用”,主动关闭时,即使有回调函数,也不会调用回调函数
DEFAULT = 1
--- GPS应用模式2.
--
-- 打开GPS后如果在GPS开启最大时长到达时没有定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后如果在GPS开启最大时长内定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gps9701.close或者gps9701.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMERORSUC = 2
--- GPS应用模式3.
--
-- 打开GPS后在GPS开启最大时长时间到达时无论是否定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gps9701.close或者gps9701.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMER = 3
--“GPS应用”表
local tList = {}
--[[
函数名delItem
功能 从“GPS应用”表中删除一项“GPS应用”并不是真正的删除只是设置一个无效标志
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function delItem(mode,para)
for i=1,#tList do
--标志有效 并且 GPS应用模式相同 并且 “GPS应用”标记相同
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then
--设置无效标志
tList[i].flag,tList[i].delay = false
break
end
end
end
--[[
函数名addItem
功能 新增一项“GPS应用”到“GPS应用”表
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function addItem(mode,para)
--删除相同的“GPS应用”
delItem(mode,para)
local item,i,fnd = {flag=true, mode=mode, para=para}
--如果是TIMERORSUC或者TIMER模式初始化GPS工作剩余时间
if mode==TIMERORSUC or mode==TIMER then item.para.remain = para.val end
for i=1,#tList do
--如果存在无效的“GPS应用”项直接使用此位置
if not tList[i].flag then
tList[i] = item
fnd = true
break
end
end
--新增一项
if not fnd then table.insert(tList,item) end
end
local function existTimerItem()
for i=1,#tList do
if tList[i].flag and (tList[i].mode==TIMERORSUC or tList[i].mode==TIMER or tList[i].para.delay) then return true end
end
end
local function timerFnc()
for i=1,#tList do
if tList[i].flag then
log.info("gps.timerFnc@"..i,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay)
local rmn,dly,md,cb = tList[i].para.remain,tList[i].para.delay,tList[i].mode,tList[i].para.cb
if rmn and rmn>0 then
tList[i].para.remain = rmn-1
end
if dly and dly>0 then
tList[i].para.delay = dly-1
end
rmn = tList[i].para.remain
if isFix() and md==TIMER and rmn==0 and not tList[i].para.delay then
tList[i].para.delay = 1
end
dly = tList[i].para.delay
if isFix() then
if dly and dly==0 then
if cb then cb(tList[i].para.tag) end
if md == DEFAULT then
tList[i].para.delay = nil
else
close(md,tList[i].para)
end
end
else
if rmn and rmn == 0 then
if cb then cb(tList[i].para.tag) end
close(md,tList[i].para)
end
end
end
end
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
--[[
函数名statInd
功能 处理GPS定位成功的消息
参数
evtGPS消息类型
返回值:无
]]
local function statInd(evt)
--定位成功的消息
if evt == "LOCATION_SUCCESS" then
for i=1,#tList do
log.info("gps.statInd@"..i,tList[i].flag,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay,tList[i].para.cb)
if tList[i].flag then
if tList[i].mode ~= TIMER then
tList[i].para.delay = 1
if tList[i].mode == DEFAULT then
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
end
end
end
end
end
--- 打开一个“GPS应用”
-- “GPS应用”指的是使用GPS功能的一个应用
-- 例如假设有如下3种需求要打开GPS则一共有3个“GPS应用”
-- “GPS应用1”每隔1分钟打开一次GPS
-- “GPS应用2”设备发生震动时打开GPS
-- “GPS应用3”收到一条特殊短信时打开GPS
-- 只有所有“GPS应用”都关闭了才会去真正关闭GPS
-- 每个“GPS应用”打开或者关闭GPS时最多有4个参数其中 GPS应用模式和GPS应用标记 共同决定了一个唯一的“GPS应用”
-- 1、GPS应用模式(必选)
-- 2、GPS应用标记(必选)
-- 3、GPS开启最大时长[可选]
-- 4、回调函数[可选]
-- 例如gps9701.open(gps9701.TIMERORSUC,{tag="TEST",val=120,cb=testGpsCb})
-- gps9701.TIMERORSUC为GPS应用模式"TEST"为GPS应用标记120秒为GPS开启最大时长testGpsCb为回调函数
-- @number mode GPS应用模式支持gps9701.DEFAULTgps9701.TIMERORSUCgps9701.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps9701.TIMERORSUC或者gps9701.TIMER时此值才有意义
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)
-- @return nil
-- @usage gps9701.open(gps9701.DEFAULT,{tag="TEST1",cb=test1Cb})
-- @usage gps9701.open(gps9701.TIMERORSUC,{tag="TEST2",val=60,cb=test2Cb})
-- @usage gps9701.open(gps9701.TIMER,{tag="TEST3",val=120,cb=test3Cb})
-- @see DEFAULT,TIMERORSUC,TIMER
function open(mode,para)
assert((para and type(para) == "table" and para.tag and type(para.tag) == "string"),"gps.open para invalid")
log.info("gps.open",mode,para.tag,para.val,para.cb)
--如果GPS定位成功
if isFix() then
if mode~=TIMER then
--执行回调函数
if para.cb then para.cb(para.tag) end
if mode==TIMERORSUC then return end
end
end
addItem(mode,para)--gps应用
--真正去打开GPS
_open()
--启动1秒的定时器
if existTimerItem() and not sys.timerIsActive(timerFnc) then
sys.timerStart(timerFnc,1000)
end
end
--- 关闭一个“GPS应用”
-- 只是从逻辑上关闭一个GPS应用并不一定真正关闭GPS是有所有的GPS应用都处于关闭状态才会去真正关闭GPS
-- @number mode GPS应用模式支持gps9701.DEFAULTgps9701.TIMERORSUCgps9701.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps9701.TIMERORSUC或者gps9701.TIMER时此值才有意义使用close接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用close接口时不需要传入此参数
-- @return nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口关闭时mode和para.tag要和gps9701.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gps9701.close(gps9701.DEFAULT,{tag="TEST1"})
-- @usage gps9701.close(gps9701.TIMERORSUC,{tag="TEST2"})
-- @usage gps9701.close(gps9701.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function close(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.close para invalid")
log.info("gps.close",mode,para.tag,para.val,para.cb)
--删除此“GPS应用”
delItem(mode,para)
local valid,i
for i=1,#tList do
if tList[i].flag then
valid = true
end
end
--如果没有一个“GPS应用”有效则关闭GPS
if not valid then _close() end
end
--- 关闭所有“GPS应用”
-- @return nil
-- @usage gps9701.closeAll()
-- @see open,DEFAULT,TIMERORSUC,TIMER
function closeAll()
for i=1,#tList do
if tList[i].flag and tList[i].para.cb then tList[i].para.cb(tList[i].para.tag) end
close(tList[i].mode,tList[i].para)
end
end
--- 判断一个“GPS应用”是否处于激活状态
-- @number mode GPS应用模式支持gps9701.DEFAULTgps9701.TIMERORSUCgps9701.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps9701.TIMERORSUC或者gps9701.TIMER时此值才有意义使用isActive接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用isActive接口时不需要传入此参数
-- @return bool result处于激活状态返回true否则返回nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口查询状态时mode和para.tag要和gps9701.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gps9701.isActive(gps9701.DEFAULT,{tag="TEST1"})
-- @usage gps9701.isActive(gps9701.TIMERORSUC,{tag="TEST2"})
-- @usage gps9701.isActive(gps9701.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function isActive(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.isActive para invalid")
for i=1,#tList do
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then return true end
end
end
--- 设置GPS模块供电控制的回调函数
-- 如果使用的是Air800或者供电控制使用的是LDO_VCAM则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gps9701.open前使用此接口传入自定义的供电控制函数cbFncGPS开启时gps9701.lua自动执行cbFnc(true)GPS关闭时gps9701.lua自动执行cbFnc(false)
-- @param cbFnc function类型用户自定义的GPS供电控制函数
-- @return nil
-- @usage gps9701.setPowerCbFnc(cbFnc)
function setPowerCbFnc(cbFnc)
powerCbFnc = cbFnc
end
--- 设置GPS模块和GSM模块之间数据通信的串口参数
-- 如果使用的是Air800或者使用的UART2(波特率115200数据位8无检验位停止位1)则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gps9701.open前使用此接口传入UART参数
-- @number id UART ID支持1和21表示UART12表示UART2
-- @number baudrate 波特率支持1200,2400,4800,9600,10400,14400,19200,28800,38400,57600,76800,115200,230400,460800,576000,921600,1152000,4000000
-- @number databits 数据位支持8
-- @number parity 校验位支持uart.PAR_NONE,uart.PAR_EVEN,uart.PAR_ODD
-- @number stopbits 停止位支持uart.STOP_1,uart.STOP_2
-- @return nil
-- @usage gps9701.setUart(2,115200,8,uart.PAR_NONE,uart.STOP_1)
function setUart(id,baudrate,databits,parity,stopbits)
uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits = id,baudrate,databits,parity,stopbits
end
--- 设置GPS模块搜星模式.
-- 如果使用的是Air800或者Air530不调用此接口配置则默认同时开启GPS和北斗定位
-- @number gps GPS定位系统1是打开0是关闭
-- @number beidou 中国北斗定位系统1是打开0是关闭
-- @number glonass 俄罗斯Glonass定位系统1是打开0是关闭
-- @number galieo 欧盟伽利略定位系统1是打开0是关闭
-- @return nil
-- @usage gps9701.setAeriaMode(1,1,0,0)
function setAerialMode(gps,beidou,glonass,galieo)
local gps = gps or 0
local glonass = glonass or 0
local beidou = beidou or 0
local galieo = galieo or 0
if gps+glonass+beidou+galieo == 0 then gps=1 beidou=1 end
if gps==1 then gps=1 end
if glonass==1 then glonass=4 end
if beidou==1 then beidou= 8 end
if galieo==1 then galieo= 2 end
local searchstar=gps+glonass+beidou+galieo
searchstar = string.format("%X",searchstar)
log.info("搜星",searchstar)
--local tmpStr = "$PGKC115,"..gps..","..glonass..","..beidou..","..galieo.."*"
local tmpStr = "$PGKC121,0,"..searchstar..",0*"
if tmpStr~=aerialModeStr then
aerialModeStr,aerialModeSetted = tmpStr
end
end
--- 设置NMEA数据处理模式.
-- 如果不调用此接口配置则默认仅gps.lua内部处理NMEA数据
-- @number mode NMEA数据处理模式0表示仅gps.lua内部处理1表示仅用户自己处理2表示gps.lua和用户同时处理
-- @param cbFnc function类型用户处理一条NMEA数据的回调函数mode为1和2时此值才有意义
-- @return nil
-- @usage gps9701.setNmeaMode(0)
-- @usage gps9701.setNmeaMode(1,cbFnc)
-- @usage gps9701.setNmeaMode(2,cbFnc)
function setNmeaMode(mode,cbFnc)
nmeaMode,nmeaCbFnc = mode,cbFnc
end
--- 设置GPS模块的运行模式.
-- 如果不调用此接口配置,则默认为正常运行模式
-- @number mode 运行模式
-- 0正常运行模式
-- 1周期超低功耗跟踪模式
-- 4直接进入超低功耗跟踪模式
-- 8低功耗模式可以通过串口发送命令唤醒
-- @number runTm 单位毫秒mode为0时表示NEMA数据的上报间隔mode为1时表示运行时长其余mode时此值无意义
-- @number sleepTm 单位毫秒mode为1时表示运行时长其余mode时此值无意义
-- @return nil
-- @usage gps9701.setRunMode(0,1000)
-- @usage gps9701.setRunMode(1,5000,2000)
function setRunMode(mode,runTm,sleepTm)
local rt,st = runTm or "",sleepTm or ""
if mode==0 and rt then
if rt>10000 then rt=10000 end
if rt<100 then rt=100 end
nmeaReportStr = "$PGKC101,"..rt.."*"
end
local tmpStr = "$PGKC105,"..mode..((mode==1) and (","..rt..","..st) or "").."*"
if tmpStr~=runModeStr then
runModeStr,runModeSetted = tmpStr
end
end
--- 设置NEMA语句的输出频率.
-- @number[opt=1] rmc 单位秒RMC语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gga 单位秒GGA语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gsa 单位秒GSA语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] gsv 单位秒GSV语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=1] vtg 单位秒VTG语句输出频率取值范围0到10之间的整数0表示不输出
-- @number[opt=0] gll 单位秒GLL语句输出频率取值范围0到10之间的整数0表示不输出
-- @return nil
-- @usage gps9701.setNemaReportFreq(5,0,0,0,0,0)
function setNemaReportFreq(rmc,gga,gsa,gsv,vtg,gll)
local tmpStr = "$PGKC242,"..(gll or 0)..","..(rmc or 1)..","..(vtg or 1)..","..(gga or 1)..","..(gsa or 1)..","..(gsv or 1)..",0,0,0,0,0,0,0,0,0,0,0,0,0".."*"
if tmpStr~=nmeaReportFreqStr then
nmeaReportFreqStr,nmeaReportFreqSetted = tmpStr
end
end
--- 设置GPS定位成功后经纬度的过滤时间.
-- @number[opt=0] seconds 单位秒GPS定位成功后丢弃前seconds秒的位置信息
-- @return nil
-- @usage gps9701.setLocationFilter(2)
function setLocationFilter(seconds)
filterSeconds = seconds or 0
end
function setFastFix(lat,lng,tm)
local t = tm.year..","..tm.month..","..tm.day..","..tm.hour..","..tm.min..","..tm.sec.."*"
log.info("gps.setFastFix",lat,lng,t)
-- writeCmd("$PGKC634,"..t)
writeCmd("$PGKC639,"..lat..","..lng..",0,"..t)
end
--- 获取GPS模块是否处于开启状态
-- @return bool resulttrue表示开启状态false或者nil表示关闭状态
-- @usage gps9701.isOpen()
function isOpen()
return openFlag
end
--- 获取GPS模块是否定位成功
-- @return bool resulttrue表示定位成功false或者nil表示定位失败
-- @usage gps9701.isFix()
function isFix()
return fixFlag
end
--- 获取GPS模块是否首次定位成功过
-- @return bool resulttrue表示曾经定位成功
-- @usage gps9701.isOnece()
function isOnece()
return fixOnece
end
-- 度分格式转换为度格式
-- @string inStr 度分格式的位置
-- @return string度格式的位置
-- @usage degreeMinuteToDegree("3114.50931")--->"31.2418218"31度14.50931分转换为31.2418218度
-- @usage degreeMinuteToDegree("12128.44954")--->"121.4741590"121度28.44954分转换为121.4741590度
local function degreeMinuteToDegree(inStr)
local integer,fraction = smatch(inStr,"(%d+)%.(%d+)")
if integer and fraction then
local intLen = slen(integer)
if intLen~=4 and intLen~=5 then log.error("gps.degreeMinuteToDegree integer error",inStr) return "" end
if slen(fraction)<5 then fraction = fraction..srep("0",5-slen(fraction)) end
fraction = ssub(fraction,1,5)
local temp = tonumber(ssub(integer,intLen-1,intLen)..fraction)*10
fraction = tostring((temp-(temp%6))/6)
local fracLen = slen(fraction)
if fracLen>7 then
fraction = ssub(fraction,1,7)
elseif fracLen<7 then
fraction = srep("0",7-fracLen)..fraction
end
return ssub(integer,1,intLen-2).."."..fraction
end
return ""
end
--- 获取度格式的经纬度信息
-- @string[opt=nil] typ 返回的经纬度格式typ为"DEGREE_MINUTE"时表示返回度分格式,其余表示返回度格式
-- @return table location
-- 例如typ为"DEGREE_MINUTE"时返回{lngType="E",lng="12128.44954",latType="N",lat="3114.50931"}
-- 例如typ不是"DEGREE_MINUTE"时返回{lngType="E",lng="121.123456",latType="N",lat="31.123456"}
-- lngTypestring类型表示经度类型取值"E""W"
-- lngstring类型表示度格式的经度值无效时为""
-- latTypestring类型表示纬度类型取值"N""S"
-- latstring类型表示度格式的纬度值无效时为""
-- @usage gps9701.getLocation()
function getLocation(typ)
return {
lngType=longitudeType,
lng=isFix() and (typ=="DEGREE_MINUTE" and longitude or degreeMinuteToDegree(longitude)) or "",
latType=latitudeType,
lat=isFix() and (typ=="DEGREE_MINUTE" and latitude or degreeMinuteToDegree(latitude)) or ""
}
end
function getLastLocation(typ)
if typ=="DEGREE_MINUTE" then
return {
lngType=longitudeType,
lng=longitude,
latType=latitudeType,
lat=latitude
}
else
return (longitude and longitude~="") and degreeMinuteToDegree(longitude) or "", (latitude and latitude~="") and degreeMinuteToDegree(latitude) or ""
end
end
--- 获取海拔
-- @return number altitude海拔单位米
-- @usage gps9701.getAltitude()
function getAltitude()
return tonumber(smatch(altitude,"(%d+)") or "0")
end
--- 获取速度
-- @return number kmSpeed第一个返回值为公里每小时的速度
-- @return number nmSpeed第二个返回值为海里每小时的速度
-- @usage gps9701.getSpeed()
function getSpeed()
local integer = tonumber(smatch(speed,"(%d+)") or "0")
return (integer*1852 - (integer*1852 %1000))/1000,integer
end
--- 获取原始速度,字符串带浮点
-- @return number speed 海里每小时的速度
-- @usage gps9701.getOrgSpeed()
function getOrgSpeed()
return speed
end
--- 获取方向角
-- @return number course方向角
-- @usage gps9701.getCourse()
function getCourse()
return tonumber(smatch(course,"(%d+)") or "0")
end
-- 获取所有可见卫星的最大信号强度
-- @return number strength最大信号强度
-- @usage gps9701.getMaxSignalStrength()
function getMaxSignalStrength()
return maxSignalStrength
end
--- 获取可见卫星的个数
-- @return number count可见卫星的个数
-- @usage gps9701.getViewedSateCnt()
function getViewedSateCnt()
return tonumber(viewedGpsSateCnt)+tonumber(viewedBdSateCnt)
end
--- 获取定位使用的卫星个数
-- @return number count定位使用的卫星个数
-- @usage gps9701.getUsedSateCnt()
function getUsedSateCnt()
return tonumber(usedSateCnt)
end
--- 获取GGA语句中度分格式的经纬度信息
-- @return string lng度分格式的经度值(dddmm.mmmm),西经会添加一个-前缀,无效时为"";例如"12112.3456"表示东经121度12.3456分,"-12112.3456"表示西经121度12.3456分
-- @return string lat度分格式的纬度值(ddmm.mmmm),南纬会添加一个-前缀,无效时为"";例如"3112.3456"表示北纬31度12.3456分,"-3112.3456"表示南纬31度12.3456分
-- @usage gps9701.getGgaloc()
function getGgaloc()
return Ggalng or "",Ggalat or ""
end
--- 获取RMC语句中的UTC时间
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第一个参数设置为true
-- @return table utcTimeUTC时间nil表示无效例如{year=2018,month=4,day=24,hour=11,min=52,sec=10}
-- @usage gps9701.getUtcTime()
function getUtcTime()
return UtcTime
end
--- 获取定位使用的大地高
-- @return number sep大地高
-- @usage gps9701.getSep()
function getSep()
return tonumber(Sep or "0")
end
--- 获取GSA语句中的可见卫星号
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第三个参数设置为true
-- @return string viewedSateId可用卫星号""表示无效
-- @usage gps9701.getSateSn()
function getSateSn()
return SateSn or ""
end
--- 获取GSV语句中的可见卫星的信噪比
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第二个参数设置为true
-- @return string gsv信噪比
-- @usage gps9701.getGsv()
function getGsv()
return Gsv or ""
end
--- 设置是否需要解析的字段
-- @bool[opt=nil] utcTime 是否解析RMC语句中的UTC时间true表示解析false或者nil不解析
-- @bool[opt=nil] gsv 是否解析GSV语句true表示解析false或者nil不解析
-- @bool[opt=nil] gsaId 是否解析GSA语句中的卫星IDtrue表示解析false或者nil不解析
-- @usage gps.setParseItem(true,true,true)
function setParseItem(utcTime,gsv,gsaId)
psUtcTime,psGsv,psSn = utcTime,gsv,gsaId
end
function init()
sys.subscribe("GPS_STATE",statInd)
end
function unInit()
sys.unsubscribe("GPS_STATE",statInd)
closeAll()
end
init()

927
4G/code/lib/gpsHxxt.lua Normal file
View File

@@ -0,0 +1,927 @@
--- 模块功能GPS模块管理
-- 注意:此功能模块中的功能接口可以分为四大类:
-- 1、GPS开启
-- 2、GPS关闭
-- 3、GPS定位数据读取
-- 4、GPS参数和功能设置
-- 1、2、3是通用功能除了支持合宙的Air530H模块理论上也支持其他厂家的串口GPS模块
-- 4是专用功能仅支持合宙的Air530H和Air82x系列模块
-- @module gpsHxxt
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2021.6.19
require"pm"
require"utils"
module(..., package.seeall)
local smatch,sfind,slen,ssub,sbyte,sformat,srep = string.match,string.find,string.len,string.sub,string.byte,string.format,string.rep
--GPS开启标志true表示开启状态false或者nil表示关闭状态
local openFlag
--GPS定位标志"2D"表示2D定位"3D"表示3D定位其余表示未定位
--GPS定位标志true表示其余表示未定位
local fixFlag,fixOnece=nil,nil
--GPS定位成功后过滤掉前filterSeconds秒的经纬度信息
--是否已经过滤完成
local filterSeconds,filteredFlag = 0
--从定位成功切换到定位失败,连续定位失败的次数
local fixFailCnt = 0
--经纬度类型和数据
local latitudeType,latitude,longitudeType,longitude = "N","","E",""
--海拔,速度,方向角
local altitude,speed,course = "0","0","0"
--参与定位的卫星个数,所有可见卫星的最大信号值,所有可见卫星的最大信号值中间缓存值
local usedSateCnt,maxSignalStrength,maxSignalStrengthVar = "0",0,0
--可见卫星个数
local viewedGpsSateCnt,viewedBdSateCnt = "0","0"
--可用卫星号UTC时间信噪比
local SateSn,UtcTime,Gsv
--大地高,度分经度,度分纬度
local Sep,Ggalng,Ggalat
--是否需要解析项
local psUtcTime,psGsv,psSn
--GPS供电设置函数
local powerCbFnc
--串口配置
uartBaudrate = 115200
local uartID,uartDatabits,uartParity,uartStopbits = 3,8,uart.PAR_NONE,uart.STOP_1
--搜星模式命令字符串,"$PGKC115," .. gps .. "," .. glonass .. "," .. beidou .. "," .. galieo .. "*"
local aerialModeStr,aerialModeSetted = ""
--启动模式命令字符串
local startModeStr,startModeSetted = "",""
--运行模式命令字符串,"$PGKC105," .. mode .. "," .. rt .. "," .. st .. "*"
local runModeStr,runModeSetted = "",""
--正常运行模式下NMEA数据上报间隔命令字符串"$PGKC101," .. interval .. "*"
local nmeaReportStr,nmeaReportSetted = ""
--每种NEMA数据的输出频率命令字符串
local nmeaReportFreqStr,nmeaReportFreqSetted = ""
--NMEA数据处理模式0表示仅gpsHxxt.lua内部处理1表示仅用户自己处理2表示gpsHxxt.lua和用户同时处理
--用户处理一条NMEA数据的回调函数
local nmeaMode,nmeaCbFnc = 0
--NMEA数据输出间隔
local nmeaInterval = 1000
--运行模式
--0正常运行模式
--1低功耗模式
local runMode = 0
--gps 的串口线程是否在工作;
local taskFlag=false
--runMode为1或者2时GPS运行状态和休眠状态的时长
local runTime,sleepTime
--检测gps是否工作正常的定时器ID
local workAbnormalTimerId
--[[
函数名getstrength
功能 解析GSV数据
参数
sgNEMA中的一行GSV数据
返回值:无
]]
local function getstrength(sg)
sg = ssub(sg, 4, #sg)
local d1,d2,curnum,lineno,total,sgv_str = sfind(sg,"GSV,(%d),(%d),(%d+),(.*)%*.*")
if not curnum or not lineno or not total or not sgv_str then
return
end
if lineno == nil then
maxSignalStrengthVar = 0
maxSignalStrength = 0
elseif tonumber(lineno) == 1 then
maxSignalStrength = maxSignalStrengthVar
maxSignalStrengthVar = 0
end
local tmpstr,i = sgv_str
for i=1,4 do
local d1,d2,id,elevation,azimuth,strength = sfind(tmpstr,"(%d+),([%-]*%d*),(%d*),(%d*)")
if id == nil then return end
if strength == "" or not strength then
strength = "00"
end
strength = tonumber(strength)
if strength > maxSignalStrengthVar then
maxSignalStrengthVar = strength
end
local idx,cur,fnd,tmpid = 0,id..","..elevation..","..azimuth..","..strength..",",false
for tmpid in string.gmatch(Gsv,"(%d+),%d*,%d*,%d*,") do
idx = idx + 1
if tmpid == id then fnd = true break end
end
if fnd then
local pattern,i = ""
for i=1,idx do
pattern = pattern.."%d+,%d*,%d*,%d*,"
end
local m1,m2 = sfind(Gsv,"^"..pattern)
if m1 and m2 then
local front = ssub(Gsv,1,m2)
local n1,n2 = sfind(front,"%d+,%d*,%d*,%d*,$")
if n1 and n2 then
Gsv = ssub(Gsv,1,n1-1)..cur..ssub(Gsv,n2+1,-1)
end
end
else
Gsv = Gsv..cur
end
tmpstr = ssub(tmpstr,d2+1,-1)
end
end
local function filterTimerFnc()
log.info("gpsHxxt.filterTimerFnc end")
filteredFlag = true
end
local function stopWorkAbnormalTimer()
if workAbnormalTimerId then
sys.timerStop(workAbnormalTimerId)
workAbnormalTimerId = nil
end
end
local function parseNmea(s)
if not s or s=="" then return end
local lat,lng,spd,cog,gpsFind,gpsTime,gpsDate,locSateCnt,hdp,latTyp,lngTyp,altd
local fixed,workAbnormal
if smatch(s,"GGA") then
lat,latTyp,lng,lngTyp,gpsFind,locSateCnt,hdp,altd,sep = smatch(s,"GGA,%d+%.%d+,(%d+%.%d+),([NS]),(%d+%.%d+),([EW]),(%d),(%d+),([%d%.]*),(.*),M,(.*),M")
if (gpsFind=="1" or gpsFind=="2" or gpsFind=="4") and altd then
--fixed = true
altitude = altd
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
usedSateCnt = locSateCnt
Ggalng,Ggalat = (lngTyp=="W" and "-" or "")..lng,(latTyp=="S" and "-" or "")..lat
Sep = sep
else
fixed = false
end
elseif smatch(s,"RMC") then
gpsTime,gpsFind,lat,latTyp,lng,lngTyp,spd,cog,gpsDate = smatch(s,"RMC,(%d%d%d%d%d%d)%.%d+,(%w),(%d*%.*%d*),([NS]*),(%d*%.*%d*),([EW]*),(.-),(.-),(%d%d%d%d%d%d),")
if gpsFind=="A" and cog then
fixed = true
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
speed = spd
course = cog
else
fixed = false
end
if psUtcTime and gpsFind == "A" and gpsTime and gpsDate and gpsTime ~= "" and gpsDate ~= "" then
local yy,mm,dd,h,m,s = tonumber(ssub(gpsDate,5,6)),tonumber(ssub(gpsDate,3,4)),tonumber(ssub(gpsDate,1,2)),tonumber(ssub(gpsTime,1,2)),tonumber(ssub(gpsTime,3,4)),tonumber(ssub(gpsTime,5,6))
UtcTime = {year=2000+yy,month=mm,day=dd,hour=h,min=m,sec=s}
end
elseif smatch(s,"GPGSV") then
viewedGpsSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"BDGSV") then
viewedBdSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"GSA") then
if psSn then
local satesn = smatch(s,"GSA,%w*,%d*,(%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,)") or ""
if slen(satesn) > 0 and smatch(satesn,"%d+,") then
SateSn = satesn
end
end
else
workAbnormal = true
end
if not workAbnormal then
stopWorkAbnormalTimer()
end
if filterSeconds>0 and fixed and not fixFlag and not filteredFlag then
if not sys.timerIsActive(filterTimerFnc) then
log.info("gpsHxxt.filterTimerFnc begin")
sys.publish("GPS_STATE","LOCATION_FILTER")
sys.timerStart(filterTimerFnc,filterSeconds*1000)
end
return
end
--定位成功
if fixed then
if not fixFlag then
fixFlag,filteredFlag = true,true
fixOnece=true
fixFailCnt = 0
sys.publish("GPS_STATE","LOCATION_SUCCESS")
end
elseif fixed==false then
if fixFlag then
fixFailCnt = fixFailCnt+1
if fixFailCnt>=20 then
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
sys.publish("GPS_STATE","LOCATION_FAIL")
end
end
end
end
local function taskRead()
local cacheData = ""
local co = coroutine.running()
while true do
local s =""
if openFlag then
s= uart.read(uartID, "*l")
end
if s == "" then
uart.on(uartID,"receive",function() coroutine.resume(co) end)
coroutine.yield()
uart.on(uartID,"receive")
else
cacheData = cacheData..s
local d1,d2,nemaStr = sfind(cacheData,"\r\n")
while d1 do
writePendingCmds()
nemaStr = ssub(cacheData,1,d2)
cacheData = ssub(cacheData,d2+1,-1)
if nmeaMode==0 or nmeaMode==2 then
--解析一行NEMA数据
parseNmea(nemaStr)
end
if (nmeaMode==1 or nmeaMode==2) and nmeaCbFnc then
nmeaCbFnc(nemaStr)
end
d1,d2 = sfind(cacheData,"\r\n")
end
end
end
end
function writeData(data)
uart.write(uartID,data)
log.info("gpsHxxt.writeData",data:toHex())
end
-- GPS串口写命令操作
-- @number class消息类
-- @number is消息编号
-- @string payload有效载荷
-- @return nil
-- @usage gpsHxxt.writeCmd(cmd)
function writeCasic(class,id,payload)
local tmp = pack.pack("<bbHbbA",0xBA,0xCE,payload:len(),class,id,payload)
local checkSum = bit.lshift(id,24)+bit.lshift(class,16)+payload:len()
for i=1,payload:len(),4 do
checkSum = checkSum + payload:byte(i) + bit.lshift(payload:byte(i+1),8) + bit.lshift(payload:byte(i+2),16) + bit.lshift(payload:byte(i+3),24)
end
tmp = tmp..pack.pack("<I",checkSum)
uart.write(uartID,tmp)
--log.info("gpsZkw.writeCasic",tmp)
log.info("gpsHxxt.writeCasic",tmp:toHex())
end
-- GPS串口写命令操作
-- @string cmdGPS指令(cmd格式"$PGKC149,1,115200*"或者"$PGKC149,1,115200*XX\r\n")
-- @bool isFullcmd是否为完整的指令格式包括校验和以及\r\ntrue表示完整false或者nil为不完整
-- @return nil
-- @usage gpsHxxt.writeCmd(cmd)
function writeCmd(cmd,isFull)
local tmp = cmd
if not isFull then
tmp = 0
for i=2,cmd:len()-1 do
tmp = bit.bxor(tmp,cmd:byte(i))
end
--tmp = cmd..(string.format("%02X",tmp)):upper().."\r\n"
tmp = cmd.."\r\n"
end
uart.write(uartID,tmp)
log.info("gpsHxxt.writecmd",tmp)
--log.info("gpsHxxt.writecmd:toHex",tmp:toHex())
end
function writePendingCmds()
if not aerialModeSetted and aerialModeStr~="" then writeCmd(aerialModeStr) aerialModeSetted=true end
--if not runModeSetted and runModeStr~="" then writeCmd(runModeStr) runModeSetted=true end
if not nmeaReportSetted and nmeaReportStr~="" then writeCmd(nmeaReportStr) nmeaReportSetted=true end
if not nmeaReportFreqSetted and nmeaReportFreqStr~="" then writeCmd(nmeaReportFreqStr) nmeaReportFreqSetted=true end
end
local function _open()
if openFlag then return end
pm.wake("gpsHxxt.lua")
uart.setup(uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits)
if not taskFlag then
taskFlag =true
sys.taskInit(taskRead)
end
if powerCbFnc then
powerCbFnc(true)
else
pmd.ldoset(15,pmd.LDO_VIBR)
rtos.sys32k_clk_out(1)
end
openFlag = true
workAbnormalTimerId = sys.timerStart(sys.publish,8000,"GPS_WORK_ABNORMAL_IND")
sys.publish("GPS_STATE","OPEN")
fixFlag,filteredFlag = false
Ggalng,Ggalat,Gsv,Sep = "","",""
log.info("gpsHxxt._open")
end
local function _close()
if not openFlag then return end
if powerCbFnc then
powerCbFnc(false)
else
pmd.ldoset(0,pmd.LDO_VIBR)
--rtos.sys32k_clk_out(0)
end
uart.close(uartID)
pm.sleep("gpsHxxt.lua")
openFlag = false
sys.publish("GPS_STATE","CLOSE",fixFlag)
stopWorkAbnormalTimer()
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
Ggalng,Ggalat,Gsv,Sep = "","",""
aerialModeSetted,runModeSetted,nmeaReportSetted,nmeaReportFreqSetted = nil
log.info("gpsHxxt._close")
end
--- GPS应用模式1.
--
-- 打开GPS后GPS定位成功时如果有回调函数会调用回调函数
--
-- 使用此应用模式调用gpsHxxt.open打开的“GPS应用”必须主动调用gpsHxxt.close或者gpsHxxt.closeAll才能关闭此“GPS应用”,主动关闭时,即使有回调函数,也不会调用回调函数
DEFAULT = 1
--- GPS应用模式2.
--
-- 打开GPS后如果在GPS开启最大时长到达时没有定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后如果在GPS开启最大时长内定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gpsHxxt.close或者gpsHxxt.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMERORSUC = 2
--- GPS应用模式3.
--
-- 打开GPS后在GPS开启最大时长时间到达时无论是否定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gpsHxxt.close或者gpsHxxt.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMER = 3
--“GPS应用”表
local tList = {}
--[[
函数名delItem
功能 从“GPS应用”表中删除一项“GPS应用”并不是真正的删除只是设置一个无效标志
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function delItem(mode,para)
for i=1,#tList do
--标志有效 并且 GPS应用模式相同 并且 “GPS应用”标记相同
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then
--设置无效标志
tList[i].flag,tList[i].delay = false
break
end
end
end
--[[
函数名addItem
功能 新增一项“GPS应用”到“GPS应用”表
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function addItem(mode,para)
--删除相同的“GPS应用”
delItem(mode,para)
local item,i,fnd = {flag=true, mode=mode, para=para}
--如果是TIMERORSUC或者TIMER模式初始化GPS工作剩余时间
if mode==TIMERORSUC or mode==TIMER then item.para.remain = para.val end
for i=1,#tList do
--如果存在无效的“GPS应用”项直接使用此位置
if not tList[i].flag then
tList[i] = item
fnd = true
break
end
end
--新增一项
if not fnd then table.insert(tList,item) end
end
local function existTimerItem()
for i=1,#tList do
if tList[i].flag and (tList[i].mode==TIMERORSUC or tList[i].mode==TIMER or tList[i].para.delay) then return true end
end
end
local function timerFnc()
for i=1,#tList do
if tList[i].flag then
log.info("gpsHxxt.timerFnc@"..i,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay)
local rmn,dly,md,cb = tList[i].para.remain,tList[i].para.delay,tList[i].mode,tList[i].para.cb
if rmn and rmn>0 then
tList[i].para.remain = rmn-1
end
if dly and dly>0 then
tList[i].para.delay = dly-1
end
rmn = tList[i].para.remain
if isFix() and md==TIMER and rmn==0 and not tList[i].para.delay then
tList[i].para.delay = 1
end
dly = tList[i].para.delay
if isFix() then
if dly and dly==0 then
if cb then cb(tList[i].para.tag) end
if md == DEFAULT then
tList[i].para.delay = nil
else
close(md,tList[i].para)
end
end
else
if rmn and rmn == 0 then
if cb then cb(tList[i].para.tag) end
close(md,tList[i].para)
end
end
end
end
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
--[[
函数名statInd
功能 处理GPS定位成功的消息
参数
evtGPS消息类型
返回值:无
]]
local function statInd(evt)
--定位成功的消息
if evt == "LOCATION_SUCCESS" then
for i=1,#tList do
log.info("gpsHxxt.statInd@"..i,tList[i].flag,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay,tList[i].para.cb)
if tList[i].flag then
if tList[i].mode ~= TIMER then
tList[i].para.delay = 1
if tList[i].mode == DEFAULT then
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
end
end
end
end
end
--- 打开一个“GPS应用”
-- “GPS应用”指的是使用GPS功能的一个应用
-- 例如假设有如下3种需求要打开GPS则一共有3个“GPS应用”
-- “GPS应用1”每隔1分钟打开一次GPS
-- “GPS应用2”设备发生震动时打开GPS
-- “GPS应用3”收到一条特殊短信时打开GPS
-- 只有所有“GPS应用”都关闭了才会去真正关闭GPS
-- 每个“GPS应用”打开或者关闭GPS时最多有4个参数其中 GPS应用模式和GPS应用标记 共同决定了一个唯一的“GPS应用”
-- 1、GPS应用模式(必选)
-- 2、GPS应用标记(必选)
-- 3、GPS开启最大时长[可选]
-- 4、回调函数[可选]
-- 例如gpsHxxt.open(gpsHxxt.TIMERORSUC,{tag="TEST",val=120,cb=testGpsCb})
-- gpsHxxt.TIMERORSUC为GPS应用模式"TEST"为GPS应用标记120秒为GPS开启最大时长testGpsCb为回调函数
-- @number mode GPS应用模式支持gpsHxxt.DEFAULTgpsHxxt.TIMERORSUCgpsHxxt.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gpsHxxt.TIMERORSUC或者gpsHxxt.TIMER时此值才有意义
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)
-- @return nil
-- @usage gpsHxxt.open(gpsHxxt.DEFAULT,{tag="TEST1",cb=test1Cb})
-- @usage gpsHxxt.open(gpsHxxt.TIMERORSUC,{tag="TEST2",val=60,cb=test2Cb})
-- @usage gpsHxxt.open(gpsHxxt.TIMER,{tag="TEST3",val=120,cb=test3Cb})
-- @see DEFAULT,TIMERORSUC,TIMER
function open(mode,para)
assert((para and type(para) == "table" and para.tag and type(para.tag) == "string"),"gpsHxxt.open para invalid")
log.info("gpsHxxt.open",mode,para.tag,para.val,para.cb)
--如果GPS定位成功
if isFix() then
if mode~=TIMER then
--执行回调函数
if para.cb then para.cb(para.tag) end
if mode==TIMERORSUC then return end
end
end
addItem(mode,para)
--真正去打开GPS
_open()
--启动1秒的定时器
if existTimerItem() and not sys.timerIsActive(timerFnc) then
sys.timerStart(timerFnc,1000)
end
end
--- 关闭一个“GPS应用”
-- 只是从逻辑上关闭一个GPS应用并不一定真正关闭GPS是有所有的GPS应用都处于关闭状态才会去真正关闭GPS
-- @number mode GPS应用模式支持gpsHxxt.DEFAULTgpsHxxt.TIMERORSUCgpsHxxt.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gpsHxxt.TIMERORSUC或者gpsHxxt.TIMER时此值才有意义使用close接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用close接口时不需要传入此参数
-- @return nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口关闭时mode和para.tag要和gpsHxxt.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gpsHxxt.close(gpsHxxt.DEFAULT,{tag="TEST1"})
-- @usage gpsHxxt.close(gpsHxxt.TIMERORSUC,{tag="TEST2"})
-- @usage gpsHxxt.close(gpsHxxt.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function close(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gpsHxxt.close para invalid")
log.info("gpsHxxt.close",mode,para.tag,para.val,para.cb)
--删除此“GPS应用”
delItem(mode,para)
local valid,i
for i=1,#tList do
if tList[i].flag then
valid = true
end
end
--如果没有一个“GPS应用”有效则关闭GPS
if not valid then _close() end
end
--- 关闭所有“GPS应用”
-- @return nil
-- @usage gpsHxxt.closeAll()
-- @see open,DEFAULT,TIMERORSUC,TIMER
function closeAll()
for i=1,#tList do
if tList[i].flag and tList[i].para.cb then tList[i].para.cb(tList[i].para.tag) end
close(tList[i].mode,tList[i].para)
end
end
--- 判断一个“GPS应用”是否处于激活状态
-- @number mode GPS应用模式支持gpsHxxt.DEFAULTgpsHxxt.TIMERORSUCgpsHxxt.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gpsHxxt.TIMERORSUC或者gpsHxxt.TIMER时此值才有意义使用isActive接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用isActive接口时不需要传入此参数
-- @return bool result处于激活状态返回true否则返回nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口查询状态时mode和para.tag要和gpsHxxt.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gpsHxxt.isActive(gpsHxxt.DEFAULT,{tag="TEST1"})
-- @usage gpsHxxt.isActive(gpsHxxt.TIMERORSUC,{tag="TEST2"})
-- @usage gpsHxxt.isActive(gpsHxxt.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function isActive(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gpsHxxt.isActive para invalid")
for i=1,#tList do
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then return true end
end
end
--- 设置GPS模块供电控制的回调函数
-- 如果使用的是Air800或者供电控制使用的是LDO_VCAM则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gpsHxxt.open前使用此接口传入自定义的供电控制函数cbFncGPS开启时gpsHxxt.lua自动执行cbFnc(true)GPS关闭时gpsHxxt.lua自动执行cbFnc(false)
-- @param cbFnc function类型用户自定义的GPS供电控制函数
-- @return nil
-- @usage gpsHxxt.setPowerCbFnc(cbFnc)
function setPowerCbFnc(cbFnc)
powerCbFnc = cbFnc
end
--- 设置GPS模块和GSM模块之间数据通信的串口参数
-- 如果使用的是Air800或者使用的UART2(波特率115200数据位8无检验位停止位1)则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gpsHxxt.open前使用此接口传入UART参数
-- @number id UART ID支持1和21表示UART12表示UART2
-- @number baudrate 波特率支持1200,2400,4800,9600,10400,14400,19200,28800,38400,57600,76800,115200,230400,460800,576000,921600,1152000,4000000
-- @number databits 数据位支持8
-- @number parity 校验位支持uart.PAR_NONE,uart.PAR_EVEN,uart.PAR_ODD
-- @number stopbits 停止位支持uart.STOP_1,uart.STOP_2
-- @return nil
-- @usage gpsHxxt.setUart(2,115200,8,uart.PAR_NONE,uart.STOP_1)
function setUart(id,baudrate,databits,parity,stopbits)
uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits = id,baudrate,databits,parity,stopbits
end
--- 设置GPS模块搜星模式
-- 同时仅支持配置为一种搜星模式
-- @number H01 设置为GPS L1+SBAS+QZSS联合定位模式
-- @number H10 设置为BDS B1定位
-- @number H101 设置为GPS+GLONASS+GALILEO+SBAS+QZSS联合定位
-- @number H11 设置为GPS+BDS+GALILEO+SBAS+QZSS联合定位
-- @return nil
-- @usage gpsHxxt.setAerialMode(nil,nil,1)
function setAerialMode(H01,H10,H101,H11)
local H01 = H01 or 0
local H10 = H10 or 0
local H101 = H101 or 0
local H11 = H11 or 0
if H01~=0 then rt=H01 end
if H10~=0 then rt=H10 end
if H101~=0 then rt=H101 end
if H11~=0 then rt=H11 end
if H01+H10+H101+H11 == 0 then H11=1 end
if H01+H10+H101+H11>1 then
log.warn("gpsHxxt.setAerialMode"," function input err")
return
end
local tmpStr = "$CFGSYS,"..rt
if tmpStr~=aerialModeStr then
aerialModeStr,aerialModeSetted = tmpStr
end
end
--- 设置NMEA数据处理模式.
-- 如果不调用此接口配置则默认仅gpsHxxt.lua内部处理NMEA数据
-- @number mode NMEA数据处理模式0表示仅gpsHxxt.lua内部处理1表示仅用户自己处理2表示gpsHxxt.lua和用户同时处理
-- @param cbFnc function类型用户处理一条NMEA数据的回调函数mode为1和2时此值才有意义
-- @return nil
-- @usage gpsHxxt.setNmeaMode(0)
-- @usage gpsHxxt.setNmeaMode(1,cbFnc)
-- @usage gpsHxxt.setNmeaMode(2,cbFnc)
function setNmeaMode(mode,cbFnc)
nmeaMode,nmeaCbFnc = mode,cbFnc
end
--- 设置GPS模块的运行模式.
-- 如果不调用此接口配置,则默认为正常运行模式
-- @number mode 运行模式
-- 0正常运行模式
-- 1低功耗模式
-- @return nil
-- @usage gpsHxxt.setRunMode(0)
-- @usage gpsHxxt.setRunMode(1)
function setRunMode(mode)
local tmpStr = "$CFGLOWPOWER,"..mode
if tmpStr~=runModeStr then
runModeStr,runModeSetted = tmpStr
end
end
--设置NEMA输出语句种类
-- @string date NEMA语句名称
-- @number flag 0关闭1打开
-- @usage gpaHxxt.setNemaReportFreq("GSV",1)
-- 开机后需等待250ms+后发送才有效
function setNemaReportFreq(date,flag)
local rt=""
if date=="RMC" then
writeCmd("$CFGMSG,0,4,"..flag)
elseif date=="GGA" then
writeCmd("$CFGMSG,0,0,"..flag)
elseif date=="GSA" then
writeCmd("$CFGMSG,0,2,"..flag)
elseif date=="VTG" then
writeCmd("$CFGMSG,0,5,"..flag)
elseif date=="GLL" then
writeCmd("$CFGMSG,0,1,"..flag)
elseif date=="GSV" then
writeCmd("$CFGMSG,0,3,"..flag)
end
end
--- 设置GPS定位成功后经纬度的过滤时间.
-- @number[opt=0] seconds 单位秒GPS定位成功后丢弃前seconds秒的位置信息
-- @return nil
-- @usage gpsHxxt.setLocationFilter(2)
function setLocationFilter(seconds)
filterSeconds = seconds or 0
end
function setFastFix(lat,lng,tm)
local tLocation = getLocation()
local Altitude = getAltitude()
lngType=tLocation.lngType
latType=tLocation.latType
local tm = os.date("*t")
local t = tm.year..","..tm.month..","..tm.day..","..tm.hour..","..tm.min..","..tm.sec..",000"
local APos = string.format("%s,%f,%s,%f,%s,%f","$AIDPOS",lat,latType,lng,lngType,Altitude)
log.info("gpsHxxt.setFastFix",lat,lng,t)
writeCmd("$AIDTIME,"..t)
writeCmd(APos)
end
--- 获取GPS模块是否处于开启状态
-- @return bool resulttrue表示开启状态false或者nil表示关闭状态
-- @usage gpsHxxt.isOpen()
function isOpen()
return openFlag
end
--- 获取GPS模块是否定位成功
-- @return bool resulttrue表示定位成功false或者nil表示定位失败
-- @usage gpsHxxt.isFix()
function isFix()
return fixFlag
end
function issFix()
fixFlag=false
end
--- 获取GPS模块是否首次定位成功过
-- @return bool resulttrue表示曾经定位成功
-- @usage gpsHxxt.isOnece()
function isOnece()
return fixOnece
end
-- 度分格式转换为度格式
-- @string inStr 度分格式的位置
-- @return string度格式的位置
-- @usage degreeMinuteToDegree("3114.50931")--->"31.2418218"31度14.50931分转换为31.2418218度
-- @usage degreeMinuteToDegree("12128.44954")--->"121.4741590"121度28.44954分转换为121.4741590度
local function degreeMinuteToDegree(inStr)
local integer,fraction = smatch(inStr,"(%d+)%.(%d+)")
if integer and fraction then
local intLen = slen(integer)
if intLen~=4 and intLen~=5 then log.error("gpsHxxt.degreeMinuteToDegree integer error",inStr) return "" end
if slen(fraction)<5 then fraction = fraction..srep("0",5-slen(fraction)) end
fraction = ssub(fraction,1,5)
local temp = tonumber(ssub(integer,intLen-1,intLen)..fraction)*10
fraction = tostring((temp-(temp%6))/6)
local fracLen = slen(fraction)
if fracLen>7 then
fraction = ssub(fraction,1,7)
elseif fracLen<7 then
fraction = srep("0",7-fracLen)..fraction
end
return ssub(integer,1,intLen-2).."."..fraction
end
return ""
end
--- 获取度格式的经纬度信息
-- @string[opt=nil] typ 返回的经纬度格式typ为"DEGREE_MINUTE"时表示返回度分格式,其余表示返回度格式
-- @return table location
-- 例如typ为"DEGREE_MINUTE"时返回{lngType="E",lng="12128.44954",latType="N",lat="3114.50931"}
-- 例如typ不是"DEGREE_MINUTE"时返回{lngType="E",lng="121.123456",latType="N",lat="31.123456"}
-- lngTypestring类型表示经度类型取值"E""W"
-- lngstring类型表示度格式的经度值无效时为""
-- latTypestring类型表示纬度类型取值"N""S"
-- latstring类型表示度格式的纬度值无效时为""
-- @usage gpsHxxt.getLocation()
function getLocation(typ)
return {
lngType=longitudeType,
lng=isFix() and (typ=="DEGREE_MINUTE" and longitude or degreeMinuteToDegree(longitude)) or "",
latType=latitudeType,
lat=isFix() and (typ=="DEGREE_MINUTE" and latitude or degreeMinuteToDegree(latitude)) or ""
}
end
function getLastLocation(typ)
if typ=="DEGREE_MINUTE" then
return {
lngType=longitudeType,
lng=longitude,
latType=latitudeType,
lat=latitude
}
else
return (longitude and longitude~="") and degreeMinuteToDegree(longitude) or "", (latitude and latitude~="") and degreeMinuteToDegree(latitude) or ""
end
end
--- 获取海拔
-- @return number altitude海拔单位米
-- @usage gpsHxxt.getAltitude()
function getAltitude()
return tonumber(smatch(altitude,"(%d+)") or "0")
end
--- 获取速度
-- @return number kmSpeed第一个返回值为公里每小时的速度
-- @return number nmSpeed第二个返回值为海里每小时的速度
-- @usage gpsHxxt.getSpeed()
function getSpeed()
local integer = tonumber(smatch(speed,"(%d+)") or "0")
return (integer*1852 - (integer*1852 %1000))/1000,integer
end
--- 获取原始速度,字符串带浮点
-- @return number speed 海里每小时的速度
-- @usage gpsHxxt.getOrgSpeed()
function getOrgSpeed()
return speed
end
--- 获取方向角
-- @return number course方向角
-- @usage gpsHxxt.getCourse()
function getCourse()
return tonumber(smatch(course,"(%d+)") or "0")
end
-- 获取所有可见卫星的最大信号强度
-- @return number strength最大信号强度
-- @usage gpsHxxt.getMaxSignalStrength()
function getMaxSignalStrength()
return maxSignalStrength
end
--- 获取可见卫星的个数
-- @return number count可见卫星的个数
-- @usage gpsHxxt.getViewedSateCnt()
function getViewedSateCnt()
return tonumber(viewedGpsSateCnt)+tonumber(viewedBdSateCnt)
end
--- 获取定位使用的卫星个数
-- @return number count定位使用的卫星个数
-- @usage gpsHxxt.getUsedSateCnt()
function getUsedSateCnt()
return tonumber(usedSateCnt)
end
--- 获取GGA语句中度分格式的经纬度信息
-- @return string lng度分格式的经度值(dddmm.mmmm),西经会添加一个-前缀,无效时为"";例如"12112.3456"表示东经121度12.3456分,"-12112.3456"表示西经121度12.3456分
-- @return string lat度分格式的纬度值(ddmm.mmmm),南纬会添加一个-前缀,无效时为"";例如"3112.3456"表示北纬31度12.3456分,"-3112.3456"表示南纬31度12.3456分
-- @usage gpsHxxt.getGgaloc()
function getGgaloc()
return Ggalng or "",Ggalat or ""
end
--- 获取RMC语句中的UTC时间
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第一个参数设置为true
-- @return table utcTimeUTC时间nil表示无效例如{year=2018,month=4,day=24,hour=11,min=52,sec=10}
-- @usage gpsHxxt.getUtcTime()
function getUtcTime()
return UtcTime
end
--- 获取定位使用的大地高
-- @return number sep大地高
-- @usage gpsHxxt.getSep()
function getSep()
return tonumber(Sep or "0")
end
--- 获取GSA语句中的可见卫星号
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第三个参数设置为true
-- @return string viewedSateId可用卫星号""表示无效
-- @usage gpsHxxt.getSateSn()
function getSateSn()
return SateSn or ""
end
--- 获取GSV语句中的可见卫星的信噪比
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第二个参数设置为true
-- @return string gsv信噪比
-- @usage gpsHxxt.getGsv()
function getGsv()
return Gsv or ""
end
--- 设置是否需要解析的字段
-- @bool[opt=nil] utcTime 是否解析RMC语句中的UTC时间true表示解析false或者nil不解析
-- @bool[opt=nil] gsv 是否解析GSV语句true表示解析false或者nil不解析
-- @bool[opt=nil] gsaId 是否解析GSA语句中的卫星IDtrue表示解析false或者nil不解析
-- @usage gpsHxxt.setParseItem(true,true,true)
function setParseItem(utcTime,gsv,gsaId)
psUtcTime,psGsv,psSn = utcTime,gsv,gsaId
end
function init()
sys.subscribe("GPS_STATE",statInd)
end
function unInit()
sys.unsubscribe("GPS_STATE",statInd)
closeAll()
end
init()

942
4G/code/lib/gpsZkw.lua Normal file
View File

@@ -0,0 +1,942 @@
--- 模块功能GPS模块管理
-- 注意:此功能模块中的功能接口可以分为四大类:
-- 1、GPS开启
-- 2、GPS关闭
-- 3、GPS定位数据读取
-- 4、GPS参数和功能设置
-- 1、2、3是通用功能除了支持合宙的Air530Z和Air82X系列模块理论上也支持其他厂家的串口GPS模块
-- 4是专用功能仅支持合宙的Air530Z和Air82X系列模块
-- @module gpsZkw
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.23
require"pm"
require"utils"
module(..., package.seeall)
local smatch,sfind,slen,ssub,sbyte,sformat,srep = string.match,string.find,string.len,string.sub,string.byte,string.format,string.rep
--GPS开启标志true表示开启状态false或者nil表示关闭状态
local openFlag
--GPS定位标志"2D"表示2D定位"3D"表示3D定位其余表示未定位
--GPS定位标志true表示其余表示未定位
local fixFlag,fixOnece=nil,nil
--GPS定位成功后过滤掉前filterSeconds秒的经纬度信息
--是否已经过滤完成
local filterSeconds,filteredFlag = 0
--从定位成功切换到定位失败,连续定位失败的次数
local fixFailCnt = 0
--经纬度类型和数据
local latitudeType,latitude,longitudeType,longitude = "N","","E",""
--海拔,速度,方向角
local altitude,speed,course = "0","0","0"
--参与定位的卫星个数,所有可见卫星的最大信号值,所有可见卫星的最大信号值中间缓存值
local usedSateCnt,maxSignalStrength,maxSignalStrengthVar = "0",0,0
--可见卫星个数
local viewedGpsSateCnt,viewedBdSateCnt = "0","0"
--可用卫星号UTC时间信噪比
local SateSn,UtcTime,Gsv
--大地高,度分经度,度分纬度
local Sep,Ggalng,Ggalat
--是否需要解析项
local psUtcTime,psGsv,psSn
--GPS供电设置函数
local powerCbFnc
--串口配置
uartBaudrate = 9600
local uartID,uartDatabits,uartParity,uartStopbits = 3,8,uart.PAR_NONE,uart.STOP_1
--改变GPS模块串口波特率命令字符串"$PCAS01," .. br .. "*"
local setBaudrateStr,setBaudrateSetted = ""
--搜星模式命令字符串,"$PGKC115," .. gps .. "," .. glonass .. "," .. beidou .. "," .. galieo .. "*"
local aerialModeStr,aerialModeSetted = ""
--运行模式命令字符串,"$PGKC105," .. mode .. "," .. rt .. "," .. st .. "*"
--local runModeStr,runModeSetted = ""
--正常运行模式下NMEA数据上报间隔命令字符串"$PGKC101," .. interval .. "*"
local nmeaReportStr,nmeaReportSetted = ""
--每种NEMA数据的输出频率命令字符串
local nmeaReportFreqStr,nmeaReportFreqSetted = ""
--NMEA数据处理模式0表示仅gps.lua内部处理1表示仅用户自己处理2表示gps.lua和用户同时处理
--用户处理一条NMEA数据的回调函数
local nmeaMode,nmeaCbFnc = 0
--NMEA数据输出间隔
local nmeaInterval = 1000
--运行模式
--0正常运行模式
--1周期超低功耗跟踪模式
--2周期低功耗模式
--4直接进入超低功耗跟踪模式
--8自动低功耗模式可以通过串口唤醒
--9, 自动超低功耗跟踪模式需要force on来唤醒
local runMode = 0
--gps 的串口线程是否在工作;
local taskFlag=false
--runMode为1或者2时GPS运行状态和休眠状态的时长
local runTime,sleepTime
--检测gps是否工作正常的定时器ID
local workAbnormalTimerId
--[[
函数名getstrength
功能 解析GSV数据
参数
sgNEMA中的一行GSV数据
返回值:无
]]
local function getstrength(sg)
sg = ssub(sg, 4, #sg)
local d1,d2,curnum,lineno,total,sgv_str = sfind(sg,"GSV,(%d),(%d),(%d+),(.*)%*.*")
if not curnum or not lineno or not total or not sgv_str then
return
end
if lineno == nil then
maxSignalStrengthVar = 0
maxSignalStrength = 0
elseif tonumber(lineno) == 1 then
maxSignalStrength = maxSignalStrengthVar
maxSignalStrengthVar = 0
end
local tmpstr,i = sgv_str
for i=1,4 do
local d1,d2,id,elevation,azimuth,strength = sfind(tmpstr,"(%d+),([%-]*%d*),(%d*),(%d*)")
if id == nil then return end
if strength == "" or not strength then
strength = "00"
end
strength = tonumber(strength)
if strength > maxSignalStrengthVar then
maxSignalStrengthVar = strength
end
local idx,cur,fnd,tmpid = 0,id..","..elevation..","..azimuth..","..strength..",",false
for tmpid in string.gmatch(Gsv,"(%d+),%d*,%d*,%d*,") do
idx = idx + 1
if tmpid == id then fnd = true break end
end
if fnd then
local pattern,i = ""
for i=1,idx do
pattern = pattern.."%d+,%d*,%d*,%d*,"
end
local m1,m2 = sfind(Gsv,"^"..pattern)
if m1 and m2 then
local front = ssub(Gsv,1,m2)
local n1,n2 = sfind(front,"%d+,%d*,%d*,%d*,$")
if n1 and n2 then
Gsv = ssub(Gsv,1,n1-1)..cur..ssub(Gsv,n2+1,-1)
end
end
else
Gsv = Gsv..cur
end
tmpstr = ssub(tmpstr,d2+1,-1)
end
end
local function filterTimerFnc()
log.info("gpsZkw.filterTimerFnc end")
filteredFlag = true
end
local function stopWorkAbnormalTimer()
if workAbnormalTimerId then
sys.timerStop(workAbnormalTimerId)
workAbnormalTimerId = nil
end
end
local function parseNmea(s)
if not s or s=="" then return end
local lat,lng,spd,cog,gpsFind,gpsTime,gpsDate,locSateCnt,hdp,latTyp,lngTyp,altd
local fixed,workAbnormal
if smatch(s,"GGA") then
lat,latTyp,lng,lngTyp,gpsFind,locSateCnt,hdp,altd,sep = smatch(s,"GGA,%d+%.%d+,(%d+%.%d+),([NS]),(%d+%.%d+),([EW]),(%d),(%d+),([%d%.]*),(.*),M,(.*),M")
if (gpsFind=="1" or gpsFind=="2" or gpsFind=="4") and altd then
--fixed = true
altitude = altd
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
usedSateCnt = locSateCnt
Ggalng,Ggalat = (lngTyp=="W" and "-" or "")..lng,(latTyp=="S" and "-" or "")..lat
Sep = sep
else
fixed = false
end
elseif smatch(s,"RMC") then
gpsTime,gpsFind,lat,latTyp,lng,lngTyp,spd,cog,gpsDate = smatch(s,"RMC,(%d%d%d%d%d%d)%.%d+,(%w),(%d*%.*%d*),([NS]*),(%d*%.*%d*),([EW]*),(.-),(.-),(%d%d%d%d%d%d),")
if gpsFind=="A" and cog then
fixed = true
latitudeType,longitudeType,latitude,longitude = latTyp,lngTyp,lat,lng
speed = spd
course = cog
else
fixed = false
end
if psUtcTime and gpsFind == "A" and gpsTime and gpsDate and gpsTime ~= "" and gpsDate ~= "" then
local yy,mm,dd,h,m,s = tonumber(ssub(gpsDate,5,6)),tonumber(ssub(gpsDate,3,4)),tonumber(ssub(gpsDate,1,2)),tonumber(ssub(gpsTime,1,2)),tonumber(ssub(gpsTime,3,4)),tonumber(ssub(gpsTime,5,6))
UtcTime = {year=2000+yy,month=mm,day=dd,hour=h,min=m,sec=s}
end
elseif smatch(s,"GPGSV") then
viewedGpsSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"BDGSV") then
viewedBdSateCnt = tonumber(smatch(s,"%d+,%d+,(%d+)") or "0")
if psGsv then getstrength(s) end
elseif smatch(s,"GSA") then
if psSn then
local satesn = smatch(s,"GSA,%w*,%d*,(%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,%d*,)") or ""
if slen(satesn) > 0 and smatch(satesn,"%d+,") then
SateSn = satesn
end
end
else
workAbnormal = true
end
if not workAbnormal then
stopWorkAbnormalTimer()
end
if filterSeconds>0 and fixed and not fixFlag and not filteredFlag then
if not sys.timerIsActive(filterTimerFnc) then
log.info("gpsZkw.filterTimerFnc begin")
sys.publish("GPS_STATE","LOCATION_FILTER")
sys.timerStart(filterTimerFnc,filterSeconds*1000)
end
return
end
--定位成功
if fixed then
if not fixFlag then
fixFlag,filteredFlag = true,true
fixOnece=true
fixFailCnt = 0
sys.publish("GPS_STATE","LOCATION_SUCCESS")
end
elseif fixed==false then
if fixFlag then
fixFailCnt = fixFailCnt+1
if fixFailCnt>=20 then
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
sys.publish("GPS_STATE","LOCATION_FAIL")
end
end
end
end
local function taskRead()
local cacheData = ""
local co = coroutine.running()
while true do
local s =""
if openFlag then
s= uart.read(uartID, "*l")
end
if s == "" then
uart.on(uartID,"receive",function() coroutine.resume(co) end)
coroutine.yield()
uart.on(uartID,"receive")
else
cacheData = cacheData..s
local d1,d2,nemaStr = sfind(cacheData,"\r\n")
while d1 do
writePendingCmds()
nemaStr = ssub(cacheData,1,d2)
cacheData = ssub(cacheData,d2+1,-1)
if nmeaMode==0 or nmeaMode==2 then
--解析一行NEMA数据
parseNmea(nemaStr)
end
if (nmeaMode==1 or nmeaMode==2) and nmeaCbFnc then
nmeaCbFnc(nemaStr)
end
d1,d2 = sfind(cacheData,"\r\n")
end
end
end
end
function writeData(data)
uart.write(uartID,data)
log.info("gpsZkw.writeData",data:toHex())
end
-- GPS串口写命令操作
-- @number class消息类
-- @number is消息编号
-- @string payload有效载荷
-- @return nil
-- @usage gps.writeCmd(cmd)
function writeCasic(class,id,payload)
local tmp = pack.pack("<bbHbbA",0xBA,0xCE,payload:len(),class,id,payload)
local checkSum = bit.lshift(id,24)+bit.lshift(class,16)+payload:len()
for i=1,payload:len(),4 do
checkSum = checkSum + payload:byte(i) + bit.lshift(payload:byte(i+1),8) + bit.lshift(payload:byte(i+2),16) + bit.lshift(payload:byte(i+3),24)
end
tmp = tmp..pack.pack("<I",checkSum)
uart.write(uartID,tmp)
--log.info("gpsZkw.writeCasic",tmp)
log.info("gpsZkw.writeCasic",tmp:toHex())
end
-- GPS串口写命令操作
-- @string cmdGPS指令(cmd格式"$PCAS04,7*XX\r\n")
-- @bool isFullcmd是否为完整的指令格式包括校验和以及\r\ntrue表示完整false或者nil为不完整
-- @return nil
-- @usage gps.writeCmd(cmd)
function writeCmd(cmd,isFull)
local tmp = cmd
if not isFull then
tmp = 0
for i=2,cmd:len()-1 do
tmp = bit.bxor(tmp,cmd:byte(i))
end
tmp = cmd..(string.format("%02X",tmp)):upper().."\r\n"
end
uart.write(uartID,tmp)
log.info("gpsZkw.writecmd",tmp)
--log.info("gpsZkw.writeCmd",tmp:toHex())
end
function writePendingCmds()
if not aerialModeSetted and aerialModeStr~="" then writeCmd(aerialModeStr) aerialModeSetted=true end
--if not runModeSetted and runModeStr~="" then writeCmd(runModeStr) runModeSetted=true end
if not nmeaReportSetted and nmeaReportStr~="" then writeCmd(nmeaReportStr) nmeaReportSetted=true end
if not nmeaReportFreqSetted and nmeaReportFreqStr~="" then writeCmd(nmeaReportFreqStr) nmeaReportFreqSetted=true end
if not setBaudrateSetted and setBaudrateStr~="" then writeCmd(setBaudrateStr) setBaudrateSetted=true end
end
local function _open()
if openFlag then return end
pm.wake("gps.lua")
uart.setup(uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits)
if not taskFlag then
taskFlag =true
sys.taskInit(taskRead)
end
if powerCbFnc then
powerCbFnc(true)
else
pmd.ldoset(10,pmd.LDO_VIBR)
rtos.sys32k_clk_out(1)
end
openFlag = true
workAbnormalTimerId = sys.timerStart(sys.publish,8000,"GPS_WORK_ABNORMAL_IND")
sys.publish("GPS_STATE","OPEN")
fixFlag,filteredFlag = false
Ggalng,Ggalat,Gsv,Sep = "","",""
log.info("gpsZkw._open")
end
local function _close()
if not openFlag then return end
if powerCbFnc then
powerCbFnc(false)
else
pmd.ldoset(0,pmd.LDO_VIBR)
rtos.sys32k_clk_out(0)
end
uart.close(uartID)
pm.sleep("gps.lua")
openFlag = false
sys.publish("GPS_STATE","CLOSE",fixFlag)
stopWorkAbnormalTimer()
fixFlag,filteredFlag = false
sys.timerStop(filterTimerFnc)
Ggalng,Ggalat,Gsv,Sep = "","",""
aerialModeSetted,runModeSetted,nmeaReportSetted,nmeaReportFreqSetted = nil
log.info("gpsZkw._close")
end
--- GPS应用模式1.
--
-- 打开GPS后GPS定位成功时如果有回调函数会调用回调函数
--
-- 使用此应用模式调用gpsZkw.open打开的“GPS应用”必须主动调用gpsZkw.close或者gpsZkw.closeAll才能关闭此“GPS应用”,主动关闭时,即使有回调函数,也不会调用回调函数
DEFAULT = 1
--- GPS应用模式2.
--
-- 打开GPS后如果在GPS开启最大时长到达时没有定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后如果在GPS开启最大时长内定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gpsZkw.close或者gpsZkw.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMERORSUC = 2
--- GPS应用模式3.
--
-- 打开GPS后在GPS开启最大时长时间到达时无论是否定位成功如果有回调函数会调用回调函数然后自动关闭此“GPS应用”
--
-- 打开GPS后在自动关闭此“GPS应用”前可以调用gpsZkw.close或者gpsZkw.closeAll主动关闭此“GPS应用”主动关闭时即使有回调函数也不会调用回调函数
TIMER = 3
--“GPS应用”表
local tList = {}
--[[
函数名delItem
功能 从“GPS应用”表中删除一项“GPS应用”并不是真正的删除只是设置一个无效标志
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function delItem(mode,para)
for i=1,#tList do
--标志有效 并且 GPS应用模式相同 并且 “GPS应用”标记相同
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then
--设置无效标志
tList[i].flag,tList[i].delay = false
break
end
end
end
--[[
函数名addItem
功能 新增一项“GPS应用”到“GPS应用”表
参数
modeGPS应用模式
para
para.tag“GPS应用”标记
para.valGPS开启最大时长
para.cb回调函数
返回值:无
]]
local function addItem(mode,para)
--删除相同的“GPS应用”
delItem(mode,para)
local item,i,fnd = {flag=true, mode=mode, para=para}
--如果是TIMERORSUC或者TIMER模式初始化GPS工作剩余时间
if mode==TIMERORSUC or mode==TIMER then item.para.remain = para.val end
for i=1,#tList do
--如果存在无效的“GPS应用”项直接使用此位置
if not tList[i].flag then
tList[i] = item
fnd = true
break
end
end
--新增一项
if not fnd then table.insert(tList,item) end
end
local function existTimerItem()
for i=1,#tList do
if tList[i].flag and (tList[i].mode==TIMERORSUC or tList[i].mode==TIMER or tList[i].para.delay) then return true end
end
end
local function timerFnc()
for i=1,#tList do
if tList[i].flag then
log.info("gpsZkw.timerFnc@"..i,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay)
local rmn,dly,md,cb = tList[i].para.remain,tList[i].para.delay,tList[i].mode,tList[i].para.cb
if rmn and rmn>0 then
tList[i].para.remain = rmn-1
end
if dly and dly>0 then
tList[i].para.delay = dly-1
end
rmn = tList[i].para.remain
if isFix() and md==TIMER and rmn==0 and not tList[i].para.delay then
tList[i].para.delay = 1
end
dly = tList[i].para.delay
if isFix() then
if dly and dly==0 then
if cb then cb(tList[i].para.tag) end
if md == DEFAULT then
tList[i].para.delay = nil
else
close(md,tList[i].para)
end
end
else
if rmn and rmn == 0 then
if cb then cb(tList[i].para.tag) end
close(md,tList[i].para)
end
end
end
end
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
--[[
函数名statInd
功能 处理GPS定位成功的消息
参数
evtGPS消息类型
返回值:无
]]
local function statInd(evt)
--定位成功的消息
if evt == "LOCATION_SUCCESS" then
for i=1,#tList do
log.info("gpsZkw.statInd@"..i,tList[i].flag,tList[i].mode,tList[i].para.tag,tList[i].para.val,tList[i].para.remain,tList[i].para.delay,tList[i].para.cb)
if tList[i].flag then
if tList[i].mode ~= TIMER then
tList[i].para.delay = 1
if tList[i].mode == DEFAULT then
if existTimerItem() then sys.timerStart(timerFnc,1000) end
end
end
end
end
end
end
--- 打开一个“GPS应用”
-- “GPS应用”指的是使用GPS功能的一个应用
-- 例如假设有如下3种需求要打开GPS则一共有3个“GPS应用”
-- “GPS应用1”每隔1分钟打开一次GPS
-- “GPS应用2”设备发生震动时打开GPS
-- “GPS应用3”收到一条特殊短信时打开GPS
-- 只有所有“GPS应用”都关闭了才会去真正关闭GPS
-- 每个“GPS应用”打开或者关闭GPS时最多有4个参数其中 GPS应用模式和GPS应用标记 共同决定了一个唯一的“GPS应用”
-- 1、GPS应用模式(必选)
-- 2、GPS应用标记(必选)
-- 3、GPS开启最大时长[可选]
-- 4、回调函数[可选]
-- 例如gpsZkw.open(gpsZkw.TIMERORSUC,{tag="TEST",val=120,cb=testGpsCb})
-- gpsZkw.TIMERORSUC为GPS应用模式"TEST"为GPS应用标记120秒为GPS开启最大时长testGpsCb为回调函数
-- @number mode GPS应用模式支持gpsZkw.DEFAULTgpsZkw.TIMERORSUCgpsZkw.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gpsZkw.TIMERORSUC或者gpsZkw.TIMER时此值才有意义
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)
-- @return nil
-- @usage gpsZkw.open(gpsZkw.DEFAULT,{tag="TEST1",cb=test1Cb})
-- @usage gpsZkw.open(gpsZkw.TIMERORSUC,{tag="TEST2",val=60,cb=test2Cb})
-- @usage gpsZkw.open(gpsZkw.TIMER,{tag="TEST3",val=120,cb=test3Cb})
-- @see DEFAULT,TIMERORSUC,TIMER
function open(mode,para)
assert((para and type(para) == "table" and para.tag and type(para.tag) == "string"),"gps.open para invalid")
log.info("gpsZkw.open",mode,para.tag,para.val,para.cb)
--如果GPS定位成功
if isFix() then
if mode~=TIMER then
--执行回调函数
if para.cb then para.cb(para.tag) end
if mode==TIMERORSUC then return end
end
end
addItem(mode,para)
--真正去打开GPS
_open()
--启动1秒的定时器
if existTimerItem() and not sys.timerIsActive(timerFnc) then
sys.timerStart(timerFnc,1000)
end
end
--- 关闭一个“GPS应用”
-- 只是从逻辑上关闭一个GPS应用并不一定真正关闭GPS是有所有的GPS应用都处于关闭状态才会去真正关闭GPS
-- @number mode GPS应用模式支持gps.DEFAULTgps.TIMERORSUCgps.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gps.TIMERORSUC或者gps.TIMER时此值才有意义使用close接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用close接口时不需要传入此参数
-- @return nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口关闭时mode和para.tag要和gpsZkw.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gpsZkw.close(gpsZkw.DEFAULT,{tag="TEST1"})
-- @usage gpsZkw.close(gpsZkw.TIMERORSUC,{tag="TEST2"})
-- @usage gpsZkw.close(gpsZkw.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function close(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.close para invalid")
log.info("gpsZkw.close",mode,para.tag,para.val,para.cb)
--删除此“GPS应用”
delItem(mode,para)
local valid,i
for i=1,#tList do
if tList[i].flag then
valid = true
end
end
--如果没有一个“GPS应用”有效则关闭GPS
if not valid then _close() end
end
--- 关闭所有“GPS应用”
-- @return nil
-- @usage gpsZkw.closeAll()
-- @see open,DEFAULT,TIMERORSUC,TIMER
function closeAll()
for i=1,#tList do
if tList[i].flag and tList[i].para.cb then tList[i].para.cb(tList[i].para.tag) end
close(tList[i].mode,tList[i].para)
end
end
--- 判断一个“GPS应用”是否处于激活状态
-- @number mode GPS应用模式支持gps.DEFAULTgps.TIMERORSUCgps.TIMER三种
-- @param para table类型GPS应用参数
-- para.tagstring类型GPS应用标记
-- para.valnumber类型GPS应用开启最大时长mode参数为gpsZkw.TIMERORSUC或者gpsZkw.TIMER时此值才有意义使用isActive接口时不需要传入此参数
-- para.cbGPS应用结束时的回调函数回调函数的调用形式为para.cb(para.tag)使用isActive接口时不需要传入此参数
-- @return bool result处于激活状态返回true否则返回nil
-- @usage GPS应用模式和GPS应用标记唯一确定一个“GPS应用”调用本接口查询状态时mode和para.tag要和gpsZkw.open打开一个“GPS应用”时传入的mode和para.tag保持一致
-- @usage gpsZkw.isActive(gpsZkw.DEFAULT,{tag="TEST1"})
-- @usage gpsZkw.isActive(gpsZkw.TIMERORSUC,{tag="TEST2"})
-- @usage gpsZkw.isActive(gpsZkw.TIMER,{tag="TEST3"})
-- @see open,DEFAULT,TIMERORSUC,TIMER
function isActive(mode,para)
assert((para and type(para)=="table" and para.tag and type(para.tag)=="string"),"gps.isActive para invalid")
for i=1,#tList do
if tList[i].flag and tList[i].mode==mode and tList[i].para.tag==para.tag then return true end
end
end
--- 设置GPS模块供电控制的回调函数
-- 如果使用的是Air800或者供电控制使用的是LDO_VCAM则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gpsZkw.open前使用此接口传入自定义的供电控制函数cbFncGPS开启时gpsZkw.lua自动执行cbFnc(true)GPS关闭时gpsZkw.lua自动执行cbFnc(false)
-- @param cbFnc function类型用户自定义的GPS供电控制函数
-- @return nil
-- @usage gpsZkw.setPowerCbFnc(cbFnc)
function setPowerCbFnc(cbFnc)
powerCbFnc = cbFnc
end
--- 设置GPS模块和GSM模块之间数据通信的串口参数
-- 如果使用的是Air800或者使用的UART2(波特率115200数据位8无检验位停止位1)则打开GPS应用前不需要调用此接口进行设置
-- 否则在调用gpsZkw.open前使用此接口传入UART参数
-- @number id UART ID支持1和21表示UART12表示UART2
-- @number baudrate 波特率支持1200,2400,4800,9600,10400,14400,19200,28800,38400,57600,76800,115200,230400,460800,576000,921600,1152000,4000000
-- @number databits 数据位支持7,8
-- @number parity 校验位支持uart.PAR_NONE,uart.PAR_EVEN,uart.PAR_ODD
-- @number stopbits 停止位支持uart.STOP_1,uart.STOP_2
-- @return nil
-- @usage gpsZkw.setUart(2,115200,8,uart.PAR_NONE,uart.STOP_1)
function setUart(id,baudrate,databits,parity,stopbits)
uartID,uartBaudrate,uartDatabits,uartParity,uartStopbits = id,baudrate,databits,parity,stopbits
end
--- 设置GPS模块搜星模式.
-- 如果使用的是Air820不调用此接口配置则默认同时开启GPS、北斗和glonass定位
-- @number gps GPS定位系统1是打开0是关闭
-- @number beidou 中国北斗定位系统1是打开0是关闭
-- @number glonass 俄罗斯Glonass定位系统1是打开0是关闭
-- @return nil
-- @usage gpsZkw.setAeriaMode(1,1,0)
function setAerialMode(gps,beidou,glonass)
local gps = gps or 0
local beidou = (beidou or 0)*2
local glonass = (glonass or 0)*4
local tmpStr = "$PCAS04,"..(gps+beidou+glonass).."*"
if tmpStr~=aerialModeStr then
aerialModeStr,aerialModeSetted = tmpStr
end
end
-- 改变GPS模块串口波特率操作
-- @string cmdGPS指令(cmd格式$PCAS01,br*CS<CR><LF>)
-- @bumber br 波特率配置 0=4800bps 1=9600bps 2=19200bps 3=38400bps 4=57600bps 5=115200bps
-- @return nil
-- @usage gpsZkw.setBaudrate(br)
function setBaudrate(br)
local br = br or 0
local tmpStr = "$PCAS01,"..br.."*"
if tmpStr~=setBaudrateStr then
setBaudrateStr,setBaudrateSetted = tmpStr
end
end
--- 设置NMEA数据处理模式.
-- 如果不调用此接口配置则默认仅gpsZkw.lua内部处理NMEA数据
-- @number mode NMEA数据处理模式0表示仅gpsZkw.lua内部处理1表示仅用户自己处理2表示gpsZkw.lua和用户同时处理
-- @param cbFnc function类型用户处理一条NMEA数据的回调函数mode为1和2时此值才有意义
-- @return nil
-- @usage gpsZkw.setNmeaMode(0)
-- @usage gpsZkw.setNmeaMode(1,cbFnc)
-- @usage gpsZkw.setNmeaMode(2,cbFnc)
function setNmeaMode(mode,cbFnc)
nmeaMode,nmeaCbFnc = mode,cbFnc
end
--[[
-- 设置GPS模块的运行模式.
-- 如果不调用此接口配置,则默认为正常运行模式
-- @number mode运行模式
-- 0正常运行模式
-- 1周期超低功耗跟踪模式
-- 2周期低功耗模式
-- 4直接进入超低功耗跟踪模式
-- 8自动低功耗模式可以通过串口唤醒
-- 9自动超低功耗跟踪模式需要force on来唤醒
-- @number runTm单位毫秒mode为0时表示NEMA数据的上报间隔mode为1或者2时表示运行时长其余mode时此值无意义
-- @number sleepTm单位毫秒mode为1或者2时表示运行时长其余mode时此值无意义
-- @return nil
-- @usage gps.setRunMode(0,1000)
-- @usage gps.setRunMode(1,5000,2000)
function setRunMode(mode,runTm,sleepTm)
local rt,st = runTm or "",sleepTm or ""
if mode==0 and rt then
if rt>10000 then rt=10000 end
if rt<200 then rt=200 end
nmeaReportStr = "$PGKC101,"..rt.."*"
end
local tmpStr = "$PGKC105,"..mode..((mode==1 or mode==2) and (","..rt..","..st) or "").."*"
if tmpStr~=runModeStr then
runModeStr,runModeSetted = tmpStr
end
end
]]
--- 设置NEMA语句的输出频率.
-- @number[opt=1] rmc 单位秒RMC语句输出频率取值范围0到9之间的整数0表示不输出
-- @number[opt=1] gga 单位秒GGA语句输出频率取值范围0到9之间的整数0表示不输出
-- @number[opt=1] gsa 单位秒GSA语句输出频率取值范围0到9之间的整数0表示不输出
-- @number[opt=1] gsv 单位秒GSV语句输出频率取值范围0到9之间的整数0表示不输出
-- @number[opt=0] vtg 单位秒VTG语句输出频率取值范围0到9之间的整数0表示不输出
-- @number[opt=0] gll 单位秒GLL语句输出频率取值范围0到9之间的整数0表示不输出
-- @return nil
-- @usage gpsZkw.setNemaReportFreq(5,0,0,0,0,0)
function setNemaReportFreq(rmc,gga,gsa,gsv,vtg,gll)
tmpStr = "$PCAS03,,,,,,,,,,,1*"
if tmpStr~=nmeaReportFreqStr then
nmeaReportFreqStr,nmeaReportFreqSetted = tmpStr
end
end
--- 设置GPS定位成功后经纬度的过滤时间.
-- @number[opt=0] seconds 单位秒GPS定位成功后丢弃前seconds秒的位置信息
-- @return nil
-- @usage gpsZkw.setLocationFilter(2)
function setLocationFilter(seconds)
filterSeconds = seconds or 0
end
function setFastFix(lat,lng,tm)
local t = tm.year..","..tm.month..","..tm.day..","..tm.hour..","..tm.min..","..tm.sec.."*"
log.info("gpsZkw.setFastFix",lat,lng,t)
log.info("gpsZkw.setFastFix",lat,lng, json.encode(tm))
if zkw530z and zkw530z.settime and zkw530z.setlocation and zkw530z.getaidini ~= nil then
if lng~=nil and lat~=nil and lng~="" and lat ~="" then
zkw530z.settime(tm.year,tm.month,tm.day,tm.hour,tm.min,tm.sec)
zkw530z.setlocation(lat,lng)--参数:lat,lon
local tmpbuf = zkw530z.getaidini()
writeData(tmpbuf)
log.info("gpsZkw.writecmd",tmpbuf:toHex())
end
else
log.info("zkw530z.settime is not supported!")
end
end
--- 获取GPS模块是否处于开启状态
-- @return bool resulttrue表示开启状态false或者nil表示关闭状态
-- @usage gpsZkw.isOpen()
function isOpen()
return openFlag
end
--- 获取GPS模块是否定位成功
-- @return bool resulttrue表示定位成功false或者nil表示定位失败
-- @usage gpsZkw.isFix()
function isFix()
return fixFlag
end
--- 获取GPS模块是否首次定位成功过
-- @return bool resulttrue表示曾经定位成功
-- @usage gpsZkw.isOnece()
function isOnece()
return fixOnece
end
-- 度分格式转换为度格式
-- @string inStr 度分格式的位置
-- @return string度格式的位置
-- @usage degreeMinuteToDegree("3114.50931")--->"31.2418218"31度14.50931分转换为31.2418218度
-- @usage degreeMinuteToDegree("12128.44954")--->"121.4741590"121度28.44954分转换为121.4741590度
local function degreeMinuteToDegree(inStr)
local integer,fraction = smatch(inStr,"(%d+)%.(%d+)")
if integer and fraction then
local intLen = slen(integer)
if intLen~=4 and intLen~=5 then log.error("gps.degreeMinuteToDegree integer error",inStr) return "" end
if slen(fraction)<5 then fraction = fraction..srep("0",5-slen(fraction)) end
fraction = ssub(fraction,1,5)
local temp = tonumber(ssub(integer,intLen-1,intLen)..fraction)*10
fraction = tostring((temp-(temp%6))/6)
local fracLen = slen(fraction)
if fracLen>7 then
fraction = ssub(fraction,1,7)
elseif fracLen<7 then
fraction = srep("0",7-fracLen)..fraction
end
return ssub(integer,1,intLen-2).."."..fraction
end
return ""
end
--- 获取度格式的经纬度信息
-- @string[opt=nil] typ 返回的经纬度格式typ为"DEGREE_MINUTE"时表示返回度分格式,其余表示返回度格式
-- @return table location
-- 例如typ为"DEGREE_MINUTE"时返回{lngType="E",lng="12128.44954",latType="N",lat="3114.50931"}
-- 例如typ不是"DEGREE_MINUTE"时返回{lngType="E",lng="121.123456",latType="N",lat="31.123456"}
-- lngTypestring类型表示经度类型取值"E""W"
-- lngstring类型表示度格式的经度值无效时为""
-- latTypestring类型表示纬度类型取值"N""S"
-- latstring类型表示度格式的纬度值无效时为""
-- @usage gpsZkw.getLocation()
function getLocation(typ)
return {
lngType=longitudeType,
lng=isFix() and (typ=="DEGREE_MINUTE" and longitude or degreeMinuteToDegree(longitude)) or "",
latType=latitudeType,
lat=isFix() and (typ=="DEGREE_MINUTE" and latitude or degreeMinuteToDegree(latitude)) or ""
}
end
function getLastLocation(typ)
if typ=="DEGREE_MINUTE" then
return {
lngType=longitudeType,
lng=longitude,
latType=latitudeType,
lat=latitude
}
else
return (longitude and longitude~="") and degreeMinuteToDegree(longitude) or "", (latitude and latitude~="") and degreeMinuteToDegree(latitude) or ""
end
end
--- 获取海拔
-- @return number altitude海拔单位米
-- @usage gpsZkw.getAltitude()
function getAltitude()
return tonumber(smatch(altitude,"(%d+)") or "0")
end
--- 获取速度
-- @return number kmSpeed第一个返回值为公里每小时的速度
-- @return number nmSpeed第二个返回值为海里每小时的速度
-- @usage gpsZkw.getSpeed()
function getSpeed()
local integer = tonumber(smatch(speed,"(%d+)") or "0")
return (integer*1852 - (integer*1852 %1000))/1000,integer
end
--- 获取原始速度,字符串带浮点
-- @return number speed 海里每小时的速度
-- @usage gpsZkw.getOrgSpeed()
function getOrgSpeed()
return speed
end
--- 获取方向角
-- @return number course方向角
-- @usage gpsZkw.getCourse()
function getCourse()
return tonumber(smatch(course,"(%d+)") or "0")
end
-- 获取所有可见卫星的最大信号强度
-- @return number strength最大信号强度
-- @usage gpsZkw.getMaxSignalStrength()
function getMaxSignalStrength()
return maxSignalStrength
end
--- 获取可见卫星的个数
-- @return number count可见卫星的个数
-- @usage gpsZkw.getViewedSateCnt()
function getViewedSateCnt()
return tonumber(viewedGpsSateCnt)+tonumber(viewedBdSateCnt)
end
--- 获取定位使用的卫星个数
-- @return number count定位使用的卫星个数
-- @usage gpsZkw.getUsedSateCnt()
function getUsedSateCnt()
return tonumber(usedSateCnt)
end
--- 获取GGA语句中度分格式的经纬度信息
-- @return string lng度分格式的经度值(dddmm.mmmm),西经会添加一个-前缀,无效时为"";例如"12112.3456"表示东经121度12.3456分,"-12112.3456"表示西经121度12.3456分
-- @return string lat度分格式的纬度值(ddmm.mmmm),南纬会添加一个-前缀,无效时为"";例如"3112.3456"表示北纬31度12.3456分,"-3112.3456"表示南纬31度12.3456分
-- @usage gpsZkw.getGgaloc()
function getGgaloc()
return Ggalng or "",Ggalat or ""
end
--- 获取RMC语句中的UTC时间
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第一个参数设置为true
-- @return table utcTimeUTC时间nil表示无效例如{year=2018,month=4,day=24,hour=11,min=52,sec=10}
-- @usage gpsZkw.getUtcTime()
function getUtcTime()
return UtcTime
end
--- 获取定位使用的大地高
-- @return number sep大地高
-- @usage gpsZkw.getSep()
function getSep()
return tonumber(Sep or "0")
end
--- 获取GSA语句中的可见卫星号
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第三个参数设置为true
-- @return string viewedSateId可用卫星号""表示无效
-- @usage gpsZkw.getSateSn()
function getSateSn()
return SateSn or ""
end
--- 获取GSV语句中的可见卫星的信噪比
-- 只有同时满足如下两个条件,返回值才有效
-- 1、开启了GPS并且定位成功
-- 2、调用setParseItem接口第二个参数设置为true
-- @return string gsv信噪比
-- @usage gpsZkw.getGsv()
function getGsv()
return Gsv or ""
end
--- 设置是否需要解析的字段
-- @bool[opt=nil] utcTime 是否解析RMC语句中的UTC时间true表示解析false或者nil不解析
-- @bool[opt=nil] gsv 是否解析GSV语句true表示解析false或者nil不解析
-- @bool[opt=nil] gsaId 是否解析GSA语句中的卫星IDtrue表示解析false或者nil不解析
-- @usage gpsZkw.setParseItem(true,true,true)
function setParseItem(utcTime,gsv,gsaId)
psUtcTime,psGsv,psSn = utcTime,gsv,gsaId
end
function init()
sys.subscribe("GPS_STATE",statInd)
end
function unInit()
sys.unsubscribe("GPS_STATE",statInd)
closeAll()
end
init()

310
4G/code/lib/http.lua Normal file
View File

@@ -0,0 +1,310 @@
--- 模块功能HTTP客户端
-- @module http
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.23
require"socket"
require"utils"
module(..., package.seeall)
local function response(client,cbFnc,result,prompt,head,body)
if not result then log.error("http.response",result,prompt) end
if cbFnc then cbFnc(result,prompt,head,body) end
if client then client:close() end
end
local function receive(client,timeout,cbFnc,result,prompt,head,body)
local res,data = client:recv(timeout)
if not res then
response(client,cbFnc,result,prompt or "receive timeout",head,body)
end
return res,data
end
local function getFileBase64Len(s)
if s then return (io.fileSize(s)+2)/3*4 end
end
local function taskClient(method,protocal,auth,host,port,path,cert,head,body,timeout,cbFnc,rcvFilePath,tCoreExtPara)
log.info("http path",path)
while not socket.isReady() do
if not sys.waitUntil("IP_READY_IND",timeout) then return response(nil,cbFnc,false,"network not ready") end
end
--计算body长度
local bodyLen = 0
if body then
if type(body)=="string" then
bodyLen = body:len()
elseif type(body)=="table" then
for i=1,#body do
bodyLen = bodyLen + (type(body[i])=="string" and string.len(body[i]) or getFileBase64Len(body[i].file_base64) or io.fileSize(body[i].file))
end
end
end
--重构head
local heads = head or {}
if not heads.Host then heads["Host"] = (port ~= 80 and port ~= 443) and (host..":"..port) or host end
if not heads.Connection then heads["Connection"] = "short" end
if bodyLen>0 and bodyLen~=tonumber(heads["Content-Length"] or "0") then heads["Content-Length"] = bodyLen end
if auth~="" and not heads.Authorization then heads["Authorization"] = ("Basic "..crypto.base64_encode(auth,#auth)) end
local headStr = ""
for k,v in pairs(heads) do
headStr = headStr..k..": "..v.."\r\n"
end
headStr = headStr.."\r\n"
local client = socket.tcp(protocal=="https",cert,tCoreExtPara)
if not client then return response(nil,cbFnc,false,"create socket error") end
if not client:connect(host,port,timeout/1000) then
return response(client,cbFnc,false,"connect fail")
end
--发送请求行+请求头+string类型的body
if not client:send(method.." "..path.." HTTP/1.1".."\r\n"..headStr..(type(body)=="string" and body or "")) then
return response(client,cbFnc,false,"send head fail")
end
--发送table类型的body
if type(body)=="table" then
for i=1,#body do
if type(body[i])=="string" then
if not client:send(body[i]) then
return response(client,cbFnc,false,"send body fail")
end
else
local file = io.open(body[i].file or body[i].file_base64,"rb")
if file then
while true do
local dat = file:read(body[i].file and 11200 or 8400)
if not dat then
io.close(file)
break
end
if body[i].file_base64 then dat=crypto.base64_encode(dat,#dat) end
if not client:send(dat) then
io.close(file)
return response(client,cbFnc,false,"send file fail")
end
end
else
return response(client,cbFnc,false,"send file open fail")
end
end
end
end
local rcvCache,rspHead,rspBody,d1,d2,result,data,statusCode,rcvChunked,contentLen = "",{},{}
--接收数据,解析状态行和头
while true do
result,data = receive(client,timeout,cbFnc,false,nil,rspHead,rcvFilePath or table.concat(rspBody))
if not result then return end
rcvCache = rcvCache..data
d1,d2 = rcvCache:find("\r\n\r\n")
if d2 then
--状态行
_,d1,statusCode = rcvCache:find("%s(%d+)%s.-\r\n")
if not statusCode then
return response(client,cbFnc,false,"parse received status error",rspHead,rcvFilePath or table.concat(rspBody))
end
--应答头
for k,v in string.gmatch(rcvCache:sub(d1+1,d2-2),"(.-):%s*(.-)\r\n") do
rspHead[k] = v
if (string.upper(k)==string.upper("Transfer-Encoding")) and (string.upper(v)==string.upper("chunked")) then rcvChunked = true end
end
if not rcvChunked then
contentLen = tonumber(rspHead["Content-Length"] or "2147483647")
end
if method == "HEAD" then
contentLen = 0
end
--未处理的body数据
rcvCache = rcvCache:sub(d2+1,-1)
break
end
end
--解析body
if rcvChunked then
local chunkSize
--循环处理每个chunk
while true do
--解析chunk size
if not chunkSize then
d1,d2,chunkSize = rcvCache:find("(%x+)\r\n")
if chunkSize then
chunkSize = tonumber(chunkSize,16)
rcvCache = rcvCache:sub(d2+1,-1)
else
result,data = receive(client,timeout,cbFnc,false,nil,rspHead,rcvFilePath or table.concat(rspBody))
if not result then return end
rcvCache = rcvCache..data
end
end
--log.info("http.taskClient chunkSize",chunkSize)
--解析chunk data
if chunkSize then
if rcvCache:len()<chunkSize+2 then
result,data = receive(client,timeout,cbFnc,false,nil,rspHead,rcvFilePath or table.concat(rspBody))
if not result then return end
rcvCache = rcvCache..data
else
if chunkSize>0 then
local chunkData = rcvCache:sub(1,chunkSize)
--保存到文件中
if type(rcvFilePath)=="string" then
local file = io.open(rcvFilePath,"a+")
if not file then return response(client,cbFnc,false,"receive: open file error",rspHead,rcvFilePath or table.concat(rspBody)) end
if not file:write(chunkData) then response(client,cbFnc,false,"receive: write file error",rspHead,rcvFilePath or table.concat(rspBody)) end
file:close()
elseif type(rcvFilePath)=="function" then --保存到缓冲区中
local userResult = rcvFilePath(data,rspHead["Content-Range"] and tonumber((rspHead["Content-Range"]):match("/(%d+)")) or contentLen,statusCode)
if userResult~=nil then
return response(client,cbFnc,userResult,userResult and statusCode or "receive: user process error",rspHead)
end
else
table.insert(rspBody,chunkData)
end
rcvCache = rcvCache:sub(chunkSize+3,-1)
chunkSize = nil
elseif chunkSize==0 then
return response(client,cbFnc,true,statusCode,rspHead,rcvFilePath or table.concat(rspBody))
end
end
end
end
else
local rmnLen = contentLen
while true do
data = rcvCache:len()<=rmnLen and rcvCache or rcvCache:sub(1,rmnLen)
if type(rcvFilePath)=="string" then
if data:len()>0 then
local file = io.open(rcvFilePath,"a+")
if not file then return response(client,cbFnc,false,"receive: open file error",rspHead,rcvFilePath or table.concat(rspBody)) end
if not file:write(data) then response(client,cbFnc,false,"receive: write file error",rspHead,rcvFilePath or table.concat(rspBody)) end
file:close()
end
elseif type(rcvFilePath)=="function" then
local userResult = rcvFilePath(data,rspHead["Content-Range"] and tonumber((rspHead["Content-Range"]):match("/(%d+)")) or contentLen,statusCode)
if userResult~=nil then
return response(client,cbFnc,userResult,userResult and statusCode or "receive: user process error",rspHead)
end
else
table.insert(rspBody,data)
end
rmnLen = rmnLen-data:len()
if rmnLen==0 then break end
result,rcvCache = receive(client,timeout,cbFnc,contentLen==0x7FFFFFFF,contentLen==0x7FFFFFFF and statusCode or nil,rspHead,rcvFilePath or table.concat(rspBody))
if not result then return end
end
return response(client,cbFnc,true,statusCode,rspHead,rcvFilePath or table.concat(rspBody))
end
end
--- 发送HTTP请求
-- @string method HTTP请求方法
-- 支持"GET""HEAD""POST""OPTIONS""PUT""DELETE""TRACE""CONNECT"
-- @string url HTTP请求url
-- url格式(除hostname外其余字段可选目前的实现不支持hash),url中如果包含UTF8编码中文则需要调用string.rawurlEncode转换成RFC3986编码。
-- |------------------------------------------------------------------------------|
-- | protocol ||| auth | host | path | hash |
-- |----------|||-----------|-----------------|---------------------------|-------|
-- | ||| | hostname | port | pathname | search | |
-- | ||| |----------|------|----------|----------------| |
-- " http[s] :// user:pass @ host.com : 8080 /p/a/t/h ? query=string # hash "
-- | ||| | | | | | |
-- |------------------------------------------------------------------------------|
-- @table[opt=nil] cert table或者nil类型ssl证书当url为https类型时此参数才有意义。cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @table[opt=nil] head nil或者table类型自定义请求头
-- http.lua会自动添加Host: XXX、Connection: short、Content-Length: XXX三个请求头
-- 如果这三个请求头满足不了需求head参数传入自定义请求头如果自定义请求头中存在Host、Connection、Content-Length三个请求头将覆盖http.lua中自动添加的同名请求头
-- head格式如下
-- 如果没有自定义请求头传入nil或者{};否则传入{head1="value1", head2="value2", head3="value3"}value中不能有\r\n
-- @param[opt=nil] body nil、string或者table类型请求实体
-- 如果body仅仅是一串数据可以直接传入一个string类型的body即可
--
-- 如果body的数据比较复杂包括字符串数据和文件则传入table类型的数据格式如下
-- {
-- [1] = "string1",
-- [2] = {file="/ldata/test.jpg"},
-- [3] = "string2"
-- }
-- 例如上面的这个body索引必须为连续的数字(从1开始),实际传输时,先发送字符串"string1",再发送文件/ldata/test.jpg的内容最后发送字符串"string2"
--
-- 如果传输的文件内容需要进行base64编码再上传请把file改成file_base64格式如下
-- {
-- [1] = "string1",
-- [2] = {file_base64="/ldata/test.jpg"},
-- [3] = "string2"
-- }
-- 例如上面的这个body索引必须为连续的数字(从1开始),实际传输时,先发送字符串"string1",再发送文件/ldata/test.jpg经过base64编码后的内容最后发送字符串"string2"
-- @number[opt=30000] timeout http请求应答整个过程中每个子过程的超时时间单位毫秒默认为30秒子过程包括如下两种
-- 1、pdp数据网络激活的超时时间
-- 2、http请求发送成功后分段接收服务器的应答数据每段数据接收的超时时间
-- @function[opt=nil] cbFnc 执行HTTP请求的回调函数(请求发送结果以及应答数据接收结果都通过此函数通知用户),回调函数的调用形式为:
-- cbFnc(result,prompt,head,body)
-- resulttrue或者falsetrue表示成功收到了服务器的应答false表示请求发送失败或者接收服务器应答失败
-- promptstring类型result为true时表示服务器的应答码result为false时表示错误信息
-- headtable或者nil类型表示服务器的应答头result为true时此参数为{head1="value1", head2="value2", head3="value3"}value中不包含\r\nresult为false时此参数为nil
-- bodystring类型如果调用request接口时传入了rcvFileName此参数表示下载文件的完整路径否则表示接收到的应答实体数据
-- @string[opt=nil] rcvFileName string类型时保存“服务器应答实体数据”的文件名可以传入完整的文件路径也可以传入单独的文件名如果是文件名http.lua会自动生成一个完整路径通过cbFnc的参数body传出
-- function类型时rcvFileName(stepData,totalLen,statusCode)
-- stepData: 本次服务器应答实体数据
-- totalLen: 实体数据的总长度
-- statusCode服务器的应答码
-- @table[opt=nil] tCoreExtPara table类型{rcvBufferSize=0}修改缓冲空间大小,解决窗口满连接超时问题,单位:字节
-- @return string rcvFilePath如果传入了rcvFileName则返回对应的完整路径其余情况都返回nil
-- @usage
-- http.request("GET","www.lua.org",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://www.lua.org",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://www.lua.org:80",nil,nil,nil,30000,cbFnc,"download.bin")
-- http.request("GET","www.lua.org/about.html",nil,nil,nil,30000,cbFnc)
-- http.request("GET","www.lua.org:80/about.html",nil,nil,nil,30000,cbFnc)
-- http.request("GET","http://wiki.openluat.com/search.html?q=123",nil,nil,nil,30000,cbFnc)
-- http.request("POST","www.test.com/report.html",nil,{Head1="ValueData1"},"BodyData",30000,cbFnc)
-- http.request("POST","www.test.com/report.html",nil,{Head1="ValueData1",Head2="ValueData2"},{[1]="string1",[2] ={file="/ldata/test.jpg"},[3]="string2"},30000,cbFnc)
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt"})
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt",clientCert = "client.crt",clientKey = "client.key"})
-- http.request("GET","https://www.baidu.com",{caCert="ca.crt",clientCert = "client.crt",clientKey = "client.key",clientPassword = "123456"})
function request(method,url,cert,head,body,timeout,cbFnc,rcvFileName,tCoreExtPara)
local protocal,auth,hostName,port,path,d1,d2,offset,rcvFilePath
d1,d2,protocal = url:find("^(%a+)://")
if not protocal then protocal = "http" end
offset = d2 or 0
d1,d2,auth = url:find("(.-:.-)@",offset+1)
offset = d2 or offset
if url:match("^[^/]+:(%d+)",offset+1) then
d1,d2,hostName,port = url:find("^([^/]+):(%d+)",offset+1)
else
d1,d2,hostName = url:find("(.-)/",offset+1)
if hostName then
d2 = d2-1
else
hostName = url:sub(offset+1,-1)
offset = url:len()
end
end
if not hostName then return response(nil,cbFnc,false,"Invalid url, can't get host") end
if port=="" or not port then port = (protocal=="https" and 443 or 80) end
offset = d2 or offset
path = url:sub(offset+1,-1)
sys.taskInit(taskClient,method,protocal,auth or "",hostName,port,path=="" and "/" or path,cert,head,body or "",timeout or 30000,cbFnc,rcvFileName,tCoreExtPara)
if type(rcvFileName) == "string" then
return rcvFileName
end
end

181
4G/code/lib/lbsLoc.lua Normal file
View File

@@ -0,0 +1,181 @@
--- 模块功能:根据基站信息查询经纬度
-- @module lbsLoc
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.25
require"socket"
require"utils"
require"common"
require"misc"
module(..., package.seeall)
local function enCellInfo(s)
local ret,t,mcc,mnc,lac,ci,rssi,k,v,m,n,cntrssi = "",{}
log.info("lbsLoc.enCellInfo",s)
for mcc,mnc,lac,ci,rssi in string.gmatch(s,"(%d+)%.(%d+)%.(%d+)%.(%d+)%.(%d+);") do
mcc,mnc,lac,ci,rssi = tonumber(mcc),tonumber(mnc),tonumber(lac),tonumber(ci),(tonumber(rssi) > 31) and 31 or tonumber(rssi)
local handle = nil
for k,v in pairs(t) do
if v.lac == lac and v.mcc == mcc and v.mnc == mnc then
if #v.rssici < 8 then
table.insert(v.rssici,{rssi=rssi,ci=ci})
end
handle = true
break
end
end
if not handle then
table.insert(t,{mcc=mcc,mnc=mnc,lac=lac,rssici={{rssi=rssi,ci=ci}}})
end
end
for k,v in pairs(t) do
ret = ret .. pack.pack(">HHb",v.lac,v.mcc,v.mnc)
for m,n in pairs(v.rssici) do
cntrssi = bit.bor(bit.lshift(((m == 1) and (#v.rssici-1) or 0),5),n.rssi)
ret = ret .. pack.pack(">bi",cntrssi,n.ci)
end
end
return string.char(#t)..ret
end
local function enWifiInfo(tWifi)
local ret,cnt,k,v = "",0
if tWifi then
for k,v in pairs(tWifi) do
log.info("lbsLoc.enWifiInfo",k,v)
ret = ret..pack.pack("Ab",(k:gsub(":","")):fromHex(),(v<0) and (v+255) or v)
cnt = cnt+1
end
end
return string.char(cnt)..ret
end
local function enMuid()
local muid = misc.getMuid()
return string.char(muid:len())..muid
end
local function trans(str)
local s = str
if str:len()<10 then
s = str..string.rep("0",10-str:len())
end
return s:sub(1,3).."."..s:sub(4,10)
end
local function taskClient(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
while not socket.isReady() do
if not sys.waitUntil("IP_READY_IND",timeout) then return cbFnc(1) end
end
local retryCnt,sck = 0
local reqStr = pack.pack("bAbAAAAA",
productKey:len(),
productKey,
(reqAddr and 2 or 0)+(reqTime and 4 or 0)+8+(reqWifi and 16 or 0)+32,
"",
common.numToBcdNum(misc.getImei()),
enMuid(),
enCellInfo(net.getCellInfoExt()),
enWifiInfo(reqWifi))
log.info("reqStr",reqStr:toHex())
while true do
sck = socket.udp()
if not sck then cbFnc(6) return end
if sck:connect(host,port) then
while true do
if sck:send(reqStr) then
local result,data = sck:recv(timeout)
if result then
sck:close()
log.info("lbsLoc receive",data:toHex())
if data:len()>=11 and (data:byte(1)==0 or data:byte(1)==0xFF) then
local locType = data:byte(1)
cbFnc(0,
trans(common.bcdNumToNum(data:sub(2,6))),
trans(common.bcdNumToNum(data:sub(7,11))),
reqAddr and data:sub(13,12+data:byte(12)) or nil,
data:sub(reqAddr and (13+data:byte(12)) or 12,-1),
locType)
else
log.warn("lbsLoc.query","根据基站查询经纬度失败")
if data:byte(1)==2 then
log.warn("lbsLoc.query","main.lua中的PRODUCT_KEY和此设备在iot.openluat.com中所属项目的ProductKey必须一致请去检查")
else
log.warn("lbsLoc.query","基站数据库查询不到所有小区的位置信息")
log.warn("lbsLoc.query","在trace中向上搜索encellinfo然后在电脑浏览器中打开http://bs.openluat.com/手动查找encellinfo后的所有小区位置")
log.warn("lbsLoc.query","如果手动可以查到位置则服务器存在BUG直接向技术人员反映问题")
log.warn("lbsLoc.query","如果手动无法查到位置,则基站数据库还没有收录当前设备的小区位置信息,向技术人员反馈,我们会尽快收录")
end
cbFnc(5)
end
return
else
sck:close()
retryCnt = retryCnt+1
if retryCnt>=3 then return cbFnc(4) end
break
end
else
sck:close()
retryCnt = retryCnt+1
if retryCnt>=3 then return cbFnc(3) end
break
end
end
else
sck:close()
retryCnt = retryCnt+1
if retryCnt>=3 then return cbFnc(2) end
end
end
end
--- 发送基站/WIFI定位请求仅支持中国区域的位置查询
-- @function cbFnc 用户回调函数,回调函数的调用形式为:
-- cbFnc(result,lat,lng,addr,time,locType)
-- resultnumber类型
-- 0表示成功
-- 1表示网络环境尚未就绪
-- 2表示连接服务器失败
-- 3表示发送数据失败
-- 4表示接收服务器应答超时
-- 5表示服务器返回查询失败
-- 6表示socket已满创建socket失败
-- 为0时后面的5个参数才有意义
-- latstring类型或者nil纬度整数部分3位小数部分7位例如"031.2425864"
-- lngstring类型或者nil经度整数部分3位小数部分7位例如"121.4736522"
-- addr目前无意义
-- timestring类型或者nil服务器返回的时间6个字节年月日时分秒需要转为十六进制读取
-- 第一个字节年减去2000例如2017年则为0x11
-- 第二个字节例如7月则为0x0712月则为0x0C
-- 第三个字节例如11日则为0x0B
-- 第四个字节例如18时则为0x12
-- 第五个字节例如59分则为0x3B
-- 第六个字节例如48秒则为0x30
-- locTypenumble类型或者nil定位类型0表示基站定位成功255表示WIFI定位成功
-- @bool[opt=nil] reqAddr 是否请求服务器返回具体的位置字符串信息,目前此功能不完善
-- @number[opt=20000] timeout 请求超时时间单位毫秒默认20000毫秒
-- @string[opt=nil] productKey IOT网站上的产品证书如果在main.lua中定义了PRODUCT_KEY变量则此参数可以传nil
-- @string[opt=nil] host 服务器域名此参数可选目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-- @string[opt=nil] port 服务器端口此参数可选目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-- @bool[opt=nil] reqTime 是否需要服务器返回时间信息true返回false或者nil不返回此参数可选目前仅lib中agps.lua使用此参数。应用脚本可以直接传nil
-- @table[opt=nil] reqWifi 搜索到的WIFI热点信息(MAC地址和信号强度)如果传入了此参数后台会查询WIFI热点对应的经纬度此参数格式如下
-- {
-- ["1a:fe:34:9e:a1:77"] = -63,
-- ["8c:be:be:2d:cd:e9"] = -81,
-- ["20:4e:7f:82:c2:c4"] = -70,
-- }
-- @return nil
-- @usage lbsLoc.request(cbFnc)
-- @usage lbsLoc.request(cbFnc,true)
-- @usage lbsLoc.request(cbFnc,nil,20000)
function request(cbFnc,reqAddr,timeout,productKey,host,port,reqTime,reqWifi)
assert(_G.PRODUCT_KEY or productKey,"undefine PRODUCT_KEY in main.lua")
sys.taskInit(taskClient,cbFnc,reqAddr,timeout or 20000,productKey or _G.PRODUCT_KEY,host or "bs.openluat.com",port or "12411",reqTime,reqWifi)
end

70
4G/code/lib/led.lua Normal file
View File

@@ -0,0 +1,70 @@
--- 模块功能LED闪灯模块
-- @module led
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.02.04
module(..., package.seeall)
--- 闪烁指示灯
-- @function ledPin ledPin(1)用pins.setup注册返回的方法
-- @number light light-亮灯时间ms
-- @number dark dark-灭灯时间ms
-- @return nil
-- @usage led.blinkPwm(lenPin,500,500)
-- @usage 调用函数需要使用任务支持
function blinkPwm(ledPin, light, dark)
ledPin(1)
sys.wait(light)
ledPin(0)
sys.wait(dark)
end
--- 等级指示灯
-- @function ledPin ledPin(1)用pins.setup注册返回的方法
-- @number bl 亮灯时间ms
-- @number bd 灭灯时间ms
-- @number cnt 重复次数 (等级的级别,亮灭1次算数字1)
-- @number gap 间隔时间 (每次循环周期的间隔)
-- @return nil
-- @usage led.leveled(ledPin,200,200,4,1000)
-- @usage 调用函数需要使用任务支持
function levelLed(ledPin, bl, bd, cnt, gap)
if not (ledPin and bl and bd and cnt and gap) then return end
for i = 1, cnt do blinkPwm(ledPin, bl, bd) end
sys.wait(gap)
end
--- 呼吸灯
-- @function ledPin 呼吸灯的ledPin(1)用pins.setup注册返回的方法
-- @return nil
-- @usage led.breateLed(ledPin)
-- @usage 调用函数需要使用任务支持
function breateLed(ledPin)
-- 呼吸灯的状态、PWM周期
local bLighting, bDarking, LED_PWM = false, true, 18
if bLighting then
for i = 1, LED_PWM - 1 do
ledPin(0)
sys.wait(i)
ledPin(1)
sys.wait(LED_PWM - i)
end
bLighting = false
bDarking = true
ledPin(0)
sys.wait(700)
end
if bDarking then
for i = 1, LED_PWM - 1 do
ledPin(0)
sys.wait(LED_PWM - i)
ledPin(1)
sys.wait(i)
end
bLighting = true
bDarking = false
ledPin(1)
sys.wait(700)
end
end

461
4G/code/lib/link.lua Normal file
View File

@@ -0,0 +1,461 @@
--- 模块功能:数据链路激活(创建、连接、状态维护)
-- @module link
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.20
-- 4G网络下不手动激活pdp注册上网后发cgdcont?等默认承载激活后上报IP_READY_IND
-- 2G网络下先cgact?查询有任一一路pdp激活则直接上报IP_READY_IND否则cgact激活cid_manual
require "net"
module(..., package.seeall)
local publish = sys.publish
local request = ril.request
local ipAddr = ""
local gprsAttached
local cid_manual = 5
local readyTable = {false, false, false}
-- 链路层网络类型:
-- 蜂窝模组数据网络
CELLULAR = 1
-- ch395以太网
CH395 = 2
-- w5500以太网
W5500 = 3
-- ESP8266WIFI网络
ESP8266 = 4
local network = CELLULAR
function setReady(mode, state)
readyTable[mode] = state
end
function getIp()
return ipAddr
end
function isReady()
return readyTable[network]
end
-- apn用户名密码
local apnname, username, password
local dnsIP
local authProt, authApn, authUser, authPassword
function setAPN(apn, user, pwd)
apnname, username, password = apn, user, pwd
end
function setDnsIP(ip1, ip2)
dnsIP = "\"" .. (ip1 or "") .. "\",\"" .. (ip2 or "") .. "\""
end
local function setCgdf()
request("AT+AUTOAPN=0")
request('AT*CGDFLT=1,"IP","' .. authApn .. '",,,,,,,,,,,,,,,,,,1')
request('AT*CGDFAUTH=1,' .. authProt .. ',"' .. authUser .. '","' .. authPassword .. '"', nil, function(cmd, result)
if result then
sys.restart("CGDFAUTH")
else
sys.timerStart(setCgdf, 5000)
end
end)
end
--- 设置专网卡APN(注意在main.lua中尽可能靠前的位置调用此接口)
-- 第一次设置成功之后,软件会自动重启,因为重启后才能生效
-- @number[opt=0] prot 加密方式0:不加密1:PAP2:CHAP
-- @string[opt=""] apn apn名称
-- @string[opt=""] user apn用户名
-- @string[opt=""] pwd apn密码
-- @return nil
-- @usage
-- c = link.setAuthApn(2,"MYAPN","MYNAME","MYPASSWORD")
function setAuthApn(prot, apn, user, pwd)
--[[
local coreVer = rtos.get_version()
local verNo = coreVer:match("Luat_V(%d+)_ASR1802_")
if verNo and tonumber(verNo)>=27 then
request("AT+AUTOAPN=0")]]
-- 0保存并重启生效
-- 1不保存立即生效
-- 2保存并立即生效
-- 3删除保存的文件
request('AT+CPNETAPN=2,"' .. apn .. '","' .. user .. '","' .. pwd .. '",' .. prot)
--[[else
authProt,authApn,authUser,authPassword = prot or 0,apn or "",user or "",pwd or ""
request("AT*CGDFLT?")
ril.regUrc("*CGDFLT", function(data)
local dftApn = data:match("CGDFLT:%s*\"%w*\",\"(.-)\"")
if dftApn~=authApn then
setCgdf()
end
end)
end]]
end
local function Pdp_Act()
log.info("link.Pdp_Act", readyTable[CELLULAR], net.getNetMode(), gprsAttached)
if readyTable[CELLULAR] then
request("AT+CGDCONT?", nil, cgdcontRsp)
return
end
if net.getNetMode() == net.NetMode_LTE then
if not gprsAttached then
gprsAttached = true
sys.publish("GPRS_ATTACH", true)
end
if not apnname then
sys.timerStart(pdpCmdCnf, 1000, "SET_PDP_4G_WAITAPN", true)
else
request("AT+CGDCONT?", nil, cgdcontRsp)
-- request(string.format('AT*CGDFLT=0,"IP","%s"', apnname), nil, pdpCmdCnf)
end
else
request('AT+CGATT?')
end
end
local function procshut(curCmd, result, respdata, interdata)
if network ~= CELLULAR then
return
end
if IsCidActived(cid_manual, interdata) then
ril.request(string.format('AT+CGACT=0,%d', cid_manual), nil, function(cmd, result)
if result then
readyTable[CELLULAR] = false
sys.publish('IP_ERROR_IND')
if net.getState() ~= 'REGISTERED' then
return
end
sys.timerStart(Pdp_Act, 2000)
end
end)
else
readyTable[CELLULAR] = false
sys.publish('IP_ERROR_IND')
if net.getState() ~= 'REGISTERED' then
return
end
sys.timerStart(Pdp_Act, 2000)
end
end
--[[
如果是默认承载,是去激活不了的,
如果是手动激活的pdp去激活cid_manual后也还是有默认承载存在
所以如果上层在去激活后要发起socket是能连上的所以这里直接上报IP_ERROR_IND由上层自己管理shut之后的逻辑
]]
function shut()
if network ~= CELLULAR then
return
end
-- ril.request("AT+CGACT?",nil,procshut)
readyTable[CELLULAR] = false
sys.publish('IP_ERROR_IND')
if net.getState() ~= 'REGISTERED' then
return
end
sys.timerStart(Pdp_Act, 2000)
end
function analysis_cgdcont(data)
local tmp, loc, result
while data do
_, loc = string.find(data, "\r\n")
if loc then
tmp = string.sub(data, 1, loc)
data = string.sub(data, loc + 1, -1)
log.info("analysis_cgdcont ", tmp, loc, data)
else
tmp = data
data = nil
log.info("analysis_cgdcont end", tmp, loc, data)
end
if tmp then
local cid, pdptyp, apn, addr = string.match(tmp, "(%d+),(.+),(.+),[\"\'](.+)[\"\']")
if not cid or not pdptyp or not apn or not addr then
log.info("analysis_cgdcont CGDCONT is empty")
ipAddr = ""
result = false
else
log.info("analysis_cgdcont ", cid, pdptyp, apn, addr)
if addr:match("%d+%.%d+%.%d+%.%d") then
ipAddr = addr
return true
else
log.info("analysis_cgdcont CGDCONT is empty1")
ipAddr = ""
return false
end
end
else
ipAddr = ""
log.info("analysis_cgdcont tmp is empty")
end
end
return result
end
function IsCidActived(cid, data)
if not data then
return
end
for k, v in string.gfind(data, "(%d+),%s*(%d)") do
log.info("iscidactived ", k, v)
if cid == tonumber(k) and v == '1' then
return true
end
end
return
end
function IsExistActivedCid(data)
if not data then
return
end
for k, v in string.gfind(data, "(%d+),%s*(%d)") do
if v == '1' then
log.info("ExistActivedCid ", k, v)
return true
end
end
return
end
local cgdcontResult
function cgdcontRsp()
if cgdcontResult then
pdpCmdCnf("CONNECT_DELAY", true)
end
end
function pdpCmdCnf(curCmd, result, respdata, interdata)
log.info("link.pdpCmdCnf", curCmd, result, respdata, interdata)
if string.find(curCmd, "CGDCONT%?") then
if result and interdata then
result = analysis_cgdcont(interdata)
else
result = false
end
end
if result then
cgdcontResult = false
if string.find(curCmd, "CGDCONT=") then
request(string.format('AT+CGACT=1,%d', cid_manual), nil, pdpCmdCnf)
elseif string.find(curCmd, "CGDCONT%?") then
-- sys.timerStart(pdpCmdCnf, 100, "CONNECT_DELAY",true)
cgdcontResult = true
elseif string.find(curCmd, "CONNECT_DELAY") and network == CELLULAR then
log.info("publish IP_READY_IND")
readyTable[CELLULAR] = true
publish("IP_READY_IND")
elseif string.find(curCmd, "CGACT=") then
request("AT+CGDCONT?", nil, cgdcontRsp)
elseif string.find(curCmd, "CGACT%?") then
if IsExistActivedCid(interdata) then
sys.timerStart(pdpCmdCnf, 100, "CONNECT_DELAY", true)
else
request(string.format('AT+CGDCONT=%d,"IP","%s"', cid_manual, authApn or apnname), nil, pdpCmdCnf)
end
elseif string.find(curCmd, "CGDFLT") then
request("AT+CGDCONT?", nil, cgdcontRsp)
elseif string.find(curCmd, "SET_PDP_4G_WAITAPN") then
if not apnname then
sys.timerStart(pdpCmdCnf, 100, "SET_PDP_4G_WAITAPN", true)
else
request("AT+CGDCONT?", nil, cgdcontRsp, 1000)
-- request(string.format('AT*CGDFLT=0,"IP","%s"', apnname), nil, pdpCmdCnf)
end
end
else
if net.getState() ~= 'REGISTERED' then
return
end
if net.getNetMode() == net.NetMode_LTE then
request("AT+CGDCONT?", nil, cgdcontRsp, 1000)
else
request("AT+CGATT?", nil, nil, 1000)
end
end
end
-- SIM卡 IMSI READY以后自动设置APN
sys.subscribe("IMSI_READY", function()
if not apnname then -- 如果未设置APN设置默认APN
local mcc, mnc = tonumber(sim.getMcc(), 16), tonumber(sim.getMnc(), 16)
apnname, username, password = apn and apn.get_default_apn(mcc, mnc) -- 如果存在APN库自动获取运营商的APN
if not apnname or apnname == '' or apnname == "CMNET" then -- 默认情况如果联通卡设置为联通APN 其他都默认为CMIOT
apnname = (mcc == 0x460 and (mnc == 0x01 or mnc == 0x06)) and 'UNINET' or 'CMIOT'
end
end
username = username or ''
password = password or ''
end)
ril.regRsp('+CGATT', function(a, b, c, intermediate)
local attached = (intermediate == "+CGATT: 1")
if gprsAttached ~= attached then
gprsAttached = attached
sys.publish("GPRS_ATTACH", attached)
end
if readyTable[CELLULAR] then
return
end
if attached then
log.info("pdp active", apnname, username, password)
request("AT+CGACT?", nil, pdpCmdCnf, 1000)
elseif net.getState() == 'REGISTERED' then
sys.timerStart(request, 2000, "AT+CGATT=1")
sys.timerStart(request, 2000, "AT+CGATT?")
end
end)
rtos.on(rtos.MSG_PDP_DEACT_IND, function()
if network ~= CELLULAR then
return
end
readyTable[CELLULAR] = false
sys.publish('IP_ERROR_IND')
if net.getState() ~= 'REGISTERED' then
return
end
sys.timerStart(Pdp_Act, 2000)
end)
-- 网络注册成功 :AT+CGDCONT?查询默认承载是否激活
-- 2/3G发起GPRS附着状态查询
sys.subscribe("NET_STATE_REGISTERED", Pdp_Act)
local function cindCnf(cmd, result)
if not result then
request("AT+CIND=1", nil, cindCnf, 1000)
end
end
local function cgevurc(data)
if network ~= CELLULAR then
return
end
local cid = 0
log.info("link.cgevurc", data)
if string.match(data, "DEACT") then
cid = string.match(data, "DEACT,(%d)")
cid = tonumber(cid)
if cid == cid_manual then
request("AT+CFUN?")
readyTable[CELLULAR] = false
sys.publish('IP_ERROR_IND')
sys.publish('PDP_DEACT_IND')
if net.getState() ~= 'REGISTERED' then
return
end
sys.timerStart(Pdp_Act, 2000)
end
end
end
request("AT+CIND=1", nil, cindCnf)
ril.regUrc("*CGEV", cgevurc)
ril.regUrc("+CGDCONT", function(data)
pdpCmdCnf("AT+CGDCONT?", true, "OK", data)
end)
--- 打开链路层网络类型
-- 注意设置网络类型后并不会关机保存下次开机会自动恢复为默认的link.CELLULAR类型
-- @number[opt=link.CELLULAR] mode 取值如下:
-- link.CELLULAR蜂窝模组数据网络
-- link.CH395CH395以太网络
-- link.ESP8266ESP8266WIFI网络
-- @table[opt=nil] para 取值如下:
-- 当mode为link.CELLULAR时参数para无意义可以直接传入nil
-- 当mode为link.CH395para为table类型表示以太网的配置参数参数结构如下
-- para= {
-- mode = 1, --1表示客户端2表示服务器默认为1
-- intPin = pio.P0_22, --以太网芯片中断通知引脚
-- rstPin = pio.P0_23, --复位以太网芯片引脚
-- spiCs = pio.P0_23, --spi片选
-- serverAddr = "192.168.1.112", --做服务器应用时,本机的地址
-- serverPort = 1888, --做服务器应用时,本机的端口
-- serverGateway = "192.168.1.1", --做服务器应用时,本机的网关地址
-- powerFunc=function(state) end --控制以太网模块的供电开关函数ret为true开启供电false关闭供电
-- spi = {spi.SPI_1,0,0,8,800000}, --SPI通道参数id,cpha,cpol,dataBits,clock默认spi.SPI_1,0,0,8,800000
-- }
-- @return true/false,执行成功返回true,失败返回false。
-- @usage
-- 设置为蜂窝数据网络:
-- c = link.setNetwork(link.CELLULAR, para)
-- 设置为CH395以太网络
-- link.setNetwork(link.CH395, para)
-- 设置为ESP8266WIFI网络:
-- link.setNetwork(link.ESP8266, para)
function openNetwork(mode, para)
local tSocketModule = {
[CH395] = socketCh395,
[W5500] = socketW5500,
[ESP8266] = socketESP8266
}
local md = mode or CELLULAR
closeNetWork()
network = md
if network == CELLULAR then
net.switchFly(false)
return true
else
ipAddr = tSocketModule[network].open(para)
if ipAddr ~= "" then
return true
else
log.info('link', 'open CH395 err')
return false
end
end
return false
end
--- 关闭链路层网络类型
-- 注意:关闭链路层网络类型,不会改变链路层网络类型,需要打开链路层网络类型配置才能切换。
-- @return true/false,执行成功返回true,失败返回false。
-- @usage
-- 关闭链路层网络类型:
-- link.closeNetWork()
function closeNetWork()
local tSocketModule = {
[CH395] = socketCh395,
[W5500] = socketW5500,
[ESP8266] = socketESP8266
}
if network == CELLULAR then
-- 飞行模式
net.switchFly(true)
return true
else
return tSocketModule[network].close()
end
return false
end
--- 获取链路层网络类型
-- @return networknumber类型取值如下
-- link.CELLULAR蜂窝模组数据网络
-- link.CH395CH395以太网络
-- link.ESP8266ESP8266WIFI网络
-- @usage
-- 获取数据网络类型:
-- mode = link.getNetwork()
function getNetwork()
return network
end

123
4G/code/lib/log.lua Normal file
View File

@@ -0,0 +1,123 @@
--- 模块功能:系统日志记录,分级别日志工具
-- @module log
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.09.26
module(..., package.seeall)
-- 定义日志级别常量可在main入口全局指定
-- 例如: LOG_LEVEL=log.LOGLEVEL_WARN
LOG_SILENT = 0x00;
LOGLEVEL_TRACE = 0x01;
LOGLEVEL_DEBUG = 0x02;
LOGLEVEL_INFO = 0x03;
LOGLEVEL_WARN = 0x04;
LOGLEVEL_ERROR = 0x05;
LOGLEVEL_FATAL = 0x06;
-- 定义日志级别标签分别对应日志级别的1-6
local LEVEL_TAG = {'T', 'D', 'I', 'W', 'E', 'F'}
local PREFIX_FMT = "[%s]-[%s]"
--- 内部函数支持不同级别的log打印及判断
-- @param level 日志级别可选LOGLEVEL_TRACELOGLEVEL_DEBUG等
-- @param tag 模块或功能名称(标签),作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage _log(LOGLEVEL_TRACE,tag, 'log content')
-- @usage _log(LOGLEVEL_DEBUG,tag, 'log content')
local function _log(level, tag, ...)
-- INFO 作为默认日志级别
local OPENLEVEL = LOG_LEVEL and LOG_LEVEL or LOGLEVEL_INFO
-- 如果日志级别为静默,或设定级别更高,则不输出日志
if OPENLEVEL == LOG_SILENT or OPENLEVEL > level then return end
-- 日志打印输出
local prefix = string.format(PREFIX_FMT, LEVEL_TAG[level], type(tag)=="string" and tag or "")
print(prefix, ...)
-- TODO支持hookup例如对某级别日志做额外处理
-- TODO支持标签过滤
end
--- 输出trace级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.trace('moduleA', 'log content')
function trace(tag, ...)
_log(LOGLEVEL_TRACE, tag, ...)
end
--- 输出debug级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.debug('moduleA', 'log content')
function debug(tag, ...)
_log(LOGLEVEL_DEBUG, tag, ...)
end
--- 输出info级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.info('moduleA', 'log content')
function info(tag, ...)
_log(LOGLEVEL_INFO, tag, ...)
end
--- 输出warn级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.warn('moduleA', 'log content')
function warn(tag, ...)
_log(LOGLEVEL_WARN, tag, ...)
end
--- 输出error级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.error('moduleA', 'log content')
function error(tag, ...)
_log(LOGLEVEL_ERROR, tag, ...)
end
--- 输出fatal级别的日志
-- @param tag 模块或功能名称,作为日志前缀
-- @param ... 日志内容,可变参数
-- @return nil
-- @usage log.fatal('moduleA', 'log content')
function fatal(tag, ...)
_log(LOGLEVEL_FATAL, tag, ...)
end
-- --- 开启或者关闭print的打印输出功能 --修复老版本波特率无法正常配置问题
-- -- @bool v false或nil为关闭其余为开启
-- -- @param uartid 输出Luatrace的端口1表示uart1,2表示uart2
-- -- @return nil
-- -- @usage log.openTrace(1,nil)
-- function openTrace(v, uartid, baudrate)
-- if uartid then
-- if v then
-- uart.setup(uartid, baudrate or 115200, 8, uart.PAR_NONE, uart.STOP_1)
-- else
-- uart.close(uartid)
-- end
-- end
-- rtos.set_trace(v and 1 or 0, uartid)
-- end
--- 开启或者关闭print的打印输出功能
-- @bool v false或nil为关闭其余为开启
-- @param uartid 输出Luatrace的端口1表示uart1,2表示uart2,3表示uart3
-- @baudrate 可修改串口波特率可选参数默认115200
-- @return nil
-- @usage log.openTrace(1,nil)
function openTrace(v, uartid, baudrate)
rtos.set_trace(v and 1 or 0, uartid,baudrate)
end

316
4G/code/lib/misc.lua Normal file
View File

@@ -0,0 +1,316 @@
--- 模块功能:配置管理-序列号、IMEI、底层软件版本号、时钟、是否校准、飞行模式、查询电池电量等功能
-- @module misc
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.20
require "ril"
local req = ril.request
module(..., package.seeall)
--sn序列号
--imeiIMEI
--modeltype:模块型号例如724720722等
--calib: 校准标志
--ant: 耦合测试标志位
--temp:模块温度
local sn, imei, calib, ver, muid, ant,modeltype,temp
local setSnCbFnc,setImeiCbFnc,setClkCbFnc,getTemperatureCbFnc
local function timeReport()
sys.publish("TIME_CLK_IND")
sys.timerStart(setTimeReport,2000)
end
function setTimeReport()
sys.timerStart(timeReport,(os.time()%60==0) and 50 or (60-os.time()%60)*1000)
end
--[[
函数名rsp
功能 本功能模块内“通过虚拟串口发送到底层core软件的AT命令”的应答处理
参数
cmd此应答对应的AT命令
successAT命令执行结果true或者false
responseAT命令的应答中的执行结果字符串
intermediateAT命令的应答中的中间信息
返回值:无
]]
local function rsp(cmd, success, response, intermediate)
local prefix = string.match(cmd, "AT(%+%u+)")
--查询序列号
if cmd == "AT+WISN?" then
result = (intermediate~="*CME ERROR: Missing SN")
if result then
sn = intermediate
sys.publish('SN_READY_IND')
end
if setSnCbFnc then setSnCbFnc(result) end
--查询IMEI
elseif cmd == "AT+CGSN" then
imei = intermediate
if setImeiCbFnc then setImeiCbFnc(true) end
sys.publish('IMEI_READY_IND')
--查询模块温度
elseif cmd =="AT+RFTEMPERATURE?" then
temp = string.match(intermediate, ':(.+)')
if getTemperatureCbFnc and type(getTemperatureCbFnc)=="function" then
if success then
getTemperatureCbFnc(temp)
else
getTemperatureCbFnc("")
end
end
--查询模块型号
elseif cmd == 'AT+CGMM' then
modeltype = string.match(intermediate, '"(.+)"')
if success then
sys.publish('MODEL_NUMBER_READY_IND')
end
elseif cmd == 'AT+VER' then
ver = intermediate
elseif prefix == '+CCLK' then
if success then
sys.publish('TIME_UPDATE_IND')
setTimeReport()
end
if setClkCbFnc then setClkCbFnc(getClock(),success) end
elseif cmd:match("AT%+WISN=") then
if success then
req("AT+WISN?")
else
if setSnCbFnc then setSnCbFnc(false) end
end
elseif cmd:match("AT%+CALIBINFO%?") then
if intermediate then
local LTE_afc = intermediate:match("LTE_afc:(%d)")
local LTE_TDD_agc = intermediate:match("LTE_TDD_agc:(%d)")
local LTE_TDD_apc = intermediate:match("LTE_TDD_apc:(%d)")
local LTE_FDD_agc = intermediate:match("LTE_FDD_agc:(%d)")
local LTE_FDD_apc = intermediate:match("LTE_FDD_apc:(%d)")
local ANT_LTE = intermediate:match("ANT_LTE:(%d)")
calib = (LTE_afc == "1" and LTE_TDD_agc == "1" and LTE_TDD_apc == "1" and LTE_FDD_agc == "1" and LTE_FDD_apc == "1")
ant = (ANT_LTE == "1")
end
elseif cmd:match("AT%*CALINFO%=R") then
if intermediate then
local LteTest=intermediate:match("LteTest%,PASS")
local LteCal=intermediate:match("LteCal%,PASS")
if LteCal then
calib=(LteCal=="LteCal,PASS")
end
if LteTest then
ant=(LteTest=="LteTest,PASS")
end
end
elseif cmd:match("AT%+WIMEI=") then
if success then
req("AT+CGSN")
else
if setImeiCbFnc then setImeiCbFnc(false) end
end
elseif cmd:match("AT%+MUID?") then
if intermediate then muid = intermediate:match("+MUID:%s*(.+)") end
end
end
--- 获取core固件名
-- @return string versioncore固件名
-- @usage
-- local version = misc.getVersion()
-- 如果core为Luat_V0026_RDA8910_TTS_FLOAT则version为string类型的"Luat_V0026_RDA8910_TTS_FLOAT"
function getVersion()
return rtos.get_version()
end
--- 设置系统时间
-- @table t 系统时间,格式参考:{year=2017,month=2,day=14,hour=14,min=2,sec=58}
-- @function[opt=nil] cbFnc 设置结果回调函数,回调函数的调用形式为:
-- cbFnc(timeresult)
-- result为true表示成功false或者nil为失败
-- time表示设置之后的系统时间table类型例如{year=2017,month=2,day=14,hour=14,min=19,sec=23}
-- @return nil
-- @usage misc.setClock({year=2017,month=2,day=14,hour=14,min=2,sec=58})
function setClock(t,cbFnc)
if type(t) ~= "table" or (t.year-2000>38) then
if cbFnc then cbFnc(getClock(),false) end
return
end
setClkCbFnc = cbFnc
req(string.format("AT+CCLK=\"%02d/%02d/%02d,%02d:%02d:%02d+32\"", string.sub(t.year, 3, 4), t.month, t.day, t.hour, t.min, t.sec), nil, rsp)
end
--- 获取系统时间
-- @return table time,{year=2017,month=2,day=14,hour=14,min=19,sec=23}
-- @usage time = getClock()
function getClock()
return os.date("*t")
end
--- 获取星期
-- @return number week1-7分别对应周一到周日
-- @usage week = misc.getWeek()
function getWeek()
local clk = os.date("*t")
return ((clk.wday == 1) and 7 or (clk.wday - 1))
end
--- 获取校准标志
-- @return bool calib, true表示已校准false或者nil表示未校准
-- @usage calib = misc.getCalib()
function getCalib()
return calib
end
--- 获取耦合测试标志
-- @return bool ant, true表示已耦合测试false或者nil表示未耦合测试
-- @usage ant = misc.getAnt()
function getAnt()
return ant
end
--- 设置SN
-- @string s 新sn的字符串
-- @function[opt=nil] cbFnc 设置结果回调函数,回调函数的调用形式为:
-- cnFnc(result)result为true表示成功false或者nil为失败
-- @return nil
-- @usage
-- misc.setSn("1234567890")
-- misc.setSn("1234567890",cbFnc)
function setSn(s, cbFnc)
if s ~= sn then
setSnCbFnc = cbFnc
req("AT+WISN=\"" .. s .. "\"")
else
if cbFnc then cbFnc(true) end
end
end
--- 获取模块序列号
-- @return string sn,序列号,如果未获取到返回""
-- 注意开机lua脚本运行之后会发送at命令去查询sn所以需要一定时间才能获取到sn。开机后立即调用此接口基本上返回""
-- @usage sn = misc.getSn()
function getSn()
return sn or ""
end
--- 设置IMEI
-- @string s 新IMEI字符串
-- @function[opt=nil] cbFnc 设置结果回调函数,回调函数的调用形式为:
-- cnFnc(result)result为true表示成功false或者nil为失败
-- @return nil
-- @usage misc.setImei(”359759002514931”)
function setImei(s, cbFnc)
if s ~= imei then
setImeiCbFnc = cbFnc
req("AT+WIMEI=\"" .. s .. "\"")
else
if cbFnc then cbFnc(true) end
end
end
--- 获取模块IMEI
-- @return string,IMEI号如果未获取到返回""
-- 注意开机lua脚本运行之后会发送at命令去查询imei所以需要一定时间才能获取到imei。开机后立即调用此接口基本上返回""
-- @usage imei = misc.getImei()
function getImei()
return imei or ""
end
--- 获取模块型号
-- @return string,模块型号,如果未获取到返回""
-- 例如模块型号为724UG,则返回值为Air724UG;模块型号为722UG,则返回值为Air722UG;模块型号为820UG,则返回值为Air820UG
-- 注意开机lua脚本运行之后会发送at命令去查询模块型号所以需要一定时间才能获取到模块型号。开机后立即调用此接口基本上返回""
-- @usage modeltype = getModelType()
function getModelType()
return modeltype or ""
end
-- 获取模块温度
-- @return string,模块温度如果要对该值进行运算可以使用带float的固件将该值转为number
-- 例如模块温度为29.77摄氏度,则返回值为29.77
function getTemperature(cb)
getTemperatureCbFnc = cb
ril.request("AT+RFTEMPERATURE?")
end
--- 获取VBAT的电池电压
-- @return number,电池电压,单位mv
-- @usage vb = getVbatt()
function getVbatt()
if type(pmd.libScriptInit)=="function" then pmd.libScriptInit() end
local v1, v2, v3, v4, v5 = pmd.param_get()
return v2
end
--- 获取VBUS连接状态
-- @return booleantrue表示VBUS连接false表示未连接
-- @usage vbus = getVbus()
function getVbus()
local v1, v2, v3, v4, v5 = pmd.param_get()
log.info("misc.getVbus",v1, v2, v3, v4, v5)
return v4
end
--- 获取模块MUID
-- @return string,MUID号如果未获取到返回""
-- 注意开机lua脚本运行之后会发送at命令去查询muid所以需要一定时间才能获取到muid。开机后立即调用此接口基本上返回""
-- @usage muid = misc.getMuid()
function getMuid()
return muid or ""
end
--- 打开并且配置PWM(支持2路PWM仅支持输出)
-- @number id PWM输出通道仅支持0和1
-- 0使用MODULE_STATUS/GPIO_5引脚
-- 1使用GPIO_13引脚注意上电的时候不要把 GPIO_13 拉高到V_GLOBAL_1V8否则模块会进入校准模式不正常开机
-- @number para1 当id为0时para1表示分频系数最大值为2047分频系数和频率的换算关系为频率=25000000/para1Hz例如para1为500时频率为50000Hz
-- 分频系数和周期的换算关系为:周期=para1/25000000例如para1为500时周期为20
-- 当id为1时para1表示时钟周期取值范围为0-7仅支持整数
-- 0-7分别对应125、250、500、1000、1500、2000、2500、3000毫秒
-- @number para2 当id为0时para2表示占空比计算系数最大值为1023占空比计算系数和占空比的计算关系为占空比=para2/para1
-- 当id为1时para2表示一个时钟周期内的高电平时间取值范围为1-15仅支持整数
-- 1-15分别对应15.6、31.2、46.8、62、78、94、110、125、140、156、172、188、200、218、234毫秒
-- @return nil
-- @usage
-- 通道0频率为50000Hz占空比为0.2
-- 频率为50000Hz表示时钟周期为1/50000=0.00002秒=0.02毫秒=20微秒
-- 占空比表示在一个时钟周期内,高电平的时长/时钟周期的时长本例子中的0.2就表示高电平时长为4微秒低电平时长为16微秒
-- misc.openPwm(0,500,100)
--
-- 通道1时钟周期为500ms高电平时间为125毫秒
-- misc.openPwm(1,2,8)
function openPwm(id, para1, para2)
pwm.open(id)
pwm.set(id,para1,para2)
end
--- 关闭PWM
-- @number id PWM输出通道仅支持0和1
-- 0使用MODULE_STATUS/GPIO_5引脚
-- 1使用GPIO_13引脚注意上电的时候不要把 GPIO_13 拉高到V_GLOBAL_1V8否则模块会进入校准模式不正常开机
-- @return nil
function closePwm(id)
assert(id == 0 or id == 1, "closepwm id error: " .. id)
pwm.close(id)
end
--注册以下AT命令的应答处理函数
ril.regRsp("+WISN", rsp)
ril.regRsp("+CGSN", rsp)
ril.regRsp("+RFTEMPERATURE",rsp)
ril.regRsp("+CGMM", rsp)
ril.regRsp("+MUID", rsp)
ril.regRsp("+WIMEI", rsp)
ril.regRsp("+AMFAC", rsp)
--ril.regRsp('+VER', rsp, 4, '^[%w_]+$')
ril.regRsp("+CALIBINFO",rsp)
ril.regRsp("*CALINFO",rsp)
--req('AT+VER')
--查询序列号
req("AT+WISN?")
--查询IMEI
req("AT+CGSN")
req("AT+MUID?")
req("AT*EXINFO?")
--查询模块温度
-- req("AT+RFTEMPERATURE?")
--查询模块型号
if string.match(rtos.get_version(),"ASR1603") then
req("AT*CALINFO=R,LteCal")
req("AT*CALINFO=R,LteTest")
end
req("AT+CGMM")
setTimeReport()

510
4G/code/lib/mqtt.lua Normal file
View File

@@ -0,0 +1,510 @@
--- 模块功能MQTT客户端
-- @module mqtt
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.24
require "log"
require "socket"
require "utils"
module(..., package.seeall)
-- MQTT 指令id
local CONNECT, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, PINGREQ, PINGRESP, DISCONNECT = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
local CLIENT_COMMAND_TIMEOUT = 60000
local function encodeLen(len)
local s = ""
local digit
repeat
digit = len % 128
len = (len - digit) / 128
if len > 0 then
digit = bit.bor(digit, 0x80)
end
s = s .. string.char(digit)
until (len <= 0)
return s
end
local function encodeUTF8(s)
if not s or #s == 0 then
return ""
else
return pack.pack(">P", s)
end
end
local function packCONNECT(clientId, keepAlive, username, password, cleanSession, will, version)
local content = pack.pack(">PbbHPAAAA",
version == "3.1" and "MQIsdp" or "MQTT",
version == "3.1" and 3 or 4,
(#username == 0 and 0 or 1) * 128 + (#password == 0 and 0 or 1) * 64 + will.retain * 32 + will.qos * 8 + will.flag * 4 + cleanSession * 2,
keepAlive,
clientId,
encodeUTF8(will.topic),
encodeUTF8(will.payload),
encodeUTF8(username),
encodeUTF8(password))
return pack.pack(">bAA",
CONNECT * 16,
encodeLen(string.len(content)),
content)
end
local function packSUBSCRIBE(dup, packetId, topics)
local header = SUBSCRIBE * 16 + dup * 8 + 2
local data = pack.pack(">H", packetId)
for topic, qos in pairs(topics) do
data = data .. pack.pack(">Pb", topic, qos)
end
return pack.pack(">bAA", header, encodeLen(#data), data)
end
local function packUNSUBSCRIBE(dup, packetId, topics)
local header = UNSUBSCRIBE * 16 + dup * 8 + 2
local data = pack.pack(">H", packetId)
for k, topic in pairs(topics) do
data = data .. pack.pack(">P", topic)
end
return pack.pack(">bAA", header, encodeLen(#data), data)
end
local function packPUBLISH(dup, qos, retain, packetId, topic, payload)
local header = PUBLISH * 16 + dup * 8 + qos * 2 + retain
local len = 2 + #topic + #payload
if qos > 0 then
return pack.pack(">bAPHA", header, encodeLen(len + 2), topic, packetId, payload)
else
return pack.pack(">bAPA", header, encodeLen(len), topic, payload)
end
end
local function packACK(id, dup, packetId)
return pack.pack(">bbH", id * 16 + dup * 8 + (id == PUBREL and 1 or 0) * 2, 0x02, packetId)
end
local function packZeroData(id, dup, qos, retain)
dup = dup or 0
qos = qos or 0
retain = retain or 0
return pack.pack(">bb", id * 16 + dup * 8 + qos * 2 + retain, 0)
end
local function unpack(s)
if #s < 2 then return end
log.debug("mqtt.unpack", #s, string.toHex(string.sub(s, 1, 50)))
-- read remaining length
local len = 0
local multiplier = 1
local pos = 2
repeat
if pos > #s then return end
local digit = string.byte(s, pos)
len = len + ((digit % 128) * multiplier)
multiplier = multiplier * 128
pos = pos + 1
until digit < 128
if #s < len + pos - 1 then return end
local header = string.byte(s, 1)
local packet = {id = (header - (header % 16)) / 16, dup = ((header % 16) - ((header % 16) % 8)) / 8, qos = bit.band(header, 0x06) / 2, retain = bit.band(header, 0x01)}
local nextpos
if packet.id == CONNACK then
nextpos, packet.ackFlag, packet.rc = pack.unpack(s, "bb", pos)
elseif packet.id == SUBACK then
nextpos, packet.ackFlag, packet.rc = pack.unpack(s, "bb", pos)
if len >= 2 then
nextpos, packet.packetId = pack.unpack(s, ">H", pos)
packet.grantedQos = string.sub(s, nextpos, pos + len - 1)
else
packet.packetId = 0
packet.grantedQos = ""
end
elseif packet.id == PUBLISH then
nextpos, packet.topic = pack.unpack(s, ">P", pos)
if packet.qos > 0 then
nextpos, packet.packetId = pack.unpack(s, ">H", nextpos)
end
packet.payload = string.sub(s, nextpos, pos + len - 1)
elseif packet.id ~= PINGRESP then
if len >= 2 then
nextpos, packet.packetId = pack.unpack(s, ">H", pos)
else
packet.packetId = 0
end
end
return packet, pos + len
end
local mqttc = {}
mqttc.__index = mqttc
--- 创建一个mqtt client实例
-- @string clientId 确保设备唯一性
-- @number[opt=300] keepAlive 心跳间隔(单位为秒)默认300秒
-- @string[opt=""] username 用户名,用户名为空配置为""或者nil
-- @string[opt=""] password 密码,密码为空配置为""或者nil
-- @number[opt=1] cleanSession 1/0
-- @table[opt=nil] will 遗嘱参数,格式为{qos=,retain=,topic=,payload=}
-- @string[opt="3.1.1"] version MQTT版本号仅支持"3.1"和"3.1.1"
-- @return table mqttc client实例
-- @usage
-- mqttc = mqtt.client("clientid-123")
-- mqttc = mqtt.client("clientid-123",200)
-- mqttc = mqtt.client("clientid-123",nil,"user","password")
-- mqttc = mqtt.client("clientid-123",nil,"user","password",nil,{qos=0,retain=0,topic="willTopic",payload="willTopic"},"3.1")
function client(clientId, keepAlive, username, password, cleanSession, will, version)
local o = {}
local packetId = 1
if will then
will.flag = 1
else
will = {flag = 0, qos = 0, retain = 0, topic = "", payload = ""}
end
o.clientId = clientId
o.keepAlive = keepAlive or 300
o.username = username or ""
o.password = password or ""
o.cleanSession = cleanSession or 1
o.version = version or "3.1.1"
o.will = will
o.commandTimeout = CLIENT_COMMAND_TIMEOUT
o.cache = {}-- 接收到的mqtt数据包缓冲
o.inbuf = "" -- 未完成的数据缓冲
o.connected = false
o.getNextPacketId = function()
packetId = packetId == 65535 and 1 or (packetId + 1)
return packetId
end
o.lastOTime = 0
setmetatable(o, mqttc)
return o
end
-- 检测是否需要发送心跳包
function mqttc:checkKeepAlive()
if self.keepAlive == 0 then return true end
if os.time() - self.lastOTime >= self.keepAlive then
if not self:write(packZeroData(PINGREQ)) then
log.info("mqtt.client:", "pingreq send fail")
return false
end
end
return true
end
-- 发送mqtt数据
function mqttc:write(data)
log.debug("mqtt.client:write", string.toHex(string.sub(data, 1, 50)))
local r = self.io:send(data)
if r then self.lastOTime = os.time() end
return r
end
-- 接收mqtt数据包
function mqttc:read(timeout, msg, msgNoResume)
if not self:checkKeepAlive() then
log.warn("mqtt.read checkKeepAlive fail")
return false
end
-- 处理之前缓冲的数据
local packet, nextpos = unpack(self.inbuf)
if packet then
self.inbuf = string.sub(self.inbuf, nextpos)
return true, packet
end
while true do
local recvTimeout
if self.keepAlive == 0 then
recvTimeout = timeout
else
local kaTimeout = (self.keepAlive - (os.time() - self.lastOTime)) * 1000
recvTimeout = kaTimeout > timeout and timeout or kaTimeout
end
local r, s, p = self.io:recv(recvTimeout == 0 and 5 or recvTimeout, msg, msgNoResume)
if r then
self.inbuf = self.inbuf .. s
elseif s == "timeout" then -- 超时,判断是否需要发送心跳包
if not self:checkKeepAlive() then
return false
elseif timeout <= recvTimeout then
return false, "timeout"
else
timeout = timeout - recvTimeout
end
else -- 其他错误直接返回
return r, s, p
end
local packet, nextpos = unpack(self.inbuf)
if packet then
--self.lastIOTime = os.time()
self.inbuf = string.sub(self.inbuf, nextpos)
if packet.id ~= PINGRESP then
return true, packet
end
end
end
end
-- 等待接收指定的mqtt消息
function mqttc:waitfor(id, timeout, msg, msgNoResume)
for index, packet in ipairs(self.cache) do
if packet.id == id then
return true, table.remove(self.cache, index)
end
end
while true do
local insertCache = true
local r, data, param = self:read(timeout, msg, msgNoResume)
if r then
if data.id == PUBLISH then
if data.qos > 0 then
if not self:write(packACK(data.qos == 1 and PUBACK or PUBREC, 0, data.packetId)) then
log.info("mqtt.client:waitfor", "send publish ack failed", data.qos)
return false
end
end
elseif data.id == PUBREC or data.id == PUBREL then
if not self:write(packACK(data.id == PUBREC and PUBREL or PUBCOMP, 0, data.packetId)) then
log.info("mqtt.client:waitfor", "send ack fail", data.id == PUBREC and "PUBREC" or "PUBCOMP")
return false
end
insertCache = false
end
if data.id == id then
return true, data
end
if insertCache then table.insert(self.cache, data) end
else
return false, data, param
end
end
end
--- 连接mqtt服务器
-- @string host 服务器地址
-- @param port string或者number类型服务器端口
-- @string[opt="tcp"] transport "tcp"或者"tcp_ssl"
-- @table[opt=nil] cert table或者nil类型ssl证书当transport为"tcp_ssl"时此参数才有意义。cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @number[opt=120] timeout 可选参数socket连接超时时间单位秒
-- @return result true表示成功false或者nil表示失败
-- @usage mqttc = mqtt.client("clientid-123", nil, nil, false); mqttc:connect("mqttserver.com", 1883, "tcp", 5)
function mqttc:connect(host, port, transport, cert, timeout)
if self.connected then
log.info("mqtt.client:connect", "has connected")
return false
end
if self.io then
self.io:close()
self.io = nil
end
if transport and transport ~= "tcp" and transport ~= "tcp_ssl" then
log.info("mqtt.client:connect", "invalid transport", transport)
return false
end
self.io = socket.tcp(transport == "tcp_ssl" or type(cert) == "table", cert)
if not self.io:connect(host, port, timeout) then
log.info("mqtt.client:connect", "connect host fail")
return false
end
if not self:write(packCONNECT(self.clientId, self.keepAlive, self.username, self.password, self.cleanSession, self.will, self.version)) then
log.info("mqtt.client:connect", "send fail")
return false
end
local r, packet = self:waitfor(CONNACK, self.commandTimeout, nil, true)
-- if not r or packet.rc ~= 0 then
-- log.info("mqtt.client:connect", "connack error", r and packet.rc or -1)
-- return false,packet.rc
-- end
if (not r) or (not packet) or packet.rc ~= 0 then
log.info("mqtt.client:connect", "connack error", r and packet.rc or -1)
return false, packet and packet.rc or -1
end
self.connected = true
return true
end
--- 订阅主题
-- @param topic string或者table类型一个主题时为string类型多个主题时为table类型主题内容为UTF8编码
-- @param[opt=0] qos number或者niltopic为一个主题时qos为number类型(0/1/2默认0)topic为多个主题时qos为nil
-- @return bool true表示成功false或者nil表示失败
-- @usage
-- mqttc:subscribe("/abc", 0) -- subscribe topic "/abc" with qos = 0
-- mqttc:subscribe({["/topic1"] = 0, ["/topic2"] = 1, ["/topic3"] = 2}) -- subscribe multi topic
function mqttc:subscribe(topic, qos)
if not self.connected then
log.info("mqtt.client:subscribe", "not connected")
return false
end
local topics
if type(topic) == "string" then
topics = {[topic] = qos and qos or 0}
else
topics = topic
end
if not self:write(packSUBSCRIBE(0, self.getNextPacketId(), topics)) then
log.info("mqtt.client:subscribe", "send failed")
return false
end
local r, packet = self:waitfor(SUBACK, self.commandTimeout, nil, true)
if not r then
log.info("mqtt.client:subscribe", "wait ack failed")
return false
end
if not (packet.grantedQos and packet.grantedQos~="" and not packet.grantedQos:match(string.char(0x80))) then
log.info("mqtt.client:subscribe", "suback grant qos error", packet.grantedQos)
return false
end
return true
end
--- 取消订阅主题
-- @param topic string或者table类型一个主题时为string类型多个主题时为table类型主题内容为UTF8编码
-- @return bool true表示成功false或者nil表示失败
-- @usage
-- mqttc:unsubscribe("/abc") -- unsubscribe topic "/abc"
-- mqttc:unsubscribe({"/topic1", "/topic2", "/topic3"}) -- unsubscribe multi topic
function mqttc:unsubscribe(topic)
if not self.connected then
log.info("mqtt.client:unsubscribe", "not connected")
return false
end
local topics
if type(topic) == "string" then
topics = {topic}
else
topics = topic
end
if not self:write(packUNSUBSCRIBE(0, self.getNextPacketId(), topics)) then
log.info("mqtt.client:unsubscribe", "send failed")
return false
end
if not self:waitfor(UNSUBACK, self.commandTimeout, nil, true) then
log.info("mqtt.client:unsubscribe", "wait ack failed")
return false
end
return true
end
--- 发布一条消息
-- @string topic UTF8编码的字符串
-- @string payload 用户自己控制payload的编码mqtt.lua不会对payload做任何编码转换
-- @number[opt=0] qos 0/1/2, default 0
-- @number[opt=0] retain 0或者1
-- @return bool 发布成功返回true失败返回false
-- @usage
-- mqttc = mqtt.client("clientid-123", nil, nil, false)
-- mqttc:connect("mqttserver.com", 1883, "tcp")
-- mqttc:publish("/topic", "publish from luat mqtt client", 0)
function mqttc:publish(topic, payload, qos, retain)
if not self.connected then
log.info("mqtt.client:publish", "not connected")
return false
end
qos = qos or 0
retain = retain or 0
if not self:write(packPUBLISH(0, qos, retain, qos > 0 and self.getNextPacketId() or 0, topic, payload)) then
log.info("mqtt.client:publish", "socket send failed")
return false
end
if qos == 0 then return true end
if not self:waitfor(qos == 1 and PUBACK or PUBCOMP, self.commandTimeout, nil, true) then
log.warn("mqtt.client:publish", "wait ack timeout")
return false
end
return true
end
--- 接收消息
-- @number timeout 接收超时时间,单位毫秒
-- @string[opt=nil] msg 可选参数控制socket所在的线程退出recv阻塞状态
-- @return result 数据接收结果true表示成功false表示失败
-- @return data
-- 如果result为true表示服务器发过来的mqtt包
--
-- 如果result为false超时失败,data为"timeout"
-- 如果result为falsemsg控制退出data为msg的字符串
-- 如果result为falsesocket连接被动断开控制退出data为"CLOSED"
-- 如果result为falsePDP断开连接控制退出data为"IP_ERROR_IND"
--
-- 如果result为falsemqtt不处于连接状态data为nil
-- 如果result为false收到了PUBLISH报文发送PUBACK或者PUBREC报文失败data为nil
-- 如果result为false收到了PUBREC报文发送PUBREL报文失败data为nil
-- 如果result为false收到了PUBREL报文发送PUBCOMP报文失败data为nil
-- 如果result为false发送PINGREQ报文失败data为nil
-- @return param 如果是msg控制退出param的值是msg的参数其余情况无意义为nil
-- @usage
-- true, packet = mqttc:receive(2000)
-- false, error_message = mqttc:receive(2000)
-- false, msg, para = mqttc:receive(2000,"APP_SEND_DATA")
function mqttc:receive(timeout, msg)
if not self.connected then
log.info("mqtt.client:receive", "not connected")
return false
end
return self:waitfor(PUBLISH, timeout, msg)
end
--- 断开与服务器的连接
-- @return nil
-- @usage
-- mqttc = mqtt.client("clientid-123", nil, nil, false)
-- mqttc:connect("mqttserver.com", 1883, "tcp")
-- process data
-- mqttc:disconnect()
function mqttc:disconnect()
if self.io then
if self.connected then self:write(packZeroData(DISCONNECT)) end
self.io:close()
self.io = nil
end
self.cache = {}
self.inbuf = ""
self.connected = false
end

787
4G/code/lib/net.lua Normal file
View File

@@ -0,0 +1,787 @@
---模块功能网络管理、信号查询、GSM网络状态查询、网络指示灯控制、临近小区信息查询
-- @module net
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.02.17
require "sys"
require "ril"
require "pio"
require "sim"
require "log"
require "utils"
module(..., package.seeall)
--加载常用的全局函数至本地
local publish = sys.publish
--netmode define
NetMode_noNet= 0
NetMode_GSM= 1--2G
NetMode_EDGE= 2--2.5G
NetMode_TD= 3--3G
NetMode_LTE= 4--4G
NetMode_WCDMA= 5--3G
local netMode = NetMode_noNet
--网络状态:
--INIT开机初始化中的状态
--REGISTERED注册上GSM网络
--UNREGISTER未注册上GSM网络
local state = "INIT"
--SIM卡状态true为异常false或者nil为正常
local simerrsta
-- 飞行模式状态
flyMode = false
--lac位置区ID
--ci小区ID
--rssi信号强度
--rsrp信号接收功率
local lac, ci, rssi, rsrp, band = "", "", 0, 0, ""
--cellinfo当前小区和临近小区信息表
--multicellcb获取多小区的回调函数
local cellinfo, multicellcb = {}
local curCellSeted
local function cops(data)
--+COPS: 0,2,"46000",7
local fmt,oper = data:match('COPS:%s*%d+%s*,(%d+)%s*,"(%d+)"')
log.info("cops",fmt,oper,curCellSeted)
if fmt=="2" and not curCellSeted then
cellinfo[1].mcc = tonumber(oper:sub(1,3),16)
cellinfo[1].mnc = tonumber(oper:sub(4,5),16)
end
end
--[[
函数名creg
功能 解析CREG信息
参数 dataCREG信息字符串例如+CREG: 2、+CREG: 1,"18be","93e1"、+CREG: 5,"18a7","cb51"
返回值:无
]]
local function creg(data)
local p1, s,act
local prefix = (netMode == NetMode_LTE) and "+CEREG: " or (netMode == NetMode_noNet and "+CREG: " or "+CGREG: ")
log.info("net.creg1",netMode,prefix)
if not data:match(prefix) then
--log.info("net.creg2",prefix)
if prefix=="+CREG: " then
--log.info("net.creg3")
prefix = "+CGREG: "
if not data:match("+CGREG: ") then
log.warn("net.creg1","no match",data)
return
end
elseif prefix=="+CGREG: " then
--log.info("net.creg4")
prefix = "+CREG: "
if not data:match("+CREG: ") then
log.warn("net.creg2","no match",data)
return
end
end
end
--获取注册状态
_, _, p1 = data:find(prefix .. "%d,(%d+)")
--log.info("net.creg5",p1 == nil)
if p1 == nil then
_, _, p1 = data:find(prefix .. "(%d+)")
--log.info("net.creg6",p1 == nil)
if p1 == nil then return end
act = data:match(prefix .. "%d+,.-,.-,(%d+)")
else
act = data:match(prefix .. "%d,%d+,.-,.-,(%d+)")
end
log.info("net.creg7",p1,act)
--设置注册状态
s = (p1=="1" or p1=="5") and "REGISTERED" or "UNREGISTER"
--log.info("net.creg8",s,state)
if prefix=="+CGREG: " and s=="UNREGISTER" then
log.info("net.creg9 ignore!!!")
return
end
--注册状态发生了改变
if s ~= state then
--临近小区查询处理
if s == "REGISTERED" then
--产生一个内部消息NET_STATE_CHANGED表示GSM网络注册状态发生变化
publish("NET_STATE_REGISTERED")
cengQueryPoll()
end
state = s
end
--已注册并且lac或ci发生了变化
if state == "REGISTERED" then
p2, p3 = data:match("\"(%x+)\",\"(%x+)\"")
if p2 and p3 and (lac ~= p2 or ci ~= p3) then
lac = p2
ci = p3
--产生一个内部消息NET_CELL_CHANGED表示lac或ci发生了变化
publish("NET_CELL_CHANGED")
--cellinfo[1].mcc = tonumber(sim.getMcc(),16)
--cellinfo[1].mnc = tonumber(sim.getMnc(),16)
cellinfo[1].lac = tonumber(lac,16)
cellinfo[1].ci = tonumber(ci,16)
cellinfo[1].rssi = 28
end
if act then
if act=="0" then
UpdNetMode("^MODE: 3,1")
elseif act=="1" then
UpdNetMode("^MODE: 3,2")
elseif act=="3" then
UpdNetMode("^MODE: 3,3")
elseif act=="7" then
UpdNetMode("^MODE: 17,17")
else
UpdNetMode("^MODE: 5,7")
end
end
end
end
--[[
函数名resetcellinfo
功能 :重置当前小区和临近小区信息表
参数 :无
返回值:无
]]
local function resetCellInfo()
local i
cellinfo.cnt = 11 --最大个数
for i = 1, cellinfo.cnt do
cellinfo[i] = {}
cellinfo[i].mcc, cellinfo[i].mnc = nil
cellinfo[i].lac = 0
cellinfo[i].ci = 0
cellinfo[i].rssi = 0
cellinfo[i].ta = 0
end
end
--[[
函数名eemMgInfoSvc
功能 解析4G网络当前小区和临近小区信息
参数
data当前小区和临近小区信息字符串例如下面中的每一行
+EEMLTESVC:xx,xx,...
返回值:无
]]
local function eemLteSvc(data)
local mcc,mnc,lac,ci,rssi,svcData
if data:match("%+EEMLTESVC:%s*%d+,%s*%d+,%s*%d+,%s*.+") then
svcData = string.match(data, "%+EEMLTESVC:(.+)")
--log.info("eemLteSvc",svcData)
if svcData then
svcDataT = string.split(svcData, ', ')
--log.info("eemLteSvc1",svcDataT[1],svcDataT[3],svcDataT[4],svcDataT[10],svcDataT[15])
if not(svcDataT[1] and svcDataT[3] and svcDataT[4] and svcDataT[10] and svcDataT[15]) then
svcDataT = string.split(svcData, ',')
log.info("eemLteSvc2",svcDataT[1],svcDataT[3],svcDataT[4],svcDataT[10],svcDataT[15])
end
mcc = svcDataT[1]
mnc = svcDataT[3]
lac = svcDataT[4]
ci = svcDataT[10]
band = svcDataT[8]
rssi = (tonumber(svcDataT[15])-(tonumber(svcDataT[15])%3))/3
if rssi>31 then rssi=31 end
if rssi<0 then rssi=0 end
end
log.info("eemLteSvc1",lac,ci,mcc,mnc)
if lac and lac~="0" and ci and ci ~= "0" and mcc and mnc then
--如果是第一条,清除信息表
resetCellInfo()
curCellSeted = true
--保存mcc、mnc、lac、ci、rssi、ta
cellinfo[1].mcc = mcc
cellinfo[1].mnc = mnc
cellinfo[1].lac = tonumber(lac)
cellinfo[1].ci = tonumber(ci)
cellinfo[1].rssi = tonumber(rssi)
--cellinfo[id + 1].ta = tonumber(ta or "0")
--产生一个内部消息CELL_INFO_IND表示读取到了新的当前小区和临近小区信息
if multicellcb then multicellcb(cellinfo) end
publish("CELL_INFO_IND", cellinfo)
end
elseif data:match("%+EEMLTEINTER") or data:match("%+EEMLTEINTRA") or data:match("%+EEMLTEINTERRAT") then
--data = "+EEMLTEINTRA: 0, 98, 39148, 51, 21, 1120, 0, 6311, 25418539"
--data = "+EEMLTEINTERRAT:0,16,1120,0,6213,26862,627,1,-77"
data = data:gsub(" ","")
if data:match("%+EEMLTEINTERRAT") then
mcc,mnc,lac,ci,rssi = data:match("[-]*%d+,[-]*%d+,([-]*%d+),([-]*%d+),([-]*%d+),([-]*%d+),[-]*%d+,[-]*%d+,([-]*%d+)")
else
rssi,mcc,mnc,lac,ci = data:match("[-]*%d+,[-]*%d+,[-]*%d+,([-]*%d+),[-]*%d+,([-]*%d+),([-]*%d+),([-]*%d+),([-]*%d+)")
end
--print(mcc,mnc,lac,ci,rssi)
if rssi then
rssi = (rssi-(rssi%3))/3
if rssi>31 then rssi=31 end
if rssi<0 then rssi=0 end
end
if lac~="0" and lac~="-1" and ci~="0" and ci~="-1" then
for i = 1, cellinfo.cnt do
--print("cellinfo["..i.."].lac="..cellinfo[i].lac)
if cellinfo[i].lac==0 then
cellinfo[i] =
{
mcc = mcc,
mnc = mnc,
lac = tonumber(lac),
ci = tonumber(ci),
rssi = tonumber(rssi)
}
break
end
end
end
end
end
--[[
函数名eemMgInfoSvc
功能 解析2G网络当前小区
参数
data当前小区信息字符串例如下面中的每一行
+EEMGINFOSVC:xx,xx,...
返回值:无
]]
local function eemGsmInfoSvc(data)
--只处理有效的CENG信息
if string.find(data, "%+EEMGINFOSVC:%s*%d+,%s*%d+,%s*%d+,%s*.+") then
local mcc,mnc,lac,ci,ta,rssi
local svcData = string.match(data, "%+EEMGINFOSVC:(.+)")
if svcData then
svcDataT = string.split(svcData, ', ')
mcc = svcDataT[1]
mnc = svcDataT[2]
lac = svcDataT[3]
ci = svcDataT[4]
ta = svcDataT[10]
rssi = svcDataT[12]
if tonumber(rssi) >31
then rssi = 31
end
if tonumber(rssi) < 0
then rssi = 0
end
end
if lac and lac~="0" and ci and ci ~= "0" and mcc and mnc then
--如果是第一条,清除信息表
resetCellInfo()
curCellSeted = true
--保存mcc、mnc、lac、ci、rssi、ta
cellinfo[1].mcc = mcc
cellinfo[1].mnc = mnc
cellinfo[1].lac = tonumber(lac)
cellinfo[1].ci = tonumber(ci)
cellinfo[1].rssi = (tonumber(rssi) == 99) and 0 or tonumber(rssi)
cellinfo[1].ta = tonumber(ta or "0")
--产生一个内部消息CELL_INFO_IND表示读取到了新的当前小区和临近小区信息
if multicellcb then multicellcb(cellinfo) end
publish("CELL_INFO_IND", cellinfo)
end
end
end
--[[
函数名eemMgInfoSvc
功能 解析2G网络临近小区信息
参数
data当前小区和临近小区信息字符串例如下面中的每一行
+EEMGINFOSVC:xx,xx,...
返回值:无
]]
local function eemGsmNCInfoSvc(data)
if string.find(data, "%+EEMGINFONC: %d+, %d+, %d+, .+") then
local mcc,mnc,lac,ci,ta,rssi,id
local svcData = string.match(data, "%+EEMGINFONC:(.+)")
if svcData then
svcDataT = string.split(svcData, ', ')
id = svcDataT[1]
mcc = svcDataT[2]
mnc = svcDataT[3]
lac = svcDataT[4]
ci = svcDataT[6]
rssi = svcDataT[7]
if tonumber(rssi) >31
then rssi = 31
end
if tonumber(rssi) < 0
then rssi = 0
end
end
if lac and ci and mcc and mnc then
--保存mcc、mnc、lac、ci、rssi、ta
cellinfo[id + 2].mcc = mcc
cellinfo[id + 2].mnc = mnc
cellinfo[id + 2].lac = tonumber(lac)
cellinfo[id + 2].ci = tonumber(ci)
cellinfo[id + 2].rssi = (tonumber(rssi) == 99) and 0 or tonumber(rssi)
--cellinfo[id + 1].ta = tonumber(ta or "0")
end
end
end
--[[
函数名eemMgInfoSvc
功能 解析3G网络当前小区和临近小区信息
参数
data当前小区和临近小区信息字符串例如下面中的每一行
+EEMUMTSSVC:xx,xx,...
返回值:无
]]
local function eemUMTSInfoSvc(data)
--只处理有效的CENG信息
if string.find(data, "%+EEMUMTSSVC: %d+, %d+, %d+, .+") then
local mcc,mnc,lac,ci,rssi
local svcData = string.match(data, "%+EEMUMTSSVC:(.+)")
local cellMeasureFlag, cellParamFlag = string.match(data, "%+EEMUMTSSVC:%d+, (%d+), (%d+), .+")
local svcDataT = string.split(svcData, ', ')
local offset = 4
if svcData and svcDataT then
if tonumber(cellMeasureFlag) ~= 0 then
offset = offset + 2
rssi = svcDataT[offset]
offset = offset + 4
else
offset = offset + 2
rssi = svcDataT[offset]
offset = offset + 2
end
if tonumber(cellParamFlag) ~= 0 then
offset = offset + 3
mcc = svcDataT[offset]
mnc = svcDataT[offset + 1]
lac = svcDataT[offset + 2]
ci = svcDataT[offset + 3]
offset = offset + 3
end
end
if lac and lac~="0" and ci and ci ~= "0" and mcc and mnc and rssi then
--如果是第一条,清除信息表
resetCellInfo()
curCellSeted = true
--保存mcc、mnc、lac、ci、rssi、ta
cellinfo[1].mcc = mcc
cellinfo[1].mnc = mnc
cellinfo[1].lac = tonumber(lac)
cellinfo[1].ci = tonumber(ci)
cellinfo[1].rssi = tonumber(rssi)
--产生一个内部消息CELL_INFO_IND表示读取到了新的当前小区和临近小区信息
if multicellcb then multicellcb(cellinfo) end
publish("CELL_INFO_IND", cellinfo)
end
end
end
--[[
函数名UpdNetMode
功能 解析NetMode
参数 dataNetMode信息字符串例如"^MODE: 17,17"
返回值:无
]]
function UpdNetMode(data)
local _, _, SysMainMode,SysMode = string.find(data, "(%d+),(%d+)")
local netMode_cur
log.info("net.UpdNetMode",netMode_cur,netMode, SysMainMode,SysMode)
if SysMainMode and SysMode then
if SysMainMode=="3" then
netMode_cur = NetMode_GSM
elseif SysMainMode=="5" then
netMode_cur = NetMode_WCDMA
elseif SysMainMode=="15" then
netMode_cur = NetMode_TD
elseif SysMainMode=="17" then
netMode_cur = NetMode_LTE
else
netMode_cur = NetMode_noNet
end
if SysMode=="3" then
netMode_cur = NetMode_EDGE
end
end
if netMode ~= netMode_cur then
netMode = netMode_cur
publish("NET_UPD_NET_MODE",netMode)
log.info("net.NET_UPD_NET_MODE",netMode)
ril.request("AT+COPS?")
if netMode == NetMode_LTE then
ril.request("AT+CEREG?")
elseif netMode == NetMode_noNet then
ril.request("AT+CREG?")
else
ril.request("AT+CGREG?")
end
end
end
--[[
函数名neturc
功能 本功能模块内“注册的底层core通过虚拟串口主动上报的通知”的处理
参数
data通知的完整字符串信息
prefix通知的前缀
返回值:无
]]
local function neturc(data, prefix)
if prefix=="+COPS" then
cops(data)
elseif prefix == "+CREG" or prefix == "+CGREG" or prefix == "+CEREG" then
--收到网络状态变化时,更新一下信号值
csqQueryPoll()
--解析creg信息
creg(data)
elseif prefix == "+EEMLTESVC" or prefix == "+EEMLTEINTRA" or prefix == "+EEMLTEINTER" or prefix=="+EEMLTEINTERRAT" then
eemLteSvc(data)
elseif prefix == "+EEMUMTSSVC" then
eemUMTSInfoSvc(data)
elseif prefix == "+EEMGINFOSVC" then
eemGsmInfoSvc(data)
elseif prefix == "+EEMGINFONC" then
eemGsmNCInfoSvc(data)
elseif prefix == "^MODE" then
UpdNetMode(data)
end
end
--- 设置飞行模式
-- 注意:如果要测试飞行模式的功耗,开机后不要立即调用此接口进入飞行模式
-- 在模块注册上网络之前,调用此接口进入飞行模式不仅无效,还会导致功耗数据异常
-- 详情参考http://doc.openluat.com/article/488/0
-- @bool mode true:飞行模式开false:飞行模式关
-- @return nil
-- @usage net.switchFly(mode)
function switchFly(mode)
if flyMode == mode then return end
flyMode = mode
-- 处理飞行模式
if mode then
ril.request("AT+CFUN=0")
-- 处理退出飞行模式
else
ril.request("AT+CFUN=1")
--处理查询定时器
csqQueryPoll()
cengQueryPoll()
--复位GSM网络状态
neturc("2", "+CREG")
end
end
--- 获取netmode
-- @return number netMode,注册的网络类型
-- 0未注册
-- 12G GSM网络
-- 22.5G EDGE数据网络
-- 33G TD网络
-- 44G LTE网络
-- 53G WCDMA网络
-- @usage net.getNetMode()
function getNetMode()
return netMode
end
--- 获取网络注册状态
-- @return string state,GSM网络注册状态
-- "INIT"表示正在初始化
-- "REGISTERED"表示已注册
-- "UNREGISTER"表示未注册
-- @usage net.getState()
function getState()
return state
end
--- 获取当前小区的mcc
-- @return string mcc,当前小区的mcc如果还没有注册GSM网络则返回sim卡的mcc
-- @usage net.getMcc()
function getMcc()
return cellinfo[1].mcc and string.format("%x",cellinfo[1].mcc) or sim.getMcc()
end
--- 获取当前小区的mnc
-- @return string mcn,当前小区的mnc如果还没有注册GSM网络则返回sim卡的mnc
-- @usage net.getMnc()
function getMnc()
return cellinfo[1].mnc and string.format("%x",cellinfo[1].mnc) or sim.getMnc()
end
--- 获取当前位置区ID
-- @return string lac,当前位置区ID(16进制字符串例如"18be")如果还没有注册GSM网络则返回""
-- @usage net.getLac()
function getLac()
return lac
end
--- 获取当前注册的网络频段
-- @return string band,当前注册的网络频段,如果还没有注册网络,则返回""
-- @usage net.getBand()
function getBand()
return band
end
--- 获取当前小区ID
-- @return string ci,当前小区ID(16进制字符串例如"93e1")如果还没有注册GSM网络则返回""
-- @usage net.getCi()
function getCi()
return ci
end
--- 获取信号强度
-- 当前注册的是2G网络就是2G网络的信号强度
-- 当前注册的是4G网络就是4G网络的信号强度
-- @return number rssi,当前信号强度(取值范围0-31)
-- @usage net.getRssi()
function getRssi()
return rssi
end
--- 4G网络信号接收功率
-- @return number rsrp,当前信号接收功率(取值范围-140 - -40)
-- @usage net.getRsrp()
function getRsrp()
return rsrp
end
function getCell()
local i,ret = 1,""
for i=1,cellinfo.cnt do
if cellinfo[i] and cellinfo[i].lac and cellinfo[i].lac ~= 0 and cellinfo[i].ci and cellinfo[i].ci ~= 0 then
ret = ret..cellinfo[i].ci.."."..cellinfo[i].rssi.."."
end
end
return ret
end
--- 获取当前和临近位置区、小区以及信号强度的拼接字符串
-- @return string cellInfo,当前和临近位置区、小区以及信号强度的拼接字符串,例如:"6311.49234.30;6311.49233.23;6322.49232.18;"
-- @usage net.getCellInfo()
function getCellInfo()
local i, ret = 1, ""
for i = 1, cellinfo.cnt do
if cellinfo[i] and cellinfo[i].lac and cellinfo[i].lac ~= 0 and cellinfo[i].ci and cellinfo[i].ci ~= 0 then
ret = ret .. cellinfo[i].lac .. "." .. cellinfo[i].ci .. "." .. cellinfo[i].rssi .. ";"
end
end
return ret
end
--- 获取当前和临近位置区、小区、mcc、mnc、以及信号的拼接字符串
-- @bool[opt=nil] rssi 信号是否拼接功率true表示功率false或者nil表示强度
-- 表示强度时信号的取值范围是0到31
-- 表示功率时,信号的计算公式为 强度*2-113取值范围为-113dB到-51dB
-- @return string cellInfo,当前和临近位置区、小区、mcc、mnc、以及信号的拼接字符串例如
-- 当rssi参数为true时"460.01.6311.49234.-73;460.01.6311.49233.-67;460.02.6322.49232.-77;"
-- 当rssi参数为false或者nil时"460.01.6311.49234.30;460.01.6311.49233.23;460.02.6322.49232.18;"
-- @usage net.getCellInfoExt()
function getCellInfoExt(rssi)
local i, ret = 1, ""
for i = 1, cellinfo.cnt do
if cellinfo[i] and cellinfo[i].mcc and cellinfo[i].mnc and cellinfo[i].lac and cellinfo[i].lac ~= 0 and cellinfo[i].ci and cellinfo[i].ci ~= 0 then
ret = ret .. string.format("%x",cellinfo[i].mcc) .. "." .. string.format("%x",cellinfo[i].mnc) .. "." .. cellinfo[i].lac .. "." .. cellinfo[i].ci .. "." .. (rssi and (cellinfo[i].rssi*2-113) or cellinfo[i].rssi) .. ";"
end
end
return ret
end
--- 获取TA值
-- @return number ta,TA值
-- @usage net.getTa()
function getTa()
return cellinfo[1].ta
end
--[[
函数名rsp
功能 本功能模块内“通过虚拟串口发送到底层core软件的AT命令”的应答处理
参数
cmd此应答对应的AT命令
successAT命令执行结果true或者false
responseAT命令的应答中的执行结果字符串
intermediateAT命令的应答中的中间信息
返回值:无
]]
local function rsp(cmd, success, response, intermediate)
local prefix = string.match(cmd, "AT(%+%u+)")
if intermediate ~= nil then
if prefix == "+CSQ" then
local s = string.match(intermediate, "+CSQ:%s*(%d+)")
if s ~= nil then
rssi = tonumber(s)
rssi = rssi == 99 and 0 or rssi
--产生一个内部消息GSM_SIGNAL_REPORT_IND表示读取到了信号强度
publish("GSM_SIGNAL_REPORT_IND", success, rssi)
end
elseif prefix == "+CESQ" then
local s = string.match(intermediate, "+CESQ: %d+,%d+,%d+,%d+,%d+,(%d+)")
if s ~= nil then
rsrp = tonumber(s)
end
elseif prefix == "+CENG" then end
end
if prefix == "+CFUN" then
if success then publish("FLYMODE", flyMode) end
end
end
--- 实时读取“当前和临近小区信息”
-- @function cbFnc 回调函数,当读取到小区信息后,会调用此回调函数,回调函数的调用形式为:
-- cbFnc(cells)其中cells为string类型格式为当前和临近位置区、小区、mcc、mnc、以及信号强度的拼接字符串例如"460.01.6311.49234.30;460.01.6311.49233.23;460.02.6322.49232.18;"
-- @return nil
function getMultiCell(cbFnc)
multicellcb = cbFnc
--发送AT+CENG?查询
ril.request("AT+EEMGINFO?")
end
--- 发起查询基站信息(当前和临近小区信息)的请求
-- @number period 查询间隔,单位毫秒
-- @return bool result, true:查询成功false:查询失败
-- @usage net.cengQueryPoll() --查询1次
-- @usage net.cengQueryPoll(60000) --每分钟查询1次
function cengQueryPoll(period)
-- 不是飞行模式 并且 工作模式为完整模式
if not flyMode then
--发送AT+CENG?查询
ril.request("AT+EEMGINFO?")
else
log.warn("net.cengQueryPoll", "flymode:", flyMode)
end
if nil ~= period then
--启动定时器
sys.timerStopAll(cengQueryPoll)
sys.timerStart(cengQueryPoll, period, period)
end
return not flyMode
end
--- 发起查询信号强度的请求
-- @number period 查询间隔,单位毫秒
-- @return bool , true:查询成功false:查询停止
-- @usage net.csqQueryPoll() --查询1次
-- @usage net.csqQueryPoll(60000) --每分钟查询1次
function csqQueryPoll(period)
--不是飞行模式 并且 工作模式为完整模式
if not flyMode then
--发送AT+CSQ查询
ril.request("AT+CSQ")
ril.request("AT+CESQ")
else
log.warn("net.csqQueryPoll", "flymode:", flyMode)
end
if nil ~= period then
--启动定时器
sys.timerStopAll(csqQueryPoll)
sys.timerStart(csqQueryPoll, period, period)
end
return not flyMode
end
--- 设置查询信号强度和基站信息的间隔
-- @number ... 查询周期,参数可变参数为nil只查询1次参数1是信号强度查询周期参数2是基站查询周期
-- @return bool true设置成功false设置失败
-- @usage net.startQueryAll()
-- @usage net.startQueryAll(60000) -- 1分钟查询1次信号强度只立即查询1次基站信息
-- @usage net.startQueryAll(60000,600000) -- 1分钟查询1次信号强度10分钟查询1次基站信息
function startQueryAll(...)
local arg = { ... }
csqQueryPoll(arg[1])
cengQueryPoll(arg[2])
if flyMode then
log.info("sim.startQuerAll", "flyMode:", flyMode)
end
return true
end
--- 停止查询信号强度和基站信息
-- @return 无
-- @usage net.stopQueryAll()
function stopQueryAll()
sys.timerStopAll(csqQueryPoll)
sys.timerStopAll(cengQueryPoll)
end
local sEngMode
--- 设置工程模式
-- @number[opt=1] mode 工程模式目前仅支持0和1
-- mode为0时不支持临近小区查询休眠时功耗较低
-- mode为1时支持临近小区查询但是休眠时功耗较高
-- @return nil
-- @usage
-- net.setEngMode(0)
function setEngMode(mode)
sEngMode = mode or 1
ril.request("AT+EEMOPT="..sEngMode,nil,function(cmd,success)
function retrySetEngMode()
setEngMode(sEngMode)
end
if success then
sys.timerStop(retrySetEngMode)
else
sys.timerStart(retrySetEngMode,3000)
end
end)
end
-- 处理SIM卡状态消息SIM卡工作不正常时更新网络状态为未注册
sys.subscribe("SIM_IND", function(para)
log.info("SIM.subscribe", simerrsta, para)
if simerrsta ~= (para ~= "RDY") then
simerrsta = (para ~= "RDY")
end
--sim卡工作不正常
if para ~= "RDY" then
--更新GSM网络状态
state = "UNREGISTER"
--产生内部消息NET_STATE_CHANGED表示网络状态发生变化
publish("NET_STATE_UNREGISTER")
else
--state = "INIT"
end
end)
--注册+CREG和+CENG通知的处理函数
ril.regUrc("+COPS", neturc)
ril.regUrc("+CREG", neturc)
ril.regUrc("+CGREG", neturc)
ril.regUrc("+CEREG", neturc)
--ril.regUrc("+CENG", neturc)
ril.regUrc("+EEMLTESVC", neturc)
ril.regUrc("+EEMLTEINTER", neturc)
ril.regUrc("+EEMLTEINTRA", neturc)
ril.regUrc("+EEMLTEINTERRAT", neturc)
ril.regUrc("+EEMGINFOSVC", neturc)
ril.regUrc("+EEMGINFONC", neturc)
ril.regUrc("+EEMUMTSSVC", neturc)
ril.regUrc("^MODE", neturc)
--ril.regUrc("+CRSM", neturc)
--注册AT+CCSQ和AT+CENG?命令的应答处理函数
ril.regRsp("+CSQ", rsp)
ril.regRsp("+CESQ",rsp)
--ril.regRsp("+CENG", rsp)
ril.regRsp("+CFUN", rsp)-- 飞行模式
--发送AT命令
ril.request("AT+COPS?")
ril.request("AT+CREG=2")
ril.request("AT+CGREG=2")
ril.request("AT+CEREG=2")
ril.request("AT+CREG?")
ril.request("AT+CGREG?")
ril.request("AT+CEREG?")
ril.request("AT+CALIBINFO?")
ril.request("AT*BAND?")
setEngMode(1)
--重置当前小区和临近小区信息表
resetCellInfo()

183
4G/code/lib/netLed.lua Normal file
View File

@@ -0,0 +1,183 @@
--- 模块功能:网络指示灯模块
-- @module netLed
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.14
module(..., package.seeall)
require "pins"
require "sim"
--SIM卡状态true为异常false或者nil为正常
local simError
--是否处于飞行模式true为是false或者nil为否
local flyMode
--是否注册上GSM网络true为是false或者nil为否
local gsmRegistered
--是否附着上GPRS网络true为是false或者nil为否
local gprsAttached
--是否有socket连接上后台true为是false或者nil为否
local socketConnected
--网络指示灯表示的工作状态
--NULL功能关闭状态
--FLYMODE飞行模式
--SIMERR未检测到SIM卡或者SIM卡锁pin码等SIM卡异常
--IDLE未注册GSM网络
--GSM已注册GSM网络
--GPRS已附着GPRS数据网络
--SCKsocket已连接上后台
local ledState = "NULL"
local ON,OFF = 1,2
--各种工作状态下配置的点亮、熄灭时长(单位毫秒)
local ledBlinkTime =
{
NULL = {0,0xFFFF}, --常灭
FLYMODE = {0,0xFFFF}, --常灭
SIMERR = {300,5700}, --亮300毫秒灭5700毫秒
IDLE = {300,3700}, --亮300毫秒灭3700毫秒
GSM = {300,1700}, --亮300毫秒灭1700毫秒
GPRS = {300,700}, --亮300毫秒灭700毫秒
SCK = {100,100}, --亮100毫秒灭100毫秒
}
--网络指示灯开关true为打开false或者nil为关闭
local ledSwitch = false
--网络指示灯默认PIN脚GPIO64
local LEDPIN = pio.P2_0
--LTE指示灯开关true为打开false或者nil为关闭
local lteSwitch = false
--LTE指示灯默认PIN脚GPIO65
local LTEPIN = pio.P2_1
--[[
-- 模块功能:更新网络指示灯表示的工作状态
-- 参数:无
-- 返回值:无
--]]
local function updateState()
--log.info("netLed.updateState",ledSwitch,ledState,flyMode,simError,gsmRegistered,gprsAttached,socketConnected)
if ledSwitch then
local newState = "IDLE"
if flyMode then
newState = "FLYMODE"
elseif simError then
newState = "SIMERR"
elseif socketConnected then
newState = "SCK"
elseif gprsAttached then
newState = "GPRS"
elseif gsmRegistered then
newState = "GSM"
end
--指示灯状态发生变化
if newState~=ledState then
ledState = newState
sys.publish("NET_LED_UPDATE")
end
end
end
--[[
-- 模块功能:网络指示灯模块的运行任务
-- 参数:
ledPinSetFunc指示灯GPIO的设置函数
-- 返回值:无
--]]
local function taskLed(ledPinSetFunc)
while true do
--log.info("netLed.taskLed",ledPinSetFunc,ledSwitch,ledState)
if ledSwitch then
local onTime,offTime = ledBlinkTime[ledState][ON],ledBlinkTime[ledState][OFF]
if onTime>0 then
ledPinSetFunc(1)
if not sys.waitUntil("NET_LED_UPDATE", onTime) then
if offTime>0 then
ledPinSetFunc(0)
sys.waitUntil("NET_LED_UPDATE", offTime)
end
end
else if offTime>0 then
ledPinSetFunc(0)
sys.waitUntil("NET_LED_UPDATE", offTime)
end
end
else
ledPinSetFunc(0)
break
end
end
end
--[[
-- 模块功能LTE指示灯模块的运行任务
-- 参数:
ledPinSetFunc指示灯GPIO的设置函数
-- 返回值:无
--]]
local function taskLte(ledPinSetFunc)
while true do
local _,arg = sys.waitUntil("LTE_LED_UPDATE")
if lteSwitch then
ledPinSetFunc(arg and 1 or 0)
end
end
end
--- 配置网络指示灯和LTE指示灯并且立即执行配置后的动作
-- @bool flag 是否打开网络指示灯和LTE指示灯功能true为打开false为关闭
-- @number ledPin 控制网络指示灯闪烁的GPIO引脚例如pio.P0_1表示GPIO1
-- @number ltePin 控制LTE指示灯闪烁的GPIO引脚例如pio.P0_4表示GPIO4
-- @return nil
-- @usage setup(true,pio.P0_1,pio.P0_4)表示打开网络指示灯和LTE指示灯功能GPIO1控制网络指示灯GPIO4控制LTE指示灯
-- @usage setup(false)表示关闭网络指示灯和LTE指示灯功能
function setup(flag,ledPin,ltePin)
--log.info("netLed.setup",flag,pin,ledSwitch)
local oldSwitch = ledSwitch
if flag~=ledSwitch then
ledSwitch = flag
sys.publish("NET_LED_UPDATE")
end
if flag and not oldSwitch then
sys.taskInit(taskLed, pins.setup(ledPin or LEDPIN, 0))
end
if flag~=lteSwitch then
lteSwitch = flag
end
if flag and ltePin and not oldSwitch then
sys.taskInit(taskLte, pins.setup(ltePin, 0))
end
end
--- 配置某种工作状态下指示灯点亮和熄灭的时长如果用户不配置使用netLed.lua中ledBlinkTime配置的默认值
-- @string state 某种工作状态,仅支持"FLYMODE"、"SIMERR"、"IDLE"、"GSM"、"GPRS"、"SCK"
-- @number on 指示灯点亮时长单位毫秒0xFFFF表示常亮0表示常灭
-- @number off 指示灯熄灭时长单位毫秒0xFFFF表示常灭0表示常亮
-- @return nil
-- @usage updateBlinkTime("FLYMODE",1000,500)表示飞行模式工作状态下指示灯闪烁规律为亮1秒灭0.5秒
-- @usage updateBlinkTime("SCK",0xFFFF,0)表示有socket连接上后台的工作状态下指示灯闪烁规律为常亮
-- @usage updateBlinkTime("SIMERR",0,0xFFFF)表示SIM卡异常状态下指示灯闪烁规律为常灭
function updateBlinkTime(state,on,off)
if not ledBlinkTime[state] then log.error("netLed.updateBlinkTime") return end
local updated
if on and ledBlinkTime[state][ON]~=on then
ledBlinkTime[state][ON] = on
updated = true
end
if off and ledBlinkTime[state][OFF]~=off then
ledBlinkTime[state][OFF] = off
updated = true
end
--log.info("netLed.updateBlinkTime",state,on,off,updated)
if updated then sys.publish("NET_LED_UPDATE") end
end
sys.subscribe("FLYMODE", function(mode) if flyMode~=mode then flyMode=mode updateState() end end)
sys.subscribe("SIM_IND", function(para) if simError~=(para~="RDY") then simError=(para~="RDY") updateState() end end)
sys.subscribe("NET_STATE_UNREGISTER", function() if gsmRegistered then gsmRegistered=false updateState() end end)
sys.subscribe("NET_STATE_REGISTERED", function() if not gsmRegistered then gsmRegistered=true updateState() end end)
sys.subscribe("GPRS_ATTACH", function(attach) if gprsAttached~=attach then gprsAttached=attach updateState() end end)
sys.subscribe("SOCKET_ACTIVE", function(active) if socketConnected~=active then socketConnected=active updateState() end end)
sys.subscribe("NET_UPD_NET_MODE", function() if lteSwitch then sys.publish("LTE_LED_UPDATE",net.getNetMode()==net.NetMode_LTE) end end)

150
4G/code/lib/ntp.lua Normal file
View File

@@ -0,0 +1,150 @@
--- 模块功能:网络授时.
-- 重要提醒!!!!!!
-- 本功能模块采用多个免费公共的NTP服务器来同步时间
-- 并不能保证任何时间任何地点都能百分百同步到正确的时间
-- 所以,如果用户项目中的业务逻辑严格依赖于时间同步功能
-- 则不要使用使用本功能模块,建议使用自己的应用服务器来同步时间
-- 参考 http://ask.openluat.com/article/912 加深对授时功能的理解
-- @module ntp
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.21
require "misc"
require "socket"
require "utils"
require "log"
local sbyte, ssub = string.byte, string.sub
module(..., package.seeall)
-- NTP服务器域名集合
local timeServer = {
"cn.pool.ntp.org",
"edu.ntp.org.cn",
"cn.ntp.org.cn",
"s2c.time.edu.cn",
"time1.aliyun.com",
"tw.pool.ntp.org",
"0.cn.pool.ntp.org",
"0.tw.pool.ntp.org",
"1.cn.pool.ntp.org",
"1.tw.pool.ntp.org",
"3.cn.pool.ntp.org",
"3.tw.pool.ntp.org",
}
-- 同步超时等待时间
local NTP_TIMEOUT = 8000
-- 同步是否完成标记
local ntpEnd = false
-- 获取NTP服务器地址列表
-- @return table,服务器地址列表
-- @usage local addtable = ntp.getServers()
function getServers()
return timeServer
end
-- 设置NTP服务器地址列表
-- @table st,tab类型服务器地址列表
-- @return 无
-- @usage ntp.getServers({"1edu.ntp.org.cn","cn.ntp.org.cn"})
function setServers(st)
timeServer = st
end
-- NTP同步标志
-- @return bool,NTP的同步状态true为成功,fasle为失败
-- @usage local sta = ntp.isEnd()
function isEnd()
return ntpEnd
end
local sTs,sFnc,sFun
-- 同步时间随机每个NTP服务器尝试1次超时8秒,适用于被任务函数调用
-- @number ts,每隔ts小时同步1次
-- @function fnc,同步成功后回调函数
-- @function fun,同步成功前回调函数
-- @return nil
-- @usage ntp.ntpTime() -- 只同步1次
-- @usage ntp.ntpTime(1) -- 1小时同步1次
-- @usage ntp.ntpTime(nil,fnc) -- 只同步1次同步成功后执行fnc()
-- @usage ntp.ntpTime(24,fnc) -- 24小时同步1次同步成功后执行fnc()
function ntpTime(ts, fnc, fun)
local rc, data, ntim
local sTs,sFnc,sFun = ts or sTs, fnc or sFnc, fun or sFun
ntpEnd = false
while true do
local tUnusedSvr = {}
for i = 1, #timeServer do
tUnusedSvr[i] = timeServer[i]
end
for i = 1, #timeServer do
while not socket.isReady() do sys.waitUntil('IP_READY_IND') end
local c = socket.udp()
local idx = rtos.tick() % #tUnusedSvr + 1
if c:connect(tUnusedSvr[idx], "123") then
if c:send(string.fromHex("E30006EC0000000000000000314E31340000000000000000000000000000000000000000000000000000000000000000")) then
rc, data = c:recv(NTP_TIMEOUT)
if rc and #data == 48 then
ntim = os.date("*t", (sbyte(ssub(data, 41, 41)) - 0x83) * 2 ^ 24 + (sbyte(ssub(data, 42, 42)) - 0xAA) * 2 ^ 16 + (sbyte(ssub(data, 43, 43)) - 0x7E) * 2 ^ 8 + (sbyte(ssub(data, 44, 44)) - 0x80) + 1)
if type(sFun) == "function" then sFun() end
misc.setClock(ntim, sFnc)
ntpEnd = true
c:close()
break
end
end
end
local cnt, n, m = #tUnusedSvr, 1
for m = 1, cnt do
if m ~= idx then
tUnusedSvr[n] = tUnusedSvr[m]
n = n + 1
end
end
tUnusedSvr[cnt] = nil
c:close()
sys.wait(1000)
end
if ntpEnd then
sys.publish("NTP_SUCCEED")
log.info("ntp.timeSync is date:", ntim.year .. "/" .. ntim.month .. "/" .. ntim.day .. "," .. ntim.hour .. ":" .. ntim.min .. ":" .. ntim.sec)
else
log.warn("ntp.timeSync is error!")
end
if sTs == nil or type(sTs) ~= "number" then break end
sys.wait(sTs * 3600 * 1000)
end
end
--- ntp同步时间任务.
-- 重要提醒!!!!!!
-- 本功能模块采用多个免费公共的NTP服务器来同步时间
-- 并不能保证任何时间任何地点都能百分百同步到正确的时间
-- 所以,如果用户项目中的业务逻辑严格依赖于时间同步功能
-- 则不要使用使用本功能模块,建议使用自己的应用服务器来同步时间
-- @number[opt=nil] period 调用本接口会立即同步一次每隔period小时再自动同步1次nil表示仅同步一次
-- @function[opt=nil] fnc 同步结束,设置系统时间后的回调函数,回调函数的调用形式为:
-- fnc(timeresult)
-- time表示设置之后的系统时间table类型例如{year=2017,month=2,day=14,hour=14,min=19,sec=23}
-- result为true表示成功false或者nil为失败
-- @function[opt=nil] fun 同步结束设置系统时间前的回调函数回调函数的调用形式为fun()
-- @return nil
--
-- @usage
-- 立即同步一次(仅同步这一次):
-- ntp.timeSync()
--
-- 立即同步一次之后每隔1小时自动同步一次
-- ntp.timeSync(1)
--
-- 立即同步一次仅同步这一次同步结束后执行fnc(time,result)
-- ntp.timeSync(nil,fnc)
--
-- 立即同步一次之后每隔24小时自动同步一次每次同步结束后执行fnc(time,result)
-- ntp.timeSync(24,fnc)
function timeSync(period, fnc, fun)
sTs,sFnc,sFun = period, fnc, fun
sys.taskInit(ntpTime)
end

345
4G/code/lib/nvm.lua Normal file
View File

@@ -0,0 +1,345 @@
--- 模块功能:参数管理
-- @module nvm
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.11.9
require "log"
module(..., package.seeall)
--实时参数配置存储在paraname文件中
--默认参数配置存储在configname文件中
--para实时参数表
--config默认参数表
paraname, paranamebak = "/nvm_para.lua", "/nvm_para_bak.lua"
para = {}
local libdftconfig, configname, cconfigname, econfigname
local sBurnSave
--[[
函数名serialize
功能 :根据不同的数据类型,按照不同的格式,写格式化后的数据到文件中
参数
pout文件句柄
o数据
返回值:无
]]
local function serialize(pout, o)
if type(o) == "number" then
--number类型直接写原始数据
pout:write(o)
elseif type(o) == "string" then
--string类型原始数据左右各加上双引号写入
pout:write(string.format("%q", o))
elseif type(o) == "boolean" then
--boolean类型转化为string写入
pout:write(tostring(o))
elseif type(o) == "table" then
--table类型加换行大括号中括号双引号写入
pout:write("{\n")
for k, v in pairs(o) do
-- if type(k) == "number" then
-- pout:write(" [" .. k .. "] = ")
-- elseif type(k) == "string" then
-- pout:write(" [\"" .. k .."\"] = ")
-- else
-- error("cannot serialize table key " .. type(o))
-- end
pout:write(" [")
serialize(pout, k)
pout:write("] = ")
serialize(pout, v)
pout:write(",\n")
end
pout:write("}\n")
else
error("cannot serialize a " .. type(o))
end
end
--[[
函数名upd
功能 :更新实时参数表
参数
overide是否用默认参数强制更新实时参数
返回值:无
]]
function upd(overide)
for k, v in pairs(libdftconfig) do
if k ~= "_M" and k ~= "_NAME" and k ~= "_PACKAGE" then
if overide or para[k] == nil then
para[k] = v
end
end
end
end
local function safePcall(file)
local oldPath = package.path
package.path = "/?.lua;".."/?.luae;".."/#lua_user#/?.lua;".."/#lua_user#/?.luae;"..package.path
local result, para = pcall(require, file)
package.path = oldPath
return result, para
end
--[[
函数名load
功能 :初始化参数
参数 :无
返回值如果参数文件异常会恢复出厂设置此时返回false其余情况返回nil
]]
local function load()
local f, fBak, fExist, fBakExist
f = io.open(paraname, "rb")
fBak = io.open(paranamebak, "rb")
if f then
fExist = f:read("*a") ~= ""
f:close()
end
if fBak then
fBakExist = fBak:read("*a") ~= ""
fBak:close()
end
print("load fExist fBakExist", fExist, fBakExist)
local fResult, fBakResult
if fExist then
if sBurnSave then
fResult, para = safePcall(paraname:match("/#lua_user#/(.+)%.lua"))
else
fResult, para = safePcall(paraname:match("/(.+)%.lua"))
end
end
print("load fResult,type(para)", fResult,type(para))
local fError,fBakError
if fResult then
if type(para)=="table" then
os.remove(paranamebak)
upd()
return
else
fError = true
end
end
if fBakExist then
if sBurnSave then
fBakResult, para = safePcall(paranamebak:match("/#lua_user#/(.+)%.lua"))
else
fBakResult, para = safePcall(paranamebak:match("/(.+)%.lua"))
end
end
print("load fBakResult,type(para)", fBakResult,type(para))
if fBakResult then
if type(para)=="table" then
os.remove(paraname)
upd()
return
else
fBakError = true
end
else
para = {}
restore()
end
--两种参数文件都不存在,不算异常,以下算是异常情况:
--1、参数文件存在备份参数文件不存在
-- (1)、参数文件require异常
--2、参数文件不存在备份参数文件存在
-- (1)、备份参数文件require异常
--3、参数文件存在备份参数文件存在
-- (1)、参数文件和备份参数文件require都异常
if fExist and not fBakExist then
if not fResult or fError then return false end
end
if not fExist and fBakExist then
if not fBakResult or fBakError then return false end
end
if fExist and fBakExist then
if (not fResult or fError) and (not fBakResult or fBakError) then return false end
end
end
--[[
函数名save
功能 :保存参数文件
参数
s是否真正保存true保存false或者nil不保存
返回值:无
]]
local function save(s)
if not s then return end
local f = {}
f.write = function(self, s)table.insert(self, s) end
f:write("module(...)\n")
for k, v in pairs(para) do
if k ~= "_M" and k ~= "_NAME" and k ~= "_PACKAGE" then
f:write(k .. " = ")
serialize(f, v)
f:write("\n")
end
end
local fparabak = io.open(paranamebak, 'wb')
fparabak:write(table.concat(f))
fparabak:close()
os.remove(paraname)
os.rename(paranamebak, paraname)
end
--- 初始化参数存储管理模块
-- @string defaultCfgFile 默认参数文件名
-- @bool burnSave 本地烧录时是否保留已有参数true为保留false或者nil为清除
-- 注意:在同一个项目,不同版本中,此参数必须保持前后版本一致
-- @return boolean result如果初始化异常会恢复出厂设置此时返回false其余情况返回nil
-- @usage
-- 初始化参数存储管理模块默认参数文件名为config.lua本地烧录时清除已有的参数
-- nvm.init("config.lua")
-- 初始化参数存储管理模块默认参数文件名为config.lua本地烧录时保留已有的参数
-- nvm.init("config.lua",true)
function init(defaultCfgFile,burnSave)
local f
f, libdftconfig = safePcall(defaultCfgFile:match("(.+)%.lua"))
configname, cconfigname, econfigname = "/lua/" .. defaultCfgFile, "/lua/" .. defaultCfgFile .. "c", "/lua/" .. defaultCfgFile .. "e"
sBurnSave = burnSave
if burnSave then
rtos.make_dir("/#lua_user#")
paraname, paranamebak = "/#lua_user#/nvm_para.lua", "/#lua_user#/nvm_para_bak.lua"
end
--初始化配置文件,从文件中把参数读取到内存中
return load()
end
--- 设置某个参数的值
-- @string k 参数名
-- @param v 参数的新值仅支持number、string、boolean、table类型
-- @param r 设置原因如果传入了非nil的有效参数并且v值和旧值相比发生了改变
-- 会产生一个PARA_CHANGED_IND内部消息携带 k,v,r 3个参数
-- @param s 是否立即写入到文件系统中false不写入其余的都写入
-- @return bool或者nil成功返回true失败返回nil
-- @usage
-- 参数name赋值为Luat立即写入文件系统
-- nvm.set("name","Luat")
--
-- 参数age赋值为12立即写入文件系统
-- 如果旧值不是12会产生一个PARA_CHANGED_IND消息携带 "age",12,"SVR" 3个参数
-- nvm.set("age",12,"SVR")
--
-- 参数class赋值为Class2不写入文件系统
-- nvm.set("class","Class2",nil,false)
--
-- 参数score赋值为{chinese=100,math=99,english=98},立即写入文件系统:
-- nvm.set("score",{chinese=100,math=99,english=98})
--
-- 连续写入4个参数前3个不保存到文件系统中写第4个时一次性全部保存到文件系统中
-- nvm.set("para1",1,nil,false)
-- nvm.set("para2",2,nil,false)
-- nvm.set("para3",3,nil,false)
-- nvm.set("para4",4)
function set(k, v, r, s)
local bchg = true
if type(v) ~= "table" then
bchg = (para[k] ~= v)
end
log.info("nvm.set", bchg, k, v, r, s)
if bchg then
para[k] = v
save(s or s == nil)
if r then sys.publish("PARA_CHANGED_IND", k, v, r) end
end
return true
end
--- 设置某个table类型参数的某一个索引的值
-- @string k table类型的参数名
-- @param kk table类型参数的某一个索引名
-- @param v table类型参数的某一个索引的新值
-- @param r 设置原因如果传入了非nil的有效参数并且v值和旧值相比发生了改变会产生一个TPARA_CHANGED_IND消息携带k,kk,v,r4个参数
-- @param s 是否立即写入到文件系统中false不写入其余的都写入
-- @return bool或者nil成功返回true失败返回nil
-- @usage nvm.sett("score","chinese",100)参数score["chinese"]赋值为100立即写入文件系统
-- @usage nvm.sett("score","chinese",100,"SVR")参数score["chinese"]赋值为100立即写入文件系统
-- 如果旧值不是100会产生一个TPARA_CHANGED_IND消息携带 "score","chinese",100,"SVR" 4个参数
-- @usage nvm.sett("score","chinese",100,nil,false)参数class赋值为Class2不写入文件系统
function sett(k, kk, v, r, s)
local bchg = true
if type(v) ~= "table" then
bchg = (para[k][kk] ~= v)
end
if bchg then
para[k][kk] = v
save(s or s == nil)
if r then sys.publish("TPARA_CHANGED_IND", k, kk, v, r) end
end
return true
end
--- 所有参数立即写入文件系统
-- @return nil
-- @usage nvm.flush()
function flush()
save(true)
end
--- 读取某个参数的值
-- @string k 参数名
-- @return 参数值
-- @usage
-- 读取参数名为name的参数值
-- nameValue = nvm.get("name")
function get(k)
return para[k]
end
--- 读取某个table类型参数的键名对应的值
-- @string k table类型的参数名
-- @param kk table类型参数的键名
-- @usage
-- 有一个table参数为score数据如下
-- score = {chinese=100, math=100, english=95}
-- 读取score中chinese对应的值
-- nvm.gett("score","chinese")
function gett(k, kk)
return para[k][kk]
end
--- 参数恢复出厂设置
-- @return nil
-- @usage nvm.restore()
function restore()
os.remove(paraname)
os.remove(paranamebak)
local fpara, fconfig = io.open(paraname, "wb"), io.open(configname, "rb")
if not fconfig then fconfig = io.open(cconfigname, "rb") end
if not fconfig then fconfig = io.open(econfigname, "rb") end
fpara:write(fconfig:read("*a"))
fpara:close()
fconfig:close()
upd(true)
end
--- 请求删除参数文件.
-- 此接口一般用在远程升级时需要用新的config.lua覆盖原来的参数文件的场景在此场景下远程升级包下载成功后在确定要重启前调用此接口
-- 下次开机执行nvm.init("config.lua")时会用新的config.lua文件自动覆盖参数文件以后再开机就不会自动覆盖了
-- 也就是说"nvm.remove()->重启->nvm.init("config.lua")"是一个仅执行一次的完整操作
-- @return nil
-- @usage nvm.remove()
function remove()
os.remove(paraname)
os.remove(paranamebak)
end

184
4G/code/lib/patch.lua Normal file
View File

@@ -0,0 +1,184 @@
--- 模块功能Lua补丁
-- @module patch
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.21
require"pm"
module(..., package.seeall)
--[[
模块名称Lua自带接口补丁
模块功能补丁某些Lua自带的接口规避调用异常时死机
模块最后修改时间2017.02.14
]]
--保存Lua自带的os.time接口
local oldostime = os.time
--[[
函数名safeostime
功能 封装自定义的os.time接口
参数
t日期表如果没有传入使用系统当前时间
返回值t时间距离1970年1月1日0时0分0秒所经过的秒数
]]
function safeostime(t)
return oldostime(t) or 0
end
--Lua自带的os.time接口指向自定义的safeostime接口
os.time = safeostime
--保存Lua自带的os.date接口
local oldosdate = os.date
--[[
函数名safeosdate
功能 封装自定义的os.date接口
参数
s输出格式
t距离1970年1月1日0时0分0秒所经过的秒数
返回值参考Lua自带的os.date接口说明
]]
function safeosdate(s, t)
if s == "*t" then
return oldosdate(s, t) or {year = 2012,
month = 12,
day = 11,
hour = 10,
min = 9,
sec = 0}
else
return oldosdate(s, t)
end
end
--Lua自带的os.date接口指向自定义的safeosdate接口
os.date = safeosdate
-- 对coroutine.resume加一个修饰器用于捕获协程错误
local rawcoresume = coroutine.resume
coroutine.resume = function(...)
local arg = { ... }
function wrapper(co,...)
local arg = { ... }
if not arg[1] then
local traceBack = debug.traceback(co) or "empty"
traceBack = (traceBack and traceBack~="") and ((arg[2] or "").."\r\n"..traceBack) or (arg[2] or "")
log.error("coroutine.resume",traceBack)
if errDump and type(errDump.appendErr)=="function" then
errDump.appendErr(traceBack)
end
if _G.COROUTINE_ERROR_RESTART then rtos.restart() end
end
return unpack(arg)
end
return wrapper(arg[1],rawcoresume(...))
end
os.clockms = function() return rtos.tick()/16 end
--保存Lua自带的json.decode接口
if json and json.decode then oldjsondecode = json.decode end
--- 封装自定义的json.decode接口
-- @string s json格式的字符串
-- @return table,第一个返回值为解析json字符串后的table
-- @return boole,第二个返回值为解析结果(true表示成功false失败)
-- @return string,第三个返回值可选只有第二个返回值为false时才有意义表示出错信息
local function safeJsonDecode(s)
local result, info = pcall(oldjsondecode, s)
if result then
return info, true
else
return {}, false, info
end
end
--Lua自带的json.decode接口指向自定义的safeJsonDecode接口
if json and json.decode then json.decode = safeJsonDecode end
local oldUartWrite = uart.write
uart.write = function(...)
pm.wake("lib.patch.uart.write")
local result = oldUartWrite(...)
pm.sleep("lib.patch.uart.write")
return result
end
if i2c and i2c.write then
local oldI2cWrite = i2c.write
i2c.write = function(...)
pm.wake("lib.patch.i2c.write")
local result = oldI2cWrite(...)
pm.sleep("lib.patch.i2c.write")
return result
end
end
if i2c and i2c.send then
local oldI2cSend = i2c.send
i2c.send = function(...)
pm.wake("lib.patch.i2c.send")
local result = oldI2cSend(...)
pm.sleep("lib.patch.i2c.send")
return result
end
end
if spi and spi.send then
oldSpiSend = spi.send
spi.send = function(...)
pm.wake("lib.patch.spi.send")
local result = oldSpiSend(...)
pm.sleep("lib.patch.spi.send")
return result
end
end
if spi and spi.send_recv then
oldSpiSendRecv = spi.send_recv
spi.send_recv = function(...)
pm.wake("lib.patch.spi.send_recv")
local result = oldSpiSendRecv(...)
pm.sleep("lib.patch.spi.send_recv")
return result
end
end
if disp and disp.sleep then
oldDispSleep = disp.sleep
disp.sleep = function(...)
pm.wake("lib.patch.disp.sleep")
oldDispSleep(...)
pm.sleep("lib.patch.disp.sleep")
end
end
if io and io.mount then
oldIoMount = io.mount
io.mount = function (...)
pm.wake("lib.patch.io.mount")
local result = oldIoMount(...)
pm.sleep("lib.patch.io.mount")
return result
end
end
local pmdInited
if pmd and pmd.init then
oldPmdInit = pmd.init
pmd.init = function (...)
if not pmdInited then pmdInited = true end
local result = oldPmdInit(...)
return result
end
end
pmd.libScriptInit = function()
if not pmdInited then pmd.init({}) end
end

116
4G/code/lib/pb.lua Normal file
View File

@@ -0,0 +1,116 @@
--- 模块功能:电话簿管理
-- @module pb
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.10
module(..., package.seeall)
require"ril"
local req = ril.request
local storagecb,readcb,writecb,deletecb
local curPb = "SM"
--- 设置电话本存储区域
-- @string storage 存储区域字符串,仅支持"SM"
-- @param cb 设置后的回调函数
--
-- 回调方式为cb(result)result为true表示成功false或者nil表示失败
-- @return 无
-- @usage pb.setStorage(storage,cb)
function setStorage(storage,cb)
if storage=="SM" or storage=="FD" then
storagecb = cb
req("AT+CPBS=\"" .. storage .. "\"" )
end
end
--- 读取一条电话本记录
-- @number index 电话本在存储区的位置
-- @function cb function类型读取后的回调函数
--
-- 回调方式为cb(result,name,number)result为true表示成功false或者nil表示失败name为姓名number为号码
-- @usage pb.read(1,cb)
function read(index,cb)
if index == "" or index == nil then
return false
end
readcb = cb
req("AT+CPBR=" .. index)
end
--- 写入一条电话本记录
-- @number index 电话本在存储区的位置
-- @string name 姓名
-- @string num 号码
-- @function cb functionl类型写入后的回调函数
--
-- 回调方式为cb(result)result为true表示成功false或者nil表示失败
-- @return 无
-- @usage pb.write(1,"zhangsan","13233334444",cb)
function write(index,name,num,cb)
if num == nil or name == nil or index == nil then
return false
end
writecb = cb
req("AT+CPBW=" .. index .. ",\"" .. num .. "\"," .. "129" .. ",\"" .. name .. "\"" )
return true
end
--- 删除一条电话本记录
-- @number index 电话本在存储区的位置
-- @function cb function类型删除后的回调函数
--
-- 回调方式为cb(result)result为true表示成功false或者nil表示失败
-- @return 无
-- @usage pb.delete(1,cb)
function delete(index,cb)
if index == "" or index == nil then
return false
end
deletecb = cb
req("AT+CPBW=" .. index)
return true
end
local function pbrsp(cmd,success,response,intermediate)
local prefix = string.match(cmd,"AT(%+%u+%?*)")
intermediate = intermediate or ""
if prefix == "+CPBR" then
local index = string.match(cmd,"AT%+CPBR%s*=%s*(%d+)")
local num,name = string.match(intermediate,"+CPBR:%s*%d+,\"([#%*%+%d]*)\",%d+,\"(%w*)\"")
num,name = num or "",name or ""
sys.publish("PB_READ_CNF",success,index,num,name)
local cb = readcb
readcb = nil
if cb then cb(success,name,num) return end
elseif prefix == "+CPBW" then
sys.publish("PB_WRITE_CNF",success)
local cb = writecb
writecb = nil
if cb then cb(success) return end
cb = deletecb
deletecb = nil
if cb then cb(success) return end
elseif prefix == "+CPBS?" then
local storage,used,total = string.match(intermediate,"+CPBS:%s*\"(%u+)\",(%d+),(%d+)")
used,total = tonumber(used),tonumber(total)
sys.publish("CPBS_READ_CNF",success,storage,used,total)
elseif prefix == "+CPBS" then
local cb = storagecb
storagecb = nil
if cb then cb(success) return end
end
end
ril.regRsp("+CPBR",pbrsp)
ril.regRsp("+CPBW",pbrsp)
ril.regRsp("+CPBS",pbrsp)
ril.regRsp("+CPBS?",pbrsp)
req("AT+CPBS=\"SM\"")

150
4G/code/lib/pins.lua Normal file
View File

@@ -0,0 +1,150 @@
--- 模块功能GPIO 功能配置包括输入输出IO和上升下降沿中断IO
-- @module pins
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.09.23 11:34
require "sys"
module(..., package.seeall)
local interruptCallbacks = {}
local dirs = {}
--- 配置GPIO模式
-- @number pin GPIOID
-- GPIO 0到GPIO 31表示为pio.P0_0到pio.P0_31
-- GPIO 32到GPIO XX表示为pio.P1_0到pio.P1_(XX-32)例如GPIO33 表示为pio.P1_1
-- GPIO 64到GPIO XX表示为pio.P2_0到pio.P2_(XX-64)例如GPIO65 表示为pio.P2_1
-- @param val number、nil或者function类型
-- 配置为输出模式时为number类型表示默认电平0是低电平1是高电平
-- 配置为输入模式时为nil
-- 配置为中断模式时为function类型表示中断处理函数
-- @param pull numberpio.PULLUP上拉模式。pio.PULLDOWN下拉模式。pio.NOPULL高阻态
-- 如果没有设置此参数,默认的上下拉参考模块的硬件设计说明书
-- @return function
-- 配置为输出模式时返回的函数可以设置IO的电平
-- 配置为输入或者中断模式时返回的函数可以实时获取IO的电平
-- @usage setOutputFnc = pins.setup(pio.P1_1,0)配置GPIO 33输出模式默认输出低电平
--执行setOutputFnc(0)可输出低电平执行setOutputFnc(1)可输出高电平
-- @usage getInputFnc = pins.setup(pio.P1_1,intFnc)配置GPIO33中断模式
-- 产生中断时自动调用intFnc(msg)函数上升沿中断时msg为cpu.INT_GPIO_POSEDGE下降沿中断时msg为cpu.INT_GPIO_NEGEDGE
-- 执行getInputFnc()即可获得当前电平如果是低电平getInputFnc()返回0如果是高电平getInputFnc()返回1
-- @usage getInputFnc = pins.setup(pio.P1_1),配置GPIO33输入模式
--执行getInputFnc()即可获得当前电平如果是低电平getInputFnc()返回0如果是高电平getInputFnc()返回1
-- @usage
--有些GPIO需要打开对应的ldo电压域之后才能正常配置工作电压域和对应的GPIO关系如下
--pmd.ldoset(x,pmd.LDO_VSIM1) -- GPIO 29、30、31
--pmd.ldoset(x,pmd.LDO_VLCD) -- GPIO 0、1、2、3、4
--pmd.ldoset(x,pmd.LDO_VMMC) -- GPIO 24、25、26、27、28
--x=0时关闭LDO
--x=1时LDO输出1.716V
--x=2时LDO输出1.828V
--x=3时LDO输出1.939V
--x=4时LDO输出2.051V
--x=5时LDO输出2.162V
--x=6时LDO输出2.271V
--x=7时LDO输出2.375V
--x=8时LDO输出2.493V
--x=9时LDO输出2.607V
--x=10时LDO输出2.719V
--x=11时LDO输出2.831V
--x=12时LDO输出2.942V
--x=13时LDO输出3.054V
--x=14时LDO输出3.165V
--x=15时LDO输出3.177V
--除了上面列举出的GPIO外其余的GPIO不需要打开特定的电压域可以直接配置工作
function setup(pin, val, pull)
-- 关闭该IO
pio.pin.close(pin)
-- 中断模式配置
if type(val) == "function" then
pio.pin.setdir(pio.INT, pin)
if pull then pio.pin.setpull(pull or pio.PULLUP, pin) end
--注册引脚中断的处理函数
interruptCallbacks[pin] = val
dirs[pin] = false
return function()
return pio.pin.getval(pin)
end
end
-- 输出模式初始化默认配置
if val ~= nil then
dirs[pin] = true
pio.pin.setdir(val == 1 and pio.OUTPUT1 or pio.OUTPUT, pin)
else
-- 输入模式初始化默认配置
dirs[pin] = false
pio.pin.setdir(pio.INPUT, pin)
if pull then pio.pin.setpull(pull or pio.PULLUP, pin) end
end
-- 返回一个自动切换输入输出模式的函数
return function(val)
val = tonumber(val)
if (not val and dirs[pin]) or (val and not dirs[pin]) then
pio.pin.close(pin)
pio.pin.setdir(val and (val == 1 and pio.OUTPUT1 or pio.OUTPUT) or pio.INPUT, pin)
if not val and pull then pio.pin.setpull(pull or pio.PULLUP, pin) end
dirs[pin] = val and true or false
return val or pio.pin.getval(pin)
end
if val then
pio.pin.setval(val, pin)
return val
else
return pio.pin.getval(pin)
end
end
end
--- 关闭GPIO模式
-- @number pin GPIOID
--
-- GPIO 0到GPIO 31表示为pio.P0_0到pio.P0_31
--
-- GPIO 32到GPIO XX表示为pio.P1_0到pio.P1_(XX-32)例如GPIO33 表示为pio.P1_1
-- @usage pins.close(pio.P1_1)关闭GPIO33
function close(pin)
pio.pin.close(pin)
end
rtos.on(rtos.MSG_INT, function(msg)
if interruptCallbacks[msg.int_resnum] == nil then
log.warn('pins.rtos.on', 'warning:rtos.MSG_INT callback nil', msg.int_resnum)
return
end
interruptCallbacks[msg.int_resnum](msg.int_id)
end)
-- 795io引脚复用 宏
IOMUX_GPIO0 = 0
IOMUX_GPIO1 = 1
IOMUX_GPIO2 = 2
IOMUX_GPIO3 = 3
IOMUX_GPIO4 = 4
IOMUX_GPIO5 = 5
IOMUX_GPIO8 = 8
IOMUX_GPIO9 = 9
IOMUX_GPIO10 = 10
IOMUX_GPIO11 = 11
IOMUX_GPIO12 = 12
IOMUX_GPIO13 = 13
IOMUX_GPIO14 = 14
IOMUX_GPIO15 = 15
IOMUX_GPIO18 = 18
IOMUX_GPIO19 = 19
IOMUX_GPIO20 = 20
IOMUX_GPIO21 = 21
IOMUX_GPIO22 = 22
IOMUX_GPIO23 = 23
IOMUX_GPIO29 = 29
IOMUX_GPIO30 = 30
IOMUX_GPIO31 = 31
IOMUX_USART_1 = 57
IOMUX_USART_2 = 58
IOMUX_USART_3 = 59
IOMUX_I2C_2 = 67
IOMUX_I2C_3 = 68

65
4G/code/lib/pm.lua Normal file
View File

@@ -0,0 +1,65 @@
--- 模块功能:休眠管理
-- @module pm
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.10.22
module(..., package.seeall)
--[[
关于休眠这一部分的说明:
目前的休眠处理有两种方式,
一种是底层core内部自动处理例如tcp发送或者接收数据时会自动唤醒发送接收结束后会自动休眠这部分不用lua脚本控制
另一种是lua脚本使用pm.sleep和pm.wake自行控制例如uart连接外围设备uart接收数据前要主动去pm.wake这样才能保证前面接收的数据不出错当不需要通信时调用pm.sleep如果有lcd的项目也是同样道理
不休眠时功耗至少30mA左右
如果不是故意控制的不休眠一定要保证pm.wake("A")了有地方去调用pm.sleep("A")
]]
--唤醒标记表
local tags = {}
--lua应用是否休眠true休眠其余没休眠
local flag = true
--- 某个Lua应用唤醒系统
-- @param tag 一般string类型某个Lua应用的唤醒标记用户自定义
-- @return 无
-- @usage pm.wake(tag)
function wake(tag)
assert(tag and tag ~= nil, "pm.wake tag invalid")
--唤醒表中此唤醒标记位置置1
tags[tag] = 1
--如果lua应用处于休眠状态
if flag == true then
--设置为唤醒状态
flag = false
--调用底层软件接口,真正唤醒系统
pmd.sleep(0)
end
end
--- 某个Lua应用休眠系统
-- @param tag 一般string类型某个Lua应用的唤醒标记用户自定义跟wake中的标记保持一致
-- @return 无
-- @usage pm.sleep(tag)
function sleep(tag)
assert(tag and tag ~= nil, "pm.sleep tag invalid")
--唤醒表中此休眠标记位置置0
tags[tag] = 0
--只要存在任何一个标记唤醒,则不睡眠
for k, v in pairs(tags) do
if v > 0 then
return
end
end
flag = true
--调用底层软件接口,真正休眠系统
pmd.sleep(1)
end
--- pm.isSleep([tag]) 读取某个Lua应用或者全局的休眠状态
-- @param tag 可选参数如果查询某个tag的休眠状态则跟wake中的tag保持一致如果查询全局休眠状态则不需要这个参数
-- @return true休眠其余没休眠
-- @usage
-- pm.isSleep() -- 查询全局休眠状态
-- pm.isSleep('lcd') -- 查询lcd的休眠状态
function isSleep(tag)
return tag and tags[tag] ~= 1 or flag
end

57
4G/code/lib/powerKey.lua Normal file
View File

@@ -0,0 +1,57 @@
--- 模块功能:开机键功能配置
-- @module powerKey
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.06.13
require"sys"
module(..., package.seeall)
--[[
sta按键状态IDLE表示空闲状态PRESSED表示已按下状态LONGPRESSED表示已经长按下状态
longprd长按键判断时长默认3秒按下大于等于3秒再弹起判定为长按键按下后在3秒内弹起判定为短按键
longcb长按键处理函数
shortcb短按键处理函数
]]
local sta,longprd,longcb,shortcb = "IDLE",3000
local function longtimercb()
log.info("keypad.longtimercb")
sta = "LONGPRESSED"
end
local function keyMsg(msg)
log.info("keyMsg",msg.key_matrix_row,msg.key_matrix_col,msg.pressed)
if msg.pressed then
sta = "PRESSED"
sys.timerStart(longtimercb,longprd)
else
sys.timerStop(longtimercb)
if sta=="PRESSED" then
if shortcb then shortcb() end
elseif sta=="LONGPRESSED" then
(longcb or rtos.poweroff)()
end
sta = "IDLE"
end
end
--- 配置开机键长按弹起和短按弹起的功能.
-- 如何定义长按键和短按键例如长按键判断时长为3秒
-- 按下大于等于3秒再弹起判定为长按键
-- 按下后在3秒内弹起判定为短按键
-- @number[opt=3000] longPrd 长按键判断时长,单位毫秒
-- @function[opt=nil] longCb 长按弹起时的回调函数如果为nil使用默认的处理函数会自动关机
-- @function[opt=nil] shortCb 短按弹起时的回调函数
-- @return nil
-- @usage
-- powerKey.setup(nil,longCb,shortCb)
-- powerKey.setup(5000,longCb)
-- powerKey.setup()
function setup(longPrd,longCb,shortCb)
longprd,longcb,shortcb = longPrd or 3000,longCb,shortCb
end
rtos.on(rtos.MSG_KEYPAD,keyMsg)
rtos.init_module(rtos.MOD_KEYPAD,0,0,0)

183
4G/code/lib/record.lua Normal file
View File

@@ -0,0 +1,183 @@
--- 模块功能:录音处理
-- @module record
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.11.23
require "log"
require "ril"
module(..., package.seeall)
local FILE = '/record.amr'
local recordType = "FILE"
local recording,stoping,recordCb,stopCbFnc
--- 开始录音
-- @number seconds 录音时长,单位:秒
-- 流录音模式下如果想长时间录音可以将此参数设置为0x7FFFFFFF相当于录音2147483647秒=24855天
-- @function[opt=nil] cbFnc 录音回调函数:
-- 当type参数为"FILE"时,回调函数的调用形式为:
-- cbFnc(result,size)
-- result录音结果true表示成功false或者nil表示失败
-- sizenumber类型录音文件的大小单位是字节在result为true时才有意义
-- 当type参数为"STREAM"时,回调函数的调用形式为:
-- cbFnc(result,size,tag)
-- result录音结果true表示成功false或者nil表示失败
-- sizenumber类型每次上报的录音数据流的大小单位是字节在result为true时才有意义
-- tagstring类型"STREAM"表示录音数据流通知,"END"表示录音结束
-- @string[opt="FILE"] type 录音模式
-- "FILE"表示文件录音模式录音数据自动保存在文件中录音结束后执行一次cbFnc函数
-- "STREAM"表示流录音模式录音数据保存在内存中每隔一段时间执行一次cbFnc函数去读取录音数据流录音结束后再执行一次cbFnc函数
-- @number[opt=1] quality 录音质量0一般质量1中等质量2高质量3无损质量
-- @number[opt=2] rcdType 录音类型n:1:mic(从麦克风录制)2:voice(录制语音通话,录制的流与上下行通道)3:voice_dual(在poc模式下从麦克风录制)
-- @number[opt=3] format 录音格式1:pcm2:wav3:amrnb4:speex
-- pcm格式录音质量参数无效采样率8000单声道采样精度16 bit5秒钟录音80KB左右
-- wav格式录音质量参数无效比特率128kbps5秒钟录音80KB左右
-- amrnb格式录音质量参数有效
-- 录音质量为0时比特率5.15kbps5秒钟录音3KB多
-- 录音质量为1时比特率6.70kbps5秒钟录音4KB多
-- 录音质量为2时比特率7.95kbps5秒钟录音4KB多
-- 录音质量为3时比特率12.2kbps5秒钟录音7KB多
-- speex格式录音质量参数无效pcm格式128kbps后的压缩格式5秒钟6KB左右
-- @number[opt=nil] streamRptLen 流录音时,每次上报的字节阀值
-- @usage
-- 文件录音模式录音5秒一般质量amrnb格式录音结束后执行cbFnc函数
-- record.start(5,cbFnc)
-- 流录音模式录音5秒一般质量amrnb格式每隔一段时间执行一次cbFnc函数录音结束后再执行一次cbFnc函数
-- record.start(5,cbFnc,"STREAM")
-- 流录音模式录音5秒一般质量amrnb格式每产生500字节的录音数据执行一次cbFnc函数录音结束后再执行一次cbFnc函数
-- record.start(5,cbFnc,"STREAM",nil,nil,500)
function start(seconds, cbFnc, type, quality, rcdType,format, streamRptLen)
if recording or stoping or seconds <= 0 or ((type~="STREAM") and seconds>50) then
log.error('record.start', recording, stoping, seconds)
if cbFnc then cbFnc() end
return
end
delete()
recordType = type or "FILE"
if type=="STREAM" then
--param1: 录音时长 n:单位秒
--param2: 录音质量 n:0一般质量 1中等质量 2高质量 3无损质量
--param3录音类型 n:1:mic 2:voice 3:voice_dual
--param4录音文件类型 n: 1:pcm 2:wav 3:amrnb
audiocore.streamrecord(seconds,quality or 1,rcdType or 1,format or 3,streamRptLen)
else
--param1: 录音保存文件
--param2: 录音时长 n:单位秒
--param3: 录音质量 n:0一般质量 1中等质量 2高质量 3无损质量
--param4录音类型 n:1:mic 2:voice 3:voice_dual
--param5录音文件类型 n: 1:pcm 2:wav 3:amrnb
audiocore.record(FILE,seconds,quality or 1,rcdType or 1,format or 3)
end
log.info("record.start",seconds,recordType,format or 3)
recording = true
recordCb = cbFnc
return true
end
--- 停止录音
-- @function[opt=nil] cbFnc 停止录音的回调函数(停止结果通过此函数通知用户),回调函数的调用形式为:
-- cbFnc(result)
-- resultnumber类型
-- 0表示停止成功
-- 1表示之前已经发送了停止动作请耐心等待停止结果的回调
-- @usage record.stop(cb)
function stop(cbFnc)
if not recording then
if cbFnc then cbFnc(0) end
return
end
if stoping then
if cbFnc then cbFnc(1) end
return
end
stopCbFnc = cbFnc
log.info("record.stop")
audiocore.stoprecord()
stoping = true
end
--- 读取录音文件的完整路径
-- @return string 录音文件的完整路径
-- @usage filePath = record.getFilePath()
function getFilePath()
return FILE
end
--- 读取录音数据
-- @param offset 偏移位置
-- @param len 长度
-- @return data 录音数据
-- @usage data = record.getData(0, 1024)
function getData(offset, len)
local f = io.open(FILE, "rb")
if not f then log.error('record.getData', 'open failed') return "" end
if not f:seek("set", offset) then log.error('record.getData', 'seek failed') f:close() return "" end
local data = f:read(len)
f:close()
log.info("record.getData", data and data:len() or 0)
return data or ""
end
--- 读取录音文件总长度,录音时长
-- @return fileSize 录音文件大小
-- @return duration 录音时长
-- @usage fileSize, duration = record.getSize()
function getSize()
local size,duration = io.fileSize(FILE),0
if size>6 then
duration = ((size-6)-((size-6)%1600))/1600
end
return size, duration
end
--- 删除录音
-- @usage record.delete()
function delete()
log.info("record.delete")
audiocore.deleterecord()
os.remove(FILE)
end
--- 判断是否存在录音
-- @return result true - 有录音 false - 无录音
-- @usage result = record.exists()
function exists()
return io.exists(FILE)
end
--- 是否正在处理录音
-- @return result true - 正在处理 false - 空闲
-- @usage result = record.isBusy()
function isBusy()
return recording or stoping
end
rtos.on(rtos.MSG_RECORD,function(msg)
log.info("record.MSG_RECORD",msg.record_end_ind,msg.record_error_ind,recordType)
--文件录音在回调时可以删除录音buf但是流录音一定要等buf读取完成后再删除
if recordType=="FILE" then audiocore.deleterecord() end
if msg.record_error_ind then
delete()
if recordCb then recordCb(false,0,"END") recordCb = nil end
recording = false
stoping = false
if stopCbFnc then stopCbFnc(0) stopCbFnc=nil end
end
if msg.record_end_ind then
if recordCb then recordCb(true,recordType=="FILE" and io.fileSize(FILE) or 0,"END") recordCb = nil end
recording = false
stoping = false
if stopCbFnc then stopCbFnc(0) stopCbFnc=nil end
end
end)
rtos.on(rtos.MSG_STREAM_RECORD,function(msg)
log.info("record.MSG_STREAM_RECORD",msg.wait_read_len)
if recordCb then recordCb(true,msg.wait_read_len,"STREAM") end
end)

554
4G/code/lib/ril.lua Normal file
View File

@@ -0,0 +1,554 @@
--- 模块功能虚拟串口AT命令交互管理
-- @module ril
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.02.13
require "uart"
require "rtos"
require "sys"
require "log"
module(..., package.seeall)
--加载常用的全局函数至本地
local vwrite = uart.write
local vread = uart.read
--是否为透传模式true为透传模式false或者nil为非透传模式
--默认非透传模式
local transparentmode
--透传模式下,虚拟串口数据接收的处理函数
local rcvfunc
--执行AT命令后1分钟无反馈判定at命令执行失败则重启软件
local TIMEOUT = 60000*3
--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,
["+CESQ"] = 2,
["+CGMM"] = 2,
["+RFTEMPERATURE"] =2,
["+MUID"] = 2,
["+CGSN"] = 1,
["+WISN"] = 4,
["+CIMI"] = 1,
["+ICCID"] = 2,
["+SIMCROSS"] = 2,
["+CGATT"] = 2,
["+CCLK"] = 2,
['+CNUM'] = 3,
--["+ATWMFT"] = 4,
["+CMGR"] = 3,
["+CMGS"] = 2,
["+CPBF"] = 3,
["+CPBR"] = 3,
['+CLCC'] = 3,
["+CTFSGETID"] = 2,
["+CTFSDECRYPT"] = 2,
["+CTFSAUTH"] = 2,
["+CGDATA"] = 10,
["+CIND"] = 2,
--["+CGDCONT"] = 3,
["+CGACT"] = 3,
["+CALIBINFO"] = 4,
["*CALINFO"] = 3,
}
--radioreadyAT命令通道是否准备就绪
--delaying执行完某些AT命令前需要延时一段时间才允许执行这些AT命令此标志表示是否在延时状态
local radioready, delaying = false
--AT命令队列
local cmdqueue = {
"ATE0",
"AT+CMEE=0",
}
--当前正在执行的AT命令,参数,反馈回调,延迟执行时间,命令头,类型,反馈格式
local currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt
--反馈结果,中间信息,结果信息
local result, interdata, respdata
--ril会出现三种情况:
--发送AT命令收到应答
--发送AT命令命令超时没有应答
--底层软件主动上报的通知下文我们简称为urc
--[[
函数名atimeout
功能 发送AT命令命令超时没有应答的处理
参数 :无
返回值:无
]]
local function atimeout()
--重启软件
sys.restart("ril.atimeout_" .. (currcmd or ""))
end
--[[
函数名defrsp
功能 AT命令的默认应答处理。如果没有定义某个AT的应答处理函数则会走到本函数
参数
cmd此应答对应的AT命令
successAT命令执行结果true或者false
responseAT命令的应答中的执行结果字符串
intermediateAT命令的应答中的中间信息
返回值:无
]]
local function defrsp(cmd, success, response, intermediate)
log.info("ril.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 ril.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
local app_rilcb=nil
--[[
函数名setrilcb
功能 AT命令的应答处理(含请求结果码和非请求结果码,返回到应用层)
参数 :无
返回值:无
]]
function setrilcb(cb)
app_rilcb =cb
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)
end
--重置全局变量
currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt = nil
result, interdata, respdata = nil
end
--[[
函数名defurc
功能 urc的默认处理。如果没有定义某个urc的应答处理函数则会走到本函数
参数
dataurc内容
返回值:无
]]
local function defurc(data)
log.info("ril.defurc", data)
end
--urc的处理表
local urctable = {}
setmetatable(urctable, {__index = function() return defurc end})
--- 注册某个urc的处理函数
-- @param prefix urc前缀最前面的连续字符串包含+、大写字符、数字的组合
-- @param handler urc的处理函数
-- @return 无
-- @usage ril.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)
--AT通道准备就绪
if data == "RDY" then
radioready = true
else
local prefix = string.match(data, "([%+%^%*]*[%u%d& ]+)")
--执行prefix的urc处理函数返回数据过滤器
urcfilter = urctable[prefix](data, prefix)
end
end
--[[
函数名procatc
功能 :处理虚拟串口收到的数据
参数
data收到的数据
返回值:无
]]
local function procatc(data)
--if data:match("^%+EEMLTEINTER") or data:match("^%+EEMLTEINTRA") or data:match("^%+EEMUMTSINTER") or data:match("^%+EEMUMTSINTRA") then return end
-- log.info("ril.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 string.find(data, "\r\n", -2) then
data = string.sub(data, 1, -3)
end
--数据为空
if data == "" then
return
end
if data:match("^%+EEMLTEINTER") or data:match("^%+EEMLTEINTRA") or data:match("^%+EEMUMTSINTER") or data:match("^%+EEMUMTSINTRA") then
else
log.info("ril.proatc", data)
end
--当前无命令在执行则判定为urc
if currcmd == nil then
urc(data)
return
end
local isurc = false
--一些特殊的错误信息转化为ERROR统一处理
if data:match("^%+CMS ERROR:") or data:match("^%+CME ERROR:") then
data = "ERROR"
end
--执行成功的应答
if 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("ril.procatc.send", currarg)
vwrite(uart.ATC, currarg, "\026")
else
log.error("error promot cmd:", currcmd)
end
else
--无类型
if cmdtype == NORESULT then
isurc = true
--全数字类型
elseif cmdtype == NUMBERIC then
local numstr = data:match("(%x+)")
if numstr == data then
interdata = data
else
isurc = true
end
--字符串类型
elseif cmdtype == STRING then
--进一步检查格式
if data:match(rspformt or "^.+$") and not data:match("^%+CPIN:") 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
--CGDATA 返回CONNECT或者ERROR
elseif cmdhead == "+CGDATA" then
if string.find(data, "CONNECT") == 1 then
result = true
respdata = data
else
isurc = true
end
else
isurc = true
end
end
--urc处理
if isurc then
urc(data)
--应答处理
elseif result ~= nil then
rsp()
end
end
--是否在读取虚拟串口数据
local readat = false
--[[
函数名getcmd
功能 解析一条AT命令
参数
itemAT命令
返回值当前AT命令的内容
]]
local function getcmd(item)
local cmd, arg, rsp, delay
--命令是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
else
log.info("ril.getcmd", "getpack unknown item")
return
end
--命令前缀
local head = string.match(cmd, "AT([%+%*%^]*%u+)")
if head == nil then
log.error("ril.getcmd", "request error cmd:", cmd)
return
end
--这两个命令必须有参数
if head == "+CMGS" or head == "+CIPSEND" then -- 必须有参数
if arg == nil or arg == "" then
log.error("ril.getcmd", "request error no arg", head)
return
end
end
--赋值全局变量
currcmd = cmd
currarg = arg
currsp = rsp
curdelay = delay
cmdhead = head
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 = nil
item.delay = nil
--设置延迟发送标志
delaying = true
--把命令重新插入命令队列的队首
table.insert(cmdqueue, 1, item)
return
end
if currcmd ~= nil then
break
end
end
--启动AT命令应答超时定时器
sys.timerStart(atimeout, TIMEOUT)
log.info("ril.sendat", currcmd)
--向虚拟串口中发送AT命令
if currcmd:match("^AT%+POC=") then
vwrite(uart.ATC, currcmd .. "\r\n")
else
vwrite(uart.ATC, currcmd .. "\r")
end
end
-- 延时执行某条AT命令的定时器回调
-- @return 无
-- @usage ril.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.ATC, "*l", 0)
if string.len(s) ~= 0 then
if transparentmode then
--透传模式下直接转发数据
rcvfunc(s)
else
--非透传模式下处理收到的数据
procatc(s)
if app_rilcb ~=nil then app_rilcb(s) end
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 ril.request("AT+CENG=1,1")
-- @usage ril.request("AT+CRSM=214,28539,0,0,12,\"64f01064f03064f002fffff\"", nil, crsmResponse)
function request(cmd, arg, onrsp, delay)
if transparentmode then return end
--插入缓冲队列
if arg or onrsp or delay or formt then
table.insert(cmdqueue, {cmd = cmd, arg = arg, rsp = onrsp, delay = delay})
else
table.insert(cmdqueue, cmd)
end
--执行AT命令发送
sendat()
end
--[[
函数名setransparentmode
功能 AT命令通道设置为透传模式
参数
fnc透传模式下虚拟串口数据接收的处理函数
返回值:无
注意:透传模式和非透传模式,只支持开机的第一次设置,不支持中途切换
]]
function setransparentmode(fnc)
transparentmode, rcvfunc = true, fnc
end
--[[
函数名sendtransparentdata
功能 :透传模式下发送数据
参数
data数据
返回值成功返回true失败返回nil
]]
function sendtransparentdata(data)
if not transparentmode then return end
vwrite(uart.ATC, data)
return true
end
--注册“AT命令的虚拟串口数据接收消息”的处理函数
uart.on(uart.ATC, "receive", atcreader)

38
4G/code/lib/scanCode.lua Normal file
View File

@@ -0,0 +1,38 @@
--- 模块功能:扫码.
-- 支持二维码、条形码扫描
-- @module scanCode
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.9.19
require"sys"
module(..., package.seeall)
local sCbFnc
--- 设置扫码请求
-- @function cbFnc 扫码返回或者超时未返回的回调函数,回调函数的调用形式为:
-- cbFnc(result,type,str)
-- resulttrue或者falsetrue表示扫码成功false表示超时失败
-- typestring或者nil类型result为true时表示扫码类型result为false时为nil支持QR-Code和CODE-128
-- strstring或者nil类型result为true时表示扫码结果的字符串result为false时为nil
-- @number[opt=10000] timeout 设置请求后等待扫码结果返回的超时时间单位毫秒默认为10秒
-- @usage
-- scanCode.request(cbFnc)
-- scanCode.request(cbFnc,5000)
function request(cbFnc,timeout)
sCbFnc = cbFnc
sys.timerStart(sCbFnc,timeout or 10000,false)
end
local function zbarMsg(msg)
--log.info("scanCode.zbarMsg",msg.result,sys.timerIsActive(sCbFnc,false))
if msg.result and sys.timerIsActive(sCbFnc,false) then
sys.timerStop(sCbFnc,false)
sCbFnc(true,msg.type,msg.data)
end
end
--注册core上报的rtos.MSG_ZBAR消息的处理函数
rtos.on(rtos.MSG_ZBAR,zbarMsg)

184
4G/code/lib/sim.lua Normal file
View File

@@ -0,0 +1,184 @@
--- 模块功能查询sim卡状态、iccid、imsi、mcc、mnc
-- @module sim
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.02.13
require "ril"
require "sys"
module(..., package.seeall)
local req = ril.request
--sim卡的imsi、sim卡的iccid
local imsi, iccid, status
local sNumber,bQueryNumber = ""
local simCross,setSimCrossCbFnc
--- 获取sim卡的iccid
-- @return string ,返回iccid如果还没有读取出来则返回nil
-- @usage 注意开机lua脚本运行之后会发送at命令去查询iccid所以需要一定时间才能获取到iccid。开机后立即调用此接口基本上返回nil
-- @usage sim.getIccid()
function getIccid()
return iccid
end
--- 获取sim卡的imsi
-- @return string ,返回imsi如果还没有读取出来则返回nil
-- @usage 开机lua脚本运行之后会发送at命令去查询imsi所以需要一定时间才能获取到imsi。开机后立即调用此接口基本上返回nil
-- @usage sim.getImsi()
function getImsi()
return imsi
end
--- 获取sim卡的mcc
-- @return string ,返回值mcc如果还没有读取出来则返回""
-- @usage 注意开机lua脚本运行之后会发送at命令去查询imsi所以需要一定时间才能获取到imsi。开机后立即调用此接口基本上返回""
-- @usage sim.getMcc()
function getMcc()
return (imsi ~= nil and imsi ~= "") and string.sub(imsi, 1, 3) or ""
end
--- 获取sim卡的getmnc
-- @return string ,返回mnc如果还没有读取出来则返回""
-- @usage 注意开机lua脚本运行之后会发送at命令去查询imsi所以需要一定时间才能获取到imsi。开机后立即调用此接口基本上返回""
-- @usage sim.getMnc()
function getMnc()
return (imsi ~= nil and imsi ~= "") and string.sub(imsi, 4, 5) or ""
end
--- 获取sim卡的状态
-- @return bool ,true表示sim卡正常false或者nil表示未检测到卡或者卡异常
-- @usage 开机lua脚本运行之后会发送at命令去查询状态所以需要一定时间才能获取到状态。开机后立即调用此接口基本上返回nil
-- @usage sim.getStatus()
function getStatus()
return status
end
--- 设置“是否打开查询本机号码”的功能
-- @bool flag 开启或者关闭查询功能的标志false或者nil为关闭其余为开启
-- @return nil
-- @usage sim.setQueryNumber(true)
function setQueryNumber(flag)
bQueryNumber = flag
end
--- 获取sim卡的本机号码
-- @return string ,返回值sNumber如果还没有读取出来或者读取失败则返回""
-- @usage 注意开机lua脚本运行之后会发送at命令去查询本机号码所以需要一定时间才能获取到本机号码。开机后立即调用此接口基本上返回""
-- @usage 注意此功能需要卡商支持卡商必须把卡写到sim卡中模块才能从卡中读出号码目前市场上的很多卡没有写入号码是无法读取得
-- @usage sim.getNumber()
function getNumber()
return sNumber or ""
end
--[[
函数名rsp
功能 本功能模块内“通过虚拟串口发送到底层core软件的AT命令”的应答处理
参数
cmd此应答对应的AT命令
successAT命令执行结果true或者false
responseAT命令的应答中的执行结果字符串
intermediateAT命令的应答中的中间信息
返回值:无
]]
local function rsp(cmd, success, response, intermediate)
if cmd == "AT+ICCID" then
if intermediate then
iccid = string.match(intermediate, "%+ICCID: (.+)")
end
elseif cmd == "AT+SIMCROSS?" then
if success then
simCross = tonumber(intermediate:match("%+SIMCROSS:%s*(%d)"))
end
if setSimCrossCbFnc then setSimCrossCbFnc(success) end
elseif cmd:match("AT%+SIMCROSS=") then
if success then
req("AT+SIMCROSS?")
else
if setSimCrossCbFnc then setSimCrossCbFnc(false) end
end
elseif cmd == "AT+CIMI" then
imsi = intermediate
--产生一个内部消息IMSI_READY通知已经读取imsi
sys.publish("IMSI_READY")
elseif cmd == "AT+CNUM" then
if success then
if intermediate then sNumber = intermediate:match("%+CNUM:%s*\".-\",\"[%+]*(%d+)\",") end
else
sys.timerStart(ril.request,5000,"AT+CNUM")
end
end
end
--[[
函数名urc
-- 功能 本功能模块内“注册的底层core通过虚拟串口主动上报的通知”的处理
参数
data通知的完整字符串信息
prefix通知的前缀
返回值:无
]]
local function urc(data, prefix)
--sim卡状态通知
if prefix == "+CPIN" then
status = false
--sim卡正常
if data == "+CPIN: READY" then
status = true
ril.request("AT+ICCID")
ril.request("AT+CIMI")
if bQueryNumber then ril.request("AT+CNUM") end
sys.publish("SIM_IND", "RDY")
--未检测到sim卡
elseif data == "+CPIN: NOT INSERTED" then
sys.publish("SIM_IND", "NIST")
else
--sim卡pin开启
if data == "+CPIN: SIM PIN" then
sys.publish("SIM_IND","SIM_PIN")
end
sys.publish("SIM_IND", "NORDY")
end
end
end
function set2gSim()
ril.request("AT+MEDCR=0,8,1")
ril.request("AT+MEDCR=0,17,240")
ril.request("AT+MEDCR=0,19,1")
end
--- 设置双卡单待sim id
-- @number id 双卡单待的simid仅支持0和1
-- @function[opt=nil] cbFnc 设置结果回调函数,回调函数的调用形式为:
-- cnFnc(result)result为true表示成功false或者nil为失败
-- @return nil
-- @usage
-- sim.setId(0)
-- sim.setId(1,cbFnc)
function setId(id,cbFnc)
if id ~= simCross then
setSimCrossCbFnc = cbFnc
ril.request("AT+SIMCROSS="..id)
else
if cbFnc then cbFnc(true) end
end
end
--- 获取目前设置的双卡单待id
-- @return number ,返回id(0或者1)如果还没有读取出来则返回nil
-- @usage 注意开机lua脚本运行之后会发送at命令去查询id所以需要一定时间才能获取到id。开机后立即调用此接口基本上返回nil
-- @usage sim.getId()
function getId()
return simCross
end
--注册AT+CCID命令的应答处理函数
ril.regRsp("+ICCID", rsp)
--注册AT+CIMI命令的应答处理函数
ril.regRsp("+CIMI", rsp)
ril.regRsp("+CNUM", rsp)
ril.regRsp("+SIMCROSS", rsp)
--注册+CPIN通知的处理函数
ril.regUrc("+CPIN", urc)
ril.request("AT+SIMCROSS?")

713
4G/code/lib/sms.lua Normal file
View File

@@ -0,0 +1,713 @@
--- 模块功能:短信功能
-- @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 表示第1个字节是0x91第2个字节为0x68......
参数
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 表示第1个字节是0x91第2个字节为0x68...... -> "+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:发送方式选择
返回值true发送成功false发送失败
]]
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短信位置
返回值true读成功false读失败
]]
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短信位置
返回值true删除成功false删除失败
]]
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模式中当使用7位编码时最多可发160个字符
参数 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消息的处理函数异步通知短信发送结果
参数
result短信发送结果true为成功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消息的处理函数异步返回读取的短信内容
参数
result短信读取结果true为成功false或者nil为失败
num短信号码ASCII码字符串格式
data短信内容UCS2大端格式的16进制字符串
pos短信的存储位置暂时没用
datetime短信日期和时间ASCII码字符串格式
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")

121
4G/code/lib/socket.lua Normal file
View File

@@ -0,0 +1,121 @@
--- 模块功能数据链路激活、SOCKET管理(创建、连接、数据收发、状态维护)
-- @module socket
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.25
require "socket4G"
module(..., package.seeall)
--- SOCKET 是否有可用
-- @return 可用true,不可用false
socket.isReady = link.isReady
local tSocketModule = nil
local function init()
tSocketModule = tSocketModule or {
[link.CELLULAR] = socket4G,
[link.CH395] = socketCh395,
[link.W5500] = socketW5500,
[link.ESP8266] = socketESP8266
}
end
--- 创建基于TCP的socket对象
-- @bool[opt=nil] ssl 是否为ssl连接true表示是其余表示否
-- @table[opt=nil] cert ssl连接需要的证书配置只有ssl参数为true时此参数才有意义cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @table[opt=nil] tCoreExtPara 建立链接扩展参数4G链接和ch395链接所需扩展参数不一样
-- @bool[ipv6=nil] ipv6 是否为ipv6连接true表示是其余表示否
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage
-- c = socket.tcp()
-- c = socket.tcp(true)
-- c = socket.tcp(true, {caCert="ca.crt"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
-- c = socket.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
function tcp(ssl, cert, tCoreExtPara, ipv6)
init()
return tSocketModule[link.getNetwork()].tcp(ssl, cert, tCoreExtPara, ipv6)
end
--- 创建基于UDP的socket对象
-- @bool[ipv6=nil] ipv6 是否为ipv6连接true表示是其余表示否
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage c = socket.udp()
function udp(ipv6)
init()
return tSocketModule[link.getNetwork()].udp(ipv6)
end
--- 设置TCP层自动重传的参数
-- @number[opt=4] retryCnt 重传次数取值范围0到12
-- @number[opt=16] retryMaxTimeout 限制每次重传允许的最大超时时间(单位秒)取值范围1到16
-- @return nil
-- @usage
-- setTcpResendPara(3,8)
-- setTcpResendPara(4,16)
function setTcpResendPara(retryCnt, retryMaxTimeout)
init()
return tSocketModule[link.getNetwork()].setTcpResendPara(retryCnt, retryMaxTimeout)
end
--- 设置域名解析参数
-- 注意0027以及之后的core版本才支持此功能
-- @number[opt=4] retryCnt 重传次数取值范围1到8
-- @number[opt=4] retryTimeoutMulti 重传超时时间倍数取值范围1到5
-- 第n次重传超时时间的计算方式为第n次的重传超时基数*retryTimeoutMulti单位为秒
-- 重传超时基数表为{1, 1, 2, 4, 4, 4, 4, 4}
-- 第1次重传超时时间为1*retryTimeoutMulti 秒
-- 第2次重传超时时间为1*retryTimeoutMulti 秒
-- 第3次重传超时时间为2*retryTimeoutMulti 秒
-- ...........................................
-- 第8次重传超时时间为8*retryTimeoutMulti 秒
-- @return nil
-- @usage
-- socket.setDnsParsePara(8,5)
function setDnsParsePara(retryCnt, retryTimeoutMulti)
init()
return tSocketModule[link.getNetwork()].setDnsParsePara(retryCnt, retryTimeoutMulti)
end
--- 打印所有socket的状态
-- @return 无
-- @usage socket.printStatus()
function printStatus()
init()
return tSocketModule[link.getNetwork()].printStatus()
end
--- 设置数据传输后,允许进入休眠状态的延时时长
-- 3024版本以及之后的版本才支持此功能
-- 此功能设置的参数,设置成功后,掉电会自动保存
-- @number tm 数据传输后允许进入休眠状态的延时时长单位为秒取值范围1到20
-- 注意:此时间越短,允许进入休眠状态越快,功耗越低;但是在某些网络环境中,此时间越短,可能会造成数据传输不稳定
-- 建议在可以接受的功耗范围内,此值设置的越大越好
-- 如果没有设置此参数此延时时长是和基站的配置有关一般来说是10秒左右
-- @return nil
-- @usage
-- socket.setLowPower(5)
function setLowPower(tm)
init()
return tSocketModule[link.getNetwork()].setLowPower(tm)
end
--- 设置IP数据流量统计上报间隔
-- 此功能设置的参数,设置成功后,掉电不会保存,每次开机,应用脚本需要重新设置
-- @number interval IP数据流量统计上报间隔单位为秒建议取值不要小于60秒0表示关闭此功能默认为0
-- @return nil
-- @usage
-- socket.setIpStatis(60)
-- 每隔60秒会通过sys.publish("LIB_IP_STATIS_RPT", dataFlow)
-- 应用脚本通过sys.subscribe("LIB_IP_STATIS_RPT", function(dataFlow)
-- --自行处理interval间隔内新增的数据流量dataFlow单位字节
-- end)
function setIpStatis(interval)
init()
return tSocketModule[link.getNetwork()].setIpStatis(interval or 0)
end

680
4G/code/lib/socket4G.lua Normal file
View File

@@ -0,0 +1,680 @@
-- 模块功能数据链路激活、socket4G管理(创建、连接、数据收发、状态维护)
-- @module socket4G
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.25
require "link"
require "utils"
module(..., package.seeall)
local sockets = {}
-- 单次发送数据最大值
local SENDSIZE = 11200
-- 缓冲区最大下标
local INDEX_MAX = 256
-- 是否有socket正处在链接
local socketsConnected = 0
-- ip流量统计间隔每个统计间隔内新增ip流量
local ipStatisInterval, ipDataFlow = 0,0
local function ipDataFlowAdd(flow)
if ipStatisInterval~=0 then
ipDataFlow = ipDataFlow+flow
end
end
-- SOCKET 是否有可用
-- @return 可用true,不可用false
-- socket4G.isReady = link.isReady
local function errorInd(error)
local coSuspended = {}
for _, c in pairs(sockets) do -- IP状态出错时通知所有已连接的socket
c.error = error
--不能打开如下3行代码IP出错时会通知每个socketsocket会主动close
--如果设置了connected=false则主动close时直接退出不会执行close动作导致core中的socket资源没释放
--会引发core中socket耗尽以及socket id重复的问题
--c.connected = false
--socketsConnected = c.connected or socketsConnected
--if error == 'CLOSED' then sys.publish("SOCKET_ACTIVE", socketsConnected) end
if c.co and coroutine.status(c.co) == "suspended" then
--coroutine.resume(c.co, false)
table.insert(coSuspended, c.co)
end
end
for k, v in pairs(coSuspended) do
if v and coroutine.status(v) == "suspended" then
coroutine.resume(v, false, error)
end
end
end
sys.subscribe("IP_ERROR_IND", function()errorInd('IP_ERROR_IND') end)
--sys.subscribe('IP_SHUT_IND', function()errorInd('CLOSED') end)
-- 创建socket函数
local mt = {}
mt.__index = mt
local function socket(protocol, cert, tCoreExtPara,ipv6)
local ssl = protocol:match("SSL")
local co = coroutine.running()
if not co then
log.warn("socket.socket: socket must be called in coroutine")
return nil
end
-- 实例的属性参数表
local o = {
id = nil,
protocol = protocol,
tCoreExtPara = tCoreExtPara,
ssl = ssl,
cert = cert,
co = co,
input = {},
output = {},
wait = "",
connected = false,
iSubscribe = false,
subMessage = nil,
isBlock = false,
msg = nil,
rcvProcFnc = nil,
ipv6 = ipv6,
}
return setmetatable(o, mt)
end
--- 创建基于TCP的socket对象
-- @bool[opt=nil] ssl 是否为ssl连接true表示是其余表示否
-- @table[opt=nil] cert ssl连接需要的证书配置只有ssl参数为true时此参数才有意义cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- insist = 1, --证书中的域名校验失败时是否坚持连接默认为1坚持连接0为不连接
-- hostNameFlag = 0, --服务器域名是否上报默认为0不上报1为上报
-- }
-- @number[opt=nil] tCoreExtPara 建立链接扩展参数
-- @bool[ipv6=nil] ipv6 是否为ipv6连接true表示是其余表示否
-- {
-- rcvBufferSize = "num" --接收缓冲区大小默认为0
-- }
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage
-- c = socket4G.tcp()
-- c = socket4G.tcp(true)
-- c = socket4G.tcp(true, {caCert="ca.crt"})
-- c = socket4G.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
-- c = socket4G.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
function tcp(ssl, cert, tCoreExtPara, ipv6)
return socket("TCP" .. (ssl == true and "SSL" or ""), (ssl == true) and cert or nil, tCoreExtPara, ipv6)
end
--- 创建基于UDP的socket对象
-- @bool[ipv6=nil] ipv6 是否为ipv6连接true表示是其余表示否
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage c = socket4G.udp()
function udp(ipv6)
return socket("UDP", nil, nil, ipv6)
end
--- 连接服务器
-- @string address 服务器地址支持ip和域名
-- @param port string或者number类型服务器端口
-- @number[opt=120] timeout 可选参数,连接超时时间,单位秒
-- @return bool result true - 成功false - 失败
-- @return string ,id '0' -- '8' ,返回通道ID编号
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
function mt:connect(address, port, timeout)
assert(self.co == coroutine.running(), "socket:connect: coroutine mismatch")
if not link.isReady() then
log.info("socket.connect: ip not ready")
return false
end
if self.protocol=="TCP" then
ipDataFlowAdd(120)
elseif self.protocol=="TCPSSL" then
ipDataFlowAdd(800)
end
self.address = address
self.port = port
local tCoreExtPara = self.tCoreExtPara or {}
-- 默认缓冲区大小
local rcvBufferSize = tCoreExtPara.rcvBufferSize or 0
local socket_connect_fnc = nil
if self.ipv6 == true then
socket_connect_fnc = socketcore.ipv6_conn
else
socket_connect_fnc = (type(socketcore.sock_conn_ext)=="function") and socketcore.sock_conn_ext or socketcore.sock_conn
end
if self.protocol == 'TCP' then
self.id = socket_connect_fnc(0, address, port, rcvBufferSize)
elseif self.protocol == 'TCPSSL' then
local cert = {hostName = address}
local insist = 1
local hostNameFlag = 0
if self.cert then
if self.cert.caCert then
if self.cert.caCert:sub(1, 1) ~= "/" then self.cert.caCert = "/lua/" .. self.cert.caCert end
cert.caCert = io.readFile(self.cert.caCert)
end
if self.cert.clientCert then
if self.cert.clientCert:sub(1, 1) ~= "/" then self.cert.clientCert = "/lua/" .. self.cert.clientCert end
cert.clientCert = io.readFile(self.cert.clientCert)
end
if self.cert.clientKey then
if self.cert.clientKey:sub(1, 1) ~= "/" then self.cert.clientKey = "/lua/" .. self.cert.clientKey end
cert.clientKey = io.readFile(self.cert.clientKey)
end
insist = self.cert.insist == 0 and 0 or 1
hostNameFlag = self.cert.hostNameFlag == 1 and 1 or 0
end
self.id = socket_connect_fnc(2, address, port, cert, rcvBufferSize, insist, nil, hostNameFlag)
else
self.id = socket_connect_fnc(1, address, port, rcvBufferSize)
end
if self.ipv6 ~= true and type(socketcore.sock_conn_ext)=="function" then
if not self.id or self.id<0 then
if self.id==-2 then
require "http"
--请求腾讯云免费HttpDns解析
http.request("GET", "119.29.29.29/d?dn=" .. address, nil, nil, nil, 40000,
function(result, statusCode, head, body)
log.info("socket.httpDnsCb", result, statusCode, head, body)
sys.publish("SOCKET_HTTPDNS_RESULT_"..address.."_"..port, result, statusCode, head, body)
end)
local _, result, statusCode, head, body = sys.waitUntil("SOCKET_HTTPDNS_RESULT_"..address.."_"..port)
--DNS解析成功
if result and statusCode == "200" and body and body:match("^[%d%.]+") then
return self:connect(body:match("^([%d%.]+)"),port,timeout)
end
end
self.id = nil
end
end
if not self.id then
log.info("socket:connect: core sock conn error", self.protocol, address, port, self.cert)
return false
end
log.info("socket:connect-coreid,prot,addr,port,cert,timeout", self.id, self.protocol, address, port, self.cert, timeout or 120)
sockets[self.id] = self
self.wait = "SOCKET_CONNECT"
self.timerId = sys.timerStart(coroutine.resume, (timeout or 120) * 1000, self.co, false, "TIMEOUT")
local result, reason = coroutine.yield()
if self.timerId and reason ~= "TIMEOUT" then sys.timerStop(self.timerId) end
if not result then
log.info("socket:connect: connect fail", reason)
if reason == "RESPONSE" then
sockets[self.id] = nil
self.id = nil
end
sys.publish("LIB_SOCKET_CONNECT_FAIL_IND", self.ssl, self.protocol, address, port)
return false
end
log.info("socket:connect: connect ok")
if not self.connected then
self.connected = true
socketsConnected = socketsConnected+1
sys.publish("SOCKET_ACTIVE", socketsConnected>0)
end
return true, self.id
end
--- 异步发送数据
-- @number[opt=nil] keepAlive 服务器和客户端最大通信间隔时间,也叫心跳包最大时间,单位秒
-- @string[opt=nil] pingreq 心跳包的字符串
-- @return boole,false 失败true 表示成功
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- while socketClient:asyncSelect() do end
function mt:asyncSelect(keepAlive, pingreq)
assert(self.co == coroutine.running(), "socket:asyncSelect: coroutine mismatch")
if self.error then
log.warn('socket.client:asyncSelect', 'error', self.error)
return false
end
self.wait = "SOCKET_SEND"
local dataLen = 0
--log.info("socket.asyncSelect #self.output",#self.output)
while #self.output ~= 0 do
local data = table.concat(self.output)
dataLen = string.len(data)
self.output = {}
local sendSize = self.protocol == "UDP" and 1472 or SENDSIZE
for i = 1, dataLen, sendSize do
-- 按最大MTU单元对data分包
if self.ipv6 == true then
socketcore.ipv6_send(self.id, data:sub(i, i + sendSize - 1))
else
socketcore.sock_send(self.id, data:sub(i, i + sendSize - 1))
end
ipDataFlowAdd((data:sub(i, i + sendSize - 1)):len()+(self.protocol == "UDP" and 40 or 80))
if self.timeout then
self.timerId = sys.timerStart(coroutine.resume, self.timeout * 1000, self.co, false, "TIMEOUT")
end
--log.info("socket.asyncSelect self.timeout",self.timeout)
local result, reason = coroutine.yield()
if self.timerId and reason ~= "TIMEOUT" then sys.timerStop(self.timerId) end
sys.publish("SOCKET_ASYNC_SEND", result)
if not result then
sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
--log.warn('socket.asyncSelect', 'send error')
return false
end
end
end
self.wait = "SOCKET_WAIT"
--log.info("socket.asyncSelect",dataLen,self.id)
if dataLen>0 then sys.publish("SOCKET_SEND", self.id, true) end
if keepAlive and keepAlive ~= 0 then
if type(pingreq) == "function" then
sys.timerStart(pingreq, keepAlive * 1000)
else
sys.timerStart(self.asyncSend, keepAlive * 1000, self, pingreq or "\0")
end
end
return coroutine.yield()
end
function mt:getAsyncSend()
if self.error then return 0 end
return #(self.output)
end
--- 异步缓存待发送的数据
-- @string data 数据
-- @number[opt=nil] timeout 可选参数发送超时时间单位秒为nil时表示不支持timeout
-- @return result true - 成功false - 失败
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- socketClient:asyncSend("12345678")
function mt:asyncSend(data, timeout)
if self.error then
log.warn('socket.client:asyncSend', 'error', self.error)
return false
end
self.timeout = timeout
table.insert(self.output, data or "")
--log.info("socket.asyncSend",self.wait)
if self.wait == "SOCKET_WAIT" then coroutine.resume(self.co, true) end
return true
end
--- 异步接收数据
-- @return data 表示接收到的数据(如果是UDP返回最新的一包数据如果是TCP,返回所有收到的数据)
-- ""表示未收到数据
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- data = socketClient:asyncRecv()
function mt:asyncRecv()
if #self.input == 0 then return "" end
if self.protocol == "UDP" then
return table.remove(self.input)
else
local s = table.concat(self.input)
self.input = {}
if self.ipv6 == true then
if self.isBlock then table.insert(self.input, socketcore.ipv6_recv(self.msg.socket_index, self.msg.recv_len)) end
else
if self.isBlock then table.insert(self.input, socketcore.sock_recv(self.msg.socket_index, self.msg.recv_len)) end
end
return s
end
end
--- 同步发送数据
-- @string data 数据
-- 此处传入的数据长度和剩余可用内存有关,只要内存够用,可以随便传入数据
-- 虽然说此处的数据长度没有特别限制但是调用core中的socket发送接口时每次最多发送11200字节的数据
-- 例如此处传入的data长度是112000字节则在这个send接口中会循环10次每次发送11200字节的数据
-- @number[opt=120] timeout 可选参数,发送超时时间,单位秒
-- @return result true - 成功false - 失败
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- socketClient:send("12345678")
function mt:send(data, timeout)
assert(self.co == coroutine.running(), "socket:send: coroutine mismatch")
if self.error then
log.warn('socket.client:send', 'error', self.error)
return false
end
log.debug("socket.send", "total " .. string.len(data or "") .. " bytes", "first 30 bytes", (data or ""):sub(1, 30))
local sendSize = self.protocol == "UDP" and 1472 or SENDSIZE
for i = 1, string.len(data or ""), sendSize do
-- 按最大MTU单元对data分包
self.wait = "SOCKET_SEND"
if self.ipv6 == true then
socketcore.ipv6_send(self.id, data:sub(i, i + sendSize - 1))
else
socketcore.sock_send(self.id, data:sub(i, i + sendSize - 1))
end
ipDataFlowAdd((data:sub(i, i + sendSize - 1)):len()+(self.protocol == "UDP" and 40 or 80))
self.timerId = sys.timerStart(coroutine.resume, (timeout or 120) * 1000, self.co, false, "TIMEOUT")
local result, reason = coroutine.yield()
if self.timerId and reason ~= "TIMEOUT" then sys.timerStop(self.timerId) end
if not result then
log.info("socket:send", "send fail", reason)
sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
return false
end
end
return true
end
--- 同步接收数据
-- @number[opt=0] timeout 可选参数,接收超时时间,单位毫秒
-- @string[opt=nil] msg 可选参数控制socket所在的线程退出recv阻塞状态
-- @bool[opt=nil] msgNoResume 可选参数控制socket所在的线程退出recv阻塞状态
-- false或者nil表示“在recv阻塞状态收到msg消息可以退出阻塞状态”true表示不退出
-- 此参数仅lib内部使用应用脚本不要使用此参数
-- @return result 数据接收结果
-- true表示成功接收到了数据
-- false表示失败没有接收到数据
-- @return data
-- 如果result为truedata表示接收到的数据(如果是UDP返回最新的一包数据如果是TCP,返回所有收到的数据)
-- 如果result为false超时失败data为"timeout"
-- 如果result为falsemsg控制退出data为msg的字符串
-- 如果result为falsesocket连接被动断开控制退出data为"CLOSED"
-- 如果result为falsePDP断开连接控制退出data为"IP_ERROR_IND"
-- @return param 如果是msg控制退出param的值是msg的参数
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- result,data = socketClient:recv(60000,"APP_SOCKET_SEND_DATA")
function mt:recv(timeout, msg, msgNoResume)
assert(self.co == coroutine.running(), "socket:recv: coroutine mismatch")
if self.error then
log.warn('socket.client:recv', 'error', self.error)
return false
end
self.msgNoResume = msgNoResume
if msg and not self.iSubscribe then
self.iSubscribe = msg
self.subMessage = function(data)
--if data then table.insert(self.output, data) end
if self.wait == "+RECEIVE" and not self.msgNoResume then
if data then table.insert(self.output, data) end
coroutine.resume(self.co, 0xAA)
end
end
sys.subscribe(msg, self.subMessage)
end
if msg and #self.output > 0 then sys.publish(msg, false) end
if #self.input == 0 then
self.wait = "+RECEIVE"
if timeout and timeout > 0 then
local r, s = sys.wait(timeout)
if r == nil then
return false, "timeout"
elseif r == 0xAA then
local dat = table.concat(self.output)
self.output = {}
return false, msg, dat
else
return r, s
end
else
local r, s = coroutine.yield()
if r == 0xAA then
local dat = table.concat(self.output)
self.output = {}
return false, msg, dat
else
return r, s
end
end
end
if self.protocol == "UDP" then
local s = table.remove(self.input)
return true, s
else
log.warn("-------------------使用缓冲区---------------")
local s = table.concat(self.input)
self.input = {}
if self.ipv6 == true then
if self.isBlock then table.insert(self.input, socketcore.ipv6_recv(self.msg.socket_index, self.msg.recv_len)) end
else
if self.isBlock then table.insert(self.input, socketcore.sock_recv(self.msg.socket_index, self.msg.recv_len)) end
end
return true, s
end
end
--- 主动关闭并且销毁一个socket
-- @return nil
-- @usage
-- socketClient = socket4G.tcp()
-- socketClient:connect("www.baidu.com","80")
-- socketClient:close()
function mt:close()
assert(self.co == coroutine.running(), "socket:close: coroutine mismatch")
if self.iSubscribe then
sys.unsubscribe(self.iSubscribe, self.subMessage)
self.iSubscribe = false
end
--此处不要再判断状态否则在连接超时失败时conneted状态仍然是未连接会导致无法close
--if self.connected then
log.info("socket:sock_close", self.id)
local result, reason
if self.id then
if self.ipv6 == true then
socketcore.ipv6_close(self.id)
else
socketcore.sock_close(self.id)
end
if self.protocol~="UDP" then ipDataFlowAdd(120) end
self.wait = "SOCKET_CLOSE"
while true do
result, reason = coroutine.yield()
if reason == "RESPONSE" then break end
end
end
if self.connected then
self.connected = false
if socketsConnected>0 then
socketsConnected = socketsConnected-1
end
sys.publish("SOCKET_ACTIVE", socketsConnected>0)
end
if self.input then
self.input = {}
end
--end
if self.id ~= nil then
sockets[self.id] = nil
end
end
-- socket接收自定义控制处理
-- @function[opt=nil] rcvCbFncsocket接收到数据后执行的回调函数回调函数的调用形式为
-- rcvCbFnc(readFnc,socketIndex,rcvDataLen)
-- rcvCbFnc内部会判断是否读取数据如果读取执行readFnc(socketIndex,rcvDataLen)返回true否则返回false或者nil
function mt:setRcvProc(rcvCbFnc)
assert(self.co == coroutine.running(), "socket:setRcvProc: coroutine mismatch")
self.rcvProcFnc = rcvCbFnc
end
local function on_response(msg)
local t = {
[rtos.MSG_SOCK_CLOSE_CNF] = 'SOCKET_CLOSE',
[rtos.MSG_SOCK_SEND_CNF] = 'SOCKET_SEND',
[rtos.MSG_SOCK_CONN_CNF] = 'SOCKET_CONNECT',
}
if not sockets[msg.socket_index] then
log.warn('response on nil socket', msg.socket_index, t[msg.id], msg.result)
return
end
if sockets[msg.socket_index].wait ~= t[msg.id] then
log.warn('response on invalid wait', sockets[msg.socket_index].id, sockets[msg.socket_index].wait, t[msg.id], msg.socket_index)
return
end
log.info("socket:on_response:", msg.socket_index, t[msg.id], msg.result)
if type(socketcore.sock_destroy) == "function" then
if (msg.id == rtos.MSG_SOCK_CONN_CNF and msg.result ~= 0) or msg.id == rtos.MSG_SOCK_CLOSE_CNF then
socketcore.sock_destroy(msg.socket_index)
end
end
coroutine.resume(sockets[msg.socket_index].co, msg.result == 0, "RESPONSE")
end
rtos.on(rtos.MSG_SOCK_CLOSE_CNF, on_response)
rtos.on(rtos.MSG_SOCK_CONN_CNF, on_response)
rtos.on(rtos.MSG_SOCK_SEND_CNF, on_response)
rtos.on(rtos.MSG_SOCK_CLOSE_IND, function(msg)
log.info("socket.rtos.MSG_SOCK_CLOSE_IND")
if not sockets[msg.socket_index] then
log.warn('close ind on nil socket', msg.socket_index, msg.id)
return
end
if sockets[msg.socket_index].connected then
sockets[msg.socket_index].connected = false
if socketsConnected>0 then
socketsConnected = socketsConnected-1
end
sys.publish("SOCKET_ACTIVE", socketsConnected>0)
end
sockets[msg.socket_index].error = 'CLOSED'
--[[
if type(socketcore.sock_destroy) == "function" then
socketcore.sock_destroy(msg.socket_index)
end]]
sys.publish("LIB_SOCKET_CLOSE_IND", sockets[msg.socket_index].ssl, sockets[msg.socket_index].protocol, sockets[msg.socket_index].address, sockets[msg.socket_index].port)
coroutine.resume(sockets[msg.socket_index].co, false, "CLOSED")
end)
rtos.on(rtos.MSG_SOCK_RECV_IND, function(msg)
if not sockets[msg.socket_index] then
log.warn('close ind on nil socket', msg.socket_index, msg.id)
return
end
-- local s = socketcore.sock_recv(msg.socket_index, msg.recv_len)
-- log.debug("socket.recv", "total " .. msg.recv_len .. " bytes", "first " .. 30 .. " bytes", s:sub(1, 30))
log.debug("socket.recv", msg.recv_len, sockets[msg.socket_index].rcvProcFnc)
ipDataFlowAdd(msg.recv_len+(sockets[msg.socket_index].protocol=="UDP" and 40 or 80))
if sockets[msg.socket_index].rcvProcFnc then
if sockets[msg.socket_index].ipv6 == true then
sockets[msg.socket_index].rcvProcFnc(socketcore.ipv6_recv, msg.socket_index, msg.recv_len)
else
sockets[msg.socket_index].rcvProcFnc(socketcore.sock_recv, msg.socket_index, msg.recv_len)
end
else
if sockets[msg.socket_index].wait == "+RECEIVE" then
if sockets[msg.socket_index].ipv6 == true then
coroutine.resume(sockets[msg.socket_index].co, true, socketcore.ipv6_recv(msg.socket_index, msg.recv_len))
else
coroutine.resume(sockets[msg.socket_index].co, true, socketcore.sock_recv(msg.socket_index, msg.recv_len))
end
else -- 数据进缓冲区,缓冲区溢出采用覆盖模式
if #sockets[msg.socket_index].input > INDEX_MAX then
log.error("socket recv", "out of stack", "block")
-- sockets[msg.socket_index].input = {}
sockets[msg.socket_index].isBlock = true
sockets[msg.socket_index].msg = msg
else
sockets[msg.socket_index].isBlock = false
if sockets[msg.socket_index].ipv6 == true then
table.insert(sockets[msg.socket_index].input, socketcore.ipv6_recv(msg.socket_index, msg.recv_len))
else
table.insert(sockets[msg.socket_index].input, socketcore.sock_recv(msg.socket_index, msg.recv_len))
end
end
sys.publish("SOCKET_RECV", msg.socket_index)
end
end
end)
--- 设置TCP层自动重传的参数
-- @number[opt=4] retryCnt 重传次数取值范围0到12
-- @number[opt=16] retryMaxTimeout 限制每次重传允许的最大超时时间(单位秒)取值范围1到16
-- @return nil
-- @usage
-- setTcpResendPara(3,8)
-- setTcpResendPara(4,16)
function setTcpResendPara(retryCnt, retryMaxTimeout)
ril.request("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
end
--- 设置域名解析参数
-- 注意0027以及之后的core版本才支持此功能
-- @number[opt=4] retryCnt 重传次数取值范围1到8
-- @number[opt=4] retryTimeoutMulti 重传超时时间倍数取值范围1到5
-- 第n次重传超时时间的计算方式为第n次的重传超时基数*retryTimeoutMulti单位为秒
-- 重传超时基数表为{1, 1, 2, 4, 4, 4, 4, 4}
-- 第1次重传超时时间为1*retryTimeoutMulti 秒
-- 第2次重传超时时间为1*retryTimeoutMulti 秒
-- 第3次重传超时时间为2*retryTimeoutMulti 秒
-- ...........................................
-- 第8次重传超时时间为8*retryTimeoutMulti 秒
-- @return nil
-- @usage
-- socket4G.setDnsParsePara(8,5)
function setDnsParsePara(retryCnt, retryTimeoutMulti)
ril.request("AT*DNSTMOUT="..(retryCnt or 4)..","..(retryTimeoutMulti or 4))
end
-- 打印所有socket的状态
-- @return 无
-- @usage socket4G.printStatus()
function printStatus()
for _, client in pairs(sockets) do
for k, v in pairs(client) do
log.info('socket.printStatus', 'client', client.id, k, v)
end
end
end
--- 设置数据传输后,允许进入休眠状态的延时时长
-- 3024版本以及之后的版本才支持此功能
-- 此功能设置的参数,设置成功后,掉电会自动保存
-- @number tm 数据传输后允许进入休眠状态的延时时长单位为秒取值范围1到20
-- 注意:此时间越短,允许进入休眠状态越快,功耗越低;但是在某些网络环境中,此时间越短,可能会造成数据传输不稳定
-- 建议在可以接受的功耗范围内,此值设置的越大越好
-- 如果没有设置此参数此延时时长是和基站的配置有关一般来说是10秒左右
-- @return nil
-- @usage
-- socket4G.setLowPower(5)
function setLowPower(tm)
ril.request("AT*RTIME="..tm)
end
local function ipStatisTimerCb()
if ipDataFlow~=0 then
sys.publish("LIB_IP_STATIS_RPT",ipDataFlow)
ipDataFlow = 0
end
end
function setIpStatis(interval)
if ipStatisInterval~=interval then
ipStatisInterval = interval
ipStatisTimerCb()
if interval==0 then
sys.timerStop(ipStatisTimerCb)
else
sys.timerLoopStart(ipStatisTimerCb,interval*1000)
end
end
end
--setDnsParsePara(4,4)
--setTcpResendPara(1,16)

1569
4G/code/lib/socketCh395.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,943 @@
--- 模块功能数据链路激活、socketESP8266管理(创建、连接、数据收发、状态维护)
-- @module socketESP8266
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.25
-- require "link_wifi"
require "utils"
module(..., package.seeall)
local powerCbFnc = nil
-- local ril = ril_wifi
local req = wifiRil.request
local valid = {"3", "2", "1", "0"}
local validSsl = {"3", "2", "1", "0"}
local sockets = {}
local socketsSsl = {}
-- 单次发送数据最大值
local SENDSIZE = 1460
-- 缓冲区最大下标
local INDEX_MAX = 256
-- 用户自定义的DNS解析器
local dnsParser
local dnsParserToken = 0
-- SOCKET 是否有可用
-- @return 可用true,不可用false
isReady = link.isReady
local function isSocketActive(ssl)
for _, c in pairs(ssl and socketsSsl or sockets) do
if c.connected then
return true
end
end
end
local function socketStatusNtfy()
sys.publish("SOCKET_ACTIVE", isSocketActive() or isSocketActive(true))
end
local function stopConnectTimer(tSocket, id)
if id and tSocket[id] and tSocket[id].co and coroutine.status(tSocket[id].co) == "suspended" and (tSocket[id].wait == "+SSLCONNECT" or tSocket[id].wait == "+CIPSTART") then
-- and (tSocket[id].wait == "+SSLCONNECT" or (tSocket[id].protocol == "UDP" and tSocket[id].wait == "+CIPSTART")) then
sys.timerStop(coroutine.resume, tSocket[id].co, false, "TIMEOUT")
end
end
local function errorInd(error)
local coSuspended = {}
for k, v in pairs({sockets, socketsSsl}) do
-- if #v ~= 0 then
for _, c in pairs(v) do -- IP状态出错时通知所有已连接的socket
-- if c.connected or c.created then
if error == 'CLOSED' and not c.ssl then
c.connected = false
socketStatusNtfy()
end
c.error = error
if c.co and coroutine.status(c.co) == "suspended" then
stopConnectTimer(v, c.id)
-- coroutine.resume(c.co, false)
table.insert(coSuspended, c.co)
end
-- end
end
-- end
end
for k, v in pairs(coSuspended) do
if v and coroutine.status(v) == "suspended" then
coroutine.resume(v, false)
end
end
end
sys.subscribe("IP_ERROR_IND", function()
errorInd('IP_ERROR_IND')
end)
sys.subscribe('IP_SHUT_IND', function()
errorInd('CLOSED')
end)
-- 订阅rsp返回的消息处理函数
local function onSocketURC(data, prefix)
local tag, id, result = string.match(data, "([SSL]*)[&]*(%d), *([%u :%d]+)")
tSocket = (tag == "SSL" and socketsSsl or sockets)
if not id or not tSocket[id] then
log.error('socket: urc on nil socket', data, id, tSocket[id], socketsSsl[id])
return
end
if result == "CONNECT" or result:match("CONNECT ERROR") or result:match("CONNECT FAIL") then
if tSocket[id].wait == "+CIPSTART" or tSocket[id].wait == "+SSLCONNECT" then
stopConnectTimer(tSocket, id)
coroutine.resume(tSocket[id].co, result == "CONNECT")
else
log.error("socket: error urc", tSocket[id].wait)
end
return
end
if tag == "SSL" and string.find(result, "ERROR:") == 1 then
return
end
if string.find(result, "ERROR") or result == "CLOSED" then
if result == 'CLOSED' and not tSocket[id].ssl then
tSocket[id].connected = false
socketStatusNtfy()
end
tSocket[id].error = result
stopConnectTimer(tSocket, id)
coroutine.resume(tSocket[id].co, false)
end
end
-- 创建socket函数
local mt = {}
mt.__index = mt
local function socket(protocol, cert)
local ssl = nil-- protocol:match("SSL")
local id = table.remove(ssl and validSsl or valid)
if not id then
log.warn("socket.socket: too many sockets")
return nil
end
local co = coroutine.running()
if not co then
log.warn("socket.socket: socket must be called in coroutine")
return nil
end
-- 实例的属性参数表
local o = {
id = id,
protocol = protocol,
ssl = ssl,
cert = cert,
co = co,
input = {},
output = {},
wait = "",
connected = false,
iSubscribe = false,
subMessage = nil
}
tSocket = (ssl and socketsSsl or sockets)
tSocket[id] = o
if cert then
local tmpPath, result, caConf, certConf, keyConf
if cert.caCert then
result = true
tmpPath = (cert.caCert:sub(1, 1) == "/") and cert.caCert or ("/lua/" .. cert.caCert)
if not io.exists("/at_ca.bin") then
result = false
else
if crypto.md5("/at_ca.bin", "file") ~= crypto.md5(tmpPath, "file") then
result = false
end
end
if not result then
io.writeFile("/at_ca.bin", io.readFile(tmpPath))
req("AT+SYSFLASH=0,\"" .. "client_ca" .. "\",0,8192", nil, nil, nil, {
id = id,
path32 = "client_ca",
path8955 = tmpPath
})
coroutine.yield()
end
caConf = true
end
if cert.clientCert then
result = true
tmpPath = (cert.clientCert:sub(1, 1) == "/") and cert.clientCert or ("/lua/" .. cert.clientCert)
if not io.exists("/at_cert.bin") then
result = false
else
if crypto.md5("/at_cert.bin", "file") ~= crypto.md5(tmpPath, "file") then
result = false
end
end
if not result then
io.writeFile("/at_cert.bin", io.readFile(tmpPath))
req("AT+SYSFLASH=0,\"" .. "client_cert" .. "\",0,8192", nil, nil, nil, {
id = id,
path32 = "client_cert",
path8955 = tmpPath
})
coroutine.yield()
end
certConf = true
end
if cert.clientKey then
result = true
tmpPath = (cert.clientKey:sub(1, 1) == "/") and cert.clientKey or ("/lua/" .. cert.clientKey)
if not io.exists("/at_key.bin") then
result = false
else
if crypto.md5("/at_key.bin", "file") ~= crypto.md5(tmpPath, "file") then
result = false
end
end
if not result then
io.writeFile("/at_key.bin", io.readFile(tmpPath))
req("AT+SYSFLASH=0,\"" .. "client_key" .. "\",0,8192", nil, nil, nil, {
id = id,
path32 = "client_key",
path8955 = tmpPath
})
coroutine.yield()
end
keyConf = true
end
if caConf and not certConf and not keyConf then
req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 2, 0, 0))
elseif not caConf and certConf and not keyConf then
req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 1, 0, 0))
elseif caConf and certConf and keyConf then
req(string.format("AT+CIPSSLCCONF=%d,%d,%d,%d", id, 3, 0, 0))
end
else
req(string.format("AT+CIPSSLCCONF=%d,%d", id, 0))
end
return setmetatable(o, mt)
end
--- 创建基于TCP的socket对象
-- @bool[opt=nil] ssl 是否为ssl连接true表示是其余表示否
-- @table[opt=nil] cert ssl连接需要的证书配置只有ssl参数为true时才参数才有意义cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- }
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage
-- c = socketESP8266.tcp()
-- c = socketESP8266.tcp(true)
-- c = socketESP8266.tcp(true, {caCert="ca.crt"})
-- c = socketESP8266.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key"})
-- c = socketESP8266.tcp(true, {caCert="ca.crt", clientCert="client.crt", clientKey="client.key", clientPassword="123456"})
function tcp(ssl, cert)
return socket((ssl == true and "SSL" or "TCP"), (ssl == true) and cert or nil)
end
--- 创建基于UDP的socket对象
-- @return client创建成功返回socket客户端对象创建失败返回nil
-- @usage c = socketESP8266.udp()
function udp()
return socket("UDP")
end
local sslInited
local tSslInputCert, sSslInputCert = {}, ""
local function sslInit()
if not sslInited then
sslInited = true
req("AT+SSLINIT")
end
local i, item
for i = 1, #tSslInputCert do
item = table.remove(tSslInputCert, 1)
req(item.cmd, item.arg)
end
tSslInputCert = {}
end
local function sslTerm()
if sslInited then
if not isSocketActive(true) then
sSslInputCert, sslInited = ""
req("AT+SSLTERM")
end
end
end
local function sslInputCert(t, f)
if sSslInputCert:match(t .. f .. "&") then
return
end
if not tSslInputCert then
tSslInputCert = {}
end
local s = io.readFile((f:sub(1, 1) == "/") and f or ("/lua/" .. f))
if not s then
log.error("inputcrt err open", path)
return
end
-- table.insert(tSslInputCert, {cmd = "AT+SSLCERT=0,\"" .. t .. "\",\"" .. f .. "\",1," .. s:len(), arg = s or ""})
table.insert(tSslInputCert, {
cmd = "AT+SYSFLASH=0,\"" .. t .. "\",\"" .. f .. "\",1," .. s:len(),
arg = s or ""
})
sSslInputCert = sSslInputCert .. t .. f .. "&"
end
local path32, path8955
--- 连接服务器
-- @string address 服务器地址支持ip和域名
-- @param port string或者number类型服务器端口
-- @return bool result true - 成功false - 失败
-- @number timeout, 链接服务器最长超时时间
-- @usage c = socketESP8266.tcp(); c:connect("www.baidu.com",80,5);
function mt:connect(address, port, timeout)
assert(self.co == coroutine.running(), "socket:connect: coroutine mismatch")
if not link.isReady() then
log.info("socket.connect: ip not ready")
return false
end
if cc and cc.anyCallExist() then
log.info("socket:connect: call exist, cannot connect")
return false
end
self.address = address
self.port = port
if self.cert then
local tConfigCert, i = {}
-- if self.cert then
-- if self.cert.caCert then
-- sslInputCert("cacrt", self.cert.caCert)
-- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"cacrt\",\"" .. self.cert.caCert .. "\"")
-- end
-- if self.cert.clientCert then
-- sslInputCert("localcrt", self.cert.clientCert)
-- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"localcrt\",\"" .. self.cert.clientCert .. "\",\"" .. (self.cert.clientPassword or "") .. "\"")
-- end
-- if self.cert.clientKey then
-- sslInputCert("localprivatekey", self.cert.clientKey)
-- table.insert(tConfigCert, "AT+SSLCERT=1," .. self.id .. ",\"localprivatekey\",\"" .. self.cert.clientKey .. "\"")
-- end
-- end
-- sslInit()
-- req(string.format("AT+SSLCREATE=%d,\"%s\",%d", self.id, address .. ":" .. port, (self.cert and self.cert.caCert) and 0 or 1))
-- self.created = true
-- for i = 1, #tConfigCert do
-- req(tConfigCert[i])
-- end
-- req("AT+SSLCONNECT=" .. self.id)
--
req(string.format("AT+CIPSTART=%d,\"%s\",\"%s\",%s", self.id, "SSL", address, port))
else
req(string.format("AT+CIPSTART=%d,\"%s\",\"%s\",%s", self.id, self.protocol, address, port))
end
-- if self.ssl or self.protocol == "UDP" then sys.timerStart(coroutine.resume, 120000, self.co, false, "TIMEOUT") end
sys.timerStart(coroutine.resume, (timeout or 120) * 1000, self.co, false, "TIMEOUT")
wifiRil.regUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
self.wait = self.ssl and "+SSLCONNECT" or "+CIPSTART"
local r, s = coroutine.yield()
if r == false and s == "DNS" then
if self.ssl then
self:sslDestroy()
self.error = nil
end
require "http"
-- 请求腾讯云免费HttpDns解析
http.request("GET", "119.29.29.29/d?dn=" .. address, nil, nil, nil, 40000, function(result, statusCode, head, body)
log.info("socket.httpDnsCb", result, statusCode, head, body)
sys.publish("SOCKET_HTTPDNS_RESULT_" .. address .. "_" .. port, result, statusCode, head, body)
end)
local _, result, statusCode, head, body = sys.waitUntil("SOCKET_HTTPDNS_RESULT_" .. address .. "_" .. port)
-- DNS解析成功
if result and statusCode == "200" and body and body:match("^[%d%.]+") then
return self:connect(body:match("^([%d%.]+)"), port)
-- DNS解析失败
else
if dnsParser then
dnsParserToken = dnsParserToken + 1
dnsParser(address, dnsParserToken)
local result, ip = sys.waitUntil("USER_DNS_PARSE_RESULT_" .. dnsParserToken, 40000)
if result and ip and ip:match("^[%d%.]+") then
return self:connect(ip:match("^[%d%.]+"), port)
end
end
end
end
if r == false then
if self.ssl then
self:sslDestroy()
end
sys.publish("LIB_SOCKET_CONNECT_FAIL_IND", self.ssl, self.protocol, address, port)
return false
end
self.connected = true
socketStatusNtfy()
return true
end
--- 异步收发选择器
-- @number keepAlive 服务器和客户端最大通信间隔时间,也叫心跳包最大时间,单位秒
-- @string pingreq 心跳包的字符串
-- @return boole,false 失败true 表示成功
function mt:asyncSelect(keepAlive, pingreq)
assert(self.co == coroutine.running(), "socket:asyncSelect: coroutine mismatch")
if self.error then
log.warn('socket.client:asyncSelect', 'error', self.error)
return false
end
self.wait = "SOCKET_SEND"
while #self.output ~= 0 do
local data = table.concat(self.output)
self.output = {}
for i = 1, string.len(data), SENDSIZE do
-- 按最大MTU单元对data分包
local stepData = string.sub(data, i, i + SENDSIZE - 1)
-- 发送AT命令执行数据发送
req(string.format("AT+" .. (self.ssl and "SSL" or "CIP") .. "SEND=%d,%d", self.id, string.len(stepData)), stepData)
self.wait = self.ssl and "+SSLSEND" or "+CIPSEND"
if not coroutine.yield() then
if self.ssl then
self:sslDestroy()
end
sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
return false
end
end
end
self.wait = "SOCKET_WAIT"
sys.publish("SOCKET_SEND", self.id)
if keepAlive and keepAlive ~= 0 then
if type(pingreq) == "function" then
sys.timerStart(pingreq, keepAlive * 1000)
else
sys.timerStart(self.asyncSend, keepAlive * 1000, self, pingreq or "\0")
end
end
return coroutine.yield()
end
--- 异步发送数据
-- @string data 数据
-- @return result true - 成功false - 失败
-- @usage c = socketESP8266.tcp(); c:connect(); c:asyncSend("12345678");
function mt:asyncSend(data)
if self.error then
log.warn('socket.client:asyncSend', 'error', self.error)
return false
end
table.insert(self.output, data or "")
if self.wait == "SOCKET_WAIT" then
coroutine.resume(self.co, true)
end
return true
end
--- 异步接收数据
-- @return nil, 表示没有收到数据
-- @return data 如果是UDP协议返回新的数据包,如果是TCP,返回所有收到的数据,没有数据返回长度为0的空串
-- @usage c = socketESP8266.tcp(); c:connect()
-- @usage data = c:asyncRecv()
function mt:asyncRecv()
if #self.input == 0 then
return ""
end
if self.protocol == "UDP" then
return table.remove(self.input)
else
local s = table.concat(self.input)
self.input = {}
return s
end
end
--- 发送数据
-- @string data 数据
-- @return result true - 成功false - 失败
-- @usage c = socketESP8266.tcp(); c:connect(); c:send("12345678");
function mt:send(data)
assert(self.co == coroutine.running(), "socket:send: coroutine mismatch")
if self.error then
log.warn('socket.client:send', 'error', self.error)
return false
end
if self.id == nil then
log.warn('socket.client:send', 'closed')
return false
end
for i = 1, string.len(data or ""), SENDSIZE do
-- 按最大MTU单元对data分包
local stepData = string.sub(data, i, i + SENDSIZE - 1)
-- 发送AT命令执行数据发送
req(string.format("AT+" .. (self.ssl and "SSL" or "CIP") .. "SEND=%d,%d", self.id, string.len(stepData)), stepData)
self.wait = self.ssl and "+SSLSEND" or "+CIPSEND"
if not coroutine.yield() then
if self.ssl then
self:sslDestroy()
end
sys.publish("LIB_SOCKET_SEND_FAIL_IND", self.ssl, self.protocol, self.address, self.port)
return false
end
end
return true
end
--- 接收数据
-- @number[opt=0] timeout 可选参数,接收超时时间,单位毫秒
-- @string[opt=nil] msg 可选参数控制socket所在的线程退出recv阻塞状态
-- @bool[opt=nil] msgNoResume 可选参数控制socket所在的线程退出recv阻塞状态false或者nil表示“在recv阻塞状态收到msg消息可以退出阻塞状态”true表示不退出
-- @return result 数据接收结果true表示成功false表示失败
-- @return data 如果成功的话,返回接收到的数据;超时时返回错误为"timeout"msg控制退出时返回msg的字符串
-- @return param 如果是msg返回的false则data的值是msgparam的值是msg的参数
-- @usage c = socketESP8266.tcp(); c:connect()
-- @usage result, data = c:recv()
-- @usage false,msg,param = c:recv(60000,"publish_msg")
function mt:recv(timeout, msg, msgNoResume)
assert(self.co == coroutine.running(), "socket:recv: coroutine mismatch")
if self.error then
log.warn('socket.client:recv', 'error', self.error)
return false
end
self.msgNoResume = msgNoResume
if msg and not self.iSubscribe then
self.iSubscribe = msg
self.subMessage = function(data)
-- if data then table.insert(self.output, data) end
if (self.wait == "+RECEIVE" or self.wait == "+SSL RECEIVE") and not self.msgNoResume then
if data then
table.insert(self.output, data)
end
coroutine.resume(self.co, 0xAA)
end
end
sys.subscribe(msg, self.subMessage)
end
if msg and #self.output ~= 0 then
sys.publish(msg, false)
end
if #self.input == 0 then
self.wait = self.ssl and "+SSL RECEIVE" or "+RECEIVE"
if timeout and timeout > 0 then
local r, s = sys.wait(timeout)
-- if not r then
-- return false, "timeout"
-- elseif r and r == msg then
-- return false, r, s
-- else
-- if self.ssl and not r then self:sslDestroy() end
-- return r, s
-- end
if r == nil then
return false, "timeout"
elseif r == 0xAA then
local dat = table.concat(self.output)
self.output = {}
return false, msg, dat
else
if self.ssl and not r then
self:sslDestroy()
end
return r, s
end
else
local r, s = coroutine.yield()
if r == 0xAA then
local dat = table.concat(self.output)
self.output = {}
return false, msg, dat
else
return r, s
end
end
end
if self.protocol == "UDP" then
return true, table.remove(self.input)
else
local s = table.concat(self.input)
self.input = {}
return true, s
end
end
function mt:sslDestroy()
assert(self.co == coroutine.running(), "socket:sslDestroy: coroutine mismatch")
if self.ssl and (self.connected or self.created) then
self.connected = false
self.created = false
req("AT+SSLDESTROY=" .. self.id)
self.wait = "+SSLDESTROY"
coroutine.yield()
socketStatusNtfy()
end
end
--- 销毁一个socket
-- @return nil
-- @usage c = socket.tcp(); c:connect(); c:send("123"); c:close()
function mt:close(slow)
assert(self.co == coroutine.running(), "socket:close: coroutine mismatch")
if self.iSubscribe then
sys.unsubscribe(self.iSubscribe, self.subMessage)
self.iSubscribe = false
end
if self.connected or self.created then
self.connected = false
self.created = false
req(self.ssl and ("AT+SSLDESTROY=" .. self.id) or ("AT+CIPCLOSE=" .. self.id .. (slow and ",0" or "")))
self.wait = self.ssl and "+SSLDESTROY" or "+CIPCLOSE"
coroutine.yield()
socketStatusNtfy()
end
if self.id ~= nil then
wifiRil.deRegUrc((self.ssl and "SSL&" or "") .. self.id, onSocketURC)
table.insert((self.ssl and validSsl or valid), 1, self.id)
if self.ssl then
socketsSsl[self.id] = nil
else
sockets[self.id] = nil
end
self.id = nil
end
end
local function onResponse(cmd, success, response, intermediate)
local prefix = string.match(cmd, "AT(%+%u+)")
local id = string.match(cmd, "AT%+%u+=(%d)")
if response == '+PDP: DEACT' then
sys.publish('PDP_DEACT_IND')
end -- cipsend 如果正好pdp deact会返回+PDP: DEACT作为回应
local tSocket = prefix:match("SSL") and socketsSsl or sockets
if not tSocket[id] then
log.warn('socket: response on nil socket', cmd, response)
return
end
log.info("cmd",cmd, success, response, intermediate)
if cmd:match("^AT%+SSLCREATE") then
tSocket[id].createResp = response
end
if tSocket[id].wait == prefix then
if (prefix == "+CIPSTART" or prefix == "+SSLCONNECT") and success then
-- CIPSTART,SSLCONNECT 返回OK只是表示被接受
return
end
if prefix == '+CIPSEND' then
if response == 'OK' then
-- 返回OK只是表示被接受
return
end
if response ~= 'SEND OK' and response ~= 'OK' then
local acceptLen = response:match("Recv (%d+) bytes")
log.info("testlen", acceptLen, cmd:match("AT%+%u+=%d,(%d+)"))
if acceptLen then
if acceptLen ~= cmd:match("AT%+%u+=%d,(%d+)") then
success = false
else
return
end
else
success = false
end
end
elseif prefix == "+SSLSEND" then
if response:match("%d, *([%u%d :]+)") ~= 'SEND OK' then
success = false
end
end
local reason, address
if not success then
if prefix == "+CIPSTART" then
address = cmd:match("AT%+CIPSTART=%d,\"%a+\",\"(.+)\",%d+")
elseif prefix == "+SSLCONNECT" and (tSocket[id].createResp or ""):match("SSL&%d+,CREATE ERROR: 4") then
address = tSocket[id].address or ""
end
if address and not address:match("^[%d%.]+$") then
reason = "DNS"
end
end
if not reason and not success then
tSocket[id].error = response
end
stopConnectTimer(tSocket, id)
coroutine.resume(tSocket[id].co, success, reason)
end
end
local function onSocketReceiveUrc(urc, prefix, cmd)
local len, datatest = string.match(urc, "+CIPRECVDATA:(%d+),(.+)")
local id = string.match(cmd, "+CIPRECVDATA=(%d),")
tSocket = (tag == "SSL" and socketsSsl or sockets)
len = tonumber(len)
if len == 0 then
return urc
end
if string.len(datatest) == len then
sys.publish("SOCKET_RECV", id)
if tSocket[id].wait == "+RECEIVE" then
coroutine.resume(tSocket[id].co, true, datatest)
else -- 数据进缓冲区,缓冲区溢出采用覆盖模式
if #tSocket[id].input > INDEX_MAX then
tSocket[id].input = {}
end
table.insert(tSocket[id].input, datatest)
end
elseif string.len(datatest) < len then
local cache = {}
table.insert(cache, datatest)
len = len - string.len(datatest)
local function filter(data)
-- 剩余未收到的数据长度
if string.len(data) >= len then -- at通道的内容比剩余未收到的数据多
-- 截取网络发来的数据
table.insert(cache, string.sub(data, 1, len))
-- 剩下的数据扔给at进行后续处理
data = string.sub(data, len + 1, -1)
if not tSocket[id] then
log.warn('socket: receive on nil socket', id)
else
sys.publish("SOCKET_RECV", id)
local s = table.concat(cache)
if tSocket[id].wait == "+RECEIVE" or tSocket[id].wait == "+SSL RECEIVE" then
coroutine.resume(tSocket[id].co, true, s)
else -- 数据进缓冲区,缓冲区溢出采用覆盖模式
if #tSocket[id].input > INDEX_MAX then
tSocket[id].input = {}
end
table.insert(tSocket[id].input, s)
end
end
return data
else
table.insert(cache, data)
len = len - string.len(data)
return "", filter
end
end
return filter
end
end
wifiRil.regUrc("+CIPCLOSE", onResponse)
wifiRil.regRsp("+CIPSEND", onResponse)
wifiRil.regRsp("+CIPSTART", onResponse)
wifiRil.regRsp("+SSLDESTROY", onResponse)
wifiRil.regRsp("+SSLCREATE", onResponse)
wifiRil.regRsp("+SSLSEND", onResponse)
wifiRil.regRsp("+SSLCONNECT", onResponse)
wifiRil.regUrc("+CIPRECVDATA", onSocketReceiveUrc)
wifiRil.regUrc("+SSL RECEIVE", onSocketReceiveUrc)
wifiRil.regRsp("+SYSFLASH", function(cmd, result, response, intermediate, param)
if cmd:find("AT%+SYSFLASH=0") then
req("AT+SYSFLASH=1,\"" .. param.path32 .. "\",0," .. io.fileSize(param.path8955), io.readFile(param.path8955), nil, nil, param.id)
elseif cmd:find("AT%+SYSFLASH=1") then
local tSocket = sockets
coroutine.resume(tSocket[param].co)
end
end)
local recvId
getRecvId = function() return recvId end
local errorNum = 0
local function wifiUrc(data, prefix)
log.info("urc上报", data, prefix)
if prefix == "STATUS" then
if not connecting or not connected then
local state = data:sub(8, -1)
if state == "0" or state == "1" or state == "5" then
errorNum = errorNum + 1
if errorNum > 5 then
wifiRil.request("AT+CWMODE=1")
-- wifiRil.request("AT+CWSTARTSMART=3")
wifiRil.request("AT+CWJAP=\"Luat_XIAOMIPRO\",\"Air123456\"")
else
wifiRil.request("AT+CIPSTATUS", nil, nil, 5000)
end
elseif state == "2" then
errorNum = 0
connected = true
sys.publish("IP_READY_IND")
return
end
end
elseif prefix == "WIFI GOT IP" then
connecting = false
connected = true
link.setReady(link.ESP8266, true)
sys.publish("IP_READY_IND")
return
elseif prefix == "WIFI CONNECTED" then
connecting = true
return
elseif prefix == "Smart get wifi info" then
stopConfig = sys.timerStart(wifiRil.request, 20000, "AT+CWSTOPSMART")
return
elseif prefix == "smartconfig connected wifi" then
connecting = true
if sys.timerIsActive(stopConfig) then sys.timerStop(stopConfig) end
wifiRil.request("AT+CWSTOPSMART", nil, nil, 6000)
elseif prefix == "+IPD" then
log.info("rcv data", data)
local lid, dataLen = string.match(data, "%+IPD,(%d),(%d+)")
recvId = lid
wifiRil.request(string.format("AT+CIPRECVDATA=%d,%d", lid, dataLen))
elseif prefix == "+CIPRECVLEN" then
log.info("rcv test", prefix, data)
local lid = {string.match(data, "%+CIPRECVLEN:(.+),(.+),(.+),(.+),(.+)")}
for k, v in pairs(lid) do
if v ~= "-1" and v ~= "0" then
wifiRil.request(string.format("AT+CIPRECVDATA=%d,%d", k - 1, 2147483647))
end
end
end
end
local function wifiRsp(cmd, success, response, intermediate)
log.info("wifi", cmd, success, response, intermediate)
if cmd == "AT+CWSTARTSMART=3" then
if success then smartConfig = true end
elseif cmd == "AT+CWSTOPSMART" then
if connecting then
connecting = false
connected = true
sys.publish("IP_READY_IND")
else
sys.timerStart(wifiRil.request, 2000, "AT+CWSTARTSMART=3")
end
end
end
wifiRil.regUrc("+IPD", wifiUrc)
wifiRil.regUrc("STATUS", wifiUrc)
wifiRil.regUrc("WIFI GOT IP", wifiUrc)
wifiRil.regUrc("WIFI CONNECTED", wifiUrc)
wifiRil.regUrc("smartconfig connected wifi", wifiUrc)
wifiRil.regUrc("Smart get wifi info", wifiUrc)
wifiRil.regRsp("+CWSTARTSMART", wifiRsp)
wifiRil.regRsp("+CWSTOPSMART", wifiRsp)
wifiRil.regUrc("+CIPRECVLEN", wifiUrc)
wifiRil.request("AT+CWMODE=1")
wifiRil.request("AT+CIPRECVMODE=1")
wifiRil.request("AT+CIPMODE=0")
wifiRil.request("AT+CIPMUX=1")
wifiRil.request("AT+CIPSTATUS")
function printStatus()
log.info('socket.printStatus', 'valid id', table.concat(valid), table.concat(validSsl))
for m, n in pairs({sockets, socketsSsl}) do
for _, client in pairs(n) do
for k, v in pairs(client) do
log.info('socket.printStatus', 'client', client.id, k, v)
end
end
end
end
-- 设置TCP层自动重传的参数
-- @number[opt=4] retryCnt 重传次数取值范围0到12
-- @number[opt=16] retryMaxTimeout 限制每次重传允许的最大超时时间(单位秒)取值范围1到16
-- @return nil
-- @usage
-- setTcpResendPara(3,8)
-- setTcpResendPara(4,16)
function setTcpResendPara(retryCnt, retryMaxTimeout)
req("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
wifiRil.setDataTimeout(((retryCnt or 4) * (retryMaxTimeout or 16) + 60) * 1000)
end
function setIpStatis(interval)
end
-- 设置用户自定义的DNS解析器.
-- 通过域名连接服务器时DNS解析的过程如下
-- 1、使用core中提供的方式连接运营商DNS服务器解析如果解析成功则结束如果解析失败走第2步
-- 2、使用脚本lib中提供的免费腾讯云HttpDns解析如果解析成功则结束如果解析失败走第3步
-- 3、如果存在用户自定义的DNS解析器则使用此处用户自定义的DNS解析器去解析
-- @function[opt=nil] parserFnc 用户自定义的DNS解析器函数函数的调用形式为
-- parserFnc(domainName,token)调用接口后会等待解析结果的消息通知或者40秒超时失败
-- domainNamestring类型表示域名例如"www.baidu.com"
-- tokenstring类型此次DNS解析请求的token例如"1"
-- 解析结束后要publish一个消息来通知解析结果消息参数中的ip地址最多返回一个sys.publish("USER_DNS_PARSE_RESULT_"..token,ip),例如:
-- sys.publish("USER_DNS_PARSE_RESULT_1","115.239.211.112")
-- 表示解析成功解析到1个IP地址115.239.211.112
-- sys.publish("USER_DNS_PARSE_RESULT_1")
-- 表示解析失败
-- @return nil
-- @usage socket.setDnsParser(parserFnc)
function setDnsParser(parserFnc)
dnsParser = parserFnc
end
-- 设置数据发送模式(在网络准备就绪之前调用此接口设置).
-- 如果设置为快发模式,注意如下两点:
-- 1、通过send接口发送的数据如果成功发送到服务器设备端无法获取到这个成功状态
-- 2、通过send接口发送的数据如果发送失败设备端可以获取到这个失败状态
-- 慢发模式可以获取到send接口发送的成功或者失败
--
-- ****************************************************************************************************************************************************************
-- TCP协议发送数据时数据发送出去之后必须等到服务器返回TCP ACK包才认为数据发送成功在网络较差的情况下这种ACK确认就会导致发送过程很慢。
-- 从而导致用户程序后续的AT处理逻辑一直处于等待状态。例如执行AT+CIPSEND动作发送一包数据后接下来要执行AT+QTTS播放TTS但是CIPSEND一直等了1分钟才返回SEND OK
-- 这时AT+QTTS就会一直等待1分钟可能不是程序中想看到的。
-- 此时就可以设置为快发模式AT+CIPSEND可以立即返回一个结果此结果表示“数据是否被缓冲区所保存”从而不影响后续其他AT指令的及时执行
--
-- AT版本可以通过AT+CIPQSEND指令、Luat版本可以通过socket.setSendMode接口设置发送模式为快发或者慢发
--
-- 快发模式下在core中有一个1460*7=10220字节的缓冲区要发送的数据首先存储到此缓冲区然后在core中自动循环发送。
-- 如果此缓冲区已满则AT+CIPSEND会直接返回ERRORsocket:send接口也会直接返回失败
--
-- 同时满足如下几种条件,适合使用快发模式:
-- 1. 发送的数据量小并且发送频率低数据发送速度远远不会超过core中的10220字节大小
-- 没有精确地判断标准可以简单的按照3分钟不超过10220字节来判断曾经有一个不适合快发模式的例子如下
-- 用户使用Luat版本的http上传一个几十K的文件设置了快发模式导致一直发送失败因为循环的向core中的缓冲区插入数据
-- 插入数据的速度远远超过发送数据到服务器的速度,所以很快就导致缓冲区慢,再插入数据时,就直接返回失败
-- 2. 对每次发送的数据,不需要确认发送结果
-- 3. 数据发送功能不能影响其他功能的及时响应
-- ****************************************************************************************************************************************************************
--
-- @number[opt=0] mode 数据发送模式0表示慢发1表示快发
-- @return nil
-- @usage socket.setSendMode(1)
-- function setSendMode(mode)
-- linkTest.setSendMode(mode)
-- end
function open(para)
powerCbFnc = para["powerFunc"] or nil
if powerCbFnc then
powerCbFnc(true)
end
end
function close()
if powerCbFnc then
powerCbFnc(false)
end
end

526
4G/code/lib/sys.lua Normal file
View File

@@ -0,0 +1,526 @@
--- 模块功能Luat协程调度框架
-- @module sys
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2017.9.13
require "utils"
require "log"
require "patch"
module(..., package.seeall)
-- lib脚本版本号只要lib中的任何一个脚本做了修改都需要更新此版本号
SCRIPT_LIB_VER = "2.4.5"
-- TaskID最大值
local TASK_TIMER_ID_MAX = 0x1FFFFFFF
-- msgId 最大值(请勿修改否则会发生msgId碰撞的危险)
local MSG_TIMER_ID_MAX = 0x7FFFFFFF
-- 任务定时器id
local taskTimerId = 0
-- 消息定时器id
local msgId = TASK_TIMER_ID_MAX
-- 定时器id表
local timerPool = {}
local taskTimerPool = {}
--消息定时器参数表
local para = {}
--定时器是否循环表
local loop = {}
-- 启动GSM协议栈。例如在充电开机未启动GSM协议栈状态下如果用户长按键正常开机此时调用此接口启动GSM协议栈即可
-- @return 无
-- @usage sys.powerOn()
function powerOn()
rtos.poweron(1)
end
--- 软件重启
-- @string r 重启原因用户自定义一般是string类型重启后的trace中会打印出此重启原因
-- @return 无
-- @usage sys.restart('程序超时软件重启')
function restart(r)
assert(r and r ~= "", "sys.restart cause null")
if errDump and errDump.appendErr and type(errDump.appendErr) == "function" then errDump.appendErr("restart[" .. r .. "];") end
log.warn("sys.restart", r)
rtos.restart()
end
--- task任务延时函数
-- 只能直接或者间接的被task任务主函数调用如果定时器创建成功则本task会挂起
-- @number ms 延时时间单位毫秒最小1最大0x7FFFFFFF
-- 实际上支持的最小超时时间是5毫秒小于5毫秒的时间都会被转化为5毫秒
-- @return result分为如下三种情况
-- 1、如果定时器创建失败本task不会被挂起直接返回nil
-- 2、如果定时器创建成功本task被挂起超时时间到达后会激活本task返回nil
-- 3、如果定时器创建成功本task被挂起在超时时间到达之前其他业务逻辑主动激活本task
-- 返回激活时携带的可变参数如果不是故意为之可能是写bug了
-- @usage
-- task延时5秒
-- sys.taskInit(function()
-- sys.wait(5000)
-- end)
function wait(ms)
-- 参数检测,参数不能为负值
assert(ms > 0, "The wait time cannot be negative!")
--4G底层不支持小于5ms的定时器
if ms < 5 then ms = 5 end
-- 选一个未使用的定时器ID给该任务线程
if taskTimerId >= TASK_TIMER_ID_MAX then taskTimerId = 0 end
taskTimerId = taskTimerId + 1
local timerid = taskTimerId
taskTimerPool[coroutine.running()] = timerid
timerPool[timerid] = coroutine.running()
-- 调用core的rtos定时器
if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
-- 挂起调用的任务线程
local message = {coroutine.yield()}
if #message ~= 0 then
rtos.timer_stop(timerid)
taskTimerPool[coroutine.running()] = nil
timerPool[timerid] = nil
return unpack(message)
end
end
--- task任务条件等待函数支持事件消息和定时器消息
-- 只能直接或者间接的被task任务主函数调用调用本接口的task会挂起
-- @string id 消息ID建议使用string类型
-- @number[opt=nil] ms 延时时间单位毫秒最小1最大0x7FFFFFFF
-- 实际上支持的最小超时时间是5毫秒小于5毫秒的时间都会被转化为5毫秒
-- @return result,data分为如下三种情况
-- 1、如果存在超时时间参数
-- (1)、在超时时间到达之前如果收到了等待的消息ID则result为truedata为消息ID携带的参数可能是多个参数
-- (2)、在超时时间到达之前如果没收到等待的消息ID则result为falsedata为nil
-- 2、如果不存在超时时间参数如果收到了等待的消息ID则result为truedata为消息ID携带的参数可能是多个参数
-- (1)、如果收到了等待的消息ID则result为truedata为消息ID携带的参数可能是多个参数
-- (2)、如果没收到等待的消息ID则task一直挂起
-- 3、还存在一种特殊情况本task挂起时可能被task的外部应用逻辑给主动激活如果不是故意为之可能是写bug了
-- @usage
-- task延时120秒或者收到"SIM_IND"消息:
-- sys.taskInit(function()
-- local result, data = sys.waitUntil("SIM_IND",120000)
-- end)
function waitUntil(id, ms)
subscribe(id, coroutine.running())
local message = ms and {wait(ms)} or {coroutine.yield()}
unsubscribe(id, coroutine.running())
return message[1] ~= nil, unpack(message, 2, #message)
end
--- 同上,但不返回等待结果
function sys.waitUntilMsg(id)
local co = sys.check_task()
sys.subscribe(id, co)
local message = {coroutine.yield()}
sys.unsubscribe(id, co)
return unpack(message, 2, #message)
end
--- Task任务的条件等待函数扩展包括事件消息和定时器消息等条件只能用于任务函数中。
-- @param id 消息ID
-- @number ms 等待超时时间单位ms最大等待126322567毫秒
-- @return message 接收到消息返回message超时返回false
-- @return data 接收到消息返回消息参数
-- @usage result, data = sys.waitUntilExt("SIM_IND", 120000)
function waitUntilExt(id, ms)
subscribe(id, coroutine.running())
local message = ms and {wait(ms)} or {coroutine.yield()}
unsubscribe(id, coroutine.running())
if message[1] ~= nil then return unpack(message) end
return false
end
--- 创建一个任务并且运行该任务
-- @param fun 任务主函数激活task时使用
-- @param ... 任务主函数fun的可变参数
-- @return co 返回该任务的线程ID
-- @usage sys.taskInit(task1,'a','b')
function taskInit(fun, ...)
local co = coroutine.create(fun)
coroutine.resume(co, ...)
return co
end
--- Luat平台初始化
-- @param mode 充电开机是否启动GSM协议栈1不启动否则启动
-- @param lprfnc 用户应用脚本中定义的“低电关机处理函数”如果有函数名则低电时本文件中的run接口不会执行任何动作否则会延时1分钟自动关机
-- @return 无
-- @usage sys.init(1,0)
function init(mode, lprfnc)
-- 用户应用脚本中必须定义PROJECT和VERSION两个全局变量否则会死机重启如何定义请参考各个demo中的main.lua
assert(PROJECT and PROJECT ~= "" and VERSION and VERSION ~= "", "Undefine PROJECT or VERSION")
collectgarbage("setpause", 80)
-- 设置AT命令的虚拟串口
uart.setup(uart.ATC, 0, 0, uart.PAR_NONE, uart.STOP_1)
log.info("poweron reason:", rtos.poweron_reason(), PROJECT, VERSION, SCRIPT_LIB_VER, rtos.get_version())
pcall(rtos.set_lua_info,"\r\n"..rtos.get_version().."\r\n"..(_G.PROJECT or "NO PROJECT").."\r\n"..(_G.VERSION or "NO VERSION"))
if type(rtos.get_build_time)=="function" then log.info("core build time", rtos.get_build_time()) end
if mode == 1 then
-- 充电开机
if rtos.poweron_reason() == rtos.POWERON_CHARGER then
-- 关闭GSM协议栈
rtos.poweron(0)
end
end
end
------------------------------------------ rtos消息回调处理部分 ------------------------------------------
--[[
函数名cmpTable
功能 比较两个table的内容是否相同注意table中不能再包含table
参数
t1第一个table
t2第二个table
返回值相同返回true否则false
]]
local function cmpTable(t1, t2)
if not t2 then return #t1 == 0 end
if #t1 == #t2 then
for i = 1, #t1 do
if unpack(t1, i, i) ~= unpack(t2, i, i) then
return false
end
end
return true
end
return false
end
--- 关闭sys.timerStart和sys.timerLoopStart创建的定时器
-- 有两种方式可以唯一标识一个定时器:
-- 1、定时器ID
-- 2、定时器回调函数和可变参数
-- @param val 有两种形式:
-- 1、为number类型时表示定时器ID
-- 2、为function类型时表示定时器回调函数
-- @param ... 可变参数当val为定时器回调函数时此可变参数才有意义表示定时器回调函数的可变回调参数
-- @return nil
-- @usage
-- 通过定时器ID关闭一个定时器
-- local timerId = sys.timerStart(publicTimerCbFnc,8000,"second")
-- sys.timerStop(timerId)
-- 通过定时器回调函数和可变参数关闭一个定时器:
-- sys.timerStart(publicTimerCbFnc,8000,"first")
-- sys.timerStop(publicTimerCbFnc,"first")
function timerStop(val, ...)
-- val 为定时器ID
local arg={ ... }
if type(val) == 'number' then
timerPool[val], para[val], loop[val] = nil
rtos.timer_stop(val)
else
for k, v in pairs(timerPool) do
-- 回调函数相同
if type(v) == 'table' and v.cb == val or v == val then
-- 可变参数相同
if cmpTable(arg, para[k]) then
rtos.timer_stop(k)
timerPool[k], para[k], loop[val] = nil
break
end
end
end
end
end
--- 关闭sys.timerStart和sys.timerLoopStart创建的某个回调函数的所有定时器
-- @function fnc 定时器回调函数
-- @return nil
-- @usage
-- 关闭回调函数为publicTimerCbFnc的所有定时器
-- local function publicTimerCbFnc(tag)
-- log.info("publicTimerCbFnc",tag)
-- end
--
-- sys.timerStart(publicTimerCbFnc,8000,"first")
-- sys.timerStart(publicTimerCbFnc,8000,"second")
-- sys.timerStart(publicTimerCbFnc,8000,"third")
-- sys.timerStopAll(publicTimerCbFnc)
function timerStopAll(fnc)
for k, v in pairs(timerPool) do
if type(v) == "table" and v.cb == fnc or v == fnc then
rtos.timer_stop(k)
timerPool[k], para[k], loop[k] = nil
end
end
end
--- 创建并且启动一个单次定时器
-- 有两种方式可以唯一标识一个定时器:
-- 1、定时器ID
-- 2、定时器回调函数和可变参数
-- @param fnc 定时器回调函数必须存在不允许为nil
-- 当定时器超时时间到达时回调函数的调用形式为fnc(...),其中...为回调参数
-- @number ms 定时器超时时间单位毫秒最小1最大0x7FFFFFFF
-- 实际上支持的最小超时时间是5毫秒小于5毫秒的时间都会被转化为5毫秒
-- @param ... 可变参数回调函数fnc的回调参数
-- @return number timerId创建成功返回定时器ID创建失败返回nil
-- @usage
-- 创建一个5秒的单次定时器回调函数打印"timerCb",没有可变参数:
-- sys.timerStart(function() log.info("timerCb") end, 5000)
-- 创建一个5秒的单次定时器回调函数打印"timerCb"和"test",可变参数为"test"
-- sys.timerStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
function timerStart(fnc, ms, ...)
--回调函数和时长检测
local arg={ ... }
local argcnt=0
for i, v in pairs(arg) do
argcnt = argcnt+1
end
assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
--4G底层不支持小于5ms的定时器
if ms < 5 then ms = 5 end
-- 关闭完全相同的定时器
if argcnt == 0 then
timerStop(fnc)
else
timerStop(fnc, ...)
end
-- 为定时器申请IDID值 1-0X1FFFFFFF 留给任务0X1FFFFFFF-0x7FFFFFFF留给消息专用定时器
while true do
if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
msgId = msgId + 1
if timerPool[msgId] == nil then
timerPool[msgId] = fnc
break
end
end
--调用底层接口启动定时器
if rtos.timer_start(msgId, ms) ~= 1 then log.debug("rtos.timer_start error") return end
--如果存在可变参数,在定时器参数表中保存参数
if argcnt ~= 0 then
para[msgId] = arg
end
--返回定时器id
return msgId
end
--- 创建并且启动一个循环定时器
-- 有两种方式可以唯一标识一个定时器:
-- 1、定时器ID
-- 2、定时器回调函数和可变参数
-- @param fnc 定时器回调函数必须存在不允许为nil
-- 当定时器超时时间到达时回调函数的调用形式为fnc(...),其中...为回调参数
-- @number ms 定时器超时时间单位毫秒最小1最大0x7FFFFFFF
-- 实际上支持的最小超时时间是5毫秒小于5毫秒的时间都会被转化为5毫秒
-- @param ... 可变参数回调函数fnc的回调参数
-- @return number timerId创建成功返回定时器ID创建失败返回nil
-- @usage
-- 创建一个5秒的循环定时器回调函数打印"timerCb",没有可变参数:
-- sys.timerLoopStart(function() log.info("timerCb") end, 5000)
-- 创建一个5秒的循环定时器回调函数打印"timerCb"和"test",可变参数为"test"
-- sys.timerLoopStart(function(tag) log.info("timerCb",tag) end, 5000, "test")
function timerLoopStart(fnc, ms, ...)
local tid = timerStart(fnc, ms, ...)
if tid then loop[tid] = (ms<5 and 5 or ms) end
return tid
end
--- 判断“通过timerStart或者timerLoopStart创建的定时器”是否处于激活状态
-- @param val 定时器标识,有两种表示形式
-- 1、number类型通过timerStart或者timerLoopStart创建定时器时返回的定时器ID此情况下不需要传入回调参数...就能唯一标识一个定时器
-- 2、function类型通过timerStart或者timerLoopStart创建定时器时的回调函数此情况下如果存在回调参数需要传入回调参数...才能唯一标识一个定时器
-- @param ... 回调参数和“通过timerStart或者timerLoopStart创建定时器”的回调参数保持一致
-- @return status定时器激活状态根据val的表示形式有不同的返回值
-- 1、val为number类型时如果处于激活状态则返回function类型的定时器回调函数否则返回nil
-- 2、val为function类型时如果处于激活状态则返回bool类型的true否则返回nil
-- @usage
-- 定时器ID形式标识定时器的使用参考
-- local timerId1 = sys.timerStart(function() end,5000)
--
-- sys.taskInit(function()
-- sys.wait(3000)
-- log.info("after 3 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
--
-- sys.wait(3000)
-- log.info("after 6 senonds, timerId1 isActive?",sys.timerIsActive(timerId1))
-- end)
--
--
-- 回调函数和回调参数标识定时器的使用参考:
-- local function timerCbFnc2(tag)
-- log.info("timerCbFnc2",tag)
-- end
--
-- sys.timerStart(timerCbFnc2,5000,"test")
--
-- sys.taskInit(function()
-- sys.wait(3000)
-- log.info("after 3 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
--
-- sys.wait(3000)
-- log.info("after 6 senonds, timerCbFnc2 test isActive?",sys.timerIsActive(timerCbFnc2,"test"))
-- end)
function timerIsActive(val, ...)
local arg={ ... }
if type(val) == "number" then
return timerPool[val]
else
for k, v in pairs(timerPool) do
if v == val then
if cmpTable(arg, para[k]) then return true end
end
end
end
end
------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
-- 订阅者列表
local subscribers = {}
--内部消息队列
local messageQueue = {}
--- 订阅消息
-- @param id 消息id
-- @param callback 消息回调处理
-- @usage subscribe("NET_STATUS_IND", callback)
function subscribe(id, callback)
if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
log.warn("warning: sys.subscribe invalid parameter", id, callback)
return
end
if not subscribers[id] then subscribers[id] = {count = 0} end
if not subscribers[id][callback] then
subscribers[id].count = subscribers[id].count + 1
subscribers[id][callback] = true
end
end
--- 取消订阅消息
-- @param id 消息id
-- @param callback 消息回调处理
-- @usage unsubscribe("NET_STATUS_IND", callback)
function unsubscribe(id, callback)
if type(id) ~= "string" or (type(callback) ~= "function" and type(callback) ~= "thread") then
log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
return
end
-- 取消订阅时将对应取消的函数赋值为false不能直接赋值为nil否则可能触发lua invalid key to 'next'异常
if subscribers[id] then
if subscribers[id][callback] then
subscribers[id].count = subscribers[id].count - 1
subscribers[id][callback] = false
end
end
end
--- 发布内部消息,存储在内部消息队列中
-- @param ... 可变参数,用户自定义
-- @return 无
-- @usage publish("NET_STATUS_IND")
function publish(...)
local arg = { ... }
table.insert(messageQueue, arg)
end
-- 分发消息
local function dispatch()
while true do
if #messageQueue == 0 then
-- 当在同一个task内sys.waitUntil()不同的消息并且没有任何消息publish时会造成内存泄漏。
-- 例如sys.waitUntil("1", 500)、sys.waitUntil("2", 500)、...、sys.waitUntil("n", 500)
for k, v in pairs(subscribers) do
if v.count == 0 then subscribers[k] = nil end
end
break
end
local message = table.remove(messageQueue, 1)
if subscribers[message[1]] then
for callback, flag in pairs(subscribers[message[1]]) do
if flag then
if type(callback) == "function" then
callback(unpack(message, 2, #message))
elseif type(callback) == "thread" then
coroutine.resume(callback, unpack(message))
end
end
end
-- 当某个subscribe的消息的回调取消订阅时在这里将它赋值为nil回收内存
if subscribers[message[1]] then
for callback, flag in pairs(subscribers[message[1]]) do
if not flag then
subscribers[message[1]][callback] = nil
end
end
--当所有subscribe消息的回调都取消订阅时在这里清空对应消息的表回收内存
if subscribers[message[1]].count == 0 then
subscribers[message[1]] = nil
end
end
end
end
end
-- rtos消息回调
local handlers = {}
setmetatable(handlers, {__index = function() return function() end end, })
--- 注册rtos消息回调处理函数
-- @number id 消息类型id
-- @param handler 消息处理函数
-- @return 无
-- @usage rtos.on(rtos.MSG_KEYPAD, function(param) handle keypad message end)
rtos.on = function(id, handler)
handlers[id] = handler
end
------------------------------------------ Luat 主调度框架 ------------------------------------------
--- run()从底层获取core消息并及时处理相关消息查询定时器并调度各注册成功的任务线程运行和挂起
-- @return 无
-- @usage sys.run()
function run()
while true do
-- 分发内部消息
dispatch()
-- 阻塞读取外部消息
local msg, param = rtos.receive(rtos.INF_TIMEOUT)
-- 判断是否为定时器消息,并且消息是否注册
if msg == rtos.MSG_TIMER and timerPool[param] then
if param <= TASK_TIMER_ID_MAX then
local taskId = timerPool[param]
timerPool[param] = nil
if taskTimerPool[taskId] == param then
taskTimerPool[taskId] = nil
coroutine.resume(taskId)
end
else
local cb = timerPool[param]
--如果不是循环定时器从定时器id表中删除此定时器
if not loop[param] then timerPool[param] = nil end
if para[param] ~= nil then
cb(unpack(para[param]))
if not loop[param] then para[param] = nil end
else
cb()
end
--如果是循环定时器,继续启动此定时器
if loop[param] then rtos.timer_start(param, loop[param]) end
end
--其他消息(音频消息、充电管理消息、按键消息等)
elseif type(msg) == "number" then
handlers[msg](param)
else
handlers[msg.id](msg)
end
end
end
require "clib"
if type(rtos.openSoftDog)=="function" then
rtos.openSoftDog(60000)
sys.timerLoopStart(rtos.eatSoftDog,20000)
end

185
4G/code/lib/sysplus.lua Normal file
View File

@@ -0,0 +1,185 @@
--[[
@module sysplus
@summary LuaTask核心增强逻辑
@version 1.0
@date 2022.04.27
@author 晨旭/刘清宇/李思琦
@usage
]]
module(..., package.seeall)
local sys = require "sys"
local sysplus = {}
----------------------------------------------
-- 提供给异步c接口使用, by 晨旭
sysplus.cwaitMt = {
wait = function(t,r)
return function()
if r and type(r) == "table" then--新建等待失败的返回
return table.unpack(r)
end
return sys.waitUntilMsg(t)
end
end,
cb = function(t,r)
return function(f)
if type(f) ~= "function" then return end
sys.taskInit(function ()
if r and type(r) == "table" then
--sys.wait(1)--如果回调里调用了sys.publish直接调用回调会触发不了下一行的吧。。。
f(table.unpack(r))
return
end
f(sys.waitUntilMsg(t))
end)
end
end,
}
sysplus.cwaitMt.__index = function(t,i)
if sysplus.cwaitMt[i] then
return sysplus.cwaitMt[i](rawget(t,"w"),rawget(t,"r"))
else
rawget(t,i)
end
end
_G.sys_cw = function (w,...)
local r = {...}
local t = {w=w,r=(#r > 0 and r or nil)}
setmetatable(t,sysplus.cwaitMt)
return t
end
-------------------------------------------------------------------
------------- 基于任务的task扩展 by 李思琦---------------------------
--任务列表
local taskList = {}
--- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数main.lua导入该模块即可
-- @param fun 任务函数名用于resume唤醒时调用
-- @param taskName 任务名称用于唤醒任务的id
-- @param cbFun 接收到非目标消息时的回调函数
-- @param ... 任务函数fun的可变参数
-- @return co 返回该任务的线程号
-- @usage sysplus.taskInitEx(task1,'a',callback)
function sysplus.taskInitEx(fun, taskName, cbFun, ...)
taskList[taskName]={msgQueue={}, To=false, cb=cbFun}
return sys.taskInit(fun, ...)
end
--- 删除由taskInitEx创建的任务线程
-- @param taskName 任务名称用于唤醒任务的id
-- @return 无
-- @usage sysplus.taskDel('a')
function sysplus.taskDel(taskName)
taskList[taskName]=nil
end
local function waitTo(taskName)
taskList[taskName].To = true
sys.publish(taskName)
end
--- 等待接收一个目标消息
-- @param taskName 任务名称用于唤醒任务的id
-- @param target 目标消息如果为nil则表示接收到任意消息都会退出
-- @param ms 超时时间如果为nil则表示无超时永远等待
-- @return msg or false 成功返回table型的msg超时返回false
-- @usage sysplus.waitMsg('a', 'b', 1000)
function sysplus.waitMsg(taskName, target, ms)
if taskList[taskName] == nil then
log.error("sysplus", "sys.taskInitEx启动的task才能使用waitMsg")
return false
end
local msg = false
local message = nil
if #taskList[taskName].msgQueue > 0 then
msg = table.remove(taskList[taskName].msgQueue, 1)
if target == nil then
return msg
end
if (msg[1] == target) then
return msg
elseif type(taskList[taskName].cb) == "function" then
taskList[taskName].cb(msg)
end
end
sys.subscribe(taskName, coroutine.running())
sys.timerStop(waitTo, taskName)
if ms and ms ~= 0 then
sys.timerStart(waitTo, ms, taskName)
end
taskList[taskName].To = false
local finish=false
while not finish do
message = coroutine.yield()
if #taskList[taskName].msgQueue > 0 then
msg = table.remove(taskList[taskName].msgQueue, 1)
-- sys.info("check target", msg[1], target)
if target == nil then
finish = true
else
if (msg[1] == target) then
finish = true
elseif type(taskList[taskName].cb) == "function" then
taskList[taskName].cb(msg)
end
end
elseif taskList[taskName].To then
-- sys.info(taskName, "wait message timeout")
finish = true
end
end
if taskList[taskName].To then
msg = nil
end
taskList[taskName].To = false
sys.timerStop(waitTo, taskName)
sys.unsubscribe(taskName, coroutine.running())
return msg
end
--- 向目标任务发送一个消息
-- @param taskName 任务名称用于唤醒任务的id
-- @param param1 消息中的参数1同时也是waitMsg里的target
-- @param param2 消息中的参数2
-- @param param3 消息中的参数3
-- @param param4 消息中的参数4
-- @return true or false 成功返回true
-- @usage sysplus.sendMsg('a', 'b')
function sysplus.sendMsg(taskName, param1, param2, param3, param4)
if taskList[taskName]~=nil then
table.insert(taskList[taskName].msgQueue, {param1, param2, param3, param4})
sys.publish(taskName)
return true
end
return false
end
function sysplus.cleanMsg(taskName)
if taskList[taskName]~=nil then
taskList[taskName].msgQueue = {}
return true
end
return false
end
function sysplus.taskCB(taskName, msg)
if taskList[taskName]~=nil then
if type(taskList[taskName].cb) == "function" then
taskList[taskName].cb(msg)
return
end
end
log.error(taskName, "no cb fun")
end
_G.sys_send = sysplus.sendMsg
_G.sys_wait = sysplus.waitMsg
return sysplus
----------------------------

115
4G/code/lib/uiWin.lua Normal file
View File

@@ -0,0 +1,115 @@
--- 模块功能UI窗口管理
-- @module uiWin
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.25
module(...,package.seeall)
--窗口管理栈
local stack = {}
--当前分配的窗口ID
local winid = 0
local function allocid()
winid = winid + 1
return winid
end
local function loseFocus()
if stack[#stack] and stack[#stack]["onLoseFocus"] then
stack[#stack]["onLoseFocus"]()
end
end
--- 新增一个窗口
-- @table wnd 窗口的元素以及消息处理函数表
-- @return number窗口ID
-- @usage uiWin.add({onUpdate = refresh})
function add(wnd)
---必须注册更新接口
assert(wnd.onUpdate)
if type(wnd) ~= "table" then
assert("unknown uiwin type "..type(wnd))
end
--上一个窗口执行失去焦点的处理函数
loseFocus()
--为新窗口分配窗口ID
wnd.id = allocid()
--新窗口请求入栈
sys.publish("UIWND_ADD",wnd)
return wnd.id
end
--- 移除一个窗口
-- @number winId 窗口ID
-- @return nil
-- @usage uiWin.remove(winId)
function remove(winId)
sys.publish("UIWND_REMOVE",winId)
end
function removeAll()
sys.publish("UIWND_REMOVEALL")
end
function update()
sys.publish("UIWND_UPDATE")
end
local function onAdd(wnd)
table.insert(stack,wnd)
stack[#stack].onUpdate()
end
local function onRemove(winid)
local istop,k,v
for k,v in ipairs(stack) do
if v.id == winid then
istop = (k==#stack)
table.remove(stack,k)
if #stack~=0 and istop then
stack[#stack].onUpdate()
end
return
end
end
end
local function onRemoveAll()
local k,v
for k,v in ipairs(stack) do
table.remove(stack,k)
end
end
local function onUpdate()
if stack[#stack] and stack[#stack].onUpdate then
stack[#stack].onUpdate()
end
end
--key自定义功能键
--value自定义功能键的状态
local function onKey(key,value)
if stack[#stack] and stack[#stack].onKey then
stack[#stack].onKey(key,value)
end
end
--- 判断一个窗口是否处于最前显示
-- @number winId 窗口ID
-- @return booltrue表示最前显示其余表示非最前显示
-- @usage uiWin.isActive(winId)
function isActive(winId)
if stack[#stack] and stack[#stack].id then
return stack[#stack].id==winId
end
end
sys.subscribe("UIWND_ADD",onAdd)
sys.subscribe("UIWND_REMOVE",onRemove)
sys.subscribe("UIWND_REMOVEALL",onRemoveAll)
sys.subscribe("UIWND_UPDATE",onUpdate)
sys.subscribe("UIWND_KEY",onKey)

230
4G/code/lib/update.lua Normal file
View File

@@ -0,0 +1,230 @@
--- 模块功能:远程升级.
-- @module update
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2018.03.29
require "misc"
require "http"
require "log"
require "common"
module(..., package.seeall)
local sUpdating,sCbFnc,sUrl,sPeriod,sRedir,sLocation,fotastart
local sProcessedLen = 0
--local sBraekTest = 0
local httpRspCode
local sGetImeiFnc,sDownloadProcessFnc
local updateMsg
local otaBegin
local function httpDownloadCbFnc(result,statusCode,head)
log.info("update.httpDownloadCbFnc",result,statusCode,head,sCbFnc,sPeriod)
sys.publish("UPDATE_DOWNLOAD",result,statusCode,head)
end
local function processOta(stepData,totalLen,statusCode)
if stepData and totalLen then
if statusCode=="200" or statusCode=="206" then
if not otaBegin then sys.publish("LIB_UPDATE_OTA_DOWNLOAD_BEGIN") otaBegin=true end
local fotaProcessStatus=rtos.fota_process((sProcessedLen+stepData:len()>totalLen) and stepData:sub(1,totalLen-sProcessedLen) or stepData,totalLen)
if fotaProcessStatus~=0 then
log.error("update.processOta","fail",fotaProcessStatus,"failFotaProcessStatus")
log.error("update.processOta","get_fs_free_size: ",rtos.get_fs_free_size()," Bytes")
sys.publish("LIB_UPDATE_OTA_DOWNLOAD_END",false)
return false
else
sProcessedLen = sProcessedLen + stepData:len()
if sDownloadProcessFnc then sDownloadProcessFnc(sProcessedLen*100/totalLen) end
log.info("update.processOta",totalLen,sProcessedLen,(sProcessedLen*100/totalLen).."%")
--if sProcessedLen*100/totalLen==sBraekTest then return false end
if sProcessedLen*100/totalLen>=100 then sys.publish("LIB_UPDATE_OTA_DOWNLOAD_END",true) return true end
end
elseif statusCode:sub(1,1)~="3" and stepData:len()==totalLen and totalLen>0 then
if totalLen<=200 then
local msg = stepData:match("\"msg\":%s*\"(.-)\"")
if msg and msg:len()<=200 then
updateMsg = common.ucs2beToUtf8((msg:gsub("\\u","")):fromHex())
log.warn("update.error",updateMsg)
end
end
httpRspCode = stepData:match("\"code\":%s*(%d+)")
end
end
end
function clientTask()
sUpdating = true
--不要省略此处代码否则下文中的misc.getImei有可能获取不到
while not socket.isReady() do sys.waitUntil("IP_READY_IND") end
while true do
local retryCnt = 0
sProcessedLen = 0
otaBegin = false
updateMsg = nil
while true do
--sBraekTest = sBraekTest+30
log.info("update.http.request",sLocation,sUrl,sProcessedLen,sBraekTest,fotastart)
if not fotastart then break end
local coreVer = rtos.get_version()
local coreName1,coreName2 = coreVer:match("(.-)_V%d+(_.+)")
local coreVersion = tonumber(coreVer:match(".-_V(%d+)"))
httpRspCode = nil
-- 合宙云平台升级地址
local iotURL="iot.openluat.com/api/site/firmware_upgrade"
-- 模块信息
local moduleInfo="?project_key=".._G.PRODUCT_KEY
.."&imei="..(sGetImeiFnc and sGetImeiFnc() or misc.getImei())
.."&firmware_name=".._G.PROJECT.."_"..coreName1..coreName2
.."&core_version="..coreVersion
.."&dfota=1&version=".._G.VERSION..(sRedir and "&need_oss_url=1" or "")
-- 如果自定义升级地址前三位为“###”,则不拼接模块信息
if sUrl and string.sub(sUrl,1,3)=="###" then
log.info("1-3",string.sub(sUrl,1,3))
log.info("3-0",string.sub(sUrl,4))
customizeUrl=string.sub(sUrl,4)
elseif sUrl then
customizeUrl=sUrl..moduleInfo
else
-- 默认向合宙云平台地址拼接模块信息
iotURL=iotURL..moduleInfo
end
http.request("GET",
sLocation or (customizeUrl or iotURL),
nil,{["Range"]="bytes="..sProcessedLen.."-"},nil,60000,httpDownloadCbFnc,processOta)
local _,result,statusCode,head = sys.waitUntil("UPDATE_DOWNLOAD")
log.info("update.waitUntil UPDATE_DOWNLOAD",result,statusCode,httpRspCode)
if result then
local needBreak
if statusCode=="200" or statusCode=="206" then
needBreak = true
local check = rtos.fota_end()
log.info("update.rtos.fota_end", check)
if sCbFnc then
if check == 0 then
sCbFnc(true)
else
sCbFnc(false)
end
else
if check == 0 then
sys.restart("UPDATE_DOWNLOAD_SUCCESS")
end
end
elseif statusCode:sub(1,1)=="3" and head and head["Location"] then
sLocation = head["Location"]
sys.wait(2000)
elseif httpRspCode=="43" then
log.info("update.clientTask","wait server create fota")
sys.wait(30000)
else
log.info("update.rtos.fota_end",rtos.fota_end())
if sCbFnc then sCbFnc(false) end
needBreak = true
end
if needBreak then
break
end
else
retryCnt = retryCnt+1
if retryCnt==30 then
rtos.fota_end()
if sCbFnc then sCbFnc(false) end
break
end
end
end
sProcessedLen = 0
if sPeriod then
sys.wait(sPeriod)
if rtos.fota_start()~=0 then
log.error("update.request","fota_start fail")
fotastart = false
else
fotastart = true
end
else
break
end
end
sUpdating = false
end
--- 启动远程升级功能
-- @function[opt=nil] cbFnc 每次执行远程升级功能后的回调函数,回调函数的调用形式为:
-- cbFnc(result)result为true表示升级包下载成功其余表示下载失败
--如果没有设置此参数,则升级包下载成功后,会自动重启
-- @string[opt=nil] url 使用http的get命令下载升级包的url如果没有设置此参数默认使用Luatiot平台的url
-- 如果用户设置了url注意仅传入完整url的前半部分(如果有参数,即传入?前一部分)http.lua会自动添加?以及后面的参数,例如:
-- 设置的url="www.userserver.com/api/site/firmware_upgrade"则http.lua会在此url后面补充下面的参数
-- "?project_key=".._G.PRODUCT_KEY
-- .."&imei="..misc.getimei()
-- .."&device_key="..misc.getsn()
-- .."&firmware_name=".._G.PROJECT.."_"..rtos.get_version().."&version=".._G.VERSION
-- 如果用户设置了url且url前面增加三个井号"###",http.lua会自动忽略"###"并以用户填入的url作为请求地址不会自动添加模块信息例如
-- 设置的url="###www.userserver.com"/api/site/firmware_upgrade?customparam=test",则http.lua会将此url开头的"###"忽略,并以此url为地址进行请求
-- "www.userserver.com"/api/site/firmware_upgrade?customparam=test"
-- 如果redir设置为true还会补充.."&need_oss_url=1"
-- @number[opt=nil] period 单位毫秒,定时启动远程升级功能的间隔,如果没有设置此参数,仅执行一次远程升级功能
-- @bool[opt=nil] redir 是否访问重定向到阿里云的升级包使用Luat提供的升级服务器时此参数才有意义
-- 为了缓解Luat的升级服务器压力从2018年7月11日起在iot.openluat.com新增或者修改升级包的升级配置时升级文件会备份一份到阿里云服务器
-- 如果此参数设置为true会从阿里云服务器下载升级包如果此参数设置为false或者nil仍然从Luat的升级服务器下载升级包
-- @return nil
-- @usage
-- update.request()
-- update.request(cbFnc)
-- update.request(cbFnc,"www.userserver.com/update")
-- update.request(cbFnc,nil,4*3600*1000)
-- update.request(cbFnc,nil,4*3600*1000,true)
function request(cbFnc,url,period,redir)
if rtos.fota_start()~=0 then
log.error("update.request","fota_start fail")
fotastart = false
return
else
fotastart = true
end
sCbFnc,sUrl,sPeriod,sRedir = cbFnc or sCbFnc,url or sUrl,period or sPeriod,sRedir or redir
log.info("update.request",sCbFnc,sUrl,sPeriod,sRedir)
if not sUpdating then
sys.taskInit(clientTask)
end
end
function setGetImeiCbFnc(cbFnc)
sGetImeiFnc = cbFnc
end
--- 设置升级包下载过程中的下载进度通知回调函数
-- @function cbFnc 下载进度通知回调函数,回调函数的调用形式如下:
-- cbFnc(step)
-- step表示下载进度取值范围是0到100下载进度更新很快建议在回调函数中每隔5或者10执行一次实际动作
-- @return nil
-- @usage update.setDownloadProcessCbFnc(function(step) end)
function setDownloadProcessCbFnc(cbFnc)
sDownloadProcessFnc = cbFnc
end
--- 获取请求升级包时服务器返回的信息
-- @return updateMsg, 若没有请求升级或服务器未返回相关信息则返回值为nil否则返回服务器返回的相关信息
-- @usage local msg = getUpdateMsg()
function getUpdateMsg()
return updateMsg
end

286
4G/code/lib/utils.lua Normal file
View File

@@ -0,0 +1,286 @@
--- 模块功能:常用工具类接口
-- @module utils
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2019.01.05
module(..., package.seeall)
--- 将Lua字符串转成HEX字符串如"123abc"转为"313233616263"
-- @string str 输入字符串
-- @string[opt=""] separator 输出的16进制字符串分隔符
-- @return hexstring 16进制组成的串
-- @return len 输入的字符串长度
-- @usage
-- string.toHex("\1\2\3") -> "010203" 3
-- string.toHex("123abc") -> "313233616263" 6
-- string.toHex("123abc"," ") -> "31 32 33 61 62 63 " 6
function string.toHex(str, separator)
return str:gsub('.', function(c)
return string.format("%02X" .. (separator or ""), string.byte(c))
end)
end
--- 将HEX字符串转成Lua字符串如"313233616263"转为"123abc", 函数里加入了过滤分隔符,可以过滤掉大部分分隔符(可参见正则表达式中\s和\p的范围
-- @string hex 16进制组成的串
-- @return charstring,字符组成的串
-- @return len,输出字符串的长度
-- @usage
-- string.fromHex("010203") -> "\1\2\3"
-- string.fromHex("313233616263:) -> "123abc"
function string.fromHex(hex)
--滤掉分隔符
local hex = hex:gsub("[%s%p]", ""):upper()
return hex:gsub("%x%x", function(c)
return string.char(tonumber(c, 16))
end)
end
-- 返回字符串tonumber的转义字符串(用来支持超过31位整数的转换)
-- @string str 输入字符串
-- @return str 转换后的lua 二进制字符串
-- @return len 转换了多少个字符
-- @usage
-- string.toValue("123456") -> "\1\2\3\4\5\6" 6
-- string.toValue("123abc") -> "\1\2\3\a\b\c" 6
function string.toValue(str)
return string.fromHex(str:gsub("%x", "0%1"))
end
--- 返回utf8编码字符串的长度
-- @string str utf8编码的字符串,支持中文
-- @return number,返回字符串长度
-- @usage local cnt = string.utf8Len("中国a"),cnt == 3
function string.utf8Len(str)
local _, count = string.gsub(str, "[^\128-\193]", "")
return count
end
--- 返回utf8编码字符串的单个utf8字符的table
-- @string str utf8编码的字符串,支持中文
-- @return table,utf8字符串的table
-- @usage local t = string.utf8ToTable("中国2018")
function string.utf8ToTable(str)
local tab = {}
for uchar in string.gfind(str, "[%z\1-\127\194-\244][\128-\191]*") do
tab[#tab + 1] = uchar
end
return tab
end
--- 返回字符串的 RFC3986 编码
-- @string str 要转换编码的字符串,支持UTF8编码中文
-- @return str, RFC3986 编码的字符串
-- @usage local str = string.rawurlEncode("####133") ,str == "%23%23%23%23133"
-- @usage local str = string.rawurlEncode("中国2018") , str == "%e4%b8%ad%e5%9b%bd2018"
function string.rawurlEncode(str)
local t = str:utf8ToTable()
for i = 1, #t do
if #t[i] == 1 then
t[i] = string.gsub(string.gsub(t[i], "([^%w_%~%.%- ])", function(c) return string.format("%%%02X", string.byte(c)) end), " ", "%%20")
else
t[i] = string.gsub(t[i], ".", function(c) return string.format("%%%02X", string.byte(c)) end)
end
end
return table.concat(t)
end
--- 返回字符串的urlEncode编码
-- @string str 要转换编码的字符串,支持UTF8编码中文
-- @return str,urlEncode编码的字符串
-- @usage local str = string.urlEncode("####133") ,str == "%23%23%23%23133"
-- @usage local str = string.urlEncode("中国2018") , str == "%e4%b8%ad%e5%9b%bd2018"
function string.urlEncode(str)
local t = str:utf8ToTable()
for i = 1, #t do
if #t[i] == 1 then
t[i] = string.gsub(string.gsub(t[i], "([^%w_%*%.%- ])", function(c) return string.format("%%%02X", string.byte(c)) end), " ", "+")
else
t[i] = string.gsub(t[i], ".", function(c) return string.format("%%%02X", string.byte(c)) end)
end
end
return table.concat(t)
end
--- 返回一个迭代器函数,每次调用函数都会返回hash表的排序后的键值对
-- @table t 要排序的hash表
-- @param f 自定义排序函数
-- @return function.
-- @usage test = {a=1,f=9,d=2,c=8,b=5}
-- @usage for name,line in pairsByKeys(test) do print(name,line) end
function table.gsort(t, f)
local a = {}
for n in pairs(t) do a[#a + 1] = n end
table.sort(a, f)
local i = 0
return function()
i = i + 1
return a[i], t[a[i]]
end
end
--- table.concat的增强版支持嵌套字符串数组
-- @table l 嵌套字符串数组
-- @return string
-- @usage print(table.rconcat({"a",{" nice "}," and ", {{" long "},{" list "}}}))
function table.rconcat(l)
if type(l) ~= "table" then return l end
local res = {}
for i = 1, #l do
res[i] =table.rconcat(l[i])
end
return table.concat(res)
end
--- 返回数字的千位符号格式
-- @number num 数字
-- @return string千位符号的数字字符串
-- @usage loca s = string.formatNumberThousands(1000) ,s = "1,000"
function string.formatNumberThousands(num)
local k, formatted
formatted = tostring(tonumber(num))
while true do
formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
if k == 0 then break end
end
return formatted
end
--- 按照指定分隔符分割字符串
-- @string str 输入字符串
-- @string delimiter 分隔符
-- @return 分割后的字符串列表
-- @usage "123,456,789":split(',') -> {'123','456','789'}
function string.split(str, delimiter)
local strlist, tmp = {}, string.byte(delimiter)
if delimiter == "" then
for i = 1, #str do strlist[i] = str:sub(i, i) end
else
for substr in string.gmatch(str .. delimiter, "(.-)" .. (((tmp > 96 and tmp < 123) or (tmp > 64 and tmp < 91) or (tmp > 47 and tmp < 58)) and delimiter or "%" .. delimiter)) do
table.insert(strlist, substr)
end
end
return strlist
end
-- 和校验
-- @string str 需要校验的字符串
-- @string number 1为返回1个字节2为返回2个字节
-- @retrun 返回和校验结果
-- @usage string.checkSum("1234",1)
function string.checkSum(str, num)
assert(type(str) == "string", "The first argument is not a string!")
local sum = 0
for i = 1, #str do
sum = sum + str:sub(i, i):byte()
end
if num == 2 then
return sum % 0x10000
else
return sum % 0x100
end
end
--- 判断文件是否存在
-- @string path 文件全名,例如:"/lua/call.mp3"
-- @return bool,存在为true,不存在为false
-- @usage local ex = io.exists("/lua/call.mp3")
function io.exists(path)
local file = io.open(path, "r")
if file then
io.close(file)
return true
end
return false
end
--- 读取文件中的所有内容
-- @string path 文件全名,例如:"/lua/call.txt"
-- @return string,文件的内容,文件不存在返回nil
-- @usage local c = io.readFile("/lua/call.txt")
function io.readFile(path)
local file = io.open(path, "rb")
if file then
local content = file:read("*a")
io.close(file)
return content
end
end
--- 写入文件指定的内容,默认为覆盖二进制模式
-- @string path 文件全名,例如:"/lua/call.txt"
-- @string content 文件内容
-- @string mode 文件写入模式,支持如下几种(默认"w+b"
-- "w"或者"w+b":空文件写入模式,如果文件不存在,则新建文件,然后从起始位置开始写入;如果文件存在,则删除已有内容,然后从起始位置开始写入
-- "a"或者"a+b":追加写入模式,如果文件不存在,则新建文件,然后从起始位置开始写入;如果文件存在,则从文件末尾开始追加写入
-- @return boolean result,文件写入结果true表示写入成功false表示写入失败
-- @usage local c = io.writeFile("/lua/call.txt","test")
function io.writeFile(path, content, mode)
local mode = mode or "w+b"
local file = io.open(path, mode)
if file then
if file:write(content) == nil then return false end
io.close(file)
return true
else
return false
end
end
--- 将文件路径分解为table信息
-- @string path 文件路径全名,例如:"/lua/call.txt"
-- @return table,{dirname="/lua/",filename="call.txt",basename="call",extname=".txt"}
-- @usage loca p = io.pathInfo("/lua/call.txt")
function io.pathInfo(path)
local pos = string.len(path)
local extpos = pos + 1
while pos > 0 do
local b = string.byte(path, pos)
if b == 46 then -- 46 = char "."
extpos = pos
elseif b == 47 then -- 47 = char "/"
break
end
pos = pos - 1
end
local dirname = string.sub(path, 1, pos)
local filename = string.sub(path, pos + 1)
extpos = extpos - pos
local basename = string.sub(filename, 1, extpos - 1)
local extname = string.sub(filename, extpos)
return {
dirname = dirname,
filename = filename,
basename = basename,
extname = extname
}
end
--- 返回文件大小
-- @string path 文件路径全名,例如:"/lua/call.txt"
-- @return number ,文件大小
-- @usage locan cnt = io.fileSize("/lua/call.txt")
function io.fileSize(path)
local size = 0
local file = io.open(path, "r")
if file then
local current = file:seek()
size = file:seek("end")
file:seek("set", current)
io.close(file)
end
return size
end
--- 返回指定位置读取的字符串
-- @string path 文件路径全名,例如:"/lua/call.txt"
-- @number offset 要读取的指定位置,相对于文件开头的偏移位置
-- @number len 要读取的字节数
-- @return string,返回要读取的数据,读取失败返回nil
function io.readStream(path, offset, len)
local file, str = io.open(path, "r")
if file then
local current = file:seek()
file:seek("set", offset)
str = file:read(len)
file:seek("set", current)
io.close(file)
end
return str
end

1
4G/code/lib/wdt.lua Normal file
View File

@@ -0,0 +1 @@
module(..., package.seeall)

427
4G/code/lib/websocket.lua Normal file
View File

@@ -0,0 +1,427 @@
--- 模块功能websocket客户端
-- @module websocket
-- @author wendal
-- @license MIT
-- @copyright OpenLuat.com
-- @release 2023.09.18
require "utils"
require "socket"
module(..., package.seeall)
local magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
local ws = {}
ws.__index = ws
local function websocket(url, cert)
return setmetatable({
io = nil,
url = url,
key = "",
wss = "",
cert = cert,
host = "",
port = "",
input = "",
callbacks = {},
send_data = {},
send_text = nil,
sendsize = 1460,
open_callback = false,
connected = false,
terminated = false,
readyState = "CONNECTING"
}, ws)
end
--- 创建 websocket 对象
-- @string url websocket服务器的连接地址,格式为ws(或wss)://xxx开头
-- @table[opt=nil] cert ssl连接需要的证书配置cert格式如下
-- {
-- caCert = "ca.crt", --CA证书文件(Base64编码 X.509格式),如果存在此参数,则表示客户端会对服务器的证书进行校验;不存在则不校验
-- clientCert = "client.crt", --客户端证书文件(Base64编码 X.509格式),服务器对客户端的证书进行校验时会用到此参数
-- clientKey = "client.key", --客户端私钥文件(Base64编码 X.509格式)
-- clientPassword = "123456", --客户端证书文件密码[可选]
-- insist = 1, --证书中的域名校验失败时是否坚持连接默认为1坚持连接0为不连接
-- }
-- @return table 返回1个websocket对象
-- @usage local ws = websocket.new("ws://121.40.165.18:8800")
function new(url, cert)
return websocket(url, cert)
end
--- ws:on 注册函数
-- @string event 事件,可选值"open","message","close","error","pong"
-- @function callback 回调方法message|error|pong形参是该方法需要的数据。
-- @usage mt:on("message",function(message) local print(message)end)
function ws:on(event, callback)
self.callbacks[event] = callback
end
--- websocket 与 websocket 服务器建立连接
-- @number timeout 与websocket服务器建立连接最长超时
-- @return bool,true,表示连接成功,false or nil 表示连接失败
-- @usage while not ws:connect(20000) do sys.wait(2000) end
function ws:connect(timeout)
self.wss, self.host, self.port, self.path = self.url:match("(%a+)://([%w%.%-]+):?(%d*)(.*)")
self.wss, self.host = self.wss:lower(), self.host:lower()
self.port = self.port ~= "" and self.port or (self.wss == "wss" and 443 or 80)
if self.wss == "wss" then
self.io = socket.tcp(true, self.cert)
else
self.io = socket.tcp()
end
if not self.io then
log.error("websocket:connect:", "没有可用的TCP通道!")
return false
end
log.info("websocket url:", self.url)
if not self.io:connect(self.host, self.port, timeout) then
log.error("websocket:connect", "服务器连接失败!")
return false
end
self.key = crypto.base64_encode(math.random(100000000000000, 999999999999999) .. 0, 16)
local req = "GET " .. self.path .. " HTTP/1.1\r\nHost: " .. self.host .. ":" .. self.port .. "\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n" .. "Origin: http://" .. self.host ..
"\r\nSec-WebSocket-Version: 13\r\n" .. "Sec-WebSocket-Key: " .. self.key .. "\r\n\r\n"
if self.io:send(req, tonumber(timeout) or 20000) then
local r, s = self.io:recv(tonumber(timeout) or 5000)
if not r then
self.io:close()
log.error("websocket:connect", "与 websocket server 握手超时!")
return false
end
local _, idx, code = s:find("%s(%d+)%s.-\r\n")
if code == "101" then
local header, accept = {}, self.key .. magic
accept = crypto.sha1(accept, #accept):fromHex()
accept = crypto.base64_encode(accept, #accept)
for k, v in string.gmatch(s:sub(idx + 1, -1), "(.-):%s*(.-)\r\n") do
header[k:lower()] = v
end
if header["sec-websocket-accept"] and header["sec-websocket-accept"] == accept then
log.info("websocket:connect", "与 websocket server 握手成功!")
self.connected, self.readyState = true, "OPEN"
if self.callbacks.open then
self.open_callback = true
end
return true
end
end
end
log.error("websocket:connect", "与 websocket server 握手失败!")
return false
end
-- 掩码加密
-- mask: 4位长度掩码字符串
-- data: 待加密的字符串
-- return: 掩码加密后的字符串
local function wsmask(mask, data)
local i = 0
return data:gsub(".", function(c)
i = i + 1
return string.char(bit.bxor(data:byte(i), mask:byte((i - 1) % 4 + 1)))
end)
end
--- websocket发送帧方法
-- @bool fin true表示结束帧,false表示延续帧
-- @number opcode 0x0--0xF,其他值非法,代码意义参考websocket手册
-- @string data 用户要发送的数据
-- @usage self:sendFrame(true, 0x1, "www.openluat.com")
function ws:sendFrame(fin, opcode, data)
if not self.connected then
return
end
local finbit, maskbit, len = fin and 0x80 or 0, 0x80, #data
local frame = pack.pack("b", bit.bor(finbit, opcode))
if len < 126 then
frame = frame .. pack.pack("b", bit.bor(len, maskbit))
elseif len < 0xFFFF then
frame = frame .. pack.pack(">bH", bit.bor(126, maskbit), len)
else
-- frame = frame .. pack.pack(">BL", bit.bor(127, maskbit), len)
log.error("ws:sendFrame", "数据长度超过最大值!")
return
end
local mask = pack.pack(">I", os.time())
frame = frame .. mask .. wsmask(mask, data)
for i = 1, #frame, self.sendsize do
if not self.io:send(frame:sub(i, i + self.sendsize - 1)) then
break
end
end
return true
end
-- websocket 发送用户数据方法
-- @string data: 用户要发送的字符串数据
-- @bool text: true 数据为文本字符串,nil或false数据为二进制数据。
-- @usage self:send("www.openluat.com")
-- @usage self:send("www.openluat.com",true)
-- @usage self:send(string.fromHex("www.openluat.com"))
local function send(ws, data, text)
if text then
log.info("websocket cleint send:", data:sub(1, 100))
ws:sendFrame(true, 0x1, data)
else
ws:sendFrame(true, 0x2, data)
end
if ws.callbacks.sent then
ws.callbacks.sent()
end
end
-- websocket发送数据包
-- @usage self:send(data, is_text)
function ws:send(data, text)
table.insert(self.send_data, {data, text})
sys.publish("WEBSOCKET_SEND_DATA", "send")
end
local function ping(ws)
ws:sendFrame(true, 0x9, "")
end
local function pong(ws, data)
ws:sendFrame(true, 0xA, data or "")
end
-- websocket发送ping包(已废弃)
-- @usage self:ping()
function ws:ping() end
-- websocket发送pong包(已废弃)
-- @usage self:pong()
function ws:pong() end
local function uplink(ws)
while #ws.send_data > 0 do
local tmp = {}
for _, v in ipairs(ws.send_data) do
table.insert(tmp, v)
end
ws.send_data = {}
for _, v in ipairs(tmp) do
send(ws, v[1], v[2])
end
end
end
-- 处理 websocket 发过来的数据并解析帧数据
-- @return string : 返回解析后的单帧用户数据
function ws:recvFrame()
uplink(self) -- 有没有待上传数据, 有就全部上报
local close_ctrl = "EXIT_TASK" .. self.io.id
local r, s, p = self.io:recv(5000, "WEBSOCKET_SEND_DATA")
if not r then
if s == "timeout" then
return false, nil, "WEBSOCKET_OK"
elseif s == "WEBSOCKET_SEND_DATA" then
if p == "send" then -- 主动上报
uplink(self)
elseif p == "ping" then -- 本地心跳上行
ping(self, "")
elseif p == "pong" then -- 服务器心跳下行,马上回应
pong(self, "")
elseif p == close_ctrl then
return false, nil, close_ctrl
end
return false, nil, "WEBSOCKET_OK"
else
return false, nil, "Read byte error!"
end
end
if #self.input ~= 0 then
s = self.input .. s
end
local _, firstByte, secondByte = pack.unpack(s:sub(1, 2), "bb")
local fin = bit.band(firstByte, 0x80) ~= 0
local rsv = bit.band(firstByte, 0x70) ~= 0
local opcode = bit.band(firstByte, 0x0f)
local isControl = bit.band(opcode, 0x08) ~= 0
-- 检查RSV1,RSV2,RSV3 是否为0,客户端不支持扩展
if rsv then
return false, nil, "服务器正在使用未定义的扩展!"
end
-- 检查数据是否存在掩码加密
local maskbit = bit.band(secondByte, 0x80) ~= 0
local length = bit.band(secondByte, 0x7f)
if isControl and (length >= 126 or not fin) then
return false, nil, "控制帧异常!"
end
if maskbit then
return false, nil, "数据帧被掩码处理过!"
end
-- 获取载荷长度
if length == 126 then
-- if not r then return false, nil, "读取帧载荷长度失败!" end
_, length = pack.unpack(s:sub(3, 4), ">H")
elseif length == 127 then
return false, nil, "数据帧长度超过支持范围!"
end
-- 获取有效载荷数据
if length > 0 then
-- TODO 支持大于64的包
-- log.info("teste", #s, length)
if length > 126 then
-- r, s = self.io:recv()
if #s < length + 4 then
self.input = s
return true, false, ""
end
s = s:sub(5, 5 + length - 1)
else
s = s:sub(3, 3 + length - 1)
end
-- log.info("s的长度", #s, length)
-- if not r then return false, nil, "读取帧有效载荷数据失败!" end
end
-- 处理切片帧
if not fin then -- 切片未完成
return true, false, s
else -- 未分片帧
if opcode < 0x3 then -- 数据帧
self.input = ""
return true, true, s
elseif opcode == 0x8 then -- close
local code, reason
if #s >= 2 then
_, code = pack.unpack(s:sub(1, 2), ">H")
end
if #s > 2 then
reason = s:sub(3)
end
self.terminated = true
-- self:close(code, reason)
self.input = ""
return false, nil, reason
elseif opcode == 0x9 then -- Ping
pong(self, s or "")
elseif opcode == 0xA then -- Pong
if self.callbacks.pong then
self.callbacks.pong(s)
end
end
self.input = ""
return true, true, nil
end
end
--- 处理 websocket 发过来的数据并拼包
-- @return result, boolean: 返回数据的状态 true 为正常, false 为失败
-- @return data, string: result为true时为数据,false时为报错信息
-- @usage local result, data = ws:recv()
function ws:recv()
local data = ""
while true do
local success, final, message = self:recvFrame()
-- 数据帧解析错误
if not success then
return success, message
end
-- 数据帧分片处理
if message then
data = data .. message
else
data = "" -- 数据帧包含控制帧处理
end
-- 数据帧处理完成
if final and message then
break
end
end
if self.callbacks.message then
self.callbacks.message(data)
end
return true, data
end
--- 关闭 websocket 与服务器的链接
-- @number code 1000或1002等,请参考websocket标准
-- @string reason 关闭原因
-- @return nil
-- @usage ws:close()
-- @usage ws:close(1002,"协议错误")
function ws:close(code, reason)
-- 1000 "normal closure" status code
self.readyState = "CLOSING"
if self.terminated then
log.error("ws:close server code:", code, reason)
elseif self.io.connected then
if code == nil and reason ~= nil then
code = 1000
end
local data = ""
if code ~= nil then
data = pack.pack(">H", code)
end
if reason ~= nil then
data = data .. reason
end
self.terminated = true
self:sendFrame(true, 0x8, data)
end
self.io:close()
self.readyState, self.connected = "CLOSED", false
if self.callbacks.close then
self.callbacks.close(code or 1001)
end
self.input = ""
end
--- 主动退出一个指定的websocket任务
-- @传入一个websocket对象
-- @return nil
-- @usage wesocket.exit(ws)
function exit(ws)
sys.publish("WEBSOCKET_SEND_DATA", "EXIT_TASK" .. ws.io.id)
end
--- 获取websocket当前状态
-- @return string,状态值("CONNECTING","OPEN","CLOSING","CLOSED")
-- @usage ws:state()
function ws:state()
return self.readyState
end
--- 获取websocket与服务器连接状态
-- @return boolean: true 连接成功,其他值连接失败
-- @usage ws:online()
function ws:online()
return self.connected
end
--- websocket 需要在任务中启动,带自动重连,支持心跳协议
-- @number[opt=nil] keepAlive websocket心跳包建议30秒
-- @function[opt=nil] proc 处理服务器下发消息的函数
-- @number[opt=1000] reconnTime 断开链接后的重连时间
-- @return nil
-- @usage sys.taskInit(ws.start,ws,30)
-- @usage sys.taskInit(ws.start,ws,30,function(msg)u1:send(msg) end)
function ws:start(keepAlive, proc, reconnTime)
reconnTime = tonumber(reconnTime) and reconnTime * 1000 or 1000
keepAlivetimer = sys.timerLoopStart(self.ping, 30000, self)
while true do
while not socket.isReady() do
sys.wait(1000)
end
if self:connect() then
if self.open_callback == true then
self.callbacks.open()
self.open_callback = false
end
local close_ctrl = "EXIT_TASK" .. self.io.id
repeat
local r, message = self:recv()
if r then
if type(proc) == "function" then
proc(message)
end
elseif message == close_ctrl then
self:close()
sys.timerStop(keepAlivetimer)
if self.io.id ~= nil then
self = nil
end
return true
elseif not r and message ~= "WEBSOCKET_OK" then
log.error('ws recv error', message)
end
until not r and message ~= "WEBSOCKET_OK"
end
self:close()
log.info("websocket:Start", "与 websocket Server 的连接已断开!")
sys.wait(reconnTime)
end
end

642
4G/code/lib/wifiRil.lua Normal file
View File

@@ -0,0 +1,642 @@
--- 模块功能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的应答处理函数则会走到本函数
参数
cmd此应答对应的AT命令
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数据
返回值成功返回true失败返回nil
]]
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)

57
4G/code/lib/wifiScan.lua Normal file
View File

@@ -0,0 +1,57 @@
--- 模块功能wifi扫描功能
-- 支持wifi热点扫描
-- @module wifiScan
-- @author openLuat
-- @license MIT
-- @copyright openLuat
-- @release 2020.5.21
require"sys"
module(..., package.seeall)
local sCbFnc
--- wifi扫描热点请求
-- @function cbFnc 扫描到热点返回或者超时未返回的回调函数,回调函数的调用形式为:
-- cbFnc(result,cnt,info)
-- resulttrue或者falsetrue表示扫描成功false表示扫描失败或者超时失败
-- cntnumber类型表示扫描到的热点个数
-- infotable或者nil类型result为false时为nilresult为true时表示扫码到的热点mac和信号信息table类型例如
-- {
-- ["1a:fe:34:9e:a1:77"] = -63,
-- ["8c:be:be:2d:cd:e9"] = -81,
-- ["20:4e:7f:82:c2:c4"] = -70,
-- }
-- @number[opt=10000] timeout 等待扫描热点返回的超时时间单位毫秒默认为10秒
-- @usage
-- wifiScan.request(cbFnc)
-- wifiScan.request(cbFnc,5000)
function request(cbFnc,timeout)
sCbFnc = cbFnc
sys.timerStart(sCbFnc,timeout or 10000,false)
wifi.getinfo()
end
local function wifiMsg(msg)
log.info("wifiScan.wifiMsg",msg.cnt,msg.info,sys.timerIsActive(sCbFnc,false))
if sys.timerIsActive(sCbFnc,false) then
sys.timerStop(sCbFnc,false)
local num,info = msg.cnt,msg.info
if num==0 then
sCbFnc(false,0)
else
--9a0074bdb0e8,183,2;40313cd7b4bb,185,4;828917c49d9a,173,2;8107999c460,175,8;c4b548f863e,160,7;
log.info("wifi.getinfo",num,info)
local tInfo,cnt = {},0
for mac,rssi,channel in string.gmatch(info,"(.-),(.-),(.-);") do
cnt = cnt+1
if mac:len()<12 then mac=string.rep("0",12-mac:len())..mac end
tInfo[mac:sub(1,2)..":"..mac:sub(3,4)..":"..mac:sub(5,6)..":"..mac:sub(7,8)..":"..mac:sub(9,10)..":"..mac:sub(11,12)] = tonumber(rssi)
end
sCbFnc(true,cnt,tInfo)
end
end
end
--注册core上报的rtos.MSG_WIFI消息的处理函数
rtos.on(rtos.MSG_WIFI,wifiMsg)

BIN
4G/tools/Luatools_v3.exe Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
{
"TheseAreComments!":[
{"Comment": "'PatchBlockSize' must be set 32K, other block size has not been supported yet!"},
{"Comment": "'FotaMemSize' is not the entire customized FOTA zone in flash but the one only for delta downloading!"},
{"Comment": "'attr' in 'ImageMem' must be set with one of these choices('BINPKG/BL/AP/CP/APP/APP2'), which is forbidden to be redefined!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC616(S)",
"PatchBlockSize": 32768,
"FotaMemSize": 491520,
"ImageMem": [
{
"attr": "APP",
"addr": 8519680,
"size": 2621440
}
]
}

View File

@@ -0,0 +1,34 @@
{
"TheseAreComments!":[
{"Comment": "'PatchBlockSize' must be set 32K, other block size has not been supported yet!"},
{"Comment": "'FotaMemSize' is not the entire customized FOTA zone in flash but the one only for delta downloading!"},
{"Comment": "'attr' in 'ImageMem' must be set with one of these choices('BINPKG/BL/AP/CP/APP/APP2'), which is forbidden to be redefined!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC618",
"PatchBlockSize": 32768,
"FotaMemSize": 491520,
"ImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8536064,
"size": 3014656
},
{
"attr": "CP",
"addr": 8388608,
"size": 524288
},
{
"attr": "APP",
"addr": 0,
"size": 0
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
FotaToolkit.exe -d config\ec618.json %1 %2 old.binpkg new.binpkg

View File

@@ -0,0 +1,3 @@
{
"generatorconfig": "E:/file/LTE/ec618/tools/FotaToolkit/config/ec618.json"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC716E",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC716S",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 217088,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8855552,
"size": 1261568
},
{
"attr": "CP",
"addr": 8495104,
"size": 360448
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 2097152
},
{
"name": "CP",
"base": 8388608,
"size": 2097152
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 1261568
},
{
"enable": 0,
"attr": "CP",
"size": 360448
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718HM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718P",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718PM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 360448,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8921088,
"size": 3174400
},
{
"attr": "CP",
"addr": 8511488,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718P",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718S",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 217088,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8855552,
"size": 1261568
},
{
"attr": "CP",
"addr": 8495104,
"size": 360448
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 2097152
},
{
"name": "CP",
"base": 8388608,
"size": 2097152
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 1261568
},
{
"enable": 0,
"attr": "CP",
"size": 360448
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718U",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718UM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 1,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
程序名称Deltagen
程序版本deltagen20221121
deltagen20221121
1.优化了相同块的差分包生成方式兼容新旧bootloader 差分库。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC716E",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC716S",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 217088,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8855552,
"size": 1261568
},
{
"attr": "CP",
"addr": 8495104,
"size": 360448
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 2097152
},
{
"name": "CP",
"base": 8388608,
"size": 2097152
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 1261568
},
{
"enable": 0,
"attr": "CP",
"size": 360448
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718HM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718P",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718PM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 360448,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8921088,
"size": 3174400
},
{
"attr": "CP",
"addr": 8511488,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718P",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 331776,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8904704,
"size": 3190784
},
{
"attr": "CP",
"addr": 8495104,
"size": 409600
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 4194304
},
{
"name": "CP",
"base": 8388608,
"size": 4194304
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 3190784
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718S",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 0,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 217088,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8855552,
"size": 1261568
},
{
"attr": "CP",
"addr": 8495104,
"size": 360448
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 2097152
},
{
"name": "CP",
"base": 8388608,
"size": 2097152
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 1261568
},
{
"enable": 0,
"attr": "CP",
"size": 360448
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718U",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

View File

@@ -0,0 +1,146 @@
{
"TheseAreComments!":[
{"Comment": "'capacity' in 'FotaCapability' is the largest size of all images in one fota processing (0-4MB;1-16MB)!"},
{"Comment": "'diffMode' in 'FotaCapability' is the flag for handling compressed section(s) in binary file (0-no zip;1-has zip)!"},
{"Comment": "'attr' in 'SysImageMem' must be set with one of these choices('BINPKG/AP/CP/APP/APP2/SYSH'), which are forbidden to be redefined!"},
{"Comment": "'attr' in 'ExtenOtaMem' must be set with one of these choices('EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'name' in 'StorageDevice' must be set with one of these choices('AP/CP/EF/SD'), which are forbidden to be redefined!"},
{"Comment": "'base' in 'StorageDevice' is the global unique starting address, and it can not be modified!"},
{"Comment": "However, other fields can be redefined to whatever you want, including the name of this config file!"}
],
"CompanyName": "EiGENCOMM",
"ProductName": "EC718UM",
"FotaCoreVer": "2.5",
"FotaCapability": {
"capacity": 0,
"diffMode": 1,
"hasPkgHlc": 1,
"metaPseg": 32,
"bkupMemSize": 98304,
"deltaMemSize": 950272,
"avlbRamSize": 196608,
"zipAlgorithm": {
"zipMeth": 0,
"bzipMode": 0
}
},
"HuluSuite": {
"ver": 1,
"enable": 0,
"adjust": 1,
"wkspSize": 450560
},
"FotaAtCmd": {
"mode": 0,
"ate": 0,
"ver": 1,
"name": "",
"pmss": 1024,
"dmwt": 300
},
"SysImageMem": [
{
"attr": "BINPKG",
"addr": 0,
"size": 0
},
{
"attr": "AP",
"addr": 8982528,
"size": 6795264
},
{
"attr": "CP",
"addr": 8572928,
"size": 655360
},
{
"attr": "APP",
"addr": 0,
"size": 0
},
{
"attr": "APP2",
"addr": 0,
"size": 0
},
{
"attr": "SYSH",
"addr": 8392704,
"size": 4096
}
],
"ExtenOtaMem": [
{
"attr": "EF",
"addr": 2147483648,
"size": 10240
}
],
"StorageDevice": [
{
"name": "AP",
"base": 8388608,
"size": 8388608
},
{
"name": "CP",
"base": 8388608,
"size": 8388608
},
{
"name": "EF",
"base": 2147483648,
"size": 8388608
}
],
"CustFeature": {
"BootSecurity":{
"enable": 1,
"shaMode": 224,
"bootCombo": [
{
"enable": 1,
"imgs": "AP,CP",
"imgh": "SYSH",
"pemUri":""
},
{
"enable": 0,
"imgs": "APP",
"imgh": "APP2",
"pemUri":""
}
]
},
"ResizeMem": [
{
"enable": 0,
"attr": "AP",
"size": 6868992
},
{
"enable": 0,
"attr": "CP",
"size": 409600
},
{
"enable": 0,
"attr": "APP",
"size": 0
}
]
},
"MiscSetting": {
"pollRx": 30,
"filter": {
"prefix": "XPKGD,PKGFLX,EF"
},
"debug": {
"delta": {
"patMem": 0,
"patVeri": 7
}
}
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
程序名称Deltagen
程序版本deltagen20221121
deltagen20221121
1.优化了相同块的差分包生成方式兼容新旧bootloader 差分库。

Some files were not shown because too many files have changed in this diff Show More