Вопросы по скриптам в Simple-Scada 2

Автор Simple-Scada, 29 января 2016, 15:19:20

KungLao

#1260
Спасибо. Но наверное вы меня не правильно поняли. Кнопка и подсветка не в СКАДе. Это физическая кнопка (в OPC - Btn1), которая расположена на фабрике, и подсветка тоже на этой кнопке (24в постоянки которые коммутируются выходом контроллера (К1 в OPC)). И условие немного отличается, при повторном нажатии ( при условии, что уже прошло n секунд с момента последнего нажатия) на эту кнопку таймер должен сбрасываться, а вот если ее не нажимать(переменная Btn1 не изменялась) то по истечению 120 минут лампа на кнопке должна начать мигать.

KungLao

 По предыдущему вопросу все получилось отлично. Работает. Подскажите пожалуйста еще один момент? как реализовать маршрут: т.е. чтобы регистрация нажатия кнопок проходила только в определенной последовательности. Допустим есть кнопки Btn1, Btn2, Btn3 они находятся в помещении повышенной опасности, которое с заданной периодичностью должно осматриваться. Но осмотр должен идти по строго заданному маршруту, вдоль которого и расположены эти кнопки. Если кнопки нажаты не последовательно, то регистрация нажатия должна происходить с пометкой о нажатии не в правильной последовательности в виде сообщения.

Timothy Clement

#1262
Цитата: KungLao от 25 августа 2021, 08:42:27Если кнопки нажаты не последовательно, то регистрация нажатия должна происходить с пометкой о нажатии не в правильной последовательности в виде сообщения.
Как один из вариантов решения задачи - Создать внутреннюю переменную типа Word, скрипт "Изменились переменные" - Btn1, Btn2, Btn3
Код: pascal
//Скрипт по изменению переменных Btn1, Btn2, Btn3. BtnWord внутренняя переменная типа Word
begin

  if Btn1.AsBool = True then
    BtnWord.Value := SetBit(BtnWord.Value, 0, True); //Если нажата кнопка 1 устанавливаем во внутренней переменной бит 0
  if Btn2.AsBool = True then
    BtnWord.Value := SetBit(BtnWord.Value, 1, True); //Если нажата кнопка 2 устанавливаем во внутренней переменной бит 1
  if Btn3.AsBool = True then
    BtnWord.Value := SetBit(BtnWord.Value, 2, True); //Если нажата кнопка 3 устанавливаем во внутренней переменной бит 2

   if BtnWord.Value=0 then Exit                                                                                    
   else if (BtnWord.Value<>1) and (BtnWord.Value<>3) and (BtnWord.Value<>7) then            //Нарушена последовательность нажатия
     AddMessage(Now, mkWarning, 'Нарушен маршрут осмотра помещения ...', True, False)
   else if BtnWord.Value=7 then BtnWord.Value := 0;       //Маршрут пройден, обнулить слово

end.

KungLao

#1263
Спасибо. подскажите пожалуйста как модифицировать ваш скрипт, я задачу немного упростил. У меня на обьекте есть кнопки с световой индикацией, по истечению заданного промежутка времени они начинают мигать (в скада это сопровождается визуализацией), также выводится соответствующее сообщение. Мигание осуществляется посредством изменения переменной в плк. Как с помощью этого скрипта сделать так, чтобы мигание прекратилось только тогда, когда нажались все три кнопки(не одновременно и не обязательно в строгой последовательности) и в этот момент сбросилась переменная vrTimer в 0, чтобы начать отсчет по новой?

pan2000

#1264
   Здравствуйте.

Сброс таймера vrTimer в 0 по трем кнопкам Btn1, Btn2, Btn3 с управлением подсветкой кнопок К1, К2, К3.
Предполагается, что кнопки реагируют на нажатие только при завершению интервала контроля, т.е. когда кнопки мигают. Нажатия кнопок запоминаются в переменной состояние кнопок vrBtnState. Для разрешения реагирования на кнопки в любой момент времени достаточно удалить условие проверки состояния таймера: (vrTimer.AsInt < (Tmax - 2)).
После нажатия всех трех кнопок может выполняться проверка на правильный порядок нажатия 1-2-3 или 3-2-1.
Скрипт "Изменились переменные Btn1, Btn2, Btn3":
Код: pascal
const Tmax = 120 * 60;       // константа используется в двух скриптах - её можно перенести в глобальный модуль
begin
  if vrTimer.AsInt < (Tmax - 2) then exit;  // интервал контроля ещё не истек (рано кнопки нажимать). Для исключения контроля удалить оператор
  if not Variable.AsBool then exit;         // переход в false - игнорировать
  if Variable = btn1 then begin K1.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 0, true); end;    // запомнить нажатые кнопки
  if Variable = btn2 then begin K2.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 1, true); end;
  if Variable = btn3 then begin K3.Value := 29; vrBtnState.Value := SetBit(vrBtnState.AsInt, 2, true); end;

