アプリケーションデザインパターン:ステートマシン

概要

ステートマシンは、LabVIEWの開発者がアプリケーションを迅速に構築するためによく使用する基本アーキテクチャの1つです。ステートマシンアーキテクチャを使用すると、状態図またはフローチャートによる複雑な条件判断アルゴリズムを実装できます。ステートマシンは固有のLabVIEW関数を使用して実装できます。アーキテクチャに別途ツールキットやモジュールを追加する必要はありません。

 

この記事では、ステートマシンとは何かについて説明し、ユースケースの例、ステートマシンのいくつかの概念的な例、ステートマシンのコード例を紹介します。

内容

ステートマシン何か

ステートマシンとは、前回のステートの値またはユーザ入力に応じて動的なステートへのフローを可能にするプログラミングアーキテクチャのことです。

このアーキテクチャは、以下で構成されるアプリケーションに適してします。

  • ステート
  • 特定のステートに遷移するタイミングを決定する条件判断論理


ステートは、プログラムが全体的なタスクを実行している間のプログラム内のステータスとして定義できます。ステートの例としては、初期化中、待機中、計算の実行中、ステータスの確認中などがあります。

論理ステートメントは、いつ新しいステートに遷移するか、どのステートに遷移するかを決めるのに役立ちます。イベントを使用すると、あるステートから次のステートへの遷移をトリガできます。イベントは、プログラムイベントやユーザ定義 (ボタンの押下など) にすることができます。

ステートマシンの各ステートは、独自の動作を行い、他のステートを呼び出します。ステートの通信は、何らかの条件またはシーケンスに依存します。状態図をLabVIEWプログラミングアーキテクチャに変換するには、以下のインフラストラクチャが必要です。

  1. Whileループ―さまざまなステートを連続的に実行
  2. ケースストラクチャ―各ケースには各ステートで実行されるコードが含まれる
  3. シフトレジスタ―ステートの遷移情報が含まれる
  4. 遷移コード―シーケンス内の次のステートを決定 (後述の遷移コードの例セクションを参照)

ステートマシン使用する理由

ステートの区別が明白なアプリケーションでは、ステートマシンを使用します。ステートでは、次のステートや複数のステートに遷移したり、プロセスフローを終了させたりできます。ステートマシンでは、ユーザの入力やステート内の計算によって、次にどのステートに進むかを決定します。多くのアプリケーションでは、「初期化」ステートと、その次に多数の動作を実行するデフォルトステートが必要です。実行できる動作は、前の入力、現在の入力、さらにステートに依存します。その後「シャットダウン」ステートを使用してクリーンアップ動作を実行できます。

条件判断アルゴリズムを実装する優れた機能に加えて、ステートマシンはアプリケーションプラニングを関数の形で表したものでもあります。アプリケーションが複雑化すればするほど、より適切な設計が必要になります。状態図とフローチャートは有用なツールであり、場合によっては設計プロセスに欠かすことができません。ステートマシンはアプリケーションプラニングにおいて有用なだけでなく、作成においても簡単だというメリットがあります。

使用例

たとえば、以下のアプリケーションにはステートマシンパターンが有効です。

  • 単一ページまたはタブ付きのダイアログボックス。ダイアログボックスの各タブは、1つのステートです。ユーザが特定のタブをクリックするとステートが遷移します。各タブのステートには、ユーザが実行できるすべての操作が含まれます。
  • 現金自動預け払い機 (ATM)。このアプリケーションには、ユーザ入力待機、要求金額に対する口座残高の確認、支払い、レシート印刷などのステートがあります。
  • 1つの測定値を取り、ディスクに記録し、次のユーザ操作を待機するアプリケーション。このアプリケーションには、ユーザ入力待機、測定、データの記録、データの表示などのステートがあります。
  • ステートマシンは、ユーザインタフェースのプログラミングでごく一般的に使用されています。ユーザインタフェースを作成すると、さまざまなユーザ操作に応じてユーザインタフェースが異なる処理ルーチンに送られます。これらの各ルーチンは、ステートマシンのステートとして機能します。これらのルーチンは、さらに処理を進めるために別のルーチンに遷移したり、別のユーザイベントを待機したりできます。この例では、ステートマシンは常にユーザを監視して次の動作を判断します。
  • 実装した処理のテストもまた、ステートマシンを使用する一般的なアプリケーションの1つです。この例では、処理のルーチン1つ1つがステートで表されます。各ステートのテスト結果に応じて、異なるステートが呼び出されます。このフローは持続的に実行できるので、テストの対象となる処理の詳細な解析を行うことができます。


