Car Infotainment¶
Covers:
Finite State Machines
Nested State Machines
This demo simulates the behavior of a (extremely simplified) car infotainment system.
The main state is simulated with a Moddy Finite state machine. (off, booting, normal_op etc).
The normal state has several nested sub-state machines, such as:
‘Apps’ (Radio, Navi) - jumps between the different applications (in this simulation, the apps have no function)
‘Volume’ - manages the audio volume
The Stim part simulates user events.
"""
Simulate the behavior of a (extremely simplified) car infotainment system.
The main state is simulated with a Moddy Finite state machine.
(off, booting, normal   op etc).
The normal state has several nested sub-state machines, such as
 'Apps' (Radio, Navi) - jumps between the different applications
   (in this simulation, the apps have no function)
 'Volume' - manages the audio volume
The Stim part simulates user events.
@author: klauspopp@gmx.de
"""
import moddy
class CarInfoSystem(moddy.SimFsmPart):
    """ Simulation of Car infotainment """
    def __init__(self, sim, obj_name):
        status_box_repr_map = {
            "off": (None, moddy.BC_BLACK_ON_WHITE),
            "standby": ("SBY", moddy.BC_WHITE_ON_RED),
            "booting": ("BOOT", moddy.BC_WHITE_ON_BLUE),
            "normal_op": ("NORM", moddy.BC_WHITE_ON_GREEN),
            "shutdown": ("SD", moddy.BC_WHITE_ON_RED),
        }
        super().__init__(
            sim=sim,
            obj_name=obj_name,
            fsm=self.FSM(),
            status_box_repr_map=status_box_repr_map,
        )
        # Ports & Timers
        self.create_ports("in", ["power_port", "ignition_port", "button_port"])
        self.create_ports("out", ["audio_port", "visual_port"])
        self.create_timers(["boot_tmr", "shutdown_tmr", "clock_tmr"])
    class FSM(moddy.Fsm):
        """ State machine of car infotainment """
        def __init__(self):
            transitions = {
                "": [("INITIAL", "off")],  # FSM uninitialized
                "off": [("PowerApplied", "standby")],
                # This transition is triggered whenever ANY message arrives
                # on the powerButtonPort
                "standby": [
                    ("PowerButton", "booting"),
                    ("IgnitionOn", "booting"),
                ],
                "booting": [("boot_tmr_expired", "normal_op")],
                "normal_op":
                # The following two lines specify nested state machines,
                # executing in parallel
                [
                    ("Apps", CarInfoSystem.FSM.ApplicationsFsm),
                    ("Vol", CarInfoSystem.FSM.VolumeFsm),
                    # This transition is triggered whenever ANY message
                    # arrives on the powerButtonPort
                    ("PowerButton", "shutdown"),
                    ("IgnitionOff", "shutdown"),
                    # This transition is triggered whenever clockTmr expires,
                    # transition to self,
                    # executes the 'Do' methode
                    ("clock_tmr_expired", "normal_op"),
                ],
                "shutdown": [("shutdown_tmr_expired", "standby")],
                "any": [("PowerRemoved", "off")],
            }
            super().__init__(dict_transitions=transitions)
        # off actions
        def state_off_entry(self):
            print("state_off_entry")
            self.moddy_part().boot_tmr.stop()
            self.moddy_part().shutdown_tmr.stop()
            self.moddy_part().clock_tmr.stop()
        # booting actions
        def state_booting_entry(self):
            print("booting_entry")
            self.moddy_part().boot_tmr.start(5)
        # shutdown actions
        def state_shutdown_entry(self):
            self.moddy_part().shutdown_tmr.start(2)
            self.moddy_part().clock_tmr.stop()
        # Cursor Blink in normal_op state
        def state_normal_op_entry(self):
            self._clockTime = 100
        def state_normal_op_do(self):
            self.moddy_part().clock_tmr.start(5)
            self.moddy_part().visual_port.send(
                "time %d" % self._clockTime, 0.1
            )
            self._clockTime += 5
        # Message handlers
        def state_any_power_port_msg(self, msg):
            if msg == "on":
                self.event("PowerApplied")
            elif msg == "off":
                self.event("PowerRemoved")
        def state_any_ignition_port_msg(self, msg):
            if msg == "on":
                self.event("IgnitionOn")
            elif msg == "off":
                self.event("IgnitionOff")
        def state_any_button_port_msg(self, msg):
            self.event(msg)  # Message are directly the event names
        # Nested state machine CarInfo System Applications
        class ApplicationsFsm(moddy.Fsm):
            def __init__(self, parentFsm):
                transitions = {
                    "": [("INITIAL", "radio")],
                    "radio": [("NaviButton", "navi")],
                    "navi": [("RadioButton", "radio")],
                }
                super().__init__(
                    dict_transitions=transitions, parent_fsm=parentFsm
                )
            def state_radio_entry(self):
                self.moddy_part().annotation("Radio activated")
            def state_navi_entry(self):
                self.moddy_part().annotation("Navi activated")
        class VolumeFsm(moddy.Fsm):
            def __init__(self, parentFsm):
                self._volume = 50
                transitions = {
                    "": [("INITIAL", "on")],
                    "on": [
                        ("MuteButton", "mute"),
                        ("VolKnobRight", "incvol"),
                        ("VolKnobLeft", "decvol"),
                    ],
                    "incvol": [("VolChangeDone", "on")],
                    "decvol": [("VolChangeDone", "on")],
                    "mute": [("MuteButton", "on"), ("VolKnobRight", "on")],
                }
                super().__init__(
                    dict_transitions=transitions, parent_fsm=parentFsm
                )
            def state_on_do(self):
                self.moddy_part().audio_port.send(
                    "volume=%d" % self._volume, 0.1
                )
            def state_mute_do(self):
                self.moddy_part().audio_port.send("volume=%d" % 0, 0.1)
            def state_incvol_entry(self):
                self._volume += 1
                self.top_fsm().event("VolChangeDone")
            def state_decvol_entry(self):
                self._volume -= 1
                self.top_fsm().event("VolChangeDone")
