Có một mô hình triển khai máy trạng thái điển hình không?


118

Chúng tôi cần phải thực hiện một máy nhà nước đơn giản trong C .
Một tuyên bố chuyển đổi tiêu chuẩn có phải là cách tốt nhất để thực hiện không?
Chúng ta có một trạng thái (trạng thái) hiện tại và một kích hoạt cho quá trình chuyển đổi.


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ó cách nào tốt hơn cho các máy trạng thái đơn giản không

CHỈNH SỬA: Đối với C ++, tôi nghĩ rằng thư viện Boost Statechart có thể là cách tốt nhất. Tuy nhiên, nó không giúp ích gì cho C. Hãy tập trung vào ca sử dụng C.


Câu trả lời:


134

Tôi thích sử dụng phương pháp hướng bảng cho hầu hết các máy trạng thái:

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

Tất nhiên, điều này có thể được mở rộng để hỗ trợ nhiều máy trạng thái, v.v. Các hành động chuyển đổi cũng có thể được đáp ứng:

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

Phương pháp hướng bảng dễ duy trì và mở rộng hơn và đơn giản hơn để ánh xạ tới biểu đồ trạng thái.


Cách rất tốt để bắt đầu, ít nhất là điểm khởi đầu đối với tôi. Một nhận xét, dòng đầu tiên của run_state () có một "." điều đó không nên ở đó.
Atilla Filiz

2
sẽ tốt hơn nếu câu trả lời này cũng sẽ nói lên 2 từ về hai cách tiếp cận khác: phương pháp "toàn cục" với trường hợp chuyển mạch lớn và tách các trạng thái bằng State Design Pattern và để mỗi trạng thái tự xử lý các chuyển đổi của nó.
erikbwork

Xin chào, tôi biết bài viết này đã cũ nhưng tôi hy vọng tôi sẽ nhận được câu trả lời của mình :) Điều gì chắc chắn nên bằng biến instance_data_t? Tôi tự hỏi làm thế nào để thay đổi trạng thái trong ngắt ... có phải là cách tốt để lưu trữ thông tin về ngắt đã xử lý trong biến này không? Ví dụ lưu trữ thông tin rằng nút đã được nhấn để thay đổi trạng thái.
grongor

@GRoNGoR Nghe có vẻ như bạn đang xử lý một máy trạng thái hướng sự kiện. Tôi nghĩ rằng bạn có thể sử dụng nó để lưu trữ dữ liệu sự kiện.
Zimano

3
Thực sự thú vị về cách định nghĩa NUM_STATES.
Albin Stigo

25

Bạn có thể đã thấy câu trả lời của tôi cho một câu hỏi C khác mà tôi đã đề cập đến FSM! Đây là cách tôi làm điều đó:

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

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

Với các macro sau được xác định

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

Điều này có thể được sửa đổi cho phù hợp với trường hợp cụ thể. Ví dụ: bạn có thể có một tệp FSMFILEmà bạn muốn điều khiển FSM của mình, vì vậy bạn có thể kết hợp hành động đọc ký tự tiếp theo vào chính macro:

#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

bây giờ bạn có hai kiểu chuyển đổi: một chuyển đến một trạng thái và đọc một ký tự mới, một chuyển đến một trạng thái mà không cần tiêu thụ bất kỳ đầu vào nào.

Bạn cũng có thể tự động hóa việc xử lý EOF với những thứ như:

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

#define ENDFSM    sx_endfsm:

Cái hay của cách tiếp cận này là bạn có thể dịch trực tiếp một biểu đồ trạng thái bạn vẽ thành mã làm việc và ngược lại, bạn có thể dễ dàng vẽ một biểu đồ trạng thái từ mã.

Trong các kỹ thuật khác để triển khai FSM, cấu trúc của quá trình chuyển đổi được chôn trong các cấu trúc điều khiển (while, if, switch ...) và được điều khiển bởi giá trị của các biến (mẹo là một statebiến) và có thể là một nhiệm vụ phức tạp để liên kết sơ đồ đẹp với một mã phức tạp.

Tôi học được kỹ thuật này từ một bài báo xuất hiện trên tạp chí "Ngôn ngữ Máy tính" tuyệt vời mà không may là nó không còn được xuất bản nữa.


