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 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 pan-tilt unit actuator that offers a method, 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:

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 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):

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 MORSE Builder API with the method morse.builder.abstractcomponent.AbstractComponent.add_overlay().

This method takes two parameters, the middleware to use (see 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:

#! /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 morse.middleware.ros.overlays.waypoints.

Name remapping

Overlays also allow you to redefine the component name by overloading the morse.core.abstractobject.AbstractObject.name() method.

Let’s complete our previous example:

# [...]

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.