【LabVIEWまずこれ㉚】キューメッセージハンドラを理解する

LabVIEWまずこれ

スポンサーリンク

LabVIEWを触ったことがない方に向けて、それなりのプログラムが書けるようになるところまで基本的な事柄を解説していこうという試みです。

シリーズ30回目として前回の記事に続きキューメッセージハンドラ(QMH)について紹介します。

この記事は、以下のような方に向けて書いています。

  • QMHの応用例を知りたい
  • QMHでプログラムを組むときの考え方は?

もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。

なお、 前回の記事はこちらです。

スポンサーリンク

QMHの復習

前回の記事で、QMHの仕組みや実際のプログラム例を紹介しました。

名前こそいかついですが、やっていることは生産者消費者デザインパターンみたいなものじゃん、と思えた方はきっとこの後のQMHレベル3も理解できると思います。

一方で、QMHレベル1やレベル2でもうかなり頭が痛い、という方は、私の説明が不十分ですいません。ただ、単純なキューの関数を使ったプログラムの仕組み、基本的な生産者消費者デザインパターンが分かれば、あとは何度もQMHのサンプルやテンプレートを読み込めば必ず理解できるようになる日が来ると思います。

QMHの理解がどうしても難しいという場合には、実行のハイライトを使うことをオススメします。デバッグツールの一つですが、これを使うことで各操作を行ったときにブロックダイアグラムでどのようなことが起きているかを目で追うことができるようになるためです。

以下では、QMHレベル1や2はもう十分理解できたという前提で進めていきます。

QMHレベル3

それではいよいよQMHレベル3を紹介します。このレベル3も、例によってプロジェクトテンプレートとして用意されており、「連続測定とログ」という名前になっています。

名前からも分かる通り、これまでの「キューメッセージハンドラの基礎」や「キューメッセージハンドラのテンプレート」と異なり、実際のアプリケーションの一例としてQMHを使用しているサンプルプロジェクトです。

連続測定とログのテンプレートを開いて、Main.viを見てみます。

一見、Whileループは3つしかないように見えます。が、2つめと3つめのループの間に二つのサブVIがあり、これらの中身を見てみると、実態はWhileループの中に様々な処理が書かれた、ステートマシンになっていることに気づくと思います。

画像で見せるとかなりごちゃついてしまうので、実際にはこのサンプルプロジェクトを実際に開いて中身をさらっと見てみた方が良いと思います。

レベル1やレベル2はWhileループが2つしかなかったのに比べて、一気にループが増えて複雑度が増しています。が、落ち着いてプログラムを追っていくと流れを追えると思います。

ここで大事なのは、「このようなプログラムをいきなり書けるようにならなくてはいけない」、ということではないということです。むしろ、「このような組み方ができるのだ」と知っておくことで、いざ自分のプログラムを作る時の方針立てに役立てるようにしよう、くらいの気持ちで、どのようにこのプログラムが処理を進めていくかの理解に努めてみてください。

まずは実行してみる

さて、いきなりブロックダイアグラムの中身を細かく見てみるのもいいのですが、このサンプルに関しては、とりあえず実行してみてください。で、フロントパネルのボタンを押して、このサンプルで何ができるのかをいろいろ試してみてください。

動作がわからない状態でブロックダイアグラムの中身を確認していくのは、特にQMHにまだ慣れていない状態では効率が悪いと思います。動作の内容を知ったうえで、このような動作を行うためにブロックダイアグラム上でどのように構成されているのか、を見ていきます。

というわけで早速プログラムを実行します。

実行すると、「準備完了」というステータスが表示され、押せるボタンが「開始」と「設定」、そして「終了」だけになり、「停止」は押せなくなっている状態となります。

なにはともあれ、とりあえず開始ボタンを押すと何やらデータが表示されるのがわかると思います。そして、先ほどまで「準備完了」と表示されていた部分には「集録とログの進行中」という表示が出て、また押せるボタンは「停止」ボタンのみとなります。

