Useful programs

Материал из WebHMI Wiki
Версия от 10:11, 26 марта 2018; Evgeniy.mozoliak (обсуждение | вклад) (Новая страница: «An example of the implementation of the run hour meter in WebHMI (the program runs in each scan):»)

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

Useful programs

Run script on rising or falling edge of the discrete signal

The script needs to be called on register's change. Inside the script, you need to check the current state of this register and perform corresponding actions either on the front (current state = 1) or on the falling edge (= 0). Example of the script:

Rising edge.png

Timer with turn-on delya (TON)

TON timer starts counting while input = 1, and after delay time is also set to "1".


-- globals
TIMER_DELAY = 20 -- timer delay 20 seconds
tmrStartTime = 0 -- time stamp of timer start time

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


-- checking condition ----------
if not startBit then
    tmrStartTime = now; -- save timer start 
    WriteReg("TON_out", 0); -- timer output 
    return 0 
else 
    if (now - tmrStartTime) > TIMER_DELAY then
      -- actions upon timer trigger
            WriteReg("TON_out", 1)
    end  -- if  
end -- if 
end -- main

Signalling (sound, relay, sms, viber, telegram) about connection errors

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; -- timer countdown flag 
timeStmp = 0;  --  time stamp 
msgSent = false; -- message sent flag


function main (userId)
  -- read inputs
  local scan = GetReg(34); -- scan time 
  local c0 = GetReg(42); -- failed connection number 
  local SCANLIMIT = GetReg(886); -- scan time limit 
  local SCANDELAY = GetReg(887); -- error reaction time 
  -- 
  if (scan == nil)  or (c0 == nil) or (SCANLIMIT == nil) or (SCANDELAY == nil) then
   ERROR("scan / c0  was read as nil");
   return 0;
  end 
  -- read time 
  local now = os.time()
  -- message pattern 
  local msg1 = "Scan is long "..tostring(scan).." ".."ms, error in connection "..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 in normal 
    if (cntdownFlag == true) and msgSent  then 
       AddInfoMessage("Скан вернулся к норме ")
       SendViberMessage(398044391, "Скан вернулся к норме ")
       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 devices 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 filter on the sample, N values ​​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 fill index 
av_sum = 0;     -- accumulator moving average 


function main (userId)


local in_value, tmp_var, out_value  = GetReg(26), 0, 0; -- read input 
   
if (queue_fill < mav_len) then -- queue not filled
    av_sum = av_sum + in_value; -- accumulating sum 
    queue_fill = queue_fill +1; -- and index 
else                            -- now filled queue
    tmp_var = av_sum / mav_len; -- store one element 
    av_sum = av_sum - tmp_var + in_value; -- subtract and add 
end
-- 
if (queue_fill == mav_len ) then
      out_value = av_sum / mav_len; -- get moving average
    else
      out_value = av_sum / queue_fill; -- arithmetic mean 
end
WriteReg("Tout_mav", out_value) -- 

end

PID - control

An example of implementing a PID controller in WebHMI:


G_LIMIT = 100 -- output limit 
--
function main (userId)
  -- local vars
  local now = os.time()
  local nexTime = GetReg("nextPidTime")
  local CYCLE_TIME = GetReg("pidCycleTime")
  
  -- pid settings
  local Kp = GetReg("Kp") -- Proportional part (DS1400@WH Valve control)
  local Ti = GetReg("Ki") -- Integral time constant (DS1404@WH Valve control)
  local Td = GetReg("Kd") -- Diff. constant  (DS1408@WH Valve control)
  local Err, dErr, iSum_Limit = 0, 0, 0 
  local Int_sum = GetReg("pidIntegral") -- integral accumulator 
  local intPart = 0 -- integral part 
  local G = GetReg("pidOut") -- pid output 
  
  -- process 
  local PV = GetReg(1436) -- power  (PWR0@Scylar 8 INT)
  local Sp = GetReg("targetPowerSp") 
  
                                    DEBUG_("seconds left for PID cycle = "..tostring(nexTime - now))
    -- condition of work             
  local auto = (GetReg("auto_mode") == 1) -- auto mode is on (ds1176@WH Global)
  local heatDemand = (GetReg("heatDemand") == 1)
  
