Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Не получили письмо с кодом активации?

Официальный форум Simple-Scada.

Просмотр сообщений

В этом разделе можно просмотреть все сообщения, сделанные этим пользователем.


Сообщения - pan2000

Страницы: 1 ... 10 11 [12] 13 14 ... 16
166
Ваши вопросы / Re: Связь с ПЛК
« : 30 Мая 2020, 02:39:00 »
   Здравствуйте!

  Во вложении примеры контроля связи (с эмулируемым ПЛК) по тайм-аутам нахождения флага InterfaceModule_disconnect_flag в одном из состояний (T, F)
для автомата состояний или по предыстории флага в сдвиг-регистре.
Правила контроля:
 - "связь установлена" после первого перехода флага T->F;
 - "связь утеряна" после 10 секунд состояния флага F или 2 секунд состояния T.

  При наличии в ПЛК тега с режимом "эхо" (запись в тег - запись в ПЛК - чтение из ПЛК - чтение в тег) можно контролировать связь записью различных значений с последующим тайм-аутом совпадения со считанным.
Пример с использованием автомата состояний во вложении.

167
Ваши вопросы / Re: CRC16 LH в скрипте
« : 18 Мая 2020, 04:56:56 »
Вычисление CRC16 для строки (без нулевых байтов!):
Код: (delphi)
function CalculateCRC16(const APacket: string; const APacketSize : integer) : Word;
const
  ArrayCRCHi : array [0..255] of byte = (
    $00, $C1, $81, $40, $01, $C0, $80, $41, $01, $C0,
  . . .
    $43, $83, $41, $81, $80, $40);

var
  i, Index : integer;
  CRCHi, CRCLo : byte;
begin
  CRCHi := $FF;
  CRCLo := $FF;
  for i := 1 to APacketSize do
  begin
    Index := CRCHi xor byte(APacket[i]);
    CRCHi := CRCLo xor ArrayCRCHi[Index];
    CRCLo := ArrayCRCLo[Index];
  end;
  CalculateCRC16 := (CRCHi shl 8) or CRCLo; //(CRCHi shl 8)
end;

Вычисление CRC16 для байтового массива:
Код: (delphi)
function CalculateCRC16(const APacket: array of byte; const APacketSize : integer) : Word;
const
  ArrayCRCHi : array [0..255] of byte = (
    $00, $C1, $81, $40, $01, $C0, $80, $41, $01, $C0,
  . . .
    $43, $83, $41, $81, $80, $40);

var
  i, Index : integer;
  CRCHi, CRCLo : byte;
begin
  CRCHi := $FF;
  CRCLo := $FF;
  for i := 0 to APacketSize - 1 do
  begin
    Index := CRCHi xor APacket[i];
    CRCHi := CRCLo xor ArrayCRCHi[Index];
    CRCLo := ArrayCRCLo[Index];
  end;
  CalculateCRC16 := (CRCHi shl 8) or CRCLo; //(CRCHi shl 8)
end;
Во вложении пример глобальной функции вычисления CRC для байтового массива.
 


168
    Здравствуйте!

  Так и не удалось заставить Ваш пример некорректно отображать число в экспоненциальной форме. Точность данных, равно как и тип (за исключением данных с фиксированным форматированием - DateTime и String) для переменной, используемой только для связи с объектом, не имеет никакого значения. В примере из вложения одна из переменных имеет тип Boolean, а другая - Byte (так делать не следует, но для иллюстрации сгодится).

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


169
Ваши вопросы / Re: Скрипт по времени
« : 26 Апреля 2020, 01:08:39 »
     Здравствуйте.

Можно использовать виртуальную переменную dtNextStart типа DateTime с автоматическим восстановлением. В переменной хранится дата-время начала очередной работы скрипта. Скрипт " Прошла секунда/Прошел час".
Код: (delphi)
begin
  if dtNextStart.value < Now then exit;
  dtNextStart.value := IncHour(dtNextStart.value, 48);  // начало следующего цикла
// выполняемая часть
  . . .
end.
Для реального проекта необходимо определить:
1. Как получить начальное значение dtNextStart при первом запуске проекта.
2. Правила работы скрипта при повторном открытии Скада после остановки на время большее чем период.

