【中学校レベル】micro:bitでつくるスタートシグナル付ストップウォッチ

# ねらい

**計測・制御のプログラミングによる問題の解決** ができるようにするとともに、 **状態遷移という考え方** を取り入れ、統一モデリング言語に含まれる**ステート図**を使って、 **自分の考えを整理し、よりよい発想を生み出せる** ようにする。

# 学び

micro:bitと **Mstate拡張機能** を使用してストップウォッチを作成し、スタートシグナル付ストップウォッチへと機能を追加する。

**「プログラムによる計測・制御」**
micro:bitの稼働時間を使って時間を計る。
1. 「時間を計る機能」をステート図で考え、紙面上でデバッグする
1. ステート図を元にプログラミングし、シミュレーターでデバッグする
1. 「時間等を表示する機能」を別のステートマシンでプログラミングする
1. 「時間を計る機能」に「時間等を表示する機能」を連動させ、ストップウォッチとして完成させる
1. 結果表示の問題点を確認し、改善する

**「計測・制御のプログラミングによる問題の解決」**
自動で計測をスタートできるようにする為、カウントダウンによるスタートシグナルを追加する
1. 「カウントダウン機能」をステート図に追加し、プログラミングする
1. 完成したプログラムをmicro:bitに転送し、実機で動作確認する
1. 実際に使って改善する

# 「時間を計る機能」

スタートからストップまでの経過時間を計るにはどうしたらよいでしょうか。

1. 起動したら、経過時間をリセットし、スタートされるのを待つ
1. スタートボタンでスタートされたら、時間の計測を開始する
1. ストップボタンでストップされたら、時間の計測を停止し、経過時間を計算する
1. 停止中にスタートボタンでスタートされたら、時間の計測を再開する
1. 停止中にリセットボタンで経過時間がリセットされたら、新たにスタートされるのを待つ

## ステート図とは

**状態遷移の考え方** で、 **ステート図** を使って「時間を計る機能」を説明します。
まずは、 **ステート（状態）** を洗い出します。

- **"準備"ステート** - 計測の準備をする
- **"計時"ステート** - 時間を計測する
- **"停止"ステート** - 経過時間を計算する

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 準備 : entry/                          \n\ndo/\n\nexit/\n
準備 --> 計時 : スタート
state 停止 : entry/                          \n\ndo/\n\nexit/\n
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/                          \n\ndo/\n\nexit/\n
計時 -r-> 停止 : ストップ
}
@enduml
```

**記号の意味**

|項目|記号|説明|備考|
|----|----|----|----|
|ステート|角丸の四角形|状態を表しており、ある特定の処理を行います。|外側の四角形は、１つのステートマシンを表しています（Mstate特有の決め事）。|
|トランジション|矢印|ある状態からある状態への状態遷移を表しています。|矢印が複数の状態を指している場合、何れか1つへの状態遷移が可能です。|
|トリガー|矢印の記述|トリガーが発生すると、その状態遷移が行われます。|【記述の省略】状態の処理が完了すると、その状態遷移が行われます。|
|開始状態|黒丸|ステートマシンが開始されると、開始状態からデフォルト・ステートへと状態遷移します。|終了状態は、二重丸で表します。|

この **ステート図** は次のことを意味しています。

1. **ステートマシン"M0"** が開始されると、 **開始状態** から **"準備"ステート** へと状態遷移する
1. **"準備"ステート** で **"スタート"トリガー** が発生すると、 **"計時"ステート** へと状態遷移する
1. **"計時"ステート** で **"ストップ"トリガー** が発生すると、 **"停止"ステート** へと状態遷移する
1. **"停止"ステート** で **"リセット"トリガー** が発生すると、 **"準備"ステート** へと状態遷移する
1. **"停止"ステート** で **"スタート"トリガー** が発生すると、 **"計時"ステート** へと状態遷移する

つまり、 **トリガー** によって、 **ステート(状態)** から **ステート(状態)** へと **トランジション(状態遷移)** することを示したのが、 **ステート図** です。

**ステート内の記述**

|項目|記号|説明|備考|
|----|----|----|----|
|entryアクション|(entry/)|その状態に入った時に、その処理を行います。|ステートマシンの開始や状態遷移のたびに実行されます。|
|doアクテビティ|(do/)|その状態にある場合に、繰り返し処理を行います。|Mstateでは繰り返し間隔（ミリ秒）を指定します。|
|exitアクション|(exit/)|その状態から出る時に、その処理を行います。|ステートマシンの終了や状態遷移のたびに実行されます。|

また、 **それぞれのステート** の中で、**entryアクション**、**doアクテビティ**、**exitアクション**といったある特定の処理を行うようにします。

## 「時間を計る機能」としての振る舞い

「時間を計る機能」を細分化すると、次のような処理を行う必要があります。

- **経過時間をリセットする**
- **計時を開始する**
- **計時を停止する**
- **経過時間を計算する**
- **計時を再開する** (≒計時を開始する)

どの **ステート** の どこで（**entryアクション**、**doアクテビティ**、**exitアクション**）、どの処理を実行すればよいのか考えてみましょう。

**ステート内の記述例（ステート図）**

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 準備 : entry/                          \n - 経過時間をリセットする\ndo/\n\nexit/\n
準備 --> 計時 : スタート
state 停止 : entry/                          \n - 計時を停止する\n - 経過時間を求める\ndo/\n\nexit/\n
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/                          \n - 計時を開始する(再開する)\ndo/\n\nexit/\n
計時 -r-> 停止 : ストップ
}
@enduml
```