ユーザインタフェースの実装に使用できる別のデザインパターンとして、キューメッセージハンドラがあります。キューメッセージハンドラは、より洗練されたステートマシンであり、柔軟性が高まりますが、複雑さも増します。 

ステートマシン作成する

効果的なステートマシンを作成するには、設計者が (1) 考えられるステートのリストを作成する必要があります。このリストを使用して、設計者は (2) 各ステートが別のステートとどのように関係するのかを計画できます。そして、状態図を (3) LabVIEWグラフィカルプログラミングアーキテクチャに変換できます。

例:大砲発射

この例では、大砲が危険なほど熱くならないように連続的に発射するアプリケーションを作成したいとします。

(1)   考えられるステートのリストを作成する
まず、タスクで考えられるすべてのステートのリストを作成します。大砲を継続的に発射するタスクを実行するには、次のことが必要です。

  • プログラムを初期化する
  • 大砲の電源を入れる
  • 大砲を発射する
  • デバイス温度 (ステータス) を調べる
  • デバイスを受動的に冷やす (温度がまだ範囲内にあるが上昇している場合)
  • デバイスを能動的に冷やす (温度が範囲外の場合)
  • デバイスをシャットダウンする
     

(2)   状態図でのステートの関係をマッピングする
次に、これらのステートが互いにどのように関係しているかを考慮し、状態図を作成します。状態図を作成するときは、ある統計から次の統計にプログラムが遷移する原因となるものを考慮します。それは自動的な遷移でしょうか。ユーザからの外部トリガがありますか。遷移は計算の結果に基づいているでしょうか。

たとえば、初期化ステートと他のステートとの関係を考えてみましょう。

  1. プログラムの最初のステップとなるのは初期化です。このステートには入力がありません。
  2. 初期化の完了後、エラーがなければ、続いて大砲の電源を入れます。
  3. エラーがある場合は、プログラムをシャットダウンします。
  4. 初期化中にユーザが停止ボタンを押した場合は、シャットダウンステートに遷移する必要があります。

ステートが互いにどのように関係しているかを十分に考えながら、これらのステート間における遷移ロジックの定義を始めたことに注意してください。コードで使用するプログラミングロジックは、(1) 考えられる遷移ステートの数、そして (2) ロジックで考慮される入力の数に依存します。コード遷移の選択肢については、以下の遷移コードの例のセクションで説明します。

引き続き完全な状態図ができるまで、ステートが互いにどのように関係しているかを調べます。状態図には、すべてのステートとそれらの間の関係が含まれることになります (図1)。この図では、ステート (楕円形のノード) は、制御プロセスがそのステートにあるときに実行される操作を表しています。一方、遷移 (矢印) は単に、プロセスがいつどのようににして、あるステートから別のステートに遷移できるのかを表しています。

図1: 大砲の発射の状態図

各ステート間の関係を参考にして、あるステートから次のステートへの遷移に必要なロジックをプログラミングできます。