1
Về cơ bản, một FSM tốt là tất cả về khả năng đọc. Điều này cung cấp một giao diện tốt và việc triển khai cũng tốt như nó có được. Thật tiếc là không có cấu trúc FSM bản địa trong ngôn ngữ. Tôi có thể xem nó bây giờ là một bổ sung muộn cho C1X!
Kelden Cowan

3
Tôi thích cách tiếp cận này cho các ứng dụng nhúng. Có cách nào để sử dụng phương pháp này với máy trạng thái hướng sự kiện không?
ARF

13

Tôi cũng đã sử dụng cách tiếp cận bảng. Tuy nhiên, có phí. Tại sao lại lưu trữ danh sách con trỏ thứ hai? Một hàm trong C không có () là một con trỏ const. Vì vậy, bạn có thể làm:

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

Tất nhiên, tùy thuộc vào yếu tố sợ hãi của bạn (tức là an toàn và tốc độ), bạn có thể muốn kiểm tra các con trỏ hợp lệ. Đối với các máy trạng thái lớn hơn ba trạng thái trở lên, cách tiếp cận trên phải ít hướng dẫn hơn so với cách tiếp cận bảng hoặc công tắc tương đương. Bạn thậm chí có thể macro-ize như:

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

Ngoài ra, tôi cảm thấy từ ví dụ của OP, rằng có một sự đơn giản hóa nên được thực hiện khi nghĩ về / thiết kế một cỗ máy trạng thái. Tôi không nghĩ rằng trạng thái chuyển đổi nên được sử dụng cho logic. Mỗi chức năng trạng thái sẽ có thể thực hiện vai trò nhất định của nó mà không cần biết rõ ràng về (các) trạng thái trong quá khứ. Về cơ bản, bạn thiết kế cách chuyển từ trạng thái bạn đang ở sang trạng thái khác.

Cuối cùng, đừng bắt đầu thiết kế một máy trạng thái dựa trên ranh giới "chức năng", hãy sử dụng các chức năng con cho điều đó. Thay vào đó, hãy phân chia các trạng thái dựa trên thời điểm bạn sẽ phải đợi điều gì đó xảy ra trước khi bạn có thể tiếp tục. Điều này sẽ giúp giảm thiểu số lần bạn phải chạy máy trạng thái trước khi nhận được kết quả. Điều này có thể quan trọng khi viết các hàm I / O hoặc trình xử lý ngắt.

Ngoài ra, một số ưu và nhược điểm của câu lệnh switch cổ điển:

Ưu điểm:

  • nó bằng ngôn ngữ, vì vậy nó được ghi lại và rõ ràng
  • trạng thái được xác định nơi chúng được gọi là
  • có thể thực hiện nhiều trạng thái trong một lệnh gọi hàm
  • mã chung cho tất cả các trạng thái có thể được thực thi trước và sau câu lệnh switch

Nhược điểm:

  • có thể thực hiện nhiều trạng thái trong một lệnh gọi hàm
  • mã chung cho tất cả các trạng thái có thể được thực thi trước và sau câu lệnh switch
  • chuyển đổi thực hiện có thể chậm

Lưu ý hai thuộc tính là cả pro và con. Tôi nghĩ việc chuyển đổi cho phép cơ hội chia sẻ quá nhiều giữa các bang và sự phụ thuộc lẫn nhau giữa các bang có thể trở nên không thể quản lý được. Tuy nhiên đối với một số ít trạng thái, nó có thể là trạng thái dễ đọc và dễ bảo trì nhất.


10

Đối với một máy trạng thái đơn giản, chỉ cần sử dụng câu lệnh switch và kiểu enum cho trạng thái của bạn. Thực hiện chuyển đổi của bạn bên trong câu lệnh chuyển đổi dựa trên đầu vào của bạn. Trong một chương trình thực, bạn rõ ràng sẽ thay đổi "if (input)" để kiểm tra các điểm chuyển tiếp của bạn. Hi vọng điêu nay co ich.

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
Có thể đáng để đặt "trạng thái" bên trong hàm và làm cho nó tĩnh.
Steve Melnikoff

