Gibt es ein typisches Implementierungsmuster für Zustandsmaschinen?


118

Wir müssen eine einfache Zustandsmaschine in C implementieren .
Ist eine Standard-switch-Anweisung der beste Weg?
Wir haben einen aktuellen Zustand (Zustand) und einen Auslöser für den Übergang.


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;
}

Gibt es einen besseren Weg für einfache Zustandsautomaten?

EDIT: Für C ++ denke ich, dass die Boost Statechart- Bibliothek der richtige Weg sein könnte. Bei C hilft es jedoch nicht . Konzentrieren wir uns auf den C-Anwendungsfall.


Antworten:


134

Ich bevorzuge für die meisten Zustandsautomaten einen tabellengesteuerten Ansatz:

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
    }
}

Dies kann natürlich erweitert werden, um mehrere Zustandsautomaten usw. zu unterstützen. Übergangsaktionen können ebenfalls berücksichtigt werden:

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;
};

Der tabellengesteuerte Ansatz ist einfacher zu warten und zu erweitern und einfacher auf Zustandsdiagramme abzubilden.


Sehr guter Einstieg, zumindest Ausgangspunkt für mich. Eine Bemerkung, die erste Zeile von run_state () hat ein ungezogenes "." das sollte nicht da sein.
Atilla Filiz

2
wäre besser, wenn diese Antwort auch mindestens 2 Wörter über die beiden anderen Ansätze aussagen würde: eine "globale" Methode mit einem großen Schalterfall und das Trennen von Zuständen mit dem Zustandsentwurfsmuster und das Überlassen, dass jeder Zustand seine Übergänge selbst handhabt.
Erikbwork

Hallo, ich weiß, dass dieser Beitrag alt ist, aber ich hoffe, ich bekomme meine Antwort :) Was sollte die Variable in instance_data_t unbedingt? Ich frage mich, wie man Zustände in Interrupts ändert ... ist es eine gute Möglichkeit, Informationen über verarbeitete Interrupts in dieser Variablen zu speichern? Speichern Sie beispielsweise Informationen, bei denen die Taste gedrückt wurde, damit der Status geändert werden kann.
Grongor

@GRoNGoR Klingt für mich so, als hätten Sie es mit einer ereignisgesteuerten Zustandsmaschine zu tun. Ich denke, Sie könnten es tatsächlich verwenden, um Ereignisdaten zu speichern.
Zimano

3
Wirklich nette Geste, wie NUM_STATES definiert ist.
Albin Stigo

25

Vielleicht haben Sie meine Antwort auf eine andere C-Frage gesehen, in der ich FSM erwähnt habe! So mache ich das:

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

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

Mit den folgenden Makros definiert

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

Dies kann an den speziellen Fall angepasst werden. Beispielsweise haben Sie möglicherweise eine Datei FSMFILE, mit der Sie Ihren FSM steuern möchten, sodass Sie die Aktion zum Lesen des nächsten Zeichens in das Makro selbst integrieren können:

#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

Jetzt haben Sie zwei Arten von Übergängen: Einer wechselt in einen Status und liest ein neues Zeichen, der andere wechselt in einen Status, ohne dass Eingaben erforderlich sind.

Sie können die Handhabung von EOF auch mit folgenden Funktionen automatisieren:

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

#define ENDFSM    sx_endfsm:

Das Gute an diesem Ansatz ist, dass Sie ein von Ihnen gezeichnetes Zustandsdiagramm direkt in Arbeitscode übersetzen können und umgekehrt einfach ein Zustandsdiagramm aus dem Code zeichnen können.

Bei anderen Techniken zur Implementierung von FSM wird die Struktur der Übergänge in Kontrollstrukturen (während, wenn, wechseln ...) vergraben und durch Variablenwerte gesteuert (typischerweise a state Variable) und es kann eine komplexe Aufgabe sein, das schöne Diagramm mit a in Beziehung zu setzen verschlungener Code.

Ich habe diese Technik aus einem Artikel in der großen Zeitschrift "Computer Language" gelernt, der leider nicht mehr veröffentlicht wird.


