Написание скриптов
Запуск редактора
Для запуска "Редактора скриптов" необходимо выбрать соответствующий пункт меню.

Окно редактора включает в себя следующие функциональные области:

- Файлы скриптов — область в которой можно создавать, редактировать скрипты и модули проекта.
- Рабочая область — область для написания кода для выбранного скрипта на языке Lua.
- Примеры — примеры кусков кода наиболее востребованных операций, применяемых в контексте работы с системой. Пример на рабочую область можно добавить, дважды нажав на нем левой кнопкой мыши.
- Отладочная консоль — окно сообщений от компилятора.
- Компилировать — кнопка запуска сборки скрипта.
- Подключиться к серверу — кнопка подключения к серверу для отладки.
- Панель переключения режимов — кнопки переключения режимов разработки и отладки скрипта.
В верхней части окна "Список скриптов" доступна панель иснтрументов:
- ➕ — создать новый файл.
- ❌ — удалить выбранный файл.
- 📁 — добавить каталог для группировки файлов.
В области редактирования есть возможность использовать следующие сочетания клавиш:
- Ctrl+C — копировать выделенные символы.
- Ctrl+V — вставить данные из буфера обмена.
- Ctrl+A — выделить все символы в области редактирования.
- Ctrl+X — вырезать выделенные символы.
- Ctrl+Z — отменить последнее действие.
- Ctrl+Y — повторить отмененное действие.
Общая информация
В Симплайт 5 реализован механизм горячей перезагрузки скриптов, позволяющий применять изменения в коде модулей без полной перезагрузки ядра системы. После развертывания проекта на сервер происходит перезагрузка только измененных модулей.
Работа "Редактора скриптов" построена на модели, которая предполагает наличие файла скрипта main.lua и файлов с подключаемыми модулями.
Для создания скрипта необходимо нажать на кнопку ➕ на панели инструментов. Откроется диалоговое окно "Создание файла" и система автоматически предложит создать новый файл с именем main.lua.
Важно
Изменение имени файла скрипта main.lua не допускается.
При именовании последующих файлов проекта (модулей) необходимо обязательно указывать расширение .lua для каждого.
Файл main.lua является основным. Он предоставляет возможность вызова всех дополнительных модулей.

Компиляция
После создания скрипта необходимо его скомпилировать с помощью соответствующей кнопки. В случае успешной компиляции в отладочной консоли появится сообщение "Компиляция выполнена успешно". В противном случае будет представлен список ошибок с указанием строки, позиции и описанием проблемы.
Логические ошибки в скриптах, которые не могут быть обнаружены на этапе компиляции, будут записаны в журнале отладки.
Пользовательская библиотека
Для того, чтобы добавить пользовательскую библиотеку необходимо поместить модуль, написанный на языке Lua, в общий каталог модулей программы: C:\Users\Public\Documents\SimpLight_5.0\scripts\sdk. После этого вызвать его соответствующим образом в скрипте. Такой подход рассмотрен в примере "Время наработки тега".
Печать сообщения
Для того чтобы вывести строку приветствия в журнале сообщений сервера, необходимо добавить в скрипт функцию print и передать ей в качестве аргумента любую строку, например "Hello, World!". Затем необходимо нажать кнопку "Компилировать". Если компиляция выполнена успешно, то следует сохранить проект.

Чтобы увидеть данное сообщение в списке служебных, следует выбрать уровень логирования "DEBUG" в настройках. Далее развернуть проект на сервер. Как только откроется окно клиента необходимо перейти в "Панель управления" для доступа к служебным сообщениям. Среди всех сообщений должна появиться строка приветствия, которую сформировал скрипт main.lua на сервере.

