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

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

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

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


Сообщения - pan2000

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

После выбора строки из более длинного списка и обновлении списка на более короткий, переменная индекса выбранной строки (авто-генерируемая или основная) сохранила значение и стала указывать на несуществующую строку списка.
Для указания индекса следует явно использовать основную переменную и обнулять при выполнении процедуры Clear:
Код: (delphi)
 . . .
    comboBoxIdDriver.Clear;                 // очищаем список список
    comboBoxIdDriverIndex.Value := 0;       // устанавливаем индекс выбранной строки на начало списка
. . .
При удалении строк из списка может возникнуть аналогичная ситуация - пустая строка выбора, причем основная переменная-индекс после удаления строки перестает быть непрерывной. Так что для исключения "артефакта пустой строки" при удалении строк наиболее приемлемый способ это полная перезапись списка с коррекцией переменной-индекса.
Посмотреть поведение списков с переменной и авто-переменной можно в проекте из вложения.

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

При подстановке шаблона свойство FlashColor переустанавливается в $FF000000. Пример во вложении.

138
    Алгоритм проверки изменения цвета ячейки:
1. Полная очистка таблицы.
2. Пауза 1-2 сек.
3. Запрос на заполнение таблицы заданной длины (поле "Limit", SQL-таблица - "trends_data").
4. По событию "Выполнен SQL-запрос отправленный от таблицы" изменяется цвет первой и последней строк таблицы.
5. С интервалом 1 секунда изменяется цвет предыдущей строки.
6. Визуальный контроль и проверка реального значения цвета ячейки (по клику ЛКМ на этой ячейке).

   Результат:
Изменение цвета ячейки непосредственно по событию выполняется только для некоторых ячеек, но все ячейки содержат правильное значение цвета.

139
Может ли объект кнопка выполнять разные функции, в зависимости от того, какой пользователь сейчас авторизован?

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

Можно при выполнении скрипта "Авторизация пользователя" каждому пользователю присвоить идентификатор (пример для двух пользователей). "Разделяемая" кнопка Button1 с основной переменной iButton:
Код: (delphi)
begin
  if auLogin = 'user1' then Button1.Tag := 1 else Button1.Tag := 0;  // идентификатор пользователя 1 или неизвестного пользователя
  if auLogin = 'user2' then Button1.Tag := 2;                        // идентификатор пользователя 2 - при необходимости использования
                                                                     //   в нескольких скриптах можно объявить внутренней переменной
  iButton.Value := 0;    // установить в исходное состояние кнопку, разделяемую двумя пользователями (и все прочие объекты и переменные)
end.
Группу объектов пользователя 2 можно скрыть посредством непрозрачного объекта (Text1) с управлением видимостью и расположенного "выше" экранируемых объектов или индивидуальным управлением видимостью каждого объекта.
Скрипт для кнопки по событию "Изменилось значение переменной iButton связанной с объектом":
Код: (delphi)
begin
  Text1.Visible := not ((Button1.Tag = 2) and (iButton.AsInt <> 0)); // экранировка группы объектов
  if (Button1.Tag = 1) and (iButton.AsInt <> 0) then Window1.ShowAll;
  if iButton.AsInt = 0 then Window1.CloseAll;
end.

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

 Скриншот показывает последовательную инициализацию при запуске проекта двух переменных StatusWord_PNS3 и Ext_Status_Word_2_PNS3 первая в 0, а потом вторая в 1.
Для исключения "ложных" индикаций можно добавить блокирующую внутреннюю логическую переменную FullyLaunched, с начальным состоянием false, и
принимающую значение true в скрипте по событию "Полностью запущен".
Изменение в скрипте "Изменились переменные StatusWord_PNS3, Ext_Status_Word_2_PNS3, FullyLaunched"(Включение переменной FullyLaunched в список обеспечивает правильную индикацию состояния после запуска проекта, если в этом нет необходимости, то переменную нужно исключить из списка):
Код: (delphi)
begin 
  if not FullyLaunched.AsBool then exit;   // игнорировать изменения переменных до завершения полного запуска проекта
  if StatusWord_PNS3.AsBool and (not GetBit(Ext_Status_Word_2_PNS3.AsInt, 21)) and (MessID.AsInt <> 1) then
    begin
      MessID.Value := 1;
    . . .
Пример в вложении. Пример позволяет просмотреть порядок вызова скрипта с указанием имени изменившейся переменной.

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

  Можно ввести блокировку повторного выполнения фрагмента скрипта (в т.ч. и вывода сообщения).
Для блокировки необходима предыстория, т.е. нужна переменная MessID, содержащая информацию о последнем выполненном фрагменте - некоторое число (перечисляемая величина).
Измененный фрагмент скрипта:
Код: (delphi)
if StatusWord_PNS3.AsBool and (not GetBit(Ext_Status_Word_2_PNS3.AsInt, 21)) and (MessID.AsInt <> 1) then
    begin
      MessID.Value := 1;

      Shape1.FlashColor := clNone;
      Shape1.Color := clGreen;
      Text12.Text := "ВВІМКНУТО";
      Text12.FontColor := clWhite;
      AddMessage(Now, mkMessage, '[ПНС 7 Гайдара] ввімкнута', true, false);
    end else if (not StatusWord_PNS3.AsBool) and GetBit(Ext_Status_Word_2_PNS3.AsInt, 21) and (MessID.AsInt <> 2)  then
    begin
      MessID.Value := 2;
      Shape1.FlashColor := clNone;
      Shape1.Color := clYellow;
      Text12.Text := "СОН";
      Text12.FontColor := clBlack;
      AddMessage(Now, mkWarning, '[ПНС 7 Гайдара] сон', true, false);
    end else if (not StatusWord_PNS3.AsBool) and (not GetBit(Ext_Status_Word_2_PNS3.AsInt, 21)) and (MessID.AsInt <> 3)  then
    begin
      MessID.Value := 3;
      Shape1.FlashColor := clNone;
      Shape1.Color := clRed;
      Text12.Text := "ВИМКНЕНО";
      Text12.FontColor := clWhite;
      AddMessage(Now, mkWarning, UTF8Encode('[ПНС 7 Гайдара] вимкнута'), true, false);
    end; 
Во вложении пример работы исходного и нового скриптов. Для сравнения вывод сообщений ведется в индивидуальные текстовые области.

Замечания.
1. Условие
Код: (delphi)
(StatusWord_PNS3.Value = TRUE)
всегда ложно (По крайней мере в моем примере)(См. вложение).
2. Есть неопределенное состояние ((StatusWord_PNS3 = TRUE), GetBit(Ext_Status_Word_2_PNS3.AsInt, 21) = TRUE).

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

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

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

 Для правильной оценки изменения параметров нужно контролировать сам параметр, а не изменение визуальных свойств объекта (эффект суслика).
Например для объекта "Кнопка" с двумя состояниями - параметр изменение цвета в состоянии 1: при состоянии 1 изменение видно, а при 0 - нет, но оно есть.
Простейшее средство контроля - объект "Текст", при клике которого в его поле выводится значение контролируемого параметра, и, возможно ещё, дополнительная информация.

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

 Для правильной работы счетчика DiscreteInputCount0_8_GeneralQ можно применить коррекцию текущего значения счетчика на величину изменения DiscreteInputCount0_8 с момента последней коррекции. Для этого используется внутренняя переменная HoldCount0_8, запоминающая значение DiscreteInputCount0_8 в момент последней коррекции.
