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

Материал из WebHMI Wiki
Перейти к: навигация, поиск
(Новая страница: «-- counting time if run_state then time_diff = (now - GetReg("P43StartTime")); -- get time difference WriteReg("P43RunTime", GetReg("P43RunTime")+tim…»)
 
(не показана одна промежуточная версия этого же участника)
Строка 1: Строка 1:
 
== Useful programs ==
 
== Useful programs ==
 
=== Running script upon rising or falling edge of the signal ===
 
=== Running script upon rising or falling edge of the signal ===
 +
<languages/>
 
The script should be defined as running upon register change.  
 
The script should be defined as running upon register change.  
 
Inside the script, there have to be checking of the current state and  espective actions on rising edge (current state = 1) or falling edge (=0).
 
Inside the script, there have to be checking of the current state and  espective actions on rising edge (current state = 1) or falling edge (=0).
Строка 287: Строка 288:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Регистры хранения моточасов и метки времени нужно делать энергонезависимыми.
+
The timekeeping registers and time stamps should be made non-volatile.
  
=== Алгоритм чередования по времени (циркуляция с функцией АВР) ===
+
=== Time circulation algorithm (together with redundancy function) ===
Данный алгоритм используется в системах, где необходимо чередовать работу механизмов (насосы, вентиляторы, кондиционеры) по времени, либо по моточасам (наработке).
+
This algorithm is used in systems where it is necessary to alternate the operation of mechanisms (pumps, fans, air conditioners) over time, or on the running hours count.
Для примера используется набор из 2-х установок, которые необходимо чередовать по времени. Если на какой-то установке возникает ошибка, тогда алгоритм начинает работать только по рабочей (функция АВР).
+
For example, a set of 2 units is used, which must be alternated in time. If an error occurs on some unit, then the algorithm starts working only on the working (redundancy function).
Пример настройки необходимых регистров приведены ниже:
+
An example of setting the required registers is given below:
 
[[Файл:Circ algorithm regs.png | 800 px|left]] <br clear = all>
 
[[Файл:Circ algorithm regs.png | 800 px|left]] <br clear = all>
Для простоты и наглядности лучше разбивать скрипты на функциональные модули, которые можно быстро проанализировать и расположить их выполнение в нужном порядке в списке программ.  
+
For simplicity and clarity, it is better to split the scripts into functional modules that can be quickly analyzed and placed in the desired order in the program list.
Первый скрипт смотрит на ошибки и если их нет, чередует работу кондиционеров по времени.  
+
The first script looks at the errors and if they do not exist, the units (air conditioners) alternate in time.
 
<syntaxhighlight lang = "lua">
 
<syntaxhighlight lang = "lua">
CIRCULATION_TIME = 30; -- на время отладки время = 30 сек.  
+
CIRCULATION_TIME = 30; -- for testing purposed circulation time = 30 sec.  
  
 
function main (userId)
 
function main (userId)
Строка 304: Строка 305:
 
   если есть ошибка на одном из кондиционеров, то он исключается из чередования  
 
   если есть ошибка на одном из кондиционеров, то он исключается из чередования  
 
   если ошибки на обоих, то стоим на месте  
 
   если ошибки на обоих, то стоим на месте  
 +
  if there are no errors, then alternate the work on the time of circulation
 +
  If there is an error on one of the air conditioners, it is excluded from the circulation
 +
  if there are errors on both, then we stand in place
 
   --]]
 
   --]]
local acError1, acError2 = (GetReg("acError1") == 1), (GetReg("acError2") ==1) ; -- Кондиционер 1 ошибка (DS101@webmi)
+
local acError1, acError2 = (GetReg("acError1") == 1), (GetReg("acError2") ==1) ; -- error on a/c #1  
local switchTime = GetReg("switchTime"); -- Время следующего переключения (DS103@webmi)
+
local switchTime = GetReg("switchTime"); -- next switch over time (DS103@webmi)
local now = os.time();
+
local now = os.time()
local curActiveAC = GetReg("activeAC"); -- Активный кондиционер (DS100@webmi)
+
local curActiveAC = GetReg("activeAC") -- active a/c (DS100@webmi)
  
 
if (not acError1) and (not acError2) then  
 
if (not acError1) and (not acError2) then  
     -- работаем по циркуляции
+
     -- working with circulation
 
     if (now >= switchTime) then  
 
     if (now >= switchTime) then  
 
         if (curActiveAC == 1) then  
 
         if (curActiveAC == 1) then  
Строка 331: Строка 335:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Второй скрипт смотрит какой кондиционер сейчас активен, и выполняет необходимые действия. В скрипте это просто отладочная печать, но здесь могут быть команды управления инфракрасным передатчиком для выдачи нужной команды, запись в журнал сообщений и переключении и т.п.
+
The second script looks at which a/c is now active, and performs the necessary actions. In a script, this is just debugging, but there may be commands for controlling the infrared transmitter for issuing the desired command, writing to the message log and switching, etc.
  
 
<syntaxhighlight lang = "lua">
 
<syntaxhighlight lang = "lua">
 
function main (userId)
 
function main (userId)
 
   --[[
 
   --[[
   включает выбранный кондицинер, в зависимости от указателя
+
   turn selected a/c on dependting on pointer
 
   --]]
 
   --]]
   local pointer = GetReg("activeAC"); -- Активный кондиционер (DS100@webmi)
+
   local pointer = GetReg("activeAC"); -- active a/c (DS100@webmi)
  
 
   if (pointer==0) then  
 
   if (pointer==0) then  
       DEBUG("все выключаем");
+
       DEBUG("all off ");
       return 0;
+
       return 0
 
   elseif  
 
   elseif  
 
       (pointer==1) then  
 
       (pointer==1) then  
           DEBUG("включаем 1-й кондиционер");
+
           DEBUG("turn on a/c #1")
 
   else  
 
   else  
           DEBUG("включаем 2-й кондиционер");
+
           DEBUG("turn on a/c #2")
 
        
 
        
 
   end -- if  
 
   end -- if  
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
Также здесь нужен будет скрипт, который будет выставлять флаги ошибок работы по неким условиям, читаю состояние автоматов защиты, регистры ошибок по интерфейсу и т.п.
+
Also here you need a script that will rise error flags upon on certain conditions, read the status of the protection devices, the error registers on the interface, and so on.
  
=== 3-х точечное управление клапанами, сервоприводами и др. ===
+
=== 3-point control for a valve or servo  ===
  
3-х точечным называется способ управления позицией клапана, сервопривода, задвижки и т.п., когда для управления приводом используются 3 провода - "общий", "питание - ВВЕРХ", " питание - ВНИЗ". Такие приводы могут быть (а могут и нет) оснащены также концевыми датчиками положения. Иногда при отсутствии датчиков положения и низких требованиях к точности позиционирования может применяться алгоритм, когда привод уезжает вниз или вверх (либо по 1 датчику положения либо подачей одной команды на время превышающее время полного хода клапана), инициализирует координату, а потом отрабатывает заданную позицию.  
+
A 3-point method is used to control the position of the valve, servo, gate valve, etc., when 3 wires are used to control the drive - 'common', 'power UP', 'power - DOWN'. Such drives may or may not be equipped with end position sensors. Sometimes, in the absence of position sensors and low requirements for positioning accuracy, an algorithm can be used when the drive leaves down or up (either one position sensor or one command for a time longer than the full valve travel time), initializes the coordinate, and then runs through the specified position.
<p>Для определения промежуточных положений используется расчетное значение, определяемое из характеристики исполнительного механизма "время полного хода", которое можно определить и экспериментальным путем.  
+
<p> To determine intermediate positions, a calculated value is used, determined from the characteristics of the actuator's 'full stroke time' , which can also be determined experimentally.
Как упоминалось выше, относительно сложные и разветвленные алгоритмы в WebHMI лучше разбить на функционально законченные и простые программы, взаимодействие и синхронизацию между которыми можно делать используя внутренние регистры.</p>
+
As mentioned above, relatively complex and branched algorithms in WebHMI are better to divide into functionally complete and simple programs, the interaction and synchronization between which can be done using internal registers. </p>
 
<br>
 
