Skip to content

❓ 常见问题 ​

LPPLC Lua 开发中常见问题与解决方案


基础问题 ​

Q: 脚本下载后没有执行? ​

检查以下几点:

  1. 是否点击了"运行"按钮
  2. 脚本是否有语法错误(查看日志输出)
  3. 是否下载到了正确的设备

Q: 脚本执行一次就停了? ​

Lua 脚本默认执行到文件末尾就结束。如果需要持续运行,使用以下方式:

lua
-- 方式一:使用定时器(推荐)
timer.start(1000, function()
    -- 每秒执行
end)

-- 方式二:要一直跑、中间还要等 —— 用任务
task.start(function()
    while true do
        do_work()
        system.sleep(1000)   -- 任务内:让出一拍,不阻塞
    end
end)

-- 方式三:几件事同时进行、要写顺 —— 用协程
coroutine.wrap(function()
    while true do
        do_work()
        system.sleep(1000)   -- 协程内:挂起,不阻塞
    end
end)()

> ⚠️ **主线程里绝不能用 `sleep` / `system.sleep` 写 `while true` 循环**:它只停住线程、不推进消息泵,
> 期间定时器与 MQTT/TCP/串口回调**全部失效**,设备像死机。`sleep` 只在**协程内**或**任务内**才不阻塞。
> 脚本侧的 `system.recv` 已于 2026-10-22 删除(泵在固件 C 侧),注册完定时器/任务让脚本自然结束即可。

Q: 定时器回调不触发? ​

先查三件事:① 脚本里是不是有 while true do ... system.sleep(n) ... end 这类自写主循环(它会堵死消息泵)—— 删掉它,让脚本自然结束;② 定时器是不是写在 if 分支里没执行到;③ 是不是重复下载了旧版本脚本。 要"一直跑、中间还要等"用 task.start,见并发模型概述。

Q: 修改脚本后行为没变化? ​

修改代码后需要重新点击"下载"按钮,仅保存编辑器内容不会更新设备上的脚本。


PLC 数据交互 ​

Q: 读取 D 寄存器返回的值不对? ​

  • 确认使用正确的数据类型函数:plc.d()(16位)、plc.dd()(32位)、plc.df()(浮点)
  • 16 位 D 寄存器范围 -32768~32767,超出范围会溢出
  • 32 位寄存器占用两个连续 D 地址,注意地址不要重叠

Q: plc.x/plc.y 返回什么类型? ​

返回整数 0 或 1,不是布尔值。判断时必须写 == 1 或 == 0:

lua
-- 正确
if plc.x(0) == 1 then end

-- 错误:Lua 里只有 nil 和 false 为假,0 也是真,这个条件永远成立
if plc.x(0) then end

Q: 远程 plc.get/put 没有数据? ​

  • 确认对方设备在线且 IP 正确,且两台在同一局域网(UDP 单播穿不了 NAT)
  • 远程通信使用 UDP 协议,端口 25388,不要把该端口映射到公网
  • 一次最多 255 点,填超过会被静默截断
  • 请求是异步的,没有返回值;plc.get 要等对方回包后数据才落到本地 D 区,丢包时本轮不更新
  • 【Linux / RDA8910 版】该平台 plc.get 收不回数据(固件未处理读回应),只能用 plc.put

完整排查顺序见 9.6 多机通讯。


网络通信 ​

Q: MQTT 连接不上? ​

  1. 检查 Broker 地址和端口是否正确
  2. 检查网络是否连通:net.ping("broker.emqx.io")
  3. Client ID 是否与其他设备冲突
  4. 用户名密码是否正确(无需认证时传空字符串 "")

Q: HTTP 请求超时? ​

  • 同步请求会阻塞,检查网络连通性
  • 检查 URL 是否正确(注意 http/https)
  • 尝试使用异步模式 get_asy/post_asy
  • 多网卡环境需指定出口 IP

Q: TCP 通信数据不完整? ​

  • TCP 是流式协议,需要自己处理消息边界
  • 使用 net.recv(sock, "*l") 按行读取
  • 或在协议中定义长度字段,按长度读取

Q: 如何读取 WiFi 信号强度(RSSI)? ​

使用 plc.AI 接口获取当前 WiFi 连接的信号强度:

lua
local result = plc.AI({
    cmd = "ShowMe",
    data = "WIFI,RSSI"
})
-- result.data 返回 RSSI 值(单位 dBm,负数,如 -50 ~ -90)
-- result.errno == 0 表示成功
-- result.errno == -4 表示 WiFi 未连接

