LoginSignup
0
1

【世界初】micro:bitの拡張機能でステートマシン・コーディングをマスターしよう

Last updated at Posted at 2023-09-03

世界初のステートマシン・コーディング

micro:bitのユーザー定義の拡張機能であるMstate拡張機能を使って、ステートマシン・コーディングをマスターできます。
g403.png

世界初1の試み
micro:bitでステートマシンを使ったブロック・コーディングは、 世界初の試みです。

コーディングを元に設計の検証をする

さて、このブロック・コーディングを読んで、これがどのような振る舞いをするのか、わかりますか?
これをわかりやすくするのが、ステート図です。一般的には、UMLなどでステート図を描いてから、ソースコードを自動生成したりしますが、Mstate拡張機能には、コーディングからステート図(PluntUML)の記述を生成する機能が備わっています。
最初だけ ブロック内に、 UML ブロックを追加します。すると、 Show data シミュレーター 画面で、コンソール・ログとして PluntUML によるステート図の記述が出力されます。これを http://www.plantuml.com/plantuml/ などの描画サービスに入力し、ステート図に変換します。
UMLの追加
image.png
コンソール・ログ
image.png
ステート図

ステート図の説明

四角形と矢印
四角形で表現しているのが、ステート(状態)です。矢印で表現しているのがトランジション(状態遷移)で、その矢印に記述してあるのが、トリガーです。トリガーのことをイベントとも呼びますが、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 ブロックの組み合わせで定義されています。
image.png

ステートでの 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個で宣言するというコーディング・ルールを決めておくと良いと思います。

entryアクション
image.png

doアクティビティ(500ms間隔)
image.png

exitアクション
image.png

ステートの振る舞いを説明する(UML表記)

そのステート(状態)での振る舞いは、ブロック・コーディングを読めばわかりますが、その説明をUMLに加えることができます。
define ブロックで、状態名称を定義していますが、":"(コロン)で区切って、アクションやアクティビティの説明を記述します。

ブロック 状態名称のみ 状態名称と説明
define "<状態名称>" "<状態名称>" + ":" + "<説明>"

例えば、各状態の状態名称を次のように編集し、UMLで出力してみます。

# 状態名称 状態名称と説明 備考
1 準備 準備:経過時間の初期化
2 計測 計測:entry/\n - 開始時間を保持\ndo/ (500ms)\n - LED点滅\nexit/\n - 経過時間を加算 \nは改行
3 停止 停止 何もしない
4 結果 結果:経過時間を表示

ブロックの例
image.png

ステート図(説明つき)

トランジションの宣言

トランジション(状態遷移)は、 transition ブロックで宣言できますが、Transitionグループにはいくつかのブロックがあります。ブロックの説明の前に、トランジションについてステート図で説明します。

Transitionグループのブロック
image.png

ステート図でのトランジション

ステート図において、トランジション(状態遷移)は、矢印で表現され、ある状態からある状態への状態遷移を示しています。
状態遷移は、トリガーによって起こります。また、 ガード によって、条件を指定することも可能です。トリガーが発生し、ガードの条件が満たされていれば、状態遷移が起こります。トリガーが発生しても、ガードの条件が満たされていなければ、状態遷移は起こりません。
状態遷移が起こる場合、 エフェクト が実行されます。
ある状態からある状態へ複数の矢印で表現することができますが、状態遷移できる矢印はその中の1つだけです。また、他の状態だけでなく、自分自身へ状態遷移することも可能です。

項目 表記 説明 補足
トランジション 矢印 状態から状態への状態遷移を示す。 複数矢印可。自分自身への状態遷移可。
トリガー トリガー名 トリガーによってその矢印の状態遷移が引き起こされる。 省略時は即座に状態遷移(completion transition)。
ガード []内に表記 ガードの条件を満たしていなければ、トリガーが発生しても状態遷移しない。 省略可。
エフェクト /に続けて表記 状態遷移が起こる場合に実行される。 省略可。

