Download PDF
        SAMOVAR 
×
Menu

About the Lua scripting language.

 
Samovar supports automation based on the scripting programming language Lua. Lua support is enabled when building the firmware: in the configurator — the "Use Lua" («Использовать Lua») checkbox ("Hardware" («Оборудование») section), in PlatformIO — the Samovar_luaenvironment, in a manual build — the line #define USE_LUA in the user_config_override.h file. The script editor is opened with the "Editor" («Редактор») button on the Settings page (address http://samovar.local/edit). No login or password is requested.
The current Lua version is 5.4.4
 
When Samovar starts, the init.lua file runs. Its purpose is to enable/disable the one-second script execution loop and to initialize variables that will be used in other scripts. As shipped, init.lua disables the loop: setNumVariable("loop_lua_fl",0).
If running scripts in a loop is enabled, two scripts are run every second: first script.lua, then the script whose name depends on the operating mode: rectificat.lua (Rectification), dist.lua (Distillation), beer.lua (Beer), bk.lua (Wash column), nbk.lua (NBK), suvid.lua (Sous-vide), cheese.lua (Cheesemaking). In "Lua mode" («Lua-режим») (an item in the mode list in Settings, visible only in a build with Lua) there is no mode script — all the mode logic is written in script.lua. A pair of scripts can also be run once by going to http://samovar.local/lua, and a separate file at http://samovar.local/lua?script=file_name.
The scripts run one after another in a single task: while one is running, the other waits. A single script run cannot last longer than 20 seconds — after that it is aborted with the message "Lua: chunk execution aborted by timeout" («Lua: выполнение чанка прервано по таймауту»). If a mode script ends with an error 5 times in a row, the one-second loop stops (loop_lua_fl is reset to 0) and one summary message is printed to the console; script.lua stops being run after 5 errors in a row until the scripts are reloaded, without stopping the loop. Script errors are written to the browser console and to the port monitor.
 
You can also add buttons to the interface to run files. The file name: btn_<mode>_button<N>.lua, where mode is rect, dist, beer, bk, nbk, suvid or cheese, and N is the button number. For example, btn_rect_button1.lua, btn_beer_button2.lua. The buttons are shown on the "Additional" («Дополнительно») tab of the main page of the mode they were created for, in the Lua block; there is also the "Lua:" («Lua:») field with the "Run Lua" («Выполнить Lua») button for running one or two lines of code, and "Lua status" («Статус Lua») — a line that the script sets with the setLuaStatus function.
The button name is set in the first line of the file by a comment of the form --|Name^ (for example, --|Start^). If there is no such line, the buttons are named LUA1, LUA2, and so on, in the order of the files found.
As shipped, each mode has two buttons: "Start" («Начать») (btn_*_button1.lua — enables the one-second loop) and "Stop" («Остановить») (btn_*_button2.lua — disables it).
The main purpose of the buttons is to start running scripts in a loop (for example, by setting a variable value that can then be read in the script running in the loop), or to switch a control device (for example, a pump) on or off.
 
So the architecture of using the buttons looks as follows (as an example - for distillation mode):
  1. The main controlled loop: once a second script.lua => dist.lua => script.lua => dist.lua =>... (controlled in the sense that it can be started/stopped programmatically from any script through a global variable)
  2. The script.lua script is common to all modes; you can put here code that must run in any mode (rectification, beer, sous-vide, etc.)
  3. The dist.lua script will be used only in distillation mode; you can put in it all the logic that must run once a second depending on parameters this script gets from the Samovar global variables, for example getNumVariable("TankTemp"), or from variables saved by any script in the common namespace, for example getObject("StartTankFilling"). That is, in the code of this script you can put, for example, the condition:
    if (StartTankFilling == "true" and TankFillingPercent < 80) then StartPump() else StopPump() end
    and every second the script will check whether there is a flag that the boiler needs to be filled, and if there is, whether it is already 80% full, and whether the previously started pump should now be stopped.
  4. The button script btn_dist_button1.lua, which, when the button is pressed, sets the flag that the boiler needs to be filled via setObject("StartTankFilling", "true"). On the next cycle a second later, the dist.lua script will read this flag and switch the pump on (if the boiler is not yet filled). Thus all scripts share a common namespace, available through setObject/getObject, which lets them exchange data.
  5. The button script btn_dist_button2.lua, which, when the button is pressed, can stop the main controlled loop via setNumVariable("loop_lua_fl", 0), for example if something goes wrong with the scripts.
 
