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

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

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

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


Сообщения - pan2000

Страницы: [1] 2 3 ... 16
1
Ваши вопросы / Re: Расписание.
« : 04 Октября 2026, 00:31:41 »
  Здравствуйте.

  Интервалы расписания можно рассматривать как двумерный массив с фиксированным числом строк (день расписания) и переменным числом столбцов (интервал расписания). Оба индекса массива начинаются с 0. Из текущей даты функцией DayOfTheWeek(Now) можно получить день недели, но в диапазоне 1 - 7.
  Определить время перехода расписания в противоположное, относительно текущего, состояние можно по анализу интервалов. Для удобства границы интервалов считаются в секундах относительно начала суток вызова скрипта.
  Однако следует учитывать, что интервалы дня не упорядочены и могут перекрываться, т.е. анализ следует вести по всем интервалам расписания, а с учётом цикличности расписания - по сегодня дважды.
  Алгоритм заключается в движении по времени суток временной метки, начиная со времени вызова скрипта и соответствующей состоянию расписания на момент вызова. Все интервалы последовательно (а, для включенного состояния ещё и многократно) сравниваются с временной меткой, и по результатам принимается решение о перемещении метки. По достижении конца суток принимается решение о переходе к следующим суткам или завершении процесса.
  Алгоритм работает как при отсутствии интервалов в расписании, так и полностью "закрашенном" в любом порядке расписанием.
  Пример во вложении.

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

Существует скрипт который исполняется раз в секунду, данные накапливаются и выводятся в рапорт, но столкнулся с проблемой что запись в переменные накопления происходят не у всех.
Вопрос: это неправильно сделанный скрипт или что то еще - куда смотреть?

  Проверить на ошибки фазы выполнения проекта. См. журнал сервера.

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

  При выполнении функции возможно изменение значения входной переменной. Значение входной переменной типа TM_Variable следует передавать как типизированное значение (формально параметр по значению передается локальной копией, однако при передаче объекта (экземпляра класса), внутри функции/процедуры работа идет со ссылкой на тот же объект, а не с копией его полей):
Код: (delphi)
procedure CalcWeightSum(FLeistung: single; var WeightSum, WeightDelta: TM_Variable);
begin
   // 1. Проверяем, входят ли текущие показания в допустимый диапазон
   if (0 <= FLeistung) and (FLeistung <= MaxLimit) then
. . .
end;

begin
   CalcWeightSum(FLeistung_BW_P10_1_db256.AsFloat, WeightSum_P10_1, WeightDelta_P10_1);
. . .
end;
или однократным присвоением локальной переменной:
Код: (delphi)
procedure CalcWeightSum(FLeistung: TM_Variable; var WeightSum, WeightDelta: TM_Variable);
var aFLeistung: single;
begin
   aFLeistung := FLeistung.AsFloat;
   // 1. Проверяем, входят ли текущие показания в допустимый диапазон
   if (0 <= aFLeistung) and (aFLeistung <= MaxLimit) then
. . .
end;

Дальнейшие проверки определятся уже из методики обнаружения отсутствия записи.


                                             Добавлено 26.09.2026.

P.S. Замечания по алгоритму скрипта и (в случае архивирования данных) использование функций для работы с архивом. Правило для вышедших за границы значений:
- принимает граничное значение. Архивирование переменной с применением фильтра "Фильтр мин. макс.", функции работы с архивом будут выдавать правильный результат;
- принимает предыдущее значение или игнорируется. Архивирование дополнительной переменной после соответствующей фильтрации. функции работы с архивом будут выдавать правильный результат;
- принимает предыдущее значение. В скрипте необходимо перенести суммирование за условное выражение, а само условное выражение оставить только для корректных значений;
- игнорируется. Как это сделано в скрипте. Появление игнорируемых входных значений приведёт к искажению результата в сторону уменьшения, т.к. [за период 3600 сек] суммируется меньшее число отсчетов. Т.е. для каждой переменной необходим счётчик числа корректных значений. Для правильного результата применяется повышающий коэффициент 3600/<значение счетчика>.

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

