it-swarm.com.de

C Zustandsmaschine Design

Ich erstelle ein kleines Projekt in gemischten C und C++. Ich baue eine kleine Staatsmaschine im Herzen eines meiner Arbeiter-Threads.

Ich habe mich gefragt, ob Sie Gurus bei SO Ihre Techniken zum Entwurf von Staatsmaschinen mitteilen würden.

HINWEIS: Ich bin in erster Linie nach bewährten Implementierungstechniken.

UPDATED: Basierend auf all dem großen Input, den SO gesammelt hat, habe ich mich für diese Architektur entschieden:

 An event pump points to an event integrator which points to a dispatcher. The dispatcher points to 1 through n actions which point back to the event integrator. A transition table with wildcards points to the dispatcher.

189
jldupont

Zustandsmaschinen, die ich zuvor entwickelt habe (C, nicht C++), sind auf ein struct-Array und eine Schleife zurückzuführen. Die Struktur besteht im Wesentlichen aus einem Zustand und einem Ereignis (zum Nachschlagen) und einer Funktion, die den neuen Zustand zurückgibt, etwa:

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

Dann definieren Sie Ihre Zustände und Ereignisse mit einfachen Definitionen (die ANY-Werte sind spezielle Marker, siehe unten):

#define ST_ANY              -1
#define ST_INIT              0
#define ST_ERROR             1
#define ST_TERM              2
: :
#define EV_ANY              -1
#define EV_KEYPRESS       5000
#define EV_MOUSEMOVE      5001

Dann definieren Sie alle Funktionen, die von den Übergängen aufgerufen werden:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

Alle diese Funktionen werden so geschrieben, dass sie keine Variablen annehmen und den neuen Status für die Zustandsmaschine zurückgeben. In diesem Beispiel werden globale Variablen verwendet, um ggf. Informationen in die Zustandsfunktionen zu übernehmen.

Die Verwendung von Globals ist nicht so schlecht, wie es sich anhört, da der FSM normalerweise in einer einzigen Kompilierungseinheit eingeschlossen ist und alle Variablen für diese Einheit statisch sind (weshalb ich oben in "global" Anführungszeichen verwendet habe FSM, als wirklich global). Wie bei allen Globals ist auch hier Vorsicht geboten.

Das Transitions-Array definiert dann alle möglichen Übergänge und die Funktionen, die für diese Übergänge aufgerufen werden (einschließlich der Catch-All-Letzten):

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

Dies bedeutet: Wenn Sie sich im ST_INIT-Zustand befinden und das EV_KEYPRESS-Ereignis erhalten, rufen Sie GotKey an.

Die Arbeitsweise des FSM wird dann zu einer relativ einfachen Schleife:

state = ST_INIT;
while (state != ST_TERM) {
    event = GetNextEvent();
    for (i = 0; i < TRANS_COUNT; i++) {
        if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
            if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
                state = (trans[i].fn)();
                break;
            }
        }
    }
}

Beachten Sie, wie oben erwähnt, die Verwendung von ST_ANY als Platzhalter, sodass ein Ereignis eine Funktion unabhängig vom aktuellen Status aufrufen kann. EV_ANY funktioniert auch ähnlich, sodass jedes Ereignis in einem bestimmten Status eine Funktion aufrufen kann.

Es kann auch garantiert werden, dass beim Erreichen des Endes des Transitions-Arrays ein Fehler angezeigt wird, der besagt, dass Ihr FSM nicht korrekt erstellt wurde (mithilfe der Kombination ST_ANY/EV_ANY).

Ich habe ähnlichen Code für eine Vielzahl von Kommunikationsprojekten verwendet, z. B. eine frühe Implementierung von Kommunikationsstacks und Protokollen für eingebettete Systeme. Der große Vorteil war die Einfachheit und die relative Leichtigkeit beim Wechseln des Transitions-Arrays.

Ich bezweifle, dass es Abstraktionen auf höherer Ebene geben wird, die heutzutage möglicherweise geeigneter sind, aber ich vermute, dass sie alle auf dieselbe Art von Struktur herunterkommen. 


Da ldog in einem Kommentar angegeben ist, können Sie die Globals ganz vermeiden, indem Sie einen Strukturzeiger an alle Funktionen übergeben (und diesen in der Ereignisschleife verwenden). Dadurch können mehrere Zustandsmaschinen störungsfrei nebeneinander laufen.

Legen Sie einfach einen Strukturtyp an, der die maschinenspezifischen Daten enthält (Zustand auf ein Minimum), und verwenden Sie diesen anstelle der Globals.

Der Grund, warum ich das selten getan habe, liegt einfach daran, dass die meisten Zustandsmaschinen, die ich geschrieben habe, Singleton-Typen waren (einmalig, bei Prozessstart, beispielsweise Lesen von Konfigurationsdateien) und nicht mehr als eine Instanz ausgeführt werden müssen . Aber es hat einen Wert, wenn Sie mehr als einen ausführen müssen.

165
paxdiablo

Die anderen Antworten sind gut, aber eine sehr "leichtgewichtige" Implementierung, die ich verwendet habe, wenn die Zustandsmaschine sehr einfach ist:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };

enum state current_state = ST_NEW;

while (current_state != ST_END)
{
    input = get_input();

    switch (current_state)
    {
        case ST_NEW:
        /* Do something with input and set current_state */
        break;

        case ST_OPEN:
        /* Do something different and set current_state */
        break;

        /* ... etc ... */
    }
}

Ich würde dies verwenden, wenn die Zustandsmaschine so einfach ist, dass der Ansatz für Funktionszeiger und Zustandsübergangstabelle übertrieben ist. Dies ist häufig nützlich für die Zeichenweise oder Word-für-Word-Analyse.

76
caf

Verzeihen Sie mir, dass Sie jede Regel in der Informatik gebrochen haben, aber eine Zustandsmaschine ist eine der wenigen Stellen (ich kann nur zwei davon zählen), an denen eine goto-Anweisung nicht nur effizienter ist, sondern auch den Code sauberer und lesbarer macht. Da goto-Anweisungen auf Labels basieren, können Sie Ihren Status benennen, anstatt ein Durcheinander von Zahlen zu verfolgen oder eine Aufzählung zu verwenden. Dies sorgt auch für viel saubereren Code, da Sie nicht die zusätzliche Menge an Funktionszeigern oder große Schalteranweisungen und while-Schleifen benötigen. Habe ich schon erwähnt, dass es auch effizienter ist?

So könnte eine Zustandsmaschine aussehen:

void state_machine() {
first_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }

second_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }
}

Sie bekommen die allgemeine Idee. Der Punkt ist, dass Sie die Zustandsmaschine auf effiziente Weise implementieren können und die relativ einfach zu lesen ist und den Leser anschreit, dass er eine Zustandsmaschine betrachtet. Wenn Sie goto-Anweisungen verwenden, müssen Sie dennoch vorsichtig sein, da Sie sich dabei sehr leicht in den Fuß schießen können.

34
Jason E

Sie könnten den State Machine Compiler in Betracht ziehen http://smc.sourceforge.net/

Dieses großartige Open Source-Dienstprogramm akzeptiert eine Beschreibung einer Zustandsmaschine in einer einfachen Sprache und kompiliert sie in eine von etwa zehn Sprachen - einschließlich C und C++. Das Dienstprogramm selbst ist in Java geschrieben und kann Bestandteil eines Builds sein.

Der Grund dafür ist, anstelle der Handcodierung mithilfe des GoF-Statusmusters oder eines anderen Ansatzes, dass die zugrunde liegende Struktur, sobald Ihre Statusmaschine als Code ausgedrückt wird, dazu neigt, unter dem Gewicht der Boilerplate zu verschwinden, die zur Unterstützung generiert werden muss. Wenn Sie diesen Ansatz verwenden, erhalten Sie eine ausgezeichnete Trennung der Anliegen, und Sie behalten die Struktur Ihrer Zustandsmaschine "sichtbar". Der automatisch generierte Code wird in Modulen gespeichert, die Sie nicht berühren müssen, sodass Sie mit der Struktur der Statusmaschine zurückkehren können, ohne den von Ihnen geschriebenen unterstützenden Code zu beeinträchtigen.

Tut mir leid, ich bin zu begeistert und bezweifle zweifellos alle. Aber es ist ein erstklassiges Dienstprogramm und auch gut dokumentiert.

29
willw