### アクテビティ図での表現

**ステート図** を使って **「時間を計る機能」** を表現してみましたが、試しに、 **ステート図** を使わずに、 **アクティビティ図** や **フローチャート図** だけで表現しようとするとどのような図になるのでしょうか。
プログラムの基本的な構成要素である、 **順次処理** 、 **反復処理** 、 **分岐処理** を使った場合を思い浮かべてみてください。

<table>
<thead><tr><th>順次処理</th><th>反復処理</th><th>分岐処理</th></tr></thead>
<tr>
<td>

```plantuml

@startuml
start
:処理1;
:処理2;
:処理3;
stop
@enduml

```

</td><td>

```plantuml
@startuml
start
:処理1;
while (繰り返し?)
  :処理2;
endwhile
:処理3;
stop
@enduml

```

</td><td>

```plantuml
@startuml
start
:処理1;
if (条件判定?) then (yes)
  :処理2;
else (no)
  :処理3;
endif
stop
@enduml

```

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

きっと、複雑な構造になってしまうことでしょう。

:::note warn
**ステート図が最適とは限りません**
どのような図が思い浮かびましたか？思い浮かばなくても問題ありません。もし、思い浮かんだとしても、とても複雑な図になってしまったと思います。
しかし、これは、**ステート図** が優れていて、 **アクティビティ図** や **フローチャート図** が劣っているということではありません。 **「すべてを１つのタイプの図で表現するのは難しい」** というだけのことです。その為、 **統一モデリング言語(UML)** では、様々なタイプの図が用意されており、適材適所で活用していくことが大切です。
:::

## micro:bitにおける時間の計り方

ここで、micro:bit における時間の計り方を考えます。
**Microsoft MakeCode for micro:bit** には、 **稼働時間（ミリ秒）** や **稼働時間（マイクロ秒）** といった**micro:bitの稼働時間**を取得できるブロックがあります。この**micro:bitの稼働時間**は、micro:bitが起動もしくはリセットされてからの時間のことです。

この稼働時間を使って、スタート時の稼働時間(t1)とストップ時の稼働時間(t2)をそれぞれ変数に保持し、その差を計測時間として求めます。今回は、計測の再開があるため、ストップするごとに計測時間の累計を経過時間として求めるようにします。

```
                ▼スタート          ▽ストップ        ▼スタート'         ▽ストップ' 
------(準備)----|------(計測)------|-----(停止)-----|-----(計測)-------|-----(停止)---->
稼働時間        t1                t2               t1'               t2' 
計測時間         |<----(t2-t1)---->|                |<----(t2'-t1')-->|
経過時間      0              <経過時間>+(t2-t1)               <経過時間>+(t2'-t1')
```

これをmicro:bitでプログラミングしてみましょう。
まずは、次の変数を用意します。

|変数名|説明|備考|
|------|----|----|
|スタート稼働時間ミリ秒|スタート時の稼働時間（ミリ秒）|t1やt1'|
|ストップ稼働時間ミリ秒|ストップ時の稼働時間（ミリ秒）|t2やt2'|
|計測時間ミリ秒|スタートからストップまでの時間（ミリ秒）|計測毎に計算|
|経過時間ミリ秒|求めたい時間<br>計測毎に計測時間ミリ秒を累計した時間|準備でゼロに初期化|

必要な代入式や計算式は次のようになります。

|処理|ブロック|
|----|--------|
| 準備処理 |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/f1f089f7-e09a-c7be-04d7-432c5bdfcf76.png)|
| 開始処理 | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/3c8e1ebc-8afc-e2cd-0001-554cf78c220e.png) |
| 停止処理 | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/af1899a1-17bd-730f-a54a-7a7e2e6db434.png) |

## entry-do-exit

micro:bitでの時間の計り方は、わかりましたが、これらをどのように実行すればよいのでしょうか。ステート図に戻って考えてみます。

**準備処理** のブロックを **"準備"ステート** の **entryアクション** で実行するようにしましょう。 **exitアクション** でも良いのかもしれません。
**開始処理** のブロックは、どうでしょうか。 **"準備"ステート** の **exitアクション** でしょうか、それとも、**"計時"ステート** の **entryアクション** でしょうか。ひとまず、 **"計時"ステート** の **entryアクション** にしておきます。
**停止処理** のブロック群は、 **"停止"ステート** の **entryアクション** で実行するのが良さそうです。 **"計時"ステート** の **exitアクション** でも良さそうです。