Сам скрипт может вызываться по событиям: изменение переменной (например, DiscreteInputCount0_8), по времени ("Прошла секунда") и т.д.
Пример скрипта, в предположении, что DiscreteInputCount0_8 представляет собой образ 32-разрядного счетчика в контроллере.
Код: (delphi)
var i, j: int64;
begin
  i := DiscreteInputCount0_8.AsInt64 and $FFFFFFFF;
  j := i - (HoldCount0_8.AsInt64 and $FFFFFFFF);       // добавка к "большому" счетчику со времени последней коррекции
  if j < 0 then j := j + 4294967296;                   // коррекция при переполнении 32-разрядного счетчика
  DiscreteInputCount0_8_GeneralQ.Value := DiscreteInputCount0_8_GeneralQ.AsInt64 + j;
  HoldCount0_8.Value := i;                   // запомнить текущее значение счетчика
end.
Скрипт установки счетчика в 0:
Код: (delphi)
begin
DiscreteInputCount0_8_GeneralQ.Value := 0;
HoldCount0_8.Value := DiscreteInputCount0_8.Value;
end.

Во вложении пример модели счетчика контроллера (изменение: прямая запись, генератор 1 Гц, +1, число 4294967290)
с возможностью отключения/подключения данных от ОРС-сервера и сохранением данных при перезапуске проекта.

К сожалению, я так и не понял алгоритм калибровки.

145
Ваши вопросы / Re: Работа с CSV файлами
« : 28 Июля 2020, 15:20:38 »
          Здравствуйте!

К сожалению, { вспомогательная процедура для обработки отдельной строки } не сможет правильно разобрать строку из CSV-файла экспорта переменных, содержащую пустые поля (и "пробелы").
Для разбора CSV-строки можно использовать функции из примера заполнения таблиц описанием переменных из CSV-файла:
Код: (delphi)
// выбор поля по индексу (произвольный выбор)
function GetField(const aStr: string; const n: integer): string;
var i, j: integer;
    aStr1: string;
begin
  j := 1; aStr1 := '';
  for i := 1 to Length(aStr) do begin
    if aStr[i] = ';' then begin
      if j = n then begin GetField := aStr1; exit; end;  // индекс выбора поля равен номеру выбранного поля
      aStr1 := '';                                       // новое поле
      j := j + 1;
    end
    else aStr1 := aStr1 + Astr[i];
  end;
  GetField := aStr1;     // пусто или последнее поле с отсутствующим разделителем ";"
end;

// последовательный выбор полей
function GetField2(const aStr: string; var n: integer): string;
var i: integer;
    aStr1: string;
begin
  aStr1 := '';
  for i := n to Length(aStr) do
    if aStr[i] = ';' then begin
      n := i + 1;                      // запомнить начало следующего поля
      GetField2 := aStr1;
      exit;
    end
    else aStr1 := aStr1 + Astr[i];
  GetField2 := aStr1;
end;

var aStr: string;
    i, j, k: integer;
begin
// открываем текстовый файл для чтения
 if TextFileOpen('MyCSV.csv', '', fomReset, fcpDefault) then begin
    TextFileReadLn;                                          // пропустить первую строку
    i := 0;
    with Table1 do
      while (not TextFileEOF) and (i < RowCount) do begin    // цикл с проходом по каждой строке текстового файла или до переполнения таблицы
        aStr := TextFileReadLn;
// функция GetField()
        for j := 1 to ColumnsCount do GetCell(j - 1, i).Text := GetField(aStr, j);

        for j := 0 to 3 do                // заполнение Table3 выбранными столбцами
          case j of
          0: Table3.GetCell(j, i).Text := GetField(aStr, 2);    // Имя переменной
          1: Table3.GetCell(j, i).Text := GetField(aStr, 3);    // Тип данных
          2: Table3.GetCell(j, i).Text := GetField(aStr, 8);    // Начальное значение
          3: Table3.GetCell(j, i).Text := GetField(aStr, 32);   // ID
          end;
// функция GetField2() - заполнение таблицы Table2 идентичной таблице Table1
        k := 1;     // индекс начала очередного поля в строке разбора aStr
        for j := 1 to ColumnsCount do Table2.GetCell(j - 1, i).Text := GetField2(aStr, k);

        i := i + 1;
      end;
    TextFileClose;
  end;
end.

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

