シリアル電文パーサー

TWELITE-トワイライトが出力するシリアルメッセージ(書式形式:アスキー形式・バイナリ形式)を解釈するC言語で記述された電文パーサーです。シリアルポートから受けたデータを本パーサに投入することで、ヘッダやチェックサムが含まれた書式形式を元のデータ列(バイト列)に戻します。
また、逆に任意のバイト列から書式形式を生成することもできます。

ダウンロード

format_parser.zip

実装方法

ダウンロードファイルには下記ソースコードが含まれています。

本アーカイブ内に同梱されているサンプルの流れを下記に示します。

  1. パーサを使用するために必要なヘッダをインクルードします。

    #include sercmd_gen.h
    #include jendefs.h
  2. 書式形式の解釈とは逆に、バイト列より書式形式を出力したい時は下記のような出力関数を定義します。出力先に応じて下記関数に記述を変更してください。
    書式形式を出力しない場合は定義不要です。

    // vOutput() で書式形式を出力するときの1バイト出力関数(標準出力の例)
    void vPutChar_STDOUT(uint8 u8char) {
        putchar(u8char);	// 1バイトのシリアル出力関数に置き換えて使用する
    }
    
  3. パーサの初期化を行います。
    ここでパーサが使用するバッファの領域確保や入出力形式の指定、パーサの初期化、書式形式を出力する場合は2.で定義した出力関数の登録を行います。

    tsSerCmd_Context serCmd; // パーサの管理構造体
    static uint8 u8buf[255]; // パーサが使用するバッファ
    #ifdef BINARY
        // バイナリ形式の場合はこちらで初期化する
        SerCmdBinary_vInit(&serCmd, u8buf, sizeof(u8buf));
    #else
        // アスキー形式の場合はこちらで初期化する
        SerCmdAscii_vInit(&serCmd, u8buf, sizeof(u8buf));
    #endif
    serCmd.vPutChar = vPutChar_STDOUT; // NULLまたは1バイト出力関数を登録
    
  4. TWELITEからのシリアル電文を1バイトずつ読み込み、パーサに渡します。
    下記コードはサンプルなので標準入力にしていますが、シリアル入力を1バイト受け取る関数に変更して使用してください。
    パーサの返戻値(u8stat)がE_SERCMD_COMPLETE(0x80)であれば解釈完了を示しており、パーサの管理構造体のau8dataに読み込んだバイト列、u16lenに読み込んだバイト列のバイト数が代入されています。
    このとき、パーサの管理構造体のau8dataのデータはヘッダやフッダ、チェックサムは取り除かれた状態で保持されており、アスキー形式の場合は2文字で1バイトの数値に変換され、バイナリ形式はそのまま保持されています。
    本サンプルコードではau8dataとu16lenを直接標準出力した後、vOutput()を使用した出力も行っております。
    また、u8statがE_SERCMD_ERRORやE_SERCMD_CHECKSUM_ERRORの場合、読み込みが失敗を示しているため次のデータを待ちます。

    int c;
    c = getchar();		// シリアル入力を1バイト受け取る
    if(c < 0) break;	// getcharの場合、0未満だったらエラーであるため何もしない
    
    uint8 u8stat;	// パーサーに通した後の状態を保存する
    // 1バイトずつ解釈をしていく
    u8stat = serCmd.u8Parse(&serCmd, c);
    // 解釈に成功したら終了
    if (u8stat == E_SERCMD_COMPLETE) {
    	// ここに読み込み後の処理を記述する
    	// 例として読み出せた系列を表示
    	printf("u8Parse() success, bytes=%d, payload=", serCmd.u16len); // データの長さは u16len
    	int j;
    	for(j = 0; j < serCmd.u16len; j++) {
    		printf("%02x", serCmd.au8data[j]);  // ペイロードは au8data
    	}
    	printf("\n");
     
    	// 入力した系列をそのまま vOutput で出力する
    	printf("vOutput() -> ");
    	serCmd.vOutput(&serCmd);
    	printf("\n");
    } else if (u8stat == E_SERCMD_CHECKSUM_ERROR) {
    	// チェックサムが違ったときの処理を記述する。
    	printf("Command checksum error: should be %x?\n", serCmd.u16cksum);
    } else if (u8stat == E_SERCMD_ERROR) {
    	// エラーが起こったときの処理を記述する。
    	printf("Command error...\n");
    }
  5. TWELITEからシリアルデータが来るたびに4.を繰り返し行います。

