PDA

Просмотр полной версии : ST, паузы, async/await/coroutines



Владимир Ситников
09.10.2017, 22:46
Пока размышлял над Драконизацией программирования (http://www.owen.ru/forum/showthread.php?t=27487), пришла мысль, что, возможно, удобной штукой будет добавить coroutines в ST.

Гипотеза в том, что последовательный код писать проще чем "автоматный".
Да, есть CASE, но расщеплять состояния приходится вручную.

Что можно было бы сделать: добавить оператор "пауза" в ST. Тогда код с паузами при компиляции автоматически бы превращался в CASE автомат.

Пример:

PROGRAM запуск_насоса
VAR
номер_насоса: INT;

WHILE TRUE
номер_насоса := найти_рабочий_насос();
IF номер_насоса=-1 THEN
авария_всех_насосов();
ПАУЗА t#1s;
CONTINUE;
END_IF;

увеличить_количество_запусков(номер_насоса);
запустить_насос(номер_насоса);

ПАУЗА t#10s;

WHILE давление_есть
ПАУЗА;
END_WHILE;

(* Давление пропало, значит насос сломался *)
остановить_насос(номер_насоса);
установить_признак_аварии(номер_насоса);
END_WHILE;
END_PROGRAM

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

Иными словами, скомпилированный код может выглядеть вот так:


PROGRAM запуск_насоса
VAR
номер_насоса: INT;
состояние : INT;
ton1: TON;
ton2: TON;
WHILE TRUE
CASE состояние OF
0:
номер_насоса := найти_рабочий_насос();
IF номер_насоса=-1 THEN
авария_всех_насосов();
состояние := 1; (* ПАУЗА 1s *)
ton1(IN := FALSE, PT := t#1s);
RETURN; (* возвращаем управление *)
END_IF;
состояние := 2;

1:
ton1(IN := TRUE); (* ПАУЗА 1s *)
IF ton1.Q THEN
состояние := 0; (* continue *)
ELSE
RETURN; (* возвращаем управление *)
END_IF;

2:
увеличить_количество_запусков(номер_насоса);
запустить_насос(номер_насоса);

ton2(IN := FALSE, PT := t#10s);
состояние := 3;
RETURN;

3:
ton2(IN := TRUE); (* ПАУЗА 10s; *)
IF ton2.Q THEN
состояние := 4;
ELSE
RETURN;
END_IF;

4:
IF давление_есть THEN
RETURN;
ELSE
(* Давление пропало, значит насос сломался *)
состояние := 5;
END_IF;

5:
остановить_насос(номер_насоса);
установить_признак_аварии(номер_насоса);
состояние := 0;
END_WHILE;
END_PROGRAM

С одной стороны, конечно, невелика наука расставить CASE.
Но, с другой стороны, на паузах код гораздо внятнее. Особенно, когда паузы внутри. Была одна строка "ПАУЗА t#1s;", а превратилась в 9 строк кода (запуск таймера, заход, выход)
Да и сам подход с async/await довольно широко сейчас используется в JavaScript, C#, Kotlin.

Что думаете?

capzap
09.10.2017, 23:01
А без вилетру можно обойтись, а то как то не то у него предназначение в плк. И пока выгода от такого подхода не раскрыта

Владимир Ситников
09.10.2017, 23:19
А без вилетру можно обойтись
Вы про 1-ый или про 2-ой вариант?

В первом варианте while true нужно т.к. там реально алгоритм такой: пока активна программа она пытается найти насос, запустить, а, если он сдох, то найти очередной, запустить и так по кругу.

Во втором варианте while true нужно для того, чтобы те переходы, которые без пауз могли бы выполняться в рамках одного и того же цикла.
Я вот сейчас посмотрел на код, и понял, что "состояние := 2;" по сути лишнее. Аналогично, "состояние := 5;" тоже "лишнее" и его можно вклеить в точку вызова.

В целом, тут теперь "while true" нужно для двух целей:
а) если прошла 1 секунда после ошибки всех насосов, то пробуем очередной на этом цикле, а не на следующем
б) если насос работал, а давление пропало, то пойдём искать насос на этом же цикле, на не на следующем
в) когда вклеил вручную состояния, по-моему, читаться стало хуже. Иными словами, само наличие while true не вредит, а позволяет разбить одно состояние на несколько переходов, чтобы было проще анализировать логику.
г) если вопрос в "потенциальных собаках", то я вообще предлагаю делать "анализ зацикливания на прикладном уровне". Иными словами, while true должно быть for i in 1..100, и, если досчитали до 100, то переводить алгоритм в аварийное состояние.

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



PROGRAM запуск_насоса
VAR
номер_насоса: INT;
состояние : INT;
ton1: TON;
ton2: TON;
WHILE TRUE
CASE состояние OF
0:
номер_насоса := найти_рабочий_насос();
IF номер_насоса=-1 THEN
авария_всех_насосов();
состояние := 1; (* ПАУЗА 1s *)
ton1(IN := FALSE, PT := t#1s);
RETURN; (* возвращаем управление *)
END_IF;
увеличить_количество_запусков(номер_насоса);
запустить_насос(номер_насоса);

ton2(IN := FALSE, PT := t#10s);
состояние := 3;
RETURN;

1:
ton1(IN := TRUE); (* ПАУЗА 1s *)
IF ton1.Q THEN
состояние := 0; (* continue *)
ELSE
RETURN; (* возвращаем управление *)
END_IF;

3:
ton2(IN := TRUE); (* ПАУЗА 10s; *)
IF ton2.Q THEN
состояние := 4;
ELSE
RETURN;
END_IF;

4:
IF давление_есть THEN
RETURN;
ELSE
(* Давление пропало, значит насос сломался *)
остановить_насос(номер_насоса);
установить_признак_аварии(номер_насоса);
состояние := 0;
END_IF;
END_WHILE;
END_PROGRAM


И пока выгода от такого подхода не раскрыта
Ну, как? Код на паузах же гораздо проще читать и писать.

Новички -- так вообще постоянно пытаются вкрячить паузу в цикл ПЛК. Им дают стандартный ответ "тут так не принято", и те уходят.
Опытные фигачат на CASE, но разве удобно вручную разбивать эти самые CASE?
А удобно добавлять-удалять состояние к имеющимся?

Как раз запись алгоритма с паузами может упрощать запись алгоритмов управления, сетевого обмена.

Возможно, стоит рассмотреть задачу посложнее.

capzap
10.10.2017, 07:35
а) и б) ни чем не отличается от обычного программирования, так же по наступлению события что то меняем и по окончании цикла это применяется к исполнительным механизмам

