QP/C++ 6.9.3
qep_hsm.cpp
Go to the documentation of this file.
1 
39 #define QP_IMPL // this is QP implementation
40 #include "qep_port.hpp" // QEP port
41 #ifdef Q_SPY // QS software tracing enabled?
42  #include "qs_port.hpp" // QS port
43  #include "qs_pkg.hpp" // QS facilities for pre-defined trace records
44 #else
45  #include "qs_dummy.hpp" // disable the QS software tracing
46 #endif // Q_SPY
47 #include "qassert.h" // QP embedded systems-friendly assertions
48 
49 
51 #define QEP_TRIG_(state_, sig_) \
52  ((*(state_))(this, &QEP_reservedEvt_[sig_]))
53 
55 #define QEP_EXIT_(state_) do { \
56  if (QEP_TRIG_(state_, Q_EXIT_SIG) == Q_RET_HANDLED) { \
57  QS_BEGIN_PRE_(QS_QEP_STATE_EXIT, qs_id) \
58  QS_OBJ_PRE_(this); \
59  QS_FUN_PRE_(state_); \
60  QS_END_PRE_() \
61  } \
62 } while (false)
63 
65 #define QEP_ENTER_(state_) do { \
66  if (QEP_TRIG_(state_, Q_ENTRY_SIG) == Q_RET_HANDLED) { \
67  QS_BEGIN_PRE_(QS_QEP_STATE_ENTRY, qs_id) \
68  QS_OBJ_PRE_(this); \
69  QS_FUN_PRE_(state_); \
70  QS_END_PRE_() \
71  } \
72 } while (false)
73 
74 
75 namespace QP {
76 
77 Q_DEFINE_THIS_MODULE("qep_hsm")
78 
79 //****************************************************************************
80 //char_t const versionStr[7] = QP_VERSION_STR;
81 
82 //****************************************************************************
83 enum : QSignal {
85  QEP_EMPTY_SIG_ = 0U
86 };
87 
88 //****************************************************************************
94 static QEvt const QEP_reservedEvt_[4] {
95 #ifdef Q_EVT_CTOR // Is the QEvt constructor provided?
96  QEvt(0U, QEvt::STATIC_EVT),
97  QEvt(1U, QEvt::STATIC_EVT),
98  QEvt(2U, QEvt::STATIC_EVT),
99  QEvt(3U, QEvt::STATIC_EVT)
100 #else // QEvt is a POD (Plain Old Datatype)
101  { 0U, 0U, 0U },
102  { 1U, 0U, 0U },
103  { 2U, 0U, 0U },
104  { 3U, 0U, 0U }
105 #endif
106 };
107 
108 
109 //****************************************************************************
117 QHsm::QHsm(QStateHandler const initial) noexcept {
118  m_state.fun = Q_STATE_CAST(&top);
119  m_temp.fun = initial;
120 }
121 
122 //****************************************************************************
127 }
128 
129 //****************************************************************************
139 void QHsm::init(void const * const e, std::uint_fast8_t const qs_id) {
141 
143  Q_REQUIRE_ID(200, (m_temp.fun != nullptr)
144  && (t == Q_STATE_CAST(&top)));
145 
146  // execute the top-most initial transition
147  QState r = (*m_temp.fun)(this, Q_EVT_CAST(QEvt));
148 
149  // the top-most initial transition must be taken
150  Q_ASSERT_ID(210, r == Q_RET_TRAN);
151 
154  QS_OBJ_PRE_(this); // this state machine object
155  QS_FUN_PRE_(t); // the source state
156  QS_FUN_PRE_(m_temp.fun); // the target of the initial transition
157  QS_END_PRE_()
158 
159  // drill down into the state hierarchy with initial transitions...
160  do {
161  QStateHandler path[MAX_NEST_DEPTH_]; // tran entry path array
162  std::int_fast8_t ip = 0; // entry path index
163 
164  path[0] = m_temp.fun;
165  static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
166  while (m_temp.fun != t) {
167  ++ip;
168  Q_ASSERT_ID(220,
169  ip < static_cast<std::int_fast8_t>(Q_DIM(path)));
170  path[ip] = m_temp.fun;
171  static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
172  }
173  m_temp.fun = path[0];
174 
175  // retrace the entry path in reverse (desired) order...
176  do {
177  QEP_ENTER_(path[ip]); // enter path[ip]
178  --ip;
179  } while (ip >= 0);
180 
181  t = path[0]; // current state becomes the new source
182 
183  r = QEP_TRIG_(t, Q_INIT_SIG); // execute initial transition
184 
185 #ifdef Q_SPY
186  if (r == Q_RET_TRAN) {
188  QS_OBJ_PRE_(this); // this state machine object
189  QS_FUN_PRE_(t); // the source state
190  QS_FUN_PRE_(m_temp.fun); // the target of the initial tran.
191  QS_END_PRE_()
192  }
193 #endif // Q_SPY
194 
195  } while (r == Q_RET_TRAN);
196 
198  QS_TIME_PRE_(); // time stamp
199  QS_OBJ_PRE_(this); // this state machine object
200  QS_FUN_PRE_(t); // the new active state
201  QS_END_PRE_()
202 
203  m_state.fun = t; // change the current active state
204  m_temp.fun = t; // mark the configuration as stable
205  static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
206 }
207 
208 //***************************************************************************/
224 QState QHsm::top(void * const me, QEvt const * const e) noexcept {
225  static_cast<void>(me); // unused parameter
226  static_cast<void>(e); // unused parameter
227  return Q_RET_IGNORED; // the top state ignores all events
228 }
229 
230 //****************************************************************************
242 void QHsm::dispatch(QEvt const * const e, std::uint_fast8_t const qs_id) {
245 
248  Q_REQUIRE_ID(400, (t != nullptr)
249  && (t == m_temp.fun));
250 
252  QS_TIME_PRE_(); // time stamp
253  QS_SIG_PRE_(e->sig); // the signal of the event
254  QS_OBJ_PRE_(this); // this state machine object
255  QS_FUN_PRE_(t); // the current state
256  QS_END_PRE_()
257 
258  QStateHandler s;
259  QState r;
260  // process the event hierarchically...
261  do {
262  s = m_temp.fun;
263  r = (*s)(this, e); // invoke state handler s
264 
265  if (r == Q_RET_UNHANDLED) { // unhandled due to a guard?
266 
268  QS_SIG_PRE_(e->sig); // the signal of the event
269  QS_OBJ_PRE_(this); // this state machine object
270  QS_FUN_PRE_(s); // the current state
271  QS_END_PRE_()
272 
273  r = QEP_TRIG_(s, QEP_EMPTY_SIG_); // find superstate of s
274  }
275  } while (r == Q_RET_SUPER);
276 
277  // transition taken?
278  if (r >= Q_RET_TRAN) {
280 
281  path[0] = m_temp.fun; // save the target of the transition
282  path[1] = t;
283  path[2] = s;
284 
285  // exit current state to transition source s...
286  for (; t != s; t = m_temp.fun) {
287  // exit handled?
288  if (QEP_TRIG_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {
290  QS_OBJ_PRE_(this); // this state machine object
291  QS_FUN_PRE_(t); // the exited state
293 
294  // find superstate of t
295  static_cast<void>(QEP_TRIG_(t, QEP_EMPTY_SIG_));
296  }
297  }
298 
299  std::int_fast8_t ip = hsm_tran(path, qs_id); // the HSM transition
300 
301 #ifdef Q_SPY
302  if (r == Q_RET_TRAN_HIST) {
303 
305  QS_OBJ_PRE_(this); // this state machine object
306  QS_FUN_PRE_(t); // the source of the transition
307  QS_FUN_PRE_(path[0]); // the target of the tran. to history
308  QS_END_PRE_()
309 
310  }
311 #endif // Q_SPY
312 
313  // retrace the entry path in reverse (desired) order...
314  for (; ip >= 0; --ip) {
315  QEP_ENTER_(path[ip]); // enter path[ip]
316  }
317  t = path[0]; // stick the target into register
318  m_temp.fun = t; // update the next state
319 
320  // drill into the target hierarchy...
321  while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN) {
322 
324  QS_OBJ_PRE_(this); // this state machine object
325  QS_FUN_PRE_(t); // the source (pseudo)state
326  QS_FUN_PRE_(m_temp.fun); // the target of the transition
327  QS_END_PRE_()
328 
329  ip = 0;
330  path[0] = m_temp.fun;
331 
332  // find superstate
333  static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
334 
335  while (m_temp.fun != t) {
336  ++ip;
337  path[ip] = m_temp.fun;
338  // find superstate
339  static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
340  }
341  m_temp.fun = path[0];
342 
343  // entry path must not overflow
344  Q_ASSERT_ID(410, ip < MAX_NEST_DEPTH_);
345 
346  // retrace the entry path in reverse (correct) order...
347  do {
348  QEP_ENTER_(path[ip]); // enter path[ip]
349  --ip;
350  } while (ip >= 0);
351 
352  t = path[0];
353  }
354 
355  QS_BEGIN_PRE_(QS_QEP_TRAN, qs_id)
356  QS_TIME_PRE_(); // time stamp
357  QS_SIG_PRE_(e->sig); // the signal of the event
358  QS_OBJ_PRE_(this); // this state machine object
359  QS_FUN_PRE_(s); // the source of the transition
360  QS_FUN_PRE_(t); // the new active state
361  QS_END_PRE_()
362  }
363 
364 #ifdef Q_SPY
365  else if (r == Q_RET_HANDLED) {
366 
368  QS_TIME_PRE_(); // time stamp
369  QS_SIG_PRE_(e->sig); // the signal of the event
370  QS_OBJ_PRE_(this); // this state machine object
371  QS_FUN_PRE_(s); // the source state
372  QS_END_PRE_()
373 
374  }
375  else {
376 
378  QS_TIME_PRE_(); // time stamp
379  QS_SIG_PRE_(e->sig); // the signal of the event
380  QS_OBJ_PRE_(this); // this state machine object
381  QS_FUN_PRE_(m_state.fun);// the current state
382  QS_END_PRE_()
383 
384  }
385 #endif // Q_SPY
386 
387  m_state.fun = t; // change the current active state
388  m_temp.fun = t; // mark the configuration as stable
389  static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
390 }
391 
392 //****************************************************************************
405  std::uint_fast8_t const qs_id)
406 {
407  std::int_fast8_t ip = -1; // transition entry path index
408  std::int_fast8_t iq; // helper transition entry path index
409  QStateHandler t = path[0];
410  QStateHandler const s = path[2];
411  QState r;
413 
414  // (a) check source==target (transition to self)
415  if (s == t) {
416  QEP_EXIT_(s); // exit the source
417  ip = 0; // enter the target
418  }
419  else {
420  // superstate of target
421  static_cast<void>(QEP_TRIG_(t, QEP_EMPTY_SIG_));
422  t = m_temp.fun;
423 
424  // (b) check source==target->super
425  if (s == t) {
426  ip = 0; // enter the target
427  }
428  else {
429  // superstate of src
430  static_cast<void>(QEP_TRIG_(s, QEP_EMPTY_SIG_));
431 
432  // (c) check source->super==target->super
433  if (m_temp.fun == t) {
434  QEP_EXIT_(s); // exit the source
435  ip = 0; // enter the target
436  }
437  else {
438  // (d) check source->super==target
439  if (m_temp.fun == path[0]) {
440  QEP_EXIT_(s); // exit the source
441  }
442  else {
443  // (e) check rest of source==target->super->super..
444  // and store the entry path along the way
445 
446  iq = 0; // indicate that the LCA was not found
447  ip = 1; // enter target and its superstate
448  path[1] = t; // save the superstate of target
449  t = m_temp.fun; // save source->super
450 
451  // find target->super->super
452  r = QEP_TRIG_(path[1], QEP_EMPTY_SIG_);
453  while (r == Q_RET_SUPER) {
454  ++ip;
455  path[ip] = m_temp.fun; // store the entry path
456  if (m_temp.fun == s) { // is it the source?
457  // indicate that the LCA was found
458  iq = 1;
459 
460  // entry path must not overflow
461  Q_ASSERT_ID(510, ip < MAX_NEST_DEPTH_);
462  --ip; // do not enter the source
463  r = Q_RET_HANDLED; // terminate the loop
464  }
465  // it is not the source, keep going up
466  else {
468  }
469  }
470 
471  // the LCA not found yet?
472  if (iq == 0) {
473  // entry path must not overflow
474  Q_ASSERT_ID(520, ip < MAX_NEST_DEPTH_);
475 
476  QEP_EXIT_(s); // exit the source
477 
478  // (f) check the rest of source->super
479  // == target->super->super...
480  //
481  iq = ip;
482  r = Q_RET_IGNORED; // indicate LCA NOT found
483  do {
484  // is this the LCA?
485  if (t == path[iq]) {
486  r = Q_RET_HANDLED; // indicate LCA found
487  ip = iq - 1; // do not enter LCA
488  iq = -1; // cause termination of the loop
489  }
490  else {
491  --iq; // try lower superstate of target
492  }
493  } while (iq >= 0);
494 
495  // LCA not found yet?
496  if (r != Q_RET_HANDLED) {
497  // (g) check each source->super->...
498  // for each target->super...
499  //
500  r = Q_RET_IGNORED; // keep looping
501  do {
502  // exit t unhandled?
504  {
506  QS_OBJ_PRE_(this);
507  QS_FUN_PRE_(t);
508  QS_END_PRE_()
509 
510  static_cast<void>(
512  }
513  t = m_temp.fun; // set to super of t
514  iq = ip;
515  do {
516  // is this LCA?
517  if (t == path[iq]) {
518  ip = iq - 1; // do not enter LCA
519  iq = -1; // break out of inner loop
520  r = Q_RET_HANDLED; // break outer loop
521  }
522  else {
523  --iq;
524  }
525  } while (iq >= 0);
526  } while (r != Q_RET_HANDLED);
527  }
528  }
529  }
530  }
531  }
532  }
533  static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
534  return ip;
535 }
536 
537 //****************************************************************************
538 #ifdef Q_SPY
540  return m_state.fun;
541  }
542 #endif
543 
544 //****************************************************************************
557 bool QHsm::isIn(QStateHandler const s) noexcept {
558 
560  Q_REQUIRE_ID(600, m_temp.fun == m_state.fun);
561 
562  bool inState = false; // assume that this HSM is not in 'state'
563  QState r;
564 
565  // scan the state hierarchy bottom-up
566  do {
567  // do the states match?
568  if (m_temp.fun == s) {
569  inState = true; // 'true' means that match found
570  r = Q_RET_IGNORED; // cause breaking out of the loop
571  }
572  else {
574  }
575  } while (r != Q_RET_IGNORED); // QHsm::top() state not reached
576  m_temp.fun = m_state.fun; // restore the stable state configuration
577 
578  return inState; // return the status
579 }
580 
581 //****************************************************************************
601  QStateHandler child = m_state.fun; // start with the current state
602  bool isFound = false; // start with the child not found
603  QState r;
604 
605  // establish stable state configuration
606  m_temp.fun = m_state.fun;
607  do {
608  // is this the parent of the current child?
609  if (m_temp.fun == parent) {
610  isFound = true; // child is found
611  r = Q_RET_IGNORED; // cause breaking out of the loop
612  }
613  else {
614  child = m_temp.fun;
616  }
617  } while (r != Q_RET_IGNORED); // QHsm::top() state not reached
618  m_temp.fun = m_state.fun; // establish stable state configuration
619 
621  Q_ENSURE_ID(810, isFound);
622 #ifdef Q_NASSERT
623  // avoid compiler warning about unused variable
624  static_cast<void>(isFound);
625 #endif
626 
627  return child; // return the child
628 }
629 
630 } // namespace QP
631 
signed int int_fast8_t
fast at-least 8-bit signed int
Definition: 16bit/stdint.h:35
unsigned int uint_fast8_t
fast at-least 8-bit unsigned int
Definition: 16bit/stdint.h:36
static constexpr QState Q_RET_HANDLED
event handled (internal transition)
Definition: qep.hpp:323
static constexpr QState Q_RET_SUPER
event passed to the superstate to handle
Definition: qep.hpp:314
static constexpr QState Q_RET_UNHANDLED
event unhandled due to a guard evaluating to 'false'
Definition: qep.hpp:320
virtual void dispatch(QEvt const *const e, std::uint_fast8_t const qs_id)
Dispatches an event to QHsm.
Definition: qep_hsm.cpp:242
static constexpr QState Q_RET_IGNORED
event silently ignored (bubbled up to top)
Definition: qep.hpp:326
@ Q_INIT_SIG
signal for nested initial transitions
Definition: qep.hpp:374
@ Q_EXIT_SIG
signal for exit actions
Definition: qep.hpp:373
static QState top(void *const me, QEvt const *const e) noexcept
the top-state.
Definition: qep_hsm.cpp:224
virtual QStateHandler getStateHandler() noexcept
Get the current state handler of the HSM.
Definition: qep_hsm.cpp:539
virtual ~QHsm()
virtual destructor
Definition: qep_hsm.cpp:126
virtual void init(void const *const e, std::uint_fast8_t const qs_id)
executes the top-most initial transition in QP::QHsm
Definition: qep_hsm.cpp:139
static constexpr QState Q_RET_TRAN
regular transition taken
Definition: qep.hpp:338
QStateHandler childState(QStateHandler const parent) noexcept
Obtain the current active child state of a given parent.
Definition: qep_hsm.cpp:600
QHsmAttr m_state
current active state (state-variable)
Definition: qep.hpp:270
static constexpr std::int_fast8_t MAX_NEST_DEPTH_
< maximum nesting depth of states in HSM
Definition: qep.hpp:463
bool isIn(QStateHandler const s) noexcept
Tests if a given state is part of the current active state configuration.
Definition: qep_hsm.cpp:557
static constexpr QState Q_RET_TRAN_HIST
transition to history of a given state
Definition: qep.hpp:347
QHsmAttr m_temp
temporary: transition chain, target state, etc.
Definition: qep.hpp:271
std::int_fast8_t hsm_tran(QStateHandler(&path)[MAX_NEST_DEPTH_], std::uint_fast8_t const qs_id)
internal helper function to take a transition in QP::QHsm
Definition: qep_hsm.cpp:404
QHsm(QStateHandler const initial) noexcept
Protected constructor of QHsm.
Definition: qep_hsm.cpp:117
namespace associated with the QP/C++ framework
Definition: struct.dox:1
std::uint_fast8_t QState
Type returned from state-handler functions.
Definition: qep.hpp:223
@ QS_QEP_STATE_INIT
an initial transition was taken in a state
Definition: qs.hpp:69
@ QS_QEP_TRAN_HIST
a tran to history was taken
Definition: qs.hpp:145
@ QS_QEP_STATE_EXIT
a state was exited
Definition: qs.hpp:68
@ QS_QEP_INIT_TRAN
the top-most initial transition was taken
Definition: qs.hpp:70
@ QS_QEP_INTERN_TRAN
an internal transition was taken
Definition: qs.hpp:71
@ QS_QEP_UNHANDLED
an event was unhandled due to a guard
Definition: qs.hpp:75
@ QS_QEP_TRAN
a regular transition was taken
Definition: qs.hpp:72
@ QS_QEP_DISPATCH
an event was dispatched (begin of RTC step)
Definition: qs.hpp:74
@ QS_QEP_IGNORED
an event was ignored (silently discarded)
Definition: qs.hpp:73
@ QEP_EMPTY_SIG_
empty signal for internal use only
Definition: qep_hsm.cpp:85
QState(*)(void *const me, QEvt const *const e) QStateHandler
Pointer to state-handler function.
Definition: qep.hpp:226
static QEvt const QEP_reservedEvt_[4]
Definition: qep_hsm.cpp:94
QStateHandler fun
pointer to a state handler function
Definition: qep.hpp:239
std::uint16_t QSignal
QSignal represents the signal of an event.
Definition: qep.hpp:146
Customizable and memory-efficient assertions for embedded systems.
#define Q_DEFINE_THIS_MODULE(name_)
Define the user-specified module name for assertions in this file.
Definition: qassert.h:120
#define Q_ASSERT_ID(id_, test_)
General purpose assertion with user-specified assertion-id.
Definition: qassert.h:155
#define Q_ENSURE_ID(id_, test_)
Assertion for checking postconditions with user-specified assertion-id.
Definition: qassert.h:298
#define Q_REQUIRE_ID(id_, test_)
Assertion for checking preconditions with user-specified assertion-id.
Definition: qassert.h:279
#define Q_DIM(array_)
Helper macro to calculate static dimension of a 1-dim array_.
Definition: qassert.h:337
#define Q_STATE_CAST(handler_)
Macro to perform casting to QStateHandler.
Definition: qep.hpp:642
#define Q_EVT_CAST(class_)
Perform downcast of an event onto a subclass of QEvt class_.
Definition: qep.hpp:108
#define QEP_ENTER_(state_)
helper macro to trigger entry action in an HSM
Definition: qep_hsm.cpp:65
#define QEP_EXIT_(state_)
helper macro to trigger exit action in an HSM
Definition: qep_hsm.cpp:55
#define QEP_TRIG_(state_, sig_)
helper macro to trigger internal event in an HSM
Definition: qep_hsm.cpp:51
QEP/C++ port, generic C++11 compiler.
#define QS_CRIT_STAT_
This is an internal macro for defining the critical section status type.
Definition: qs.hpp:783
#define QS_TIME_PRE_()
Definition: qs.hpp:266
Dummy definitions of the QS macros that avoid code generation from the QS instrumentation.
#define QS_BEGIN_PRE_(rec_, qs_id_)
Definition: qs_dummy.hpp:96
#define QS_OBJ_PRE_(obj_)
Definition: qs_dummy.hpp:107
#define QS_FUN_PRE_(fun_)
Definition: qs_dummy.hpp:108
#define QS_SIG_PRE_(sig_)
Definition: qs_dummy.hpp:105
#define QS_END_PRE_()
Definition: qs_dummy.hpp:97
Internal (package scope) QS/C++ interface.
QS/C++ port to a 32-bit CPU, generic compiler.
QEvt base class.
Definition: qep.hpp:209
QSignal sig
signal of the event instance
Definition: qep.hpp:210