主题
4.6 设备端 HTTP 上报
前面几节是「后台程序调服务器接口」。这一节反过来:设备主动用 HTTP 把数据 POST 到服务器,作为 MQTT 之外的另一条上云通道。
4.6.1 什么时候用 HTTP 而不是 MQTT
| MQTT | HTTP | |
|---|---|---|
| 长连接 | 是,需要保持 | 否,一次一断 |
| 实时下发 | 支持(订阅) | 不支持,只能设备主动轮询 |
| 上报频率 | 秒级、亚秒级都合适 | 分钟级为宜 |
| 流量开销 | 小 | 每次都带完整头部和认证,明显更大 |
| 穿透性 | 需要放行 1883 | 只用 80/443,通常无需额外放行 |
| 实现复杂度 | 要 MQTT 库 | 一个 HTTP POST 就够 |
优先选择 MQTT,以下情况使用 HTTP:
- 网络环境只放行 80/443,1883 被封
- 设备端没有可用的 MQTT 库,但有 HTTP
- 上报频率很低(几分钟一次),不值得维持长连接
- 已有的第三方系统只会发 HTTP
4.6.2 上报就是调用 set_real
设备端没有专门的上报接口,直接用 4.3 的 set_real:
POST /web
Content-Type: application/json
{
"cmd": "set_real",
"username": "设备子账号",
"password": "密码",
"project": "main",
"data": [
{"name": "车间1/1号机/温度", "value": "25.6"},
{"name": "车间1/1号机/湿度", "value": "60"}
]
}数据点不存在会自动创建,效果与 MQTT 上报一致:同样能被云组态绑定、设置存盘、配置事件触发。
响应 {"cmdreq":"ok"} 表示写入成功。
4.6.3 LP 设备 Lua 示例
lua
-- 带自定义头必须用对象模式创建客户端
hc = http.creat("http://192.168.1.100/web")
hc:add_head("Content-Type", "application/json")
-- 每 60 秒上报一次
t = timer.start(60000, function()
local body = json.encode({
cmd = "set_real",
username = "dev01",
password = "密码",
project = "main",
data = {
{ name = "车间1/1号机/温度", value = tostring(plc.d(0) / 10) },
{ name = "车间1/1号机/湿度", value = tostring(plc.d(1) / 10) },
}
})
-- 定时器回调里必须用异步接口,同步接口会阻塞整个事件循环
hc:post_asy("http://192.168.1.100/web", body, function(errcode, data, len)
if errcode ~= 0 then
-- 发送失败,可在这里做本地缓存或计数
return
end
-- data 是响应体字符串,可解析确认 cmdreq
end)
end)需使用异步接口
http.post() / client:post() 是同步的,固定 15 秒超时且会阻塞整个 Lua 事件循环。在定时器回调里用同步接口,网络一卡整台设备的脚本、串口采集都会停住。定时上报应使用 post_asy。
4.6.4 设备用 HTTP 拉取云端指令
HTTP 没有下发能力,要接收云端指令只能设备主动轮询 get_real:
lua
hc2 = http.creat("http://192.168.1.100/web")
hc2:add_head("Content-Type", "application/json")
t2 = timer.start(10000, function()
local body = json.encode({
cmd = "get_real",
username = "dev01", password = "密码", project = "main",
data = { "车间1/1号机/启停" }
})
hc2:post_asy("http://192.168.1.100/web", body, function(errcode, resp, len)
if errcode ~= 0 then return end
local ok, obj = pcall(json.decode, resp) -- 外部数据一律 pcall
if not ok or obj.cmdreq ~= "ok" then return end
for _, p in ipairs(obj.data or {}) do
if p.name == "车间1/1号机/启停" then
plc.y(0, tonumber(p.value) == 1)
end
end
end)
end)轮询间隔决定了指令的响应延迟。10 秒轮询意味着按钮操作最多 10 秒后才生效。要求实时控制的场景应使用 MQTT 订阅,不要用 HTTP 轮询。
4.6.5 注意事项
- 账号密码在每个请求体里明文出现,公网上报必须用 HTTPS(
https://域名/web,端口 443) - 给每台设备开独立子账号,泄露时只影响一台
- 上报失败要有重试或本地缓存,否则断网期间的数据会丢失
- 上报频率不宜过高。每分钟一次是合适的量级,秒级上报应改用 MQTT