код избыточен, зачем использовать ретурны если это и так произойдет, зачем столько таймеров если выполняется только один кейс

Вобщем пока всё равно не понятно

melky
10.10.2017, 08:32
acync только добавит мешанину в код ПЛК, так как вы посылаете несколько процедур на выполнение которые шли по порядку 1,2,3,4,5 а ответы могут прилететь по мере их выполнения 3,2,5,1,4

Что же в таком случае вы будете делать на ST для определения где чей ответ ?
В случае C# и использования структурных данных, где каждый ответ должен совпадать с определенным массивом это тоже добавляет неудобств.

Владимир Ситников
10.10.2017, 10:40
Приведу ещё пример: асинхронная запись в файл, которую настоятельно рекомендует ОВЕН (http://www.owen.ru/forum/showthread.php?t=23035&p=191738&viewfull=1#post191738) (по понятным причинам).

Пример от ОВЕН выглядит так:

CASE state_res OF

0:
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res=ASYNC_WORKING THEN
state:=1;
END_IF


1:
res:=OwenFileOpenAsync('test.dat','a',ADR(handle)) ;
IF res=ASYNC_DONE THEN
IF handle<>0 THEN
state:=2;
ELSE
state:=0;
END_IF
ELSIF res<0 THEN
state:=0;
END_IF



2:
res:=OwenFileWriteAsync(handle,ADR(bufout),14,ADR( result));
IF res=ASYNC_WORKING THEN
state:=3;
ELSE
state:=6;
END_IF



3:
res:=OwenFileWriteAsync(handle,ADR(bufout),14,ADR( result));
IF res=ASYNC_DONE THEN
IF result=14 THEN
state:=4;
ELSE
state:=6;
END_IF
ELSIF res<0 THEN
state:=6;
END_IF



4:
res:=OwenFileReadAsync(handle,ADR(bufin),14,ADR(re sult));
IF res=ASYNC_WORKING THEN
state:=5;
ELSE
state:=6;
END_IF



5:
res:=OwenFileReadAsync(handle,ADR(bufin),14,ADR(re sult));
IF res=ASYNC_DONE THEN
IF result>=0 THEN
state:=6;
counter:=counter+1;
ELSE
state:=6;
END_IF
ELSIF res<0 THEN
state:=6;
END_IF



6:
res:=OwenFileCloseAsync(handle,ADR(result));
IF res=ASYNC_WORKING THEN
state:=7;
ELSE
state:=0;
END_IF



7:
res:=OwenFileCloseAsync(handle,ADR(result));
IF res=ASYNC_DONE THEN
IF result=0 THEN

state:=0;
ELSE
state:=0;
END_IF
ELSIF res<0 THEN
state:=0;
END_IF


ELSE
state:=0;
END_CASE
Что? Всё понятно? Прямо прочитали и сразу поняли?
Контрольный вопрос: чем отличается state=0 от state=1? Если честно, то я не особо понял.

На паузах это будет так (40 строк вместо 100):


WHILE TRUE
REPEAT
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;

IF res<0 OR handle=0 THEN
CONTINUE; (* ошибка, поехали сначала *)
END_IF

REPEAT
res:=OwenFileWriteAsync(handle,ADR(bufout),14,ADR( result));
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;

(* читаем файл *)
IF result=14 THEN
REPEAT
res := OwenFileReadAsync(handle,ADR(bufin),14,ADR(result) );
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;
END_IF;

(* Закрытие файла *)
REPEAT
res := OwenFileCloseAsync(handle,ADR(result));
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;
END_WHILE;


По-моему, вариант с паузами гораздо понятнее. Операции "открыть файл", "записать", "прочитать", "закрыть" стали встречаться в коде только 1 раз. В исходном варианте они встречались по 2 раза. Да и разнообразные "IF ... state:=5;" никак не прибавляют читабельности и понятности коду.

Можно ли было изначально написать на CASE более внятно? Вполне возможно.

Но, и вариант "на паузах" можно написать гораздо компактнее. А именно: видно, что каждая операция с файлом обёрнута в repeat-until.
Если в самой библиотеке объявить вспомогательные функции типа таких


ASYNC FUNCTION OwenFileOpenAsync2 : ASYNC_RET_VALUE
VAR_INPUT
stFileName: STRING(255);
stMode : STRING[3];
returnvalue:POINTER TO DWORD;
END_VAR
res: ASYNC_RET_VALUE;
VAR
END_VAR
REPEAT
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);
OwenFileOpenAsync2 := res;
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;
END_FUNCTION


То "код примера работы с асинхронной библиотекой файлов станет таким.
16 понятных строк, там где было 100 строк на CASE state=2/state=6.

При этом стоит понимать, что код по-прежнему выполняется асинхронно, т.е. "не занимает" цикл ПЛК.



WHILE TRUE
res := OwenFileOpenAsync2('usb:test.dat','a',ADR(handle)) ;

IF res<0 OR handle=0 THEN
CONTINUE; (* ошибка, поехали сначала *)
END_IF

res := OwenFileWriteAsync2(handle,ADR(bufout),14,ADR(resu lt));

IF result=14 THEN
res := OwenFileReadAsync2(handle,ADR(bufin),14,ADR(result ));
END_IF;

res := OwenFileCloseAsync2(handle,ADR(result));
END_WHILE;

capzap
10.10.2017, 11:01
"ктож его посадит, он же памятник"
пример как пример, пытались донести жизненный цикл процесса, вот обработка, вот завершение, конечно можно было бы обойтись одним кейсом и добавить еще один elsif(или заменить на еще CASE) чтоб обрабатывать, а вернее ни чего не делать когда результат возвращает ASYNC_WORKING, так же как и -1 . Зачем добавлять паузу, когда и так сидим ждем(находимся в паузе) возврата DONE и правильного дескриптора файла.
Зависнув в паузе из вилетру, мы не дадим возможности выполнится другому коду, не менне важному чем та же запись в файл, технологический то процесс не должен проставивать пока пишутся данные

Владимир Ситников
10.10.2017, 11:42
пример как пример, пытались донести жизненный цикл процесса, вот обработка, вот завершение, конечно можно было бы обойтись одним кейсом и добавить еще один elsif(или заменить на еще CASE) чтоб обрабатывать,
Смотрите: я в 16 строк донёс жизненный цикл процесса. Вот открытие файла, вот запись, вот чтение. Утверждаете, что "вариант ОВЕН" понятнее?


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

Ещё раз: код с использованием OwenFileWriteAsync2 абсолютно идентичен исходному коду из примера.
Что и где зависнет?

capzap
10.10.2017, 11:53
попробую обяснить :) сейчас я прихожу на работу заглядываю в туалет если туалетная бумага если что, Вы предлагаете сидеть возле туалета, пока уборщица не принесет бумагу, т.е есть если она сегодня не придет я и работать не буду и если буду принципиальным то на работе заночую, что уже пахнет Кащенко

Владимир Ситников
10.10.2017, 12:16
попробую обяснить :) сейчас я прихожу на работу заглядываю в туалет если туалетная бумага если что, Вы предлагаете сидеть возле туалета, пока уборщица не принесет бумагу, т.е есть если она сегодня не придет я и работать не буду и если буду принципиальным то на работе заночую, что уже пахнет Кащенко
Где я такое предлагаю? Показывайте где и что зависнет и заблокирует другие задачи.

capzap
10.10.2017, 12:26
Где я такое предлагаю? Показывайте где и что зависнет и заблокирует другие задачи.в этом коде. Если максимум цикла например две секунды, а бибка вернет DONE только после третьей, плк уйдет в перегрузку

Владимир Ситников
10.10.2017, 12:41
в этом коде. Если максимум цикла например две секунды, а бибка вернет DONE только после третьей, плк уйдет в перегрузку
Слушайте, ну неужто вы меня подозреваете в непонимании watchdog механизма?

Прочитайте, пожалуйста, ещё раз 1-е сообщение в этой теме. Я же написал, что "команда ПАУЗА" будет компилироваться в CASE-автомат.

Касательно вашего возражения: команда "ПАУЗА" как раз и будет приводить к выходу в основной цикл.
Как раз в моём REPEAT цикле пауза есть:


REPEAT
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res = ASYNC_WORKING THEN
ПАУЗА; <--- ВОТ ОНА, ПАУЗА!!!
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;

Как раз, если OwenFileOpenAsync вернуло ASYNC_WORKING, то это означает, что функция ещё не доработала и нам нужно вернуть управление в ПЛК и пробовать в следущий раз.
Именно это ПАУЗА и сделает. Она вернёт управление в ПЛК, а при следующем вызове мы продолжим как раз с этого момента.

К слову, если возвращаемых значений более чем 2 (ASYNC_WORKING / ASYNC_DONE), то это не меняет саму суть.


Использовать примерно так:



PROGRAM PLC_PRG
VAR
i: INT;
filesCreated: INT;
END_VAR

i := i+1;
(* Т.е. в основном цикле ПЛК пинаем программу FileWriteRead, она продвигается (или не продвигается) и тут же возвращает управление нам *)
ВЫПОЛНИТЬ_ШАГ FileWriteRead;
filesCreated := FileWriteRead.cntr;
END_PROGRAM

ASYNC PROGRAM FileWriteRead
VAR_OUTPUT
cntr: INT;
END_VAR;
WHILE TRUE
res := OwenFileOpenAsync2('usb:test.dat','a',ADR(handle)) ;

IF res<0 OR handle=0 THEN
CONTINUE; (* ошибка, поехали сначала *)
END_IF

res := OwenFileWriteAsync2(handle,ADR(bufout),14,ADR(resu lt));

IF result=14 THEN
res := OwenFileReadAsync2(handle,ADR(bufin),14,ADR(result ));
END_IF;

res := OwenFileCloseAsync2(handle,ADR(result));
cntr := cntr+1;
END_WHILE;
END_PROGRAM;

capzap
10.10.2017, 13:01
Именно это ПАУЗА и сделает. Она вернёт управление в ПЛКкак она это сделает? Из цикла REPEAT она не вырвется пока не выполниться условие, пока не наступит ASYNC_DONE и не будет ни какого следующего цикла плк, только постоянный вызов ПАУЗЫ. Если Ваш пример работает, то только потому что запись или чтение успевают свершиться до максимального времени цикла поставте его чуть больше минимального, нагрузите проект дополнительной работой и сразу начнутся перегрузки. Если так нужна асинхронность процессов, используйте разные задачи, это более перспективно чем жонглировать циклами

Владимир Ситников
10.10.2017, 13:46
как она это сделает? Из цикла REPEAT она не вырвется пока не выполниться условие, пока не наступит ASYNC_DONE и не будет ни какого следующего цикла плк, только постоянный вызов ПАУЗЫ.

Да прочитайте пожалуйста, 1-е сообщение. Там написано в какой код будет компилироваться ПАУЗА.

Рассмотрим, для простоты вот такой код:


ASYNC FUNCTION OwenFileOpenAsync2 : ASYNC_RET_VALUE
VAR_INPUT
stFileName: STRING(255);
stMode : STRING[3];
returnvalue:POINTER TO DWORD;
END_VAR
VAR
res: ASYNC_RET_VALUE;
END_VAR
REPEAT
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);
OwenFileOpenAsync2 := res;
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;
END_FUNCTION

Он будет компилироваться в такой код. Паузы как таковой вообще не останется в финальной программе.


FUNCTION_BLOCK OwenFileOpenAsync2 : ASYNC_RET_VALUE
VAR_INPUT
stFileName: STRING(255);
stMode : STRING[3];
returnvalue:POINTER TO DWORD;
END_VAR
VAR_INPUT_OUTPUT
state : INT; (* эту переменную создал компилятор *)
END_VAR
VAR_OUTPUT
res: ASYNC_RET_VALUE;
END_VAR
WHILE true DO
CASE state OF (* и этот case тоже создал компилятор *)
0:
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);
IF res = ASYNC_WORKING THEN
state := 1;
RETURN; (* !! вернуть управление *)
END_IF;
state := 1;
1:
IF res<>ASYNC_DONE THEN (* это ошмёток UNTIL res<>ASYNC_DONE *)
state := 0;
ELSE
state := 2;
END_IF;
2:
RETURN;
END_CASE;
END_WHILE;
END_FUNCTION_BLOCK


Иными словами, за 1 вызов оно 1 раз вызовет OwenFileOpenAsync. Если же получен ответ ASYNC_DONE, то оно прекратит вызовы OwenFileOpenAsync.

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

Владимир Ситников
10.10.2017, 14:48
не понимаю этого кода. Может быть не ретурн должен использоваться ( или ФБ вместо функции), он должен выкинуть из функции, следовательно state при следующем вызове функции вновь будет равна нулю и ни когда не случиться поимка ASYNC_DONE в таком случае

Ну, да, тут подразумевается, что "state" будет принудительно сбрасываться в 0 "только перед самым первым вызовом" и что state будет сохраняться от вызова к вызову.
Поправил на FB.

capzap
10.10.2017, 14:54
1:
IF res<>ASYNC_DONE THEN (* это ошмёток UNTIL res<>ASYNC_DONE *)
state := 0;
ELSE
state := 2;
END_IF;чему в этом кейсе равен res?

Владимир Ситников
10.10.2017, 14:57
чему в этом кейсе равен res?

Тому, что вернула функция OwenFileOpenAsync на шаге

0:
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);

ASYNC_WORKING, ASYNC_DONE и т.п. статусы.

В чём вопрос-то? Оно же не может сходу взять и зайти в case 1. Туда попадают только из case 0.

capzap
10.10.2017, 15:02
а зачем проверка условия, если вдруг в нулевом кейсе не выполнилось условие res = ASYNC_WORKING?
а если мы попали из нулевого кейса с результатом res := ASYNC_WORKING, то когда он узнает что наступило ASYNC_DONE?

Владимир Ситников
10.10.2017, 15:11
а если мы попали из нулевого кейса с результатом res := ASYNC_WORKING, то когда он узнает что наступило ASYNC_DONE?
Если мы попали из 0-го с результатом res := ASYNC_WORKING, то это и означает, что "ASYNC_DONE ещё НЕ наступило".
Оно увидит, что res<>ASYNC_DONE, выполнит state:=0, тут же (RETURN-а то не было) выполнит ещё одну итерацию внешнего WHILE TRUE, зайдёт ещё раз в CASE, выполнит OwenFile..., посмотрит на res, и так далее.

Владимир Ситников
10.10.2017, 15:14
а зачем проверка условия, если вдруг в нулевом кейсе не выполнилось условие res = ASYNC_WORKING?
Я не пытался показать оптимальный код решения конкретной задачи.
Я показал в какой код может компилировать *тупой* компилятор. Так, чтобы было видно, что "из кода на паузах" легко и непринуждённо может автоматически сгенерироваться код на CASE'ах.

Если посмотреть логику исходной программы, то там такой код:


IF res = ASYNC_WORKING THEN
ПАУЗА; (* <-- после "выхода" из паузы, код проваливается ниже и всё равно сравнивает DONE, хотя очевидно, что равенства там не будет *)
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;


Т.е. исходная программа сравнивала "<>DONE" даже в случае "res = ASYNC_WORKING". Да, была такая "неоптимальность".

Технически, можно было бы написать исходную программу чуть более оптимально (ну, человеку понятно, что если было ASYNC_WORKING, то условие UNTIL можно не проверять, а сразу переходить к новой итерации):



IF res = ASYNC_WORKING THEN
ПАУЗА;
CONTINUE; (* <--- оптимизация, переходим к новой итерации без проверок на _DONE *)
END_IF;
UNTIL res<>ASYNC_DONE
END_REPEAT;


И тогда компилятор сделал бы такой код:


FUNCTION_BLOCK OwenFileOpenAsync2 : ASYNC_RET_VALUE
VAR_INPUT
stFileName: STRING(255);
stMode : STRING[3];
returnvalue:POINTER TO DWORD;
END_VAR
VAR_INPUT_OUTPUT
state : INT; (* эту переменную создал компилятор *)
END_VAR
VAR_OUTPUT
res: ASYNC_RET_VALUE;
END_VAR
WHILE true DO
CASE state OF (* и этот case тоже создал компилятор *)
0:
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);
IF res = ASYNC_WORKING THEN
state := 0; (* CONTINUE -- значит новое состояние это "начало цикла" -- т.е. 0 *)
RETURN; (* !! вернуть управление *)
END_IF;
(* state=1 используется из одного места -- можно не создавать отдельную case ветку *)
IF res<>ASYNC_DONE THEN (* это ошмёток UNTIL res<>ASYNC_DONE *)
state := 0;
ELSE
state := 2;
END_IF;
2:
RETURN;
END_CASE;
END_WHILE;
END_FUNCTION_BLOCK

capzap
10.10.2017, 15:15
тогда два момента
почему в одном кейсе не проверять и WORKING и DONE и зачем тогда CASE вобще нужно
во вторых еще раз спрошу если DONE не наступит ни когда что будет с циклом while ?

Владимир Ситников
10.10.2017, 15:26
тогда два момента
почему в одном кейсе не проверять и WORKING и DONE и зачем тогда CASE вобще нужно
Этот вопрос точно относится к обсуждаемому?

Я спрошу: вы поняли *в какой код* я предлагаю компилировать "код на паузах"?
У вас исходно было возражение: код с паузами "зациклится, подвесит ПЛК, приведёт к перезагрузке". Это возражение осталось?

По-моему, я весьма подробно описал почему код не зависнет. Нет?

Наличие или отсутствие CASE это экономия на спичках. На что влияет есть тут CASE или нет?



во вторых еще раз спрошу если DONE не наступит ни когда что будет с циклом while ?
Есть 2 варианта:
1) Если оно будет равно ASYNC_WORKING, то state застрянет в 0 и ФБ будет возвращать управление
2) Если res окажется ни WORKING ни DONE (а хренью какой-нибудь), то, разумеется, цикл зависнет (вызовет перезагрузку ПЛК).

Но это как это относится к сути предлагаемого мною подхода?

Владимир Ситников
10.10.2017, 15:28
и где тогда goto ?

Не понимаю вопроса.
Во что компилировать (в WHILE TRUE CASE или в GOTO или ещё во что) -- вопрос не такой принципиальный.

Тут более важен сам подход: пишем код в синхронном стиле, а оно само разворачивает в автомат.

capzap
10.10.2017, 15:36
Тут более важен сам подход: пишем код в синхронном стиле, а оно само разворачивает в автомат.
вопрос кто это будет разворачивать, кому доверять, как КДС будет анализировать


ILE true DO
CASE state OF (* и этот case тоже создал компилятор *)
0:
res:=OwenFileOpenAsync(stFileName, stMode, returnvalue);
IF res = ASYNC_WORKING THEN
state := 0; (* CONTINUE -- значит новое состояние это "начало цикла" -- т.е. 0 *)
RETURN; (* !! вернуть управление *)
END_IF;
(* state=1 используется из одного места -- можно не создавать отдельную case ветку *)
IF res<>ASYNC_DONE THEN (* это ошмёток UNTIL res<>ASYNC_DONE *)
state := 0;
ELSE
state := 2;
END_IF;
2:
RETURN;
END_CASE;
кода еще не было когда я написал пост, "По-моему, я весьма подробно описал почему код не зависнет. Нет?" если ни WORKING ни DONE state становиться равной двум вероятность этого огромна, мне такая ПАУЗА не нужна, лучше по старинке, что то ручками написать

Владимир Ситников
10.10.2017, 15:47
вопрос кто это будет разворачивать, кому доверять, как КДС будет анализировать
Разумеется, разворачивать будет не КДС.



кода еще не было когда я написал пост, "По-моему, я весьма подробно описал почему код не зависнет. Нет?" если ни WORKING ни DONE state становиться равной двум вероятность этого огромна, мне такая ПАУЗА не нужна, лучше по старинке, что то ручками написать

Во-первых, такую ошибку легко найти визуально:

REPEAT
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res = ASYNC_WORKING THEN
ПАУЗА;
END_IF;
UNTIL res<>ASYNC_DONE (* видно, что если ни DONE, ни WORKING, то будем крутиться без пауз *)
END_REPEAT;


Более правильным, наверное, был бы такой вариант:


WHILE TRUE
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res < 0 OR res = ASYNC_DONE THEN
EXIT; (* или RETURN -- тут не так важно *)
END_IF;
ПАУЗА;
END_WHILE;


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

Но в более сложных случаях (почти во всех в реальности) нужно делать цепочки вызовов, и следующий вызов нужно начинать только как предыдущий закончится.
В итоге "на паузах" код будет простой:

WHILE TRUE
res := OwenFileOpenAsync2('usb:test.dat','a',ADR(handle)) ;

IF res<0 OR handle=0 THEN
CONTINUE; (* ошибка, поехали сначала *)
END_IF

res := OwenFileWriteAsync2(handle,ADR(bufout),14,ADR(resu lt));

IF result=14 THEN
res := OwenFileReadAsync2(handle,ADR(bufin),14,ADR(result ));
END_IF;

res := OwenFileCloseAsync2(handle,ADR(result));
END_WHILE;
А под капотом там CASE на кучу состояний. В примере ОВЕН'а там 7 состояний и 100 строк кода.
Найти ошибку в коде ОВЕН гораздо сложнее чем в коде на паузах.



Во-вторых, можно в генерируемый код добавлять счётчик "дануна (с) Валенок".
Тогда в самой программе можно будет понять *где именно* она зациклилась.


Что говорит тов. Филоненко сейчас? "Сработала собака? Пытайтесь как-нибудь угадать в каком месте кода".

А тут же ПЛКшная собака не сработает, и можно будет подключиться в online и посмотреть в каком состоянии оно оказалось.

capzap
10.10.2017, 15:55
Разумеется, разворачивать будет не КДС.
т.е. вместо привычной записи
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res = ASYNC_DONE THEN
(* выполняем дальнейшие действия по теме *)
END_IF;
в КДС будет "портянка" кода, даже если она кем то протестирована

Владимир Ситников
10.10.2017, 16:04
т.е. вместо привычной записи
res:=OwenFileOpenAsync('usb:test.dat','a',ADR(hand le));
IF res = ASYNC_DONE THEN
(* выполняем дальнейшие действия по теме *)
END_IF;
Такая запись не подойдёт, ведь, вы что, на каждый цикл ПЛК будете заново открывать файл?

После того как он откроется его до закрытия открывать не нужно.
Иными словами, если мы начали файл писать, то открывать его ещё раз даже вредно.

Владимир Ситников
10.10.2017, 16:07
хочу вернутся на пару строк вверх

Наверняка что-то умное, но всё равно непонятное.

Владимир Ситников
10.10.2017, 16:09
так в кейсе можно шагать куда угодно

Можно, и что?

capzap
10.10.2017, 16:12
а почему я должен его постоянно открывать? Взять те же примеры по сетевому обмену, отдельно функция по открытию сокета, отдельно по ожиданию приема


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

Владимир Ситников
10.10.2017, 16:15
а почему я должен его постоянно открывать? Взять те же примеры по сетевому обмену, отдельно функция по открытию сокета, отдельно по ожиданию приема

Покажете как на ваш взгляд должен выглядеть "привычный код" для случая

открыть файл
записать
IF записалось
прочитать
END_IF
закрыть файл

повторить (ну, чтобы файл обновлялся новыми данными по мере того, как работает ПЛК)

ОВЕН показали. У них это 7 состояний и 100 строк (http://www.owen.ru/forum/showthread.php?t=23035&p=191738&viewfull=1#post191738)

Владимир Ситников
10.10.2017, 16:17
сложные случаю, как раз таки быстро дадут понять, что паузы через бесконечные циклы не дадут работать

Пример "сложного случая" есть? Ну, чтобы обсуждать что-то конкретное, а не абстрактное "быстро дадут понять".

Я переписал пример "асинхронной записи файлов" -- стало намного понятнее.

Владимир Ситников
10.10.2017, 16:18
так это и есть одна из основных фишек кейса

Ну, фишка, и что?
Вы о том, что "всё и так можно сделать на CASE'ах?

Наверняка же понимаете, что "делить CASE состояния вручную" тяжелее и менее надёжно.

Владимир Ситников
10.10.2017, 17:04
Обычное начало потокофилов ))

Снова какие-то странные комментарии.

Владимир Ситников
10.10.2017, 17:06
Не. Вы выделяете часть задач делаемых на автоматах для другого редактора ?

Почему часть? Можно и все задачи в другой редактор перенести.

Вон, например, ПЛК110 + MasterScada4D. Там вообще КДС нет. И, ничего.

Владимир Ситников
10.10.2017, 17:35
А зачем там ST с кейсом оставили ?

В 4D?
Так в стандарте есть CASE, вот и оставили. Какие с ним проблемы?

Я же не говорю, что нужно срочно все CASE'ы переписать на паузы. Я говорю, что ПАУЗА и встраивание асинхронных ФБ в сам компилятор может сильно упростить понимание, поддержку и написание кода.

capzap
10.10.2017, 17:49
Покажете как на ваш взгляд должен выглядеть "привычный код" для случая

открыть файл
записать
IF записалось
прочитать
END_IF
закрыть файл

повторить (ну, чтобы файл обновлялся новыми данными по мере того, как работает ПЛК)

ОВЕН показали. У них это 7 состояний и 100 строк (http://www.owen.ru/forum/showthread.php?t=23035&p=191738&viewfull=1#post191738)
нет у меня сейчас ни чего с асинхронной бибкой, но было бы что то подобное этому (http://www.owen.ru/forum/showthread.php?t=14918&p=109415&viewfull=1#post109415)

Владимир Ситников
10.10.2017, 17:58
нет у меня сейчас ни чего с асинхронной бибкой, но было бы что то подобное этому (http://www.owen.ru/forum/showthread.php?t=14918&p=109415&viewfull=1#post109415)
Тот код выглядит странным, ведь там на каждом цикле ПЛК файл открывается, читается и закрывается.

Если же SysFileRead будет читать по 1 байту (имеет право), то ваш код вообще никогда не сможет файл прочитать.

И, всё-таки, оно будет не "подобное", а с кучей IF/CASE. Как-никак, если код сначала M циклов тратит на открытие файла, потом N циклов на запись, потом K циклов на закрытие, то где-то нужно хранить "что же мы сейчас должны делать" и нужно правильно учитывать "а что делать дальше" (например, чтобы не забыть закрыть)

Поэтому я и говорю: "обычный рукописный код" будет сложнее чем 1 IF, который вы показываете.

capzap
10.10.2017, 18:02
Тот код выглядит странным, ведь там на каждом цикле ПЛК файл открывается, читается и закрывается.

Если же SysFileRead будет читать по 1 байту (имеет право), то ваш код вообще никогда не сможет файл прочитать.

И, всё-таки, оно будет не "подобное", а с кучей IF/CASE. Как-никак, если код сначала M циклов тратит на открытие файла, потом N циклов на запись, потом K циклов на закрытие, то где-то нужно хранить "что же мы сейчас должны делать" и нужно правильно учитывать "а что делать дальше" (например, чтобы не забыть закрыть)

Поэтому я и говорю: "обычный рукописный код" будет сложнее чем 1 IF, который вы показываете.

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

ЗЫ не приходило в голову что сишный код в библиотеке сам решает первый это запрос на открытие или ожидание ответа

Владимир Ситников
10.10.2017, 18:14
это кусок рабочей программы с производства, показывающий как я читаю файлы, ни чего больше к нему я дописывать не должен, максимум код может повториться один для настроек программы, другой для рецептуры

Ещё раз: то, что код работает на конкретном ПЛК (на конкретной прошивке) и на конкретном размере файла не означает, что он будет продолжать работать и дальше.

Вы где-нибудь видели гарантии, что SysFileRead читает всегда ровно столько байт, сколько её просили?

Из документации: "The return value is the number of successfully read bytes"
Она запросто может прочитать 1 байт (или 1024 байта или вообще сколько там КДС/ОВЕН задумали) и всего делов.
А у вас потом недорецептура получится. Из всей структуры заполнится только 1 байт, а вы посмотрели, что <>0 и пошли пользоваться такой палёной рецептурой. Счастливой отладки!

Вот и получается, что, возможно, тот ваш код работает только на конкретной прошивке и конкретном размере исходного файла.

Знаете же, что есть слово POSIX и там read не обязано прочитать всё до конца.

Владимир Ситников
10.10.2017, 18:15
ЗЫ не приходило в голову что сишный код в библиотеке сам решает первый это запрос на открытие или ожидание ответа
Ничего он не решает. В вашем коде всегда жёсткая последовательность открыть-прочитать-закрыть и всё всегда в одном цикле ПЛК.

capzap
10.10.2017, 18:22
Ничего он не решает. В вашем коде всегда жёсткая последовательность открыть-прочитать-закрыть и всё всегда в одном цикле ПЛК.

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

capzap
10.10.2017, 18:25
Ещё раз: то, что код работает на конкретном ПЛК (на конкретной прошивке) и на конкретном размере файла не означает, что он будет продолжать работать и дальше.

Вы где-нибудь видели гарантии, что SysFileRead читает всегда ровно столько байт, сколько её просили?

Из документации: "The return value is the number of successfully read bytes"
Она запросто может прочитать 1 байт (или 1024 байта или вообще сколько там КДС/ОВЕН задумали) и всего делов.
А у вас потом недорецептура получится. Из всей структуры заполнится только 1 байт, а вы посмотрели, что <>0 и пошли пользоваться такой палёной рецептурой. Счастливой отладки!

Вот и получается, что, возможно, тот ваш код работает только на конкретной прошивке и конкретном размере исходного файла.

Знаете же, что есть слово POSIX и там read не обязано прочитать всё до конца.

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

Владимир Ситников
10.10.2017, 18:27
дескриптор файла я откуда беру, через указатель из функции, следовательно поймать фронт изменения больше нуля не составляет проблем, а дальше если он не равен нулю ОС может не открывать повторно файл, ей ли не знать что она уже его открыла

Вы о чём вообще?
Цитирую ваш код:


1 IF loadReception THEN (* при загрузке плк *)
2 hFile:=SysFileOpen('rcpt.bin', 'r'); (* получение дескриптора файла *)
3 IF hFile>0 THEN (* если файл существует *)
4 logFileSize := SysFileGetSize('rcpt.bin'); (* вычисляем размер файла *)
5 (* читаем данные по минимуму, либо размер файла, либо размер массива *)
6 IF SysFileRead(hFile, ADR(reception)
7 ,MIN(UDINT_TO_DWORD(logFileSize)
8 ,INT_TO_DWORD(SIZEOF(reception)))) <> 0 THEN (* если приняли соответствующее количество байт *)
9 SysFileClose(hFile); (* закрываем файл *)
10 loadReception:=FALSE;
11 ELSE SysFileClose(hFile);loadReception:=FALSE; END_IF;
12 ELSE loadReception:=FALSE; END_IF;
END_IF;


Выполняться он будет так:
1) loadReception=TRUE (иначе вообще в IF не зайдём)
2) открываем файл. Считаем, что файл открылся, т.к. иначе неинтересно
3) файл открылся
4) берём filesize
6) пытаемся прочитать файл. <-- Вы признаёте, что эта функция может прочитать только 1 байт и вернуть 1?

Дальше 2 варианта:
а) Заходим в IF и на строке 9 закрываем файл <-- здесь может оказаться, что мы прочитали только 1 байт, а файл уже закрыли и флаг loadReception сбросили в FALSE
б) заходим в ELSE и на строке 11 закрываем файл <-- это ошибка чтения, поэтому не так интересно. Но файл всё равно закрывается

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

Просто согласитесь, что у вас там косяк получился.

Владимир Ситников
10.10.2017, 18:31
ну мы же условились не обсуждать оптимизацию кода :), вызов библиотечных функций от этого не измениться, за исключением SysFileRead которая пару циклов дольше прочтется, если что

Оптимизацию -- пофиг.
В вашем коде алгоритимически-логическая ошибка.

Код завязан на то, что SysFileRead всегда сможет прочитать весь файл целиком.
Вы файл закрываете на строках 9 или 11.
А на строках 10 или 11 вообще ставите loadReception:=FALSE, т.е. вся эта операция по загрузке рецепта максимум 1 раз будет.
Никаких "постепенных дочитываний" код не предполагает

capzap
10.10.2017, 18:38
Вы о чём вообще?
Цитирую ваш код:


1 IF loadReception THEN (* при загрузке плк *)
2 hFile:=SysFileOpen('rcpt.bin', 'r'); (* получение дескриптора файла *)
3 IF hFile>0 THEN (* если файл существует *)
4 logFileSize := SysFileGetSize('rcpt.bin'); (* вычисляем размер файла *)
5 (* читаем данные по минимуму, либо размер файла, либо размер массива *)
6 IF SysFileRead(hFile, ADR(reception)
7 ,MIN(UDINT_TO_DWORD(logFileSize)
8 ,INT_TO_DWORD(SIZEOF(reception)))) <> 0 THEN (* если приняли соответствующее количество байт *)
9 SysFileClose(hFile); (* закрываем файл *)
10 loadReception:=FALSE;
11 ELSE SysFileClose(hFile);loadReception:=FALSE; END_IF;
12 ELSE loadReception:=FALSE; END_IF;
END_IF;


Выполняться он будет так:
1) loadReception=TRUE (иначе вообще в IF не зайдём)
2) открываем файл. Считаем, что файл открылся, т.к. иначе неинтересно
3) файл открылся
4) берём filesize
6) пытаемся прочитать файл. <-- Вы признаёте, что эта функция может прочитать только 1 байт и вернуть 1?

Дальше 2 варианта:
а) Заходим в IF и на строке 9 закрываем файл
б) заходим в ELSE и на строке 11 закрываем файл

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

Просто согласитесь, что у вас там косяк получился.
С указателями напутал, это из асинхронной библиотеки, здесь сама функция возвращает ссылку на файл.
6) может прочитать и меньше, не зря я вычисляю logFileSize , но видимо не пригодился и так работает на плк, не подводил. Даже если будем делать проверку, чем это будет отличаться от Вашего кода, там её тоже нет, поэтому количество строк возрастет одинаково
вместе с закрытием файла я сбрасываю loadReception до следующего открытия файла, поэтому многократные открытия не предвидятся

capzap
10.10.2017, 18:45
Никаких "постепенных дочитываний" код не предполагает
будете соглашаться, что некорректность этого кода проявилась бы в первый же раз после перезагрузки плк заказчиком и я бы уже исправил ошибку за свой счет по гарантии и не выкладывал бы код заведомо не рабочий?
Если есть желание поставить кучу проверок чтоб себя обезопасить, Ваше право

Владимир Ситников
10.10.2017, 18:47
6) может прочитать и меньше, не зря я вычисляю logFileSize , но видимо не пригодился и так работает на плк, не подводил.
Об этом я и говорю:
а) может оказаться, что на другой прошивке SysFileRead прочитает не весь файл, а частично
б) может оказаться, что у SysFileRead есть какой-то внутренний предел (например, не более 8 килобайт за раз). Тогда код прочитает только часть файла. И начнёт работать с неправильной рецептурой.

Да, сейчас вам везёт, что "всё читается за 1 цикл ПЛК и целиком". Хотите -- продолжайте верить в то, что так всегда будет и дальше.
Вы же именно в такой ошибке упрекали мой пример, что, мол "мне везёт, что за 1 цикл всё читается".

А по факту, получается наоборот: мой пример защищён от такого поведения, а ваш код подвержен такой проблеме.



Даже если будем делать проверку, чем это будет отличаться от Вашего кода, там её тоже нет, поэтому количество строк возрастет одинаково
вместе с закрытием файла я сбрасываю loadReception до следующего открытия файла, поэтому многократные открытия не предвидятся
Будет. Будет.
Если вы сделаете "поддержку дочитывания" (ну, повторный вызов read, если на прошлом цикле недочиталось), то у вас появится переменная "что сейчас делаем" (начинаем, дочитываем или подобная). Как раз из-за неё код и усложнится.

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

Владимир Ситников
10.10.2017, 18:50
будете соглашаться, что некорректность этого кода проявилась бы в первый же раз после перезагрузки плк заказчиком и я бы уже исправил ошибку за свой счет по гарантии и не выкладывал бы код заведомо не рабочий?

Нет. С этим я не соглашусь.
Есть POSIX, и там говорится, что read может прочитать меньше, чем её просили.
Почему именно она прочитает меньше -- да фиг знает. Может, она посмотрит, что "цикл ПЛК подходит к концу, и давайте прочитаем поменьше".

Есть ли там гарантия, что "read всегда будет работать одинаково" (например, одинаково падать)? Тоже нет.

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


Если есть желание поставить кучу проверок чтоб себя обезопасить, Ваше право
Я говорю не о случае "в ПЛК прилетит нейтрино и он перейдёт в состояние СТОП", а о вполне понятном моменте, что read *не обязано* прочитать всё и сразу.

capzap
10.10.2017, 18:56
Есть POSIXпускай он есть, как он относится ко мне, между мной и им есть еще приложение в виде набора билиотек syslibfile, мне оно возвращает готовый результат, ни где не сказано что я общаюсь на прямую с ОС

Владимир Ситников
10.10.2017, 18:58
пускай он есть, как он относится ко мне, между мной и им есть еще приложение в виде набора билиотек syslibfile, мне оно возвращает готовый результат, ни где не сказано что я общаюсь на прямую с ОС

А где-то сказано, что SysFileRead всегда читает целиком?
Я найти не смог. Максимум нашёл то, что "SysFileRead возвращает количество прочитанных байт". Это намекает на то, что количество прочитанных байт может отличаться от того количества, которое "просили прочитать".

capzap
10.10.2017, 19:03
диалог опять превратился в то как вы "давите своим интеллектом", обсуждение нужности пауз превратилось в обсуждение что я выложил неправильный код. Напоминаю, что в выложенном Вами коде нет намека на дочитывание файла, не хотите принять что мой код проще Ваших замудренностей, да наздоровье

Владимир Ситников
10.10.2017, 19:08
Напоминаю, что в выложенном Вами коде нет намека на дочитывание файла, не хотите принять что мой код проще Ваших замудренностей, да наздоровье
Если вы не увидили "дочитывание" в моём примере, что ж, жаль.