<br>
Ниже приведен вариант 3-х точечного управления для привода с 2-мя концевыми датчиками.
+
Below one of the examples for 3-point valve control with 2 limit switches is provided
Программа разбита на 6 частей:
+
The program split into 6 parts:
  
 
[[Файл:3-point control.png|800px|left]]
 
[[Файл:3-point control.png|800px|left]]
 
<br clear = all>
 
<br clear = all>
::'''v3 OpenClose Valve Manual''' - скрипт управляет приводом в ручном режиме. Запускается по изменению номера нажатой кнопки с дешборда.  
+
::'''v3 OpenClose Valve Manual''' - The script controls the drive in manual mode. Started by changing the number of the pressed button from the dashboard
::'''v4 Auto Valve Control''' - основной скрипт управления в авт. режиме. Он автоматически выполняет первую инициализацию, и при несовпадении заданной координаты от текущей включает привод в нужном направлении, по достижении позиции останавливает. Также при совпадении заданной позиции как крайней (0,100), скрипт продолжает держать команду, пока не произойдет наезд на концевой выключатель, таким образом выполняя периодическую синхронизацию расчетной позиции с реальной.  
+
::'''v4 Auto Valve Control''' - the main control script in the auto mode. It automatically performs the first initialization, and if the specified coordinate does not match the current one, it turns the drive in the desired direction, upon reaching the position, stops. Also, if the specified position is the same as the limit positions (0,100), the script continues to hold the command until the physical limit switch reached, thereby performing a periodic synchronization of the calculated position with the real position.
::'''v5 LatchStart_Time''' - скрипт "захвата" начальной позиции движения, выполняется по изменению флага inMotionFlag, который устанавливают предыдущие программы v3 или v4.  
+
::'''v5 LatchStart_Time''' - script 'capturing' the initial position of the movement, is executed by changing the flag inMotionFlag, which is set by the previous programs v3 or v4.
::'''v6 CalcNewPosition''' - выполняется 1 раз в сек. (по изменению системного времени), скрипт работает, пока есть флаг inMotionFlag и пересчитывает текущее время, пройденное с начала движения в текущую позицию (которая в свою очередь используется программой v4).
+
::'''v6 CalcNewPosition''' - is executed 1 time per second. (on system time change), the script works as long as there is an inMotionFlag flag and re-counts the current time passed from the beginning of the movement to the current position (which in turn is used by the v4 program).
::'''v7 Limit sw reaction''' - по достижении конечных выключателей снимает команды, а также снимает запрос от кнопок ручного управления.  
+
::'''v7 Limit sw reaction''' -When the limit switches are reached, it removes commands, and also removes the request from the manual control buttons.
::'''v8 Drop Cur Cmd on Manual mode''' - при переходе в ручной режим выключает текущую команду.
+
::'''v8 Drop Cur Cmd on Manual mode''' - when switching to manual mode, turns off the current command.
  
В начале программ обозначен порядок требуемый порядок выполнения (v3..v8), поскольку необходимый порядок выполнения программ может измениться нежелательным образом, например при сортировке программ и неточном "перетаскивании" программ в списке. Таким образом префикс напоминает о нужном порядке, также он может отражать функциональную принадлежность скрмпта - "v" Valves, "t- temperature control и т.д. чтобы удобнее ориентироваться в больших списках и ссылаться на него.
+
At the beginning of the program names, the order of the required execution order (v3..v8) is indicated, since the necessary order of program execution can change undesirably, for example, when sorting programs and inaccurate 'dragging' programs in the list. Thus, the prefix reminds you of the desired order, it can also reflect the functionality of the script - 'v' Valves, 't' - temperature control, etc. It is more convenient to navigate in large lists and refer to it.
<p>Исходные тексты скриптов:</p>
+
<p> Text of the programs:</p>
 
'''v3'''
 
'''v3'''
 
<syntaxhighlight lang = lua>
 
<syntaxhighlight lang = lua>
------- РУЧНОЕ УПРАВЛЕНИЕ С КНОПОК НА ЭКРАНЕ OpenClose Valve Manual -------------------
+
------- MANUAL CONTROL FROM SCREEN BUTTONS OpenClose Valve Manual -------------------
 
function main (userId)
 
function main (userId)
 
    
 
    
   local button_value = GetReg("valveMan_code"); -- номер от кнопок нажатия ОТКР / ЗАКР.
+
   local button_value = GetReg("valveMan_code"); -- key code from buttons open / close
   local manual_mode = (GetReg("auto_mode") == 0) ; -- АВТ. РЕЖИМ ВКЛЮЧЕН (ds1176@WH Global)
+
   local manual_mode = (GetReg("auto_mode") == 0) ; -- auto mode is on (ds1176@WH Global)
  
 
if manual_mode then
 
if manual_mode then
  
 
     if (button_value == 10) then  
 
     if (button_value == 10) then  
         WriteReg("openCmd", 1); -- Команда "Открытие" (DS1007@WH Valve control)
+
         WriteReg("openCmd", 1); -- open command  (DS1007@WH Valve control)
 
         WriteReg("closeCmd", 0);
 
         WriteReg("closeCmd", 0);
 
     elseif (button_value == 5) then  
 
     elseif (button_value == 5) then  
           WriteReg("closeCmd", 1); -- Команда "Закрытие" (DS1008@WH Valve control)
+
           WriteReg("closeCmd", 1); -- close command (DS1008@WH Valve control)
           WriteReg("openCmd", 0);
+
           WriteReg("openCmd", 0)
 
     else
 
     else
         -- на недопустимое значение снимаем команды
+
         -- invalid value - switch every command off
 
                                     DEBUG("Read button value as "..tostring(button_value));
 
                                     DEBUG("Read button value as "..tostring(button_value));
         WriteReg("openCmd", 0);
+
         WriteReg("openCmd", 0)
         WriteReg("closeCmd", 0);
+
         WriteReg("closeCmd", 0)
         WriteReg("inMotion_flag",0); --
+
         WriteReg("inMotion_flag",0)
 
     end --if  
 
     end --if  
 
      
 
      
     -- Фиксация начальной позиции, времени начала движения для скрпита обсчета текущей позиции
+
     -- capture start position and start time
 
     if (button_value == 10) or (button_value == 5) then  
 
     if (button_value == 10) or (button_value == 5) then  
         WriteReg("startPosition", GetReg("curPosition")); -- Начальная позиция движения  (DS1020@WebHMI 3point control)
+
         WriteReg("startPosition", GetReg("curPosition")); -- starting position (DS1020@WebHMI 3point control)
         WriteReg("startPosTime", os.time()); -- Метка времени начала движения  (DS1012@WebHMI 3point control)
+
         WriteReg("startPosTime", os.time()); -- start time stamp (DS1012@WebHMI 3point control)
         WriteReg("inMotion_flag",1);
+
         WriteReg("inMotion_flag",1)
 
     end
 
     end
 
end -- manual_mode
 
end -- manual_mode
Строка 411: Строка 415:
 
'''v4'''
 
'''v4'''
 
<syntaxhighlight lang = lua>
 
<syntaxhighlight lang = lua>
-- АВТ. УПРАВЛЕНИЕ ДВИЖЕНИЕМ КЛАПАНА Auto Valve Control ----  
+
                -- AUTO VALVE CONTROL ----  
 
function main (userId)
 
function main (userId)
 
      
 
      
   local now = os.time(); -- текущее время
+
   local now = os.time(); -- current time
   local timeStmp = GetReg("endPosTime"); -- Метка времени окончания движения  (D1001@WebHMI Kitothemr)
+
   local timeStmp = GetReg("endPosTime"); -- end position time stamp (D1001@WebHMI Kitothemr)
   local auto_mode = (GetReg("auto_mode")==1);
+
   local auto_mode = (GetReg("auto_mode")==1)
 
    
 
    
 
if auto_mode then
 
if auto_mode then
   -- проверка на самое первое включение, когда метка времения = 0  
+
   -- check if very first run = 0  
 
   if (timeStmp == 0) and (not home_mode) then  
 
   if (timeStmp == 0) and (not home_mode) then  
       WriteReg("homingBit",1);
