Skip to content

HTTP 客户端例程 3:周期异步上报(推荐做法)+ 失败退避 ​

这一页只讲这一个例程。同组其余例程、以及这个库的函数说明都在 05-01-http。

适用:同上 | 依赖模块:http json timer plc | 难度:★★☆

场景:每 30 秒把现场数据 POST 到第三方平台,不能卡住主循环(对照很多设备上习以为常的 while true do sleep(30000) end 写法 —— 那样会把定时器和所有回调一起冻死)。异步接口不阻塞;连续失败时按 2 倍退避,最长停 16 个周期再试,避免网络恢复前一直无效重发。

完整代码(可直接下载运行):

lua
-- 周期上报的标准写法:异步请求 + 在途保护 + 指数退避
local API = "http://192.168.1.10:8080/api/upload"
local INTERVAL = 30000                  -- 上报周期,定时器最小 10ms,实际别低于 1000

httpc = http.creat(API)
httpc:add_head("Content-Type", "application/json")
httpc:add_head("X-Device-Id", plc.plc_name())

local busy  = false                     -- 上一包还没回来,别再发
local fails = 0                         -- 连续失败次数
local skip  = 0                         -- 退避:还要跳过几个周期

timer.start(INTERVAL, function()
    if skip > 0 then skip = skip - 1 return end
    if busy then print("上一包未回,跳过本轮") return end
    busy = true

    local body = json.encode({
        dev  = plc.plc_name(),
        temp = plc.df(100),             -- 浮点必须 df(),plc.DF100 读出来是 nil
        y0   = plc.y(0),
        ts   = os.time()
    })

    httpc:post_asy(API, body, function(errcode, data, datalen)
        busy = false
        if errcode == 0 then
            fails = 0
            print("上报成功", datalen, "字节")
        else
            fails = fails + 1
            skip  = 2 ^ math.min(fails, 4) - 1      -- 退避 1/3/7/15 个周期,再往上封顶
            print("上报失败 err=", errcode, "退避", skip + 1, "个周期")
        end
    end)
end)

要点:

  • post_asy 是唯一适合周期任务的写法:请求在后台跑,结果通过消息队列回到主循环执行回调,期间定时器和其它回调照常工作。
  • busy 是在途保护:异步请求不会因为慢就自动排队,一个周期发一包、对端卡住时会堆积。
  • 退避用「跳过 N 个周期」实现,不改定时器周期 —— 定时器周期由固件持有,改不了也没必要重启它。
  • plc.df(100) 读浮点;plc.d(100) 只能读 16 位,两者别混。
  • 失败计数只在成功时清零,失败次数累加到 4 就封顶,退避不会无限变长。

验证方法:

  1. 下载运行后,每 30 秒日志出现一次 上报成功 N 字节;服务端能看到带 dev/temp/ts 的 JSON。
  2. 拔网线后日志出现 上报失败 err=... 并开始退避,间隔越来越稀疏(最长约 16×30s)。
  3. 插回网线,下一次成功后退避归零、恢复 30 秒节奏。
  4. 整个过程设备还活着:另起一个 timer.start(1000, function() print("alive") end) 应一直打印 —— 这是「没有阻塞主循环」的直接证据。

注意事项:

  • 别用 while true do ... system.sleep(30000) ... end 做周期上报:sleep 不泵消息队列,HTTP 回调、定时器、串口回调会全部永不触发。
  • errcode 是 HTTP 层的错误码,服务端回了 500 也算失败,但 data 里可能有 body,需要时照样能读。
  • 客户端对象、定时器都不用刻意用全局持有(定时器由固件持有),但 HTTP client 建议长期持有:请求在途时被回收会丢响应。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研