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

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

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

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


Сообщения - pan2000

Страницы: 1 ... 9 10 [11] 12 13 ... 15
151
   Здравствуйте!

lipvasko, по-видимому компилятор не привел тип строки, нужно явно указать тип функцией UTF8Encode(' Создан новый файл ').

Миханик, очень похоже на отсутствие переменной привязанной к ячейке.

152
Но этот пример не совсем решает мою проблему. Во вложении примерный вид элемента управления как я его себе представляю.
     Здравствуйте!
Во вложении пример возможной реализации элемента управления.

153
Нужно отобразить 4ю кнопку "Неактивное"....
Здравствуйте!
Это кнопка с восьмью состояниями. Бонусом - индикация запрещенных комбинаций. При отсутствии оных - можно обойтись кнопкой с пятью состояниями. См. пример во вложении.

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

Возможным вариантом наименования переменных - использование суффикса, ссылающегося на набор переменных, например:
 <имя переменной>_<номер Модуля><номер Камеры из двух цифр, начиная с 1>.
В этом случае для работы с одним общим окном (пример с двумя объектами) достаточно универсального скрипта (события "Пользователь перешел на эту страницу", "Пользователь перешел на эту подстраницу", "Пользователь закрыл окно"):
Код: (delphi)
const ObjName: array [1..2] of string[10] = ('Field1', 'Button1');         //  связывание переменной с объектом окна
      VarName: array [1..2] of string[10] = ('Var1_', 'iButt1_');    //  для большого числа переменных удобнее двумерный массив (как в 3 окнах)
var i: integer;
begin
  for i := 1 to 2 do                // привязать реальные переменные к объектам окна
    GetObjectByName(ObjName[i]).Variable := GetVariableByName(string(VarName[i]) + IntToStr(Sender.Tag));

  GetPageByName('Set' + IntToStr((Sender.Tag div 100) * 100)).Tag := Sender.Tag;  // запомнить  набора для этой страницы (

  Window1.Tag := Sender.Tag;   // запомнить для восстановления окна после попытки закрыть "незакрываемое" окно.
  Window1.ShowAll;
end.
Существенным является определение тегов и название страниц/подстраниц:
- для подстраниц постоянное значение равное индексу набора;
- для страниц - начальное значение равное индексу первой подстраницы;
- для окна - начальное ноль, к переменным подключен первый набор;
- в данном примере страница - Set<номер Модуля>00, подстраница - Set<номер Модуля><номер Камеры>.

Добавление "новых" окон сводится к созданию нового набора переменных и добавлению подстраницы или страницы.


Вариант с 3 окнами - скрипт аналогичного вида с событием "Изменились переменные" для переменных "Вектор выбора подстраницы" (в примере iSelPage<номер Модуля>) у каждой страницы. По имени и значению переменной скрипт формирует индекс набора и далее привязывает переменные.

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

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

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

Синхронную подсветку строки в нескольких таблицах можно реализовать двумя способами:

- для таблиц с полосой прокрутки изменением цвета ячеек. Скрипт по событию "Пользователь кликнул ячейку":
Код: (delphi)
const nTables = 4;      // число таблиц с однотипными последовательно нумеруемыми именами
var i, j, k, l: integer;
begin
  i := Cell.Row;        // индекс новой строки
  j := Table1.Tag;      // индекс подсвеченной строки. Начальное значение -1
  if i = j then exit;
  Table1.Tag := i;      // хранение индекса подсвеченной строки (можно и в виртуальной переменной).

  for l := 1 to nTables do
    with GetTableByName('Table' + IntToStr(l)) as TM_Table do begin
      if j >= 0 then for k := 0 to ColumnsCount - 1 do GetCell(k, j).Color := $6D6D6D;  // удалить подсветку
      for k := 0 to ColumnsCount - 1 do GetCell(k, i).Color := clMediumTurquoise;       // подсветить
    end;
end.
В данной реализации теряется исходная информация о цвете ячейки. Есть несколько вариантов восстановления цвета - вычисление по индексам, подсветка части ячеек и т.д.

- для таблиц без полосы прокрутки - позиционированием подсвечивающего объекта над/под таблицей. Скрипт для двух таблиц:
Код: (delphi)
const Y5 = 128; Y6 = 176;      // Y-координаты верхней строки таблиц  (Свойство Y у обоих таблиц = 88 ! )
var i: integer;
begin
  i := Cell.Row;        // индекс новой строки
  Text1.Y := Y5 + i * Table5.RowHeight;
  Text2.Y := Y6 + i * Table6.RowHeight;
end.
Объект над таблицей блокирует доступ к таблице, под - требует наличие прозрачности у таблицы (либо торчащие "уши" объекта шире таблицы).
Для выделения строки контуром можно применить объект "Линия" над таблицей.
Примеры во вложении.

Предложения - см.
Замечания и пожелания по результатам использования объекта "Таблица":

 Раз уж зашла речь о таблице, то могу добавить следующие пожелания (некритично, обходится посредством скриптов, но лучше перевести эти действия в Simple-Scada):

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

2. Форматирование "вправо,  ..." для строк, выходящих за размер соответствующего столбца.
Условно, урезанием строки слева, но несколько проблематично для не моноширинного шрифта.

156
Ваши вопросы / Re: Список он же ComboBox V2
« : 08 Июня 2020, 23:17:26 »
    Здравствуйте!

 Исключить дублирующие записи можно следующим SQL-запросом:
Код: (delphi)
RunSQL('SELECT `ProdName` FROM `archive` GROUP BY `ProdName`;', ComboBox3, 10);
Сравнение запросов с и без исключения дублирующих записей в проекте из вложении.

157
Ваши вопросы / Re: Связь с ПЛК
« : 30 Мая 2020, 02:39:00 »
   Здравствуйте!

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

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

158
Ваши вопросы / 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 для байтового массива.
 


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

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

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


160
Ваши вопросы / 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. Правила работы скрипта при повторном открытии Скада после остановки на время большее чем период.

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

Вариант вывода таблицы на печать через построение отчета с последующей печатью:
- перед построением отчета необходимо подготовить данные из таблицы либо как 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. При использовании таблицы БД необходимо дополнительно включить в таблицу упорядочивающий столбец строк исходной таблицы (номер строки).

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

162
Ваши вопросы / 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;
 . . .

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

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

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

Для информирования оператора о существовании таблицы можно предварительно проверить ее наличие.
Универсальный скрипт:
Код: (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-запроса.
Проект узкоспецифичный, смысла в приведении программного кода нет. См. вложение.


164
Ваши вопросы / 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.

165
Нужно, чтобы пользователь кликал в "поле", вводил уставку в переменную 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.

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