Для добавления дополнительного модуля необходимо повторно нажать кнопку ➕ на панели инструментов, в диалоговом окне указать имя и активировать флаг "Добавить вызов модуля в скрипт main.lua". Это позволит автоматически добавить вызов модуля в основной скрипт.
Для загрузки модуля в основном скрипте используется функция require. Система автоматически добавляет вызов этой функции, если установлен соответствующий флаг при создании модуля, либо вызов указывается самостоятельно в основном скрипте в формате require(имя_модуля).
setInterval(function() -- объявление циклически выполняемой функции
print("cyclic message") -- печать сообщения "cyclic message"
end, 1000) -- с интервалом 1000 мс (1 секунда)
Чтение и запись значений в теги
В Симплайт 5 поддерживаются два основных подхода к реализации скриптов:
- асинхронный — основан на использовании блока
async/awaitи применяется для операций, требующих ожидания внешних событий — таких как запросы к оборудованию, ожидание значений тегов или работа с базами данных. Данный подход не блокирует основной поток выполнения системы. - синхронный — используется для определенных вычислений, обработки данных в реальном времени и критичных по времени операций, где выполнение скрипта должно быть завершено за минимальный промежуток времени.
Выбор подхода зависит от характера решаемой задачи: асинхронные функции рекомендуются для операций ввода-вывода и длительных процессов, тогда как синхронные — для вычислений с гарантированным временем выполнения и обработки данных в циклах управления.
Ниже представлен пример работы асинхронного подхода. Модуль async упрощает работу с асинхронным кодом, позволяя описывать последовательности асинхронных операций так, как если бы они были синхронными. Функция, объявленная с помощью модуля async, может содержать вызовы с оператором await, который "приостанавливает" выполнение кода до завершения указанной асинхронной операции.
Пример:
Необходимо вычислить среднее значение тегов supplyWaterTemp и tankTemp, которые представляют собой температуру воды в подающем трубопроводе и температуру воды в баке-аккумуляторе соответственно, и присвоить значение тегу averageTemp.
local async = require("async") -- загрузка модуля для выполнения асинхронных функций
local tagApi = require("simplight.api.channels") -- загрузка модуля чтения/записи тегов через функции readMany и writeMany
setInterval(function() -- функция циклического выполнения
async(function() -- async принимает анонимную функцию
-- await приостанавливает выполнение async до получения функцией readMany значений тегов
local tags = await(tagApi.readMany({ "supplyWaterTemp", "tankTemp"}))
local supply = tags[1].value -- извлечение значения тега "supplyWaterTemp"
local tank = tags[2].value -- извлечение значения тега "tankTemp"
local average = (supply + tank)/2 -- вычисление среднего значения
-- запись в тег averageTemp значения переменной average и признака качества сигнала (0xC0 — хорошее качество)
await(tagApi.writeMany({values = {{ name="averageTemp", value=average, quality=0xC0 }}
}))
end):catch(function(err) -- обработка возможных ошибок
print("error", err)
end)
end, 1000) -- интервал выполнения функции setInterval (1000 мс)
Результат вычислений значения тега averageTemp скрипта после развёртывания проекта на сервере можно проверить с помощью теста тегов проекта.
Обработка сигнала с задержкой
В данном разделе представлен пример скрипта, который позволяет реализовать задержку по времени при обработке сигнала.
Пример:
Если значение температуры averageTemp, рассчитанное предыдущим скриптом, будет превышать 10 °C в течение 5 секунд, то будет считаться, что возникла аварийная ситуация (тег alarmTag будет установлен в значение true).
local async = require("async") -- загрузка модуля асинхронных операций
local tagApi = require("simplight.api.channels") -- загрузка модуля работы с тегами
local time = require('time') -- загрузка модуля работы со временем
local tempAlTime -- таймер аварии. Если аварии нет, то значение остаётся nil
setInterval(function()
async(function()
local tempAvg = (await(tagApi.readMany({"averageTemp"})))[1].value
if tempAvg > 10 then -- проверка условия превышения температуры
if not tempAlTime then
tempAlTime = time.now() + 5 * 1000 -- расчет времени задержки: текущее время + 5 секунд
elseif time.now() > tempAlTime then -- если текущее значение времени превышает время задержки
await(tagApi.writeMany({values = {{name = "alarmTag", value = true, quality = 0xC0}} -- в аварийный тег записывается true и пр.качества
}))
tempAlTime = nil -- и выполняется сброс переменной таймера для следующего цикла
end
else
tempAlTime = nil
-- если условие превышения температуры не выполнилось в аварийный тег записывается false
await(tagApi.writeMany({values = {{ name = "alarmTag", value = false, quality = 0xC0}}
}))
end
end):catch(function(err) -- обработка возможных ошибок
print("error", err)
end)
end, 1000)
Для проверки корректности перехода тега alarmTag в аварийное состояние после разворачивания проекта на сервер, необходимо выполнить проверку с использованием теста тегов проекта.
Пользовательские события
Все пользовательские события в Симплайт 5 обрабатываются с помощью скриптов.
Пример:
Необходимо сформировать 2 типа пользовательских событий:
- Аварийное событие — переход тега alarmTag из состояния false в состояние true.
- Информационное событие — переход тега alarmTag из состояния true в состояние false.
В зависимости от типа события на мнемосхеме оператора должно отображаться соответствующее сообщение. В окне "Редактор категорий событий" нет возможности отредактировать текст сообщения пользовательских событий — доступно только изменение цвета текста и фона сообщения. Все остальные параметры будут обрабатываться программно.
В данном скрипте отсутствует циклический опрос, присутствовавший в предыдущих примерах. Работа программы подразумевает программную реакцию на прошедшее событие в виде перехода значения тега tagAlarm из одного состояние в другое.
local async = require("async") -- загрузка модуля для выполнения асинхронных функций
local events = require("simp-events") -- загрузка модуля для работы с событиями
local funcs = require("funcs") -- загрузка модуля вспомогательных функций
local EventType = funcs.eventType -- переменная для работы с событиями
events.on("channelChanges", function(event) -- подписка на событие "channelChanges" (изменение тегов)
local changes = event.changes
if changes[1].name == "alarmTag" then -- проверка, если имя измененного канала "alarmTag"
Actions_LogEvents(changes[1].value) -- вызывается функция с новым значением тега
end
end)
function Actions_LogEvents(newValue)
async(function()
print("Actions.LogEvents")
if newValue == true then -- если новое значение TRUE формируется аварийное событие
funcs.logEvents({
type=EventType.alarm,
text="Авария средней температуры воды",
})
else -- если новое значение FALSE формируется информационное событие
funcs.logEvents({
type=EventType.info,
text="Средняя температура воды в норме",
})
end
end):catch(function(err) -- обработка возможных ошибок
print("error", err)
end)
end
Также система регистрирует данные о событии в журнале событий.
Время наработки тега
В данном разделе будет рассмотрен пример добавления пользовательской библиотеки. Для этого необходимо поместить модуль, написанный на языке Lua, в рабочий каталог программы.
Например, для решения задачи подсчета времени наработки агрегата в системе требуется создать скрипт с именем motoHours.lua, а затем разместить его в специально созданном каталоге для пользовательских библиотек, например, с именем lib. Данный каталог должен быть расположен по пути: C:\Users\Public\Documents\SimpLight_5.0\scripts\sdk.
local async = require("async")
local tagApi = require("simplight.api.channels")
local time = require("time")
local promise = require("promise")
local function timeSince(t)
local elapsedTime = time.now() - t
return elapsedTime
end
local function localStorage_getNumberDef(key, default)
local v = localStorage:getItem(key, default)
if v == nil then
return default
end
return tonumber(v)
end
local function composeLabel(tagName)
return string.format('$simplight.motoHours.%s.totalWorkTime', tagName)
end
local M = {}
local watchByTagNameMap = {}
local function checkTag(tagName, tagValue)
local watch = watchByTagNameMap[tagName]
if watch == nil or watch.isCalcing then
return promise.resolve(true)
end
return async(function()
watch.isCalcing = true
local label = composeLabel(watch.watchTagName)
local totalWorkTime = localStorage_getNumberDef(label, 0)
local v = watch.valueGetter(tagValue)
if v == 1 or v == true then
if not watch.isRun then
watch.lastUpdateTime = time.now()
watch.isRun = true
end
totalWorkTime = totalWorkTime + timeSince(watch.lastUpdateTime)
watch.lastUpdateTime = time.now()
else
if watch.isRun then
totalWorkTime = totalWorkTime + timeSince(watch.lastUpdateTime)
watch.lastUpdateTime = 0
watch.isRun = false
end
end
if watch.resetTagName ~= '' then
local resetTag = await(tagApi.read(watch.resetTagName))
if resetTag.value then
totalWorkTime = 0
watch.lastUpdateTime = 0
watch.isRun = false
await(tagApi.write(watch.resetTagName, 0, 0xC0))
end
end
watch.totalWorkTime = totalWorkTime
localStorage:setItem(label, totalWorkTime)
end):catch(function(err)
print("MotoHours.checkTag:", err)
end):finally(function()
watch.isCalcing = false
end)
end
function M.watch(options)
local watchTagName = options.watchTagName
if type(watchTagName) ~= "string" then
error("motoHours.watch: watchTagName must be string")
end
local label = composeLabel(watchTagName)
local totalWorkTime = localStorage_getNumberDef(label, 0)
local lWatch = {
lastUpdateTime = 0,
isRun = false,
resetTagName = options.resetTagName,
valueGetter = options.valueGetter,
totalWorkTime = totalWorkTime,
watchTagName = watchTagName,
}
if valueGetter == nil then
lWatch.valueGetter = function(srcValue)
return srcValue
end
end
watchByTagNameMap[watchTagName] = lWatch
local intervalId = setInterval(function()
async(function()
local tag = await(tagApi.read(watchTagName))
await(checkTag(tag.name, tag.value))
end):catch(function(err)
print("motoHours.watch: interval:", err)
end)
end, 10000)
return {
cancel = function()
clearInterval(intervalId)
watchByTagNameMap[watchTagName] = nil
end,
getTotalWorkTime = function()
return lWatch.totalWorkTime or 0
end
}
end
return M
Чтобы подключить данный модуль в скрипте требуется оформить вызов:
Пример:
Выводить сообщение с временем наработки тега waterPump в служебных сообщениях сервера каждые 10 секунд.
local async = require("async")
local motoHours = require("lib.motoHours")
local watch = motoHours.watch({
watchTagName = "waterPump",
resetTagName = "resetHours",
})
setInterval(function()
local t = watch.getTotalWorkTime() / 1000
print(string.format("Время наработки тега: %d сек", t))
end, 10 * 1000)
Формирование отчета
Этот раздел содержит пример по формированию отчета напрямую из скрипта. Скрипты позволяют программно генерировать документы, извлекать данные, а также настраивать выходные форматы для последующего использования.
Пример:
Необходимо формировать отчет при записи значения в тег, а также в соответствии с расписанием в 7:00 и 21:00.
local async = require("async")
local time = require("time")
local reportApi = require("reports")
local events = require("simp-events")
local tagApi = require("simplight.api.channels")
local function build()
async(function()
print("build")
local lEnd = time.now() -- конечная временная точка
local lStart = lEnd - 60 * 60 * 1000 -- вычисление начальной точки (за час до текущего времени)
await(tagApi.write("Report.StartTime", lStart, 0xC0)) -- запись начальной временной точки
await(tagApi.write("Report.EndTime", lEnd, 0xC0)) -- запись конечной временной точки
await(reportApi.buildReport({ -- вызов функции построения отчета с указанием пути сохранения отчета
templatePath = "УтроВечер",
actions = {
save = {
filepath = "C:/Users/User/Desktop/moringEvening.pdf",
}
},
}))
end):catch(function(err)
print("error", err)
end)
end
events.on("channelWrite", function(event) -- формирование отчета по записи в тег СформироватьОтчет
for _, w in ipairs(event.writes) do
if w.name == "СформироватьОтчет" then
build()
end
end
end)
setInterval(function() -- планирование построение отчета в 7 утра и 21 час вечера.
local parts = time.toTable(time.now())
if (parts.hour == 7 and parts.min == 0)
or (parts.hour == 21 and parts.min == 0) then
build()
end
end, 60 * 1000)