Each component is basically represented by two files, which must be created when adding a new sensor or actuator:
- A Blender file: has the representation of the sensor on blender, and associate the script with the blender object
- A Python script: contains the logic of the sensor
You need to implement a sub-class of morse.core.sensor.MorseSensorClass (respectively of morse.core.actuator.MorseActuatorClass)
Important things to do :
- in the constructor of the object (__init__), initialize each variable you want to expose to the world into local_data
- override default_action : it must contains the logic of our component. Avoid to do some big computation here : the function is called often, and it will slow down the whole processing of the Game Engine
- First, create a nice modelling of your object, and save it in $MORSE_ROOT/data/sensors/
- Press N to display the properties of the object. Change its name.
- Press F4 to enter in the logic mode
- Add the three following properties:
- a boolean property named Component_Tag ((other possible tags are Robot_Tag, Modifier_Tag and Middleware_Tag)) (the value of the property doesn’t matter)
- Class of type string, which contains the name of the associated python class <Sensor>Class (e.g. GyroscopeClass)
- Path of type string, which contains the path to the associated python script (within the src/ directory, and without the trailing .py): morse/sensors/<Sensor> (e.g. morse/sensors/gyroscope)
- You can add more properties if needed for your components. Additional properties can be used to configure the behaviour of the component, and can be integrated into a GUI in future versions of MORSE.
The names specified in the Path and Class properties must match exactly the location of the Python file and the name of the defined class, respectively. The information in these variables will be used to dynamically load the module and class during initialisation of the simulation.
A component is not really useful if it doesn’t get any input (for an actuator) or if you can’t use the output of a sensor. You can use different middleware to import / export data.
In the simplest case, you can use automatic serialization, which will try to convert the data in local_data OrderedDict into the appropriate format to send through the middleware. This works only for the basic data types of integer, float or string. If you want more specific behaviour for other data types, you need to add a method to the middleware provider of your choice (for example, if you want to export a new sensor through YARP, you need to add a method to MorseYarpClass, in $MORSE_ROOT/src/morse/middleware/yarp_mw.py). The method must have the following prototype ::
def your_method(self, component_instance):
For instance, a specific serialization method has been defined to serialize RGBA images for YARP ::
def post_image_RGBA(self, component_instance):
""" Send an RGBA image through the given named port."""
#...formatting the sensor data stored in component_instance.local_data
yarp_port.write()
(see $MORSE_ROOT/src/morse/middleware/yarp_mw.py for the complete method)
In this method, you can access / store component information through its dictionary local_data. In case of a sensor, it is not expected that you change the content of the sensor, but only read information in this array.
After that, you need to register your new function into the middleware abstraction. For that, you need to modify the method register_component. It is basically a switch case with the different possible functions. This method is called when parsing the configuration file for the scene, so it is the right place to initialize stuff (opening Yarp ports, sockets, files ...)
In MorseYarpClass, the different port_name are stored in a dictionary _component_ports, indexed by the name of the component (component.blender_obj.name). You can retrieve the associated port with the method getPort(port_name)
Example:
port_name = self._component_ports[component_instance.blender_obj.name]
try:
yarp_port = self.getPort(port_name)
except KeyError as detail:
print ("ERROR: Specified port does not exist: ", detail)
return
In MorsePocolibsClass, the different poster_id are stored in a dictionary _poster_dict, indexed by the name of the component (component.blender_obj.name)
In TextOutClass, the different files are stored in a dictionary _file_list, indexed by the name of the component (component.blender_obj.name)