Skip to content
Home » Articles » Lua script support

Lua script support

The Samovar supports automation in the scripting language Lua. Scripts control external equipment (valves, pumps, a stirrer, heaters, relays), read sensor values and change the process logic to suit a particular setup. The controller’s basic functionality does not change.

The script editor is built into the web interface: Settings → Editor. It supports syntax highlighting for Lua, JS, CSS, HTML and JSON, checking of bracket and function … end block balance, and autocompletion with the Tab key.

Interpreter: Lua 5.4.4. The base API from the Arduino library built into the ESP32 is extended with functions for access to sensors, heating, pumps, the stirrer and relays.

How to enable it

By default, Lua support is disabled in the firmware. To enable it, open the Samovar_ini.h file in the firmware directory and replace the line

//#define USE_LUA

with

#define USE_LUA

After that, rebuild the firmware and upload it to the controller.

Architecture

When the controller starts, the init.lua file is executed. Its job is to initialize the variables that will be used in other scripts and to turn the once-per-second execution loop on or off (setNumVariable("loop_lua_fl", 1)).

If the loop is on, two scripts are executed one after another every second:

  1. The common script.lua — runs in all modes.
  2. The script of the current mode: beer.lua (beer), bk.lua (wash column), nbk.lua (continuous wash column), dist.lua (distillation), rectificat.lua (rectification), cheese.lua (cheesemaking) or suvid.lua (sous-vide).

If a script has not finished by the next launch, the current launch is skipped: a script that is already running is not interrupted.

A one-time run of all scripts is available at http://samovar.local/lua.

Besides the main scripts, additional mode buttons are supported — files of the form btn_<mode>_buttonN.lua, for example btn_rect_button1.lua. They are shown in the web interface and let you call your own code when pressed.

Important. When the firmware is updated, the web interface downloads the reference versions of init.lua, script.lua, the mode files and btn_*.lua from the server, but only if such a file does not yet exist in the controller’s memory. User scripts are not overwritten. Even so, keep your own versions on your computer — after a factory reset or a reinstall of the web interface they will be lost.

External equipment

I²C port expanders can be connected to scripts:

  • PCF8575 — an expander with 16 digital ports (defined by the USE_EXPANDER flag). Any actuator or “button”-type sensor can be connected to each port. All ports are available for both reading and writing.
  • PCF8591 — an analog port expander (defined by the USE_ANALOG_EXPANDER flag). Used for analog sensors — for example, a pH electrode in cheesemaking mode.

There are many ways to work with expanders: you can build your own safety sensor that shuts the Samovar down from a script when it triggers, or control an additional heating element based on the steam temperature. The specific logic depends on your equipment.

Functions

GPIO and Arduino-compatible

pinMode(pin, mode) — the input/output mode. Only the pins RELE_CHANNEL1, RELE_CHANNEL2, RELE_CHANNEL3, RELE_CHANNEL4 and LUA_PIN are available. The numeric pin values depend on the pin map and are given in the GPIO description.

digitalWrite(pin, value) — output HIGH (1) or LOW (0) on a pin.

digitalRead(pin) — read the current value of a pin (HIGH or LOW).

analogRead() — read the analog value of the LUA_PIN pin in the range 0–4095. A pH electrode or an MPX5010DP pressure sensor is attached to this pin, so do not apply more than 3.3 V to it: that will destroy the ESP32.

delay(ms) — a pause in milliseconds.

millis() — milliseconds since the controller started.

Port expanders

exp_pinMode(pin, mode), exp_digitalWrite(pin, value), exp_digitalRead(pin) — the same as the GPIO functions, but for the PCF8575 ports (0–15).

exp_analogRead(), exp_analogWrite(value) — analog operations on the PCF8591. Available only when USE_ANALOG_EXPANDER is enabled.

Process control

setPower(power) — turn heating on (1) or off (0).

setCurrentPower(value) — set the voltage on the power regulator. If the regulator is controlled by power, the argument sets the power. Available only with SAMOVAR_USE_POWER.

setBodyTemp(temp) — set the hearts temperature during rectification. In other modes the function only prints a message to the console and to Blynk.

setMixer(value) — turn the stirrer on (1) or off (0).

openValve(value) — open (1) or close (0) the water supply valve.

setAlarm() — emergency mode: the controller turns off heating, closes the valve and switches off the water pump.

setNextProgram() — go to the next program line (equivalent to the button in the interface).

setPauseWithdrawal(value) — pause (1) or resume (0) the take-off.

setCapacity(num) — switch the current take-off container. Valid range: 0…CAPACITY_NUM.

setLuaStatus(value) — set the Lua mode status. If busy, it throws the error “Lua_status busy”.

setPumpPwm(pwm) — PWM control of the water pump’s output (0…1023). Available with USE_WATER_PUMP.

setServoAngle(angle) — set the servo angle (0…SERVO_ANGLE). Available with SERVO_PIN.

setTimer(num, sec) — set timer num (1…10) for sec seconds. There are 10 timers in total.

getTimer(num) — the time remaining on the timer in milliseconds. If the timer is not set or has already expired, 0 is returned.

getState() — the numeric status of the controller.

Controller variables

getNumVariable(name) and setNumVariable(name, value) — reading and writing numeric variables. Writing is possible only for variables that are explicitly marked as writable (see the table below). Example: setNumVariable("TankTemp", 85) will set the boiler temperature to 85 °C. Without understanding the Samovar’s logic, changing variables is risky — it can break the algorithms.

getStrVariable(name) and setStrVariable(name, value) — the same for string variables.

The names of the variables available from a script are given in the table in the next section.

Objects — persistent storage