**各処理の記述（ステート図）**

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 準備 : entry/                          \n - 準備処理\ndo/\n\nexit/\n
準備 --> 計時 : スタート
state 停止 : entry/                          \n - 停止処理\ndo/\n\nexit/\n
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/                          \n - 開始処理\ndo/\n\nexit/\n
計時 -r-> 停止 : ストップ
}
@enduml
```

## ステート図でのデバッグ

作成した **ステート図** が意図したとおりに動作するかを紙面上で **デバッグ** してみます。
ステート図を見ながら、 **トリガーの発生** と **状態遷移** 、 **各ステート** での **entryアクション** がどのように行われるのかをイメージしていきます。

**【パターン1】**
まずは、スタートして、ストップ、リセットの動きを見てみます。

**スタート→ストップ→リセット**

1. **ステートマシン"M0"** が起動する
1. **"準備"ステート** の **"entry"アクション** で、 **「準備処理」** を行う
→ "経過時間ミリ秒"変数の値は、ゼロ
1. **"スタート"トリガー** で、**"計時"ステート** へ 状態遷移する
1. **"計時"ステート** の **"entry"アクション** で、 **「開始処理」** を行う
→ "スタート稼働時間ミリ秒"変数の値は、スタート時の「稼働時間（ミリ秒）」
1. **"ストップ"トリガー** で、**"停止"ステート** へ 状態遷移する
1. **"停止"ステート** の **"entry"アクション** で、 **「停止処理」** を行う
→ "ストップ稼働時間ミリ秒"変数の値は、ストップ時の「稼働時間（ミリ秒）」
 （"計測時間"と"経過時間"が計算されている）
1. **"リセット"トリガー** で、**"準備"ステート** へ 状態遷移する
1. **"準備"ステート** の **"entry"アクション** で、 **「準備処理」** を行う
→ "経過時間ミリ秒"変数の値は、ゼロ

**【パターン2】**
次に **"停止"ステート** から **"計時"ステート** へと、計時が再開される動きを見てみます。スタート/ストップで計時が３回（以上）行われるパターンで確認します。

**スタート(1)→ストップ(1)→スタート(2)→ストップ(2)→スタート(3)→ストップ(3)→リセット**

1. **（パターン1の続きから）**
→ "経過時間ミリ秒"変数の値は、ゼロ
1. **"スタート"トリガー(1)** で、**"計時"ステート** へ 状態遷移する
1. **"計時"ステート** の **"entry"アクション** で、 **「開始処理」** を行う
→ "スタート稼働時間ミリ秒"変数の値は、スタート時の「稼働時間（ミリ秒）」
1. **"ストップ"トリガー(1)** で、**"停止"ステート** へ 状態遷移する
1. **"停止"ステート** の **"entry"アクション** で、 **「停止処理」** を行う
→ "ストップ稼働時間ミリ秒"変数の値は、ストップ時の「稼働時間（ミリ秒）」
 （"計測時間(1)"と"経過時間"が計算されており、"経過時間"は、"計測時間(1)"）
1. **"スタート"トリガー(2)** で、**"計時"ステート** へ 状態遷移する
1. **"計時"ステート** の **"entry"アクション** で、 **「開始処理」** を行う
→ "スタート稼働時間ミリ秒"変数の値は、スタート時の「稼働時間（ミリ秒）」
1. **"ストップ"トリガー(2)** で、**"停止"ステート** へ 状態遷移する
1. **"停止"ステート** の **"entry"アクション** で、 **「停止処理」** を行う
→ "ストップ稼働時間ミリ秒"変数の値は、ストップ時の「稼働時間（ミリ秒）」
 （"計測時間(2)"と"経過時間"が計算されており、"経過時間"は、"計測時間(1)"+"計測時間(2)"）
1. **"スタート"トリガー(3)** で、**"計時"ステート** へ 状態遷移する
1. **"計時"ステート** の **"entry"アクション** で、 **「開始処理」** を行う
→ "スタート稼働時間ミリ秒"変数の値は、スタート時の「稼働時間（ミリ秒）」
1. **"ストップ"トリガー(3)** で、**"停止"ステート** へ 状態遷移する
1. **"停止"ステート** の **"entry"アクション** で、 **「停止処理」** を行う
→ "ストップ稼働時間ミリ秒"変数の値は、ストップ時の「稼働時間（ミリ秒）」
 （"計測時間(3)"と"経過時間"が計算されており、"経過時間"は、"計測時間(1)"+"計測時間(2)"+"計測時間(3)"）
1. **"リセット"トリガー** で、**"準備"ステート** へ 状態遷移する
1. **"準備"ステート** の **"entry"アクション** で、 **「準備処理」** を行う
→ "経過時間ミリ秒"変数の値は、ゼロ

特に問題なさそうです。また、「ひとまず」として仮決めした「開始処理」も特に問題がないようなので、ここに決定します。

## ステートの定義とトランジションの宣言

いよいよ、 **ステート図** を元に **Mstate拡張機能** のブロックを使って、プログラミングします。
まずは、ステートを `define` ブロックで定義し、トランジションを `transition` ブロックで宣言します。
`最初だけ` ブロックの中に、 `UML` ブロックと `start` ブロックとを配置します。

![rect6956.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/f1970dae-d3fe-88f2-3ec9-803cbc4bb001.png)

:::note warn
**Mstate拡張機能の追加手順**
1. https://github.com/jp-rad/pxt-mstate/releases を開く
1. 最新のHEXファイルをダウンロードする(例: pxt-mstate.0.6.1.hex)
1. https://makecode.microbit.org/ でプロジェクトを新規作成する
1. 「＋拡張機能」をクリックし、拡張機能画面を開く
1. 「ファイルを読み込む」をクリックし、ダイアログを開く
1. ダウンロードしたHEXファイルを選択し、「つづける」をクリックする
1. プロジェクトの画面に「Mstate」が追加される
:::

## ステート図のUML構文出力

`UML` ブロックが実行されると、シミュレーターのコンソール画面に[PlantUML](http://www.plantuml.com/plantuml/)のUML構文が出力されます。
これを、 http://www.plantuml.com/plantuml/ などの描画ツールに入力すると、ステート図に変換されます。

1. `UML`ブロックを`最初だけ`に配置する（ステートマシン"M0"を指定）
1. **Show data シミュレーター**ボタンをクリックし、コンソール画面を表示する
1. `@startuml`から`@enduml`までをコピーする
1. http://www.plantuml.com/plantuml/ を開き、枠内にペーストする
1. ステート図に変換されて、表示される

**Show data シミュレーターのコンソール画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/f6cc808a-1319-7abc-df97-067205d96d48.png)

**ステート図への変換**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/7ca072d2-e627-d670-0acb-e720cbbb28ce.png)

## entryアクションの宣言

それぞれのステート定義の中に、`on entry`ブロックを配置し、 **entryアクション** を宣言します。
また、各ステートの名称の欄に、":"(コロン)で区切って、その説明を記述することができます("\n"は改行)。

**ブロック**
![g1039.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/8b2fa346-183b-ff8b-ffa5-d50ccc94aa11.png)

**ステート図**
```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 停止 : entry/\n - 停止処理
停止 --> 準備 : リセット
停止 --> 計時 : スタート
state 計時 : entry/\n - 開始処理
計時 --> 停止 : ストップ
state 準備 : entry/\n - 準備処理
準備 --> 計時 : スタート
}
@enduml
```

## ボタン操作

`start` ブロックが実行されると、 **"準備"ステート** を **デフォルト・ステート** として、 **ステートマシン"M0"** が開始されます。まだ、 **トリガー** を発生させていませんので、 **"準備"ステート** に留まったままです。
それでは、ボタン操作でトリガーを発生させて、ステートマシンを動かしてみましょう。

|ボタン|発生させるトリガー|コード|
|:----:|----------------|------|
|A|スタート| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/2a741419-2421-79c5-0c47-44efa6346176.png) |
|B|ストップ| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/f78c1534-a267-65d5-e4cb-97d1da4dab25.png) |
|A+B|リセット| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/b7896cbb-3072-fd56-7fcb-fe3ddb6ecc57.png) |


## デバッグ モード

ここまでのプログラムは、 **「時間を計る機能」のみ** ですので、表示出力がなく、見た目上、動作しているのかどうかわかりません。そこで、 **デバッグ モード** に切り替えて、 **ブレークポイント** を設定し、動作中のプログラムを 一時停止したり、ステップ実行したりすることで、どのようにブロックが実行されているのかを確認します。

まずは、 **デバッグ モード** に切り替えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/e95c5b9b-a9b5-ae2e-3f63-f06b37b8a07d.png)

次に、一時停止させたいブロックに赤丸のチェックをつけて、ブレークポイントを設定ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/6f2574ad-7221-1071-fdd8-476ba0d0b7c7.png)

ボタンA、ボタンB、ボタンA+Bで操作するとブレークポイントで一時停止しますので、Stepボタンで中に進んだり、再生ボタンで実行を継続したりして、ブロックの実行を確認できます。


# 「時間等を表示する機能」

ここまで、 **「時間を計る機能」** をプログラミングしてきましたが、ここから、 **「時間等を表示する機能」** を追加していきます。
今回、 **「時間を計る機能」** と **「時間等を表示する機能」** とを明示的に分けて考えています。これは、それぞれを **独立したステートマシン** にすることにより、プログラムが肥大化したり、複雑化したりすることを防ぎ、品質を高めるためです。

そこで、別のステートマシン(**ステートマシン"M1"**)で **「時間等を表示する機能」** を定義・宣言します。

**ステートマシン"M1"（ステート図）**

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M1__ {
[*] --> 初期表示
state 初期表示 : □アイコン
初期表示 --> 点滅表示 : 計時中
state 結果表示 : 繰り返し表示
結果表示 -l-> 点滅表示 : 計時中
結果表示 -u-> 初期表示 : 準備中
state 点滅表示 : ♥点滅
点滅表示 -r-> 結果表示 : 停止中
}
@enduml
```

