Behavior
The behavior object represents a black box that will include some logic, in form of a python script, that can be used to model the component behavior. The behavior logic will be interfaced using variables that can be connected to other elements.
Variables will be defined inside the 'variable' element by their name, data type (smtk_...), type (input, output, constant or parameter) and optionally public or not.
Attributes
- Name: The name used when referring to this element from other places.
Mandatory children
- step_time: Step time to force the execution of the behavior logic periodically in seconds. If step_time is 0, behavior is executed when any input changes.
- Var: smtk_float
- Type: input
- Default value:
0.0
Optional children
- variable: Behavior variable linked to the python script in order to exchange information.
Selectable child
- script: Python script asset GUID.
- Var: smtk_guid
- Type: constant
- Default value:
<empty>
- File extension: .py
Available Python functionality
Python Libraries
- NumPy
- JSON
Python Classes
- tuple - Sequence type -
(value1, value2)
- dict - Dictionary Mapping type -
{key : value, key2 : value2}
- list - Sequence type -
[value1, value2]
- int - Integer number -
1
- float - Floating point number -
0.0
- bool - Boolean -
True/False
- str - Text -
"Value"
- range - Sequence type -
range(5)
Basic Python functions
- len() - Returns the length of an object.
- iter() - Returns an iterator object.
- print() - Prints to the standard output device.
- abs() - Returns the absolute value of a number.
- max() - Returns the largest item in an iterable.
- min() - Returns the smallest item in an iterable.
- round() - Rounds a number.
- chr() - Returns a character.
- ord() - Returns an integer number from a character.
- hex() - Converts a number to hexadecimal.
Specific methods
- Transform2Euler() - Converts transform in quaternion to transform in RPY angles in radians.
- Transform2Quat() - Converts RPY angles in radians to quaternion.
- on_{ParamterName}() - Method that is called when a variable of the type parameter is changed.
Reserved variable names
- initialize - smtk_bool - Returns true when workspace loads or reloads a component.
- clock - smtk_float - Time since emulation started in seconds.
- step_time - smtk_float - Used to force the execution of the behavior logic periodically in seconds. If step_time is 0, behavior is executed when any input changes.
Tip
If you are new to Python programming or want to know more, please check out our Academy course on Python.
Behavior structure
When a behavior is added to a component it contains the following code.
if initialize:
# Enter your initialization code here
# It will be executed after resetting the emulation
pass
else:
# Enter your behavior code here
# It will be executed depending on the step_time and input variable changes
pass
Initialize
When the component is loaded to or reloaded in a workspace the reserved boolean variable initialize is True. This can be used to control how the components shall behave when resetting the emulation or to declare and define outputs. For example, to reset an output variable named origin with data type smtk_transform the initialize part could look like this:
if initialize:
# Enter your initialization code here
# It will be executed after resetting the emulation
origin = [0, 0, 0, 0, 0, 0, 1]
else:
# Enter your behavior code here
# It will be executed depending on the step_time and input variable changes
pass
Regular execution
The regular behavior execution can be placed indented after the else:. For example, by using the reserved variables step_time to force execution and clock to change the origin during execution could look like this:
if initialize:
# Enter your initialization code here
# It will be executed after resetting the emulation
origin = [0, 0, 0, 0, 0, 0, 1]
else:
# Enter your behavior code here
# It will be executed depending on the step_time and input variable changes
step_time = 0.1 # Execute the behavior every 100 ms
origin[0] = clock # The X-position of the transform will move along the x-axis with 1 m/s.
Tip
There is an Academy course that will guide you through the steps required to edit the behavior of a component.
Behavior functions
Parameter change
For a variable of the type parameter a method can be added that is called when the parameter is changed, this method is declared as def on_{parameter name}(). In the example below two parameters are defined, driver_type and setup_params, when the user changes the driver_type in the workspace the setup_data variable is changed accordingly. This variable can be used in the regular parts of the behavior, as for the example below be used to set up a dictionary, setup_dict, for a communication driver. See the initialization part in the code snippet below.
Note that the variables that need to be accessed in the method need to be defined as globals to be able to use them outside of the local scope.
if initialize:
# Prepare setup telegram
setup_dict = {
'parameters': json.loads(setup_params),
'variables': {
input_variable : {'datatype': 'word', 'size': 1, 'operation': 'write'},
output_variable : {'datatype': 'word', 'size': 1, 'operation': 'read'}
}
}
# The code below is executed when the driver type is selected
def on_driver_type():
global driver_type, setup_params
if driver_type == "opcua_client":
setup_params = '{"url": "opc.tcp://localhost:4840"}'
elif driver_type == 's7protocol' or driver_type == 'allenbradley_logix':
setup_params = '{"ip": "192.168.0.1"}'
else:
setup_params = '{}'
Print to log
To print to the workspace log use the method print()
. By using a argument of type string this will show up in the workspace log. By also using a second parameter dest and set it to LOG_ERROR
it will display it as an error.
if initialize:
# Prints text to the workspace log
print("Initializing component.")
else:
if some_error_occured:
# Prints text in red to the workspace log and indicates error.
print("Error in component", dest="LOG_ERROR")


