El Samovar admite la automatización en el lenguaje de scripts Lua. Los scripts controlan equipos externos (válvulas, bombas, un agitador, calentadores, relés), leen los valores de los sensores y modifican la lógica del proceso para adaptarla a una instalación concreta. La funcionalidad básica del controlador no cambia.
El editor de scripts está integrado en la interfaz web: Ajustes → Editor. Admite el resaltado de sintaxis para Lua, JS, CSS, HTML y JSON, la comprobación del equilibrio de paréntesis y de bloques function … end, y el autocompletado con la tecla Tab.
Intérprete: Lua 5.4.4. La API básica de la biblioteca Arduino integrada en el ESP32 se amplía con funciones para acceder a los sensores, al calentamiento, a las bombas, al agitador y a los relés.
Cómo activarlo
De forma predeterminada, la compatibilidad con Lua está desactivada en el firmware. Para activarla, abra el archivo Samovar_ini.h en el directorio del firmware y sustituya la línea
//#define USE_LUA
por
#define USE_LUA
Después, vuelva a compilar el firmware y cárguelo en el controlador.
Arquitectura
Al arrancar el controlador se ejecuta el archivo init.lua. Su función es inicializar las variables que se usarán en otros scripts y activar o desactivar el ciclo de ejecución una vez por segundo (setNumVariable("loop_lua_fl", 1)).
Si el ciclo está activado, cada segundo se ejecutan dos scripts, uno tras otro:
- El
script.luacomún, que se ejecuta en todos los modos. - El script del modo actual:
beer.lua(cerveza),bk.lua(columna de mosto),nbk.lua(columna de mosto continua),dist.lua(destilación),rectificat.lua(rectificación),cheese.lua(elaboración de queso) osuvid.lua(sous vide).
Si un script no ha terminado cuando llega el siguiente lanzamiento, ese lanzamiento se omite: un script que ya se está ejecutando no se interrumpe.
En http://samovar.local/lua se puede ejecutar todos los scripts una sola vez.
Además de los scripts principales, se admiten botones adicionales de modo: archivos con el formato btn_<mode>_buttonN.lua, por ejemplo btn_rect_button1.lua. Se muestran en la interfaz web y permiten llamar a su propio código al pulsarlos.
Importante. Al actualizar el firmware, la interfaz web descarga del servidor las versiones de referencia de init.lua, script.lua, los archivos de los modos y btn_*.lua, pero solo si ese archivo aún no existe en la memoria del controlador. Los scripts del usuario no se sobrescriben. Aun así, conserve sus propias versiones en el ordenador: tras un restablecimiento de fábrica o una reinstalación de la interfaz web se perderán.
Equipos externos
A los scripts se pueden conectar expansores de puertos I²C:
- PCF8575: un expansor con 16 puertos digitales (se define con la bandera
USE_EXPANDER). A cada puerto se puede conectar cualquier actuador o sensor de tipo «botón». Todos los puertos están disponibles tanto para lectura como para escritura. - PCF8591: un expansor de puertos analógicos (se define con la bandera
USE_ANALOG_EXPANDER). Se usa para sensores analógicos, por ejemplo un electrodo de pH en el modo de elaboración de queso.
Hay muchas formas de trabajar con los expansores: puede construir su propio sensor de seguridad que apague el Samovar desde un script cuando se active, o controlar una resistencia calefactora adicional según la temperatura del vapor. La lógica concreta depende de su equipo.
Funciones
GPIO y compatibles con Arduino
pinMode(pin, mode): modo de entrada/salida. Solo están disponibles los pines RELE_CHANNEL1, RELE_CHANNEL2, RELE_CHANNEL3, RELE_CHANNEL4 y LUA_PIN. Los valores numéricos de los pines dependen del mapa de pines y se indican en la descripción de los GPIO.
digitalWrite(pin, value): pone HIGH (1) o LOW (0) en un pin.
digitalRead(pin): lee el valor actual de un pin (HIGH o LOW).
analogRead(): lee el valor analógico del pin LUA_PIN en el intervalo 0–4095. A este pin se conecta un electrodo de pH o un sensor de presión MPX5010DP, por lo que no aplique más de 3,3 V: destruiría el ESP32.
delay(ms): pausa en milisegundos.
millis(): milisegundos transcurridos desde el arranque del controlador.
Expansores de puertos
exp_pinMode(pin, mode), exp_digitalWrite(pin, value), exp_digitalRead(pin): igual que las funciones de GPIO, pero para los puertos del PCF8575 (0–15).
exp_analogRead(), exp_analogWrite(value): operaciones analógicas en el PCF8591. Solo están disponibles cuando USE_ANALOG_EXPANDER está activado.
Control del proceso
setPower(power): enciende (1) o apaga (0) el calentamiento.
setCurrentPower(value): establece la tensión en el regulador de potencia. Si el regulador se controla por potencia, el argumento establece la potencia. Solo está disponible con SAMOVAR_USE_POWER.
setBodyTemp(temp): establece la temperatura del corazón durante la rectificación. En los demás modos la función solo imprime un mensaje en la consola y en Blynk.
setMixer(value): enciende (1) o apaga (0) el agitador.
openValve(value): abre (1) o cierra (0) la válvula de suministro de agua.
setAlarm(): modo de emergencia: el controlador apaga el calentamiento, cierra la válvula y desconecta la bomba de agua.
setNextProgram(): pasa a la siguiente línea del programa (equivale al botón de la interfaz).
setPauseWithdrawal(value): pone en pausa (1) o reanuda (0) la extracción.
setCapacity(num): cambia el recipiente de extracción actual. Intervalo válido: 0…CAPACITY_NUM.
setLuaStatus(value): establece el estado del modo Lua. Si está ocupado, lanza el error «Lua_status busy».
setPumpPwm(pwm): control PWM de la salida de la bomba de agua (0…1023). Disponible con USE_WATER_PUMP.
setServoAngle(angle): establece el ángulo del servo (0…SERVO_ANGLE). Disponible con SERVO_PIN.
setTimer(num, sec): programa el temporizador num (1…10) durante sec segundos. Hay 10 temporizadores en total.
getTimer(num): el tiempo que queda en el temporizador, en milisegundos. Si el temporizador no está programado o ya ha vencido, devuelve 0.
getState(): el estado numérico del controlador.
Variables del controlador
getNumVariable(name) y setNumVariable(name, value): lectura y escritura de variables numéricas. Solo se pueden escribir las variables marcadas expresamente como modificables (véase la tabla más abajo). Ejemplo: setNumVariable("TankTemp", 85) fijará la temperatura de la caldera en 85 °C. Sin entender la lógica del Samovar, modificar variables es arriesgado: se pueden romper los algoritmos.
getStrVariable(name) y setStrVariable(name, value): lo mismo para las variables de cadena.
Los nombres de las variables disponibles desde un script se indican en la tabla de la sección siguiente.
Objetos: almacenamiento persistente
Las variables normales de Lua solo existen mientras el script se ejecuta y se pierden en el siguiente lanzamiento. Para pasar un valor de un lanzamiento a otro, use setObject y getObject:
setObject("name", value) -- save
getObject("name") -- read (returns "" if absent)
getObject("name", "NUMERIC") -- read as a number (returns 0 if absent)
Como máximo 32 claves (LUA_OBJECT_STORE_MAX_KEYS). Los valores se conservan hasta que se reinicia el controlador.
Registro, depuración, HTTP
sendMsg(msg, level): muestra un mensaje. Con level = -1, en el puerto COM y en la consola del navegador (para depuración). Con level = 0, 1, 2, en la consola y en Blynk.
http_request(url [, method, headers, body]): una petición HTTP. Resulta útil para la integración con servicios externos: enviar notificaciones de Telegram, registrar datos, webhooks. El tiempo de espera es menor que en una descarga normal y la llamada se ejecuta bajo un mutex compartido, de modo que las llamadas simultáneas desde distintos scripts no entran en conflicto.
Dispositivos I²C
check_I2C_device(address): comprueba si hay un dispositivo en el bus I²C en la dirección indicada. Devuelve 1 si el dispositivo respondió y 0 en caso contrario.
get_i2c_rele_state(relay) y set_i2c_rele_state(relay, state): estado y conmutación de un relé I²C. Números válidos: 1…4.
Módulo de motor paso a paso
set_stepper_by_time(speed, direction, seconds): hace funcionar el motor paso a paso a la velocidad indicada (0…65535) y en el sentido indicado (0 o 1) durante el tiempo especificado. Devuelve 1 si se realizó el arranque.
set_stepper_target(speed, direction, target): ejecuta el número de pasos indicado. Devuelve 1 si se realizó el arranque.
get_stepper_status(): el estado actual del módulo de motor paso a paso.
set_mixer_pump_target(target) y get_mixer_pump_status(): control de la bomba del agitador (0 o 1).
Bomba I²C
Disponible cuando use_I2C_dev = 2:
i2cpump_start(rate, ml) -- start the pump at the given speed and volume
i2cpump_stop() -- stop
i2cpump_get_speed() -- current speed
i2cpump_get_target_ml() -- target volume
i2cpump_get_remaining_ml() -- remaining volume to pump
i2cpump_get_running() -- 1 if running, otherwise 0
Los números incorrectos (NaN, Inf, ≤ 0) se ignoran en silencio, sin error.
Watchdog de corrutinas
En la versión para ESP32, el límite de llamadas C anidadas de Lua se reduce de 200 a 60 (LUAI_MAXCCALLS = 60), porque con la pila de 8 KB de la tarea do_lua_script el límite original es inalcanzable. Si su script usa coroutine.create / wrap / resume, no olvide llamar a armCoroutineWatchdog() en init.lua: de lo contrario, una corrutina agotará la pila antes de que actúe el límite.
Variables
A continuación se enumeran las variables disponibles desde un script. RO significa solo lectura; RW, que se puede cambiar mediante setNumVariable.
Lecturas de sensores (RO)
SteamTemp: la temperatura del sensor de vapor.PipeTemp: la temperatura del sensor del tramo de columna.WaterTemp: la temperatura del sensor de agua.TankTemp: la temperatura del sensor de la caldera.ACPTemp: la temperatura del sensor del TCA (tubo de venteo atmosférico).pressure_value: la presión en el sistema.alcohol: la graduación calculada en tiempo real.alcohol_s: la graduación en estado estabilizado.
Estado y situaciones (RO)
PowerOn: 0/1, bandera de calentamiento encendido.PauseOn: 0/1, bandera de pausa de la extracción.pump_started: 0/1, la bomba de agua está funcionando.valve_status: 0/1, la válvula de agua.water_pump_speed: la velocidad actual de la bomba.program_Wait: 0/1, el programa está en pausa.SamSetup_Mode: el modo de funcionamiento del Samovar.Samovar_Mode: el modo de proceso actual.Samovar_CR_Mode: el modo actual según la clasificación interna.target_power_volt: la tensión objetivo del regulador.WFpulseCount,WFflowRate,WFtotalMilliLitres: lecturas del caudalímetro (si lo hay).
Los parámetros de la línea actual del programa (program_volume, program_speed, program_temp, program_power, program_time, program_capacity_num) y el recipiente de extracción actual (capacity_num) también son de solo lectura.
Parámetros que se pueden cambiar (RW)
acceleration_temp: la temperatura de la resistencia calefactora de aceleración.boil_temp: la temperatura de ebullición.loop_lua_fl: 0/1, desactiva o activa el lanzamiento del script una vez por segundo.SetScriptOff: 0/1, apagado forzado de los scripts.show_lua_script: 0/1, muestra el cuerpo del script y los valores de las variables en el puerto COM y en la consola (para depuración).test_num_val: una variable numérica para depuración.wp_count: el número de pulsos del caudalímetro (conUSE_WATER_PUMP).
Variables de cadena
SamovarStatus(RW): el estado actual que se muestra en la interfaz.program_type(RO): el tipo de programa en ejecución.test_str_val(RW): una variable de cadena para depuración.Msg(especial): la lectura avanza el cursor por el anillo de eventos; la escritura se hace consetStrVariable.
Sensores virtuales (SAMOVAR_LUA_SIMULATION)
Cuando la simulación está activada, están disponibles VirtualSteamTemp, VirtualPipeTemp, VirtualWaterTemp, VirtualTankTemp, VirtualACPTemp (todas RW). Se usan para depurar scripts sin hardware.
Reloj (RO)
YY, MM, DD, HH, MI, SS: el año, mes, día, hora, minuto y segundo actuales según el RTC del controlador.
Ejemplos
setObject y getObject
n = 156
setObject("MyObject", n)
print(n) -- 156
n = n + 5
print(n) -- 161
n = getObject("MyObject")
print(n) -- 156 (read back the saved value)
Envío de la temperatura de la caldera a 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)
Sensor analógico de nivel y bomba de agua
El ejemplo lee un sensor analógico de nivel y enciende la bomba cuando el nivel entra en un intervalo dado. El estado de la bomba se conserva entre lanzamientos mediante 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
Seguridad y limitaciones
- 10 temporizadores a la vez.
- 32 claves en el almacén de objetos (
LUA_OBJECT_STORE_MAX_KEYS). - El límite de llamadas C anidadas de Lua: 60 (
LUAI_MAXCCALLS). Si usacoroutine.*, no olvide llamar aarmCoroutineWatchdog(). - Pines para
pinMode: soloRELE_CHANNEL1…RELE_CHANNEL4yLUA_PIN. El pin ocupado por el electrodo de pH del modo queso no se puede usar: la función devolverá un error. - Aplique como máximo 3,3 V a
analogRead(). setBodyTempfunciona solo en el modo de rectificación; en los demás modos muestra una advertencia.- Al llamar a
setAlarm(), el controlador detiene el calentamiento, cierra la válvula y apaga la bomba. - Los argumentos numéricos de
i2cpump_startdeben ser números positivos finitos;NaN/Inf/≤0 se ignoran en silencio.
Recursos adicionales
- El manual de referencia oficial de Lua 5.4
- «Programming in Lua» (PDF, R. Ierusalimschy et al.)
- Blockly: un generador visual de scripts Lua, si prefiere montar la lógica con bloques.
