Просмотр исходного кода

add: 把bsp/air724重新搭一下

Wendal Chen 5 лет назад
Родитель
Сommit
0f542967b1

+ 14 - 0
bsp/air724/README.md

@@ -0,0 +1,14 @@
+# Air724@LuatOS
+
+## Air724是什么?
+
+合宙Air724/Air720UG/Air722/Air820, 是基于RDA8910的4G cat.1模块
+
+## LuatOS为它提供哪些功能
+
+* 1024kb 的系统内存, 1526kb 的Lua专属内存
+* 刷机脚本区256kb, OTA脚本区256kb, 常规文件系统区1024kb
+* 基于Lua 5.3.6, 提供95%的原生库支持
+
+LuatOS大QQ群: 1061642968
+

+ 23 - 0
bsp/air724/demo/gpio/main.lua

@@ -0,0 +1,23 @@
+
+PROJECT = "gpiodemo"
+VERSION = "1.0.0"
+
+local sys = require "sys"
+
+pmd.ldoset(1800, pmd.LDO_VLCD)
+
+sys.taskInit(function()
+    netled = gpio.setup(1, 0)
+    --netmode = gpio.setup(4, 0)
+    while 1 do
+        netled(1)
+        --netmode(0)
+        sys.wait(500)
+        netled(0)
+        --netmode(1)
+        sys.wait(500)
+        log.info("luatos", "hi", os.date())
+    end
+end)
+
+sys.run()

+ 14 - 0
bsp/air724/demo/hi/main.lua

@@ -0,0 +1,14 @@
+
+PROJECT = "hi"
+VERSION = "1.0.0"
+
+local sys = require "sys"
+
+sys.taskInit(function()
+    while 1 do
+        log.info("luatos", "hi", os.date())
+        sys.wait(1000)
+    end
+end)
+
+sys.run()

+ 33 - 0
bsp/air724/demo/uart/main.lua

