How to code state machines more simply using mstate extension with micro:bit

# Simple State Machine

The following video illustrates an example of coding a state machine on the BBC micro:bit. This is a rare and valuable video explaining state machine coding on the micro:bit.

https://youtu.be/L4VGmE2nhu4

<details><summary><b>Source Code (JavaScript)</b></summary>

```js:JavaScript
let pumpStopTime = 0
let pumpStartTime = 0
let PUMP_ON_TIME = 5000
let PUMP_OFF_TIME = 3000
let pumpState = 0
pins.digitalWritePin(DigitalPin.P1, 0)
basic.forever(function () {
    if (pumpState == 0) {
        if (input.buttonIsPressed(Button.A)) {
            pumpState = 1
            basic.showNumber(1)
            pumpStartTime = input.runningTime()
            pins.digitalWritePin(DigitalPin.P1, 1)
        }
    }
    if (pumpState == 1) {
        if (input.runningTime() > pumpStartTime + PUMP_ON_TIME) {
            basic.showNumber(2)
            pumpState = 2
            pins.digitalWritePin(DigitalPin.P1, 0)
            pumpStopTime = input.runningTime()
        }
    }
    if (pumpState == 2) {
        if (input.runningTime() > pumpStopTime + PUMP_OFF_TIME) {
            basic.showNumber(3)
            pumpState = 3
        }
    }
    if (pumpState == 3) {
        basic.showString("Cycle Complete")
        pumpState = 0
    }
    if (pumpState == 4) {
        basic.showString("Water Empty")
        if (!(input.buttonIsPressed(Button.B))) {
            pumpState = 0
            basic.showIcon(IconNames.Yes)
        }
    }
})
basic.forever(function () {
    if (input.buttonIsPressed(Button.B)) {
        if (pumpState != 4) {
            pumpState = 4
            basic.showNumber(4)
            pins.digitalWritePin(DigitalPin.P1, 0)
        }
    }
})

```

</details>

https://makecode.microbit.org/_W2aFEyYKXcUy


# Using mstate Extension

Typically, the `forever` and `if` constructs are utilized in coding state machines. Libraries, such as the `mstate` extension, simplify the coding process and enhance comprehensibility.

https://jp-rad.github.io/pxt-mstate/

:::note info
This article uses pxt-mstate ver. 0.10.0.
https://github.com/jp-rad/pxt-mstate/releases/tag/0.10.0
:::


# Step1 - Normal Operation

In normal operation, pumpState transitions from 0,1,2,3,0.
The mstate extension allows you to define the state as follows:

**Blocks**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/1feedc5e-dd1f-ef5c-ef3a-b1b30afc3098.png)

<details><summary><b>Source Code (JavaScript)</b></summary>

```js: JavaScript
mstate.defineState(StateMachines.M0, "s3", function () {
    mstate.onState(0, function (tickcount) {
        basic.showString("Cycle Complete")
    })
    mstate.onTrigger("", ["s0"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.defineState(StateMachines.M0, "s1", function () {
    mstate.onState(5000, function (tickcount) {
        timeoutedCount = tickcount
    })
    mstate.onTrigger("", ["s2"], function () {
        if (0 < timeoutedCount) {
            basic.showNumber(2)
            mstate.traverse(StateMachines.M0, 0)
            pins.digitalWritePin(DigitalPin.P1, 0)
        }
    })
})
mstate.defineState(StateMachines.M0, "s2", function () {
    mstate.onState(3000, function (tickcount) {
        timeoutedCount = tickcount
    })
    mstate.onTrigger("", ["s3"], function () {
        if (0 < timeoutedCount) {
            basic.showNumber(3)
            mstate.traverse(StateMachines.M0, 0)
        }
    })
})
mstate.defineState(StateMachines.M0, "s0", function () {
    mstate.onTrigger("e", ["s1"], function () {
        mstate.traverse(StateMachines.M0, 0)
        basic.showNumber(1)
        pins.digitalWritePin(DigitalPin.P1, 1)
    })
})
let timeoutedCount = 0
mstate.start(StateMachines.M0, "s0")

```

</details>

# Step2 - Exception Handling

If the water is empty (pumpState is 4), the pump must be stopped.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/e0a7d9c1-8688-244c-c1f7-99a69669afff.png)

If the water volume sensor detects empty when the state is not 4, the system should transition to state 4.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/dc47185a-f56a-8cfd-bb15-31aef1dc2f6f.png)

<details><summary><b>Source Code (JavaScript)</b></summary>

```js: JavaScript
mstate.defineState(StateMachines.M0, "s3", function () {
    mstate.onState(0, function (tickcount) {
        basic.showString("Cycle Complete")
    })
    mstate.onTrigger("", ["s0"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.defineState(StateMachines.M0, "s1", function () {
    mstate.onState(5000, function (tickcount) {
        timeoutedCount = tickcount
    })
    mstate.onTrigger("", ["s2"], function () {
        if (0 < timeoutedCount) {
            basic.showNumber(2)
            mstate.traverse(StateMachines.M0, 0)
            pins.digitalWritePin(DigitalPin.P1, 0)
        }
    })
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "s2", function () {
    mstate.onState(3000, function (tickcount) {
        timeoutedCount = tickcount
    })
    mstate.onTrigger("", ["s3"], function () {
        if (0 < timeoutedCount) {
            basic.showNumber(3)
            mstate.traverse(StateMachines.M0, 0)
        }
    })
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "s0", function () {
    mstate.onTrigger("e", ["s1"], function () {
        mstate.traverse(StateMachines.M0, 0)
        basic.showNumber(1)
        pins.digitalWritePin(DigitalPin.P1, 1)
    })
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "s4", function () {
    mstate.onState(100, function (tickcount) {
        if (0 == tickcount) {
            pins.digitalWritePin(DigitalPin.P1, 0)
        }
        basic.showString("Water Empty")
    })
    mstate.onTrigger("", ["s0"], function () {
        if (!(input.buttonIsPressed(Button.B))) {
            mstate.traverse(StateMachines.M0, 0)
            basic.showIcon(IconNames.Yes)
        }
    })
})
let waterLevel = 0
let timeoutedCount = 0
mstate.start(StateMachines.M0, "s0")
basic.forever(function () {
    if (input.buttonIsPressed(Button.B)) {
        if (0 != waterLevel) {
            waterLevel = 0
            mstate.send(StateMachines.M0, "empty")
        }
    } else {
        waterLevel = 1
    }
})

```

</details>


# Refactoring

To refactor, output to a state diagram to see the overall structure.
The mstate extension has the ability to output state diagrams in PlantUML syntax.

## PlantUML

Place an `exportUML` block inside the `on start` block. The PlantUML syntax will be output to the simulator's log (`Show data Simulator`), which will be drawn by the PlantUML Web server.

[**PlantUML Web server:** https://www.plantuml.com/plantuml/](https://www.plantuml.com/plantuml/)

It is also possible to omit the arrow notation to `s4` with the `description` block. This way, the normal transitions of operations are easier to understand.

<table>
<tr><th>Blocks</th><th>PlantUML</th><th>(as description)</th></tr>
<tr><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/03438d9d-8099-5e2a-a420-f167570f9e18.png)

</td><td>

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

```
</td><td>

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

```

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

## Effect to Entry
The first step is to change each action performed as an effect to be performed as an entry.
In the mstate extension, to make it run as an entry, it evaluates whether the `tickcount` is `0`.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/1f278592-8be2-a615-63bc-2c2465c4b5e1.png)

## Sensor detection

The guard of state transition in s4 simulates the sensor directly, so this should be changed to be determined by `waterLevel`.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/e9c2ad4e-6b9d-e673-fefd-16ef43b6f542.png)

## Add detailed description

Added detailed explanations to make the state diagram easier to understand.


<table>
<tr><th>Blocks</th><th>PlantUML</th></tr>
<tr><td>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252891/690ad0ba-2ed4-5026-bb5e-b80511d3e1aa.png)

</td><td>