2
@Steve Melnikoff: chỉ khi bạn chỉ có một máy trạng thái. Giữ nó bên ngoài chức năng và bạn có thể có một loạt các máy trạng thái với trạng thái riêng của chúng.
Vicky

@Vicky: Một hàm có thể chứa bao nhiêu máy trạng thái tùy thích, với một mảng các biến trạng thái nếu được yêu cầu, có thể nằm bên trong hàm (dưới dạng biến tĩnh) nếu chúng không được sử dụng ở nơi khác.
Steve Melnikoff

10

Trong UML Distilled của Martin Fowler , ông nói (không có ý định chơi chữ) trong Chương 10 Sơ đồ trạng thái máy (tôi nhấn mạnh):

Biểu đồ trạng thái có thể được triển khai theo ba cách chính: chuyển đổi lồng nhau , mẫu trạng tháibảng trạng thái .

Hãy sử dụng một ví dụ đơn giản về các trạng thái của màn hình điện thoại di động:

nhập mô tả hình ảnh ở đây

Công tắc lồng nhau

Fowler đã đưa ra một ví dụ về mã C #, nhưng tôi đã điều chỉnh nó cho phù hợp với ví dụ của mình.

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

Mô hình trạng thái

Đây là cách triển khai ví dụ của tôi với mẫu Trạng thái GoF:

nhập mô tả hình ảnh ở đây

Bảng trạng thái

Lấy cảm hứng từ Fowler, đây là một bảng cho ví dụ của tôi:

Nguồn State Target State Event Guard Action
-------------------------------------------------- ------------------------------------
ScreenOff ScreenOff pressButton power Màn hình thấpLowPowerMessage  
ScreenOff ScreenOn pressButton! PowerLow
ScreenOn ScreenOff pressButton
ScreenOff ScreenCharging plugPower
ScreenOn ScreenCharging plugPower
ScreenCharging ScreenOff rút phích cắm Nguồn

So sánh

Công tắc lồng nhau giữ tất cả logic ở một chỗ, nhưng mã có thể khó đọc khi có nhiều trạng thái và quá trình chuyển đổi. Nó có thể an toàn hơn và dễ xác thực hơn các cách tiếp cận khác (không đa hình hoặc thông dịch).

Việc triển khai mẫu trạng thái có khả năng lan truyền logic trên một số lớp riêng biệt, điều này có thể khiến việc hiểu toàn bộ nó trở thành một vấn đề. Mặt khác, các lớp nhỏ dễ hiểu riêng biệt. Thiết kế đặc biệt mỏng manh nếu bạn thay đổi hành vi bằng cách thêm hoặc xóa các chuyển tiếp, vì chúng là các phương thức trong hệ thống phân cấp và có thể có nhiều thay đổi đối với mã. Nếu bạn sống theo nguyên tắc thiết kế của các giao diện nhỏ, bạn sẽ thấy mẫu này không thực sự hoạt động tốt. Tuy nhiên, nếu máy trạng thái ổn định, thì những thay đổi đó sẽ không cần thiết.

Cách tiếp cận bảng trạng thái yêu cầu viết một số loại trình thông dịch cho nội dung (điều này có thể dễ dàng hơn nếu bạn phản ánh bằng ngôn ngữ bạn đang sử dụng), điều này có thể còn rất nhiều việc phải làm trước. Như Fowler đã chỉ ra, nếu bảng của bạn tách biệt với mã của bạn, bạn có thể sửa đổi hành vi của phần mềm mà không cần biên dịch lại. Tuy nhiên, điều này có một số ý nghĩa bảo mật; phần mềm đang hoạt động dựa trên nội dung của tệp bên ngoài.

Chỉnh sửa (không thực sự dành cho ngôn ngữ C)

Có một cách tiếp cận giao diện thông thạo (hay còn gọi là ngôn ngữ dành riêng cho miền nội bộ), có thể được tạo điều kiện bởi các ngôn ngữ có các chức năng hạng nhất . Các thư viện quốc tịch tồn tại và chương trình viết blog một ví dụ đơn giản với mã. Một triển khai Java (trước Java8) được thảo luận. Tôi cũng đã được xem một ví dụ Python trên GitHub .


Bạn đã sử dụng phần mềm nào để tạo ra những bức tranh?
sjas

1
Tôi nghi ngờ nó có thể đã được tạo qua PlantUML plantuml.com/state-diagram
Seidleroni


4

Đối với các trường hợp đơn giản, bạn có thể chuyển đổi kiểu của bạn. Những gì tôi đã thấy rằng hoạt động tốt trong quá khứ là đối phó với quá trình chuyển đổi:

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
}

Tôi không biết gì về thư viện tăng cường, nhưng kiểu tiếp cận này rất đơn giản, không yêu cầu bất kỳ phụ thuộc bên ngoài nào và rất dễ thực hiện.


4

switch () là một cách mạnh mẽ và tiêu chuẩn để triển khai các máy trạng thái trong C, nhưng nó có thể làm giảm khả năng bảo trì nếu bạn có một số lượng lớn các trạng thái. Một phương pháp phổ biến khác là sử dụng con trỏ hàm để lưu trữ trạng thái tiếp theo. Ví dụ đơn giản này thực hiện một flip-flop đặt / đặt lại:

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

Tôi đã tìm thấy một triển khai C thực sự khéo léo của Moore FSM trong khóa học edx.org Hệ thống nhúng - Định hình thế giới UTAustinX - UT.6.02x, chương 10, bởi Jonathan Valvano và 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

Bạn có thể muốn xem xét phần mềm tạo FSM libero . Từ ngôn ngữ mô tả trạng thái và / hoặc trình soạn thảo sơ đồ trạng thái (cửa sổ), bạn có thể tạo mã cho C, C ++, java và nhiều ngôn ngữ khác ... cùng với tài liệu và sơ đồ đẹp mắt. Nguồn và mã nhị phân từ iMatix



2

Một trong những mẫu yêu thích của tôi là mẫu thiết kế trạng thái. Phản hồi hoặc hành xử khác nhau với cùng một nhóm đầu vào nhất định.
Một trong những vấn đề khi sử dụng câu lệnh switch / case cho các máy trạng thái là khi bạn tạo nhiều trạng thái hơn, switch / case trở nên khó đọc / khó sử dụng hơn để đọc / bảo trì, thúc đẩy mã spaghetti không có tổ chức và ngày càng khó thay đổi mà không làm hỏng thứ gì đó. Tôi thấy việc sử dụng các mẫu thiết kế giúp tôi tổ chức dữ liệu của mình tốt hơn, đó là toàn bộ điểm trừu tượng. Thay vì thiết kế mã trạng thái của bạn xung quanh trạng thái bạn đến, thay vào đó hãy cấu trúc mã của bạn để mã ghi lại trạng thái khi bạn nhập trạng thái mới. Bằng cách đó, bạn có được một bản ghi về trạng thái trước đó của mình một cách hiệu quả. Tôi thích câu trả lời của @ JoshPetit và đã thực hiện giải pháp của anh ấy thêm một bước nữa, lấy ngay từ sách của 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;
}

Đối với hầu hết các Máy Nhà nước, đặc biệt. Các máy trạng thái hữu hạn, mỗi trạng thái sẽ biết trạng thái tiếp theo của nó là gì và các tiêu chí để chuyển sang trạng thái tiếp theo của nó. Đối với các thiết kế trạng thái lỏng lẻo, điều này có thể không đúng, do đó tùy chọn để hiển thị API cho các trạng thái chuyển đổi. Nếu bạn muốn trừu tượng hơn, mỗi trình xử lý trạng thái có thể được tách ra thành tệp riêng của nó, tương đương với các trình xử lý trạng thái cụ thể trong sách GoF. Nếu thiết kế của bạn đơn giản với chỉ một vài trạng thái, thì cả stateCtxt.c và statehandlers.c đều có thể được kết hợp thành một tệp duy nhất để đơn giản hóa.


State3 và State2 có giá trị trả về mặc dù được khai báo là void.
Ant

1

