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

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

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

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


Сообщения - pan2000

Страницы: 1 ... 8 9 [10] 11 12 ... 16
136
Как избавится от запроса в каждом цикле?
Вариант с накоплением запросов в списке с их последующим выполнением после заполнения списка:
1. создание списка в глобальном модуле:
Код: (delphi)
interface

var
  myList: TM_StringList;          // объявляем список с именем myList

implementation

initialization                    // во время запуска проекта
  myList := TM_StringList.Create; // создаём список

finalization                // во время выключения проекта
  FreeAndNil(myList);       // !!! обязательно удаляем список

end.
2. Собственно запись в список во всех процедурах:
Код: (delphi)
procedure InsertDmCod;
begin
  myList.Add('INSERT INTO DmExplorer.dbo.TMP_DeFileRead (DFR_DmCod, DFR_TimeStamp) '+
             'VALUES (' +QuotedStr(vrUploadStr.AsStr)+', '+SQLServerDateTime(Now, dttMillisecond)+  ')');
end;
3. Запуск цикла по списку:
Код: (delphi)
{PRG************************************************************************************************************************}
begin
  imgLoad1.Visible := true;
  . . .
    MyList.Clear;                // очистить список
    while not TextFileEOF do                           // цикл с проходом по каждой строке текстового файла
      begin
        . . .
      end;
    TextFileClose;                                     // закрываем файл

    MyLisyIndex.Value := 0;                              // индекс текущего элемента списка
    if MyList.Count > 0 then RunSQL(MyList[0], nil, 4)   // запуск цикла по непустому списку
    else imgLoad1.Visible := false;
  end;
end.
4. Цикл по списку (скрипт "Выполнен SQL-запрос"):
Код: (delphi)
begin
  if DataSet.Tag = 4 then begin
    MyLisyIndex.Value := MyLisyIndex.AsInt + 1;      // индекс текущего элемента списка = параметр цикла
    if MyList.Count > MyLisyIndex.AsInt then RunSQL(MyList[MyLisyIndex.AsInt], nil, 4)
    else imgLoad1.Visible := false;
  end;
end.

137
Тогда один из комбо-боксов мне придется прятать, потому что пользователь будет взаимодействовать только с одним из них. А если комбо-бокс нужен только для того, чтобы хранить список, то разве создание самого списка без визуальной оболочки не было бы логичнее?
Да, конечно.

138
   Здравствуйте, metamorphogenesis.

  Ошибка в том, что функция repeatSign оказалась в разделе finalization. Её нужно перенести в раздел implementation:
Код: (delphi)
interface
var
    sl800ProductsNames, sl800ProductsCodes: TM_StringList;

function repeatSign(const sign: string; const count: integer): string;

implementation

function repeatSign(const sign: string; const count: integer): string;
var i: integer;
    temp: string;
begin
  temp := '';
  for i := 1 to count do temp := temp + sign;
  Result := temp;
end;

initialization
    sl800ProductsNames := TM_StringList.Create;
    sl800ProductsCodes := TM_StringList.Create;

finalization
    FreeAndNil(sl800ProductsNames);
    FreeAndNil(sl800ProductsCodes);
end.

Достаточно ли обосновано применение списков строк? Возможно проще использовать пару объектов ComboBox.

139
Ваши вопросы / Re: Скролл в таблицах
« : 23 Марта 2021, 19:30:09 »
   Здравствуйте.

  Таблицу с исходными данными можно сделать невидимой, а для отображения данных использовать таблицу фиксированного размера. Данные в таблицу отображения переписываются по изменению переменной, определяющей смещение в таблице данных.
Для прокрутки по таблице отображений можно использовать объект "Уровень". Пример во вложении.

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

  Разделение одного параметра на поля (не обязательно двоичные):
Tag = <парам.1> * 100 + <парам.2>, для Вашего примера Tag=1303. Использование:
Код: (delphi)
...
if GetBit(AsInt, Tag div 100) then            // основная переменная - парам.1
...
...
if GetBit(VariableEx.AsInt, Tag mod 100) then // дополнительная переменная - парам.2
...

141
Ваши вопросы / Re: Чтение из CSV-файла
« : 22 Февраля 2021, 16:42:22 »
    Здравствуйте.

