This chapter describes the required steps for generating Python code with itemis CREATE. Furthermore, all components of the generated code will be described in detail and each configurable generator feature will be explained.
Table of content:
We will use the following example to explain the code generation, the generated code and the integration with some application code. The example model describes a light switch with two states and the following behavior:
The ‘Python Code Generation’ example can be found in the example wizard:
File ->
New ->
Example... ->
CREATE Statechart Examples ->
Getting Started – Code Generation ->
Python Code Generation
Statechart example model
Generating Python code from a statechart requires a generator file (.sgen). It must at least specify the
create::python generator, reference a statechart and define the
targetProject and
targetFolder in the
Outlet feature. By specifying these attributes, Python state machine code can be generated.
Example:
GeneratorModel for create::python {
statechart LightSwitch {
feature Outlet {
targetProject = "com.yakindu.examples.python.lightswitch"
targetFolder = "src-gen"
}
}
}
You can create a generator model with the CREATE Statechart generator model wizard by selecting File → New → Code generator model.
The code generation is performed automatically whenever the statechart or the generator file is modified. See also chapter Running a generator for more information.
Generated code files can be categorized into base and library files.
The base source file is the implementation of the state machine model as a Python class. It is generated into the folder defined by the targetFolder parameter. Its package is composed of the basePackage parameter and the statechart’s namespace. The file name is derived from the statechart name.
Library files are independent of the concrete state machine model. They are generated into the libraryTargetFolder , or if that one is not defined, into the targetFolder . Their package is yakindu if not otherwise defined by the libraryPackage parameter.
The Python code generator translates the state machine model into a Python class.
The state machine class contains fundamental methods to enter and exit the state machine, as well as a method to execute a run-to-completion step. For the light switch example, these functions are generated as follows:
class LightSwitch:
def __init__(self):
""" Declares all necessary variables including list of states, histories etc.
"""
self.user = LightSwitch.User(self)
self.light = LightSwitch.Light(self)
...
# for timed statechart:
self.timer_service = None
...
def enter(self):
...
def exit(self):
...
def run_cycle(self):
...
Furthermore, the class defines methods to check whether the state machine is active, final or whether a specific state is active:
def is_active(self):
...
def is_final(self):
...
def is_state_active(self, state):
...
false
.
The state machine class allows to raise events and access variables which are defined in the statechart’s interface declarations. Each named statechart interface is reflected by an inner class. For the example model above, the two statechart interfaces user and light are transformed into the two classes User and Light:
class User:
"""Implementation of scope User.
"""
def __init__(self, statemachine):
self.on_button = None
self.off_button = None
self.statemachine = statemachine
def raise_on_button(self):
self.statemachine._LightSwitch__in_event_queue.put(self.raise_on_button_call)
self.statemachine.run_cycle()
def raise_on_button_call(self):
self.on_button = True
def raise_off_button(self):
self.statemachine._LightSwitch__in_event_queue.put(self.raise_off_button_call)
self.statemachine.run_cycle()
def raise_off_button_call(self):
self.off_button = True
class Light:
"""Implementation of scope Light.
"""
def __init__(self, statemachine):
self.brightness = None
self.on = None
self.on_observable = Observable()
self.off = None
self.off_observable = Observable()
self.statemachine = statemachine
def __raise_on(self):
self.on = True
def __raise_off(self):
self.off = True
In this example code you can see the following:
A statechart can also define an unnamed interface. In this case, all the members and methods are defined directly in the state machine class.
Bringing it all together, the resulting class for the light switch example looks like the following. For documentation purposes we only show the public members here:
# Implementation of statechart light_switch.
from yakindu.rx import Observable
import queue
class LightSwitch:
class State:
(
main_region_off,
main_region_on,
null_state
) = range(3)
class User:
def __init__(self, statemachine):
self.on_button = None
self.off_button = None
self.statemachine = statemachine
def raise_on_button(self):
...
def raise_off_button(self):
...
class Light:
def __init__(self, statemachine):
self.brightness = None
self.on = None
self.on_observable = Observable()
self.off = None
self.off_observable = Observable()
self.statemachine = statemachine
def __init__(self):
""" Declares all necessary variables including list of states, histories etc.
"""
self.user = LightSwitch.User(self)
self.light = LightSwitch.Light(self)
...
# for timed statechart:
self.timer_service = None
...
def is_active(self):
...
def is_final(self):
...
def is_state_active(self, state):
...
def time_elapsed(self, event_id):
...
def raise_time_event(self, event_id):
...
def enter(self):
...
def exit(self):
...
def run_cycle(self):
...
The following code snippet demonstrates how to use the state machine API:
class Main:
def __init__(self):
# Instantiates the state machine
self.sm = LightSwitch()
def run(self):
# Enters the state machine; from this point on the state machine is ready to react on incoming event
self.sm.enter()
# Raises the on_button event in the state machine which causes the corresponding transition to be taken
self.sm.user.raise_on_button()
# Gets the value of the brightness variable
brightness = self.sm.light.brightness
# Exit the state machine
self.sm.exit()
There are basically two ways to access outgoing events, getters and observables. The desired option can be enabled in the generator model, see OutEventAPI .
The getter mechanism is straight forward and simply allows to check if an outgoing event is raised by calling an is_raised method that returns a boolean:
class LightSwitch:
...
class Light:
def is_raised_on(self):
return self.on
def is_raised_off(self):
return self.off
The observable mechanism is more complex to set up, but it allows to get notified whenever the event is raised. Thus, the client code does not need to check the event status explicitly. The client code basically needs to set an Observer object to the out event’s Observable.
First of all, we need to specify the Observer:
# Observer with callback for the light.on event
class LightOnObserver(Observer):
def next(self):
print("Light is on.")
Then, we need to instantiate the observer and subscribe it to the out event observable:
# Subscribes observers to the state machine's observables
self.lightOnObserver = self.LightOnObserver()
self.sm.light.on_observable.subscribe(self.lightOnObserver)
With that code, observer’s
next() function will be called whenever the out event
on in interface
light is raised by the state machine.
itemis CREATE supports
operations that are executed by a state machine as actions, but are implemented by client-side code.
As a simple example a function
myOp can be defined in the definition section of the
LightSwitch example:
interface:
operation myOp()
Calling the operation myOp in the On state of the LightSwitch example generates following operation call:
def entry_action_main_region_on(self):
self.sci_interface.operation_callback.my_op()
The operation callback must be set through the state machine’s API. At first, the Callback must be implemented:
class Callback
def _init_(self):
#empty constructor
pass
def my_op(self):
print('Operation myOp has been called by the state machine')
After this, the callback must be set while before running the state machine. This could be realized like this:
class Main:
def __init__(self):
self.sm = LightSwitch()
self.cb = Callback()
def setup(self):
self.sm.sci_interface.operation_callback = self.cb
self.sm.enter()
State machines using timed triggers, for example after 10 s, are timed and need a timer service. The timer service needs to be set by the client code before entering the state machine.
The python code generator comes with a timer out of the box. To generate the default timer, the property DefaultTimer can be set in GeneralFeatures.
feature GeneralFeatures {
DefaultTimer = true
}
Activating this feature provides a generated timer implementation ready to use for timed state machines. To use the timer, create an instance and hand it over using the state machine’s API.
from lightswitch.timer.sct_timer import Timer
class Main:
def __init__(self):
self.sm = LightSwitch()
self.timer = Timer()
def setup(self):
self.sm.set_timer_service(self.timer)
self.sm.enter()
The python code generator comes with a runtime service template. To generate it, set the RuntimeTemplate flag in GeneralFeatures.
feature GeneralFeatures {
RuntimeTemplate = true
}
Activating the runtime template feature generates a default_runtime.py file, which supports basic functionalities to run a state machine. Custom code for use case depending projects can be added at the commented areas. Calling the state machine using the template ensures initializing and entering the state machine. After this, the state machine will be called in a while True loop:
import sys, os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from lightswitch.lightswitch_statemachine import LightSwitch
class LightSwitchRuntime:
def __init__(self):
self.sm = LightSwitch()
# Enter custom init code here..
"""
Enter custom methods here..
"""
def setup(self):
""" Get statemachine ready and enter it.
"""
self.sm.enter()
def run(self):
""" Include your interface actions here
"""
while True:
# enter what you like to do
self.sm.run_cycle()
def shutdown(self):
""" Unset timer and exit statemachine.
"""
print('State machine shuts down.')
self.sm.exit()
print('Bye!')
if __name__ == "__main__":
sr = LightSwitchRuntime()
sr.setup()
sr.run()
sr.shutdown()
Beside the general code generator features, there are language specific generator features, which are listed in the following chapter.
The Naming feature allows the configuration of package names for the generated classes.
Naming is an optional feature.
Example:
feature Naming {
basePackage = "org.ourproject.sct.impl"
libraryPackage = "org.ourproject.sct.lib"
}
Base Package
Library Package
The GeneralFeatures feature allows to configure additional services to be generated along with the state machine. Per default, all parameters are false, meaning to disable the corresponding features, respectively.
GeneralFeatures is an optional feature.
Example:
feature GeneralFeatures {
DefaultTimer = true
RuntimeTemplate = true
}
Default Timer
Runtime Template
Using the PyPackaging feature allows the user to specify a setup.py file, which can be used for packaging.
Example:
feature PyPackaging {
CreateFiles = false
Author = "admin"
Version = "0.0.1"
ShortDescription = "Some description"
License = "WTFPL"
URL = "www.your-homepage.com"
}
}
The
PyPackaging feature allows the configuration of:
Create Files
Author
Version
Short Description
License
URL