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: