Состояния, события и машина состояний на ПЛК. Основа.

Машина состояний — один из моих любимых вопросов, когда я собеседую кандидата. Из интересных наблюдений, что даже если кандидат не знает определения, то скорее всего он уже пару разе ее реализовывал.

В 2021 году я уже выпускал статью про машину состояний Конечный автомат в АСУТП для ПЛК. Избавляемся от множества IF-ов и CASE-ов в Codesys 3.5, но это было давно, и там было очень много абстракций, большой сегмент инициализации данных, да и сейчас я назову этот подход далеким от реальности. Сейчас я хочу разобраться в машине состояние получше, понять как можно построить событийно-ориентированные системы и насколько они могут быть большими.

Здесь будет ST, Codesys и никакого ООП.

Глоссарий

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

Машина состояний(Конечный автомат) — это математическая модель последовательности событий и действий, описывающих определенный (логический) процесс.

Сама машина состояний состоит из трех частей.

Состояния — это разделенный на различные элементы, наборы условий, в которых может существовать система.

Для нас состояния — это различные режимы работы или этапы технологического процесса. Состояния всегда детерминированы.

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

Здесь можно добавить, что события бывают внешними, которые приходят к нам и внутренние, которые порождаются самой системой. Внешние сигналы-это все что может прийти с панели оператора, какие-то сработки датчиков или выходы других ФБ, внутренние события — это внутренние таймеры, какие-то внутренние условия или проверки, все то, что нам необходимо для функционирования внутри системы.

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

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

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

Также я не буду сильно уходить в математику и абстрактные математические вычисления, так что про Автомат Мили, если будет желание, можно прочитать на википедии.

Также я опущу про концепции исторических состояний или составную иерархию машины состояний, сосредоточившись на простых примерах.

Варианты использования

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

Изменение поведения

Машину состояний можно применять для изменения поведения функционального блока в зависимости от его состояния. Один из таких примеров — это одна кнопка на «Старт/Стоп»

Сама машина будет выглядеть следующим образом:

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

FUNCTION_BLOCK fbSimpleState
VAR_INPUT
	xButton:BOOL;
END_VAR
VAR_OUTPUT
	iOUT: INT;
END_VAR
VAR
	State: enSimpleState;
	EventDetection: R_TRIG;
END_VAR

EventDetection(CLK:=xButton); //Детекция события
CASE State OF
	START: // Состояние
		IF EventDetection.Q THEN //Событие
			(*Переход*)
			iOUT:=1;
			State:= STOP;
		END_IF
	STOP:
		IF EventDetection.Q THEN
			iOUT:=2;
			State:= START;
		END_IF			
END_CASE

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

Стандартное управление

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

Управление клапаном можно уместить в такую машину.

Рассматриваемый клапан является упрощенным и тут не будут рассматриваться все граничные условия его работы. Важен сам принцип построения машины состояний и переходов.

При старте ПЛК наш ФБ будет в состояние IDLE, которое можно интерпретировать как ожидание, но оно помимо ожидания делает проверку состояния клапана. Если приходит один из концевиков, то система переходит в следующее состояние, если же система не получает сигнал от какого-нибудь концевика, то в состоянии IDLE система отрабатывает как команды открытия, так и команды закрытия.

Если пришла команда открытия OPEN COMMAND, то система перейдет в состояние OPENING — открывается, где будет ожидать или сигнала от концевика, или срабатывание аварийного таймера.

Если концевик пришел раньше таймера, то система переходит в состояние OPENED. Единственным способом перейти в другое состояние — подать CLOSE COMMAND.

Переход в состояние CLOSING -> CLOSED идентичен OPENING -> OPENED.

Если во время любого перехода концевик не придет за указанное время, то система переходит в состояние ALARM, переход из которой в состояние IDLE идет только через команду оператора RESET.

FUNCTION_BLOCK fbSimpleStateMachine
VAR_INPUT
	OperationCommand: enSimpleStateMachineEvents;
	Valve: enValveStatus;
END_VAR
VAR_OUTPUT
	xAlarm: BOOL;
