Полезные советы — различия между версиями
(→Формирование суточных, недельных и т.п. отчетов) |
(→Преобразование числа в битовую табличку) |
||
Строка 299: | Строка 299: | ||
return tab; | return tab; | ||
end -- getBits | end -- getBits | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Далее можно переставить нужные биты местами и т.п. и получить нужное число. Ниже приведен пример перестановки битов в ответе счетчика расходомера производства Асвега-У | ||
+ | |||
+ | <syntaxhighlight lang = "lua"> | ||
+ | |||
+ | |||
+ | function main (userId) | ||
+ | |||
+ | local input_num = GetReg(3); -- прочитать регистр с числом | ||
+ | local bitTable = getBits(input_num,32); -- таблица для обработки результата | ||
+ | local tmp_bit = ""; -- вспомогательный бит | ||
+ | |||
+ | tmp_bit = bitTable[1]; | ||
+ | bitTable[1] = bitTable[9]; | ||
+ | bitTable[9] = tmp_bit; | ||
+ | -- конкатенация готовой таблицы в строку и преобразование в число из строки двоичного представления | ||
+ | WriteReg(1, tonumber(table.concat(bitTable),2)); | ||
+ | |||
+ | end | ||
</syntaxhighlight> | </syntaxhighlight> |
Версия 11:25, 15 марта 2017
Содержание
- 1 Запуск скрипта по фронта или срезу дискретного сигнала
- 2 Создание переключателя
- 3 Реализация таймера - задержки включения (TON)
- 4 Cигнализация (Звуковая, релейная, sms, viber, telegram) об ошибках связи
- 5 Скользящее среднее
- 6 ПИД - регулятор
- 7 Счетчик моточасов
- 8 Отладка сложных скриптов
- 9 Формирование суточных, недельных и т.п. отчетов
- 10 Преобразование числа в битовую табличку
Запуск скрипта по фронта или срезу дискретного сигнала
Скрипт нужно вызываеть по изменению регистра. Внутри скрипта нужно сделать проверку текущего состояния этого регистра и выполнять соответветсующие действия либо по фронту (текущее состояние =1), либо по срезу (=0). Пример скрипта:
Создание переключателя
Элемент слайдер можно использовать для управления типа открыть- закрыть, вкл.-выкл. и т.п. Для этого нужно "привязать" его к битовому регистру и указать опцию "user can change value" на дешборде. Также такой слайдер наглядно может отобразить положение переключателя (ручн. - акт., местное - дистанционное управение), заслонки , шибера и т.д.
Реализация таймера - задержки включения (TON)
Таймер TON начинает отчет пока вход = 1, по истечении времени задержки выход тоже устанавливается в "1".
TIMER_DELAY = 20; -- задержка таймера 20 сек.
tmr = false; -- начальное состояние - отсчет времени не идет
tmrStartTime = 0; -- время начала работы таймера
--
function main (userId)
-- переменные
local in_value = (GetReg(212) == 1); -- Просто бит (D301@WebHMI)
local now = GetReg(28); -- Текущее время (T0@WebHMI)
-- ПРОВЕРЯЕМ УСЛОВИЕ - СОСТОЯНИЕ ВХОДА ----------
if in_value then
if not tmr then
tmr = true;
tmrStartTime = now; -- запомнить время начала отсчета
else
if ( now - tmrStartTime ) > TIMER_DELAY then
-- действие по истечении таймера
WriteReg("TON_out", 1); -- сигнал таймера , битовый регистр с псевдонимом "TON_out"
end
end
else
tmr = false;
tmrStartTime = 0;
WriteReg("TON_out", 0); -- сигнал таймера
end
-- КОНЕЦ
end
Cигнализация (Звуковая, релейная, sms, viber, telegram) об ошибках связи
Можно анализировать скриптом время скана и при выходе его за допустимый предел, сигнализировать об этом разными способами. Ниже приведен пример обработки большого времени скана с сигнализацией в буфер сообщений и по Viber.
cntdownFlag = false; -- флаг обратного отчета таймера
timeStmp = 0; -- метка времени
msgSent = false; -- флаг отправки сообщения
function main (userId)
-- Читаем входные
local scan = GetReg(34); -- время скана
local c0 = GetReg(42); -- номер неработающего соединения
local SCANLIMIT = GetReg(886); -- предел времения скана
local SCANDELAY = GetReg(887); -- задержка реагирования на ошибку
--
if (scan == nil) or (c0 == nil) or (SCANLIMIT == nil) or (SCANDELAY == nil) then
ERROR("scan / c0 was read as nil");
return 0;
end
-- читаем время
local now = os.time();
-- шаблон сообщения
local msg1 = "Cкан тайм большой "..tostring(scan).." ".."ms, ошибка в соед. "..tostring(c0);
--
if (scan > SCANLIMIT) then -- скан выше нормы
if not cntdownFlag then
cntdownFlag = true;
timeStmp = now;
else
if (now - timeStmp) > SCANDELAY then
if not msgSent then
AddAlertMessage(msg1);
SendViberMessage(398044391, msg1); -- Женя
-- SendViberMessage(642997589, msg1); -- Игорь
SendViberMessage(335584075, msg1); -- Костя
msgSent = true;
end
end
end
else -- скан в норме
if (cntdownFlag == true) and msgSent then
AddInfoMessage("Скан вернулся к норме ");
SendViberMessage(398044391, "Скан вернулся к норме ");
SendViberMessage(335584075, "Скан вернулся к норме ");
msgSent = false;
end
cntdownFlag = false;
end
end -- main
В WebHMI имеются buzzer для подачи звукового сигнала, и выходные реле 2 шт. , которыми можно управлять для сигнализации (выдать на сигнальную колонну либо в ПЛК сигнал о проблеме).
Скользящее среднее
Скользящее среднее полезно для сглаживания значений параметров, имеющих шумы, пульсации.
Алгоритм скользящего среднего:
в начале работы фильтра на выборке в N значений идет подсчет арифметического среднего значения, по достижении конца выборки один элемент отбрасывается (путем деления суммы на длину очереди), вместо него добавляется новый и сумма снова делится на длину очереди.
-- глобальные переменные, сохраняют значения между вызовами программы
mav_len = 20; -- длина очереди
queue_fill = 0; -- индекс заполнения очереди
av_sum = 0; -- аккумулятор ск. среднего
function main (userId)
local in_value, tmp_var, out_value = GetReg(26), 0, 0; -- читаем значение параметра
if (queue_fill < mav_len) then -- очередь не заполнена
av_sum = av_sum + in_value; -- накапливаем сумму
queue_fill = queue_fill +1; -- и индекс
else -- очередь полная дальше будет движение по очереди
tmp_var = av_sum / mav_len; -- запомнить один элемент
av_sum = av_sum - tmp_var + in_value; -- вычесть его и добавить новый
end
--
if (queue_fill == mav_len ) then
out_value = av_sum / mav_len; -- посчитать ск. среднее
else
out_value = av_sum / queue_fill; -- среднее арифм.
end
WriteReg("Tout_mav", out_value); -- Наружная температура среднее
end
ПИД - регулятор
Пример реализации ПИД регулятора в WebHMI:
-- глобальные переменные, сохраняются между вызовами скрипта
Kp = 1; -- пропорциональная составляющая
Ti = 0.9; -- инт. составляющая
Td = 1; -- дифф. составляющая
SampleTime = 10 ; -- время цикла ПИД
TimeStamp = 0; -- метка для запоминания времения последнего вызова
Limit = 100; -- ограничение выхода регулятора
Int_sum = 0; -- интегральный накопитель
--
function main (userId)
-- локальные переменные
local now = GetReg("SysTime"); -- Время
local PV = GetReg("PID_PV"); -- PV (D14@Тест) обратная связь
local Sp = GetReg("PID_Sp"); -- Sp (D10@Тест) задание
local prevErr = 0.0; -- предыдущая ошибка для вычисления дифф. составляющей
--
if (TimeStamp == 0) then
TimeStamp = now; -- запомнить время входа в цикл
--
local Err = PV - Sp; -- вычисляем ошибку
local dErr = Err - prevErr; -- вычисляем производную ошибки
-- проверяем интегральное насыщение
local iSum_Limit = Limit * Ti / (Kp);
if (Int_sum <= iSum_Limit) and (Int_sum >= 0.0) then
Int_sum = Int_sum + Err; -- накапливаем интеграл ошибки
elseif Int_sum < 0 then
Int_sum = 0;
else
Int_sum = iSum_Limit; -- ограничиваем интегральную составляющую
end;
-- ПИД - регулятор
G = Kp * (Err + (1/Ti)*Int_sum + Td*dErr);
-- проверка выхода за диапазон
if G < 0 then
G = 0;
end
if G > Limit then
G = Limit;
end
prevErr = Err; -- запомнить предыдущую ошибку для след. скана
WriteReg("PID_out", G); -- Выход ПИД (D0@Тест) записать в регистр
else
if (now - TimeStamp > SampleTime ) then -- проверка начала цикла работы
TimeStamp = 0;
end
end
end
Данный алгоритм является типовым для применения в ПЛК. Поскольку регулятор выполняется через равные интервалы времени, т.е. дифф. и инт. составляющие всегда вычисляются в одном мастштабе времени, поэтому делить и умножать их на время для получения производной и интеграла необязательно, можно подбирать постоянные времени Ti, Td. В данном алгоритме Ti является обратной величиной (чем больше ее величина, тем меньше вклад интегральной ошибки)
Счетчик моточасов
Счетчик моточасов удобен для автоматической генерации сообщения о необходимости регламенных работ для узла оборудования, смены ведущего насоса в насосной группе для выравнивания наработки и т.п.
Пример реализации счетчика моточасов на Lua в WebHMI (программа выполняется в каждом скане):
-- глобальные переменные, сохраняются между вызовами скрипта
run_state = false; -- для запоминания текущего состояния
function main (userId)
-- локальные переменные
local check_mask = tonumber("0000100000000000",2); -- маска для проверки бита вращения в частотном приводе FC 51 Danfoss
local run_status = (bit.band(GetReg(109),check_mask) ~= 0); -- результат проверка как переменная типа bool
local now = os.time(); -- текущее время системы
local time_diff = 0; -- разница во времени между текущим временем и временем последнего вызова
-- ловим фронт события включения механизма для инициализации
if (not run_state) and run_status then
WriteReg("P43StartTime", now); -- Время старта привода №П43
-- считаем время
if run_state then
time_diff = (now - GetReg("P43StartTime")); -- посчитать разницу времени
WriteReg("P43RunTime", GetReg("P43RunTime")+time_diff); -- увеличить счетчик моточасов
WriteReg("P43StartTime", now); -- переписать начальную точку времени
end
run_state = run_status;
end
Регистры хранения моточасов и метки времени нужно делать энергонезависимыми.
Отладка сложных скриптов
Рекомендуюется разбивать сложные скрипты на более простые и часто используемые функции, которые можно использовать повторно. Эти функции можно отладить в пользовательском скрипте, выделив часть внутренних регистров под входные переменные, часть под выходные. Эти же регистры вынести на дешборд, вместе с кнопокой запуска отлаживаемого скрипта. Тогда меняя входные наборы данных удобно видеть тут же результат выполнения. Желательно сразу ставить после ключевых моментов логики отладочную печать функциями TRACE, c DEBUG c номером скрипта или названием функции. Тогда эти фрагменты удобно искать и анализировать в коммуникационном логе.
Формирование суточных, недельных и т.п. отчетов
Для оптимизации производительности устройства данные для отчетов целесообразно регистрировать "в потоке". В WebHMI для этого есть механизм событий, которые после регистрации фактически дают готовые отчеты.
- Необходимые переменные, скрипты и события -
- Необходимые переменные, скрипты и события -
- скрипт, выдающий номер дня, месяца, недели, квартала и т.п. из системного времени во внутренний регистр "день недели", "час", "секунда"
- скрипт подсчета разницы "текущие показание - предыдущие", и фиксации результата в регистре "счетчик" (скрипт работает также по изменению регистра "минута")
- регистр-флаг генерации события, однократного, достаточного для записи текущего посчитанного счетчика и метки времени
- скрипт, снимающий флаг, когда есть событие (т.е. данные уже зафиксированы)'
- событие для регистрации
Для демонстрации можно использовать пример, в котором на каждой секунде "виртуальный" счетчик увеличивается на 2. Другие скрипты реализуют поминутную запись разницы его показаний в отчет. Для добавления других параметров, необходимо по аналогии добавить их внутрь соответствующих скриптов и событий. Скрипт, генерирующий номера минут:
function main (userId)
-- получить номер минуты
local min = os.date("%M",os.time());
-- записать во внутренний регистр
WriteReg("minute", min); -- Минуты
end
Скрипт, работающий на изменение регистра "minute":
function main (userId)
local current = GetReg("FlowMeter"); -- текущие показания
local prevMin = GetReg("pMinTotal"); -- предыдущее значение
local cnt = current - prevMin; -- посчитать разницу
WriteReg("MinuteFlow", cnt); -- записать счетчик во внутр. регистр. он используется в событии
WriteReg("pMinTotal", current); -- записать текущее в предыдущее для след. периода
-- поднять флаг для отчета
WriteReg("EventFlag", 1); -- Установить флаг для события
end
Скрипт, снимающий флаг и соответственно событие после записи:
function main (userId)
-- проверяем выполняется ли событие
local event_state = (GetReg("ES1") == 1); -- Событие 1 выполняется (ES1@Internal register)
-- и сбрасываем флаг
if event_state then
WriteReg("EventFlag", 0); -- Флаг минутного отчета
end
end
Настройки события для данного отчета:
Результатом будет являться отчет такого вида:
При поминутной регистрации данные результаты получаться, если время скана будет меньше чем секунда. Тогда "виртуальный" электросчетчик не пропустит своих секунд.
Преобразование числа в битовую табличку
В lua нет встроенной операции получения строкового представления двоичного числа. Однако эта функция очень полезна в пользовательсиких протоколах и др. задачах, где требуется перераспределить или каким то образом обработать отдельные биты числа. Текст варианта такой функции:
function getBits(input_num,length)
local tab = {}; -- пустая табличка для ответа
local max_i = length - 1;
local remainder = input_num; -- остаток порязрядного взешивания
for i=max_i,0,-1 do
if remainder - 2^i >= 0 then
table.insert(tab, "1") ;
remainder = remainder - 2^i;
else
table.insert(tab, "0") ;
end
end
return tab;
end -- getBits
Далее можно переставить нужные биты местами и т.п. и получить нужное число. Ниже приведен пример перестановки битов в ответе счетчика расходомера производства Асвега-У
function main (userId)
local input_num = GetReg(3); -- прочитать регистр с числом
local bitTable = getBits(input_num,32); -- таблица для обработки результата
local tmp_bit = ""; -- вспомогательный бит
tmp_bit = bitTable[1];
bitTable[1] = bitTable[9];
bitTable[9] = tmp_bit;
-- конкатенация готовой таблицы в строку и преобразование в число из строки двоичного представления
WriteReg(1, tonumber(table.concat(bitTable),2));
end