Полный пример был в 6-ом сообщении (http://www.owen.ru/forum/showthread.php?t=27498&p=259860&viewfull=1#post259860)
И смысл 6-го сообщения в том, что REPEAT там как раз и используются для "дооткрывания", "дочитывания", "дозаписывания". Во 2-ом коде 6-го сообщения эти самые REPEAT фигурируют в коде явно. В последнем же коде 6-го сообщения эти "REPEAT" просто скрыты внутри вспомогательных функций OwenFileOpenAsync2.

Ну а в сообщении 14 (http://www.owen.ru/forum/showthread.php?t=27498&p=259887&viewfull=1#post259887) я показал, что эти самые "REPEAT" не будут приводить к собакам, а раскроются компилятором в нехитрые CASE, и по факту за каждый ПЛК цикл будет очередное продвижение. Либо "дооткрывание", либо "дочитывание" и так далее.

Да, в моём примере наверняка неправильно рассматриваются коды возвратов, но именно на механизм "дочитывания" они не влияют.

Владимир Ситников
10.10.2017, 19:15
не хотите принять что мой код проще Ваших замудренностей, да наздоровье

У вас код работает *только* для случая, когда всё чтение сработает успешно и за 1 цикл ПЛК.
Разумеется, для такого конкретного случая ваш код проще (вернее, он визуально ничем от моего не отличается, но не суть). Да и вообще сложно придумать что-то проще 3х последовательных вызовов функции.

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

Сейчас получается интересно: я показываю своё сравнение (как обычный ST так и ST-с-паузами, "обычный код" длиннее в 6 раз, а поведение при этом абсолютно идентично), вы говорите "да это всё ненужно, обычный проще", но при этом показываете код, решающий гораздо более простую задачу.

Владимир Ситников
10.10.2017, 23:38
Просто эквивалент tthread'а с блокирующими операциями. Но к ним тогда нужны человечьи события, а их тут нету.

Снова мимо кассы?

"события", "state machine" это, да, может требоваться одновременно с асинхронным выполнением, но это не исключающие, а дополняющие друг друга штуки.

Пример:
"работа с файлом" -- это просто асинхронный код (возможно, с небольшим ветвлением, но почти прямолинейно)
На примере с файлом видно, что "никакие события" там не нужны. Просто берём и используем.

"обработка сетевого протокола" -- тут, возможно, поинтереснее. И, вполне возможно, что тут нужна комбинация автомата (ну, обычного) и асинхронщины.

Как вариант, можно сделать "полноценную библиотеку modbus" и посмотреть потребуются ли там "события" или нет.

Владимир Ситников
11.10.2017, 00:04
Где ? Вы это и пытаетесь с самого начала предложить. Пост #35 - просто классический вариант из статей на тему "а потоке это все проще .."

Тут я вас поправлю: пост 35 называется "а в императивном духе гораздо проще".
К потокам он не имеет отношения, т.к. я с самого начала заявлял, что код не будет "блокироваться".

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

Я же говорю про async/await/coroutines/fibers -- когда "сама работа делится на небольшие кусочки и выполняется понемногу".
Смысл не в том, чтобы "с помощью какой-то матери создать поток в КДС", а в том, чтобы "составлять программу в обычном императивном стиле, а компилятор при этом подменял код на автомат, прерывающийся и продолжающий работать оттуда, где закончил".

Да, грубо (ну, очень грубо) можно это назвать потоками, но это называется fiber (https://en.wikipedia.org/wiki/Fiber_(computer_science)), coroutine (https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D 0%BC%D0%BC%D0%B0) и т.п.

Владимир Ситников
11.10.2017, 00:10
Так у вас же неявный автомат. И как другие автоматы без событий поймут что этот - готов/не готов ?

1) Разумеется, "просто 1 раз вызвать автомат недостаточно". Нужно сначала его сбросить, потом вызывать (пока он не закончится). Поэтому, да, у "сгенерированного автомата" должен быть какой-то признак, что он "закончился".
2) Но тут стоит понимать, что и сам автомат и вызывающий код генерирует один и тот же компилятор. Он-то может сам с собой договориться, как он будет определять, что "вызываемый автомат закончился"? Например, использовать state=-1 как признак "самого финального" состояния. Или просто var_output done:bool. Мало ли способов?

А, если автомат вызывается "из обычного кода", то он ничем не отличается от других "асинхронных" ФБ, у которых признак "done:bool" появляется за несколько циклов.

Владимир Ситников
11.10.2017, 00:17
Так у вас же неявный автомат. И как другие автоматы без событий поймут что этот - готов/не готов ?

Ну и ещё: "послать событие" можно через установку input переменной. Например, "cancel=TRUE". И потом ждать, пока он "закончится".
И "асинхронный ФБ" будет проверять в нужных местах этот cancel флаг и делать что нужно. Например, если успел открыть файл, то переходить к закрытию и т.п.

Вполне норм, нет?

Владимир Ситников
11.10.2017, 00:26
Будет. Конкретно эта ветка "зависла в шаге". Ессно не собака.
Ну, да, где обычный код будет уходить в собаку асинхронный будет "зависать на шаге".
По-моему, второе гораздо лучше, т.к. вручную вызвать собаку всегда можно, а вот понять "а где же код зациклился" после собаки уже никак.


Когда бабка продает семечки - это тоже можно назвать
Ась? Как раз coroutine (https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D 0%BC%D0%BC%D0%B0) это и есть одно слово и ровно обозначающее то, о чём я говорю.


Сопрограмма (англ. coroutine) — компонент программы, обобщающий понятие подпрограммы, который дополнительно поддерживает множество входных точек (а не одну, как подпрограмма), остановку и продолжение выполнения с сохранением определённого положения.

Семечки уж точно мимо кассы.

Владимир Ситников
11.10.2017, 00:41
Вы ж предлагает часть работы автоматизировать : блокирующая нитка => неявный кейс.
Ну так я и спрашиваю - когда дойдем до события => поллинг флажков ?
Вопрос хороший, но тут же мозги нужны.
coroutine легко подсмотреть (и ничего кроме спинного мозга для этого не нужно), например, у Kotlin: https://kotlinlang.org/docs/reference/coroutines.html

И тут же оказывается, что в Kotlin'е не предлагается никаких "автоматизированных" механизмов для отправки событий-флажков.


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


FB modbus_mddv_16r(..., cancel: bool)
...
выполнить_полностью lib_modbus_send(cancel := cancel); (* этот самый cancel может пробасываться *)


Но нужно смотреть зачем вообще эти флажки могут быть нужны.

ВладОвен
27.05.2022, 11:28
Владимир Ситников (https://owen.ru/forum/member.php?u=75340), есть какие-либо продвижения в этой теме?
Получилось ли реализовать компилятор, превращающий код на паузах в автоCASE?

IVM
27.05.2022, 11:44
Владимир Ситников уже давно не появляется на форуме. Он занят куда более важными делами.

ВладОвен
27.05.2022, 11:58
Какими? (стало даже интересно...)

IVM
27.05.2022, 12:15
Какими? (стало даже интересно...)

Владимир Ситников - профессиональный программист. Для него форум ОВЕН - это легкое развлечение.