主题
🚀 第一个 Lua 脚本
从编写到运行,5 分钟完成第一个 LPPLC Lua 程序
📖 编写 Lua 脚本
入口有两个,推荐用新版上位机
设备自带的 Web 管理页面可以写脚本;新版上位机工具里有基于 Monaco 的 Lua 编辑器 (自动补全、语法检查、右侧 API 面板),体验更好 —— 用法见 上位机工具 · Lua 脚本编辑器。 两边都是下载后要重启设备才生效。
第 1 步:进入 Lua 编辑页面
- 浏览器访问 LPPLC 的 IP 地址(如
http://192.168.1.100) - 登录管理页面
- 点击 "Lua 脚本" 菜单进入编辑器
第 2 步:编写脚本
在编辑器中输入以下代码:
lua
-- 我的第一个 Lua 脚本
print("Hello, LPPLC!")
-- 读取 X0 输入状态
local x0 = plc.x(0)
print("X0 状态: " .. x0)
-- 将 D100 的值加 1 写入 D101
local val = plc.d(100)
plc.d(101, val + 1)
print("D100 = " .. val .. ", D101 = " .. (val + 1))
-- 控制 Y0 输出
if x0 == 1 then
plc.y(0, 1)
print("Y0 已开启")
else
plc.y(0, 0)
print("Y0 已关闭")
end
print("脚本执行完毕")第 3 步:下载运行
- 点击 "下载" 按钮将脚本写入设备
- 点击 "运行" 按钮执行脚本
- 在 "日志输出" 窗口查看运行结果
预期输出
Hello, LPPLC!
X0 状态: 0
D100 = 0, D101 = 1
Y0 已关闭
脚本执行完毕🔄 让脚本持续运行
上面的脚本只执行一次就结束了。要让它一直跑,不需要自己写主循环:把定时器、回调这些注册好, 让脚本正常走到最后一条语句结束就行 —— 脚本一结束,固件自动进入消息泵分发消息, 之后到点的定时器、收到的 MQTT / 串口数据照常触发回调。
写法一:注册完定时器,让脚本自然结束(推荐)
lua
-- 定时器模式:注册回调函数,不需要主循环
print("脚本启动")
-- 每 1 秒读取 D100 并打印
timer.start(1000, function()
local val = plc.d(100)
print("D100 = " .. val)
end)
-- 每 5 秒上报云平台
timer.start(5000, function()
local temp = plc.df(100)
plc.cloud_publish("temperature", tostring(temp))
end)
print("定时器已注册")说明:这段脚本没有循环、没有
return,跑到
写法二:一个脚本同时做几件事 —— 用协程
要一边轮询串口、一边按时上报,就把两条流程各写进一个协程,在协程里用 system.sleep 等待: 协程内的 sleep 是挂起,不是阻塞,主线程照旧分发消息,两条流程互不耽误。 完整写法见 协程 的例程 1。
⚠️ 协程那套循环只能在协程里跑。把它抄到主线程上(while true do ... system.sleep ... end), 消息泵就断了 —— 症状是"定时器不触发、MQTT 回调没反应、设备像死机", 成因见下面写法三里那个警示框。
写法三:要一直跑、中间还要等 —— 用任务
比协程更省事的形状:task.start 起一个后台任务,循环写在任务体里, system.sleep / event.wait 都是让出。适合"采集 → 等设备回应 → 再处理"这类分步流程。
lua
-- 任务模式:一段代码在后台一直跑,等待期间其它功能照常
task.start(function()
while true do
local val = plc.d(100)
print("D100 = " .. val)
system.sleep(1000) -- 让出一拍,不是阻塞
end
end)主循环里只能用 system.recv,绝不能用 sleep
主线程里的 while true do ... system.sleep(n) ... end 会堵死消息泵 —— 期间定时器、MQTT/TCP/串口回调、on_change 一个都不会触发,设备看起来像死机。
lua
-- 错误:回调永远不触发
while true do
do_work()
system.sleep(5000) -- 真阻塞,不推进消息泵
end
-- 正确①:按时做几件事 → 定时器(推荐)
timer.start(5000, do_work)
-- 正确②:几件事同时进行 → 协程
coroutine.wrap(function()
while true do
do_work()
system.sleep(5000) -- 协程内:挂起,不阻塞
end
end)()
-- 正确③:一直跑、中间还要等 → 任务
task.start(function()
while true do
do_work()
system.sleep(5000) -- 任务内:让出一拍
end
end)system.sleep 只在协程内或任务内才不阻塞(那里是让出而非阻塞),主线程里永远不要用。
2026-10-22 起脚本侧的
system.recv已删除(消息泵改在固件 C 侧)。 更早的工程里那种while true do system.recv(10) end手写主循环,现在会报attempt to call a nil value—— 把整段删掉即可,脚本注册完定时器/任务直接结束,泵由固件接管。
怎么选:只是按时做几件事 → 写法一;几件事同时进行、要写顺 → 写法二(协程); 一直跑、中间还要等 → 写法三(任务)。
🛠️ 实用入门脚本
闪烁 LED
lua
local led_state = 0
timer.start(500, function()
led_state = 1 - led_state
plc.led(led_state)
end)
print("LED 闪烁中")定时采集存储
lua
-- 每 2 秒采集 D100 温度值存入 D200-D219 循环缓冲
local buf_index = 0
local buf_size = 20
timer.start(2000, function()
local temp = plc.d(100)
plc.d(200 + buf_index, temp)
buf_index = (buf_index + 1) % buf_size
end)
print("采集任务已启动")简单 MQTT 上报
lua
local client = mqtt.creat("broker.emqx.io", 1883, "lpplc_demo", "", "")
client:reg(mqtt.CONNECTED, function()
print("MQTT 已连接")
end)
client:reg(mqtt.DISCONNECTED, function()
print("MQTT 已断开")
end)
client:start()
-- 每 10 秒上报一次
timer.start(10000, function()
if mqtt.is_linked(client) then
local temp = plc.d(100)
mqtt.publish(client, "demo/temp", tostring(temp), 1)
print("已上报: " .. temp)
end
end)⚠️ 常见问题
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 日志无输出 | 脚本未下载或未运行 | 点击"下载"→"运行" |
| 脚本执行一次就停了 | 没有 timer.start 或 while 循环 | 使用定时器模式 |
plc 报 nil | 模块名拼写错误 | 使用小写 plc,不是 PLC |
| 定时器回调不触发 | 脚本里用 sleep 阻塞了,或自己写了 while true 死循环 | 改回 timer.start;要"一直跑、中间还要等"用 task.start |
| 修改脚本后行为不变 | 只修改未下载 | 修改后需重新点击"下载" |
相关例程
想直接抄可运行的脚本,去 例程总览 B-0:按模块分组列出了每个页面里的 3 个完整例程(场景 / 代码 / 要点 / 验证 / 注意事项)。
- 通信:com 串口 · tcp_server · mqtt · pipe 概述 · net
- 云与数据:cloud · json · data · fdb
- 综合工程:Modbus 采集 + MQTT 上报 + 云端下发
写例程的硬规矩(禁止阻塞主循环、API 不编造等)见 B-0 的「写例程时的硬性约定」。