菜单

系统回调函数

下载

适用版本:1.0.27 更新日期:2026-10-09

回调由系统在相应事件发生时调用。脚本只需定义要使用的回调,不要主动循环调用,也不要把回调定义成局部函数。

1. 回调索引与执行顺序

回调签名 触发时机 参数与返回值
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()。

回调应声明为全局函数:

lua 复制代码
function on_init()
    print("Lua initialized")
end

使用 local function on_init() 后,系统无法按名称找到该回调。同名函数后一次定义会覆盖前一次;合并多个示例时,应把业务分支放进同一个回调。

2. 初始化与周期运行

2.1 on_init()

适合初始化脚本变量、本地寄存器、预读范围和定时器。此时启动页尚未创建,不应在这里设置控件外观或创建画布。

lua 复制代码
local LW = 1

function on_init()
    set_uint16(LW, 0x1014, 0)
    set_run_cycle(100)
    start_timer(1, 1000, 0, 0)
end

2.2 on_run(screen)

默认约每 100 ms 调用一次。set_run_cycle(cycle) 设置周期,单位毫秒,最小有效周期为 10 ms;传入 0 不会停止周期回调。执行时机受界面主循环调度影响。

screen 是当前实际主页面 ID。存在滑动嵌入页面时,需要获取嵌入视图可使用 get_view_page();弹窗和系统键盘不改变此参数的主页面含义。

lua 复制代码
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

不要在周期回调中忙等、无限循环或连续输出大量日志。需要延迟执行时,使用定时器或保存状态,等待下次回调继续处理。

3. 触摸与页面切换

3.1 on_press(state, x, y)

参数 含义
state = 1 按下或按住时的触摸更新,移动过程中可能多次调用
state = 0 松开
x, y 屏幕像素坐标;原点在左上角,向右和向下为正方向
lua 复制代码
function on_press(state, x, y)
    if state == 0 then
        print("touch released", x, y)
    end
end

全屏触摸回调没有控件 ID;需要分辨哪个控件产生了事件时,使用 on_widget_event()。

3.2 on_screen_change(screen)

普通主页面成功创建并切换后触发。使用切页动画时,目标页面已经可用,但此回调不表示动画播放完成。切换到相同页面、无效页面或仅打开弹窗、键盘、嵌入预览,不会产生一次新的主页面切换回调。

lua 复制代码
local current_screen = nil

function on_screen_change(screen)
    current_screen = screen
    print("main page changed", screen)
end

页面业务定时器不会因切页自动停止;按业务需要在此回调中调用 stop_timer(),或在定时回调中判断页面。

4. 变量更新:on_update

函数名是 on_update,不是 on_updata。

lua 复制代码
function on_update(slave, vtype, addr)
    -- 用对应 get_* 函数读取最新缓存值。
end
参数 含义
slave 工程从站索引,从 0 开始,不是通信站号;本地 LW/RW 为 0
vtype 变量类型,例如 LW 为 1、RW 为 2
addr 实际发生更新的地址,多字数据可能对应多个地址

回调发生在可读缓存更新后。外部源首次收到有效值,即使值为 0,也会通知;之后相同值通常不重复通知。分发前同一地址的多次变化可能合并为一次,读取时得到最新缓存值。

主站读取依赖缓存与预读,不能把调用 get_*() 理解为同步等待 PLC 应答。处理多从站值前,应按 slave 选择正确的配置索引。

lua 复制代码
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 执行确认是不同阶段;不能用此回调替代对端协议的应答确认。完整读写与通知规则见寄存器操作函数。

5. 控件事件:on_widget_event

这是用于接收控件事件的回调,不负责创建或修改控件。控件接口函数另行说明。

参数 含义
screen_id 事件控件自身所属的页面 ID,包括弹窗、键盘和嵌入内容,不一定等于当前主页面
control_id 工程中的控件 ID
event 事件码,取决于控件类型和实际事件
常用事件码 含义
1 按下
7 点击
8 松开
26 值发生变化
lua 复制代码
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

不同控件不一定产生全部事件。业务判断应同时匹配页面、控件和事件码,避免同一操作的按下、点击、松开分别执行重复业务。

6. 定时器与串口接收

6.1 on_timer(timer_id)

多个定时器共享一个回调,按编号分配任务。start_timer(id, timeout, 0, repeat_count) 中,repeat_count = 0 表示无限循环,正数表示总触发次数。

lua 复制代码
function on_timer(timer_id)
    if timer_id == 1 then
        print("one-second task")
    end
end

同一轮界面循环对一个到期定时器最多调用一次,不补发错过的周期。完整参数与重设方法见定时器函数。

6.2 on_uart_recv(channel, packet, length)

接收通道 0 为工程通信口,3 为独立第二串口。发送时使用的通道别名不会改变接收通道号。packet 是保留零字节及高位字节的二进制字符串,length 是本次字节数,与 #packet 一致,单次最多 512 字节。一次回调可能是部分帧,也可能包含多帧,应按通道分别缓存并解析。

lua 复制代码
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

所有内置协议及自定义协议均支持原始业务字节回调;它不保证完整帧、有效帧或校验成功,也不等于变量已经更新。回调返回值不会阻止内置协议继续解析。模拟器主口需选择“工程通信”模式;“手动调试”不向工程脚本分发主口接收数据,第二口独立收发。合法下载控制数据不进入主口业务回调。收发限制、双串口配置、拆包和粘包的完整例子见自由协议串口函数。

7. 可直接使用的 main.lua 示例

本例每秒将 LW1014 加 1,在变量更新后输出日志。工程可把数值控件绑定到该变量观察结果,不需要调用控件设置接口。

lua 复制代码
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 并同步修改变量绑定。

8. 常见问题

现象 检查方法
回调不执行 核对函数名大小写、全局声明、脚本加载日志及实际触发条件
合并示例后部分逻辑失效 同名回调被覆盖;将逻辑合并到同一个函数
初始化时提示控件不存在 启动页面在 on_init 后才创建,页面操作放到页面可用之后
周期回调使界面卡顿 删除忙等与长循环,用定时器或分次处理
变量回调没有每次写入都触发 相同值不重复通知,同地址待分发更新会合并
PLC 写入后回调触发但设备未变化 缓存提交不等于设备执行,应核对串口应答、地址及协议设置
串口偶尔收到半包或两包 正常流式接收现象;按实际协议拼帧和校验
旧脚本等待 on_draw 当前版本没有此绘图回调

旧示例中的 on_control_notify 不是当前控件事件的可靠入口;接收事件使用本页的 on_widget_event。脚本加载或顶层执行失败时,运行库停止该脚本;on_init 失败时也会关闭脚本。其他回调错误应根据运行日志定位,不能把没有业务结果直接当成没有触发事件。

模拟器中,Lua print()、脚本加载/运行错误及未被脚本自行捕获的回调异常显示在串口副屏的“运行日志”。选择“运行日志”或“全部日志”查看,无需连接 COM;这些信息不作为串口 TX 发送,也不计入 RX/TX 统计。用 pcall 捕获的错误需由脚本自行 print(err) 输出。完整说明见自由协议串口函数。

上一个
第五章 Lua 脚本编程
下一个
寄存器操作函数
最近修改: 2026-10-09Powered by