LabVIEWでプログラムを組むときに必要になるデザインテンプレートの案を紹介しています。本ブログで紹介している具体的なプログラム例のベースとなる組み方を説明しています。
この記事ではイベント駆動型ステートマシンを紹介しています。
イベント駆動型ステートマシンに必要な知識
イベント駆動型ステートマシンは、その名の通り、イベントに応じて各ステートを実行させるタイプのステートマシンです。
そのためイベントストラクチャを使用することになります。
必要な知識としては
- 単純なステートマシンの作り方
- イベントストラクチャの使い方
が必要となります。
単純なステートマシンの作り方とは、ステートマシンがどうやって状態を移っていくか、そのためにLabVIEWでどのように組む必要があるかということがわかっていれば十分かなと思います。
具体的には以下の記事で紹介しているようなプログラムが特に不自由なく組めるのであれば問題ないかなと思います。
イベントストラクチャについては、イベントの定義の仕方がわかればいいかなと思います。
この記事でも紹介しますがイベントストラクチャ使用上の注意点(フロントパネルロックなど)があらかじめわかっている状態であればそれで十分です。
もしイベントストラクチャ自体およびその使用についての注意点について不安があるよという方は以下の記事も参考にしてもらえるかなと思います。
イベント駆動型ステートマシンの基本的な形
イベント駆動型のステートマシンは、ケースストラクチャで定義するステートの一部にイベントストラクチャを配置し、発生したイベントに応じて次の状態を指定します。
基本的な構造としては以下の図のような形です。

それぞれのイベントを検出し各ステートに移るためには、eventステートが基点になる必要があります。
そのため、eventステートから他のステートに移行しても、またeventステートに戻ってくるような仕組みとします。
例として、以下のように3つのブール制御器(ラッチ動作にしたボタン)があるプログラムで、ユーザーが各ボタンを押すたびにそれぞれ異なるステートに移行するというプログラムが考えられます。

実行の状態を分かりやすくするため、ボタンが押されたら「state1実行」などと表示されるようにしておくと、プログラムの流れがわかりやすくなります。

このような構造をもつプログラムを作る場合、初期化処理のためのステート(以下の図ではinit)やプログラム終了のためのステート(以下の図ではfinish)以外だと、イベントを検出するためのeventステートと、そこから移行する各ステートをケースストラクチャで用意します。

それぞれの中身は以下のような形となります。

eventステートでは、イベントストラクチャの中に各ボタン毎にイベントを定義しており、それぞれによって異なるステートに進むようにシフトレジスタに列挙体を渡しています。

各ステートに対しては、それぞれのステートで行う処理(下の図では単に今自分がどのステートにいるかをポップアップ表示するという処理を実装)を書いて、その後eventステートに移るという構造にします。
なお、あるステートからeventステート以外の別のステートに移ることももちろんできますが、最終的にはeventステートに移る必要があります。
例えば以下ではstate2からはstate3に移るようにしていますが、state3はこの次にeventステートに進むようになっているので、最終的に「eventステートに戻る必要がある」という要件を満たしています。

なお、押されたボタンによってイベントを分ける場合、ブール制御器の「値変更」イベントをそれぞれ作ればいいのですが、ボタンが多くなるとイベントの数が増えていきます。
まぁそれでもいいのですが、イベントストラクチャとしては似たような内容が続くため、これらはまとめてしまう、という組み方も考えられます。
例えば、押されたボタンのラベルの名前から「どのブール制御器が押されたか」を判断し処理内容を変えれば、一つのイベントケースでそれぞれのボタンの値変更イベントを登録することでイベントケースを増やさなくても済みます。

プログラムの規模が大きくなるほどイベントの数も増えていくことと思うので、見やすさの観点からするとイベントケースは少ない方が見やすくなります。
イベント駆動型ステートマシンの弱点と対策
イベントストラクチャを使用すると、無駄にループが回るという状態を減らせたり、各イベントに応じて確実にその内容を実行させるといった組み方が可能になります。
が、いいことばかりではなく、気を付けて組まないと意図した動作をさせられないということが起こりえます。
例えば、以下の図のようなプログラムを考えます。
これは、各ステートでわざと時間がかかるようにし、eventステートにすぐには戻らないような場面を意図的に作っています。
実際のプログラムでも、何らかの処理が数秒かかる(ハードウェア操作やファイルIOの操作など)ことは十分考えられるため、このような場面は起こりえます。

