Component overlays ================== A **component overlay** is a special *pseudo* component (sensor or actuator) that overrides the default component behaviour to better fit into a particular architecture. .. warning:: As of `MORSE 1.0`, only :doc:`services <../dev/services>` can be overridden. In future versions, the *stream-oriented* interfaces of components (like ROS topics or YARP port) may be overridable as well. Overlay example --------------- The example below presents a typical use for overlays: MORSE provides a :doc:`pan-tilt unit ` actuator that offers a method, :meth:`morse.actuators.ptu.PTU.set_pan_tilt`, to set the pan and tilt angles of the PTU. But in your architecture, you are using two different methods, ``SetTilt`` and ``SetPan``. The following overlay maps your functions to the default MORSE ones: .. code-block:: python from morse.core.services import service from morse.core.overlay import MorseOverlay from morse.core import status class MyPTU(MorseOverlay): def __init__(self, overlaid_object): # Call the constructor of the parent class MorseOverlay.__init__(self, overlaid_object) self.last_tilt = 0.0 self.last_pan = 0.0 @service def SetTilt(self, tilt): self.last_tilt = float(tilt) self.overlaid_object.set_tilt_pan(self.last_tilt, self.last_pan) @service def SetPan(self, pan): self.last_pan = float(pan) self.overlaid_object.set_tilt_pan(self.last_tilt, self.last_pan) You can save this overlay anywhere, for instance in a ``morse.my_overlays.py`` file, accessible from MORSE's Python path. For asynchronous services, it is a bit more complex, as we need to provide a callback. The :meth:`morse.core.overlay.MorseOverlay.chain_callback` takes care of this operation. You can pass an optional function to this method to modify the returned data, or modify the state of your object. If provided, the callback *must* take one parameter (a tuple ``(status, result)``) and return a new tuple ``(status, result)``: .. code-block:: python from morse.core.services import async_service from morse.core.overlay import MorseOverlay from morse.core import status class MyPTU(MorseOverlay): def __init__(self, overlaid_object): # Call the constructor of the parent class MorseOverlay.__init__(self, overlaid_object) self.last_tilt = 0.0 self.last_pan = 0.0 def format_pan_tilt_return(self, result): # this callback will be called when SetTilt or SetPan # completes. # It simply re-formats the return value of the asynchronous # services. status, value = result return (status, "PTU->{:.2f},{:.2f}".format(self.last_pan, self.last_tilt)) @async_service def SetTilt(self, tilt): self.last_tilt = float(tilt) self.overlaid_object.set_tilt_pan( self.chain_callback(self.format_pan_tilt_return), self.last_tilt, self.last_pan) @async_service def SetPan(self, pan): self.last_pan = float(pan) self.overlaid_object.set_tilt_pan( self.chain_callback(self.format_pan_tilt_return), self.last_tilt, self.last_pan) .. warning:: The behaviour is currently undefined in the case of a service name collision between the original sensor services and the services defined in the overlay. Scene setup ----------- With the MORSE Builder API ++++++++++++++++++++++++++ Components can easily be overlaid using the :doc:`MORSE Builder API <../user/builder>` with the method :meth:`morse.builder.abstractcomponent.AbstractComponent.add_overlay`. This method takes two parameters, the middleware to use (see :mod:`morse.builder.data` for the list of available options), and the full-qualified Python name of the overlay class (for instance, ``morse.my_overlays.MyPTU``) The following example is taken from one of the ROS unit-tests: .. code-block:: python #! /usr/bin/env morseexec from morse.builder import * robot = ATRV() waypoint = Waypoint() robot.append(waypoint) waypoint.add_overlay('ros', 'morse.middleware.ros.overlays.waypoints.WayPoint') env = Environment('indoors-1/indoor-1') Here, the ``waypoint`` actuator has been overlaid by the ``WayPoint`` class defined in the module :mod:`morse.middleware.ros.overlays.waypoints`. Name remapping -------------- Overlays also allow you to redefine the component name by overloading the :meth:`morse.core.abstractobject.AbstractObject.name` method. Let's complete our previous example: .. code-block:: python # [...] class MyPTU(MorseOverlay): # [...] def name(self): return "MyPTU" # [...] In this case, at initialization, a new (pseudo) component (called ``MyPTU`` in this case) is created, with services as defined in the overlay class. The original component is also created and remains available as usual.