Skip to content

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 = '{}'

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") 
alt text alt text

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

alt text

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!")