+
       WriteReg("homingBit",1)
 
                                 DEBUG("timeStmp = 0, homing needed! ");
 
                                 DEBUG("timeStmp = 0, homing needed! ");
 
   end  
 
   end  
 
    
 
    
   local home_mode = (GetReg("homingBit") == 1); -- Бит репозиционирования  (D1006@WebHMI Kitothemr)
+
   local home_mode = (GetReg("homingBit") == 1); -- homing bit (D1006@WebHMI Kitothemr)
   local full_close = GetReg("LLsw"); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)
+
   local full_close = GetReg("LLsw"); -- full close limit switch (D1010@WebHMI Kitothemr)
  
 
   if home_mode then  
 
   if home_mode then  
       -- едем вниз до упора чтобы определить позицию
+
       -- go down till close switch to determine position
 
               DEBUG("giving home_mode close cmd ! ");
 
               DEBUG("giving home_mode close cmd ! ");
       WriteReg("closeCmd", 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
+
       WriteReg("closeCmd", 1)  
 
        
 
        
 
           if (full_close == 1) then  
 
           if (full_close == 1) then  
                   DEBUG("full close ! ");
+
                   DEBUG("full close ! ")
 
                  
 
                  
 
                 WriteReg("closeCmd",0);
 
                 WriteReg("closeCmd",0);
                 WriteReg("endPosTime", now); -- чтобы избежать повторного home_mode
+
                 WriteReg("endPosTime", now); -- to avoid repeated homing
 
                 WriteReg("homingBit",0);
 
                 WriteReg("homingBit",0);
                 WriteReg("curPosition", 0); -- Текущее положение клапана (D1005@WebHMI Kitothemr)
+
                 WriteReg("curPosition", 0); -- current position
 
           end
 
           end
       return 0; -- выходим пока не закончим выход в 0
+
       return 0 -- exit while not the end
 
   end -- home mode  
 
   end -- home mode  
 
    
 
    
   -- ОСНОВНАЯ ЧАСТЬ ----  
+
   -- MAIN PART ----  
 
                           DEBUG("-------- auto valve control ");
 
                           DEBUG("-------- auto valve control ");
 
    
 
    
   local openingStatus = (GetReg("openCmd") == 1); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
+
   local openingStatus = (GetReg("openCmd") == 1); --  
   local closingStatus = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
+
   local closingStatus = (GetReg("closeCmd") == 1); --
   local inMotionFlag = openingStatus or closingStatus;
+
   local inMotionFlag = openingStatus or closingStatus
 
    
 
    
   local target_dir= 0; -- знак движения
+
   local target_dir= 0; -- moving sign
   local posSV = GetReg("posSetpoint"); -- Уставка положения клапана (D1000@WebHMI Kitothemr)
+
   local posSV = GetReg("posSetpoint"); -- valve setpoint
   local posPV = GetReg("curPosition"); -- текущее положение
+
   local posPV = GetReg("curPosition"); -- current position
 
    
 
    
     -- определить начальные время и позицию
+
     -- determine start position and time
 
   local startTime = GetReg("startPosTime");
 
   local startTime = GetReg("startPosTime");
 
   local startPos = GetReg("startPosition");
 
   local startPos = GetReg("startPosition");
   local pathdone = 0; -- обсчета пройденного пути
+
   local pathdone = 0; -- path done value
 
    
 
    
   local OpenSw = GetReg("HLsw"); -- Датчик полного открытия (D1009@WebHMI Kitothemr)
+
   local OpenSw = GetReg("HLsw"); -- full open switch
   local CloseSw = GetReg("LLsw"); -- Датчик полного закр.  (D1009@WebHMI Kitothemr)
+
   local CloseSw = GetReg("LLsw"); -- full close switch
 
    
 
    
 
                             DEBUG("posPV  "..tostring(posPV).." pos SV"..tostring(posSV));
 
                             DEBUG("posPV  "..tostring(posPV).." pos SV"..tostring(posSV));
       -- Уже едем
+
       -- already in motion
 
       if inMotionFlag then  
 
       if inMotionFlag then  
                             DEBUG("Мы уже в движении ");
+
                             DEBUG("in motion now")
         -- доехали вниз ?
+
         -- full close ?
 
           if (closingStatus) and (posPV <= posSV) then  
 
           if (closingStatus) and (posPV <= posSV) then  
               -- если задан 0 как позицию, то едем до нижнего конечника, чтобы синхронизировать позицию с реальным положением
+
               -- if 0 set then wait close limit switch, auto calibrate
               if (posPV == 0) and (not CloseSw) then  
+
               if (posSV == 0) and (not CloseSw) then  
 
                   WriteReg("closeCmd", 1);
 
                   WriteReg("closeCmd", 1);
                   return 0; -- выходим, команду сбросит скрипт Limit Sw Reaction
+
                   return 0 -- cmd will be off in limit switch reaction script
 
               end  
 
               end  
                   WriteReg("closeCmd", 0);
+
                   WriteReg("closeCmd", 0)
                   WriteReg("inMotion_flag",0);
+
                   WriteReg("inMotion_flag",0)
 
             end  
 
             end  
         -- доехали вверх? 
+
         -- upper limit
 
           if (openingStatus) and (posPV >= posSV) then  
 
           if (openingStatus) and (posPV >= posSV) then  
               -- если задан верхний предел, то едем до конечника чтобы синхронизировать позицию с реальным положением
+
               -- same as for close
                   if (posPV == 100) and (not OpenSw) then  
+
                   if (posSV == 100) and (not OpenSw) then  
 
                       WriteReg("openCmd", 1);
 
                       WriteReg("openCmd", 1);
                       return 0; -- выходим, команду сбросит скрипт Limit Sw Reaction
+
                       return 0; -- cmd will be off in limit switch reaction script
 
                   end  
 
                   end  
               WriteReg("openCmd", 0);
+
               WriteReg("openCmd", 0)
               WriteReg("inMotion_flag",0);
+
               WriteReg("inMotion_flag",0)
 
           end  
 
           end  
      -- позиция не доехали ?
+
    else -- was stopped
      else
+
 
           if (posSV ~= posPV) then  
 
           if (posSV ~= posPV) then  
               -- уставка поменялась ?
+
               --  
             target_dir = (posSV - posPV)/math.abs(posSV - posPV); -- определить знак
+
             target_dir = (posSV - posPV)/math.abs(posSV - posPV); -- get direction
 
                                             DEBUG_("target_dir =  "..tostring(target_dir));
 
                                             DEBUG_("target_dir =  "..tostring(target_dir));
 
               if (target_dir > 0) then  
 
               if (target_dir > 0) then  
Строка 500: Строка 503:
 
                                             DEBUG_("will close...");
 
                                             DEBUG_("will close...");
 
               end
 
               end
             WriteReg("inMotion_flag", 1); -- уст. флаг для скриптов работающих по началу движения                                   
+
             WriteReg("inMotion_flag", 1); -- set the start flag for other scripts
 
           end
 
           end
 
   end -- auto mode motion control  
 
   end -- auto mode motion control  
Строка 511: Строка 514:
 
'''v5'''
 
'''v5'''
 
<syntaxhighlight lang = lua>
 
<syntaxhighlight lang = lua>
-- Захват позиции LatchStart_Time ---
+
-- LatchStart_Time ---
 
function main (userId)
 
function main (userId)
 
      
 
      
 
                 DEBUG("Entered  latch start time and position");
 
                 DEBUG("Entered  latch start time and position");
  -- Add your code here
+
   local flag = (GetReg("inMotion_flag") == 1) ; -- in motion flag @WebHMI 3point control
   local flag = (GetReg("inMotion_flag") == 1) ; -- Флаг "в движении" (DS1016@WebHMI 3point control)
+
 
                 DEBUG("motion flag = "..tostring(flag));
 
                 DEBUG("motion flag = "..tostring(flag));
 
+
   
 
+
 
   if flag then  
 
   if flag then  
 
                   DEBUG("now flag = 1");
 
                   DEBUG("now flag = 1");
       WriteReg("startPosition", GetReg("curPosition")); -- Начальная позиция движения  (DS1020@WebHMI 3point control)
+
       WriteReg("startPosition", GetReg("curPosition")); -- start position stored (DS1020@WebHMI 3point control)
       WriteReg("startPosTime", os.time()); -- Метка времени начала движения  (DS1012@WebHMI 3point control)
+
       WriteReg("startPosTime", os.time()); -- start time remembered (DS1012@WebHMI 3point control)
 
                  
 
                  
 
   end  
 
   end  
Строка 531: Строка 532:
 
'''v6'''
 
'''v6'''
 
<syntaxhighlight lang=lua>
 
<syntaxhighlight lang=lua>
--- Расчет новой позиции CalcNewPosition
+
--- CalcNewPosition
-- константы
+
-- constants
FULLPATH = 127 ; -- время полного открытия в сек.
+
FULLPATH = 127 ; -- full path time
K = 100 / FULLPATH ; -- коэф. пересчета % открытия в временной интервал
+
K = 100 / FULLPATH ; -- time to position k
  
 
function main (userId)
 
function main (userId)
 
      
 
      
 
                             DEBUG("Entered #5 script, calc. cur position");
 
                             DEBUG("Entered #5 script, calc. cur position");
   local now = os.time(); -- текущее время
+
   local now = os.time(); --  
   local open_sts = (GetReg("openCmd") == 1); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
+
   local open_sts = (GetReg("openCmd") == 1)  
   local close_sts = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
+
   local close_sts = (GetReg("closeCmd") == 1)
   local inMotionFlag = (open_sts or close_sts) ; -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
+
   local inMotionFlag = (open_sts or close_sts)  
 
                             DEBUG("in motion flag = "..tostring(inMotionFlag));
 
                             DEBUG("in motion flag = "..tostring(inMotionFlag));
 
    
 
    
   local cur_dir = 0; -- знак направления движения
+
   local cur_dir = 0
   local posPV = GetReg("curPosition"); -- текущая позиция
+
   local posPV = GetReg("curPosition");  
 +
 
  
-- определить начальные время и позицию
 
 
   local startTime = GetReg("startPosTime");
 
   local startTime = GetReg("startPosTime");
 
   local startPos = GetReg("startPosition");
 
   local startPos = GetReg("startPosition");
 
   local pathdone = 0; -- пройденный путь  
 
   local pathdone = 0; -- пройденный путь  
 
    
 
    
  -- Уже едем
 
 
   if inMotionFlag then  
 
   if inMotionFlag then  
      -- определяем направление движения
 
 
       if open_sts then  
 
       if open_sts then  
 
             cur_dir = 1;
 
             cur_dir = 1;
Строка 561: Строка 560:
 
             cur_dir = -1;
 
             cur_dir = -1;
 
       end  
 
       end  
   pathdone = GetPathDone(startTime, now); -- вычисляем пройеднный путь
+
   pathdone = GetPathDone(startTime, now)
   varPos = startPos + cur_dir*pathdone; -- теперь текущую координату
+
   varPos = startPos + cur_dir*pathdone
 
        
 
        
      -- проверка выхода текущей позиции за допустимые границы
 
 
       if (varPos < 0) or (varPos > 100) then  
 
       if (varPos < 0) or (varPos > 100) then  
 
                             DEBUG("new varPos calculaed outside limits, cur value "..tostring(varPos));
 
                             DEBUG("new varPos calculaed outside limits, cur value "..tostring(varPos));
Строка 572: Строка 570:
 
        
 
        
 
                       DEBUG("startPos , pathdone = , new varPos  "..tostring(startPos).." "..tostring(pathdone).." "..tostring(varPos));
 
                       DEBUG("startPos , pathdone = , new varPos  "..tostring(startPos).." "..tostring(pathdone).." "..tostring(varPos));
       WriteReg("curPosition", varPos); -- пишем текущую позицию
+
       WriteReg("curPosition", varPos); --  
 
   end -- auto mode motion control  
 
   end -- auto mode motion control  
 
end -- main  
 
end -- main  
  
------------- Функция вычисления остатка движения ---------------     
+
------------- calculates path done ---------------     
 
function GetPathDone(startTime, curTime)   
 
function GetPathDone(startTime, curTime)   
    -- смотрим на тек. время и высчитываем %
+
 
 
     local curPos = (curTime - startTime) * K;
 
     local curPos = (curTime - startTime) * K;
 
     local remainder = curPos - math.floor(curPos);
 
     local remainder = curPos - math.floor(curPos);
Строка 596: Строка 594:
 
      
 
      
 
   local now = os.time();
 
   local now = os.time();
   local open_sts = (GetReg("openCmd") == 1); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
+
   local open_sts = (GetReg("openCmd") == 1)
   local close_sts = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
+
   local close_sts = (GetReg("closeCmd") == 1)
 
    
 
    
   local full_close = (GetReg("LLsw") == 1); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)