元となるtaransitionブロック

Transitionグループにはいくつかのブロックがありますが、transitionブロックである declareSimpleTransitiondeclareTimeoutedTransitionは、declareCustomTransitionを元に簡素化したブロックです。その為、元となるdeclareCustomTransitionから説明します。

image.png

transitionブロック - declareCustomTransition

declareCustomTransition ブロックを説明するために、次のような疑似コードを定義し、ステート図に出力しました(疑似コードは、実際には動作しません)。

ブロック・コード(疑似コード)
image.png

ステート図(疑似コード)

a ステート、 b ステート、 c ステートがあり、 e トリガーによって、 トランジション が引き起こされます。ただし、それぞれのトランジションには、ガードがあり、その条件を満たしていなければ、状態遷移しません。
e トリガーによって引き起こされる トランジション は、ブロック・コード図で示した transition ブロックで宣言できます。遷移先は、配列として定義します。
もし、 e トリガーが発生した場合、この transition ブロック内が実行されます。この中で、ガードの条件を満たしているかどうかを判断し、状態遷移を起こします。ガードの条件を満たしていれば、 transit ブロックで、遷移先の添え字(配列のインデックス)を設定することで、状態遷移が起こるのです。 transit ブロックで、有効な遷移先の添え字を設定しなければ、状態遷移は起こりません。
また、 エフェクト は、この中で実行するようにコーディングします。transit ブロックの前でも後でも順序はどちらでも構いません。このtransition ブロック内のコードが実行された後に、(状態遷移があれば)exitアクションが呼び出されます(transit ブロックでは、まだ、状態遷移が起こりません)。

トリガーによる単純なトランジション - declareSimpleTransition

トランジションの多くは、一つの遷移先へ遷移する為、 declareSimpleTransition ブロックを予め実装しています。

次の2つのコーディングは同じ振る舞いをします。
image.png

e トリガーが、発生したら、常に b ステートへ状態遷移するように transit ブロックで、添え字 0 を設定しています。

タイムアウトによるトランジション - declareTimeoutedTransition

タイムアウトによるトランジションである、declareTimeoutedTransition ブロックも予め実装しています。
タイムアウトというのは、 ガード のみで判断しており、トリガーはありません(空の文字列)。

次の2つのコーディングは同じ振る舞いをします。
image.png

timeouted ブロックは、その状態に遷移してからの経過時間(ミリ秒)で判断されます。

transitionブロックでの名称の省略(空の文字列)

transtion ブロックの トリガーステート において、その名称を省略することがあります(空の文字列)が、これらには、特別な意味があります。

トリガー の名称を空の文字列で省略すると、completion transition であり、即座に状態遷移することを示しています。つまり、entryアクションdoアクティビティが一度だけ実行され、exitアクションの後に状態遷移します。

また、遷移先の ステート の名称を空の文字列で省略すると、それは、終了状態を意味します。

ブロック・コードの例
image.png

ステート図

トランジションの振る舞いを説明する(UML表記)

既にお気づきかと思いますが、 トランジションガードエフェクト の説明をブロック・コード内に記述することができます。

transition ブロックで、遷移先の状態名称を定義していますが、":"(コロン)で区切って、ガードエフェクトを記述することができます。

ブロック 状態名称のみ 状態名称とガード、エフェクト
transition "<状態名称>" "<状態名称>" + ":" + "<ガード>" + "/" + "<エフェクト>"

尚、"<ガード>"の記述を省略することも可能です。その際は、"<状態名称>" + ":" + "/" + "<エフェクト>" と記述します。

タイムアウトの追加とトランジションの表記形式

ここで、プロジェクトを改造してみます。結果 ステートにタイムアウトによるトランジション を追加し、10秒経過したら、準備 ステートへ状態遷移するようにします。もちろん、 Reset トリガーでも状態遷移します。

