1import logging; logger = logging.getLogger("morse." + __name__)
2logger.setLevel(logging.DEBUG)
3from abc import ABCMeta, abstractmethod
4from functools import partial
5from morse.core.abstractobject import AbstractObject
6from morse.core.exceptions import MorseRPCInvokationError
7
8class MorseOverlay(AbstractObject):
9    """ This class allows to define 'overlay'. An 'overlay' is a pseudo component
10    that masks a MORSE default component behind a custom facet (with for instance
11    custom signatures for services, ports, etc.).
12
13    This is especially useful when integrating MORSE into an existing robotic
14    architecture where components have custom services/ports/topics whose
15    signature does not match MORSE defaults.
16
17    As of MORSE 0.4, only services can currently be overlaid.
18    """
19
20    # Make this an abstract class
21    __metaclass__ = ABCMeta
22
23    def __init__ (self, overlaid_object):
24
25        AbstractObject.__init__(self)
26
27        # Fill in the data sent as parameters
28        self.overlaid_object = overlaid_object
29
30        if not self.overlaid_object:
31            logger.critical("[INTERNAL ERROR] An overlay can not be initialized before " + \
32            "the component it overlays!")
33
34    def _chain_callback(self, fn, result):
35        logger.debug("Calling " + self.name() + " chain callback")
36
37        if fn:
38            result = fn(result)
39
40        self.on_completion(result)
41        self.on_completion = None
42
43    def chain_callback(self, fn = None):
44        """ When calling a component asynchronous service from
45        an overlay, a callback (used to notify the client upon
46        service completion) must be passed through. This callback
47        does not usually appear in the service signature since it
48        is added by the ``@async_service`` decorator.
49
50        Under normal circumstances, you must use this method as
51        callback.
52
53        For instance, assume a ``Dummy`` component and an overlay
54        ``MyDummy``:
55
56        .. code-block:: python
57
58            class Dummy(MorseObject):
59                @async_service
60                def dummy_service(self, arg1):
61                    # Here, dummy_service has a callback parameter added
62                    # by the decorator
63                    pass
64
65            class MyDummy(MorseAbstractobject):
66                @async_service
67                def mydummy_service(self, arg1):
68                    # [...do smthg useful]
69
70                    # We call the overlaid asynchronous service
71                    # 'dummy_service' by passing a special callback
72                    # returned by 'self.chain_callback()'
73                    self.overlaid_object.dummy_service(self.chain_callback(), arg1)
74
75        ``chain_callback`` takes a functor as an optional parameter.
76        This functor is called after the (overlaid) service completion, but
77        just before notifying the simulator client.
78
79        It can be used for output formatting for instance.
80
81        The functor *must* take one single parameter (a tuple ``(status, result)``)
82        and must as well return a tuple ``(status, result)``.
83
84        .. code-block:: python
85
86            class MyDummy(MorseAbstractobject):
87
88                def mydummy_on_completion(self, result):
89                    # This functor - here a simple function - simply
90                    # format the result output.
91                    # It could do anything else.
92                    status, value = result
93                    return (status, " . ".join(value))
94
95                @async_service
96                def mydummy_service(self, arg1):
97                    self.overlaid_object.dummy_service(self.chain_callback(self.mydummy_on_completion), arg1)
98
99        :param fn: a functor to be called on service completion, before
100                    notifying the clients. Must have the following signature:
101                    (status, result) fn((status, result))
102
103        """
104        return partial(self._chain_callback, fn)
105
106
107    def name(self):
108        """ Returns the overlaid component name.
109
110        By default, the name of the class of the overlaid component.
111
112        Override this method in your overlay to expose an alternative name.
113        """
114        return self.overlaid_object.name()
115
116    def interrupt(self):
117        if self.overlaid_object.on_completion:
118            self.overlaid_object.interrupt()
119        else:
120            AbstractObject.interrupt(self)
121