【micro:bit ステートマシン】基礎と基本 ～状態遷移図で学ぶ状態遷移～

# micro:bitで使えるステートマシン・ブロック

mstate拡張機能(pxt-mstate)は、Microsoft MakeCode for micro:bitでステートマシンによるコーディングを実現できるブロックです。

:::note warn
本記事は、ver.0.10.0 を元にしています（2024/03/30編集）。
[リリースページ：pxt-mstate.0.10.0.hex
](https://github.com/jp-rad/pxt-mstate/releases/tag/0.10.0)
:::

**mstate拡張機能(pxt-mstate)**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/c5fe670c-706d-4788-fff6-d393027f33db.png)

https://makecode.microbit.org/_6dEEvb5MdPKy

:::note info

**セットアップ手順**
次の手順で、mstate拡張機能(pxt-mstate)をセットアップしてください。
1. mstate拡張機能([HEX形式ファイル](https://github.com/jp-rad/pxt-mstate/releases))をダウンロードします
→ https://github.com/jp-rad/pxt-mstate/releases
1. [Microsoft MakeCode for micro:bit](https://makecode.microbit.org/) で新しいプロジェクトを作成します
→ https://makecode.microbit.org/
1. `拡張機能`を開き、`ファイルを読み込む`ボタンで、ダウンロードしたHEX形式ファイルを選択します（`つづける`で確定）

:::

# mstate拡張機能の使い方

`define`ブロックで、ステートマシンの状態を定義していきます。
尚、サブステートマシンやサブ状態といった階層構造を直接的に定義できませんので、`M0`から`M5`までのステートマシンを使って、相互に連携させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/3f50e52e-401f-14cf-3a57-d8c9796b2c37.png)

:::note info
**ステート図の読み方・書き方**
mstate拡張機能(pxt-mstate)のステートマシンを[PlantUMLのステート・ダイアグラム](https://plantuml.com/state-diagram)としてステート図に表現することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/b2c32a04-f50d-5eeb-3616-0688db51d225.png)
を選択すると、ログとして出力されますので、出力された内容をコピーし、 https://www.plantuml.com/plantuml/ に貼り付けます。

ステート図（ステートマシン図、状態遷移図）の読み方や書き方は、統一モデリング言語（UML）の参考書を参照してください。また、本記事と参考書とを読み比べてmstate拡張機能(pxt-mstate)で出来ること、出来ないこと、実現方法の違いなどを確認してみてください。
:::

**▼おススメの参考書（UML入門書）**
UMLモデリングの基本が簡潔にまとめられています。
**かんたん UML入門 [改訂2版] – 2017/7/1**

https://www.amazon.co.jp/%E3%81%8B%E3%82%93%E3%81%9F%E3%82%93-UML%E5%85%A5%E9%96%80-%E6%94%B9%E8%A8%822%E7%89%88-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8-%E7%AB%B9%E6%94%BF/dp/477419039X?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dib=eyJ2IjoiMSJ9.Dr_p-xg8RdELrqxMBgKjRDrnCMIY5vmlJxdqKSUtSPsrar1Fw32GqDmv7r_ltpEvkkHBgCHfNJbYsFIWKNNTCpMc9H9fn0esj_xKgCLSxXAJJgUy5_kvP1LvBeo-48iYiMWHl9bzaitHlHP6LH1nVftWS7CMSG5tWCx0nTP61w4dnk_oRW1ryXNvBbgY1CaVyJ8vb7QZ5pjZWOL5_NTQ3Gn4aPliJa32gpiRNT0YawfHlwFiDtuO6TrvIrXwEA-RtO2vJkSHnI_zVEnL36X3R2_u6goNCeh6bQuQzfj3b0k.2Ggoz1fMfq_gcG4WnzlRwqp_jGtOX7uQM5zMtYc7XF4&dib_tag=se&keywords=uml&qid=1708288401&sr=8-1-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&psc=1&linkCode=ll1&tag=ahxyz0b-22&linkId=d8f786a10f562672f343d9aa968b6180&language=ja_JP&ref_=as_li_ss_tl

:::note warn
**ステート図の出力**
mstate拡張機能(pxt-mstate)で状態を定義すると、それをPlantUMLのWebサービスを使って、ステート図に表すことができます。
また、そのステート図の状態等に説明を補足する為、`(UML) description`ブロックを使っていますが、本記事ではその使い方の説明を省略しています。
尚、このブロックは、プログラムの動作に対して何も影響しません。ただし、文字列としてのメモリを消費しますので、HEX形式ファイルの容量オーバーでコンパイルできなかったり、micro:bit本体に転送できなかったりする場合があります。

**容量オーバーの例**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/a118b1ed-a8e1-abde-fffd-2ddaa19651b5.png)

:::


## 状態（ステート）

ステートマシンには、次の３つの状態の概念があり、状態から状態へと遷移します。

- 初期疑似状態（Initial Pseudostate）
- 状態（State）
- 終了状態（FinalState）

:::note info
**初期疑似状態と終了状態**
mstate拡張機能(pxt-mstate)の内部において、初期疑似状態と終了状態とは同じ状態です。また、その状態名は、`""`(空の文字列)です。
:::

ステートマシンが初期疑似状態である場合に、`start`ブロックを実行するとステートマシンが開始され、初期疑似状態から指定したデフォルト状態へと遷移します。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a
a -> [*]
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/5061253a-7c19-0f09-643b-127a28686db8.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.onTrigger("", [""], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.start(StateMachines.M0, "a")
})
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

:::note info
**ステートマシンの開始と終了**
初期疑似状態からデフォルト状態（図中の状態`a`）へ遷移（開始）するには、`start`ブロックを実行します。
終了状態へ遷移（終了）するには、`on trigger`ブロックの`targets`リストの要素に`""`(空の文字列)を設定します。`traverse`ブロックで、その要素の添え字を指定します（0番目から数える）。
:::

## 遷移（トランジション）

ステートマシン内では、トリガー（trigger）により、遷移元の状態から遷移先の状態（終了状態を含む）へと遷移します。

- 遷移（transition）
- トリガー（trigger）
- 遷移元の状態（source）
- 遷移先の状態（target）

ステートマシンにトリガー（trigger）を送るには、`send`ブロックを実行します。
トリガーを受けて、遷移するには、遷移元の状態定義（`define`ブロック）の内側に、`on trigger`ブロックを配置し、トリガー（trigger）と遷移先（target）とを指定します。
さらに、`on trigger`ブロックの内側（処理部）に、`traverse`ブロックで何番目の遷移先へ遷移するのかを指定します(0番目から始まる添え字)。

:::note warn
**完了時遷移（Completion Transition）**
`on trigger`ブロックの`trigger`に`""`(空の文字列)を指定すると、完了時遷移（Completion Transition）が行われます。
尚、この完了時遷移（Completion Transition）は、mstate拡張機能(pxt-mstate)によってコントロールされている為、`send`ブロックで`""`(空の文字列)を送ってもトリガーとしての遷移は行われません。
:::

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a
a --> b : e
state b
b -> [*] : e
b --> a : A+B
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/791a6a4d-880c-d7f2-c98d-941deb317c54.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScriptmstate.defineState(StateMachines.M0, "a", function () {
    mstate.onTrigger("e", ["b"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "b", function () {
    mstate.onTrigger("e", [""], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("A+B", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.start(StateMachines.M0, "a")
})
input.onButtonPressed(Button.AB, function () {
    mstate.send(StateMachines.M0, "A+B")
})
input.onButtonPressed(Button.B, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

## 状態の振る舞い（Behavior）

ステートマシンの状態には、３つの振る舞い（Behavior）があり、それぞれの振る舞いがそれぞれのタイミングで実行されます。

- entry - 状態に入った時（entryアクション）
- doActivity - その状態にある時（doアクティビティ）
- exit - 状態から出る前（exitアクション）

<table><tr><th>３つの振る舞いとタイミング</th><th>説明</th></tr><tr><td>

```plantuml
@startuml
state entry : entryアクションを実行する
state doActivity : doアクティビティを実行する\n（状態遷移を待つ）
state exit : exitアクションを実行する

[*] -> entry : 状態へ遷移
entry --> doActivity : 実行完了
doActivity -u-> doActivity : （遷移なし）
doActivity --> exit : 状態遷移(遷移前)
exit -l-> [*] : 実行完了
@enduml
```

</td><td>

1. ある状態へ遷移すると
entryアクションを実行します
1. entryの実行を完了すると
doアクテビティを実行します
（状態遷移を待つ）
1. 状態遷移が行われようとすると（遷移前）
exitアクションを実行します
1. exitの実行を完了すると
状態を抜け出し、状態遷移が行われます

</td></tr></table>

mstate拡張機能(pxt-mstate) では、`on state`ブロックと`on exit`ブロックの２つを使って、これら３つの振る舞いをコーディングします。

:::note warn
**entryアクションとdoアクティビティの実現方法は独特です**
mstate拡張機能(pxt-mstate) では、entryアクションとdoアクティビティの実現方法は独特であり、entryアクションとdoアクティビティとを一つにした`on state`ブロックを使ってコーディングします。詳しくは[「on stateブロックの使い方」](#on-stateブロックの使い方)を参照してください。
:::

次のブロック・コードでは、これら３つの振る舞いがどのようなタイミングで実行されるのかを確認できます。
ボタンAで、ステートマシン`M0`を開始後、トリガー`e`（ボタンB）によって、状態`a` と 状態`b`とで交互に状態遷移します。
また、トリガー`self`（ボタンA+B）によって、自分自身の状態へ状態遷移しますが、その際にも３つの振る舞いが実行されることを確認してください。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a : entry/ A表示\ndo/ ハート表示
a --> b : e
a --> a : self
state b : entry/ B表示\ndo/ カウンター表示
b --> a : e
b --> b : self
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/16fe40b2-d4d7-1d06-a65f-1d6e531ad2c7.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.descriptionUml("entry/ A表示")
    mstate.descriptionUml("do/ ハート表示")
    mstate.onState(1000, function (tickcount) {
        if (0 == tickcount) {
            basic.showString("A")
        } else {
            basic.showIcon(IconNames.Heart)
        }
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Happy)
    })
    mstate.onTrigger("e", ["b"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("self", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "b", function () {
    mstate.descriptionUml("entry/ B表示")
    mstate.descriptionUml("do/ カウンター表示")
    mstate.onState(1000, function (tickcount) {
        if (0 == tickcount) {
            basic.showString("B")
        } else {
            basic.showNumber(tickcount)
        }
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Sad)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("self", ["b"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.start(StateMachines.M0, "a")
})
input.onButtonPressed(Button.AB, function () {
    mstate.send(StateMachines.M0, "self")
})
input.onButtonPressed(Button.B, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

:::note info
**mstate拡張機能(pxt-mstate)における振る舞い**
状態における３つの振る舞いに加え、遷移においても振る舞いがあり、エフェクト（`effect`）と呼びます。
mstate拡張機能(pxt-mstate)では、振る舞いを２つのタイプで分類しています。
<table>
<tr><th>振る舞い</th><th>タイプ</th><th>対象</th><th>項目</th><th>呼び方</th></tr>
<tr><th rowspan="4">Behavior</th><td rowspan="3">Action</td><td rowspan="2">状態</td><td>entry</td><td>entryアクション</td></tr>
<tr> <td>exit</td><td>exitアクション</td></tr>
<tr> <td>遷移</td><td>effect</td><td>エフェクト</td></tr>
<tr> <td>Activity</td><td>状態</td><td>doActivity</td><td>doアクティビティ</td></tr>
</table>

タイプとして分類したアクション（Action）とアクテビティ（Activity）には、その振る舞い（Behavior）が、単発的か継続的かの違いがあります。
ただし、mstate拡張機能(pxt-mstate)では、entryアクションとdoアクテビティとを`on state`ブロックで実現しています。
:::


## 完了遷移（Completion Transition）

トリガーによる遷移の他に、完了遷移（Completion Transition）と呼ばれる遷移もあります。それは、ある状態への遷移でentryアクションとdoアクティビティの一連の処理が行われ、その一連の処理が完了した直後に行われる遷移です。

完了遷移（Completion Transition）を行うには、`transition`ブロックの`trigger`に`""`(空の文字列)を指定します。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a : entry/ A表示\ndo/ ハート表示
a --> b : e
a --> c : A+B
state b : entry/ B表示\ndo/ カウンター表示
b --> a : e
b --> c : A+B
state c : (完了遷移)
c --> a
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/558bb731-8a88-48a3-d43c-d42fac44792a.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.descriptionUml("entry/ A表示")
    mstate.descriptionUml("do/ ハート表示")
    mstate.onState(1000, function (tickcount) {
        if (0 == tickcount) {
            basic.showString("A")
        } else {
            basic.showIcon(IconNames.Heart)
        }
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Happy)
    })
    mstate.onTrigger("e", ["b"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("A+B", ["c"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "b", function () {
    mstate.descriptionUml("entry/ B表示")
    mstate.descriptionUml("do/ カウンター表示")
    mstate.onState(1000, function (tickcount) {
        if (0 == tickcount) {
            basic.showString("B")
        } else {
            basic.showNumber(tickcount)
        }
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Sad)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("A+B", ["c"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.start(StateMachines.M0, "a")
})
mstate.defineState(StateMachines.M0, "c", function () {
    mstate.descriptionUml("(完了遷移)")
    mstate.onState(0, function (tickcount) {
        basic.showString("C")
    })
    mstate.onTrigger("", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.AB, function () {
    mstate.send(StateMachines.M0, "A+B")
})
input.onButtonPressed(Button.B, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>


# `on state`ブロックの使い方

mstate拡張機能(pxt-mstate) では、entryアクションとdoアクティビティの実現方法は独特であり、entryアクションとdoアクティビティとを一つにした`on state`ブロックを使ってコーディングします。
`on state`ブロックの処理部は、指定したインターバル時間(ms)毎に繰り返し呼び出されます。状態遷移時にリセットされ、`0`から始まるカウンターを内部で保持しており、インターバル時間(ms)毎に加算されます。そのカウンターの値が`tickcount`引数で渡されます。
`on state`ブロックの処理部で、`tickcount`の値が`0`の場合に、entryアクションもしくはdoアクテビティとしての処理を実行するようにコーディングし、`tickcount`の値が正の値の場合は、doアクテビティとしての処理を実行するようにコーディングします。
尚、インターバル時間(ms)を`0`以下に指定すると、`tickcount=0`である１回目だけが呼び出され、繰り返されません。

:::note warn
**`tickcount`引数の値比較**
`on state`ブロックの呼び出しがスキップされることを考慮して、`tickcount`引数の値を比較します。

```js:JavaScript

if (0 == tickcount) {
    basic.showIcon(IconNames.Heart)
} else if (10 > tickcount) {
    basic.showNumber(tickcount)
} else {
    basic.showIcon(IconNames.Yes)
}

```

:::

また、インターバル時間(ms)が短すぎたり、処理部の実行時間が長すぎたりすると、2回目以降の呼び出しで、その呼び出しがスキップされるかもしれません(`tickcount=0`である１回目は必ず呼び出されます)。スキップされても、カウンターの値は、インターバール時間毎に内部で加算されていますので、`tickcount`引数の値と比較する際は、このことを考慮してください（等しいかどうかだけで比較しないようにします）。

<table><tr><th>項目</th><th>説明</th><th>備考</th></tr>
<tr><th> on state ブロック </th><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/69808041-1330-b4e9-a619-5c97434d9eed.png)

</td><td>entryアクション-doアクテビティとして、繰り返し呼び出されます。</td></tr>
<tr><th> インターバル時間(ms) </th><td>繰り返し呼び出される間隔を指定します。<br>インターバール時間が<code>0</code>以下の場合、1回目だけが呼び出され、繰り返されません。</td><td>インターバル時間が短すぎると、呼び出しがスキップされることがあります。ただし、1回目は必ず呼び出されます（<code>tickcount=0</code>）。</td></tr>
<tr><th> <code>tickcount</code>引数 </th><td>インターバル時間毎に加算されるカウンターを内部で保持していますが、その値が<code>tickcount</code>引数（変数）で渡されます。内部カウンターの値は、状態遷移時にリセットされ、0から始まります。</td><td>呼び出しがスキップされても、カウンターは内部で加算されていますので、<code>tickcount</code>引数の値が連続しない場合があります。ただし、1回目は必ず呼び出されます（<code>tickcount=0</code>）。</td></tr>
</table>

## 例1) １回目だけの呼び出し

`on state`ブロックのインターバル時間(ms)に `0` 以下の値を指定すると、1回目だけ呼び出され、繰り返されません。その時の`tickcount`引数の値は、`0`です。

<table><tr><th>ブロック・コード</th><th>説明</th></tr><tr><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/20faa70a-53d7-1a4b-4abc-dbb882e8598f.png)

</td><td>

インターバル時間(ms)に0以下の値として、`0`を指定します。
ステートマシンを開始すると、状態`a`に遷移し、`tickcount`引数の値`0`が表示されます。
ボタンAで、トリガー`e`を送信すると、exitアクションで、`Yes(チェック)`アイコンが表示され、再び、状態`a`に遷移し、`0`が表示されます。

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.onState(0, function (tickcount) {
        basic.showNumber(tickcount)
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Yes)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.start(StateMachines.M0, "a")

```

</details>

## 例2) 繰り返し

インターバル時間(ms)に`0`より大きい値を指定すると、そのインターバル時間(ms)毎に`on state`ブロックが呼び出されようとします(呼び出しのスキップあり)。
`on state`ブロック毎に、それぞれ独立したカウンターを内部に持っており、状態遷移後、`0`から加算されていきます。そのカウンターの値は、`on state`ブロックの`tickcount`引数で渡されます。

<table><tr><th>ブロック・コード</th><th>説明</th></tr><tr><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/722cf8ff-c6bc-1098-d0a5-753afa5c5c62.png)

</td><td>

インターバル時間(ms)に`1000`を指定します(1,000ms=1秒)。
ステートマシンを開始すると、状態`a`に遷移し、`tickcount`引数の値が1秒毎に繰り返し表示されます(`0`からのカウントアップ)。
ボタンAで、トリガー`e`を送信すると、exitアクションで、`Yes(チェック)`アイコンが表示された後、再び、状態`a`に遷移し、`0`からのカウントアップ表示が始まります。

</td></tr></table>

:::note warn
**カウントアップが連続しない**
繰り返しのサンプルコードを実行して、動作を確認してみるとわかりますが、`tickcount`引数の値が10以上になったり、状態遷移でアイコン表示したりすると、カウントアップが連続しなくなります。これは、表示処理がインターバール時間以内に完了しない為、呼び出しのスキップが発生しているためです。
:::


<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.onState(1000, function (tickcount) {
        basic.showNumber(tickcount)
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Yes)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.start(StateMachines.M0, "a")

```

</details>

## 例3) スキップされた繰り返し

`on state`ブロックの内部カウンターは、インターバル時間(ms)毎に必ず加算されますが、インターバル時間(ms)が短すぎると`on state`ブロックの処理部の呼び出しがスキップされることもあります。
例えば、`数を表示`ブロックを500ms毎に呼び出そうとした場合、表示される数が連続せず、呼び出しがスキップされた様子を確認できます。これは、`数を表示`ブロックにおいて、その数を一定時間表示しようとして、内部で一時停止（600ms以上）の処理が実行されているからです。
`on state`ブロックの処理部の呼び出しがスキップされても、内部のカウンターは、インターバル時間(ms)毎に加算される為、`tickcount`引数の値を表示することでスキップの様子も確認できるのです。

<table><tr><th>ブロック・コード</th><th>説明</th></tr><tr><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/3771dc10-0f81-807d-0acf-39f69104d4ab.png)

</td><td>

インターバル時間(ms)に`500`を指定します。
ステートマシンを開始すると、状態`a`に遷移し、`tickcount`引数の値が`0`からカウントアップ表示されますが、その表示は連続しておらず、スキップされた様子を確認できます。
ただし、1回目は必ず呼び出されます（`tickcount=0`）。
</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.onState(500, function (tickcount) {
        basic.showNumber(tickcount)
    })
    mstate.onExit(function () {
        basic.showIcon(IconNames.Yes)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.start(StateMachines.M0, "a")

```

</details>

:::note info
**数の表示をすばやくする方法**

`数の表示`ブロックは、デフォルトでその処理に600ms以上の時間を要します。これをすばやくするには、JavaScriptで第２引数(`interval`引数)に小さい値を指定します(1以上)。
```js:JavaScript
    basic.showNumber(counter, 1)
```

また、counterの値が2桁以上になると、スクロールが発生してしまうため、1桁目を表示するように工夫したりします(10で割った余り)。
```js:JavaScript
    basic.showNumber(counter % 10, 1)
```

尚、第２引数に0(ゼロ)を指定すると、micro:bit v1の実機では、異常終了してしまいますので、1以上の値を指定してください。micro:bit v2の実機やMakeCodeのシミュレーターでは、0(ゼロ)を指定しても異常終了しないようです。

:::


# 遷移におけるガードとエフェクト

遷移には、トリガーによる遷移と完了遷移（Completion Transition）とがありますが、どちらの遷移も、ガード（guard）による条件でその遷移を抑制することができます。また、遷移に伴い、エフェクト（effect）と呼ばれるアクションを実行することも可能です。
これらを実現するには、`on trigger`ブロックの内部（処理部）で、ガード（guard）やエフェクト（effect）をコーディングします。

:::note warn
**遷移の評価で遷移の許可を忘れずに**
`on trigger`ブロックの内部（処理部）で、その遷移を評価し、その結果として遷移させる場合、`traverse`ブロックで遷移先の添え字を指定し、遷移を許可します。遷移先の添え字を指定しなかったり、配列に対して無効な添え字を指定したりすると、遷移が抑止されます。
:::


`on trigger`ブロックの内部（処理部）で、ガード（guard）による条件を満たしているかを判定します。これを遷移の評価と呼んでいます。その条件を満たしていれば、`traverse`ブロックを実行することで遷移を許可します。その条件を満たしていなければ、`traverse`ブロックを実行せずに遷移を抑止します。１つの`on trigger`ブロックに複数の遷移先を配列で定義できますので、遷移の評価で求められた遷移先の添え字を`traverse`ブロックの引数に指定します。
また、遷移の評価において、ガード（guard）による条件を満たしていれば、その分岐処理の中で、エフェクト（effect）を実行するようにコーディングします。尚、mstate拡張機能(pxt-mstate) は、エフェクト（effect）を実行するかどうかについて関与しませんので、`on trigger`ブロックの内部（処理部）でガード（guard）による分岐処理とエフェクト（effect）の実行処理とを適切にコーディングします。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a : 余りの計算\n（奇数・偶数）
a --> 奇数 : e [ 余り=1 ]
a --> 偶数 : e [ 余り<>1 ]
state 偶数
偶数 --> a : e
state 奇数
奇数 --> a : e
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml

```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/45fe86a4-a15d-9187-39f4-7a5b2f5a5ac5.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "偶数", function () {
    mstate.onState(0, function (tickcount) {
        basic.showIcon(IconNames.Square)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.descriptionUml("余りの計算\\n（奇数・偶数）")
    mstate.onState(1000, function (tickcount) {
        basic.showNumber(tickcount)
        余り = tickcount % 2
    })
    mstate.descriptionUml("余り=1")
    mstate.descriptionUml("余り<>1")
    mstate.onTrigger("e", ["奇数", "偶数"], function () {
        if (1 == 余り) {
            mstate.traverse(StateMachines.M0, 0)
        } else {
            mstate.traverse(StateMachines.M0, 1)
        }
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.defineState(StateMachines.M0, "奇数", function () {
    mstate.onState(0, function (tickcount) {
        basic.showIcon(IconNames.Diamond)
    })
    mstate.onTrigger("e", ["a"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
let 余り = 0
mstate.start(StateMachines.M0, "a")
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

# トリガーの引数

`send`ブロックでトリガーを送る際に、引数`args`（数値の配列）を渡すことができます。
これまで、ボタンAやボタンB、ボタンA+Bのそれぞれのイベントに対応した別々のトリガーを送っていましたが、もしこれが、パソコンのキーボードだったらいくつのトリガーが必要でしょうか。トリガーの引数`args`を使えば、１つのトリガーを用意し、その引数`args`にキーの番号を渡すことで、遷移の評価において、その引数`args`の値を取得し、どのキーが押されたのかを判断することができます。

:::note warn
**トリガーのタイムラグ**
トリガーを送信してから遷移の評価が起こるまで(`on trigger`ブロックの処理部が実行されるまで)、そこにはタイムラグがあります。ステートマシンが何らか別の振る舞いを処理している場合、その振る舞いが完了するまで、送信されたトリガーは保留にされるからです。
例えば、ボタンでスタートとストップできるストップウォッチを作成しようとした場合、`稼働時間（ミリ秒）`ブロックを使って稼働時間を取得するかと思いますが、ボタンが押された直後の稼働時間をトリガーの引数で渡す必要があります。そうすれば、トリガーのタイムラグがあったとしても、スタートとストップが押された直後の稼働時間(スタート)と稼働時間(ストップ)とで経過時間を計算できます。
:::

次の例では、ボタンAやボタンB、ボタンA+Bが操作された際に、トリガー`e`を送るとともに、引数`args`の0番目の値に対象のボタン番号（1:A, 2;B, 3:A+B）を、1番目の値にP0端子のデジタル入力値（0, 1）を渡しています。
渡された引数`args`は、`args`ブロックで数値の配列として取得できますので（`on trigger`ブロック内でのみ取得が可能）、その値を元にアルファベットを表示しています(P0端子をキーボードのシフトキーに見立てています)。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a : トリガー引数で\nエフェクト（内部遷移）
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml

```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/fc410ad3-4861-8d28-0be2-23231fb0dd8c.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.descriptionUml("トリガー引数で\\nエフェクト（内部遷移）")
    mstate.onTrigger("e", [], function () {
        ボタン = mstate.args(StateMachines.M0)[0]
        P0値 = mstate.args(StateMachines.M0)[1]
        if (1 == ボタン) {
            if (0 == P0値) {
                basic.showString("a")
            } else {
                basic.showString("A")
            }
        } else if (2 == ボタン) {
            if (0 == P0値) {
                basic.showString("b")
            } else {
                basic.showString("B")
            }
        } else if (3 == ボタン) {
            if (0 == P0値) {
                basic.showString("c")
            } else {
                basic.showString("C")
            }
        } else {
            basic.showIcon(IconNames.No)
        }
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e", [1, pins.digitalReadPin(DigitalPin.P0)])
})
input.onButtonPressed(Button.AB, function () {
    mstate.send(StateMachines.M0, "e", [3, pins.digitalReadPin(DigitalPin.P0)])
})
input.onButtonPressed(Button.B, function () {
    mstate.send(StateMachines.M0, "e", [2, pins.digitalReadPin(DigitalPin.P0)])
})
let ボタン = 0
let P0値 = 0
P0値 = pins.digitalReadPin(DigitalPin.P0)
mstate.start(StateMachines.M0, "a")
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

:::note warn
**内部遷移と自己遷移（外部遷移）**
トリガーの引数を確認するために、`on trigger`ブロックで遷移しない遷移をコーディングしましたが、これを内部遷移と呼びます。また、これとは別に自己遷移と呼ばれる遷移（外部遷移）もあります。結果的にどちらも元の状態のままに見えますが遷移の有無に違いがあります。

- 内部遷移 - 遷移しない為、exitアクション等が行われない
- 自己遷移 - 遷移する為、exitアクション、entryアクションが行われる

この内部遷移を使えば、トリガー等に反応して、エフェクトだけを実行することが可能です。また、mstate拡張機能(pxt-mstate)では、階層構造を直接的に定義できませんので、この内部遷移のエフェクトにおいて、他のステートマシンを操作することが想定されます。

:::

# `on state`ブロックと完了遷移（Completion Transition）

ある状態に遷移すると、entryアクションとdoアクテビティとが実行された直後に完了遷移（Completion Transition）に対する遷移の評価が行われます。mstate拡張機能(pxt-mstate)では、さらに、２回目以降のdoアクテビティが実行された直後にも完了遷移（Completion Transition）に対する遷移の評価が繰り返されます。

:::note info
**The run-to-completion paradigm**
統一モデリング言語（UML）の仕様書では、ステートマシン自体を動作させるための処理のサイクルについても説明しており、それをrun-to-completion パラダイム（paradigm）と呼んでいます。また、そのパラダイムにおけるそれぞれの処理をrun-to-completion ステップ（step）と呼んでいます。
また、パラダイムの中には、待機ポイント（wait point）と呼ばれる安定した状況（state configuration）があります。トリガー等のイベントが発生しても、前のイベントに対応する処理が実行されていたりすれば、待機ポイントになるまでそのイベントに対応する処理の実行は保留とされます。
つまり、そのステートマシンにおいて、複数のイベントに対応する処理が同時に実行されることはありません。

```plantuml
@startuml
' State Diagram - https://plantuml.com/state-diagram
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
state __ステートマシン自体の動作__ {
[*] -> 待機ポイント : run-to-completionの開始
state イベント振り分け
イベント振り分け -r-> 処理の実行 : / 対応する処理の準備
state 処理の実行
処理の実行 --> 待機ポイント : [ 完了 ]
state 待機ポイント
待機ポイント --> イベント振り分け : 次のイベント
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

:::


インターバル時間の異なる`on state`ブロックが複数ある場合（例えば、0msと3000ms、7000ms）、次のように処理が実行され、遷移の評価が行われます。

1. 状態が遷移すると全ての`on state`ブロックが実行されます
（`tickcount=0`：entryアクションとdoアクティビィとして呼び出される）
1. 最初の完了遷移（Completion Transition）が評価されます
1. いずれかの`on state`ブロックが呼び出されます
（例えば、3000msのブロックが、`tickcount>0`：doアクティビィとして呼び出される）
1. 完了遷移（Completion Transition）が評価されます
1. いずれかの`on state`ブロックが呼び出されます
（例えば、3000msのブロックが、`tickcount>0`：doアクティビィとして呼び出される）
1. 完了遷移（Completion Transition）が評価されます
1. いずれかの`on state`ブロックが呼び出されます
（例えば、7000msのブロックが、`tickcount>0`：doアクティビィとして呼び出される）
1. 完了遷移（Completion Transition）が評価されます
1. ボタンAでトリガーを送ります
1. 対応するトリガーの遷移が評価されますが、完了遷移（Completion Transition）は評価されません

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a : entry/ 1を設定\ndo/\n1. 3秒毎に2を設定\n2. 7秒毎に3を設定\n評価/\n- 完了遷移の評価\n- トリガーによる遷移の評価
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/7ddd4a61-b95c-b2c8-9ebf-53eeed1ccaea.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.descriptionUml("entry/ 1を設定")
    mstate.onState(0, function (tickcount) {
        評価事由 = 1
    })
    mstate.descriptionUml("do/")
    mstate.descriptionUml("1. 3秒毎に2を設定")
    mstate.onState(3000, function (tickcount) {
        if (tickcount > 0) {
            評価事由 = 2
        }
    })
    mstate.descriptionUml("2. 7秒毎に3を設定")
    mstate.onState(7000, function (tickcount) {
        if (tickcount > 0) {
            評価事由 = 3
        }
    })
    mstate.descriptionUml("評価/")
    mstate.descriptionUml("- 完了遷移の評価")
    mstate.onTrigger("", [], function () {
        basic.showNumber(評価事由)
        basic.clearScreen()
        評価事由 = 0
    })
    mstate.descriptionUml("- トリガーによる遷移の評価")
    mstate.onTrigger("e", [], function () {
        basic.showString("e")
        basic.clearScreen()
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.start(StateMachines.M0, "a")
})
input.onButtonPressed(Button.B, function () {
    mstate.send(StateMachines.M0, "e")
})
let 評価事由 = 0
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

# アフター５の実現

所定の時間が経過したら（タイムアウトしたら）、何らかのアクションを行ったり、状態を遷移させたりしたい場合があります。
次の例では、doアクテビティと完了遷移とを組み合わせて、5秒経過したら状態を遷移するようにしています。

<table><tr><th>ステート図</th><th>ブロック・コード</th></tr><tr><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> a : <<start>>
state a
a --> b : [ after 5s ]
state b
b --> a : [ after 5s ]
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

</td><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/68399846-41ab-b3d3-028c-974bab0c0f9e.png)

</td></tr></table>

<details><summary><b>ソースコード</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "a", function () {
    mstate.onState(5000, function (tickcount) {
        if (0 == tickcount) {
            basic.showIcon(IconNames.Heart)
        }
        after5 = tickcount
    })
    mstate.descriptionUml("after 5s")
    mstate.onTrigger("", ["b"], function () {
        if (0 < after5) {
            mstate.traverse(StateMachines.M0, 0)
        }
    })
})
mstate.defineState(StateMachines.M0, "b", function () {
    mstate.onState(5000, function (tickcount) {
        if (0 == tickcount) {
            basic.showIcon(IconNames.SmallHeart)
        }
        after5 = tickcount
    })
    mstate.descriptionUml("after 5s")
    mstate.onTrigger("", ["a"], function () {
        if (0 < after5) {
            mstate.traverse(StateMachines.M0, 0)
        }
    })
})
let after5 = 0
mstate.start(StateMachines.M0, "a")
mstate.exportUml(StateMachines.M0, "a", ModeExportUML.StateDiagram)

```

</details>

:::note info
**「後日、こちらからご連絡差し上げます」**
これは、ものごとをお断りをする際の常套句であり、ハリウッドの原則（Hollywood Principle）とも言うようです（"Don't Call Us, We'll Call You."）。
プログラミングにおけるフレームワークにおいてもハリウッドの原則が適用されています。しかし、その意図するところは異なり、フレームワークからは所定の条件と順序で適切かつ確実に処理が呼び出されます。
mstate拡張機能(pxt-mstate)もそうですが、フレームワークの活用で堅牢なシステムの構築が可能です。
:::

# まとめ

ステートマシンの基礎と基本として、ステート図とコードを示しながらmstate拡張機能(pxt-mstate)の使い方を中心に説明しました。
mstate拡張機能(pxt-mstate)というフレームワークを利用すれば、ステートマシンという考え方で設計し、コーディングすることができます。単にアイコン表示やカウント表示をする機能であれば、ステートマシンの必要性は感じないかもしれません。それでも、ステート図で表現するとその振る舞いをより理解できるようになると思います。
また、組み込み系のmicro:bitプログラミングにおいては、その振る舞いが複雑化し、フローチャートだけでは上手く表現しきれない場面があります。そんな時、ステート図も用いると、要求を実現させた不具合の少ないコーディングが可能になると信じています。

https://www.youtube.com/watch?v=hiERNxxfxJQ&list=PLMMBk9hE-SerTLN2D6NiI7v7DJ78IBIAq&index=2

**【ステートマシン版】Flashing Heart**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/a6b5a997-79c9-88d4-0d3a-5aca6d29d3e8.png)

https://makecode.microbit.org/_a8eCx9W4KHpj