Achten Sie darauf, die Arbeit von Miro Samek (Blog State Space , Website State Machines & Tools ), dessen Artikel bei den C/C++ - Benutzern Tagebuch waren super.

Die Website enthält eine vollständige (C/C++) Implementierung eines Zustandsmaschinen-Frameworks (QP-Framework), eines Ereignishandlers (QEP), in Open Source- und kommerziellen Lizenzen Basic Modeling Tool (QM) und Tracing Tool (QSpy), mit denen Zustandsautomaten gezeichnet, Code erstellt und Fehler behoben werden können.

Das Buch enthält eine ausführliche Erklärung über das Was/Warum der Implementierung und deren Verwendung und ist auch ein großartiges Material, um die Grundlagen von hierarchischen und endlichen Zustandsmaschinen zu verstehen.

Die Website enthält auch Links zu verschiedenen Board-Support-Paketen für die Verwendung der Software mit eingebetteten Plattformen.

20
Daniel Daranas

Ich habe etwas Ähnliches getan, was paxdiablo beschreibt, nur statt eines Arrays von Status-/Ereignisübergängen habe ich ein zweidimensionales Array von Funktionszeigern mit dem Ereigniswert als Index einer Achse und dem aktuellen Statuswert als eingerichtet das andere. Dann rufe ich einfach state = state_table[event][state](params) an und das Richtige passiert. Zellen, die ungültige Status-/Ereigniskombinationen darstellen, erhalten natürlich einen Zeiger auf eine Funktion, die dies sagt. 

Offensichtlich funktioniert dies nur, wenn die Status- und Ereigniswerte zusammenhängende Bereiche sind und bei 0 beginnen oder nahe genug sind.

11
ceo

Ein sehr schönes Template-basiertes C++ - State-Machine-Framework wird von Stefan Heinzmann in seinem article gegeben.

Da es im Artikel keinen Link zu einem vollständigen Codedownload gibt, habe ich mir die Freiheit genommen, den Code in ein Projekt einzufügen und ihn auszuchecken. Das folgende Material ist getestet und enthält die wenigen kleineren, aber ziemlich offensichtlichen fehlenden Teile.

Die wichtigste Neuerung hierbei ist, dass der Compiler sehr effizienten Code generiert. Leere Ein- und Ausstiegsaktionen haben keine Kosten. Nicht leere Eintritts-/Austrittsaktionen sind eingebettet. Der Compiler überprüft auch die Vollständigkeit des Statecharts. Fehlende Aktionen erzeugen Verknüpfungsfehler. Das einzige, was nicht gefangen wird, ist der fehlende Top::init.

Dies ist eine sehr schöne Alternative zur Implementierung von Miro Samek, wenn Sie ohne das Fehlen leben können, was fehlt. Dies ist weit von einer vollständigen UML Statechart-Implementierung entfernt, obwohl sie die UML-Semantik korrekt implementiert, wohingegen Sameks Code von Design Exit/Transition nicht verarbeitet/Eingangsaktionen in der richtigen Reihenfolge.

Wenn dieser Code für das, was Sie tun müssen, funktioniert und Sie einen anständigen C++ - Compiler für Ihr System haben, wird er wahrscheinlich besser arbeiten als die C/C++ - Implementierung von Miro. Der Compiler generiert für Sie eine vereinfachte Implementierung der Übergangszustandsmaschine O(1). Wenn die Prüfung der Assembly-Ausgabe bestätigt, dass die Optimierungen wie gewünscht funktionieren, kommen Sie der theoretischen Leistung nahe. Das Beste: es ist relativ winziger, leicht verständlicher Code.

#ifndef HSM_HPP
#define HSM_HPP

// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252

/* This is a basic implementation of UML Statecharts.
 * The key observation is that the machine can only
 * be in a leaf state at any given time. The composite
 * states are only traversed, never final.
 * Only the leaf states are ever instantiated. The composite
 * states are only mechanisms used to generate code. They are
 * never instantiated.
 */

// Helpers

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
    class Yes { char a[1]; };
    class No  { char a[10]; };
    static Yes Test(B*); // undefined
    static No Test(...); // undefined
public:
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};

template<bool> class Bool {};

// Top State, Composite State and Leaf State

template <typename H>
struct TopState {
    typedef H Host;
    typedef void Base;
    virtual void handler(Host&) const = 0;
    virtual unsigned getId() const = 0;
};

template <typename H, unsigned id, typename B>
struct CompState;

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
    typedef B Base;
    typedef CompState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
    typedef TopState<H> Base;
    typedef CompState<H, 0, Base> This;
    template <typename X> void handle(H&, const X&) const {}
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
    typedef H Host;
    typedef B Base;
    typedef LeafState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    virtual void handler(H& h) const { handle(h, *this); }
    virtual unsigned getId() const { return id; }
    static void init(H& h) { h.next(obj); } // don't specialize this
    static void entry(H&) {}
    static void exit(H&) {}
    static const LeafState obj; // only the leaf states have instances
};

template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;

// Transition Object

template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
    typedef typename C::Host Host;
    typedef typename C::Base CurrentBase;
    typedef typename S::Base SourceBase;
    typedef typename T::Base TargetBase;
    enum { // work out when to terminate template recursion
        eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
        eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
        eS_C = IsDerivedFrom<S, C>::Res,
        eC_S = IsDerivedFrom<C, S>::Res,
        exitStop = eTB_CB && eS_C,
        entryStop = eS_C || eS_CB && !eC_S
    };
    // We use overloading to stop recursion.
    // The more natural template specialization
    // method would require to specialize the inner
    // template without specializing the outer one,
    // which is forbidden.
    static void exitActions(Host&, Bool<true>) {}
    static void exitActions(Host&h, Bool<false>) {
        C::exit(h);
        Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
    }
    static void entryActions(Host&, Bool<true>) {}
    static void entryActions(Host& h, Bool<false>) {
        Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
        C::entry(h);
    }
    Tran(Host & h) : Host_(h) {
        exitActions(Host_, Bool<false>());
    }
    ~Tran() {
        Tran<T, S, T>::entryActions(Host_, Bool<false>());
        T::init(Host_);
    }
    Host& Host_;
};

// Initializer for Compound States

template <typename T>
struct Init {
    typedef typename T::Host Host;
    Init(Host& h) : Host_(h) {}
    ~Init() {
        T::entry(Host_);
        T::init(Host_);
    }
    Host& Host_;
};

#endif // HSM_HPP

Testcode folgt.

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"

/* Implements the following state machine from Miro Samek's
 * Practical Statecharts in C/C++
 *
 * |-init-----------------------------------------------------|
 * |                           s0                             |
 * |----------------------------------------------------------|
 * |                                                          |
 * |    |-init-----------|        |-------------------------| |
 * |    |       s1       |---c--->|            s2           | |
 * |    |----------------|<--c----|-------------------------| |
 * |    |                |        |                         | |
 * |<-d-| |-init-------| |        | |-init----------------| | |
 * |    | |     s11    |<----f----| |          s21        | | |
 * | /--| |------------| |        | |---------------------| | |
 * | a  | |            | |        | |                     | | |
 * | \->| |            |------g--------->|-init------|    | | |
 * |    | |____________| |        | |-b->|    s211   |---g--->|
 * |    |----b---^       |------f------->|           |    | | |
 * |    |________________|        | |<-d-|___________|<--e----|
 * |                              | |_____________________| | |
 * |                              |_________________________| |
 * |__________________________________________________________|
 */

class TestHSM;

typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };

class TestHSM {
public:
    TestHSM() { Top::init(*this); }
    ~TestHSM() {}
    void next(const TopState<TestHSM>& state) {
        state_ = &state;
    }
    Signal getSig() const { return sig_; }
    void dispatch(Signal sig) {
        sig_ = sig;
        state_->handler(*this);
    }
    void foo(int i) {
        foo_ = i;
    }
    int foo() const {
        return foo_;
    }
private:
    const TopState<TestHSM>* state_;
    Signal sig_;
    int foo_;
};

bool testDispatch(char c) {
    static TestHSM test;
    if (c<'a' || 'h'<c) {
        return false;
    }
    printf("Signal<-%c", c);
    test.dispatch((Signal)(c-'a'));
    printf("\n");
    return true;
}

int main(int, char**) {
    testDispatch('a');
    testDispatch('e');
    testDispatch('e');
    testDispatch('a');
    testDispatch('h');
    testDispatch('h');
    return 0;
}

#define HSMHANDLER(State) \
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const