次に停止を押して、設定をクリックすると別のウィンドウが表示されどのような信号を測定するかを選んだりデータをログするためのパスを選択することができます。

設定はそのままでも構わないのですが、せっかくなので試しに「ノイズがある方形波」を選んでみます。

実際にまた開始を押すと今度は別の波形が表示されることがわかります。

プログラムは終了ボタンを押すことで終了できます。ただし、開始した状態では一度停止ボタンを押さないと終了ボタンを押すことはできません。

終了したら、設定の画面で指定していたパスに移ると、このサンプルで模擬として測定していた信号を保存したTDMSファイルが見つかります。

TDMSファイルはExcelで開く場合にはアドインがないと開けませんが、中身を見てみると、プログラム実行時に表示されていたデータが保存されている様子がわかります。

このように、このサンプルでは以下の動作を行えるようにしています。

  • 開始、停止ボタンを押すことで(模擬の)信号を測定する
  • 設定ボタンを押すことで、測定に関する設定(信号の種類、測定データの保存先)を行う
  • プログラムの現在の状況(準備完了、集録とログの進行中、など)をステータスとして表示する
  • 各操作を行うにあたってボタンを適宜グレーアウトさせることで不必要な操作を避ける
  • 終了ボタンを押してプログラムを終了させられる

プログラム実行時の一連の流れ自体はとてもシンプルでわかりやすいと思います。特別意味の分からない操作もないですし。

実行時の挙動がわかったところで、こうしたプログラムがQMHを使っていかに構築されているか、いよいよ見ていきます。

ブロックダイアグラムの構造を知る

さて、上で確かめた動作をするプログラムがどのようなブロックダイアグラムを持っているかを見てみます。

(なお、ここからの説明は実際に手元で同プロジェクトテンプレートを開きながら見てください。極力図を多用して流れを説明しているつもりですが、複数のVIが絡んで複雑になっているため、各サブVIなど見つつ読み進めてもらった方がいいと思います。)

もう既にブロックダイアグラムの外観はちらっと紹介していました。注目すべきは、それぞれのループが機能を持っているという点です。以下の5つのループ(そのうち二つはサブVI化しているので一目ではループとはわかりませんが)があります。

  • イベント処理ループ:イベントストラクチャを持っていて、フロントパネルやその他の場所でのイベントに対して反応、UIメッセージループに命令を出す
  • UIメッセージループ:このプログラムの司令塔。イベント処理ループから受け取ったメッセージを基に、他の機能ループへ指示を送る
  • 集録ループ:Acquisition Message Loop.viの中身。データの測定を行う機能を持つ。
  • ログループ:Logging Message Loop.viの中身。データのログを行う機能を持つ。
  • データ表示ループ:Acquisition Message Loop.viから受け取ったデータをフロントパネルに表示する機能を持つ

これらが絡み合ってこの「連続測定とログ」のプログラムを形作っています。

各機能ループ間でメッセージを渡すためには複数のキューが必要ですが、それらしいものは一つしかありません。そう、QMHレベル2で登場した「メッセージ専用キュー」です。

ただしQMHレベル2とは中身が異なり、このQMHレベル3のプログラムでは3つのキューを束にしています。それぞれ、UI、集録、ログと名前が付けられ、各機能ループに紐づいています。

このように用意したメッセージ専用キューを使用する場合、例えばある機能ループから集録ループであるAcquisition Message Loop.viにメッセージを送るには「集録」のキューリファレンスを使用すればいいことになります。

それ以外には、ユーザイベント用の関数が使用されているのがわかります。これもQMHレベル2で出てきた、プログラム的にイベントストラクチャを終わらせるための仕組みですね。

また、Acquisition Message Loop.viとLogging Message Loop.viには通常のキュー関数が使用されていて、取得したデータをAcquisition Message Loop.viからLogging Message Loop.viに渡すことで保存する仕組みをもっています。

