Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
汎用クラス・関数
基本クラス、関数
Loading...
Loading...
ユーティリティ関数
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
書式パーサー
書式パーサーは、IParserを基底クラスとして、書式ごとに派生クラスを実装します。
パーサーは、シリアルポートのように1バイトずつ入力されるデバイスを想定し、1バイト単位での解釈を行い、都度状態を遷移する状態遷移マシンとして実装されます。
アプリ記述setup(),loop()の切り替え
アプリケーションはsetup()
,loop()
の2つの関数で記述されますが、例えば1画面ごとに大きく振る舞いが違うもの(ここではサブアプリと呼びます)を複数定義して、メニューで切り替えるといった動作を行う場合は、そのままsetup()
,loop()
内に記述しては煩雑になります。また変数についても管理する必要があります。
setup(), loop(), 関連する変数の切り替えを簡素化するためMWM5ライブラリでは以下の2つの仕組みを実装しています。
サブアプリ : setup()
, loop()
, 関連変数
をクラスとしてまとめ、切り替える
アプリハンドラ: サブアプリ内で setup()
, loop()
を切り替える
上記例は説明に必要な最小限のコードのみを記述します。APP_DEF
を継承したApp_Foo
,App_Bar
が、2種類のサブアプリの定義となり、各々がsetup()
,loop()
メンバー関数を定義しています。実際には振る舞いごとに必要なメンバー変数やその他メンバー関数が定義されます。サブアプリの登録や切り替えはライブラリ定義のクラスオブジェクトthe_app
に対して操作を行います。s_change_app()
関数はサブアプリを切り替える要求(サブアプリ終了時など)に対して、実際の切り替え部を実装していて、この関数をsetup()
中で呼び出すthe_app.setup()
のパラメータとして渡しておきます。loop()
中にはthe_app.loop()
を記述しておきます。
void setup()
とvoid loop()
を記述しておきます。setup()はサブアプリが初期化されるときに1度だけ呼び出されます。loop()
はthe_app.loop()
関数中で呼び出されます。
実行中のサブアプリが終了する場合に呼び出されます。サブアプリは1以上のIDにより種別を管理し、切り替えのために関数int (*)(THE_APP_MGR&, int, int, int)
を実装しておきます。
パラメータn_appsel
には、切り替えたいアプリケーションのIDが渡されます。パラメータはthe_app.exit()
で指定した値です。-1
が渡された場合は、デフォルトのサブアプリに切り替えるよう記述しておきます。
パラメータprev_app
には、直前のサブアプリIDです。直前が無ければ0となります。
パラメータexit_id
は、サブアプリがthe_app.exit()
で指定した値です。
n_appsel
で次に動作させるサブアプリを指定し、exit_id
はサブアプリに対するパラメータとして用います。
サブアプリ切り替え関数内部ではthe_app.new_app<T>()
(T
は次に生成するサブアプリのクラス)を呼び出しています。この呼び出しによりクラスT
のオブジェクトが生成され、同時にこれまで動作していたサブアプリのオブジェクトは破棄されます。
setup()
とサブアプリ切り替え関数内で呼び出し、サブアプリのオブジェクトを生成し、同時に現在のサブアプリを破棄します。
最初に動作させるサブアプリはsetup()
で生成します。
テンプレートパラメータT
はサブアプリ定義クラスでAPP_DEF
を継承している必要があります。
テンプレートパラメータargs
は、コンストラクタに渡されるパラメータです。オブジェクトはnew T(std::forward(args)...)
で生成されます。サブアプリがパラメータを指定しないデフォルトコンストラクタで構築される場合は何も指定しません。パラメータを指定する場合はサブアプリのコンストラクタの定義が必要です(例えばApp_Foo::App_Bar(int)
を定義しておいてthe_app.new_app<App_Bar>(exit_id)
)。
サブアプリのloop()
内で呼び出し、loop()
を抜けた直後にサブアプリを切り替えます。
上記の例のように大本のloop()
からはthe_app.loop()
が呼び出され、さらにサブアプリのloop()
が呼び出されます。サブアプリのloop()
中でthe_app.exit()
が呼び出されると、the_app.loop()
内でアプリケーションの切り替えが行われます。実際に切り替えているのはユーザが定義するサブアプリ切り替え関数です。
パラメータexit_code
は、サブアプリの終了コードを指定します。任意の数を指定できます。
パラメータnext_app
は、次のサブアプリを明示的に指定する意味合いの識別子です。-1
はデフォルトのサブアプリ、0
は切り替えを行わない、1
以上が次のサブアプリの識別子と意味付けをしています。
exit_code
とnext_app
は、そのままサブアプリ切り替え関数に渡されます。サブアプリ切り替え関数は、その2つのパラメータを参照して次のサブアプリを決定します。
大本のloop()
からthe_app
で生成したサブアプリのクラスオブジェクトを参照し、さらにサブアプリが指定したvoid*
型のポインタデータを取得します。
例えば、サブアプリ構築時に共通のクラスオブジェクトを持っていて(例えばメイン画面となるターミナル画面)、大本のloop()
から共通のオブジェクトにアクセスします(例えばメイン画面上の文字列を取得)。
アプリハンドラAPP_HNDLR
は、サブアプリ中での簡易的なsetup()
,loop()
処理の切り替えを行います。例えば、リスト一覧→リスト選択後、エラーといった複数の画面を切り替えるような動作です。
サブアプリの実装ではAPPDEF
を継承しましたが、さらにAPP_HNDLR<S>
(Sはサブアプリクラスを指定します)を継承します。サブアプリ中に処理関数(アプリハンドラ)を実装し、アプリハンドラを切り替えます。
上記がアプリハンドラを用いたときのソース構造の例です。
hndr_scr_def()
とhndr_scr_next()
がアプリハンドラ関数です。パラメータとしてev
とarg
が与えられ、ev
はEV_SETUP
,EV_LOOP
,EV_EXIT
のいずれかになり、setup()
,loop()
と終了処理に対応します。
アプリハンドラ関数はsetup()
,loop()
に相当する処理を行います。加えてハンドラが切り替えられる、つまり現在登録されているハンドラが終了するときの処理を記述できます。
パラメータev
に対応した処理を記述します。
パラメータarg
は、APP_HNDLR::new_hnbdlr()
やAPP_HNDLR::loop()
でパラメータが与えられたときに、値が格納されます。
アプリハンドラを初回生成する(サブアプリクラスのsetup()
)、またはアプリハンドラ内から次のアプリハンドラへ切り替えます。
パラメータhnd_next
はアプリハンドラ関数(メンバー関数のポインタ)を指定します。
パラメータarg
はアプリハンドラ関数のEV_SETUP
呼び出し時のパラメータを指定します。(EV_SETUP
による初期化時の処理を分岐したいときに利用します)
サブアプリのloop()
から毎回呼び出します。
パラメータarg
を指定するとアプリハンドラにその値が渡されます。
サブアプリのデストラクタから呼び出すようにしてください。
この記述を省略した場合、サブアプリのオブジェクトが破棄されるときに、アプリハンドラも破棄されますが EX_EXIT
メッセージが呼び出されません。
アプリハンドラは、サブアプリ内でsetup(),loop()コンテキスト切り替えの仕組みで、サブアプリ下でのメンバー関数の実行という携帯です。そのため、アクセスできるデータはサブアプリのメンバー変数となります。
ここでは、アプリハンドラ独自のデータを保持するクラスを追加します。
APPHNDLR_DCを継承したクラスを準備します。このクラスにはアプリハンドラ内で使用するデータやメンバー関数を定義します。以下の例ではDC_MyAppHndlr
というクラスを定義しています。
以下の定義を行う必要があります。
static const int CLS_ID
: データ格納クラス種別を識別するための個別のIDです。
int get_class_id()
: CLS_ID
を戻すメンバ関数です。
コンストラクタ : サブアプリクラスオブジェクトをパラメータとします。このオブジェクトは上記の例ではappに保存しDC_MyAppHndlr
からサブアプリにアクセスする目的で利用します。
継承したAPPHNDLR_DCをAPPHNDLR_DC(CLS_ID)
として初期化します。
DC_MyAppHndlr
はサブアプリのメンバー変数や関数にアクセスすることも多いためfriend
宣言しておきます。もちろん一般のクラス設計のようにpublicインタフェースを用意してカプセル化してもよいのですが、この場合、カプセル化の利点があまりなく記述が煩雑になるためです。
アプリハンドラではAPP_HNDLR::use<>()
を用いてデータオブジェクトを取得します。オブジェクトの生成・破棄は暗黙に行われます。別のアプリハンドラに切り替えられたときにデータオブジェクトが破棄されます。
上記の例では、アプリハンドラ関数の最初でdc
を取得しています。初回呼び出しにオブジェクトが生成され、それ以降は生成されたオブジェクトを参照します。
あとは生成されたdc
オブジェクトを操作します。
EV_SETUP
setup()に相当し、アプリハンドラが登録・切替時に呼び出される。
EV_LOOP
loop()に相当し、都度呼び出される。
EV_EXIT
アプリハンドラの切り替えられ、現在のハンドラが終了する際に呼び出される。
アスキー形式のパーサー
アスキー書式の解釈を行うパーサーですが、TWESYS::TimeOut
クラスをベースクラスに持つことで、タイムアウト処理を行っています。
パーサーオブジェクトを生成します。
生成時のパラメータにmaxbuffsiz
を与えると、maxbuffsiz
をバッファサイズとして動的にメモリ確保して、パーサーを初期化します。
あらかじめ生成されたSmplBuf_Byte
配列bobj
を参照して、パーサーを初期化することもできます。
アスキー書式の解釈アルゴリズムを実装します。バイトの入力のたびにタイムアウトのチェックを行います。
書式出力を行います。s_Output()
メソッドを呼び出します。
vPutByte()
は、ストリームに対して与えられたバイトu8byte
をアスキー2文字で出力します。例えば0x9Aであれば"9A"という2バイト文字になります。
s_vOutput()
は、ストリームに対して、与えられたバイト配列SmplBuf_Byte
のバイト列をアスキー形式で出力します。
パケットオブジェクト
パケットデータは種別によってデータ構造が違いますが、様々な種類のパケットを一元管理するための基底クラスです。
spTwePacket
はメモリ管理のためのスマートポインタです。std::shared_ptr
を用いています。
TwePacketクラスは、パケットデータのパケット種別の管理を行います。また、パケットデータの解釈を行うための仮想関数parse()メソッドを定義しています。パケット特有のデータ構造に基づく解釈やデータの保存等の取り扱いは、派生クラスに実装します。
デフォルトでは、未解釈状態として E_PKT::PKT_ERROR
で初期化します。
パケットの種別をE_PKT
型で返します。
パケットデータのバイト列を与えて、パケットデータを解釈する。
派生クラスで、そのパケットに対応するデータ構造を解釈するための実装を行います。
戻り値は E_PKT
型 で、成功時は解釈されたパケット種別を、エラー時に E_PKT::PKT_ERROR
を返します。
パーサーの基底クラス
パーサーオブジェクトに1バイトずつ電文を投入することで、電文系列を解釈する状態遷移マシンです。
パーサーに1バイト入力します。入力のたびにパーサーの状態が変化し、パーサーの解釈が完了するとstate()
がE_TWESERCMD_COMPLETE
に変化し解釈完了状態となります。
パーサーの状態を取得します。
パーサーの状態がE_TWESERCMD_COMPLETE
の場合true
になります。
パーサーで解釈済みのバイト列のデータ長を返します。
パーサーの解釈済みのバイト列にアクセスします。
パーサーの解釈済みのバイト列を格納した配列クラスSmplBuf_Byte
を参照します。
パーサーオブジェクトを用いて出力を構築する際に、データ列をコピーします。コピー元はSmplBuf_Byte
データ構造またはuint8_t*
型の先頭と終端+1ポインタの組み合わせです。
パーサーの解釈途中の内容を破棄し、新たな解釈を始めます。
IStreamOutをベースクラスにもつストリームオブジェクトに、書式出力します。
1バイト入力して解釈を進める仮想関数です。派生クラスにより実装されます。
バイト配列bobj
に格納されるバイト列に対応する書式をストリームp
に出力する仮想関数です。派生クラスにより実装されます。
パケット種別定義
以下のパケットに対応します。
App_Wings の親機で出力されるアスキー書式に対応します。
パケット種別判定
パケット定義
書式を解釈して得られたデータ列は、受信したパケット情報が含まれます。ここでは、このデータ列のことをパケットデータと呼びます。
このオブジェクトは無線パケットのデータ量に準じたメモリ領域を消費し、また、アプリケーションでは多数のパケットを保持することも考えられます。メモリ管理を簡略化するためTwePacket
をスマートポインタstd:shared_ptr<TwePacket>
にて管理します。このスマートポインタをspTwePacket
にtypedef
しています。
以下に、TWELITE PALのパケットデータの場合のクラス関係を示します。TwePacketPal
の基底クラスの一つDataPal
はTWELITE PAL特有のデータを格納しています。TWELITE PALには、さらに接続されるセンサーパルによって格納すべきデータが異なります。TwePacketPal
からさらにPalAmb
やPalMot
を生成します。例えばPalAmb
には温室センサーの値や照度センサーの値が格納されます。このTwePacketPal
をスマートポインタspTwePacket
という形に生成するのがnewTwePacket()
です。
spTwePacket
型はスマートポインタですので、オブジェクトのコピー渡しによる記述を行うことで、コピーのオーバーヘッドを最小にしつつ、メモリーの管理を自動化できます。以下の例はパケットの履歴を管理する単純なクラスです。
パケットデータのバイト列を入力として、パケットの種別を判定します。また既に生成済みのオブジェクトの種別を返します。戻り値はです。
パケットデータはクラスで表現されます。TwePacket
クラスのデータを解釈することで、その種別を判定し、各アプリケーションや接続ハードウェアに応じたデータ構造となります。TwePacket
クラスは基底クラスでさらにパケット種別ごとの派生クラスとなります。