A program line of type L (a Lua stage). In the programs of all modes except NBK (Rectification, Distillation, Wash column, Beer, Cheesemaking) there is a line of type L — a stage controlled by Lua. When this type is selected in the program editor, the "Lua stage" («Lua-этап») window opens, in which you set:
When the program reaches an L line, Samovar runs the selected file and waits for a setNextProgram() call — this is the move to the next line. A script error ("Lua finished with an error" («Lua завершилась с ошибкой»)) or a timeout stops the mode. The other fields of the line (temperature, rate, container, device) are not set for type L. In text form such a line is stored as the file and parameters separated by the ^ character, for example L;120;hops.lua^3^45;0;0;0 for rectification.
An example of the dose.lua script, which switches on the relay whose number is in the first parameter for the time in the second (in the program: file dose.lua, parameters 3 and 1500):
set_i2c_rele_state(tonumber(arg[1]), 1)
delay(tonumber(arg[2]))
set_i2c_rele_state(tonumber(arg[1]), 0)
setNextProgram()
If during operation you upload through the file editor a new version of the file that is currently being run by an L line, Samovar will re-read the script without restarting the program.
 
Samovar functions
From scripts you can access Samovar's internal variables (some of them cannot be changed, they are read-only) and call Samovar functions. While a mode switch is in progress, functions that change the Samovar state end with the error "mode switch blocks state changes".
Pin numbers depend on the board. Here and below, pass their numeric value:
The constants INPUT, OUTPUT, LOW, HIGH and the command result codes ACTUATOR_COMMAND_ACCEPTED, ACTUATOR_COMMAND_PENDING, ACTUATOR_COMMAND_APPLIED, ACTUATOR_COMMAND_FAILED are defined in scripts.
 
