Skip to content

综合例程:HTTP 上报与拉取(同步 / 异步) ​

对接第三方平台最常见的就是 HTTP:定时把数据 POST 过去,或者周期性 GET 回一段 JSON 指令。 http 有同步、异步两套接口,用错一套会让整台设备停摆十几秒。 本页给出该用哪一套、以及异步回调参数的正确写法(这一处几乎人人写错,见 第五节)。


一、先选同步还是异步 ​

同步 http.get / http.post异步 get_asy / post_asy
写法直接拿到返回值,代码是顺序的结果在回调里来
等待期间阻塞当前 Lua 执行,内部固定 15 秒超时不阻塞,脚本继续往下走
适合脚本最开始的一次性动作(拉配置、注册)周期性上报 / 轮询拉取,以及一切联网较慢的场合
风险平台一慢,所有回调(串口、MQTT、定时器)跟着冻最多 15 秒回调参数顺序记错,拿到的是错误码不是内容

别在定时器里用同步请求

同步 get / post 在 Lua 主线程里执行时会占住整条事件循环直到返回或 15 秒超时。 写在 timer.start 回调里,就等于每个周期都可能把设备冻住十几秒: 串口采集停、MQTT 心跳发不出、梯形图交互无响应。 周期性的一律用 *_asy。


二、场景与数据流 ​

设备每 10 秒把本机 D100(温度)、D101(状态)POST 到平台; 每 30 秒 GET 一次平台的指令接口,拿到 JSON 后把 set 字段写回 D200。

位置内容
上报地址http://192.168.0.238/api/upload
指令地址http://192.168.0.238/api/command
上报体{"dev":"lp3232_01","temp":25.3,"run":1}
指令体{"set":88} → 写回本机 D200
本机数据源D100 温度、D101 运行标志(由采集脚本或梯形图维护)
出口网卡多网卡机型用 plc.eth_ip() 固定走以太网

三、完整代码(可直接下载运行) ​

lua
-- ============================================================
-- HTTP 上报 + 拉取指令:全程异步,脚本注册完就结束
-- ============================================================

local UPLOAD_URL = "http://192.168.0.238/api/upload"
local CMD_URL    = "http://192.168.0.238/api/command"
local ETH_IP     = plc.eth_ip()                       -- 多网卡时固定出口;单网卡可去掉该参数

-- ---------- 1. 建一个长期持有的对象,复用请求头 ----------
hc = http.creat()                                    -- 全局持有:局部变量会被回收
hc:add_head("Content-Type", "application/json")
hc:add_head("Authorization", "Bearer token123")       -- 按平台要求;空值 "" 表示删除该头部

-- ---------- 2. 异步上报(每 10 秒) ----------
timer.start(10000, function()
    local body = json.encode({
        dev  = "lp3232_01",
        temp = plc.d(100),
        run  = plc.d(101),
    })
    hc:post_asy(UPLOAD_URL, body, function(errcode, data, datalen)
        if errcode == 0 then
            print("上报成功,平台回应", datalen, "字节:", data)
        else
            print("上报失败,错误码:", errcode)        -- 失败不重试,下个周期自然再来
        end
    end, ETH_IP)
end)

-- ---------- 3. 异步拉取指令(每 30 秒) ----------
timer.start(30000, function()
    hc:get_asy(CMD_URL, function(errcode, data, datalen)
        if errcode ~= 0 then
            print("拉取失败,错误码:", errcode)
            return
        end
        -- data 是 JSON 文本;平台可能返回别的内容,解不开就跳过本轮
        local ok, cmd = pcall(json.decode, data)
        if not ok or type(cmd) ~= "table" then
            print("返回不是合法 JSON 对象,忽略:", data)
            return
        end
        if cmd.set ~= nil then
            plc.d(200, cmd.set)                        -- 写回本机 D200,梯形图直接取用
            print("收到下发指令 set =", cmd.set)
        end
    end, ETH_IP)
end)

-- ---------- 4.(可选)脚本最开始用同步拉一次配置 ----------
-- 只此一次、且在注册任何周期任务之前,用同步是安全的
local cfg_text, cfg_err = http.get("http://192.168.0.238/api/config", ETH_IP)
if cfg_text then
    print("启动时拉到配置:", cfg_text)