+
   local full_close = (GetReg("LLsw") == 1)
   local full_open = (GetReg("HLsw") == 1); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)
+
   local full_open = (GetReg("HLsw") == 1)
 
    
 
    
 
   if (full_open or full_close) then  
 
   if (full_open or full_close) then  
    --  проверить наезд на конечники
+
 
 
           if full_open then
 
           if full_open then
              -- посчитать новую позицию
 
 
               WriteReg("curPosition", 100);
 
               WriteReg("curPosition", 100);
 
               WriteReg("openCmd", 0);
 
               WriteReg("openCmd", 0);
               WriteReg("valveMan_code", 0); -- для ручнго режима
+
               WriteReg("valveMan_code", 0)
 
           end  
 
           end  
 
            
 
            
 
           if full_close then  
 
           if full_close then  
               WriteReg("curPosition", 0);
+
               WriteReg("curPosition", 0)
               WriteReg("closeCmd", 0);
+
               WriteReg("closeCmd", 0)
               WriteReg("valveMan_code", 0); -- для ручного режима
+
               WriteReg("valveMan_code", 0)
 
           end  
 
           end  
 
            
 
            
       WriteReg("inMotion_flag", 0);
+
       WriteReg("inMotion_flag", 0)
       WriteReg("endPosTime", now); -- Метка времени окончания движения  (DS1001@WH Valve control)
+
       WriteReg("endPosTime", now)
 
      
 
      
 
   end  
 
   end  
 
    
 
    
   -- индикация соcтояния привода
+
   -- code of current state
 
   if (not open_sts) and (not close_sts) then  
 
   if (not open_sts) and (not close_sts) then  
       WriteReg("valveStatus", 0); -- Стоим
+
       WriteReg("valveStatus", 0); -- stopped
 
   elseif open_sts then  
 
   elseif open_sts then  
       WriteReg("valveStatus", 1); -- Открытие
+
       WriteReg("valveStatus", 1); -- opening
 
   else  
 
   else  
       WriteReg("valveStatus", 2); -- Закрытие
+
       WriteReg("valveStatus", 2); -- closing
 
   end  
 
   end  
 
    
 
    
Строка 638: Строка 635:
 
------ v8 Drop Cur Cmd on Manual mode ----  
 
------ v8 Drop Cur Cmd on Manual mode ----  
 
function main (userId)  
 
function main (userId)  
     local auto_on = (GetReg("auto_mode")==1); -- работаем по изменению регистра режима
+
     local auto_on = (GetReg("auto_mode")==1); --  
 
      
 
      
 
     if not auto_on then  
 
     if not auto_on then  
 
                                     DEBUG_("Dropped cmds in manua mode ! ");
 
                                     DEBUG_("Dropped cmds in manua mode ! ");
         WriteReg("openCmd", 0);
+
         WriteReg("openCmd", 0)
         WriteReg("closeCmd", 0);
+
         WriteReg("closeCmd", 0)
         WriteReg("inMotion_flag",0);
+
         WriteReg("inMotion_flag",0)
 
     end  
 
     end  
     prev_mode = auto_on;
+
     prev_mode = auto_on
 
end -- main   
 
end -- main   
  
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Битовые операции ===
+
=== Bit operation ===
  
 
==== Удобные битовые операции ====
 
==== Удобные битовые операции ====

Текущая версия на 12:25, 23 марта 2018

Useful programs

Running script upon rising or falling edge of the signal

Другие языки:
English • ‎русский

The script should be defined as running upon register change. Inside the script, there have to be checking of the current state and espective actions on rising edge (current state = 1) or falling edge (=0). Example:

Rising edge.png

Timer with ON-delay (TON)