HSMHANDLER(S0) {
    switch (h.getSig()) {
    case E_SIG: { Tran<X, This, S211> t(h);
        printf("s0-E;");
        return; }
    default:
        break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S1) {
    switch (h.getSig()) {
    case A_SIG: { Tran<X, This, S1> t(h);
        printf("s1-A;"); return; }
    case B_SIG: { Tran<X, This, S11> t(h);
        printf("s1-B;"); return; }
    case C_SIG: { Tran<X, This, S2> t(h);
        printf("s1-C;"); return; }
    case D_SIG: { Tran<X, This, S0> t(h);
        printf("s1-D;"); return; }
    case F_SIG: { Tran<X, This, S211> t(h);
        printf("s1-F;"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S11) {
    switch (h.getSig()) {
    case G_SIG: { Tran<X, This, S211> t(h);
        printf("s11-G;"); return; }
    case H_SIG: if (h.foo()) {
            printf("s11-H");
            h.foo(0); return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S2) {
    switch (h.getSig()) {
    case C_SIG: { Tran<X, This, S1> t(h);
        printf("s2-C"); return; }
    case F_SIG: { Tran<X, This, S11> t(h);
        printf("s2-F"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S21) {
    switch (h.getSig()) {
    case B_SIG: { Tran<X, This, S211> t(h);
        printf("s21-B;"); return; }
    case H_SIG: if (!h.foo()) {
            Tran<X, This, S21> t(h);
            printf("s21-H;"); h.foo(1);
            return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S211) {
    switch (h.getSig()) {
    case D_SIG: { Tran<X, This, S21> t(h);
        printf("s211-D;"); return; }
    case G_SIG: { Tran<X, This, S0> t(h);
        printf("s211-G;"); return; }
    }
    return Base::handle(h, x);
}

#define HSMENTRY(State) \
    template<> inline void State::entry(TestHSM&) { \
        printf(#State "-ENTRY;"); \
    }

HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)

#define HSMEXIT(State) \
    template<> inline void State::exit(TestHSM&) { \
        printf(#State "-EXIT;"); \
    }

HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)

#define HSMINIT(State, InitState) \
    template<> inline void State::init(TestHSM& h) { \
       Init<InitState> i(h); \
       printf(#State "-INIT;"); \
    }

HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)
9
Kuba Ober

Einfachster Fall

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum { THIS, THAT } state;
  switch (state)
  {
    case THIS:
    switch (event)
    {
      case ET_THIS:
      // Handle event.
      break;

      default:
      // Unhandled events in this state.
      break;
    }
    break;

    case THAT:
    // Handle state.
    break;
  }
}

Punkte: State ist privat, nicht nur für die Kompilierungseinheit, sondern auch für den event_handler . Sonderfälle können getrennt vom Hauptschalter behandelt werden, wobei das Konstrukt verwendet wird, das als notwendig erachtet wird.

komplexer Fall

Wenn der Schalter größer als ein paar Bildschirme wird, teilen Sie ihn in Funktionen auf, die jeden Status behandeln, und verwenden Sie eine Statustabelle, um die Funktion direkt abzurufen. Der Staat ist dem Eventhandler immer noch privat. Die Zustandsbehandlungsfunktionen geben den nächsten Zustand zurück. Bei Bedarf können einige Ereignisse im Main Event Handler noch gesondert behandelt werden. Ich mag es, Pseudo-Ereignisse für den Zustandseintritt und -ausgang und vielleicht den Zustandsmaschinenstart einzubringen:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum state_type state;
  static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
  enum state_type next_state = state_handler[state](event, parm);
  if (NA != next_state && state != next_state)
  {
    (void)state_handler[state](ET_EXIT, 0);
    state = next_state;
    (void)state_handler[state](ET_ENTER, 0);
  }
}

Ich bin mir nicht sicher, ob ich die Syntax genau getroffen habe, insbesondere was die Anordnung der Funktionszeiger angeht. Ich habe nichts davon über einen Compiler laufen lassen. Bei der Überprüfung habe ich festgestellt, dass ich vergessen habe, den nächsten Status explizit zu verwerfen, wenn die Pseudoereignisse (die (void)) Klammern vor dem Aufruf von state_handler () behandelt werden. Dies ist etwas, was ich gerne mache, auch wenn Compiler die Auslassung stillschweigend akzeptieren. Es teilt den Lesern des Codes mit, dass "ja, ich wollte die Funktion tatsächlich ohne den Rückgabewert aufrufen", und kann statische Analysewerkzeuge daran hindern, darüber zu warnen. Es kann eigenartig sein, weil ich mich nicht daran erinnern kann, dass jemand anderes dies gesehen hat.

Punkte: Wenn Sie ein wenig Komplexität hinzufügen (prüfen, ob der nächste Status vom aktuellen Status abweicht), kann doppelter Code an anderer Stelle vermieden werden, da die Statushandlerfunktionen die Pseudoereignisse genießen können, die auftreten, wenn ein Status eingegeben und verlassen wird. Beachten Sie, dass sich der Status bei der Verarbeitung der Pseudoereignisse nicht ändern kann, da das Ergebnis des Statushandlers nach diesen Ereignissen verworfen wird. Sie können das Verhalten natürlich ändern. 

Ein State-Handler würde so aussehen:

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
  enum state_type next_state = NA;
  switch (event)
  {
    case ET_ENTER:
    // Start a timer to do whatever.
    // Do other stuff necessary when entering this state.
    break;

    case ET_WHATEVER:
    // Switch state.
    next_state = THAT;
    break;

    case ET_TIMEOUT:
    // Switch state.
    next_state = FOO;
    break;

    case ET_EXIT:
    // Stop the timer.
    // Generally clean up this state.
    break;
  }
  return next_state;
}

Mehr Komplexität

Wenn die Kompilierungseinheit zu groß wird (was immer Sie auch meinen, ich sollte etwa 1000 Zeilen sagen), legen Sie jeden Status-Handler in eine separate Datei. Wenn jeder Zustandshandler länger als ein paar Bildschirme wird, teilen Sie jedes Ereignis in einer separaten Funktion auf, ähnlich wie der Zustandsschalter aufgeteilt wurde. Sie können dies auf verschiedene Arten tun, unabhängig vom Status oder durch Verwendung einer allgemeinen Tabelle oder durch Kombination verschiedener Schemata. Einige von ihnen wurden hier von anderen abgedeckt. Sortieren Sie Ihre Tabellen und verwenden Sie die binäre Suche, wenn Geschwindigkeit erforderlich ist.

Generische Programmierung

Ich möchte, dass der Präprozessor sich mit Themen wie dem Sortieren von Tabellen oder sogar dem Generieren von Zustandsmaschinen aus Beschreibungen befasst, so dass Sie "Programme über Programme schreiben" können. Ich glaube, dafür nutzen die Boost-Leute C++ - Vorlagen, aber ich finde die Syntax kryptisch.

Zweidimensionale Tabellen

Ich habe in der Vergangenheit Zustands-/Ereignistabellen verwendet, aber ich muss sagen, dass ich sie in den einfachsten Fällen nicht für notwendig halte und ich bevorzuge die Klarheit und Lesbarkeit der switch-Anweisung, auch wenn sie einen Bildschirm voll ausfüllt. Für komplexere Fälle geraten die Tische schnell außer Kontrolle, wie andere bemerkt haben. Die Redewendungen, die ich hier präsentiere, ermöglichen Ihnen, eine Reihe von Ereignissen und Zuständen hinzuzufügen, wenn Sie möchten, ohne eine speicherverbrauchende Tabelle führen zu müssen (selbst wenn es sich um Programmspeicher handelt). 

Haftungsausschluss

Besondere Bedürfnisse können diese Redewendungen weniger nützlich machen, aber ich habe sie für sehr klar und wartbar gehalten.

5
Joe the Hamster

Die Technik, die ich für Zustandsmaschinen (zumindest für die Programmsteuerung) mag, ist die Verwendung von Funktionszeigern. Jeder Zustand wird durch eine andere Funktion dargestellt. Die Funktion nimmt ein Eingangssymbol und gibt den Funktionszeiger für den nächsten Zustand zurück. Die zentrale Dispatch-Schleife überwacht die nächste Eingabe, führt sie in den aktuellen Status ein und verarbeitet das Ergebnis.

Das Tippen darauf wird etwas seltsam, da C keine Möglichkeit hat, Typen von Funktionszeigern anzugeben, die sich selbst zurückgeben, so dass die Zustandsfunktionen void* zurückgeben. Aber du kannst so etwas machen:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
    state_handler current = initial_handler;
    /* Let's assume returning null indicates end-of-machine */
    while (current) {
        current = current(get_input);
    }
 }

Dann können Ihre einzelnen Zustandsfunktionen ihre Eingabe einschalten und den entsprechenden Wert zurückgeben.

5

Ein weiteres interessantes Open Source-Tool ist Yakindu Statechart Tools auf statecharts.org . Es verwendet Harel-Zustandsdiagramme, stellt somit hierarchische und parallele Zustände bereit und generiert C- und C++ - Code (sowie Java). Es verwendet keine Bibliotheken, sondern folgt einem einfachen Code. Der Code wendet grundsätzlich Switch-Case-Strukturen an. Die Codegeneratoren können auch angepasst werden. Zusätzlich bietet das Tool viele weitere Funktionen.

4
Axel T.

Die Antwort von paxdiable hat mir sehr gefallen und ich habe mich dazu entschlossen, alle fehlenden Funktionen für meine Anwendung zu implementieren, wie z. 

Ich habe meine Implementierung auf diese Site hochgeladen, um sie mit der Community zu teilen. Es wurde mit der IAR Embedded Workbench für ARM getestet.

https://sourceforge.net/projects/compactfsm/

4
user108570

Extrem ungetestet, aber Spaß am Code, jetzt in einer verfeinerten Version als meine ursprüngliche Antwort. Aktuelle Versionen finden Sie unter Mercurial.intuxication.org :

sm.h

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
    "use '#define SM_ARGS (void)' to get an empty argument list"
#endif

#ifndef SM_STATES
#error "SM_STATES undefined: " \
    "you must provide a list of comma-separated states"
#endif

typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)

#define sm_def(NAME) \
    static sm_state NAME ## _fn SM_ARGS; \
    static const sm_state NAME = (sm_state)NAME ## _fn; \
    static sm_state NAME ## _fn SM_ARGS

example.c

#include <stdio.h>

#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"

sm_def(EVEN)
{
    printf("even %i\n", i);
    return ODD;
}

sm_def(ODD)
{
    printf("odd  %i\n", i);
    return EVEN;
}

int main(void)
{
    int i = 0;
    sm_state state = EVEN;

    for(; i < 10; ++i)
        state = sm_transit(state)(i);

    return 0;
}
4
Christoph

Ich glaube, Mine ist etwas anders als alle anderen. Ein bisschen mehr Trennung von Code und Daten als ich in den anderen Antworten sehe. Ich habe wirklich die Theorie gelesen, um dies zu schreiben, was eine vollständige reguläre Sprache (ohne reguläre Ausdrücke, leider) implementiert. Ullman, Minsky, Chomsky. Ich kann nicht sagen, dass ich alles verstanden habe, aber ich habe so direkt wie möglich von den alten Meistern gezogen: durch ihre Worte.

Ich benutze einen Funktionszeiger auf ein Prädikat, das den Übergang in einen 'Ja'-Zustand oder einen' Nein'-Zustand bestimmt. Dies erleichtert die Erstellung eines endlichen Zustandsakzeptors für eine reguläre Sprache, die Sie auf eine mehr Assembly-ähnliche Sprache programmieren. Bitte lassen Sie sich nicht von meinen dummen Namenwahl abschrecken. 'czek' == 'check'. 'grok' == [im Hacker-Wörterbuch nachschlagen].

Daher ruft czek für jede Iteration eine Prädikatsfunktion mit dem aktuellen Zeichen als Argument auf. Wenn das Prädikat true zurückgibt, wird das Zeichen verbraucht (der Zeiger wird vorgerückt) und wir folgen dem Übergang 'y', um den nächsten Status auszuwählen. Wenn das Prädikat false zurückgibt, wird das Zeichen NICHT konsumiert und wir folgen dem Übergang 'n'. Also ist jede Anweisung ein Zweiwegzweig! Ich muss damals die Geschichte von Mel gelesen haben.

Dieser Code stammt direkt aus meinem Postscript-Interpreter und wurde unter Anleitung der Stipendiaten zu comp.lang.c. in seine jetzige Form gebracht. Da Postscript im Wesentlichen keine Syntax hat (nur ausgeglichene Klammern erforderlich), fungiert ein regulärer Sprachakzeptor wie dieser auch als Parser. 

/* currentstr is set to the start of string by czek
   and used by setrad (called by israd) to set currentrad
   which is used by israddig to determine if the character
   in question is valid for the specified radix
   --
   a little semantic checking in the syntax!
 */
char *currentstr;
int currentrad;
void setrad(void) {
    char *end;
    currentrad = strtol(currentstr, &end, 10);
    if (*end != '#' /* just a sanity check,
                       the automaton should already have determined this */
    ||  currentrad > 36
    ||  currentrad < 2)
        fatal("bad radix"); /* should probably be a simple syntaxerror */
}

/*
   character classes
   used as tests by automatons under control of czek
 */
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
    if (EQ('#',c)) { setrad(); return true; }
    return false;
}
int israddig(int c) {
    return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ

/*
   the automaton type
 */
typedef struct { int (*pred)(int); int y, n; } test;

/*
   automaton to match a simple decimal number
 */
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }

/*
   automaton to match a radix number
 */
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }

/*
   automaton to match a real number
 */
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
   [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
   [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
   The complexity comes from ensuring at least one
   digit in the integer or the fraction with optional
   sign and optional optionally-signed exponent.
   So passing isdot in state 3 means at least one integer digit has been found
   but passing isdot in state 4 means we must find at least one fraction digit
   via state 5 or the whole thing is a bust.
 */
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
    switch(i) {
        case 2: /* integer */
        case 6: /* real */
        case 10: /* real with exponent */
            return true;
    }
    return false;
}

/*
   Helper function for grok.
   Execute automaton against the buffer,
   applying test to each character:
       on success, consume character and follow 'y' transition.
       on failure, do not consume but follow 'n' transition.
   Call yes function to determine if the ending state
   is considered an acceptable final state.
   A transition to -1 represents rejection by the automaton
 */
int czek (char *s, test *fsm, int (*yes)(int)) {
    int sta = 0;
    currentstr = s;
    while (sta!=-1 && *s) {
        if (fsm[sta].pred((int)*s)) {
            sta=fsm[sta].y;
            s++;
        } else {
            sta=fsm[sta].n;
        }
    }
    return yes(sta);
}

/*
   Helper function for toke.
   Interpret the contents of the buffer,
   trying automatons to match number formats;
   and falling through to a switch for special characters.
   Any token consisting of all regular characters
   that cannot be interpreted as a number is an executable name
 */
object grok (state *st, char *s, int ns,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {

    if (czek(s, fsm_dec, acc_dec)) {
        long num;
        num = strtol(s,NULL,10);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_rad, acc_rad)) {
        long ra,num;
        ra = (int)strtol(s,NULL,10);
        if (ra > 36 || ra < 2) {
            error(st,limitcheck);
        }
        num = strtol(strchr(s,'#')+1, NULL, (int)ra);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_real, acc_real)) {
        double num;
        num = strtod(s,NULL);
        if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
            error(st,limitcheck);
        } else {
            return consreal(num);
        }
    }

    else switch(*s) {
        case '(': {
            int c, defer=1;
            char *sp = s;

            while (defer && (c=next(st,src)) != EOF ) {
                switch(c) {
                    case '(': defer++; break;
                    case ')': defer--;
                        if (!defer) goto endstring;
                        break;
                    case '\\': c=next(st,src);
                        switch(c) {
                            case '\n': continue;
                            case 'a': c = '\a'; break;
                            case 'b': c = '\b'; break;
                            case 'f': c = '\f'; break;
                            case 'n': c = '\n'; break;
                            case 'r': c = '\r'; break;
                            case 't': c = '\t'; break;
                            case 'v': c = '\v'; break;
                            case '\'': case '\"':
                            case '(': case ')':
                            default: break;
                        }
                }
                if (sp-s>ns) error(st,limitcheck);
                else *sp++ = c;
            }
endstring:  *sp=0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '<': {
            int c;
            char d, *x = "0123456789abcdef", *sp = s;
            while (c=next(st,src), c!='>' && c!=EOF) {
                if (isspace(c)) continue;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d = (char)c << 4;
                while (isspace(c=next(st,src))) /*loop*/;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d |= (char)c;
                if (sp-s>ns) error(st,limitcheck);
                *sp++ = d;
            }
            *sp = 0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '{': {
            object *a;
            size_t na = 100;
            size_t i;
            object proc;
            object fin;

            fin = consname(st,"}");
            (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
            for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
                if (i == na-1)
                (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
            }
            proc = consarray(st,i);
            { size_t j;
                for (j=0; j<i; j++) {
                    a_put(st, proc, j, a[j]);
                }
            }
            free(a);
            return proc;
        }

        case '/': {
            s[1] = (char)next(st,src);
            puff(st, s+2, ns-2, src, next, back);
            if (s[1] == '/') {
                Push(consname(st,s+2));
                opexec(st, op_cuts.load);
                return pop();
            }
            return cvlit(consname(st,s+1));
        }

        default: return consname(st,s);
    }
    return null; /* should be unreachable */
}

/*
   Helper function for toke.
   Read into buffer any regular characters.
   If we read one too many characters, put it back
   unless it's whitespace.
 */
int puff (state *st, char *buf, int nbuf,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {
    int c;
    char *s = buf;
    while (isreg(c=next(st,src))) {
        if (s-buf >= nbuf-1) return false;
        *s++ = c;
    }
    *s = 0;
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
    return true;
}

/*
   Helper function for Stoken Ftoken.
   Read a token from src using next and back.
   Loop until having read a bona-fide non-whitespace non-comment character.
   Call puff to read into buffer up to next delimiter or space.
   Call grok to figure out what it is.
 */
#define NBUF MAXLINE
object toke (state *st, object *src,
        int (*next)(state *, object *),
        void (*back)(state *, int, object *)) {
    char buf[NBUF] = "", *s=buf;
    int c,sta = 1;
    object o;

    do {
        c=next(st,src);
        //if (c==EOF) return null;
        if (c=='%') {
            if (DUMPCOMMENTS) fputc(c, stdout);
            do {
                c=next(st,src);
                if (DUMPCOMMENTS) fputc(c, stdout);
            } while (c!='\n' && c!='\f' && c!=EOF);
        }
    } while (c!=EOF && isspace(c));
    if (c==EOF) return null;
    *s++ = c;
    *s = 0;
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);

    if (sta) {
        o=grok(st,buf,NBUF-1,src,next,back);
        return o;
    } else {
        return null;
    }
}
3
luser droog

im Lieferumfang von boost.org sind zwei verschiedene Zustandsdiagramm-Implementierungen enthalten:

Wie immer wird Boost Sie in die Hölle strahlen.

Die erste Bibliothek ist für leistungskritische Zustandsmaschinen gedacht. Die zweite Bibliothek bietet Ihnen einen direkten Übergangspfad von einem UML-Zustandsdiagramm zu Code.

Hier ist die SO - Frage, in der nach einem Vergleich zwischen den beiden gefragt wird, wo beide Autoren antworten.

3
Roland Wolf

Zu spät kommen (wie üblich), aber das Scannen der Antworten bis heute denke ich, dass etwas Wichtiges fehlt.

Ich habe in meinen eigenen Projekten herausgefunden, dass es sehr hilfreich sein kann, nicht für jede gültige Status/Ereignis-Kombination eine Funktion zu haben. Ich mag die Idee, effektiv eine 2D-Tabelle von Zuständen/Ereignissen zu haben. Aber ich mag die Tabellenelemente mehr als einen einfachen Funktionszeiger. Stattdessen versuche ich, mein Design so zu organisieren, dass es im Kern aus einer Reihe einfacher atomarer Elemente oder Aktionen besteht. Auf diese Weise kann ich diese einfachen atomaren Elemente an jedem Schnittpunkt meiner Zustands-/Ereignistabelle auflisten. Die Idee ist, dass Sie nicht eine Masse von N quadrierten (normalerweise sehr einfachen) Funktionen definieren müssen. Warum etwas so fehleranfällig, zeitaufwändig, schwer zu schreiben, schwer zu lesen, nennen Sie es?

Ich füge auch einen optionalen neuen Status und einen optionalen Funktionszeiger für jede Zelle in der Tabelle ein. Der Funktionszeiger ist für die Ausnahmefälle vorhanden, in denen Sie nicht einfach eine Liste atomarer Aktionen auslösen möchten.

Sie wissen, dass Sie es richtig machen, wenn Sie viele verschiedene Funktionen ausführen können, indem Sie einfach Ihre Tabelle bearbeiten und keinen neuen Code schreiben.

3
Bill Forster

Diese Serie von Ars OpenForum-Beiträgen über ein etwas kompliziertes Stück Steuerlogik enthält eine sehr leicht zu befolgende Implementierung als Zustandsmaschine in C.

2
Steven Huwig

Sah das irgendwo

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

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

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

Da Sie implizieren, dass Sie C++ und damit OO Code verwenden können, würde ich vorschlagen, das "GoF" -Statemuster auszuwerten (GoF = Gang of Four, die Jungs, die das Design Pattern-Buch geschrieben haben, das Designmuster ins Rampenlicht brachte) .

Es ist nicht besonders komplex und wird häufig verwendet und diskutiert, so dass es leicht ist, Beispiele und Erklärungen online zu sehen.

Es ist wahrscheinlich auch für jeden erkennbar, der zu einem späteren Zeitpunkt Ihren Code verwaltet.

Wenn Effizienz die Sorge ist, lohnt es sich tatsächlich, ein Benchmarking durchzuführen, um sicherzustellen, dass ein Nicht-OO -Ansatz effizienter ist, da viele Faktoren die Leistung beeinflussen und nicht immer einfach OO schlechter, funktionaler Code gut ist. Wenn die Speichernutzung für Sie eine Einschränkung ist, lohnt es sich ebenfalls, einige Tests oder Berechnungen durchzuführen, um zu sehen, ob dies für Ihre spezielle Anwendung tatsächlich ein Problem ist, wenn Sie das Statusmuster verwenden. 

Im Folgenden finden Sie einige Links zum "Gof" -Statusmuster, wie Craig vorschlägt:

2
Mick

Ihre Frage ist ziemlich allgemein
Hier sind zwei Referenzartikel, die nützlich sein könnten: 

  1. Embedded State Machine Implementierung

    Dieser Artikel beschreibt einen einfachen Ansatz zum Implementieren einer Zustandsmaschine für ein eingebettetes System. In diesem Artikel wird eine Zustandsmaschine als ein Algorithmus definiert, der sich in einem von wenigen Zuständen befinden kann. Ein Zustand ist eine Bedingung, die eine vorgeschriebene Beziehung von Eingaben zu Ausgängen und von Eingaben zu nächsten Zuständen bewirkt.
    Ein versierter Leser wird schnell feststellen, dass die in diesem Artikel beschriebenen Zustandsmaschinen Mealy-Maschinen sind. Eine Mealy-Maschine ist eine Zustandsmaschine, bei der die Ausgaben eine Funktion sowohl des aktuellen Zustands als auch der Eingabe sind, im Gegensatz zu einer Moore-Maschine, bei der die Ausgaben nur eine Zustandsfunktion sind.

    • Codieren von Zustandsautomaten in C und C++

      In diesem Artikel beschäftige ich mich mit den Grundlagen der Zustandsmaschine und einigen einfachen Programmierrichtlinien für das Kodieren von Zustandsmaschinen in C oder C++. Ich hoffe, dass diese einfachen Techniken allgemeiner werden können, sodass Sie (und andere) die Zustandsmaschinenstruktur direkt aus dem Quellcode erkennen können. 

1
nik

Dies ist ein alter Beitrag mit vielen Antworten, aber ich dachte, ich würde der endlichen Zustandsmaschine in C einen eigenen Ansatz hinzufügen. Ich habe ein Python-Skript erstellt, um den C-Code für eine beliebige Anzahl von Zuständen zu erstellen. Dieses Skript ist auf GituHub unter FsmTemplateC dokumentiert.

Dieses Beispiel basiert auf anderen Ansätzen, über die ich gelesen habe. Es verwendet keine goto- oder switch-Anweisungen, sondern verfügt stattdessen über Übergangsfunktionen in einer Zeigermatrix (Nachschlagetabelle). Der Code basiert auf einem großen Multi-Line-Initializer-Makro und C99-Funktionen (als Initialisierer und zusammengesetzte Literale bezeichnet). Wenn Sie diese Dinge nicht mögen, mögen Sie diesen Ansatz möglicherweise nicht. 

Hier ist ein Python-Skript eines Turnstile-Beispiels , das unter Verwendung von FsmTemplateC den skeleton C-Code generiert:

# dict parameter for generating FSM
fsm_param = {
    # main FSM struct type string
    'type': 'FsmTurnstile',
    # struct type and name for passing data to state machine functions
    # by pointer (these custom names are optional)
    'fopts': {
        'type': 'FsmTurnstileFopts',
        'name': 'fopts'
    },
    # list of states
    'states': ['locked', 'unlocked'],
    # list of inputs (can be any length > 0)
    'inputs': ['coin', 'Push'],
    # map inputs to commands (next desired state) using a transition table
    # index of array corresponds to 'inputs' array
    # for this example, index 0 is 'coin', index 1 is 'Push'
    'transitiontable': {
        # current state |  'coin'  |  'Push'  |
        'locked':       ['unlocked',        ''],
        'unlocked':     [        '',  'locked']
    }
}

# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'

# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

Der generierte Ausgabe-Header enthält die Typedefs:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
    /* define your options struct here */
} FsmTurnstileFopts;

/* transition check */
typedef enum eFsmTurnstileCheck {
    EFSM_TURNSTILE_TR_RETREAT,
    EFSM_TURNSTILE_TR_ADVANCE,
    EFSM_TURNSTILE_TR_CONTINUE,
    EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;

/* states (enum) */
typedef enum eFsmTurnstileState {
    EFSM_TURNSTILE_ST_LOCKED,
    EFSM_TURNSTILE_ST_UNLOCKED,
    EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;

/* inputs (enum) */
typedef enum eFsmTurnstileInput {
    EFSM_TURNSTILE_IN_COIN,
    EFSM_TURNSTILE_IN_Push,
    EFSM_TURNSTILE_NUM_INPUTS,
    EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;

/* finite state machine struct */
typedef struct FsmTurnstile {
    eFsmTurnstileInput input;
    eFsmTurnstileCheck check;
    eFsmTurnstileState cur;
    eFsmTurnstileState cmd;
    eFsmTurnstileState **transition_table;
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;

/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheck wird verwendet, um zu bestimmen, ob ein Übergang mit EFSM_TURNSTILE_TR_RETREAT blockiert wurde, mit EFSM_TURNSTILE_TR_ADVANCE fortfahren kann oder ob dem Funktionsaufruf kein Übergang mit EFSM_TURNSTILE_TR_CONTINUE vorangestellt wurde.
  • enum eFsmTurnstileState ist einfach die Liste der Zustände.
  • enum eFsmTurnstileInput ist einfach die Liste der Eingaben.
  • Die FsmTurnstile struct ist das Herzstück der Zustandsmaschine mit der Übergangsprüfung, der Funktionsnachschlagetabelle, dem aktuellen Status, dem befohlenen Status und einem Alias ​​für die primäre Funktion, die die Maschine ausführt.
  • Jeder Funktionszeiger (Alias) in FsmTurnstile sollte nur aus der Struktur aufgerufen werden und muss seine erste Eingabe als Zeiger auf sich selbst haben, um einen persistenten, objektorientierten Stil zu erhalten.

Nun zu den Funktionsdeklarationen im Header:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

Funktionsnamen haben das Format {prefix}_{from}_{to}, wobei {from} der vorherige (aktuelle) Status und {to} der nächste Status ist. Wenn die Übergangstabelle bestimmte Übergänge nicht zulässt, wird ein NULL-Zeiger anstelle eines Funktionszeigers gesetzt. Schließlich geschieht die Magie mit einem Makro. Hier erstellen wir die Übergangstabelle (Matrix von Zustandsennummen) und die Zustandsübergangsfunktionen suchen Tabelle (eine Matrix von Funktionszeigern):

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
    .input = EFSM_TURNSTILE_NOINPUT, \
    .check = EFSM_TURNSTILE_TR_CONTINUE, \
    .cur = EFSM_TURNSTILE_ST_LOCKED, \
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        }, \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        } \
    }, \
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_locked_locked, \
            fsm_turnstile_locked_unlocked \
        }, \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_unlocked_locked, \
            fsm_turnstile_unlocked_unlocked \
        } \
    }, \
    .run = fsm_turnstile_run \
}

Bei der Erstellung des FSM muss das Makro FSM_EXAMPLE_CREATE() verwendet werden. 

Im Quellcode sollte nun jede oben deklarierte Zustandsübergangsfunktion gefüllt werden. Die FsmTurnstileFopts-Struktur kann verwendet werden, um Daten an/von der Zustandsmaschine zu übergeben. Jeder Übergang muss fsm->check so einstellen, dass er entweder EFSM_EXAMPLE_TR_RETREAT entspricht, um den Übergang zu verhindern, oder EFSM_EXAMPLE_TR_ADVANCE, um den Übergang in den befohlenen Zustand zu ermöglichen. Ein Arbeitsbeispiel finden Sie unter (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC] .

Hier ist die sehr einfache tatsächliche Verwendung in Ihrem Code: 

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
    .msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;

/* main loop */
for (;;) {
    /* wait for timer signal, inputs, interrupts, whatever */
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_Push for example) */
    /* run state machine */
    my_fsm.run(&my_fsm, &my_fopts, my_input);
}

Das ganze Header-Geschäft und all diese Funktionen, nur um eine einfache und schnelle Schnittstelle zu haben, ist es meiner Meinung nach wert.

1
ChisholmKyle

Ich habe State Machine Compiler in Java- und Python-Projekten mit Erfolg eingesetzt.

1
user177800
void (* StateController)(void); 
void state1(void);
void state2(void);

void main()
{
 StateController=&state1;
 while(1)
 {
  (* StateController)();
 }
}

void state1(void)
{
 //do something in state1
 StateController=&state2;
}

void state2(void)
{
 //do something in state2
 //Keep changing function direction based on state transition
 StateController=&state1;
}
0

Sie können die Open-Source-Bibliothek OpenFST verwenden.

OpenFst ist eine Bibliothek zum Erstellen, Kombinieren, Optimieren und Suchen von gewichteten Finite-State-Transducern (FSTs). Gewichtete Finite-State-Transducer sind Automaten, bei denen jeder Übergang eine Eingangsbezeichnung, eine Ausgangsbezeichnung und eine Gewichtung aufweist. Der bekanntere Akzeptor im endlichen Zustand wird als Transducer dargestellt, wobei die Eingangs- und Ausgangskennungen jedes Übergangs gleich sind. Akzeptoren mit endlichen Zuständen werden verwendet, um Stringsätze darzustellen (insbesondere reguläre oder rationale Sets). Finite-State-Transducer werden verwendet, um binäre Beziehungen zwischen Strangpaaren darzustellen (insbesondere rationale Transduktionen). Die Gewichte können verwendet werden, um die Kosten eines bestimmten Übergangs darzustellen.

0
Vicky Chijwani

Ich persönlich verwende selbstverweisende Strukturen in Kombination mit Zeigerarrays . Ich habe vor einiger Zeit ein Tutorial zu Github hochgeladen. Link: 

https://github.com/mmelchger/polling_state_machine_c

Hinweis: Ich weiß, dass dieser Thread ziemlich alt ist, aber ich hoffe, Anregungen und Gedanken zum Design der Zustandsmaschine zu bekommen und ein Beispiel für ein mögliches Zustandsmaschinendesign in C geben zu können.

0
mmoment

Hier ist ein Beispiel für eine Finite State Machine für Linux, die Nachrichtenwarteschlangen als Ereignisse verwendet. Die Ereignisse werden in die Warteschlange gestellt und in der richtigen Reihenfolge behandelt. Der Status ändert sich je nachdem, was für jedes Ereignis passiert.

Dies ist ein Beispiel für eine Datenverbindung mit Zuständen wie:

  • Nicht initialisiert
  • Initialisiert
  • In Verbindung gebracht
  • MTU ausgehandelt
  • Authentifiziert

Eine kleine zusätzliche Funktion, die ich hinzugefügt habe, war ein Zeitstempel für jede Nachricht/jedes Ereignis. Der Ereignishandler ignoriert Ereignisse, die zu alt sind (sie sind abgelaufen). Dies kann in der realen Welt häufig vorkommen, wenn Sie unerwartet in einem Zustand stecken bleiben.

Dieses Beispiel läuft unter Linux. Verwenden Sie das unten stehende Makefile, um es zu kompilieren und damit herumzuspielen.

state_machine.c

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>   // sysconf()
#include <errno.h>    // errno
#include <string.h>   // strerror()
#include <sys/time.h> // gettimeofday()
#include <fcntl.h>    // For O_* constants
#include <sys/stat.h> // For mode constants

#include <mqueue.h>
#include <poll.h>

//------------------------------------------------
// States
//------------------------------------------------
typedef enum
{
    ST_UNKNOWN = 0,
    ST_UNINIT,
    ST_INIT,
    ST_CONNECTED,
    ST_MTU_NEGOTIATED,
    ST_AUTHENTICATED,
    ST_ERROR,
    ST_DONT_CHANGE,
    ST_TERM,
} fsmState_t;

//------------------------------------------------
// Events
//------------------------------------------------
typedef enum
{
    EV_UNKNOWN = 0,
    EV_INIT_SUCCESS,
    EV_INIT_FAIL,
    EV_MASTER_CMD_MSG,
    EV_CONNECT_SUCCESS,
    EV_CONNECT_FAIL,
    EV_MTU_SUCCESS,
    EV_MTU_FAIL,
    EV_AUTH_SUCCESS,
    EV_AUTH_FAIL,
    EV_TX_SUCCESS,
    EV_TX_FAIL,
    EV_DISCONNECTED,
    EV_DISCON_FAILED,
    EV_LAST_ENTRY,
} fsmEvName_t;

typedef struct fsmEvent_type
{
    fsmEvName_t name;
    struct timeval genTime; // Time the event was generated.
                            // This allows us to see how old the event is.
} fsmEvent_t;

// Finite State Machine Data Members
typedef struct fsmData_type
{
    int  connectTries;
    int  MTUtries;
    int  authTries;
    int  txTries;
} fsmData_t;

// Each row of the state table
typedef struct stateTable_type {
    fsmState_t  st;             // Current state
    fsmEvName_t evName;         // Got this event
    int (*conditionfn)(void *);  // If this condition func returns TRUE
    fsmState_t nextState;       // Change to this state and
    void (*fn)(void *);          // Run this function
} stateTable_t;

// Finite State Machine state structure
typedef struct fsm_type
{
    const stateTable_t *pStateTable; // Pointer to state table
    int        numStates;            // Number of entries in the table
    fsmState_t currentState;         // Current state
    fsmEvent_t currentEvent;         // Current event
    fsmData_t *fsmData;              // Pointer to the data attributes
    mqd_t      mqdes;                // Message Queue descriptor
    mqd_t      master_cmd_mqdes;     // Master command message queue
} fsm_t;

// Wildcard events and wildcard state
#define   EV_ANY    -1
#define   ST_ANY    -1
#define   TRUE     (1)
#define   FALSE    (0)

// Maximum priority for message queues (see "man mq_overview")
#define FSM_PRIO  (sysconf(_SC_MQ_PRIO_MAX) - 1)

static void addev                              (fsm_t *fsm, fsmEvName_t ev);
static void doNothing                          (void *fsm) {addev(fsm, EV_MASTER_CMD_MSG);}
static void doInit                             (void *fsm) {addev(fsm, EV_INIT_SUCCESS);}
static void doConnect                          (void *fsm) {addev(fsm, EV_CONNECT_SUCCESS);}
static void doMTU                              (void *fsm) {addev(fsm, EV_MTU_SUCCESS);}
static void reportFailConnect                  (void *fsm) {addev(fsm, EV_ANY);}
static void doAuth                             (void *fsm) {addev(fsm, EV_AUTH_SUCCESS);}
static void reportDisConnect                   (void *fsm) {addev(fsm, EV_ANY);}
static void doDisconnect                       (void *fsm) {addev(fsm, EV_ANY);}
static void doTransaction                      (void *fsm) {addev(fsm, EV_TX_FAIL);}
static void fsmError                           (void *fsm) {addev(fsm, EV_ANY);}

static int currentlyLessThanMaxConnectTries    (void *fsm) {
    fsm_t *l = (fsm_t *)fsm;
    return (l->fsmData->connectTries < 5 ? TRUE : FALSE);
}
static int        isMoreThanMaxConnectTries    (void *fsm) {return TRUE;}
static int currentlyLessThanMaxMTUtries        (void *fsm) {return TRUE;}
static int        isMoreThanMaxMTUtries        (void *fsm) {return TRUE;}
static int currentyLessThanMaxAuthTries        (void *fsm) {return TRUE;}
static int       isMoreThanMaxAuthTries        (void *fsm) {return TRUE;}
static int currentlyLessThanMaxTXtries         (void *fsm) {return FALSE;}
static int        isMoreThanMaxTXtries         (void *fsm) {return TRUE;}
static int didNotSelfDisconnect                (void *fsm) {return TRUE;}

static int  waitForEvent                       (fsm_t *fsm);
static void runEvent                           (fsm_t *fsm);
static void runStateMachine(fsm_t *fsm);
static int newEventIsValid(fsmEvent_t *event);
static void getTime(struct timeval *time);
void printState(fsmState_t st);
void printEvent(fsmEvName_t ev);

// Global State Table
const stateTable_t GST[] = {
    // Current state         Got this event          If this condition func returns TRUE     Change to this state and    Run this function
    { ST_UNINIT,             EV_INIT_SUCCESS,        NULL,                                   ST_INIT,                    &doNothing              },
    { ST_UNINIT,             EV_INIT_FAIL,           NULL,                                   ST_UNINIT,                  &doInit                 },
    { ST_INIT,               EV_MASTER_CMD_MSG,      NULL,                                   ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_SUCCESS,     NULL,                                   ST_CONNECTED,               &doMTU                  },
    { ST_INIT,               EV_CONNECT_FAIL,        &currentlyLessThanMaxConnectTries,      ST_INIT,                    &doConnect              },
    { ST_INIT,               EV_CONNECT_FAIL,        &isMoreThanMaxConnectTries,             ST_INIT,                    &reportFailConnect      },
    { ST_CONNECTED,          EV_MTU_SUCCESS,         NULL,                                   ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_CONNECTED,          EV_MTU_FAIL,            &currentlyLessThanMaxMTUtries,          ST_CONNECTED,               &doMTU                  },
    { ST_CONNECTED,          EV_MTU_FAIL,            &isMoreThanMaxMTUtries,                 ST_CONNECTED,               &doDisconnect           },
    { ST_CONNECTED,          EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_MTU_NEGOTIATED,     EV_AUTH_SUCCESS,        NULL,                                   ST_AUTHENTICATED,           &doTransaction          },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &currentyLessThanMaxAuthTries,          ST_MTU_NEGOTIATED,          &doAuth                 },
    { ST_MTU_NEGOTIATED,     EV_AUTH_FAIL,           &isMoreThanMaxAuthTries,                ST_MTU_NEGOTIATED,          &doDisconnect           },
    { ST_MTU_NEGOTIATED,     EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_AUTHENTICATED,      EV_TX_SUCCESS,          NULL,                                   ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &currentlyLessThanMaxTXtries,           ST_AUTHENTICATED,           &doTransaction          },
    { ST_AUTHENTICATED,      EV_TX_FAIL,             &isMoreThanMaxTXtries,                  ST_AUTHENTICATED,           &doDisconnect           },
    { ST_AUTHENTICATED,      EV_DISCONNECTED,        &didNotSelfDisconnect,                  ST_INIT,                    &reportDisConnect       },
    { ST_ANY,                EV_DISCON_FAILED,       NULL,                                   ST_DONT_CHANGE,             &doDisconnect           },
    { ST_ANY,                EV_ANY,                 NULL,                                   ST_UNINIT,                  &fsmError               }    // Wildcard state for errors
};

