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