О скриптовом языке программирования Lua.
Самовар поддерживает автоматизацию на основе скриптового языка программирования
Lua. Поддержка Lua включается при сборке прошивки: в конфигураторе — флажок «Использовать Lua» (раздел «Оборудование»), в PlatformIO — окружение
Samovar_lua, при ручной сборке — строка
#define USE_LUA в файле user_config_override.h. Редактор скриптов открывается кнопкой «Редактор» на странице Настроек (адрес http://samovar.local/edit). Логин и пароль не запрашиваются.
Текущая версия Lua 5.4.4
При запуске Самовара выполняется файл init.lua. Его назначение – включить/выключить секундный цикл выполнения скриптов, а также проинициализировать переменные, которые будут использоваться в других скриптах. В поставке init.lua цикл выключает: setNumVariable("loop_lua_fl",0).
Если включен запуск скриптов в цикле, каждую секунду запускается два скрипта: сначала - script.lua, после него скрипт с именем, которое определяется в зависимости от режима работы: rectificat.lua (Ректификация), dist.lua (Дистилляция), beer.lua (Пиво), bk.lua (Бражная колонна), nbk.lua (НБК), suvid.lua (Су-вид), cheese.lua (Сыроварение). В режиме «Lua-режим» (пункт списка режимов в Настройках, виден только в сборке с Lua) режимного скрипта нет — вся логика режима пишется в script.lua. Так же пару скриптов можно запустить разово, обратившись по адресу http://samovar.local/lua, а отдельный файл – по адресу http://samovar.local/lua?script=имя_файла.
Скрипты выполняются по очереди в одной задаче: пока идёт один, другой ждёт. Один запуск скрипта не может длиться дольше 20 секунд — по истечении он прерывается с сообщением «Lua: выполнение чанка прервано по таймауту». Если режимный скрипт завершается ошибкой 5 раз подряд, секундный цикл останавливается (loop_lua_fl сбрасывается в 0) и в консоль выводится одно итоговое сообщение; script.lua после 5 ошибок подряд перестаёт запускаться до перезагрузки скриптов, не останавливая цикл. Ошибки скриптов пишутся в консоль браузера и в монитор порта.
Так же есть возможность добавить кнопки в интерфейс для запуска файлов. Имя файла: btn_<режим>_button<N>.lua, где режим – rect, dist, beer, bk, nbk, suvid или cheese, N – номер кнопки. Например, btn_rect_button1.lua, btn_beer_button2.lua. Кнопки показываются на вкладке «Дополнительно» главной страницы того режима, для которого они созданы, в блоке Lua; там же поле «Lua:» с кнопкой «Выполнить Lua» для запуска одной-двух строк кода и «Статус Lua» — строка, которую скрипт задаёт функцией setLuaStatus.
Название кнопки задаётся в первой строке файла комментарием вида --|Название^ (например, --|Начать^). Если такой строки нет, кнопки получают названия LUA1, LUA2 и т. д. по порядку найденных файлов.
В поставке для каждого режима есть две кнопки: «Начать» (btn_*_button1.lua — включает секундный цикл) и «Остановить» (btn_*_button2.lua — выключает его).
Основное назначение кнопок - запустить выполнение скриптов в цикле (например, установив значение переменной, которое потом можно считать в скрипте, выполняющемся в цикле), или включить/выключить управляющее устройство (например, насос).
Таким образом, архитектура использования кнопок выглядит следующим образом (как пример - для режима дистилляции):
Главный управляемый цикл: раз в секунду script.lua => dist.lua => script.lua => dist.lua =>... (управляемый в том смысле, его можно запустить/остановить программно из любого скрипта через глобальную переменную)
Скрипт script.lua - единый для всех режимов, сюда можно поместить код, который должен выполняться при любом режиме (ректификация, пиво, су-вид и т.д.)
Скрипт dist.lua - будет использован только в режиме дистилляции, в него можно поместить всю логику, которая должна выполняться раз в секунду в зависимости от параметров, которые этот скрипт получает из глобальных переменных Самовара, например, getNumVariable("TankTemp") или переменных, сохранённых любым скриптом в общем пространстве имён, например, getObject("StartTankFilling"). То есть в коде этого скрипта, например, можно поместить условие:
if (StartTankFilling == "true" and TankFillingPercent < 80) then StartPump() else StopPump() end
и каждую секунду скрипт будет проверять, нет ли флага необходимости наполнить куб, а если есть - не заполнен ли он уже на 80%, и не нужно ли ранее запущенный насос уже остановить.
Скрипт кнопки btn_dist_button1.lua, который, при нажатии кнопки, и установит флаг необходимости наполнить куб через setObject("StartTankFilling", "true"). При следующем цикле через секунду скрипт dist.lua прочтёт этот флаг и включит насос (если куб ещё не наполнен). Таким образом, все скрипты имеют общее пространство имён, доступное через setObject/getObject, что даёт им возможность обмениваться данными.
Скрипт кнопки btn_dist_button2.lua, который, при нажатии кнопки, может остановить выполнение главного управляемого цикла через setNumVariable("loop_lua_fl", 0), например, если что-то пошло не так со скриптами.
Строка программы типа L (Lua-этап). В программах всех режимов, кроме НБК (Ректификация, Дистилляция, БК, Пиво, Сыроварение), есть строка типа L — этап под управлением Lua. При выборе этого типа в редакторе программы открывается окно «Lua-этап», в котором задаются:
Файл Lua — любой файл с расширением .lua из памяти Самовара (список берётся из редактора файлов). Свой сценарий можно загрузить через редактор файлов веб-интерфейса.
Тайм-аут этапа, сек — от 1 до 65535 секунд. Если за это время скрипт не вызвал setNextProgram(), Самовар выдаёт сообщение «Lua не завершила операцию до тайм-аута» и завершает режим.
Параметры — любое число строк, которые передаются в скрипт. В скрипте они доступны в таблице arg: arg[0] — имя файла, arg[1], arg[2]… — параметры по порядку. Пустой параметр записывается как "".
Когда программа доходит до строки L, Самовар запускает выбранный файл и ждёт вызова setNextProgram() — это переход к следующей строке. Ошибка скрипта («Lua завершилась с ошибкой») или тайм-аут останавливают режим. Остальные поля строки (температура, скорость, ёмкость, устройство) у типа L не задаются. В текстовом виде такая строка хранится как файл и параметры через символ ^, например L;120;hops.lua^3^45;0;0;0 для ректификации.
Пример скрипта dose.lua, который включает реле с номером из первого параметра на время из второго (в программе: файл dose.lua, параметры 3 и 1500):
set_i2c_rele_state(tonumber(arg[1]), 1)
delay(tonumber(arg[2]))
set_i2c_rele_state(tonumber(arg[1]), 0)
setNextProgram()
Если во время работы загрузить через редактор файлов новую версию файла, который сейчас выполняется строкой L, Самовар перечитает сценарий без перезапуска программы.
Функции Самовара
Из скриптов можно получить доступ к внутренним переменным Самовара (часть из них менять нельзя, они доступны только на чтение), а также вызывать функции Самовара. Пока идёт смена режима, функции, меняющие состояние Самовара, завершаются ошибкой «mode switch blocks state changes».
Номера выводов зависят от платы. Здесь и далее нужно передавать их числовое значение:
ESP32 DEVKIT: RELE_CHANNEL1 – 2, RELE_CHANNEL2 – 15, RELE_CHANNEL3 – 14, RELE_CHANNEL4 – 13, WATER_PUMP_PIN – 4, LUA_PIN – 34, ALARM_BTN_PIN – 35.
ESP32-S3: RELE_CHANNEL1 – 2, RELE_CHANNEL2 – 42, RELE_CHANNEL3 – 41, RELE_CHANNEL4 – 40, WATER_PUMP_PIN – 11, LUA_PIN – 4, ALARM_BTN_PIN – 48.
LILYGO T-Relay: RELE_CHANNEL1 – 21, RELE_CHANNEL2 – 19, RELE_CHANNEL3 – 18, RELE_CHANNEL4 – 5, WATER_PUMP_PIN – 2, LUA_PIN – 12.
В скриптах определены константы INPUT, OUTPUT, LOW, HIGH и коды результата команд ACTUATOR_COMMAND_ACCEPTED, ACTUATOR_COMMAND_PENDING, ACTUATOR_COMMAND_APPLIED, ACTUATOR_COMMAND_FAILED.
pinMode(pin, mode) – аналогично одноименной функции Arduino, устанавливает режим работы заданного входа/выхода (pin) как входа или как выхода. Изменение режима доступно только для портов RELE_CHANNEL1–RELE_CHANNEL4 и LUA_PIN. То есть, если нужно установить порт RELE_CHANNEL4 платы DEVKIT как вход – надо вызвать pinMode(13, INPUT), как выход - pinMode(13, OUTPUT).
digitalWrite(pin, Value) – аналогично одноименной функции Arduino, подает HIGH или LOW значение на цифровой выход (pin). Доступны порты RELE_CHANNEL1–RELE_CHANNEL4, WATER_PUMP_PIN, LUA_PIN, ALARM_BTN_PIN и BTN_PIN, остальные игнорируются. То есть, если нужно установить на выходе RELE_CHANNEL4 высокое значение, надо вызвать digitalWrite(13, 1). Для WATER_PUMP_PIN при насосе с ШИМ значение 1 включает насос на полную скорость, 0 – останавливает. Если сработала аварийная защита нагрева, запись в каналы нагревателя (RELE_CHANNEL1, RELE_CHANNEL4) не выполняется.
digitalRead(pin) – аналогично одноименной функции Arduino, считывает значение с заданного входа - HIGH или LOW. То есть, если нужно прочитать установленное на входе или выходе RELE_CHANNEL4 значение, надо вызвать digitalRead(13)
analogRead() – аналогично одноименной функции Arduino, считывает значение с аналогового входа LUA_PIN (параметров нет). Напряжение, поданное на аналоговый вход, будет преобразовано в значение от 0 до 4095. ВНИМАНИЕ! Подача напряжения на вход больше, чем 3.3 вольта может привести к выходу порта из строя, или всей ESP32. В режиме Сыроварение LUA_PIN занят датчиком pH: pinMode и digitalWrite для него завершаются ошибкой.
exp_pinMode(pin, mode) – аналогично pinMode(pin, mode), но для управления расширителем портов PCF8575, pin – номер порта расширителя (0–15), mode – INPUT, OUTPUT или INPUT_PULLUP. Все порты расширителя доступны для выбора типа – чтение или вывод
exp_digitalWrite(pin, Value) – аналогично digitalWrite(pin, Value), но для управления расширителем портов PCF8575, pin – номер порта расширителя (0–15), Value 1 или 0. Все порты расширителя доступны для записи
exp_digitalRead(pin) – аналогично digitalRead(pin), но для управления расширителем портов PCF8575, pin – номер порта расширителя (0–15). Все порты расширителя доступны для чтения
exp_analogWrite(Value) – аналогично analogWrite(pin, Value), но для управления расширителем портов PCF8591, Value - значение от 0 до 255. У расширителя один порт, доступный для вывода.
exp_analogRead(pin) – аналогично analogRead(), но для управления расширителем портов PCF8591, pin – номер входа расширителя (0–3), возвращает значение от 0 до 255.
Функции exp_* есть только в прошивке, собранной с USE_EXPANDER (PCF8575) и USE_ANALOG_EXPANDER (PCF8591).
delay(ms) – аналогично одноименной функции Arduino, останавливает выполнение программы на заданное в параметре количество миллисекунд (1000 миллисекунд в 1 секунде). Не больше 1000.
millis() – аналогично одноименной функции Arduino, Возвращает количество миллисекунд с момента начала работы Самовара
setTimer(Num, Sec) – установить таймер номер Num на Sec секунд (до 65535). Доступно 10 таймеров с 1 по 10. То есть одновременно скрипты могут работать не больше, чем с 10 таймерами
getTimer(Num) – получить оставшееся время по таймеру номер Num в секундах. Если таймер не установлен, или время закончилось, функция вернет 0
sendMsg(Msg, Level) – Если Level = -1, сообщение Msg будет выведено в com-port и в консоль браузера, удобно для отладки. Если Level 0, 1 или 2 - сообщение отправляется как сообщение системы (0 – тревога, 1 – предупреждение, 2 – уведомление): на дисплей, в web-интерфейс, в приложения и на сайт.
setPower(Power) – включает/выключает Самовар. setPower(0) – выключит, setPower(1) – включит (то же, что кнопка включения нагрева текущего режима)
setCurrentPower(Value) – установить значение Value на регуляторе. Если используется регулятор с управлением по мощности (СЭМ/AVR), то тогда Value – это мощность в ваттах, которую нужно установить, иначе - напряжение с точностью до 0,1 В. Функция есть только в сборке с регулятором мощности.
setBodyTemp() – установить температуру тела в процессе ректификации. Работать будет только в режиме ректификации. В других режимах будет выводить сообщение в консоль о невозможности установить температуру тела
setMixer(Val) – включить/выключить мешалку. setMixer(0) – выключить, setMixer(1) – включить
openValve(Val) – открыть/закрыть клапан воды. openValve(0) – закрыть, openValve(1) – открыть
setPumpPwm(Val) – задать скорость насоса воды, Val от 0 до 1023. Есть только в сборке с ШИМ-насосом (USE_WATER_PUMP).
setMixer, openValve, setCurrentPower и setPumpPwm возвращают код результата команды (константы ACTUATOR_COMMAND_*).
setAlarm() – установить режим тревоги. Самовар выключит питание, закроет клапан воды и отключит насос воды
setNextProgram() – перейти на следующую программу. Аналогично нажатию на кнопку в интерфейсе - Следующая программа. Работает только при включённом нагреве.
setPauseWithdrawal(Val) – поставить/снять отбор на паузу. setPauseWithdrawal(0) – снять с паузы, setPauseWithdrawal(1) – поставить на паузу
setCapacity(Num) – повернуть сервопривод на ёмкость номер Num (0 – слив, 1 и далее – ёмкости).
setServoAngle(Angle) – повернуть сервопривод на угол Angle градусов (0–180). Возвращает 1, если в прошивке есть выход для серво (SERVO_PIN), иначе 0. Используется для самодельных дозаторов добавок (см.
Дозаторы добавок); серво общее с setCapacity, поэтому после поворота в ректификации ёмкость нужно задать заново.
getState() – получить статус Самовара (число), удобно для определения, в каком статусе находится Самовар: 0 – простой; ректификация: 10 – идёт отбор, 15 – автопауза между строками программы, 20 – программа выполнена, 30 – калибровка насоса отбора, 50 – разгон, 51 – стабилизация, 52 – стабилизация завершена; 40 – ручная пауза (в любом режиме); 1000 – идёт дистилляция, 2000 – пиво, 3000 – БК, 4000 – НБК, 5000 – сыроварение.
setNumVariable("Variable", Val) – установить внутреннюю числовую переменную Самовара. Не все переменные доступны для установки значений (список ниже). setNumVariable("loop_lua_fl", 1) – включит секундный цикл скриптов. Внимание! Если не понимаете, как работает Самовар, эту функцию лучше не использовать, так как потенциально можно нарушить работу Самовара
setStrVariable("Variable", Val) – установить внутреннюю строковую переменную Самовара. Не все переменные доступны для установки значений. setStrVariable("SamovarStatus", "Тестовый статус Самовара")
getNumVariable("Variable") – получить внутреннюю числовую переменную Самовара. TankTemp = getNumVariable("TankTemp") – присвоит переменной скрипта TankTemp значение температуры куба
getStrVariable("Variable") – получить внутреннюю строковую переменную Самовара.
program_type = getStrVariable("program_type")– присвоит переменной скрипта program_type тип текущей выполняемой программы.
Переменные скрипта существуют только в момент выполнения скрипта. При следующем запуске их значения не сохранятся. Иногда бывает необходимо запомнить значение переменной в одном запуске скрипта и считать его в другом. Для этого есть две функции – setObject("Object",Val) и getObject("Object")/getObject("Object","NUMERIC"). Установленные значения переменных сохраняются до перезагрузки. Разных объектов может быть не больше 32, при превышении setObject завершается ошибкой.
setLuaStatus(Status) - для отображения статуса работы скрипта в интерфейсе («Статус Lua» на вкладке «Дополнительно»). Например, отслеживание выполнения скрипта:
i = getObject("cnt", "NUMERIC")
i = i + 1
setLuaStatus("Счетчик cnt = "..i)
setObject("cnt", i)
setObject("Object",Val) – запомнит объект Object со значением Val в памяти Самовара.
getObject("Object") – получит ранее сохраненное значение объекта Object. Если такой объект не был сохранен, вернется пустая строка. При попытке работать с пустой строкой как с числом, оно примет значение nil – не определено. Для того, чтобы было удобнее работать с числовыми значениями, и не проверять на nil, можно эту функцию вызвать с дополнительным параметром "NUMERIC", в этом случае, если объект еще был не инициализирован, вернется 0.
Пример работы с setObject/getObject:
n = 156
setObject("MyObject", n)
print (n)
n = n + 5
print (n)
n = getObject("MyObject")
print (n)
Если запустить этот скрипт, он в мониторе порта Arduino выведет
156
161
156
http_request(Url) – выполнить GET-запрос по адресу Url и вернуть тело ответа (или строку "error"). Например, можно отправить показания на свой веб-сервис. Так же можно отправить POST запрос, передав четыре параметра: адрес, метод, заголовок Content-Type и тело:
http_request("http://test.com:80/post?foo1=bar1", "POST", "Content-Type: text/text; charset=utf-8", "body")
Пример скрипта, который отправит температуру куба на внешний веб-сервис (адрес и параметры замените на свои):
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" -- ключ доступа вашего сервиса
http_request("http://example.com/notify?key=" .. key .. "&text=" .. urlencode(text))
end
local text = "Текущая температура куба = ".. getNumVariable("TankTemp")
SendToServer(text)
Функции платы I2CStepper (мешалка, дозирующий насос, реле) – 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 – описаны на странице
I2CStepper.
Переменные
Список внутренних переменных, которые по умолчанию доступны в скрипте (их значения подставляются перед каждым запуском скрипта, менять их через присваивание бесполезно):
bme_pressure – текущее значение атмосферного давления
capacity_num – номер емкости, в которую идет отбор
SamovarStatusInt – статус работы Самовара (то же, что getState())
ProgramNum – текущий номер программы
ProgramLen – количество строк в программе
ActualVolumePerHour – текущая скорость отбора в литрах
WthdrwlProgress - прогресс текущего отбора
PowerOn – признак включенного/выключенного нагрева. 0 – выключено, 1 - включено
PauseOn – признак паузы. 0 – нет, 1 – да
StepperMoving – признак работы перистальтического насоса. 0 – не работает, 1 – работает
program_Pause – признак запущенной программы паузы. 0 – нет, 1 – да
program_Wait – признак, что программа стоит на паузе. 0 – нет, 1 – да
program_Wait_Type – причина постановки на паузу – превышение по царге или пару. Значения "(пар)" или "(царга)"
WFflowMilliLitres, WFtotalMilliLitres, WFflowRate – объём за последний интервал, общий объём (мл) и расход воды охлаждения по датчику потока (только в сборке с датчиком потока)
WthdrwTimeAll – оставшееся время отбора
WthdrwTime – время отбора текущей строки программы
WthdrwTimeAllS – оставшееся время отбора строкой
WthdrwTimeS – время отбора текущей строки программы строкой
pump_started – статус работы насоса воды. 0 – нет, 1 – да
heater_state – статус нагрева при затирке. 0 – не нагревается, 1 – нагревается
mixer_status – статус работы мешалки. 0 – не работает, 1 – работает
alarm_event – признак сработавшей кнопки тревоги. 0 – нет, 1 – да
acceleration_heater – признак включенного разгонного тэна. 0 – выключен, 1 – включен
valve_status – статус клапана подачи воды. 0 – закрыт, 1 – открыт
program_type – тип текущей программы (из описания программ ректификации и пива)
program_volume – объем отбора в мл
program_speed – скорость отбора в л/ч
program_temp – температура, при которой отбирается эта часть погона. 0 - определяется автоматически
program_power – напряжение/мощность, при которой отбирается эта часть погона
program_time – время, необходимое для работы этой строки программы
program_capacity_num – номер емкости для отбора
SamSetup_Mode – режим работы Самовара: 0 – ректификация, 1 – дистилляция, 2 – пиво, 3 – БК, 4 – НБК, 5 – су-вид, 6 – Lua-режим, 7 – сыроварение
test_num_val – числовая переменная для отладки
test_str_val – строковая переменная для отладки
SteamTemp – температура датчика пара
PipeTemp – температура датчика царги
WaterTemp – температура датчика воды
TankTemp – температура датчика куба
ACPTemp – температура датчика ТСА
current_power_mode – текущий режим работы регулятора
target_power_volt – заданное напряжение работы регулятора
wp_count
Переменные, доступные через getNumVariable (только чтение): WFpulseCount, pump_started, valve_status, SamSetup_Mode, Samovar_Mode, Samovar_CR_Mode (текущий и переключаемый режим, номера как у 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 (спиртуозность в кубе по температуре), alcohol_s (спиртуозность по пару), water_pump_speed (скорость насоса воды 0–1023), pressure_value (давление в кубе), YY, MM, DD, HH, MI, SS (год, месяц, день, часы, минуты, секунды).
Переменные, доступные через getNumVariable и setNumVariable: acceleration_temp, boil_temp (запомненная температура кипения), wp_count, test_num_val, loop_lua_fl, SetScriptOff, show_lua_script. Только через setNumVariable: pmpKp, pmpKi, pmpKd – коэффициенты PID насоса воды.
Строковые переменные (getStrVariable/setStrVariable): SamovarStatus – текст статуса на главной странице (чтение и запись), test_str_val (чтение и запись), program_type (только чтение), Msg – при чтении возвращает очередное непрочитанное сообщение системы или пустую строку, при записи добавляет сообщение.
Переменные для управления скриптами:
loop_lua_fl – значения 0 или 1. Если 0 – не выполнять ежесекундный цикл скриптов, 1 – выполнять.
SetScriptOff – значения 0 или 1. Если 1 – остановить ежесекундный цикл (сбрасывает loop_lua_fl). Самовар сам выставляет её в 1 по окончании программы.
show_lua_script – значения 0 или 1. Если 0 – не показывать выполняемый скрипт в консоли и мониторе порта, 1 – показывать. Можно использоваться при отладке. Так же покажет значения всех переменных, установленных для этого скрипта.
Например, если в скрипте init.lua вызвать эти две функции, то ежесекундно будет запускаться два скрипта (script.lua и определяемый текущим режимом Самовара), и каждый скрипт будет выводиться в монитор порта и консоль браузера
setNumVariable("loop_lua_fl",1)
setNumVariable("show_lua_script",1)