image.png

さらにステート図でひと工夫します。タイムアウトのようなトランジションは多くの状態から矢印が引かれることになり、ステート図が矢印だらけになって、正常系の状態遷移がわかりにくくなってしまいます。そこで、状態遷移先の":"の記述の後にもう一つ":"を追加してください(つまり、"::")。すると、矢印が消え、ステートの中に説明として表記されます。

矢印として表記 説明として表記

image.png

image.png

トリガー発生時の引数

トリガー発生時の何らかの値(数値)を配列で渡すことができます。
次のブロック・コーディング例では、 fire ブロックで、それぞれ異なる値を渡し、 transition ブロックの引数で( args )で、その値を参照しています(0番目の値)。 

ブロック・コーディング例
image.png

活用例

どのような場合にこの引数が必要になりそうでしょうか。
例えば、無線のシリアル番号を使って通信相手を区別したいときに役立ちます。

image.png

重い処理を行っている最中に別のmicro:bitから無線で受信した場合でも、 現在の相手 を維持することができます。
もし、無線で受信したときに、 現在の相手 を設定してしまうと 別のmicro:bitのシリアル番号で上書きしてしまうことになるでしょう。

本プロジェクトでの適用例

実は、本プロジェクトでは、その表示で遅延が発生しないように工夫していますが、アイコンや文字列等の表示において処理が中断しています。その為、ボタンAが押されたとき イベントが発生してから、entryアクションexitアクションで、稼働時間(ミリ秒)を取得するまでに少しばかり時間が経過してしまう可能性があります。
そこで、ボタンAが押されたとき イベントが発生したタイミングで、稼働時間(ミリ秒)を取得し、トリガーの引数で取得した値を渡すようにすると、より精度が高くなると考えられます。
しかし、実際にコーディングしてみると、ステート図エフェクト版のようになり、コーディング量が増え、不具合の危険性が高まります(entry/exitアクションの利点が失われる)。
そこで、これらを合わせたミックス版のようにするのが良いのかもしれません。

ステート図

アクション版 エフェクト版 ミックス版

複数のステートマシンを同時実行する

Mstate拡張機能では、合成状態やフォークを実現することができません。代わりに複数のステートマシンを定義・宣言し、同時に実行することで同様の振る舞いを実現します。

メイン サブ

image.png

image.png

おわりに

ブロック・コーデイングやステート図を示しながら、Mstate拡張機能について学びました。

  1. micro:bitでステートマシンを使ったブロック・コーディングは、世界初1の試みであることを謳いました
  2. PluntUMLによるステート図の記述を出力できる為、コーディングを元に設計の検証をすることができます
  3. ステートにおけるentryアクション、doアクティビティ、exitアクションの振る舞いとコーディングをマスターしました
  4. トランジションの振る舞いとコーディングをマスターしました
  5. トリガー発生時にその引数でトランジションへ値(配列)を渡せることをマスターしました
  6. 最終成果物であるプロジェクトとステート図へのリンクをここに示します

プロジェクト(共有)

ステート図(Webサービス)

追記:ビジュアル・プログラミングにおけるステートマシン

ビジュアル・プログラミング(Blocklyやmicro:bit)におけるステートマシンライブラリの実装や、ステートマシンに関する教材等についての資料・文献をメモしておきます(参考文献ではありません)。

Mstate拡張機能が、日本における中学校段階でのプログラミング教育に役に立つのかもしれません。

  1. Blocky and State Machines
    → 提案のみ
  2. AI-Planning/blockly-pddl
    → blocklyでの実装
  3. micro:bitを用いた状態遷移システムの教材開発およびプログラムの実装
    → 会員のみ閲覧可能
  4. 計測制御システムの状態遷移を可視化する教材開発と情報教育への応用に関する研究
    → Rtoysでの実装、中学校段階のプログラミング教育
  1. 自分調べ:2023年9月3日現在** 2

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1