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