Esiste un tipico modello di implementazione della macchina a stati?


118

Dobbiamo implementare una semplice macchina a stati in C .
Un'istruzione switch standard è il modo migliore per procedere?
Abbiamo uno stato corrente (stato) e un trigger per la transizione.


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

C'è un modo migliore per semplici macchine a stati

EDIT: Per C ++, penso che la libreria Boost Statechart potrebbe essere la strada da percorrere. Tuttavia, lo fa non aiuta con C. Concentriamoci sul caso d'uso C.


Risposte:


134

Preferisco utilizzare un approccio basato su tabelle per la maggior parte delle macchine a stati:

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

Questo può ovviamente essere esteso per supportare più macchine a stati, ecc. Possono essere adattate anche le azioni di transizione:

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

L'approccio basato sulla tabella è più facile da mantenere ed estendere e più semplice da mappare ai diagrammi di stato.


Un modo molto carino per iniziare, almeno punto di partenza per me. Un'osservazione, la prima riga di run_state () ha un "." quello non dovrebbe essere lì.
Atilla Filiz

2
Sarebbe meglio se anche questa risposta dicesse almeno 2 parole sugli altri due approcci: un metodo "globale" con un grande caso di commutazione e separare gli stati con lo State Design Pattern e lasciare che ogni stato gestisca le proprie transizioni.
erikbwork

Ciao, so che questo post è vecchio ma spero di ottenere la mia risposta :) Cosa dovrebbe certamente fare nella variabile instance_data_t? Mi chiedo come cambiare gli stati negli interrupt ... è un buon modo per memorizzare le informazioni sull'interrupt elaborato in questa variabile? Ad esempio, memorizzare le informazioni sul pulsante premuto, quindi lo stato deve essere modificato.
grongor

@GRoNGoR Mi sembra che tu abbia a che fare con una macchina a stati guidata dagli eventi. Penso che potresti usarlo per memorizzare i dati degli eventi davvero.
Zimano

3
Davvero un bel tocco come vengono definiti NUM_STATES.
Albin Stigo

25

Potresti aver visto la mia risposta a un'altra domanda in C in cui ho menzionato FSM! Ecco come lo faccio:

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

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

Con le seguenti macro definite

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

Questo può essere modificato per adattarsi al caso specifico. Ad esempio, potresti avere un file FSMFILEche desideri guidare il tuo FSM, quindi potresti incorporare l'azione di leggere il carattere successivo nella macro stessa:

#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

ora hai due tipi di transizioni: una va in uno stato e legge un nuovo carattere, l'altra va in uno stato senza consumare alcun input.

Puoi anche automatizzare la gestione di EOF con qualcosa come:

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

#define ENDFSM    sx_endfsm:

La cosa buona di questo approccio è che puoi tradurre direttamente un diagramma di stato disegnato in codice funzionante e, al contrario, puoi facilmente disegnare un diagramma di stato dal codice.

In altre tecniche per l'implementazione di FSM la struttura delle transizioni è sepolta in strutture di controllo (mentre, if, switch ...) e controllata da variabili value (tipicamente un state variabile) e può essere un compito complesso mettere in relazione il bel diagramma con un codice contorto.

Ho imparato questa tecnica da un articolo apparso sulla grande rivista "Computer Language" che, purtroppo, non è più pubblicata.


1
Fondamentalmente, un buon FSM è tutto basato sulla leggibilità. Ciò fornisce una buona interfaccia e l'implementazione è la migliore possibile. È un peccato che non ci sia una struttura FSM nativa nella lingua. Ora lo vedo come un'aggiunta tardiva a C1X!
Kelden Cowan

3
Adoro questo approccio per le applicazioni incorporate. C'è un modo per utilizzare questo approccio con una macchina a stati guidata dagli eventi?
ARF

13

Ho anche usato l'approccio al tavolo. Tuttavia, c'è un sovraccarico. Perché memorizzare un secondo elenco di puntatori? Una funzione in C senza () è un puntatore const. Quindi puoi fare:

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

Ovviamente a seconda del tuo fattore di paura (cioè sicurezza vs velocità) potresti voler verificare la presenza di indicazioni valide. Per macchine a stati più grandi di tre o giù di lì stati, l'approccio di cui sopra dovrebbe essere meno istruzioni di un equivalente switch o approccio di tabella. Puoi anche macro-ridimensionare come:

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