あとはノーティファイアも使用されていることがわかります。これによりAcquisition Message Loop.viからデータ表示ループにデータを渡してデータ表示ループがデータをグラフに表示できるようにします。

大まかですが、各ループとそれらを結ぶ仕組みについてはこんなところでしょうか。

プログラムの流れを知る

では、プログラム実行時の流れを追ってみることにします。

プログラム実行後、最初に実行されるのはUIメッセージループとAcquisition Message Loop.viとLogging Message Loop.viの中身のInitializeステートです。

これらは、最初のメッセージキューを作成するときの関数でそれぞれのキュー(「集録」、「ログ」、「UI」)に対してInitializeのメッセージを送ることで実行されます。

このサンプルではAcquisition Message Loop.viとLogging Message Loop.viのInitializeでは特に何もしていません。

実際にQMHでプログラムを組む場合には、Initializeステートによってそのループで使用する様々な値を初期化する役割を持たせます。

UIメッセージループに関してはInitializeステートの中身で構成ファイルの読み取りと次にどのステートに移るかの指示があります。

構成ファイルの読み取りとは、この後で出てくるBroadcast settingsで使用されるデータで、要は「どのようなハードウェアを使用するか」や「どこにログするか」という情報の初期値をファイルから読み取る操作を指します。

QMHでは特に、このような「ある処理を行ってその結果をシフトレジスタで回し他のステートで使用できるようにする」書き方がよく用いられます。今回の例でいえば、「構成」というクラスタの中身を書き換えたり、その中身を使って別の処理をする、といったことをよく行います。

下の図で赤枠で囲ったサブVIが「構成」クラスタを書き換えるための処理を行っています。(赤枠のサブVIの説明は省略しますので気になる方は中身覗いてみてください。以降の他のステートの説明でも他のサブVIについて細かい説明はしていません)

どのステートに移るか、というのは、同じUIキューにメッセージを送ることで指示を出します。

キューに渡されているメッセージを見ると、この次にUIメッセージループで実行されるのはInit UI RefsとBroadcast Settings、そしてUpdate Statusですね。このうち、メッセージデータを必要とするUpdate Statusにはメッセージデータが渡されていますが、最初の二つはただ単にステートを変化させるように指示しているだけです。

これらはInitializeステート後、特にフロントパネル上でユーザーの操作、いわゆるイベントが発生していなくてもどんどん進行していきます。

ではUIメッセージループでInitializeのステートが終わった後の各処理を見ていきます。

Init UI Refsのステートでは、フロントパネル上の各制御器のリファレンスを取得してこれをUI Refsというクラスタにまとめシフトレジスタで回しています。

プログラムを実行しているときに、場面に応じてボタンがグレーアウトしているのに気付いたと思いますが、これはプロパティノードで実現しています。このプロパティノードを使用する際に、UI RefのクラスタをUIメッセージループの複数のステートで使用します。

この次に実行されるBroadcast settingsでは、Initializeでファイルから読み取ったハードウェアの構成やログファイルのパスの情報を集録キューやログキューに渡しています。これらはそれぞれAcquisition Message Loop.viとLogging Message Loop.viに紐づいていて、Update Settingsというステートを実行するように指示を出していますね。

なお、UIメッセージループではこの後にUpdate Statusステートが実行され、メッセージデータとして渡した文字列(準備完了…)を表示するようにしています。

さて、ここでUIメッセージループの動作はいったん終了です。以降はイベントが起きないとUIメッセージループは特に何もせずに待機状態になります。

UIメッセージループのBroadcast settingsでハードウェアやログの構成を受け取ったAcquisition Message Loop.viとLogging Message Loop.viについてもみていきます。

Acquisition Message Loop.viの中のUpdate Settingsステートが実行されることになり、読み取った値が集録ループのシフトレジスタに渡されます。これで、「ハードウェア構成」の情報はAcquisition Message Loop.viの中の集録ループで各ステートから参照できるようになりました。