@@ -0,0 +1,33 @@
+
+PROJECT = "uartdemo"
+VERSION = "1.0.0"
+
+local sys = require "sys"
+
+pmd.ldoset(1800, pmd.LDO_VLCD)
+
+sys.taskInit(function()
+    netled = gpio.setup(1, 0)
+    while 1 do
+        netled(1)
+        sys.wait(300)
+        netled(0)
+        sys.wait(300)
+        log.info("luatos", "hi", os.date())
+    end
+end)
+
+sys.subscribe("UART_WRITE", function (id, data)
+    if #data > 0 then
+        uart.write(id, data)
+    end
+end)
+
+uart.setup(2, 115200)
+uart.on(2, "recv", function (id, len)
+    local data = uart.read(id, 1024)
+    log.info("uart", id, len, #data)
+    sys.publish("UART_WRITE", id, data)
+end)
+
+sys.run()

+ 26 - 0
bsp/air724/demo/wifiscan/main.lua

@@ -0,0 +1,26 @@
+
+PROJECT = "wlandemo"
+VERSION = "1.0.0"
+
+local sys = require "sys"
+
+pmd.ldoset(1800, pmd.LDO_VLCD)
+
+sys.taskInit(function()
+    local netled = gpio.setup(1, 0)
+    local count = 1
+    while 1 do
+        netled(1)
+        sys.wait(1000)
+        netled(0)
+        sys.wait(1000)
+        log.info("luatos", "hi", count, os.date())
+        count = count + 1
+    end
+end)
+
+sys.timerLoopStart(function ()
+    wlan.scan()
+end, 60000)
+
+sys.run()

+ 6 - 0
bsp/air724/doc/pac_version.md

@@ -0,0 +1,6 @@
+# 固件定义
+
+预计推出2个版本: 基础数传版 , 全功能版(可定制)
+
+* 基础数传版 - 核心功能强测试保证, 联网/uart/gpio
+* 全功能版 - 外设-i2c/spi/adc,显示-disp/u8g2/lvgl,语音-volte/tts/mic,蓝牙-bt/wifiscan, 有啥就加啥

+ 4 - 0
bsp/air724/lib/log.lua

@@ -0,0 +1,4 @@
+-- 这个文件只是为了方便luatools刷机. log库是内置的
+local log = {}
+
+return log

+ 551 - 0
bsp/air724/lib/mqtt.lua

@@ -0,0 +1,551 @@
+
+--- 模块功能:MQTT客户端
+-- @module mqtt
+-- @author openLuat
+-- @license MIT
+-- @copyright openLuat
+-- @release 2017.10.24
+
+local mqtt = {}
+
+-- 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 sys = require "sys"
+local pack = _G.pack
+local string = _G.string
+local encodeLen = mqttcore.encodeLen
+--local encodeUTF8 = mqttcore.encodeUTF8
+-- 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)
+--             digit = digit | 0x80
+--         end
+--         s = s .. string.char(digit)
+--     until (len <= 0)
+--     return s
+-- end
+
+local encodeUTF8 = mqttcore.encodeUTF8
+-- local function encodeUTF8(s)
+--     if not s or #s == 0 then
+--         return ""
+--     else
+--         return pack.pack(">P", s)
+--     end
+-- end
+
+local packCONNECT = mqttcore.packCONNECT
+
+-- 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))
+--     local mydata = pack.pack(">bAA",
+--         CONNECT * 16,
+--         encodeLen(string.len(content)),
+--         content)
+--     local tdata = mqttcore.packCONNECT(clientId, keepAlive, username, password, cleanSession, will, version)
+--     log.info("mqtt", "true", mydata:toHex())
+--     log.info("mqtt", "false", tdata:toHex())
+--     return mydata
+-- end
+
+local packSUBSCRIBE = mqttcore.packSUBSCRIBE
+
+-- 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
+--     local mydata = pack.pack(">bAA", header, encodeLen(#data), data)
+--     log.info("mqtt", "true", mydata:toHex())
+--     local tdata = mqttcore.packSUBSCRIBE(dup, packetId, topics)
+--     log.info("mqtt", "false", tdata:toHex())
+--     return mydata
+-- end
+
+local packUNSUBSCRIBE = mqttcore.packUNSUBSCRIBE
+-- 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 packPUBLISH = mqttcore.packPUBLISH
+
+-- local function packPUBLISH(dup, qos, retain, packetId, topic, payload)
+--     local header = PUBLISH * 16 + dup * 8 + qos * 2 + retain
+--     local len = 2 + #topic + #payload
+--     local mydata = nil
+--     if qos > 0 then
+--         mydata = pack.pack(">bAPHA", header, encodeLen(len + 2), topic, packetId, payload)
+--     else
+--         mydata = pack.pack(">bAPA", header, encodeLen(len), topic, payload)
+--     end
+--     local tdata = mqttcore.packPUBLISH(dup, qos, retain, packetId, topic, payload)
+--     log.info("mqtt", "true", mydata:toHex())
+--     log.info("mqtt", "false", tdata:toHex())
+--     return mydata
+-- end
+
+local packACK = mqttcore.packACK
+
+-- 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 packZeroData = mqttcore.packZeroData
+
+-- 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 packet = {id = (header - (header % 16)) >> 4, dup = ((header % 16) - ((header % 16) % 8)) >> 3, qos = (header & 0x06) >> 1, retain = (header & 0x01)}
+    local nextpos
+    
+    if packet.id == CONNACK then
+        nextpos, packet.ackFlag, packet.rc = pack.unpack(s, "bb", pos)
+    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版本号
+-- @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,nil,"3.1")
+function mqtt.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
+    o.pkgs = {}
+    
+    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 topic = "MQTTC_PKG_" .. tostring(self.io:id())
+    local result, data, param = sys.waitUntil({topic, msg}, timeout)
+    --log.debug("mqtt.read", result, data, param)
+    if result then -- 收到topic消息
+        local pkg = table.remove(self.pkgs, 1)
+        if pkg ~= nil then
+            --log.debug("mqtt", "get packet", pkg.id, pkg.packetId)
+            return true, pkg
+        end
+        --log.debug("mqtt", "get sys.msg", msg, data)
+        return false, msg, data
+    else
+        if self.io:closed() == 1 then
+            return false
+        else
+            return false, "timeout"
+        end
+    end
+end
+
+local function update_resp(_self, data)
+    if #data > 0 then
+        if #_self.inbuf > 0 then
+            _self.inbuf = _self.inbuf .. data
+        else
+            _self.inbuf = data
+        end
+    end
+    --log.debug("mqttc", "data recv to unpack", _self.inbuf:toHex())
+    local packet, nextpos = unpack(_self.inbuf)
+    if packet then
+        log.info("mqttc", "msg unpack ok", packet.id)
+        _self.inbuf = string.sub(_self.inbuf, nextpos)
+        table.insert(_self.pkgs, packet)
+        sys.publish("MQTTC_PKG_" .. tostring(_self.io:id()))
+        if #_self.inbuf > 0 then
+            update_resp(_self, "")
+        end
+    else
+        log.info("mqttc", "data not full")
+    end
+
+    return true
+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 timeout, 链接服务器最长超时时间
+-- @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:clean()
+        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)
+    self.io:host(host)
+    self.io:port(port)
+    local connect_topic = "NETC_CONNECT_" .. tostring(self.io:id())
+    self.io:on("connect", function(id, re)
+        log.info("mqtt", "connect result", re, re == 1 and "OK" or "FAIL")
+        sys.publish(connect_topic, re == 1)
+        if re == 0 then
+            self.io:clean()
+            self.io:close()
+        end
+    end)
+    self.io:on("recv", function(id, data)
+        if not update_resp(self, data) then
+            log.info("mqtt", "close connect for bad data")
+            self.io:clean()
+            self.io:close()
+        end
+    end)
+    if not self.io:start() then
+        self.io:clean()
+        self.io:close()
+        log.info("mqtt", "fail to start socket thread")
+        return false
+    end
+    --log.info("mqtt", "wait for connect")
+    local result, linked = sys.waitUntil(connect_topic, self.commandTimeout) -- 原本是15秒,在NBIOT下超时的概率不低,改成用commandTimeout
+    if not result then
+        log.info("mqtt", "connect timeout")
+        self.io:clean()
+        self.io:close()
+        return false
+    end
+    if not linked  or self.io:closed() == 1 then
+        log.info("mqtt", "connect fail", result, linked, self.io:closed() == 1)
+        self.io:clean()
+        self.io:close()
+        return false
+    end
+    --log.info("mqtt", "send packCONNECT")
+    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
+    --log.info("mqtt", "waitfor CONNACK")
+    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
+    end
+    
+    self.connected = true
+    --log.info("mqtt", "connected!~!")
+    
+    return true
+end
+
+--- 订阅主题
+-- @param topic,string或者table类型,一个主题时为string类型,多个主题时为table类型,主题内容为UTF8编码
+-- @param[opt=0] qos,number或者nil,topic为一个主题时,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
+    
+    if not self:waitfor(SUBACK, self.commandTimeout, nil, true) then
+        log.info("mqtt.client:subscribe", "wait ack failed")
+        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,表示服务器发过来的包;如果result为false,表示错误信息,超时失败时为"timeout"
+-- @return param msg控制退出时,返回msg的字符串
+-- @usage
+-- true, packet = mqttc:receive(2000)
+-- false, error_message = mqttc:receive(2000)
+-- false, msg, para = mqttc:receive(2000)
+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
+
+return mqtt

