PDA

Просмотр полной версии : Модели поведения в CODESYS 2.3



Осинский Алексей
08.07.2018, 14:14
Как Вы, возможно, знаете PLCOpen разработали документ Creating PLCopen Compliant Libraries (http://www.plcopen.org/pages/pc2_training/fb_libraries.htm), в котором описаны стандартные интерфейсы и поведения функциональных блоков разработанных на языках стандарта МЭК-61131 (всем нам знакомые FBD, CFC, ST и т.д.).
Не так давно этот документ был переведен на русский язык (доступен по ссылке (https://ftp.owen.ru/CoDeSys3/98_Books/plcopen_compliant_libraries_v10_ru.pdf)).

В стандарте приведены примеры для всех "моделей поведения", но их использование в проекте затруднено т.к. все примеры приведены в виде картинок и текст придется перенабирать вручную.
В этой теме я буду постепенно выкладывать исходный код ФБ (на языке ST) тех моделей поведения, которые использовал в работе.
Так Вы сможете просто

скопировать перечисление, содержащее состояния ФБ в Ваш проект;
скопировать "скелет" ФБ;
доработать "скелет" ФБ в соответствии с Вашей задачей (места, которые необходимо доработать под задачу выделил комментариями (* TODO: *)).

без необходимости перевводить код ФБ вручную.

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

Итак:

Префикс BM - сокращение от Behavior model (модель поведения)


(*
Перечисление, содержащее все состояния ФБ
*)
TYPE BM_STATES :
(
BM_DORMANT := 0, (* Ожидание запуска *)
BM_EXECUTING := 1, (* Выполнение операции *)
BM_DONE := 2, (* Завершение операции *)
BM_ERROR := 3, (* Ошибка *)
BM_RESETTING := 4, (* Переинициализация *)
BM_ABORTING := 5, (* Прерывание работы ФБ *)
BM_ABORTED := 6 (* Работа ФБ прервана *)
);
END_TYPE






ETrig





Старт по переднему фронту;
Нет возможности прерывания до окончания работы блока;
Нет ограничения по времени цикла;
Нет ограничения по времени выполнения.




Интерфейс ФБ


FUNCTION_BLOCK ETrig
VAR_INPUT
xExecute : BOOL;
END_VAR
VAR_OUTPUT
xBusy : BOOL;
xDone : BOOL;
xError : BOOL;
END_VAR
VAR
m_eState : BM_STATES := BM_DORMANT;
m_xNeedToChangeState : BOOL;
m_xResetRequest : BOOL;
m_xIsExecutionDone : BOOL;
m_xIsExecutionFail : BOOL;
END_VAR

Тело ФБ


m_xNeedToChangeState := TRUE;
WHILE m_xNeedToChangeState DO
m_xNeedToChangeState := FALSE;
CASE m_eState OF
BM_DORMANT:
IF xExecute THEN
xBusy := TRUE;
m_eState := BM_EXECUTING;
m_xNeedToChangeState := TRUE;
(* TODO: запомнить состояние остальных входов *)
END_IF
BM_EXECUTING:
(* TODO: m_xIsExecutionDone должно стать TRUE, когда выполнение операции завершено успешно *)
m_xIsExecutionDone := TRUE;
(* TODO: m_xIsExecutionFail должно стать TRUE, когда возникла ошибка выполнения операции *)
m_xIsExecutionFail := FALSE;
IF m_xIsExecutionFail THEN
m_eState := BM_ERROR;
m_xNeedToChangeState := TRUE;
ELSIF m_xIsExecutionDone THEN
m_eState := BM_DONE;
m_xNeedToChangeState := TRUE;
END_IF
BM_DONE:
IF xDone AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
m_xNeedToChangeState:= TRUE;
ELSE
xBusy := FALSE;
xDone := TRUE;
m_xResetRequest := NOT xExecute;
m_xNeedToChangeState:= FALSE;
END_IF
BM_ERROR:
IF xError AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
m_xNeedToChangeState:= TRUE;
ELSE
xBusy := FALSE;
xError := TRUE;
m_xResetRequest := NOT xExecute;
m_xNeedToChangeState:= FALSE;
END_IF
BM_RESETTING:
(* TODO: освобождение ресурсов здесь *)
xBusy := FALSE;
xDone := FALSE;
xError := FALSE;
m_eState := BM_DORMANT;
m_xNeedToChangeState := m_xResetRequest;
m_xResetRequest := FALSE;
m_xIsExecutionDone := FALSE;
m_xIsExecutionFail := FALSE;
END_CASE
END_WHILE









ETrigA





Старт по переднему фронту;
Есть возможность прерывания до окончания работы блока;
Нет ограничения по времени цикла;
Нет ограничения по времени выполнения.





Интерфейс ФБ


FUNCTION_BLOCK ETrigA
VAR_INPUT
xExecute : BOOL;
xAbort : BOOL;
END_VAR
VAR_OUTPUT
xBusy : BOOL;
xDone : BOOL;
xError : BOOL;
xAborted : BOOL;
END_VAR
VAR
m_eState : BM_STATES := BM_DORMANT;
m_xNeedToChangeState : BOOL;
m_xResetRequest : BOOL;
m_xIsExecutionDone : BOOL;
m_xIsExecutionFail : BOOL;
m_xIsAbortingDone : BOOL;
m_xIsAbortingFail : BOOL;
END_VAR


Тело ФБ


m_xNeedToChangeState := TRUE;
WHILE m_xNeedToChangeState DO
m_xNeedToChangeState := FALSE;
CASE m_eState OF
BM_DORMANT:
IF xExecute THEN
xBusy := TRUE;
m_eState := BM_EXECUTING;
m_xNeedToChangeState := TRUE;
(* TODO: запомнить состояние остальных входов *)
END_IF
BM_EXECUTING:
(* TODO: m_xIsExecutionDone должно стать TRUE, когда выполнение операции завершено успешно *)
m_xIsExecutionDone := TRUE;
(* TODO: m_xIsExecutionFail должно стать TRUE, когда возникла ошибка выполнения операции *)
m_xIsExecutionFail := FALSE;
IF m_xIsExecutionFail THEN
m_eState := BM_ERROR;
m_xNeedToChangeState := TRUE;
ELSIF m_xIsExecutionDone THEN
m_eState := BM_DONE;
m_xNeedToChangeState := TRUE;
END_IF
IF xAbort THEN
m_xNeedToChangeState := TRUE;
m_eState := BM_ABORTING;
END_IF
BM_DONE:
IF xDone AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
m_xNeedToChangeState:= TRUE;
ELSE
xBusy := FALSE;
xDone := TRUE;
m_xResetRequest := NOT xExecute;
m_xNeedToChangeState:= FALSE;
END_IF
BM_ERROR:
IF xError AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
m_xNeedToChangeState:= TRUE;
ELSE
xBusy := FALSE;
xError := TRUE;
m_xResetRequest := NOT xExecute;
m_xNeedToChangeState:= FALSE;
END_IF
BM_RESETTING:
(* TODO: освобождение ресурсов здесь *)
xBusy := FALSE;
xDone := FALSE;
xError := FALSE;
m_eState := BM_DORMANT;
m_xNeedToChangeState := m_xResetRequest;
m_xResetRequest := FALSE;
m_xIsExecutionDone := FALSE;
m_xIsExecutionFail := FALSE;
m_xIsAbortingDone := FALSE;
m_xIsAbortingFail := FALSE;
BM_ABORTING:
(* TODO: m_xIsAbortingDone должно стать TRUE, когда прерывание операции выполнено *)
m_xIsAbortingDone := TRUE;
(* TODO: m_xIsAbortingFail должно стать TRUE, когда возникла ошибка прерывания операции *)
m_xIsAbortingFail := FALSE;
IF m_xIsAbortingFail THEN
m_eState := BM_ERROR;
m_xNeedToChangeState := TRUE;
ELSIF m_xIsAbortingDone THEN
m_eState := BM_ABORTED;
m_xNeedToChangeState:= TRUE;
END_IF
BM_ABORTED:
IF xAborted AND (m_xResetRequest OR (NOT xExecute)) THEN
m_eState := BM_RESETTING;
m_xNeedToChangeState:= TRUE;
ELSE
xBusy := FALSE;
xAborted := TRUE;
m_xResetRequest := NOT xExecute;
m_xNeedToChangeState:= FALSE;
END_IF
END_CASE
END_WHILE






P.S. Не смотря на то, что в теме указано "Модели поведения в CODESYS 2.3". Этот же исходный код будет работать и в CODESYS 3.5.





Название модели поведения




Краткое описание блока



Интерфейс ФБ



Тело ФБ

capzap
08.07.2018, 14:26
не опасно ли всё через while делать и отдавать на использование неподготовленному пользователю

Осинский Алексей
08.07.2018, 15:00
не опасно ли всё через while делать и отдавать на использование неподготовленному пользователю
Так реализовано в стандарте.
Можно реализовать без WHILE, но, в таком случае, выполнение работы ФБ займет как минимум 3 цикла ПЛК (DORMANT -> EXECUTING -> DONE).
Реализация внутри WHILE позволяет (если задача выполнена уже сейчас) сразу по переднему фронту xExecute перейти на DONE.
Я выделил места, в которые нужно внести изменения.
Если неподготовленный пользователь решит менять условия перехода на следующий шаг, а точнее, значение переменной m_xNeedToChangeState, то да, это может привести к "зависанию".
Но здесь даны ссылки на стандарты не зря. Пользователь, изучивший стандарты, уже не будет неподготовленным. Ведь так?

capzap
08.07.2018, 18:06
я имел ввиду что неподготовленный пользователь считает документацию для дураков, т.ч. ни кто не будет читать стандарты а захочет сразу применять выложенный код. Я кстати пока не читал документацию, но хотел бы предложить переделать всё это в функцию, а все локальные переменные оформить в UDT структуру и подавать на вход функции через указатель, как мне кажется функция охладит пыл создавать достаточно сложный код

ЗЫ прочитал, в доках используется REPEAT почему используете WHILE

Осинский Алексей
08.07.2018, 18:46
я имел ввиду что неподготовленный пользователь считает документацию для дураков, т.ч. ни кто не будет читать стандарты а захочет сразу применять выложенный код. Я кстати пока не читал документацию, но хотел бы предложить переделать всё это в функцию, а все локальные переменные оформить в UDT структуру и подавать на вход функции через указатель, как мне кажется функция охладит пыл создавать достаточно сложный код

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

В "модели поведения" основная идея в том, что по интерфейсу ФБ можно быстро понять как этот ФБ работает:

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

А решаемую при помощи этого ФБ задачу программист реализует сам, накладывая решение на "скелет".

Обратите внимание на п.3 списка выше.
Есть модель поведения, которая позволяет ограничить время, которое ФБ потратит в 1 цикле ПЛК, после чего отложит выполнение на следующий цикл ПЛК (это модели поведения с буквами TL в названии (например, ETrigTl)).
Т.е. если есть предпосылки для того, что работа ФБ будет сильно увеличивать время цикла ПЛК, нужно будет просто использовать соответствующую модель поведения, и сама модель позаботиться о том, чтобы отложить выполнение при достижении ограничения.
Пользователю лишь останется написать код так, чтобы при следующем вызове выполнение продолжилось с того же места, на котором было прервано в предыдущем цикле.

Я постараюсь выделить время и подготовить пару примеров "из жизни" для того, чтобы ближе познакомить новичков с моделями поведения и их использованием в проектах.


все локальные переменные оформить в UDT структуру
В данном случае не вижу преимуществ в передаче структуры в функцию и ФБ.
Заменив переменные ФБ на структуру мы обяжем пользователя работать со структурой в его программе (мониторить xDone, xBusy, изменять xExecute), что на CFC совсем не очевидно.
По итогу результат будет тем же, но

подавать на вход функции через указатель
новичков это отпугнет.

Осинский Алексей
08.07.2018, 18:50
ЗЫ прочитал, в доках используется REPEAT почему используете WHILE
Мы не используем цикл REPEAT т.к. его использование ухудшает читабельность программы:
чтобы понять до каких пор будет выполняться цикл нужно пролистать до конца цикла и только потом станет понятно условие выхода.

Цикл WHILE лишен этого недостатка: условие выхода становится понятным уже на 1й строке.

capzap
08.07.2018, 19:05
SysLibFile, SysLibMem, SysLibSocket мы разве не используем ADR(что_то_там) и ни кого не пугает, я бы и эти ФБ закрыл в запароленную библиотеку, а условия переходов между кейсами вывел во входные аргументы. Использование структуры дисциплинирует, быстрее относительно других способов можно увидеть чего и сколько используется

capzap
08.07.2018, 19:19
Мы не используем цикл REPEAT т.к. его использование ухудшает читабельность программы:
чтобы понять до каких пор будет выполняться цикл нужно пролистать до конца цикла и только потом станет понятно условие выхода.

Цикл WHILE лишен этого недостатка: условие выхода становится понятным уже на 1й строке.

не совсем так, переменную мы найдем в while в начале блока, а в repeat в конце блока, а условие выхода нужно искать в теле обоих циклов и чтоб быть уверенным что оно несколько раз не изменяется, в каждом случае надо пролистывать от начала и до конца блока, между ними лишь одна разница repeat один раз но выполниться

Осинский Алексей
08.07.2018, 19:31
SysLibFile, SysLibMem, SysLibSocket мы разве не используем ADR(что_то_там) и ни кого не пугает, я бы и эти ФБ закрыл в запароленную библиотеку, а условия переходов между кейсами вывел во входные аргументы.

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


Использование структуры дисциплинирует, быстрее относительно других способов можно увидеть чего и сколько используется
Хотя мне и ставили в укор то, что я "фанат функций", объективных преимуществ использования набора "структура + функция" против функционального блока в данной ситуации я не вижу.
Я использую

набор "cтруктура + функция" в тех случаях, когда результат работы 100% будет получен сразу после вызова функции;
В данном случае структура используется для того, чтобы сократить количество передаваемых функции параметров.
ФБ в тех случаях, когда выполнение задачи предположительно может потребовать более 1 цикла ПЛК и необходимо хранить промежуточные значения.

Осинский Алексей
08.07.2018, 19:36
не совсем так, переменную мы найдем в while в начале блока, а в repeat в конце блока, а условие выхода нужно искать в теле обоих циклов и чтоб быть уверенным что оно несколько раз не изменяется, в каждом случае надо пролистывать от начала и до конца блока, между ними лишь одна разница repeat один раз но выполниться

Мы не используем (за исключением редких случаев) прерывание цикла при помощи CONTINUE и EXIT т.к. это ухудшает читабельность кода (п. 6.5.2 PLCopen Coding Guidelines (https://oscat-ru.weebly.com/)):
пробежавшись глазами по телу цикла легко пропустить такой оператор, и сидеть, недоумевая, над программой, в попытках понять, почему этот конкретный участок кода не выполняется.
А спустя какое-то время увидеть, что перед ним стоял оператор EXIT.

В большинстве случаев, тело цикла можно переписать так, чтобы обойтись без этих операторов.

UPD: Из тех же соображений избегаем использования RETURN.

capzap
08.07.2018, 20:04
на счет структур конечно не настаиваю, мне удобно за счет того что в тиа портале это тоже что и блок данных


легко пропустить такой оператортак это равнозначно относиться как к while так и к repeat, только из-за того чтоб кто то не на косячил с условиями перед while Вы вынуждены присваивать переменной TRUE

Осинский Алексей
08.07.2018, 21:45
так это равнозначно относиться как к while так и к repeat, только из-за того чтоб кто то не на косячил с условиями перед while Вы вынуждены присваивать переменной TRUE
Если накосячить с условиями - то WHILE совсем не выполниться (и понятно, где искать проблему: в условии входа в цикл),
если же в теле цикла есть оператор, прерывающий выполнение, то программа войдет в цикл, но до места, которое Вы хотите отладить не дойдет (и, если внутри цикла много кода, будет сложно понять, куда смотреть).
Тут Вы можете сказать, что

функции должны быть макисмально аскетичны,
не должны содержать больше 2х уровней вложенности
и т.д.

тогда их будет легко отладить.
Со всем вышеперечисленным я соглашусь.

Но бывают ситуации, когда этим приходиться пренебрегать в связи с ограничениями.
Например В ПЛК63\ПЛК73 есть ограничение на 256 POU и в больших проектах в это ограничение легко упереться.
Приходиться функции делать больше.

К примеру, я переписал цикл из тела ETrig, ориентировав его на использование оператора оператора EXIT:


WHILE TRUE DO
CASE m_eState OF
BM_DORMANT:
IF xExecute THEN
xBusy := TRUE;
m_eState := BM_EXECUTING;
(* TODO: запомнить состояние остальных входов *)
ELSE
EXIT;
END_IF
BM_EXECUTING:
(* TODO: m_xIsExecutionDone должно стать TRUE, когда выполнение операции завершено успешно *)
m_xIsExecutionDone := TRUE;
(* TODO: m_xIsExecutionFail должно стать TRUE, когда возникла ошибка выполнения операции *)
m_xIsExecutionFail := FALSE;
IF m_xIsExecutionFail THEN
m_eState := BM_ERROR;
ELSIF m_xIsExecutionDone THEN
m_eState := BM_DONE;
; (*ЗДЕСЬ ДОЛЖНО БЫТЬ ELSE EXIT *)
END_IF
BM_DONE:
IF xDone AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
ELSE
xBusy := FALSE;
xDone := TRUE;
m_xResetRequest := NOT xExecute;
EXIT;
END_IF
BM_ERROR:
IF xError AND (m_xResetRequest OR NOT xExecute) THEN
m_eState := BM_RESETTING;
ELSE
xBusy := FALSE;
xError := TRUE;
m_xResetRequest := NOT xExecute;
EXIT;
END_IF
BM_RESETTING:
(* TODO: освобождение ресурсов здесь *)
xBusy := FALSE;
xDone := FALSE;
xError := FALSE;
m_eState := BM_DORMANT;
m_xResetRequest := FALSE;
m_xIsExecutionDone := FALSE;
m_xIsExecutionFail := FALSE;
EXIT;
END_CASE
END_WHILE


Работать он должен точно так же, как в 1м посте темы (но я не проверял т.к. думаю, что для понимания идеи его работоспособность не важна).
Допустим, есть проект. ПЛК перезагружается на вызове этого ФБ.
Я открываю код, и вижу:

WHILE TRUE DO
бесконечный цикл внутри ФБ для ПЛК.

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

ага, с этого шага выход через EXIT, с этого есть, а вот с этого шага как должно выходить?
А, EXIT потеряли.
или, для разнообразия, проблема вообще не в самом цикле, а на шаге RESETTING есть запись по некорректному указателю.



Я, конечно, утрирую. Но такая ситуация вполне могла возникнуть.

С другой стороны:
Получаю программу, ПЛК все так же перезагружается на вызове ФБ, но реализован так, как в 1м посте


m_xNeedToChangeState := TRUE;
WHILE m_xNeedToChangeState DO

Первая мысль: где-то внутри цикла переменная m_xNeedToChangeState не изменяется на FALSE.
Пробегаю, смотрю, нет, все хорошо, меняется на FALSE везде, где должна.
Начинаю искать проблему в других местах.

capzap
08.07.2018, 22:57
http://www.owen.ru/forum/showthread.php?t=29087&p=282567&viewfull=1#post282567

ни чего не понял, в обоих случаях исследуете код тела цикла, только в первом варианте красочно расписываете как всё плохо, а во втором зачем то сделали свой REPEAT, хотя если в теле такие же проблемы как и в первом варианте то разницы между обеими вариантами нет
Теперь если использовать REPEAT и перед ним принудительно переменной присваивать FALSE то код выполниться один раз, если не активировать условие цикличности внутри тела, а если это будет функция, то и фальш присваивать не обязательно.
И к тому же такие способы диагностики некорректного кода больше подходят к чужому коду а не собственному, вот пользуясь случаем хочу спросить про Вашу подпись, какими тестами пользуетесь в КДС, мне например не встречались, сам я только записывал нужные значения в фифо

capzap
08.07.2018, 23:14
Если я правильно понял, то на графических языках "функция + структура" отображается как ФБ?
Я слышал о том, что при программировании этих контроллеров используется подход "функция + структура", но этого не знал.

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

я не использую графические языки ни там ни там уже достаточно давно. Для меня проще в плане того, что если переносить код из_в я просто обычной заменой меняю обозначение указателя в КДС на решетку в TIA, ну конечно подправив еще несколько вещей, это касается функций заменяющих ФБ, зато экземпляры одинаково смотрятся в обоих средах

Осинский Алексей
09.07.2018, 00:06
И к тому же такие способы диагностики некорректного кода больше подходят к чужому коду а не собственному
В первую очередь - да. По работе, иногда, приходится читать чужой код.
Но и свой, написанный давно, код можно сходу не разобрать. Не знаю, как у Вас, но у меня с течением времени подход к проектированию меняется. Иногда смотрю на свой код 3-6 летней давности, и думаю, "зачем так написал? можно же было вот-так... и было бы лучше, быстрее, понятнее (ненужное зачеркнуть)".


Вот пользуясь случаем хочу спросить про Вашу подпись, какими тестами пользуетесь в КДС, мне например не встречались, сам я только записывал нужные значения в фифо
Для тестирования модулей (ФБ, функций) на текущий момент использую самописные тесты.
и для CDS2 и для CDS3 подход схожий (ниже тестируется некий ФБ для управления прожектором):
1) Объявляю структуру, содержащую желаемые значения входов и выходов тестируемого ФБ



TYPE SpotlightTest :
STRUCT
eDirection : SPOTLIGHT_DIRECTION;
eSensorsState : END_SENSOR;
xNeedToTurnOn : BOOL;
eCurrentDirection : SPOTLIGHT_DIRECTION;
xIsTurnedOn : BOOL;
xIsMoving : BOOL;
xDirectionChanged : BOOL;
END_STRUCT
END_TYPE


2) Реализую функцию, назначение которой:
2.1) Установить значения входов;
2.2) Вызвать тестируемый ФБ;
2.3) Сравнить значения выходов с ожидаемыми;
2.4) Вернуть TRUE, если состояние выходов ФБ равно ожидаемому, иначе вернуть FALSE и записать сообщение в лог с сообщением о том почему тест провален

