QP/C++ 8.1.1
Real-Time Event Framework
Loading...
Searching...
No Matches
qep_msm.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 "qp_pkg.hpp" // QP package-scope interface
32#include "qsafe.h" // QP Functional Safety (FuSa) Subsystem
33#ifdef Q_SPY // QS software tracing enabled?
34 #include "qs_port.hpp" // QS port
35 #include "qs_pkg.hpp" // QS facilities for pre-defined trace records
36#else
37 #include "qs_dummy.hpp" // disable the QS software tracing
38#endif // Q_SPY
39
40// unnamed namespace for local definitions with internal linkage
41namespace {
42
43Q_DEFINE_THIS_MODULE("qep_msm")
44
45// maximum depth of state nesting in a QMsm (including the top level)
46static constexpr std::int_fast8_t QMSM_MAX_NEST_DEPTH_ {6};
47
48//! @cond INTERNAL
49
50// top-state object for QMsm-style state machines
51constexpr QP::QMState l_msm_top_s = {
52 nullptr,
53 nullptr,
54 nullptr,
55 nullptr,
56 nullptr
57};
58
59} // unnamed namespace
60
61#ifdef Q_SPY
62// helper macro to trace state action (entry/exit)
63#define QS_STATE_ACT_(rec_, state_) \
64 QS_CRIT_ENTRY(); \
65 QS_BEGIN_PRE((rec_), qsId) \
66 QS_OBJ_PRE(this); \
67 QS_FUN_PRE(state_); \
68 QS_END_PRE() \
69 QS_CRIT_EXIT()
70
71// internal helper macro to top-most init
72#define QS_TOP_INIT_(rec_, trg_) \
73 QS_CRIT_ENTRY(); \
74 QS_BEGIN_PRE((rec_), qsId) \
75 QS_TIME_PRE(); \
76 QS_OBJ_PRE(this); \
77 QS_FUN_PRE(trg_); \
78 QS_END_PRE() \
79 QS_CRIT_EXIT()
80
81// internal helper macro to trace transition segment
82#define QS_TRAN_SEG_(rec_, src_, trg_) \
83 QS_CRIT_ENTRY(); \
84 QS_BEGIN_PRE((rec_), qsId) \
85 QS_OBJ_PRE(this); \
86 QS_FUN_PRE(src_); \
87 QS_FUN_PRE(trg_); \
88 QS_END_PRE() \
89 QS_CRIT_EXIT()
90
91// internal helper macro to trace transition begin/end
92#define QS_TRAN0_(rec_, trg_) \
93 QS_CRIT_ENTRY(); \
94 QS_BEGIN_PRE((rec_), qsId) \
95 QS_TIME_PRE(); \
96 QS_SIG_PRE(e->sig); \
97 QS_OBJ_PRE(this); \
98 QS_FUN_PRE(trg_); \
99 QS_END_PRE() \
100 QS_CRIT_EXIT()
101
102// internal helper macro to trace regular transition
103#define QS_TRAN_END_(rec_, src_, trg_) \
104 QS_CRIT_ENTRY(); \
105 QS_BEGIN_PRE((rec_), qsId) \
106 QS_TIME_PRE(); \
107 QS_SIG_PRE(e->sig); \
108 QS_OBJ_PRE(this); \
109 QS_FUN_PRE(src_); \
110 QS_FUN_PRE(trg_); \
111 QS_END_PRE() \
112 QS_CRIT_EXIT()
113
114#else
115#define QS_STATE_ACT_(rec_, state_) (static_cast<void>(0))
116#define QS_TOP_INIT_(rec_, trg_) (static_cast<void>(0))
117#define QS_TRAN_SEG_(rec_, src_, trg_) (static_cast<void>(0))
118#define QS_TRAN0_(rec_, trg_) (static_cast<void>(0))
119#define QS_TRAN_END_(rec_, src_, trg_) (static_cast<void>(0))
120#endif
121
122//! @endcond
123
124//============================================================================
125namespace QP {
126
127//............................................................................
128QMsm::QMsm(QStateHandler const initial) noexcept
129 : QAsm()
130{
131 m_state.obj = &l_msm_top_s; // the current state (top)
132 m_temp.fun = initial; // the initial tran. handler
133}
134
135//............................................................................
136void QMsm::init(
137 void const * const e,
138 std::uint_fast8_t const qsId)
139{
140#ifndef Q_SPY
141 Q_UNUSED_PAR(qsId);
142#endif
143
144 // current state must be initialized to &l_msm_top_s in QMsm_ctor()
145 Q_REQUIRE_LOCAL(200, m_state.obj == &l_msm_top_s);
146
147 // temp contains the top-most initial tran. handler, which must be valid
148 Q_REQUIRE_LOCAL(210, m_temp.fun != nullptr);
149
150 // execute the top-most initial tran.
151 QState r = (*m_temp.fun)(this, Q_EVT_CAST(QEvt));
152
153 // the top-most initial tran. must be taken
155
156 // the top-most initial tran. must set the tran-action table in temp
157 Q_ASSERT_LOCAL(250, m_temp.tatbl != nullptr);
158
160 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
161 m_state.obj->stateHandler, m_temp.tatbl->target->stateHandler);
162
163 // set state to the last tran. target
164 m_state.obj = m_temp.tatbl->target;
165
166 // drill down into the state hierarchy with initial transitions...
167 do {
168 r = execTatbl_(m_temp.tatbl, qsId);
169 } while (r == Q_RET_TRAN_INIT);
170
171 QS_TOP_INIT_(QS_QEP_INIT_TRAN, m_state.obj->stateHandler);
172
173#ifndef Q_UNSAFE
174 // establish stable state configuration at the end of RTC step
176#endif
177}
178
179//............................................................................
180void QMsm::dispatch(
181 QEvt const * const e,
182 std::uint_fast8_t const qsId)
183{
184#ifndef Q_SPY
185 Q_UNUSED_PAR(qsId);
186#endif
187
188 // this state machine must be in a stable state configuration
189 // NOTE: stable state configuration is established after every RTC step.
192
193 // the event to be dispatched must be valid
194 Q_REQUIRE_LOCAL(310, e != nullptr);
195
196 QMState const *s = m_state.obj; // the current state
197 QMState const * const t = s; // store the current state for later
199 QS_TRAN0_(QS_QEP_DISPATCH, s->stateHandler);
200
201 // scan the state hierarchy up to the top state...
202 QState r;
203 do {
204 r = (*s->stateHandler)(this, e); // call state handler function
205 if (r >= Q_RET_HANDLED) { // event handled? (the most frequent case)
206 break; // done scanning the state hierarchy
207 }
208#ifdef Q_SPY
209 if (r == Q_RET_UNHANDLED) { // event unhandled due to a guard?
212 QS_SIG_PRE(e->sig);
213 QS_OBJ_PRE(this);
214 QS_FUN_PRE(s->stateHandler);
215 QS_END_PRE()
216 QS_CRIT_EXIT();
217 }
218#endif
219 s = s->superstate; // advance to the superstate
220 } while (s != nullptr);
221
222 if (s == nullptr) { // event bubbled to the 'top' state?
223#ifdef Q_SPY
224 QS_TRAN0_(QS_QEP_IGNORED, t->stateHandler);
225#endif
226 }
227 else if (r == Q_RET_HANDLED) { // was the event e handled?
228 QS_TRAN0_(QS_QEP_INTERN_TRAN, s->stateHandler); // output QS record
229 }
230 else if ((r == Q_RET_TRAN) || (r == Q_RET_TRAN_HIST)) { //any tran. taken?
231#ifdef Q_SPY
232 QMState const * const ts = s; // tran. source for QS tracing
233#endif // Q_SPY
234
235 if (r == Q_RET_TRAN) { // tran. taken?
236 struct QMTranActTable const * const tatbl = m_temp.tatbl;
237 exitToTranSource_(t, s, qsId);
238 r = execTatbl_(tatbl, qsId);
239 }
240 else { // must be tran. to history
241 QMState const * const hist = m_state.obj; // save history
242 m_state.obj = t; // restore the original state
243
244 QS_TRAN_SEG_(QS_QEP_TRAN_HIST,
245 s->stateHandler, hist->stateHandler);
246
247 // save the tran-action table before it gets clobbered
248 struct QMTranActTable const * const tatbl = m_temp.tatbl;
249 exitToTranSource_(t, s, qsId);
250 static_cast<void>(execTatbl_(tatbl, qsId));
251 r = enterHistory_(hist, qsId);
252 }
253#ifdef Q_SPY
254 s = m_state.obj;
255#endif
256
257 while (r == Q_RET_TRAN_INIT) { // initial tran. in the target?
258
259 r = execTatbl_(m_temp.tatbl, qsId);
260#ifdef Q_SPY
261 s = m_state.obj;
262#endif
263 }
264
265 QS_TRAN_END_(QS_QEP_TRAN, ts->stateHandler, s->stateHandler);
266 }
267 else {
268 Q_ERROR_LOCAL(360); // last action handler returned impossible value
269 }
270
271#ifndef Q_UNSAFE
272 // establish stable state configuration at the end of RTC step
274#endif
275}
276
277//............................................................................
279 QMTranActTable const * const tatbl,
280 std::uint_fast8_t const qsId)
281{
282#ifndef Q_SPY
283 Q_UNUSED_PAR(qsId);
284#endif
285
286 // the tran-action table parameter must be valid
287 Q_REQUIRE_LOCAL(400, tatbl != nullptr);
288
291 QActionHandler const *a = &tatbl->act[0];
292 while (*a != nullptr) { // not the end of the tran-action table?
293 r = (*(*a))(this); // call the action through the 'a' pointer
294 ++a; // advance to the next entry in the tran-action table
295
296 if (r == Q_RET_ENTRY) { // was the action a state entry?
297 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, m_temp.obj->stateHandler);
298 }
299 else if (r == Q_RET_EXIT) { // was the action a state exit?
300 QS_STATE_ACT_(QS_QEP_STATE_EXIT, m_temp.obj->stateHandler);
301 }
302 else if (r == Q_RET_TRAN_INIT) { // was the action a state init?
303 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
304 tatbl->target->stateHandler,
305 m_temp.tatbl->target->stateHandler);
306 }
307 else {
308 // the action returned unexptected status (corrupt SM?)
309 Q_ERROR_LOCAL(460);
310 }
311 }
312
313 m_state.obj = tatbl->target; // set new current state
314 return r;
315}
316
317//............................................................................
319 QMState const * const curr_state,
320 QMState const * const tran_source,
321 std::uint_fast8_t const qsId)
322{
323#ifndef Q_SPY
324 Q_UNUSED_PAR(qsId);
325#endif
327
328 // exit states from the current state to the tran. source state
329 // NOTE: the following loop does not need the fixed loop bound check
330 // because the path from the current state to the tran.source has
331 // been already checked in the invariant 340.
332 QMState const *s = curr_state;
333 while (s != tran_source) {
334 if (s->exitAction != nullptr) { // exit action provided?
335 // execute the exit action, ignore the result
336 static_cast<void>((*s->exitAction)(this));
337 QS_STATE_ACT_(QS_QEP_STATE_EXIT, m_temp.obj->stateHandler);
338 }
339 s = s->superstate; // advance to the superstate
340 }
341}
342
343//............................................................................
345 QMState const * const hist,
346 std::uint_fast8_t const qsId)
347{
348#ifndef Q_SPY
349 Q_UNUSED_PAR(qsId);
350#endif
351
352 // record the entry path from current state to history
353 std::array<QMState const *, QMSM_MAX_NEST_DEPTH_> path;
354 QMState const *s = hist;
355 std::int_fast8_t i = -1; // entry path index (one below [0])
356 while (s != m_state.obj) {
357 if (s->entryAction != nullptr) { // does s have an entry action?
358 ++i;
359 Q_INVARIANT_LOCAL(610, i < QMSM_MAX_NEST_DEPTH_);
360 path[i] = s;
361 }
362 s = s->superstate;
363 }
364
366 // retrace the entry path in reverse (desired) order...
367 // NOTE: i is the fixed loop bound already checked in invariant 610
368 for (; i >= 0; --i) {
369 // enter the state in path[i], ignore the result
370 static_cast<void>((*path[i]->entryAction)(this));
371 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, path[i]->stateHandler);
372 }
373
374 m_state.obj = hist; // set current state to the tran. target
375
376 // initial tran. present?
378 if (hist->initAction != nullptr) { // init. action provided?
379 r = (*hist->initAction)(this); // execute the init. action
380 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
381 hist->stateHandler, m_temp.tatbl->target->stateHandler);
382 }
383 return r; // inform the caller if the init action was taken
384}
385
386//............................................................................
387QMState const * QMsm::topQMState() noexcept {
388 // return the top state (object pointer)
389 return &l_msm_top_s;
390}
391//............................................................................
392bool QMsm::isIn(QStateHandler const stateHndl) noexcept {
393 bool inState = false; // assume that this SM is not in 'state'
394 QMState const *s = m_state.obj;
395 while (s != nullptr) {
396 if (s->stateHandler == stateHndl) { // match found?
397 inState = true;
398 break;
399 }
400 s = s->superstate; // advance to the superstate
401 }
402
403 return inState;
404}
405
406//............................................................................
407QMState const * QMsm::childStateObj(QMState const * const parentHndl)
408 const noexcept
409{
410 QMState const *s = m_state.obj; // start with current state
411 QMState const *child = s;
412 bool isFound = false; // assume the child NOT found
413 while (s != nullptr) { // top of state hierarchy not reached yet?
414 if (s == parentHndl) {
415 isFound = true; // child is found
416 break;
417 }
418 child = s;
419 s = s->superstate;
420 }
421 // the child state must be found, or the state machine is corrupt
422 Q_ENSURE_LOCAL(890, isFound);
423
424#ifdef Q_UNSAFE
425 Q_UNUSED_PAR(isFound);
426#endif
427
428 return child;
429}
430//............................................................................
431QStateHandler QMsm::getStateHandler() const noexcept {
432 // return the current state handler (function pointer)
433 return m_state.obj->stateHandler;
434}
435
436} // namespace QP
Abstract State Machine class (state machine interface).
Definition qp.hpp:167
static constexpr QState Q_RET_UNHANDLED
Definition qp.hpp:175
static constexpr QState Q_RET_TRAN
Definition qp.hpp:177
static constexpr QState Q_RET_ENTRY
Definition qp.hpp:184
QAsmAttr m_temp
Temporary storage for target/act-table etc.
Definition qp.hpp:170
static constexpr QState Q_RET_SUPER
Definition qp.hpp:174
static constexpr QState Q_RET_TRAN_INIT
Definition qp.hpp:186
static constexpr QState Q_RET_TRAN_HIST
Definition qp.hpp:178
static constexpr QState Q_RET_EXIT
Definition qp.hpp:185
QAsmAttr m_state
Current state (pointer to the current state-handler function).
Definition qp.hpp:169
static constexpr QState Q_RET_HANDLED
Definition qp.hpp:176
Event class.
Definition qp.hpp:101
QMState const * childStateObj(QMState const *const parentHndl) const noexcept
Obtain the current active child state of a given parent in QP::QMsm.
void exitToTranSource_(QMState const *const curr_state, QMState const *const tran_source, std::uint_fast8_t const qsId)
Exit the current state up to the explicit transition source.
QState execTatbl_(QMTranActTable const *const tatbl, std::uint_fast8_t const qsId)
Execute transition-action table.
static QMState const * topQMState() noexcept
void dispatch(QEvt const *const e, std::uint_fast8_t const qsId) override
Virtual function to dispatch an event to the state machine.
QState enterHistory_(QMState const *const hist, std::uint_fast8_t const qsId)
Enter history of a composite state.
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.
bool isIn(QStateHandler const stateHndl) noexcept override
Tests if a given state is part of the current active state configuration.
QMsm(QStateHandler const initial) noexcept
Constructor of QP::QMsm.
QStateHandler getStateHandler() const noexcept override
Obtain the current active state from a MSM (read only).
QP/C++ Framework namespace.
Definition qequeue.hpp:36
@ QS_QEP_DISPATCH
an event was dispatched (begin of RTC step)
Definition qs.hpp:64
@ QS_QEP_STATE_ENTRY
a state was entered
Definition qs.hpp:57
@ QS_QEP_STATE_EXIT
a state was exited
Definition qs.hpp:58
@ QS_QEP_TRAN_HIST
a tran. to history was taken
Definition qs.hpp:137
@ QS_QEP_IGNORED
an event was ignored (silently discarded)
Definition qs.hpp:63
@ QS_QEP_UNHANDLED
an event was un-handled due to a guard
Definition qs.hpp:65
@ QS_QEP_STATE_INIT
an initial transition was taken in a state
Definition qs.hpp:59
@ QS_QEP_TRAN
a regular transition was taken
Definition qs.hpp:62
@ QS_QEP_INIT_TRAN
the top-most initial transition was taken
Definition qs.hpp:60
@ QS_QEP_INTERN_TRAN
an internal transition was taken
Definition qs.hpp:61
QState(*)(void *const me) QActionHandler
Pointer to an action-handler function.
Definition qp.hpp:139
static T_ dis_update(T_ const org)
Internal function template to calculate the Duplicate Inverse Storage (DIS).
Definition qp_pkg.hpp:61
static bool dis_verify(T_ const org, T_ const dis)
Internal function template to verify the Duplicate Inverse Storage (DIS).
Definition qp_pkg.hpp:68
std::uint_fast8_t QState
Type returned from state-handler functions.
Definition qp.hpp:134
QState(*)(void *const me, QEvt const *const e) QStateHandler
Pointer to a state-handler function.
Definition qp.hpp:138
#define Q_EVT_CAST(subclass_)
Perform downcast of an event onto a subclass of QP::QEvt class_.
Definition qp.hpp:384
#define Q_UNUSED_PAR(par_)
Helper macro to clearly mark unused parameters of functions.
Definition qp.hpp:89
QP Framework in C++ internal (package-scope) interface
Sample QP/C++ port.
#define QS_CRIT_STAT
Internal QS macro for defining the critical section status.
Definition qs.hpp:430
#define QS_CRIT_EXIT()
Internal macro for exiting a critical section.
Definition qs.hpp:438
#define QS_CRIT_ENTRY()
Internal macro for entering a critical section.
Definition qs.hpp:434
QS (QP/Spy software tracing) internal (package-scope) interface.
#define QS_OBJ_PRE(obj_)
Output pre-formatted object pointer element.
Definition qs_pkg.hpp:89
#define QS_SIG_PRE(sig_)
Output pre-formatted event signal data element.
Definition qs_pkg.hpp:90
#define QS_FUN_PRE(fun_)
Output pre-formatted function pointer element.
Definition qs_pkg.hpp:97
#define QS_END_PRE()
Pre-formatted QS trace record end.
Definition qs_pkg.hpp:77
#define QS_BEGIN_PRE(rec_, qsId_)
Pre-formatted QS trace record begin.
Definition qs_pkg.hpp:71
Sample QS/C++ port.
QP Functional Safety (FuSa) Subsystem.
#define Q_REQUIRE_LOCAL(id_, expr_)
Assertion for checking a precondition (local critical section).
Definition qsafe.h:99
#define Q_ERROR_LOCAL(id_)
Assertion with user-specified ID for a wrong path through the code (local critical section).
Definition qsafe.h:111
#define Q_ENSURE_LOCAL(id_, expr_)
Assertion for checking a postcondition (local critical section).
Definition qsafe.h:104
#define Q_ASSERT_LOCAL(id_, expr_)
General-purpose assertion with user-specified ID number (local critical section).
Definition qsafe.h:72
#define Q_INVARIANT_LOCAL(id_, expr_)
Assertion for checking a postcondition (local critical section).
Definition qsafe.h:109
State object for the QP::QMsm class (QM State Machine).
Definition qp.hpp:142