世界初のステートマシン・コーディング
micro:bitのユーザー定義の拡張機能であるMstate拡張機能を使って、ステートマシン・コーディングをマスターできます。
世界初1の試み
micro:bitでステートマシンを使ったブロック・コーディングは、 世界初の試みです。
コーディングを元に設計の検証をする
さて、このブロック・コーディングを読んで、これがどのような振る舞いをするのか、わかりますか?
これをわかりやすくするのが、ステート図です。一般的には、UMLなどでステート図を描いてから、ソースコードを自動生成したりしますが、Mstate拡張機能には、コーディングからステート図(PluntUML)の記述を生成する機能が備わっています。
最初だけ
ブロック内に、 UML
ブロックを追加します。すると、 Show data シミュレーター
画面で、コンソール・ログとして PluntUML によるステート図の記述が出力されます。これを http://www.plantuml.com/plantuml/ などの描画サービスに入力し、ステート図に変換します。
UMLの追加
コンソール・ログ
ステート図
ステート図の説明
四角形と矢印
四角形で表現しているのが、ステート
(状態)です。矢印で表現しているのがトランジション
(状態遷移)で、その矢印に記述してあるのが、トリガー
です。トリガー
のことをイベント
とも呼びますが、micro:bitにおけるイベント
と区別するために、トリガー
と呼ぶようにします。
尚、外側の四角形は、 1つのステートマシン を表現しています。Mstate拡張機能では、複数のステートマシンを定義できます。
ステートマシンの開始
黒丸は、開始状態です。 start
ブロックを実行すると、指定された デフォルト・ステート
としての 準備
ステートへ状態遷移し、ステートマシンが開始されます。
トリガーと状態遷移
準備
ステートからは、 StartPause
トリガーにより、 計測
ステートへと状態遷移します。 StartPause
トリガーは、 ボタンAが押されたとき
イベントで、 fire
ブロックによって発生させています。尚、 準備
ステートでは、 StartPasue
トリガー以外では、状態遷移が起こりません。
計測
ステートでは、 StartPause
トリガー、または、Stop
トリガーによって状態遷移します。 StartPause
トリガーは、計測を開始したり、停止したり、再開したりと、複数の状態遷移を引き起こします。つまり、Aボタンだけで、開始・停止・再開を操作できるということです。
ステート図から読み取る動作
ブロック・コーディングよりもステート図のほうが、どのような振る舞いをするのかわかりやすいと思います。
StartPause
トリガー(ボタンA)によって、計測を開始・停止・再開でき、 Stop
トリガー(ボタンB)によって、結果になり、 Reset
トリガー(ボタンA+B)によって、準備に戻るという振る舞いを読み取ることができます。
※ QiitaのMarkdown記法において、PluntUMLに対応していますので、本記事でもMarkdownでステート図を記述しています。→ ダイアグラム-PlantUMLを使う
ステートマシンの振る舞い
ステート図から振る舞いを読み取ることができましたが、もう少し詳しくブロックのコードを読んでみましょう。
例えば、計測
ステートは、 on entry
ブロック、 on do
ブロック、 on exit
ブロック、および、transition
ブロックの組み合わせで定義されています。
ステートでの entry-do-exit
ある ステート
(状態)において、次の2つのアクションと1つのアクテビティが実行されます。
- entryアクション
- doアクティビティ
- exitアクション
entryアクション は、その状態に状態遷移した直後に1度だけ実行されます。その後、その状態で doアクテビティ が繰り返し実行されます。状態遷移が起こる場合、状態遷移する直前に exitアクション が1度だけ実行されます。
尚、状態遷移には、他の状態への状態遷移だけでなく、自身の状態への状態遷移もあります。いずれの状態遷移であっても、exitアクションとentryアクションとが実行されます。
実行順序 | 状態遷移 |
---|---|
アクションとアクティビティの宣言
お察しいただいている通り、Mstate拡張機能では、entryアクション、doアクティビティ、exitアクションをそれぞれ、 on entry
ブロック、 on do
ブロック、 on exit
ブロックで宣言します。
on do
ブロックは、繰り返し呼び出され、その呼び出し間隔を指定できます(ミリ秒)。ただし、その精度は低いので注意してください(デフォルトでは100ms以上の精度)。
尚、宣言する順序は、任意です。また、同一のアクションやアクティビティを複数宣言した場合、その実行順序は不定です。通常、entry-do-exitの順に配置し、アクションやアクティビティは、ゼロまたは1個で宣言するというコーディング・ルールを決めておくと良いと思います。
ステートの振る舞いを説明する(UML表記)
そのステート
(状態)での振る舞いは、ブロック・コーディングを読めばわかりますが、その説明をUMLに加えることができます。
define
ブロックで、状態名称を定義していますが、":"
(コロン)で区切って、アクションやアクティビティの説明を記述します。
ブロック | 状態名称のみ | 状態名称と説明 |
---|---|---|
define | "<状態名称>" | "<状態名称>" + ":" + "<説明>" |
例えば、各状態の状態名称を次のように編集し、UMLで出力してみます。
# | 状態名称 | 状態名称と説明 | 備考 |
---|---|---|---|
1 | 準備 | 準備:経過時間の初期化 | |
2 | 計測 | 計測:entry/\n - 開始時間を保持\ndo/ (500ms)\n - LED点滅\nexit/\n - 経過時間を加算 | \nは改行 |
3 | 停止 | 停止 | 何もしない |
4 | 結果 | 結果:経過時間を表示 |
ステート図(説明つき)
トランジションの宣言
トランジション
(状態遷移)は、 transition
ブロックで宣言できますが、Transitionグループにはいくつかのブロックがあります。ブロックの説明の前に、トランジション
についてステート図で説明します。
ステート図でのトランジション
ステート図において、トランジション
(状態遷移)は、矢印で表現され、ある状態からある状態への状態遷移を示しています。
状態遷移は、トリガー
によって起こります。また、 ガード
によって、条件を指定することも可能です。トリガー
が発生し、ガード
の条件が満たされていれば、状態遷移が起こります。トリガー
が発生しても、ガード
の条件が満たされていなければ、状態遷移は起こりません。
状態遷移が起こる場合、 エフェクト
が実行されます。
ある状態からある状態へ複数の矢印で表現することができますが、状態遷移できる矢印はその中の1つだけです。また、他の状態だけでなく、自分自身へ状態遷移することも可能です。
項目 | 表記 | 説明 | 補足 |
---|---|---|---|
トランジション | 矢印 | 状態から状態への状態遷移を示す。 | 複数矢印可。自分自身への状態遷移可。 |
トリガー | トリガー名 | トリガーによってその矢印の状態遷移が引き起こされる。 | 省略時は即座に状態遷移(completion transition)。 |
ガード | []内に表記 | ガードの条件を満たしていなければ、トリガーが発生しても状態遷移しない。 | 省略可。 |
エフェクト | /に続けて表記 | 状態遷移が起こる場合に実行される。 | 省略可。 |
元となるtaransitionブロック
Transitionグループにはいくつかのブロックがありますが、transition
ブロックである declareSimpleTransition
とdeclareTimeoutedTransition
は、declareCustomTransition
を元に簡素化したブロックです。その為、元となるdeclareCustomTransition
から説明します。
transitionブロック - declareCustomTransition
declareCustomTransition
ブロックを説明するために、次のような疑似コードを定義し、ステート図に出力しました(疑似コードは、実際には動作しません)。
ステート図(疑似コード)
a
ステート、 b
ステート、 c
ステートがあり、 e
トリガーによって、 トランジション
が引き起こされます。ただし、それぞれのトランジション
には、ガード
があり、その条件を満たしていなければ、状態遷移しません。
e
トリガーによって引き起こされる トランジション
は、ブロック・コード図で示した transition
ブロックで宣言できます。遷移先は、配列として定義します。
もし、 e
トリガーが発生した場合、この transition
ブロック内が実行されます。この中で、ガード
の条件を満たしているかどうかを判断し、状態遷移を起こします。ガード
の条件を満たしていれば、 transit
ブロックで、遷移先の添え字(配列のインデックス)を設定することで、状態遷移が起こるのです。 transit
ブロックで、有効な遷移先の添え字を設定しなければ、状態遷移は起こりません。
また、 エフェクト
は、この中で実行するようにコーディングします。transit
ブロックの前でも後でも順序はどちらでも構いません。このtransition
ブロック内のコードが実行された後に、(状態遷移があれば)exitアクションが呼び出されます(transit
ブロックでは、まだ、状態遷移が起こりません)。
トリガーによる単純なトランジション - declareSimpleTransition
トランジション
の多くは、一つの遷移先へ遷移する為、 declareSimpleTransition
ブロックを予め実装しています。
e
トリガーが、発生したら、常に b
ステートへ状態遷移するように transit
ブロックで、添え字 0
を設定しています。
タイムアウトによるトランジション - declareTimeoutedTransition
タイムアウトによるトランジション
である、declareTimeoutedTransition
ブロックも予め実装しています。
タイムアウトというのは、 ガード
のみで判断しており、トリガー
はありません(空の文字列)。
timeouted
ブロックは、その状態に遷移してからの経過時間(ミリ秒)で判断されます。
transitionブロックでの名称の省略(空の文字列)
transtion
ブロックの トリガー
と ステート
において、その名称を省略することがあります(空の文字列)が、これらには、特別な意味があります。
トリガー
の名称を空の文字列で省略すると、completion transition
であり、即座に状態遷移することを示しています。つまり、entryアクション、doアクティビティが一度だけ実行され、exitアクションの後に状態遷移します。
また、遷移先の ステート
の名称を空の文字列で省略すると、それは、終了状態を意味します。
ステート図
トランジションの振る舞いを説明する(UML表記)
既にお気づきかと思いますが、 トランジション
の ガード
、エフェクト
の説明をブロック・コード内に記述することができます。
transition
ブロックで、遷移先の状態名称を定義していますが、":"
(コロン)で区切って、ガード
やエフェクト
を記述することができます。
ブロック | 状態名称のみ | 状態名称とガード、エフェクト |
---|---|---|
transition | "<状態名称>" | "<状態名称>" + ":" + "<ガード>" + "/" + "<エフェクト>" |
尚、"<ガード>"
の記述を省略することも可能です。その際は、"<状態名称>" + ":" + "/" + "<エフェクト>"
と記述します。
タイムアウトの追加とトランジションの表記形式
ここで、プロジェクトを改造してみます。結果
ステートにタイムアウトによるトランジション
を追加し、10秒経過したら、準備
ステートへ状態遷移するようにします。もちろん、 Reset
トリガーでも状態遷移します。
さらにステート図でひと工夫します。タイムアウトのようなトランジション
は多くの状態から矢印が引かれることになり、ステート図が矢印だらけになって、正常系の状態遷移がわかりにくくなってしまいます。そこで、状態遷移先の":"
の記述の後にもう一つ":"
を追加してください(つまり、"::"
)。すると、矢印が消え、ステート
の中に説明として表記されます。
矢印として表記 | 説明として表記 |
---|---|
トリガー発生時の引数
トリガー発生時の何らかの値(数値)を配列で渡すことができます。
次のブロック・コーディング例では、 fire
ブロックで、それぞれ異なる値を渡し、 transition
ブロックの引数で( args
)で、その値を参照しています(0番目の値)。
活用例
どのような場合にこの引数が必要になりそうでしょうか。
例えば、無線のシリアル番号を使って通信相手を区別したいときに役立ちます。
重い処理を行っている最中に別のmicro:bitから無線で受信した場合でも、 現在の相手
を維持することができます。
もし、無線で受信したときに、 現在の相手
を設定してしまうと 別のmicro:bitのシリアル番号で上書きしてしまうことになるでしょう。
本プロジェクトでの適用例
実は、本プロジェクトでは、その表示で遅延が発生しないように工夫していますが、アイコンや文字列等の表示において処理が中断しています。その為、ボタンAが押されたとき
イベントが発生してから、entryアクションやexitアクションで、稼働時間(ミリ秒)
を取得するまでに少しばかり時間が経過してしまう可能性があります。
そこで、ボタンAが押されたとき
イベントが発生したタイミングで、稼働時間(ミリ秒)
を取得し、トリガー
の引数で取得した値を渡すようにすると、より精度が高くなると考えられます。
しかし、実際にコーディングしてみると、ステート図のエフェクト版のようになり、コーディング量が増え、不具合の危険性が高まります(entry/exitアクションの利点が失われる)。
そこで、これらを合わせたミックス版のようにするのが良いのかもしれません。
ステート図
アクション版 | エフェクト版 | ミックス版 |
---|---|---|
複数のステートマシンを同時実行する
Mstate拡張機能では、合成状態やフォークを実現することができません。代わりに複数のステートマシンを定義・宣言し、同時に実行することで同様の振る舞いを実現します。
メイン | サブ |
---|---|
おわりに
ブロック・コーデイングやステート図を示しながら、Mstate拡張機能について学びました。
- micro:bitでステートマシンを使ったブロック・コーディングは、世界初1の試みであることを謳いました
- PluntUMLによるステート図の記述を出力できる為、コーディングを元に設計の検証をすることができます
- ステートにおけるentryアクション、doアクティビティ、exitアクションの振る舞いとコーディングをマスターしました
- トランジションの振る舞いとコーディングをマスターしました
- トリガー発生時にその引数でトランジションへ値(配列)を渡せることをマスターしました
- 最終成果物であるプロジェクトとステート図へのリンクをここに示します
プロジェクト(共有)
ステート図(Webサービス)
追記:ビジュアル・プログラミングにおけるステートマシン
ビジュアル・プログラミング(Blocklyやmicro:bit)におけるステートマシンライブラリの実装や、ステートマシンに関する教材等についての資料・文献をメモしておきます(参考文献ではありません)。
Mstate拡張機能が、日本における中学校段階でのプログラミング教育に役に立つのかもしれません。
-
Blocky and State Machines
→ 提案のみ -
AI-Planning/blockly-pddl
→ blocklyでの実装 -
micro:bitを用いた状態遷移システムの教材開発およびプログラムの実装
→ 会員のみ閲覧可能 -
計測制御システムの状態遷移を可視化する教材開発と情報教育への応用に関する研究
→ Rtoysでの実装、中学校段階のプログラミング教育