Интерфейс


(*
Задает состояние входов ФБ типа prv_FastOutPulseGenerator по адресу pfbFopgToTest.
Вызывает его и проверяет состояние выходов.
Если состояние выходов не соответствует ожиданию - добавляет запись в лог.
Возвращает TRUE, если состояние выходов соответствовало ожиданию.
FALSE во всех остальных случаях
*)
FUNCTION AssertSpotlightOutputs : BOOL
VAR_INPUT
sModuleName : TestMessage; (* Название теста *)
pfbSpotlightToTest : POINTER TO Spotlight;
stTestToExecute : SpotlightTest;
END_VAR

Реализация:


AssertSpotlightOutputs := FALSE;
IF pfbSpotlightToTest = 0 THEN
AddMessageToTestLog ('AssertSpotlightOutputs', 'Некорректный указатель на ФБ');
ELSE

pfbSpotlightToTest^.eSensorsState := stTestToExecute.eSensorsState;
MoveSpotlightToDirection (pfbSpotlightToTest, stTestToExecute.eDirection);
pfbSpotlightToTest^.xNeedToTurnOn := stTestToExecute.xNeedToTurnOn;

pfbSpotlightToTest^();

IF pfbSpotlightToTest^.xIsTurnedOn <> stTestToExecute.xIsTurnedOn THEN
AddMessageToTestLog (sModuleName, 'Некорректное состояние xIsTurnedOn');
ELSIF pfbSpotlightToTest^.eMoveDirection <> stTestToExecute.eCurrentDirection THEN
AddMessageToTestLog (sModuleName, 'Некорректное состояние eMoveDirection');
ELSIF pfbSpotlightToTest^.xIsTurnedOn <> stTestToExecute.xIsTurnedOn THEN
AddMessageToTestLog (sModuleName, 'Некорректное состояние xIsTurnedOn');
ELSIF pfbSpotlightToTest^.xIsMoving <> stTestToExecute.xIsMoving THEN
AddMessageToTestLog (sModuleName, 'Некорректное состояние xIsMoving');
ELSIF pfbSpotlightToTest^.xDirectionChanged <> stTestToExecute.xDirectionChanged THEN
AddMessageToTestLog (sModuleName, 'Некорректное состояние xDirectionChanged');
ELSE
AssertSpotlightOutputs := TRUE;
END_IF
END_IF