Inoltre, dall'esempio dell'OP, sento che c'è una semplificazione che dovrebbe essere fatta quando si pensa / si progetta una macchina a stati. Non credo che lo stato di transizione debba essere usato per la logica. Ciascuna funzione statale dovrebbe essere in grado di svolgere il suo ruolo dato senza una conoscenza esplicita degli stati passati. Fondamentalmente progetti come passare dallo stato in cui ti trovi a un altro.

Infine, non iniziare la progettazione di una macchina a stati basata su confini "funzionali", usa le sotto-funzioni per questo. Dividi invece gli stati in base a quando dovrai aspettare che accada qualcosa prima di poter continuare. Ciò contribuirà a ridurre al minimo il numero di volte in cui è necessario eseguire la macchina a stati prima di ottenere un risultato. Questo può essere importante quando si scrivono funzioni di I / O o gestori di interrupt.

Inoltre, alcuni pro e contro della classica istruzione switch:

Professionisti:

  • è nella lingua, quindi è documentato e chiaro
  • gli stati sono definiti dove vengono chiamati
  • può eseguire più stati in una chiamata di funzione
  • il codice comune a tutti gli stati può essere eseguito prima e dopo l'istruzione switch

Contro:

  • può eseguire più stati in una chiamata di funzione
  • il codice comune a tutti gli stati può essere eseguito prima e dopo l'istruzione switch
  • l'implementazione dello switch può essere lenta

Nota i due attributi che sono sia pro che contro. Penso che il passaggio consenta un'eccessiva condivisione tra gli stati e l'interdipendenza tra gli stati possa diventare ingestibile. Tuttavia, per un numero limitato di stati, potrebbe essere il più leggibile e gestibile.


10

Per una semplice macchina a stati usa semplicemente un'istruzione switch e un tipo enum per il tuo stato. Esegui le tue transizioni all'interno dell'istruzione switch in base al tuo input. In un programma reale dovresti ovviamente cambiare "if (input)" per verificare i tuoi punti di transizione. Spero che questo ti aiuti.

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
Potrebbe valere la pena inserire "state" nella funzione e renderla statica.
Steve Melnikoff,

2
@ Steve Melnikoff: solo se hai solo una macchina a stati. Tienilo fuori dalla funzione e puoi avere un array di macchine a stati con il proprio stato.
Vicky

@Vicky: una funzione può contenere tutte le macchine a stati che desideri, con un array di variabili di stato se necessario, che possono vivere all'interno della funzione (come variabili statiche) se non vengono utilizzate altrove.
Steve Melnikoff,

10

In UML Distilled di Martin Fowler , afferma (nessun gioco di parole) nel Capitolo 10 Diagrammi della macchina a stati (enfasi mia):

Un diagramma di stato può essere implementato in tre modi principali: switch annidato , modello di stato e tabelle di stato .

Usiamo un esempio semplificato degli stati del display di un telefono cellulare:

inserisci qui la descrizione dell'immagine

Interruttore annidato

Fowler ha fornito un esempio di codice C #, ma l'ho adattato al mio esempio.

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

Modello di stato

Ecco un'implementazione del mio esempio con il pattern GoF State:

inserisci qui la descrizione dell'immagine

Tabelle di stato

Prendendo ispirazione da Fowler, ecco una tabella per il mio esempio:

Stato di origine Stato di destinazione Azione di protezione eventi
-------------------------------------------------- ------------------------------------
Schermo Spento Schermo Spento premere Pulsante alimentazione Bassa visualizzazione Bassa potenza Messaggio  
Schermo Fuori dallo schermo Su premere il pulsante! PowerLow
ScreenOn ScreenOff premere il pulsante
Schermo Fuori schermo Spina di ricarica Alimentazione
ScreenOn Screen Connettore di ricarica Alimentazione
ScreenCharging ScreenOff unplugPower

Confronto

L'interruttore annidato mantiene tutta la logica in un punto, ma il codice può essere difficile da leggere quando ci sono molti stati e transizioni. È forse più sicuro e più facile da convalidare rispetto agli altri approcci (nessun polimorfismo o interpretazione).

L'implementazione del pattern State potenzialmente diffonde la logica su diverse classi separate, il che può rendere la sua comprensione nel suo insieme un problema. D'altra parte, le classi piccole sono facili da capire separatamente. Il design è particolarmente fragile se si modifica il comportamento aggiungendo o rimuovendo le transizioni, poiché sono metodi nella gerarchia e potrebbero esserci molte modifiche al codice. Se vivi secondo il principio di progettazione delle piccole interfacce, vedrai che questo modello non funziona molto bene. Tuttavia, se la macchina a stati è stabile, tali modifiche non saranno necessarie.

L'approccio delle tabelle di stato richiede la scrittura di un qualche tipo di interprete per il contenuto (questo potrebbe essere più facile se hai una riflessione nella lingua che stai usando), che potrebbe essere molto lavoro da fare in anticipo. Come sottolinea Fowler, se la tua tabella è separata dal tuo codice, potresti modificare il comportamento del tuo software senza ricompilarlo. Tuttavia, ciò ha alcune implicazioni sulla sicurezza; il software si comporta in base al contenuto di un file esterno.

Modifica (non proprio per il linguaggio C)

Esiste anche un approccio all'interfaccia fluente (noto anche come linguaggio interno specifico del dominio), probabilmente facilitato dai linguaggi che hanno funzioni di prima classe . La libreria Stateless esiste e quel blog mostra un semplice esempio con il codice. Viene discussa un'implementazione Java (pre Java8) . Mi è stato mostrato anche un esempio di Python su GitHub .


Quale software hai utilizzato per creare le immagini?
sjas

1
Sospetto che possa essere stato creato tramite PlantUML plantuml.com/state-diagram
Seidleroni


4

Per casi semplici, puoi cambiare il metodo di stile. Quello che ho scoperto che funziona bene in passato è gestire anche le transizioni:

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
}

Non so nulla della libreria boost, ma questo tipo di approccio è semplicissimo, non richiede dipendenze esterne ed è facile da implementare.


4

switch () è un modo potente e standard di implementare macchine a stati in C, ma può diminuire la manutenibilità se si dispone di un gran numero di stati. Un altro metodo comune consiste nell'usare i puntatori a funzione per memorizzare lo stato successivo. Questo semplice esempio implementa un flip-flop set / reset:

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

Ho trovato un'implementazione C davvero eccellente di Moore FSM nel corso edx.org Embedded Systems - Shape the World UTAustinX - UT.6.02x, capitolo 10, di Jonathan Valvano e 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

Potresti voler esaminare il software del generatore di libero FSM. Da un linguaggio di descrizione dello stato e / o un editor di diagrammi di stato (Windows) puoi generare codice per C, C ++, java e molti altri ... oltre a una bella documentazione e diagrammi. Sorgente e binari da iMatix



2

Uno dei miei modelli preferiti è il modello di progettazione statale. Rispondi o comportati in modo diverso rispetto allo stesso insieme di input.
Uno dei problemi con l'utilizzo di istruzioni switch / case per macchine a stati è che man mano che si creano più stati, lo switch / case diventa più difficile / ingombrante da leggere / mantenere, promuove codice spaghetti non organizzato e sempre più difficile da modificare senza rompere qualcosa. Trovo che l'utilizzo di modelli di progettazione mi aiuti a organizzare meglio i miei dati, che è il punto centrale dell'astrazione. Invece di progettare il codice di stato intorno allo stato da cui provieni, struttura invece il tuo codice in modo che registri lo stato quando entri in un nuovo stato. In questo modo, ottieni effettivamente un record del tuo stato precedente. Mi piace la risposta di @ JoshPetit e ho portato la sua soluzione un ulteriore passo avanti, presa direttamente dal libro di GoF:

stateCtxt.h:

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

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

stateCtxt.c:

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

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

static FsmT      fsm;
static pfnStateT UsbState ;

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

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

        // Update the State Machine structure
        UsbState = targetState ;

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

void STATECTXT_Handle(void *pvEvent)
{
    pfnStateT newState;

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


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

statehandlers.h:

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

statehandlers.c:

#include "stateCtxt.h:"

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

    return nextState;
}

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

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