// СОСТОЯНИЕ КНОПОК - другой оператор должен быть закомментирован или удален
//  этот оператор контролирует порядок нажатия кнопок 1-2-3 или 3-2-1
  case vrBtnState.AsInt of
  2, 5: vrBtnState.Value := SetBit(vrBtnState.AsInt, 3, true);   // установить флаг ошибки порядка нажатия кнопок
     7: vrTimer.Value := 0;              // сброс таймера
    15: begin
          vrTimer.Value := 0;            // сброс таймера
          AddMessage(Now, mkWarning, 'Он шел на Одессу, а вышел к Херсону...(матрос-партизан Железняк)', true, false);
        end;
  end;
//   этот оператор контролирует факт нажатия всех кнопок
//  if vrBtnState.AsInt = 7 then vrTimer.Value := 0;              // сброс таймера

end.

Скрипт "Прошла секунда":
Код: pascal
const Tmax = 120 * 60;       // константа используется в двух скриптах - её можно перенести в глобальный модуль
begin
  vrTimer.inc(1, 0, Tmax);

  case vrTimer.AsInt of
  1: vrBtnState.Value := 0;       // сброс регистра нажатых кнопок
  2, Tmax - 1: begin
                 if not Getbit(vrBtnState.AsInt, 0) then K1.Value := 31;      // включать индикатор не нажатой кнопки
                 if not Getbit(vrBtnState.AsInt, 1) then K2.Value := 31;
                 if not Getbit(vrBtnState.AsInt, 2) then K3.Value := 31;
               end;
  Tmax: begin
          K1.Value := 29;     // выключить индикаторы кнопок
          K2.Value := 29;
          K3.Value := 29;
          vrTimer.inc( -2);
        end;
  end;
end.


Возможно ещё нужен контроль времени нажатия всех трёх кнопок.

Пример во вложении.

KungLao

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

strs

#1266
Здравствуйте!

Есть необходимость видеть в интерфейсе оператора, запущена ли служба опроса модемов. Нашел пример для Делфи (приведен ниже), но не знаю, можно ли его применить в скриптах скады или это невозможно.
При попытке вставить первую часть кода в скрипт типа "глобальный модуль", получаю ошибку File "WinSvc.PCU not found".

Если напрямую в скриптах скады это сделать невозможно, может посоветуете, как по-другому можно вывести статус службы в интерфейс?
Код: pascal
uses
  WinSvc;
 
