【micro:bit編】状態遷移表からの実装

# 状態遷移表からpxt-mstateで実装

次の連載記事で、状態遷移表からの実装について、その実装方法の紹介と比較表が掲載されています。

https://monoist.itmedia.co.jp/mn/articles/1211/07/news003.html

- 基本仕様から直接実装
- 状態遷移表からイベントドリブン型で実装
- 状態遷移表からステートドリブン型で実装

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/4b9d011c-2a6c-eec4-7a4b-3f13645ba06b.png)
[引用: 状態遷移表からの実装(3/3) 今回のまとめ 表1](https://monoist.itmedia.co.jp/mn/articles/1211/07/news003_3.html) [(ay_jyotai05_hyo01.jpg)](https://image.itmedia.co.jp/mn/articles/1211/07/ay_jyotai05_hyo01.jpg)

:::note info
**引用（はじめに）**
組み込みソフトウェアが抱える一番の課題は「設計品質の向上」です。本連載の主役「状態遷移表」であれば、“イベント”と“状態”の全ての組み合わせを捉えることができるため、「モレ」「ヌケ」のない品質の良い設計が可能です。そして、不具合発生による手戻りコストの削減や開発効率の向上にも役立ちます。

　こうした理由から、組み込みソフトウェア開発の世界では、長年、状態遷移系モデルで設計が行われています。

 https://monoist.itmedia.co.jp/mn/articles/1211/07/news003.html
:::


そこで、micro:bitの [pxt-mstate拡張機能](https://github.com/jp-rad/pxt-mstate) を使って状態遷移表から実装した場合の「状態遷移表からpxt-mstate実装」を次の表にまとめました（実装例は、次の章を参照）。

|比較項目|状態遷移表からpxt-mstate実装|優位性|
|:------:|--------------------------|:---:|
|ループ構造| なし |〇|
|状態遷移構造| 宣言のみ（切り替え不要） |〇|
|イベント参照部分数| 5箇所 |-|
|イベント変更/追加| 3箇所検討する必要がある |-|
|状態変更/追加| 1箇所検討すればよい |-|
|順序性のない状態への遷移| 可能<br>※状態遷移表のとおり |〇|

:::note info
**pxt-mstate実装**
[Microsoft MakeCode for micro:bit](https://makecode.microbit.org/) の拡張機能である [pxt-mstate拡張機能](https://github.com/jp-rad/pxt-mstate)を使えば、状態遷移表を元にブロックを使った実装が可能です。
:::


# micro:bitでの実装例

micro:bitの [pxt-mstate拡張機能](https://github.com/jp-rad/pxt-mstate) を使って、状態遷移表のとおりに、実装してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/6862dbfd-58ff-06de-5345-b9fa9bfe70b4.png)
[引用：図5　状態遷移表（連載第3回「状態遷移表を使用した要求分析モデル」の最終成果物のモデルより）](https://monoist.itmedia.co.jp/mn/articles/1211/07/news003.html)

この実装例で、次の優位性を確認することができます。

- ループ構造が、なくなります。
- 状態遷移構造の切り替えが不要で、状態を宣言するだけです。
- 順序性のない状態への遷移も、状態遷移表のとおりに可能です。

**実装例（ブロック）**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/7571d4b0-52c1-1c81-e770-dd971ef60597.png)

<details><summary>ソースコード(JavaScript)</summary>

```js: JavaScript
function カウントダウン時間表示 (ミリ秒: number) {
    表示値秒 = Math.ceil(ミリ秒 / 1000)
    if (9 < 表示値秒) {
        basic.showLeds(`
            # . # # #
            # . # . #
            # . # . #
            # . # . #
            # . # # #
            `)
    } else if (0 < 表示値秒) {
        basic.showNumber(表示値秒)
    }
}
mstate.defineState(StateMachines.M1, "時間設定", function () {
    mstate.descriptionUml("- カウントダウン時間（設定）の表示")
    mstate.declareEntry(function () {
        if (0 >= タイマー設定値ミリ秒) {
            タイマー設定値ミリ秒 = 10000
        }
        カウントダウン時間表示(タイマー設定値ミリ秒)
    })
    mstate.descriptionUml("/カウントダウン時間設定")
    mstate.declareCustomTransition("時間設定ボタン", ["時間設定"], function () {
        ボタン番号 = mstate.getTriggerArgs(StateMachines.M1)[0]
        if (0 == ボタン番号) {
            タイマー設定値ミリ秒 = 0
        } else {
            タイマー設定値ミリ秒 += 1000
            タイマー設定値ミリ秒 = 1000 * Math.ceil(タイマー設定値ミリ秒 / 1000)
            if (10000 < タイマー設定値ミリ秒) {
                タイマー設定値ミリ秒 = 1000
            }
        }
        mstate.transitTo(StateMachines.M1, 0)
    })
    mstate.descriptionUml("/カウントダウン開始")
    mstate.declareCustomTransition("スタートストップボタン", ["カウントダウン"], function () {
        スタート稼働時間ミリ秒 = mstate.getTriggerArgs(StateMachines.M1)[0]
        mstate.transitTo(StateMachines.M1, 0)
    })
})
mstate.defineState(StateMachines.M1, "アラーム音出力", function () {
    mstate.descriptionUml("- ハートアイコン")
    mstate.declareEntry(function () {
        basic.showIcon(IconNames.Heart)
    })
    mstate.descriptionUml("/アラーム音停止")
    mstate.declareCustomTransition("スタートストップボタン", ["時間設定"], function () {
        music.stopAllSounds()
        mstate.transitTo(StateMachines.M1, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.sendTriggerArgs(StateMachines.M1, "時間設定ボタン", [1])
})
input.onButtonPressed(Button.AB, function () {
    mstate.sendTriggerArgs(StateMachines.M1, "時間設定ボタン", [0])
})
input.onButtonPressed(Button.B, function () {
    mstate.sendTriggerArgs(StateMachines.M1, "スタートストップボタン", [control.millis()])
})
mstate.defineState(StateMachines.M1, "カウントダウン", function () {
    mstate.descriptionsUml(["- カウントダウン", "- カウントダウン時間の表示"])
    mstate.declareDo(1000, function () {
        カウントダウン時間表示(タイマー設定値ミリ秒 - (control.millis() - スタート稼働時間ミリ秒))
    })
    mstate.descriptionUml("/カウントダウン停止")
    mstate.declareCustomTransition("スタートストップボタン", ["時間設定"], function () {
        タイマー設定値ミリ秒 = タイマー設定値ミリ秒 - (mstate.getTriggerArgs(StateMachines.M1)[0] - スタート稼働時間ミリ秒)
        if (0 < タイマー設定値ミリ秒) {
            mstate.transitTo(StateMachines.M1, 0)
        }
    })
    mstate.descriptionUml("カウントダウン終了/カウントダウン停止＆アラーム音出力")
    mstate.declareCustomTransition("", ["アラーム音出力"], function () {
        if (タイマー設定値ミリ秒 < control.millis() - スタート稼働時間ミリ秒) {
            タイマー設定値ミリ秒 = 0
            music._playDefaultBackground(music.builtInPlayableMelody(Melodies.PowerUp), music.PlaybackMode.LoopingInBackground)
            mstate.transitTo(StateMachines.M1, 0)
        }
    })
})
let スタート稼働時間ミリ秒 = 0
let ボタン番号 = 0
let タイマー設定値ミリ秒 = 0
let 表示値秒 = 0
mstate.settingsMachineEvent(StateMachines.M1, 32768, 20)
mstate.exportUml(StateMachines.M1, "時間設定")
mstate.start(StateMachines.M1, "時間設定")

```

</details>

**実装例（プロジェクト）**

https://makecode.microbit.org/_6MJ2oEHwvMjk

# おわりに

[「状態遷移表による設計手法（5） 状態遷移表からの実装」](https://monoist.itmedia.co.jp/mn/articles/1211/07/news003.html) を題材に、micro:bitで状態遷移表からの実装を試み、比較表にまとめ、優位性を確認しました。

[pxt-mstate拡張機能](https://github.com/jp-rad/pxt-mstate) を使うと、「ループ構造」、「状態遷移構造」、「順序性のない状態への遷移」の３つの比較項目において優位性があり、「設計品質の向上」とともに、実装がより容易になると思われます。

# おまけ

[pxt-mstate拡張機能](https://github.com/jp-rad/pxt-mstate) には、UMLの状態遷移図を出力する補助機能が備わっており、次のような出力が可能です。

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
state __M1__ {
[*] --> 時間設定
state 時間設定 : - カウントダウン時間（設定）の表示
時間設定 --> 時間設定 : 時間設定ボタン / カウントダウン時間設定
時間設定 --> カウントダウン : スタートストップボタン / カウントダウン開始
state アラーム音出力 : - ハートアイコン
アラーム音出力 --> 時間設定 : スタートストップボタン / アラーム音停止
state カウントダウン : - カウントダウン\n- カウントダウン時間の表示
カウントダウン --> 時間設定 : スタートストップボタン / カウントダウン停止
カウントダウン --> アラーム音出力 : [カウントダウン終了] / カウントダウン停止＆アラーム音出力
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml
```

**参考**

https://monoist.itmedia.co.jp/mn/series/615/