TON timer counts while input = 1, and after delay time sets output to "1".

-- globals
TIMER_DELAY = 20 -- timer delay 20 sec. 
tmrStartTime = 0 -- timer start time

function main (userId)
  -- local vars
local now = os.time(); -- current time
local startBit = (GetReg("tmrStartBit") == 1) -- just bit (D301@WebHMI)

-- CHECKING CONDITION ----------
if not startBit then
    tmrStartTime = now -- here start time is stored 
    WriteReg("TON_out", 0) -- timer output bit register with alias"TON_out"
    return 0 ;
else 
    if (now - tmrStartTime) > TIMER_DELAY then
      -- end of countdown actions
            WriteReg("TON_out", 1)
    end  -- if  
end -- if 
end -- main

Alarming (sound, relya, sms, viber, telegram) about connection error

It is possible to analyze the scan time by the script and if it exceeds the acceptable limit, signal it in different ways. Below is an example of processing a large scan time with signaling to the message buffer and Viber.

cntdownFlag = false -- countdown flag 
timeStmp = 0  -- метка времени time stamp
msgSent = false; -- message sent flag

function main (userId)
  -- reading inputs
  local scan = GetReg(34); -- scan time 
  local c0 = GetReg(42); -- bad connection number 
  local SCANLIMIT = GetReg(886); -- scan time limit 
  local SCANDELAY = GetReg(887); -- error reaction delay 
  -- 
  if (scan == nil)  or (c0 == nil) or (SCANLIMIT == nil) or (SCANDELAY == nil) then
   ERROR("scan / c0  was read as nil");
   return 0;
  end 
  -- reading time
  local now = os.time()
  -- шаблон сообщения
  local msg1 = "Scan time long "..tostring(scan).." ".."ms, error in conn. "..tostring(c0);
--
if (scan > SCANLIMIT) then  -- scan above limit
     if not cntdownFlag then 
         cntdownFlag = true
         timeStmp = now
     else 
         if (now - timeStmp) > SCANDELAY then 
             if not msgSent then 
                     AddAlertMessage(msg1)
                      SendViberMessage(398044391, msg1) 
                     msgSent = true
             end 
         end 
     end 
else -- scan is normal 
    if (cntdownFlag == true) and msgSent  then 
        AddInfoMessage("Scan is normal now");
       SendViberMessage(398044391, "Scan is normal now");
       msgSent = false
    end 
    cntdownFlag = false
    
end 

end -- main

In WebHMI there is a buzzer for the sound signal, and 2 output relays, which can be controlled for signaling (send a signal to the signal column or the PLC about the problem).

Moving average

The moving average is useful for smoothing the values ​​of parameters that have noises, pulsations. Algorithm of the moving average:
at the beginning of the filtering, first N values ​​of sample are counted by the arithmetic mean, after reaching the end of the sample, one element is discarded (by dividing the sum by the length of the queue), a new one is added instead of it, and the amount is again divided by the length of the queue.

-- globals 
mav_len = 20;   -- queue length
queue_fill = 0; -- queue filling index 
av_sum = 0;     -- accumulator for the moving average 

function main (userId)

    local in_value, tmp_var, out_value  = GetReg(26), 0, 0; -- reading parameter
   
if (queue_fill < mav_len) then -- queue is not filled
    av_sum = av_sum + in_value; -- accumulating sum 
    queue_fill = queue_fill +1; -- and index 
else                            -- queue is full 
    tmp_var = av_sum / mav_len; -- store one element 
    av_sum = av_sum - tmp_var + in_value; -- subtract and add new 
end
-- 
if (queue_fill == mav_len ) then
      out_value = av_sum / mav_len; -- calc. moving average 
    else
      out_value = av_sum / queue_fill; -- mean average
end
WriteReg("Tout_mav", out_value); -- outside temperature 

end

PID - control

An example of implementing a PID controller in WebHMI:

G_LIMIT = 100 -- ограничение выхода регулятора
--
function main (userId)
  -- локальные переменные 
  local now = os.time()
  local nexTime = GetReg("nextPidTime")
  local CYCLE_TIME = GetReg("pidCycleTime")
  
  -- параметры регулятора 
  local Kp = GetReg("Kp") -- Пропорц. коэф. (DS1400@WH Valve control)
  local Ti = GetReg("Ki") -- Интегральный коэф. (DS1404@WH Valve control)
  local Td = GetReg("Kd") -- Дифф. коэф.  (DS1408@WH Valve control)
  local Err, dErr, iSum_Limit = 0, 0, 0 
  local Int_sum = GetReg("pidIntegral") -- интегральный накопитель 
  local intPart = 0 -- инт. часть формулы 
  local G = GetReg("pidOut") -- выход регулятора
  
  -- процесс 
  local PV = GetReg(1436) -- Мощность (PWR0@Scylar 8 INT)
  local Sp = GetReg("targetPowerSp") 
  
                                    DEBUG_("seconds left for PID cycle = "..tostring(nexTime - now))
    -- условие работы             
  local auto = (GetReg("auto_mode") == 1) -- АВТ. РЕЖИМ ВКЛЮЧЕН (ds1176@WH Global)
  local heatDemand = (GetReg("heatDemand") == 1)
  
if auto then 
  
  if heatDemand then 
                                    -- РЕГУЛЯТОР
          if (now >= nexTime) then 
                                                          DEBUG_("PID compute cycle")
                WriteReg("nextPidTime", now + CYCLE_TIME)
                Err =  Sp - PV -- вычисляем ошибку 
                                                    DEBUG_("sp pv Err = "..Sp.." "..PV.." "..Err)
                dErr = Err - GetReg("pidPrevError") -- вычисляем производную ошибки 
                                                    DEBUG_("dErr = "..dErr)
                    -- проверяем интегральное насыщение 
                iSum_Limit = (G_LIMIT * Ti / Kp) / 5
                                                            DEBUG_("iSum_Limit = "..iSum_Limit)
                
                            -- ПИД - регулятор
                    --проверка на 0 инт. составляющей 
                                                            DEBUG_("prev Int_sum = "..Int_sum)
                                                            if (intPart <= iSum_Limit) and (intPart >= 0.0) then
                        Int_sum = Int_sum + Err -- накапливаем интеграл ошибки 
                                                   DEBUG_("added error to Int_sum ")
                                                            
                    elseif Int_sum < 0 then
                        Int_sum = 0
                    else
                        Int_sum = iSum_Limit -- ограничиваем интегральную составляющую
                    end
                    if (Ti == 0) then 
                        intPart = 0 
                    else 
                        intPart = (1/Ti)*Int_sum
                    end 
                    
                    
                                                            DEBUG_("new Int_part = "..intPart)
                G = Kp * (Err + intPart + Td*dErr)
                                                            DEBUG_("Calculated G as "..G)
                G = Round(G)
                                                            DEBUG_("Rounded G as "..G)
                    -- проверка выхода за диапазон 
                if G < 0 then
                    G = 0  
                end
                if G > G_LIMIT then
                    G = G_LIMIT
                end
                
                WriteReg("pidPrevError", Err) -- запомнить предыдущую ошибку для след. скана
                WriteReg("dErr", dErr) -- запомнить предыдущую ошибку для след. скана
                WriteReg("pidIntegral", intPart) -- запомнить интегральную составляющую
                WriteReg("pidOut", G) 
                WriteReg("posSPinput", G) -- дать уставку на клапан 
        end -- time stamp 
  else 
                DEBUG_("no heatDemand") -- вывести ПИД выход 
      G = 0 
  end -- heatDemand 
                -- DEBUG_("PID_out = "..G) -- вывести ПИД выход 
                WriteReg("pidOut", G) 
                WriteReg("posSPinput", G) -- дать уставку на клапан 
end -- if auto 

end -- main 

-- rounding 
function Round(var)
    
    local integer, fraction = math.modf(var)
    
        if fraction >= 0.5 then 
            integer = integer + 1
        end 
    
    return math.floor(integer)
end 

------ debug printing ------
thisScriptID = 45

function DEBUG_(str)
                --ERROR("entered DEBUG_ in"..thisScriptID.." script");
local i = 0;
local tmp = "";
local id = tostring(thisScriptID);
local debug_id = GetReg("debug_IDs");