Per la maggior parte delle macchine a stati, esp. Macchine a stati finiti, ogni stato saprà quale dovrebbe essere il suo stato successivo e i criteri per passare al suo stato successivo. Per i progetti a stato libero, questo potrebbe non essere il caso, da qui la possibilità di esporre l'API per gli stati di transizione. Se desideri più astrazione, ogni gestore di stato può essere separato in un proprio file, che è equivalente ai gestori di stato concreti nel libro GoF. Se il tuo progetto è semplice con solo pochi stati, allora sia stateCtxt.c che statehandlers.c possono essere combinati in un unico file per semplicità.


State3 e State2 hanno valori di ritorno anche se dichiarati void.
Ant

1

Nella mia esperienza l'uso dell'istruzione "switch" è il modo standard per gestire più stati possibili. Anche se sono sorpreso che tu stia trasmettendo un valore di transizione all'elaborazione per stato. Pensavo che il punto centrale di una macchina a stati fosse che ogni stato eseguiva una singola azione. Quindi l'azione / input successivo determina in quale nuovo stato passare. Quindi mi sarei aspettato che ogni funzione di elaborazione dello stato eseguisse immediatamente tutto ciò che è stato fissato per entrare nello stato e quindi decidere in seguito se è necessaria la transizione a un altro stato.


2
Esistono diversi modelli sottostanti: macchine Mealy e macchine Moore. Le azioni di Mealy dipendono dalla transizione, quelle di Moore dipendono dallo stato.
xmjx

1

C'è un libro intitolato Practical Statecharts in C / C ++ . Tuttavia, è modo troppo pesante per quello che ci serve.


2
Ho avuto la stessa identica reazione al libro. Come possono essere necessarie più di 700 pagine per descrivere e implementare qualcosa che penso sia piuttosto intuitivo e diretto?!?!?
Dan

1

Per i compilatori che supportano __COUNTER__, puoi usarli per semplici (ma grandi) macchine a stati.

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

Il vantaggio di utilizzare al __COUNTER__posto di numeri hardcoded è che puoi aggiungere stati nel mezzo di altri stati, senza rinumerare ogni volta tutto. Se il compilatore non supporta __COUNTER__, in modo limitato è possibile utilizzarlo con precauzione__LINE__


Potresti spiegare di più la tua risposta?
abarisone

In una normale macchina di stato "switch", hai ad esempio caso 0, caso 1, caso 2, ... caso 100. Se ora vuoi aggiungere 3 casi tra 5 e 6, devi rinumerare il resto a 100, che ora sarebbe 103. L'uso di __COUNTER__elimina la necessità di rinumerare, perché il precompilatore fa la numerazione durante la compilazione.
Seb

1

È possibile utilizzare il framework minimalista della macchina a stati UML in c. https://github.com/kiishor/UML-State-Machine-in-C

Supporta macchine a stati sia finite che gerarchiche. Ha solo 3 API, 2 strutture e 1 enumerazione.

La macchina a stati è rappresentata dalla state_machine_tstruttura. È una struttura astratta che può essere ereditata per creare una macchina a stati.

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

Lo stato è rappresentato da un puntatore alla state_tstruttura nel framework.

Se il framework è configurato per la macchina a stati finiti, allora state_tcontiene,

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;

Il framework fornisce un'API dispatch_eventper inviare l'evento alla macchina a stati e due API per l'attraversamento dello stato.

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

Per ulteriori dettagli su come implementare la macchina a stati gerarchica, fare riferimento al repository GitHub.

esempi di codice
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


puoi anche aggiungere un esempio di codice che si adatti alla domanda?
Giulio Caccin

1
La cartella demo nel repository ha un esempio. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . Attualmente sto lavorando a un altro esempio di sistema integrato che coinvolge chiave, led e timer, ma non è ancora completo. Ti farò sapere quando sarà pronto.
Nandkishor Biradar


0

La tua domanda è simile a "esiste un tipico modello di implementazione del database"? La risposta dipende da cosa vuoi ottenere? Se si desidera implementare una macchina a stati deterministica più ampia, è possibile utilizzare un modello e un generatore di macchina a stati. Gli esempi possono essere visualizzati su www.StateSoft.org - SM Gallery. Janusz Dobrowolski


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.