Ordinary Lua variables live only while the script is running and are lost at the next launch. To pass a value between launches, use setObject and getObject:

setObject("name", value)     -- save
getObject("name")            -- read (returns "" if absent)
getObject("name", "NUMERIC") -- read as a number (returns 0 if absent)

At most 32 keys (LUA_OBJECT_STORE_MAX_KEYS). Values are kept until the controller is rebooted.

Log, debugging, HTTP

sendMsg(msg, level) — output a message. With level = -1 — to the COM port and the browser console (for debugging). With level = 0, 1, 2 — to the console and to Blynk.

http_request(url [, method, headers, body]) — an HTTP request. Handy for integration with external services: sending Telegram notifications, logging, webhooks. The timeout is shorter than for a normal download, and the call runs under a shared mutex, so simultaneous calls from different scripts do not conflict.

I²C devices

check_I2C_device(address) — check whether a device is present on the I²C bus at the given address. Returns 1 if the device responded, otherwise 0.

get_i2c_rele_state(relay) and set_i2c_rele_state(relay, state) — the state and switching of an I²C relay. Valid numbers: 1…4.

Stepper module

set_stepper_by_time(speed, direction, seconds) — run the stepper motor at the given speed (0…65535) and direction (0 or 1) for the specified time. Returns 1 if the start was performed.

set_stepper_target(speed, direction, target) — execute the given number of steps. Returns 1 if the start was performed.

get_stepper_status() — the current state of the stepper module.

set_mixer_pump_target(target) and get_mixer_pump_status() — control of the stirrer pump (0 or 1).

I²C pump

Available when 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

Bad numbers (NaN, Inf, ≤ 0) are silently ignored, without an error.

Coroutine watchdog

In the ESP32 version the limit on nested Lua C calls is lowered from 200 to 60 (LUAI_MAXCCALLS = 60), because on the 8 KB stack of the do_lua_script task the original limit is unreachable. If your script uses coroutine.create / wrap / resume, be sure to call armCoroutineWatchdog() in init.lua: otherwise a coroutine will eat up the stack before the limit kicks in.

Variables

The table below lists the variables available from a script. RO means read-only, RW means it can be changed via setNumVariable.

Sensor readings (RO)

  • SteamTemp — the steam sensor temperature.
  • PipeTemp — the column section sensor temperature.
  • WaterTemp — the water sensor temperature.
  • TankTemp — the boiler sensor temperature.
  • ACPTemp — the TCA (atmospheric vent tube) sensor temperature.
  • pressure_value — the pressure in the system.
  • alcohol — the calculated strength in real time.
  • alcohol_s — the strength in the stabilized state.

State and statuses (RO)

  • PowerOn — 0/1, the heating-on flag.
  • PauseOn — 0/1, the take-off pause flag.
  • pump_started — 0/1, the water pump is running.
  • valve_status — 0/1, the water valve.
  • water_pump_speed — the current pump speed.
  • program_Wait — 0/1, the program is paused.
  • SamSetup_Mode — the Samovar’s operating mode.
  • Samovar_Mode — the current process mode.
  • Samovar_CR_Mode — the current mode by the internal classification.
  • target_power_volt — the target regulator voltage.
  • WFpulseCount, WFflowRate, WFtotalMilliLitres — flow meter readings (if present).

The parameters of the current program line (program_volume, program_speed, program_temp, program_power, program_time, program_capacity_num) and the current take-off container (capacity_num) are also read-only.

Parameters that can be changed (RW)

  • acceleration_temp — the temperature of the acceleration heating element.
  • boil_temp — the boiling temperature.
  • loop_lua_fl — 0/1, turns the once-per-second script launch off or on.
  • SetScriptOff — 0/1, forced shutdown of scripts.
  • show_lua_script — 0/1, print the script body and variable values to the COM port and the console (for debugging).
  • test_num_val — a numeric variable for debugging.
  • wp_count — the number of flow meter pulses (with USE_WATER_PUMP).

String variables

  • SamovarStatus (RW) — the current status shown in the interface.
  • program_type (RO) — the type of the program being run.
  • test_str_val (RW) — a string variable for debugging.
  • Msg (special) — reading advances the cursor along the event ring; writing is via setStrVariable.

Virtual sensors (SAMOVAR_LUA_SIMULATION)

When simulation is enabled, VirtualSteamTemp, VirtualPipeTemp, VirtualWaterTemp, VirtualTankTemp, VirtualACPTemp are available (all RW). They are used to debug scripts without hardware.

Clock (RO)

YY, MM, DD, HH, MI, SS — the current year, month, day, hour, minute and second according to the controller’s RTC.

Examples

setObject and 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)

Sending the boiler temperature to 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)

Analog level sensor and water pump

The example reads an analog level sensor and turns on the pump when the level falls within a given range. The pump state is kept between launches via 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

Safety and limitations

  • 10 timers at a time.
  • 32 keys in the object store (LUA_OBJECT_STORE_MAX_KEYS).
  • The limit on nested Lua C calls: 60 (LUAI_MAXCCALLS). If you use coroutine.*, be sure to call armCoroutineWatchdog().
  • Pins for pinMode: only RELE_CHANNEL1…RELE_CHANNEL4 and LUA_PIN. The pin occupied by the cheese pH electrode cannot be used — the function will return an error.
  • Apply no more than 3.3 V to analogRead().
  • setBodyTemp works only in rectification mode; in other modes it prints a warning.
  • When setAlarm() is called, the controller stops heating, closes the valve and switches off the pump.
  • The numeric arguments of i2cpump_start must be finite positive numbers; NaN/Inf/≤0 are silently ignored.

Additional resources

Leave a Reply