Строка для разбора:
0R0,Dm=184D,Sm=2.4M,Ta=-12.9C,Tp=-12.2C,Ua=78.8P,Pa=1009.6H.
- не ясно что делать с 0R0;
- принадлежность завершающей "точки" к строке (да, нет);
- левый ограничитель подстроки значения - уникальный ключ в формате "<имя ключа>=";
- правый ограничитель - единица измерения в формате "<ед.изм.>," или "<ед.изм.>." (последняя пара) или "<ед.изм.>" (можно обойтись без "запятой")
Для удобства можно связать ограничители с непосредственно с переменной: левый как "Описание" переменной, правый - "Единица измерения" шкалы.
Например для ключа "Ta": "Описание" - "Ta=", "Единица измерения" - "C," или "C".
Для переменной, связанной с полем как основная и разбираемой строкой как дополнительная скрипт по изменению доп. переменной:
Код: (delphi)
var aStr: UTF8String;
    i: integer;
begin
  with Sender as TM_Field do begin
    i := StrPos(UTF8ToString(Variable.Description), VariableEx.AsStr);  // позиция ключа (левого ограничителя)
    if i = 0 then exit;                                                 // ОШИБКА: ключ не найден
    aStr := UTF8Copy(VariableEx.AsUTF8String, i + UTF8Length(Variable.Description), 200); // урезать строку до начала значения (200 - макс. длина подстроки значения !!!)
    i := StrPos(UTF8ToString(Variable.Units), UTF8ToString(aStr));      // позиция завершения подстроки значения (правого ограничителя)
    if i = 0 then exit;                                                 // ОШИБКА: завершение не найдено
    Value := StrToFloatDef(UTF8ToString(UTF8Copy(aStr,1, i - 1)), -999999); // значение из строки или по-умолчанию
  end;
end.
Если нет необходимости привязки переменных к полям (заполнение таблицы), скрипт можно переписать как процедуру или функцию (с двумя параметрами типа TM_Variable) в составе скрипта по изменению строки разбора, где собственно в скрипте организуется цикл (для таблицы) или перебор (произвольный набор переменных) вызовов функции/процедуры по всем переменным.
Во вложении пример (необходимо пересохранить) для набора полей и таблицы. Выпадающий список позволяет выбрать строку, в т.ч. и с ошибками. Можно вносить изменения в разбираемую строку и увидеть результат в полях и таблице.

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

... у меня в скаду из aropc приезжает пакет MQTT, как тег,  вида
{“T1”:70, “P1”:2.5}. Как мне разобрать его скриптами на два внутренних тега.

 Для выделения подстроки, содержащей значение тега, необходимо определить индексы левого и правого ограничителей подстроки.
Правым ограничителем являются одиночные символы "запятая" или "закрывающая фигурная скобка".
Левым ограничителем, в зависимости от способа выборки подстроки, может быть или "двоеточие" (последовательная выборка подстрок) или уникальное имя или часть имени тега завершающаяся символом "двоеточие" (доступ к произвольной подстроке).
Скрипт выборки подстроки по имени тега (выборка произвольной подстроки). Скрипт последовательной выборки см. пример во вложении.
Код: (delphi)
function TagSubString(TagName: string; InputString: string): string;
var i: integer;
    aStr: UTF8String;
begin
  i := StrPos(TagName, InputString);                             // поиск левого ограничителя
  if i = 0 then begin Result := ''; exit; end;                   // ОШИБКА: тега с таким именем нет
  aStr := UTF8Copy(UTF8Encode(InputString), i + Length(TagName),
          Length(InputString));                                  // обрезать строку по началу подстроки

  i := StrPos(',', UTF8ToString(aStr));                          // индекс правого ограничителя "запятая"
  if i = 0 then i := StrPos('}', UTF8ToString(aStr));            // если "запятая" отсутствует, то проверить на "скобку"
  if i = 0 then begin Result := ''; exit; end;                   // ОШИБКА: отсутствует правый ограничитель
  Result := UTF8ToString(UTF8Copy(aStr, 1, i - 1));              // подстрока
end;
var aStr: string;
begin
  aStr := TagSubString('"T1":', MQTTstring.AsStr);       // выделение подстроки тега "T1":
  if aStr <> '' then Tag1.Value := StrToInt(aStr);       // контроль наличия тега и перевод в целое
  aStr := TagSubString('"P1":', MQTTstring.AsStr);       // выделение подстроки тега "Р1":
  if aStr <> '' then Tag2.Value := StrToFloat(aStr);     // контроль наличия тега и перевод в вещественное
