17 from threading
import Event
26 ScriptableBase is a superclass that has common functionality for Scriptable... callbacks. 28 It allows to set a default reply that can be overridden if need be, so custom replies can be defined 31 def __init__(self, name, goal_formatter=format, reply_formatter=format, default_reply=None, default_reply_delay=0):
33 Set up a ScriptableBase. Must be subclassed. 35 :param name: the action namespace the server should operate on (e.g. `move_base`) 36 :param goal_formatter: a function accepting a goal and returning a nice-looking summary str of it 37 :param reply_formatter: a function accepting a result and returning a nice-looking summary str of it 38 :param default_reply: optional. If set, this will be returned after default_result_delay, 39 otherwise a .reply* call is needed to send a reply. Overriding the default reply and 40 doing something custom is possible with the `custom_reply`-context manager 41 :param default_reply_delay: If default_result is defined, the Scriptable... waits for this delay before returning. 42 This delay is also used when no more specific reply_delay is specified in a reply_*-call 79 If the process is blocked by waiting for some Events to be set, stop sets those Events. 84 def reply(self, result, timeout=None, reply_delay=None, marker=None):
86 Reply to the next goal with the given result, after `reply_delay` amount of seconds. 87 An AssertionError is raised when a goal is not received within the given timeout. 89 :param result: an ...ActionResult of the type associated with the Action-type of this server 90 :param timeout: how long to wait for the goal? Defaults to None to wait indefinitely 91 :param reply_delay: how to to reply_delay/calculate on this goal before sending the reply 92 :param marker: A str that is printed in the output for easy reference between different replies 95 if reply_delay
is None:
98 print(
'\n######## {}.reply{} ###########'.format(self.
_name,
'({})'.format(marker)
if marker
else ''))
100 assert self.
_waiting_for is None,
"reply{} cannot follow an 'await_goal', use reply_directly".format(
'({})'.format(marker)
if marker
else '')
103 print(
"{}.reply{}: Waiting {}for goal...".format(self.
_name,
'({})'.format(marker)
if marker
else '',
104 str(timeout)+
's ' if timeout
is not None else ''))
106 assert self._request.wait(timeout), \
107 "{}.reply{} did not get a goal in time".format(self.
_name,
'({})'.format(marker)
if marker
else '')
109 self._request.clear()
110 print(
"{}.reply{}: Got goal: {}" 116 countdown_sleep(reply_delay, text=
"{}.reply{}: Think for {}s. ".format(self.
_name,
'({})'.format(marker)
if marker
else '', reply_delay) +
"Remaining {}s...")
122 print(
"{}.reply{}: Finished replying" 123 .format(self.
_name,
'({})'.format(marker)
if marker
else ''))
125 def reply_conditionally(self, condition, true_result, false_result, timeout=None, reply_delay=None, marker=None):
127 Reply one of two possibilities, based on a condition. This is a callable that, given a Goal, returns a bool 128 If True, then reply with the true_reply and vice versa. 129 An AssertionError is raised when a goal is not received within the given timeout. 131 :param condition: callable(...Goal) -> bool 132 :param true_result: a ...Result 133 :param false_result: a ...Result 134 :param timeout: seconds to wait for the goal. Defaults to None to wait indefinitely 135 :param reply_delay: Delay the reply by this amount of seconds 136 :param marker: A str that is printed in the output for easy reference between different replies 139 if reply_delay
is None:
142 print(
'\n######## {}.reply_conditionally{} ###########' 143 .format(self.
_name,
'({})'.format(marker)
if marker
else ''))
145 assert self.
_waiting_for is None,
"reply_conditionally{} cannot follow an 'await_goal', use reply_directly".format(
'({})'.format(marker)
if marker
else '')
148 print(
"{}.reply_conditionally{}: Waiting {}for goal..." 149 .format(self.
_name,
'({})'.format(marker)
if marker
else '',
150 str(timeout)+
's ' if timeout
is not None else ''))
151 assert self._request.wait(timeout),
"{}.reply_conditionally{} did not get a goal in time"\
152 .format(self.
_name,
'({})'.format(marker)
if marker
else '')
154 self._request.clear()
155 print(
"{}.reply_conditionally{}: Got goal: {}" 158 print(
"{}: Think for {}s...".format(self.
_name, reply_delay))
164 countdown_sleep(reply_delay, text=
"{}.reply_conditionally{}: Think for {}s. " 165 .format(self.
_name,
'({})'.format(marker)
if marker
else '', reply_delay) +
"Remaining {}s...")
172 print(
"{}.reply_conditionally{}: Finished replying" 173 .format(self.
_name,
'({})'.format(marker)
if marker
else ''))
179 Await a goal to be sent to this Scriptable... and return that goal for close inspection. 180 Based on that, send a reply via `direct_reply` 181 An AssertionError is raised when a goal is not received within the given timeout. 183 :param timeout: how long to wait for the goal? Defaults to None to wait indefinitely 184 :param marker: A str that is printed in the output for easy reference between different replies 185 :return: the received goal 187 print(
'\n######## {}.await_goal{} ###########' 188 .format(self.
_name,
'({})'.format(marker)
if marker
else ''))
191 print(
"{}.await_goal{}: Waiting {}for goal..." 192 .format(self.
_name,
'({})'.format(marker)
if marker
else '',
193 str(timeout)+
's ' if timeout
is not None else ''))
194 assert self._request.wait(timeout),
"{}.await_goal{} did not get a goal in time".format(self.
_name,
'({})'.format(marker)
if marker
else '')
195 self._request.clear()
196 print(
"{}.await_goal{}: Got goal: {}" 205 Reply to the current goal with the given result, after `reply_delay` amount of seconds. 207 :param result: a ...Result of the type associated with the type of this server 208 :param reply_delay: how long to 'reply_delay/calculate' on this goal before sending the reply 209 :param marker: A str that is printed in the output for easy reference between different replies 211 assert self.
_waiting_for ==
'direct_reply',
"reply cannot follow an 'await_goal', use reply_directly" 213 if reply_delay
is None:
220 .format(self.
_name,
'({})'.format(marker)
if marker
else '', reply_delay) +
"Remaining {}s...")
228 print(
"{}.direct_reply{}: Finished replying".format(self.
_name,
'({})'.format(marker)
if marker
else ''))
233 The Result that is currently set to be returned by default 237 @default_reply.setter
240 Set the current default reply. 241 If this is None, there will not be a default reply, then a reply must be defined via a reply*-call 243 :param result: a ...Result of the type associated with the type of this server 250 Wait this amount of time before returning the default reply 254 @default_reply_delay.setter
256 """Set the delay after which the `default_result` is sent 258 :param delay number of seconds to wait before sending the `default_result`""" 261 @contextlib.contextmanager
264 Use this context manager to temporarily define your own replies and go back to defaulting outside the context 268 >>> server = ScriptableBase() 269 >>> server.default_result = True 270 >>> # Do other stuff that does not require special attention of server 271 >>> with server.custom_reply(): 272 >>> server.reply(False) 273 >>> # Continue to do other stuff that does not require special attention of server 283 Clear the log of goals that we've received 290 The goal we've received last 297 all goals received (since the last `clear_goals()` call 303 Add a callback that is called just before a result is being sent back 305 :param callback: callable that will receive the goal and result. The return value of the callable is discarded 307 assert isinstance(callback, collections.Callable)
312 Trigger all callback in the order they were added, with the current goal and the reply to it 314 :param goal: current goal 315 :param reply: reply to that goal 322 Find out if this server has any goal in it's history that is also in `match_against` 324 :param match_against: We're looking for any of the items in `match_against` 325 :param key: optionally transform the received goals with this callable into the same type as `match_against`'s elements 326 :return: The matching elements 328 assert isinstance(key, collections.Callable),
"key must be a callable" 331 return set(match_against).intersection(set(processed_previous_goals))
333 @contextlib.contextmanager
336 Remember goals only instance of this context. 337 Goals before this context are forgotten; 338 Goals received during/in the context only are remembered; 339 after the context everything is forgotten.