次のブロック例のように **ステートマシン"M1"** をプログラミングします。

**ブロック例**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/466e780b-d61d-e16b-6462-71ad08310335.png)

## 機能の連動

**時間を計測する機能(ステートマシン"M0")** に連動して、 **時間等を表示する機能(ステートマシン"M1")** が動作するように、**ステートマシン"M0"** の **ステート** に連動して、 **ステートマシン"M1"** への **トリガー** を発生させます。

<table>
<tr><th>時間を計測する機能</th><th>時間等を表示する機能</th></tr>
<tr><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 停止 : entry/\n - 停止処理\n - __M1へ"停止中"を送信__
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/\n - 開始処理\n - __M1へ"計時中"を送信__
計時 -r-> 停止 : ストップ
state 準備 : entry/\n - 準備処理\n - __M1へ"準備中"を送信__
準備 --> 計時 : スタート
}
@enduml
```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M1__ {
[*] --> 初期表示
state 初期表示 : □アイコン
初期表示 --> 点滅表示 : 計時中
state 結果表示 : 繰り返し表示
結果表示 -l-> 点滅表示 : 計時中
結果表示 -u-> 初期表示 : 準備中
state 点滅表示 : ♥点滅
点滅表示 -r-> 結果表示 : 停止中
}
@enduml
```

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

各ステートの `on entry` ブロック内に、 `fire` ブロックを追加します。