Using Transform2Quat and Transform2Euler
Handling transformation (positioning and rotation in 3D space) of objects, can be described in different methods, two common are Euler and Quaternions. A quaternion transform in the emulation platform is represented by an array of 7 elements: [x, y, z, qx,qy,qz,qw]
. And the euler transform is represented by an array of 6 elements: [x, y, z, roll, pitch, yaw]
. The emulation platform uses Quaternions to calculate transforms of objects. Quaternions can be tricky to calculate manually, therefore two built-in functions can be used.
Transform2Euler() - Converts transform in quaternion to transform in RPY angles in radians.
[x, y, z, roll, pitch, yaw] = Transform2Euler([x, y, z, qx,qy,qz,qw])
Transform2Quat() - Converts RPY angles in radians to quaternion.
[x, y, z, qx,qy,qz,qw] = Transform2Quat([x, y, z, roll, pitch, yaw])
Example
In the example below the output variable origin
is connected to the origin of a visual and the input variable user_action
is connected to the property user_action on the same visual. In the init section the origin of the component is set to 2 meters up along the components z-axis and rotated 180 degrees around the z-axis. In the regular execution part of the behavior the quaternion transform is being represented as an euler transform, and the yaw value adds 30 degrees on every user action. In the workspace this means that if the component is pressed the component rotates 30 degrees around its z-axis.
if initialize:
# Init the orientation of the component to origo
origin = Transform2Quat([0, 0, 2, 0, 0, 180 * numpy.pi /180])
else:
if user_action: # If the user presses the visual
euler = Transform2Euler(origin) # Save the provious value-
euler[5] += 30 * numpy.pi / 180 # Add 30 degrees to yaw (rotation around z-axis)
origin = Transform2Quat(euler) # Update the transform
Tip
In the above example the angular value in radians is calculated with angle in degrees * pi / 180. To calculate the value in degrees, multiplicate the value of angle in radians * 180 / pi
Creating and using custom classes
When creating a component that includes multiple objects of the same type, it might be a good idea to create objects or classes to keep the behavior simple and well structured. The emulation platform allows to create custom python classes. In the example below a class edge_detector
is created to handle the search for a rising edge on a sensor. The objects sensor1 and sensor2 is constructed in the if initialize:
part, in the regular execution the class methon detect_rising_edge is called for both sensors, if a rising edge is detected, it writes a message to the log.
class edge_detector:
last_status = False
def detect_rising_edge(self, new_status):
if new_status != self.last_status:
self.last_status = new_status
return True
else:
self.last_status = new_status
return False
if initialize:
sensor1 = edge_detector()
sensor2 = edge_detector()
else:
if sensor1.detect_rising_edge(sensor_status_1):
print("sensor1 detected new object!")
if sensor2.detect_rising_edge(sensor_status_2):
print("sensor2 detected new object!")