pinMode(pin, mode) – like the Arduino function of the same name, sets the operating mode of the given input/output (pin) as an input or an output. Changing the mode is available only for the ports RELE_CHANNEL1–RELE_CHANNEL4 and LUA_PIN. That is, if you need to set the RELE_CHANNEL4 port of the DEVKIT board as an input, call pinMode(13, INPUT); as an output - pinMode(13, OUTPUT).
digitalWrite(pin, Value) – like the Arduino function of the same name, applies a HIGH or LOW value to a digital output (pin). The ports RELE_CHANNEL1–RELE_CHANNEL4, WATER_PUMP_PIN, LUA_PIN, ALARM_BTN_PIN and BTN_PIN are available, the others are ignored. That is, if you need to set a high value on the RELE_CHANNEL4 output, call digitalWrite(13, 1). For WATER_PUMP_PIN with a PWM pump, the value 1 switches the pump on at full speed, 0 stops it. If the emergency heating protection has triggered, writing to the heater channels (RELE_CHANNEL1, RELE_CHANNEL4) is not performed.
digitalRead(pin) – like the Arduino function of the same name, reads the value from the given input - HIGH or LOW. That is, if you need to read the value set on the RELE_CHANNEL4 input or output, call digitalRead(13)
analogRead() – like the Arduino function of the same name, reads the value from the analog input LUA_PIN (no parameters). The voltage applied to the analog input will be converted to a value from 0 to 4095. ATTENTION! Applying a voltage higher than 3.3 volts to the input can damage the port or the whole ESP32. In Cheesemaking mode LUA_PIN is occupied by the pH sensor: pinMode and digitalWrite for it end with an error.
exp_pinMode(pin, mode) – like pinMode(pin, mode), but for controlling the PCF8575 port expander; pin is the expander port number (0–15), mode is INPUT, OUTPUT or INPUT_PULLUP. All expander ports can be selected as either read or output
exp_digitalWrite(pin, Value) – like digitalWrite(pin, Value), but for controlling the PCF8575 port expander; pin is the expander port number (0–15), Value is 1 or 0. All expander ports are available for writing
exp_digitalRead(pin) – like digitalRead(pin), but for controlling the PCF8575 port expander; pin is the expander port number (0–15). All expander ports are available for reading
exp_analogWrite(Value) – like analogWrite(pin, Value), but for controlling the PCF8591 port expander, Value is a value from 0 to 255. The expander has one port available for output.
exp_analogRead(pin) – like analogRead(), but for controlling the PCF8591 port expander; pin is the expander input number (0–3), returns a value from 0 to 255.
The exp_* functions exist only in firmware built with USE_EXPANDER (PCF8575) and USE_ANALOG_EXPANDER (PCF8591).
delay(ms) – like the Arduino function of the same name, pauses program execution for the number of milliseconds given in the parameter (1000 milliseconds in 1 second). No more than 1000.
millis() – like the Arduino function of the same name, returns the number of milliseconds since Samovar started
setTimer(Num, Sec) – set timer number Num for Sec seconds (up to 65535). There are 10 timers, 1 to 10. That is, scripts can work with no more than 10 timers at the same time
getTimer(Num) – get the time remaining on timer number Num in seconds. If the timer is not set or the time has run out, the function returns 0
sendMsg(Msg, Level) – If Level = -1, the message Msg is printed to the COM port and to the browser console, convenient for debugging. If Level is 0, 1 or 2, the message is sent as a system message (0 – alarm, 1 – warning, 2 – notification): to the display, the web interface, the apps and the website.
setPower(Power) – switches Samovar on/off. setPower(0) switches it off, setPower(1) switches it on (the same as the heating button of the current mode)
setCurrentPower(Value) – set the value Value on the regulator. If a regulator with power control (SEM/AVR) is used, then Value is the power in watts to set, otherwise it is the voltage with 0.1 V accuracy. The function exists only in a build with a power regulator.
setBodyTemp() – set the hearts temperature during rectification. It works only in rectification mode. In other modes it prints a message to the console that the hearts temperature cannot be set
setMixer(Val) – switch the stirrer on/off. setMixer(0) – off, setMixer(1) – on
openValve(Val) – open/close the water valve. openValve(0) – close, openValve(1) – open
setPumpPwm(Val) – set the water pump speed, Val from 0 to 1023. Exists only in a build with a PWM pump (USE_WATER_PUMP).
setMixer, openValve, setCurrentPower and setPumpPwm return the command result code (the ACTUATOR_COMMAND_* constants).
setAlarm() – set the alarm mode. Samovar will switch off the power, close the water valve and switch off the water pump
setNextProgram() – go to the next program. Like pressing the "Next program" («Следующая программа») button in the interface. Works only when heating is on.
setPauseWithdrawal(Val) – pause/resume the take-off. setPauseWithdrawal(0) – resume, setPauseWithdrawal(1) – pause
setCapacity(Num) – turn the servo to container number Num (0 – drain, 1 and up – containers).
setServoAngle(Angle) – turn the servo to Angle degrees (0–180). Returns 1 if the firmware has a servo output (SERVO_PIN), otherwise 0. Used for homemade additive dispensers (see Additive dispensers); the servo is shared with setCapacity, so after turning it in rectification the container must be set again.
getState() – get the Samovar status (a number), convenient for determining the state Samovar is in: 0 – idle; rectification: 10 – take-off in progress, 15 – automatic pause between program lines, 20 – program completed, 30 – take-off pump calibration, 50 – boost, 51 – stabilization, 52 – stabilization completed; 40 – manual pause (in any mode); 1000 – distillation in progress, 2000 – beer, 3000 – wash column, 4000 – NBK, 5000 – cheesemaking.
setNumVariable("Variable", Val) – set an internal numeric Samovar variable. Not all variables can have values set (list below). setNumVariable("loop_lua_fl", 1) will enable the one-second script loop. Attention! If you do not understand how Samovar works, it is better not to use this function, since you could potentially disrupt Samovar's operation
setStrVariable("Variable", Val) – set an internal string Samovar variable. Not all variables can have values set. setStrVariable("SamovarStatus", "Test Samovar status")
getNumVariable("Variable") – get an internal numeric Samovar variable. TankTemp = getNumVariable("TankTemp") will assign the boiler temperature value to the script variable TankTemp
getStrVariable("Variable") – get an internal string Samovar variable.
program_type = getStrVariable("program_type")– will assign the type of the program currently running to the script variable program_type.
 