Logging Message Loop.viについても理屈は全く同じで、シフトレジスタに渡すことでログの情報を各ステートで共有できるようになりました。

さて、これでようやく「プログラムが実行されてすぐに行われること」の一連の流れが終わりました。これ以降はイベントが発生しないとどのループも処理は全く進みません。

では、イベントとして「開始ボタンが押された」とします。すると、イベント処理ループではイベントストラクチャで開始の値変更イベントが実行され、StartというメッセージがUIメッセージループにわたります。

UIメッセージループのStartステートでは以下の命令を他のループに与えたりしています。

  • UIキューへUpdate Statusのメッセージを送り「集録とログの進行中…」というメッセージデータを渡す
  • ログキューへStartメッセージを送る
  • 集録キューへStartメッセージを送る
  • (フロントパネル上の一部のボタンの無効化を行う)

最後のは他の機能ループへの命令ではなく、「UIを更新する」という目的で実行される内容で、開始ボタンが押されたら停止以外のボタンを押せなくするようにしています。

UIメッセージループのUpdate Settingsは既に紹介済みなのでここでは省きます。

ログキューはいったんおいておいて、先に集録キューの動作を確認するためにAcquisition Message Loop.viの方から見ます。集録キューにStartと渡されたことでAcquisition Message Loop.viの中の集録ループのStartステートが実行されます。

この中ではHW Refつまり「ハードウェアの構成情報」のクラスタを読み取りハードウェアの初期化や構成をしている以外に、集録キューにAcquireというメッセージを送っています。

なお、集録キューでStartステートが実行される一つ前は、先ほど実行されていたUpdate Statusだったと思います。このUpdate Statusのステートで、列挙体として「idle」が渡されていたため、Startステートにてidleのケースが実行されます。

同様に、Startステートから次のAcquireにいく際にも列挙体として「Acquiring」が送られていることを覚えておきます。

このサンプルはハードウェアを使用していないのですが、もしサンプルを書き換えて実際のハードウェアを使用する際にサブVI(下の図の赤枠、緑枠の部分)の中身を編集します。

集録キューにAcquireというメッセージが渡されたことから、集録キューによって動くこのループはUIメッセージループに関係なく次にAcquireステートを実行させることができます。

Acquireステートでは、ハードウェアから信号を測定するという処理(を模したサブVI)と再びAcquireのメッセージを集録キューに渡すという動作をしています。

つまり何らかのきっかけで他のステートに進まない限りこのAcquireステートを続け、下の赤枠で示したサブVIにより、信号集録(の模擬)が行われ続けることになります。

信号測定を模したサブVIの中身では、測定した信号データのダミーをノーティファイアとキューに入れています。これらは、それぞれデータ表示ループとLogging Message Loop.viにデータを渡すための仕組みです。

ノーティファイアやキューでそれぞれ受け取ったデータは、Main.viのブロックダイアグラムを見るとわかりますが、それぞれLogging Message Loop.viやデータ表示ループに渡されていきます。

次にLogging Message Loop.viがStartメッセージを受け取ってからどう動くかですが、中身を見るとTDMSファイルを新規に用意する以外にログキューへLogメッセージを渡しています。

ここでも、ログループのStartステートの一つ前に実行されていたであろうUpdate Statusにて列挙体「Idle」が渡されていたためにIdleケースが実行されていることに注意します。

Logステートでは、先ほど見た集録キューのAcquireにあるサブVIから受け取ったデータがデータキューによって送られ、そのデータをTDMSに書き込んでいきます。

また、Logメッセージをログキューに渡すことで、これまた他の機能ループからのメッセージを受けずに自分自身でLogステートを実行し続けるようになります。

これで、ユーザが開始ボタンを押した際の各機能ループの一連の処理の流れは以上です。

つまり、開始ボタンを押してからイベントが発生しUIメッセージループが動作、これにより集録ループとログループが実行され、この二つのループは特に他の指示がない限りそれぞれずっとAcquireステートおよびLogステートを実行し続ける状態となっています。