3) Пишу ФБ-тесты (1 ФБ - 1 тестируемая концепция), в которых
3.1) массив структур SpotlightTest заполнен в соответствии с тем, что хочу протестировать
3.2) Происходит последовательный вызов функции из п.2 и передача ей необходимого теста и ФБ



VAR_OUTPUT
xDone : BOOL;
xBusy : BOOL;
xPassed : BOOL;
xFailed : BOOL;
END_VAR
VAR CONSTANT
m_c_sModuleName : TestMessage := 'Spotlight.EndPositionTest';
m_c_usiTestStepsCount : USINT := 9;
m_c_astSteps : ARRAY [1..m_c_usiTestStepsCount] OF SpotlightTest :=
(eDirection := DIRECTION_TOPLEFT, xIsMoving := TRUE, xDirectionChanged := TRUE, eCurrentDirection := DIRECTION_TOPLEFT),
....


Ну а наполнение, думаю, понятно.

4) Дело за малым - вызывать тесты по очереди.

Для CODESYS 3.5 разрабатываю библиотеку, чтобы уменьшить количество написанной вручную обвязки.
Но, пока что, она не на той стадии готовности, когда ее можно показывать людям.

А для тестирования проекта в целом использую Process Simulator (http://automation.ucoz.com/) (ПО бесплатно, с открытым исходным кодом).
Такая мини-скада, основная задача которой эмулировать поведение исполнительных механизмов.
Есть возможность писать свои скрипты на C#.
Все никак руки не дойдут до написания статьи на эту тему.

Осинский Алексей
09.07.2018, 00:11
я не использую графические языки ни там ни там уже достаточно давно. Для меня проще в плане того, что если переносить код из_в я просто обычной заменой меняю обозначение указателя в КДС на решетку в TIA, ну конечно подправив еще несколько вещей, это касается функций заменяющих ФБ, зато экземпляры одинаково смотрятся в обоих средах

У меня статистика, примерно, такая:
1) В CDS 2.3 PLC_PRG на CFC т.к. хорошо видно все ли входы-выходы привязаны (в 3.5 на ST т.к. в основном использую методы/property для передачи параметров, а на CFC это болезненный процесс);
2) 10% SFC (там, где четко прослеживаются отдельные состояния и понятны все переходы между состояниями. Модели поведения тоже на SFC раньше "писал", сейчас модели поведения реализую на ST);
3) 90% ST.