#define GST_COUNT (sizeof(GST)/sizeof(stateTable_t))

int main()
{
    int ret = 0;
    fsmData_t dataAttr;
    dataAttr.connectTries = 0;
    dataAttr.MTUtries     = 0;
    dataAttr.authTries    = 0;
    dataAttr.txTries      = 0;

    fsm_t lfsm;
    memset(&lfsm, 0, sizeof(fsm_t));
    lfsm.pStateTable       = GST;
    lfsm.numStates         = GST_COUNT;
    lfsm.currentState      = ST_UNINIT;
    lfsm.currentEvent.name = EV_ANY;
    lfsm.fsmData           = &dataAttr;

    struct mq_attr attr;
    attr.mq_maxmsg = 30;
    attr.mq_msgsize = sizeof(fsmEvent_t);

    // Dev info
    //printf("Size of fsmEvent_t [%ld]\n", sizeof(fsmEvent_t));

    ret = mq_unlink("/abcmq");
    if (ret == -1) {
        fprintf(stderr, "Error on mq_unlink(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
    }

    lfsm.mqdes = mq_open("/abcmq", O_CREAT | O_RDWR, S_IWUSR | S_IRUSR, &attr);
    if (lfsm.mqdes == (mqd_t)-1) {
        fprintf(stderr, "Error on mq_open(), errno[%d] strerror[%s]\n",
                errno, strerror(errno));
        return -1;
    }

    doInit(&lfsm);  // This will generate the first event
    runStateMachine(&lfsm);

    return 0;
}


static void runStateMachine(fsm_t *fsm)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    // Cycle through the state machine
    while (fsm->currentState != ST_TERM) {
        printf("current state [");
        printState(fsm->currentState);
        printf("]\n");

        ret = waitForEvent(fsm);
        if (ret == 0) {
            printf("got event [");
            printEvent(fsm->currentEvent.name);
            printf("]\n");

            runEvent(fsm);
        }
        sleep(2);
    }
}