end.       

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

Для успешной компиляции нужно создать эти две переменные в редакторе (тип внутренняя, integer).
Пример в проекте во вложении к ответу #1.

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

Для нескольких клиентов можно определить правила вызова окна (только один клиент, несколько, для конкретного пользователя ...).

Пример во вложении (процесс создания шторок в комментарии скрипта).

145
Ошибки / Re: Ошибка с фиксацией окон
« : 01 Ноября 2020, 15:58:25 »
    Здравствуйте.

Стоит галка "Запоминать положение" и выбран вариант "По заданным координатам"
Описание галки в редакторе: "Сохранить положение окна после перемещения". При снятой - следует ожидать, что положение открываемого окна определяется "Положением окна при первом открытии".
Тесты не подтвердили это утверждение, в некоторых оно работало, в некоторых - нет. Так что с галкой полный туман.
Вариант "По заданным координатам" правильный - положение окна при первом открытии определяется парой свойств окна (.X, .Y). Далее подразумевается что все окна "По заданным координатам".

Цитировать
... то когда пользователь заходит на страницу, то он единовременно прописывает координаты для окна. После закрытия окна, оно обращается за координатами к той прописи, которая была в самом начале, ...
Только для окон с изменяемым начальным положением, прописывать координаты для окон с фиксированным положением нет необходимости.
При закрытии окна функция SetPosAll(Xокна,Yокна) перемещает окно к начальному положению (делает то, что ожидалось от снятой галки "Запоминать положение").

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

Цитировать
Записал гифку с моими действиями...
Пример управления окнами по Вашим действиям во вложении - проект "Начальные_Положения_Окон". Проект содержит два "лишних" скрипта из-за особенностей текущей реализации возвращаемых значений функции SetPos.


      Теперь о проблеме позиционирования окон, как она выглядит со стороны пользователя.

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

146
Ошибки / Re: Ошибка с фиксацией окон
« : 28 Октября 2020, 22:31:22 »
Здравствуйте.

Для текущей версии первый раз на странице окно открывается в положении задаваемым редактором, далее повторно открывается по внутренним координатам, включая их изменение пользователем или скриптом.
Для Вашей задачи можно использовать окно с положением "По заданным координатам", для которого начальное положение определяется парой свойств окна (.X, .Y).
Скрипт "Пользователь перешел на эту страницу" устанавливает значение пар (X, Y) для всех окон (с таким правилом поведения) данной страницы. Например, для одного окна при индивидуальным положении на каждой странице скрипт выглядит так (переменные или константы (X1,Y1) - координаты окна на первой странице, (X2,Y2) - на второй и т.д., свойство Tag страницы определяет номер набора):
Код: (delphi)
begin
  Window1.X := GetVariableByName('X' + IntToStr(Sender.Tag)).Value;
  Window1.Y := GetVariableByName('Y' + IntToStr(Sender.Tag)).Value;
end.
Для восстановления начального положения при повторном открывании окна необходимо установить координаты окна скриптом "Пользователь закрыл это окно":
Код: (delphi)
begin
  Window1.SetPosAll(Window1.X, Window1.Y);
end.
Пример во вложении (после разархивирования проекта его необходимо пересохранить).

147
. . .   
в OPC изменения значения видны, но в скаде не работает, что не так?
Задача передать 32 значения в скаду одной переменной, и на каждое создать определенные скрипты для индикации состояний.
. . .


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

Скорее всего не вызывается скрипт по причине отсутствия события: либо скрипт не назначен на событие, либо отсутствует связь переменных in32scada и myvar1.

Для групп объектов с одинаковым поведением определяемым битом из in32scada, можно построить следующие схемы:
 - основная переменная объекта in32scada, номер бита упакован в свойство Tag объекта, скрипт на событие "Изменилась основная переменная":
Код: (delphi)
begin
  if Sender is TM_Image then   // сначала убедимся, что скрипт вызван объектом изображение
    with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
      if GetBit(AsInt, Tag) then begin
        FlashColor := clGreen; // включить мигание объекта зеленым цветом
        Color := clWhite;      // второй цвет мигания
      end else begin
        FlashColor := clNone;  // выключить мигание объекта
        Color := clBlack;      // цвет объекта в этом (false) состоянии
      end;
end.
;
 