170
Подскажите пожалуйста , наиболее простой, автоматический способ распечатать содержимое окна , и желательно только его содержимое , в окне находиться таблица , вот её мне и надо распечатать .

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

Пример скриптов при использовании файла для печати таблицы из трех столбцов ("параметры" - текст в ячейках, "уставки" и "значения" - ячейки связаны с переменными).
После изменения параметров и нажатия кнопки "Пуск" начинается отсчет таймера, по завершении отсчета формируется отчет.
Скрипт "Изменилась переменная (control, связанная с кнопкой "Пуск")":
Код: (delphi)
begin
  if GetBit(control.AsInt, 0) then begin
    Y7.Value := int(X7.Value);          // таймер интервала X7 - уставка, Y7 - текущее значение
    DTstart.Value := now;               // метка времени начала

    TextFileOpen('PrintTable.csv', '', fomRewrite, fcpANSI);
    TextFileWriteLn('');                // файл с пустой строкой
    TextFileClose;
  end;
end.

Скрипт "Прошла секунда"
Код: (delphi)
var i: integer;
    aReport: TM_Report;
begin
  if GetBit(control.AsInt, 0) then
    if CompareDateTime(Y7.Value, X7.Value) = -1 then
      Y7.Value := IncSecond(Y7.AsDateTime, 1)     // счет таймера
    else
      if Table1.Tag = 0 then begin                                   // Table1.Tag = 0  - запись файла
        TextFileOpen('PrintTable.csv', '', fomRewrite, fcpANSI);     // запись данных в CSV-файл
        TextFileWriteLn(';;');                                       // заголовок (имена столбцов)
        with Table1 as TM_Table do
          for i := 1 to 6 do
            TextFileWriteLn(UTF8ToString(GetCell(0,i).Text) + ';' + GetCell(1, i).Variable.AsStr + ';' + GetCell(2, i).Variable.AsStr);

        TextFileWriteLn(' Интервал;' + TimeToStr(DTstart.AsDateTime) + ';' + TimeToStr(Now));    // время начала и конца процесса
        TextFileClose;
        Table1.Tag := 1;
      end
      else begin    // Table1.Tag <> 0   - ожидание завершения записи файла и построение отчета
        if TextFileOpen('PrintTable.csv', '', fomReset, fcpANSI) then
          if TextFileReadLn = ';;' then begin          // первая строка содержит заголовок, файл записан
            aReport := ReportBuild('Print_1');
            aReport.View(ClientName.AsStr);            // осталось дождаться отчета для просмотра или печати (.Print)
            Table1.Tag := 0;                           // сбросить флаг построения отчета
            control.Value := 0;                        // завершено - отпустить кнопку и
          end;
        TextFileClose;
      end;
end.

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

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

171
Ваши вопросы / Re: Список он же ComboBox V2
« : 03 Апреля 2020, 18:38:49 »
     Здравствуйте!
 
  Для заполнения списка из БД вполне можно обойтись обработкой запроса к БД, составленного таким образом, что данные для заполнения списка будут расположены в первом поле (с индексом 0) результата в DataSet, а ссылка на заполняемый список будет указана в параметрах вызова:
Код: (delphi)
- RunSQL('SHOW TABLES;', ComboBox1, 1);  // заполнить список ComboBox1 именами таблиц БД;

- RunSQL('DESC ' + UTF8ToString(ComboBox1.Text) + ';', ComboBox2, 1);    // список столбцов выбранной таблицы (для запроса DESC - первое поле записи);

- RunSQL('SELECT `' + UTF8ToString(ComboBox2.Text) + '` FROM `' + UTF8ToString(ComboBox1.Text) + '`;', ComboBox3, 1);   // выбранный столбец из выбранной таблицы. 

Фрагмент скрипта "Выполнен SQL-запрос":
Код: (delphi)
 . . .
  1: with DataSet.Sender as TM_ComboBox do begin
       Clear;                                     // очистить список
       while not DataSet.EOF do begin
         AddItem(DataSet.Fields[0].AsStr);        // заполнение списка
         DataSet.Next;                            // следующая запись
       end;
       Enabled := not DataSet.IsEmpty;            // список заполнен, разрешить доступ для непустого списка
     end;
 . . .

Похожий цикл и для заполнения списка из файла.

