組み込みプロジェクト用のツリージェネレーターを備えた軽量ステートマシンコア

画像

はじめに



私の練習では、有限状態マシンの使用が最も正しいソリューションである状況がしばしば発生しましたが、開発の緊急性、サポートの複雑さ、またはその他の理由により、それを拒否しなければなりませんでした。 この投稿では、ツリー構造を視覚的に表示する機能を使用して、ステートマシンをプロジェクトに簡単に統合できる、私が開発したソリューションを共有したいと思います。



ソリューションの本質



すぐに言わなければなりません:このソリューションは、C ++で開発されたプロジェクトへの統合を目的としています。 現在、他の言語のサポートは利用できません(必要に応じて、将来それらのサポートが追加されます)。



ソリューションは、2つの段階の組み合わせです。



  1. PlantUMLファイルでは、統一スタイルで記述されたプロジェクトツリーが作成され、その後ジェネレータプログラムが呼び出され、グラフの頂点間のリンクのツリーを含むC ++ファイルが生成されます。 ツリーの記述ルールはgithubの プロジェクト記述にあります。 ビルドするには、ライブラリQT 5.10以降が必要です。
  2. ステートマシンのサポートが必要なクラスに追加されます。
    • ステートマシンのサポートを追加する必要があるクラスで、テンプレートクラスfsm_classのオブジェクトが作成されます。 前述のクラスは、テンプレートパラメーターとして渡されます。
    • また、クラスには、ステートマシンのステップを実装する静的メソッドが追加されています。
    • クラスの特定のメソッドでは、有限状態マシンのツリーとそのカーネルをリンクする機能が呼び出され、その後、グラフの通過を開始するメソッドが呼び出されます。


問題の声明



外部から受信した入力メッセージを認識する例として、このソリューションの使用を検討してください。



メッセージオプションは次のとおりです。



  1. メッセージは、長さが不定の文字の配列の形式で到着しますが、512文字を超えません(0終了文字を含む)。
  2. メッセージは0で終了します。


メッセージが一連の行で構成されていることは確かに知られています:スペース文字で区切られた「コマンド」+「パラメータ」。 文字列「command」は「read」または「set」の値を取ることができ、「parameters」はコマンド「set」が入力された場合にのみ必要です。 この場合、int変数には値が必要です。