次に、ユーザがフロントパネル上で停止ボタンを押したとします。するとイベント処理ループではStopというメッセージがUIメッセージループに送られるので、Stopステートを確認します。

この場合、Startステート同様、必要なボタンの無効化プロパティを変更して再び開始ボタンなどを押せるようにする以外に、Acquisition Message Loop.viとLogging Message Loop.viにどちらもStopメッセージを送っています。

Acquisition Message Loop.viにあるStopステートでは、使用していたハードウェアを安全に停止させる処理(を模したサブVI、以下の図の赤枠)とUIキューにAcquisition Stoppedというメッセージを送っています。

Logging Message Loop.viにあるStopステートでは、ログするデータをログするだけしておいて(以下の図の赤枠)UIキューにLogging Stoppedメッセージを渡します。

Acquisition Message Loop.viとLogging Message Loop.viそれぞれからメッセージを受け取ったUIメッセージループは順次メッセージを受け取って処理を行います。

これで、停止ボタンを押したことでデータの集録とログの二つが終わったことになります。

Acquisition Message Loop.viとLogging Message Loop.viは、どちらも次のステートに進むためのメッセージを受け取っていないので、待機状態になっています。

では3つめの操作として、設定ボタンを押した場合を考えます。イベントとしては設定の値変更で、Launch Settings DisplayというメッセージがUIメッセージループに渡されます。

すると、UIメッセージループではリファレンス呼び出しによってサブVIが呼び出され、この呼び出されたサブVIが実行された後の結果に応じて処理が変わります。

さらっと書きましたが、「リファレンス呼び出しでサブVIを呼ぶ」についてはまずこれのシリーズでは扱っていないので、一体何のこと?と思われる方がいるかもしれません。

気になる方は以下の記事を確認してみてください。

このサブVIはまさに設定を行わせるための機能を持っていて、キャンセルが押されたか否かのブール値をメインVIに渡し、もしキャンセルではなくOKボタンが押された場合にはUIキューにBroadcast Settingsのメッセージを渡すことになります。

Broadcast Settingsのステートは既にかなり最初の方で出てきましたね。このステートにより、UIメッセージループが持つクラスタ、を読み取って、集録キューとログキューにそれぞれメッセージとメッセージデータを渡すことで、二つのキューが持つクラスタを変更しているのでした。

なのでまた「開始」ボタンが押された際に、もし「設定」で今までと異なる設定を行っていた場合にはそれら新しい設定値が反映された状態でAcquisition Message Loop.viとLogging Message Loop.viそれぞれの中にある集録ループおよびログループが実行されることになります。

これで設定が押された場合の処理も終わりです。

では最後に、終了のボタンが押された時の動作を確認します。終了のボタンが押された場合には終了の値変更イベントが発生します。このイベントは優先メッセージとしてUIメッセージループに渡ります。

簡単なプログラムを書く場合、「終了」ボタンが押されたことを検出するイベントストラクチャが入ったWhileループは、この終了の値変更イベントで停止させるように、条件端子にTrueを配線することが多いのですが、このサンプルのイベントではイベントストラクチャが入ったWhileループは止めません。

UIメッセージループにおけるExitのステートでは、集録キューとログキューにExitのメッセージを渡しこれまた優先メッセージとなっています。そしてその後特にエラーがなければユーザイベントを発生させるための関数を経てUIメッセージループは終了します。

ユーザイベントが発生するとイベント処理ループが終わるのは、QMHレベル2のプログラムと同様ですね。

Acquisition Message Loop.viとLogging Message Loop.viのそれぞれもExitのケースは自分自身のWhileループを止めるという動きをします。

全てのリファレンスが解放されることでデータ表示ループ含め全ループが終了し、プログラム全体が終わります。

これでQMHレベル3のサンプル、連続測定とログの一連の動きの説明は終わりです。

実際は上で紹介していないイベントやUIメッセージループのステート、エラー処理のためのサブVIがいくつかあるのですが、それらも上で紹介したように各ループ間でのメッセージのやり取りを追っていくことで理解できると思います。