(3) LabVIEWでステートマシンを構築する
状態図で定義したステートとそれらの関係は、LabVIEWでコーディングアーキテクチャに変換することができます (図2)。状態図 (図1) のステート間のフローは、ループによって実装されます。個々のステートはケースストラクチャのケースに置き換えられます。前の図の各ステートは、ケースストラクチャのサブダイアグラムに対応します。各ステートは次の動作を実行します。

  1. 操作を実行する
  2. Whileループのシフトレジスタに命令 (遷移コードど) を渡すことによって、次のステートが何かをステートマシンに伝達する


Whileループのシフトレジスタは現在のステートを保持し、それをケースストラクチャの入力に供給します。

図2: ステートマシン

ステートマシンサンプルプログラム

測定アプリケーションにおけるこのテンプレートの使用例については、プロジェクトを作成ダイアログボックスでシングルショット測定サンプルプロジェクトを参照してください。

  • 初期化後に、ステートマシンはイベントを待機ステートに遷移します。このステートには、フロントパネルの変更を待機するイベントストラクチャが含まれます。ユーザがボタンをクリックすると、LabVIEWによりイベントが認識され、イベントストラクチャの適切なサブダイアグラムに切り替わります。このサブダイアグラムによって適切なステートへの遷移がトリガされます。
  • 各ステートはデータクラスタにアクセスできます。このクラスタのデータタイプはData.ctlで定義されています。
  • 有効なステートは、タイプ定義であるState.ctlにリストされています。タイプ定義によってステートを遷移させることで使用できるステートが制限され、ステートマシンが不明なステートに遷移する可能性を減らすことができます。
  • アプリケーションを停止できるのは停止ステートだけです。この設計では、以下を保証することで意図しないシャットダウンまたは部分的なシャットダウンを回避できます。
     

1.     ユーザがアプリケーションの停止を意図した場合にのみシャットダウンコードを実行する。
2.     シャットダウンコードを必ず最後まで実行する。

  • ステートは一度に1つだけ実行され、1つのWhileループではすべてのタスクが同じレートで実行されます。マルチレートでの実行または並列タスクが必要な場合は、プロジェクトを作成ダイアログボックスで選択できる「キューメッセージハンドラ」テンプレートまたは「アクターフレームワーク」テンプレートの使用をお勧めします。
  • イベントを待機ステートは、ユーザ入力を認識する唯一のステートです。ユーザ入力を受け入れるためには、ステートマシンがこのステートである必要があります。

独自ステートマシンアプリケーション作成する

作業を始める方法については、『Modify the Simple State Machine LabVIEW Template』チュートリアルを参照してください。 

必要こと決める

テンプレートのカスタマイズを始める前に、以下の点を明確にします。

  • アプリケーションに必要なステート。 これを元に、追加するステートを決定します。
  • 各ステートの後続ステート。 これにより、各ステートがWhileループのシフトレジスタへ送信する次のステート列挙体の値が決まります。

    条件に応じて、1つのステートから複数の異なるステートへ遷移することもできます。たとえば、このテンプレートのイベントを待機ステートは、ユーザの入力内容に応じて異なるステートへ遷移します。
  • 各ステートでアクセスするデータの種類。 これを元に、Data.ctlに追加するデータのタイプを決定できます。
  • 発生する可能性があるエラーと、それらに対するアプリケーションの対応。 これを元に、必要なエラー処理の程度を決定できます。
     

遷移コードの例

次にどのステートに遷移するかを決定するには、以下で説明するさまざまな方法があります。
サンプルの図は「初期化」ステートを示していますが、これらの遷移の可能性はどのステートにも適用できることに注意してください。

1対1:常にステートAからステートBに遷移する場合は、ロジックをプログラムする必要はなく、次のケースの名前 (ケースB) をシフトレジスタに出力するだけです。

図3a:可能な遷移ステートは1つのみ

1対2:ステートAからステートBまたはステートCに遷移する可能性がある場合は、選択関数を使用して、インジケータのステータスを評価できます。何らかの評価を実行して、どのステートに遷移させるかを決定する必要があります。たとえば図3bでは、ユーザの停止ボタンの入力によって、電源投入ステートから遷移するかシャットダウンに進むかが決定されていることがわかります。

