QP/C++  8.0.3
Real-Time Event Framework
Loading...
Searching...
No Matches
qep_hsm.cpp
Go to the documentation of this file.
1//============================================================================
2// QP/C++ Real-Time Event Framework (RTEF)
3//
4// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved.
5//
6// Q u a n t u m L e a P s
7// ------------------------
8// Modern Embedded Software
9//
10// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial
11//
12// This software is dual-licensed under the terms of the open-source GNU
13// General Public License (GPL) or under the terms of one of the closed-
14// source Quantum Leaps commercial licenses.
15//
16// Redistributions in source code must retain this top-level comment block.
17// Plagiarizing this software to sidestep the license obligations is illegal.
18//
19// NOTE:
20// The GPL does NOT permit the incorporation of this code into proprietary
21// programs. Please contact Quantum Leaps for commercial licensing options,
22// which expressly supersede the GPL and are designed explicitly for
23// closed-source distribution.
24//
25// Quantum Leaps contact information:
26// <www.state-machine.com/licensing>
27// <info@state-machine.com>
28//============================================================================
29#define QP_IMPL // this is QP implementation
30#include "qp_port.hpp" // QP port
31#include "qsafe.h" // QP Functional Safety (FuSa) Subsystem
32#ifdef Q_SPY // QS software tracing enabled?
33 #include "qs_port.hpp" // QS port
34 #include "qs_pkg.hpp" // QS facilities for pre-defined trace records
35#else
36 #include "qs_dummy.hpp" // disable the QS software tracing
37#endif // Q_SPY
38
39// unnamed namespace for local definitions with internal linkage
40namespace {
41
42Q_DEFINE_THIS_MODULE("qep_hsm")
43
44// maximum depth of state nesting in a QHsm (including the top level)
45// must be >= 3
46static constexpr std::int_fast8_t QHSM_MAX_NEST_DEPTH_ {6};
47
48//! @cond INTERNAL
49
50// immutable events corresponding to the reserved signals.
51static QP::QEvt const l_reservedEvt_[4] {
56};
57
58//! @endcond
59
60} // unnamed namespace
61
62//============================================================================
63//! @cond INTERNAL
64
65// internal helper macro to pass a reserved event into the state handler
66#define QHSM_RESERVED_EVT_(state_, sig_) \
67 ((*(state_))(this, &l_reservedEvt_[(sig_)]))
68
69#ifdef Q_SPY
70// helper macro to trace state action (entry/exit)
71#define QS_STATE_ACT_(rec_, state_) \
72 QS_CRIT_ENTRY(); \
73 QS_BEGIN_PRE((rec_), qsId) \
74 QS_OBJ_PRE(this); \
75 QS_FUN_PRE(state_); \
76 QS_END_PRE() \
77 QS_CRIT_EXIT()
78
79// internal helper macro to top-most init
80#define QS_TOP_INIT_(rec_, trg_) \
81 QS_CRIT_ENTRY(); \
82 QS_BEGIN_PRE((rec_), qsId) \
83 QS_TIME_PRE(); \
84 QS_OBJ_PRE(this); \
85 QS_FUN_PRE(trg_); \
86 QS_END_PRE() \
87 QS_CRIT_EXIT()
88
89// internal helper macro to trace transition segment
90#define QS_TRAN_SEG_(rec_, src_, trg_) \
91 QS_CRIT_ENTRY(); \
92 QS_BEGIN_PRE((rec_), qsId) \
93 QS_OBJ_PRE(this); \
94 QS_FUN_PRE(src_); \
95 QS_FUN_PRE(trg_); \
96 QS_END_PRE() \
97 QS_CRIT_EXIT()
98
99// internal helper macro to trace transition action
100#define QS_TRAN_ACT_(rec_, state_) \
101 QS_CRIT_ENTRY(); \
102 QS_BEGIN_PRE((rec_), qsId) \
103 QS_SIG_PRE(e->sig); \
104 QS_OBJ_PRE(this); \
105 QS_FUN_PRE(state_); \
106 QS_END_PRE() \
107 QS_CRIT_EXIT()
108
109// internal helper macro to trace transition begin/end
110#define QS_TRAN0_(rec_, trg_) \
111 QS_CRIT_ENTRY(); \
112 QS_BEGIN_PRE((rec_), qsId) \
113 QS_TIME_PRE(); \
114 QS_SIG_PRE(e->sig); \
115 QS_OBJ_PRE(this); \
116 QS_FUN_PRE(trg_); \
117 QS_END_PRE() \
118 QS_CRIT_EXIT()
119
120// internal helper macro to trace regulsr transition
121#define QS_TRAN_END_(rec_, src_, trg_) \
122 QS_CRIT_ENTRY(); \
123 QS_BEGIN_PRE((rec_), qsId) \
124 QS_TIME_PRE(); \
125 QS_SIG_PRE(e->sig); \
126 QS_OBJ_PRE(this); \
127 QS_FUN_PRE(src_); \
128 QS_FUN_PRE(trg_); \
129 QS_END_PRE() \
130 QS_CRIT_EXIT()
131
132#else
133#define QS_STATE_ACT_(rec_, state_) (static_cast<void>(0))
134#define QS_TOP_INIT_(rec_, trg_) (static_cast<void>(0))
135#define QS_TRAN_SEG_(rec_, src_, trg_) (static_cast<void>(0))
136#define QS_TRAN_ACT_(rec_, state_) (static_cast<void>(0))
137#define QS_TRAN0_(rec_, trg_) (static_cast<void>(0))
138#define QS_TRAN_END_(rec_, src_, trg_) (static_cast<void>(0))
139#endif
140
141//! @endcond
142
143namespace QP {
144
145//............................................................................
146QHsm::QHsm(QStateHandler const initial) noexcept
147: QAsm()
148{
149 m_state.fun = &top;
150 m_temp.fun = initial;
151}
152
153//............................................................................
155 void const * const e,
156 std::uint_fast8_t const qsId)
157{
159
160 // produce QS dictionary for QP::QHsm::top()
161#ifdef Q_SPY
163 bool isDone = true;
164 if ((QS::priv_.flags & 0x01U) == 0U) {
165 QS::priv_.flags |= 0x01U;
166 isDone = false;
167 }
168 QS_CRIT_EXIT();
169 if (!isDone) {
171 }
172#else
173 Q_UNUSED_PAR(qsId);
174#endif // def Q_SPY
175
176 QStateHandler t = m_state.fun;
177
180 (m_temp.fun != nullptr)
181 && (t == Q_STATE_CAST(&top)));
182 QF_CRIT_EXIT();
183
184 // execute the top-most initial tran.
185 QState const r = (*m_temp.fun)(this, Q_EVT_CAST(QEvt));
186
188 // the top-most initial tran. must be taken
189 Q_ASSERT_INCRIT(210, r == Q_RET_TRAN);
190 QF_CRIT_EXIT();
191
192 QS_TRAN_SEG_(QS_QEP_STATE_INIT, t, m_temp.fun);
193
194 // drill down into the state hierarchy with initial transitions...
195 QStateHandler path[QHSM_MAX_NEST_DEPTH_]; // tran. entry path array
196 path[0] = m_temp.fun;
197 static_cast<void>(QHSM_RESERVED_EVT_(m_temp.fun, Q_EMPTY_SIG));
198
199 std::int_fast8_t ip = 1; // tran. entry path index (also the loop bound)
200 for (; (m_temp.fun != t) && (ip < QHSM_MAX_NEST_DEPTH_); ++ip) {
201 path[ip] = m_temp.fun;
202 static_cast<void>(QHSM_RESERVED_EVT_(m_temp.fun, Q_EMPTY_SIG));
203 }
205 // must NOT be too many state nesting levels or "malformed" HSM
206 Q_ASSERT_INCRIT(220, ip <= QHSM_MAX_NEST_DEPTH_);
207 QF_CRIT_EXIT();
208
209 m_temp.fun = path[0];
210 enter_target_(&path[0], ip - 1, qsId);
211 t = path[0];
212
213 QS_TOP_INIT_(QS_QEP_INIT_TRAN, t);
214
215 m_state.fun = t; // change the current active state
216#ifdef Q_UNSAFE
217 Q_UNUSED_PAR(r);
218#endif
219}
220
221//............................................................................
223 QEvt const * const e,
224 std::uint_fast8_t const qsId)
225{
226#ifndef Q_SPY
227 Q_UNUSED_PAR(qsId);
228#endif
229
230 QStateHandler s = m_state.fun;
231 QStateHandler t = s;
233
236 (e != nullptr)
237 && (s != nullptr));
238 QF_CRIT_EXIT();
239
240 QS_TRAN0_(QS_QEP_DISPATCH, s);
241
243
244 // process the event hierarchically...
245 m_temp.fun = s;
246 std::int_fast8_t ip = QHSM_MAX_NEST_DEPTH_;
247 // NOTE: ip is the fixed loop upper bound
248 for (; ip > 0; --ip) {
249 s = m_temp.fun;
250 r = (*s)(this, e); // invoke state handler s
251
252 if (r == Q_RET_UNHANDLED) { // unhandled due to a guard?
253 QS_TRAN_ACT_(QS_QEP_UNHANDLED, s);
254 r = QHSM_RESERVED_EVT_(s, Q_EMPTY_SIG); // superstate of s
255 }
256 if (r != Q_RET_SUPER) { // event NOT "bubbled up"
257 break;
258 }
259 }
261 Q_ASSERT_INCRIT(310, ip > 0);
262 QF_CRIT_EXIT();
263
264 if (r >= Q_RET_TRAN) { // tran. (regular or history) taken?
265#ifdef Q_SPY
266 if (r == Q_RET_TRAN_HIST) { // tran. to history?
267 QS_TRAN_SEG_(QS_QEP_TRAN_HIST, s, m_temp.fun);
268 }
269#endif // Q_SPY
270
271 QStateHandler path[QHSM_MAX_NEST_DEPTH_];
272 path[0] = m_temp.fun; // tran. target
273 path[1] = t; // current state
274 path[2] = s; // tran. source
275
276 // exit current state to tran. source s...
277 while (t != s) {
278 // exit from t
279 if (QHSM_RESERVED_EVT_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {
280 QS_STATE_ACT_(QS_QEP_STATE_EXIT, t);
281 // find superstate of t
282 static_cast<void>(QHSM_RESERVED_EVT_(t, Q_EMPTY_SIG));
283 }
284 t = m_temp.fun;
285 }
286
287 // take the tran...
288 ip = tran_simple_(&path[0], qsId);
289 if (ip < -1) { // not a simple tran.?
290 ip = tran_complex_(&path[0], qsId);
291 }
292
293 enter_target_(&path[0], ip, qsId);
294 t = path[0];
295 QS_TRAN_END_(QS_QEP_TRAN, s, t);
296 }
297#ifdef Q_SPY
298 else if (r == Q_RET_HANDLED) {
299 QS_TRAN0_(QS_QEP_INTERN_TRAN, s);
300 }
301 else {
302 QS_TRAN0_(QS_QEP_IGNORED, m_state.fun);
303 }
304#endif // Q_SPY
305
306 m_state.fun = t; // change the current active state
307}
308
309//............................................................................
310bool QHsm::isIn(QStateHandler const stateHndl) noexcept {
311 bool inState = false; // assume that this HSM is not in 'state'
312
313 // scan the state hierarchy bottom-up
314 QStateHandler s = m_state.fun;
316 while (r != Q_RET_IGNORED) {
317 if (s == stateHndl) { // do the states match?
318 inState = true; // 'true' means that match found
319 break; // break out of the for-loop
320 }
321 r = QHSM_RESERVED_EVT_(s, Q_EMPTY_SIG);
322 s = m_temp.fun;
323 }
324 return inState; // return the status
325}
326
327//............................................................................
328QStateHandler QHsm::childState(QStateHandler const parentHndl) noexcept {
329#ifndef Q_UNSAFE
330 bool isFound = false; // assume the child state NOT found
331#endif
332
333 QStateHandler child = m_state.fun; // start with current state
334 m_temp.fun = child; // establish stable state configuration
336 while (r != Q_RET_IGNORED) {
337 // have the parent of the current child?
338 if (m_temp.fun == parentHndl) {
339#ifndef Q_UNSAFE
340 isFound = true; // indicate that child state was found
341#endif
342 break;
343 }
344 child = m_temp.fun;
345 r = QHSM_RESERVED_EVT_(child, Q_EMPTY_SIG);
346 }
349 Q_ASSERT_INCRIT(590, isFound);
350 QF_CRIT_EXIT();
351
352 return child;
353}
354
355//............................................................................
356//! @private @memberof QHsm
357std::int_fast8_t QHsm::tran_simple_(
358 QStateHandler * const path,
359 std::uint_fast8_t const qsId)
360{
361#ifndef Q_SPY
362 Q_UNUSED_PAR(qsId);
363#endif
364
365 QStateHandler t = path[0];
366 QStateHandler const s = path[2];
367 std::int_fast8_t ip = 0; // tran. entry path index
369
370 // (a) check source==target (tran. to self)...
371 if (s == t) {
372 // exit source s
373 if (QHSM_RESERVED_EVT_(s, Q_EXIT_SIG) == Q_RET_HANDLED) {
374 QS_STATE_ACT_(QS_QEP_STATE_EXIT, s);
375 }
376 ip = 0; // enter the target
377 }
378 else {
379 // find superstate of target
380 static_cast<void>(QHSM_RESERVED_EVT_(t, Q_EMPTY_SIG));
381
382 t = m_temp.fun;
383
384 // (b) check source==target->super...
385 if (s == t) {
386 ip = 0; // enter the target
387 }
388 else {
389 // find superstate of src
390 static_cast<void>(QHSM_RESERVED_EVT_(s, Q_EMPTY_SIG));
391
392 // (c) check source->super==target->super...
393 if (m_temp.fun == t) {
394 // exit source s
395 if (QHSM_RESERVED_EVT_(s, Q_EXIT_SIG) == Q_RET_HANDLED) {
396 QS_STATE_ACT_(QS_QEP_STATE_EXIT, s);
397 }
398 ip = 0; // enter the target
399 }
400 // (d) check source->super==target...
401 else if (m_temp.fun == path[0]) {
402 // exit source s
403 if (QHSM_RESERVED_EVT_(s, Q_EXIT_SIG) == Q_RET_HANDLED) {
404 QS_STATE_ACT_(QS_QEP_STATE_EXIT, s);
405 }
406 ip = -1; // do not enter the target
407 }
408 else {
409 path[1] = t; // save the superstate of target
410 ip = -2; // cause execution of complex tran.
411 }
412 }
413 }
414 return ip;
415}
416
417//............................................................................
418//! @private @memberof QHsm
419std::int_fast8_t QHsm::tran_complex_(
420 QStateHandler * const path,
421 std::uint_fast8_t const qsId)
422{
423#ifndef Q_SPY
424 Q_UNUSED_PAR(qsId);
425#endif
426
427 // (e) check rest of source==target->super->super..
428 // and store the entry path along the way
429 std::int_fast8_t iq = 0; // indicate that LCA was found
430 std::int_fast8_t ip = 1; // enter target and its superstate
431 QStateHandler const s = path[2]; // source state
432 QStateHandler t = m_temp.fun; // source->super
434
435 // find target->super->super...
436 // note: ip is the fixed upper loop bound
437 QState r = QHSM_RESERVED_EVT_(path[1], Q_EMPTY_SIG);
438 while ((r == Q_RET_SUPER) && (ip < (QHSM_MAX_NEST_DEPTH_ - 1))) {
439 ++ip;
440 path[ip] = m_temp.fun; // store the entry path
441 if (m_temp.fun == s) { // is it the source?
442 iq = 1; // indicate that the LCA found
443 --ip; // do not enter the source
444 break; // terminate the loop
445 }
446 r = QHSM_RESERVED_EVT_(m_temp.fun, Q_EMPTY_SIG);
447 }
449 Q_INVARIANT_INCRIT(711, ip < (QHSM_MAX_NEST_DEPTH_ - 1));
450 QF_CRIT_EXIT();
451
452 // the LCA not found yet?
453 if (iq == 0) {
454
455 // exit source s
456#ifndef Q_SPY
457 static_cast<void>(QHSM_RESERVED_EVT_(s, Q_EXIT_SIG));
458#else
459 if (QHSM_RESERVED_EVT_(s, Q_EXIT_SIG) == Q_RET_HANDLED) {
460 QS_STATE_ACT_(QS_QEP_STATE_EXIT, s);
461 }
462#endif // def Q_SPY
463
464 // (f) check the rest of
465 // source->super == target->super->super...
466 iq = ip;
467 r = Q_RET_IGNORED; // indicate that the LCA NOT found
468 // note: iq is the fixed upper loop bound
469 do {
470 if (t == path[iq]) { // is this the LCA?
471 r = Q_RET_HANDLED; // indicate the LCA found
472 ip = iq - 1; // do not enter the LCA
473 break; // terminate the loop
474 }
475 --iq; // try lower superstate of target
476 } while (iq >= 0);
477
478 if (r != Q_RET_HANDLED) { // the LCA still not found?
479 // (g) check each source->super->...
480 // for each target->super...
481 r = Q_RET_SUPER; // keep looping
482 while (r != Q_RET_HANDLED) {
483 // exit from t
484 if (QHSM_RESERVED_EVT_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {
485 QS_STATE_ACT_(QS_QEP_STATE_EXIT, t);
486 // find superstate of t
487 static_cast<void>(QHSM_RESERVED_EVT_(t, Q_EMPTY_SIG));
488 }
489 t = m_temp.fun; // set to super of t
490 iq = ip;
491 do {
492 if (t == path[iq]) { // is this the LCA?
493 ip = iq - 1; // do not enter the LCA
494 r = Q_RET_HANDLED; // break outer loop
495 break; // terminate the inner loop
496 }
497 --iq; // try lower superstate of target
498 } while (iq >= 0);
499 }
500 }
501 }
502 return ip;
503}
504
505//............................................................................
506//! @private @memberof QHsm
508 QStateHandler * const path,
509 std::int_fast8_t const depth,
510 std::uint_fast8_t const qsId)
511{
512#ifndef Q_SPY
513 Q_UNUSED_PAR(qsId);
514#endif
515
517
519 Q_REQUIRE_INCRIT(800, depth < QHSM_MAX_NEST_DEPTH_);
520 QF_CRIT_EXIT();
521
522 std::int_fast8_t ip = depth;
523 // execute state entry actions in the desired order...
524 // note: ip is the fixed upper loop bound
525 for (; ip >= 0; --ip) {
526 // enter path[ip]
527 if (QHSM_RESERVED_EVT_(path[ip], Q_ENTRY_SIG)
528 == Q_RET_HANDLED)
529 {
530 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, path[ip]);
531 }
532 }
533 QStateHandler t = path[0];
534 m_temp.fun = t; // update the next state
535
536 // drill into the target hierarchy...
537 while (QHSM_RESERVED_EVT_(t, Q_INIT_SIG) == Q_RET_TRAN) {
538
539 QS_TRAN_SEG_(QS_QEP_STATE_INIT, t, m_temp.fun);
540
541 ip = 0;
542 path[0] = m_temp.fun;
543
544 // find superstate
545 static_cast<void>(QHSM_RESERVED_EVT_(m_temp.fun, Q_EMPTY_SIG));
546
547 // note: ip is the fixed upper loop bound
548 while ((m_temp.fun != t) && (ip < (QHSM_MAX_NEST_DEPTH_ - 1))) {
549 ++ip;
550 path[ip] = m_temp.fun;
551 // find superstate
552 static_cast<void>(QHSM_RESERVED_EVT_(
553 m_temp.fun, Q_EMPTY_SIG));
554 }
556 // too many state nesting levels or "malformed" HSM
557 Q_INVARIANT_INCRIT(891, ip < QHSM_MAX_NEST_DEPTH_);
558 QF_CRIT_EXIT();
559
560 m_temp.fun = path[0];
561
562 // retrace the entry path in reverse (correct) order...
563 // note: ip is the fixed upper loop bound
564 do {
565 // enter path[ip]
566 if (QHSM_RESERVED_EVT_(path[ip], Q_ENTRY_SIG) == Q_RET_HANDLED) {
567 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, path[ip]);
568 }
569 --ip;
570 } while (ip >= 0);
571
572 t = path[0]; // current state becomes the new source
573 }
574}
575
576} // namespace QP
QAsm() noexcept
Constructor of the QP::QAsm base class.
Definition qp.hpp:214
static constexpr QState Q_RET_UNHANDLED
Definition qp.hpp:187
static constexpr QState Q_RET_TRAN
Definition qp.hpp:201
QAsmAttr m_temp
Temporary storage for target/act-table etc.
Definition qp.hpp:181
static constexpr QState Q_RET_IGNORED
Definition qp.hpp:191
static constexpr QState Q_RET_SUPER
Definition qp.hpp:186
static QState top(void *const me, QEvt const *const e) noexcept
Top state handler that ignores all events.
Definition qp.hpp:252
static constexpr QState Q_RET_TRAN_HIST
Definition qp.hpp:205
static constexpr QSignal Q_INIT_SIG
Definition qp.hpp:211
QAsmAttr m_state
Current state (pointer to the current state-handler function)
Definition qp.hpp:180
static constexpr QSignal Q_ENTRY_SIG
Definition qp.hpp:209
static constexpr QSignal Q_EXIT_SIG
Definition qp.hpp:210
static constexpr QSignal Q_EMPTY_SIG
Definition qp.hpp:208
static constexpr QState Q_RET_HANDLED
Definition qp.hpp:190
Event class.
Definition qp.hpp:115
std::int_fast8_t tran_complex_(QStateHandler *const path, std::uint_fast8_t const qsId)
void dispatch(QEvt const *const e, std::uint_fast8_t const qsId) override
Virtual function to dispatch an event to the state machine.
Definition qep_hsm.cpp:222
std::int_fast8_t tran_simple_(QStateHandler *const path, std::uint_fast8_t const qsId)
QStateHandler childState(QStateHandler const parentHndl) noexcept
Definition qep_hsm.cpp:328
void init(void const *const e, std::uint_fast8_t const qsId) override
Virtual function to take the top-most initial transition in the state machine.
Definition qep_hsm.cpp:154
QHsm(QStateHandler const initial) noexcept
Definition qep_hsm.cpp:146
void enter_target_(QStateHandler *const path, std::int_fast8_t const depth, std::uint_fast8_t const qsId)
bool isIn(QStateHandler const stateHndl) noexcept override
Definition qep_hsm.cpp:310
QP/C++ framework.
Definition qequeue.hpp:36
std::uint_fast8_t QState
Type returned from state-handler functions.
Definition qp.hpp:146
QState(*)(void *const me, QEvt const *const e) QStateHandler
Pointer to a state-handler function.
Definition qp.hpp:150
std::uint16_t QSignal
The signal of event QP::QEvt.
Definition qp.hpp:108
#define Q_EVT_CAST(subclass_)
Definition qp.hpp:417
#define Q_UNUSED_PAR(par_)
Helper macro to clearly mark unused parameters of functions.
Definition qp.hpp:94
#define Q_STATE_CAST(handler_)
Definition qp.hpp:418
Sample QP/C++ port.
QS/C++ dummy public interface.
#define QS_CRIT_STAT
Definition qs_dummy.hpp:164
#define QS_FUN_DICTIONARY(fun_)
Definition qs_dummy.hpp:71
#define QS_CRIT_EXIT()
Definition qs_dummy.hpp:166
#define QS_CRIT_ENTRY()
Definition qs_dummy.hpp:165
Sample QS/C++ port.
QP Functional Safety (FuSa) Subsystem.
#define QF_CRIT_ENTRY()
Definition qsafe.h:39
#define Q_ASSERT_INCRIT(id_, expr_)
General-purpose assertion with user-specified ID number (in critical section)
Definition qsafe.h:49
#define Q_INVARIANT_INCRIT(id_, expr_)
Definition qsafe.h:92
#define QF_CRIT_EXIT()
Definition qsafe.h:43
#define Q_REQUIRE_INCRIT(id_, expr_)
Assertion for checking a precondition (in critical section)
Definition qsafe.h:86
#define QF_CRIT_STAT
Definition qsafe.h:35