static int waitForEvent(fsm_t *fsm)
{
    //const int numFds = 2;
    const int numFds = 1;
    struct pollfd fds[numFds];
    int timeout_msecs = -1; // -1 is forever
    int ret = 0;
    int i = 0;
    ssize_t num = 0;
    fsmEvent_t newEv;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return -1;
    }

    fsm->currentEvent.name = EV_ANY;

    fds[0].fd     = fsm->mqdes;
    fds[0].events = POLLIN;
    //fds[1].fd     = fsm->master_cmd_mqdes;
    //fds[1].events = POLLIN;
    ret = poll(fds, numFds, timeout_msecs);

    if (ret > 0) {
        // An event on one of the fds has occurred
        for (i = 0; i < numFds; i++) {
            if (fds[i].revents & POLLIN) {
                // Data may be read on device number i
                num = mq_receive(fds[i].fd, (void *)(&newEv),
                                 sizeof(fsmEvent_t), NULL);
                if (num == -1) {
                    fprintf(stderr, "Error on mq_receive(), errno[%d] "
                            "strerror[%s]\n", errno, strerror(errno));
                    return -1;
                }

                if (newEventIsValid(&newEv)) {
                    fsm->currentEvent = newEv;
                } else {
                    return -1;
                }
            }
        }
    } else {
        fprintf(stderr, "Error on poll(), ret[%d] errno[%d] strerror[%s]\n",
                ret, errno, strerror(errno));
        return -1;
    }

    return 0;
}


