Lua 脚本#

Lua 脚本模块为 CycBox 提供了强大的沙箱化脚本环境,用于自定义消息处理。它能够实时转换、解析和分析接收到的消息,无需编译插件。

概述#

Lua 脚本系统拦截处理管道中的消息,允许您:

  • 解析自定义协议 - 解码专有或未公开的数据格式
  • 转换消息数据 - 修改载荷、提取值或添加元数据
  • 定时发送消息 - 以精确的时间控制发送命令
  • 提取遥测数据 - 添加可用于图表的值以实现实时可视化
  • 记录诊断信息 - 在开发过程中输出调试信息

架构#

Lua 脚本在 CycBox 消息处理管道中执行:

传输层 → 编解码器 → 格式化器 → 面板 → Lua 脚本 → 用户界面
                                    ↓
                               on_message()

on_message() 钩子函数接收每条传入消息作为全局 message 对象,可以直接读取和修改。脚本运行具有可配置的超时时间(默认: 1000ms)以防止阻塞。

快速入门#

基本脚本结构#

每个 Lua 脚本必须实现 on_message() 函数:

-- 为每条接收到的消息调用
-- 通过全局 'message' 对象访问消息字段进行读取/修改
-- 必须返回 true(如果修改了消息)或 false(未修改)
function on_message()
    -- 您的处理逻辑
    return false  -- 如果修改了消息则返回 true
end

重要: 返回值控制是否将修改后的消息复制回去:

  • true - 应用消息修改(载荷、值等)
  • false - 消息未更改地通过(性能优化)

消息 API#

全局 message 对象提供以下方法:

载荷访问#

-- 读取/写入原始载荷(字节)
local payload = message:get_payload()
message:set_payload(bytes)

-- 读取/写入帧数据(包括协议开销)
local frame = message:get_frame()
message:set_frame(bytes)

值提取(用于图表)#

读取管道早期阶段解码的现有值:

-- 如果存在则按 id 返回值,否则返回 nil
-- 支持的类型: boolean, int8-64, uint8-64, float32/64, string
local temperature = message:get_value("temperature")

写入用于仪表板面板可视化的新值:

-- 添加整数值
message:add_int_value(id, value, [group])

-- 添加浮点数值
message:add_float_value(id, value, [group])

-- 添加字符串值(用于标签/状态)
message:add_string_value(id, value, [group])

-- 添加布尔值
message:add_bool_value(id, value, [group])

参数:

  • id (字符串) - 值标识符,显示为图表标题
  • value - 数值或字符串数据
  • group (可选字符串) - 多系列图表的组名(具有相同 id 但不同 group 的值显示在同一图表上)

元数据#

-- 获取消息时间戳(单位为微秒)
local timestamp = message:get_timestamp()

工具函数#

日志记录#

log(level, message)

级别: "info", "warn", "error"

示例:

log("info", "Processing message with " .. #payload .. " bytes")
log("warn", "Unexpected payload length")

延迟发送消息#

success = send_after(payload, delay_ms)

定时发送消息,在指定延迟后执行,可多次调用。

参数:

  • payload (字符串) - 要发送的消息数据
  • delay_ms (整数) - 延迟时间(毫秒)

返回: 成功返回 true,失败返回 false

示例:

-- 500ms 后发送命令
local ok = send_after("AT+INFO\r\n", 500)
if not ok then
    log("error", "Failed to schedule message")
end

二进制读取辅助函数#

CycBox 提供了从字节串读取二进制数据的辅助函数。所有偏移量都是从 1 开始索引(Lua 约定)。

8 位整数#

value = read_u8(bytes, offset)   -- 无符号 (0-255)
value = read_i8(bytes, offset)   -- 有符号 (-128 到 127)

16 位整数#

value = read_u16_be(bytes, offset)  -- 无符号大端序
value = read_u16_le(bytes, offset)  -- 无符号小端序
value = read_i16_be(bytes, offset)  -- 有符号大端序
value = read_i16_le(bytes, offset)  -- 有符号小端序

32 位整数#

value = read_u32_be(bytes, offset)  -- 无符号大端序
value = read_u32_le(bytes, offset)  -- 无符号小端序
value = read_i32_be(bytes, offset)  -- 有符号大端序
value = read_i32_le(bytes, offset)  -- 有符号小端序

浮点数#

value = read_float_be(bytes, offset)   -- 32 位大端序
value = read_float_le(bytes, offset)   -- 32 位小端序
value = read_double_be(bytes, offset)  -- 64 位大端序
value = read_double_le(bytes, offset)  -- 64 位小端序

错误处理: 如果偏移量超出范围,函数会抛出运行时错误。

示例: PMS9103M 空气质量传感器#

PMS9103M 是一种颗粒物传感器,以自定义二进制协议输出数据:

协议规范#

字段 偏移 类型 描述
前缀 0-1 字节 固定头部 0x42 0x4D (“BM”)
长度 2-3 U16 BE 载荷长度(始终为 28 = 26 字节数据 + 2 字节校验和)
PM1.0 CF=1 4-5 U16 BE PM1.0 浓度(μg/m³, CF=1)
PM2.5 CF=1 6-7 U16 BE PM2.5 浓度(μg/m³, CF=1)
PM10 CF=1 8-9 U16 BE PM10 浓度(μg/m³, CF=1)
PM1.0 ATM 10-11 U16 BE PM1.0 浓度(μg/m³, 大气环境)
PM2.5 ATM 12-13 U16 BE PM2.5 浓度(μg/m³, 大气环境)
PM10 ATM 14-15 U16 BE PM10 浓度(μg/m³, 大气环境)
颗粒 >0.3μm 16-17 U16 BE 每 0.1L 空气中的计数
颗粒 >0.5μm 18-19 U16 BE 每 0.1L 空气中的计数
颗粒 >1.0μm 20-21 U16 BE 每 0.1L 空气中的计数
颗粒 >2.5μm 22-23 U16 BE 每 0.1L 空气中的计数
颗粒 >5.0μm 24-25 U16 BE 每 0.1L 空气中的计数
颗粒 >10μm 26-27 U16 BE 每 0.1L 空气中的计数
保留 28-29 U16 BE 版本/错误码
校验和 30-31 U16 BE Sum16 校验和

完整实现#

-- PMS9103M 空气质量传感器解析器
-- 解析颗粒物浓度和颗粒计数

function on_message()
    local payload = message:get_payload()

    -- 验证载荷长度(去除前缀/长度/校验和后为 26 字节)
    if #payload ~= 26 then
        log("warn", "Invalid PMS9103M payload length: " .. #payload .. " (expected 26)")
        return false
    end

    -- 解析 PM 浓度(CF=1, 标准颗粒) 单位: μg/m³
    local pm1_0_cf1 = read_u16_be(payload, 1)   -- 偏移 1 (完整帧中的字节 4-5)
    local pm2_5_cf1 = read_u16_be(payload, 3)   -- 偏移 3 (字节 6-7)
    local pm10_cf1  = read_u16_be(payload, 5)   -- 偏移 5 (字节 8-9)

    -- 解析 PM 浓度(大气环境) 单位: μg/m³
    local pm1_0_atm = read_u16_be(payload, 7)   -- 偏移 7 (字节 10-11)
    local pm2_5_atm = read_u16_be(payload, 9)   -- 偏移 9 (字节 12-13)
    local pm10_atm  = read_u16_be(payload, 11)  -- 偏移 11 (字节 14-15)

    -- 解析颗粒计数(每 0.1L 空气中的颗粒数)
    local particles_0_3um = read_u16_be(payload, 13)  -- 偏移 13 (字节 16-17)
    local particles_0_5um = read_u16_be(payload, 15)  -- 偏移 15 (字节 18-19)
    local particles_1_0um = read_u16_be(payload, 17)  -- 偏移 17 (字节 20-21)
    local particles_2_5um = read_u16_be(payload, 19)  -- 偏移 19 (字节 22-23)
    local particles_5_0um = read_u16_be(payload, 21)  -- 偏移 21 (字节 24-25)
    local particles_10um  = read_u16_be(payload, 23)  -- 偏移 23 (字节 26-27)

    -- 将 PM 浓度添加到图表(CF=1 vs 大气环境对比)
    -- 具有相同 id 但不同 group 的值显示在同一图表上
    message:add_int_value("PM1.0 (μg/m³)", pm1_0_cf1, "CF=1")
    message:add_int_value("PM1.0 (μg/m³)", pm1_0_atm, "ATM")

    message:add_int_value("PM2.5 (μg/m³)", pm2_5_cf1, "CF=1")
    message:add_int_value("PM2.5 (μg/m³)", pm2_5_atm, "ATM")

    message:add_int_value("PM10 (μg/m³)", pm10_cf1, "CF=1")
    message:add_int_value("PM10 (μg/m³)", pm10_atm, "ATM")

    -- 将颗粒计数添加到图表(所有在同一组中进行对比)
    message:add_int_value("颗粒 >0.3μm", particles_0_3um, "颗粒计数 (每 0.1L)")
    message:add_int_value("颗粒 >0.5μm", particles_0_5um, "颗粒计数 (每 0.1L)")
    message:add_int_value("颗粒 >1.0μm", particles_1_0um, "颗粒计数 (每 0.1L)")
    message:add_int_value("颗粒 >2.5μm", particles_2_5um, "颗粒计数 (每 0.1L)")
    message:add_int_value("颗粒 >5.0μm", particles_5_0um, "颗粒计数 (每 0.1L)")
    message:add_int_value("颗粒 >10μm", particles_10um, "颗粒计数 (每 0.1L)")

    -- 记录解析值用于调试
    log("info", string.format("PM2.5: CF=%d ATM=%d μg/m³", pm2_5_cf1, pm2_5_atm))

    -- 返回 true 因为我们向消息添加了值
    return true
end

预期输出#

连接到 PMS9103M 传感器后,仪表板面板将显示:

  1. 三个 PM 浓度图表(PM1.0、PM2.5、PM10):

    • 每个图表有两条系列线: “CF=1”(蓝色)和 “ATM”(红色)
    • Y 轴: 浓度单位 μg/m³
    • 实时折线图,包含历史数据
  2. 颗粒计数图表:

    • 六条系列线显示不同粒径阈值的颗粒计数
    • Y 轴: 每 0.1L 空气中的颗粒数
    • 用于分析空气质量分布