Czy istnieje typowy wzorzec implementacji maszyny stanów?

118

Musimy wdrożyć prostą maszynę stanów w C .
Czy standardowe oświadczenie dotyczące przełącznika jest najlepszym rozwiązaniem?
Mamy aktualny stan (stan) i wyzwalacz przejścia.


switch(state)
{
  case STATE_1:
     state = DoState1(transition);
     break;
  case STATE_2:
     state = DoState2(transition);
     break;
}
...
DoState2(int transition)
{
   // Do State Work
   ...
   if(transition == FROM_STATE_2) {
     // New state when doing STATE 2 -> STATE 2
   }
   if(transition == FROM_STATE_1) {
    // New State when moving STATE 1 -> STATE 2
   }
   return new_state;
}

Czy istnieje lepszy sposób na proste maszyny stanowe

EDYCJA: W przypadku C ++ myślę, że biblioteka Boost Statechart może być drogą do zrobienia. Jednak to nie pomaga w przypadku C. Skoncentrujmy się na przypadku użycia C.

Benoit
źródło

Odpowiedzi:

134

Wolę używać podejścia opartego na tabeli dla większości maszyn stanowych:

typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t;
typedef struct instance_data instance_data_t;
typedef state_t state_func_t( instance_data_t *data );

state_t do_state_initial( instance_data_t *data );
state_t do_state_foo( instance_data_t *data );
state_t do_state_bar( instance_data_t *data );

state_func_t* const state_table[ NUM_STATES ] = {
    do_state_initial, do_state_foo, do_state_bar
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    return state_table[ cur_state ]( data );
};

int main( void ) {
    state_t cur_state = STATE_INITIAL;
    instance_data_t data;

    while ( 1 ) {
        cur_state = run_state( cur_state, &data );

        // do other program logic, run other state machines, etc
    }
}

Można to oczywiście rozszerzyć, aby obsługiwać wiele automatów stanowych itp. Można również uwzględnić działania przejścia:

typedef void transition_func_t( instance_data_t *data );

void do_initial_to_foo( instance_data_t *data );
void do_foo_to_bar( instance_data_t *data );
void do_bar_to_initial( instance_data_t *data );
void do_bar_to_foo( instance_data_t *data );
void do_bar_to_bar( instance_data_t *data );

transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = {
    { NULL,              do_initial_to_foo, NULL },
    { NULL,              NULL,              do_foo_to_bar },
    { do_bar_to_initial, do_bar_to_foo,     do_bar_to_bar }
};

state_t run_state( state_t cur_state, instance_data_t *data ) {
    state_t new_state = state_table[ cur_state ]( data );
    transition_func_t *transition =
               transition_table[ cur_state ][ new_state ];

    if ( transition ) {
        transition( data );
    }

    return new_state;
};

Podejście oparte na tabelach jest łatwiejsze do utrzymania i rozszerzania oraz prostsze do odwzorowania na diagramach stanu.

Frank Szczerba
źródło
Bardzo fajny sposób na rozpoczęcie, przynajmniej dla mnie punkt wyjścia. Jedna uwaga, pierwsza linia run_state () zawiera niegrzeczny znak „.” to nie powinno tam być.
Atilla Filiz
2
Byłoby lepiej, gdyby ta odpowiedź zawierała również co najmniej 2 słowa o pozostałych dwóch podejściach: metodzie „globalnej” z dużym przypadkiem przełączania oraz oddzielaniu stanów za pomocą wzorca projektowania stanów i pozwalaniu każdemu ze stanów samodzielnie obsługiwać swoje przejścia.
erikbwork,
Witam, wiem, że ten post jest stary, ale mam nadzieję, że dostanę odpowiedź :) Co na pewno warto umieścić w zmiennej instance_data_t? Zastanawiam się, jak zmieniać stany w przerwaniach ... czy to dobry sposób na przechowywanie informacji o przetworzonych przerwaniach w tej zmiennej? Na przykład zapisz informację, że przycisk został naciśnięty, aby zmienić stan.
grongor,
@GRoNGoR Wydaje mi się, że masz do czynienia z maszyną stanu sterowaną zdarzeniami. Myślę, że rzeczywiście można by go użyć do przechowywania danych o wydarzeniach.
Zimano
3
Naprawdę miły akcent, jak zdefiniowano NUM_STATES.
Albin Stigo
25