Осинский Алексей
09.07.2018, 00:20
ни чего не понял, в обоих случаях исследуете код тела цикла, только в первом варианте красочно расписываете как всё плохо, а во втором зачем то сделали свой REPEAT, хотя если в теле такие же проблемы как и в первом варианте то разницы между обеими вариантами нет
В варианте с переменной в условии четко видно, на что обращать внимание: где эта переменная меняется.
В варианте только с EXIT'ами может быть не очевидно, что из этого цикла вообще есть выход.
Если бы я реализовал вариант и с переменной и с EXIT'ами стало бы совсем не очевидно: в условии видно, что выход произойдет по значению переменной, но при детальном разборе оказывается, что есть еще некие условия выхода.

Но, как мы и проговорили выше, все это, в основном, актуально для "чужого" кода. Если код читает только автор - то тут вопросов нет.

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

Салихов Ильдар
09.07.2018, 08:56
В "модели поведения" основная идея в том, что по интерфейсу ФБ можно быстро понять как этот ФБ работает:

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



А можно небольшое объяснение?
По пунктам 2,3,4 какое то понимание есть, а вот по 1 пункту... Понятно как это работает, непонятно как применить. В каких случаях следует использовать логику по уровню, а в каких по фронту?

Спасибо.

Осинский Алексей
09.07.2018, 09:14
А можно небольшое объяснение?
По пунктам 2,3,4 какое то понимание есть, а вот по 1 пункту... Понятно как это работает, непонятно как применить. В каких случаях следует использовать логику по уровню, а в каких по фронту?