問題解決



  1. 空のディレクトリを作成し、 そこに fsmカーネルのサブモジュールとジェネレータープログラムを追加します。



    git submodule add git@github.com:Vadimatorik/module_fsm.git git submodule add git@github.com:Vadimatorik/plantuml_to_fsm_tree_generator.git
          
          



  2. user_codeフォルダーとその中にファイルuser_string_parsing.cppおよびuser_string_parsing.hを作成します。これには、着信メッセージを分析するクラスの説明が含まれます。
  3. クラスの空白を書きましょう。



     class user_string_parsing_class { public: user_string_parsing_class(); void start_parsing ( char* string ); private: ///     . char* p = nullptr; /// ,      . int data = 0; ///  . fsm_class< user_string_parsing_class > fsm; };
          
          



  4. ステートマシンのツリーの説明が含まれるuser_codeフォルダーにtree.puファイルを作成します
  5. 有限状態マシンについて説明します。



     @startuml [*] --> start state "team_search" as start { start:     start:  . } start --> fts: 0 start --> arg1: 1 start --> arg2: 2 state "team_search_fail" as fts { fts:   fts:   . fts:    fts: . } state "set_param" as arg1 { arg1:   . } state "read_param" as arg2 { arg2:   . } arg1 --> spd: 0 arg1 --> spn: 1 state "set_param_done" as spd { spd:    , spd:     . } state "set_param_fail" as spn { spn:    , spn:     . } @enduml
          
          





    ツリーの形式は次のとおりです。



    画像
  6. グラフの頂点である静的メソッドをクラスに追加します。 より短いレコードの場合は、defineを介して宣言します。 user_string_parsing.hファイルの形式は次のとおりです。



     #pragma once #include "fsm.h" #define HANDLER_USPC_FSM_STEP(NAME_STEP)\ static int NAME_STEP ( const fsm_step< user_string_parsing_class >* previous_step,\ user_string_parsing_class* obj ) class user_string_parsing_class { public: user_string_parsing_class(); void start_parsing ( char* string ); HANDLER_USPC_FSM_STEP( fsm_step_func_team_search ); HANDLER_USPC_FSM_STEP( fsm_step_func_team_search_fail ); HANDLER_USPC_FSM_STEP( fsm_step_func_set_param ); HANDLER_USPC_FSM_STEP( fsm_step_func_read_param ); HANDLER_USPC_FSM_STEP( fsm_step_func_set_param_done ); HANDLER_USPC_FSM_STEP( fsm_step_func_set_param_fail ); private: ///     . char* p = nullptr; /// ,      . int data = 0; ///  . fsm_class< user_string_parsing_class > fsm; };
          
          



  7. クラスメソッドを実装します。



     ///    : ///   +    + _fsm_step /// user_string_parsing_class_ + team_search + _fsm_step extern const fsm_step< user_string_parsing_class > \ user_string_parsing_class_team_search_fsm_step; user_string_parsing_class::user_string_parsing_class() { ///         . this->fsm.relinking( &user_string_parsing_class_team_search_fsm_step, this ); } void user_string_parsing_class::start_parsing ( char* string ) { ///    ,   . this->p = string; ///   . this->fsm.start(); }
          
          



  8. グラフの頂点を実現します。



     #define FSM_F_USPC \ const fsm_step< user_string_parsing_class >* \ previous_step, user_string_parsing_class* obj int user_string_parsing_class::fsm_step_func_team_search ( FSM_F_USPC ) { ( void )previous_step; if ( strncmp( obj->p, "set", sizeof( "set" ) - 1 ) == 0 ) { obj->p += sizeof( "set" ); return 1; } if ( strcmp( obj->p, "read" ) == 0 ) { return 2; } return 0; } int user_string_parsing_class::fsm_step_func_team_search_fail ( FSM_F_USPC ) { ( void )previous_step; ( void )obj; cout << "Command search fail! Available commands: set, read." << endl << endl; return 0; } int user_string_parsing_class::fsm_step_func_set_param ( FSM_F_USPC ) { ( void )previous_step; int r; r = sscanf( obj->p, "%d", &obj->data ); return ( r == 1 ) ? 0 : 1; } int user_string_parsing_class::fsm_step_func_read_param ( FSM_F_USPC ) { ( void )previous_step; cout << "Data = " << obj->data << endl; return 0; } int user_string_parsing_class::fsm_step_func_set_param_done ( FSM_F_USPC ) { ( void )previous_step; ( void )obj; cout << "The parameter done set!" << endl << endl; return 0; } int user_string_parsing_class::fsm_step_func_set_param_fail ( FSM_F_USPC ) { ( void )previous_step; ( void )obj; cout << "The parameter must be int!" << endl << endl; return 0; }
          
          



  9. メインプログラム(main.cppファイル)でクラスのオブジェクトを作成し、いくつかのテストをスキップします。



     #include <fstream> #include <stdint.h> #include <string.h> #include "user_string_parsing.h" using namespace std; ///      . char buf[512]; ///     . void get_data ( char* b, int n ) { switch( n ) { case 0: memcpy( b, "fsdfhsd", sizeof("fsdfhsd") ); break; case 1: memcpy( b, "read", sizeof("read") ); break; case 2: memcpy( b, "set", sizeof("set") ); break; case 3: memcpy( b, "set sfdf", sizeof("set sfdf") ); break; case 4: memcpy( b, "set 21", sizeof("set 21") ); break; case 5: memcpy( b, "read", sizeof("read") ); break; } } int main ( void ) { user_string_parsing_class usp; for ( int l = 0; l < 6; l++ ) { get_data( buf, l ); usp.start_parsing( buf ); } return 0; }
          
          



  10. ツリージェネレータープログラムと、以前にコンパイルされたジェネレーターを使用するプログラムを構築するための個別の目標を持つメイクファイルを作成します。



     CPP := g++ CPP_FLAGS := -O0 -g3 -Werror -Wall -Wextra -std=c++1z LDFLAGS := -O0 -g3 -Werror -Wall -Wextra PU = plantuml_to_fsm_tree_generator/build/plantuml_to_fsm_tree_generator #       user_code. USER_CPP_FILE := $(shell find user_code/ -maxdepth 5 -type f -name "*.cpp" ) USER_DIR := $(shell find user_code/ -maxdepth 5 -type d -name "*" ) USER_PATH := $(addprefix -I, $(USER_DIR)) USER_OBJ_FILE := $(addprefix build/obj/, $(USER_CPP_FILE)) USER_OBJ_FILE := $(patsubst %.cpp, %.o, $(USER_OBJ_FILE)) PROJECT_PATH += $(USER_PATH) PROJECT_OBJ_FILE += $(USER_OBJ_FILE) FSM_PU_FILE = $(shell find user_code/ -maxdepth 5 -type f -name "*.pu" ) FSM_CPP_FILE += $(patsubst %.pu, %.cpp, $(FSM_PU_FILE)) FSM_OBJ_FILE += $(patsubst %.pu, build/obj/%.o, $(FSM_PU_FILE)) PROJECT_OBJ_FILE += $(FSM_OBJ_FILE) include module_fsm/makefile %.cpp: %.pu @echo [PU] $< @$(PU) $< $@ user_string_parsing_class user_string_parsing.h build/obj/%.o: %.cpp @echo [CPP] $< @mkdir -p $(dir $@) @$(CPP) $(CPP_FLAGS) \ $(PROJECT_PATH) \ -c $< -o $@ all: $(PROJECT_OBJ_FILE) @$(CPP) $(PROJECT_OBJ_FILE) -o build/example @size --format=Berkeley build/example clean: @rm -R build/ @echo Clean all! pfsm_build: mkdir -p plantuml_to_fsm_tree_generator/build cd plantuml_to_fsm_tree_generator/build && qmake -qt=qt5 .. && make pfsm_clean: cd plantuml_to_fsm_tree_generator/ && rm -R build pfsm_rebuild: cd plantuml_to_fsm_tree_generator/ && rm -R build mkdir plantuml_to_fsm_tree_generator/build cd plantuml_to_fsm_tree_generator/build && qmake -qt=qt5 .. && make
          
          



  11. プロジェクトをコンパイルします。 まず、ジェネレータープログラム、次にプロジェクトです。



     make pfsm_build make all
          
          





この記事で説明されている例は、 ここからダウンロードしてビルドすることができます

すべての不正確さ、タイプミス、エラー、希望については、プライベートメッセージに書いてください。



All Articles