Sfoglia il codice sorgente

add:实现通过ril使用8266上网

guoliang 3 anni fa
parent
commit
d0d177eccf

+ 98 - 0
demo/ril_8266/link_wifi.lua

@@ -0,0 +1,98 @@
+local link = {}
+
+local connected, connecting, stopConfig, recvId
+
+function link.isReady()
+    return connected
+end
+
+function link.shut()
+    log.info("link_wifi", "shut")
+end
+
+link.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
+                    ril_wifi.request("AT+CWMODE=1")
+                    ril_wifi.request("AT+CWSTARTSMART=3")
+                else
+                    point("later")
+                    sys.timerStart(ril_wifi.request, 5000, "AT+CIPSTATUS")
+                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
+        sys.publish("IP_READY_IND")
+        return
+    elseif prefix == "WIFI CONNECTED" then
+        connecting = true
+        return
+    elseif prefix == "Smart get wifi info" then
+        stopConfig = sys.timerStart(ril_wifi.request, 20000, "AT+CWSTOPSMART")
+        return
+    elseif prefix == "smartconfig connected wifi" then
+        connecting = true
+        if sys.timerIsActive(stopConfig) then sys.timerStop(stopConfig) end
+        ril_wifi.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
+        ril_wifi.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
+                ril_wifi.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(ril_wifi.request, 2000, "AT+CWSTARTSMART=3")
+        end
+    end
+end
+
+ril_wifi.regUrc("+IPD", wifiUrc)
+ril_wifi.regUrc("STATUS", wifiUrc)
+ril_wifi.regUrc("WIFI GOT IP", wifiUrc)
+ril_wifi.regUrc("WIFI CONNECTED", wifiUrc)
+ril_wifi.regUrc("smartconfig connected wifi", wifiUrc)
+ril_wifi.regUrc("Smart get wifi info", wifiUrc)
+ril_wifi.regRsp("+CWSTARTSMART", wifiRsp)
+ril_wifi.regRsp("+CWSTOPSMART", wifiRsp)
+ril_wifi.regUrc("+CIPRECVLEN", wifiUrc)
+
+ril_wifi.request("AT+CIPRECVMODE=1")
+ril_wifi.request("AT+CIPMODE=0")
+ril_wifi.request("AT+CIPMUX=1")
+ril_wifi.request("AT+CIPSTATUS")
+
+return link

+ 114 - 0
demo/ril_8266/main.lua

@@ -0,0 +1,114 @@
+PROJECT = "adcdemo"
+VERSION = "1.0.0"
+
+sys = require 'sys'
+local mqtt = require "mqtt"
+_G.point = function(...)
+    --local name = debug.getinfo(2).short_src
+    --local line = debug.getinfo(2).currentline
+    --print("[" .. name .. " : " .. line .. "]", ...)
+end
+
+require "wifi"
+-- require "mqtt"
+-- -- 这里请填写修改为自己的IP和端口
+-- local host, port = "lbsmqtt.airm2m.com", 1884
+
+
+sys.taskInit(function()
+    -- 服务器配置信息
+    local host, port, selfid = "1.15.81.195", 1883, "AIR105"
+    sys.waitUntil("IP_READY_IND")
+    -- 等待联网成功
+    while true do
+        while not socket.isReady() do 
+            log.info("net", "wait for network ready")
+            sys.waitUntil("IP_READY_IND", 1000)
+        end
+        log.info("main", "Airm2m mqtt loop")
+        
+        local mqttc = mqtt.client("clientid-123")
+        while not mqttc:connect(host, port) do sys.wait(2000) end
+        local topic_req = string.format("/device")
+        local topic_report = string.format("/device")
+        local topic_resp = string.format("/device/%s/resp", selfid)
+        log.info("mqttc", "mqtt seem ok", "try subscribe", topic_req)
+        if mqttc:subscribe(topic_req) then
+            log.info("mqttc", "mqtt subscribe ok", "try publish")
+            if mqttc:publish(topic_report, "test publish " .. os.date(), 1) then
+                while true do
+                    log.info("mqttc", "wait for new msg")
+                    local r, data, param = mqttc:receive(120000, "pub_msg")
+                    log.info("mqttc", "mqttc:receive", r, data, param)
+                    if r then
+                        log.info("mqttc", "get message from server", data.payload or "nil", data.topic)
+                    elseif data == "pub_msg" then
+                        log.info("mqttc", "send message to server", data, param)
+                        mqttc:publish(topic_resp, "response " .. param)
+                    elseif data == "timeout" then
+                        log.info("mqttc", "wait timeout, send custom report")
+                        mqttc:publish(topic_report, "test publish " .. os.date())
+                    else
+                        log.info("mqttc", "ok, something happen", "close connetion")
+                        break
+                    end
+                end
+            end
+        end
+        mqttc:disconnect()
+        sys.wait(5000) -- 等待一小会, 免得疯狂重连
+    end
+
+end)
+-- sys.taskInit(function()
+--     sys.waitUntil("IP_READY_IND")
+--     while true do
+--         while not socket.isReady() do
+--             sys.wait(1000)
+--         end
+--         local c = socket.tcp()
+--         while not c:connect("112.125.89.8", 36091) do
+--             sys.wait(2000)
+--         end
+
+--         while true do
+--             c:send("1234567890")
+--             r, s, p = c:recv(5000, "pub_msg")
+--             if r then
+--                 log.info("这是收到了服务器下发的消息:", s)
+--             elseif s == "pub_msg" then
+--                 log.info("这是收到了订阅的消息和参数显示:", s, p)
+--                 if not c:send(p) then
+--                     break
+--                 end
+--             elseif s == "timeout" then
+--                 log.info("这是等待超时发送心跳包的显示!")
+--                 if not c:send("\0") then
+--                     break
+--                 end
+--             else
+--                 log.info("这是socket连接错误的显示!")
+--                 break
+--             end
+--             sys.wait(5000)
+--         end
+--         c:close()
+--     end
+-- end)
+
+
+-- print(uart.setup(4,115200))
+
+-- uart.on(4,"receive",function (id,len)
+--     print(uart.read(id,len))
+-- end)
+
+-- sys.timerLoopStart(uart.write,1000,4,"ATE0\r\n")
+sys.timerLoopStart(function()
+    log.info("mem.lua", rtos.meminfo())
+    -- log.info("mem.lua", rtos.meminfo("sys"))
+    -- 打印占用的RAM
+    collectgarbage("collect")
+end, 3000)
+sys.run()
+sys.run()

