适用版本:1.0.27 更新日期:2026-10-09
回调由系统在相应事件发生时调用。脚本只需定义要使用的回调,不要主动循环调用,也不要把回调定义成局部函数。
| 回调签名 | 触发时机 | 参数与返回值 |
|---|---|---|
on_init() |
脚本加载成功后初始化一次 | 无参数,不使用返回值 |
on_run(screen) |
按运行周期处理业务 | screen 为当前主页面 ID,不使用返回值 |
on_press(state, x, y) |
触摸按下、按住移动或松开 | 状态与屏幕像素坐标,不使用返回值 |
on_screen_change(screen) |
普通主页面成功切换后 | screen 为目标主页面 ID,不使用返回值 |
on_update(slave, vtype, addr) |
可读变量缓存产生有效更新后 | 从站索引、变量类型、更新地址,不使用返回值 |
on_widget_event(screen_id, control_id, event) |
控件产生事件时 | 控件所属页面、控件 ID、事件码,不使用返回值 |
on_timer(timer_id) |
脚本定时器到期时 | 定时器编号,不使用返回值 |
on_uart_recv(channel, packet, length) |
串口收到业务字节片段时 | 通道、二进制字符串、字节数,不使用返回值 |
启动顺序为:注册原生接口 → 执行 main.lua 顶层代码 → 调用 on_init() → 创建启动页面 → 进入界面主循环。on_init() 可以省略;省略它不会关闭 on_run()。
回调应声明为全局函数:
function on_init()
print("Lua initialized")
end
使用 local function on_init() 后,系统无法按名称找到该回调。同名函数后一次定义会覆盖前一次;合并多个示例时,应把业务分支放进同一个回调。
适合初始化脚本变量、本地寄存器、预读范围和定时器。此时启动页尚未创建,不应在这里设置控件外观或创建画布。
local LW = 1
function on_init()
set_uint16(LW, 0x1014, 0)
set_run_cycle(100)
start_timer(1, 1000, 0, 0)
end
默认约每 100 ms 调用一次。set_run_cycle(cycle) 设置周期,单位毫秒,最小有效周期为 10 ms;传入 0 不会停止周期回调。执行时机受界面主循环调度影响。
screen 是当前实际主页面 ID。存在滑动嵌入页面时,需要获取嵌入视图可使用 get_view_page();弹窗和系统键盘不改变此参数的主页面含义。
local LW = 1
local last_value = nil
function on_run(screen)
local value = get_uint16(LW, 0x1014)
if value ~= last_value then
last_value = value
print("page", screen, "value", value)
end
end
不要在周期回调中忙等、无限循环或连续输出大量日志。需要延迟执行时,使用定时器或保存状态,等待下次回调继续处理。
| 参数 | 含义 |
|---|---|
state = 1 |
按下或按住时的触摸更新,移动过程中可能多次调用 |
state = 0 |
松开 |
x, y |
屏幕像素坐标;原点在左上角,向右和向下为正方向 |
function on_press(state, x, y)
if state == 0 then
print("touch released", x, y)
end
end
全屏触摸回调没有控件 ID;需要分辨哪个控件产生了事件时,使用 on_widget_event()。
普通主页面成功创建并切换后触发。使用切页动画时,目标页面已经可用,但此回调不表示动画播放完成。切换到相同页面、无效页面或仅打开弹窗、键盘、嵌入预览,不会产生一次新的主页面切换回调。
local current_screen = nil
function on_screen_change(screen)
current_screen = screen
print("main page changed", screen)
end
页面业务定时器不会因切页自动停止;按业务需要在此回调中调用 stop_timer(),或在定时回调中判断页面。
函数名是 on_update,不是 on_updata。
function on_update(slave, vtype, addr)
-- 用对应 get_* 函数读取最新缓存值。
end
| 参数 | 含义 |
|---|---|
slave |
工程从站索引,从 0 开始,不是通信站号;本地 LW/RW 为 0 |
vtype |
变量类型,例如 LW 为 1、RW 为 2 |
addr |
实际发生更新的地址,多字数据可能对应多个地址 |
回调发生在可读缓存更新后。外部源首次收到有效值,即使值为 0,也会通知;之后相同值通常不重复通知。分发前同一地址的多次变化可能合并为一次,读取时得到最新缓存值。
主站读取依赖缓存与预读,不能把调用 get_*() 理解为同步等待 PLC 应答。处理多从站值前,应按 slave 选择正确的配置索引。
local HOLDING = 6
function on_init()
start_read(0, HOLDING, 100, 1)
end
function on_update(slave, vtype, addr)
if slave == 0 and vtype == HOLDING and addr == 100 then
select_slave(slave)
print("cached value", get_uint16(vtype, addr))
end
end
回调内部同步写变量不会递归触发新的 on_update()。本地提交、HMI 写入通知与 PLC 执行确认是不同阶段;不能用此回调替代对端协议的应答确认。完整读写与通知规则见寄存器操作函数。
这是用于接收控件事件的回调,不负责创建或修改控件。控件接口函数另行说明。
| 参数 | 含义 |
|---|---|
screen_id |
事件控件自身所属的页面 ID,包括弹窗、键盘和嵌入内容,不一定等于当前主页面 |
control_id |
工程中的控件 ID |
event |
事件码,取决于控件类型和实际事件 |
| 常用事件码 | 含义 |
|---|---|
| 1 | 按下 |
| 7 | 点击 |
| 8 | 松开 |
| 26 | 值发生变化 |
function on_widget_event(screen_id, control_id, event)
if event == 7 then
print("clicked", screen_id, control_id)
elseif event == 26 then
print("value changed", screen_id, control_id)
end
end
不同控件不一定产生全部事件。业务判断应同时匹配页面、控件和事件码,避免同一操作的按下、点击、松开分别执行重复业务。
多个定时器共享一个回调,按编号分配任务。start_timer(id, timeout, 0, repeat_count) 中,repeat_count = 0 表示无限循环,正数表示总触发次数。
function on_timer(timer_id)
if timer_id == 1 then
print("one-second task")
end
end
同一轮界面循环对一个到期定时器最多调用一次,不补发错过的周期。完整参数与重设方法见定时器函数。
接收通道 0 为工程通信口,3 为独立第二串口。发送时使用的通道别名不会改变接收通道号。packet 是保留零字节及高位字节的二进制字符串,length 是本次字节数,与 #packet 一致,单次最多 512 字节。一次回调可能是部分帧,也可能包含多帧,应按通道分别缓存并解析。
function on_uart_recv(channel, packet, length)
local hex = {}
for i = 1, length do
hex[i] = string.format("%02X", string.byte(packet, i))
end
print("RX", channel, table.concat(hex, " "))
end
所有内置协议及自定义协议均支持原始业务字节回调;它不保证完整帧、有效帧或校验成功,也不等于变量已经更新。回调返回值不会阻止内置协议继续解析。模拟器主口需选择“工程通信”模式;“手动调试”不向工程脚本分发主口接收数据,第二口独立收发。合法下载控制数据不进入主口业务回调。收发限制、双串口配置、拆包和粘包的完整例子见自由协议串口函数。
本例每秒将 LW1014 加 1,在变量更新后输出日志。工程可把数值控件绑定到该变量观察结果,不需要调用控件设置接口。
local LW = 1
local COUNTER_ADDR = 0x1014
local TIMER_COUNTER = 1
function on_init()
set_uint16(LW, COUNTER_ADDR, 0)
set_run_cycle(100)
start_timer(TIMER_COUNTER, 1000, 0, 0)
end
function on_run(screen)
-- 常规业务写在这里;保持短小,不阻塞界面。
end
function on_timer(timer_id)
if timer_id == TIMER_COUNTER then
local value = get_uint16(LW, COUNTER_ADDR)
set_uint16(LW, COUNTER_ADDR, (value + 1) % 65536)
end
end
function on_update(slave, vtype, addr)
if vtype == LW and addr == COUNTER_ADDR then
print("counter", get_uint16(LW, COUNTER_ADDR))
end
end
请确认示例地址没有被工程其他业务使用;如有冲突,更换 COUNTER_ADDR 并同步修改变量绑定。
| 现象 | 检查方法 |
|---|---|
| 回调不执行 | 核对函数名大小写、全局声明、脚本加载日志及实际触发条件 |
| 合并示例后部分逻辑失效 | 同名回调被覆盖;将逻辑合并到同一个函数 |
| 初始化时提示控件不存在 | 启动页面在 on_init 后才创建,页面操作放到页面可用之后 |
| 周期回调使界面卡顿 | 删除忙等与长循环,用定时器或分次处理 |
| 变量回调没有每次写入都触发 | 相同值不重复通知,同地址待分发更新会合并 |
| PLC 写入后回调触发但设备未变化 | 缓存提交不等于设备执行,应核对串口应答、地址及协议设置 |
| 串口偶尔收到半包或两包 | 正常流式接收现象;按实际协议拼帧和校验 |
旧脚本等待 on_draw |
当前版本没有此绘图回调 |
旧示例中的 on_control_notify 不是当前控件事件的可靠入口;接收事件使用本页的 on_widget_event。脚本加载或顶层执行失败时,运行库停止该脚本;on_init 失败时也会关闭脚本。其他回调错误应根据运行日志定位,不能把没有业务结果直接当成没有触发事件。
模拟器中,Lua print()、脚本加载/运行错误及未被脚本自行捕获的回调异常显示在串口副屏的“运行日志”。选择“运行日志”或“全部日志”查看,无需连接 COM;这些信息不作为串口 TX 发送,也不计入 RX/TX 统计。用 pcall 捕获的错误需由脚本自行 print(err) 输出。完整说明见自由协议串口函数。