1
Grundsätzlich dreht sich bei einem guten FSM alles um Lesbarkeit. Dies bietet eine gute Schnittstelle und die Implementierung ist so gut wie es nur geht. Es ist eine Schande, dass es in der Sprache keine native FSM-Struktur gibt. Ich kann es jetzt als späte Ergänzung zu C1X sehen!
Kelden Cowan

3
Ich liebe diesen Ansatz für eingebettete Anwendungen. Gibt es eine Möglichkeit, diesen Ansatz mit einer ereignisgesteuerten Zustandsmaschine zu verwenden?
ARF

13

Ich habe auch den Tabellenansatz verwendet. Es gibt jedoch Overhead. Warum eine zweite Liste von Zeigern speichern? Eine Funktion in C ohne () ist ein const-Zeiger. So können Sie tun:

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
    }
}

Abhängig von Ihrem Angstfaktor (dh Sicherheit gegenüber Geschwindigkeit) möchten Sie möglicherweise nach gültigen Zeigern suchen. Bei Zustandsautomaten mit mehr als drei Zuständen sollte der obige Ansatz weniger Anweisungen enthalten als ein äquivalenter Switch- oder Tabellenansatz. Sie könnten sogar makroisieren:

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

Aus dem Beispiel des OP geht auch hervor, dass es eine Vereinfachung gibt, die beim Nachdenken über / Entwerfen einer Zustandsmaschine vorgenommen werden sollte. Ich denke nicht, dass der Übergangszustand für die Logik verwendet werden sollte. Jede Zustandsfunktion sollte in der Lage sein, ihre gegebene Rolle ohne explizite Kenntnis vergangener Zustände auszuführen. Grundsätzlich legen Sie fest, wie Sie von dem Zustand, in dem Sie sich befinden, in einen anderen Zustand übergehen.

Beginnen Sie schließlich nicht mit dem Entwurf einer Zustandsmaschine, die auf "funktionalen" Grenzen basiert, sondern verwenden Sie dafür Unterfunktionen. Teilen Sie die Zustände stattdessen danach auf, wann Sie warten müssen, bis etwas passiert, bevor Sie fortfahren können. Dies hilft dabei, die Häufigkeit zu minimieren, mit der Sie die Zustandsmaschine ausführen müssen, bevor Sie ein Ergebnis erhalten. Dies kann wichtig sein, wenn Sie E / A-Funktionen schreiben oder Handler unterbrechen.

Außerdem ein paar Vor- und Nachteile der klassischen switch-Anweisung:

Vorteile:

  • es ist in der Sprache, also ist es dokumentiert und klar
  • Zustände werden dort definiert, wo sie aufgerufen werden
  • kann mehrere Zustände in einem Funktionsaufruf ausführen
  • Code, der allen Zuständen gemeinsam ist, kann vor und nach der switch-Anweisung ausgeführt werden

Nachteile:

  • kann mehrere Zustände in einem Funktionsaufruf ausführen
  • Code, der allen Zuständen gemeinsam ist, kann vor und nach der switch-Anweisung ausgeführt werden
  • Die Switch-Implementierung kann langsam sein

Beachten Sie die beiden Attribute Pro und Contra. Ich denke, der Wechsel bietet die Möglichkeit, zu viel zwischen Staaten zu teilen, und die gegenseitige Abhängigkeit zwischen Staaten kann unüberschaubar werden. Für eine kleine Anzahl von Zuständen ist es jedoch möglicherweise am besten lesbar und wartbar.


10

Verwenden Sie für eine einfache Zustandsmaschine einfach eine switch-Anweisung und einen Aufzählungstyp für Ihren Zustand. Führen Sie Ihre Übergänge innerhalb der switch-Anweisung basierend auf Ihrer Eingabe durch. In einem realen Programm würden Sie offensichtlich das "if (Eingabe)" ändern, um nach Ihren Übergangspunkten zu suchen. Hoffe das hilft.

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;
    }
    ...
}

1
Es könnte sich lohnen, "state" in die Funktion einzufügen und statisch zu machen.
Steve Melnikoff