if auto then 
  
  if heatDemand then 
                                    -- PID - loop 
          if (now >= nexTime) then 
                                                          DEBUG_("PID compute cycle")
                WriteReg("nextPidTime", now + CYCLE_TIME)
                Err =  Sp - PV -- get error 
                                                    DEBUG_("sp pv Err = "..Sp.." "..PV.." "..Err)
                dErr = Err - GetReg("pidPrevError") -- get error tendency 
                                                    DEBUG_("dErr = "..dErr)
                    -- calc. integral limit
                iSum_Limit = (G_LIMIT * Ti / Kp) / 5
                                                            DEBUG_("iSum_Limit = "..iSum_Limit)
                
                            -- PID loop 
                    --check integral part 
                                                            DEBUG_("prev Int_sum = "..Int_sum)
                                                            if (intPart <= iSum_Limit) and (intPart >= 0.0) then
                    Int_sum = Int_sum + Err -- accumulating integral of error
                                                   DEBUG_("added error to Int_sum ")
                                                            
                    elseif Int_sum < 0 then
                        Int_sum = 0
                    else
                        Int_sum = iSum_Limit -- strict integral part 
                    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)
                    -- check output for limits 
                if G < 0 then
                    G = 0  
                end
                if G > G_LIMIT then
                    G = G_LIMIT
                end
                
                WriteReg("pidPrevError", Err) -- remember previous error 
                WriteReg("dErr", dErr) -- 
                WriteReg("pidIntegral", intPart) -- remember integral 
                WriteReg("pidOut", G) 
                WriteReg("posSPinput", G) -- output for valve position
        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 
        -- found , check equality 
        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 hours meter

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


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

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


Регистры хранения моточасов и метки времени нужно делать энергонезависимыми.

Алгоритм чередования по времени (циркуляция с функцией АВР)

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

Circ algorithm regs.png

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

CIRCULATION_TIME = 30; -- на время отладки время = 30 сек. 


function main (userId)
  --[[
  если нет ошибок, то чередуем работу по времени циркуляции
  если есть ошибка на одном из кондиционеров, то он исключается из чередования 
  если ошибки на обоих, то стоим на месте 
  --]]
local acError1, acError2 = (GetReg("acError1") == 1), (GetReg("acError2") ==1) ; -- Кондиционер 1 ошибка (DS101@webmi)
local switchTime = GetReg("switchTime"); -- Время следующего переключения (DS103@webmi)
local now = os.time();
local curActiveAC = GetReg("activeAC"); -- Активный кондиционер (DS100@webmi)


if (not acError1) and (not acError2) then 
    -- работаем по циркуляции 
    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


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


function main (userId)
  --[[
  включает выбранный кондицинер, в зависимости от указателя 
  --]]
  local pointer = GetReg("activeAC"); -- Активный кондиционер (DS100@webmi)


if (pointer==0) then 
      DEBUG("все выключаем");
      return 0;
  elseif 
      (pointer==1) then 
          DEBUG("включаем 1-й кондиционер");
  else 
          DEBUG("включаем 2-й кондиционер");
      
  end -- if 
end

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

3-х точечное управление клапанами, сервоприводами и др.

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

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


Ниже приведен вариант 3-х точечного управления для привода с 2-мя концевыми датчиками. Программа разбита на 6 частей:

3-point control.png


v3 OpenClose Valve Manual - скрипт управляет приводом в ручном режиме. Запускается по изменению номера нажатой кнопки с дешборда.
v4 Auto Valve Control - основной скрипт управления в авт. режиме. Он автоматически выполняет первую инициализацию, и при несовпадении заданной координаты от текущей включает привод в нужном направлении, по достижении позиции останавливает. Также при совпадении заданной позиции как крайней (0,100), скрипт продолжает держать команду, пока не произойдет наезд на концевой выключатель, таким образом выполняя периодическую синхронизацию расчетной позиции с реальной.
v5 LatchStart_Time - скрипт "захвата" начальной позиции движения, выполняется по изменению флага inMotionFlag, который устанавливают предыдущие программы v3 или v4.
v6 CalcNewPosition - выполняется 1 раз в сек. (по изменению системного времени), скрипт работает, пока есть флаг inMotionFlag и пересчитывает текущее время, пройденное с начала движения в текущую позицию (которая в свою очередь используется программой v4).
v7 Limit sw reaction - по достижении конечных выключателей снимает команды, а также снимает запрос от кнопок ручного управления.
v8 Drop Cur Cmd on Manual mode - при переходе в ручной режим выключает текущую команду.


