Skill.cpp
Go to the documentation of this file.
1 #include "Skill.h"
2 
4 
6 
7 namespace armarx
8 {
9  namespace skills
10  {
11  Skill::Skill(const SkillDescription& desc) : description(desc), constructing(true)
12  {
13  // replace constructor if you want to have a specific logging tag
14  Logging::setTag("armarx::skills::" + description.skillId.toString());
15 
17  if (!desc.rootProfileDefaults->fullfillsType(desc.parametersType))
18  {
19  ARMARX_WARNING << "Missing root profile defaults for skill " << desc.skillId;
20  }
21  }
22 
23  // install a local condition via a lambda
24  void
25  Skill::installConditionWithCallback(std::function<bool()>&& f, std::function<void()>&& cb)
26  {
27  std::scoped_lock l(conditionCallbacksMutex);
28  conditionCallbacks.push_back({f, cb});
29  }
30 
31  std::optional<TerminatedSkillStatusUpdate>
32  Skill::callSubskill(const SkillProxy& proxy)
33  {
34  return callSubskill(proxy, proxy.getRootProfileParameters());
35  }
36 
37  std::optional<TerminatedSkillStatusUpdate>
38  Skill::callSubskill(const SkillProxy& proxy, const aron::data::DictPtr& parameters)
39  {
40  auto executionId = callSubskillAsync(proxy, parameters);
41  auto ret = proxy.join(executionId);
42 
43  // While the sub skill was running, our skill might also have been aborted.
44  // In this case, the correct behavour would be aborting ourselves.
45  // The caller of callSubskill() can catch the thrown error::SkillAbortedException
46  // if necessary.
47  throwIfSkillShouldTerminate();
48  return ret;
49  }
50 
52  Skill::callSubskillAsync(const skills::SkillProxy& prx)
53  {
54  return callSubskillAsync(prx, prx.getRootProfileParameters());
55  }
56 
58  Skill::callSubskillAsync(const skills::SkillProxy& prx, const aron::data::DictPtr& params)
59  {
60  std::string executorHistory = this->executorName + "->" + getSkillId().toString();
61 
62  throwIfSkillShouldTerminate();
63  auto eid = prx.executeSkillAsync(executorHistory, params);
64 
65  std::unique_lock l(subskillsMutex);
66  throwIfSkillShouldTerminate(
67  [&]()
68  {
69  prx.abortSkillAsync(eid);
70  }); // also notify newly added skill as it was not added to subskills list yet
71  this->subskills.push_back(eid);
72  return eid;
73  }
74 
75  std::optional<TerminatedSkillStatusUpdate>
76  Skill::callSubskill(const SkillID& skillId)
77  {
78  return callSubskill(SkillProxy(manager, skillId));
79  }
80 
81  std::optional<TerminatedSkillStatusUpdate>
82  Skill::callSubskill(const SkillID& skillId, const aron::data::DictPtr& parameters)
83  {
84  return callSubskill(SkillProxy(manager, skillId), parameters);
85  }
86 
87  std::optional<TerminatedSkillStatusUpdate>
88  Skill::callSubskill(const SkillID& skillId,
89  std::function<void(aron::data::DictPtr&)> parametersFunction)
90  {
91  SkillProxy proxy(manager, skillId);
92 
93  aron::data::DictPtr parameters = proxy.getRootProfileParameters();
94  if (not parameters)
95  {
96  parameters = armarx::aron::make_dict();
97  }
98 
99  parametersFunction(parameters);
100 
101  return callSubskill(proxy, parameters);
102  }
103 
104  skills::SkillExecutionID
105  Skill::callSubskillAsync(const SkillID& skillId,
106  std::function<void(aron::data::DictPtr&)> parametersFunction)
107  {
108  SkillProxy proxy(manager, skillId);
109 
110  aron::data::DictPtr parameters = proxy.getRootProfileParameters();
111  if (not parameters)
112  {
113  parameters = armarx::aron::make_dict();
114  }
115 
116  parametersFunction(parameters);
117 
118  return callSubskillAsync(proxy, parameters);
119  }
120 
121  void
122  Skill::updateParameters(const aron::data::DictPtr& d)
123  {
124  std::scoped_lock l(this->parametersMutex);
125  if (this->parameters == nullptr)
126  {
127  // set params as there has been no update before.
128  this->parameters = d;
129  }
130  else
131  {
132  // merge params into existing. Note that this may update already set params.
133  this->parameters->mergeAndReplaceCopy(d);
134  }
135  }
136 
137  void
138  Skill::setParameters(const aron::data::DictPtr& d)
139  {
140  // we only set the params if the skill is not already running
141  if (running or exiting or finished)
142  {
143  return;
144  }
145 
146  std::scoped_lock l(this->parametersMutex);
147  this->parameters = d;
148  }
149 
151  Skill::getParameters() const
152  {
153  return this->parameters;
154  }
155 
157  Skill::_init()
158  {
159  //ARMARX_IMPORTANT << "Initializing skill '" << description.skillName << "'";
160  this->initializing = true;
161  this->constructing = false;
162  this->preparing = false;
163  this->running = false;
164  this->exiting = false;
165  this->finished = false;
166 
167  // install timeout condition
168  installConditionWithCallback(
169  [&]()
170  {
171  return (armarx::core::time::DateTime::Now() >= (started + description.timeout));
172  },
173  [&]() { notifyTimeoutReached(); });
174 
175  conditionCheckingThread = std::thread(
176  [&]()
177  {
178  armarx::core::time::Metronome metronome(conditionCheckingThreadFrequency);
179  while (initializing or preparing or
180  running) // when the skill ends/aborts this variable will be set to false
181  {
182  {
183  std::scoped_lock l(conditionCallbacksMutex);
184  for (auto& p : conditionCallbacks)
185  {
186  auto& f = p.first;
187  auto& cb = p.second;
188  if (f())
189  {
190  cb();
191  }
192  }
193  }
194 
195  const auto sleepDuration = metronome.waitForNextTick();
196  if (not sleepDuration.isPositive())
197  {
199  << "ConditionCheckingThread: execution took too long ("
200  << -sleepDuration << " vs "
201  << conditionCheckingThreadFrequency.toCycleDuration()
202  << ")";
203  }
204  }
205  });
206  return {.status = TerminatedSkillStatus::Succeeded};
207  }
208 
209  Skill::PrepareResult
210  Skill::_prepare()
211  {
212  this->preparing = true;
213  this->initializing = false;
214  this->constructing = false;
215  this->running = false;
216  this->exiting = false;
217  this->finished = false;
218 
219  if (shouldSkillTerminate())
220  {
221  return {.status = ActiveOrTerminatedSkillStatus::Aborted};
222  }
223 
224  // Default nothing to prepare
225  if (not description.parametersType)
226  {
227  return {.status = ActiveOrTerminatedSkillStatus::Succeeded};
228  }
229  if (this->parameters && this->parameters->fullfillsType(description.parametersType))
230  {
231  // wait until parameters fulfill type
232  return {.status = ActiveOrTerminatedSkillStatus::Succeeded};
233  }
234 
235  // false if we have to wait for parameters
236  return {.status = ActiveOrTerminatedSkillStatus::Running};
237  }
238 
239  Skill::MainResult
240  Skill::_main()
241  {
242  this->running = true;
243  this->initializing = false;
244  this->constructing = false;
245  this->preparing = false;
246  this->exiting = false;
247  this->finished = false;
248  return {.status = TerminatedSkillStatus::Succeeded};
249  }
250 
251  Skill::ExitResult
252  Skill::_exit()
253  {
254  // ARMARX_IMPORTANT << "Exiting Skill '" << description.skillName << "'";
255  this->exiting = true;
256  this->running = false;
257  this->initializing = false;
258  this->constructing = false;
259  this->preparing = false;
260  this->finished = false;
261 
262  if (conditionCheckingThread.joinable())
263  {
264  conditionCheckingThread.join();
265  }
267 
268  this->finished = true;
269  this->exiting = false;
270  return {.status = TerminatedSkillStatus::Succeeded};
271  }
272 
273  Skill::InitResult
274  Skill::initSkill()
275  {
276  std::scoped_lock l(parametersMutex);
277  auto _res = this->_init();
278  auto res = this->init();
279  return {.status = skills::mergeSkillStatuseses(_res.status, res.status)};
280  }
281 
283  Skill::prepareSkill()
284  {
285  std::scoped_lock l(parametersMutex);
286  auto _res = this->_prepare();
287  auto res = this->prepare();
288  return {.status = skills::mergeSkillStatuseses(_res.status, res.status)};
289  }
290 
292  Skill::mainOfSkill()
293  {
294  std::scoped_lock l(parametersMutex);
295  auto _res = this->_main();
296  auto res = this->main();
297  return {.status = skills::mergeSkillStatuseses(_res.status, res.status),
298  .data = res.data};
299  }
300 
302  Skill::exitSkill()
303  {
304  std::scoped_lock l(parametersMutex);
305  auto res = this->exit();
306  auto _res = this->_exit();
307  return {.status = skills::mergeSkillStatuseses(_res.status, res.status)};
308  }
309 
310  void
311  Skill::throwIfSkillShouldTerminate(const std::function<void()>& do_before,
312  const std::string& abortedMessage)
313  {
314  if (shouldSkillTerminate())
315  {
316  do_before();
317  throwIfSkillShouldTerminate(abortedMessage);
318  }
319  }
320 
321  void
322  Skill::throwIfSkillShouldTerminate(const std::string& abortedMessage)
323  {
324  if (stopped)
325  {
326  std::string message =
327  std::string("The skill '" + getSkillId().toString() + "' was asked to stop.");
328  message += abortedMessage.empty() ? "" : " Additional message: " + abortedMessage;
329 
330  throw error::SkillAbortedException(__PRETTY_FUNCTION__, message);
331  return;
332  }
333 
334  if (timeoutReached)
335  {
336  std::string message =
337  std::string("The skill '" + getSkillId().toString() + "' reached timeout.");
338  message += abortedMessage.empty() ? "" : " Additional message: " + abortedMessage;
339 
341  throw error::SkillFailedException(__PRETTY_FUNCTION__, message);
342  }
343  }
344 
346  Skill::MakeSucceededResult(aron::data::DictPtr data)
347  {
348  return MainResult{
349  .status = TerminatedSkillStatus::Succeeded,
350  .data = data,
351  };
352  }
353 
355  Skill::MakeFailedResult()
356  {
357  return MainResult{
358  .status = TerminatedSkillStatus::Failed,
359  .data = nullptr,
360  };
361  }
362 
364  Skill::MakeAbortedResult()
365  {
366  return MainResult{
367  .status = TerminatedSkillStatus::Aborted,
368  .data = nullptr,
369  };
370  }
371 
372  void
373  Skill::notifySkillToStop()
374  {
375  if (stopped)
376  {
377  // skill already got stopped. Ignore
378  return;
379  }
380  std::scoped_lock l(subskillsMutex);
381  stopped = true;
382  _onStopRequested();
383  onStopRequested();
384  }
385 
386  void
387  Skill::notifyTimeoutReached()
388  {
389  if (stopped || timeoutReached)
390  {
391  // skill already got timeoutReached. Ignore
392  return;
393  }
394 
395  std::scoped_lock l(subskillsMutex);
396  timeoutReached = true;
397  _onTimeoutReached();
398  onTimeoutReached();
399  }
400 
401  bool
402  Skill::shouldSkillTerminate() const
403  {
404  return stopped || timeoutReached;
405  }
406 
407  // condition effects
408  void
409  Skill::_onTimeoutReached()
410  {
411  // WE ASSUME THAT THE LOCK IS ALREADY TAKEN
412 
413  if (!manager)
414  {
415  return;
416  }
417 
418  for (const auto& execId : subskills)
419  {
420  manager->abortSkillAsync(execId.toManagerIce());
421  }
422  }
423 
424  void
425  Skill::_onStopRequested()
426  {
427  // WE ASSUME THAT THE LOCK IS ALREADY TAKEN
428 
429  if (!manager)
430  {
431  return;
432  }
433 
434  for (const auto& execId : subskills)
435  {
436  manager->abortSkillAsync(execId.toManagerIce());
437  }
438  }
439 
440  void
441  Skill::onTimeoutReached()
442  {
443  }
444 
445  void
446  Skill::onStopRequested()
447  {
448  }
449 
450  // always called before prepare (should not take longer than 100ms)
452  Skill::init()
453  {
454  // Default nothing to init
455  return {.status = TerminatedSkillStatus::Succeeded};
456  }
457 
458  // always called before main (should not take longer than 100ms)
460  Skill::prepare()
461  {
462  // Default nothing to prepare
463  return {.status = ActiveOrTerminatedSkillStatus::Succeeded};
464  }
465 
466  // always called after main or if skill fails (should not take longer than 100ms)
468  Skill::exit()
469  {
470  // Default nothing to exit
471  return {.status = TerminatedSkillStatus::Succeeded};
472  }
473 
476  {
477  // This is just a dummy implementation
478  ARMARX_IMPORTANT << "Dummy executing skill '" << description.skillId
479  << "'. Please overwrite this method.";
480  return {.status = TerminatedSkillStatus::Succeeded, .data = nullptr};
481  }
482 
483  void
484  Skill::setProviderId(const skills::ProviderID& pid)
485  {
486  description.skillId.providerId = pid;
487  }
488 
489  void
490  Skill::setCallback(const CallbackT& callback)
491  {
492  this->callback = callback;
493  }
494 
495  void
496  Skill::setManager(const manager::dti::SkillManagerInterfacePrx& manager)
497  {
498  this->manager = manager;
499  }
500 
501  void
502  Skill::setExecutorName(const std::string& executorName)
503  {
504  this->executorName = executorName;
505  }
506 
508  Skill::getSkillDescription() const
509  {
510  return description;
511  }
512 
513  SkillID
514  Skill::getSkillId() const
515  {
516  return description.skillId;
517  }
518 
519  Skill::~Skill()
520  {
521  //ARMARX_IMPORTANT << "DESTROY SKILL " << getSkillId();
522  }
523  } // namespace skills
524 } // namespace armarx
armarx::skills::SkillProxy::getRootProfileParameters
aron::data::DictPtr getRootProfileParameters() const
get the default parameters of the skill. TODO: Skill profiles in memory!
Definition: SkillProxy.cpp:125
armarx::skills::Skill::ExitResult::status
TerminatedSkillStatus status
Definition: Skill.h:48
armarx::skills::SkillExecutionID
Definition: SkillExecutionID.h:15
armarx::aron::ret
ReaderT::InputType T & ret
Definition: rw.h:13
skills
This file is part of ArmarX.
Exception.h
ARMARX_IMPORTANT
#define ARMARX_IMPORTANT
Definition: Logging.h:190
armarx::skills::Skill::PrepareResult
A result struct for skill preparing.
Definition: Skill.h:33
armarx::skills::error::SkillAbortedException
Definition: Exception.h:53
armarx::aron::make_dict
aron::data::DictPtr make_dict(_Args &&... args)
Definition: Dict.h:107
armarx::skills::SkillDescription
Definition: SkillDescription.h:17
armarx::skills::Skill::Skill
Skill()=delete
We completely remove the default constructor! A skill without a desciption cannot exist.
armarx::core::time::DateTime::Now
static DateTime Now()
Definition: DateTime.cpp:51
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
KITProsthesis::ProsthesisState::Running
@ Running
Definition: KITProstheticHandInterface.ice:43
armarx::skills::mergeSkillStatuseses
TerminatedSkillStatus mergeSkillStatuseses(const TerminatedSkillStatus t1, const TerminatedSkillStatus t2)
Definition: SkillStatusUpdate.cpp:42
armarx::control::common::MPStatus::finished
@ finished
message
message(STATUS "Boost-Library-Dir: " "${Boost_LIBRARY_DIRS}") message(STATUS "Boost-LIBRARIES
Definition: CMakeLists.txt:8
armarx::skills::Skill::CallbackT
std::function< void(const SkillStatus s, const armarx::aron::data::DictPtr &)> CallbackT
Definition: Skill.h:24
armarx::skills::error::SkillFailedException
Definition: Exception.h:61
armarx::skills::SkillProxy
Definition: SkillProxy.h:13
armarx::skills::Skill::PrepareResult::status
ActiveOrTerminatedSkillStatus status
Definition: Skill.h:35
armarx::skills::SkillExecutionID::toString
std::string toString() const
Definition: SkillExecutionID.cpp:50
deactivateSpam
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition: Logging.cpp:75
armarx::skills::ProviderID
Definition: ProviderID.h:12
Skill.h
data
uint8_t data[1]
Definition: EtherCATFrame.h:68
armarx::skills::SkillDescription::skillId
SkillID skillId
Definition: SkillDescription.h:19
Metronome.h
armarx::skills::Skill::MainResult
A result struct for th main method of a skill.
Definition: Skill.h:39
armarx::skills::SkillDescription::rootProfileDefaults
aron::data::DictPtr rootProfileDefaults
Definition: SkillDescription.h:21
armarx::skills::Skill::ExitResult
A result struct for skill exit function.
Definition: Skill.h:46
armarx::aron::data::DictPtr
std::shared_ptr< Dict > DictPtr
Definition: Dict.h:41
armarx::skills::Skill::MainResult::status
TerminatedSkillStatus status
Definition: Skill.h:41
armarx::skills::SkillProxy::executeSkillAsync
SkillExecutionID executeSkillAsync(const std::string &executorName, const aron::data::DictPtr &params=nullptr) const
execute a skill. Do not block during execution
Definition: SkillProxy.cpp:67
armarx::skills::Skill::InitResult::status
TerminatedSkillStatus status
Definition: Skill.h:29
armarx::core::time::Metronome
Simple rate limiter for use in loops to maintain a certain frequency given a clock.
Definition: Metronome.h:34
armarx::skills::Skill::InitResult
A result struct for skill initialization.
Definition: Skill.h:27
armarx::viz::toString
const char * toString(InteractionFeedbackType type)
Definition: Interaction.h:28
armarx::skills::SkillProxy::join
std::optional< TerminatedSkillStatusUpdate > join(const SkillExecutionID &executionId) const
poll execution status and block until its null or terminated
Definition: SkillProxy.cpp:81
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:193
armarx::skills::SkillDescription::parametersType
aron::type::ObjectPtr parametersType
Definition: SkillDescription.h:23
armarx::skills::SkillProxy::abortSkillAsync
bool abortSkillAsync(const SkillExecutionID &executionId) const
ask skill to abort ASAP
Definition: SkillProxy.cpp:117
armarx::skills::SkillID
Definition: SkillID.h:14
main
int main(int argc, char *argv[])
Definition: Admin.cpp:45
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:27