QMHのご利用は計画的に

QMHレベル2からレベル3はかなり飛躍があったかもしれません。実際ループの数が増えて各機能ループ同士の関係が複雑に絡むとなると、何も考えずにいきなり書き始めてミスなくプログラムを書き終えるのはほぼ不可能だと思います。

QMHほどのデザインパターンをマスターすれば、プログラム作りに困ることは少なくなると思います。ただし、「ちゃんと」QMHを書くにはまずプログラム全体の流れを考えてしっかりとした設計図を持っている必要があります。

どのような動きをしたときにどのような処理を行うか、これを一つの機能ループの単位でも考え、また機能ループ同士のメッセージのやりとりとしても考えていくことになります。

コツとしては、イベントを先に考えておくことです。すべてではないと思いますが、多くの場合プログラムにはフロントパネルにブールを始めとした制御器があり、ユーザーが何か操作をしたことをイベントとして検出し処理が始まります。

幸いなことにLabVIEWはブロックダイアグラムを後回しにして先にフロントパネルを用意することができるため、例えば操作に必要な(ブール)ボタンを用意しておいてフロントパネル全体が完成した後にそれぞれのボタンに対してどの特定イベントを発生させるか決めることができます。

あとはそのイベントによってUIメッセージループにメッセージを送り、これが司令塔となって他の各機能ループにメッセージやメッセージデータを送るようにします。

機能ループでは、その機能ループの各ステートで繰り返し更新されるような値をシフトレジスタで回せるようにしておきます。連続測定とログのプログラムでは例えばUIメッセージループについては構成やUI Refのクラスタがこれにあたります。

ほとんどの場合はクラスタや配列になると思いますが、これらを一つの機能ループの各ステートで何度も使い、時には他の機能ループから受け取ったメッセージデータで更新することができるように意識します。

なのでプログラムの設計の段階では各機能ループがこういったクラスタや配列としてどういったデータを持つべきかを考えます。

このプロジェクトテンプレートではハードウェア操作の部分は全て模擬で行っていますが、測定に必要なパラメタをクラスタでひとくくりにし、集録ループ単体で実行するにあたって必要となるであろう情報を各ステート間で共有するようにプログラムを書きます。

あとは、各機能ループの中身、つまりステートを逐次決めていき、その各ステートでシフトレジスタで回しているクラスタや配列の中身を編集したりそれらの中身を読みだして処理するようにします。

テンプレートを有効活用する

プログラムの設計ができたらあとはもうそれを実際にブロックダイアグラムに落とし込むだけなのですが、メッセージ専用キューやユーザイベントを操作するための仕組み(サブVIなど)を毎回一から作るのはとても面倒だと思います。

そんなときは、遠慮なくプロジェクトテンプレートにあるサブVIを拝借すればいいと思います。今回紹介した連続測定とログのプログラムも、プロジェクトエクスプローラ上にlvlibとしてまとまっているので、これをそのまま拝借し、何ならメインVIのブロックダイアグラムも丸々コピーしてしまえばいいと思います(作ろうとしているプログラムの設計と全然違うのであれば最初から作った方が早い場合もありますが)。

連続測定とログのサンプルでもドキュメントがあり、プログラムを変更する例が載っています。そもそもがハードウェアから信号を取るということを目的としたサンプルなので、実機のハードウェアからの信号を受け取って測定をするプログラムに書き換えることができます。

本記事では、QMHレベル3の全体の流れ、そしてQMHのデザインを採用してどのような順番でプログラムを書くかの一例を紹介しました。

「まずこれ」なんて題したシリーズなものの、QMHは上級者向けの内容だと思います。でも、マスターしたらかなり強力な武器になります。

前回の記事と今回の記事でQMHデザインについて少しでも理解が進めばうれしいです。

ここまで読んでいただきありがとうございました。

コメント

タイトルとURLをコピーしました