Для временного контроля используются скрипты с событиями "Прошла секунда" и "Прошел час". Для работы проекта необходимы две переменные целочисленного типа: iButton - состояние кнопки и iTimer - таймер отсчета временного интервала.
Скрипт "Прошла секунда":
Код: (delphi)
const Tmax = 10;
begin
// таймер с прямым отсчетом и с перезапуском
  if iButton.AsInt <> 0 then begin
    iTimer.Inc(1);                                   // работает в пределах шкалы переменной iTimer, максимальное значение 12
    if iTimer.AsInt >= Tmax then iButton.Value := 0; // если таймер достиг конечного значения, то выключить кнопку
  end
  else iTimer.Value := 0;                            // установить начальное значение таймера
end.
Скрипт примера из вложения дополнительно содержит коды для таймера с обратным отсчетом и перезапуском и для таймера с обратным отсчетом и приостановом.
(перезапуск - установка таймера в начальное значение при выключении кнопки, приостанов - сохранение значения при выключении или включении кнопки, установка таймера в начальное значение только при достижении конечного.

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

Очевидно, что не выполняется сравнение строго с 1.
Правильное сравнение возможно для одинаковых типов. Например, значение типа Value может отличаться от значений типа AsBool для true. В примере можно посмотреть как изменяются значения переменной sen2 в зависимости от явного указания типа.
Код: (delphi)
//                                                                               результат для sen2 = true
  Text1608.Text := 'VariableEx.Value=' + IntToStr(VariableEx.value) + #10       //  -1
                 + 'VariableEx.AsInt=' + IntToStr(VariableEx.asint) + #10       //   1
                 + 'VariableEx.AsBool=' + IntToStr(integer(VariableEx.AsBool)); //   1

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

В Вашем случае всего четыре состояния, циклически изменяющихся: "Закрыто", "Открывается", "Открыто", "Закрывается" - вполне достаточно двух кнопок по четыре состояния, связанных одной переменной, в следующих парах для одного состояния:
0 - "Открыть" - "Закрыто";
1 - "Открывается" - "" (назовите состояние сами, в примере пустое поле);
2 - "Открыто" - "Закрыть";
4 - "" - "Закрывается".
Общая переменная привязана к основной переменной кнопок, индивидуальные переменные состояния привязаны к дополнительным переменным своих кнопок. Теги кнопок 0 и 2.
Скрипт "Изменилась основная переменная":
Код: (delphi)
begin
  with Sender as TM_Button do begin
    Enabled := Variable.AsInt = Tag;
    Open.Value := Variable.AsInt = 1;       // управляющая линия "Открыть"   
    Close.Value := Variable.AsInt = 3;      // управляющая линия "Закрыть"
  end;
end.
Скрипт "Изменилась дополнительная переменная":
Код: (delphi)
begin
  with Sender as TM_Button do if VariableEx.AsBool then Variable.Value := (Tag + 2) mod 4; // условие достижения крайнего положения = true
end.

Пример с моделью электромеханического клапана во вложении.

149
. . . Очень нужен, а то пол таблицы не видно.
Пока не добавлен горизонтальный скролл, можно воспользоваться примером реализации горизонтального скролла для широкой таблицы. Пример - 10 столбцов шириной по 400 с просмотром в окне шириной 1440. Одинаковый размер столбца и плавный движок (горизонтальный объект "Уровень") для скролла исключительно для простоты. Столбцы можно сделать разной ширины, а движок дискретным.

150
lipvasko, вот правильный вариант:
Код: (delphi)
TextFileWriteLn(UTF8ToString(DateTimeToStr(Now)) +' Создан новый файл ');
Выражение:
Код: (delphi)
UTF8ToString(DateTimeToStr(ADateTime: TDateTime): String)
подразумевает две конвертации: неявную String->UTF8 и явную UTF8->String, на мой взгляд проще явно указать тип компилятору:
Код: (delphi)
String(DateTimeToStr(Now)) + ' Создан новый файл ';

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