| 追加処理 | ブロック |
|---------|---------|
| M1へ"準備中"を送信 | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/243234bb-b736-a2a5-3bb8-81f2cff1926f.png) |
| M1へ"計時中"を送信 | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/058293ed-9a53-fedf-9dd6-93860f6bb0fd.png) |
| M1へ"停止中"を送信 | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/94a2ef0b-917d-1b19-3bc9-1ffaf1bc8763.png) |

## 動作確認と問題点

これでストップウォッチが完成しましたので、シミュレーター上で、動作確認をしてください。

概ねステート図で考えた通りに動作したかと思いますが、一つ問題点があります。
それは、結果表示がスクロールされている最中に、スタートしたり、リセットしたりしても、即座に点滅表示や初期表示に切り替わらないことです。
これは、 `on do` ブロック内で、 `表示を消す` と `数を表示` とが **順次処理** されているためです。 `数を表示` ブロックで、スクロール表示が終わるまで、この **順次処理** が完了せず(つまり、 **doアクテビティ** が完了せず)、 **トリガー** が発生しても即座に **状態遷移** が行われない為です（遅れて行われます）。このスクロール表示を中断させる必要があります。

|#|トリガー|処理|補足|
|---|------|----|----|
|1|停止中| | |
|2|     |点滅表示->結果表示|状態遷移|
|3|     |表示時間の計算|entryアクション|
|4|     |表示時間の表示|doアクテビティの開始|
|5|**準備中**|(表示時間の表示) | **トリガー保留** |
|6|     |表示時間の表示|doアクテビティの完了|
|7|**(準備中)**|| **保留されていたトリガー** |
|7|     |結果表示->初期表示|**状態遷移（遅れ）**|

このスクロール表示を止めるには、 `アニメーションを停止する` ブロックを実行する必要があります。
さて、どこで実行すればよいのでしょうか。まず、思い浮かぶのは、M1へ **"準備中"トリガー** や **"計時中"トリガー** を送信しているあたりです。

試しに、 **"準備"ステート** の **entryアクション** で、 `fire` ブロックに続けて、 `アニメーションを停止する` ブロックを配置してみてください。シミュレーターで動作確認すると、リセットで即座に初期表示になることを確認できたかと思います。これは期待する動作です。

しかし、これには設計思想上の問題点があります。これまで、 **時間を計測する機能** と **時間等を表示する機能** とを意図的に分離して設計してきました。これにより、**時間を計測する機能** は、 **時間等を表示する機能** に依存していません（その逆は **"経過時間ミリ秒"変数** で、依存しています）。
**時間を計測する機能** である **ステートマシン"M0"** に表示を直接制御する `アニメーションを停止する`　ブロックを配置したくありません。
また、 **"準備"ステート** の **entryアクション** だけでなく、 **"計時"ステート** の **entryアクション** にも `アニメーションを停止する`ブロックを配置しなければなりません。

それでは、どのようにステート図を改良すれば、よいのでしょうか。

それは、 **"結果表示"ステート** でスクロール表示するのではなく、さらに、別の **ステートマシン"M2"** で、スクロール表示をするようにします（**結果表示機能**）。

<table>
<tr><th>時間を計測する機能</th><th>時間等を表示する機能</th><th>結果表示機能</th></tr>
<tr><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 停止 : entry/\n - 停止処理\n - __M1へ"停止中"を送信__
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/\n - 開始処理\n - __M1へ"計時中"を送信__
計時 -r-> 停止 : ストップ
state 準備 : entry/\n - 準備処理\n - __M1へ"準備中"を送信__
準備 --> 計時 : スタート
}
@enduml
```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M1__ {
[*] --> 初期表示
state 結果表示 : entry/\n - 表示時間秒の計算\n - __M2の開始__\nexit/\n - __M2へ"終了"を送信__\n - アニメーションの停止
結果表示 -l-> 点滅表示 : 計時中
結果表示 -u-> 初期表示 : 準備中
state 初期表示 : □アイコン
初期表示 --> 点滅表示 : 計時中
state 点滅表示 : ♥点滅
点滅表示 -r-> 結果表示 : 停止中
}
@enduml
```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M2__ {
[*] --> 結果表示
state 結果表示 : 繰り返し表示
結果表示 --> [*] : 終了
}
@enduml
```

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

:::note alert
**Mstate拡張機能の制限事項**
一般的には、ステートの中にサブステートを持つことができますが、Mstate拡張機能では、サブステートを定義・宣言することができません。
:::

**ブロック (M1)**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/ffc86db8-d662-42a6-8896-e66185b20948.png)

**ブロック (M2)**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/7c40c286-432a-217f-c3d5-59167f526c51.png)

:::note info
一般的な解決策 - 非同期処理によるスクロール表示
ここでは、スクロール表示を停止するために、　`アニメーションを停止する`ブロックを使いました。`数を表示`ブロックが、同期処理として実行できるようにしているためです（表示が完了するまで待つか停止されるまで待つ）。
このブロックを非同期処理として実行できるようにすれば、わざわざ、ステートマシンを追加する必要はありません。ユーザー定義の拡張機能として非同期処理のブロックを開発することで実現可能です。
:::

# 「スタートシグナル機能」の追加

ここまでで作成したストップウォッチに **「スタートシグナル機能」** を追加してみましょう。
**"準備”ステート** で、スタートしたときに、即座に **"計時”ステート** で時間の計測を開始するのではなく、カメラシャッターのタイマー機能のように、"5"、"4"、"3"、"2"、"1"、"0"とカウントダウン表示し、"0"が表示されたタイミングで、時間の計測を開始するようにします。

**ステート図のヒント**

1. カウントダウンの減算処理は、 **doアクテビティ** で繰り返し行います。
1. カウントダウンの値が0になったかどうかの判断は、 **トランジション** の **ガード** を使います。
1. カウントダウンの値が変化する毎に **トリガー** を発生させ、 **ステートマシン"M1"** が状態遷移する度に、表示を更新します。

## カウントダウン（「時間を計測する機能」）

**ステートマシン"M0"** に **"秒読み"ステート** を挿入します。
**"カウントダウン"変数** を5から0へと1秒毎に減算し、**"カウントダウン"変数** の値が0になったら、状態遷移するようにします。

**ステート図**

```plantuml