END_VAR
VAR
	tWait: TON:=(PT:=T#5S);
	State : enSimpleStateMachineState;
END_VAR

tWait();
xAlarm:=FALSE;
CASE State OF
	IDLE:
		IF Valve = VALVE_OPENED THEN
			State:=OPENED;
			RETURN;
		END_IF
		IF Valve = VALVE_CLOSED THEN
			State:=CLOSED;
			RETURN;
		END_IF
		IF OperationCommand = OPEN_COMMAND THEN
			tWait.IN:=TRUE;
			State:=OPENING;
			RETURN;
		END_IF
		IF OperationCommand = CLOSE_COMMAND THEN
			tWait.IN:=TRUE;
			State:=CLOSING;
			RETURN;
		END_IF
	OPENING:
		IF VALVE=VALVE_OPENED THEN
			tWait.IN:=FALSE;
			State:=OPENED;
			RETURN;
		END_IF
		IF tWait.Q THEN
			State:=ALARM;
			RETURN;
		END_IF
	OPENED:
		IF OperationCommand=CLOSE_COMMAND THEN
			tWait.IN:=TRUE;
			State:=CLOSING;
		END_IF
	CLOSING:
		IF VALVE=VALVE_CLOSED THEN
			tWait.IN:=FALSE;
			State:=CLOSED;
			RETURN;
		END_IF
		IF tWait.Q THEN
			State:=ALARM;
			RETURN;
		END_IF
	CLOSED:
		IF OperationCommand=OPEN_COMMAND THEN
			State:=OPENING;
		END_IF
	ALARM:
		xAlarm:=TRUE;
		IF OperationCommand = RESET_COMMAND THEN
			tWait.IN:=FALSE;
			State:=IDLE;
		END_IF
END_CASE

Управление с обратной связью

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

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

Команда старт процесса и смена — это команды, которые происходят путем обмена данными с устройством.

Пример такого обмена

FMS — это рассматриваемая машина состояний, Driver — это еще один ФБ, который принадлежит к другому логическому слою ППО и само устройство.

FMS отправляет событие Send Command на драйвер устройства. И в этот момент внутренним событием переходит в состояние ожидание ответа от драйвера. Драйвер отправляет команду на устройство, получает ответ и, обработав его, возвращает результат в виде какого-то события FMS.

Если событие APPLY, то мы можем применить все действия для перехода, если ERROR, то срабатывает ветка обработки ошибки.

Начинается все также с IDLE STATE. Событием перехода является Timer, но запускается он с команды оператора.

Cостояния START PROCESS и CHANGE, которые инициируют отправку команды на устройство совершают переход в состояние APPLY.

Состояние APPLY, в случае события ERROR, совершает переход в прошлое состояние, чтобы повторить отправку команды, если спустя N попыток события Apply не было, то система считает, что у нее необъявленное поведение(UB) и переходит в аварийное состояние EMERGENCY, где будет ожидать сброса от оператора.

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

Мной сознательно было принято решение организовать обработку ошибки таким образом

FUNCTION_BLOCK fbFMSFeedback
VAR_IN_OUT
	xApply: BOOL;
	xError: BOOL;
	xStart: BOOL;
	xReset: BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
	NextState: 	enFMSFeedbackState;
	LastState: enFMSFeedbackState;
	State : 	enFMSFeedbackState;
	TryCount: UINT;
	tWait: TON;
	xContinue: BOOL:=TRUE;
END_VAR
VAR CONSTANT
	uiTryCountLimit:UINT:=3;
END_VAR

tWait(IN:=xStart, PT:=T#10S);
CASE State OF
	IDLE_STATE:
		IF tWait.Q THEN
			State:=START_PROCESS;
			xStart:=FALSE;
		END_IF
	START_PROCESS:
		(*Событие на старт на старт*)
		LastState:=State;
		State:=APPLY;
		NextState:=END_PROCESS;
	END_PROCESS:
		(*Обработка сигнала о завершении процесса*)
		State:=CHECK;
	CHECK:
		(*Проверка на продолжение*)
		IF xContinue THEN
			State:=CHANGE;
		ELSE
			State:=IDLE_STATE;
		END_IF
	CHANGE:
		(*Событие на смену*)
		LastState:=State;
		State:=APPLY;
		NextState:=START_PROCESS;
	APPLY:
		IF xApply THEN
			TryCount:=0;
			xApply:=FALSE;
			State:=NextState;
		END_IF
		IF xError THEN
			 CASE TryCount OF
				 3: 
					State:=EMERGENCY;
			 	ELSE
					xError:=FALSE;
					State:=LastState;
				  	TryCount:=TryCount+1;
			 END_CASE
		END_IF
	EMERGENCY:
		IF xReset THEN
			xError:=FALSE;
			xReset:=FALSE;
			TryCount:=0;
			State:=IDLE;
		END_IF
END_CASE;

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

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

Итог

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

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

Почта для ваших предложений и сотрудничества: info@engcore.ru

Мой ТГ канал: «Я вам че — Автоматизатор?»

Ответить