Embedded Template Library 1.0
hfsm.h
1/******************************************************************************
2The MIT License(MIT)
3
4Embedded Template Library.
5https://github.com/ETLCPP/etl
6https://www.etlcpp.com
7
8Copyright(c) 2021 Jeremy Overesch, John Wellbelove
9
10Permission is hereby granted, free of charge, to any person obtaining a copy
11of this software and associated documentation files(the "Software"), to deal
12in the Software without restriction, including without limitation the rights
13to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
14copies of the Software, and to permit persons to whom the Software is
15furnished to do so, subject to the following conditions :
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
23AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26SOFTWARE.
27******************************************************************************/
28
29#ifndef ETL_HFSM_INCLUDED
30#define ETL_HFSM_INCLUDED
31
32#include "fsm.h"
33
34namespace etl
35{
36 //***************************************************************************
40 //***************************************************************************
41 class hfsm : public etl::fsm
42 {
43 public:
44
45 //*******************************************
47 //*******************************************
48 hfsm(etl::message_router_id_t id)
49 : fsm(id)
50 {
51 }
52
53 //*******************************************
59 //*******************************************
60 void start(bool call_on_enter_state = true) ETL_OVERRIDE
61 {
62 // Can only be started once.
63 if (p_state == ETL_NULLPTR)
64 {
65 p_state = state_list[0];
66 ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
67
68 if (call_on_enter_state)
69 {
70 etl::fsm_state_id_t next_state = do_enters(ETL_NULLPTR, p_state, true);
71
72 if (next_state != ifsm_state::No_State_Change)
73 {
74 p_state = state_list[next_state];
75 }
76 }
77 }
78 }
79
80 //*******************************************
83 //*******************************************
84 virtual void reset(bool call_on_exit_state = false) ETL_OVERRIDE
85 {
86 if ((p_state != ETL_NULLPTR) && call_on_exit_state)
87 {
88 do_exits(ETL_NULLPTR, p_state);
89 }
90
91 p_state = ETL_NULLPTR;
92 }
93
94 using fsm::receive;
95
96 //*******************************************
98 //*******************************************
99 void receive(const etl::imessage& message) ETL_OVERRIDE
100 {
101 etl::fsm_state_id_t next_state_id = p_state->process_event(message);
102
103 if (next_state_id != ifsm_state::No_State_Change)
104 {
105 ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
106 etl::ifsm_state* p_next_state = state_list[next_state_id];
107
108 // Have we changed state?
109 if (p_next_state != p_state)
110 {
111 etl::ifsm_state* p_root = common_ancestor(p_state, p_next_state);
112 do_exits(p_root, p_state);
113
114 p_state = p_next_state;
115
116 next_state_id = do_enters(p_root, p_next_state, true);
117
118 if (next_state_id != ifsm_state::No_State_Change)
119 {
120 ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
121 p_state = state_list[next_state_id];
122 }
123 }
124 }
125 }
126
127 private:
128
129 //*******************************************
131 //*******************************************
132 static etl::ifsm_state* common_ancestor(etl::ifsm_state* s1, etl::ifsm_state* s2)
133 {
134 size_t depth1 = get_depth(s1);
135 size_t depth2 = get_depth(s2);
136
137 // Adjust s1 and s2 to the same depth.
138 if (depth1 > depth2)
139 {
140 s1 = adjust_depth(s1, depth1 - depth2);
141 }
142 else
143 {
144 s2 = adjust_depth(s2, depth2 - depth1);
145 }
146
147 // Now they're aligned to the same depth they can step towards the root together.
148 while (s1 != s2)
149 {
150 s1 = s1->p_parent;
151 s2 = s2->p_parent;
152 }
153
154 return s1;
155 }
156
157 //*******************************************
159 //*******************************************
160 static size_t get_depth(etl::ifsm_state* s)
161 {
162 size_t depth = 0UL;
163
164 while (s != ETL_NULLPTR)
165 {
166 s = s->p_parent;
167 ++depth;
168 }
169
170 return depth;
171 }
172
173 //*******************************************
175 //*******************************************
176 static etl::ifsm_state* adjust_depth(etl::ifsm_state* s, size_t offset)
177 {
178 while (offset != 0U)
179 {
180 s = s->p_parent;
181 --offset;
182 }
183
184 return s;
185 }
186
187 //*******************************************
189 //*******************************************
190 static etl::fsm_state_id_t do_enters(const etl::ifsm_state* p_root, etl::ifsm_state* p_target, bool activate_default_children)
191 {
192 ETL_ASSERT(p_target != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
193
194 // We need to go recursively up the tree if the target and root don't match
195 if ((p_root != p_target) && (p_target->p_parent != ETL_NULLPTR))
196 {
197 if (p_target->p_parent != p_root)
198 {
199 // The parent we're calling shouldn't activate its defaults, or this state will be deactivated.
200 do_enters(p_root, p_target->p_parent, false);
201 }
202
203 // Set us as our parent's active child
204 p_target->p_parent->p_active_child = p_target;
205 }
206
207 etl::fsm_state_id_t next_state = p_target->on_enter_state();
208 ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden));
209
210 // Activate default child if we need to activate any initial states in an active composite state.
211 if (activate_default_children)
212 {
213 while (p_target->p_default_child != ETL_NULLPTR)
214 {
215 p_target = p_target->p_default_child;
216 p_target->p_parent->p_active_child = p_target;
217 next_state = p_target->on_enter_state();
218 ETL_ASSERT(ifsm_state::No_State_Change == next_state, ETL_ERROR(etl::fsm_state_composite_state_change_forbidden));
219 }
220
221 next_state = p_target->get_state_id();
222 }
223
224 return next_state;
225 }
226
227 //*******************************************
229 //*******************************************
230 static void do_exits(const etl::ifsm_state* p_root, etl::ifsm_state* p_source)
231 {
232 etl::ifsm_state* p_current = p_source;
233
234 // Iterate down to the lowest child
235 while (p_current->p_active_child != ETL_NULLPTR)
236 {
237 p_current = p_current->p_active_child;
238 }
239
240 // Run exit state on all states up to the root
241 while (p_current != p_root)
242 {
243 p_current->on_exit_state();
244 p_current = p_current->p_parent;
245 }
246 }
247 };
248}
249#endif
Exception for null state pointer.
Definition: fsm.h:112
Exception for forbidden state chages.
Definition: fsm.h:164
Exception for invalid state id.
Definition: fsm.h:125
The FSM class.
Definition: fsm.h:325
void receive(const etl::imessage &message) ETL_OVERRIDE
Top level message handler for the FSM.
Definition: fsm.h:398
Definition: hfsm.h:42
void receive(const etl::imessage &message) ETL_OVERRIDE
Top level message handler for the HFSM.
Definition: hfsm.h:99
void start(bool call_on_enter_state=true) ETL_OVERRIDE
Definition: hfsm.h:60
virtual void reset(bool call_on_exit_state=false) ETL_OVERRIDE
Definition: hfsm.h:84
hfsm(etl::message_router_id_t id)
Constructor.
Definition: hfsm.h:48
Interface class for FSM states.
Definition: fsm.h:198
etl::fsm_state_id_t get_state_id() const
Gets the id for this state.
Definition: fsm.h:223
Definition: message.h:69
Definition: message.h:84
#define ETL_ASSERT(b, e)
Definition: error_handler.h:316
bitset_ext
Definition: absolute.h:38
uint_least8_t fsm_state_id_t
Allow alternative type for state id.
Definition: fsm.h:75