この記事は、初心者向けのまずこれシリーズ第29、30回の補足記事です。QMHってどんなときに使うのがいいのか知りたい、という方向けにもう少し内容を補足しています。
どんなときにQMHを考えるか
まずこれのシリーズでは、キューメッセージハンドラ、QMHを扱いました。
それまでに扱っていた、ステートマシンや生産者消費者デザインと比べると、規模も大きくて理解するのに一苦労すると思いますが、大枠としてはステートマシンの考え方や生産者消費者でも使用するキューを扱っているなど、基本的なデザインパターンを一緒くたにまとめたようなデザインになっています。
ただ、QMHのサンプルを理解できるのと、実際に一から書き始めるのとはまた違った難しさがあると思います。また、作るのが難しいということで、デバッグもそれだけ複雑になります。
ここで大事なのは、なんでもかんでも難しいデザインパターンで作成しない、ということです。
いざプログラムを書くというときに実装時の選択肢になるくらい、QMHの書き方に慣れているに越したことはないですが、もし簡単なアルゴリズムやデザインパターンで実装できるのであればその方が効率がいいことも多々あります。
そもそも、プログラムは設計が命です。プログラムをいきなり作り始めるのではなく、どんなデザインパターンで実装できそうかという設計をまずは考える必要がある、ということです。
設計段階で、どんなふうにやりたいことをプログラムに落とし込んでいくかを考えていく間に「どうしてもこの方法ではうまくいかない」といった壁にぶつかったとき、新たなデザインパターンを選択肢にするという形で考え進めていけばいいと思います。
とはいえ、デザインパターンとしてどれがいいか頭の中で(もしくは紙に書いて)考えるのは慣れないと難しかったりします。
あくまで私個人の考えですが、QMH(もしくはQMHのような考え方でプログラムを組む)がいいのではないかと考える一つの基準としては、
- 定期的に更新されることと不定期に更新されることが混在する場合にQMHの考え方で実装を考えてみる
ということが挙げられると思っています。
実際には他にもありますが、今回の記事では上の基準に対して例を紹介していこうと思います。
ただし、どんなときでも必ず!という話でもないと思うので最終的には自己責任でデザインパターンを選んで組んでください。
データ取得プログラムで考える
もし難しいデザインより簡単なデザインが採用できるのであればそれを考えるべきですが、ここでは以下のようなプログラムを作る場合を例に、どのようなデザインパターンがいいかを考えてみます。
- データを取得する動作を模擬している。取得したデータはグラフに表示される。
- データ取得のサンプリングレートは1 kHz、一度にグラフに表示させるデータ数は1000。よってグラフは1秒に1回更新される。
- グラフには、2つめのプロットとして赤い四角が設定されており、取得したデータ点のうち一点を囲んでいて、その四角がある位置のY値の具体的な値が文字として表示されている。
- 赤い四角は、別画面で用意されたウィンドウ(操作パネル)上のボタンを押すことで、左右に動かすことができる。
- ただし、赤い四角はグラフ上のデータが更新されるタイミングとは無関係のタイミングで動かせる(例えばグラフが1秒に1回更新される間に赤い四角は5回位置を変えることができる、といったような操作が可能)ものとする。
具体的なプログラムの見た目で説明すると、まずフロントパネルとしては以下の図のような見た目であるとします。
実行すると、波形が表示されるプログラムと、別ウィンドウで操作パネルとして機能するプログラムが表示されて、波形データそのものの更新は1秒に1回行われつつ、任意のタイミングでユーザーが操作パネルを操作したときに赤い四角が動く、ということになります。
さて、このようなプログラムを実装するにあたって、どのようなデザインを選べばいいか?を一つずつ考えていくことにします。
まず、単純なステートマシンで考えると、色々な壁にぶつかってしまいます。
例えば、別画面を表示させるためにサブVIを使用することになりますが、その場合にはサブVIが終わらないとメインVIはそのサブVIの部分で停滞することになるため、ステートマシンのWhileループの中のいずれかのステート中に置くことができません。
この問題は、ステートマシンのWhileループとは独立した場所にサブVIを置くことで簡単にできます。
ではステートマシンのWhileループとは別の場所にサブVIを置いたとして、今度は値の扱い方を考えます。
サブVIとメインVIとの値のやりとりはグローバル変数を使用してできなくもないですが、赤い四角を動かすタイミングとグラフ上のデータが更新されるタイミングが独立したような状態で書くことはできないです。
次に、単純な生産者消費者デザイン(生産者ループと消費者ループが1つずつ)でも考えてみますが、結果は同じく、ばらばらのタイミングでデータ表示や赤い四角を移動させることができません。
これらステートマシンでも生産者消費者でもできない理由としては「周期的な操作と、ユーザー操作との両方を受け付けるといった組み方ができない」ことが挙げられます。
グラフ上の表示に対して、データがある決まった周期で取得されそれを表示させるということと、それとは別のタイミング(ユーザーの操作次第)で行われる四角を動かすという操作を実装するには、
- ユーザー操作の結果をグラフに渡す(不定期的に更新)
- 一定周期でデータをグラフに渡す(定期的に更新)
- 1や2の方法で渡された値を基にグラフを更新する(定期的な更新と不定期な更新が混在している状態でグラフの更新をする)
という動作が実現される必要があります。
こういった要求に対して出番が来るのがQMH的な考え方です。
QMHで実装する場合の、用意するべきループは次の3つになります。
- コントロールループ(イベントハンドリングループ)
- データ取得ループ
- データ表示ループ
それぞれが、上で書いた3つの動作に対応しています。
なお、QMHではよくメッセージハンドリングループが存在していることがありますが、今回はイベントハンドリングループがその役割を担っています。
以下の図は実際のプログラムの中身の一例です。
このプログラムの具体的な中身について説明します。
まずはユーザーの操作を受け付けるための操作パネルを持つcontrol.viです。
ユーザー操作を受け付けるということで、イベントストラクチャを持っているだけなのですが、そもそもこれはメインVI(波形を表示させているVI)にとってのサブVI扱いとしているので、サブVI自体が表示されるようにするためにcontrol.viのVIプロパティ(Ctrl + Iのショートカットキーで開く)の「ウィンドウの外観」カテゴリでカスタマイズを行っています。
用意したイベントは以下の通りです。
停止ボタンやウィンドウパネルを閉じるの機能以外は一つだけで、左もしくは右ボタンが押されたことを検出するだけです。
このとき、左ボタンが押されたかどうかで、後で計算に使用するために「1」か「-1」を選択関数で選びエンキューしています。
このエンキューの操作は、メインVIで用意したキューに対して行い、デキュー操作はデータ表示ループで行われます。
メインVIにはデータ取得のループもあり、ここで生成された波形データもまたエンキューされて、データ表示ループでデキューされます。
つまり、不定期に更新される情報と定期的に更新される情報がどちらも一つのデータ表示ループで処理されるようになっています。
そんなデータをどう「さばいて」いるのかというと、グラフ表示を更新させるというステートをあらかじめ用意し、「他のループからデータを受け取ったら自分でグラフ表示更新のステートに移る」ようにします。
そのために、グラフ表示のためのdisplayステートを以下のようにしておきます。
あとは例えばcontrol.viから赤い四角の位置を変更する指示が来たら、その位置の情報(pointer position)のデータを更新して、自分自身でdisplayステートに移るようにしています。
こうすることで、不定期なタイミングでcontrolステートが実行されてもデータ表示ループ自身でdisplayステートに移りグラフ表示を更新してくれます。
似たような仕組みで、データ取得ループからきた新しい波形データもnewdataステートで受け取り、displayステートに移るようにしています。
このように、controlステートおよびnewdataステートどちらもそれぞれのステートの後にdisplayステートを実行してグラフの表示を更新しています。
displayステートと他のステート(controlおよびnewdata)とのデータの共有にはシフトレジスタを使っています。
このように、自動的(一定周期ごと)にデータ表示を更新する以外に、手動で(ユーザーがボタンを押したタイミングで)もデータ表示(赤い四角の位置)を変えることができるようにするには、データ表示そのものを一つのループで扱い、他のループからの命令(自動データ更新はデータ取得ループ、手動でデータ表示を変えるのはイベントハンドリングループ)によってそれぞれ独立したタイミングでデータ表示のループに指示を与えるような仕組みを用意することになります。
今回の例ではこれでプログラムは終わりなので特に機能拡張もしないのですが、もし他の機能(例えばデータを保存するなど)を付ける場合には、もしかするとイベントハンドリングループとメッセージハンドリングループは分けて用意した方が良いかもしれません。
LabVIEWに限らないと思いますが、全体の構想(どのデザインにするかだけでなく、例えばQMHならどのようなループを用意してそれぞれのループでどのようなデータをシフトレジスタで回すか等)をプログラム作成に入る前の段階で全て決めてしまうことがとても大切です。
逆に言えば、その構想さえしっかりできているのであれば、あとはプログラムに落とし込むだけであり、LabVIEWに慣れている人であればあまり時間がかからずできてしまうと思います。
プログラム作成に慣れるには
プログラムの作成方法について慣れるにはどうしたらいいか、これは悩ましいのですが、一つにはとにかく実際書いてみることがとても大切だと思います。
LabVIEWについては、まずはサンプルファインダを眺めることがいいと思います。各関数の使い方も学べますし、簡単なつくりのサンプルだけでなく、凝った作りのサンプルもちょくちょくあります。
また、私のブログでも様々なプログラム例を紹介しています。「これがベストな組み方だ」というつもりはなく、どちらかというと「こういうプログラムが実装の一例ですよ」というスタンスで書いていますが、それでもLabVIEWでプログラムを書くのに慣れていない方にとってはいくつか参考にしてもらえそうな内容もあると思います。(ただしQMHの例は少ないのですが・・・)
プログラム例では「どのような動作を実現しているか」をフロントパネルの画面とともに紹介しています。その様子を見て、「ブロックダイアグラムの中身」を見る前に、自分ならこう実装する、といったことを考えてみても面白いと思います。
そして、一通り実装し終えたら、ブロックダイアグラムの中身を見て自分が作ったプログラムと似ているか照らしてみてください(私のプログラムがいつも完璧というつもりは毛頭ありません。繰り返しますがあくまで一例です。むしろもっとうまい組み方ができる例も多くあると思います)
本記事では、デザインパターン適用の考え方について書いてみました。
特にQMHのデザインを採用する基準の一つとして、表示状態を自動的、手動的両方でバラバラのタイミングで変更する場合を紹介しました。
結局プログラムを書くことに慣れるのが一番なのですが、そのためには様々なプログラムの例にふれて、実装のイメージがパッと思い浮かぶようになればいいと思います。
ここまで読んでいただきありがとうございました。
コメント