Спасибо.


Не так давно этот документ был переведен на русский язык (доступен по ссылке (https://oscat-ru.weebly.com/)).
Не смотрели документ по ссылке выше?

Если вкратце, то

ФБ с работой "по уровню" выполняет некую задачу до тех пор, пока на входе xEnable присутствует высокий уровень сигнала (например, утюг, поддерживает температуру до тех пор, пока включен в розетку);
ФБ с работой "по фронту" начинает выполнение некой задачи по переднему фронту сигнала xExecute и до тех пор, пока задача не будет выполнена (например, стиральная машина: программу выбрали, кнопку нажали, закончит стирку - пропищит).

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

Осинский Алексей
09.07.2018, 09:45
Ильдар, пока писал о нагреватель, работающий по фронту, сообразил что мы с Вами уже общались соседней теме (http://www.owen.ru/forum/showthread.php?t=29066).
Вам не подошло решение, которое я там предложил?
Или подошло и Вы "для себя" разбираетесь?

Салихов Ильдар
10.07.2018, 08:08
Решение подошло. Спасибо.
И да, есть нюансы, не всегда очевидные для меня. Пытаюсь с этим разобраться.

Салихов Ильдар
12.07.2018, 13:54
А вот библиотеки CAA (CoDeSys Automation Alliance) согласно какому документу разработаны? И как это стыкуется с PLCopen?

Осинский Алексей
12.07.2018, 14:50
А вот библиотеки CAA (CoDeSys Automation Alliance) согласно какому документу разработаны? И как это стыкуется с PLCopen?

В CODESYS 3.5 есть библиотека CAA Behavior Model, которая разработана в соответствии стандарту, который мы здесь обсуждаем (или стандарт на ее основе разработан, тут уж история умалчивает).
UPD: меня тут поправляют, что история, таки, не умалчивает, и в основу стандарта PLCopen Compliant Libraries легла библиотека CAA Behavior Model, разработка которой началась еще в 2004 году.

Остальные ФБ группы CAA разработаны с учетом правил, описанных в стандарте (пока что бросается в глаза единственное различие: в стандарте модель называется LCont, а в CBM - LTrig. Но это мелочи).

VSU
12.09.2018, 14:10
А нафига вообще, в автоматизации ООП? Для того, чтобы выпендриться? Наибольшая часть задач решается процедурным программированием.
Нафига код усложнять?

Осинский Алексей
13.09.2018, 10:33
А нафига вообще, в автоматизации ООП? Для того, чтобы выпендриться? Наибольшая часть задач решается процедурным программированием.
Нафига код усложнять?

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

capzap
13.09.2018, 10:47
я бы еще добавил, а кто навязывает использовать ООП?

Осинский Алексей
29.10.2018, 17:12
Tolya25, выделил Ваше сообщение в отдельную тему http://www.owen.ru/forum/showthread.php?t=29688

Салихов Ильдар
13.03.2019, 13:33
Подскажите, пожалуйста. Вопрос по применению "скелета", например ETrig.
Как мне свою логику на CASE "внедрить" в этот скелет?
Просто вставляю обработку своего CASE в CASE BM_EXECUTING ?

Спасибо.

Салихов Ильдар
13.03.2019, 19:38
Прочитал описание стандарта.

Существует два варианта активации ФБ: 1. По фронту (через вход Execute). В этом случае происходит однократное выполнение ФБ, которое в случае отсутствия ошибок завершается сигналом на выходе Done.
Однократное - это как? ФБ должен отработать за один цикл? Т.е. свой CASE использовать не получится?

Евгений Кислов
13.03.2019, 20:05
Просто вставляю обработку своего CASE в CASE BM_EXECUTING?

Да.


Однократное - это как? ФБ должен отработать за один цикл?

Однократное выполнение следует понимать как "ФБ выполнит свою операцию один раз". Т.е. ФБ отправки запроса по протоколу Modbus по переднему фронту на входе xExecute отправит один запрос - а не два, три или бесконечно много.
(если только внутренней логикой блока не предусмотрено некое число повторных запросов при отсутствии ответа)
При этом сама операция, которая включает в себя формирование запроса, отправку данных в порт, ожидание ответа, проверку корректности ответа, выделения данных (и возможно еще что-то) - займет несколько циклов ПЛК.