美文网首页Luat开源硬件
Luat实例教程:tcp透传

Luat实例教程:tcp透传

作者: Luat物联网通信模块 | 来源:发表于2018-03-23 13:25 被阅读64次

    本示例实现的功能是:基于TCP的socket透传demo项目,uart1透传数据到指定服务器。

    功能描述:

    1、数据网络准备就绪后,连接后台

    2、连接成功后,循环“读取mcu通过串口发送过来的数据,每次最多发送1K字节”

    3、与后台保持长连接,断开后主动再去重连,连接成功仍然按照第2条发送数据

    4、收到后台的数据时,在rcv函数中打印出来,并且通过串口透传给mcu

    测试时请搭建自己的服务器,并且修改下面的PROT,ADDR,PORT,支持域名和IP地址

    此例子为长连接,只要是软件上能够检测到的网络异常,可以自动去重新连接

    1.本功能需要socket和uart两个模块,所以分为两个文件来实现。实现sockt功能,在编辑工具建立一个sck.lua的文件(不一定叫这个名字,用户可以自己随便取名)

    2.设置本文件被全体可见。也就意味着,一旦test被某一文件加载,则test在任何文件中均可被看见,即test中全局变量和函数均可被任何文件调用。

    module(...,package.seeall)
    

    3.test文件头需要 require "xxx" 模块。加载后,就可以调用xxx.lua库文件中的全局变量和函数了

    require"socket"
    

    4.定义print函数,调试用

    local function print(...)
        _G.print("sck",...)
    end
    

    5.定义相关参数

    local ssub,schar,smatch,sbyte,slen = string.sub,string.char,string.match,string.byte,string.len
    --测试时请搭建自己的服务器
    local SCK_IDX,PROT,ADDR,PORT = 1,"TCP","120.26.196.195",9999
    --linksta:与后台的socket连接状态
    local linksta
    --一个连接周期内的动作:如果连接后台失败,会尝试重连,重连间隔为RECONN_PERIOD秒,最多重连RECONN_MAX_CNT次
    --如果一个连接周期内都没有连接成功,则等待RECONN_CYCLE_PERIOD秒后,重新发起一个连接周期
    --如果连续RECONN_CYCLE_MAX_CNT次的连接周期都没有连接成功,则重启软件
    local RECONN_MAX_CNT,RECONN_PERIOD,RECONN_CYCLE_MAX_CNT,RECONN_CYCLE_PERIOD = 3,5,3,20
    --reconncnt:当前连接周期内,已经重连的次数
    --reconncyclecnt:连续多少个连接周期,都没有连接成功
    --一旦连接成功,都会复位这两个标记
    --conning:是否在尝试连接
    local reconncnt,reconncyclecnt,conning = 0,0
    --正在发送的数据
    local sndingdata = ""
    

    6.创建到后台服务器的连接

    --[[创建到后台服务器的连接;
    如果数据网络已经准备好,会理解连接后台;否则,连接请求会被挂起,等数据网络准备就绪后,自动去连接后台 ntfy:socket状态的处理函数
    rcv:socket接收数据的处理函数]]--
    function connect()
        socket.connect(SCK_IDX,PROT,ADDR,PORT,ntfy,rcv)
        conning = true
    end
    

    7.socket状态的处理函数

    --[[
    函数名:ntfy
    功能  :socket状态的处理函数
    参数  :
            idx:number类型,socket.lua中维护的socket idx,跟调用socket.connect时传入的第一个参数相同,程序可以忽略不处理
            evt:string类型,消息事件类型
            result: bool类型,消息事件结果,true为成功,其他为失败
            item:table类型,{data=,para=},消息回传的参数和数据,目前只是在SEND类型的事件中用到了此参数,例如调用socket.send时传入的第2个和第3个参数分别为dat和par,则item={data=dat,para=par}
    返回值:无
    ]]
    function ntfy(idx,evt,result,item)
        print("ntfy",evt,result,item)
        --连接结果(调用socket.connect后的异步事件)
        if evt == "CONNECT" then
            conning = false
            --连接成功
            if result then
                reconncnt,reconncyclecnt,linksta = 0,0,true
                --停止重连定时器
                sys.timer_stop(reconn)
                --发送mcu通过串口传过来的数据到后台
                sndmcuartdata()
            --连接失败
            else
                --RECONN_PERIOD秒后重连
                sys.timer_start(reconn,RECONN_PERIOD*1000)
            end 
        --数据发送结果(调用socket.send后的异步事件)
        elseif evt == "SEND" then
            if item then
                sndcb(item,result)
            end
            --发送失败,RECONN_PERIOD秒后重连后台,不要调用reconn,此时socket状态仍然是CONNECTED,会导致一直连不上服务器
            --if not result then sys.timer_start(reconn,RECONN_PERIOD*1000) end
            if not result then link.shut() end
        --连接被动断开
        elseif evt == "STATE" and result == "CLOSED" then
            linksta = false
            reconn()
        --连接主动断开(调用link.shut后的异步事件)
        elseif evt == "STATE" and result == "SHUTED" then
            linksta = false
            reconn()
        --连接主动断开(调用socket.disconnect后的异步事件)
        elseif evt == "DISCONNECT" then
            linksta = false
            reconn()        
        end
        --其他错误处理,断开数据链路,重新连接
        if smatch((type(result)=="string") and result or "","ERROR") then
            --RECONN_PERIOD秒后重连,不要调用reconn,此时socket状态仍然是CONNECTED,会导致一直连不上服务器
            --sys.timer_start(reconn,RECONN_PERIOD*1000)
            link.shut()
        end
    end
    
    --[[
    函数名:rcv
    功能  :socket接收数据的处理函数
    参数  :
            idx :socket.lua中维护的socket idx,跟调用socket.connect时传入的第一个参数相同,程序可以忽略不处理
            data:接收到的数据
    返回值:无
    ]]
    function rcv(idx,data)
        print("rcv",slen(data)>200 and slen(data) or data)
        --抛出SVR_TRANSPARENT_TO_MCU消息,携带socket收到的数据
        sys.dispatch("SVR_TRANSPARENT_TO_MCU",data)
    end
    

    8.发送函数,sndmcuartdata()的具体实现,以及对应的回调函数。

    --[[
    函数名:snd
    功能  :调用发
    
    送接口发送数据
    参数  :
            data:发送的数据,在发送结果事件处理函数ntfy中,会赋值到item.data中
            para:发送的参数,在发送结果事件处理函数ntfy中,会赋值到item.para中 
    返回值:调用发送接口的结果(并不是数据发送是否成功的结果,数据发送是否成功的结果在ntfy中的SEND事件中通知),true为成功,其他为失败
    ]]
    function snd(data,para)
        return socket.send(SCK_IDX,data,para)
    end
    
    --[[
    函数名:sndmcuartdata
    功能  :如果还有等待发送的mcu通过串口传过来的数据,则继续发送
    参数  :无
    返回值:无
    ]]
    local function sndmcuartdata()
        if sndingdata=="" then
            sndingdata = mcuart.resumesndtosvr()
        end
        if linksta and sndingdata~="" then snd(sndingdata,"TRANSPARENT") end
    end
    
    --[[
    函数名:sndcb
    功能  :数据发送结果处理
    参数  :          
            item:table类型,{data=,para=},消息回传的参数和数据,例如调用socket.send时传入的第2个和第3个参数分别为dat和par,则item={data=dat,para=par}
            result: bool类型,发送结果,true为成功,其他为失败
    返回值:无
    ]]
    local function sndcb(item,result)
        print("sndcb",item.para,result)
        if not item.para then return end
        if item.para=="TRANSPARENT" then
            --发送成功,继续发送下包数据
            if result then
                sndingdata = ""
                --sys.dispatch("SND_TO_SVR_CNF",true)
                sndmcuartdata()
            end
        end
    end
    

    9.断线自动重连

    --[[
    函数名:reconn
    功能  :重连后台处理
            一个连接周期内的动作:如果连接后台失败,会尝试重连,重连间隔为RECONN_PERIOD秒,最多重连RECONN_MAX_CNT次
            如果一个连接周期内都没有连接成功,则等待RECONN_CYCLE_PERIOD秒后,重新发起一个连接周期
            如果连续RECONN_CYCLE_MAX_CNT次的连接周期都没有连接成功,则重启软件
    参数  :无
    返回值:无
    ]]
    local function reconn()
        print("reconn",reconncnt,conning,reconncyclecnt)
        --conning表示正在尝试连接后台,一定要判断此变量,否则有可能发起不必要的重连,导致reconncnt增加,实际的重连次数减少
        if conning then return end
        --一个连接周期内的重连
        if reconncnt = RECONN_CYCLE_MAX_CNT then
                sys.restart("connect fail")
            end
            sys.timer_start(reconn,RECONN_CYCLE_PERIOD*1000)
        end
    end
    

    10.定时器启动connect()函数以及消息注册

    connect()
    --消息处理函数列表
    local procer =
    {
        SND_TO_SVR_REQ = sndmcuartdata,
    }
    
    --注册消息处理函数列表
    sys.regapp(procer)
    

    11.实现uart功能,在编辑工具建立一个mcuart.lua的文件(不一定叫这个名字,用户可以自己随便取名),同理参照2,3,4步骤。

    module(...,package.seeall)
    
    require"pm"
    local function print(...)
        _G.print("mcuart",...)
    end
    

    12.定义相关参数

    --串口ID,1对应uart1
    local UART_ID = 1
    
    --SND_UNIT_MAX:每次发送最大的字节数,只要累积收到的数据大于等于这个最大字节数,并且没有正在发送数据到后台,则立即发送前SND_UNIT_MAX字节数据给后台
    --SND_DELAY:每次串口收到数据时,重新延迟SND_DELAY毫秒后,没有收到新的数据,并且没有正在发送数据到后台,则立即发送最多前SND_UNIT_MAX字节数据给后台
    --这两个变量配合使用,只要任何一个条件满足,都会触发发送动作
    --例如:SND_UNIT_MAX,SND_DELAY = 1024,1000,有如下几种情况
    --串口收到了500字节数据,接下来的1000毫秒没有收到数据,并且没有正在发送数据到后台,则立即发送这500字节数据给后台
    --串口收到了500字节数据,800毫秒后,又收到了524字节数据,此时没有正在发送数据到后台,则立即发送这1024字节数据给后台
    local SND_UNIT_MAX,SND_DELAY = 1024,1000
    
    --sndingtosvr:是否正在发送数据到后台
    local sndingtosvr
    
    --unsndbuf:还没有发送的数据
    --sndingbuf:正在发送的数据
    local readbuf--[[,sndingbuf]] = ""--[[,""]]
    

    13.串口发送数据

    --s:要发送的数据
    function write(s)
        print("write",s)
        uart.write(UART_ID,s)   
    end
    

    14.读取串口接收到的数据

    --通知数据发送功能模块,串口数据已准备好,可以发送
    local function sndtosvr()
        --print("sndtosvr",sndingtosvr)
        if not sndingtosvr then
            sys.dispatch("SND_TO_SVR_REQ")
        end
    end
    
    --[[
    函数名:proc
    功能  :处理串口接收到的数据
    参数  :
            data:当前一次读取到的串口数据
    返回值:无
    ]]
    local function proc(data)
        if not data or string.len(data) == 0 then return end
        --追加到未发送数据缓冲区末尾
        readbuf = readbuf..data
        if string.len(readbuf)>=SND_UNIT_MAX then sndtosvr() end
        sys.timer_start(sndtosvr,SND_DELAY)
    end
    
    --读取串口接收到的数据
    local function read()
        local data = ""
        --底层core中,串口收到数据时:
        --如果接收缓冲区为空,则会以中断方式通知Lua脚本收到了新数据;
        --如果接收缓冲器不为空,则不会通知Lua脚本
        --所以Lua脚本中收到中断读串口数据时,每次都要把接收缓冲区中的数据全部读出,这样才能保证底层core中的新数据中断上来,此read函数中的while语句中就保证了这一点
        while true do
            data = uart.read(UART_ID,"*l",0)
            if not data or string.len(data) == 0 then break end
            --print("read",string.len(data)--[[data,common.binstohexs(data)]])
            proc(data)
        end
    end
    

    15.消息注册处理

    --消息处理函数列表
    local procer =
    {
        SVR_TRANSPARENT_TO_MCU = write,
        --SND_TO_SVR_CNF = sndcnf,
    }
    --注册消息处理函数列表
    sys.regapp(procer)
    --保持系统处于唤醒状态,不会休眠
    pm.wake("mcuart")
    --注册串口的数据接收函数,串口收到数据后,会以中断方式,调用read接口读取数据
    sys.reguart(UART_ID,read)
    --配置并且打开串口
    uart.setup(UART_ID,9600,8,uart.PAR_NONE,uart.STOP_1)
    

    16.在编辑工具中建立一个名为main.lua的文件。lua脚本的执行从main.lua开始,main.lua是入口文件(注意:main.lua只能有一个)。在main.lua中把test加载进去就好了。sys.init()是对系统初始化,sys.run()是系统主程序。这两句必须有。

    
    --重要提醒:必须在这个位置定义MODULE_TYPE、PROJECT和VERSION变量
    --MODULE_TYPE:模块型号,目前仅支持Air201、Air202、Air800
    --PROJECT:ascii string类型,可以随便定义,只要不使用,就行
    --VERSION:ascii string类型,如果使用Luat物联云平台固件升级的功能,必须按照"X.X.X"定义,X表示1位数字;否则可随便定义
    MODULE_TYPE = "Air202"
    PROJECT = "SOCKET_LONG_CONNECTION_TRANSPARENT"
    VERSION = "1.0.0"
    require"sys"
    require"mcuart"
    require"sck"
    if MODULE_TYPE=="Air201" then
    require"wdt"
    end
    sys.init(0,0)
    sys.run()
    

    !!!attention

    一个工程只有一个main.lua
    

    相关文章

      网友评论

        本文标题:Luat实例教程:tcp透传

        本文链接:https://www.haomeiwen.com/subject/qtxxcftx.html