Показано с 1 по 9 из 9

Тема: Наследование методов и DUT полиморфизм в CODESYS 3.5 .16

  1. #1

    По умолчанию Наследование методов и DUT полиморфизм в CODESYS 3.5 .16

    Общий вопрос по программированию ПЛК в CODESYS.
    Хотя проект выполнен на другом вендоре, есть перспектива переноса на ПЛК ОВЕН.
    Также, т.к. вопрос общий, думаю он может быть интересен всем, кто работает на CODESYS.
    Потому и на вашем форуме решил скромно попросить совета.

    Проект уже написан и крутится на объекте, но есть кривой момент, который хочется устранить.
    Кроме основной проблемы, буду рад любым советам по описанной далее структуре данных,
    т.к. это моя первая большая программа, причём сразу для резервированного ПЛК.
    Были маленькие программы, был небольшой опыт на языках программирования общего назначения.
    Был синий пояс на codewars Ну и n-ное количество лет электромонтером и инженером. Предложения о работе в личку

    ПЛК Эмикон, резервированный, из особенностей: отсутствие аппаратной части в проекте, нет поддержки RETAIN.
    Вместо этого статическая меркерная память и отдельный конфигуратор, что не суть, т.к. не в этом проблема.
    По ссылке внизу выложен архив проекта, точнее кусок, выдранный из реального проекта, чтобы показать проблему.
    Можно запустить через эмулятор Эмикон, среду CONT-Designer и полный импорт проекта, если кто-то знаком c этим вендором.
    Если нет, не советую ставить их среду, эмулятор или вообще пользоваться данным ПЛК Откройте проект в любом CODESYS, ничего не импортируйте, далее вы можете видеть код, при желании скопировать его в рабочий проект на вашей среде, с которой работает ваш эмулятор.

    Были применены элементы ООП, ради удобства и чтобы не дублировать код.
    Проект написан смесью CFC и ST, но CFCшная часть в представленном куске минимизирована до голых вызов ФБ.
    В реальном проекте, это не только вызовы ФБ, но и возможность управлять объектом онлайн, имея CFC диаграммы в качестве мнемосхемы на втором мониторе, при ПНР это удобно. Там расписаны входа-выхода, чтобы всё отображалось.

    Описание проекта.
    - Есть Basic_FB, содержит поля, нужные всем, абстрактный.

    - Есть абстрактные ФБ (DE_Basic, RVS_Basic) агрегатов, в примере: дренажные емкости DE и резервуары RVS - наследующие Basic_FB.
    В этих абстрактных ФБ расписан набор методов с модификатором protected, без аргументов, работающих напрямую с полями ФБ.
    Переход на работу через аргументы ничего не изменит в проблеме, скажу сразу, т.к. дело не в самих аргументах, а в их типе.

    - Есть конкретные ФБ агрегатов, наследующие абстрактным ФБ своей группы, т.е. ФБ DE1, DE2 наследуют DE_Basic. Им доступны все поля и методы родителя, своих полей они не имеют, разве что отладочные поля или при появлении специфического для данного агрегата функционала. Создаются в программе в единственном экземпляре. Т.е. это так называемые синглтоны, но без программного.
    Методы родителя могут быть переопределены, если агрегат вдруг стал чем-то отличаться от остальных.

    - Есть отдельные функции (хелперы), но разумеется они могут выполнять лишь часть функционала, который точно одинаков для всех.

    - Есть структуры данных, свои для каждой группы агрегатов, лежат в папке DUT. Все структуры этого куска проекты имеют один тип внутри, чтобы их объединить в Union с таким же массивом и иметь сразу и возможность работы с циклом через индексы, и доступ по имени через точку. Там же есть папка Default, для таких структур с уставками по умолчанию, но без Union, т.к. эти структуры просто копируются в рабочие при необходимости.

    Там же есть папка Unions, содержащая юнионы, в каждом юнионе два поля, strt и arr.

    Структуры тоже используют наследование. Структуры вида ***_Sensors содержат REAL поля для датчиков, а все структуры с уставками наследуют структуре вида ***_Sensors, автоматически получая тот же набор полей. Есть структура Flags, где те же самые поля, но типа WORD, чтобы взводить в них биты, связанные со срабатыванием уставок. Наконец, ***_SETPOINTS_RETAIN содержит полями все Unions уставок, чтобы всё это разом можно было синхронизировать между ведущим и ведомым ПЛК через статическую память. Т.е. в ФБ работа ведётся именно через эту структуры, а вся остальная иерархия нужна, чтобы быть объявленной в ней.

    Код:
    {attribute 'pack_mode' := '2'}
    TYPE RVS_Sensors :
    STRUCT
    	Tempr					:REAL;
    	Level					:REAL;
    	empty3					:REAL;
    	empty4					:REAL;
    	empty5					:REAL;
    	empty6					:REAL;
    	empty7					:REAL;
    	empty8					:REAL;
    	empty9					:REAL;
    	empty10					:REAL;
    END_STRUCT
    END_TYPE
    Код:
    {attribute 'pack_mode' := '2'}
    TYPE RVS_Presets_HH EXTENDS RVS_Sensors:
    STRUCT
    END_STRUCT
    END_TYPE
    Код:
    {attribute 'pack_mode' := '2'}
    TYPE RVS_Flags :
    STRUCT
    	Tempr					:WORD;
    	Level					:WORD;
    END_STRUCT
    END_TYPE
    Код:
    {attribute 'pack_mode' := '2'}
    TYPE uRVS_Sensors :
    UNION
    	strt			:RVS_Sensors;
    	arr			:ARRAY[0..CONST.RVS_Sensors_Count] OF REAL;
    END_UNION
    END_TYPE
    Для индекса массивов применены константы, позволяющие задавать длину в одном глобальном списке. В структурах заложены пустые поля, которые можно отрезать, указав длину юнион-массива меньше, чем количество полей структуры. Всё довольно просто, это как птица, тело которой - основные структуры, левое крыло - юнион с массивом для расширения функционала, правое крыло - дефолтные значения уставок. А, сама птица посажена в "клетку" вида ***_SETPOINTS_RETAIN, чтобы её переносить, т.е. для синхронизации памяти. Напомню, что это кусок проекта, в рабочем я ещё и STATE также синхронизирую, куда входят команды, флаги, и.т.п. Т.е. в представленном куске проекта есть артефакты, которые задействованы в полном проекте.

    Всё, структура данных и наследование, в двух словах, описаны. Кто сказал, что программирование это сложно?

    Проблема.
    В каждом абстрактном агрегатном ФБ: DE_Basic, RVS_Basic, а в реальном проекте их конечно больше - есть полностью одинаковые методы. В данном куске оставлен метод AI_check(). Приведу его код, хотя сам код тут не имеет никакого значения. Он работает с полями ФБ, в котором объявлен, т.к. они ему доступны непосредственно. Экземпляры, получив всё от родителей, имеют свои уникальные "копии" всего этого функционала.

    Код:
    METHOD PROTECTED AI_check : BOOL
    VAR
    	i					:DINT;
    END_VAR
    
    FOR i:= LOWER_BOUND(Sensors.arr, 1) TO UPPER_BOUND(Sensors.arr, 1) DO
    	
    	IF LIMIT_LOW(Sensors.arr[i], Presets_LL.arr[i]) THEN BIT_IN_WORD( Flags.arr[i], 1); END_IF
    
    	IF CHECK_LOW(Sensors.arr[i], Presets_Low.arr[i], Sensors_Hyst.arr[i] )
    	THEN BIT_IN_WORD( Flags.arr[i], 2);
    	ELSE ZERO_IN_WORD( Flags.arr[i], 2);
    	END_IF
    	
    	IF CHECK_HIGH(Sensors.arr[i], Presets_High.arr[i], Sensors_Hyst.arr[i])
    	THEN BIT_IN_WORD( Flags.arr[i], 3);
    	ELSE ZERO_IN_WORD( Flags.arr[i], 3);
    	END_IF
    
    	IF LIMIT_HIGH(Sensors.arr[i], Presets_HH.arr[i]) THEN BIT_IN_WORD( Flags.arr[i], 4); END_IF
    	
    END_FOR
    Код методов полностью одинаковый из-за того, что везде применены одни и те же имена, т.е. идентификаторы для экземпляров пользовательских типов, объявленных в абстрактных ФБ. Но, типы разные, хотя имена те же!!!

    Поэтому, нет возможности вынести эти одинаковые методы на уровень выше, т.е. в Basic_FB. Конечно, что просто их туда скопировать, они и поля "видеть" перестанут, т.к. в Baisc_FB, ни Sensors, ни Flags ещё не объявлены, т.е. на этапе компиляции они недоступны. ИХ можно передать аргументами, но аргументы какого типа мы будем принимать? Верно, один для одного, другой для другого, работать так не будет.

    Решение которое я знаю - применение низкоуровневого копирования. Но, если для Эмикон это можно считать нормой, для того же Regul, например, уже нельзя. Как тогда решить проблему?
    Есть информация о том, как организовать ООП полиморфизм для стандартных типов, хоть runtime, хоть compiletime.
    Но, здесь типы пользовательские, утрачивать имеющуюся архитектуру данных не хотелось бы, да и не ясно, как всё это вообще организовать человечески, без применения структур?

    В языках общего назначения есть функционал типа Generic'ов, хотя я очень поверхностно с этим знаком, который вроде как позволяет делать именно это, т.е подставлять нужный тип. Хотя, повторюсь, могу ошибаться. В Codesys такого функционала нет.

    При любом изменении кода этих методов, его приходится вручную копировать во все ФБ, что как механические ошибки вызывает, т.к. просто "криво" по своей сути. Такой код должен быть где-то в одном месте, плюс должна быть возможность его переопределить. Т.е. это должны быть именно наследуемые методы. Но, как добиться, чтобы метод принимал данные разных пользовательских типов, в этом случае? Во время написания проекта я убил где-то день на попытку решить этот вопрос, после чего оставил как есть, т.к. сроки были ограничены. Теперь я попробую ещё раз, но сразу хочу посоветоваться на предмет возможных решений. Плюс, возможно моя архитектура данных неудачна, хоть она мне и нравится?

    Прошу у форума помощи в решении озвученной проблемы.

    https://disk.yandex.ru/d/ezVjYTNgdu0Dzw

  2. #2

    По умолчанию

    вы надеюсь не сторонник теории троичной логики? а то весной еле-еле пережили весеннее обострение, не хотелось бы снова входить в реку дважды

  3. #3

    По умолчанию

    В каждом абстрактном агрегатном ФБ: DE_Basic, RVS_Basic......оставлен метод AI_check()
    а зачем засовывать обработку аналога в агрегатный ФБ?

  4. #4

    По умолчанию

    Цитата Сообщение от In_Da_Cher_A Посмотреть сообщение
    а зачем засовывать обработку аналога в агрегатный ФБ?
    Для проверки уставок, которые относятся к агрегату.
    Вы как обрабатываете аналоги и проверяете уставки?

    В плане троичной логики, разве что слышать про Сетунь. Есть какие-то более свежие варианты её применения?

  5. #5

    По умолчанию

    такое ощущение, что вы углубились в программирование ради программирования, забыв для чего всё это делается
    которые относятся к агрегату
    агрегат может находиться в "рабочем" состоянии или "нерабочем" состоянии
    уставки для состояний соответсвенно - либо разные, либо вообще должны исключаться из обработки
    соответсвенно применение одного шаблона внутри агрегатного ФБ, даже не касаясь его кода по смыслу, глядя на этот аналог, не совсем корректно
    это я к тому, что вы увлеклись игрой в матрёшку и некоторые моменты имхо надо вынести в отдельный "класс", а не засовывать в внутрь очёредной матрёшки. не привязывать всё к объекту

  6. #6
    Пользователь
    Регистрация
    27.11.2011
    Адрес
    Краснодар
    Сообщений
    12,982

    По умолчанию

    с чего вдруг разные типы с одинаковыми именами будут работать? нужно приводить к требуемым типам в языках общего назначения.

  7. #7

    По умолчанию

    С того, что как минимум можно применить интерфейс (ITF), который указывается в качестве типа того, что мы передаём, вместо требуемого типа. Другое дело, что для этого нужен ФБ, реализующий интерфейс, а в интерфейсе должны быть объявлены properties со стандартными типами, и тем не менее.

    Другой момент, почему это могло бы работать, создав ПУСТОЙ метод в самом базовом ФБ и вызвав его в самом дочернем, по THIS мы имеем ссылку на экземпляр, т.е. на конкретный объект в памяти, дочернего ФБ.
    Т.е. в runtime у нас есть доступ без каких-либо дополнительных объявлений.
    method.png

    По ссылке есть пример техники из Java под названием generic method https://docs.oracle.com/javase/tutor...s/methods.html.

    Мой вопрос в том, как добиться чего-то подобного в CODESYS?

  8. #8

    По умолчанию

    Ещё раз, у меня для каждого агрегата: первая дренажка, вторая, и.т.д. - собственный ФБ. В runtime он превращается в один объект, один экземпляр конкретного агрегатного ФБ. Т.е. там задаётся персональный код агрегата.
    Но, ничего не мешает ввести поле вкл/выкл агрегата и написать нужную логику, причём именно в абстрактном агрегатном ФБ, который наследуют все конкретные агрегатные ФБ.
    Сделать разные уставки для разный состояний моя структура позволяет, как ни странно, потому что хранение уставок в статической памяти и рабочий набор в оперативной - разные экземпляры структур данных.
    Можно этих наборов добавить ещё сколько хотите хоть в оперативку, хоть в статику.
    Вот тут подходим к самому интересному, почему вы решили, что я программирую ради программирования???

    Подход рождался по мере выполнения поставленной задачи в результате применения того, что я читал и пытался осмыслить на протяжении последних нескольких лет.
    Я не говорю, что получилось супер, но я вижу преимущества и удобство подхода - всё-таки люди вроде Боба Мартина дураками не были и не просто так всё это мутили.
    Другое дело, что в ПЛК, очевидно, своя специфика, которую надо учесть.

    Последний раз непонимание того, зачём всё это так "ветвить" или "матрёшить" я слышал на собеседовании от людей, который не знают, что такое многострочное редактирование в программе Sublime Text, как и что есть такая программа.
    На моё решение тестового задания, мне было предложено, вместо "матрёшек", свалить всё в кучу в одном ФБ, нарушив таким образом SRP - принцип единственной ответственности, на который можно молиться, настолько хорошо он помогает структурировать код. У меня вот ощущение, что и вы туда же

    Вы предлагает создать отдельный класс для проверки всех аналоговых уставок для всех агрегатов?
    Хорошо, это называется антипаттерн "God object".
    Может быть в АСУТП принято его использовать? Вполне возможно, но тогда вопрос, почему?

    Или отдельный класс для проверки уставок данного агрегата?
    Это можно, но его надо где-то объявить и вызывать, а где?
    Наверное, в каком-то "контейнере" куда мы объединим всё, связанное с данным агрегатом, логично?
    Т.е. надо выделить ФБ под данный агрегат, ч.т.д.

  9. #9
    Пользователь
    Регистрация
    27.11.2011
    Адрес
    Краснодар
    Сообщений
    12,982

    По умолчанию

    А, ну если в пример приводят не типизированный яваскрипт, тоды ладно.
    Попробуйте найти подобный же пример для типизированного трутип, и посмотреть там как оно?
    Ну или других типизированных языков.
    Есть подозрение, что обломается всё.

    Где-то видел пример написания кода для хлебопечек, не помню, возможно на C#, но думаю, что можно применить и к ST. Надо порыться на компе, вроде ссылку где-то сохранял.
    Там как раз было как применять наследников и интерфейсы вроде.
    Последний раз редактировалось melky; 23.10.2025 в 06:58.

Похожие темы

  1. Ответов: 5
    Последнее сообщение: 19.10.2025, 16:29
  2. Ответов: 4
    Последнее сообщение: 19.06.2024, 13:33
  3. Перегрузка методов в CODESYS 3.5
    от aposternak35 в разделе ПЛК2хх
    Ответов: 1
    Последнее сообщение: 21.12.2023, 16:59
  4. адаптация проекта с Codesys V3 SP5 Patch 5 на Codesys V3.5 SP17 Patch3
    от VladimirZHTEC в разделе СПК210, СПК1xx [М01]
    Ответов: 1
    Последнее сообщение: 22.02.2023, 11:50
  5. Ответов: 1
    Последнее сообщение: 29.06.2017, 11:21

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •