Samovar 支持使用脚本语言 Lua 实现自动化。脚本可以控制外部设备(阀门、水泵、搅拌器、加热器、继电器),读取传感器数值,并根据具体的装置调整流程逻辑。控制器的基本功能不会改变。
脚本编辑器内置于网页界面中:Settings → Editor(设置 → 编辑器)。它支持 Lua、JS、CSS、HTML 和 JSON 的语法高亮,检查括号以及 function … end 代码块是否配对,并可用 Tab 键自动补全。
解释器:Lua 5.4.4。ESP32 内置 Arduino 库的基础 API 经过扩展,增加了访问传感器、加热、水泵、搅拌器和继电器的函数。
如何启用
默认情况下,固件中的 Lua 支持是关闭的。要启用它,请打开固件目录中的 Samovar_ini.h 文件,将这一行
//#define USE_LUA
替换为
#define USE_LUA
之后重新编译固件并上传到控制器。
架构
控制器启动时会执行 init.lua 文件。它的作用是初始化其他脚本要用到的变量,并开启或关闭每秒一次的循环执行(setNumVariable("loop_lua_fl", 1))。
如果循环已开启,每秒会依次执行两个脚本:
- 通用脚本
script.lua——在所有模式下运行。 - 当前模式的脚本:
beer.lua(啤酒)、bk.lua(发酵醪精馏塔)、nbk.lua(连续式发酵醪精馏塔)、dist.lua(蒸馏)、rectificat.lua(精馏)、cheese.lua(奶酪制作)或suvid.lua(低温慢煮 (sous-vide))。
如果脚本在下一次启动时还没有执行完,本次启动会被跳过:已经在运行的脚本不会被中断。
在 http://samovar.local/lua 可以一次性运行所有脚本。
除了主脚本之外,还支持额外的模式按钮——文件名形如 btn_<mode>_buttonN.lua,例如 btn_rect_button1.lua。它们会显示在网页界面中,按下时可以调用您自己的代码。
重要提示。更新固件时,网页界面会从服务器下载 init.lua、script.lua、各模式文件和 btn_*.lua 的参考版本,但仅限控制器存储器中尚不存在该文件的情况。用户脚本不会被覆盖。即便如此,也请在电脑上保存好自己的版本——恢复出厂设置或重新安装网页界面后,它们会丢失。
外部设备
脚本可以连接 I²C 端口扩展器:
- PCF8575——16 路数字端口的扩展器(由
USE_EXPANDER标志定义)。每个端口都可以连接任意执行器或“按钮”类传感器。所有端口都可读可写。 - PCF8591——模拟端口扩展器(由
USE_ANALOG_EXPANDER标志定义)。用于模拟传感器,例如奶酪制作模式中的 pH 电极。
使用扩展器的方式有很多:可以自制安全传感器,触发时由脚本关闭 Samovar;也可以根据蒸汽温度控制额外的电加热管。具体逻辑取决于您的设备。
函数
GPIO 与 Arduino 兼容函数
pinMode(pin, mode)——输入/输出模式。只有引脚 RELE_CHANNEL1、RELE_CHANNEL2、RELE_CHANNEL3、RELE_CHANNEL4 和 LUA_PIN 可用。引脚的具体数值取决于引脚图,详见 GPIO 说明。
digitalWrite(pin, value)——在引脚上输出 HIGH(1)或 LOW(0)。
digitalRead(pin)——读取引脚的当前值(HIGH 或 LOW)。
analogRead()——读取 LUA_PIN 引脚的模拟值,范围 0–4095。该引脚上接有 pH 电极或 MPX5010DP 压力传感器,因此不要向它施加超过 3.3 V 的电压:否则会烧毁 ESP32。
delay(ms)——暂停,单位为毫秒。
millis()——自控制器启动以来经过的毫秒数。
端口扩展器
exp_pinMode(pin, mode)、exp_digitalWrite(pin, value)、exp_digitalRead(pin)——与 GPIO 函数相同,但作用于 PCF8575 的端口(0–15)。
exp_analogRead()、exp_analogWrite(value)——PCF8591 的模拟操作。仅在启用 USE_ANALOG_EXPANDER 时可用。
过程控制
setPower(power)——开启(1)或关闭(0)加热。
setCurrentPower(value)——设置功率调节器上的电压。如果调节器按功率控制,则该参数设置的是功率。仅在启用 SAMOVAR_USE_POWER 时可用。
setBodyTemp(temp)——设置精馏时的酒心温度。在其他模式下,该函数只向控制台和 Blynk 输出一条消息。
setMixer(value)——开启(1)或关闭(0)搅拌器。
openValve(value)——打开(1)或关闭(0)供水阀。
setAlarm()——紧急模式:控制器关闭加热,关闭阀门并停止水泵。
setNextProgram()——转到程序的下一行(相当于界面中的按钮)。
setPauseWithdrawal(value)——暂停(1)或恢复(0)取酒。
setCapacity(num)——切换当前的取酒容器。有效范围:0…CAPACITY_NUM。
setLuaStatus(value)——设置 Lua 模式的状态。如果处于忙碌状态,会抛出错误“Lua_status busy”。
setPumpPwm(pwm)——对水泵输出进行 PWM 控制(0…1023)。在启用 USE_WATER_PUMP 时可用。
setServoAngle(angle)——设置舵机角度(0…SERVO_ANGLE)。在定义了 SERVO_PIN 时可用。
setTimer(num, sec)——将计时器 num(1…10)设置为 sec 秒。共有 10 个计时器。
getTimer(num)——计时器的剩余时间,单位为毫秒。如果计时器未设置或已经到期,则返回 0。
getState()——控制器的数字状态。
控制器变量
getNumVariable(name) 和 setNumVariable(name, value)——读取和写入数值变量。只有明确标记为可写的变量才能写入(见下表)。示例:setNumVariable("TankTemp", 85) 会把蒸馏釜温度设为 85 °C。如果不了解 Samovar 的逻辑,修改变量有风险——可能会破坏算法。
getStrVariable(name) 和 setStrVariable(name, value)——字符串变量的同类函数。
脚本中可用的变量名称见下一节的表格。
对象——持久存储
普通的 Lua 变量只在脚本运行期间存在,下次启动时就会丢失。要在多次启动之间传递数值,请使用 setObject 和 getObject:
setObject("name", value) -- 保存
getObject("name") -- 读取(不存在则返回 "")
getObject("name", "NUMERIC") -- 按数字读取(不存在则返回 0)
最多 32 个键(LUA_OBJECT_STORE_MAX_KEYS)。数值会保留到控制器重启为止。
日志、调试、HTTP
sendMsg(msg, level)——输出一条消息。level = -1 时输出到 COM 端口和浏览器控制台(用于调试)。level = 0, 1, 2 时输出到控制台和 Blynk。
http_request(url [, method, headers, body])——HTTP 请求。便于与外部服务集成:发送 Telegram 通知、记录日志、调用 webhook。超时时间比普通下载短,并且调用在共享互斥锁下执行,因此不同脚本同时调用也不会冲突。
I²C 设备
check_I2C_device(address)——检查 I²C 总线上指定地址处是否有设备。设备有响应时返回 1,否则返回 0。
get_i2c_rele_state(relay) 和 set_i2c_rele_state(relay, state)——I²C 继电器的状态读取和切换。有效编号:1…4。
步进电机模块
set_stepper_by_time(speed, direction, seconds)——让步进电机以给定的速度(0…65535)和方向(0 或 1)运转指定的时间。如果成功启动则返回 1。
set_stepper_target(speed, direction, target)——执行给定数量的步数。如果成功启动则返回 1。
get_stepper_status()——步进电机模块的当前状态。
set_mixer_pump_target(target) 和 get_mixer_pump_status()——搅拌泵控制(0 或 1)。
I²C 水泵
当 use_I2C_dev = 2 时可用:
i2cpump_start(rate, ml) -- 以给定的速度和体积启动水泵
i2cpump_stop() -- 停止
i2cpump_get_speed() -- 当前速度
i2cpump_get_target_ml() -- 目标体积
i2cpump_get_remaining_ml() -- 剩余待泵送体积
i2cpump_get_running() -- 运行中为 1,否则为 0
无效的数值(NaN、Inf、≤ 0)会被静默忽略,不会报错。
协程看门狗
在 ESP32 版本中,Lua C 调用的嵌套上限从 200 降到了 60(LUAI_MAXCCALLS = 60),因为在 do_lua_script 任务的 8 KB 栈上,原来的上限根本无法达到。如果您的脚本使用 coroutine.create / wrap / resume,务必在 init.lua 中调用 armCoroutineWatchdog():否则协程会在触及上限之前耗尽栈空间。
变量
下表列出了脚本中可用的变量。RO 表示只读,RW 表示可以通过 setNumVariable 修改。
传感器读数(RO)
SteamTemp——蒸汽传感器温度。PipeTemp——塔节传感器温度。WaterTemp——水传感器温度。TankTemp——蒸馏釜传感器温度。ACPTemp——TCA(通大气管)传感器温度。pressure_value——系统中的压力。alcohol——实时计算出的酒精度。alcohol_s——稳定状态下的酒精度。
状态(RO)
PowerOn——0/1,加热开启标志。PauseOn——0/1,取酒暂停标志。pump_started——0/1,水泵正在运行。valve_status——0/1,水阀状态。water_pump_speed——水泵当前速度。program_Wait——0/1,程序已暂停。SamSetup_Mode——Samovar 的运行模式。Samovar_Mode——当前的过程模式。Samovar_CR_Mode——按内部分类的当前模式。target_power_volt——调节器的目标电压。WFpulseCount、WFflowRate、WFtotalMilliLitres——流量计读数(如果有)。
当前程序行的参数(program_volume、program_speed、program_temp、program_power、program_time、program_capacity_num)以及当前取酒容器(capacity_num)也是只读的。
可以修改的参数(RW)
acceleration_temp——加速加热管的温度。boil_temp——沸腾温度。loop_lua_fl——0/1,关闭或开启每秒一次的脚本启动。SetScriptOff——0/1,强制关闭脚本。show_lua_script——0/1,将脚本内容和变量值输出到 COM 端口和控制台(用于调试)。test_num_val——用于调试的数值变量。wp_count——流量计脉冲数(在启用USE_WATER_PUMP时)。
字符串变量
SamovarStatus(RW)——界面中显示的当前状态。program_type(RO)——正在运行的程序类型。test_str_val(RW)——用于调试的字符串变量。Msg(特殊)——读取时会沿事件环移动游标;写入通过setStrVariable进行。
虚拟传感器(SAMOVAR_LUA_SIMULATION)
启用模拟后,可使用 VirtualSteamTemp、VirtualPipeTemp、VirtualWaterTemp、VirtualTankTemp、VirtualACPTemp(均为 RW)。它们用于在没有硬件的情况下调试脚本。
时钟(RO)
YY、MM、DD、HH、MI、SS——根据控制器 RTC 得到的当前年、月、日、时、分和秒。
示例
setObject 和 getObject
n = 156
setObject("MyObject", n)
print(n) -- 156
n = n + 5
print(n) -- 161
n = getObject("MyObject")
print(n) -- 156(读回了保存的值)
将蒸馏釜温度发送到 Telegram
local function urlencode(url)
if url == nil then return end
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w ])", function(c)
return string.format("%%%02X", string.byte(c))
end)
url = url:gsub(" ", "+")
return url
end
function SendTelegram(text)
local token = "5177...:AAG0b...."
local chat_id = "38806....."
http_request("http://api.telegram.org/bot" .. token
.. "/sendMessage?chat_id=" .. chat_id
.. "&text=" .. urlencode(text))
end
local text = "Current boiler temperature = "
.. getNumVariable("TankTemp")
SendTelegram(text)
模拟液位传感器与水泵
该示例读取模拟液位传感器,当液位落在给定范围内时开启水泵。水泵状态通过 setObject 在多次启动之间保存。
start_pump = getObject("start_pump", "NUMERIC") + 0
sensor = analogRead()
if sensor >= 1000 and sensor <= 2000 and start_pump == 0 then
setObject("start_pump", 1)
digitalWrite(4, 1)
print("Start pump")
elseif sensor == 0 then
setObject("start_pump", 0)
if start_pump == 1 then
digitalWrite(4, 0)
print("Finish pump")
end
end
安全与限制
- 同时最多 10 个计时器。
- 对象存储中最多 32 个键(
LUA_OBJECT_STORE_MAX_KEYS)。 - Lua C 调用的嵌套上限:60(
LUAI_MAXCCALLS)。如果使用coroutine.*,务必调用armCoroutineWatchdog()。 pinMode可用的引脚:仅RELE_CHANNEL1…RELE_CHANNEL4和LUA_PIN。奶酪 pH 电极占用的引脚不能使用——函数会返回错误。- 向
analogRead()施加的电压不得超过 3.3 V。 setBodyTemp仅在精馏模式下有效;在其他模式下会输出警告。- 调用
setAlarm()时,控制器会停止加热、关闭阀门并关闭水泵。 i2cpump_start的数值参数必须是有限的正数;NaN/Inf/≤0 会被静默忽略。
其他资源
- Lua 5.4 官方参考手册
- 《Lua 程序设计》(PDF,R. Ierusalimschy 等)
- Blockly——可视化 Lua 脚本生成器,适合喜欢用积木块搭建逻辑的人。