local capture_mask = "%s+(%d+)%s+"

while true do 
i,_, tmp = string.find(debug_id,capture_mask,i+1)

    if (i == nil)  then 
        break -- not found 
    end  
    -- найдено
    if (tmp ~= "0") then 
        if (tmp == tostring(thisScriptID)) then 
             DEBUG(str)
             return 0
        end 
    end 
end 

end -- DEBUG_
------------------------------

This algorithm is typical for use in PLCs. Because the regulator is run at regular intervals, i.e. diff. and int. the components are always computed on the same time scale, so it is not necessary to divide and multiply them by time to obtain the derivative and integral, we can select the time constants Ti, Td. In this algorithm, Ti is an inverse quantity (the larger its value, the smaller the contribution of the integral error)

Running hour meter

The hour meter is convenient for automatically generating a message about the need for maintenance for the equipment unit, changing the master pump in group to equalize the operating time, and so on.

An example of the implementation of the running hour meter on Lua in WebHMI (the program runs in each scan):

-- globals
run_state = false; -- to store current state 
function main (userId)
  -- locals
  local check_mask = tonumber("0000100000000000",2); -- the mask to check running state bit in status register of the freq. drive FC 51 Danfoss
  local run_status = (bit.band(GetReg(109),check_mask) ~= 0); -- checking result as a bool var 
  local now = os.time(); -- current system time 
  local time_diff = 0; -- time difference between calls

  -- catching rising edge of the event 
  if (not run_state) and run_status then 
      WriteReg("P43StartTime", now); -- Drive start time №43

  -- counting time 
 if run_state then 
     time_diff = (now - GetReg("P43StartTime")); -- get time difference
     WriteReg("P43RunTime", GetReg("P43RunTime")+time_diff); -- increase running hour meter 
     WriteReg("P43StartTime", now); -- overwrite start time stamp
 end 
 run_state = run_status
end

The timekeeping registers and time stamps should be made non-volatile.

Time circulation algorithm (together with redundancy function)

This algorithm is used in systems where it is necessary to alternate the operation of mechanisms (pumps, fans, air conditioners) over time, or on the running hours count. For example, a set of 2 units is used, which must be alternated in time. If an error occurs on some unit, then the algorithm starts working only on the working (redundancy function). An example of setting the required registers is given below:

Circ algorithm regs.png

For simplicity and clarity, it is better to split the scripts into functional modules that can be quickly analyzed and placed in the desired order in the program list. The first script looks at the errors and if they do not exist, the units (air conditioners) alternate in time.

CIRCULATION_TIME = 30; -- for testing purposed circulation time = 30 sec. 

function main (userId)
  --[[
  если нет ошибок, то чередуем работу по времени циркуляции
  если есть ошибка на одном из кондиционеров, то он исключается из чередования 
  если ошибки на обоих, то стоим на месте 
  if there are no errors, then alternate the work on the time of circulation
  If there is an error on one of the air conditioners, it is excluded from the circulation
  if there are errors on both, then we stand in place
  --]]
local acError1, acError2 = (GetReg("acError1") == 1), (GetReg("acError2") ==1) ; -- error on a/c #1 
local switchTime = GetReg("switchTime"); -- next switch over time (DS103@webmi)
local now = os.time()
local curActiveAC = GetReg("activeAC") -- active a/c (DS100@webmi)

if (not acError1) and (not acError2) then 
    -- working with circulation 
    if (now >= switchTime) then 
        if (curActiveAC == 1) then 
            WriteReg("activeAC", 2);
        else 
            WriteReg("activeAC", 1);
        end
        WriteReg("switchTime", now + CIRCULATION_TIME);
    end 
elseif acError1 and (not acError2) then 
        WriteReg("activeAC", 2);
elseif acError2 and (not acError1) then 
        WriteReg("activeAC", 1);
else
        WriteReg("activeAC", 0);
end -- if no errors 

end -- main

The second script looks at which a/c is now active, and performs the necessary actions. In a script, this is just debugging, but there may be commands for controlling the infrared transmitter for issuing the desired command, writing to the message log and switching, etc.

function main (userId)
  --[[
  turn selected a/c on dependting on pointer
  --]]
  local pointer = GetReg("activeAC"); -- active a/c (DS100@webmi)

  if (pointer==0) then 
      DEBUG("all off ");
      return 0
  elseif 
      (pointer==1) then 
          DEBUG("turn on a/c #1")
  else 
          DEBUG("turn on a/c #2")
      
  end -- if 
end

Also here you need a script that will rise error flags upon on certain conditions, read the status of the protection devices, the error registers on the interface, and so on.

3-point control for a valve or servo

A 3-point method is used to control the position of the valve, servo, gate valve, etc., when 3 wires are used to control the drive - 'common', 'power UP', 'power - DOWN'. Such drives may or may not be equipped with end position sensors. Sometimes, in the absence of position sensors and low requirements for positioning accuracy, an algorithm can be used when the drive leaves down or up (either one position sensor or one command for a time longer than the full valve travel time), initializes the coordinate, and then runs through the specified position.

To determine intermediate positions, a calculated value is used, determined from the characteristics of the actuator's 'full stroke time' , which can also be determined experimentally. As mentioned above, relatively complex and branched algorithms in WebHMI are better to divide into functionally complete and simple programs, the interaction and synchronization between which can be done using internal registers.


Below one of the examples for 3-point valve control with 2 limit switches is provided The program split into 6 parts:

3-point control.png


v3 OpenClose Valve Manual - The script controls the drive in manual mode. Started by changing the number of the pressed button from the dashboard
v4 Auto Valve Control - the main control script in the auto mode. It automatically performs the first initialization, and if the specified coordinate does not match the current one, it turns the drive in the desired direction, upon reaching the position, stops. Also, if the specified position is the same as the limit positions (0,100), the script continues to hold the command until the physical limit switch reached, thereby performing a periodic synchronization of the calculated position with the real position.
v5 LatchStart_Time - script 'capturing' the initial position of the movement, is executed by changing the flag inMotionFlag, which is set by the previous programs v3 or v4.
v6 CalcNewPosition - is executed 1 time per second. (on system time change), the script works as long as there is an inMotionFlag flag and re-counts the current time passed from the beginning of the movement to the current position (which in turn is used by the v4 program).
v7 Limit sw reaction -When the limit switches are reached, it removes commands, and also removes the request from the manual control buttons.
v8 Drop Cur Cmd on Manual mode - when switching to manual mode, turns off the current command.

At the beginning of the program names, the order of the required execution order (v3..v8) is indicated, since the necessary order of program execution can change undesirably, for example, when sorting programs and inaccurate 'dragging' programs in the list. Thus, the prefix reminds you of the desired order, it can also reflect the functionality of the script - 'v' Valves, 't' - temperature control, etc. It is more convenient to navigate in large lists and refer to it.

Text of the programs:

v3

------- MANUAL CONTROL FROM SCREEN BUTTONS OpenClose Valve Manual -------------------
function main (userId)
  
  local button_value = GetReg("valveMan_code"); -- key code from buttons open / close 
  local manual_mode = (GetReg("auto_mode") == 0) ; -- auto mode is on (ds1176@WH Global)

if manual_mode then

    if (button_value == 10) then 
         WriteReg("openCmd", 1); -- open command  (DS1007@WH Valve control)
         WriteReg("closeCmd", 0);
    elseif (button_value == 5) then 
          WriteReg("closeCmd", 1); -- close command (DS1008@WH Valve control)
          WriteReg("openCmd", 0)
    else
        -- invalid value - switch every command off 
                                    DEBUG("Read button value as "..tostring(button_value));
        WriteReg("openCmd", 0)
        WriteReg("closeCmd", 0)
        WriteReg("inMotion_flag",0)
    end --if 
    
    -- capture start position and start time 
    if (button_value == 10) or (button_value == 5) then 
        WriteReg("startPosition", GetReg("curPosition")); -- starting position (DS1020@WebHMI 3point control)
        WriteReg("startPosTime", os.time()); -- start time stamp (DS1012@WebHMI 3point control)
        WriteReg("inMotion_flag",1)
    end
end -- manual_mode

end -- main -----------------------

v4

                -- AUTO VALVE CONTROL ---- 
function main (userId)
    
  local now = os.time(); -- current time
  local timeStmp = GetReg("endPosTime"); -- end position time stamp (D1001@WebHMI Kitothemr)
  local auto_mode = (GetReg("auto_mode")==1)
  
if auto_mode then
  -- check if very first run = 0 
  if (timeStmp == 0) and (not home_mode) then 
      WriteReg("homingBit",1)
                                DEBUG("timeStmp = 0, homing needed! ");
  end 
  
  local home_mode = (GetReg("homingBit") == 1); -- homing bit (D1006@WebHMI Kitothemr)
  local full_close = GetReg("LLsw"); -- full close limit switch (D1010@WebHMI Kitothemr)

  if home_mode then 
      -- go down till close switch to determine position
               DEBUG("giving home_mode close cmd ! ");
      WriteReg("closeCmd", 1) 
      
          if (full_close == 1) then 
                   DEBUG("full close ! ")
                
                WriteReg("closeCmd",0);
                WriteReg("endPosTime", now); -- to avoid repeated homing 
                WriteReg("homingBit",0);
                WriteReg("curPosition", 0); -- current position
          end
      return 0 -- exit while not the end 
  end -- home mode 
  
  -- MAIN PART ---- 
                           DEBUG("-------- auto valve control ");
  
  local openingStatus = (GetReg("openCmd") == 1); -- 
  local closingStatus = (GetReg("closeCmd") == 1); --
  local inMotionFlag = openingStatus or closingStatus
  
  local target_dir= 0; -- moving sign
  local posSV = GetReg("posSetpoint"); -- valve setpoint 
  local posPV = GetReg("curPosition"); -- current position 
  
    -- determine start position and time
  local startTime = GetReg("startPosTime");
  local startPos = GetReg("startPosition");
  local pathdone = 0; -- path done value 
  
  local OpenSw = GetReg("HLsw"); -- full open switch 
  local CloseSw = GetReg("LLsw"); -- full close switch 
  
                            DEBUG("posPV  "..tostring(posPV).." pos SV"..tostring(posSV));
      -- already in motion 
      if inMotionFlag then 
                            DEBUG("in motion now")
        -- full close ?
          if (closingStatus) and (posPV <= posSV) then 
              -- if 0 set then wait close limit switch, auto calibrate
              if (posSV == 0) and (not CloseSw) then 
                   WriteReg("closeCmd", 1);
                   return 0 -- cmd will be off in limit switch reaction script
              end 
                  WriteReg("closeCmd", 0)
                  WriteReg("inMotion_flag",0)
            end 
        -- upper limit 
          if (openingStatus) and (posPV >= posSV) then 
              -- same as for close 
                  if (posSV == 100) and (not OpenSw) then 
                       WriteReg("openCmd", 1);
                       return 0; -- cmd will be off in limit switch reaction script
                  end 
              WriteReg("openCmd", 0)
              WriteReg("inMotion_flag",0)
          end 
     else -- was stopped
           if (posSV ~= posPV) then 
               -- 
            target_dir = (posSV - posPV)/math.abs(posSV - posPV); -- get direction
                                            DEBUG_("target_dir =  "..tostring(target_dir));
               if (target_dir > 0) then 
                   WriteReg("openCmd" , 1);
                                            DEBUG_("will open...");
               else 
                   WriteReg("closeCmd" , 1);
                                            DEBUG_("will close...");
               end
            WriteReg("inMotion_flag", 1); -- set the start flag for other scripts 
           end
  end -- auto mode motion control 

end -- auto_mode
end -- main

v5

-- LatchStart_Time ---
function main (userId)
    
                 DEBUG("Entered  latch start time and position");
  local flag = (GetReg("inMotion_flag") == 1) ; -- in motion flag @WebHMI 3point control
                 DEBUG("motion flag = "..tostring(flag));
    
  if flag then 
                  DEBUG("now flag = 1");
      WriteReg("startPosition", GetReg("curPosition")); -- start position stored (DS1020@WebHMI 3point control)
      WriteReg("startPosTime", os.time()); -- start time remembered (DS1012@WebHMI 3point control)
                
  end 

end

v6

--- CalcNewPosition
-- constants
FULLPATH = 127 ; -- full path time 
K = 100 / FULLPATH ; -- time to position k

function main (userId)
    
                            DEBUG("Entered #5 script, calc. cur position");
  local now = os.time(); -- 
  local open_sts = (GetReg("openCmd") == 1) 
  local close_sts = (GetReg("closeCmd") == 1)
  local inMotionFlag = (open_sts or close_sts) 
                            DEBUG("in motion flag = "..tostring(inMotionFlag));
  
  local cur_dir = 0
  local posPV = GetReg("curPosition"); 


  local startTime = GetReg("startPosTime");
  local startPos = GetReg("startPosition");
  local pathdone = 0; -- пройденный путь 
  
  if inMotionFlag then 
      if open_sts then 
            cur_dir = 1;
      else 
            cur_dir = -1;
      end 
  pathdone = GetPathDone(startTime, now)
  varPos = startPos + cur_dir*pathdone
      
      if (varPos < 0) or (varPos > 100) then 
                            DEBUG("new varPos calculaed outside limits, cur value "..tostring(varPos));
         if (varPos < 0) then varPos = 0 end 
         if varPos > 100 then varPos = 100 end 
      end 
      
                       DEBUG("startPos , pathdone = , new varPos  "..tostring(startPos).." "..tostring(pathdone).." "..tostring(varPos));
      WriteReg("curPosition", varPos); -- 
   end -- auto mode motion control 
end -- main 

------------- calculates path done ---------------    
function GetPathDone(startTime, curTime)  

    local curPos = (curTime - startTime) * K;
    local remainder = curPos - math.floor(curPos);
    if (remainder >= 0.5) then 
        -- округл. вниз
        curPos = math.floor(curPos) + 1;
    else 
        curPos = math.floor(curPos) ;
    end
    return curPos;
end

v7

-- Limit sw reaction------------
function main (userId) 
    
  local now = os.time();
  local open_sts = (GetReg("openCmd") == 1)
  local close_sts = (GetReg("closeCmd") == 1)
  
  local full_close = (GetReg("LLsw") == 1)
  local full_open = (GetReg("HLsw") == 1)
  
  if (full_open or full_close) then 

          if full_open then
              WriteReg("curPosition", 100);
              WriteReg("openCmd", 0);
              WriteReg("valveMan_code", 0)
          end 
          
          if full_close then 
              WriteReg("curPosition", 0)
              WriteReg("closeCmd", 0)
              WriteReg("valveMan_code", 0)
          end 
          
      WriteReg("inMotion_flag", 0)
      WriteReg("endPosTime", now)
    
  end 
  
  -- code of current state
   if (not open_sts) and (not close_sts) then 
      WriteReg("valveStatus", 0); -- stopped 
  elseif open_sts then 
      WriteReg("valveStatus", 1); -- opening
  else 
      WriteReg("valveStatus", 2); -- closing
  end 
  
end -- main --------------------------------

v8

------ v8 Drop Cur Cmd on Manual mode ---- 
function main (userId) 
    local auto_on = (GetReg("auto_mode")==1); -- 
    
    if not auto_on then 
                                    DEBUG_("Dropped cmds in manua mode ! ");
        WriteReg("openCmd", 0)
        WriteReg("closeCmd", 0)
        WriteReg("inMotion_flag",0)
    end 
    prev_mode = auto_on
end -- main

Bit operation

Удобные битовые операции

Можно определить несколько удобных битовых операций, помня о том что в lua биты нумеруются с 1. Ссылка взята отсюда, там же есть и другие полезные функции.

function bit(n)
  return 2 ^ (n - 1)  -- возвращает число - вес разряда номер n
end

function hasbit(x, p)
  return x % (p + p) >= p  -- возвращает состояние бита p как true/false; if hasbit(value, bit(2)) then ...
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p -- установить бит № p в х Пример использования:  х = setbit(х, bit(p))
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x -- тоже только снять бит
end

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

Данная функция может быть полезна для нестандартных преобразований, когда число хранится в специфичном формате и его необходимо обработать по частям, используя заданную позицию 1-го бита и длину поля значения.