Script variables exist only while the script is running. On the next run their values are not preserved. Sometimes you need to remember a variable's value in one script run and read it in another. There are two functions for this – setObject("Object",Val) and getObject("Object")/getObject("Object","NUMERIC"). The set variable values are kept until reboot. There can be no more than 32 different objects; if exceeded, setObject ends with an error.
setLuaStatus(Status) - to show the script status in the interface ("Lua status" («Статус Lua») on the "Additional" («Дополнительно») tab). For example, tracking script execution:
i = getObject("cnt", "NUMERIC")
i = i + 1
setLuaStatus("Counter cnt = "..i)
setObject("cnt", i)
setObject("Object",Val) – will remember the object Object with the value Val in the Samovar memory.
getObject("Object") – gets the previously saved value of the object Object. If no such object was saved, an empty string is returned. If you try to use an empty string as a number, it will take the value nil – undefined. To make working with numeric values easier and avoid checking for nil, you can call this function with the additional parameter "NUMERIC"; in this case, if the object has not been initialized yet, 0 is returned.
An example of working with setObject/getObject:
n = 156
setObject("MyObject", n)
print (n)
n = n + 5
print (n)
n = getObject("MyObject")
print (n)
If you run this script, it will print in the Arduino port monitor
156
161
156
 
http_request(Url) – perform a GET request to the address Url and return the response body (or the string "error"). For example, you can send readings to your own web service. You can also send a POST request by passing four parameters: the address, the method, the Content-Type header and the body:
http_request("http://test.com:80/post?foo1=bar1", "POST", "Content-Type: text/text; charset=utf-8", "body")
An example of a script that sends the boiler temperature to an external web service (replace the address and parameters with your own):
local char_to_hex = function(c)
  return string.format("%%%02X", string.byte(c))
end
 
local function urlencode(url)
  if url == nil then
    return
  end
  url = url:gsub("\n", "\r\n")
  url = url:gsub("([^%w ])", char_to_hex)
  url = url:gsub(" ", "+")
  return url
end
 
function SendToServer(text)
  local key = "my_secret_key" -- your service access key
  http_request("http://example.com/notify?key=" .. key .. "&text=" .. urlencode(text))
end
 
local text = "Current boiler temperature = ".. getNumVariable("TankTemp")
SendToServer(text)
 
The functions of the I2CStepper board (stirrer, dosing pump, relay) – set_stepper_by_time, set_stepper_target, get_stepper_status, i2cpump_start, i2cpump_stop, i2cpump_get_speed, i2cpump_get_target_ml, i2cpump_get_remaining_ml, i2cpump_get_running, set_mixer_pump_target, get_mixer_pump_status, set_i2c_rele_state, get_i2c_rele_state, check_I2C_device – are described on the page I2CStepper.
 
Variables
The list of internal variables available in a script by default (their values are filled in before each script run, changing them by assignment is useless):
 
Variables available through getNumVariable (read-only): WFpulseCount, pump_started, valve_status, SamSetup_Mode, Samovar_Mode, Samovar_CR_Mode (the current and switchable mode, numbers as in SamSetup_Mode), SteamTemp, PipeTemp, WaterTemp, TankTemp, ACPTemp, WFtotalMilliLitres, WFflowRate, program_volume, program_speed, program_temp, program_power, program_time, program_capacity_num, capacity_num, target_power_volt, PowerOn, PauseOn, program_Wait, alcohol (the alcohol content in the boiler by temperature), alcohol_s (the alcohol content by vapor), water_pump_speed (the water pump speed 0–1023), pressure_value (the boiler pressure), YY, MM, DD, HH, MI, SS (year, month, day, hours, minutes, seconds).
Variables available through getNumVariable and setNumVariable: acceleration_temp, boil_temp (the remembered boiling temperature), wp_count, test_num_val, loop_lua_fl, SetScriptOff, show_lua_script. Only through setNumVariable: pmpKp, pmpKi, pmpKd – the PID coefficients of the water pump.
String variables (getStrVariable/setStrVariable): SamovarStatus – the status text on the main page (read and write), test_str_val (read and write), program_type (read only), Msg – when read, returns the next unread system message or an empty string, when written, adds a message.
 
Variables for controlling scripts:
loop_lua_fl – values 0 or 1. If 0 – do not run the every-second script loop, 1 – run it.
SetScriptOff – values 0 or 1. If 1 – stop the every-second loop (resets loop_lua_fl). Samovar sets it to 1 itself at the end of a program.
show_lua_script – values 0 or 1. If 0 – do not show the script being run in the console and the port monitor, 1 – show it. Can be used for debugging. It will also show the values of all variables set for this script.
For example, if you call these two functions in the init.lua script, two scripts (script.lua and the one determined by the current Samovar mode) will run every second, and each script will be printed to the port monitor and the browser console
setNumVariable("loop_lua_fl",1)
setNumVariable("show_lua_script",1)