@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 停止 : entry/\n - 停止処理\n - __M1へ"停止中"を送信__
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/\n - 開始処理\n - __M1へ"計時中"を送信__
計時 -r-> 停止 : ストップ
state 秒読み : entry/\n - カウントダウン=5\ndo[1s]/\n - カウントダウンの減算
秒読み --> 計時 : [カウントダウン=0]
state 準備 : entry/\n - 準備処理\n - __M1へ"準備中"を送信__
準備 --> 秒読み : スタート
}
@enduml

```

**記号の意味**

|項目|記号|説明|備考|
|----|----|----|----|
|（トリガーの省略）|矢印の記述省略|矢印の記述を省略をするとトリガーではなく、doアクテビティの完了で、状態遷移します。| Completion Transition |
|ガード|矢印の[]記述|トリガーの発生とガードの条件を満たしていれば、状態遷移します。| |

この **ステート図** には **ガード** がありますので、 **トランジション** のプログラミングが少し複雑になります。
**"カウントダウン"変数** を追加し、 `on entry` ブロック内で、開始値である`5`を代入します。
`on do` ブロックは、 **反復処理** としても機能しますので、その繰り返し間隔を `1000ms (1秒)` にし、そのブロック内で、 **"カウントダウン"変数** の値を１ずつ減算します。
`transition` ブロックを配置し、その中に、 **ガード** となる **分岐処理** をプログラミングします。 **"カウントダウン"変数** の値が 0 (以下)となった場合、 `transit` ブロックで、状態遷移させます。

**ブロック**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/93c58b4b-9ea3-a3c7-6470-0cab9b4935a0.png)

:::note info
**`transition` ブロックと `transit` ブロック**
ある **ステート** において、１つの **トリガー** で、１つ以上の（複数の） **トランジション** （複数の遷移先）を宣言できますが、どの **ステート** へ状態遷移するのかは、その **ガード** で最終的に決定します。
**ガード** の条件を満たしているかどうかを **分岐処理** し、遷移先である **ステート** 配列の添え字（0から始まるインデックス番号）を指定した `transit` ブロックを実行します。
尚、`transit` ブロックが実行されない場合は、どの **ガード** の条件も満たしていないということで、状態遷移されません。
:::

まだ、カウントダウンの表示ができていませんが、シミュレーターで動作を確認すると、ボタンAでスタートしてから、 **概ね5秒後** にハートマークの点滅がはじまるかと思います。

## カウントダウン（「時間等を表示する機能」）
**"秒読み"ステート** で、カウントダウンできるようになりましたので、カウントダウンしている様子を表示する機能を追加します。

**ステートマシン"M1"** に **"秒読み表示"ステート** を挿入し、 **"秒読み更新"トリガー** で状態遷移し、カウントダウンの値を表示します。
**"秒読み表示"ステート** でも、 **"秒読み更新"トリガー** が発生したら、 **"秒読み表示"ステート** 自身へ状態遷移し、カウントダウンの値を表示（更新）します。

**ステート図**

<table>
<tr><th>時間を計測する機能</th><th>時間等を表示する機能</th><th>結果表示機能</th></tr>
<tr><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 停止 : entry/\n - 停止処理\n - __M1へ"停止中"を送信__
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 計時 : entry/\n - 開始処理\n - __M1へ"計時中"を送信__
計時 -r-> 停止 : ストップ
state 秒読み : entry/\n - カウントダウン=5\ndo[1s]/\n - カウントダウンの減算\n - __M1へ"秒読み更新"を送信__
秒読み --> 計時 : [カウントダウン=0]
state 準備 : entry/\n - 準備処理\n - __M1へ"準備中"を送信__
準備 --> 秒読み : スタート
}
@enduml
```

</td><td>