else
    print("启动时拉配置失败:", cfg_err)                -- 失败原因字符串,如 HTTP GET request failed
end

逐段要点:

  1. 回调签名固定是 function(errcode, data, datalen),第一个参数是错误码不是数据。 0 才是成功。
  2. 对象必须长期持有(这里用全局 hc):http 对象被回收时底层连接资源会一起释放, 请求做一半对象没了就永远等不到回调。
  3. add_head 设一次,之后每个请求都带上;传空串 "" 是删除该头部。
  4. post_asy 不返回结果,成功与否只能在回调里看;所以失败分支里不需要重试逻辑, 交给下一个定时周期最省事。
  5. 拉回来的 JSON 必须 pcall 包一层:HTTP 是对方的接口,返回 HTML 报错页、 空串、纯文本都很常见,直接 json.decode 会让回调报错。
  6. ip 参数在多网卡机型上很有用:WiFi 和以太网同时在线时, 不指定就可能从另一块网卡出去,到不了内网平台。
  7. 第 4 段演示了同步唯一的合理用法:启动时一次性动作。它也会阻塞, 但只阻塞这一次,且发生在注册周期任务之前。

四、同步接口什么时候用 ​

lua
-- 简单模式:不建对象,一行搞定(第 2 参 ip 可省)
local data, err = http.get("http://192.168.0.238/index.html")
local data2, err2 = http.post("http://192.168.0.238/api/upload", '{"a":1}')

-- 对象模式:需要自定义请求头时
local one = http.creat("http://192.168.0.238/api/upload")
one:add_head("Content-Type", "application/json")
local data3 = one:post('{"a":1}')       -- 失败返回 nil + 错误信息

要点:

  • 成功返回响应数据字符串(可能是空串 ""),失败返回 nil 加一个错误信息字符串。 所以判成败要写 if data then,不能写 if #data > 0 then —— 平台正常回应空 body 也是成功。
  • 超时固定 15 秒不可配,别指望调参数缓解。
  • 用完想立刻释放对象所占的资源:置 nil 后再 collectgarbage("collect")。 不写 collectgarbage 也行,只是回收时点不确定。
lua
one = nil
collectgarbage("collect")                -- 触发析构,立即归还底层资源

五、错误写法 vs 正确写法对照 ​

这段是能正常跑通、但结果完全不对的写法:

lua
-- 错误:把回调的第一个参数当成了响应内容
x = http.creat("http://www.example.com/index.html")
http.get_asy(x, "http://www.example.com/index.html", function(msg)
    print(msg)                          -- 打印出来的是 errcode(一个数字),不是网页内容
end)
问题后果正确做法
回调只写一个形参拿到的是 errcode,日志里永远只有一个 0,看不到任何响应内容function(errcode, data, datalen),用 data 取内容
没判 errcode == 0失败时也照样往下解 JSON,报错信息被 nil 取代,很难定位先判 errcode,非 0 直接 return
同步 http.get 写在 10 秒定时器里平台慢时整条事件循环被占住最多 15 秒,串口和 MQTT 全停周期任务改 get_asy / post_asy
http 对象写成函数里的 local函数返回后对象被回收,回调永远不来用全局变量或脚本顶层 local 长期持有
直接 json.decode(data)对方返回 HTML 或空串时回调报错pcall(json.decode, data) 再判 type(...) == "table"
用 if #data > 0 then 判成功空 body 的正常回应被判成失败,反复重试用 if data then(失败才是 nil)

六、注意事项汇总 ​

  • HTTPS 需要网络可达且证书可用;内网自签证书平台建议先用 http:// 验通链路再上 https://。
  • errcode 是非 0 的错误码,本模块不给出细分的错误含义表;先把它当"成/不成"用, 具体原因结合平台侧日志判断。
  • 一次请求对应一次回调,别在回调里再触发同一个请求,容易叠成无限请求。
  • 回调都排队在主循环里执行,回调内不要做长耗时操作(要处理的重活转给梯形图或下一个定时器)。
  • 请求头名和值都必须是字符串,传数字会报错。
  • 需要收文件、断点续传这类活不要拿 http 硬做,它只提供 GET/POST。
  • 上报内容里带中文时,确保 json.encode 出来的是 UTF-8,平台侧按 UTF-8 解; 定长字段名比中文值更省事。

七、相关文档 ​

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