QP/C++  7.0.1
Real-Time Embedded Framework
qep_hsm.cpp
Go to the documentation of this file.
1//============================================================================
2// QP/C++ Real-Time Embedded Framework (RTEF)
3// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved.
4//
5// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial
6//
7// This software is dual-licensed under the terms of the open source GNU
8// General Public License version 3 (or any later version), or alternatively,
9// under the terms of one of the closed source Quantum Leaps commercial
10// licenses.
11//
12// The terms of the open source GNU General Public License version 3
13// can be found at: <www.gnu.org/licenses/gpl-3.0>
14//
15// The terms of the closed source Quantum Leaps commercial licenses
16// can be found at: <www.state-machine.com/licensing>
17//
18// Redistributions in source code must retain this top-level comment block.
19// Plagiarizing this software to sidestep the license obligations is illegal.
20//
21// Contact information:
22// <www.state-machine.com>
23// <info@state-machine.com>
24//============================================================================
32
33#define QP_IMPL // this is QP implementation
34#include "qep_port.hpp" // QEP port
35#ifdef Q_SPY // QS software tracing enabled?
36 #include "qs_port.hpp" // QS port
37 #include "qs_pkg.hpp" // QS facilities for pre-defined trace records
38#else
39 #include "qs_dummy.hpp" // disable the QS software tracing
40#endif // Q_SPY
41#include "qassert.h" // QP embedded systems-friendly assertions
42
43// local helper macros...
44
46#define QEP_TRIG_(state_, sig_) \
47 ((*(state_))(this, &QEP_reservedEvt_[sig_]))
48
50#define QEP_EXIT_(state_) do { \
51 if (QEP_TRIG_(state_, Q_EXIT_SIG) == Q_RET_HANDLED) { \
52 QS_BEGIN_PRE_(QS_QEP_STATE_EXIT, qs_id) \
53 QS_OBJ_PRE_(this); \
54 QS_FUN_PRE_(state_); \
55 QS_END_PRE_() \
56 } \
57} while (false)
58
60#define QEP_ENTER_(state_) do { \
61 if (QEP_TRIG_(state_, Q_ENTRY_SIG) == Q_RET_HANDLED) { \
62 QS_BEGIN_PRE_(QS_QEP_STATE_ENTRY, qs_id) \
63 QS_OBJ_PRE_(this); \
64 QS_FUN_PRE_(state_); \
65 QS_END_PRE_() \
66 } \
67} while (false)
68
69// unnamed namespace for local definitions with internal linkage
70namespace {
71
72Q_DEFINE_THIS_MODULE("qep_hsm")
73
74//============================================================================
75enum : QP::QSignal {
77 QEP_EMPTY_SIG_ = 0U
78};
79
80//============================================================================
86static QP::QEvt const QEP_reservedEvt_[4] {
87#ifdef Q_EVT_CTOR // Is the QEvt constructor provided?
88 QP::QEvt(0U, QP::QEvt::STATIC_EVT),
89 QP::QEvt(1U, QP::QEvt::STATIC_EVT),
90 QP::QEvt(2U, QP::QEvt::STATIC_EVT),
91 QP::QEvt(3U, QP::QEvt::STATIC_EVT)
92#else // QEvt is a POD (Plain Old Datatype)
93 { 0U, 0U, 0U },
94 { 1U, 0U, 0U },
95 { 2U, 0U, 0U },
96 { 3U, 0U, 0U }
97#endif
98};
99
100} // unnamed namespace
101
102namespace QP {
103
104//============================================================================
105//char const versionStr[7] = QP_VERSION_STR;
106
107//============================================================================
116QHsm::QHsm(QStateHandler const initial) noexcept {
117 m_state.fun = Q_STATE_CAST(&top);
118 m_temp.fun = initial;
119}
120
121//============================================================================
126}
127
128//============================================================================
140void QHsm::init(void const * const e, std::uint_fast8_t const qs_id) {
141 static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
142
144
146 Q_REQUIRE_ID(200, (m_temp.fun != nullptr)
147 && (t == Q_STATE_CAST(&top)));
148
149 // execute the top-most initial transition
150 QState r = (*m_temp.fun)(this, Q_EVT_CAST(QEvt));
151
152 // the top-most initial transition must be taken
153 Q_ASSERT_ID(210, r == Q_RET_TRAN);
154
157 QS_OBJ_PRE_(this); // this state machine object
158 QS_FUN_PRE_(t); // the source state
159 QS_FUN_PRE_(m_temp.fun); // the target of the initial transition
161
162 // drill down into the state hierarchy with initial transitions...
163 do {
164 QStateHandler path[MAX_NEST_DEPTH_]; // tran entry path array
165 std::int_fast8_t ip = 0; // entry path index
166
167 path[0] = m_temp.fun;
168 static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
169 while (m_temp.fun != t) {
170 ++ip;
171 Q_ASSERT_ID(220, ip < MAX_NEST_DEPTH_);
172 path[ip] = m_temp.fun;
173 static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
174 }
175 m_temp.fun = path[0];
176
177 // retrace the entry path in reverse (desired) order...
178 do {
179 QEP_ENTER_(path[ip]); // enter path[ip]
180 --ip;
181 } while (ip >= 0);
182
183 t = path[0]; // current state becomes the new source
184
185 r = QEP_TRIG_(t, Q_INIT_SIG); // execute initial transition
186
187#ifdef Q_SPY
188 if (r == Q_RET_TRAN) {
190 QS_OBJ_PRE_(this); // this state machine object
191 QS_FUN_PRE_(t); // the source state
192 QS_FUN_PRE_(m_temp.fun); // the target of the initial tran.
194 }
195#endif // Q_SPY
196
197 } while (r == Q_RET_TRAN);
198
200 QS_TIME_PRE_(); // time stamp
201 QS_OBJ_PRE_(this); // this state machine object
202 QS_FUN_PRE_(t); // the new active state
204
205 m_state.fun = t; // change the current active state
206 m_temp.fun = t; // mark the configuration as stable
207}
208
209//***************************************************************************/
228QState QHsm::top(void * const me, QEvt const * const e) noexcept {
229 static_cast<void>(me); // unused parameter
230 static_cast<void>(e); // unused parameter
231 return Q_RET_IGNORED; // the top state ignores all events
232}
233
234//============================================================================
249void QHsm::dispatch(QEvt const * const e, std::uint_fast8_t const qs_id) {
252
255 Q_REQUIRE_ID(400, (t != nullptr)
256 && (t == m_temp.fun));
257
259 QS_TIME_PRE_(); // time stamp
260 QS_SIG_PRE_(e->sig); // the signal of the event
261 QS_OBJ_PRE_(this); // this state machine object
262 QS_FUN_PRE_(t); // the current state
264
266 QState r;
267 // process the event hierarchically...
269 do {
270 s = m_temp.fun;
271 r = (*s)(this, e); // invoke state handler s
273 if (r == Q_RET_UNHANDLED) { // unhandled due to a guard?
274
276 QS_SIG_PRE_(e->sig); // the signal of the event
277 QS_OBJ_PRE_(this); // this state machine object
278 QS_FUN_PRE_(s); // the current state
280
281 r = QEP_TRIG_(s, QEP_EMPTY_SIG_); // find superstate of s
283 } while (r == Q_RET_SUPER);
284
285 // regular transition taken?
287 if (r >= Q_RET_TRAN) {
289
290 path[0] = m_temp.fun; // save the target of the transition
291 path[1] = t;
292 path[2] = s;
293
294 // exit current state to transition source s...
296 for (; t != s; t = m_temp.fun) {
297 // exit handled?
300 QS_OBJ_PRE_(this); // this state machine object
301 QS_FUN_PRE_(t); // the exited state
303
304 // find superstate of t
305 static_cast<void>(QEP_TRIG_(t, QEP_EMPTY_SIG_));
306 }
307 }
308
309 std::int_fast8_t ip = hsm_tran(path, qs_id); // the HSM transition
310
311#ifdef Q_SPY
312 if (r == Q_RET_TRAN_HIST) {
313
315 QS_OBJ_PRE_(this); // this state machine object
316 QS_FUN_PRE_(t); // the source of the transition
317 QS_FUN_PRE_(path[0]); // the target of the tran. to history
319
320 }
321#endif // Q_SPY
322
323 // execute state entry actions in the desired order...
325 for (; ip >= 0; --ip) {
326 QEP_ENTER_(path[ip]); // enter path[ip]
327 }
328 t = path[0]; // stick the target into register
329 m_temp.fun = t; // update the next state
330
331 // drill into the target hierarchy...
333 while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN) {
334
336 QS_OBJ_PRE_(this); // this state machine object
337 QS_FUN_PRE_(t); // the source (pseudo)state
338 QS_FUN_PRE_(m_temp.fun); // the target of the transition
340
341 ip = 0;
342 path[0] = m_temp.fun;
343
344 // find superstate
345 static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
346
347 while (m_temp.fun != t) {
348 ++ip;
349 path[ip] = m_temp.fun;
350 // find superstate
351 static_cast<void>(QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_));
352 }
353 m_temp.fun = path[0];
354
355 // entry path must not overflow
356 Q_ASSERT_ID(410, ip < MAX_NEST_DEPTH_);
357
358 // retrace the entry path in reverse (correct) order...
359 do {
360 QEP_ENTER_(path[ip]); // enter path[ip]
361 --ip;
362 } while (ip >= 0);
363
364 t = path[0];
365 }
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 of the transition
372 QS_FUN_PRE_(t); // the new active state
374 }
375
376#ifdef Q_SPY
377 else if (r == Q_RET_HANDLED) {
378
380 QS_TIME_PRE_(); // time stamp
381 QS_SIG_PRE_(e->sig); // the signal of the event
382 QS_OBJ_PRE_(this); // this state machine object
383 QS_FUN_PRE_(s); // the source state
385
386 }
387 else {
388
390 QS_TIME_PRE_(); // time stamp
391 QS_SIG_PRE_(e->sig); // the signal of the event
392 QS_OBJ_PRE_(this); // this state machine object
393 QS_FUN_PRE_(m_state.fun);// the current state
395
396 }
397#endif // Q_SPY
398
399 m_state.fun = t; // change the current active state
400 m_temp.fun = t; // mark the configuration as stable
401 static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
402}
403
404//============================================================================
419std::int_fast8_t QHsm::hsm_tran(QStateHandler (&path)[MAX_NEST_DEPTH_],
420 std::uint_fast8_t const qs_id)
421{
422 std::int_fast8_t ip = -1; // transition entry path index
423 QStateHandler t = path[0];
424 QStateHandler const s = path[2];
426
427 // (a) check source==target (transition to self)
428 if (s == t) {
429 QEP_EXIT_(s); // exit the source
430 ip = 0; // enter the target
431 }
432 else {
433 // superstate of target
434 static_cast<void>(QEP_TRIG_(t, QEP_EMPTY_SIG_));
435 t = m_temp.fun;
436
437 // (b) check source==target->super
438 if (s == t) {
439 ip = 0; // enter the target
440 }
441 else {
442 // superstate of src
443 static_cast<void>(QEP_TRIG_(s, QEP_EMPTY_SIG_));
444
445 // (c) check source->super==target->super
446 if (m_temp.fun == t) {
447 QEP_EXIT_(s); // exit the source
448 ip = 0; // enter the target
449 }
450 else {
451 // (d) check source->super==target
452 if (m_temp.fun == path[0]) {
453 QEP_EXIT_(s); // exit the source
454 }
455 else {
456 // (e) check rest of source==target->super->super..
457 // and store the entry path along the way
458 std::int_fast8_t iq = 0; // indicate that LCA was found
459 ip = 1; // enter target and its superstate
460 path[1] = t; // save the superstate of target
461 t = m_temp.fun; // save source->super
462
463 // find target->super->super
464 QState r = QEP_TRIG_(path[1], QEP_EMPTY_SIG_);
465 while (r == Q_RET_SUPER) {
466 ++ip;
467 path[ip] = m_temp.fun; // store the entry path
468 if (m_temp.fun == s) { // is it the source?
469 // indicate that the LCA was found
470 iq = 1;
471
472 // entry path must not overflow
473 Q_ASSERT_ID(510, ip < MAX_NEST_DEPTH_);
474 --ip; // do not enter the source
475 r = Q_RET_HANDLED; // terminate the loop
476 }
477 // it is not the source, keep going up
478 else {
479 r = QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_);
480 }
481 }
482
483 // the LCA not found yet?
484 if (iq == 0) {
485 // entry path must not overflow
486 Q_ASSERT_ID(520, ip < MAX_NEST_DEPTH_);
487
488 QEP_EXIT_(s); // exit the source
489
490 // (f) check the rest of source->super
491 // == target->super->super...
492 //
493 iq = ip;
494 r = Q_RET_IGNORED; // indicate LCA NOT found
495 do {
496 // is this the LCA?
497 if (t == path[iq]) {
498 r = Q_RET_HANDLED; // indicate LCA found
499 ip = iq - 1; // do not enter LCA
500 iq = -1; // cause termination of the loop
501 }
502 else {
503 --iq; // try lower superstate of target
504 }
505 } while (iq >= 0);
506
507 // LCA not found yet?
508 if (r != Q_RET_HANDLED) {
509 // (g) check each source->super->...
510 // for each target->super...
511 //
512 r = Q_RET_IGNORED; // keep looping
513 do {
514 // exit t unhandled?
516 {
518 QS_OBJ_PRE_(this);
519 QS_FUN_PRE_(t);
521
522 static_cast<void>(
523 QEP_TRIG_(t, QEP_EMPTY_SIG_));
524 }
525 t = m_temp.fun; // set to super of t
526 iq = ip;
527 do {
528 // is this LCA?
529 if (t == path[iq]) {
530 ip = iq - 1; // do not enter LCA
531 iq = -1; // break out of inner loop
532 r = Q_RET_HANDLED; // break outer loop
533 }
534 else {
535 --iq;
536 }
537 } while (iq >= 0);
538 } while (r != Q_RET_HANDLED);
539 }
540 }
541 }
542 }
543 }
544 }
545 static_cast<void>(qs_id); // unused parameter (if Q_SPY not defined)
546 return ip;
547}
548
549//============================================================================
550#ifdef Q_SPY
552 return m_state.fun;
553 }
554#endif
555
556//============================================================================
572bool QHsm::isIn(QStateHandler const s) noexcept {
573
575 Q_REQUIRE_ID(600, m_temp.fun == m_state.fun);
576
577 bool inState = false; // assume that this HSM is not in 'state'
578
579 // scan the state hierarchy bottom-up
580 QState r;
581 do {
582 // do the states match?
583 if (m_temp.fun == s) {
584 inState = true; // 'true' means that match found
585 r = Q_RET_IGNORED; // cause breaking out of the loop
586 }
587 else {
588 r = QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_);
589 }
590 } while (r != Q_RET_IGNORED); // QHsm::top() state not reached
591 m_temp.fun = m_state.fun; // restore the stable state configuration
592
593 return inState; // return the status
594}
595
596//============================================================================
619 QStateHandler child = m_state.fun; // start with the current state
620 bool isFound = false; // start with the child not found
621
622 // establish stable state configuration
623 m_temp.fun = m_state.fun;
624 QState r;
625 do {
626 // is this the parent of the current child?
627 if (m_temp.fun == parent) {
628 isFound = true; // child is found
629 r = Q_RET_IGNORED; // cause breaking out of the loop
630 }
631 else {
632 child = m_temp.fun;
633 r = QEP_TRIG_(m_temp.fun, QEP_EMPTY_SIG_);
634 }
635 } while (r != Q_RET_IGNORED); // QHsm::top() state not reached
636 m_temp.fun = m_state.fun; // establish stable state configuration
637
639 Q_ENSURE_ID(810, isFound);
640#ifdef Q_NASSERT
641 // avoid compiler warning about unused variable
642 static_cast<void>(isFound);
643#endif
644
645 return child; // return the child
646}
647
648} // namespace QP
649
static constexpr QState Q_RET_HANDLED
event handled (internal transition)
Definition: qep.hpp:303
static constexpr QState Q_RET_SUPER
event passed to the superstate to handle
Definition: qep.hpp:294
static constexpr QState Q_RET_UNHANDLED
event unhandled due to a guard evaluating to 'false'
Definition: qep.hpp:300
virtual void dispatch(QEvt const *const e, std::uint_fast8_t const qs_id)
Dispatches an event to QHsm.
Definition: qep_hsm.cpp:249
static constexpr QState Q_RET_IGNORED
event silently ignored (bubbled up to top)
Definition: qep.hpp:306
@ Q_INIT_SIG
signal for nested initial transitions
Definition: qep.hpp:354
@ Q_EXIT_SIG
signal for exit actions
Definition: qep.hpp:353
static QState top(void *const me, QEvt const *const e) noexcept
the top-state.
Definition: qep_hsm.cpp:228
virtual QStateHandler getStateHandler() noexcept
Get the current state handler of the HSM.
Definition: qep_hsm.cpp:551
virtual ~QHsm()
virtual destructor
Definition: qep_hsm.cpp:125
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:140
static constexpr QState Q_RET_TRAN
regular transition taken
Definition: qep.hpp:318
QStateHandler childState(QStateHandler const parent) noexcept
Obtain the current active child state of a given parent.
Definition: qep_hsm.cpp:618
QHsmAttr m_state
current active state (state-variable)
Definition: qep.hpp:250
static constexpr std::int_fast8_t MAX_NEST_DEPTH_
< maximum nesting depth of states in HSM
Definition: qep.hpp:443
bool isIn(QStateHandler const s) noexcept
Tests if a given state is part of the current active state configuration.
Definition: qep_hsm.cpp:572
static constexpr QState Q_RET_TRAN_HIST
transition to history of a given state
Definition: qep.hpp:327
QHsmAttr m_temp
temporary: transition chain, target state, etc.
Definition: qep.hpp:251
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:419
QHsm(QStateHandler const initial) noexcept
Protected constructor of QHsm.
Definition: qep_hsm.cpp:116
namespace associated with the QP/C++ framework
Definition: exa_native.dox:1
std::uint_fast8_t QState
Type returned from state-handler functions.
Definition: qep.hpp:205
@ QS_QEP_STATE_INIT
an initial transition was taken in a state
Definition: qs.hpp:61
@ QS_QEP_TRAN_HIST
a tran to history was taken
Definition: qs.hpp:137
@ QS_QEP_STATE_EXIT
a state was exited
Definition: qs.hpp:60
@ QS_QEP_INIT_TRAN
the top-most initial transition was taken
Definition: qs.hpp:62
@ QS_QEP_INTERN_TRAN
an internal transition was taken
Definition: qs.hpp:63
@ QS_QEP_UNHANDLED
an event was unhandled due to a guard
Definition: qs.hpp:67
@ QS_QEP_TRAN
a regular transition was taken
Definition: qs.hpp:64
@ QS_QEP_DISPATCH
an event was dispatched (begin of RTC step)
Definition: qs.hpp:66
@ QS_QEP_IGNORED
an event was ignored (silently discarded)
Definition: qs.hpp:65
QState(*)(void *const me, QEvt const *const e) QStateHandler
Pointer to state-handler function.
Definition: qep.hpp:208
QStateHandler fun
pointer to a state handler function
Definition: qep.hpp:221
std::uint16_t QSignal
QSignal represents the signal of an event.
Definition: qep.hpp:129
Customizable and memory-efficient assertions for embedded systems.
#define Q_DEFINE_THIS_MODULE(name_)
Definition: qassert.h:102
#define Q_ASSERT_ID(id_, test_)
Definition: qassert.h:135
#define Q_ENSURE_ID(id_, test_)
Definition: qassert.h:271
#define Q_REQUIRE_ID(id_, test_)
Definition: qassert.h:252
#define Q_STATE_CAST(handler_)
Macro to perform casting to QStateHandler.
Definition: qep.hpp:622
#define Q_EVT_CAST(class_)
Perform downcast of an event onto a subclass of QEvt class_.
Definition: qep.hpp:93
#define QEP_ENTER_(state_)
helper macro to trigger entry action in an HSM
Definition: qep_hsm.cpp:60
#define QEP_EXIT_(state_)
helper macro to trigger exit action in an HSM
Definition: qep_hsm.cpp:50
#define QEP_TRIG_(state_, sig_)
helper macro to trigger internal event in an HSM
Definition: qep_hsm.cpp:46
#define QS_CRIT_STAT_
This is an internal macro for defining the critical section status type.
Definition: qs.hpp:746
#define QS_TIME_PRE_()
Definition: qs.hpp:225
Internal (package scope) QS/C++ interface.
#define QS_BEGIN_PRE_(rec_, qs_id_)
Internal QS macro to begin a predefined QS record with critical section.
Definition: qs_pkg.hpp:109
#define QS_OBJ_PRE_(obj_)
Internal QS macro to output object pointer data element.
Definition: qs_pkg.hpp:178
#define QS_FUN_PRE_(fun_)
Internal QS macro to output an unformatted function pointer data element.
Definition: qs_pkg.hpp:199
#define QS_END_PRE_()
Internal QS macro to end a predefined QS record with critical section.
Definition: qs_pkg.hpp:120
QEvt base class.
Definition: qep.hpp:191
QSignal sig
signal of the event instance
Definition: qep.hpp:192