Во вложении карта памяти [Modbus] устройства РС83-В1 с журналом аварий на 100 событий. Структура ошибки очень похожа, но способ чтения совершенно другой.
1. Для начала нужно получить доступ к данным. Вам необходимо построить (пытая китайское устройство) или подсмотреть в китайском руководстве (цифры-то арабские) аналогичную карту.
2. Определить критерий, когда приняты все данные ошибки, особенно если линии связи с прибором медленные.
3. Из Вашего сообщения и документа на РС83-В1 можно предположить:
 - чтение журнала ведется из очереди аварий по одной аварии через буфер, который заполняется при записи в регистр (4000) смещения относительно начала очереди;
 - аварии последовательно заполняют очередь, и при её переполнении новая авария вытесняет первую аварию из очереди. Скорее всего для контроля этой ситуации используется возвращаемое значение "номер аварии" - порядковое число 0..65535;
 - в незаполненной очереди признаком конца очереди является авария с нулевыми данными;
 - полностью заполненная очередь создаёт еще одну ошибку: если между чтением двух аварий вклинится новая запись аварии-события, то произойдет сдвиг аварий в очереди, что нарушит всю последовательность чтения по номеру аварии.
4. Что ещё следует поискать в китайском устройстве и что, по моему мнению, крайне необходимо. так это удаление прочитанной ошибки из очереди, что даёт возможность (если не зевать с удалением ошибок) исключить переполнение очереди.
5. Самое простое китайское решение - НЕ ДЕЛАТЬ НИЧЕГО ВООБЩЕ, т.е. считать что есть только кольцевой буфер аварий, с началом индексации от самой старой сохранившейся аварии.  В этом случае алгоритм на основе анализа возвращаемого значения "номер аварии" пытается упорядочить прочитанные аварии в порядке последовательного возрастания номеров аварий.
  Пример из вложения содержит модель "Китайского устройства" (генерация ошибки/серии, очистка очереди) и окно получения данных (чтение ошибки по смещению, чтение списка аварий, очистка окна). Пример только для иллюстрации алгоритма, тщательно проверять его нет смысла пока не будут решены вопрос п.1 и п.2.
Для ввода повторного значения используйте лидирующий 0.

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