2
@Steve Melnikoff: Nur wenn Sie nur eine Zustandsmaschine haben. Halten Sie es außerhalb der Funktion, und Sie können eine Reihe von Zustandsautomaten mit ihrem eigenen Zustand haben.
Vicky

@ Vicky: Eine Funktion kann beliebig viele Zustandsautomaten enthalten, bei Bedarf mit einem Array von Zustandsvariablen, die innerhalb der Funktion (als statische Variablen) gespeichert werden können, wenn sie nicht anderweitig verwendet werden.
Steve Melnikoff

10

In Martin Fowlers UML Distilled erklärt er (kein Wortspiel beabsichtigt) in Kapitel 10 Zustandsmaschinendiagramme (Hervorhebung von mir):

Ein Zustandsdiagramm kann auf drei Arten implementiert werden: verschachtelter Schalter , Zustandsmuster und Zustandstabellen .

Verwenden wir ein vereinfachtes Beispiel für den Status der Anzeige eines Mobiltelefons:

Geben Sie hier die Bildbeschreibung ein

Verschachtelter Schalter

Fowler gab ein Beispiel für C # -Code, aber ich habe es an mein Beispiel angepasst.

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;
    }
}

Zustandsmuster

Hier ist eine Implementierung meines Beispiels mit dem GoF-Statusmuster:

Geben Sie hier die Bildbeschreibung ein

Zustandstabellen

Hier ist eine Tabelle für mein Beispiel, die sich von Fowler inspirieren lässt:

Quellstatus Zielstatus Ereignisschutzaktion
-------------------------------------------------- ------------------------------------
ScreenOff ScreenOff drücken Sie die Taste powerLow displayLowPowerMessage  
ScreenOff ScreenOn pressButton! PowerLow
ScreenOn ScreenOff drücken Sie die Taste
ScreenOff ScreenCharging plugPower
ScreenOn ScreenCharging plugPower
ScreenCharging ScreenOff unplugPower

Vergleich

Der verschachtelte Schalter hält die gesamte Logik an einem Ort, aber der Code kann bei vielen Zuständen und Übergängen schwer zu lesen sein. Es ist möglicherweise sicherer und einfacher zu validieren als die anderen Ansätze (kein Polymorphismus oder Dolmetschen).

Die Implementierung des Zustandsmusters verteilt die Logik möglicherweise auf mehrere separate Klassen, was das Verständnis als Ganzes zu einem Problem machen kann. Andererseits sind die kleinen Klassen separat leicht zu verstehen. Das Design ist besonders fragil, wenn Sie das Verhalten durch Hinzufügen oder Entfernen von Übergängen ändern, da es sich um Methoden in der Hierarchie handelt und möglicherweise viele Änderungen am Code vorgenommen werden. Wenn Sie nach dem Designprinzip kleiner Schnittstellen leben, werden Sie feststellen, dass dieses Muster nicht so gut funktioniert. Wenn die Zustandsmaschine jedoch stabil ist, sind solche Änderungen nicht erforderlich.

Der Ansatz für Statustabellen erfordert das Schreiben einer Art Interpreter für den Inhalt (dies ist möglicherweise einfacher, wenn Sie sich in der von Ihnen verwendeten Sprache widerspiegeln), was im Vorfeld eine Menge Arbeit bedeuten kann. Wie Fowler betont, können Sie das Verhalten Ihrer Software ändern, wenn Ihre Tabelle von Ihrem Code getrennt ist, ohne sie neu zu kompilieren. Dies hat jedoch einige Auswirkungen auf die Sicherheit. Die Software verhält sich basierend auf dem Inhalt einer externen Datei.

Bearbeiten (nicht wirklich für C-Sprache)

Es gibt auch einen fließenden Schnittstellenansatz (auch bekannt als interne domänenspezifische Sprache), der wahrscheinlich durch Sprachen mit erstklassigen Funktionen erleichtert wird . Die zustandslose Bibliothek existiert und dieser Blog zeigt ein einfaches Beispiel mit Code. Eine Java-Implementierung (vor Java8) wird diskutiert. Mir wurde auch ein Python-Beispiel auf GitHub gezeigt .


Mit welcher Software haben Sie die Bilder erstellt?
Sjas

1
Ich vermute, es wurde möglicherweise über PlantUML plantuml.com/state-diagram
Seidleroni


4

In einfachen Fällen können Sie Ihre Switch-Style-Methode verwenden. Ich habe festgestellt, dass es in der Vergangenheit gut funktioniert, sich auch mit Übergängen zu befassen:

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
}

Ich weiß nichts über die Boost-Bibliothek, aber diese Art von Ansatz ist denkbar einfach, erfordert keine externen Abhängigkeiten und ist einfach zu implementieren.


4

switch () ist eine leistungsstarke und standardmäßige Methode zum Implementieren von Zustandsautomaten in C, kann jedoch die Wartbarkeit verringern, wenn Sie über eine große Anzahl von Zuständen verfügen. Eine andere übliche Methode besteht darin, Funktionszeiger zu verwenden, um den nächsten Zustand zu speichern. Dieses einfache Beispiel implementiert ein Set / Reset-Flip-Flop:

/* 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;
}

4

Ich fand eine wirklich raffinierte C-Implementierung von Moore FSM im edx.org-Kurs Embedded Systems - Shape the World UTAustinX - UT.6.02x, Kapitel 10, von Jonathan Valvano und Ramesh 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];  
  }
}

2

Vielleicht möchten Sie einen Blick auf die libero FSM-Generator-Software werfen . Aus einer Zustandsbeschreibungssprache und / oder einem (Windows-) Zustandsdiagramm-Editor können Sie Code für C, C ++, Java und viele andere generieren ... sowie eine schöne Dokumentation und Diagramme. Quelle und Binärdateien von iMatix



2

Eines meiner Lieblingsmuster ist das Zustandsmuster. Reagieren Sie auf dieselben Eingaben oder verhalten Sie sich anders.
Eines der Probleme bei der Verwendung von switch / case-Anweisungen für Zustandsautomaten besteht darin, dass das Erstellen / Verwalten von switch / case-Dateien schwieriger / unhandlicher wird, unorganisierten Spaghetti-Code fördert und zunehmend schwieriger zu ändern ist, ohne etwas zu beschädigen. Ich finde, dass die Verwendung von Entwurfsmustern mir hilft, meine Daten besser zu organisieren, was den ganzen Punkt der Abstraktion ausmacht. Anstatt Ihren Statuscode anhand des Status zu entwerfen, aus dem Sie gekommen sind, strukturieren Sie Ihren Code so, dass er den Status aufzeichnet, wenn Sie einen neuen Status eingeben. Auf diese Weise erhalten Sie effektiv eine Aufzeichnung Ihres vorherigen Zustands. Ich mag die Antwort von @ JoshPetit und habe seine Lösung einen Schritt weiter gebracht, direkt aus dem GoF-Buch:

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;
}

Für die meisten Staatsmaschinen, insb. Bei endlichen Zustandsmaschinen weiß jeder Zustand, wie sein nächster Zustand aussehen soll und welche Kriterien für den Übergang in seinen nächsten Zustand gelten. Bei Designs mit losen Zuständen ist dies möglicherweise nicht der Fall, daher besteht die Möglichkeit, die API für Übergangszustände verfügbar zu machen. Wenn Sie mehr Abstraktion wünschen, kann jeder Statushandler in eine eigene Datei aufgeteilt werden, die den konkreten Statushandlern im GoF-Buch entspricht. Wenn Ihr Entwurf mit nur wenigen Status einfach ist, können sowohl stateCtxt.c als auch statehandlers.c der Einfachheit halber in einer einzigen Datei kombiniert werden.


State3 und State2 haben Rückgabewerte, obwohl sie für ungültig erklärt wurden.
Ameise

1

Nach meiner Erfahrung ist die Verwendung der Anweisung 'switch' die Standardmethode, um mehrere mögliche Zustände zu behandeln. Obwohl ich überrascht bin, dass Sie einen Übergangswert an die Verarbeitung pro Status übergeben. Ich dachte, der springende Punkt einer Zustandsmaschine sei, dass jeder Zustand eine einzelne Aktion ausführt. Dann bestimmt die nächste Aktion / Eingabe, in welchen neuen Zustand übergegangen werden soll. Ich hätte also erwartet, dass jede Zustandsverarbeitungsfunktion sofort alles ausführt, was für den Eintritt in den Zustand festgelegt ist, und anschließend entscheidet, ob ein Übergang in einen anderen Zustand erforderlich ist.


2
Es gibt verschiedene zugrunde liegende Modelle: Mealy-Maschinen und Moore-Maschinen. Mealys Handlungen hängen vom Übergang ab, Moores vom Staat.
xmjx

1

Es gibt ein Buch mit dem Titel Practical Statecharts in C / C ++ . Es ist jedoch viel zu schwer für das, was wir brauchen.


2
Ich hatte genau die gleiche Reaktion auf das Buch. Wie können mehr als 700 Seiten benötigt werden, um etwas zu beschreiben und zu implementieren, das meiner Meinung nach ziemlich intuitiv und unkompliziert ist?!?!?
Dan

1

Für Compiler, die dies unterstützen __COUNTER__, können Sie sie für einfache (aber große) Status-Mashines verwenden.

  #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;
     } 
  } 

Der Vorteil der Verwendung __COUNTER__anstelle von fest codierten Zahlen besteht darin, dass Sie Zustände in der Mitte anderer Zustände hinzufügen können, ohne jedes Mal alles neu zu nummerieren. Wenn der Compiler dies nicht unterstützt __COUNTER__, kann er in begrenztem Umfang vorsichtshalber verwendet werden__LINE__


Könnten Sie bitte Ihre Antwort näher erläutern?
Abarisone

In einer normalen "Switch" -Zustandsmaschine haben Sie z. B. Fall 0, Fall 1, Fall 2, ... Fall 100. Wenn Sie jetzt 3 Fälle zwischen 5 und 6 hinzufügen möchten, müssen Sie den Rest auf 100 umnummerieren, was Jetzt wäre es 103. Die Verwendung von macht __COUNTER__eine Neunummerierung überflüssig, da der Precompiler die Nummerierung während der Kompilierung vornimmt.
Seb

1

Sie können das minimalistische UML-Zustandsmaschinen-Framework in c verwenden. https://github.com/kiishor/UML-State-Machine-in-C

Es unterstützt sowohl endliche als auch hierarchische Zustandsmaschinen. Es hat nur 3 APIs, 2 Strukturen und 1 Aufzählung.

Die Zustandsmaschine wird durch state_machine_tStruktur dargestellt. Es ist eine abstrakte Struktur, die geerbt werden kann, um eine Zustandsmaschine zu erstellen.

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

Der Zustand wird durch einen Zeiger auf dargestellt state_t Struktur im Framework dargestellt.

Wenn das Framework für die Finite-State-Maschine konfiguriert ist, state_tenthält es:

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;

Das Framework bietet eine API dispatch_event zum Versenden des Ereignisses an die Zustandsmaschine und zwei APIs für die Zustandsüberquerung.

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*);

Weitere Informationen zum Implementieren einer hierarchischen Zustandsmaschine finden Sie im GitHub-Repository.

Codebeispiele
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


Können Sie auch ein Codebeispiel hinzufügen, das zur Frage passt?
Giulio Caccin

Der Demo-Ordner im Repository enthält ein Beispiel. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . Ich arbeite derzeit an einem weiteren Beispiel für ein eingebettetes System, das Schlüssel, LED und Timer umfasst. Es ist jedoch noch nicht vollständig. Lassen Sie es Sie wissen, sobald es fertig ist.
Nandkishor Biradar


0

Ihre Frage ähnelt "Gibt es ein typisches Datenbankimplementierungsmuster"? Die Antwort hängt davon ab, was Sie erreichen möchten. Wenn Sie eine größere deterministische Zustandsmaschine implementieren möchten, können Sie ein Modell und einen Zustandsmaschinengenerator verwenden. Beispiele können unter www.StateSoft.org - SM Gallery eingesehen werden. Janusz Dobrowolski


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.