```plantuml

@startuml
' http://www.plantuml.com/plantuml/
state __M1__ {
[*] --> 初期表示
state 結果表示 : entry/\n - 表示時間秒の計算\n - __M2の開始__\nexit/\n - __M2へ"終了"を送信__\n - アニメーションの停止
結果表示 -l-> 点滅表示 : 計時中
結果表示 -u-> 初期表示 : 準備中
state 初期表示 : □アイコン
初期表示 --> 秒読み表示 : 秒読み更新
state 点滅表示 : ♥点滅
点滅表示 -r-> 結果表示 : 停止中
state 秒読み表示 : entry/\n - カウントダウン表示
秒読み表示 -u-> 秒読み表示 : 秒読み更新
秒読み表示 --> 点滅表示 : 計時中
}
@enduml

```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M2__ {
[*] --> 結果表示
state 結果表示 : 繰り返し表示
結果表示 --> [*] : 終了
}
@enduml
```

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

このように、 **"do"アクティビィ** で繰り返し表示を更新するのではなく、 **"entry"アクション** で表示するようにしているのは、そのタイミングを明示的に **ステートマシン"M0"** が制御し、表示（更新）の実行を最小限にしています。 **"カウントダウン"変数** の値が変更された時だけ、表示を更新すればよいのです。

**ブロック（M0とM1）**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/d8e8fe47-7cc6-aef2-5e59-67031bc424d9.png)

さぁ、完成しましたが、シミュレーターで動作を確認すれば、不具合に気が付いたと思います。カウントダウンの値が、"4"から始まってしまうようです。

**"カウントダウン"変数**の初期化で、その値を `5` にしていましたが、処理の流れとして、その直後に減算してから、値を表示しています。初期化での変数の値を `6` にする必要があります。

今度こそ、スタートシグナル付ストップウォッチが完成しました。

# 実機での動作確認

micro:bit本体にHEXファイルを転送し、実機で動作確認をしてください。

**実は、実機で動作しないのです。**
原因は不明ですが、 **Mstate拡張機能** に不具合があるようです。その為、LEDの表示が行われず、プログラムが実行されているのかどうかもわかりません。

:::note warn
**この不具合は、 Version 0.6.1 で修正されました。**
これは、Version 0.6.0 の HEXファイルを使用した場合に再現する不具合です。原因は、ステート等の名称に漢字を使っており、シミュレーターのJavaScriptは漢字に対応していますが、micro:bit本体で動作するHEXファイルのコンパイラが漢字に対応していない為のようです。
Hex 0.6.1 以降のHEXファイルを使用してください。
https://github.com/jp-rad/pxt-mstate/releases
:::

この **不具合に対する回避策** は、次のように **"準備待ち"ステート** や **"結果待ち"ステート** を **デフォルト・ステート** として追加することです。

**ステート図**

<table>
<tr><th>時間を計測する機能</th><th>時間等を表示する機能</th><th>結果表示機能</th></tr>
<tr><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M0__ {
[*] --> 準備
state 計時 : entry/\n - 開始処理\n - __M1へ"計時中"を送信__
計時 -r-> 停止 : ストップ
state 準備 : entry/\n - 準備処理\n - __M1へ"準備中"を送信__
準備 --> 秒読み : スタート
state 停止 : entry/\n - 停止処理\n - __M1へ"停止中"を送信__
停止 -u-> 準備 : リセット
停止 -l-> 計時 : スタート
state 秒読み : entry/\n - カウントダウン=6\ndo[1s]/\n - カウントダウンの減算\n - __M1へ"秒読み更新"を送信__
秒読み --> 計時 : [カウントダウン=0]
}
@enduml
```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M1__ {
[*] --> 準備待ち
state 初期表示 : □アイコン
初期表示 --> 秒読み表示 : 秒読み更新
state 準備待ち
準備待ち --> 初期表示 : 準備中
state 点滅表示 : ♥点滅
点滅表示 -r-> 結果表示 : 停止中
state 結果表示 : entry/\n - 表示時間秒の計算\n - __M2へ"結果表示"を送信__\nexit/\n - __M2へ"結果停止"を送信__\n - アニメーションの停止
結果表示 -l-> 点滅表示 : 計時中
結果表示 -u-> 初期表示 : 準備中
state 秒読み表示 : entry/\n - カウントダウン表示
秒読み表示 --> 秒読み表示 : 秒読み更新
秒読み表示 --> 点滅表示 : 計時中
}
@enduml
```

</td><td>

```plantuml
@startuml
' http://www.plantuml.com/plantuml/
state __M2__ {
[*] --> 結果待ち
state 結果待ち
結果待ち --> 結果表示 : 結果表示
state 結果表示 : 繰り返し表示
結果表示 --> 結果待ち : 結果停止
}
@enduml
```

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

<details><summary>JavaScript</summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "計時:entry/\\n - 開始処理\\n - __M1へ\"計時中\"を送信__", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        スタート稼働時間ミリ秒 = control.millis()
        mstate.fire(StateMachines.M1, "計時中", [])
    })
    mstate.declareSimpleTransition(machine, state, "ストップ", "停止")
})
mstate.defineState(StateMachines.M2, "結果待ち", function (machine, state) {
    mstate.declareSimpleTransition(machine, state, "結果表示", "結果表示")
})
mstate.defineState(StateMachines.M1, "初期表示:□アイコン", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        basic.showIcon(IconNames.Square)
    })
    mstate.declareSimpleTransition(machine, state, "秒読み更新", "秒読み表示")
})
input.onButtonPressed(Button.A, function () {
    mstate.fire(StateMachines.M0, "スタート", [])
})
mstate.defineState(StateMachines.M1, "準備待ち", function (machine, state) {
    mstate.declareSimpleTransition(machine, state, "準備中", "初期表示")
})
mstate.defineState(StateMachines.M0, "準備:entry/\\n - 準備処理\\n - __M1へ\"準備中\"を送信__", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        経過時間ミリ秒 = 0
        mstate.fire(StateMachines.M1, "準備中", [])
    })
    mstate.declareSimpleTransition(machine, state, "スタート", "秒読み")
})
mstate.defineState(StateMachines.M0, "停止:entry/\\n - 停止処理\\n - __M1へ\"停止中\"を送信__", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        ストップ稼働時間ミリ秒 = control.millis()
        計測時間ミリ秒 = ストップ稼働時間ミリ秒 - スタート稼働時間ミリ秒
        経過時間ミリ秒 = 経過時間ミリ秒 + 計測時間ミリ秒
        mstate.fire(StateMachines.M1, "停止中", [])
    })
    mstate.declareSimpleTransition(machine, state, "リセット", "準備")
    mstate.declareSimpleTransition(machine, state, "スタート", "計時")
})
mstate.defineState(StateMachines.M0, "秒読み:entry/\\n - カウントダウン=6\\ndo[1s]/\\n - カウントダウンの減算\\n - __M1へ\"秒読み更新\"を送信__", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        カウントダウン = 6
    })
    mstate.declareDo(machine, state, 1000, function () {
        カウントダウン += -1
        mstate.fire(StateMachines.M1, "秒読み更新", [])
    })
    mstate.declareCustomTransition(machine, state, "", ["計時:カウントダウン=0"], function (args) {
        if (0 >= カウントダウン) {
            mstate.transitTo(machine, 0)
        }
    })
})
mstate.defineState(StateMachines.M1, "点滅表示:♥点滅", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        led.setBrightness(255)
        basic.showIcon(IconNames.Heart)
    })
    mstate.declareDo(machine, state, 500, function () {
        if (255 == led.brightness()) {
            led.setBrightness(100)
        } else {
            led.setBrightness(255)
        }
    })
    mstate.declareExit(machine, state, function () {
        led.setBrightness(255)
    })
    mstate.declareSimpleTransition(machine, state, "停止中", "結果表示")
})
mstate.defineState(StateMachines.M2, "結果表示:繰り返し表示", function (machine, state) {
    mstate.declareDo(machine, state, 5000, function () {
        basic.clearScreen()
        basic.showNumber(表示時間秒)
    })
    mstate.declareSimpleTransition(machine, state, "結果停止", "結果待ち")
})
mstate.defineState(StateMachines.M1, "結果表示:entry/\\n - 表示時間秒の計算\\n - __M2へ\"結果表示\"を送信__\\nexit/\\n - __M2へ\"結果停止\"を送信__\\n - アニメーションの停止", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        表示時間秒 = 経過時間ミリ秒 / 1000
        mstate.fire(StateMachines.M2, "結果表示", [])
    })
    mstate.declareExit(machine, state, function () {
        mstate.fire(StateMachines.M2, "結果停止", [])
        led.stopAnimation()
    })
    mstate.declareSimpleTransition(machine, state, "計時中", "点滅表示")
    mstate.declareSimpleTransition(machine, state, "準備中", "初期表示")
})
input.onButtonPressed(Button.AB, function () {
    mstate.fire(StateMachines.M0, "リセット", [])
})
input.onButtonPressed(Button.B, function () {
    mstate.fire(StateMachines.M0, "ストップ", [])
})
mstate.defineState(StateMachines.M1, "秒読み表示:entry/\\n - カウントダウン表示", function (machine, state) {
    mstate.declareEntry(machine, state, function () {
        basic.showNumber(カウントダウン)
    })
    mstate.declareSimpleTransition(machine, state, "秒読み更新", "秒読み表示")
    mstate.declareSimpleTransition(machine, state, "計時中", "点滅表示")
})
let 表示時間秒 = 0
let カウントダウン = 0
let 計測時間ミリ秒 = 0
let ストップ稼働時間ミリ秒 = 0
let 経過時間ミリ秒 = 0
let スタート稼働時間ミリ秒 = 0
mstate.exportUml(StateMachines.M0, "準備")
mstate.exportUml(StateMachines.M1, "準備待ち")
mstate.exportUml(StateMachines.M2, "結果待ち")
mstate.start(StateMachines.M2, "結果待ち")
mstate.start(StateMachines.M1, "準備待ち")
mstate.start(StateMachines.M0, "準備")
```

</details>

https://makecode.microbit.org/_9JXgF0cYchRr

この **回避策** は試行錯誤の上、偶然に見つけたものです。全てのステートマシンが開始されるまで、LEDの表示を行ってはいけないようです。
**この不具合の原因は不明** のため、 **解決策** ではなく、あくまでも **回避策** です。

:::note warn
**この不具合は、 Version 0.6.1 で修正されました。**
これは、Version 0.6.0 の HEXファイルを使用した場合に再現する不具合です。原因は、ステート等の名称に漢字を使っており、シミュレーターのJavaScriptは漢字に対応していますが、micro:bit本体で動作するHEXファイルのコンパイラが漢字に対応していない為のようです。
Hex 0.6.1 以降のHEXファイルを使用してください。
https://github.com/jp-rad/pxt-mstate/releases
:::

# 今後の課題 - 実際に使って改善する

今後の課題として、スポーツ競技等の現場で実際に使ってみて、その使いやすさを検証し、改善してみましょう。
そして、自分の考えを整理し、よりよい発想を生み出してみましょう。

**考えや発想のメモ**

1.
1.
1.
1.
1.

https://makecode.microbit.org/_CfkdwPURE4sv