PDA

Просмотр полной версии : Динамический вызов функций либо FB, и все-таки...



dorofeevms
11.03.2019, 08:59
Добрый день всем!
Нравятся мне не просто рабочие программы и алгоритмы, но еще и Красивые.. И задумался я над реализацией красивой и лаконичной FSM (final state machine) для ПЛК, и, в частности, plc63.
Уж больно красиво выглядит решение на C на любые контроллеры от arduino до siemens.
А case и IF многочисленные - "не вштыривают".

И вот к чему пришел (синтаксис объявление и деклараций не полностью приведен - скопировал только суть, размер массивов значение не имеет - это демонстрационный пример , все "с запасом"
пример реального использования динамического вызова не привожу - чтобы не усложнять вопрос - там все тривиально - в зависимости от события(=индекс_массива) выбирается следующее состояние FSM):
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
объявим на глобальном уровне тип struct:
type................
struct stSTATE:
iActionHandler: int;
iActionHandlerPTR: dword;
arTransitionTable: array[1..10] of int;
end_struct;
end_type;.......................

объявлены FB fbNORMAL и fbSTBY с одинаковыми ДЕЙСТВИЯМИ "run" (в них для целей отладки по одному оператору типа fVar1test := true; fVar2test := true;

глобальные переменные:
arStates: array [1..10] of stSTATE;
fInit: bool;

Ну и PLC_PRG:

PROGRAM PLC_PRG
VAR
iThisStateHandler : INT;
fbSNORMAL: fbNORMAL;
fbSSTBY: fbSTBY;
fbInst : POINTER TO fbSTBY ;
END_VAR

IF NOT fInit
THEN fInit := init(); (*проверка на холодный старт, если ДА - инициализировать значения. В init код такой: arStates[1].iActionHandler := INDEXOF(fbSTBY); arStates[2].iActionHandler := INDEXOF(fbNORMAL); но далее не используется.*)
arStates[1].iActionHandlerPTR := ADR(fbSSTBY); (*для простоты вынес СЮДА - получаем адрес экземпляра блока и сохраняем в массив состояний для состояния 1 в dword*)
arStates[2].iActionHandlerPTR := ADR(fbSNORMAL); (*для простоты вынес СЮДА - получаем адрес экземпляра ДРУГОГО блока и сохраняем в массив состояний для состояния 2 в dword*)
END_IF;
fbInst := arStates[1].iActionHandlerPTR; (*а вот и мясо с изюминкой - получим указатель на FB для ПЕРВОГО состояния *)
fbInst^.run(); (*и посмотрим, выполнится ли он*)
fbInst := arStates[2].iActionHandlerPTR; (*а вот и мясо с изюминкой - получим указатель на FB для ВТОРОГО состояния *)
fbInst^.run(); (*и посмотрим, выполнится ли он*)

(*iThisStateHandler := arStates[1].iActionHandler;*) (*а это "игры" с индексами POU*)

(*IF iThisStateHandler <> 0 THEN И ЧТО-БЫ ТАКОЕ С ИНДЕКСОМ СДЕЛАТЬ ?!

END_IF*)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Интересно то, что код прекрасно компилируется без ошибок и предупреждений и в симуляторе выполняется, вот только
ВТОРОЙ вызов fbInst^.run(); ВЫПОЛНЯЕТ RUN ДЛЯ БЛОКА fbSSTBY !! то есть для первого блока, чей адрес сохранен в arStates[1].iActionHandlerPTR,
а не для arStates[2].iActionHandlerPTR как можно было бы предположить.
При этом я готов был выслушать от компилятора жалобу на то, что fbInst := arStates[2].iActionHandlerPTR; присваивает указателю адрес на на тот инстанс, на который объявлен указатель,
но жалобы не последовало. Удивительно, arStates[1].iActionHandlerPTR и arStates[2].iActionHandlerPTR содержат РАЗНЫЕ адреса! Это видно при отладке, однако ж - run вызывается от первого FB.

Трудно понять логику - для чего есть прекрасный оператор INDEXOF если результат его выполнения нельзя использовать (в примере выше код для использования indexof закомментирован) ?!
Поиск аналогичных вопросов и ответов привел на forum.codesys.com, и там, о чудо, об использовании CAA_Callback.lib как обертки вокруг CB_CallFunctionByIndex
Но вот беда - и эта либа только для Codesys 3...А PLC63 "весь под codesys 2".

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Итак, вопрос:
- каким образом программно можно вызвать функцию, либо метод - действие FB, определенные при компиляции ? То есть , имея переменную, как индекс, выбирать из массива указатели/... на FB или функции и динамически вызывать их ?
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Вообще выбор plc63M продиктован необычайной удобностью вкупе с дисплеем, нормированными преобразователями физ величин на борту, понятными алгоритмами и тд. Да и ЦЕНОЙ.
Ну и 3 выхода минимум 0..10в нужны для управления сервами.
Переложил бы задачу на ПЛК1хх какой нибудь, но только ради CODESYS v3.5 это делать "религия не позволяет". Да еще и дисплей тянуть дополнительный для визуализации 3-5 параметров.

Есть ли какие-нибудь соображения на этот счет, уважаемые коллеги и разработчики ?

capzap
11.03.2019, 09:54
мне вообще не заметно где здесь динамическое выполнение, ладно бы использовалась SysMemAlloc

второе, а почему не написать fbInst := ADR(fbSSTBY);

третье
ПЛК1хх какой нибудь, но только ради CODESYS v3.5 такое невозможно впринципе, а не из-за Вашей религии

dorofeevms
11.03.2019, 10:21
Коротко - тута обратного вызова нету. Совсем
Видимо придется смириться. Пока не надоест до конца будут if then писать :):)