```plantuml
@startuml
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
' State Diagram - https://plantuml.com/state-diagram
state __M0__ {
[*] -> s0 : <<start>>
state s0 : waiting
s0 --> s1 : e
state s0 : --> s4 : empty
state s1 : entry/ moter on
s1 --> s2 : [ after 5s ]
state s1 : --> s4 : empty
state s2 : entry/ moter off
s2 --> s3 : [ after 3s ]
state s2 : --> s4 : empty
state s3 : entry/ "Cycle Complete"
s3 --> s0
state s3 : --> s4 : empty
state s4 : entry/ moter off\ndo/ "Water Empty"
s4 --> s0 : [ waterLevel <> 0 ] / show icon
}
'
' generator: https://github.com/jp-rad/pxt-mstate
'
' PlantUML Web server:
' https://www.plantuml.com/plantuml/
'
@enduml

```

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

<details><summary><b>Source Code (JavaScript)</b></summary>

```js:JavaScript
mstate.defineState(StateMachines.M0, "s3", function () {
    mstate.descriptionUml("entry/ \"Cycle Complete\"")
    mstate.onState(0, function (tickcount) {
        basic.showNumber(3)
        basic.showString("Cycle Complete")
    })
    mstate.onTrigger("", ["s0"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.descriptionUml(":")
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
input.onButtonPressed(Button.A, function () {
    mstate.send(StateMachines.M0, "e")
})
mstate.defineState(StateMachines.M0, "s1", function () {
    mstate.descriptionUml("entry/ moter on")
    mstate.onState(5000, function (tickcount) {
        if (0 == tickcount) {
            pins.digitalWritePin(DigitalPin.P1, 1)
            basic.showNumber(1)
        }
        timeoutedCount = tickcount
    })
    mstate.descriptionUml("after 5s")
    mstate.onTrigger("", ["s2"], function () {
        if (0 < timeoutedCount) {
            mstate.traverse(StateMachines.M0, 0)
        }
    })
    mstate.descriptionUml(":")
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "s2", function () {
    mstate.descriptionUml("entry/ moter off")
    mstate.onState(3000, function (tickcount) {
        if (0 == tickcount) {
            pins.digitalWritePin(DigitalPin.P1, 0)
            basic.showNumber(2)
        }
        timeoutedCount = tickcount
    })
    mstate.descriptionUml("after 3s")
    mstate.onTrigger("", ["s3"], function () {
        if (0 < timeoutedCount) {
            mstate.traverse(StateMachines.M0, 0)
        }
    })
    mstate.descriptionUml(":")
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
})
mstate.defineState(StateMachines.M0, "s0", function () {
    mstate.onTrigger("e", ["s1"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.descriptionUml(":")
    mstate.onTrigger("empty", ["s4"], function () {
        mstate.traverse(StateMachines.M0, 0)
    })
    mstate.descriptionUml("waiting")
})
mstate.defineState(StateMachines.M0, "s4", function () {
    mstate.descriptionUml("entry/ moter off")
    mstate.descriptionUml("do/ \"Water Empty\"")
    mstate.onState(100, function (tickcount) {
        if (0 == tickcount) {
            pins.digitalWritePin(DigitalPin.P1, 0)
        }
        basic.showString("Water Empty")
    })
    mstate.descriptionUml("waterLevel <> 0/show icon")
    mstate.onTrigger("", ["s0"], function () {
        if (0 != waterLevel) {
            mstate.traverse(StateMachines.M0, 0)
            basic.showIcon(IconNames.Yes)
        }
    })
})
let waterLevel = 0
let timeoutedCount = 0
mstate.start(StateMachines.M0, "s0")
mstate.exportUml(StateMachines.M0, "s0", ModeExportUML.StateDiagram)
basic.forever(function () {
    if (input.buttonIsPressed(Button.B)) {
        if (0 != waterLevel) {
            waterLevel = 0
            mstate.send(StateMachines.M0, "empty")
        }
    } else {
        waterLevel = 1
    }
})

```

</details>

https://makecode.microbit.org/_90RU1vRyL63V

# Conclusion

The state machines are useful if you can define a finite number of states and events that will trigger transitions to them. With the mstate extension, you can develop simpler and more robust projects.