Być może widziałeś moją odpowiedź na inne pytanie w C, w którym wspomniałem o FSM! Oto jak to robię:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

Ze zdefiniowanymi następującymi makrami

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

Można to zmodyfikować w celu dostosowania do konkretnego przypadku. Na przykład, możesz mieć plik FSMFILE, którym chcesz sterować FSM, więc możesz włączyć akcję odczytu następnego znaku do samego makra:

#define FSM
#define STATE(x)         s_##x : FSMCHR = fgetc(FSMFILE); sn_##x :
#define NEXTSTATE(x)     goto s_##x
#define NEXTSTATE_NR(x)  goto sn_##x

teraz masz dwa rodzaje przejść: jeden przechodzi do stanu i odczytuje nowy znak, a drugi przechodzi do stanu bez zużywania jakichkolwiek danych wejściowych.

Możesz także zautomatyzować obsługę EOF za pomocą czegoś takiego:

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

Zaletą tego podejścia jest to, że możesz bezpośrednio przetłumaczyć diagram stanu, który narysujesz, na działający kod i odwrotnie, możesz łatwo narysować diagram stanu z kodu.

W innych technikach implementacji FSM struktura przejść jest ukryta w strukturach kontrolnych (while, if, switch ...) i kontrolowana przez wartość zmiennych (zazwyczaj statezmienna), a powiązanie ładnego diagramu z a może być złożonym zadaniem. zawiły kod.

Nauczyłem się tej techniki z artykułu, który ukazał się w wielkim magazynie „Computer Language”, który niestety nie jest już publikowany.

Remo.D
źródło
1
Zasadniczo w dobrym FSM chodzi o czytelność. Zapewnia to dobry interfejs, a implementacja jest tak dobra, jak to tylko możliwe. Szkoda, że ​​w języku nie ma rodzimej struktury FSM. Widzę to teraz jako późny dodatek do C1X!
Kelden Cowan
3
Uwielbiam to podejście do aplikacji wbudowanych. Czy istnieje sposób użycia tego podejścia w przypadku maszyny stanu sterowanej zdarzeniami?
ARF
13

Użyłem również podejścia tabeli. Jednak istnieje obciążenie. Po co przechowywać drugą listę wskaźników? Funkcja w C bez () jest wskaźnikiem do stałej. Możesz więc:

struct state;
typedef void (*state_func_t)( struct state* );

typedef struct state
{
  state_func_t function;

  // other stateful data

} state_t;

void do_state_initial( state_t* );
void do_state_foo( state_t* );
void do_state_bar( state_t* );

void run_state( state_t* i ) {
    i->function(i);
};

int main( void ) {
    state_t state = { do_state_initial };

    while ( 1 ) {
        run_state( state );

        // do other program logic, run other state machines, etc
    }
}

Oczywiście w zależności od twojego współczynnika strachu (tj. Bezpieczeństwo vs prędkość) możesz chcieć sprawdzić poprawne wskazówki. W przypadku maszyn stanowych większych niż trzy lub więcej stanów powyższe podejście powinno obejmować mniej instrukcji niż równoważne podejście do przełącznika lub tabeli. Możesz nawet makroizować jako:

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

Czuję również na przykładzie OP, że istnieje uproszczenie, które należy zrobić, myśląc o / projektowaniu automatu stanowego. Nie uważam, że stan przejściowy powinien być używany do logiki. Każda funkcja państwa powinna być w stanie pełnić swoją rolę bez wyraźnej wiedzy o przeszłych stanach. Zasadniczo projektujesz sposób przejścia ze stanu, w którym się znajdujesz, do innego stanu.

Wreszcie, nie zaczynaj projektowania automatu stanowego w oparciu o granice „funkcjonalne”, użyj do tego podfunkcji. Zamiast tego podziel stany na podstawie tego, kiedy będziesz musiał poczekać, aż coś się wydarzy, zanim będziesz mógł kontynuować. Pomoże to zminimalizować liczbę uruchomień automatu stanowego, zanim otrzymasz wynik. Może to być ważne podczas pisania funkcji we / wy lub obsługi przerwań.

