commit 8a24549cdb50ae28b5c000e2d0d20f624b7de179 Author: Joeri Exelmans Date: Thu Nov 28 23:47:19 2024 +0100 commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48713f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +StartingPoint/srcgen/solution.py +StartingPoint/Solution.ysc + +# python +__pycache__/ + +# eclipse thing +.metadata \ No newline at end of file diff --git a/StartingPoint/.project b/StartingPoint/.project new file mode 100644 index 0000000..88488b0 --- /dev/null +++ b/StartingPoint/.project @@ -0,0 +1,23 @@ + + + StartingPoint + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + com.yakindu.sct.builder.SCTBuilder + + + + + + org.eclipse.xtext.ui.shared.xtextNature + com.yakindu.sct.builder.SCTNature + + diff --git a/StartingPoint/.settings/org.eclipse.core.resources.prefs b/StartingPoint/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..dad872d --- /dev/null +++ b/StartingPoint/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/LockController.ysc=UTF-8 diff --git a/StartingPoint/CodeGen.sgen b/StartingPoint/CodeGen.sgen new file mode 100644 index 0000000..553d02b --- /dev/null +++ b/StartingPoint/CodeGen.sgen @@ -0,0 +1,20 @@ +GeneratorModel for yakindu::python { + + const PROJECT : string = "StartingPoint" + const FOLDER : string = "srcgen" + + statechart LockController { + feature Outlet { + targetProject = PROJECT + targetFolder = FOLDER + libraryTargetFolder = "lib" + } + } + statechart WaterLevelSimulator { + feature Outlet { + targetProject = PROJECT + targetFolder = FOLDER + libraryTargetFolder = "lib" + } + } +} \ No newline at end of file diff --git a/StartingPoint/LockController.ysc b/StartingPoint/LockController.ysc new file mode 100644 index 0000000..0fad802 --- /dev/null +++ b/StartingPoint/LockController.ysc @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StartingPoint/WaterLevelSimulator.ysc b/StartingPoint/WaterLevelSimulator.ysc new file mode 100644 index 0000000..7a9ac48 --- /dev/null +++ b/StartingPoint/WaterLevelSimulator.ysc @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StartingPoint/gui.py b/StartingPoint/gui.py new file mode 100644 index 0000000..e24bcd3 --- /dev/null +++ b/StartingPoint/gui.py @@ -0,0 +1,217 @@ +import tkinter +from lib.controller import pretty_time +import random + +WATER_COLOR = '#1c8ce8' +SKY_COLOR = '#ffc4d4' +DOOR_COLOR = '#633f09' + +TRAFFIC_LIGHT_OFF_COLOR = '#362d2d' +TRAFFIC_LIGHT_RED_COLOR = '#ff0000' +TRAFFIC_LIGHT_GREEN_COLOR = '#4be81c' + +DEFAULT_WIDGET_COLOR = '#d9d9d9' + +# Paints a single lock door onto canvas +class LockDoorView: + def __init__(self, canvas, x, scale=1): + self.canvas = canvas + self.door_id = canvas.create_rectangle((x-10)*scale, 70*scale, (x+10)*scale, 250*scale) + self.flow_id = canvas.create_rectangle((x-12)*scale, 220*scale, (x+12)*scale, 240*scale, outline='') + + canvas.create_oval((x-15)*scale, 10, (x+15)*scale, 54, fill='black') + self.red_light_id = canvas.create_oval((x-10)*scale, 12, (x+10)*scale, 32, outline='') + self.green_light_id = canvas.create_oval((x-10)*scale, 32, (x+10)*scale, 52, outline='') + + self.set_doors(open=False) + self.set_flow(open=False) + self.set_red_light() + + def set_doors(self, open): + if open: + self.canvas.itemconfig(self.door_id, fill='', outline='black', dash=(4,6)) + else: + self.canvas.itemconfig(self.door_id, fill=DOOR_COLOR, outline='', dash=None) + + def set_flow(self, open): + if open: + self.canvas.itemconfig(self.flow_id, fill=WATER_COLOR) + else: + self.canvas.itemconfig(self.flow_id, fill='') + + def set_green_light(self): + self.canvas.itemconfig(self.red_light_id, fill=TRAFFIC_LIGHT_OFF_COLOR) + self.canvas.itemconfig(self.green_light_id, fill=TRAFFIC_LIGHT_GREEN_COLOR) + + def set_red_light(self): + self.canvas.itemconfig(self.red_light_id, fill=TRAFFIC_LIGHT_RED_COLOR) + self.canvas.itemconfig(self.green_light_id, fill=TRAFFIC_LIGHT_OFF_COLOR) + +# TkInter canvas with water levels, doors +class LockView: + def __init__(self, parent, scale=1): + self.scale = scale + self.canvas = tkinter.Canvas(parent, bg=SKY_COLOR, width=600*scale, height=250*scale) + # LOW side: + self.canvas.create_rectangle(0, 200*scale, 200*scale, 250*scale, fill=WATER_COLOR, outline='') + # MIDDLE side: + self.middle_rectangle_id = self.canvas.create_rectangle(200*scale, 200*scale, 400*scale, 250*scale, fill=WATER_COLOR, outline='') + # HIGH side: + self.canvas.create_rectangle(400*scale, 100*scale, 600*scale, 250*scale, fill=WATER_COLOR, outline='') + + self.ldoor = LockDoorView(self.canvas, 200, scale) + self.hdoor = LockDoorView(self.canvas, 400, scale) + + def set_water_lvl(self, water_lvl): + self.canvas.coords(self.middle_rectangle_id, 200*self.scale, (250-water_lvl/10)*self.scale, 400*self.scale, 250*self.scale) + +class GUI: + def __init__(self, sim, sc, wlvlsc, toplevel, randomseed=0): + # to raise input events + self.sim = sim + self.sc = sc + self.wlvlsc = wlvlsc + + self.rand = random.Random(randomseed) # seed + + toplevel.resizable(0,0) + toplevel.title("Lock Simulator") + + self.lock_view = LockView(toplevel) + self.lock_view.canvas.pack(side=tkinter.TOP) + + self.var_simtime = tkinter.StringVar() + self.var_ldoors = tkinter.StringVar(value="CLOSED") + self.var_hdoors = tkinter.StringVar(value="CLOSED") + self.var_lflow = tkinter.StringVar(value="CLOSED") + self.var_hflow = tkinter.StringVar(value="CLOSED") + self.var_lsignal = tkinter.StringVar(value="RED") + self.var_hsignal = tkinter.StringVar(value="RED") + self.var_sensor = tkinter.StringVar(value="") + self.var_real_lvl = tkinter.StringVar(value="") + self.var_sensor_status = tkinter.StringVar(value="NO FAILURE DETECTED") + + sim_frame = tkinter.LabelFrame(toplevel, text="Environment") + tkinter.Label(sim_frame, text="Real Water Level").grid(column=0, row=0) + tkinter.Entry(sim_frame, state='readonly', width=8, textvariable=self.var_real_lvl, justify=tkinter.RIGHT).grid(column=1, row=0) + tkinter.Label(sim_frame, text="cm").grid(column=2, row=0) + + tkinter.Label(sim_frame, text="Water Level Sensor").grid(column=0, row=1) + self.entry_sensor = tkinter.Entry(sim_frame, state='readonly', width=8, textvariable=self.var_sensor, justify=tkinter.RIGHT) + self.entry_sensor.grid(row=1, column=1) + tkinter.Label(sim_frame, text="cm").grid(row=1, column=2) + + self.button_broken_sensor = tkinter.Button(sim_frame, text="Break Sensor", + command=self.break_sensor, width=14) + self.button_unbroken_sensor = tkinter.Button(sim_frame, text="Un-break Sensor", + command=self.unbreak_sensor, width=14, state=tkinter.DISABLED) + self.button_broken_sensor.grid(column=0, row=3, columnspan=3) + self.button_unbroken_sensor.grid(column=0, row=4, columnspan=3) + + tkinter.Label(sim_frame, text="Simulated Time").grid(column=0, row=5) + tkinter.Entry(sim_frame, state='readonly', width=8, textvariable=self.var_simtime, justify=tkinter.RIGHT).grid(row=5, column=1) + tkinter.Label(sim_frame, text="s").grid(row=5, column=2) + sim_frame.pack(side=tkinter.LEFT) + + request_frame = tkinter.LabelFrame(toplevel, text="Actions") + self.button_change_lvl = tkinter.Button(request_frame, command=lambda: self.sim.add_input_now(self.sc, "request_lvl_change"), width=18) + self.button_change_lvl.pack() + self.button_resume = tkinter.Button(request_frame, text="Resume (make sure sensor is repaired first!)", + command=self.resume, wraplength=160, width=18, state=tkinter.DISABLED) + self.button_resume.pack() + request_frame.pack(side=tkinter.LEFT) + + self.set_request_pending(value=False) + + status_frame = tkinter.LabelFrame(toplevel, text="Status") + tkinter.Label(status_frame, text="Low").grid(row=0, column=1) + tkinter.Label(status_frame, text="High").grid(row=0, column=2) + tkinter.Label(status_frame, text="Doors").grid(row=1, column=0) + tkinter.Label(status_frame, text="Flow").grid(row=2, column=0) + tkinter.Label(status_frame, text="Signal").grid(row=3, column=0) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_ldoors).grid(row=1, column=1) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_hdoors).grid(row=1, column=2) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_lflow).grid(row=2, column=1) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_hflow).grid(row=2, column=2) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_lsignal).grid(row=3, column=1) + tkinter.Entry(status_frame, state='readonly', width=8, textvariable=self.var_hsignal).grid(row=3, column=2) + tkinter.Label(status_frame, text="Sensor").grid(row=4, column=0) + tkinter.Entry(status_frame, state='readonly', width=18, textvariable=self.var_sensor_status).grid(row=4, column=1, columnspan=2) + status_frame.pack(side=tkinter.LEFT) + + def set_doors(self, side, open): + strval = "OPEN" if open else "CLOSED" + if side == self.sc.LOW: + self.var_ldoors.set(strval) + self.lock_view.ldoor.set_doors(open) + elif side == self.sc.HIGH: + self.var_hdoors.set(strval) + self.lock_view.hdoor.set_doors(open) + + def set_flow(self, side, open): + strval = "OPEN" if open else "CLOSED" + eventname = "open_flow" if open else "close_flow" + self.sim.add_input_sync(self.wlvlsc, eventname, value=side) + if side == self.sc.LOW: + self.var_lflow.set(strval) + self.lock_view.ldoor.set_flow(open) + elif side == self.sc.HIGH: + self.var_hflow.set(strval) + self.lock_view.hdoor.set_flow(open) + + def set_green_light(self, side): + if side == self.sc.LOW: + self.var_lsignal.set("GREEN") + self.lock_view.ldoor.set_green_light() + else: + self.var_hsignal.set("GREEN") + self.lock_view.hdoor.set_green_light() + + def set_red_light(self, side): + if side == self.sc.LOW: + self.var_lsignal.set("RED") + self.lock_view.ldoor.set_red_light() + else: + self.var_hsignal.set("RED") + self.lock_view.hdoor.set_red_light() + + def break_sensor(self): + self.sim.add_input_now(self.wlvlsc, "toggle_sensor_broken") + self.entry_sensor.config(readonlybackground='red') + self.button_broken_sensor.config(state=tkinter.DISABLED) + self.button_unbroken_sensor.config(state=tkinter.NORMAL) + + def unbreak_sensor(self): + self.sim.add_input_now(self.wlvlsc, "toggle_sensor_broken") + self.entry_sensor.config(readonlybackground=DEFAULT_WIDGET_COLOR) # default color + self.button_broken_sensor.config(state=tkinter.NORMAL) + self.button_unbroken_sensor.config(state=tkinter.DISABLED) + + def resume(self): + self.sim.add_input_now(self.sc, "resume") + self.button_resume.config(state=tkinter.DISABLED, bg=DEFAULT_WIDGET_COLOR) + self.var_sensor_status.set("NO FAILURE DETECTED") + + def set_request_pending(self, value): + if value: + self.button_change_lvl.config(state=tkinter.DISABLED, text="Change requested") + else: + self.button_change_lvl.config(state=tkinter.NORMAL, text="Change water level") + + def on_water_level_reading(self, water_level): + # the measured water level - can be nonsense if sensor is broken + noisy_water_level = int(water_level + self.rand.random()*10) + self.sim.add_input_sync(self.sc, "water_lvl", value=noisy_water_level) + self.var_sensor.set(noisy_water_level) + + def on_real_water_level(self, water_level): + # the actual water level + self.var_real_lvl.set(int(water_level)) + self.lock_view.set_water_lvl(water_level) + + def set_sensor_broken(self): + self.button_resume.config(state=tkinter.NORMAL, bg='yellow') + self.var_sensor_status.set("FAILURE DETECTED") + + def time_changed(self, simtime): + self.var_simtime.set(pretty_time(simtime)) diff --git a/StartingPoint/lib/controller.py b/StartingPoint/lib/controller.py new file mode 100644 index 0000000..85c9975 --- /dev/null +++ b/StartingPoint/lib/controller.py @@ -0,0 +1,80 @@ +# Author: Joeri Exelmans + +class QueueEntry: + __slots__ = ('timestamp', 'raise_method', 'value', 'canceled', 'event_name') # For MAXIMUM performance :) + + def __init__(self, timestamp, raise_method, value, event_name): + self.timestamp = timestamp + self.raise_method = raise_method + self.value = value + self.event_name = event_name # name of the event - only needed for debugging + self.canceled = False + + def __repr__(self): + return f"({self.timestamp}, {self.event_name}, {self.value})" + +# The main primitive for discrete event simulation. +# An event queue / event loop, using virtualized (simulated) time, independent of wall clock time. +class Controller: + def __init__(self): + self.event_queue = [] + self.simulated_time = 0 + self.input_tracers = [] + + # timestamp = absolute value, in simulated time (since beginning of simulation) + def add_input(self, sc, event_name, timestamp, value=None): + if '.' in event_name: + interface, short_event_name = event_name.split('.') + raise_method = getattr(getattr(sc, interface), 'raise_' + short_event_name) + else: + raise_method = getattr(sc, 'raise_' + event_name) + self.add_input_lowlevel(timestamp, raise_method, value, event_name) + + # time_offset = relative to current simulated time + def add_input_relative(self, sc, event_name, time_offset=0, value=None): + timestamp = self.simulated_time + time_offset + return self.add_input(sc, event_name, timestamp, value) + + def add_input_lowlevel(self, timestamp, raise_method, value, event_name): + e = QueueEntry(timestamp, raise_method, value, event_name) + self.event_queue.append(e) + # important to use a stable sorting algorithm here, + # so the order between equally-timestamped events is preserved: + self.event_queue.sort(key = lambda entry: entry.timestamp) + return e + + # difference here is that the added event will occur BEFORE equally-timestamped events that were already in the queue + def add_input_lowlevel_interrupt(self, timestamp, raise_method, value, event_name): + e = QueueEntry(timestamp, raise_method, value, event_name) + self.event_queue.insert(0, e) + self.event_queue.sort(key = lambda entry: entry.timestamp) + return e + + # Runs simulation as-fast-as-possible, until 'until'-timestamp (in simulated time) + # blocking, synchronous function + def run_until(self, until): + # print('running until', pretty_time(until)) + while self.have_event() and self.get_earliest() <= until: + e = self.event_queue[0] + for sc, tracer in self.input_tracers: + if sc == e.raise_method.__self__: + tracer(e.timestamp, e.event_name, e.value) + # e = self.event_queue.pop(); + self.event_queue = self.event_queue[1:] + if not e.canceled: + self.simulated_time = e.timestamp + if e.value == None: + e.raise_method() + else: + e.raise_method(e.value) + + def have_event(self): + return len(self.event_queue) > 0 + + def get_earliest(self): + # return self.event_queue[-1].timestamp + return self.event_queue[0].timestamp + + +def pretty_time(time_ns): + return f'{round(time_ns / 1000000000, 3)} s' diff --git a/StartingPoint/lib/realtime/event_loop.py b/StartingPoint/lib/realtime/event_loop.py new file mode 100644 index 0000000..9108f14 --- /dev/null +++ b/StartingPoint/lib/realtime/event_loop.py @@ -0,0 +1,67 @@ +from lib.controller import Controller +from lib.realtime.realtime import WallClock, AbstractRealTimeSimulation +import time +import abc + +class AbstractEventLoop: + # delay in nanoseconds + # should be non-blocking + # should return timer ID + @abc.abstractmethod + def schedule(self, delay, callback): + pass + + @abc.abstractmethod + def cancel(self, timer_id): + pass + +# Runs virtual (simulated) time as close as possible to (scaled) wall-clock time. +# Depending on how fast your computer is, simulated time will always run a tiny bit behind wall-clock time, but this error will NOT grow over time. +class EventLoopRealTimeSimulation(AbstractRealTimeSimulation): + + def __init__(self, controller: Controller, event_loop: AbstractEventLoop, wall_clock: WallClock, termination_condition=lambda: False, time_advance_callback=lambda simtime:None): + self.controller = controller + self.event_loop = event_loop + + self.wall_clock = wall_clock + + self.termination_condition = termination_condition + + # Just a callback indicating that the current simulated time has changed. + # Can be useful for displaying the simulated time in a GUI or something + self.time_advance_callback = time_advance_callback + + # At most one timer will be scheduled at the same time + self.scheduled_id = None + + def poke(self): + if self.scheduled_id is not None: + self.event_loop.cancel(self.scheduled_id) + + self.controller.run_until(self.wall_clock.time_since_start()) # this call may actually consume some time + + self.time_advance_callback(self.controller.simulated_time) + + if self.termination_condition(): + print("Termination condition satisfied. Stop mainloop.") + return + + if self.controller.have_event(): + # schedule next wakeup + sleep_duration = self.wall_clock.sleep_duration_until(self.controller.get_earliest()) + self.scheduled_id = self.event_loop.schedule(sleep_duration, self.poke) + # print("sleeping for", pretty_time(sleep_duration)) + else: + # print("sleeping until woken up") + pass + + # generate input event at the current wall clock time + # this method should be used for generating events that represent e.g., button clicks, key presses + def add_input_now(self, sc, event, value=None): + self.controller.add_input(sc, event, timestamp=self.wall_clock.time_since_start(), value=value) + self.poke() + + # for events that need to happen immediately, at the current point in simulated time + def add_input_sync(self, sc, event, value=None): + self.controller.add_input_relative(sc, event, value=value) + self.poke() diff --git a/StartingPoint/lib/realtime/realtime.py b/StartingPoint/lib/realtime/realtime.py new file mode 100644 index 0000000..9a29878 --- /dev/null +++ b/StartingPoint/lib/realtime/realtime.py @@ -0,0 +1,35 @@ +import time +import abc + +# Use time_scale different from 1.0 for scaled real-time execution: +# time_scale > 1 speeds up simulation +# 0 < time_scale < 1 slows down simulation +class WallClock: + def __init__(self, time_scale=1.0): + self.time_scale = time_scale + self.purposefully_behind = 0 + + def record_start_time(self): + self.start_time = time.perf_counter_ns() + + def time_since_start(self): + time_since_start = time.perf_counter_ns() - self.start_time + return (time_since_start * self.time_scale) + self.purposefully_behind + + def sleep_duration_until(self, earliest_event_time): + now = self.time_since_start() + sleep_duration = int((earliest_event_time - now) / self.time_scale) + # sleep_duration can be negative, if the next event is in the past + # This indicates that our computer is too slow, and cannot keep up with the simulation. + # Like all things fate-related, we embrace this slowness, rather than fighting it: + # We will temporarily run the simulation at a slower pace, which has the benefit of the simulation remaining responsive to user input. + self.purposefully_behind = min(sleep_duration, 0) # see above comment + actual_sleep_duration = max(sleep_duration, 0) # can never sleep less than 0 + return actual_sleep_duration + +class AbstractRealTimeSimulation: + # Generate input event at the current wall clock time (with time-scale applied, of course) + # This method should be used for interactive simulation, for generating events that were caused by e.g., button clicks, key presses, ... + @abc.abstractmethod + def add_input_now(self, sc, event, value=None): + pass diff --git a/StartingPoint/lib/realtime/threaded.py b/StartingPoint/lib/realtime/threaded.py new file mode 100644 index 0000000..c3e8edc --- /dev/null +++ b/StartingPoint/lib/realtime/threaded.py @@ -0,0 +1,43 @@ +import threading + +from lib.realtime.realtime import WallClock, AbstractRealTimeSimulation +from lib.controller import Controller, pretty_time + +# Runs simulation, real-time, in its own thread +# +# Typical usage: +# thread = threading.Thread( +# target=ThreadedRealTimeSimulation(...).mainloop, +# ) +# thread.start() +class ThreadedRealTimeSimulation(AbstractRealTimeSimulation): + def __init__(self, controller: Controller, wall_clock: WallClock, termination_condition = lambda: False): + self.controller = controller + self.wall_clock = wall_clock + self.termination_condition = termination_condition + self.condition = threading.Condition() + + def mainloop(self): + while True: + self.controller.run_until(self.wall_clock.time_since_start()) + if self.termination_condition(): + print("Termination condition satisfied. Stop mainloop.") + return + if self.controller.have_event(): + earliest_event_time = self.controller.get_earliest() + sleep_duration = self.wall_clock.sleep_duration_until(earliest_event_time) + with self.condition: + # print('thread sleeping for', pretty_time(sleep_duration), 'or until interrupted') + self.condition.wait(sleep_duration / 1000000000) + # print('thread woke up') + else: + with self.condition: + # print('thread sleeping until interrupted') + self.condition.wait() + + def add_input_now(self, sc, event, value=None): + with self.condition: + self.controller.add_input(sc, event, + timestamp=self.wall_clock.time_since_start(), + value=value) + self.condition.notify() diff --git a/StartingPoint/lib/realtime/tk_event_loop.py b/StartingPoint/lib/realtime/tk_event_loop.py new file mode 100644 index 0000000..8a67b85 --- /dev/null +++ b/StartingPoint/lib/realtime/tk_event_loop.py @@ -0,0 +1,13 @@ +from lib.realtime.event_loop import AbstractEventLoop + +# schedules calls in an existing tkinter eventloop +class TkEventLoopAdapter(AbstractEventLoop): + def __init__(self, tk): + self.tk = tk + + def schedule(self, delay, callback): + return self.tk.after(int(delay / 1000000), # ns to ms + callback) + + def cancel(self, timer): + self.tk.after_cancel(timer) diff --git a/StartingPoint/lib/test.py b/StartingPoint/lib/test.py new file mode 100644 index 0000000..9401273 --- /dev/null +++ b/StartingPoint/lib/test.py @@ -0,0 +1,136 @@ +from difflib import ndiff + +from lib.controller import Controller, pretty_time +from lib.tracer import Tracer +from lib.yakindu_helpers import YakinduTimerServiceAdapter, trace_output_events + +# Can we ignore event in 'trace' at position 'idx' with respect to idempotency? +def can_ignore(trace, idx, IDEMPOTENT): + (timestamp, event_name, value) = trace[idx] + if event_name in IDEMPOTENT: + # If the same event occurred earlier, with the same parameter value, then this event can be ignored: + for (earlier_timestamp, earlier_event_name, earlier_value) in reversed(trace[0:idx]): + if (earlier_event_name, earlier_value) == (event_name, value): + # same event name and same parameter value (timestamps allowed to differ) + return True + elif event_name == earlier_event_name: + # same event name, but different parameter value: + # stop looking into the past: + break + # If the same event occurs later event, but with the same timestamp, this event is overwritten and can be ignored: + for (later_timestamp, later_event_name, later_value) in trace[idx+1:]: + if (later_timestamp, later_event_name) == (timestamp, event_name): + # if a later event with same name and timestamp occurs, ours will be overwritten: + return True + if later_timestamp != timestamp: + # no need to look further into the future: + break + return False + +def postprocess_trace(trace, INITIAL, IDEMPOTENT): + # Prepend trace with events that set assumed initial state: + result = [(0, event_name, value) for (event_name, value) in INITIAL] + trace + # Remove events that have no effect: + while True: + filtered = [tup for (idx, tup) in enumerate(result) if not can_ignore(result, idx, IDEMPOTENT)] + # Keep on filtering until no more events could be removed: + if len(filtered) == len(result): + return filtered + result = filtered + +def compare_traces(expected, actual): + i = 0 + while i < len(expected) and i < len(actual): + # Compare tuples: + if expected[i] != actual[i]: + print("Traces differ!") + # print("expected: (%i, \"%s\", %s)" % expected[i]) + # print("actual: (%i, \"%s\", %s)" % actual[i]) + return False + i += 1 + if len(expected) != len(actual): + print("Traces have different length:") + print("expected length: %i" % len(expected)) + print("actual length: %i" % len(actual)) + return False + print("Traces match.") + return True + +def run_scenario(input_trace, expected_output_trace, statechart_class, INITIAL, IDEMPOTENT, verbose=False): + controller = Controller() + sc = statechart_class() + tracer = Tracer(verbose=False) + controller.input_tracers.append((sc, tracer.record_input_event)) + trace_output_events(controller, sc, callback=tracer.record_output_event) + sc.timer_service = YakinduTimerServiceAdapter(controller) + + # Put entire input trace in event queue, ready to go! + for tup in input_trace: + (timestamp, event_name, value) = tup + controller.add_input(sc, event_name, timestamp, value) + + sc.enter() # enter default state(s) + + if len(expected_output_trace) > 0: + last_output_event_timestamp = expected_output_trace[-1][0] + else: + last_output_event_timestamp = 0 + + # Blocking synchronous call: + controller.run_until(last_output_event_timestamp) + + actual_output_trace = tracer.output_events + + clean_expected = postprocess_trace(expected_output_trace, INITIAL, IDEMPOTENT) + clean_actual = postprocess_trace(actual_output_trace, INITIAL, IDEMPOTENT) + + # clean_expected = expected_output_trace + # clean_actual = actual_output_trace + + def print_diff(): + # The diff printed will be a diff of the 'raw' traces, not of the cleaned up traces + # A diff of the cleaned up traces would be confusing to the user. + have_plus = False + have_minus = False + have_useless = False + for diffline in ndiff( + [str(tup)+'\n' for tup in expected_output_trace], + [str(tup)+'\n' for tup in actual_output_trace], + charjunk=None, + ): + symbol = diffline[0] + if symbol == '+': + have_plus = True + if symbol == '-': + have_minus = True + if symbol == '?': + continue + rest = diffline[2:-1] # drop last character (=newline) + useless_line = ( + symbol == '-' and rest not in [str(tup) for tup in clean_expected] + or symbol == '+' and rest not in [str(tup) for tup in clean_actual] + # or symbol == ' ' and rest not in [str(tup) for tup in clean_actual] + ) + if useless_line: + print(" (%s) %s" % (symbol, rest)) + have_useless = True + else: + print(" %s %s" % (symbol, rest)) + + if have_minus or have_plus or have_useless: + print("Legend:") + if have_minus: + print(" -: expected, but did not happen") + if have_plus: + print(" +: happened, but was not expected") + if have_useless: + print(" (-) or (+): indicates a \"useless event\" (because it has no effect), either in expected output (-) or in actual output (+).") + print("\n\"Useless events\" are ignored by the comparison algorithm, and will never cause your test to fail. In this assignment, your solution is allowed to contain useless events.") + + if not compare_traces(clean_expected, clean_actual): + print("Raw diff between expected and actual output event trace:") + print_diff() + return False + elif verbose: + print_diff() + return True diff --git a/StartingPoint/lib/tracer.py b/StartingPoint/lib/tracer.py new file mode 100644 index 0000000..336d014 --- /dev/null +++ b/StartingPoint/lib/tracer.py @@ -0,0 +1,36 @@ +from lib.controller import pretty_time + +# Records input/output events +class Tracer: + def __init__(self, verbose=True): + self.verbose = verbose + self.input_events = [] + self.output_events = [] + + def record_input_event(self, simtime, event_name, value): + if self.verbose: + print(f"time = {pretty_time(simtime)}, input event: {event_name}, value = {value}") + if not event_name.startswith("__timer"): + # we don't record timer events - they are specific to the statechart (not part of any fixed interface), and they are auto-generated by the timer interface + self.input_events.append( (simtime, event_name, value) ) + + def record_output_event(self, simtime, event_name, value): + if self.verbose: + print(f"time = {pretty_time(simtime)}, output event: {event_name}, value = {value}") + self.output_events.append( (simtime, event_name, value)) + + +def format_trace_as_python_code(trace, indent=0): + txt = "[\n" + for (timestamp, event_name, value) in trace: + txt += (" "*indent)+" (%i, \"%s\", %s),\n" % (timestamp, event_name, value) + txt += (" "*indent)+"]," + return txt + +# almost same as Python, but with arrays instead of tuples +def format_trace_as_json(trace, indent=0): + txt = "[\n" + for (timestamp, event_name, value) in trace: + txt += (" "*indent)+" [%i, \"%s\", %s],\n" % (timestamp, event_name, value) + txt += (" "*indent)+"]," + return txt diff --git a/StartingPoint/lib/yakindu/__init__.py b/StartingPoint/lib/yakindu/__init__.py new file mode 100644 index 0000000..6ead047 --- /dev/null +++ b/StartingPoint/lib/yakindu/__init__.py @@ -0,0 +1,5 @@ +""" + +Empty file that initializes the package it is contained in. + +""" diff --git a/StartingPoint/lib/yakindu/rx.py b/StartingPoint/lib/yakindu/rx.py new file mode 100644 index 0000000..629d84b --- /dev/null +++ b/StartingPoint/lib/yakindu/rx.py @@ -0,0 +1,48 @@ +"""Implementation for Observer and Observables used for out events. +Generated by itemis CREATE code generator. +""" + + +class Observer(): + """Observer implementation. + """ + + def next(self, value=None): + """Abstract next method, which must be implemented.""" + raise NotImplementedError('user must define next() to use this base class') + + +class Observable(): + """Observable implementation. + """ + + def __init__(self): + self.observers = [] + + def next(self, value=None): + """Calls next function from every observer. + """ + for observer in self.observers: + if observer is not None: + if value is None: + observer.next() + else: + observer.next(value) + + def subscribe(self, observer): + """Subscribe on specified observer. + """ + if observer is not None: + self.observers.append(observer) + return True + return False + + def unsubscribe(self, observer): + """Unsubscribe from specified observer. + """ + if observer is None: + return False + if observer in self.observers: + self.observers.remove(observer) + return True + return False diff --git a/StartingPoint/lib/yakindu_helpers.py b/StartingPoint/lib/yakindu_helpers.py new file mode 100644 index 0000000..85f73cc --- /dev/null +++ b/StartingPoint/lib/yakindu_helpers.py @@ -0,0 +1,74 @@ +# In this module, stuff that is specific to Yakindu's generated code +# Author: Joeri Exelmans + +from lib.controller import Controller, pretty_time + +# for some stupid reason, we have to import the 'Observable' class like this, or `type(obj) == Observable` will fail: +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) +from yakindu.rx import Observable, Observer + +# Adapter to allow Yakindu generated code to (un)set timeouts +# Uses event queue of the underlying Controller, making all timed transitions scheduled in simulated time (instead of wall-clock time as in Yakindu's own TimerService). +class YakinduTimerServiceAdapter: + def __init__(self, controller: Controller): + self.controller = controller; + self.timers = {} + + # Duration: milliseconds + def set_timer(self, sc, event_id, duration, periodic): + self.unset_timer(None, event_id) + + controller_duration = duration * 1000000 # ms to ns + + e = self.controller.add_input_lowlevel( + self.controller.simulated_time + controller_duration, # timestamp relative to simulated time + raise_method=sc.time_elapsed, + value=event_id, + event_name="__timer"+str(event_id)) + + self.timers[event_id] = e + + def unset_timer(self, _, event_id): + try: + e = self.timers[event_id] + e.canceled = True + except KeyError: + pass + + +# Could not find a better way to get list of output events of a YAKINDU statechart +def iter_output_observables(sc): + for attr in dir(sc): + obj = getattr(sc, attr) + if type(obj) == Observable: + yield (attr[0:-11], obj) + + +# Useful for debugging +class OutputEventTracer(Observer): + def __init__(self, controller, event_name, callback): + self.controller = controller + self.event_name = event_name + self.callback = callback + def next(self, value=None): + self.callback(self.controller.simulated_time, self.event_name, value) + +def trace_output_events(controller, sc, callback, iface=None): + if iface == None: + for event_name, observable in iter_output_observables(sc): + observable.subscribe(OutputEventTracer(controller, event_name, callback)) + else: + for event_name, observable in iter_output_observables(getattr(sc, iface)): + full_event_name = iface + '.' + event_name + observable.subscribe(OutputEventTracer(controller, full_event_name, callback)) + +# Allows use of a simple callback to respond to an output event +class CallbackObserver(Observer): + def __init__(self, callback): + self.callback = callback + def next(self, value=None): + if value == None: + self.callback() + else: + self.callback(value) diff --git a/StartingPoint/runner_gui.py b/StartingPoint/runner_gui.py new file mode 100644 index 0000000..cede20d --- /dev/null +++ b/StartingPoint/runner_gui.py @@ -0,0 +1,83 @@ +import tkinter +import atexit + +# load generated Statechart code +from srcgen.water_level_simulator import WaterLevelSimulator +# from srcgen.lock_controller import LockController +from srcgen.solution import Solution as LockController # Teacher's solution + +from lib.yakindu.rx import Observer +from lib.controller import Controller, pretty_time +from lib.tracer import Tracer, format_trace_as_python_code +from lib.yakindu_helpers import YakinduTimerServiceAdapter, CallbackObserver, trace_output_events +from lib.realtime.realtime import WallClock +from lib.realtime.event_loop import EventLoopRealTimeSimulation +from lib.realtime.tk_event_loop import TkEventLoopAdapter + +from gui import GUI + +if __name__ == "__main__": + + # read time scale from command line + try: + time_scale = float(sys.argv[1]) + except: + time_scale = 1.0 + print(f"TIME SCALE is {time_scale}") + + + sc = LockController() + wlvlsc = WaterLevelSimulator() # our environment is also modeled as a statechart :) + + controller = Controller() + + # We'll record input and output events + tracer = Tracer() + controller.input_tracers.append((sc, tracer.record_input_event)) + trace_output_events(controller, sc, callback=tracer.record_output_event) + + sc.timer_service = YakinduTimerServiceAdapter(controller) + wlvlsc.timer_service = YakinduTimerServiceAdapter(controller) + + toplevel = tkinter.Tk() + + wall_clock = WallClock(time_scale) + sim = EventLoopRealTimeSimulation(controller, TkEventLoopAdapter(toplevel), wall_clock) + + gui = GUI(sim, sc, wlvlsc, toplevel) + + sim.time_advance_callback = gui.time_changed + + # output event handlers of LockController + sc.set_request_pending_observable.subscribe(CallbackObserver(gui.set_request_pending)) + sc.open_flow_observable.subscribe(CallbackObserver(lambda side: gui.set_flow(side, True))) + sc.close_flow_observable.subscribe(CallbackObserver(lambda side: gui.set_flow(side, False))) + sc.open_doors_observable.subscribe(CallbackObserver(lambda side: gui.set_doors(side, True))) + sc.close_doors_observable.subscribe(CallbackObserver(lambda side: gui.set_doors(side, False))) + sc.green_light_observable.subscribe(CallbackObserver(gui.set_green_light)) + sc.red_light_observable.subscribe(CallbackObserver(gui.set_red_light)) + sc.set_sensor_broken_observable.subscribe(CallbackObserver(gui.set_sensor_broken)) + + # output event handlers of WaterLevelSimulator + wlvlsc.sensor_reading_observable.subscribe(CallbackObserver(gui.on_water_level_reading)) + wlvlsc.real_water_level_observable.subscribe(CallbackObserver(gui.on_real_water_level)) + + def print_trace_on_exit(): + print("End of simulation. Full I/O trace:") + print("{") + print(' "name": "interactive",') + print(' "input_events": ', end='') + print(format_trace_as_python_code(tracer.input_events, indent=4)) + print(' "output_events": ', end='') + print(format_trace_as_python_code(tracer.output_events, indent=4)) + print("}") + atexit.register(print_trace_on_exit) + + wall_clock.record_start_time() # start_time is NOW! + + # Enter default states + sc.enter() + wlvlsc.enter() + + sim.poke() # schedule first simulator wakeup (in tk event loop) + toplevel.mainloop() # everything is controlled by tkinter's eventloop diff --git a/StartingPoint/runner_tests.py b/StartingPoint/runner_tests.py new file mode 100644 index 0000000..fd8c34a --- /dev/null +++ b/StartingPoint/runner_tests.py @@ -0,0 +1,240 @@ +import functools +from lib.test import run_scenario + +# from srcgen.lock_controller import LockController +from srcgen.solution import Solution as LockController # Teacher's solution + +# For each test scenario, sends a sequence of timed input events to the statechart, and checks if the expected sequence of timed output events occurs. + +# Each timed event is a tuple (timestamp, event_name, parameter_value) +# For events that don't have a parameter, the parameter value is always 'None'. +# Timestamps are in nanoseconds since simulation start! + +SCENARIOS = [ +{ + "name": "normal operation, serve two requests", + "input_events": [ + (0, "water_lvl", 508), + (2393556604, "request_lvl_change", None), + (4493556604, "water_lvl", 675), + (4593556604, "water_lvl", 811), + (4693556604, "water_lvl", 926), + (4793556604, "water_lvl", 1025), + (4893556604, "water_lvl", 1105), + (4993556604, "water_lvl", 1176), + (5093556604, "water_lvl", 1228), + (5193556604, "water_lvl", 1276), + (5293556604, "water_lvl", 1316), + (5393556604, "water_lvl", 1352), + (5493556604, "water_lvl", 1375), + (5593556604, "water_lvl", 1395), + (5693556604, "water_lvl", 1419), + (5793556604, "water_lvl", 1433), + (5893556604, "water_lvl", 1443), + (5993556604, "water_lvl", 1460), + (6093556604, "water_lvl", 1470), + (6193556604, "water_lvl", 1476), + (6293556604, "water_lvl", 1483), + (6393556604, "water_lvl", 1482), + (6493556604, "water_lvl", 1491), + (6593556604, "water_lvl", 1496), + (6693556604, "water_lvl", 1497), + (6793556604, "water_lvl", 1498), + (6893556604, "water_lvl", 1496), + (6993556604, "water_lvl", 1501), + (7093556604, "water_lvl", 1504), + (7193556604, "water_lvl", 1509), + (9193747734, "request_lvl_change", None), + (11293747734, "water_lvl", 1341), + (11393747734, "water_lvl", 1197), + (11493747734, "water_lvl", 1084), + (11593747734, "water_lvl", 981), + (11693747734, "water_lvl", 906), + (11793747734, "water_lvl", 836), + (11893747734, "water_lvl", 774), + (11993747734, "water_lvl", 735), + (12093747734, "water_lvl", 692), + (12193747734, "water_lvl", 664), + (12293747734, "water_lvl", 636), + (12393747734, "water_lvl", 606), + (12493747734, "water_lvl", 592), + (12593747734, "water_lvl", 581), + (12693747734, "water_lvl", 561), + (12793747734, "water_lvl", 551), + (12893747734, "water_lvl", 548), + (12993747734, "water_lvl", 533), + (13093747734, "water_lvl", 531), + (13193747734, "water_lvl", 522), + (13293747734, "water_lvl", 525), + (13393747734, "water_lvl", 520), + (13493747734, "water_lvl", 513), + (13593747734, "water_lvl", 507), + (13693747734, "water_lvl", 507), + (13793747734, "water_lvl", 507), + (13893747734, "water_lvl", 510), + (13993747734, "water_lvl", 501), + (14093747734, "water_lvl", 504), + ], + "output_events": [ + (0, "open_doors", 0), + (0, "green_light", 0), + (2393556604, "red_light", 0), + (2393556604, "set_request_pending", True), + (4393556604, "close_doors", 0), + (4393556604, "open_flow", 1), + (7193556604, "close_flow", 1), + (7193556604, "set_request_pending", False), + (7193556604, "open_doors", 1), + (7193556604, "green_light", 1), + (9193747734, "red_light", 1), + (9193747734, "set_request_pending", True), + (11193747734, "close_doors", 1), + (11193747734, "open_flow", 0), + (14193747734, "close_flow", 0), + (14193747734, "set_request_pending", False), + (14193747734, "open_doors", 0), + (14193747734, "green_light", 0), + ], +}, +{ + "name": "break sensor, fix sensor, then change water lvl", + "input_events": [ + (0, "water_lvl", 508), + (2084169493, "water_lvl", 99007), + (4084274216, "water_lvl", 504), + (5420871976, "resume", None), + (7100735485, "request_lvl_change", None), + (9200735485, "water_lvl", 670), + (9300735485, "water_lvl", 812), + (9400735485, "water_lvl", 927), + (9500735485, "water_lvl", 1028), + (9600735485, "water_lvl", 1104), + (9700735485, "water_lvl", 1173), + (9800735485, "water_lvl", 1231), + (9900735485, "water_lvl", 1281), + (10000735485, "water_lvl", 1316), + (10100735485, "water_lvl", 1346), + (10200735485, "water_lvl", 1378), + (10300735485, "water_lvl", 1399), + (10400735485, "water_lvl", 1414), + (10500735485, "water_lvl", 1436), + (10600735485, "water_lvl", 1450), + (10700735485, "water_lvl", 1459), + (10800735485, "water_lvl", 1469), + (10900735485, "water_lvl", 1471), + (11000735485, "water_lvl", 1481), + (11100735485, "water_lvl", 1488), + (11200735485, "water_lvl", 1490), + (11300735485, "water_lvl", 1492), + (11400735485, "water_lvl", 1491), + (11500735485, "water_lvl", 1497), + (11600735485, "water_lvl", 1501), + (11700735485, "water_lvl", 1506), + (11800735485, "water_lvl", 1508), + (11900735485, "water_lvl", 1504), + ], + "output_events": [ + (0, "open_doors", 0), + (0, "green_light", 0), + (2084169493, "red_light", 0), + (2084169493, "close_doors", 0), + (2084169493, "set_sensor_broken", None), + (5420871976, "open_doors", 0), + (5420871976, "green_light", 0), + (7100735485, "red_light", 0), + (7100735485, "set_request_pending", True), + (9100735485, "close_doors", 0), + (9100735485, "open_flow", 1), + (11900735485, "close_flow", 1), + (11900735485, "set_request_pending", False), + (11900735485, "open_doors", 1), + (11900735485, "green_light", 1), + ], +}, +{ + "name": "break sensor DURING water lvl change, then fix and resume", + "input_events": [ + (0, "water_lvl", 508), + (2661508910, "request_lvl_change", None), + (4761508910, "water_lvl", 675), + (4861508910, "water_lvl", 811), + (4961508910, "water_lvl", 926), + (5061508910, "water_lvl", 1025), + (5093300938, "water_lvl", 99004), + (7821829184, "water_lvl", 1028), + (9213791769, "resume", None), + (9313791769, "water_lvl", 1104), + (9413791769, "water_lvl", 1173), + (9513791769, "water_lvl", 1231), + (9613791769, "water_lvl", 1281), + (9713791769, "water_lvl", 1316), + (9813791769, "water_lvl", 1346), + (9913791769, "water_lvl", 1378), + (10013791769, "water_lvl", 1399), + (10113791769, "water_lvl", 1414), + (10213791769, "water_lvl", 1436), + (10313791769, "water_lvl", 1450), + (10413791769, "water_lvl", 1459), + (10513791769, "water_lvl", 1469), + (10613791769, "water_lvl", 1471), + (10713791769, "water_lvl", 1481), + (10813791769, "water_lvl", 1488), + (10913791769, "water_lvl", 1490), + (11013791769, "water_lvl", 1492), + (11113791769, "water_lvl", 1491), + (11213791769, "water_lvl", 1497), + (11313791769, "water_lvl", 1501), + (11413791769, "water_lvl", 1506), + (11513791769, "water_lvl", 1508), + (11613791769, "water_lvl", 1504), + ], + "output_events": [ + (0, "open_doors", 0), + (0, "green_light", 0), + (2661508910, "red_light", 0), + (2661508910, "set_request_pending", True), + (4661508910, "close_doors", 0), + (4661508910, "open_flow", 1), + (5093300938, "close_flow", 1), + (5093300938, "set_sensor_broken", None), + (9213791769, "open_flow", 1), + (11613791769, "close_flow", 1), + (11613791769, "set_request_pending", False), + (11613791769, "open_doors", 1), + (11613791769, "green_light", 1), + ], +} +] + +# The following output events are safe to repeat: (with same value) +# This will be taken into account while comparing traces. +# Do not change this: +IDEMPOTENT = [ + "open_doors", + "close_doors", + "red_light", + "green_light", + "set_request_pending", + "open_flow", + "close_flow", +] +# We pretend that initially, these events occur: +# Do not change this: +INITIAL = [ + ("open_doors", 0), + ("close_doors", 1), + ("green_light", 0), + ("red_light", 1), + ("set_request_pending", False) +] + +if __name__ == "__main__": + ok = True + for scenario in SCENARIOS: + print(f"Running scenario: {scenario["name"]}") + ok = run_scenario(scenario["input_events"], scenario["output_events"], LockController, INITIAL, IDEMPOTENT, verbose=False) and ok + print("--------") + if ok: + print("All scenarios passed.") + else: + print("Some scenarios failed.") diff --git a/StartingPoint/srcgen/lock_controller.py b/StartingPoint/srcgen/lock_controller.py new file mode 100644 index 0000000..52f7303 --- /dev/null +++ b/StartingPoint/srcgen/lock_controller.py @@ -0,0 +1,742 @@ +"""Implementation of statechart lock_controller. +Generated by itemis CREATE code generator. +""" + +import queue +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) +from yakindu.rx import Observable + +class LockController: + """Implementation of the state machine LockController. + """ + + class State: + """ State Enum + """ + ( + main_region_o, + main_region_or1a, + main_region_or1b, + main_region_or2c, + main_region_or2d, + main_region_or2e, + main_region_or2f, + main_region_or3g, + main_region_or3h, + null_state + ) = range(10) + + + def __init__(self): + """ Declares all necessary variables including list of states, histories etc. + """ + + self.LOW = 0 + self.HIGH = 1 + self.LOW_LVL = 500 + self.HIGH_LVL = 1500 + self.request_lvl_change = None + self.water_lvl = None + self.water_lvl_value = None + self.resume = None + self.door_obstructed = None + self.door_obstructed_value = None + self.set_request_pending = None + self.set_request_pending_value = None + self.set_request_pending_observable = Observable() + self.set_sensor_broken = None + self.set_sensor_broken_observable = Observable() + self.open_flow = None + self.open_flow_value = None + self.open_flow_observable = Observable() + self.close_flow = None + self.close_flow_value = None + self.close_flow_observable = Observable() + self.open_doors = None + self.open_doors_value = None + self.open_doors_observable = Observable() + self.close_doors = None + self.close_doors_value = None + self.close_doors_observable = Observable() + self.green_light = None + self.green_light_value = None + self.green_light_observable = Observable() + self.red_light = None + self.red_light_value = None + self.red_light_observable = Observable() + + self.in_event_queue = queue.Queue() + + # enumeration of all states: + self.__State = LockController.State + self.__state_conf_vector_changed = None + self.__state_vector = [None] * 3 + for __state_index in range(3): + self.__state_vector[__state_index] = self.State.null_state + + # for timed statechart: + self.timer_service = None + self.__time_events = [None] * 8 + + # initializations: + self.__is_executing = False + self.__state_conf_vector_position = None + + def is_active(self): + """Checks if the state machine is active. + """ + return self.__state_vector[0] is not self.__State.null_state or self.__state_vector[1] is not self.__State.null_state or self.__state_vector[2] is not self.__State.null_state + + def is_final(self): + """Checks if the statemachine is final. + Always returns 'false' since this state machine can never become final. + """ + return False + + def is_state_active(self, state): + """Checks if the state is currently active. + """ + s = state + if s == self.__State.main_region_o: + return (self.__state_vector[0] >= self.__State.main_region_o)\ + and (self.__state_vector[0] <= self.__State.main_region_or3h) + if s == self.__State.main_region_or1a: + return self.__state_vector[0] == self.__State.main_region_or1a + if s == self.__State.main_region_or1b: + return self.__state_vector[0] == self.__State.main_region_or1b + if s == self.__State.main_region_or2c: + return self.__state_vector[1] == self.__State.main_region_or2c + if s == self.__State.main_region_or2d: + return self.__state_vector[1] == self.__State.main_region_or2d + if s == self.__State.main_region_or2e: + return self.__state_vector[1] == self.__State.main_region_or2e + if s == self.__State.main_region_or2f: + return self.__state_vector[1] == self.__State.main_region_or2f + if s == self.__State.main_region_or3g: + return self.__state_vector[2] == self.__State.main_region_or3g + if s == self.__State.main_region_or3h: + return self.__state_vector[2] == self.__State.main_region_or3h + return False + + def time_elapsed(self, event_id): + """Add time events to in event queue + """ + if event_id in range(8): + self.in_event_queue.put(lambda: self.raise_time_event(event_id)) + self.run_cycle() + + def raise_time_event(self, event_id): + """Raise timed events using the event_id. + """ + self.__time_events[event_id] = True + + def __execute_queued_event(self, func): + func() + + def __get_next_event(self): + if not self.in_event_queue.empty(): + return self.in_event_queue.get() + return None + + + def raise_request_lvl_change(self): + """Raise method for event request_lvl_change. + """ + self.in_event_queue.put(self.__raise_request_lvl_change_call) + self.run_cycle() + + def __raise_request_lvl_change_call(self): + """Raise callback for event request_lvl_change. + """ + self.request_lvl_change = True + + def raise_water_lvl(self, value): + """Raise method for event water_lvl. + """ + self.in_event_queue.put(lambda: self.__raise_water_lvl_call(value)) + self.run_cycle() + + def __raise_water_lvl_call(self, value): + """Raise callback for event water_lvl. + """ + self.water_lvl = True + self.water_lvl_value = value + + def raise_resume(self): + """Raise method for event resume. + """ + self.in_event_queue.put(self.__raise_resume_call) + self.run_cycle() + + def __raise_resume_call(self): + """Raise callback for event resume. + """ + self.resume = True + + def raise_door_obstructed(self, value): + """Raise method for event door_obstructed. + """ + self.in_event_queue.put(lambda: self.__raise_door_obstructed_call(value)) + self.run_cycle() + + def __raise_door_obstructed_call(self, value): + """Raise callback for event door_obstructed. + """ + self.door_obstructed = True + self.door_obstructed_value = value + + def __entry_action_main_region_o_r1_a(self): + """Entry action for state 'A'.. + """ + #Entry action for state 'A'. + self.timer_service.set_timer(self, 0, (1 * 1000), False) + self.open_flow_observable.next(self.HIGH) + + def __entry_action_main_region_o_r1_b(self): + """Entry action for state 'B'.. + """ + #Entry action for state 'B'. + self.timer_service.set_timer(self, 1, (1 * 1000), False) + self.open_flow_observable.next(self.LOW) + + def __entry_action_main_region_o_r2_c(self): + """Entry action for state 'C'.. + """ + #Entry action for state 'C'. + self.timer_service.set_timer(self, 2, 500, False) + self.green_light_observable.next(self.LOW) + + def __entry_action_main_region_o_r2_d(self): + """Entry action for state 'D'.. + """ + #Entry action for state 'D'. + self.timer_service.set_timer(self, 3, 500, False) + self.green_light_observable.next(self.HIGH) + + def __entry_action_main_region_o_r2_e(self): + """Entry action for state 'E'.. + """ + #Entry action for state 'E'. + self.timer_service.set_timer(self, 4, 500, False) + self.red_light_observable.next(self.LOW) + + def __entry_action_main_region_o_r2_f(self): + """Entry action for state 'F'.. + """ + #Entry action for state 'F'. + self.timer_service.set_timer(self, 5, 500, False) + self.red_light_observable.next(self.HIGH) + + def __entry_action_main_region_o_r3_g(self): + """Entry action for state 'G'.. + """ + #Entry action for state 'G'. + self.timer_service.set_timer(self, 6, 250, False) + + def __entry_action_main_region_o_r3_h(self): + """Entry action for state 'H'.. + """ + #Entry action for state 'H'. + self.timer_service.set_timer(self, 7, 250, False) + + def __exit_action_main_region_o_r1_a(self): + """Exit action for state 'A'.. + """ + #Exit action for state 'A'. + self.timer_service.unset_timer(self, 0) + self.close_flow_observable.next(self.HIGH) + + def __exit_action_main_region_o_r1_b(self): + """Exit action for state 'B'.. + """ + #Exit action for state 'B'. + self.timer_service.unset_timer(self, 1) + self.close_flow_observable.next(self.LOW) + + def __exit_action_main_region_o_r2_c(self): + """Exit action for state 'C'.. + """ + #Exit action for state 'C'. + self.timer_service.unset_timer(self, 2) + + def __exit_action_main_region_o_r2_d(self): + """Exit action for state 'D'.. + """ + #Exit action for state 'D'. + self.timer_service.unset_timer(self, 3) + + def __exit_action_main_region_o_r2_e(self): + """Exit action for state 'E'.. + """ + #Exit action for state 'E'. + self.timer_service.unset_timer(self, 4) + + def __exit_action_main_region_o_r2_f(self): + """Exit action for state 'F'.. + """ + #Exit action for state 'F'. + self.timer_service.unset_timer(self, 5) + + def __exit_action_main_region_o_r3_g(self): + """Exit action for state 'G'.. + """ + #Exit action for state 'G'. + self.timer_service.unset_timer(self, 6) + + def __exit_action_main_region_o_r3_h(self): + """Exit action for state 'H'.. + """ + #Exit action for state 'H'. + self.timer_service.unset_timer(self, 7) + + def __enter_sequence_main_region_o_default(self): + """'default' enter sequence for state O. + """ + #'default' enter sequence for state O + self.__enter_sequence_main_region_o_r1_default() + self.__enter_sequence_main_region_o_r2_default() + self.__enter_sequence_main_region_o_r3_default() + + def __enter_sequence_main_region_o_r1_a_default(self): + """'default' enter sequence for state A. + """ + #'default' enter sequence for state A + self.__entry_action_main_region_o_r1_a() + self.__state_vector[0] = self.State.main_region_or1a + self.__state_conf_vector_position = 0 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r1_b_default(self): + """'default' enter sequence for state B. + """ + #'default' enter sequence for state B + self.__entry_action_main_region_o_r1_b() + self.__state_vector[0] = self.State.main_region_or1b + self.__state_conf_vector_position = 0 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_c_default(self): + """'default' enter sequence for state C. + """ + #'default' enter sequence for state C + self.__entry_action_main_region_o_r2_c() + self.__state_vector[1] = self.State.main_region_or2c + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_d_default(self): + """'default' enter sequence for state D. + """ + #'default' enter sequence for state D + self.__entry_action_main_region_o_r2_d() + self.__state_vector[1] = self.State.main_region_or2d + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_e_default(self): + """'default' enter sequence for state E. + """ + #'default' enter sequence for state E + self.__entry_action_main_region_o_r2_e() + self.__state_vector[1] = self.State.main_region_or2e + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_f_default(self): + """'default' enter sequence for state F. + """ + #'default' enter sequence for state F + self.__entry_action_main_region_o_r2_f() + self.__state_vector[1] = self.State.main_region_or2f + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r3_g_default(self): + """'default' enter sequence for state G. + """ + #'default' enter sequence for state G + self.__entry_action_main_region_o_r3_g() + self.__state_vector[2] = self.State.main_region_or3g + self.__state_conf_vector_position = 2 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r3_h_default(self): + """'default' enter sequence for state H. + """ + #'default' enter sequence for state H + self.__entry_action_main_region_o_r3_h() + self.__state_vector[2] = self.State.main_region_or3h + self.__state_conf_vector_position = 2 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_default(self): + """'default' enter sequence for region main region. + """ + #'default' enter sequence for region main region + self.__react_main_region__entry_default() + + def __enter_sequence_main_region_o_r1_default(self): + """'default' enter sequence for region r1. + """ + #'default' enter sequence for region r1 + self.__react_main_region_o_r1__entry_default() + + def __enter_sequence_main_region_o_r2_default(self): + """'default' enter sequence for region r2. + """ + #'default' enter sequence for region r2 + self.__react_main_region_o_r2__entry_default() + + def __enter_sequence_main_region_o_r3_default(self): + """'default' enter sequence for region r3. + """ + #'default' enter sequence for region r3 + self.__react_main_region_o_r3__entry_default() + + def __exit_sequence_main_region_o_r1_a(self): + """Default exit sequence for state A. + """ + #Default exit sequence for state A + self.__state_vector[0] = self.State.main_region_o + self.__state_conf_vector_position = 0 + self.__exit_action_main_region_o_r1_a() + + def __exit_sequence_main_region_o_r1_b(self): + """Default exit sequence for state B. + """ + #Default exit sequence for state B + self.__state_vector[0] = self.State.main_region_o + self.__state_conf_vector_position = 0 + self.__exit_action_main_region_o_r1_b() + + def __exit_sequence_main_region_o_r2_c(self): + """Default exit sequence for state C. + """ + #Default exit sequence for state C + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + self.__exit_action_main_region_o_r2_c() + + def __exit_sequence_main_region_o_r2_d(self): + """Default exit sequence for state D. + """ + #Default exit sequence for state D + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + self.__exit_action_main_region_o_r2_d() + + def __exit_sequence_main_region_o_r2_e(self): + """Default exit sequence for state E. + """ + #Default exit sequence for state E + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + self.__exit_action_main_region_o_r2_e() + + def __exit_sequence_main_region_o_r2_f(self): + """Default exit sequence for state F. + """ + #Default exit sequence for state F + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + self.__exit_action_main_region_o_r2_f() + + def __exit_sequence_main_region_o_r3_g(self): + """Default exit sequence for state G. + """ + #Default exit sequence for state G + self.__state_vector[2] = self.State.main_region_o + self.__state_conf_vector_position = 2 + self.__exit_action_main_region_o_r3_g() + + def __exit_sequence_main_region_o_r3_h(self): + """Default exit sequence for state H. + """ + #Default exit sequence for state H + self.__state_vector[2] = self.State.main_region_o + self.__state_conf_vector_position = 2 + self.__exit_action_main_region_o_r3_h() + + def __exit_sequence_main_region(self): + """Default exit sequence for region main region. + """ + #Default exit sequence for region main region + state = self.__state_vector[0] + if state == self.State.main_region_or1a: + self.__exit_sequence_main_region_o_r1_a() + elif state == self.State.main_region_or1b: + self.__exit_sequence_main_region_o_r1_b() + state = self.__state_vector[1] + if state == self.State.main_region_or2c: + self.__exit_sequence_main_region_o_r2_c() + elif state == self.State.main_region_or2d: + self.__exit_sequence_main_region_o_r2_d() + elif state == self.State.main_region_or2e: + self.__exit_sequence_main_region_o_r2_e() + elif state == self.State.main_region_or2f: + self.__exit_sequence_main_region_o_r2_f() + state = self.__state_vector[2] + if state == self.State.main_region_or3g: + self.__exit_sequence_main_region_o_r3_g() + elif state == self.State.main_region_or3h: + self.__exit_sequence_main_region_o_r3_h() + + def __react_main_region_o_r1__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r1_a_default() + + def __react_main_region_o_r2__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r2_c_default() + + def __react_main_region_o_r3__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r3_g_default() + + def __react_main_region__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_default() + + def __react(self, transitioned_before): + """Implementation of __react function. + """ + #State machine reactions. + return transitioned_before + + + def __main_region_o_react(self, transitioned_before): + """Implementation of __main_region_o_react function. + """ + #The reactions of state O. + return self.__react(transitioned_before) + + + def __main_region_o_r1_a_react(self, transitioned_before): + """Implementation of __main_region_o_r1_a_react function. + """ + #The reactions of state A. + transitioned_after = self.__main_region_o_react(transitioned_before) + if transitioned_after < 0: + if self.__time_events[0]: + self.__exit_sequence_main_region_o_r1_a() + self.__time_events[0] = False + self.__enter_sequence_main_region_o_r1_b_default() + transitioned_after = 0 + return transitioned_after + + + def __main_region_o_r1_b_react(self, transitioned_before): + """Implementation of __main_region_o_r1_b_react function. + """ + #The reactions of state B. + transitioned_after = self.__main_region_o_react(transitioned_before) + if transitioned_after < 0: + if self.__time_events[1]: + self.__exit_sequence_main_region_o_r1_b() + self.__time_events[1] = False + self.__enter_sequence_main_region_o_r1_a_default() + transitioned_after = 0 + return transitioned_after + + + def __main_region_o_r2_c_react(self, transitioned_before): + """Implementation of __main_region_o_r2_c_react function. + """ + #The reactions of state C. + transitioned_after = transitioned_before + if transitioned_after < 1: + if self.__time_events[2]: + self.__exit_sequence_main_region_o_r2_c() + self.__time_events[2] = False + self.__enter_sequence_main_region_o_r2_d_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r2_d_react(self, transitioned_before): + """Implementation of __main_region_o_r2_d_react function. + """ + #The reactions of state D. + transitioned_after = transitioned_before + if transitioned_after < 1: + if self.__time_events[3]: + self.__exit_sequence_main_region_o_r2_d() + self.__time_events[3] = False + self.__enter_sequence_main_region_o_r2_e_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r2_e_react(self, transitioned_before): + """Implementation of __main_region_o_r2_e_react function. + """ + #The reactions of state E. + transitioned_after = transitioned_before + if transitioned_after < 1: + if self.__time_events[4]: + self.__exit_sequence_main_region_o_r2_e() + self.__time_events[4] = False + self.__enter_sequence_main_region_o_r2_f_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r2_f_react(self, transitioned_before): + """Implementation of __main_region_o_r2_f_react function. + """ + #The reactions of state F. + transitioned_after = transitioned_before + if transitioned_after < 1: + if self.__time_events[5]: + self.__exit_sequence_main_region_o_r2_f() + self.__time_events[5] = False + self.__enter_sequence_main_region_o_r2_c_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r3_g_react(self, transitioned_before): + """Implementation of __main_region_o_r3_g_react function. + """ + #The reactions of state G. + transitioned_after = transitioned_before + if transitioned_after < 2: + if self.__time_events[6]: + self.__exit_sequence_main_region_o_r3_g() + self.set_request_pending_observable.next(False) + self.__time_events[6] = False + self.__enter_sequence_main_region_o_r3_h_default() + transitioned_after = 2 + return transitioned_after + + + def __main_region_o_r3_h_react(self, transitioned_before): + """Implementation of __main_region_o_r3_h_react function. + """ + #The reactions of state H. + transitioned_after = transitioned_before + if transitioned_after < 2: + if self.__time_events[7]: + self.__exit_sequence_main_region_o_r3_h() + self.set_request_pending_observable.next(True) + self.__time_events[7] = False + self.__enter_sequence_main_region_o_r3_g_default() + transitioned_after = 2 + return transitioned_after + + + def __clear_in_events(self): + """Implementation of __clear_in_events function. + """ + self.request_lvl_change = False + self.water_lvl = False + self.resume = False + self.door_obstructed = False + self.__time_events[0] = False + self.__time_events[1] = False + self.__time_events[2] = False + self.__time_events[3] = False + self.__time_events[4] = False + self.__time_events[5] = False + self.__time_events[6] = False + self.__time_events[7] = False + + + def __micro_step(self): + """Implementation of __micro_step function. + """ + transitioned = -1 + self.__state_conf_vector_position = 0 + state = self.__state_vector[0] + if state == self.State.main_region_or1a: + transitioned = self.__main_region_o_r1_a_react(transitioned) + elif state == self.State.main_region_or1b: + transitioned = self.__main_region_o_r1_b_react(transitioned) + if self.__state_conf_vector_position < 1: + state = self.__state_vector[1] + if state == self.State.main_region_or2c: + transitioned = self.__main_region_o_r2_c_react(transitioned) + elif state == self.State.main_region_or2d: + transitioned = self.__main_region_o_r2_d_react(transitioned) + elif state == self.State.main_region_or2e: + transitioned = self.__main_region_o_r2_e_react(transitioned) + elif state == self.State.main_region_or2f: + transitioned = self.__main_region_o_r2_f_react(transitioned) + if self.__state_conf_vector_position < 2: + state = self.__state_vector[2] + if state == self.State.main_region_or3g: + self.__main_region_o_r3_g_react(transitioned) + elif state == self.State.main_region_or3h: + self.__main_region_o_r3_h_react(transitioned) + + + def run_cycle(self): + """Implementation of run_cycle function. + """ + #Performs a 'run to completion' step. + if self.timer_service is None: + raise ValueError('Timer service must be set.') + + if self.__is_executing: + return + self.__is_executing = True + next_event = self.__get_next_event() + if next_event is not None: + self.__execute_queued_event(next_event) + condition_0 = True + while condition_0: + self.__micro_step() + self.__clear_in_events() + condition_0 = False + next_event = self.__get_next_event() + if next_event is not None: + self.__execute_queued_event(next_event) + condition_0 = True + self.__is_executing = False + + + def enter(self): + """Implementation of enter function. + """ + #Activates the state machine. + if self.timer_service is None: + raise ValueError('Timer service must be set.') + + if self.__is_executing: + return + self.__is_executing = True + #Default enter sequence for statechart LockController + self.__enter_sequence_main_region_default() + self.__is_executing = False + + + def exit(self): + """Implementation of exit function. + """ + #Deactivates the state machine. + if self.__is_executing: + return + self.__is_executing = True + #Default exit sequence for statechart LockController + self.__exit_sequence_main_region() + self.__state_vector[0] = self.State.null_state + self.__state_vector[1] = self.State.null_state + self.__state_vector[2] = self.State.null_state + self.__state_conf_vector_position = 2 + self.__is_executing = False + + + def trigger_without_event(self): + """Implementation of triggerWithoutEvent function. + """ + self.run_cycle() + diff --git a/StartingPoint/srcgen/water_level_simulator.py b/StartingPoint/srcgen/water_level_simulator.py new file mode 100644 index 0000000..e0bd477 --- /dev/null +++ b/StartingPoint/srcgen/water_level_simulator.py @@ -0,0 +1,635 @@ +"""Implementation of statechart water_level_simulator. +Generated by itemis CREATE code generator. +""" + +import queue +import sys, os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib'))) +from yakindu.rx import Observable + +class WaterLevelSimulator: + """Implementation of the state machine WaterLevelSimulator. + """ + + class State: + """ State Enum + """ + ( + main_region_o, + main_region_or1no_flow, + main_region_or1low_flow, + main_region_or1b_no_flow, + main_region_or1b_high_flow, + main_region_or2sensor_good, + main_region_or2sensor_broken, + main_region_or3d, + null_state + ) = range(9) + + + def __init__(self): + """ Declares all necessary variables including list of states, histories etc. + """ + + self.open_flow = None + self.open_flow_value = None + self.close_flow = None + self.close_flow_value = None + self.toggle_sensor_broken = None + self.real_water_level = None + self.real_water_level_value = None + self.real_water_level_observable = Observable() + self.sensor_reading = None + self.sensor_reading_value = None + self.sensor_reading_observable = Observable() + + self.__internal_event_queue = queue.Queue() + self.in_event_queue = queue.Queue() + self.LOW = 0 + self.HIGH = 1 + self.LOW_LVL = 500 + self.HIGH_LVL = 1500 + self.FLOW_RATE = 50 + self.__water_level = None + self.water_lvl_changed = None + + # enumeration of all states: + self.__State = WaterLevelSimulator.State + self.__state_conf_vector_changed = None + self.__state_vector = [None] * 4 + for __state_index in range(4): + self.__state_vector[__state_index] = self.State.null_state + + # for timed statechart: + self.timer_service = None + self.__time_events = [None] * 2 + + # initializations: + #Default init sequence for statechart WaterLevelSimulator + self.__water_level = self.LOW_LVL + self.__is_executing = False + self.__state_conf_vector_position = None + + def is_active(self): + """Checks if the state machine is active. + """ + return self.__state_vector[0] is not self.__State.null_state or self.__state_vector[1] is not self.__State.null_state or self.__state_vector[2] is not self.__State.null_state or self.__state_vector[3] is not self.__State.null_state + + def is_final(self): + """Checks if the statemachine is final. + Always returns 'false' since this state machine can never become final. + """ + return False + + def is_state_active(self, state): + """Checks if the state is currently active. + """ + s = state + if s == self.__State.main_region_o: + return (self.__state_vector[0] >= self.__State.main_region_o)\ + and (self.__state_vector[0] <= self.__State.main_region_or3d) + if s == self.__State.main_region_or1no_flow: + return self.__state_vector[0] == self.__State.main_region_or1no_flow + if s == self.__State.main_region_or1low_flow: + return self.__state_vector[0] == self.__State.main_region_or1low_flow + if s == self.__State.main_region_or1b_no_flow: + return self.__state_vector[1] == self.__State.main_region_or1b_no_flow + if s == self.__State.main_region_or1b_high_flow: + return self.__state_vector[1] == self.__State.main_region_or1b_high_flow + if s == self.__State.main_region_or2sensor_good: + return self.__state_vector[2] == self.__State.main_region_or2sensor_good + if s == self.__State.main_region_or2sensor_broken: + return self.__state_vector[2] == self.__State.main_region_or2sensor_broken + if s == self.__State.main_region_or3d: + return self.__state_vector[3] == self.__State.main_region_or3d + return False + + def time_elapsed(self, event_id): + """Add time events to in event queue + """ + if event_id in range(2): + self.in_event_queue.put(lambda: self.raise_time_event(event_id)) + self.run_cycle() + + def raise_time_event(self, event_id): + """Raise timed events using the event_id. + """ + self.__time_events[event_id] = True + + def __execute_queued_event(self, func): + func() + + def __get_next_event(self): + if not self.__internal_event_queue.empty(): + return self.__internal_event_queue.get() + if not self.in_event_queue.empty(): + return self.in_event_queue.get() + return None + + + def raise_water_lvl_changed(self): + """Raise method for event water_lvl_changed. + """ + self.__internal_event_queue.put(self.__raise_water_lvl_changed_call) + + def __raise_water_lvl_changed_call(self): + """Raise callback for event water_lvl_changed. + """ + self.water_lvl_changed = True + + def raise_open_flow(self, value): + """Raise method for event open_flow. + """ + self.in_event_queue.put(lambda: self.__raise_open_flow_call(value)) + self.run_cycle() + + def __raise_open_flow_call(self, value): + """Raise callback for event open_flow. + """ + self.open_flow = True + self.open_flow_value = value + + def raise_close_flow(self, value): + """Raise method for event close_flow. + """ + self.in_event_queue.put(lambda: self.__raise_close_flow_call(value)) + self.run_cycle() + + def __raise_close_flow_call(self, value): + """Raise callback for event close_flow. + """ + self.close_flow = True + self.close_flow_value = value + + def raise_toggle_sensor_broken(self): + """Raise method for event toggle_sensor_broken. + """ + self.in_event_queue.put(self.__raise_toggle_sensor_broken_call) + self.run_cycle() + + def __raise_toggle_sensor_broken_call(self): + """Raise callback for event toggle_sensor_broken. + """ + self.toggle_sensor_broken = True + + def __entry_action_main_region_o_r1_low_flow(self): + """Entry action for state 'LowFlow'.. + """ + #Entry action for state 'LowFlow'. + self.timer_service.set_timer(self, 0, 100, False) + + def __entry_action_main_region_o_r1b_high_flow(self): + """Entry action for state 'HighFlow'.. + """ + #Entry action for state 'HighFlow'. + self.timer_service.set_timer(self, 1, 100, False) + + def __entry_action_main_region_o_r3_d(self): + """Entry action for state 'D'.. + """ + #Entry action for state 'D'. + self.real_water_level_observable.next(self.__water_level) + self.sensor_reading_observable.next(99000 if (self.__state_vector[2] == self.State.main_region_or2sensor_broken) else self.__water_level) + + def __exit_action_main_region_o_r1_low_flow(self): + """Exit action for state 'LowFlow'.. + """ + #Exit action for state 'LowFlow'. + self.timer_service.unset_timer(self, 0) + + def __exit_action_main_region_o_r1b_high_flow(self): + """Exit action for state 'HighFlow'.. + """ + #Exit action for state 'HighFlow'. + self.timer_service.unset_timer(self, 1) + + def __enter_sequence_main_region_o_default(self): + """'default' enter sequence for state O. + """ + #'default' enter sequence for state O + self.__enter_sequence_main_region_o_r1_default() + self.__enter_sequence_main_region_o_r1b_default() + self.__enter_sequence_main_region_o_r2_default() + self.__enter_sequence_main_region_o_r3_default() + + def __enter_sequence_main_region_o_r1_no_flow_default(self): + """'default' enter sequence for state NoFlow. + """ + #'default' enter sequence for state NoFlow + self.__state_vector[0] = self.State.main_region_or1no_flow + self.__state_conf_vector_position = 0 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r1_low_flow_default(self): + """'default' enter sequence for state LowFlow. + """ + #'default' enter sequence for state LowFlow + self.__entry_action_main_region_o_r1_low_flow() + self.__state_vector[0] = self.State.main_region_or1low_flow + self.__state_conf_vector_position = 0 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r1b_no_flow_default(self): + """'default' enter sequence for state NoFlow. + """ + #'default' enter sequence for state NoFlow + self.__state_vector[1] = self.State.main_region_or1b_no_flow + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r1b_high_flow_default(self): + """'default' enter sequence for state HighFlow. + """ + #'default' enter sequence for state HighFlow + self.__entry_action_main_region_o_r1b_high_flow() + self.__state_vector[1] = self.State.main_region_or1b_high_flow + self.__state_conf_vector_position = 1 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_sensor_good_default(self): + """'default' enter sequence for state SensorGood. + """ + #'default' enter sequence for state SensorGood + self.__state_vector[2] = self.State.main_region_or2sensor_good + self.__state_conf_vector_position = 2 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r2_sensor_broken_default(self): + """'default' enter sequence for state SensorBroken. + """ + #'default' enter sequence for state SensorBroken + self.__state_vector[2] = self.State.main_region_or2sensor_broken + self.__state_conf_vector_position = 2 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_o_r3_d_default(self): + """'default' enter sequence for state D. + """ + #'default' enter sequence for state D + self.__entry_action_main_region_o_r3_d() + self.__state_vector[3] = self.State.main_region_or3d + self.__state_conf_vector_position = 3 + self.__state_conf_vector_changed = True + + def __enter_sequence_main_region_default(self): + """'default' enter sequence for region main region. + """ + #'default' enter sequence for region main region + self.__react_main_region__entry_default() + + def __enter_sequence_main_region_o_r1_default(self): + """'default' enter sequence for region r1. + """ + #'default' enter sequence for region r1 + self.__react_main_region_o_r1__entry_default() + + def __enter_sequence_main_region_o_r1b_default(self): + """'default' enter sequence for region r1b. + """ + #'default' enter sequence for region r1b + self.__react_main_region_o_r1b__entry_default() + + def __enter_sequence_main_region_o_r2_default(self): + """'default' enter sequence for region r2. + """ + #'default' enter sequence for region r2 + self.__react_main_region_o_r2__entry_default() + + def __enter_sequence_main_region_o_r3_default(self): + """'default' enter sequence for region r3. + """ + #'default' enter sequence for region r3 + self.__react_main_region_o_r3__entry_default() + + def __exit_sequence_main_region_o_r1_no_flow(self): + """Default exit sequence for state NoFlow. + """ + #Default exit sequence for state NoFlow + self.__state_vector[0] = self.State.main_region_o + self.__state_conf_vector_position = 0 + + def __exit_sequence_main_region_o_r1_low_flow(self): + """Default exit sequence for state LowFlow. + """ + #Default exit sequence for state LowFlow + self.__state_vector[0] = self.State.main_region_o + self.__state_conf_vector_position = 0 + self.__exit_action_main_region_o_r1_low_flow() + + def __exit_sequence_main_region_o_r1b_no_flow(self): + """Default exit sequence for state NoFlow. + """ + #Default exit sequence for state NoFlow + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + + def __exit_sequence_main_region_o_r1b_high_flow(self): + """Default exit sequence for state HighFlow. + """ + #Default exit sequence for state HighFlow + self.__state_vector[1] = self.State.main_region_o + self.__state_conf_vector_position = 1 + self.__exit_action_main_region_o_r1b_high_flow() + + def __exit_sequence_main_region_o_r2_sensor_good(self): + """Default exit sequence for state SensorGood. + """ + #Default exit sequence for state SensorGood + self.__state_vector[2] = self.State.main_region_o + self.__state_conf_vector_position = 2 + + def __exit_sequence_main_region_o_r2_sensor_broken(self): + """Default exit sequence for state SensorBroken. + """ + #Default exit sequence for state SensorBroken + self.__state_vector[2] = self.State.main_region_o + self.__state_conf_vector_position = 2 + + def __exit_sequence_main_region_o_r3_d(self): + """Default exit sequence for state D. + """ + #Default exit sequence for state D + self.__state_vector[3] = self.State.main_region_o + self.__state_conf_vector_position = 3 + + def __exit_sequence_main_region(self): + """Default exit sequence for region main region. + """ + #Default exit sequence for region main region + state = self.__state_vector[0] + if state == self.State.main_region_or1no_flow: + self.__exit_sequence_main_region_o_r1_no_flow() + elif state == self.State.main_region_or1low_flow: + self.__exit_sequence_main_region_o_r1_low_flow() + state = self.__state_vector[1] + if state == self.State.main_region_or1b_no_flow: + self.__exit_sequence_main_region_o_r1b_no_flow() + elif state == self.State.main_region_or1b_high_flow: + self.__exit_sequence_main_region_o_r1b_high_flow() + state = self.__state_vector[2] + if state == self.State.main_region_or2sensor_good: + self.__exit_sequence_main_region_o_r2_sensor_good() + elif state == self.State.main_region_or2sensor_broken: + self.__exit_sequence_main_region_o_r2_sensor_broken() + state = self.__state_vector[3] + if state == self.State.main_region_or3d: + self.__exit_sequence_main_region_o_r3_d() + + def __react_main_region_o_r1__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r1_no_flow_default() + + def __react_main_region_o_r1b__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r1b_no_flow_default() + + def __react_main_region_o_r2__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r2_sensor_good_default() + + def __react_main_region_o_r3__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_r3_d_default() + + def __react_main_region__entry_default(self): + """Default react sequence for initial entry . + """ + #Default react sequence for initial entry + self.__enter_sequence_main_region_o_default() + + def __react(self, transitioned_before): + """Implementation of __react function. + """ + #State machine reactions. + return transitioned_before + + + def __main_region_o_react(self, transitioned_before): + """Implementation of __main_region_o_react function. + """ + #The reactions of state O. + return self.__react(transitioned_before) + + + def __main_region_o_r1_no_flow_react(self, transitioned_before): + """Implementation of __main_region_o_r1_no_flow_react function. + """ + #The reactions of state NoFlow. + transitioned_after = self.__main_region_o_react(transitioned_before) + if transitioned_after < 0: + if (self.open_flow) and (self.open_flow_value == self.LOW): + self.__exit_sequence_main_region_o_r1_no_flow() + self.__enter_sequence_main_region_o_r1_low_flow_default() + transitioned_after = 0 + return transitioned_after + + + def __main_region_o_r1_low_flow_react(self, transitioned_before): + """Implementation of __main_region_o_r1_low_flow_react function. + """ + #The reactions of state LowFlow. + transitioned_after = self.__main_region_o_react(transitioned_before) + if transitioned_after < 0: + if (self.close_flow) and (self.close_flow_value == self.LOW): + self.__exit_sequence_main_region_o_r1_low_flow() + self.__enter_sequence_main_region_o_r1_no_flow_default() + transitioned_after = 0 + elif (self.__time_events[0]) and (self.__water_level > self.LOW_LVL): + self.__exit_sequence_main_region_o_r1_low_flow() + self.__water_level = (((((self.__water_level * 5) + self.LOW_LVL)) / 6) - 1) + self.raise_water_lvl_changed() + self.__time_events[0] = False + self.__enter_sequence_main_region_o_r1_low_flow_default() + transitioned_after = 0 + return transitioned_after + + + def __main_region_o_r1b_no_flow_react(self, transitioned_before): + """Implementation of __main_region_o_r1b_no_flow_react function. + """ + #The reactions of state NoFlow. + transitioned_after = transitioned_before + if transitioned_after < 1: + if (self.open_flow) and (self.open_flow_value == self.HIGH): + self.__exit_sequence_main_region_o_r1b_no_flow() + self.__enter_sequence_main_region_o_r1b_high_flow_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r1b_high_flow_react(self, transitioned_before): + """Implementation of __main_region_o_r1b_high_flow_react function. + """ + #The reactions of state HighFlow. + transitioned_after = transitioned_before + if transitioned_after < 1: + if (self.close_flow) and (self.close_flow_value == self.HIGH): + self.__exit_sequence_main_region_o_r1b_high_flow() + self.__enter_sequence_main_region_o_r1b_no_flow_default() + transitioned_after = 1 + elif (self.__time_events[1]) and (self.__water_level < self.HIGH_LVL): + self.__exit_sequence_main_region_o_r1b_high_flow() + self.__water_level = (((((self.__water_level * 5) + self.HIGH_LVL)) / 6) + 1) + self.raise_water_lvl_changed() + self.__time_events[1] = False + self.__enter_sequence_main_region_o_r1b_high_flow_default() + transitioned_after = 1 + return transitioned_after + + + def __main_region_o_r2_sensor_good_react(self, transitioned_before): + """Implementation of __main_region_o_r2_sensor_good_react function. + """ + #The reactions of state SensorGood. + transitioned_after = transitioned_before + if transitioned_after < 2: + if self.toggle_sensor_broken: + self.__exit_sequence_main_region_o_r2_sensor_good() + self.raise_water_lvl_changed() + self.__enter_sequence_main_region_o_r2_sensor_broken_default() + transitioned_after = 2 + return transitioned_after + + + def __main_region_o_r2_sensor_broken_react(self, transitioned_before): + """Implementation of __main_region_o_r2_sensor_broken_react function. + """ + #The reactions of state SensorBroken. + transitioned_after = transitioned_before + if transitioned_after < 2: + if self.toggle_sensor_broken: + self.__exit_sequence_main_region_o_r2_sensor_broken() + self.raise_water_lvl_changed() + self.__enter_sequence_main_region_o_r2_sensor_good_default() + transitioned_after = 2 + return transitioned_after + + + def __main_region_o_r3_d_react(self, transitioned_before): + """Implementation of __main_region_o_r3_d_react function. + """ + #The reactions of state D. + transitioned_after = transitioned_before + if transitioned_after < 3: + if self.water_lvl_changed: + self.__exit_sequence_main_region_o_r3_d() + self.__enter_sequence_main_region_o_r3_d_default() + transitioned_after = 3 + return transitioned_after + + + def __clear_in_events(self): + """Implementation of __clear_in_events function. + """ + self.open_flow = False + self.close_flow = False + self.toggle_sensor_broken = False + self.__time_events[0] = False + self.__time_events[1] = False + + + def __clear_internal_events(self): + """Implementation of __clear_internal_events function. + """ + self.water_lvl_changed = False + + + def __micro_step(self): + """Implementation of __micro_step function. + """ + transitioned = -1 + self.__state_conf_vector_position = 0 + state = self.__state_vector[0] + if state == self.State.main_region_or1no_flow: + transitioned = self.__main_region_o_r1_no_flow_react(transitioned) + elif state == self.State.main_region_or1low_flow: + transitioned = self.__main_region_o_r1_low_flow_react(transitioned) + if self.__state_conf_vector_position < 1: + state = self.__state_vector[1] + if state == self.State.main_region_or1b_no_flow: + transitioned = self.__main_region_o_r1b_no_flow_react(transitioned) + elif state == self.State.main_region_or1b_high_flow: + transitioned = self.__main_region_o_r1b_high_flow_react(transitioned) + if self.__state_conf_vector_position < 2: + state = self.__state_vector[2] + if state == self.State.main_region_or2sensor_good: + transitioned = self.__main_region_o_r2_sensor_good_react(transitioned) + elif state == self.State.main_region_or2sensor_broken: + transitioned = self.__main_region_o_r2_sensor_broken_react(transitioned) + if self.__state_conf_vector_position < 3: + state = self.__state_vector[3] + if state == self.State.main_region_or3d: + self.__main_region_o_r3_d_react(transitioned) + + + def run_cycle(self): + """Implementation of run_cycle function. + """ + #Performs a 'run to completion' step. + if self.timer_service is None: + raise ValueError('Timer service must be set.') + + if self.__is_executing: + return + self.__is_executing = True + next_event = self.__get_next_event() + if next_event is not None: + self.__execute_queued_event(next_event) + condition_0 = True + while condition_0: + self.__micro_step() + self.__clear_in_events() + self.__clear_internal_events() + condition_0 = False + next_event = self.__get_next_event() + if next_event is not None: + self.__execute_queued_event(next_event) + condition_0 = True + self.__is_executing = False + + + def enter(self): + """Implementation of enter function. + """ + #Activates the state machine. + if self.timer_service is None: + raise ValueError('Timer service must be set.') + + if self.__is_executing: + return + self.__is_executing = True + #Default enter sequence for statechart WaterLevelSimulator + self.__enter_sequence_main_region_default() + self.__is_executing = False + + + def exit(self): + """Implementation of exit function. + """ + #Deactivates the state machine. + if self.__is_executing: + return + self.__is_executing = True + #Default exit sequence for statechart WaterLevelSimulator + self.__exit_sequence_main_region() + self.__state_vector[0] = self.State.null_state + self.__state_vector[1] = self.State.null_state + self.__state_vector[2] = self.State.null_state + self.__state_vector[3] = self.State.null_state + self.__state_conf_vector_position = 3 + self.__is_executing = False + + + def trigger_without_event(self): + """Implementation of triggerWithoutEvent function. + """ + self.run_cycle() +