def stimProg(self):
    self.ignition_port.set_color("red")
    self.button_port.set_color("blue")
    while True:
        self.power_port.send("on", 1)
        self.wait(2)
        self.button_port.send("PowerButton", 1)
        self.wait(8)
        self.button_port.send("NaviButton", 0.5)
        self.wait(2)
        self.button_port.send("VolKnobRight", 0.5)
        self.button_port.send("VolKnobRight", 0.5)
        self.wait(1)
        self.button_port.send("VolKnobLeft", 0.5)
        self.wait(1)
        self.button_port.send("MuteButton", 0.5)
        self.wait(1)
        self.button_port.send("VolKnobRight", 0.5)
        self.wait(5)
        self.ignition_port.send("off", 1)
        self.wait(4)
        self.power_port.send("off", 1)
        self.wait(None)
if __name__ == "__main__":
    SIMU = moddy.Sim()
    CIS = CarInfoSystem(SIMU, "CarInfoSys")
    STIM = moddy.VSimpleProg(
        sim=SIMU,
        obj_name="Stim",
        target=stimProg,
        elems={
            "out": ["power_port", "ignition_port", "button_port"],
            "SamplingIn": ["audio_port", "visual_port"],
        },
    )
    # bind ports
    SIMU.smart_bind(
        [
            ["Stim.power_port", "CarInfoSys.power_port"],
            ["Stim.ignition_port", "CarInfoSys.ignition_port"],
            ["Stim.button_port", "CarInfoSys.button_port"],
            ["Stim.visual_port", "CarInfoSys.visual_port"],
            ["Stim.audio_port", "CarInfoSys.audio_port"],
        ]
    )
    moddy.gen_fsm_graph(
        fsm=CIS.fsm, file_name="output/3_carinfo_fsm.svg", keep_gv_file=True
    )
    SIMU.run(30)
    moddy.gen_interactive_sequence_diagram(
        sim=SIMU,
        file_name="output/3_carinfo.html",
        show_parts_list=[STIM, CIS],
        time_per_div=0.3,
        pix_per_div=30,
        title="Car Info FSM Demo",
    )
The simulation outputs: