主题
📚 Lua基础库 - 新手必看的入门指南
📖 什么是Lua基础库?
Lua基础库就像是编程的"工具箱",里面有最常用的基本工具。这些功能不需要额外加载,打开软件就能直接使用,就像手机里自带的计算器、时钟一样方便。
能做什么:
- 💬 显示信息和调试程序
- 🔢 处理数字和文字
- ✅ 检查数据类型
- 🛠️ 处理错误和异常
🚀 3分钟快速上手
第1步:显示信息
lua
-- 在屏幕上显示文字(最常用的功能)
print("Hello World")
print("程序开始运行")
-- 显示多个内容
print("温度:", 25, "度")
print("设备状态:", "正常")第2步:处理数据
lua
-- 定义变量(存储数据的盒子)
local 温度 = 25.5
local 设备名称 = "温控器"
local 运行中 = true
-- 显示变量内容
print("当前温度:", 温度)
print("设备:", 设备名称)
print("是否运行:", 运行中)第3步:数据转换
lua
-- 把文字转换成数字
local 文字数字 = "123"
local 真正数字 = tonumber(文字数字)
print("转换结果:", 真正数字) -- 显示:123
-- 把数字转换成文字
local 数字 = 456
local 文字 = tostring(数字)
print("转换结果:", 文字) -- 显示:"456"
-- 安全调用函数
local success, result = pcall(function()
return 10 / 0 -- 可能出错的代码
end)
if success then
print("结果:", result)
else
print("错误:", result)
end📚 API参考
📋 函数列表
| 函数名 | 功能描述 | 示例 |
|---|---|---|
📋 type(value) | 获取数据类型 | type(123) → "number" |
🔢 tonumber(str) | 转换为数字 | tonumber("123") → 123 |
📝 tostring(value) | 转换为字符串 | tostring(123) → "123" |
💬 print(...) | 输出内容 | print("Hello", 123) |
🛡️ pcall(func, ...) | 安全调用函数 | pcall(math.sqrt, -1) |
⚠️ assert(condition, msg) | 断言检查 | assert(x > 0, "x必须大于0") |
📝 实用示例
数据验证
lua
function validate_number(value, name)
assert(value ~= nil, name .. "不能为空")
local num = tonumber(value)
assert(num ~= nil, name .. "必须是数字")
return num
end
-- 使用
local age = validate_number("25", "年龄")
print("年龄:", age)安全除法
lua
function safe_divide(a, b)
if type(a) ~= "number" then a = tonumber(a) end
if type(b) ~= "number" then b = tonumber(b) end
if not a or not b then
return nil, "参数必须是数字"
end
if b == 0 then
return nil, "除数不能为0"
end
return a / b
end
-- 使用
local result, err = safe_divide(10, 2)
if result then
print("结果:", result)
else
print("错误:", err)
end⚠️ 注意事项
- 类型转换:使用
tonumber()和tostring()进行安全转换 - 错误处理:重要操作使用
pcall()进行保护 - 断言检查:使用
assert()验证输入参数 - 调试输出:使用
print()输出调试信息
例程
基础库(
type/tonumber/tostring/pcall/assert)是 Lua 自带的,无需加载模块。下面例程都是可直接下载运行的完整脚本:顶层执行完即结束,不阻塞固件主循环。工业现场最常见的用途是「把外部进来的字符串安全变成数字」和「兜住会出错的外设调用」。
例程 1:把云/串口下发的字符串安全转成数字
适用:全部机型 / 软PLC模拟器 / 上位机 | 依赖模块:base | 难度:★☆☆
场景:云端或串口下发的是 "25.6" 这种字符串,算法要的是 number。直接 tonumber 失败会返回 nil,必须用兜底处理,否则后面算术运算会炸。
完整代码(可直接下载运行):
lua
-- 把任意来源的数值字符串转成 number,失败给默认值并报警
local function to_num(s, default)
local n = tonumber(s)
if n == nil then
print("数值解析失败,使用默认值:", default, "原始:", s)
return default
end
return n
end
local setpoint = to_num("350", 350)
print("目标值 =", setpoint)
-- 后续用它写 D 区或做 PID:plc.dw(200, setpoint)要点:
tonumber("25.6")→25.6;tonumber("abc")→nil(不是 0,也不会报错)。- 十六进制字符串用
tonumber("1A", 16)指定进制。 type(n) == "number"是判断「到底是不是数字」最可靠的办法。
验证方法:
- 把输入改成
"abc",日志应打印「数值解析失败,使用默认值: 350」而非崩溃。 - 把输入改成
"25.6",日志打印「目标值 = 25.6」。
注意事项:
tonumber失败时返回nil,不是0;拿nil做+运算会直接抛错中断脚本。- 工业报文里常见
" 25.6 "(带空格),先用string.match(s, "%d+%.?%d*")抓数字再tonumber更稳(见字符串库例程)。 - 浮点别用
==直接比大小,用math.abs(a-b) < 1e-6(见数学库例程)。
例程 2:用 pcall 兜住会出错的外设调用
适用:同上 | 依赖模块:base com(可选)| 难度:★☆☆
场景:com.recv、com.send、读某个可能不存在的寄存器、解析坏 JSON 都可能抛异常。一旦顶层未捕获,整个脚本就挂了。用 pcall 包一层,出错时只记录、不中断。
完整代码(可直接下载运行):
lua
local ok, err = pcall(function()
-- 任何可能出错的外设操作都放这里
local data = com.recv(1000) -- 等 1 秒收一帧
local j = json.decode(data) -- 坏数据会抛异常
plc.dw(100, j.value)
end)
if not ok then
print("本次处理失败,已跳过:", err)
-- 不 return / 不崩溃,脚本继续往下走或结束
end要点:
pcall(f)返回true, 返回值...或false, 错误信息。- 把「会出错的一小段」包进
pcall,而不是把整个脚本都包进去,否则真正的问题会被吞掉。 assert(cond, msg)等价于if not cond then error(msg) end,配合pcall使用。
验证方法:
- 让
com.recv收到一段非 JSON 文本,json.decode抛错:pcall捕获,日志打印「本次处理失败,已跳过: ...」且脚本不挂。 - 收到正常 JSON,走通
plc.dw。
注意事项:
pcall只捕获 Lua 错误,不捕获固件底层的硬件超时(那种通常自己返回错误码,不是抛异常)—— 别以为包了pcall就万事大吉。pcall里return只从匿名函数返回,不会跳出外层;要提前结束整段逻辑用if not ok then return end。
例程 3:数据校验包装,统一返回 nil, err
适用:同上 | 依赖模块:base | 难度:★★☆
场景:写一个被多处调用的解析函数,约定「成功返回结果,失败返回 nil + 错误串」,调用方用 local v, e = parse(...) 判空即可,不用到处写 pcall。
完整代码(可直接下载运行):
lua
local function parse_temp(raw)
if type(raw) ~= "string" then
return nil, "输入必须是字符串"
end
local n = tonumber(raw)
if n == nil then
return nil, "无法解析为数字: " .. raw
end
if n < -50 or n > 150 then
return nil, "温度超出合理范围: " .. raw
end
return n
end
local v, e = parse_temp("23.5")
if v then
print("温度 =", v)
else
print("解析失败:", e)
end要点:
- 约定
nil, err返回风格与pcall的false, err一致,调用方处理简单。 type()先挡住非字符串,再tonumber,再范围校验,层层兜底。- 拼接错误信息用
..,注意数字要先tostring。
验证方法:
- 输入
"23.5"→ 打印「温度 = 23.5」。 - 输入
"abc"→ 打印「解析失败: 无法解析为数字: abc」。 - 输入
"200"→ 打印「解析失败: 温度超出合理范围: 200」。
注意事项:
assert(parse_temp("abc"))会在失败时抛错而非返回nil;若想用返回值风格就别用assert。- 错误串里拼接
nil/number会抛错,拼接前确保是字符串(tostring)。
例程 4:现场常用脚本 内存回收.LPLUA(原文)
适用:同上 | 依赖模块:base(collectgarbage) | 难度:★☆☆
场景:设备跑久了内存只涨不跌,先要能看见「涨了多少、能不能回来」。这份脚本用 1000 个空表 把水位顶上去,再置 nil + collect 看它能不能回落(文件 内存回收.LPLUA),原文收录如下。
完整代码(内存回收.LPLUA 原文):
lua
collectgarbage("collect");--为了有干净的环境,先把可以收集的其他垃圾赶走先
local c1 = collectgarbage("count")
print("最开始,Lua的内存为",c1,"kb")
mtable = {["x"]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",}--是本地变量
print("现在,声明1000个数组,并加到mtable 中")
for i=1,1000 do
table.insert(mtable,{}) --不断的把新创建的数组,放到一个全局的变量中
end
print("现在内存为:",collectgarbage("count"),"kb")
mtable = nil --不再使用表了 ,要释放掉
print("调用收集…")
collectgarbage("collect")--回收内存
print("收集后,当前的Lua内存为",collectgarbage("count"),"kb")要点:
collectgarbage("count")返回当前 Lua 占用内存,单位 KB,带小数(不是整数),别拿==比它。- 开头先
"collect"一次是为了拿到干净基线,否则第一次读数里混着上一轮的垃圾。 - ⚠ 注释写着「是本地变量」,但
mtable = {…}没有local,它其实是全局变量。这份脚本靠的正是「全局 → 置nil→ 能被回收」这条路径;真按local写,脚本一结束变量本来就没了,反而看不出效果。 - 全局表不清引用就永远占着内存 —— 这是设备上内存只涨不跌最常见的原因。
collectgarbage("collect")是完整一轮 GC,在设备上会有一次短暂停顿;用它做演示没问题,但不要放进高频定时器回调里当常规手段。mtable = nil后面紧接collectgarbage("collect")是这类脚本的常见写法:com/tcp_client/http这些对象句柄要等一次真正的 collect 才跑__gc(http.LPLUA里也是这么写的)。
验证方法:
- 下载运行,日志依次打印三个 KB 数:最开始 → 加了 1000 个表之后(明显上去)→ 置
nil并收集之后(应回到接近最开始的水位)。 - 把
mtable = nil那一行注掉再跑一次,最后一行仍会明显高于基线 —— 用这个对比直接证实「不清引用就回收不了」。
注意事项:
- 只删了表的一部分元素、外层表还挂在全局上,回收不掉。
- 只看
count的绝对值:不同机型、不同已加载模块下基线差很多,只有同一次运行里的前后差有意义。 - 把
collectgarbage("collect")当成「防内存泄漏的保命药」周期调用:它只能回收已经断掉引用的对象,引用没断时再调也没用。
函数速查表
下表列出本库全部函数与方法(含尚未单独成节的),点「说明」可跳转到对应小节。签名按通用记法:括号内按顺序列全参数,方括号里的可以省略(不传走默认值),... 表示还可继续传同类参数;h: 开头的是对象方法,先用库级函数拿回句柄再冒号调用。
| 方法与签名 | 作用 | 说明 |
|---|---|---|
base._VERSION | Lua 版本号字符串(是变量不是函数,不要加括号调用) | 跳转 |
base.assert(...) | 断言条件成立,否则报错中断脚本 | 本文开头 |
base.collectgarbage(…) | 手动触发内存垃圾回收 | 本文开头 |
base.dofile(…) | 读取并立即执行一个 Lua 文件 | 跳转 |
base.error(…) | 主动抛出一个错误 | 跳转 |
base.getmetatable(...) | 取得一个值的元表 | 跳转 |
base.ipairs(...) | 按 1、2、3… 顺序遍历表的整数下标(for 循环用) | 跳转 |
base.load(…) | 把字符串编译成函数(不执行) | 跳转 |
base.loadfile(…) | 读取文件并编译成函数(不执行) | 跳转 |
base.next(…) | 取表中的下一对键值(遍历的底层函数) | 跳转 |
base.pairs(...) | 遍历表的全部键值对(for 循环用) | 跳转 |
base.pcall(...) | 保护调用:被调函数出错时返回 false+错误信息,不中断脚本 | 跳转 |
base.print(...) | 打印内容到日志/控制台 | 本文开头 |
base.rawequal(...) | 比较两个值是否相等,不触发元方法 | 跳转 |
base.rawget(...) | 读表元素,不触发 __index 元方法 | 跳转 |
base.rawlen(…) | 取表或字符串的长度,不触发 __len 元方法 | 跳转 |
base.rawset(...) | 写表元素,不触发 __newindex 元方法 | 跳转 |
base.select(integer, ...) | 按序号取可变参数("#" 返回参数个数) | 跳转 |
base.setmetatable(…) | 设置一个值的元表 | 跳转 |
base.tonumber(...) | 把值转成数字(可指定进制),转不了返回 nil | 跳转 |
base.tostring(...) | 把任意值转成字符串 | 跳转 |
base.type(…) | 返回值的类型名(number/string/table/...) | 跳转 |
base.warn(string, ...) | 输出一条警告信息 | 跳转 |
base.xpcall(...) | 带错误处理函数的保护调用,出错时先回调再返回 | 跳转 |
base._VERSION
- 作用:Lua 版本号字符串(是变量不是函数,不要加括号调用)
- 形态:
base._VERSION不是可调用函数(不要加括号),按变量使用 - 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.dofile
- 作用:读取并立即执行一个 Lua 文件
- 调用形态:
base.dofile(…)(库级函数) - 参数:无参数
- 可用机型:软 PLC 模拟器 / 上位机
base.error
- 作用:主动抛出一个错误
- 调用形态:
base.error(…)(库级函数) - 参数:无参数
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.getmetatable
- 作用:取得一个值的元表
- 调用形态:
base.getmetatable(...)(库级函数) - 参数:1 个参数:第 1 个为任意(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.ipairs
- 作用:按 1、2、3… 顺序遍历表的整数下标(for 循环用)
- 调用形态:
base.ipairs(...)(库级函数) - 参数:1 个参数:第 1 个为任意(必填)
- 返回:3 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.load
- 作用:把字符串编译成函数(不执行)
- 调用形态:
base.load(…)(库级函数) - 参数:无参数
- 可用机型:软 PLC 模拟器 / 上位机
base.loadfile
- 作用:读取文件并编译成函数(不执行)
- 调用形态:
base.loadfile(…)(库级函数) - 参数:无参数
- 可用机型:软 PLC 模拟器 / 上位机
base.next
- 作用:取表中的下一对键值(遍历的底层函数)
- 调用形态:
base.next(…)(库级函数) - 参数:无参数
- 返回:2 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.pairs
- 作用:遍历表的全部键值对(for 循环用)
- 调用形态:
base.pairs(...)(库级函数) - 参数:1 个参数:第 1 个为任意(必填)
- 返回:4 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.pcall
- 作用:保护调用:被调函数出错时返回 false+错误信息,不中断脚本
- 调用形态:
base.pcall(...)(库级函数) - 参数:1 个参数:第 1 个为任意(必填),其后还有可变参数
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.rawequal
- 作用:比较两个值是否相等,不触发元方法
- 调用形态:
base.rawequal(...)(库级函数) - 参数:2 个参数:第 1 个为任意(必填)、第 2 个为任意(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.rawget
- 作用:读表元素,不触发 __index 元方法
- 调用形态:
base.rawget(...)(库级函数) - 参数:1 个参数:第 2 个为任意(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.rawlen
- 作用:取表或字符串的长度,不触发 __len 元方法
- 调用形态:
base.rawlen(…)(库级函数) - 参数:无参数
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.rawset
- 作用:写表元素,不触发 __newindex 元方法
- 调用形态:
base.rawset(...)(库级函数) - 参数:2 个参数:第 2 个为任意(必填)、第 3 个为任意(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.select
- 作用:按序号取可变参数("#" 返回参数个数)
- 调用形态:
base.select(integer, ...)(库级函数) - 参数:1 个参数:第 1 个为整数(必填),其后还有可变参数
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.setmetatable
- 作用:设置一个值的元表
- 调用形态:
base.setmetatable(…)(库级函数) - 参数:无参数
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.tonumber
- 作用:把值转成数字(可指定进制),转不了返回 nil
- 调用形态:
base.tonumber(...)(库级函数) - 参数:2 个参数:第 1 个为任意(必填)、第 2 个为整数(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.tostring
- 作用:把任意值转成字符串
- 调用形态:
base.tostring(...)(库级函数) - 参数:1 个参数:第 1 个为任意(必填)
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.type
- 作用:返回值的类型名(number/string/table/...)
- 调用形态:
base.type(…)(库级函数) - 参数:无参数
- 返回:1 个值
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.warn
- 作用:输出一条警告信息
- 调用形态:
base.warn(string, ...)(库级函数) - 参数:1 个参数:第 1 个为字符串(必填),其后还有可变参数
- 返回:无
- 可用机型:ESP32 机型 · 软 PLC 模拟器 / 上位机
base.xpcall
- 作用:带错误处理函数的保护调用,出错时先回调再返回
- 调用形态:
base.xpcall(...)(库级函数) - 参数:无参数
- 可用机型:软 PLC 模拟器 / 上位机