LabVIEWを触ったことがない方に向けて、それなりのプログラムが書けるようになるところまで基本的な事柄を解説していこうという試みです。
シリーズ21回目としてこれまでの内容でプログラムを作るための具体的なテンプレートの一つであるステートマシンを扱っています。
この記事は、以下のような方に向けて書いています。
- デザインテンプレートって何?
- ステートマシンって何?
- ステートマシンを使ってみたい
もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。
なお、前回の記事はこちらです。
デザインパターンとは
これまでの回を全て読んでいただいた方は
- 各種データタイプの種類を区別
- データの保存
- ループで繰り返しの処理
- 条件分岐で場合分けをした処理
- イベントストラクチャによる効率のいいプログラム
といった内容を通して、プログラムの基本的な構造についてどんなものか、どういう使い方をするのかを見てきたことになります。
さらに、これらのプログラムの構造以外の部分でも
- サブVIを作る
- プロパティノード、インボークノードを扱う
- タイプ定義を使う
といった内容でより柔軟に、便利にプログラムを作る方法についても見てきました。
しかし、では上記の内容がわかったところで「はい、もう皆さん色々なプログラム書けますよね」と放り出されても、いざプログラムを書こうとしたときにどう書けばいいか分からないと思います。
そうですよね、いくら今までの説明の内容がある程度わかったとしても、プログラムを一から書くこととはそれはそれで別の問題です。
「簡単にWhileループが一つおいてあって計算をするだけ」みたいな、単純なプログラムならともかく、多くの場合にはそんな単純なプログラムは使わないと思います。
そこで便利なのが、ある程度の典型的な書き方、テンプレートを覚えてしまうことです。LabVIEWでこれらのテンプレートはデザインパターンと呼ばれています。
これらのテンプレートをしっかり使えるようにするために、今までの記事で様々な基本的なトピックを紹介してきました。
テンプレートと聞くと、一辺倒なプログラムしか書けないのではないかと思われるかもしれませんがそんなことはありません。これまで様々な人が考案してきたLabVIEWの「うまい書き方」のノウハウ、それがデザインパターンであり、このテンプレートを文字通り雛形としてプログラムを作るだけで立派なアプリケーションが出来上がります。
実際デザインパターンという名前で検索するとネット上にいろいろ見つかると思いますが、これらを使うことで、無理なく、初心者の方でも本格的なプログラムが書けてしまいます。
もちろん、最初はある程度慣れないと難しい部分もあると思います。ではどうやって慣れるかについて。答えは簡単、「よくある書き方をひたすらマネする」です。これが単純ながらも強力な方法です。
テンプレートを知る利点
具体的なデザインパターンに入る前に、これらを知ってそれに沿ってプログラムを書く利点についてみていきます。具体的には
- 決まった形に添えば、それなりに機能するプログラムが書ける
- そのパターンを知っている他の人が見て内容を把握しやすい
- デバッグがしやすい
- プログラムに慣れることができる、応用ができる
といったことが挙げられます。
決まった形に添えば、それなりに機能するプログラムが書ける
決まった形に添うと、「こういうような仕組みを持つプログラムはこういった考え方でつくればいい」という考え方が身につきます。あとはそこにアレンジを加えて自分のオリジナルのプログラムを作ることができます。
そのパターンを知っている他の人が見て内容を把握しやすい
デザインパターンはLabVIEWでプログラムを書く人の中では有名なものが多いです。そのため、そのデザインパターンを知っている人どうしではプログラムの内容が把握しやすいです。
内容が把握しやすいというのは、複数人でプログラムを作る際にも連携がとりやすいことにつながります。スムーズな連携を行うのに、広く知られたデザインパターンの共有はかかせません。
デバッグがしやすい
内容が把握しやすいということはデバッグをしやすいことにもつながります。プログラムが期待したとおりに動かない時に、どこを重点的に見ればいいのかといった勘所がつかみやすくなります。
なぜなら、プログラム全体の仕組みがデザインパターンに沿っている場合、プログラムの「ここがこう動いたら次にここがこう動いて・・・」という流れが追いやすくなっているので、エラーがどこで起きているか、どこに影響しているかを理解しやすくなるためです。
プログラムに慣れることができる、応用ができる
デザインパターンというテンプレートをベースにLabVIEWでプログラムを書く感覚に慣れるようになると、自然とアレンジも加えられるようになります。
より複雑なプログラムを書くための道具でまだ紹介していないものはたくさんあります。また、それらを知らないと書けないプログラムがあるのも事実です。ただ、道具をたくさん知っていくばかりでは面白くないと思います。
そこで今までの説明の内容で使える強力なデザインパターンの一つである、「ステートマシン」について紹介していきます。
ステートマシンを理解する
ステートマシンの構造
ステートマシンは、「ステートを切り替えながらプログラムを実行していくタイプのプログラミング手法」です。ステートという名前は初めて出てきましたが、実態は条件分岐であるケースストラクチャの各項目に対応します。
つまり、プログラム全体で条件分岐を繰り返していくという手法になります。この手法、別の言い方をすると、フローチャートを書けるようなプログラムを(ある程度)そのままLabVIEWプログラムに落とし込める書き方、と言えます。
フローチャートは開始から終わりまでのプログラムの流れを示したチャート図です。途中である特定の条件によって処理を変えたり前の処理に戻ったりといった構造を視覚的に示します。
言葉の通り分解すると、今まで扱ってきた内容の組み合わせであることが分かると思います。
プログラム全体で「条件分岐」を「繰り返して」いくので、「ケースストラクチャ」と「ループ」を使用します。ここにあと二つ、「(初期化された)シフトレジスタ」と「(タイプ定義された)列挙体」を加えて、これでステートマシンの必要最低限の要素がそろいます。
まだこれではイメージがつかないですよね?では、最も簡単なステートマシンのプログラムを実際にお見せします。
以下の図では、initializeとfinishの二つのステートしかない最もシンプルなステートマシンです。
このプログラム、中身は今までの内容を理解している方であれば、読み取れるはずです。
プログラムの大枠にはWhileループが使用されていて、その中にケースストラクチャ、つまり条件分岐を行うための枠組みがあります。
ケースストラクチャのセレクタ端子には、列挙体、より正確にはタイプ定義された列挙体が配線されています。列挙体には、二つのステートであるinitializeとfinishの二つの要素があります。
そして、その列挙体はWhileループの両端でシフトレジスタが使用されていて、これはWhileループの外から値を与えられているので初期化されている状態になります。
このプログラムの読み方がわからない場合、この先の話はあまり理解できないかもしれません。その場合には各項目について復習することをオススメします。
さて、このプログラムの構造が分かったところで、実際に動かしてみます。もし実際に手元で作った場合には、どのような動作になるかを想像してから動かしてみてください。
まず、プログラムを実行すると、シフトレジスタの初期値がinitializeになっていることから、ケースストラクチャには「initialize」の項目が入るため、ケースは「initialize」が実行されます。
この中身では、finishという列挙体項目が出る動作しかありません。そのため、Whileループの一回目のループとしては、列挙体「finish」を渡すという動作しか行われません。そしてこれがシフトレジスタにつながれています。
よって、Whileループの二周目では、今度はシフトレジスタからfinishという列挙体項目が出て、ケースストラクチャのセレクタ端子に渡されます。
これにより、ケース「finish」が実行されます。このケースでは、TRUE定数をWhileループの条件端子に渡すという動作を行っています。
Whileループの条件端子にTRUEが渡ることで、Whileループが終了し、プログラム自体も終了します。
なんだ、それだけのことか、と思えた方はもう立派にステートマシンを理解できる知識を持っている状態といえると思います。ステートマシンの仕組みは、上で説明した内容以上のことがありません。
実際こんなの覚えて何になるの?という方。確かに上の例は「味気なさすぎ」ですよね。なので少し工夫を加えてみます。initializeのステートを以下のように変更してみます。
このプログラム、選択関数を使っています。選択関数は、入力の真ん中に配線したブール情報がTRUEかFALSEかで、出力するデータを変えることができる関数でした。
このプログラムだと、フロントパネルに配置した停止のボタンが押されると、選択関数にTRUEが入ることになりますが、TRUEが押されないとFALSEのままです。そして、選択関数はTRUEのときに列挙体でfinishの項目を、FALSEのときに列挙体でinitializeの項目を出すようにしています。
このプログラムを実行すると、最初initializeのケースが実行されるのですが、フロントパネルの停止ボタンを押さない限り、選択関数からはinitializeが出て、これがシフトレジスタに渡されるという動作を繰り返すことになるのがわかるでしょうか?
ある瞬間に停止のボタンを押すと、選択関数にTRUEが渡され、その時の出力項目であるfinishがシフトレジスタに渡されます。よってその次のループではfinishの項目がケースストラクチャに渡されて、無事、whileループが終わることができます。
小規模ステートマシンを作ってみる
上の例ではまだ簡単すぎてその効果が実感できないと思うので、もう少し複雑なステートマシンに挑戦しようと思います。ずばり、データをとってそれを保存するような仕組みを作ります。
まずはフローチャートを考えます。これから作るステートマシンは以下のようなフローチャートを持つとします。
さて、ステートは全部で5個あるので(開始は含みません)、列挙体もこれに合わせて5個項目を用意します。そしてあとは各ステートごとにケースストラクチャの中身を書いていくだけですね。
上のフローチャート通りのプログラムにするために例えば下の図のようなフロントパネルが考えられます。
5つのステートを見ていきます。まずは初期化のステートです。ここでは、グラフ表示の初期化を行わせるために次のように組むことができます。
初期化のステートで、次のステートを待機のステートと決めました。この待機のステートは、フロントパネル上で新規データ、保存、終了のいずれかのボタンが押されるまで待機し、押されたらそれぞれのステートに移るので例えば以下のように書くことができます。
この書き方は、どれかのボタンが押されたらそのボタンがTRUEとなることを利用して選択関数で特定のステートを指示するための方法になります。ただし、このプログラムではほぼ問題になりませんが、後で判断されるもの(新規データボタンよりも、保存ボタン、それよりも後の停止ボタン)が既に押されているとステートが「上書き」されてしまいます。
例えば仮に新規データボタンと停止ボタンが押されている場合、シフトレジスタに渡されるのは停止ボタンの情報になる、ということです(これを防ぐ方法を後で紹介します)。
ただ実際このプログラムではこれらのボタンが同時に押されている状態はほぼ起こりえません。いずれかのボタンを押した瞬間に、そのステートが実行され、他のボタンを押す隙がないためです。
さて、新しいデータを生成するステートに移ったとして、その中身は例えばこのように書けます。
データを生成してオレンジのワイヤの数値配列に渡している状態です。波形グラフにもランダムな数値が10個表示されるはずです。
このステートの後、また待機のステートに戻ります。もしまた新規データのボタンが押されればもう一度新たにランダムな数値が生成されますが、待機のステートでデータ保存のボタンが押されると例えば以下のような書き方でランダムな数値10個をファイルに出力させることができます。
なお、Application Directory.viとはこの場合、このステートマシンのVIが置かれたフォルダ階層と同じパスを示します。そのためパス生成関数は、このステートマシンのプログラムの階層に「保存ファイル名」文字列制御器の名前のファイルのファイルパスを出力します。
最後に残った終了のステートは以下のように書けます。
これで先ほどのフローチャートのプログラムが完成しました。
ステートマシンに機能追加
さて、上記のステートマシン、完成したのですが、「グラフを画像としても保存したい」と思ってしまったとします。よく言う、仕様変更、ですね。
とんでもない仕様変更なら最初から作り直さなければいけなくなるかもしれないですが、ある程度なら修正が効きます。流れを以下に紹介します。
例えば、フロントパネルに画像表示のボタンを加えたとします。
新たな動作を加えるので、ステートを増やす必要があります。つまり、列挙体の項目を増やすということになります。
ステートマシンはプログラムの中で大量に列挙体を使用します。項目を増やすとなるとそれら全部に手を加える必要が出てくる・・・のですが、ここで「タイプ定義」が効力を発揮します。タイプ定義の大元であるctlファイルで列挙体の項目を増やしたら、「すべてに適用」することでプログラムの中のすべての列挙体が一度に更新されます。
ということでタイプ定義したctlファイルで画像保存用のステート、imagesaveを追加したとします。
今回の編集の場合、待機のステートも変更する必要があるので以下のようにします。
そして、画像保存のステートにはグラフに対するインボークノードを使用します。
これで機能追加の完了、となります。
あまり無茶な仕様変更ではない限り、ステートを後からでも追加して対応することができる、これが簡単ながらも強力なステートマシンというテンプレートです。
イベント型のステートマシンにアレンジ
少し応用的な話ですが、イベント駆動型のステートマシンが便利なので紹介します。応用と言っても、そこまで難しいことは行いません。
上記の例で、待機のステートについて、「後で判断されるもの(新規データボタンよりも、保存ボタン、それよりも後の停止ボタン)が既に押されているとステートが「上書き」されてしま」うと説明しました。
これを防ぐための構造がイベント駆動型のステートマシンです。上記のステートマシンの待機のステートに、イベントストラクチャを使用するだけです。
このようなプログラムでは、先ほどまでの待機のステートの作りで起こりうる「上書き」が起こりません。
また、イベントが起きるのを待っているステートでは、LabVIEWは「無駄な」動きをしません。
そのため、イベントストラクチャを使用したステートマシンの方がより効率がいいプログラムとなります。また、ステートマシンを構成するWhileループの終了と同時にプログラムが終わる(ようにした)場合、イベントストラクチャの回で紹介した注意点も回避できることが分かると思います。
ただし、イベント駆動型ステートマシンは、慣れないうちはあまりこだわらなくてもいいと思います。ちょっとした応用例として考えるようにしてください。
ステートマシンには他にも応用が考えられますが、また別の記事で取り扱うことにします。
ステートマシンのサンプル、テンプレート
ステートマシンの作り方は以上となります。あとはもっと具体的なサンプルについても紹介します。
サンプルファインダの検索タブで「ステートマシン」と検索すると見つかる「ビジー設定」や「ユーザイベント生成」のサンプル、これらもステートマシンを使用しています。
特にユーザイベント生成というサンプルは面白いと思います。まずこれシリーズの記事で扱っていないユーザイベントという構造を使用していますが、ステートマシンの組み方としては基本的で、プログラムの内容も面白いと思います。
また実は、LabVIEWにはステートマシン自体のテンプレートがあり、ある程度「型」が出来上がった状態から開始できるようになっています。
その型は、LabVIEWを開いて「プロジェクトを作成」を選択し、一覧から簡易ステートマシンを選ぶことで開けるようになります。もちろん、プログラムによって列挙体の項目の数など増やす必要があるのでそこは適宜調整します。
ステートマシンは「フローチャートを書けるようなプログラムをLabVIEWプログラムに落とし込める」方法として強力なことがわかりましたでしょうか?
フローチャートの各ステップは、ステートマシンのステートに対応します。ステップを移ることは、ステートを移ることに対応します。ステップを移る際、条件によってその先に移るステップを変えるのは、ステートごとにシフトレジスタに渡す列挙体を変えることに対応します。
ここまでの内容でかなりLabVIEWプログラムに慣れることができたと思えるのではないかと思います。ただ、最低限知っておきたい内容を網羅的に紹介しただけなので、まだまだLabVIEWの便利な使い方は紹介しきれていません。
ステートマシンが理解できるようになったら、次のステップに向けてさらに新しいトピックについて扱っていこうと思います。その準備として、次回はプログラムの並列処理について紹介していこうと思います。
もしよろしければ次の記事も見ていってもらえると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント
突然の質問失礼いたします。
ブロックダイアグラムにおける列挙体の表示方法についてお伺いしたくコメントさせていただきました。
自分の環境では列挙体が著者様の用に表示されず、”◀▶”としか表示されません。こちらの表示方法を変更するにはどのようにしたらよいかご教授いただければ幸いです。
コメントいただきありがとうございます!
”◀▶”という左右矢印の表記は、その列挙体が「制御器」になっている場合かと思います。フロントパネルでは列挙体の項目名が表示されているのではないでしょうか?
ブロックダイアグラム上で列挙体の項目の名前が出るようにする場合には、列挙体を「定数」としていただく(右クリックで「定数に変更」)と表示されるようになると思います。
もしご質問の内容私が勘違いしていましたら申し訳ありません。解決されたかコメントいただけますと幸いです。
返信が遅くなり申し訳ありません。
著者様のおっしゃる通り定数に変更することで解決いたしました。
ありがとうございます。