мне вообще не заметно где здесь динамическое выполнение, ладно бы использовалась SysMemAlloc

второе, а почему не написать fbInst := ADR(fbSSTBY);


Что касается первого, я же написал, что сам код динамического вызова "в процессе машины состояний" не приведен, тут я лишь написал САМ вызов, чтобы показать что не работает как хотелось бы.
Сама идея проста: в массиве лежат адреса вызова fb (или функций), индекс массива = коду события (или наоборот - как душе угодно), тогда:
адрес_функции := массив[код_события или код_следующего состояния или структура.массив[код_следующего_состояния] ] , ну вот так упрощенно человеческим языком :)
Ну и далее call (*адрес_функции). Очень близко к этому делаю в C. Бесконечный цикл вызова состояний состоит из 4 строк, обработчик состояния - одна функция/процедура. Модифицировать код - проще некуда (я давно перестал писать с нуля, беру старый проект и переименовываю состояния, если, конечно, суть задачи описывается автоматным программированием). Код события обычно "вычисляю-перевожу" из событий клавиатуры, таймеров и тд в некий int
В C использую enum, как код состояния и индекс массива, и как код события (отдельный enum конечно).

ко-второму: так именно так и написано, только в начале выполнения (при инициализации) в массив состояний заносятся адреса FB, который потом будут выбираться и вызываться через индексы, то есть
fbInst := arStates[1].iActionHandlerPTR как раз и получает ADR(...)
На этапе программирования мы же не знаем адреса FB, поэтому в начале выполнения заполняем массив состояний адресами вызываемых FB.

Вот смотрю, и думаю, а что интересно будет, если fbInst объявить тоже как DWORD ?... вечером проверю.

А невозможно ЧТО (про плк1ххх) ? В CS3 есть даже наследование и "полиморфизм" (https://help.codesys.com/api-content/2/codesys/3.5.13.0/en/_cds_method_call/#calling-methods).
Изумительный пример и образец. Такой подход , только в жесткой форме hardcoding'ка, использую давно.
Попробовал в теории в режиме симуляции - работает. Очень удобно и, главное, элегантно так получается. А значит правильно работать будет.

lara197a
11.03.2019, 11:07
1. А что мешает использовать цикл For и выход из него Exit, для обработки больших массивов и структур?

2. Указатели в программе использовать не нужно.
Прямой и косвенной адресации вполне достаточно.
Это одно из нарушений правил стандартов MISRA.

3. Возможно Вам понравится:
https://habr.com/ru/company/hexlet/blog/303160/

capzap
11.03.2019, 11:27
Видимо придется смириться. Пока не надоест до конца будут if then писать :):)