static int newEventIsValid(fsmEvent_t *event)
{
    if (event == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return FALSE;
    }

    printf("[%s]\n", __func__);

    struct timeval now;
    getTime(&now);

    if ( (event->name < EV_LAST_ENTRY) &&
         ((now.tv_sec - event->genTime.tv_sec) < (60*5))
       )
    {
        return TRUE;
    } else {
        return FALSE;
    }
}


//------------------------------------------------
// Performs event handling on the FSM (finite state machine).
// Make sure there is a wildcard state at the end of
// your table, otherwise; the event will be ignored.
//------------------------------------------------
static void runEvent(fsm_t *fsm)
{
    int i;
    int condRet = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    // Find a relevant entry for this state and event
    for (i = 0; i < fsm->numStates; i++) {
        // Look in the table for our current state or ST_ANY
        if (  (fsm->pStateTable[i].st == fsm->currentState) ||
              (fsm->pStateTable[i].st == ST_ANY)
           )
        {
            // Is this the event we are looking for?
            if ( (fsm->pStateTable[i].evName == fsm->currentEvent.name) ||
                 (fsm->pStateTable[i].evName == EV_ANY)
               )
            {
                if (fsm->pStateTable[i].conditionfn != NULL) {
                    condRet = fsm->pStateTable[i].conditionfn(fsm->fsmData);
                }

                // See if there is a condition associated
                // or we are not looking for any condition
                //
                if ( (condRet != 0) || (fsm->pStateTable[i].conditionfn == NULL))
                {
                    // Set the next state (if applicable)
                    if (fsm->pStateTable[i].nextState != ST_DONT_CHANGE) {
                        fsm->currentState = fsm->pStateTable[i].nextState;
                        printf("new state [");
                        printState(fsm->currentState);
                        printf("]\n");
                    }

                    // Call the state callback function
                    fsm->pStateTable[i].fn(fsm);
                    break;
                }
            }
        }
    }
}


