Полезные советы — различия между версиями

Материал из WebHMI Wiki
Перейти к: навигация, поиск
(Формирование суточных, недельных и т.п. отчетов)
(Формирование суточных, недельных и т.п. отчетов)
Строка 275: Строка 275:
 
[[Файл:Rep example.png | 800 px]]<br>
 
[[Файл:Rep example.png | 800 px]]<br>
 
При поминутной регистрации данные результаты получаться, если время скана будет меньше чем секунда. Тогда "виртуальный" электросчетчик не пропустит своих секунд.
 
При поминутной регистрации данные результаты получаться, если время скана будет меньше чем секунда. Тогда "виртуальный" электросчетчик не пропустит своих секунд.
 +
 +
== Преобразование числа в битовую табличку ==
 +
 +
В lua нет встроенной операции получения строкового представления двоичного числа. Однако эта функция очень полезна в пользовательсиких протоколах и др. задачах, где требуется перераспределить или каким то образом обработать отдельные биты числа.
 +
Текст варианта такой функции:
 +
 +
<syntaxhighlight lang = "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
 +
</syntaxhighlight>

Версия 11:19, 15 марта 2017

Запуск скрипта по фронта или срезу дискретного сигнала

Скрипт нужно вызываеть по изменению регистра. Внутри скрипта нужно сделать проверку текущего состояния этого регистра и выполнять соответветсующие действия либо по фронту (текущее состояние =1), либо по срезу (=0). Пример скрипта:

Rising edge.png

Создание переключателя

Элемент слайдер можно использовать для управления типа открыть- закрыть, вкл.-выкл. и т.п. Для этого нужно "привязать" его к битовому регистру и указать опцию "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

Настройки события для данного отчета:
Rep example2.png
Rep example3.png


Результатом будет являться отчет такого вида:
Rep example.png
При поминутной регистрации данные результаты получаться, если время скана будет меньше чем секунда. Тогда "виртуальный" электросчетчик не пропустит своих секунд.

Преобразование числа в битовую табличку

В 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