function getNumberFromTab(tab,start,length) -- получить число из таблички 
    local result_str = "";
    
    for i=start,(start+length-1) do 
            result_str = result_str..tostring(tab[i]);
    end 
    return tonumber(result_str,2);
end -- getNumberFromTab

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

В lua (в версии, используемой в WebHMI) нет встроенной операции получения строкового представления двоичного числа. Однако эта функция очень полезна в пользовательсиких протоколах и др. задачах, где требуется перераспределить или каким то образом обработать отдельные биты числа. Текст варианта такой функции:

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

Далее можно переставить нужные биты местами и т.п. и получить нужное число. Ниже приведен пример перестановки битов 31 и 23 битов (при нумерации битов с 0) в ответе счетчика расходомера ВЛР 2301/2304 производства Асвега-У

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];                 -- 31 бит это 1 элемент так как таблица развернута слева-направо, номера элементов табл.в lua c 1
  bitTable[9] = tmp_bit;
  -- конкатенация готовой таблицы в строку и преобразование в число из строки двоичного представления 
  WriteReg(1, tonumber(table.concat(bitTable),2));       

end

Обработка чисел с плавающей точкой двойной точности

Некоторые устройства могут хранить данные в формате с повышенной точностью double float. В WebHMI текущей версии поддерживаются только 32 битные регистры, поэтому чтобы обработать 64 битное число необходимо будет использовать скрипт, которые "сцепит" два регистра в исходное число, а затем преобразует и запишет в обратно в float. Регистры должны быть типа double uint. Для "сцепки" чисел можно использовать описанную выше функцию getBits чтобы получить 2 таблицы, а затем дополнить первую таблицу битами из второй используя table.insert. В этом примере в качестве проверочного числа для простоты используется константа.

--------------------------------------
local test_var = 0xC1D312D000000000;
local NaN = tonumber("11111111111111111111111111111111",2);
--------------------------------------

function main (userId)
  -- получить результирующую табличку "0" "1"
local result_tab = getBits(test_var,64);  

WriteReg(11,table.concat(result_tab)); -- отладочная печать в регстр - строку 

local result_num = 0.0; -- для хранения результата 

local sign,exp,mantissa = 0,0,0;
local fraction_table = {}; -- табл. для дробной части 

-- определить знак
if result_tab[1] == "1" then 
    sign = -1;
else 
    sign = 1;
end 
-- экспоненту
exp = getNumberFromTab(result_tab,2,11);
                DEBUG("exp = "..tostring(exp)); -- отл. печать 
-- мантиссу 
for i=13,64 do 
    table.insert(fraction_table,result_tab[i]);
end 
-- посчитать мантиссу по-разрядно !!! 
for j=1,52 do 
    if fraction_table[j]=="1" then 
    mantissa = mantissa +(2^(-1*j));
    end 
end 

mantissa = mantissa +1;
                DEBUG("m = "..tostring(mantissa)); -- отл. печать 
result_num = sign*(2^(exp - 1023))*mantissa;

-- Обработка исключений 
if exp == 0 then -- subnormals
   result_num = sign*(2^(-1022))*(mantissa-1);
end 

if exp == 0x7ff then -- nan 
   result_num = NaN;
end 
-- Вывод результата в регистр типа float 
WriteReg(10, result_num);

end -- main

Формирование общего регистра аварий по разным условиям

В WebHMI есть механизм аварий. Т.е. по состоянию бита в регистре можно автоматически генерировать записи в журнале о моменте возникновения, снятия аварии, квитировании ее оператором.

Однако в некоторых случаях использовать данный механизм непосредственно, как есть, может не совсем быть удобно. Например - аварийным считается определенный бит или их комбинация, разные коды ошибок (и их надо отличать друг от друга), значение больше или меньше порога и т.д. Если регистр имеет уже настроенные состояния для аварийной ситуации, используемое для отображения, то его нужно просто продублировать, выставив бит в общем аварийном регистре. Также может потребоваться введение задержки на срабатывание аварии, так как значение может иметь "дребезг" который надо фильтровать, прежде чем генерировать аварию в журнале.

Кроме этого удобнее, когда все аварии описаны в одном месте, а не разбросаны по сотням регистров. Т.е. рациональнее использовать один или несколько несколько 32-битных регистров для описания всех возможных аварий, и скрипт в котором эти аварии будут записываться в этот регистр по разным условиям. Ниже приведен пример такого скрипта:

Допустим, есть некая функциональная единица, например приточная вентиляционная установка, в которой есть некий общий признак аварии, есть несколько регистров с кодами текущих ошибок от частотных преобразователей и ситуация, когда один из регистров (влажность, СО2 и др.) вышел за допустимый диапазон, что является также аварийной ситуацией.

Alerts.png


Тогда скрипт, который обработает все эти ситуации и сформирует нужные флаги на своих местах может выглядеть так:

allAlerts = {          -- регистр    бит - код ошибки    тип операции
              {srcAlias = "pv1Status", bits = {[1] = 1}, "bit9"},
              {srcAlias = "vfdStatus_1", bits = {[2] =  10, [3] = 20, [4] = 30, [5] = 40}, "=="},
              {srcAlias = "vfdStatus_2",   bits = {[6] =  10, [7] = 20, [8] = 30, [9] = 40}, "=="},
              {srcAlias = "pv1ZoneHumidity", bits = {[10] = 90}, ">="}
 }

intAlertReg = "alertsReg" -- ОБЩИЙ  РЕГИСТР АВАРИЙ

function main (userId)

local alertInput, alertOut = 0,0 -- временные переменные для чтения аварий и формирования результата 
local digit = 0 -- номер бита в исходном регистре, указывающий на аварию..
--[[
Если назвать единообразно все  параметры, 
из которых надо формировать аварии и сложить их в одну структуру, 
тогда можно использовать только один цикл for, например так:
--]]
   for i,v in pairs(allAlerts) do 
         alertInput = GetReg(v.srcAlias) -- читаем регистр

         for j,k in pairs(v.bits) do -- выполнится по числу элементов в под-табличке bits
                          -- проверяем тип операции - точное совпадение или другое условие 
                if (v[3] == "==") then 

                   -- проверяем регистр на один из кодов ошибок
                   if (alertInput == k) then
                      alertOut = setbit(alertOut, j)
                   else
                      alertOut = clearbit(alertOut, j)
                   end

                   -- проверяем определенный бит
                elseif (string.find(v[3], "bit%d+") ~= nil) then              -- %d+    паттерн любая последовательность цифр
                        _,_,digit = tonumber(string.find(v[3], "bit(%d+)"))   -- выделить из паттерна цифру и преобразовать в число
                       if hasbit(alertInput, bit(digit)) then                 -- если заданный бит установлен во входном регистре
                          alertOut = setbit(alertOut, j)
                       else 
                          alertOut = clearbit(alertOut, j)
                       end 
                else 
                    --- другие операции
                end  
         end -- for bits inside 

   end -- for allAlerts 
                    -- теперь просто записать то что получилось в общий регистр аварий
WriteReg(intAlertReg,  alertOut)

end -- main 

----------------- ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ----------------------------------

function bit(n)
  return 2 ^ (n - 1)  -- возвращает число - вес разряда номер n
end

function hasbit(x, p)
  return x % (p + p) >= p  -- возвращает состояние бита p как true/false; if hasbit(value, bit(2)) then ...
end

function setbit(x, p)
  return hasbit(x, p) and x or x + p -- установить бит № p в х Пример использования:  х = setbit(х, bit(p))
end

function clearbit(x, p)
  return hasbit(x, p) and x - p or x -- тоже только снять бит
end

Если различных операций над значениями много: не только сравнение, но и например проверка конкретного бита и т.п. то можно детали реализации спрятать в функцию, которая получит на вход табличку с типом операции (установлен ли конкретный бит, код ошибки, параметр в границах и т.п.) и входным значением и поставит или сбросит нужный бит.

for i,v in pairs(allAlerts) do 
     alertInput = GetReg(v.srcAlias) -- читаем регистр
     alertOut = MyGetAlerts(alertInput, alertOut, v) -- вызываем функцию, которая посмотрит что надо сделать по полю v[3], т.е. allAlerts[3] 
end -- for