Возник вопрос: А если обработку поставить на скрипт "Изменилась переменная" и сделать флаги bool, которые завести как доппеременную и по ней менять цвет. Обработка будет быстрее или без разницы?
В данном случае это вопрос из серии "кто кого поборет: слон или кит?".
В первом приближении сравнивать времена обработки можно по количеству отдельных временных интервалов выполнения действия: как то вызов события, доступ к переменной (прямо, функция, метод), вычисления.
Скрипт "Изменилась переменная" (далее Скрипт, а вариант с событиями ячеек таблицы - Таблица):
- дополнительный вызов события при изменении флага (хотя ожидается, что это событие достаточно редкое), дополнительные переменные-паразиты под флаги, зависимость объема Скрипта/Скриптов от количества переменных;
- потенциальная возможность ошибок из-за необходимости явного перечисления имён в Скрипте. При соответствующим выборе имен переменных для Таблицы-шаблона достаточно только номера набора;
- выбор буриданова осла: один Скрипт со всем "легионом" входных переменных (а при большом количестве "массовки" незаметно подкрадётся ... ошибка) или множество индивидуальных Скриптов только со своими переменными. В первом случае, переменные по очереди пролазят через "бутылочное горлышко" параметра Скрипта и его выполнение начнется с селекции переменной (либо пачка операторов IF, либо CASE, причем следует ожидать, что с увеличением числа переменных пропорционально увеличится и среднее время выполнения Скрипта. Во втором - копирование и нудная правка скриптов. Вторая неприятность заключается в выборе всех необходимых для вычисления переменных (три тока), и тут начнётся шаманство с формированием имён для GetVariableByName(). В Таблице переменная скрыта за номером столбца и строки (своеобразный массив), в имени переменной нет необходимости, доступ к ней посредством метода GetCell(,).

ИТОГ: Таблица и Скрипт по количеству соизмеримых временных интервалов практически идут "ноздря в ноздрю", но ресурсов Скрипту/Скриптам потребуется больше: флаг-переменные и объём скрипта пропорциональный количеству переменных. И вишенка на торте - сравнение затрат и простоты при увеличении количества переменных.

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

1. По-видимому ячейка TM_TableCell лишь частично обладает свойствми TM_Control и TM_Object
2. Столбцы и строки таблицы всегда нумеруются с нуля. Если под заголовком понимается "Фиксированная строка", то это только сервисная расскраска таблицы, равно как и "Фиксированный столбец" и "Чередование".
3. Ячейка имеет свойство Variable, доступ к значению переменной ячейки: Cell.Variable.<тип значения>.
4. Обратится к значению другой ячейки можно только на уровне таблицы: <имя таблицы>.GetCell(<номер столбца>,<номер строки>).Variable.<тип значения> или <имя таблицы>.Columns[<номер столбца>].Cells[<номер строки>]...
Пример во вложении. Значения величин записываются непосредственно в ячейку. Можно выбрать один из двух скриптов, изменяющий цвет при авариях тока: все или только аварийные.

P.S. Изменение цвета шрифта не лучшая идея, по моему мнению более заметно изменение фона ячейки (причем для границ - стандартными средствами, без скрипта).

6
        Здравствуйте.
... в момент OnInit проверка (Sender is TM_Window) не проходит, да и свойства самого экземпляра еще толком не подтянулись.
К моменту события OnInit шаблонного окна все его объекты уже определены. Проблема состоит в том, что ссылка на экземпляр не приводится к типу TM_Window (возможно будет обновление-исправление). Однако работает ссылка как на владельца любого объекта окна.
Код: (delphi)
var
  idx : Integer;
  SaveButton : TM_Button;
begin
  if (TM_Window(GetTemplateObject('Flow1').GetOwner).Title <> 'Настройки ВКТ')then
    for idx := 0 to 2 do
      GetTemplateObject('Flow' + IntToStr(idx)).Visible := True;
end.
Цитировать
...эстетическая проблема...
эстетическая проблема должна исчезнуть после первого открытия окна, т.к. конфигурация объектов уже установилась.

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

Фиксированное положение окна не есть хорошо, если пользователь захочет посмотреть что же там за окном.

Артефакт есть следствие неправильной последовательности действий с окнами. Правильная последовательность: открыть новое окно, когда окно откроется и аккуратно перекроет предыдущее (событие окна OnShow), закрыть все прочие окна. Но для окна-формы, значения из которого запоминаются, эта последовательность не работает, т.к. окно автоматически закроется.

Для интерфейса настроек Вам необходимо выбрать характеристики окон (обычное - форма ввода), количество групп окон и возможность дальнейшего масштабирования проекта. Кроме того определить структуру имен объектов и переменных, что будет важно при использовании универсальных скриптов. (Я бы предложил вынести название агрегата в префикс: GeneralSettingPOU25 -> POU25_GeneralSetting или POU25_wndGeneralSetting, что по крайней мере соберёт в инспекторе все объекты в "кучку").

Во вложении пример для настройки с группой окон-форм двух агрегатов ПОУ25 и ПОУ26.
Дополнительные затраты для настроек ещё одного аналогичного агрегата:
- набор окон;
- объект/объекты начального вызова группы и скрипт вызова.
Для новой группы окон дополнительно ещё потребуются шаблон для переключения окон, скрипт его инициализации и скрипты работы с формой.

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

Во вложении пример интерфейса настроек на основе больших (размером в экран) оконных форм. Настройки одной формы образуют связанную группу данных, не подверженных влиянию других пользователей. Т.е. в форме желательно иметь максимальное количество настроек.
Интерфейс обеспечивает переключение между окнами и повторное открытие окна после действия "отправить форму".
Увы, но имеется артефакт переключения - между окнами "проскальзывает" изображения страницы.
 
P.S. можно уменьшить размер окон, но тогда по причине отсутствия механизмов перемещения окон или их фиксации, окна просто "разбегутся" по экрану

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

Для выделенной области сообщение формируется только при переходе значения переменной в эту область (включая границы), а изменение значения внутри области (включая границы) и выход из области не формирует сообщение. Выход из области добавляет время выхода в уже сформированное сообщение.
Цитировать
Мне важно уточнить, правильный ли я вывод делаю, что одна из границ в диапазоне "-55..-53" является не включённой, то есть [-55...-53)?
Дополню, что если ставить диапазон "-55..-55" то ничего не работает, что полагаю логичным.
Оба вывода неправильные, т.к. значения температуры задаются в вещественном виде от эмулятора, а границы - целыми числами.
Диапазон  "-55..-55" правильно формирует сообщение, хотя судя по "Основному руководству/Свойства состояний сообщения", установить одинаковые граничные значения "от" и "до" нельзя.
Судя по:
Цитировать
Через эмулятор я отправляю аналоговое значение "-53" - сообщение НЕ формируется на скаде.
Отправленное значение на самом деле содержит ненулевую дробную часть например, -52.99999ххх.

Цитировать
Отправляю "-54" - сообщение формируется на скаде.
Это правильно, т.к. вещественное "около -54" стало первым попаданием в область.

Цитировать
Отправляю "-55" - сообщение формируется на скаде.
Весьма неожиданно, т.к. для сообщения значение должно было выйти из области и войти в неё вновь (либо Вы ошиблись, отправив перед этим значение вне области, либо такое значение отправил эмулятор).

Цитировать
Для проверки отправляю "-56" - сообщение НЕ формируется на скаде.
Правильно, любое новое значение после формирования сообщения НЕ приводит к формированию сообщения.

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

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

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

 Для построения таблицы гибридного отчёта можно использовать данные из системных таблиц Simple-Scada по следующему алгоритму:
- по таблице messages строится таблица с необходимыми для отчета колонками и добавлением колонок для переменных. Критерием отбора строк служит номер группы сообщений и временное окно построения;
- из таблицы отчета выбираются все временные метки и помещаются в динамический массив;
- по массиву в цикле выполняются получение значений переменных из архива по временной метке (ArchiveValueByTime) с последующей записью в таблицу отчёта.
Из-за использования архивной функции построение таблицы достаточно медленное. Однако данный алгоритм слабо связан с проектом и не требует дополнительных ресурсов БД.
Пример во вложении предельно упрощен. Секундный генератор переменных. Две кнопки событий и фиксированные имена переменных. Отбор только по номеру группы сообщений (можно предварительно почистить таблицы). Кнопка "Построить таблицу", визуализация результата в таблице Simple-Scada.

11
Ваши вопросы / Re: Иконки кнопок
« : 22 Июля 2026, 22:30:34 »
      Здравствуйте.

 Для однотипных кнопок можно использовать шаблон, по крайней мере нет необходимости в ручной связке кнопки и изображения, которая выполняется при инициализации экземпляра шаблона.
 Шаблон для битовой кнопки является универсальным, т.к. имеет две подстановки - имя переменной и номер бита. Пример во вложении.

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

Подскажите как в скрипте из события OnEnter объекта Page получить имя текущей подстраницы?
В скрипте OnEnter объекта Page нет возможности получить имя текущей подстраницы. При переходе на страницу (подстраницы ещё скрыты) однократно последовательно вызываются скрипты OnEnter и OnEnterSubpage текущей подстраницы, далее при переходам по подстраницам вызывается скрипт OnEnterSubpage.
Следовательно, достаточно поместить текст обработки имени текущей подстраницы в скрипт OnEnterSubpage и обеспечить однократность его выполнения при первом вызове. В универсальных скриптах для флажка однократности можно использовать свойство Tag страницы.
Скрипт OnEnter:
Код: (delphi)
begin
  Sender.Tag := 0;       // 0 - скрипт OnEnterSubpage ещё не вызывался
end.
Скрипт OnEnterSubpage:
Код: (delphi)
begin
  with TM_Subpage(Sender).OwnerPage do         // страница для текущей подстраницы
    if Tag = 0 then
    begin
      Tag := 1;    // однократность вызова скрипта при переходе по подстраницам
// действия по обработке имени текущей подстраницы

      Text1.Text := Text1.Text + 'ПЕРЕХОД: Page=' + Name + '    SubPage=' + TM_Subpage(Sender).Name + #10;

    end;
  Window1.ShowAll;
end.
Пример из вложения показывает список имён последних активных подстраниц перед переходом к другой странице.

P.S. Если Tag занят, то в качестве флага и имени подстраницы можно использовать строковую переменную. Для универсальности скриптов имя переменной должно быть связано с именем страницы, например так: vrSubPageName_<Имя страницы>.

13
Ваши вопросы / Re: Иконки кнопок
« : 24 Июня 2026, 13:21:37 »
      Здравствуйте.

  Если без своих иконок не обойтись, то можно использовать комбинацию объектов "изображение" (иконки - покадрово) поверх объекта "кнопка".
Взаимодействие осуществляется посредством общей переменной, ссылка на "кнопку" - свойство Hint объекта "изображение".
  Пример для кнопки с 5-ю состояниями, кнопки с 2-состояниями (более простые скрипты) и битовой кнопки во вложении.
Предварительно необходимо добавить файл иконок animals.png из папки ""User files" проекта в папку изображений "Pictures".

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

Если Вашу задачу не удается перенести на ПЛК, то её можно решить с помощью трёх скриптов по изменению переменных: первые два отвечают за присвоение значения изменившейся переменной из выбранной группы (auto или manual) соответствующей переменной из выходной группы (trend), а третий - за перезапись всей группы (auto или manual) в выходную по изменению значения переменной выбора режима.
Код: (delphi)
// запись по изменению переменной из группы auto, если она выбрана
begin
  if not vrMode.AsBool then exit;    // выбрана группа manual
  with Variable do                   // запись для группы auto
    GetVariableByName(Copy(Name, 1, Length(Name) - 4) + 'trend').Value := Value;
end.
Код: (delphi)
// запись по изменению переменной из группы manual, если она выбрана
begin
  if vrMode.AsBool then exit;    // выбрана группа auto
  with Variable do               // запись для группы manual
    GetVariableByName(Copy(Name, 1, Length(Name) - 6) + 'trend').Value := Value;
end.       
Код: (delphi)
const ListName: array[1..2] of string [32] = ('Cucumber_','Alcohol_');
var i, j: integer;
    aStr: string;
begin
  for i := 1 to High(ListName) do            // цикл по реагентам
    for j := 1 to 3 do                       // цикл по номеру стадии
    begin
      aStr := ListName[i] + IntToStr(j);     // префикс имени переменной = %Название реагента%_%Номер стадии%
      if vrMode.AsBool then                  // выбор переменной-источника по переменной режима
        GetVariableByName(aStr + '_trend').Value := GetVariableByName(aStr + '_auto').Value
      else
        GetVariableByName(aStr + '_trend').Value := GetVariableByName(aStr + '_manual').Value;
    end;
end.
Пример двух реагентов по три стадии во вложении.   

15
Ваши вопросы / Re: Integer overflow
« : 13 Марта 2026, 07:24:28 »
Здравствуйте.

для начала нужно разделить ошибки функции и аргумента
Код: (delphi)
  j := AsInt*60;   // var j: integer;
  Text := SecondsToStr(j);

Цитировать
Предполагаем, что именно 51000 вызывает переполнение.
"Кофейная гуща" не поможет выявить ошибку :D. Для отлова ошибок (особенно редких) можно изменить скрипт:
Код: (delphi)
begin
  if Sender is TM_Text then   // проверяем, что Sender это текст
    with Sender as TM_Text do // приводим Sender к типу "TM_Text"
    try
      Text := SecondsToStr(AsInt*60);
    except
      AddMessageToGroup(now, mkAlarm, 1, 'ошибка: объект ' + Name + '    переменная '
           + Variable.Name + ' = ' + AsStr + ' * 60 = ' + IntToStr(int64(AsInt) * 60)
           , true, false);
    end;
end.
Сообщение об ошибке будет в журнале сервера и, в несколько более детальном виде, в выделенной группе сообщений.
Проект контроля переполнений из примера можно не подключать к БД, при просмотре группы сообщений будет выдано предупреждение, но поступающие сообщения будут отображаться.

Страницы: [1] 2 3 ... 16