+ 274 - 0
bsp/air724/lib/mqtt2.lua

@@ -0,0 +1,274 @@
+--[[
+异步MQTT客户端
+1. 自动重连
+2. 异步收发信息
+
+暂不支持的特性:
+1. qos 2的消息不被支持,以后也不会添加
+2. 不支持取消订阅(也许会添加,也许不会)
+
+用法请参考demo
+
+]]
+
+-- 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 packCONNECT = mqttcore.packCONNECT
+local packPUBLISH = mqttcore.packPUBLISH
+local packSUBSCRIBE = mqttcore.packSUBSCRIBE
+local packACK = mqttcore.packACK
+local packZeroData = mqttcore.packZeroData
+
+
+local mclog = log.debug
+
+local unpack = mqttcore.unpack or function(s)
+    if #s < 2 then return end
+    --mclog("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 packet = {id = (header - (header % 16)) >> 4, dup = ((header % 16) - ((header % 16) % 8)) >> 3, qos = (header & 0x06) >> 1, retain = (header & 0x01)}
+    local nextpos
+
+    if packet.id == CONNACK then
+        nextpos, packet.ackFlag, packet.rc = pack.unpack(s, "bb", pos)
+    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 mqtt2 = {}
+
+local mqttc = {}
+mqttc.__index = mqttc
+
+function mqtt2.new(clientId, keepAlive, username, password, cleanSession, host, port, topics, cb, ckey)
+    local c = {
+        clientId = clientId,
+        keepAlive = keepAlive or 300,
+        username = username or "",
+        password = password or "",
+        cleanSession = cleanSession == nil and 1 or 0,
+        host = host,
+        port = port,
+        lping = 0,
+        stat = 0, -- 状态 0, 未连接, 1 已连接成功
+        nextid = 0, -- pkg的ID
+        running = false,
+        inpkgs = {},
+        outpkgs = {},
+        buff = "",
+        topics = topics or {},
+        cb = cb,
+        ckey = ckey or ""
+    }
+    if c.ckey == "" then c.ckey = "mqtt_" .. tostring(c) end
+    --mclog("mqtt", "MQTT Client Key", c.ckey)
+    setmetatable(c, mqttc)
+    return c
+end
+
+-- 内部方法, 用于获取下一个pkg的id
+function mqttc:genId()
+    self.nextid = self.nextid == 65535 and 1 or (self.nextid + 1)
+    --mclog("mqtt", "next packet id", self.nextid)
+    return self.nextid
+end
+
+-- 内部方法,用于处理待处理的数据包
+function mqttc:handle(netc)
+    local mc = self
+    -- 先处理服务器下发的数据包
+    if #mc.inpkgs > 0 then
+        --mclog("mqtt", "inpkgs count", #mc.inpkgs)
+        while 1 do
+            local pkg = table.remove( mc.inpkgs, 1 )
+            if pkg == nil then
+                break
+            end
+            -- 处理服务器下发的包
+            --mclog("mqtt", "handle pkg", json.encode(pkg))
+            if pkg.id == CONNACK then
+                mc.stat = 1
+                mc.lping = os.time()
+                --mclog("mqtt", "GOT CONNACK")
+                for k, v in pairs(mc.topics) do
+                    --mclog("mqtt", "sub topics", json.encode(mc.topics))
+                    netc:send(packSUBSCRIBE(0, mc:genId(), mc.topics))
+                    break
+                end
+            elseif pkg.id == PUBACK then
+                --mclog("mqtt", "GOT PUBACK")
+                sys.publish(mc.ckey .. "PUBACK")
+            elseif pkg.id == SUBACK then
+                --mclog("mqtt", "GOT SUBACK")
+                sys.publish(mc.ckey .. "SUBACK")
+            elseif pkg.id == PINGRESP then
+                mc.lping = os.time()
+                --mclog("mqtt", "GOT PINGRESP", mc.lping)
+            elseif pkg.id == UNSUBACK then
+                --mclog("mqtt", "GOT UNSUBACK")
+            elseif pkg.id == DISCONNECT then
+                --mclog("mqtt", "GOT DISCONNECT")
+            elseif pkg.id == PUBLISH then
+                --mclog("mqtt", "GOT PUBLISH", pkg.topic, pkg.qos)
+                if pkg.packetId > 0 then
+                    -- 发送PUBACK
+                    --mclog("mqtt", "send back PUBACK")
+                    table.insert( mc.outpkgs, packACK(PUBACK, 0, pkg.packetId))
+                end
+                if mc.cb then
+                    --mclog("mqtt", "Callback for PUBLISH", mc.cb)
+                    mc.cb(pkg)
+                end
+            end
+        end
+    end
+    -- 处理需要上报的数据包
+    if #mc.outpkgs > 0 then
+        --mclog("mqtt", "outpkgs count", #mc.outpkgs)
+        while 1 do
+            local buff = table.remove( mc.outpkgs, 1)
+            if buff == nil then
+                break
+            end
+            --mclog("mqtt", "netc send", buff:toHex())
+            netc:send(buff)
+        end
+    end
+    -- 是否需要发心跳
+    if mc.lping > 0 and os.time() - mc.lping > mc.keepAlive * 0.75 then
+        --mclog("mqtt", "time for ping", mc.lping)
+        mc.lping = os.time()
+        netc:send(packZeroData(PINGREQ)) -- 发送心跳包
+    end
+end
+
+-- 启动mqtt task, 要么在task里面执行, 要么新建一个task执行本方法
+function mqttc:run()
+    local mc = self
+    mc.running = true
+    while mc.running do
+        if socket.isReady() then
+            -- 先复位全部临时对象
+            mc.buff = ""
+            mc.inpkgs = {}
+            mc.outpkgs = {}
+            -- 建立socket对象
+            --mclog("mqtt", "try connect")
+            local netc = socket.tcp()
+            netc:host(mc.host)
+            netc:port(mc.port)
+            netc:on("connect", function(id, re)
+                --mclog("mqtt", "connect", id , re)
+                if re then
+                    -- 发送CONN包
+                    table.insert(mc.outpkgs, packCONNECT(mc.clientId, mc.keepAlive, mc.username, mc.password, mc.cleanSession, {topic="",payload="",qos=0,retain=0,flag=0}))
+                    sys.publish(mc.ckey)
+                end
+            end)
+            netc:on("recv", function(id, data)
+                --mclog("mqtt", "recv", id , data:sub(1, 10):toHex())
+                mc.buff = mc.buff .. data
+                while 1 do
+                    local packet, nextpos = unpack(mc.buff)
+                    if not packet then
+                        if #mc.buff > 4096 then
+                            log.warn("mqtt", "packet is too big!!!")
+                            netc:close()
+                        end
+                        break
+                    else
+                        mc.buff = mc.buff:sub(nextpos)
+                        --mclog("mqtt", "recv new pkg", json.encode(packet))
+                        table.insert( mc.inpkgs, packet)
+                        if #mc.buff < 2 then
+                            break
+                        end
+                    end
+                end
+                if #mc.inpkgs > 0 then
+                    sys.publish(mc.ckey)
+                end
+            end)
+            if netc:start() == 0 then
+                --mclog("mqtt", "start success")
+                local endTopic = "NETC_END_" .. netc:id()
+                while (netc:closed()) == 0 do
+                    mc:handle(netc)
+                    sys.waitUntil({endTopic, mc.ckey}, 30000)
+                    if not mc.running then netc:close() end
+                    --mclog("mqtt", "handle/timeout/ping", (netc:closed()))
+                end
+            end
+            -- 清理socket上下文
+            mclog("mqtt", "clean up")
+            netc:clean()
+            netc:close()
+            -- 将所有状态复位
+            mc.stat = 0
+
+            mclog("mqtt", "wait 5s for next loop")
+            sys.wait(5*1000) -- TODO 使用级数递增进行延时
+        else
+            sys.wait(1000)
+        end
+    end
+    -- 线程退出, 只可能是用户主动shutdown
+    mclog("mqtt", self.ckey, "exit")
+end
+
+-- 订阅topic, table形式
+function mqttc:sub(topics)
+    table.insert(self.outpkgs, packSUBSCRIBE(0, self:genId(), topics))
+    sys.publish(self.ckey)
+    return sys.waitUntil(self.ckey .. "SUBACK", 30000)
+end
+
+-- 上报数据
+function mqttc:pub(topic, qos, payload)
+    -- local function packPUBLISH(dup, qos, retain, packetId, topic, payload)
+    table.insert(self.outpkgs, packPUBLISH(0, qos, 0, qos > 0 and self:genId() or 0, topic, payload))
+    sys.publish(self.ckey)
+    if qos > 0 then
+        return sys.waitUntil(self.ckey .. "PUBACK", 30000)
+    end
+end
+
+function mqttc:shutdown()
+    self.running = false
+    sys.publish(self.ckey)
+end
+
+return mqtt2

+ 396 - 0
bsp/air724/lib/sys.lua

@@ -0,0 +1,396 @@
+--- 模块功能:Luat协程调度框架
+--module(..., package.seeall)
+
+local sys = {}
+
+local table = _G.table
+local unpack = table.unpack
+local rtos = _G.rtos
+local coroutine = _G.coroutine
+local log = _G.log
+
+-- lib脚本版本号,只要lib中的任何一个脚本做了修改,都需要更新此版本号
+SCRIPT_LIB_VER = "1.0.0"
+
+-- TaskID最大值
+local TASK_TIMER_ID_MAX = 0x1FFFFF
+-- msgId 最大值(请勿修改否则会发生msgId碰撞的危险)
+local MSG_TIMER_ID_MAX = 0x7FFFFF
+
+-- 任务定时器id
+local taskTimerId = 0
+-- 消息定时器id
+local msgId = TASK_TIMER_ID_MAX
+-- 定时器id表
+local timerPool = {}
+local taskTimerPool = {}
+--消息定时器参数表
+local para = {}
+--定时器是否循环表
+local loop = {}
+--lua脚本运行出错时,是否回退为本地烧写的版本
+local sRollBack = true
+
+_G.COROUTINE_ERROR_ROLL_BACK = true
+_G.COROUTINE_ERROR_RESTART = true
+
+-- 对coroutine.resume加一个修饰器用于捕获协程错误
+local rawcoresume = coroutine.resume
+sys.coresume = function(...)
+    function wrapper(co,...)
+        local arg = {...}
+        if not arg[1] then
+            local traceBack = debug.traceback(co)
+            traceBack = (traceBack and traceBack~="") and (arg[2].."\r\n"..traceBack) or arg[2]
+            log.error("coroutine.resume",traceBack)
+            if errDump and type(errDump.appendErr)=="function" then
+                errDump.appendErr(traceBack)
+            end
+            if _G.COROUTINE_ERROR_ROLL_BACK then
+                sys.timerStart(assert,500,false,traceBack)
+            elseif _G.COROUTINE_ERROR_RESTART then
+                rtos.reboot()
+            end
+        end
+        return ...
+    end
+    local arg = {...}
+    return wrapper(arg[1], rawcoresume(...))
+end
+
+--- Task任务延时函数,只能用于任务函数中
+-- @number ms  整数,最大等待126322567毫秒
+-- @return 定时结束返回nil,被其他线程唤起返回调用线程传入的参数
+-- @usage sys.wait(30)
+function sys.wait(ms)
+    -- 参数检测,参数不能为负值
+    --assert(ms > 0, "The wait time cannot be negative!")
+    -- 选一个未使用的定时器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任务的条件等待函数(包括事件消息和定时器消息等条件),只能用于任务函数中。
+-- @param id 消息ID
+-- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
+-- @return result 接收到消息返回true,超时返回false
+-- @return data 接收到消息返回消息参数
+-- @usage result, data = sys.waitUntil("SIM_IND", 120000)
+function sys.waitUntil(id, ms)
+    sys.subscribe(id, coroutine.running())
+    local message = ms and {sys.wait(ms)} or {coroutine.yield()}
+    sys.unsubscribe(id, coroutine.running())
+    return message[1] ~= nil, 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 sys.waitUntilExt(id, ms)
+    sys.subscribe(id, coroutine.running())
+    local message = ms and {sys.wait(ms)} or {coroutine.yield()}
+    sys.unsubscribe(id, coroutine.running())
+    if message[1] ~= nil then return unpack(message) end
+    return false
+end
+
+--- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数,main.lua导入该模块即可
+-- @param fun 任务函数名,用于resume唤醒时调用
+-- @param ... 任务函数fun的可变参数
+-- @return co  返回该任务的线程号
+-- @usage sys.taskInit(task1,'a','b')
+function sys.taskInit(fun, ...)
+    local co = coroutine.create(fun)
+    sys.coresume(co, ...)
+    return co
+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
+
+--- 关闭定时器
+-- @param val 值为number时,识别为定时器ID,值为回调函数时,需要传参数
+-- @param ... val值为函数时,函数的可变参数
+-- @return 无
+-- @usage timerStop(1)
+function sys.timerStop(val, ...)
+    -- val 为定时器ID
+    if type(val) == 'number' then
+        timerPool[val], para[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({...}, para[k]) then
+                    rtos.timer_stop(k)
+                    timerPool[k], para[k] = nil
+                    break
+                end
+            end
+        end
+    end
+end
+
+--- 关闭同一回调函数的所有定时器
+-- @param fnc 定时器回调函数
+-- @return 无
+-- @usage timerStopAll(cbFnc)
+function sys.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] = nil
+        end
+    end
+end
+
+function sys.timerAdvStart(fnc, ms, _repeat, ...)
+    --回调函数和时长检测
+    --assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
+    --assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
+    -- 关闭完全相同的定时器
+    local arg = {...}
+    if #arg == 0 then
+        sys.timerStop(fnc)
+    else
+        sys.timerStop(fnc, ...)
+    end
+    -- 为定时器申请ID,ID值 1-20 留给任务,20-30留给消息专用定时器
+    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, _repeat) ~= 1 then return end
+    --如果存在可变参数,在定时器参数表中保存参数
+    if #arg ~= 0 then
+        para[msgId] = arg
+    end
+    --返回定时器id
+    return msgId
+end
+
+--- 开启一个定时器
+-- @param fnc 定时器回调函数
+-- @number ms 整数,最大定时126322567毫秒
+-- @param ... 可变参数 fnc的参数
+-- @return number 定时器ID,如果失败,返回nil
+function sys.timerStart(fnc, ms, ...)
+    return sys.timerAdvStart(fnc, ms, 0, ...)
+end
+
+--- 开启一个循环定时器
+-- @param fnc 定时器回调函数
+-- @number ms 整数,最大定时126322567毫秒
+-- @param ... 可变参数 fnc的参数
+-- @return number 定时器ID,如果失败,返回nil
+function sys.timerLoopStart(fnc, ms, ...)
+    return sys.timerAdvStart(fnc, ms, -1, ...)
+end
+
+--- 判断某个定时器是否处于开启状态
+-- @param val 有两种形式
+--一种是开启定时器时返回的定时器id,此形式时不需要再传入可变参数...就能唯一标记一个定时器
+--另一种是开启定时器时的回调函数,此形式时必须再传入可变参数...才能唯一标记一个定时器
+-- @param ... 可变参数
+-- @return number 开启状态返回true,否则nil
+function sys.timerIsActive(val, ...)
+    if type(val) == "number" then
+        return timerPool[val]
+    else
+        for k, v in pairs(timerPool) do
+            if v == val then
+                if cmpTable({...}, 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 sys.subscribe(id, callback)
+    --if not id or type(id) == "boolean" or (type(callback) ~= "function" and type(callback) ~= "thread") then
+    --    log.warn("warning: sys.subscribe invalid parameter", id, callback)
+    --    return
+    --end
+    --log.debug("sys", "subscribe", id, callback)
+    if type(id) == "table" then
+        -- 支持多topic订阅
+        for _, v in pairs(id) do sys.subscribe(v, callback) end
+        return
+    end
+    if not subscribers[id] then subscribers[id] = {} end
+    subscribers[id][callback] = true
+end
+--- 取消订阅消息
+-- @param id 消息id
+-- @param callback 消息回调处理
+-- @usage unsubscribe("NET_STATUS_IND", callback)
+function sys.unsubscribe(id, callback)
+    --if not id or type(id) == "boolean" or (type(callback) ~= "function" and type(callback) ~= "thread") then
+    --    log.warn("warning: sys.unsubscribe invalid parameter", id, callback)
+    --    return
+    --end
+    --log.debug("sys", "unsubscribe", id, callback)
+    if type(id) == "table" then
+        -- 支持多topic订阅
+        for _, v in pairs(id) do sys.unsubscribe(v, callback) end
+        return
+    end
+    if subscribers[id] then subscribers[id][callback] = nil end
+    -- 判断消息是否无其他订阅
+    for k, _ in pairs(subscribers[id]) do
+        return
+    end
+    subscribers[id] = nil
+end
+
+--- 发布内部消息,存储在内部消息队列中
+-- @param ... 可变参数,用户自定义
+-- @return 无
+-- @usage publish("NET_STATUS_IND")
+function sys.publish(...)
+    table.insert(messageQueue, {...})
+end
+
+-- 分发消息
+local function dispatch()
+    while true do
+        if #messageQueue == 0 then
+            break
+        end
+        local message = table.remove(messageQueue, 1)
+        if subscribers[message[1]] then
+            for callback, _ in pairs(subscribers[message[1]]) do
+                if type(callback) == "function" then
+                    callback(unpack(message, 2, #message))
+                elseif type(callback) == "thread" then
+                    sys.coresume(callback, unpack(message))
+                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)
+--function sys.on(id, handler)
+--    handlers[id] = handler
+--end
+
+------------------------------------------ Luat 主调度框架  ------------------------------------------
+local function safeRun()
+    -- 分发内部消息
+    dispatch()
+    -- 阻塞读取外部消息
+    local msg, param, exparam = rtos.receive(rtos.INF_TIMEOUT)
+    --log.info("sys", msg, param, exparam, tableNSize(timerPool), tableNSize(para), tableNSize(taskTimerPool), tableNSize(subscribers))
+    -- 空消息?
+    if not msg or msg == 0 then
+        -- 无任何操作
+    -- 判断是否为定时器消息,并且消息是否注册
+    elseif 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
+                sys.coresume(taskId)
+            end
+        else
+            local cb = timerPool[param]
+            --如果不是循环定时器,从定时器id表中删除此定时器
+            if exparam == 0 then timerPool[param] = nil end
+            if para[param] ~= nil then
+                cb(unpack(para[param]))
+                if exparam == 0 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, exparam)
+    --else
+    --    handlers[msg.id](msg)
+    end
+end
+
+--- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
+-- @return 无
+-- @usage sys.run()
+function sys.run()
+    local result, err
+    while true do
+        --if sRollBack then
+            safeRun()
+        --else
+        --    result, err = pcall(safeRun)
+        --    if not result then rtos.restart(err) end
+        --end
+    end
+end
+
+_G.sys_pub = sys.publish
+
+return sys
+----------------------------