//------------------------------------------------
//               EVENT HANDLERS
//------------------------------------------------
static void getTime(struct timeval *time)
{
    if (time == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s]\n", __func__);

    int ret = gettimeofday(time, NULL);
    if (ret != 0) {
        fprintf(stderr, "gettimeofday() failed: errno [%d], strerror [%s]\n",
                errno, strerror(errno));
        memset(time, 0, sizeof(struct timeval));
    }
}


static void addev (fsm_t *fsm, fsmEvName_t ev)
{
    int ret = 0;

    if (fsm == NULL) {
        fprintf(stderr, "[%s] NULL argument\n", __func__);
        return;
    }

    printf("[%s] ev[%d]\n", __func__, ev);

    if (ev == EV_ANY) {
        // Don't generate a new event, just return...
        return;
    }

    fsmEvent_t newev;
    getTime(&(newev.genTime));
    newev.name = ev;

    ret = mq_send(fsm->mqdes, (void *)(&newev), sizeof(fsmEvent_t), FSM_PRIO);
    if (ret == -1) {
        fprintf(stderr, "[%s] mq_send() failed: errno [%d], strerror [%s]\n",
                __func__, errno, strerror(errno));
    }
}
//------------------------------------------------
//           end EVENT HANDLERS
//------------------------------------------------