- основная переменная объекта myvar<i>, где i - номер бита, скрипт на событие "Изменилась основная переменная":
Код: (delphi)
begin
  if Sender is TM_Image then   // сначала убедимся, что скрипт вызван объектом изображение
    with Sender as TM_Image do // далее будем работать с объектом Sender, как с изображением
      if AsBool then begin
          FlashColor := clGreen; // включить мигание объекта зеленым цветом
          Color := clWhite;      // второй цвет мигания
        end
      else begin
          FlashColor := clNone;  // выключить мигание
          Color := clBlack;      // цвет объекта в этом (false) состоянии
        end;
end.
, связь переменных in32scada и набора myvar<i> осуществляет скрипт "Изменилась переменная in32scada":
Код: (delphi)
var i: integer;
    aVar: TM_Variable;
begin
  for i := 0 to 31 do begin       // в реальном проекте можно ограничить цикл или индивидуальные присвоения переменным без анализа наличия
    aVar := GetVariableByName('myvar' + IntToStr(i));
    if aVar <> nil then aVar.Value := GetBit(Variable.AsInt, i);
  end;
end.
При наличии нескольких групп объектов необходимо для каждой группы написать свой скрипт.

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

148
Ваши вопросы / Re: Таблицы и TM_DataSet
« : 07 Октября 2020, 10:33:05 »
 Здравствуйте.

  Собственно проблема в блокировке вмешательства в некоторый процесс во время его выполнения, в Вашем случае - загрузка ПЛК. Варианты:

1. Загрузка без вмешательства оператора ("кнопку нажал и мешок на спине"), процесс завершится через некоторое время. Достаточно ввести флаг загрузки для запрета вмешательства на время загрузки.
В прилагаемом примере (стр. "Загрузка по времени") в качестве флага использовано свойство видимости кнопки "Загрузка ПЛК". При нажатии кнопки устанавливается в False, по завершении загрузки в True.

2. Оператор может вмешиваться в процесс загрузки. У него отображается активный экран, отличающийся от остальных минимально наличием элементов управления.
Признаком активного экрана (и соответственно способа построения) может быть уникальное имя клиента или имя оператора. Аналогично предыдущему случаю есть и флаг загрузки (= имя активного клиента, пример стр. "Загрузка под контролем оператора"), однако для него возникают проблемы сброса флага:
- активный оператор "уснул";
- активный оператор вышел из проекта не сбросив флаг (не завершив загрузку).
И если для первого случая можно ввести временной контроль активности оператора, то для второго нужен выход по кнопке с запретом выхода по комбинации клавиш из Simple-Scada и по alt/F4. Но простое пропадание питания ПК активного клиента (с сохранением на ПК сервера) прервет загрузку и не сбросит флаг.
Для двух мониторов, равно как для двух клиентов с одинаковыми именами, повторного начала загрузки не будет, но управление возможно от обоих.

Появление в Simple-Scada процедуры "Получить список клиентов данного проекта" сделало бы возможным отслеживание "отпавших" клиентов (активных).

149
 
присвоение переменной DeltaT.value разницы T1.Value через каждые 10 секунд.
Не совсем понял разницы T1.Value с чем.
     Здравствуйте.

Для вычисления изменения переменной за интервал времени необходимо добавить виртуальную переменную previousT1 для хранения предыдущего значения переменной T1, локальную temporaryT1 для текущего значения переменной T1 и изменить скрипт следующим образом:
Код: (delphi)
const
  INTERVAL = 10;  // Интервал таймера 10 сек
var temporaryT1: <тип T1>;
begin
  vrTimer.Value := vrTimer.Value + 1; // накапливаем секунды в vrTimer

  // если прошло больше, чем INTERVAL секунд с последнего срабатывания таймера
  if vrTimer.Value >= INTERVAL then
  begin
    // код размещенный здесь будет выполняться каждые 10 секунд
    temporaryT1 := T1.Value;     // для разностных схем следует использовать только одно значение в качестве конечного и начального на смежных интервалах
    DeltaT.Value := temporaryT1 - previousT1.Value; // присваиваем переменной DeltaT.Value результат изменения T1.Value за интервал
    previousT1.Value :=  temporaryT1;               // сохраняем как предыдущее значение для вычисления следующей разности

    vrTimer.Value := 0; // обнуляем счетчик
  end;
end.

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

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

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