このとき、ボタンを押すことで指定した状態に移ること自体はできるのですが、その状態からeventステートに戻ってくるまでの間にユーザーがさらにボタンを押した場合、フロントパネルはロックされてしまいます。
これは当然で、イベントストラクチャはイベントストラクチャがあるステートであるeventケースでないと実行できず、また一度に実行されるイベントは1つなので、「eventステートからeventステートに移る」ような処理でもない限り、eventステートから別のステートに移りその後eventステートに戻ってくるまでの間はイベントストラクチャがイベントを処理できないためです。

このロックされるという状況自体はイベントストラクチャのデフォルト機能ですが、この機能をオフにすることができます。
ただし、オフにしたとしてもデフォルトのままだとしても、ボタン操作をしてすぐにイベントが反応しない、という問題は残ります。
結局、eventステートに戻ってイベントストラクチャで一つ一つのイベントを消費しなくてはならないことに変わりないからです。
消費されずに残っているイベントは、イベントストラクチャを使用したプログラムのデバッグで有効な、イベント検査ウィンドウ(メニューバーの「表示」から選択)で確認することができます。

こういった状態に陥ってしまうことについて軽く考えていると危険です。
ただ単にロックしてしまうこと自体も不便は不便なのですが、ユーザーがこの仕様を知らない場合、「あれ、ボタンを押しても反応しないな」ということで、ボタンを連打してしまうことになりかねません。
イベント自体は一つずつイベントストラクチャで消費されるまで「処理されていないイベント」としてたまっていくので、ユーザーがボタン操作した分のイベントは次々に発生してしまいます。
つまり、意図した回数やタイミングでのイベント発生ができず、またプログラムも止められないといった事態を引き起こしかねない、ということになります。

この状態を防ぐ方法の一つとしては、そもそも一つのイベントが発生したら、再度イベントにすぐに反応する(eventステートに戻ってくる)まで、ユーザーのボタン操作を禁じる、という組み方にすることです。
これは、無効プロパティによって実現できます。
こうすれば、ユーザーはボタンを押せないのだと視覚的に判断できますし、万が一ボタンを押したとしてもイベントは発生しない(「値変更」とならないので)ため、一つ一つのイベントを意図したタイミングで実現でき、また停止ボタンによる終了処理へもeventステートから素早く移行できます。

無効プロパティを使用する方法はどのような組み方でもいいと思いますが、簡便なのは以下の図のような組み方で、どれかイベントが起こるとその時点で全てのボタンの無効プロパティを操作するサブVIを実行するような方法です。
eventステートの、イベントストラクチャの外側に、無効プロパティの設定でEnabledを指定するようにしておけば、別のステートからeventステートに戻ってくるたびボタンが押せるようになります。

サブVIは以下のようにしておくと複数のボタンに対してまとめて無効プロパティを設定できるので便利です。

なお、停止ボタンに対しては敢えて無効プロパティを設定していません。
もちろん設定してもいいのですが、本当になるはやで停止させたいときには停止ボタンが押せるようになっていた方が安心ですし。
それでもeventステートでないと停止ボタン押下によるイベントを検出できないことに変わりはありません。
なので望ましいのは(停止処理以外の)各ステートで、それぞれかかる時間(言い換えればeventステートに戻るまでの時間)がより短くなるように調整することです。
処理したい内容によっては難しいこともありますが、例えばあるステートにおいて何かしらの処理を繰り返す必要がある場合、その繰り返すという作業をそのステートの中にForループ(やWhileループ)を入れて繰り返させるのではなく、そのステート自体を繰り返させるという組み方にすることができます。
この場合、繰り返したい回数を定数としておいておき、カウンタの役割を持つ数値をシフトレジスタで回し、そのステートに移るイベントが検出されたときにカウンタを0として、ステートの中でインクリメントさせ、繰り返したい回数になるまでそのステート自身を次のステートに指定するようにします。
この組み方と、停止ボタンをイベントストラクチャから独立させて、常に停止ボタンが押されたかどうかを判定させるような仕組みを組み合わせることで、停止ボタンが押されて終了処理に移行するための応答速度を早めます。

あれ、これだと停止ボタンはイベントストラクチャに入ってないけどいいの?と思うかもしれませんが、もしイベントストラクチャに停止ボタンを入れたまま上記のような組み方を実現させる場合には単純なステートマシンの構造では無理で、キューを使ったプログラムにする必要があります。
この記事では、ユーザーによる操作を必要とするステートマシンをより効率よく実行するためのイベント駆動型ステートマシンについて紹介しました。
「無駄なループはまわさない」という観点からイベントストラクチャを使用してプログラムのパフォーマンスを上げるのは、必要な場面がありつつも、実際は今回の記事で紹介したような弱点に気を付けないと、思わぬ動作をしてしまいかえって使いにくいプログラムになりかねないので十分注意してください。
ここまで読んでいただきありがとうございました。




コメント