Сценарии LUA

Материал из WebHMI
Перейти к: навигация, поиск

О скриптах Lua

WebHMI начиная с версии 1.10.0.3393 позволяет создавать пользовательские скрипты на языке Lua 5.1 1.

Управление скриптами происходит в пункте меню Setup --> Scripts:

Lua-scripts-1.png Scripts edit.png

Каждый скрипт должен содержать в себе функцию с именем main. Именно она будет вызвана в необходимый момент (в зависимости от настроек скрипта - в каждом цикле, при действии на dashboard или при изменении значения регистра). Также, весь скрипт будет выполнен один раз при запуске демона. Это необходимо для компиляции сценария Lua и его валидации. Выполнен будет весь код, который находится в глобальной области видимости (т.е. вне функции main, функция main не будет вызвана). Это может быть полезно для инициализации некоторых переменный и т.п.

В функцию main будет передан один параметр — userId — ID пользователя, который выполнил этот скрипт. Если скрипт выполняется в каждом цикле или при изменении регистра то userId будет равен нулю.

WebHMI накладывает следующие ограничения на стандартные функции и библиотеки языка Lua:

  • не доступны такие функции и таблицы: require, print, loadfile, dofile, os.execute, os.getenv, os.remove, os.rename, os.tmpname, package, debug.debug, debug.getfenv, debug.getregistry, arg
  • не доступны библиотеки: io

Помимо стандартных функций Lua, в WebHMI определены еще такие функции:

  • GetReg
  • SetReg
  • WriteReg
  • AddInfoMessage
  • AddWarningMessage
  • AddAlertMessage
  • SendSMS
  • SendTelegramMessage
  • SendViberMessage
  • ERROR
  • INFO
  • DEBUG
  • TRACE
  • ApplyRecipe

Для удобства работы с регистрами из программ Lua, каждому регистру можно присвоить символическое имя и работать из Lua уже с этим именем. Это имя указывается в настройках регистра в поле Variable name:

Lua-scripts-3.png Lua-scripts-4.png

Пример простейшего скрипта, который показывает вызовы всех WebHMI функций:

function main (userId)
    local randomVal = GetReg("random_val");
    SetReg("random_val_copy", randomVal);
    WriteReg("result", randomVal + 2);
 
    AddInfoMessage("This is a Lua script", userId);
    AddWarningMessage("Warning. This is a Lua script", userId);
    AddAlertMessage("Alert! This is a Lua script", userId);
 
    SendSMS("380501234567", "This is a test SMS");
 
    ERROR("This message will be added to communication log with ERROR level");
    INFO("This message will be added to communication log with INFO level");
    DEBUG("This message will be added to communication log with DEBUG level");
    TRACE("This message will be added to communication log with TRACE level");
end

Несмотря на то, что встроенный редактор проверяет синтаксис программы, он не в состоянии выявить все ошибки, которые могу возникнуть в процессе выполнения скрипта. В случаях, если во время его выполнения возникнут какие-либо ошибки, они будут выведены в Communication Log.

Например, при выполнении этого скрипта произойдет ошибка т.к. переменная random не объявлена.

function main (userId)
 
  local v1 = GetReg("Drying_Humidity1_Value");
  v1 = v1+random; -- ошибка здесь, если убрать random, всё становится ок
 
  SetReg("Drying_Humidity1_Value", v1);
  WriteReg("Drying_Humidity1_Value", v1); 
 
  AddInfoMessage(v1, userId);
end

В Communication Log можно будет увидеть такие записи:

Jan 23 12:09:27.047: ERROR: LUA scripts: Can't execute LUA script #1. Error #2: [string "Calculate humidity"]:4: attempt to perform arithmetic on a nil value (global 'random')
Jan 23 12:09:27.551: ERROR: LUA scripts: Can't execute LUA script #1. Error #2: [string "Calculate humidity"]:4: attempt to perform arithmetic on a nil value (global 'random')
Jan 23 12:09:28.051: ERROR: LUA scripts: Can't execute LUA script #1. Error #2: [string "Calculate humidity"]:4: attempt to perform arithmetic on a nil value (global 'random')

Здесь видно, что произошла ошибка при попытке выполнения арифметической операции с неопределенной переменной (nil). Ошибка произошла в скрипте номер 1, его название "Calculate humidity", ошибка произошла в строке номер 4. Переменная, которая вызвала ошибку называется 'random'.

Таким образом, с помощью этого лога можно найти все ошибки выполнения сценариев.

GetReg(variable_name[, connection_name])

Функция GetReg возвращает текущее значение регистра с именем переменной variable_name где variable_name — это значение Srcipt alias из настроек регистра. Вместо variable_name можно указывать число — ID регистра. Однако, ID регистра — это менее наглядный способ и он усложняет чтение программы.

Опциональный параметр connection_name можно указать если в нескольких разных соединениях есть регистры с одинаковым variable_name. В этом случае connection_name указывает из какого конкретно соединения необходимо читать регистр с именем variable_name. Также, вместо строки connection_name можно указывать ID соединения. Однако, опять же, константы усложняют чтение кода и предпочтительней использовать именованные строки.

Если регистр не найден или не был прочитан то возвращается значение nil.

Для удобства выбора регистров возле редактора скриптов есть три дополнительные кнопки.

Help-buttons.png

При нажатии на любую из них появляется всплывающее окно со списком регистров.

Regs-select.png

После щелчка по интересующему регистру в редактор будет вставлен код с идентификатором выбранного регистра (variable_name если есть или ID) и комментарием с именем регистра, его адресом и именем соединения.

Reg-selected.png

SetReg(variable_name[,connection_name] , new_value)

Функция SetReg устанавливает текущее значение регистра с именем переменной variable_name в new_value для текущего цикла. Запись во внешние устройства не происходит и при опросе этого регистра в последующих циклах будет прочитано старое значение. Возвращает 1 если произошла ошибка и 0 в случае успеха.

Параметры variable_name и connection_name работают так же как и в функции GetReg.

WriteReg(variable_name, new_value)

Функция WriteReg устанавливает текущее значение регистра с именем переменной variable_name в new_value для текущего цикла цикле и записывает это значение во внешнее устройство в начале следующего цикла. При опросе этого регистра в последующих циклах будет прочитано новое значение (если оно не изменится самим устройством). Возвращает 1 если произошла ошибка и 0 в случае успеха.

Параметры variable_name и connection_name работают так же как и в функции GetReg.

SendSMS(phone_number, message)

Функция SendSMS отправляет запрос на отправку SMS на номер phone_number с текстом message. Возвращает false если произошла ошибка и true в случае успеха. Успех не означает доставку сообщения, а только успех при создании запроса. Для работы этой функции необходимо наличие интернет-подключения и предоплаты за услуги SMS.

Формат phone_number — только цифры с указанием кода страны, без пробелов, скобок, знака плюс. Например, 380123456789.

SendTelegramMessage(chatId, message)

Функция SendTelegramMessage отправляет сообщение с текстом message в чат с Id = chatId. Возвращает 1 если произошла ошибка и 0 в случае успеха. Успех не означает доставку сообщения, а только успех при создании запроса. Для работы этой функции необходимо наличие работающего интернет-подключения и доступа к серверу webhmicloud.com.

Для получения ChatId перейдите по ссылке http://telegram.me/webhmibot на мобильном телефоне (предварительно установив Telegram) и начните диалог с ботом webhmibot командой /start. В ответ вы получите сообщение с уникальным идентификатором чата ChatId.

Пример:

Chatid.png

SendViberMessage(chatId, message)

Функция SendViberMessage отправляет сообщение с текстом message в чат с Id = chatId. Возвращает 1 если произошла ошибка и 0 в случае успеха. Успех не означает доставку сообщения, а только успех при создании запроса. Для работы этой функции необходимо наличие работающего интернет-подключения, учетной записи в системе Level2 и положительного баланса в этой системе. Отправка сообщений в Viber — платная услуга. Эта возможность доступна начиная с версии прошивки 2.4.4227.

Для получения ChatId перейдите по ссылке http://viber.com/webhmi на мобильном телефоне (предварительно установив Viber), нажмите кнопку Have a look.

Viber-pa.png

Вы попадете в публичный аккаунт WebHMI. Перейдите в личные сообщения публичного аккаунта нажав кнопку справа вверху.

Viber-private-message.png