詳しい記述例はsercmd_test.cを参照してください。

サンプルコードのテスト方法

sercmd_gen.cとsercmd_test.cをコンパイルすると本パーサの動作をテストすることができます。
sercmd_gen.cで標準入力より読み込まれた文字列を書式形式に変換し、sercmd_test.cで変換したデータを読み込み、本パーサを使用して解析を行います。
実行手順は次の通りです。

$ make
$ cat data | ./sercmd_gen | ./sercmd_test
u8Parse() success, bytes=4, payload=31323334
vOutput() -> :3132333436

u8Parse() success, bytes=7, payload=61626364656667
vOutput() -> :6162636465666744
...

主な関数・構造体の使用方法

tsSerCmd_Context構造体

シリアルコマンドの解釈及びデータの管理を行う構造体。シリアルデータを読み込んだ場合はこの構造体に格納され、書式出力する場合もデータ列などの情報はここに格納する。

主なメンバ変数

uint16 u16len:バイト列の長さ
uint8 *au8data:バイト列(初期化時に固定配列へのポインタを格納する)
uint16 u16cksum:シリアル電文のチェックサム
uint8 u8state:パーサの読み込みの状態
uint8 (*u8Parse)(tsSerCmd_Context *pc, uint8 u8byte):パーサ関数を登録する
void (*vOutput)(tsSerCmd_Context *pc):出力するバイト列にヘッダとフッタをつけてバイト列を出力する関数を登録する
void (*vPutChar)(uint8 u8byte):1バイト出力する関数を登録する

SerCmdBinary_vInit()/SerCmdAscii_vInit()

電文パーサーの初期化を行う関数。書式形式がバイナリ形式の場合はSerCmdBinary_vInit()を、アスキー形式の場合はSerCmdAscii_vInit()を呼び出します。
本関数でtsSerCmd_ContextのvOutput及びu8Parseに関数の登録を行います。

引数

tsSerCmd_Context *pc:tsSerCmd_Contextのポインタ
uint8 *pbuff:入力系列を保持するバッファのポインタ
uint16 u16maxlen:バッファのバイト数

返戻値

無し

u8Parse()

読み込んだデータを1バイトずつ解釈する関数。解釈が成功した時にE_SERCMD_COMPLETE、失敗したときはE_SERCMD_ERRORやE_SERCMD_CHECKSUM_ERRORを返します。

引数

tsSerCmd_Context *pc:tsSerCmd_Contextのポインタ
uint8 u8byte:読み込んだデータ

返戻値

uint8:下記状態コード

備考

バイナリ形式の場合はSerCmdBinary_u8Parse()、アスキー形式の場合はSerCmdAscii_u8Parse()が実関数です。

vOutput()

データに書式形式に応じたヘッダとチェックサム、フッダをつけて出力する関数。tsSerCmd_Context構造体内のau8dataに出力するデータを、u16lenには出力するデータのバイト数を代入し実行してください。
事前に1バイト出力用のvPutCharの定義が必要です。
例では入力用のtsSerCmd_Contextを流用して出力していますが、通常は出力用のtsSerCmd_Contextを用意し、本関数で出力します。

引数

tsSerCmd_Context *pc:tsSerCmd_Contextのポインタ

返戻値

無し

備考

バイナリ形式の場合はSerCmdBinary_Output()、アスキー形式の場合はSerCmdAscii_Output()が実関数です。