В начале программ обозначен порядок требуемый порядок выполнения (v3..v8), поскольку необходимый порядок выполнения программ может измениться нежелательным образом, например при сортировке программ и неточном "перетаскивании" программ в списке. Таким образом префикс напоминает о нужном порядке, также он может отражать функциональную принадлежность скрмпта - "v" Valves, "t" - temperature control и т.д. чтобы удобнее ориентироваться в больших списках и ссылаться на него.

Исходные тексты скриптов:

v3

------- РУЧНОЕ УПРАВЛЕНИЕ С КНОПОК НА ЭКРАНЕ OpenClose Valve Manual -------------------
function main (userId)
  
  local button_value = GetReg("valveMan_code"); -- номер от кнопок нажатия ОТКР / ЗАКР. 
  local manual_mode = (GetReg("auto_mode") == 0) ; -- АВТ. РЕЖИМ ВКЛЮЧЕН (ds1176@WH Global)


if manual_mode then


if (button_value == 10) then 
         WriteReg("openCmd", 1); -- Команда "Открытие" (DS1007@WH Valve control)
         WriteReg("closeCmd", 0);
    elseif (button_value == 5) then 
          WriteReg("closeCmd", 1); -- Команда "Закрытие" (DS1008@WH Valve control)
          WriteReg("openCmd", 0); 
    else
        -- на недопустимое значение снимаем команды 
                                    DEBUG("Read button value as "..tostring(button_value));
        WriteReg("openCmd", 0); 
        WriteReg("closeCmd", 0);
        WriteReg("inMotion_flag",0); -- 
    end --if 
    
    -- Фиксация начальной позиции, времени начала движения для скрпита обсчета текущей позиции
    if (button_value == 10) or (button_value == 5) then 
        WriteReg("startPosition", GetReg("curPosition")); -- Начальная позиция движения  (DS1020@WebHMI 3point control)
        WriteReg("startPosTime", os.time()); -- Метка времени начала движения  (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(); -- текущее время 
  local timeStmp = GetReg("endPosTime"); -- Метка времени окончания движения  (D1001@WebHMI Kitothemr)
  local auto_mode = (GetReg("auto_mode")==1);
  
if auto_mode then
  -- проверка на самое первое включение, когда метка времения = 0 
  if (timeStmp == 0) and (not home_mode) then 
      WriteReg("homingBit",1);
                                DEBUG("timeStmp = 0, homing needed! ");
  end 
  
  local home_mode = (GetReg("homingBit") == 1); -- Бит репозиционирования  (D1006@WebHMI Kitothemr)
  local full_close = GetReg("LLsw"); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)