Напишите любой текст в чате. В ответ вы получите сообщение с уникальным идентификатором чата ChatId.

Viber-chat-id.png

AddInfoMessage(message, userId)

Функция AddInfoMessage добавляет сообщение message с уровнем Info в журнал Messages. userId — это ID пользователя, от имени которого должно быть добавлено сообщение. Возвращает 1 если произошла ошибка и 0 в случае успеха.

AddWarningMessage(message, userId)

Функция AddWarningMessage добавляет сообщение message с уровнем Warning в журнал Messages. userId — это ID пользователя, от имени которого должно быть добавлено сообщение. Возвращает 1 если произошла ошибка и 0 в случае успеха.

AddAlertMessage(message, userId)

Функция AddAlertMessage добавляет сообщение message с уровнем Alert в журнал Messages. userId — это ID пользователя, от имени которого должно быть добавлено сообщение. Возвращает 1 если произошла ошибка и 0 в случае успеха.

ERROR(message)

Функция ERROR добавляет сообщение message уровня ERROR в Communication Log (Maintenance --> Communication Log). Возвращает 1 если произошла ошибка и 0 в случае успеха.

INFO(message)

Функция INFO добавляет сообщение message уровня INFO в Communication Log (Maintenance --> Communication Log). Возвращает 1 если произошла ошибка и 0 в случае успеха.

DEBUG(message)

Функция DEBUG добавляет сообщение message уровня DEBUG в Communication Log (Maintenance --> Communication Log). Возвращает 1 если произошла ошибка и 0 в случае успеха.

TRACE(message)

Функция TRACE добавляет сообщение message уровня TRACE в Communication Log (Maintenance --> Communication Log). Возвращает 1 если произошла ошибка и 0 в случае успеха.

ApplyRecipe(recipeId, userId)

Функция ApplyRecipe применяет рецепт с номером recipeId от имени пользователя с id = userId. Если у данного пользователя нет прав доступа к этому рецепту то рецепт применен не будет. Применение рецепта заключается в записи в регистры, которые указаны в рецепте, соответствующих значений.

GetCurrentAlerts()

Функция GetCurrentAlerts возвращает список текущих аварий. Возвращаемое значение — таблица. Ключем является номер аварии, значением — таблица со свойствами аварии. Свойства аварии:

Свойство Описание Тип данных
startTime Время возникновения аварии Number, UnixTime
regId Номер регистра, в котором флаг аварии привел к этой аварии Number
regAlias Имя переменной регистра для программ String
bit Номер бита с флагом аварии Number
title Название аварии String
connectionTitle Название соединения String
connectionId ID соединения Number
connectionAlias Имя переменной соединения для программ String
acknowledgedBy Имя пользователя, который подтвердил аварию. Если авария не подтверждена — пустая строка. String
acknowledgedTime Время, когда авария был подтверждена. Если авария не подтверждена — 0. Number, UnixTime
canBeAcknowledged Флаг, обозначающий что данную аварию можно подтверждать. Boolean

Используя данную структура, можно реализовать, например, различные сценарии оповещения о неподтвержденных авариях.

Пример такой программы:

local notificationsSent = {};
function main (userId)
    local alerts = GetCurrentAlerts();
    if (#alerts > 0) then
        for num,alert in pairs(alerts) do 
            local now = os.time();
            local canBeAcknowledged = alert['canBeAcknowledged'];
            local acknowledgedTime = alert['acknowledgedTime'];
            local startTime = alert['startTime'];
            local waitSecondsBeforeEscalate = 300; -- оповещать об авариях, которые неподтвержены более 5 минут
            local regId = alert['regId'];
            local bit = alert['bit'];
            if (canBeAcknowledged and acknowledgedTime == 0) then
                if (now - startTime > waitSecondsBeforeEscalate) then
                    if (notificationsSent[regId] == nil or notificationsSent[regId][bit] ~= startTime) then
                        --ERROR("Alert" .. alert['title'] .. " was not acknowledged!");
                        SendSMS("380501234567", "Не подтведжена авария: " .. alert['title']);
                        if (notificationsSent[regId] == nil) then
                            notificationsSent[regId] = {};
                        end
                        notificationsSent[regId][bit] = startTime;
                    end
                end
            end
        end
    end
end