void printState(fsmState_t st)
{
    switch(st) {
        case    ST_UNKNOWN:
        printf("ST_UNKNOWN");
            break;
        case    ST_UNINIT:
        printf("ST_UNINIT");
            break;
        case    ST_INIT:
        printf("ST_INIT");
            break;
        case    ST_CONNECTED:
        printf("ST_CONNECTED");
            break;
        case    ST_MTU_NEGOTIATED:
        printf("ST_MTU_NEGOTIATED");
            break;
        case    ST_AUTHENTICATED:
        printf("ST_AUTHENTICATED");
            break;
        case    ST_ERROR:
        printf("ST_ERROR");
            break;
        case    ST_TERM:
        printf("ST_TERM");
            break;
        default:
        printf("unknown state");
            break;
    }
}

void printEvent(fsmEvName_t ev)
{
    switch (ev) {
        case    EV_UNKNOWN:
        printf("EV_UNKNOWN");
            break;
        case    EV_INIT_SUCCESS:
        printf("EV_INIT_SUCCESS");
            break;
        case    EV_INIT_FAIL:
        printf("EV_INIT_FAIL");
            break;
        case    EV_MASTER_CMD_MSG:
        printf("EV_MASTER_CMD_MSG");
            break;
        case    EV_CONNECT_SUCCESS:
        printf("EV_CONNECT_SUCCESS");
            break;
        case    EV_CONNECT_FAIL:
        printf("EV_CONNECT_FAIL");
            break;
        case    EV_MTU_SUCCESS:
        printf("EV_MTU_SUCCESS");
            break;
        case    EV_MTU_FAIL:
        printf("EV_MTU_FAIL");
            break;
        case    EV_AUTH_SUCCESS:
        printf("EV_AUTH_SUCCESS");
            break;
        case    EV_AUTH_FAIL:
        printf("EV_AUTH_FAIL");
            break;
        case    EV_TX_SUCCESS:
        printf("EV_TX_SUCCESS");
            break;
        case    EV_TX_FAIL:
        printf("EV_TX_FAIL");
            break;
        case    EV_DISCONNECTED:
        printf("EV_DISCONNECTED");
            break;
        case    EV_LAST_ENTRY:
        printf("EV_LAST_ENTRY");
            break;
        default:
        printf("unknown event");
            break;
    }
}

Makefile

CXX = gcc
COMPFLAGS = -c -Wall -g

state_machine: state_machine.o
    $(CXX) -lrt state_machine.o -o state_machine

state_machine.o: state_machine.c
    $(CXX) $(COMPFLAGS) state_machine.c

clean:
    rm state_machine state_machine.o
0
Brad Grissom