図3b:可能な遷移ステートは2つ

配列を使用した1対多:遷移できる複数のステートがある場合は、列挙定数に関連付けられたブール領域を使用して遷移をプログラムできます。たとえば図3cでは、何らかのコードが実行され、その結果によって遷移が決定されます。遷移の出力はブール値の配列です。ブール配列は、遷移可能なステートのリストを含む列挙定数と相互に関係しています。指標配列関数を使用すると、ブール配列の最初の「True」ブールのインデックスが出力されます。次に、部分配列関数を使用して、相互に関係する列挙定数から適切なものを引き出すことができます。

図3c

ヒント:配列のインデックスは0から始まり、列挙体のインデックスは1から始まります。ブール配列と列挙定数を相互に関連付けるには、インクリメント関数を使用してそのオフセットを修正します。

Whileループを使用した1対多:複数の遷移ステートが考えられる別の選択肢として、ケース内側でのwhileループの使用があります。ブールステータスがTrueに設定されて停止ボタンがトリガされるまで、whileループの内側のコードが続行されます。これにより、トリガイベントが発生するまでコードを効果的に実行できます。

図3dは、内側ループとケースストラクチャを使用して次のステートに遷移する「初期化」ステートを示しています。内側のケースストラクチャには、現在のステートを離れる遷移ごとにダイアグラムが1つずつ含まれています。内側のケースストラクチャの各ケースには2つの出力があります。1つは遷移が必要かどうかを指定するブール値で、もう1つは遷移先のステートを指定する列挙定数です。ケースストラクチャへの入力としてループインデックスを使用することにより、このコードは、「True」のブール出力を持つダイアグラムが見つかるまで、各遷移ケースを1つずつ効果的に実行します。「True」のブール出力が見つかると、遷移先となる新しいステートがケースから出力されます。この方法は前の方法よりも少し複雑に見えるかもしれませんが、ループインデックスの出力を列挙体に「キャスト」することで、遷移に名前を追加する機能を提供できます。こうした利点により、遷移コードに「自動ドキュメント」を追加できます。

図3d

プログラミングにおける他の注意事項

 

コードの冗長化
問題:ステートマシンの作成で最も難しいのは、状態図において考えられるステートを区別することです。たとえば、自動販売機の状態図 (図4) では、「応答を待機」ステートを設けて、投入されたコインの種類に応じて、あるステートから別のステートに遷移するのではなく、0、5、10、15、20、25、30、35、40、45、50セントのステートを持たせることもできました。その場合は、まったく同じケースダイアグラムで11個の異なる状態が作成されることになります。コードが冗長になると、大規模なアプリケーションで大きな問題を引き起こす可能性があります。

解決策
:異なるステートに同じケースダイアグラムがある場合は、それらを1つのステートに結合してみてください。たとえば、コードの冗長性を避けるために、「応答を待機」ステートを作成します。

列挙体の使用
問題:列挙体は、ステートマシンのケースセレクタとして広く利用されています。ユーザがこの列挙体に対してステートの追加または削除をしようとすると、その列挙体のコピーにつながっている残りの連結線が壊れます。このことは、列挙体を使用したステートマシンの実装を難しくする最も一般的な妨げの1つになっています。

解決策: この問題については以下の2つの解決策が考えられます。
1.変更を加えた列挙体からすべての列挙体がコピーされると、連結線の壊れはなくなる。
2.列挙体を使用して新しいコントロールを作成し、サブメニューから「typedef」を選択する。typedefを選択することで、ユーザがステートを追加または削除した場合に列挙体のすべてのコピーが自動的に更新される。

ステップ

LabVIEWのステートマシンやその他の高度なアーキテクチャについてご興味のある方は、LabVIEW 実践集中コース 2 カスタマー トレーニングコースをご検討ください。

Was this information helpful?

Yes

No