Сложные переходы состояний: лучшие практики

Я работаю со встроенными компонентами, и у меня есть программный модуль, который управляет оборудованием. Этот модуль имеет состояние, и переходы состояния сложны: в зависимости от событий модуль может выходить из состояния 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;
}
Другие вопросы по тегам