+ 539 - 0
demo/ril_8266/mqtt.lua

@@ -0,0 +1,539 @@
+
+--- 模块功能: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: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或者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
+    
+    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为false,msg控制退出,data为msg的字符串
+--                如果result为false,socket连接被动断开控制退出,data为"CLOSED"
+--                如果result为false,PDP断开连接控制退出,data为"IP_ERROR_IND"
+--
+--                如果result为false,mqtt不处于连接状态,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
+
+
+return mqtt

+ 656 - 0
demo/ril_8266/ril_wifi.lua

@@ -0,0 +1,656 @@
+--- 模块功能:虚拟串口AT命令交互管理
+-- @module ril
+-- @author openLuat
+-- @license MIT
+-- @copyright openLuat
+-- @release 2017.02.13
+
+local ril_wifi = {}
+
+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
+}
+
+--radioready:AT命令通道是否准备就绪
+--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("ril.atimeout_" .. (currcmd or ""))
+end
+
+--[[
+函数名:defrsp
+功能  :AT命令的默认应答处理。如果没有定义某个AT的应答处理函数,则会走到本函数
+参数  :
+cmd:此应答对应的AT命令
+success:AT命令执行结果,true或者false
+response:AT命令的应答中的执行结果字符串
+intermediate:AT命令的应答中的中间信息
+返回值:无
+]]
+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 ril_wifi.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
+    --重置全局变量
+    --log.info("在rsp被清除了吗", currcmd, currarg, currsp, curdelay, cmdhead)
+    if cmdhead == "+CIPSEND" and cipsendflag then
+        return
+    end
+    currcmd, currarg, currsp, curdelay, cmdhead, cmdtype, rspformt = nil
+    result, interdata, respdata = nil
+end
+
+--[[
+函数名:defurc
+功能  :urc的默认处理。如果没有定义某个urc的应答处理函数,则会走到本函数
+参数  :
+data:urc内容
+返回值:无
+]]
+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 ril_wifi.regUrc(prefix, handler)
+    urctable[prefix] = handler
+end
+
+--- 解注册某个urc的处理函数
+-- @param prefix    urc前缀,最前面的连续字符串,包含+、大写字符、数字的组合
+-- @return 无
+-- @usage deRegUrc("+CREG")
+function ril_wifi.deRegUrc(prefix)
+    urctable[prefix] = nil
+end
+
+--“数据过滤器”,虚拟串口收到的数据时,首先需要调用此函数过滤处理一下
+local urcfilter
+
+--[[
+函数名:urc
+功能  :urc处理
+参数  :
+data:urc数据
+返回值:无
+]]
+local function urc(data)
+    
+    --AT通道准备就绪
+
+    point(#data, data)
+    if  string.find(data, "ready") then
+        radioready = true
+        
+    else
+        local prefix = string.match(data, "([%+%*]*[%a%d& ]+)")
+        -- 执行prefix的urc处理函数,返回数据过滤器
+        urcfilter = urctable[prefix](data, prefix)
+    end
+end
+
+--[[
+函数名:procatc
+功能  :处理虚拟串口收到的数据
+参数  :
+data:收到的数据
+返回值:无
+]]
+local function procatc(data)
+    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 not data:find("+CIPRECVDATA") then
+        if string.find(data, "\r\n", -2) then
+            point(#data)
+            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
+        --log.info("被判定成配网数据了", data, isurc, result)
+        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.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("ril.procatc.send", currarg)
+            vwrite(UART_ID, currarg, "\026")
+        --发送数据
+        elseif cmdhead == "+CIPSEND" or cmdhead == "+SSLSEND" or cmdhead == "+SSLCERT" then
+            log.info("ril.procatc.send", "first 200 bytes", currarg:sub(1,200))
+            vwrite(UART_ID, currarg)
+        elseif cmdhead == "+SYSFLASH" then
+            log.info("ril.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)
+    --应答处理
+    elseif result ~= nil then
+        rsp()
+    end
+end
+
+--是否在读取虚拟串口数据
+local readat = false
+
+--[[
+函数名:getcmd
+功能  :解析一条AT命令
+参数  :
+item:AT命令
+返回值:当前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("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
+    --log.info("走到赋值全局变量了吗", cmd, arg, rsp, delay, head)
+    --赋值全局变量
+    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
+    --log.info("已就绪")
+    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("ril.sendat", currcmd)
+    --向虚拟串口中发送AT命令
+    vwrite(UART_ID, currcmd .. "\r\n")
+end
+
+-- 延时执行某条AT命令的定时器回调
+-- @return 无
+-- @usage ril.delayfunc()
+function ril_wifi.delayfunc()
+    --清除延时标志
+    delaying = nil
+    --执行AT命令发送
+    sendat()
+end
+
+--[[
+函数名:atcreader
+功能  :“AT命令的虚拟串口数据接收消息”的处理函数,当虚拟串口收到数据时,会走到此函数中
+参数  :无
+返回值:无
+]]
+local function atcreader(id,len)
+    local alls = vread(UART_ID, len):split("\r\n")
+
+    if not transparentmode then
+        readat = true
+    end
+    -- 循环读取虚拟串口收到的数据
+    for i=1,#alls do
+        local s = alls[i]
+        --每次读取一行
+        --s = vread(UART_ID, len)
+        log.debug("uart.log",s)
+        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 ril.request("AT+CENG=1,1")
+-- @usage ril.request("AT+CRSM=214,28539,0,0,12,\"64f01064f03064f002fffff\"", nil, crsmResponse)
+function ril_wifi.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命令发送
+    point(cmd)
+    sendat()
+end
+
+--[[
+函数名:setransparentmode
+功能  :AT命令通道设置为透传模式
+参数  :
+fnc:透传模式下,虚拟串口数据接收的处理函数
+返回值:无
+注意:透传模式和非透传模式,只支持开机的第一次设置,不支持中途切换
+]]
+function ril_wifi.setransparentmode(fnc)
+    transparentmode, rcvfunc = true, fnc
+end
+
+--[[
+函数名:sendtransparentdata
+功能  :透传模式下发送数据
+参数  :
+data:数据
+返回值:成功返回true,失败返回nil
+]]
+function ril_wifi.sendtransparentdata(data)
+    if not transparentmode then
+        return
+    end
+    vwrite(UART_ID, data)
+    return true
+end
+
+function ril_wifi.setDataTimeout(tm)
+    DATA_TIMEOUT = (tm<120000 and 120000 or tm)
+end
+
+uart.setup(UART_ID, 115200)
+--注册“AT命令的虚拟串口数据接收消息”的处理函数
+uart.on(UART_ID, "receive", atcreader)
+
+uart.write(UART_ID,"AT+RST\r\n")
+
+return ril_wifi

+ 836 - 0
demo/ril_8266/socket_wifi.lua

@@ -0,0 +1,836 @@
+--- 模块功能:数据链路激活、SOCKET管理(创建、连接、数据收发、状态维护)
+-- @module socket
+-- @author openLuat
+-- @license MIT
+-- @copyright openLuat
+-- @release 2017.9.25
+local socket_wifi = {}
+
+link_wifi = require "link_wifi"
+
+local ril = ril_wifi
+local req = ril.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
+socket_wifi.isReady = link_wifi.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 ("/ldata/" .. 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 ("/ldata/" .. 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 ("/ldata/" .. 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 = 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 socket_wifi.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 = socket.udp()
+function socket_wifi.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 ("/ldata/" .. 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 = socket.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")
+	
+    ril.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 = socket.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 = socket.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 = socket.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的值是msg,param的值是msg的参数
+-- @usage c = socket.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
+        ril.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
+
+    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 ~= 'SEND OK' and response ~= 'OK' then
+                local acceptLen = response:match("Recv (%d) bytes")
+                if acceptLen then
+                    if acceptLen ~= cmd:match("AT%+%u+=%d,(%d+)") then
+                        success = false
+                    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)
+    local len, datatest = string.match(urc, "+CIPRECVDATA:(%d+),(.+)")
+    local id = link_wifi.getRecvId()
+    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
+        log.info("不等的情况")
+        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
+
+ril.regRsp("+CIPCLOSE", onResponse)
+ril.regRsp("+CIPSEND", onResponse)
+ril.regRsp("+CIPSTART", onResponse)
+ril.regRsp("+SSLDESTROY", onResponse)
+ril.regRsp("+SSLCREATE", onResponse)
+ril.regRsp("+SSLSEND", onResponse)
+ril.regRsp("+SSLCONNECT", onResponse)
+ril.regUrc("+CIPRECVDATA", onSocketReceiveUrc)
+ril.regUrc("+SSL RECEIVE", onSocketReceiveUrc)
+
+ril.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)
+
+
+function socket_wifi.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 socket_wifi.setTcpResendPara(retryCnt, retryMaxTimeout)
+    req("AT+TCPUSERPARAM=6," .. (retryCnt or 4) .. ",7200," .. (retryMaxTimeout or 16))
+    ril.setDataTimeout(((retryCnt or 4) * (retryMaxTimeout or 16) + 60) * 1000)
+end
+
+--- 设置用户自定义的DNS解析器.
+-- 通过域名连接服务器时,DNS解析的过程如下:
+-- 1、使用core中提供的方式,连接运营商DNS服务器解析,如果解析成功,则结束;如果解析失败,走第2步
+-- 2、使用脚本lib中提供的免费腾讯云HttpDns解析,如果解析成功,则结束;如果解析失败,走第3步
+-- 3、如果存在用户自定义的DNS解析器,则使用此处用户自定义的DNS解析器去解析
+-- @function[opt=nil] parserFnc,用户自定义的DNS解析器函数,函数的调用形式为:
+--      parserFnc(domainName,token),调用接口后会等待解析结果的消息通知或者40秒超时失败
+--          domainName:string类型,表示域名,例如"www.baidu.com"
+--          token:string类型,此次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 socket_wifi.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会直接返回ERROR,socket: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 socket_wifi.setSendMode(mode)
+    linkTest.setSendMode(mode)
+end
+
+-- setTcpResendPara(4, 16)
+return socket_wifi

+ 45 - 0
demo/ril_8266/wifi.lua

@@ -0,0 +1,45 @@
+--module(..., package.seeall)
+
+-- require "pm"
+-- pm.wake("WIFI")
+
+-- require "pins"
+
+-- sys.taskInit(function()
+--     pmd.ldoset(6, pmd.LDO_VLCD)
+--     local urtIO = pins.setup(15, 0)
+--     urtIO(0)
+--     sys.wait(5000)
+--     print("pins.setup")
+--     urtIO(1)
+-- end)
+
+ril_wifi = require "ril_wifi"
+_G.link = require "link_wifi"
+_G.socket = require "socket_wifi"
+_G.net = {
+    switchFly = function(f)
+        log.info("net", "switchFly", f)
+    end,
+    startQueryAll = print
+}
+
+local unlod = {
+    socket = true,
+    link = true,
+    net = true
+}
+
+local require_wifi = _G.require
+_G.require = function(name)
+    if not unlod[name] then
+        require_wifi(name)
+    end
+end
+
+_G.point = function(...)
+    local name = debug.getinfo(2).short_src:sub(6, -5)
+    local line = debug.getinfo(2).currentline
+    print("[" .. name .. " : " .. line .. "]", ...)
+end
+