Сложные переходы состояний: лучшие практики
Я работаю со встроенными компонентами, и у меня есть программный модуль, который управляет оборудованием. Этот модуль имеет состояние, и переходы состояния сложны: в зависимости от событий модуль может выходить из состояния A
заявить B
или возможно C
, Но когда он выходит из какого-то состояния, он должен выполнить некоторые действия с аппаратным обеспечением, чтобы поддерживать его также в правильном состоянии.
Для довольно простых модулей у меня есть пара таких функций:
enum state_e {
MY_STATE__A,
MY_STATE__B,
};
static enum state_e _cur_state;
void state_on_off(enum state_e state, bool on)
{
switch (state){
case MY_STATE__A:
if (on){
//-- entering the state A
prepare_hardware_1(for_state_a);
prepare_hardware_2(for_state_a);
} else {
//-- exiting the state A
finalize_hardware_2(for_state_a);
finalize_hardware_1(for_state_a);
}
break;
case MY_STATE__B:
if (on){
//-- entering the state B
prepare_hardware_1(for_state_b);
prepare_hardware_2(for_state_b);
} else {
//-- exiting the state B
finalize_hardware_2(for_state_b);
finalize_hardware_1(for_state_b);
}
break;
}
}
void state_set(enum state_e new_state)
{
state_on_off(_cur_state, false);
_cur_state = new_state;
state_on_off(_cur_state, true);
}
Очевидно, что нам нужно сохранить все необходимые действия для всех государств в _state_on_off()
функция, и когда нам нужно перейти в другое состояние, мы просто вызываем _state_set(new_state)
и переход состояния происходит плавно независимо от направления: все необходимые действия выполняются.
Но это работает только для простых ситуаций. Что если у нас есть что-то общее между государствами MY_STATE__B
а также MY_STATE__C
, так что когда состояние меняется с MY_STATE__B
в MY_STATE__C
и обратно мы должны выполнять только укороченное удаление / построение? Но когда мы идем в какое-то другое состояние (скажем, MY_STATE__A
), мы должны выполнить полное уничтожение.
То, что приходит на ум, это подсостояния. Итак, у нас одно государство MY_STATE__BC
и подсостояния как MY_BC_SUBSTATE__B
а также MY_BC_SUBSTATE__C
; и, конечно, у нас есть своя функция, как _state_bc_on_off()
, Даже это уже боль, но представьте себе что-то более сложное: это идет ужасно.
Итак, каковы лучшие практики для таких вещей?
1 ответ
Несколько более общий конечный автомат имеет
- примитивы - подпрограммы, которые выполняют определенное действие на определенном оборудовании
- последовательности - один или несколько примитивов, вызываемых в определенном порядке
- переходы - одна или несколько последовательностей, выполненных в определенном порядке
Переходы кодируются в виде массива структур. Последовательности выбираются оператором switch, и каждая последовательность вызывает один или несколько примитивов.
#define stA 0x00000001 // bit mask for state A
#define stB 0x00000002 // bit mask for state B
#define stC 0x00000004 // bit mask for state C
#define stAny 0xffffffff // matches any state
enum { seqXtoY, seqError, seqEnterA, seqExitA, seqEnterB, seqExitB, seqEnableC, seqDisableC, seqEnd };
typedef struct
{
int oldState; // bit mask that represents one or more states that we're transitioning from
int newState; // bit mask that represents one or more states that we're transitioning to
int seqList[10]; // an array of sequences that need to be executed
}
stTransition;
static stTransition transition[] =
{
// transitions from state A to B or C
{ stA, stB, { seqExitA, seqXtoY, seqEnterB, seqEnd } },
{ stA, stC, { seqExitA, seqXtoY, seqEnableC, seqEnterB, seqEnd } },
// transitions from state B to A or C
{ stB, stA, { seqExitB, seqXtoY, seqEnterA, seqEnd } },
{ stB, stC, { seqXtoY, seqEnableC, seqEnd } },
// transitions from states C to A or B
{ stC, stA, { seqDisableC, seqExitB, seqXtoY, seqEnterA, seqEnd } },
{ stC, stB, { seqDisableC, seqXtoY, seqEnd } },
// any other transition (should never get here)
{ stAny, stAny, { seqError, seqEnd } }
};
static int currentState = stA;
void executeSequence( int sequence )
{
switch ( sequence )
{
case seqEnterA:
prepare_hardware_1(for_state_a);
prepare_hardware_2(for_state_a);
break;
case seqExitA:
finalize_hardware_2(for_state_a);
finalize_hardware_1(for_state_a);
break;
case seqEnterB:
prepare_hardware_1(for_state_b);
prepare_hardware_2(for_state_b);
break;
case seqExitB:
finalize_hardware_2(for_state_b);
finalize_hardware_1(for_state_b);
break;
case seqEnableC:
enable_hardware_3();
break;
case seqDisableC:
disable_hardware_3();
break;
}
}
void executeTransition( int newState )
{
if ( newState == currentState )
return;
// search the transition table to find the entry that matches the old and new state
stTransition *tptr;
for ( tptr = transition; tptr->seqList[0] != seqError; tptr++ )
if ( (tptr->oldState & currentState) && (tptr->newState & newState) )
break;
// execute the sequence list
int *seqptr;
for ( seqptr = tptr->seqList; *seqptr != seqEnd; seqptr++ )
{
if ( *seqptr == seqXtoY )
currentState = newState;
else if ( *seqptr == seqError )
printf( "The state table is missing the transition from %d to %d\n", currentState, newState );
else
executeSequence( *seqptr );
}
// if the seqList doesn't have an explicit update, then we update at the end
currentState = newState;
}