Пример получения списка значений выбранного столбца из выбранной таблицы текущей БД во вложении.

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

Для информирования оператора о существовании таблицы можно предварительно проверить ее наличие.
Универсальный скрипт:
Код: (delphi)
  . . .
aQuery := 'SHOW TABLES LIKE ' + QuotedStr(NameNewTable.AsStr) + ';';
RunSQL(aQuery, nil, 100);
  . . .
Скрипт "Выполнен SQL-запрос"
Код: (delphi)
  case DataSet.Tag of
  . . .
  100: if DataSet.IsEmpty then RunSQL('CREATE TABLE `' + NameNewTable.AsStr + '` LIKE `TemplateTable`;', nil, 101)
       else AddMessage(Now, mkWarning, 'Таблица не добавлена', True, True);
  . . .
  end;

         Получить тэг запроса для события "Ошибка SQL-запроса" (пока это не реализовано средствами Simple-Scada).
В предположении, что порядок вызовов процедуры RunSQL соответствует порядку событий завершения, можно организовать очередь тэгов, добавляя новый тэг при вызове и извлекая тэг по событию SQL-запроса или ошибки SQL-запроса.
Проект узкоспецифичный, смысла в приведении программного кода нет. См. вложение.


173
Ваши вопросы / Re: Работа с таблицей
« : 03 Марта 2020, 22:32:00 »
      Здравствуйте!

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

1.В скрипте "Прошел час" необходимо добавить переменные интервала времени и вызов архивной функции
Код: (delphi)
var
  ...
  DateStart, DateEnd: TDateTime;
begin
  ...
  DateEnd:= Now;                     // текущие дата и время
  DateStart:= IncHour(DateEnd, -1);  // уменьшить дату и время на 1 час

{ Синхронизация, с завершением работы функции по событию "Изменилась переменная" MyVariableRes, предполагает до вызова
функции присвоить переменной значение вне ожидаемого интервала значений функции. Например, для неотрицательных значений
функции это может быть -1. События с таким значением переменной игнорируются. Для пропуска инициализации начальное значение
переменной должно быть таким же (-1)} 
  MyVariableRes.Value := -1;
 
 { вычислить максимальное значение переменной "MyVariable" за период
   от "DateStart" до "DateEnd" и записать результат в переменную "MyVariableRes" }
  ArchiveMax(MyVariable, MyVariableRes, DateStart, DateEnd);
  ...
end.

2. Скрипт "Изменилась переменная" MyVariableRes
Код: (delphi)
var i, j: integer;
begin
  i := Variable.AsInt;  // пример для целой переменной
  if i < 0 then exit;   // это событие по инициализации (нач.значение MyVariableRes = -1) или перед вызовом архивной функции

  j := (HourOf(Now) + 23) mod 24; // предыдущий час
  Table1.GetCell(4, j + 1).Text := IntToStr(i); // вывод результата функции в столбец с индексом 4 таблицы Table1 с фиксированной 1-ой строкой
end.

174
Нужно, чтобы пользователь кликал в "поле", вводил уставку в переменную A1_w и видел бы значение из переменной A1_r. А1_r и A1_w могут не совпадать, т.к. при перезагрузке железки A1_w = 0. В идеале элемент "поле" должен являться элементом "таблицы"
Поскольку требуется работа только с одной переменной, то вторую можно индицировать периодической перезаписью свойства "Text" (В версии 2.3.6.7 Simple-Scada запись в свойство "Text" приводит к отображению этого свойства на объекте).
Пример скрипта (проект во вложении) "Прошла секунда" для поля Field1 и таблицы Table1:
Код: (delphi)
begin
  Field1.Text := A0_r.AsStr;                  // Поле Field1 связано с переменной A0_w
  Table1.GetCell(1,1).Text := A1_r.AsStr;     // Ячейки таблицы связаны с переменными Ai_w (i = 1..3)
  Table1.GetCell(1,2).Text := A2_r.AsStr;
  Table1.GetCell(1,3).Text := A3_r.AsStr;
end.

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

Ещё часто подходит функция StrToIntDef, которая в случае неудачи возвращает заданное вами значение. Например:

Значение по умолчанию - это частный случай для ограниченного диапазона значений. Петрову поможет, а Сидорову - нет. Чем try, как общий случай, то плох?
Года три назад try - это был единственный вариант борьбы с отладкой по TeamViewer, когда проект на удаленной машине вставал 'раком' по завершении цикла отладки.