Что касается первого, я же написал, что сам код динамического вызова "в процессе машины состояний" не приведен, тут я лишь написал САМ вызов, чтобы показать что не работает как хотелось бы.
...
ко-второму: так именно так и написано, только в начале выполнения (при инициализации) в массив состояний заносятся адреса FB, который потом будут выбираться и вызываться через индексы, то есть
fbInst := arStates[1].iActionHandlerPTR как раз и получает ADR(...)
На этапе программирования мы же не знаем адреса FB, поэтому в начале выполнения заполняем массив состояний адресами вызываемых FB.

так Вы все же динамически выделяете память под нужное ПОУ или все они объявляются изначально в окне объявлений. Если, первое, то тогда с чего решили что начальная точка будет одна и та же каждый цикл как и в первый скан?

ПЛК1хх поддерживают только КДС2.3

dorofeevms
11.03.2019, 12:26
так Вы все же динамически выделяете память под нужное ПОУ или все они объявляются изначально в окне объявлений. Если, первое, то тогда с чего решили что начальная точка будет одна и та же каждый цикл как и в первый скан?

ПЛК1хх поддерживают только КДС2.3

Извиняюсь, capzap, не совсем верно понял первый Ваш вопрос и уточнение, поэтому отреагировал не вполне адекватно.:rolleyes:
Как я делаю (или точнее - хочу сделать):
- создаю экземпляр FB (fbSNORMAL: fbNORMAL; fbSSTBY: fbSTBY; ).
- получаю и сохраняю адрес
- в ходе исполнения беру сохраненный адрес и вызываю метод fbInst^.run();
(это то я даже проверил и "оно" работает)

Индекс тоже можно получить на стадии инициализации, куда его только потом запихать не нашел. И этот путь "Закомментировал".
В чистом Си (при программировании для ПЛК или МК) классы (и ООП в целом) не использую, там я вместо FB использую функции void*... (Объявляю прототип, позже объявляю, сохраняю указатель в массиве и т.д.)
Тут с использованием функций в интересующем русле вообще тупик, поэтому и зашел со стороны FB, которые "появляются в памяти" и у которых есть действия.

По поводу ПЛК1хх - промашка моя вышла, Вы правы. Тем более обиднее, и 63й и 73й для задачи подходят.

И, видимо, придется махнуть рукой и делать как делал. Интересно, а вообще либа для CS2.3 существует, которая делает нечто вроде CB_CallFunctionByIndex в либе CAA_Callback.lib?...
За ссылку про программистов НАСА спасибо :) Понравилось, видел раньше.
Боюсь показаться слишком самоуверенным, но я не очень уважаю программистов НАСА и вообще... пишущих на клавиатурах без ЙЦУКЕН.
Я там был (в том числе, в ЦУП Хьюстон кстати, но не в отделе разработки ПО), и своими глазами Видел, Кто пишет код для НАСА, "нефтянки" и иже с ними :):):cool:

capzap
11.03.2019, 12:48
- в ходе исполнения беру сохраненный адрес и вызываю метод fbInst^.run();
(это то я даже проверил и "оно" работает)

вызываете действие, а сами ФБ выполняются или только объявлены

dorofeevms
11.03.2019, 13:45
вызываете действие, а сами ФБ выполняются или только объявлены

Вызываются только действия. Я полагаю, что компилятор организует и выделяет память для fb при объявлении, для действий где-то же он место выделяет.
Есть мысли, что если вызвать блок поведение будет другим ? Или сделать что-то вроде "инициализации" блока , вызвав его код (добавить конечно) ?
Можно попробовать вызывать сам FB (типа (fbInst^)() - на что-то страшное похоже ... :) ). А вообще интересно очень что получится... эх, cs нет на работе....
У меня по ходу обсуждения появилась еще затея ... Ради эксперимента вечером покопаюсь еще.

dorofeevms
11.03.2019, 20:49
Может кому интересны результаты извращений...
Проделал все что в голову пришло, не работает. Во вложении экспорт проекта.
Пока будут думать над обходным решением вопроса. Ибо такой расклад меня пока не устраивает,
хочу динамический выбор вызываемого fb.....

dorofeevms
12.03.2019, 06:10
2Валенок, понял - нету здесь :) про void* - это я пытался объяснить алгоритм, который пытаюсь тут реализовать, может проще было фрагмент кода сразу привести... Ясен пень, общего там ничего. Все что вы привели в пример - в export тест-примера, все именно так и работает, как сказали. Спасибо всем откликнувшимся!
Вопрос исчерпан.