function ServiceGetStatus(sMachine, sService: PChar): DWORD;
{******************************************} 
  {*** Parameters: ***} 
  {*** sService: specifies the name of the service to open 
  {*** sMachine: specifies the name of the target computer 
  {*** ***} 
  {*** Return Values: ***} 
  {*** -1 = Error opening service ***} 
  {*** 1 = SERVICE_STOPPED ***} 
  {*** 2 = SERVICE_START_PENDING ***} 
  {*** 3 = SERVICE_STOP_PENDING ***} 
  {*** 4 = SERVICE_RUNNING ***} 
  {*** 5 = SERVICE_CONTINUE_PENDING ***} 
  {*** 6 = SERVICE_PAUSE_PENDING ***} 
  {*** 7 = SERVICE_PAUSED ***} 
  {******************************************} 
 
var 
  SCManHandle, SvcHandle: SC_Handle; 
  SS: TServiceStatus; 
  dwStat: DWORD; 
begin 
  dwStat := 0; 
  // Open service manager handle. 
  SCManHandle := OpenSCManager(sMachine, nil, SC_MANAGER_CONNECT); 
  if (SCManHandle > 0) then 
  begin 
    SvcHandle := OpenService(SCManHandle, sService, SERVICE_QUERY_STATUS); 
    // if Service installed 
    if (SvcHandle > 0) then 
    begin
      // SS structure holds the service status (TServiceStatus); 
      if (QueryServiceStatus(SvcHandle, SS)) then 
        dwStat := ss.dwCurrentState; 
      CloseServiceHandle(SvcHandle); 
    end; 
    CloseServiceHandle(SCManHandle); 
  end; 
  Result := dwStat; 
end;
 
function ServiceRunning(sMachine, sService: PChar): Boolean; 
begin 
  Result := SERVICE_RUNNING = ServiceGetStatus(sMachine, sService); 
end;

Код: pascal
if ServiceRunning(nil, 'Имя службы') then
begin
{Действия если служба запущена}
end else
begin
{Действия если служба не запущена}
end;


Simple-Scada

Здравствуйте.

Компилятор выдал правильную ошибку о том, что модуль WinSvc, подключенный в приведённом Вами коде не найден, ведь его нет.

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

pan2000

     Здравствуйте.
Цитата: strs от 05 сентября 2021, 10:14:16Есть необходимость видеть в интерфейсе оператора, запущена ли служба опроса модемов.
Получить состояние служб можно с помощью командного процессора.
Однако командный процессор всегда запускается в видимом окне, что крайне нежелательно. Запустить командный процессор без видимого окна можно в файле сценария VBScript. Полученные данные перенаправляются в файл. Далее следует дождаться обновления данных в файле и обработать их.

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

strs

Цитата: pan2000 от 08 сентября 2021, 07:38:26Пример из вложения после ввода имени службы получает её состояние.
Спасибо! Была проблема, из-за русского имени пользователя в винде не создавался текстовый файл. Но с переездом в "D:\" все заработало.
Вопрос: а зачем сканировать директорию на наличие файла, если мы точно знаем как он называется и можем открыть его напрямую?

pan2000

Цитата: strs от 09 сентября 2021, 14:19:56Вопрос: а зачем сканировать директорию на наличие файла, если мы точно знаем как он называется и можем открыть его напрямую?
Файл результата открывается как перезаписываемый - т.е. новый замещает старый. Узнать что файл обновился можно по изменению даты записи файла, она должна быть больше чем дата начала выполнения скрипта.

strs

#1271
Цитата: pan2000 от 09 сентября 2021, 17:51:39Файл результата открывается как перезаписываемый - т.е. новый замещает старый. Узнать что файл обновился можно по изменению даты записи файла, она должна быть больше чем дата начала выполнения скрипта.

Все равно не понял. Мы же и так можем узнать дату файла, зная имя. Ну да ладно, в итоге привел к такому виду, вроде все работает:

Код: pascal
var
    aPath: String;
    i: integer;
    tempString: String;
const
    servName:string = 'iRZ_Collector_Server';
begin

if SecondOf(Now) = 0 then begin    //проверяем статус службы и создаем файл с информацией 1 раз в минуту
  aPath := GetProjectPath + 'User Files\';
  StartTime.Value := NOW;                                       // начало ожидания
// чтение состояния службы через VB скрипт для подавления вывода окна командной строки
  TextFileOpen('Service.vbs', aPath, fomRewrite, fcpUTF8);
  TextFileWriteLn('Set WshShell = CreateObject("WScript.Shell")');
  TextFileWriteLn('WshShell.Run "cmd /A /C sc query ' + servName + '> ""'                // выполнить команду чтения состояния службы
  + 'D:\Service.txt""", 0');                                                             //  и записать результат в файл
  TextFileWriteLn('Set WshShell = Nothing');
  TextFileClose;
  RunApplication(SS_SERVER_NAME, aPath + 'Service.vbs', '');    // выполнить VBS-файл

  end;

  /////////////////////////////////////////////////

   if SecondOf(Now) = 2 then begin    //считываем файл для анализа через 2 секунды после создания

    if (FileExists('Service.txt', 'D:\')) and (SecondsBetween(FileAge('D:\Service.txt'), StartTime.AsDateTime) < 10) then begin  // условия корректности файла

       TextFileOpen('Service.txt', 'D:\', fomReset, fcpCyrillic_windows866); //открываем файл

       tempString := UTF8Encode(TextFileReadLn);     //переходим к требуемой строчке в файле
       tempString := UTF8Encode(TextFileReadLn);
       tempString := UTF8Encode(TextFileReadLn);
       tempString := UTF8Encode(TextFileReadLn);
           if tempString = '        Состояние          : 4  RUNNING ' then
             begin txtService.Text := 'Служба опроса модемов работает';
                   txtService.BorderColor := clLime;
             end
           else if tempString = '        STATE              : 1  STOPPED ' then
             begin txtService.Text := 'Служба опроса модемов остановлена';
                   txtService.BorderColor := clRed;
             end
           else
             begin txtService.Text := 'Ошибка получения статуса службы опроса модемов';    //в нужной строчке не те данные, которых мы ожидали
                   txtService.BorderColor := clYellow;
             end;

        TextFileClose;          // закрыть файл
    end
    else begin txtService.Text := 'Ошибка получения статуса службы опроса модемов'; //файл не существует либо слишком старый
               txtService.BorderColor := clYellow;
         end;

    end;   // if SecondOf(Now) = 2
end.

Спасибо!

pan2000

#1272
   Здравствуйте.
Цитата: strs от 10 сентября 2021, 08:42:44Мы же и так можем узнать дату файла, зная имя.
Мой пример предполагает однократный запрос состояния службы с минимальным временем отклика, анализ наличия обновленного файла начинается с 0 секунды относительно запроса.

В Вашем случае это автомат состояний с периодом в 60 тактов, запрос выполняется в 0, а анализ во 2 тактах. Сам процесс получения данных асинхронный, и можно ожидать что он завершится за 2 секунды. Но если информация о состоянии файла обновится через 3 секунды и более, то возникнет ошибка.
Поскольку процесс получения состояния службы периодический и не связан с действиями оператора (как в примере), то анализ файла можно смело перенести до 55 такта (секунды).

Вывод названия параметра на разных языках ("Состояние" <-> "STATE") это, предположительно, отголосок локализации ОС. Я бы проверял строку на наличие подстрок ": 4  RUNNING" и ": 1  STOPPED".

И ещё одна особенность автомата при запуске проекта, с вероятностью 1/30 в течение первой минуты будет выдано сообщение "Ошибка получения статуса службы опроса модемов".
Для устранения этого можно использовать условие (начальное значение переменной - 0) :
Код: pascal
(if SecondOf(Now) = 2) and (StartTime.AsFloat > 1) then begin    //считываем файл для анализа через 2 секунды после создания и только после запуска скрипта

strs

#1273
Цитата: pan2000 от 10 сентября 2021, 14:02:02Поскольку процесс получения состояния службы периодический и не связан с действиями оператора (как в примере), то анализ файла можно смело перенести до 55 такта (секунды).
Так и сделаю пожалуй.

Цитата: pan2000 от 10 сентября 2021, 14:02:02Вывод названия параметра на разных языках ("Состояние" <-> "STATE") это, предположительно, отголосок локализации ОС. Я бы проверял строку на наличие подстрок ": 4  RUNNING" и ": 1  STOPPED".
Проект работает на конкретном компьютере, универсальность не требуется, поэтому усложнять лишний раз не хочется.

Цитата: pan2000 от 10 сентября 2021, 14:02:02И ещё одна особенность автомата при запуске проекта, с вероятностью 1/30 в течение первой минуты будет выдано сообщение "Ошибка получения статуса службы опроса модемов".
Для устранения этого можно использовать условие (начальное значение переменной - 0)
Совсем не критично, перезапуски - редкое явление. Но раз есть возможность улучшить - почему бы и нет.

Спасибо!

KungLao

Добрый день. В своем проекте использую скрипт из примера, который вывод на основной экран состояние связи с ПЛК (привязанно к пингу: Есть пинг - есть связь, нет пинга - нет связи).
Код выглядит следующим образом:
Код: pascal
begin
if pingdata.Result then
ping_plc.color := clgreen
else begin 
ping_plc.color := clred;
AddMessage (Now, mkAlarm, 'Отсутствие связи с контроллером', true, false);
end;
end 
В такой реализации аварийное сообщение о потере связи генерируется постоянно (в течении всего времени отсутствия связи).
Подскажите как реализовать однократное появление сообщения при потере связи и подобное сообщение когда связь востановилась?