Ponadto kilka zalet i wad klasycznej instrukcji przełącznika:

Plusy:

  • jest w języku, więc jest udokumentowany i jasny
  • stany są definiowane tam, gdzie są wywoływane
  • może wykonywać wiele stanów w jednym wywołaniu funkcji
  • kod wspólny dla wszystkich stanów może być wykonywany przed i po instrukcji switch

Cons:

  • może wykonywać wiele stanów w jednym wywołaniu funkcji
  • kod wspólny dla wszystkich stanów może być wykonywany przed i po instrukcji switch
  • implementacja przełącznika może być powolna

Zwróć uwagę na dwa atrybuty, które są zarówno za, jak i przeciw. Myślę, że zmiana stwarza okazję do zbyt dużego współdzielenia między stanami, a współzależność między stanami może stać się niemożliwa do zarządzania. Jednak w przypadku niewielkiej liczby stanów może być najbardziej czytelny i łatwy w utrzymaniu.

Josh Petitt
źródło
10

W przypadku prostego automatu stanów użyj po prostu instrukcji switch i typu wyliczenia dla swojego stanu. Wykonuj przejścia wewnątrz instrukcji switch na podstawie wprowadzonych danych. W prawdziwym programie oczywiście zmieniłbyś "if (wejście)", aby sprawdzić punkty przejścia. Mam nadzieję że to pomoże.

typedef enum
{
    STATE_1 = 0,
    STATE_2,
    STATE_3
} my_state_t;

my_state_t state = STATE_1;

void foo(char input)
{
    ...
    switch(state)
    {
        case STATE_1:
            if(input)
                state = STATE_2;
            break;
        case STATE_2:
            if(input)
                state = STATE_3;
            else
                state = STATE_1;
            break;
        case STATE_3:
            ...
            break;
    }
    ...
}
jsl4980
źródło
1
Może warto umieścić „stan” wewnątrz funkcji i uczynić ją statyczną.
Steve Melnikoff
2
@Steve Melnikoff: tylko jeśli masz tylko jeden automat stanowy. Pozostaw to poza funkcją, a możesz mieć tablicę maszyn stanowych z własnym stanem.
Vicky,
@Vicky: Jedna funkcja może zawierać dowolną liczbę automatów stanu, w razie potrzeby z tablicą zmiennych stanu, które mogą znajdować się wewnątrz funkcji (jako zmienne statyczne), jeśli nie są używane gdzie indziej.
Steve Melnikoff
10

W UML Distilled Martina Fowlera stwierdza (gra słów nie jest przeznaczona) w Rozdziale 10 Diagramy Maszyn Stanowych (wyróżnienie moje):

Diagram stanów można zaimplementować na trzy główne sposoby: zagnieżdżony przełącznik , wzorzec stanu i tabele stanów .

Skorzystajmy z uproszczonego przykładu stanów wyświetlacza telefonu komórkowego:

wprowadź opis obrazu tutaj

Zagnieżdżony przełącznik

Fowler podał przykład kodu C #, ale dostosowałem go do mojego przykładu.

public void HandleEvent(PhoneEvent anEvent) {
    switch (CurrentState) {
    case PhoneState.ScreenOff:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            if (powerLow) { // guard condition
                DisplayLowPowerMessage(); // action
                // CurrentState = PhoneState.ScreenOff;
            } else {
                CurrentState = PhoneState.ScreenOn;
            }
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenOn:
        switch (anEvent) {
        case PhoneEvent.PressButton:
            CurrentState = PhoneState.ScreenOff;
            break;
        case PhoneEvent.PlugPower:
            CurrentState = PhoneState.ScreenCharging;
            break;
        }
        break;
    case PhoneState.ScreenCharging:
        switch (anEvent) {
        case PhoneEvent.UnplugPower:
            CurrentState = PhoneState.ScreenOff;
            break;
        }
        break;
    }
}

Wzór stanu

Oto implementacja mojego przykładu ze wzorcem GoF State:

wprowadź opis obrazu tutaj

Tabele stanów

Czerpiąc inspirację z Fowlera, oto mój przykład:

Działanie ochrony stanu docelowego stanu źródła
-------------------------------------------------- ------------------------------------
ScreenOff ScreenOff pressButton powerLow displayLowPowerMessage  
ScreenOff ScreenOn pressButton! PowerLow
ScreenOn ScreenOff pressButton
ScreenOff ScreenCharging plugPower
Ekran na ekranie Wtyczka do ładowania Zasilanie
ScreenCharging ScreenOff unplugPower

Porównanie

Zagnieżdżony przełącznik utrzymuje całą logikę w jednym miejscu, ale kod może być trudny do odczytania, gdy występuje wiele stanów i przejść. Jest prawdopodobnie bezpieczniejszy i łatwiejszy do zweryfikowania niż inne podejścia (bez polimorfizmu lub interpretacji).

Implementacja wzorca stanu potencjalnie rozkłada logikę na kilka oddzielnych klas, co może sprawić, że zrozumienie jej jako całości będzie problemem. Z drugiej strony, małe klasy są łatwe do zrozumienia oddzielnie. Projekt jest szczególnie delikatny, jeśli zmienisz zachowanie, dodając lub usuwając przejścia, ponieważ są to metody w hierarchii i może być wiele zmian w kodzie. Jeśli żyjesz zgodnie z zasadą projektowania małych interfejsów, zobaczysz, że ten wzorzec nie działa tak dobrze. Jeśli jednak automat stanowy jest stabilny, takie zmiany nie będą potrzebne.

Podejście oparte na tabelach stanów wymaga napisania jakiegoś interpretera dla treści (może to być łatwiejsze, jeśli masz refleksję w języku, którego używasz), co może wymagać dużo pracy z góry. Jak zauważa Fowler, jeśli twoja tabela jest oddzielona od twojego kodu, możesz modyfikować zachowanie swojego oprogramowania bez ponownej kompilacji. Ma to jednak pewne konsekwencje dla bezpieczeństwa; oprogramowanie zachowuje się w oparciu o zawartość zewnętrznego pliku.

Edytuj (raczej nie dla języka C)

Istnieje również płynny interfejs (znany również jako wewnętrzny język specyficzny dla domeny), który prawdopodobnie jest ułatwiony przez języki, które mają funkcje pierwszej klasy . Biblioteka Stateless istnieje i że blog pokazuje prosty przykład z kodem. Implementacja Javy (pre Java8) jest omawiany. Pokazano mi również przykład Pythona na GitHubie .

Fuhrmanator
źródło
Z jakiego oprogramowania korzystałeś do tworzenia obrazów?
sjas
1
Podejrzewam, że mógł zostać utworzony przez PlantUML plantuml.com/state-diagram
Seidleroni
9

istnieje również siatka logiczna, która jest łatwiejsza do utrzymania w miarę powiększania się maszyny stanu

geocoin
źródło
4

W prostych przypadkach możesz zastosować metodę zmiany stylu. W przeszłości odkryłem, że dobrze sprawdza się również w przypadku przejść:

static int current_state;    // should always hold current state -- and probably be an enum or something

void state_leave(int new_state) {
    // do processing on what it means to enter the new state
    // which might be dependent on the current state
}

void state_enter(int new_state) {
    // do processing on what is means to leave the current atate
    // might be dependent on the new state

    current_state = new_state;
}

void state_process() {
    // switch statement to handle current state
}

Nie wiem nic o bibliotece boost, ale tego typu podejście jest banalnie proste, nie wymaga żadnych zewnętrznych zależności i jest łatwe do wdrożenia.

znak
źródło
4

switch () to potężny i standardowy sposób implementacji maszyn stanu w C, ale może zmniejszyć łatwość konserwacji, jeśli masz dużą liczbę stanów. Inną popularną metodą jest użycie wskaźników funkcji do przechowywania następnego stanu. Ten prosty przykład implementuje przerzutnik ustawiania / resetowania:

/* Implement each state as a function with the same prototype */
void state_one(int set, int reset);
void state_two(int set, int reset);

/* Store a pointer to the next state */
void (*next_state)(int set, int reset) = state_one;

/* Users should call next_state(set, reset). This could
   also be wrapped by a real function that validated input
   and dealt with output rather than calling the function
   pointer directly. */

/* State one transitions to state one if set is true */
void state_one(int set, int reset) {
    if(set)
        next_state = state_two;
}

/* State two transitions to state one if reset is true */
void state_two(int set, int reset) {
    if(reset)
        next_state = state_one;
}
Commodore Jaeger
źródło
4

Znalazłem naprawdę zgrabną implementację Moore FSM w C na kursie edx.org Embedded Systems - Shape the World UTAustinX - UT.6.02x, rozdział 10, autorstwa Jonathana Valvano i Ramesha Yerraballi ....

struct State {
  unsigned long Out;  // 6-bit pattern to output
  unsigned long Time; // delay in 10ms units 
  unsigned long Next[4]; // next state for inputs 0,1,2,3
}; 

typedef const struct State STyp;

//this example has 4 states, defining constants/symbols using #define
#define goN   0
#define waitN 1
#define goE   2
#define waitE 3


//this is the full FSM logic coded into one large array of output values, delays, 
//and next states (indexed by values of the inputs)
STyp FSM[4]={
 {0x21,3000,{goN,waitN,goN,waitN}}, 
 {0x22, 500,{goE,goE,goE,goE}},
 {0x0C,3000,{goE,goE,waitE,waitE}},
 {0x14, 500,{goN,goN,goN,goN}}};
unsigned long currentState;  // index to the current state 

//super simple controller follows
int main(void){ volatile unsigned long delay;
//embedded micro-controller configuration omitteed [...]
  currentState = goN;  
  while(1){
    LIGHTS = FSM[currentState].Out;  // set outputs lines (from FSM table)
    SysTick_Wait10ms(FSM[currentState].Time);
    currentState = FSM[currentState].Next[INPUT_SENSORS];  
  }
}
user153222
źródło
2

Możesz zajrzeć do oprogramowania generatora Libero FSM. Z języka opisu stanu i / lub edytora diagramów stanu (Windows) możesz wygenerować kod dla C, C ++, java i wielu innych ... plus ładną dokumentację i diagramy. Źródło i pliki binarne z iMatix

pklausner
źródło
2

Jednym z moich ulubionych wzorów jest wzór projektowania państwowego. Reaguj lub zachowuj się inaczej w przypadku tego samego zestawu danych wejściowych.
Jednym z problemów związanych z używaniem instrukcji switch / case dla maszyn stanu jest to, że gdy tworzysz więcej stanów, przełącznik / case staje się trudniejszy / nieporęczny do odczytania / utrzymania, promuje niezorganizowany kod spaghetti i coraz trudniej jest go zmienić bez zepsucia czegoś. Uważam, że używanie wzorców projektowych pomaga mi lepiej organizować dane, co jest celem abstrakcji. Zamiast projektować kod stanu w oparciu o stan, z którego pochodzisz, skonstruuj kod tak, aby rejestrował stan, gdy wchodzisz w nowy stan. W ten sposób skutecznie uzyskasz zapis swojego poprzedniego stanu. Podoba mi się odpowiedź @ JoshPetita i poszedłem o krok dalej, zaczerpnięte prosto z książki GoF:

stateCtxt.h:

#define STATE (void *)
typedef enum fsmSignal
{
   eEnter =0,
   eNormal,
   eExit
}FsmSignalT;

typedef struct fsm 
{
   FsmSignalT signal;
   // StateT is an enum that you can define any which way you want
   StateT currentState;
}FsmT;
extern int STATECTXT_Init(void);
/* optionally allow client context to set the target state */
extern STATECTXT_Set(StateT  stateID);
extern void STATECTXT_Handle(void *pvEvent);

stateCtxt.c:

#include "stateCtxt.h"
#include "statehandlers.h"

typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent);

static FsmT      fsm;
static pfnStateT UsbState ;

int STATECTXT_Init(void)
{    
    UsbState = State1;
    fsm.signal = eEnter;
    // use an enum for better maintainability
    fsm.currentState = '1';
    (*UsbState)( &fsm, pvEvent);
    return 0;
}

static void ChangeState( FsmT *pFsm, pfnStateT targetState )
{
    // Check to see if the state has changed
    if (targetState  != NULL)
    {
        // Call current state's exit event
        pFsm->signal = eExit;
        STATE dummyState = (*UsbState)( pFsm, pvEvent);

        // Update the State Machine structure
        UsbState = targetState ;

        // Call the new state's enter event
        pFsm->signal = eEnter;            
        dummyState = (*UsbState)( pFsm, pvEvent);
    }
}

void STATECTXT_Handle(void *pvEvent)
{
    pfnStateT newState;

    if (UsbState != NULL)
    {
         fsm.signal = eNormal;
         newState = (*UsbState)( &fsm, pvEvent );
         ChangeState( &fsm, newState );
    }        
}


void STATECTXT_Set(StateT  stateID)
{
     prevState = UsbState;
     switch (stateID) 
     {
         case '1':               
            ChangeState( State1 );
            break;
          case '2':
            ChangeState( State2);
            break;
          case '3':
            ChangeState( State3);
            break;
     }
}

statehandlers.h:

/* define state handlers */
extern STATE State1(void);
extern STATE State2(void);
extern STATE State3(void);

statehandlers.c:

#include "stateCtxt.h:"

/* Define behaviour to given set of inputs */
STATE State1(FsmT *fsm, void *pvEvent)
{   
    STATE nextState;
    /* do some state specific behaviours 
     * here
     */
    /* fsm->currentState currently contains the previous state
     * just before it gets updated, so you can implement behaviours 
     * which depend on previous state here
     */
    fsm->currentState = '1';
    /* Now, specify the next state
     * to transition to, or return null if you're still waiting for 
     * more stuff to process.  
     */
    switch (fsm->signal)
    {
        case eEnter:
            nextState = State2;
            break;
        case eNormal:
            nextState = null;
            break;
        case eExit:
            nextState = State2;
            break;
    }

    return nextState;
}

STATE  State3(FsmT *fsm, void *pvEvent)
{
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '2';
    /* Now, specify the next state
     * to transition to
     */
     return State1;
}

STATE   State2(FsmT *fsm, void *pvEvent)
{   
    /* do some state specific behaviours 
     * here
     */
    fsm->currentState = '3';
    /* Now, specify the next state
     * to transition to
     */
     return State3;
}

W przypadku większości maszyn stanowych, zwł. Maszyny skończone, każdy stan będzie wiedział, jaki powinien być jego następny stan i jakie są kryteria przejścia do następnego stanu. W przypadku projektów stanu swobodnego może tak nie być, stąd opcja udostępnienia API dla stanów przejściowych. Jeśli chcesz więcej abstrakcji, każdy program obsługi stanu może zostać oddzielony do własnego pliku, który jest równoważny z konkretnymi procedurami obsługi stanu w książce GoF. Jeśli twój projekt jest prosty i ma tylko kilka stanów, wówczas zarówno stateCtxt.c, jak i statehandlers.c mogą zostać połączone w jeden plik dla uproszczenia.

Phileo99
źródło
State3 i State2 zwracają wartości, mimo że zostały uznane za nieważne.
Ant
1

Z mojego doświadczenia wynika, że ​​użycie instrukcji „przełącznik” jest standardowym sposobem obsługi wielu możliwych stanów. Chociaż jestem zaskoczony, że przekazujesz wartość przejścia do przetwarzania na stan. Myślałem, że sednem automatu stanowego jest to, że każdy stan wykonuje jedną akcję. Następnie następne działanie / wejście określa, w który nowy stan przejść. Spodziewałbym się więc, że każda funkcja przetwarzania stanu natychmiast wykona wszystko, co jest ustalone dla wejścia w stan, a następnie zdecyduje, czy potrzebne jest przejście do innego stanu.

Phil Wright
źródło
2
Istnieją różne podstawowe modele: maszyny Mealy i maszyny Moore. Działania Mealy'ego zależą od przejścia, działania Moore'a zależą od stanu.
xmjx
1

Istnieje książka zatytułowana Practical Statecharts in C / C ++ . Jednak jest to droga zbyt dużej gramaturze do czego potrzebujemy.

Benoit
źródło
2
Dokładnie tak samo zareagowałem na książkę. Jak można potrzebować ponad 700 stron, aby opisać i wdrożyć coś, co moim zdaniem jest dość intuicyjne i proste?!?!?
Dan,
1

W przypadku kompilatora, który obsługuje __COUNTER__, możesz ich użyć do prostych (ale dużych) maszyn stanu.

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  {
    switch (state)
    {
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
     } 
  } 

Zaletą używania __COUNTER__zamiast zakodowanych liczb jest to, że możesz dodawać stany w środku innych stanów, bez ponownego numerowania wszystkiego za każdym razem. Jeśli kompilator nie obsługuje __COUNTER__, w ograniczony sposób można go używać ostrożnie__LINE__

Seb
źródło
Czy mógłbyś wyjaśnić bardziej swoją odpowiedź?
abarisone
W normalnym urządzeniu ze stanem „przełącznika” masz np. Przypadek 0, przypadek 1, przypadek 2,… przypadek 100. Jeśli chcesz teraz dodać 3 przypadki od 5 do 6, musisz zmienić numerację pozostałych na 100, co teraz byłoby 103. Użycie opcji __COUNTER__eliminuje potrzebę zmiany numeracji, ponieważ prekompilator wykonuje numerację podczas kompilacji.
Seb
1

Możesz użyć minimalistycznej struktury maszyny stanów UML w c. https://github.com/kiishor/UML-State-Machine-in-C

Obsługuje skończoną i hierarchiczną maszynę stanów. Ma tylko 3 API, 2 struktury i 1 wyliczenie.

Maszyna stanów jest reprezentowana przez state_machine_tstrukturę. Jest to abstrakcyjna struktura, którą można odziedziczyć w celu stworzenia maszyny stanu.

//! Abstract state machine structure
struct state_machine_t
{
   uint32_t Event;          //!< Pending Event for state machine
   const state_t* State;    //!< State of state machine.
};

Stan jest reprezentowany przez wskaźnik do state_t struktury w ramach.

Jeśli struktura jest skonfigurowana dla maszyny skończonej, a następnie state_tzawiera,

typedef struct finite_state_t state_t;

// finite state structure
typedef struct finite_state_t{
  state_handler Handler;        //!< State handler function (function pointer)
  state_handler Entry;          //!< Entry action for state (function pointer)
  state_handler Exit;           //!< Exit action for state (function pointer)
}finite_state_t;

Struktura zapewnia interfejs API dispatch_eventdo wysyłania zdarzenia do automatu stanowego i dwa interfejsy API do przechodzenia do stanu.

state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const, const state_t*);

state_machine_result_t traverse_state(state_machine_t* const, const state_t*);

Aby uzyskać więcej informacji na temat implementacji hierarchicznej maszyny stanów, zapoznaj się z repozytorium GitHub.

przykłady kodu
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in -C / blob / master / demo / simple_state_machine_enhanced / readme.md

Nandkishor Biradar
źródło
czy możesz również dodać przykładowy kod, który pasuje do pytania?
Giulio Caccin
1
Folder demonstracyjny w repozytorium ma jeden przykład. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . Obecnie pracuję nad jeszcze jednym przykładem systemu wbudowanego, który obejmuje klucz, diody LED i liczniki czasu, ale nadal nie jest kompletny. Poinformuje Cię, gdy będzie gotowy.
Nandkishor Biradar
0

Twoje pytanie jest podobne do „czy istnieje typowy wzorzec implementacji bazy danych”? Odpowiedź zależy od tego, co chcesz osiągnąć? Jeśli chcesz zaimplementować większą deterministyczną maszynę stanów, możesz użyć modelu i generatora automatu stanów. Przykłady można obejrzeć na www.StateSoft.org - SM Gallery. Janusz Dobrowolski

Janusz Dobrowolski
źródło