RSSI 参考范围:

  • -30 ~ -50 dBm:信号极强(紧挨路由器)
  • -50 ~ -70 dBm:信号良好
  • -70 ~ -85 dBm:信号较弱,可能偶有延迟
  • 低于 -85 dBm:信号很差,容易断连

注意:此功能仅 ESP32 版本支持,且设备需通过 WiFi STA 方式连接路由器后才可读取。

想在上位机或网页里持续看信号强度? 设备不会自动上报 RSSI,用 Lua 定时把它写进一个 D 寄存器,再用现有手段读这个寄存器即可:

lua
-- 周期任务用 timer.start,别用 while true + sleep 当主循环(那会堵死消息泵)
timer.start(1000, function()
    local r = plc.AI({ cmd = "ShowMe", data = "WIFI,RSSI" })
    plc.d(500, (r and r.errno == 0) and r.data or 0)   -- 0 = 读不到(多为未连上 WiFi)
end)

之后用上位机「命令控制台」执行 plc read --type D --start 500 --count 1,或在云平台的数据点/组态画面里显示 D500。读回 0 就说明当前没连上 WiFi(而不是信号为 0)。

Q: 多网卡环境如何选择出口? ​

指定本地 IP 地址作为出口:

lua
-- HTTP 指定以太网出口
local client = http.creat("http://api.example.com", plc.eth_ip())

-- MQTT 使用 WiFi 出口
-- 当前系统自动选择,如需指定请使用对应模块的 IP 参数

系统与内存 ​

Q: 系统运行一段时间后变慢或崩溃? ​

  • 避免在循环或定时器回调中不断创建新对象(闭包、大字符串)
  • 使用 local 变量代替全局变量
  • 检查是否有不断增长的 table(如缓冲区没有清理)

Q: 可用内存有多少? ​

ESP32 Lua 堆内存约 40-80KB,可通过以下方式查看:

lua
-- 查看文件系统空间
local total, used, free = system.fs_info()
print("Flash 剩余:", free, "字节")

Q: system.sleep 在主线程会怎样? ​

会阻塞整个 Lua 运行时。消息泵推不动,所以这期间定时器回调、MQTT/TCP/UDP/串口回调、BACnet 的 on_change 和 reg() 回调一个都不会触发。

所以不要自己写 while true do ... system.sleep(n) end 当主循环。周期任务交给 timer.start(ms, fn),脚本 return 后固件自动接管消息泵;要"一直跑、中间还要等"用 task.start 起任务。协程里可以放心用 sleep,那里的 sleep 是挂起而不是阻塞,不影响主循环。脚本侧的 system.recv 已于 2026-10-22 删除。


文件系统 ​

Q: 文件写入失败? ​

  • SPIFFS 是扁平文件系统,不支持创建目录
  • Flash 有擦写次数限制,避免频繁写入
  • 检查存储空间是否充足:system.fs_info()

Q: 如何持久化存储少量数据? ​

使用 plc.kv_set/kv_get 或 fdb.kv_set/kv_get:

lua
-- 方式一:plc 模块(简单键值)
plc.kv_set("calibration", "1.025")
local val = plc.kv_get("calibration")

-- 方式二:fdb 模块(支持更多类型)
fdb.kv_set("config", {ip = "192.168.1.1", port = 502})
local config = fdb.kv_get("config")

调试技巧 ​

Q: 如何调试 Lua 脚本? ​

  1. 使用 print() 输出到串口/日志
  2. 使用 string.format() 格式化复杂数据
  3. 用 type() 检查变量类型
  4. 用 pcall() 捕获运行时错误
lua
-- pcall 错误捕获
local ok, err = pcall(function()
    -- 可能出错的代码
    local val = plc.d(99999)  -- 越界访问
end)
if not ok then
    print("错误:", err)
end

Q: 如何查看 PLC 寄存器状态? ​

lua
-- 批量打印 D0-D9
for i = 0, 9 do
    print(string.format("D%d = %d", i, plc.d(i)))
end

-- 打印所有输入 X0-X7
for i = 0, 7 do
    print(string.format("X%d = %d", i, plc.x(i)))
end

相关例程 ​

想直接抄可运行的脚本,去 例程总览 B-0:按模块分组列出了每个页面里的 3 个完整例程(场景 / 代码 / 要点 / 验证 / 注意事项)。

写例程的硬规矩(禁止阻塞主循环、API 不编造等)见 B-0 的「写例程时的硬性约定」。


最后更新: 2026-05-05

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