176
Всем хорошего дня , подскажите как наиболее коротко и оптимально проверить строку, что она не сможет преобразоваться в интежер, т.е. в ней есть посторонние символы и буквы
  Здравствуйте!
На мой взгляд, наиболее оптимальное разрешение спорных вопросов - использование исключений, в Вашем случае:
Код: (delphi)
begin
  try
    Text1.Text := IntToStr(StrToInt(aSTR.AsStr));  // попытались
  except
    Text1.Text := 'Увы, это не переводимая игра слов C(К/ф""Бриллиантовая рука"")';  // не удалось
  end.           

aStr - внутренний тег типа строка.
Пример во вложении (!)
 

177
  Здравствуйте!

Восстановление таблицы при запуске проекта - скрипт "Проект полностью запущен"
Код: (delphi)
begin
// заполнение таблицы при запуске проекта
  Table1.RunSQL('SELECT * FROM `preferences`;', tsSaveFixRow);
end.

Добавить пользователя по кнопке:
Код: (delphi)
begin
// собственно запись
  RunSQL(' INSERT INTO  `preferences` (`cart`, `name`, `surname`, `pref`) VALUES('
        + cart.AsStr + ', '''
        + name.AsStr + ''', '''
        + surname.AsStr + ''', '''
        + pref.AsStr + ''');', nil, 0);
// обновление таблицы
  Table1.RunSQL('SELECT * FROM `preferences`;', tsSaveFixRow);
end.

Для работы с реальной таблицей необходимы:
- контроль входных данных: уникальность номера карты, заполнение всех полей и т.д.;
- редактирование для некоторых столбцов ("номер карты", "предпочтения") и удаление строк;
- сортировка строк по каким-либо критериям: по номеру карты, по фамилии и т.д,
- действия после заполнения таблицы: форматирование столбцов, "раскраска" ячеек по характеру данных и т.д.

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

178
Цвет фона ячейки доступен для записи. Для контроля временного интервала, привязанного к началу часа, можно использовать событие "Прошел час".
Пример скрипа для таблицы Table1 c фиксированной первой строкой:
Код: (delphi)
// подсветка ячейки, соответствующей текущему часу
var i: integer;
begin
  for i := 0 to 23 do
    with Table1.GetCell(0,i + 1) do
      if i = HourOf(Now) then Color := clFuchsia else Color := clGray;
end.
При запуске проекта можно установить цвет фона текущего интервала в скрипте "Проект полностью запущен":
Код: (delphi)
begin
  Table1.GetCell(0,HourOf(Now) + 1).Color := clFuchsia;
end.

179
Здравструйте!

Каждый оператор IF должен выполнятся только при изменении "своей" переменной, например:
Код: (delphi)
 . . .
  //Скважина 1
  if Variable.Name = 'Danfoss_disconnect_flag' then   // проверка на "свою" переменную
    if Danfoss_disconnect_flag.AsBool then
      AddMessage(Now, mkMessage, 'Зв''язок з перетворювачем частоти відновлено [Свердловина 1]', TRUE, FALSE)
    else
      AddMessage(Now, mkAlarm, 'Зв''язок з перетворювачем частоти втрачено [Свердловина 1]', TRUE, FALSE);
 . . .

180
Ваши вопросы / Re: Вопрос по отчётам
« : 04 Октября 2019, 00:52:24 »
    Здравствуйте!
Скорее всего, лишние строки появляются в исходных данных из-за неопределенных (нет связи) состояний внешнего тега run_fc.
Исключить ложные переходы для тега run_fc (нет связи -> 0, нет связи -> 1) можно сохраняя его значения во внутренней переменной run_fc_1.

Скрипт по изменению внешнего тега:
Код: (delphi)
begin
  run_fc_1.Value := run_fc.Value;     // присвоить текущее состояние насоса внутренней переменной
end.
Скрипт по изменению внутренней переменной:
Код: (delphi)
begin
  if run_fc_1.AsBool then vrOn.Value  := Now   // время включения насоса
                     else vrOff.Value := Now;  // время выключения насоса
end.

Страницы: 1 ... 10 11 [12] 13 14 ... 16