主题
综合例程: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逐段要点:
- 回调签名固定是
function(errcode, data, datalen),第一个参数是错误码不是数据。0才是成功。 - 对象必须长期持有(这里用全局
hc):http对象被回收时底层连接资源会一起释放, 请求做一半对象没了就永远等不到回调。 add_head设一次,之后每个请求都带上;传空串""是删除该头部。post_asy不返回结果,成功与否只能在回调里看;所以失败分支里不需要重试逻辑, 交给下一个定时周期最省事。- 拉回来的 JSON 必须
pcall包一层:HTTP 是对方的接口,返回 HTML 报错页、 空串、纯文本都很常见,直接json.decode会让回调报错。 ip参数在多网卡机型上很有用:WiFi 和以太网同时在线时, 不指定就可能从另一块网卡出去,到不了内网平台。- 第 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 解; 定长字段名比中文值更省事。
七、相关文档
- http — HTTP 客户端:全部函数与回调签名
- json — JSON:
encode/decode - plc — PLC 数据交互:
plc.d/plc.eth_ip - system — 系统功能:
collectgarbage相关的内存说明 - net — 网络工具:先用
net.ping确认平台可达 - 综合例程:MQTT 上报(电表)
- 快速入门:对接 HTTP 上报与拉取数据