if home_mode then 
      -- едем вниз до упора чтобы определить позицию 
               DEBUG("giving home_mode close cmd ! ");
      WriteReg("closeCmd", 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
      
          if (full_close == 1) then 
                   DEBUG("full close ! ");
                
                WriteReg("closeCmd",0);
                WriteReg("endPosTime", now); -- чтобы избежать повторного home_mode
                WriteReg("homingBit",0);
                WriteReg("curPosition", 0); -- Текущее положение клапана (D1005@WebHMI Kitothemr)
          end
      return 0; -- выходим пока не закончим выход в 0 
  end -- home mode 
  
  -- ОСНОВНАЯ ЧАСТЬ ---- 
                           DEBUG("-------- auto valve control ");
  
  local openingStatus = (GetReg("openCmd") == 1); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
  local closingStatus = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
  local inMotionFlag = openingStatus or closingStatus;
  
  local target_dir= 0; -- знак движения 
  local posSV = GetReg("posSetpoint"); -- Уставка положения клапана (D1000@WebHMI Kitothemr)
  local posPV = GetReg("curPosition"); -- текущее положение 
  
    -- определить начальные время и позицию 
  local startTime = GetReg("startPosTime");
  local startPos = GetReg("startPosition");
  local pathdone = 0; -- обсчета пройденного пути 
  
  local OpenSw = GetReg("HLsw"); -- Датчик полного открытия (D1009@WebHMI Kitothemr)
  local CloseSw = GetReg("LLsw"); -- Датчик полного закр.  (D1009@WebHMI Kitothemr)
  
                            DEBUG("posPV  "..tostring(posPV).." pos SV"..tostring(posSV));
      -- Уже едем 
      if inMotionFlag then 
                            DEBUG("Мы уже в движении ");
        -- доехали вниз ?
          if (closingStatus) and (posPV <= posSV) then 
              -- если задан 0 как позицию, то едем до нижнего конечника, чтобы синхронизировать позицию с реальным положением 
              if (posPV == 0) and (not CloseSw) then 
                   WriteReg("closeCmd", 1);
                   return 0; -- выходим, команду сбросит скрипт Limit Sw Reaction
              end 
                  WriteReg("closeCmd", 0);
                  WriteReg("inMotion_flag",0);
            end 
        -- доехали вверх?  
          if (openingStatus) and (posPV >= posSV) then 
              -- если задан верхний предел, то едем до конечника чтобы синхронизировать позицию с реальным положением 
                  if (posPV == 100) and (not OpenSw) then 
                       WriteReg("openCmd", 1);
                       return 0; -- выходим, команду сбросит скрипт Limit Sw Reaction
                  end 
              WriteReg("openCmd", 0);
              WriteReg("inMotion_flag",0);
          end 
      -- позиция не доехали ? 
       else 
           if (posSV ~= posPV) then 
               -- уставка поменялась ?
            target_dir = (posSV - posPV)/math.abs(posSV - posPV); -- определить знак
                                            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); -- уст. флаг для скриптов работающих по началу движения                                     
           end
  end -- auto mode motion control 


end -- auto_mode
end -- main


v5

-- Захват позиции LatchStart_Time ---
function main (userId)
    
                 DEBUG("Entered  latch start time and position");
  -- Add your code here
  local flag = (GetReg("inMotion_flag") == 1) ; -- Флаг "в движении" (DS1016@WebHMI 3point control)
                 DEBUG("motion flag = "..tostring(flag));
  
  
  if flag then 
                  DEBUG("now flag = 1");
      WriteReg("startPosition", GetReg("curPosition")); -- Начальная позиция движения  (DS1020@WebHMI 3point control)
      WriteReg("startPosTime", os.time()); -- Метка времени начала движения  (DS1012@WebHMI 3point control)
                
  end 


end

v6

--- Расчет новой позиции CalcNewPosition
-- константы
FULLPATH = 127 ; -- время полного открытия в сек. 
K = 100 / FULLPATH ; -- коэф. пересчета % открытия в временной интервал


function main (userId)
    
                            DEBUG("Entered #5 script, calc. cur position");
  local now = os.time(); -- текущее время 
  local open_sts = (GetReg("openCmd") == 1); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
  local close_sts = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
  local inMotionFlag = (open_sts or close_sts) ; -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
                            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 


------------- Функция вычисления остатка движения ---------------    
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); -- Команда "Открытие" (D1007@WebHMI Kitothemr)
  local close_sts = (GetReg("closeCmd") == 1); -- Команда "Закрытие" (D1008@WebHMI Kitothemr)
  
  local full_close = (GetReg("LLsw") == 1); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)
  local full_open = (GetReg("HLsw") == 1); -- Датчик полного закрытия (D1010@WebHMI Kitothemr)
  
  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); -- Метка времени окончания движения  (DS1001@WH Valve control)
    
  end 
  
  -- индикация соcтояния привода 
   if (not open_sts) and (not close_sts) then 
      WriteReg("valveStatus", 0); -- Стоим 
  elseif open_sts then 
      WriteReg("valveStatus", 1); -- Открытие 
  else 
      WriteReg("valveStatus", 2); -- Закрытие
  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