Theo kinh nghiệm của tôi, sử dụng câu lệnh 'switch' là cách tiêu chuẩn để xử lý nhiều trạng thái có thể xảy ra. Mặc dù tôi rất ngạc nhiên rằng bạn đang chuyển một giá trị chuyển tiếp sang xử lý theo từng trạng thái. Tôi nghĩ toàn bộ điểm của máy trạng thái là mỗi trạng thái thực hiện một hành động duy nhất. Sau đó, hành động / đầu vào tiếp theo xác định trạng thái mới cần chuyển sang. Vì vậy, tôi đã mong đợi mỗi chức năng xử lý trạng thái thực hiện ngay lập tức bất cứ điều gì được cố định để nhập trạng thái và sau đó quyết định xem có cần chuyển đổi sang trạng thái khác hay không.


2
Có các mô hình cơ bản khác nhau: máy Mealy và máy Moore. Hành động của Mealy phụ thuộc vào quá trình chuyển đổi, hành động của Moore phụ thuộc vào trạng thái.
xmjx 25/09/08

1

Có một cuốn sách có tựa đề Sơ đồ thực hành trong C / C ++ . Tuy nhiên, nó là cách quá nặng cho những gì chúng ta cần.


2
Tôi đã có phản ứng chính xác với cuốn sách. Làm thế nào có thể cần hơn 700 trang để mô tả & triển khai một thứ mà tôi nghĩ là khá trực quan và đơn giản?!?!?
Dan

1

Đối với trình biên dịch hỗ trợ __COUNTER__, bạn có thể sử dụng chúng cho các mashines trạng thái đơn giản (nhưng lớn).

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

Ưu điểm của việc sử dụng __COUNTER__thay vì các số được mã hóa cứng là bạn có thể thêm trạng thái vào giữa các trạng thái khác mà không cần đánh số lại mọi thứ. Nếu trình biên dịch không hỗ trợ __COUNTER__, theo một cách hạn chế, nó có thể được sử dụng một cách thận trọng__LINE__


Bạn có thể vui lòng giải thích thêm câu trả lời của bạn?
abarisone

Trong mashine trạng thái "chuyển đổi" bình thường, bạn có ví dụ: trường hợp 0, trường hợp 1, trường hợp 2, ... trường hợp 100. Nếu bây giờ bạn muốn thêm 3 trường hợp từ 5 đến 6, bạn phải đánh số lại phần còn lại thành 100, bây giờ sẽ là 103. Việc sử dụng __COUNTER__loại bỏ sự cần thiết phải đánh số lại, bởi vì trình biên dịch trước thực hiện việc đánh số trong quá trình biên dịch.
Seb

1

Bạn có thể sử dụng khung máy trạng thái UML tối giản trong c. https://github.com/kiishor/UML-State-Machine-in-C

Nó hỗ trợ cả máy trạng thái hữu hạn và phân cấp. Nó chỉ có 3 API, 2 cấu trúc và 1 kiểu liệt kê.

Máy trạng thái được biểu diễn bằng state_machine_tcấu trúc. Nó là một cấu trúc trừu tượng có thể được kế thừa để tạo ra một máy trạng thái.

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

Trạng thái được đại diện bởi con trỏ tới state_t cấu trúc trong khuôn khổ.

Nếu khung công tác được cấu hình cho máy trạng thái hữu hạn thì state_tchứa,

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;

Khung cung cấp một API dispatch_eventđể gửi sự kiện đến máy trạng thái và hai API để truyền trạng thái.

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

Để biết thêm chi tiết về cách triển khai máy trạng thái phân cấp, hãy tham khảo kho lưu trữ GitHub.

ví dụ mã
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


bạn cũng có thể thêm một ví dụ mã phù hợp với câu hỏi?
Giulio Caccin

Thư mục demo trong kho có một ví dụ. github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/… . Tôi hiện đang làm việc trên một ví dụ hệ thống nhúng khác liên quan đến khóa, đèn dẫn và bộ hẹn giờ, vẫn chưa hoàn thành. Sẽ cho bạn biết khi nó đã sẵn sàng.
Nandkishor Biradar


0

Câu hỏi của bạn tương tự như "có mô hình triển khai Cơ sở dữ liệu điển hình" không? Câu trả lời phụ thuộc vào những gì bạn muốn đạt được? Nếu bạn muốn triển khai một máy trạng thái xác định lớn hơn, bạn có thể sử dụng một mô hình và một bộ tạo máy trạng thái. Có thể xem các ví dụ tại www.StateSoft.org - Thư viện SM. Janusz Dobrowolski


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.